diff --git a/Makefile b/Makefile index a971469ae..ccd3179ed 100644 --- a/Makefile +++ b/Makefile @@ -104,7 +104,7 @@ build.push/multiarch: for arch in $(ARCHS); do \ image="$(IMAGE):$(VERSION)-$${arch}" ;\ # pre-pull due to https://github.com/kubernetes-sigs/cluster-addons/pull/84/files ;\ - docker pull $${arch}/alpine:3.13 ;\ + docker pull $${arch}/alpine:3.14 ;\ docker pull golang:1.16 ;\ DOCKER_BUILDKIT=1 docker build --rm --tag $${image} --build-arg VERSION="$(VERSION)" --build-arg ARCH="$${arch}" . ;\ docker push $${image} ;\ diff --git a/charts/external-dns/Chart.yaml b/charts/external-dns/Chart.yaml index 60fb086ad..fc0c979f6 100644 --- a/charts/external-dns/Chart.yaml +++ b/charts/external-dns/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: external-dns description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers. type: application -version: 1.2.0 -appVersion: 0.9.0 +version: 1.4.0 +appVersion: 0.10.1 keywords: - kubernetes - external-dns @@ -17,5 +17,5 @@ maintainers: email: steve.hipwell@gmail.com annotations: artifacthub.io/changes: | - - kind: added - description: Initial official release. + - kind: changed + description: "Update image to v0.10.1" diff --git a/charts/external-dns/templates/deployment.yaml b/charts/external-dns/templates/deployment.yaml index ca598d48a..4d7eda2f5 100644 --- a/charts/external-dns/templates/deployment.yaml +++ b/charts/external-dns/templates/deployment.yaml @@ -59,6 +59,7 @@ spec: - --source={{ . }} {{- end }} - --policy={{ .Values.policy }} + - --registry={{ .Values.registry }} {{- if eq .Values.registry "txt" }} {{- if .Values.txtOwnerId }} - --txt-owner-id={{ .Values.txtOwnerId }} diff --git a/docs/faq.md b/docs/faq.md index 2ce3ed481..be9ac3b63 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -268,6 +268,11 @@ Note: the `--ingress-class` argument cannot be used at the same time as a `kuber Beware when using multiple sources, e.g. `--source=service --source=ingress`, `--annotation-filter` will filter every given source objects. If you need to filter only one specific source you have to run a separated external dns service containing only the wanted `--source` and `--annotation-filter`. +**Note:** Filtering based on annotation means that the external-dns controller will receive all resources of that kind and then filter on the client-side. +In larger clusters with many resources which change frequently this can cause performance issues. If only some resources need to be managed by an instance +of external-dns then label filtering can be used instead of annotation filtering. This means that only those resources which match the selector specified +in `--label-filter` will be passed to the controller. + ### How do I specify that I want the DNS record to point to either the Node's public or private IP when it has both? If your Nodes have both public and private IP addresses, you might want to write DNS records with one or the other. diff --git a/docs/tutorials/infoblox.md b/docs/tutorials/infoblox.md index 71a3cafc3..bab67dfaf 100644 --- a/docs/tutorials/infoblox.md +++ b/docs/tutorials/infoblox.md @@ -78,6 +78,7 @@ spec: - --infoblox-wapi-port=443 # (optional) Infoblox WAPI port. The default is "443". - --infoblox-wapi-version=2.3.1 # (optional) Infoblox WAPI version. The default is "2.3.1" - --infoblox-ssl-verify # (optional) Use --no-infoblox-ssl-verify to skip server certificate verification. + - --infoblox-create-ptr # (optional) Use --infoblox-create-ptr to create a ptr entry in addition to an entry. env: - name: EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS value: "10" # (optional) Infoblox WAPI request connection pool size. The default is "10". @@ -158,6 +159,7 @@ spec: - --infoblox-wapi-port=443 # (optional) Infoblox WAPI port. The default is "443". - --infoblox-wapi-version=2.3.1 # (optional) Infoblox WAPI version. The default is "2.3.1" - --infoblox-ssl-verify # (optional) Use --no-infoblox-ssl-verify to skip server certificate verification. + - --infoblox-create-ptr # (optional) Use --infoblox-create-ptr to create a ptr entry in addition to an entry. env: - name: EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS value: "10" # (optional) Infoblox WAPI request connection pool size. The default is "10". @@ -268,3 +270,11 @@ There is also the ability to filter results from the Infoblox zone_auth service ``` --infoblox-fqdn-regex=^staging.*test.com$ ``` + +## Infoblox PTR record support + +There is an option to enable PTR records support for infoblox provider. PTR records allow to do reverse dns search. To enable PTR records support, add following into arguments for external-dns: +`--infoblox-create-ptr` to allow management of PTR records. +You can also add a filter for reverse dns zone to limit PTR records to specific zones only: +`--domain-filter=10.196.0.0/16` change this to the reverse zone(s) as defined in your infoblox. +Now external-dns will manage PTR records for you. diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go index 7d7be8e00..9a78ffc97 100644 --- a/endpoint/endpoint.go +++ b/endpoint/endpoint.go @@ -35,6 +35,8 @@ const ( RecordTypeSRV = "SRV" // RecordTypeNS is a RecordType enum value RecordTypeNS = "NS" + // RecordTypePTR is a RecordType enum value + RecordTypePTR = "PTR" ) // TTL is a structure defining the TTL of a DNS record diff --git a/go.mod b/go.mod index 1b23f7214..c41477dbb 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,11 @@ require ( cloud.google.com/go v0.97.0 git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d github.com/Azure/azure-sdk-for-go v46.4.0+incompatible - github.com/Azure/go-autorest/autorest v0.11.20 + github.com/Azure/go-autorest/autorest v0.11.21 github.com/Azure/go-autorest/autorest/adal v0.9.16 github.com/Azure/go-autorest/autorest/to v0.4.0 github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 + github.com/StackExchange/dnscontrol v0.2.8 github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect github.com/alecthomas/colour v0.1.0 // indirect github.com/alecthomas/kingpin v2.2.5+incompatible @@ -21,7 +22,7 @@ require ( github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 github.com/datawire/ambassador v1.6.0 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba - github.com/digitalocean/godo v1.44.0 + github.com/digitalocean/godo v1.69.1 github.com/dnsimple/dnsimple-go v0.60.0 github.com/exoscale/egoscale v0.73.2 github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99 @@ -57,7 +58,7 @@ require ( github.com/transip/gotransip/v6 v6.6.2 github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60 github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556 - github.com/vultr/govultr/v2 v2.8.1 + github.com/vultr/govultr/v2 v2.9.0 go.etcd.io/etcd/api/v3 v3.5.0 go.etcd.io/etcd/client/v3 v3.5.0 go.uber.org/ratelimit v0.2.0 @@ -80,3 +81,5 @@ require ( k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) + +replace k8s.io/klog/v2 => github.com/Raffo/knolog v0.0.0-20211016155154-e4d5e0cc970a diff --git a/go.sum b/go.sum index 28c4de5ea..e1c8538f9 100644 --- a/go.sum +++ b/go.sum @@ -66,14 +66,15 @@ github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKn github.com/Azure/go-autorest/autorest v0.11.6/go.mod h1:V6p3pKZx1KKkJubbxnDWrzNhEIfOy/pTGasLqzHIPHs= github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M= -github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= +github.com/Azure/go-autorest/autorest v0.11.21 h1:w77zY/9RnUAWcIQyDC0Fc89mCvwftR8F+zsR/OH6enk= +github.com/Azure/go-autorest/autorest v0.11.21/go.mod h1:Do/yuMSW/13ayUkcVREpsMHGG+MvV81uzSCFgYPj4tM= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.4/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.16 h1:P8An8Z9rH1ldbOLdFpxYorgOt2sywL9V24dAwWHPuGc= github.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= @@ -120,10 +121,14 @@ github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Raffo/knolog v0.0.0-20211016155154-e4d5e0cc970a h1:4D87FDUGSrfLp/NJmYxybiC8+OR8s5eULyNgmmxAI9U= +github.com/Raffo/knolog v0.0.0-20211016155154-e4d5e0cc970a/go.mod h1:AsBYmtPY5rgsIyjQZDLO+Dwdx91Jmi6uxu7LO6Xf9iw= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Venafi/vcert/v4 v4.13.1/go.mod h1:Z3sJFoAurFNXPpoSUSHq46aIeHLiGQEMDhprfxlpofQ= +github.com/StackExchange/dnscontrol v0.2.8 h1:7jviqDH9cIqRSRpH0UxgmpT7a8CwEhs9mLHBhoYhXo8= +github.com/StackExchange/dnscontrol v0.2.8/go.mod h1:BH+5nX50JxHDdb3+AD/z/UfYMCc7iaqEkRtQ+NjcFGE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -273,8 +278,9 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.44.0 h1:IMElzMUpO1dVR8qjSg53+5vDkOLzMbhJt4yTAq7NGCQ= github.com/digitalocean/godo v1.44.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= +github.com/digitalocean/godo v1.69.1 h1:aCyfwth8R3DeOaWB9J9E8v7cjlDIlF19eXTt8R3XhTE= +github.com/digitalocean/godo v1.69.1/go.mod h1:epPuOzTOOJujNo0nduDj2D5O1zu8cSpp9R+DdN0W9I0= github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/dnsimple/dnsimple-go v0.60.0 h1:N+q+ML1CZGf+5r4udu9Opy7WJNtOaFT9aM86Af9gLhk= @@ -726,6 +732,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d h1:JV46OtdhH2vVt8mJ1EWUE94k99vbN9fZs1WQ8kcEapU= +github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d/go.mod h1:CHQ3o5KBH1PIS2Fb1mRLTIWO5YzP9kSUB3KoCICwlvA= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= @@ -788,6 +796,8 @@ github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nr github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.36-0.20210109083720-731b191cabd1 h1:kZZmnTeY2r+88mDNCVV/uCXL2gG3rkVPTN9jcYfGQcI= github.com/miekg/dns v1.1.36-0.20210109083720-731b191cabd1/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b h1:5f5B1kp+QerGOF91q1qVJcUWWvXsVEN3OKiyEzAAjIM= +github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b/go.mod h1:PizLs/1ddmVrXpFgWOGNmTJ2YHSWUkpUXMYuUkTo3Go= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -1080,8 +1090,8 @@ github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+ github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556 h1:UbVjBjgJUYGD8MlobEdOR+yTeNqaNa2Gf1/nskVNCSE= github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg= -github.com/vultr/govultr/v2 v2.8.1 h1:AjRcJWfTfb4DidRNCeojUIgLVC5XShuc5IAW99K3wHU= -github.com/vultr/govultr/v2 v2.8.1/go.mod h1:JjUljQdSZx+MELCAJvZ/JH32bJotmflnsyS0NOjb8Jg= +github.com/vultr/govultr/v2 v2.9.0 h1:n0a0fGOiHAE07twu1VR3jWTDFDE0+DJ/cIqZqX9IlNw= +github.com/vultr/govultr/v2 v2.9.0/go.mod h1:JjUljQdSZx+MELCAJvZ/JH32bJotmflnsyS0NOjb8Jg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= diff --git a/kustomize/external-dns-clusterrole.yaml b/kustomize/external-dns-clusterrole.yaml index 2ca6ceed2..5bcc705b4 100644 --- a/kustomize/external-dns-clusterrole.yaml +++ b/kustomize/external-dns-clusterrole.yaml @@ -9,6 +9,9 @@ rules: - apiGroups: ['extensions'] resources: ['ingresses'] verbs: ['get', 'watch', 'list'] - - apiGroups: [''] - resources: ['nodes'] - verbs: ['list'] + - apiGroups: ["networking.k8s.io"] + resources: ["ingresses"] + verbs: ["get","watch","list"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["watch", "list"] diff --git a/kustomize/kustomization.yaml b/kustomize/kustomization.yaml index 6f87be0ad..ac086b49c 100644 --- a/kustomize/kustomization.yaml +++ b/kustomize/kustomization.yaml @@ -3,7 +3,7 @@ kind: Kustomization images: - name: k8s.gcr.io/external-dns/external-dns - newTag: v0.9.0 + newTag: v0.10.0 resources: - ./external-dns-deployment.yaml diff --git a/main.go b/main.go index bc26e0f4d..4a461e028 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" log "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/labels" _ "k8s.io/client-go/plugin/pkg/client/auth" "sigs.k8s.io/external-dns/controller" @@ -99,11 +100,14 @@ func main() { go serveMetrics(cfg.MetricsAddress) go handleSigterm(cancel) + // error is explicitly ignored because the filter is already validated in validation.ValidateConfig + labelSelector, _ := labels.Parse(cfg.LabelFilter) + // Create a source.Config from the flags passed by the user. sourceCfg := &source.Config{ Namespace: cfg.Namespace, AnnotationFilter: cfg.AnnotationFilter, - LabelFilter: cfg.LabelFilter, + LabelFilter: labelSelector, IngressClassNames: cfg.IngressClassNames, FQDNTemplate: cfg.FQDNTemplate, CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation, @@ -245,6 +249,7 @@ func main() { MaxResults: cfg.InfobloxMaxResults, DryRun: cfg.DryRun, FQDNRexEx: cfg.InfobloxFQDNRegEx, + CreatePTR: cfg.InfobloxCreatePTR, }, ) case "dyn": diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index a59e356be..a43e3bafd 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -23,6 +23,8 @@ import ( "strconv" "time" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/external-dns/endpoint" "github.com/alecthomas/kingpin" @@ -110,6 +112,7 @@ type Config struct { InfobloxView string InfobloxMaxResults int InfobloxFQDNRegEx string + InfobloxCreatePTR bool DynCustomerName string DynUsername string DynPassword string `secure:"yes"` @@ -186,7 +189,7 @@ var defaultConfig = &Config{ Sources: nil, Namespace: "", AnnotationFilter: "", - LabelFilter: "", + LabelFilter: labels.Everything().String(), IngressClassNames: nil, FQDNTemplate: "", CombineFQDNAndAnnotation: false, @@ -239,6 +242,7 @@ var defaultConfig = &Config{ InfobloxView: "", InfobloxMaxResults: 0, InfobloxFQDNRegEx: "", + InfobloxCreatePTR: false, OCIConfigFile: "/etc/kubernetes/oci.yaml", InMemoryZones: []string{}, OVHEndpoint: "ovh-eu", @@ -363,7 +367,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress") app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace) app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter) - app.Flag("label-filter", "Filter sources managed by external-dns via label selector when listing all resources; currently only supported by source CRD").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter) + app.Flag("label-filter", "Filter sources managed by external-dns via label selector when listing all resources; currently supported by source types CRD, ingress, service and openshift-route").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter) app.Flag("ingress-class", "Require an ingress to have this class name (defaults to any class; specify multiple times to allow more than one class)").StringsVar(&cfg.IngressClassNames) app.Flag("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate) app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation) @@ -427,6 +431,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("infoblox-view", "DNS view (default: \"\")").Default(defaultConfig.InfobloxView).StringVar(&cfg.InfobloxView) app.Flag("infoblox-max-results", "Add _max_results as query parameter to the URL on all API requests. The default is 0 which means _max_results is not set and the default of the server is used.").Default(strconv.Itoa(defaultConfig.InfobloxMaxResults)).IntVar(&cfg.InfobloxMaxResults) app.Flag("infoblox-fqdn-regex", "Apply this regular expression as a filter for obtaining zone_auth objects. This is disabled by default.").Default(defaultConfig.InfobloxFQDNRegEx).StringVar(&cfg.InfobloxFQDNRegEx) + app.Flag("infoblox-create-ptr", "When using the Infoblox provider, create a ptr entry in addition to an entry").Default(strconv.FormatBool(defaultConfig.InfobloxCreatePTR)).BoolVar(&cfg.InfobloxCreatePTR) app.Flag("dyn-customer-name", "When using the Dyn provider, specify the Customer Name").Default("").StringVar(&cfg.DynCustomerName) app.Flag("dyn-username", "When using the Dyn provider, specify the Username").Default("").StringVar(&cfg.DynUsername) app.Flag("dyn-password", "When using the Dyn provider, specify the password").Default("").StringVar(&cfg.DynPassword) diff --git a/pkg/apis/externaldns/validation/validation.go b/pkg/apis/externaldns/validation/validation.go index 9f8c3889a..c5ebc3845 100644 --- a/pkg/apis/externaldns/validation/validation.go +++ b/pkg/apis/externaldns/validation/validation.go @@ -20,6 +20,8 @@ import ( "errors" "fmt" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/external-dns/pkg/apis/externaldns" ) @@ -110,5 +112,9 @@ func ValidateConfig(cfg *externaldns.Config) error { return errors.New("txt-prefix and txt-suffix are mutual exclusive") } + _, err := labels.Parse(cfg.LabelFilter) + if err != nil { + return errors.New("--label-filter does not specify a valid label selector") + } return nil } diff --git a/provider/cloudflare/cloudflare.go b/provider/cloudflare/cloudflare.go index 1235186c3..033a0084f 100644 --- a/provider/cloudflare/cloudflare.go +++ b/provider/cloudflare/cloudflare.go @@ -308,7 +308,7 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud } err := p.Client.UpdateDNSRecord(zoneID, recordID, change.ResourceRecord) if err != nil { - log.WithFields(logFields).Errorf("failed to delete record: %v", err) + log.WithFields(logFields).Errorf("failed to update record: %v", err) } } else if change.Action == cloudFlareDelete { recordID := p.getRecordID(records, change.ResourceRecord) diff --git a/provider/google/google.go b/provider/google/google.go index 29eb3ea74..4c68e7c76 100644 --- a/provider/google/google.go +++ b/provider/google/google.go @@ -145,10 +145,11 @@ func NewGoogleProvider(ctx context.Context, project string, domainFilter endpoin if project == "" { mProject, mErr := metadata.ProjectID() - if mErr == nil { - log.Infof("Google project auto-detected: %s", mProject) - project = mProject + if mErr != nil { + return nil, fmt.Errorf("failed to auto-detect the project id: %w", mErr) } + log.Infof("Google project auto-detected: %s", mProject) + project = mProject } zoneTypeFilter := provider.NewZoneTypeFilter(zoneVisibility) diff --git a/provider/infoblox/infoblox.go b/provider/infoblox/infoblox.go index 9c98adf2e..f38f7a67f 100644 --- a/provider/infoblox/infoblox.go +++ b/provider/infoblox/infoblox.go @@ -19,12 +19,14 @@ package infoblox import ( "context" "fmt" + "net" "net/http" "os" "sort" "strconv" "strings" + transform "github.com/StackExchange/dnscontrol/pkg/transform" ibclient "github.com/infobloxopen/infoblox-go-client" "github.com/sirupsen/logrus" @@ -33,6 +35,11 @@ import ( "sigs.k8s.io/external-dns/provider" ) +const ( + // provider specific key to track if PTR record was already created or not for A records + providerSpecificInfobloxPtrRecord = "infoblox-ptr-record-exists" +) + // InfobloxConfig clarifies the method signature type InfobloxConfig struct { DomainFilter endpoint.DomainFilter @@ -47,6 +54,7 @@ type InfobloxConfig struct { View string MaxResults int FQDNRexEx string + CreatePTR bool } // InfobloxProvider implements the DNS provider for Infoblox. @@ -58,6 +66,7 @@ type InfobloxProvider struct { view string dryRun bool fqdnRegEx string + createPTR bool } type infobloxRecordSet struct { @@ -143,6 +152,7 @@ func NewInfobloxProvider(infobloxConfig InfobloxConfig) (*InfobloxProvider, erro dryRun: infobloxConfig.DryRun, view: infobloxConfig.View, fqdnRegEx: infobloxConfig.FQDNRexEx, + createPTR: infobloxConfig.CreatePTR, } return provider, nil @@ -170,6 +180,9 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E } for _, res := range resA { newEndpoint := endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, res.Ipv4Addr) + if p.createPTR { + newEndpoint.WithProviderSpecific(providerSpecificInfobloxPtrRecord, "false") + } // Check if endpoint already exists and add to existing endpoint if it does foundExisting := false for _, ep := range endpoints { @@ -203,7 +216,13 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E } for _, res := range resH { for _, ip := range res.Ipv4Addrs { - endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, ip.Ipv4Addr)) + // host record is an abstraction in infoblox that combines A and PTR records + // for any host record we already should have a PTR record in infoblox, so mark it as created + newEndpoint := endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, ip.Ipv4Addr) + if p.createPTR { + newEndpoint.WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") + } + endpoints = append(endpoints, newEndpoint) } } @@ -222,6 +241,29 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeCNAME, res.Canonical)) } + if p.createPTR { + // infoblox doesn't accept reverse zone's fqdn, and instead expects .in-addr.arpa zone + // so convert our zone fqdn (if it is a correct cidr block) into in-addr.arpa address and pass that into infoblox + // example: 10.196.38.0/24 becomes 38.196.10.in-addr.arpa + arpaZone, err := transform.ReverseDomainName(zone.Fqdn) + if err == nil { + var resP []ibclient.RecordPTR + objP := ibclient.NewRecordPTR( + ibclient.RecordPTR{ + Zone: arpaZone, + View: p.view, + }, + ) + err = p.client.GetObject(objP, "", &resP) + if err != nil { + return nil, fmt.Errorf("could not fetch PTR records from zone '%s': %s", zone.Fqdn, err) + } + for _, res := range resP { + endpoints = append(endpoints, endpoint.NewEndpoint(res.PtrdName, endpoint.RecordTypePTR, res.Ipv4Addr)) + } + } + } + var resT []ibclient.RecordTXT objT := ibclient.NewRecordTXT( ibclient.RecordTXT{ @@ -242,10 +284,66 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeTXT, res.Text)) } } + + // update A records that have PTR record created for them already + if p.createPTR { + // save all ptr records into map for a quick look up + ptrRecordsMap := make(map[string]bool) + for _, ptrRecord := range endpoints { + if ptrRecord.RecordType != endpoint.RecordTypePTR { + continue + } + ptrRecordsMap[ptrRecord.DNSName] = true + } + + for i := range endpoints { + if endpoints[i].RecordType != endpoint.RecordTypeA { + continue + } + // if PTR record already exists for A record, then mark it as such + if ptrRecordsMap[endpoints[i].DNSName] { + found := false + for j := range endpoints[i].ProviderSpecific { + if endpoints[i].ProviderSpecific[j].Name == providerSpecificInfobloxPtrRecord { + endpoints[i].ProviderSpecific[j].Value = "true" + found = true + } + } + if !found { + endpoints[i].WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") + } + } + } + } logrus.Debugf("fetched %d records from infoblox", len(endpoints)) return endpoints, nil } +func (p *InfobloxProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint { + if !p.createPTR { + return endpoints + } + + // for all A records, we want to create PTR records + // so add provider specific property to track if the record was created or not + for i := range endpoints { + if endpoints[i].RecordType == endpoint.RecordTypeA { + found := false + for j := range endpoints[i].ProviderSpecific { + if endpoints[i].ProviderSpecific[j].Name == providerSpecificInfobloxPtrRecord { + endpoints[i].ProviderSpecific[j].Value = "true" + found = true + } + } + if !found { + endpoints[i].WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") + } + } + } + + return endpoints +} + // ApplyChanges applies the given changes. func (p *InfobloxProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { zones, err := p.zones() @@ -301,6 +399,17 @@ func (p *InfobloxProvider) mapChanges(zones []ibclient.ZoneAuth, changes *plan.C } // Ensure the record type is suitable changeMap[zone.Fqdn] = append(changeMap[zone.Fqdn], change) + + if p.createPTR && change.RecordType == endpoint.RecordTypeA { + reverseZone := p.findReverseZone(zones, change.Targets[0]) + if reverseZone == nil { + logrus.Debugf("Ignoring changes to '%s' because a suitable Infoblox DNS reverse zone was not found.", change.Targets[0]) + return + } + changecopy := *change + changecopy.RecordType = endpoint.RecordTypePTR + changeMap[reverseZone.Fqdn] = append(changeMap[reverseZone.Fqdn], &changecopy) + } } for _, change := range changes.Delete { @@ -338,6 +447,28 @@ func (p *InfobloxProvider) findZone(zones []ibclient.ZoneAuth, name string) *ibc return result } +func (p *InfobloxProvider) findReverseZone(zones []ibclient.ZoneAuth, name string) *ibclient.ZoneAuth { + ip := net.ParseIP(name) + networks := map[int]*ibclient.ZoneAuth{} + maxMask := 0 + + for i, zone := range zones { + _, net, err := net.ParseCIDR(zone.Fqdn) + if err != nil { + logrus.WithError(err).Debugf("fqdn %s is no cidr", zone.Fqdn) + } else { + if net.Contains(ip) { + _, mask := net.Mask.Size() + networks[mask] = &zones[i] + if mask > maxMask { + maxMask = mask + } + } + } + } + return networks[maxMask] +} + func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool, targetIndex int) (recordSet infobloxRecordSet, err error) { switch ep.RecordType { case endpoint.RecordTypeA: @@ -359,6 +490,25 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool, targ obj: obj, res: &res, } + case endpoint.RecordTypePTR: + var res []ibclient.RecordPTR + obj := ibclient.NewRecordPTR( + ibclient.RecordPTR{ + PtrdName: ep.DNSName, + Ipv4Addr: ep.Targets[targetIndex], + View: p.view, + }, + ) + if getObject { + err = p.client.GetObject(obj, "", &res) + if err != nil { + return + } + } + recordSet = infobloxRecordSet{ + obj: obj, + res: &res, + } case endpoint.RecordTypeCNAME: var res []ibclient.RecordCNAME obj := ibclient.NewRecordCNAME( @@ -483,6 +633,10 @@ func (p *InfobloxProvider) deleteRecords(deleted infobloxChangeMap) { for _, record := range *recordSet.res.(*[]ibclient.RecordA) { _, err = p.client.DeleteObject(record.Ref) } + case endpoint.RecordTypePTR: + for _, record := range *recordSet.res.(*[]ibclient.RecordPTR) { + _, err = p.client.DeleteObject(record.Ref) + } case endpoint.RecordTypeCNAME: for _, record := range *recordSet.res.(*[]ibclient.RecordCNAME) { _, err = p.client.DeleteObject(record.Ref) diff --git a/provider/infoblox/infoblox_test.go b/provider/infoblox/infoblox_test.go index a91e03664..9a9a57d3d 100644 --- a/provider/infoblox/infoblox_test.go +++ b/provider/infoblox/infoblox_test.go @@ -25,6 +25,7 @@ import ( "testing" ibclient "github.com/infobloxopen/infoblox-go-client" + "github.com/miekg/dns" "github.com/stretchr/testify/assert" "sigs.k8s.io/external-dns/endpoint" @@ -89,6 +90,21 @@ func (client *mockIBConnector) CreateObject(obj ibclient.IBObject) (ref string, ) obj.(*ibclient.RecordTXT).Ref = ref ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(obj.(*ibclient.RecordTXT).Name)), obj.(*ibclient.RecordTXT).Name) + case "record:ptr": + client.createdEndpoints = append( + client.createdEndpoints, + endpoint.NewEndpoint( + obj.(*ibclient.RecordPTR).PtrdName, + endpoint.RecordTypePTR, + obj.(*ibclient.RecordPTR).Ipv4Addr, + ), + ) + obj.(*ibclient.RecordPTR).Ref = ref + reverseAddr, err := dns.ReverseAddr(obj.(*ibclient.RecordPTR).Ipv4Addr) + if err != nil { + return ref, fmt.Errorf("unable to create reverse addr from %s", obj.(*ibclient.RecordPTR).Ipv4Addr) + } + ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(obj.(*ibclient.RecordPTR).PtrdName)), reverseAddr) } *client.mockInfobloxObjects = append( *client.mockInfobloxObjects, @@ -163,6 +179,22 @@ func (client *mockIBConnector) GetObject(obj ibclient.IBObject, ref string, res } } *res.(*[]ibclient.RecordTXT) = result + case "record:ptr": + var result []ibclient.RecordPTR + for _, object := range *client.mockInfobloxObjects { + if object.ObjectType() == "record:ptr" { + if ref != "" && + ref != object.(*ibclient.RecordPTR).Ref { + continue + } + if obj.(*ibclient.RecordPTR).PtrdName != "" && + obj.(*ibclient.RecordPTR).PtrdName != object.(*ibclient.RecordPTR).PtrdName { + continue + } + result = append(result, *object.(*ibclient.RecordPTR)) + } + } + *res.(*[]ibclient.RecordPTR) = result case "zone_auth": *res.(*[]ibclient.ZoneAuth) = *client.mockInfobloxZones } @@ -246,6 +278,24 @@ func (client *mockIBConnector) DeleteObject(ref string) (refRes string, err erro ), ) } + case "record:ptr": + var records []ibclient.RecordPTR + obj := ibclient.NewRecordPTR( + ibclient.RecordPTR{ + Name: result[2], + }, + ) + client.GetObject(obj, ref, &records) + for _, record := range records { + client.deletedEndpoints = append( + client.deletedEndpoints, + endpoint.NewEndpoint( + record.PtrdName, + endpoint.RecordTypePTR, + "", + ), + ) + } } return "", nil } @@ -339,16 +389,25 @@ func createMockInfobloxObject(name, recordType, value string) ibclient.IBObject }, }, ) + case endpoint.RecordTypePTR: + return ibclient.NewRecordPTR( + ibclient.RecordPTR{ + Ref: ref, + PtrdName: name, + Ipv4Addr: value, + }, + ) } return nil } -func newInfobloxProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, client ibclient.IBConnector) *InfobloxProvider { +func newInfobloxProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, createPTR bool, client ibclient.IBConnector) *InfobloxProvider { return &InfobloxProvider{ client: client, domainFilter: domainFilter, zoneIDFilter: zoneIDFilter, dryRun: dryRun, + createPTR: createPTR, } } @@ -376,7 +435,7 @@ func TestInfobloxRecords(t *testing.T) { }, } - provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, &client) + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, false, &client) actual, err := provider.Records(context.Background()) if err != nil { @@ -399,10 +458,66 @@ func TestInfobloxRecords(t *testing.T) { validateEndpoints(t, actual, expected) } +func TestInfobloxAdjustEndpoints(t *testing.T) { + client := mockIBConnector{ + mockInfobloxZones: &[]ibclient.ZoneAuth{ + createMockInfobloxZone("example.com"), + createMockInfobloxZone("other.com"), + }, + mockInfobloxObjects: &[]ibclient.IBObject{ + createMockInfobloxObject("example.com", endpoint.RecordTypeA, "123.123.123.122"), + createMockInfobloxObject("example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), + createMockInfobloxObject("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), + createMockInfobloxObject("host.example.com", "HOST", "125.1.1.1"), + }, + } + + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, true, &client) + actual, err := provider.Records(context.Background()) + if err != nil { + t.Fatal(err) + } + provider.AdjustEndpoints(actual) + + expected := []*endpoint.Endpoint{ + endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122").WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true"), + endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""), + endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), + endpoint.NewEndpoint("host.example.com", endpoint.RecordTypeA, "125.1.1.1").WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true"), + } + validateEndpoints(t, actual, expected) +} + +func TestInfobloxRecordsReverse(t *testing.T) { + + client := mockIBConnector{ + mockInfobloxZones: &[]ibclient.ZoneAuth{ + createMockInfobloxZone("10.0.0.0/24"), + createMockInfobloxZone("10.0.1.0/24"), + }, + mockInfobloxObjects: &[]ibclient.IBObject{ + createMockInfobloxObject("example.com", endpoint.RecordTypePTR, "10.0.0.1"), + createMockInfobloxObject("example2.com", endpoint.RecordTypePTR, "10.0.0.2"), + }, + } + + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"10.0.0.0/24"}), provider.NewZoneIDFilter([]string{""}), true, true, &client) + actual, err := provider.Records(context.Background()) + + if err != nil { + t.Fatal(err) + } + expected := []*endpoint.Endpoint{ + endpoint.NewEndpoint("example.com", endpoint.RecordTypePTR, "10.0.0.1"), + endpoint.NewEndpoint("example2.com", endpoint.RecordTypePTR, "10.0.0.2"), + } + validateEndpoints(t, actual, expected) +} + func TestInfobloxApplyChanges(t *testing.T) { client := mockIBConnector{} - testInfobloxApplyChangesInternal(t, false, &client) + testInfobloxApplyChangesInternal(t, false, false, &client) validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{ endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"), @@ -423,7 +538,39 @@ func TestInfobloxApplyChanges(t *testing.T) { endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""), endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""), endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""), - endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeTXT, ""), + endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""), + }) + + validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{}) +} + +func TestInfobloxApplyChangesReverse(t *testing.T) { + client := mockIBConnector{} + + testInfobloxApplyChangesInternal(t, false, true, &client) + + validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{ + endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"), + endpoint.NewEndpoint("example.com", endpoint.RecordTypePTR, "1.2.3.4"), + endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"), + endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"), + endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypePTR, "1.2.3.4"), + endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"), + endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"), + endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"), + endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"), + endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"), + endpoint.NewEndpoint("new.example.com", endpoint.RecordTypeA, "111.222.111.222"), + endpoint.NewEndpoint("newcname.example.com", endpoint.RecordTypeCNAME, "other.com"), + endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeA, "1.2.3.4,3.4.5.6,8.9.10.11"), + endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeTXT, "tag-multiple-A-records"), + }) + + validateEndpoints(t, client.deletedEndpoints, []*endpoint.Endpoint{ + endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""), + endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""), + endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""), + endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypePTR, ""), endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""), }) @@ -435,7 +582,7 @@ func TestInfobloxApplyChangesDryRun(t *testing.T) { mockInfobloxObjects: &[]ibclient.IBObject{}, } - testInfobloxApplyChangesInternal(t, true, &client) + testInfobloxApplyChangesInternal(t, true, false, &client) validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{}) @@ -444,14 +591,16 @@ func TestInfobloxApplyChangesDryRun(t *testing.T) { validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{}) } -func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient.IBConnector) { +func testInfobloxApplyChangesInternal(t *testing.T, dryRun, createPTR bool, client ibclient.IBConnector) { client.(*mockIBConnector).mockInfobloxZones = &[]ibclient.ZoneAuth{ createMockInfobloxZone("example.com"), createMockInfobloxZone("other.com"), + createMockInfobloxZone("1.2.3.0/24"), } client.(*mockIBConnector).mockInfobloxObjects = &[]ibclient.IBObject{ createMockInfobloxObject("deleted.example.com", endpoint.RecordTypeA, "121.212.121.212"), createMockInfobloxObject("deleted.example.com", endpoint.RecordTypeTXT, "test-deleting-txt"), + createMockInfobloxObject("deleted.example.com", endpoint.RecordTypePTR, "121.212.121.212"), createMockInfobloxObject("deletedcname.example.com", endpoint.RecordTypeCNAME, "other.com"), createMockInfobloxObject("old.example.com", endpoint.RecordTypeA, "121.212.121.212"), createMockInfobloxObject("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"), @@ -461,6 +610,7 @@ func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient endpoint.NewDomainFilter([]string{""}), provider.NewZoneIDFilter([]string{""}), dryRun, + createPTR, client, ) @@ -493,11 +643,14 @@ func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient deleteRecords := []*endpoint.Endpoint{ endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, "121.212.121.212"), - endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeTXT, "test-deleting-txt"), endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, "other.com"), endpoint.NewEndpoint("deleted.nope.com", endpoint.RecordTypeA, "222.111.222.111"), } + if createPTR { + deleteRecords = append(deleteRecords, endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypePTR, "121.212.121.212")) + } + changes := &plan.Changes{ Create: createRecords, UpdateNew: updateNewRecords, @@ -516,11 +669,12 @@ func TestInfobloxZones(t *testing.T) { createMockInfobloxZone("example.com"), createMockInfobloxZone("lvl1-1.example.com"), createMockInfobloxZone("lvl2-1.lvl1-1.example.com"), + createMockInfobloxZone("1.2.3.0/24"), }, mockInfobloxObjects: &[]ibclient.IBObject{}, } - provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, &client) + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com", "1.2.3.0/24"}), provider.NewZoneIDFilter([]string{""}), true, false, &client) zones, _ := provider.zones() var emptyZoneAuth *ibclient.ZoneAuth assert.Equal(t, provider.findZone(zones, "example.com").Fqdn, "example.com") @@ -531,6 +685,26 @@ func TestInfobloxZones(t *testing.T) { assert.Equal(t, provider.findZone(zones, "lvl2-1.lvl1-1.example.com").Fqdn, "lvl2-1.lvl1-1.example.com") assert.Equal(t, provider.findZone(zones, "lvl2-2.lvl1-1.example.com").Fqdn, "lvl1-1.example.com") assert.Equal(t, provider.findZone(zones, "lvl2-2.lvl1-2.example.com").Fqdn, "example.com") + assert.Equal(t, provider.findZone(zones, "1.2.3.0/24").Fqdn, "1.2.3.0/24") +} + +func TestInfobloxReverseZones(t *testing.T) { + client := mockIBConnector{ + mockInfobloxZones: &[]ibclient.ZoneAuth{ + createMockInfobloxZone("example.com"), + createMockInfobloxZone("1.2.3.0/24"), + createMockInfobloxZone("10.0.0.0/8"), + }, + mockInfobloxObjects: &[]ibclient.IBObject{}, + } + + provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com", "1.2.3.0/24", "10.0.0.0/8"}), provider.NewZoneIDFilter([]string{""}), true, false, &client) + zones, _ := provider.zones() + var emptyZoneAuth *ibclient.ZoneAuth + assert.Equal(t, provider.findReverseZone(zones, "nomatch-example.com"), emptyZoneAuth) + assert.Equal(t, provider.findReverseZone(zones, "192.168.0.1"), emptyZoneAuth) + assert.Equal(t, provider.findReverseZone(zones, "1.2.3.4").Fqdn, "1.2.3.0/24") + assert.Equal(t, provider.findReverseZone(zones, "10.28.29.30").Fqdn, "10.0.0.0/8") } func TestExtendedRequestFDQDRegExBuilder(t *testing.T) { diff --git a/source/crd.go b/source/crd.go index a897d45aa..705d770bc 100644 --- a/source/crd.go +++ b/source/crd.go @@ -43,7 +43,7 @@ type crdSource struct { crdResource string codec runtime.ParameterCodec annotationFilter string - labelFilter string + labelSelector labels.Selector } func addKnownTypes(scheme *runtime.Scheme, groupVersion schema.GroupVersion) error { @@ -103,12 +103,12 @@ func NewCRDClientForAPIVersionKind(client kubernetes.Interface, kubeConfig, apiS } // NewCRDSource creates a new crdSource with the given config. -func NewCRDSource(crdClient rest.Interface, namespace, kind string, annotationFilter string, labelFilter string, scheme *runtime.Scheme) (Source, error) { +func NewCRDSource(crdClient rest.Interface, namespace, kind string, annotationFilter string, labelSelector labels.Selector, scheme *runtime.Scheme) (Source, error) { return &crdSource{ crdResource: strings.ToLower(kind) + "s", namespace: namespace, annotationFilter: annotationFilter, - labelFilter: labelFilter, + labelSelector: labelSelector, crdClient: crdClient, codec: runtime.NewParameterCodec(scheme), }, nil @@ -126,11 +126,7 @@ func (cs *crdSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error err error ) - if cs.labelFilter != "" { - result, err = cs.List(ctx, &metav1.ListOptions{LabelSelector: cs.labelFilter}) - } else { - result, err = cs.List(ctx, &metav1.ListOptions{}) - } + result, err = cs.List(ctx, &metav1.ListOptions{LabelSelector: cs.labelSelector.String()}) if err != nil { return nil, err } diff --git a/source/crd_test.go b/source/crd_test.go index a0b054ec2..a88fb4616 100644 --- a/source/crd_test.go +++ b/source/crd_test.go @@ -30,6 +30,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -381,9 +382,13 @@ func testCRDSourceEndpoints(t *testing.T) { require.NoError(t, err) scheme := runtime.NewScheme() - addKnownTypes(scheme, groupVersion) + require.NoError(t, addKnownTypes(scheme, groupVersion)) - cs, _ := NewCRDSource(restClient, ti.namespace, ti.kind, ti.annotationFilter, ti.labelFilter, scheme) + labelSelector, err := labels.Parse(ti.labelFilter) + require.NoError(t, err) + + cs, err := NewCRDSource(restClient, ti.namespace, ti.kind, ti.annotationFilter, labelSelector, scheme) + require.NoError(t, err) receivedEndpoints, err := cs.Endpoints(context.Background()) if ti.expectError { diff --git a/source/ingress.go b/source/ingress.go index 33a16c3b8..0b0081ede 100644 --- a/source/ingress.go +++ b/source/ingress.go @@ -63,10 +63,11 @@ type ingressSource struct { ingressInformer netinformers.IngressInformer ignoreIngressTLSSpec bool ignoreIngressRulesSpec bool + labelSelector labels.Selector } // NewIngressSource creates a new ingressSource with the given config. -func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool, ingressClassNames []string) (Source, error) { +func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool, labelSelector labels.Selector, ingressClassNames []string) (Source, error) { tmpl, err := parseTemplate(fqdnTemplate) if err != nil { return nil, err @@ -119,6 +120,7 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilt ingressInformer: ingressInformer, ignoreIngressTLSSpec: ignoreIngressTLSSpec, ignoreIngressRulesSpec: ignoreIngressRulesSpec, + labelSelector: labelSelector, } return sc, nil } @@ -126,7 +128,7 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilt // Endpoints returns endpoint objects for each host-target combination that should be processed. // Retrieves all ingress resources on all namespaces func (sc *ingressSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { - ingresses, err := sc.ingressInformer.Lister().Ingresses(sc.namespace).List(labels.Everything()) + ingresses, err := sc.ingressInformer.Lister().Ingresses(sc.namespace).List(sc.labelSelector) if err != nil { return nil, err } diff --git a/source/ingress_test.go b/source/ingress_test.go index f79a7b222..d74fc3463 100644 --- a/source/ingress_test.go +++ b/source/ingress_test.go @@ -26,6 +26,7 @@ import ( v1 "k8s.io/api/core/v1" networkv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/external-dns/endpoint" @@ -63,6 +64,7 @@ func (suite *IngressSuite) SetupTest() { false, false, false, + labels.Everything(), []string{}, ) suite.NoError(err, "should initialize ingress source") @@ -157,6 +159,7 @@ func TestNewIngressSource(t *testing.T) { false, false, false, + labels.Everything(), ti.ingressClassNames, ) if ti.expectError { @@ -372,6 +375,7 @@ func testIngressEndpoints(t *testing.T) { ignoreHostnameAnnotation bool ignoreIngressTLSSpec bool ignoreIngressRulesSpec bool + ingressLabelSelector labels.Selector ingressClassNames []string }{ { @@ -1234,6 +1238,41 @@ func testIngressEndpoints(t *testing.T) { Targets: endpoint.Targets{"4.5.6.7"}, }, }, + }, + { + ingressLabelSelector: labels.SelectorFromSet(labels.Set{"app": "web-external"}), + title: "ingress with matching labels", + targetNamespace: "", + ingressItems: []fakeIngress{ + { + name: "fake1", + namespace: namespace, + dnsnames: []string{"example.org"}, + ips: []string{"8.8.8.8"}, + labels: map[string]string{"app": "web-external", "name": "reverse-proxy"}, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "example.org", + Targets: endpoint.Targets{"8.8.8.8"}, + }, + }, + }, + { + ingressLabelSelector: labels.SelectorFromSet(labels.Set{"app": "web-external"}), + title: "ingress without matching labels", + targetNamespace: "", + ingressItems: []fakeIngress{ + { + name: "fake1", + namespace: namespace, + dnsnames: []string{"example.org"}, + ips: []string{"8.8.8.8"}, + labels: map[string]string{"app": "web-internal", "name": "reverse-proxy"}, + }, + }, + expected: []*endpoint.Endpoint{}, }, } { ti := ti @@ -1246,6 +1285,11 @@ func testIngressEndpoints(t *testing.T) { _, err := fakeClient.NetworkingV1().Ingresses(ingress.Namespace).Create(context.Background(), ingress, metav1.CreateOptions{}) require.NoError(t, err) } + + if ti.ingressLabelSelector == nil { + ti.ingressLabelSelector = labels.Everything() + } + source, _ := NewIngressSource( fakeClient, ti.targetNamespace, @@ -1255,6 +1299,7 @@ func testIngressEndpoints(t *testing.T) { ti.ignoreHostnameAnnotation, ti.ignoreIngressTLSSpec, ti.ignoreIngressRulesSpec, + ti.ingressLabelSelector, ti.ingressClassNames, ) // Informer cache has all of the ingresses. Retrieve and validate their endpoints. @@ -1278,6 +1323,7 @@ type fakeIngress struct { namespace string name string annotations map[string]string + labels map[string]string ingressClassName string } @@ -1287,6 +1333,7 @@ func (ing fakeIngress) Ingress() *networkv1.Ingress { Namespace: ing.namespace, Name: ing.name, Annotations: ing.annotations, + Labels: ing.labels, }, Spec: networkv1.IngressSpec{ Rules: []networkv1.IngressRule{}, diff --git a/source/openshift_route.go b/source/openshift_route.go index 39da5945e..4f27918fc 100644 --- a/source/openshift_route.go +++ b/source/openshift_route.go @@ -48,6 +48,7 @@ type ocpRouteSource struct { combineFQDNAnnotation bool ignoreHostnameAnnotation bool routeInformer routeInformer.RouteInformer + labelSelector labels.Selector } // NewOcpRouteSource creates a new ocpRouteSource with the given config. @@ -58,6 +59,7 @@ func NewOcpRouteSource( fqdnTemplate string, combineFQDNAnnotation bool, ignoreHostnameAnnotation bool, + labelSelector labels.Selector, ) (Source, error) { tmpl, err := parseTemplate(fqdnTemplate) if err != nil { @@ -66,11 +68,11 @@ func NewOcpRouteSource( // Use a shared informer to listen for add/update/delete of Routes in the specified namespace. // Set resync period to 0, to prevent processing when nothing has changed. - informerFactory := extInformers.NewFilteredSharedInformerFactory(ocpClient, 0, namespace, nil) - routeInformer := informerFactory.Route().V1().Routes() + informerFactory := extInformers.NewSharedInformerFactoryWithOptions(ocpClient, 0, extInformers.WithNamespace(namespace)) + informer := informerFactory.Route().V1().Routes() // Add default resource event handlers to properly initialize informer. - routeInformer.Informer().AddEventHandler( + informer.Informer().AddEventHandler( cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { }, @@ -92,7 +94,8 @@ func NewOcpRouteSource( fqdnTemplate: tmpl, combineFQDNAnnotation: combineFQDNAnnotation, ignoreHostnameAnnotation: ignoreHostnameAnnotation, - routeInformer: routeInformer, + routeInformer: informer, + labelSelector: labelSelector, }, nil } @@ -104,7 +107,7 @@ func (ors *ocpRouteSource) AddEventHandler(ctx context.Context, handler func()) // Retrieves all OpenShift Route resources on all namespaces, unless an explicit namespace // is specified in ocpRouteSource. func (ors *ocpRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { - ocpRoutes, err := ors.routeInformer.Lister().Routes(ors.namespace).List(labels.Everything()) + ocpRoutes, err := ors.routeInformer.Lister().Routes(ors.namespace).List(ors.labelSelector) if err != nil { return nil, err } diff --git a/source/openshift_route_test.go b/source/openshift_route_test.go index c586b2eda..f2307a5e3 100644 --- a/source/openshift_route_test.go +++ b/source/openshift_route_test.go @@ -23,6 +23,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "k8s.io/apimachinery/pkg/labels" routev1 "github.com/openshift/api/route/v1" fake "github.com/openshift/client-go/route/clientset/versioned/fake" @@ -48,6 +49,7 @@ func (suite *OCPRouteSuite) SetupTest() { "{{.Name}}", false, false, + labels.Everything(), ) suite.routeWithTargets = &routev1.Route{ @@ -104,6 +106,7 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) { annotationFilter string fqdnTemplate string expectError bool + labelFilter string }{ { title: "invalid template", @@ -124,8 +127,15 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) { expectError: false, annotationFilter: "kubernetes.io/ingress.class=nginx", }, + { + title: "valid label selector", + expectError: false, + labelFilter: "app=web-external", + }, } { ti := ti + labelSelector, err := labels.Parse(ti.labelFilter) + require.NoError(t, err) t.Run(ti.title, func(t *testing.T) { t.Parallel() @@ -136,6 +146,7 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) { ti.fqdnTemplate, false, false, + labelSelector, ) if ti.expectError { @@ -160,6 +171,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) { ocpRoute *routev1.Route expected []*endpoint.Endpoint expectError bool + labelFilter string }{ { title: "route with basic hostname and route status target", @@ -240,6 +252,61 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, expectError: false, }, + { + title: "route with matching labels", + labelFilter: "app=web-external", + ignoreHostnameAnnotation: false, + ocpRoute: &routev1.Route{ + + Spec: routev1.RouteSpec{ + Host: "my-annotation-domain.com", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "route-with-matching-labels", + Annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/target": "my.site.foo.com", + }, + Labels: map[string]string{ + "app": "web-external", + "name": "service-frontend", + }, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "my-annotation-domain.com", + Targets: []string{ + "my.site.foo.com", + }, + }, + }, + expectError: false, + }, + { + title: "route without matching labels", + labelFilter: "app=web-external", + ignoreHostnameAnnotation: false, + ocpRoute: &routev1.Route{ + + Spec: routev1.RouteSpec{ + Host: "my-annotation-domain.com", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "route-without-matching-labels", + Annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/target": "my.site.foo.com", + }, + Labels: map[string]string{ + "app": "web-internal", + "name": "service-frontend", + }, + }, + }, + expected: []*endpoint.Endpoint{}, + expectError: false, + }, } { tc := tc t.Run(tc.title, func(t *testing.T) { @@ -251,6 +318,9 @@ func testOcpRouteSourceEndpoints(t *testing.T) { _, err := fakeClient.RouteV1().Routes(tc.ocpRoute.Namespace).Create(context.Background(), tc.ocpRoute, metav1.CreateOptions{}) require.NoError(t, err) + labelSelector, err := labels.Parse(tc.labelFilter) + require.NoError(t, err) + source, err := NewOcpRouteSource( fakeClient, "", @@ -258,6 +328,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) { "{{.Name}}", false, false, + labelSelector, ) require.NoError(t, err) diff --git a/source/service.go b/source/service.go index 31c4f26a5..b9707be34 100644 --- a/source/service.go +++ b/source/service.go @@ -63,10 +63,11 @@ type serviceSource struct { podInformer coreinformers.PodInformer nodeInformer coreinformers.NodeInformer serviceTypeFilter map[string]struct{} + labelSelector labels.Selector } // NewServiceSource creates a new serviceSource with the given config. -func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, alwaysPublishNotReadyAddresses bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool) (Source, error) { +func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, alwaysPublishNotReadyAddresses bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool, labelSelector labels.Selector) (Source, error) { tmpl, err := parseTemplate(fqdnTemplate) if err != nil { return nil, err @@ -137,12 +138,13 @@ func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilt podInformer: podInformer, nodeInformer: nodeInformer, serviceTypeFilter: serviceTypes, + labelSelector: labelSelector, }, nil } // Endpoints returns endpoint objects for each service that should be processed. func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { - services, err := sc.serviceInformer.Lister().Services(sc.namespace).List(labels.Everything()) + services, err := sc.serviceInformer.Lister().Services(sc.namespace).List(sc.labelSelector) if err != nil { return nil, err } diff --git a/source/service_test.go b/source/service_test.go index 9cf6d8f49..38bf2a2d9 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/suite" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/kubernetes/fake" "sigs.k8s.io/external-dns/endpoint" @@ -75,6 +76,7 @@ func (suite *ServiceSuite) SetupTest() { false, []string{}, false, + labels.Everything(), ) suite.NoError(err, "should initialize service source") } @@ -153,6 +155,7 @@ func testServiceSourceNewServiceSource(t *testing.T) { false, ti.serviceTypesFilter, false, + labels.Everything(), ) if ti.expectError { @@ -187,1056 +190,804 @@ func testServiceSourceEndpoints(t *testing.T) { serviceTypesFilter []string expected []*endpoint.Endpoint expectError bool + serviceLabelSelector string }{ { - "no annotated services return no endpoints", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + title: "no annotated services return no endpoints", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "no annotated services return no endpoints when ignoring annotations", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - true, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + title: "no annotated services return no endpoints when ignoring annotations", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + ignoreHostnameAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "annotated services return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "hostname annotation on services is ignored", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - true, - map[string]string{}, - map[string]string{ + title: "hostname annotation on services is ignored", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + ignoreHostnameAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "annotated ClusterIp aren't processed without explicit authorization", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated ClusterIp aren't processed without explicit authorization", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "1.2.3.4", - []string{}, - []string{}, - []string{}, - []*endpoint.Endpoint{}, - false, + clusterIP: "1.2.3.4", + externalIPs: []string{}, + lbs: []string{}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "FQDN template with multiple hostnames return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", - false, - false, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + title: "FQDN template with multiple hostnames return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "FQDN template with multiple hostnames return an endpoint with target IP when ignoring annotations", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", - false, - true, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + title: "FQDN template with multiple hostnames return an endpoint with target IP when ignoring annotations", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + ignoreHostnameAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "FQDN template and annotation both with multiple hostnames return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", - true, - false, - map[string]string{}, - map[string]string{ + title: "FQDN template and annotation both with multiple hostnames return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + combineFQDNAndAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org., bar.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "FQDN template and annotation both with multiple hostnames while ignoring annotations will only return FQDN endpoints", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", - true, - true, - map[string]string{}, - map[string]string{ + title: "FQDN template and annotation both with multiple hostnames while ignoring annotations will only return FQDN endpoints", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.fqdn.org,{{.Name}}.fqdn.com", + combineFQDNAndAnnotation: true, + ignoreHostnameAnnotation: true, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org., bar.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.fqdn.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.fqdn.com", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "annotated services with multiple hostnames return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services with multiple hostnames return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org., bar.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "annotated services with multiple hostnames and without trailing period return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services with multiple hostnames and without trailing period return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org, bar.example.org", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "annotated services return an endpoint with target hostname", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services return an endpoint with target hostname", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"lb.example.com"}, // Kubernetes omits the trailing dot - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"lb.example.com"}, // Kubernetes omits the trailing dot + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}}, }, - false, }, { - "annotated services can omit trailing dot", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services can omit trailing dot", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org", // Trailing dot is omitted }, - "", - []string{}, - []string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "lb.example.com"}, // Kubernetes omits the trailing dot + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"lb.example.com"}}, }, - false, }, { - "our controller type is kops dns controller", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "our controller type is kops dns controller", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ controllerAnnotationKey: controllerAnnotationValue, hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "different controller types are ignored even (with template specified)", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.ext-dns.test.com", - false, - false, - map[string]string{}, - map[string]string{ + title: "different controller types are ignored even (with template specified)", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.ext-dns.test.com", + labels: map[string]string{}, + annotations: map[string]string{ controllerAnnotationKey: "some-other-tool", hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "services are found in target namespace", - "testing", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services are found in target namespace", + targetNamespace: "testing", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "services that are not in target namespace are ignored", - "testing", - "", - "other-testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services that are not in target namespace are ignored", + targetNamespace: "testing", + svcNamespace: "other-testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "services are found in all namespaces", - "", - "", - "other-testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services are found in all namespaces", + svcNamespace: "other-testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "valid matching annotation filter expression", - "", - "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "valid matching annotation filter expression", + annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "OnlyLocal", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "valid non-matching annotation filter expression", - "", - "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "valid non-matching annotation filter expression", + annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "SomethingElse", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "invalid annotation filter expression", - "", - "service.beta.kubernetes.io/external-traffic in (Global OnlyLocal)", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "invalid annotation filter expression", + annotationFilter: "service.beta.kubernetes.io/external-traffic in (Global OnlyLocal)", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "OnlyLocal", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - true, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, + expectError: true, }, { - "valid matching annotation filter label", - "", - "service.beta.kubernetes.io/external-traffic=Global", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "valid matching annotation filter label", + annotationFilter: "service.beta.kubernetes.io/external-traffic=Global", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "Global", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "valid non-matching annotation filter label", - "", - "service.beta.kubernetes.io/external-traffic=Global", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "valid non-matching annotation filter label", + annotationFilter: "service.beta.kubernetes.io/external-traffic=Global", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", "service.beta.kubernetes.io/external-traffic": "OnlyLocal", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "no external entrypoints return no endpoints", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "no external entrypoints return no endpoints", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "annotated service with externalIPs returns a single endpoint with multiple targets", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated service with externalIPs returns a single endpoint with multiple targets", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{"10.2.3.4", "11.2.3.4"}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{"10.2.3.4", "11.2.3.4"}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.2.3.4", "11.2.3.4"}}, }, - false, }, { - "multiple external entrypoints return a single endpoint with multiple targets", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "multiple external entrypoints return a single endpoint with multiple targets", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4", "8.8.8.8"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "8.8.8.8"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4", "8.8.8.8"}}, }, - false, }, { - "services annotated with legacy mate annotations are ignored in default mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services annotated with legacy mate annotations are ignored in default mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ "zalando.org/dnsname": "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, }, { - "services annotated with legacy mate annotations return an endpoint in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "mate", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "services annotated with legacy mate annotations return an endpoint in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "mate", + labels: map[string]string{}, + annotations: map[string]string{ "zalando.org/dnsname": "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "services annotated with legacy molecule annotations return an endpoint in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "molecule", - "", - false, - false, - map[string]string{ + title: "services annotated with legacy molecule annotations return an endpoint in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "molecule", + labels: map[string]string{ "dns": "route53", }, - map[string]string{ + annotations: map[string]string{ "domainName": "foo.example.org., bar.example.org", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "load balancer services annotated with DNS Controller annotations return an endpoint with A and CNAME targets in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "kops-dns-controller", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "load balancer services annotated with DNS Controller annotations return an endpoint with A and CNAME targets in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "kops-dns-controller", + labels: map[string]string{}, + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org", }, - "", - []string{}, - []string{"1.2.3.4", "lb.example.com"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "lb.example.com"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"lb.example.com"}}, }, - false, }, { - "load balancer services annotated with DNS Controller annotations return an endpoint with both annotations in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "kops-dns-controller", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "load balancer services annotated with DNS Controller annotations return an endpoint with both annotations in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "kops-dns-controller", + labels: map[string]string{}, + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "internal.bar.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, - { - "not annotated services with set fqdnTemplate return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.bar.example.com", - false, - false, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4", "elb.com"}, - []string{}, - []*endpoint.Endpoint{ + title: "not annotated services with set fqdnTemplate return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "elb.com"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"elb.com"}}, }, - false, }, { - "annotated services with set fqdnTemplate annotation takes precedence", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Name}}.bar.example.com", - false, - false, - map[string]string{}, - map[string]string{ + title: "annotated services with set fqdnTemplate annotation takes precedence", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4", "elb.com"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4", "elb.com"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"elb.com"}}, }, - false, }, { - "compatibility annotated services with tmpl. compatibility takes precedence", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "mate", - "{{.Name}}.bar.example.com", - false, - false, - map[string]string{}, - map[string]string{ + title: "compatibility annotated services with tmpl. compatibility takes precedence", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + compatibility: "mate", + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{}, + annotations: map[string]string{ "zalando.org/dnsname": "mate.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "mate.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "not annotated services with unknown tmpl field should not return anything", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "{{.Calibre}}.bar.example.com", - false, - false, - map[string]string{}, - map[string]string{}, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{}, - true, + title: "not annotated services with unknown tmpl field should not return anything", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + fqdnTemplate: "{{.Calibre}}.bar.example.com", + labels: map[string]string{}, + annotations: map[string]string{}, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{}, + expectError: true, }, { - "ttl not annotated should have RecordTTL.IsConfigured set to false", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "ttl not annotated should have RecordTTL.IsConfigured set to false", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, }, - false, }, { - "ttl annotated but invalid should have RecordTTL.IsConfigured set to false", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "ttl annotated but invalid should have RecordTTL.IsConfigured set to false", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", ttlAnnotationKey: "foo", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, }, - false, }, { - "ttl annotated and is valid should set Record.TTL", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "ttl annotated and is valid should set Record.TTL", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", ttlAnnotationKey: "10", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(10)}, }, - false, }, { - "ttl annotated (in duration format) and is valid should set Record.TTL", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "ttl annotated (in duration format) and is valid should set Record.TTL", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", ttlAnnotationKey: "1m", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(60)}, }, - false, }, { - "Negative ttl is not valid", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "Negative ttl is not valid", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", ttlAnnotationKey: "-10", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(0)}, }, - false, }, { - "filter on service types should include matching services", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "filter on service types should include matching services", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{string(v1.ServiceTypeLoadBalancer)}, - []*endpoint.Endpoint{ + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "filter on service types should exclude non-matching services", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "filter on service types should exclude non-matching services", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "", - []string{}, - []string{"1.2.3.4"}, - []string{string(v1.ServiceTypeLoadBalancer)}, - []*endpoint.Endpoint{}, - false, + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{string(v1.ServiceTypeLoadBalancer)}, + expected: []*endpoint.Endpoint{}, }, { - "internal-host annotated services return an endpoint with Cluster IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "internal-host annotated services return an endpoint with Cluster IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ internalHostnameAnnotationKey: "foo.internal.example.org.", }, - "1.1.1.1", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.internal.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, }, - false, }, { - "internal-host annotated and host annotated services return an endpoint with Cluster IP and an endpoint with lb IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeLoadBalancer, - "", - "", - false, - false, - map[string]string{}, - map[string]string{ + title: "internal-host annotated and host annotated services return an endpoint with Cluster IP and an endpoint with lb IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", internalHostnameAnnotationKey: "foo.internal.example.org.", }, - "1.1.1.1", - []string{}, - []string{"1.2.3.4"}, - []string{}, - []*endpoint.Endpoint{ + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + expected: []*endpoint.Endpoint{ {DNSName: "foo.internal.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, + }, + { + title: "service with matching labels and fqdn filter should be included", + svcNamespace: "testing", + svcName: "fqdn", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{ + "app": "web-external", + }, + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + serviceLabelSelector: "app=web-external", + fqdnTemplate: "{{.Name}}.bar.example.com", + expected: []*endpoint.Endpoint{ + {DNSName: "fqdn.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}}, + }, + }, + { + title: "service with matching labels and hostname annotation should be included", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{ + "app": "web-external", + }, + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + serviceLabelSelector: "app=web-external", + annotations: map[string]string{hostnameAnnotationKey: "annotation.bar.example.com"}, + expected: []*endpoint.Endpoint{ + {DNSName: "annotation.bar.example.com", Targets: endpoint.Targets{"1.2.3.4"}}, + }, + }, + { + title: "service without matching labels and fqdn filter should be excluded", + svcNamespace: "testing", + svcName: "fqdn", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{ + "app": "web-internal", + }, + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + serviceLabelSelector: "app=web-external", + fqdnTemplate: "{{.Name}}.bar.example.com", + expected: []*endpoint.Endpoint{}, + }, + { + title: "service without matching labels and hostname annotation should be excluded", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeLoadBalancer, + labels: map[string]string{ + "app": "web-internal", + }, + clusterIP: "1.1.1.1", + externalIPs: []string{}, + lbs: []string{"1.2.3.4"}, + serviceTypesFilter: []string{}, + serviceLabelSelector: "app=web-external", + annotations: map[string]string{hostnameAnnotationKey: "annotation.bar.example.com"}, + expected: []*endpoint.Endpoint{}, }, } { tc := tc @@ -1278,6 +1029,14 @@ func testServiceSourceEndpoints(t *testing.T) { _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) require.NoError(t, err) + var sourceLabel labels.Selector + if tc.serviceLabelSelector != "" { + sourceLabel, err = labels.Parse(tc.serviceLabelSelector) + require.NoError(t, err) + } else { + sourceLabel = labels.Everything() + } + // Create our object under test and get the endpoints. client, err := NewServiceSource( kubernetes, @@ -1291,7 +1050,9 @@ func testServiceSourceEndpoints(t *testing.T) { false, tc.serviceTypesFilter, tc.ignoreHostnameAnnotation, + sourceLabel, ) + require.NoError(t, err) res, err := client.Endpoints(context.Background()) @@ -1456,6 +1217,7 @@ func testMultipleServicesEndpoints(t *testing.T) { false, tc.serviceTypesFilter, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) @@ -1502,85 +1264,77 @@ func TestClusterIpServices(t *testing.T) { labels map[string]string annotations map[string]string clusterIP string - lbs []string expected []*endpoint.Endpoint expectError bool + labelSelector string }{ { - "annotated ClusterIp services return an endpoint with Cluster IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated ClusterIp services return an endpoint with Cluster IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "1.2.3.4", - []string{}, - []*endpoint.Endpoint{ + clusterIP: "1.2.3.4", + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}}, }, - false, }, { - "hostname annotated ClusterIp services are ignored", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "", - true, - map[string]string{}, - map[string]string{ + title: "hostname annotated ClusterIp services are ignored", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + ignoreHostnameAnnotation: true, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - "1.2.3.4", - []string{}, - []*endpoint.Endpoint{}, - false, + clusterIP: "1.2.3.4", + expected: []*endpoint.Endpoint{}, }, { - "non-annotated ClusterIp services with set fqdnTemplate return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "{{.Name}}.bar.example.com", - false, - map[string]string{}, - map[string]string{}, - "4.5.6.7", - []string{}, - []*endpoint.Endpoint{ + title: "non-annotated ClusterIp services with set fqdnTemplate return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + fqdnTemplate: "{{.Name}}.bar.example.com", + clusterIP: "4.5.6.7", + expected: []*endpoint.Endpoint{ {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"4.5.6.7"}}, }, - false, }, { - "Headless services do not generate endpoints", - "", - "", - "testing", - "foo", - v1.ServiceTypeClusterIP, - "", - "", - false, - map[string]string{}, - map[string]string{}, - v1.ClusterIPNone, - []string{}, - []*endpoint.Endpoint{}, - false, + title: "Headless services do not generate endpoints", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + clusterIP: v1.ClusterIPNone, + expected: []*endpoint.Endpoint{}, + }, + { + title: "ClusterIP service with matching label generates an endpoint", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{"app": "web-internal"}, + clusterIP: "4.5.6.7", + expected: []*endpoint.Endpoint{ + {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"4.5.6.7"}}, + }, + labelSelector: "app=web-internal", + }, + { + title: "ClusterIP service without matching label generates an endpoint", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeClusterIP, + fqdnTemplate: "{{.Name}}.bar.example.com", + labels: map[string]string{"app": "web-internal"}, + clusterIP: "4.5.6.7", + expected: []*endpoint.Endpoint{}, + labelSelector: "app=web-external", }, } { tc := tc @@ -1591,15 +1345,6 @@ func TestClusterIpServices(t *testing.T) { kubernetes := fake.NewSimpleClientset() // Create a service to test against - ingresses := []v1.LoadBalancerIngress{} - for _, lb := range tc.lbs { - if net.ParseIP(lb) != nil { - ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb}) - } else { - ingresses = append(ingresses, v1.LoadBalancerIngress{Hostname: lb}) - } - } - service := &v1.Service{ Spec: v1.ServiceSpec{ Type: tc.svcType, @@ -1611,16 +1356,18 @@ func TestClusterIpServices(t *testing.T) { Labels: tc.labels, Annotations: tc.annotations, }, - Status: v1.ServiceStatus{ - LoadBalancer: v1.LoadBalancerStatus{ - Ingress: ingresses, - }, - }, } _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) require.NoError(t, err) + var labelSelector labels.Selector + if tc.labelSelector != "" { + labelSelector, err = labels.Parse(tc.labelSelector) + require.NoError(t, err) + } else { + labelSelector = labels.Everything() + } // Create our object under test and get the endpoints. client, _ := NewServiceSource( kubernetes, @@ -1634,6 +1381,7 @@ func TestClusterIpServices(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labelSelector, ) require.NoError(t, err) @@ -1671,32 +1419,25 @@ func TestServiceSourceNodePortServices(t *testing.T) { expected []*endpoint.Endpoint expectError bool nodes []*v1.Node - podnames []string + podNames []string nodeIndex []int phases []v1.PodPhase + labelSelector labels.Selector }{ { - "annotated NodePort services return an endpoint with IP addresses of the cluster's nodes", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated NodePort services return an endpoint with IP addresses of the cluster's nodes", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1717,29 +1458,18 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "hostname annotated NodePort services are ignored", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - true, - map[string]string{}, - map[string]string{ + title: "hostname annotated NodePort services are ignored", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + ignoreHostnameAnnotation: true, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{}, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1760,30 +1490,20 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, + expected: []*endpoint.Endpoint{}, }, { - "non-annotated NodePort services with set fqdnTemplate return an endpoint with target IP", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "{{.Name}}.bar.example.com", - false, - map[string]string{}, - map[string]string{}, - nil, - []*endpoint.Endpoint{ + title: "non-annotated NodePort services with set fqdnTemplate return an endpoint with target IP", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + fqdnTemplate: "{{.Name}}.bar.example.com", + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1804,32 +1524,21 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "annotated NodePort services return an endpoint with IP addresses of the private cluster's nodes", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated NodePort services return an endpoint with IP addresses of the private cluster's nodes", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1848,32 +1557,21 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "annotated NodePort services with ExternalTrafficPolicy=Local return an endpoint with IP addresses of the cluster's nodes where pods is running only", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeLocal, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated NodePort services with ExternalTrafficPolicy=Local return an endpoint with IP addresses of the cluster's nodes where pods is running only", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1894,32 +1592,25 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{"pod-0"}, - []int{1}, - []v1.PodPhase{v1.PodRunning}, + podNames: []string{"pod-0"}, + nodeIndex: []int{1}, + phases: []v1.PodPhase{v1.PodRunning}, }, { - "annotated NodePort services with ExternalTrafficPolicy=Local and multiple pods on a single node return an endpoint with unique IP addresses of the cluster's nodes where pods is running only", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeLocal, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "annotated NodePort services with ExternalTrafficPolicy=Local and multiple pods on a single node return an endpoint with unique IP addresses of the cluster's nodes where pods is running only", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeLocal, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1940,33 +1631,26 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{"pod-0", "pod-1"}, - []int{1, 1}, - []v1.PodPhase{v1.PodRunning, v1.PodRunning}, + podNames: []string{"pod-0", "pod-1"}, + nodeIndex: []int{1, 1}, + phases: []v1.PodPhase{v1.PodRunning, v1.PodRunning}, }, { - "access=private annotation NodePort services return an endpoint with private IP addresses of the cluster's nodes", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "access=private annotation NodePort services return an endpoint with private IP addresses of the cluster's nodes", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", accessAnnotationKey: "private", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -1987,33 +1671,23 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "access=public annotation NodePort services return an endpoint with public IP addresses of the cluster's nodes", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "", - "", - false, - map[string]string{}, - map[string]string{ + title: "access=public annotation NodePort services return an endpoint with public IP addresses of the cluster's nodes", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + labels: map[string]string{}, + annotations: map[string]string{ hostnameAnnotationKey: "foo.example.org.", accessAnnotationKey: "public", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV}, {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", }, @@ -2034,32 +1708,23 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "node port services annotated DNS Controller annotations return an endpoint where all targets has the node role", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "kops-dns-controller", - "", - false, - map[string]string{}, - map[string]string{ + title: "node port services annotated DNS Controller annotations return an endpoint where all targets has the node role", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + compatibility: "kops-dns-controller", + labels: map[string]string{}, + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"10.0.1.1"}}, {DNSName: "internal.bar.example.org", Targets: endpoint.Targets{"10.0.1.1"}}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Labels: map[string]string{ @@ -2086,32 +1751,22 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "node port services annotated with internal DNS Controller annotations return an endpoint in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "kops-dns-controller", - "", - false, - map[string]string{}, - map[string]string{ + title: "node port services annotated with internal DNS Controller annotations return an endpoint in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + compatibility: "kops-dns-controller", + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "internal.foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}}, {DNSName: "internal.bar.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Labels: map[string]string{ @@ -2138,32 +1793,22 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "node port services annotated with external DNS Controller annotations return an endpoint in compatibility mode", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "kops-dns-controller", - "", - false, - map[string]string{}, - map[string]string{ + title: "node port services annotated with external DNS Controller annotations return an endpoint in compatibility mode", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + compatibility: "kops-dns-controller", + annotations: map[string]string{ kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org", }, - nil, - []*endpoint.Endpoint{ + expected: []*endpoint.Endpoint{ {DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}}, {DNSName: "bar.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}}, }, - false, - []*v1.Node{{ + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Labels: map[string]string{ @@ -2190,30 +1835,21 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, { - "node port services annotated with both kops dns controller annotations return an empty set of addons", - "", - "", - "testing", - "foo", - v1.ServiceTypeNodePort, - v1.ServiceExternalTrafficPolicyTypeCluster, - "kops-dns-controller", - "", - false, - map[string]string{}, - map[string]string{ + title: "node port services annotated with both kops dns controller annotations return an empty set of addons", + svcNamespace: "testing", + svcName: "foo", + svcType: v1.ServiceTypeNodePort, + svcTrafficPolicy: v1.ServiceExternalTrafficPolicyTypeCluster, + compatibility: "kops-dns-controller", + labels: map[string]string{}, + annotations: map[string]string{ kopsDNSControllerInternalHostnameAnnotationKey: "internal.foo.example.org., internal.bar.example.org", kopsDNSControllerHostnameAnnotationKey: "foo.example.org., bar.example.org", }, - nil, - []*endpoint.Endpoint{}, - false, - []*v1.Node{{ + expected: []*endpoint.Endpoint{}, + nodes: []*v1.Node{{ ObjectMeta: metav1.ObjectMeta{ Name: "node1", Labels: map[string]string{ @@ -2240,9 +1876,6 @@ func TestServiceSourceNodePortServices(t *testing.T) { }, }, }}, - []string{}, - []int{}, - []v1.PodPhase{}, }, } { tc := tc @@ -2260,7 +1893,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { } // Create pods - for i, podname := range tc.podnames { + for i, podname := range tc.podNames { pod := &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{}, @@ -2317,6 +1950,7 @@ func TestServiceSourceNodePortServices(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) @@ -2651,6 +2285,7 @@ func TestHeadlessServices(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) @@ -3006,6 +2641,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) @@ -3116,6 +2752,7 @@ func TestExternalServices(t *testing.T) { false, []string{}, tc.ignoreHostnameAnnotation, + labels.Everything(), ) require.NoError(t, err) @@ -3156,7 +2793,20 @@ func BenchmarkServiceEndpoints(b *testing.B) { _, err := kubernetes.CoreV1().Services(service.Namespace).Create(context.Background(), service, metav1.CreateOptions{}) require.NoError(b, err) - client, err := NewServiceSource(kubernetes, v1.NamespaceAll, "", "", false, "", false, false, false, []string{}, false) + client, err := NewServiceSource( + kubernetes, + v1.NamespaceAll, + "", + "", + false, + "", + false, + false, + false, + []string{}, + false, + labels.Everything(), + ) require.NoError(b, err) for i := 0; i < b.N; i++ { diff --git a/source/store.go b/source/store.go index 9e5743ffb..b3dcf80b6 100644 --- a/source/store.go +++ b/source/store.go @@ -29,6 +29,7 @@ import ( "github.com/pkg/errors" log "github.com/sirupsen/logrus" istioclient "istio.io/client-go/pkg/clientset/versioned" + "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -42,7 +43,7 @@ var ErrSourceNotFound = errors.New("source not found") type Config struct { Namespace string AnnotationFilter string - LabelFilter string + LabelFilter labels.Selector IngressClassNames []string FQDNTemplate string CombineFQDNAndAnnotation bool @@ -184,13 +185,13 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err if err != nil { return nil, err } - return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation) + return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter) case "ingress": client, err := p.KubeClient() if err != nil { return nil, err } - return NewIngressSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.IngressClassNames) + return NewIngressSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter, cfg.IngressClassNames) case "pod": client, err := p.KubeClient() if err != nil { @@ -254,7 +255,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err if err != nil { return nil, err } - return NewOcpRouteSource(ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation) + return NewOcpRouteSource(ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter) case "fake": return NewFakeSource(cfg.FQDNTemplate) case "connector":