From 84da6be1ddabfbadde4f444562b004d26c0700c8 Mon Sep 17 00:00:00 2001 From: Michel Loiseleur Date: Wed, 29 May 2024 16:01:45 +0200 Subject: [PATCH] chore: Remove infoblox in-tree provider --- README.md | 3 - docs/proposal/multi-target.md | 117 ++- docs/tutorials/infoblox.md | 290 ------ go.mod | 1 - go.sum | 2 - main.go | 21 - pkg/apis/externaldns/types.go | 37 +- pkg/apis/externaldns/types_test.go | 34 - pkg/apis/externaldns/validation/validation.go | 10 - provider/infoblox/infoblox.go | 744 -------------- provider/infoblox/infoblox_test.go | 939 ------------------ 11 files changed, 59 insertions(+), 2139 deletions(-) delete mode 100644 docs/tutorials/infoblox.md delete mode 100644 provider/infoblox/infoblox.go delete mode 100644 provider/infoblox/infoblox_test.go diff --git a/README.md b/README.md index c81bbbe68b..6efa7b4df9 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,6 @@ ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchroniz * [RcodeZero](https://www.rcodezero.at/) * [DigitalOcean](https://www.digitalocean.com/products/networking) * [DNSimple](https://dnsimple.com/) -* [Infoblox](https://www.infoblox.com/products/dns/) * [Dyn](https://dyn.com/dns/) * [OpenStack Designate](https://docs.openstack.org/designate/latest/) * [PowerDNS](https://www.powerdns.com/) @@ -118,7 +117,6 @@ The following table clarifies the current status of the providers according to t | RcodeZero | Alpha | | | DigitalOcean | Alpha | | | DNSimple | Alpha | | -| Infoblox | Alpha | @saileshgiri | | Dyn | Alpha | | | OpenStack Designate | Alpha | | | PowerDNS | Alpha | | @@ -187,7 +185,6 @@ The following tutorials are provided: * [Using Google's Default Ingress Controller](docs/tutorials/gke.md) * [Using the Nginx Ingress Controller](docs/tutorials/nginx-ingress.md) * [Headless Services](docs/tutorials/hostport.md) -* [Infoblox](docs/tutorials/infoblox.md) * [Istio Gateway Source](docs/tutorials/istio.md) * [Kubernetes Security Context](docs/tutorials/security-context.md) * [Linode](docs/tutorials/linode.md) diff --git a/docs/proposal/multi-target.md b/docs/proposal/multi-target.md index 0d0a007616..a59020235f 100644 --- a/docs/proposal/multi-target.md +++ b/docs/proposal/multi-target.md @@ -1,64 +1,64 @@ -# Multiple Targets per hostname +# Multiple Targets per hostname *(November 2017)* -## Purpose +## Purpose -One should be able to define multiple targets (IPs/Hostnames) in the **same** Kubernetes resource object and expect -ExternalDNS create DNS record(s) with a specified hostname and all targets. So far the connection between k8s resources (ingress/services) and DNS records +One should be able to define multiple targets (IPs/Hostnames) in the **same** Kubernetes resource object and expect +ExternalDNS create DNS record(s) with a specified hostname and all targets. So far the connection between k8s resources (ingress/services) and DNS records were not streamlined. This proposal aims to make the connection explicit, making k8s resources acquire or release certain DNS names. As long as the resource -ingress/service owns the record it can have multiple targets enable iff they are specified in the same resource. +ingress/service owns the record it can have multiple targets enable iff they are specified in the same resource. -## Use cases +## Use cases See https://github.com/kubernetes-sigs/external-dns/issues/239 -## Current behaviour +## Current behaviour *(as of the moment of writing)* -Central piece of enabling multi-target is having consistent and correct behaviour in `plan` component in regards to how endpoints generated +Central piece of enabling multi-target is having consistent and correct behaviour in `plan` component in regards to how endpoints generated from kubernetes resources are mapped to dns records. Current implementation of the `plan` has inconsistent behaviour in the following scenarios, all -of which must be resolved before multi-target support can be enabled in the provider implementations: +of which must be resolved before multi-target support can be enabled in the provider implementations: -1. No records registered so far. Two **different** ingresses request same hostname but different targets, e.g. Ingress A: example.com -> 1.1.1.1 and Ingress B: example.com -> 2.2.2.2 +1. No records registered so far. Two **different** ingresses request same hostname but different targets, e.g. Ingress A: example.com -> 1.1.1.1 and Ingress B: example.com -> 2.2.2.2 * *Current Behaviour*: both are added to the "Create" (records to be created) list and passed to Provider - * *Expected Behaviour*: only one (random/ or according to predefined strategy) should be chosen and passed to Provider - - **NOTE**: while this seems to go against multi-target support, this is done so no other resource can "hijack" already created DNS record. Multi targets are supported only -on per single resource basis + * *Expected Behaviour*: only one (random/ or according to predefined strategy) should be chosen and passed to Provider -2. Now let's say Ingress A was chosen and successfully created, but both ingress A and B are still there. So on next iteration ExternalDNS would see both again in the Desired list. + **NOTE**: while this seems to go against multi-target support, this is done so no other resource can "hijack" already created DNS record. Multi targets are supported only +on per single resource basis + +2. Now let's say Ingress A was chosen and successfully created, but both ingress A and B are still there. So on next iteration ExternalDNS would see both again in the Desired list. * *Current Behaviour*: DNS record target will change to that of Ingress B. * *Expected Behaviour*: Ingress A should stay unchanged. Ingress B record is not created 3. DNS record for Ingress A was created but its target has changed. Ingress B is still there - * *Current Behaviour*: Undetermined behaviour based on which ingress will be parsed last. + * *Current Behaviour*: Undetermined behaviour based on which ingress will be parsed last. * *Expected Behaviour*: DNS record should point to the new target specified in A. Ingress B should still be ignored. - - **NOTE**: both 2. and 3. can be resolved if External DNS is aware which resource has already acquired DNS record - + + **NOTE**: both 2. and 3. can be resolved if External DNS is aware which resource has already acquired DNS record + 4. Ingress C has multiple targets: 1.1.1.1 and 2.2.2.2 - * *Current Behaviour*: Both targets are split into different endpoints and we end up in one of the cases above - * *Expected Behaviour*: Endpoint should contain list of targets and treated as one ingress object. + * *Current Behaviour*: Both targets are split into different endpoints and we end up in one of the cases above + * *Expected Behaviour*: Endpoint should contain list of targets and treated as one ingress object. ## Requirements and assumptions -For this feature to work we have to make sure that: +For this feature to work we have to make sure that: -1. DNS records are now owned by certain ingress/service resources. For External DNS it would mean that TXT records now -should store back-reference for the resource this record was created for, i.e. `"heritage=external-dns,external-dns/resource=ingress/default/my-ingress-object-name"` -2. DNS records are updated only: +1. DNS records are now owned by certain ingress/service resources. For External DNS it would mean that TXT records now +should store back-reference for the resource this record was created for, i.e. `"heritage=external-dns,external-dns/resource=ingress/default/my-ingress-object-name"` +2. DNS records are updated only: - - If owning resource target list has changed + - If owning resource target list has changed - If owning resource record is not found in the desired list (meaning it was deleted), therefore it will now be owned by another record. So its target list will be updated - - Changes related to other record properties (e.g. TTL) + - Changes related to other record properties (e.g. TTL) -4. All of the issues described in `Current Behaviour` sections are resolved +4. All of the issues described in `Current Behaviour` sections are resolved Once Create/Update/Delete lists are calculated correctly (this is where conflicts based on requested DNS names are resolved) they are passed to `provider`, where `provider` specific implementation will decide how to convert the structures into required formats. If DNS provider does not (or partially) support multi targets -then it is up to the provider to make sure that the change list of records passed to the DNS provider API is valid. **TODO**: explain best strategy. +then it is up to the provider to make sure that the change list of records passed to the DNS provider API is valid. **TODO**: explain best strategy. Additionally see https://github.com/kubernetes-sigs/external-dns/issues/258 @@ -66,54 +66,53 @@ Additionally see https://github.com/kubernetes-sigs/external-dns/issues/258 Brief summary of open PRs and what they are trying to address: -### PRs +### PRs 1. https://github.com/kubernetes-sigs/external-dns/pull/243 - first attempt to add support for multiple targets. It is lagging far behind from tip - + *what it does*: unfinished attempt to extend `Endpoint` struct, for it to allow multiple targets (essentially `target string -> targets []string`) - - *action*: evaluate if rebasing makes sense, or we can just close it. - -2. https://github.com/kubernetes-sigs/external-dns/pull/261 - attempt to rework `plan` to make it work correctly with multiple targets. - + + *action*: evaluate if rebasing makes sense, or we can just close it. + +2. https://github.com/kubernetes-sigs/external-dns/pull/261 - attempt to rework `plan` to make it work correctly with multiple targets. + *what it does* : attempts to fix issues with `plan` described in `Current Behaviour` section above. Included tests reveal the current problem with `plan` - + *action*: rebase on default branch and make necessary changes to satisfy requirements listed in this document including back-reference to owning record - -3. https://github.com/kubernetes-sigs/external-dns/pull/326 - attempt to add multiple target support. - + +3. https://github.com/kubernetes-sigs/external-dns/pull/326 - attempt to add multiple target support. + *what it does*: for each pair `DNS Name` + `Record Type` it aggregates **all** targets from the cluster and passes them to Provider. It adds basic support - for DO, Azure, Cloudflare, AWS, GCP, however those are not tested (?). (DNSSimple and Infoblox providers were not updated) - - *action*: the `plan` logic will probably needs to be reworked, however the rest concerning support in Providers and extending `Endpoint` struct can be reused. + + *action*: the `plan` logic will probably needs to be reworked, however the rest concerning support in Providers and extending `Endpoint` struct can be reused. Rebase on default branch and add missing pieces. Depends on `2`. - + Related PRs: https://github.com/kubernetes-sigs/external-dns/pull/331/files, https://github.com/kubernetes-sigs/external-dns/pull/347/files - aiming at AWS Route53 weighted records. These PRs should be considered after common agreement about the way to address multi-target support is achieved. Related discussion: https://github.com/kubernetes-sigs/external-dns/issues/196 ### How to proceed from here -The following steps are needed: -1. Make sure consensus regarding the approach is achieved via collaboration on the current document +The following steps are needed: +1. Make sure consensus regarding the approach is achieved via collaboration on the current document 2. Notify all PR (see above) authors about the agreed approach -3. Implementation: - +3. Implementation: + a. `Plan` is working as expected - either based on #261 above or from scratch. `Plan` should be working correctly regardless of multi-target support - + b. Extensive testing making sure new `plan` does not introduce any breaking changes - + c. Change Endpoint struct to support multiple targets - based on #326 - integrate it with new `plan` @sethpollack - - d. Make sure new endpoint format can still be used in providers which have only partial support for multi targets ~~**TODO**: how ?~~ . This is to be done by simply using first target in the targets list. - - e. Add support for multi target which are already addressed in #326. It goes alongside c. and can be based on the same PR @sethpollack. New providers - added since then should maintain same functionality. + + d. Make sure new endpoint format can still be used in providers which have only partial support for multi targets ~~**TODO**: how ?~~ . This is to be done by simply using first target in the targets list. + + e. Add support for multi target which are already addressed in #326. It goes alongside c. and can be based on the same PR @sethpollack. New providers + added since then should maintain same functionality. 5. Extensive testing on **all** providers before making new release -6. Update all related documentation and explain how multi targets are supported on per provider basis -7. Think of introducing weighted records (see PRs section above) and making them configurable. - -## Open questions +6. Update all related documentation and explain how multi targets are supported on per provider basis +7. Think of introducing weighted records (see PRs section above) and making them configurable. + +## Open questions - Handling cases when ingress/service targets include both hostnames and IPs - postpone this until use cases occurs - "Weighted records scope": https://github.com/kubernetes-sigs/external-dns/issues/196 - this should be considered once multi-target support is implemented diff --git a/docs/tutorials/infoblox.md b/docs/tutorials/infoblox.md deleted file mode 100644 index ce8ed958f1..0000000000 --- a/docs/tutorials/infoblox.md +++ /dev/null @@ -1,290 +0,0 @@ -# Setting up ExternalDNS for Infoblox - -This tutorial describes how to setup ExternalDNS for usage with Infoblox. - -Make sure to use **>=0.4.6** version of ExternalDNS for this tutorial. The only WAPI version that -has been validated is **v2.3.1**. It is assumed that the API user has rights to create objects of -the following types: `zone_auth`, `record:a`, `record:cname`, `record:txt`. - -This tutorial assumes you have substituted the correct values for the following environment variables: - -``` -export GRID_HOST=127.0.0.1 -export WAPI_PORT=443 -export WAPI_VERSION=2.3.1 -export WAPI_USERNAME=admin -export WAPI_PASSWORD=infoblox -``` - -## Creating an Infoblox DNS zone - -The Infoblox provider for ExternalDNS will find suitable zones for domains it manages; it will -not automatically create zones. - -Create an Infoblox DNS zone for "example.com": - -``` -$ curl -kl \ - -X POST \ - -d fqdn=example.com \ - -u ${WAPI_USERNAME}:${WAPI_PASSWORD} \ - https://${GRID_HOST}:${WAPI_PORT}/wapi/v${WAPI_VERSION}/zone_auth -``` - -Substitute a domain you own for "example.com" if desired. - -## Creating an Infoblox Configuration Secret - -For ExternalDNS to access the Infoblox API, create a Kubernetes secret. - -To create the secret: - -``` -$ kubectl create secret generic external-dns \ - --from-literal=EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME=${WAPI_USERNAME} \ - --from-literal=EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD=${WAPI_PASSWORD} -``` - -## Deploy ExternalDNS - -Connect your `kubectl` client to the cluster you want to test ExternalDNS with. -Then apply one of the following manifests file to deploy ExternalDNS. - -### Manifest (for clusters without RBAC enabled) -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: external-dns -spec: - strategy: - type: Recreate - selector: - matchLabels: - app: external-dns - template: - metadata: - labels: - app: external-dns - spec: - containers: - - name: external-dns - image: registry.k8s.io/external-dns/external-dns:v0.14.2 - args: - - --source=service - - --domain-filter=example.com # (optional) limit to only example.com domains. - - --provider=infoblox - - --infoblox-grid-host=${GRID_HOST} # (required) IP of the Infoblox Grid host. - - --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. - - --infoblox-view="" # (optional) DNS view (default: "") - env: - - name: EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS - value: "10" # (optional) Infoblox WAPI request connection pool size. The default is "10". - - name: EXTERNAL_DNS_INFOBLOX_HTTP_REQUEST_TIMEOUT - value: "60" # (optional) Infoblox WAPI request timeout in seconds. The default is "60". - - name: EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME - valueFrom: - secretKeyRef: - name: external-dns - key: EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME - - name: EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD - valueFrom: - secretKeyRef: - name: external-dns - key: EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD -``` - -### Manifest (for clusters with RBAC enabled) - -```yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: external-dns ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: external-dns -rules: -- apiGroups: [""] - resources: ["services","endpoints","pods"] - verbs: ["get","watch","list"] -- apiGroups: ["extensions","networking.k8s.io"] - resources: ["ingresses"] - verbs: ["get","watch","list"] -- apiGroups: [""] - resources: ["nodes"] - verbs: ["list"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: external-dns-viewer -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: external-dns -subjects: -- kind: ServiceAccount - name: external-dns - namespace: default ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: external-dns -spec: - strategy: - type: Recreate - selector: - matchLabels: - app: external-dns - template: - metadata: - labels: - app: external-dns - spec: - serviceAccountName: external-dns - containers: - - name: external-dns - image: registry.k8s.io/external-dns/external-dns:v0.14.2 - args: - - --source=service - - --domain-filter=example.com # (optional) limit to only example.com domains. - - --provider=infoblox - - --infoblox-grid-host=${GRID_HOST} # (required) IP of the Infoblox Grid host. - - --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. - - --infoblox-view="" # (optional) DNS view (default: "") - env: - - name: EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS - value: "10" # (optional) Infoblox WAPI request connection pool size. The default is "10". - - name: EXTERNAL_DNS_INFOBLOX_HTTP_REQUEST_TIMEOUT - value: "60" # (optional) Infoblox WAPI request timeout in seconds. The default is "60". - - name: EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME - valueFrom: - secretKeyRef: - name: external-dns - key: EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME - - name: EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD - valueFrom: - secretKeyRef: - name: external-dns - key: EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD -``` - - -## Deploying an Nginx Service - -Create a service file called 'nginx.yaml' with the following contents: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx -spec: - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - image: nginx - name: nginx - ports: - - containerPort: 80 ---- -apiVersion: v1 -kind: Service -metadata: - name: nginx - annotations: - external-dns.alpha.kubernetes.io/hostname: example.com -spec: - selector: - app: nginx - type: LoadBalancer - ports: - - protocol: TCP - port: 80 - targetPort: 80 -``` - -Note the annotation on the service; use the same hostname as the Infoblox DNS zone created above. The annotation may also be a subdomain -of the DNS zone (e.g. 'www.example.com'). - -ExternalDNS uses this annotation to determine what services should be registered with DNS. Removing the annotation -will cause ExternalDNS to remove the corresponding DNS records. - -Create the deployment and service: - -``` -$ kubectl create -f nginx.yaml -``` - -It takes a little while for the Infoblox cloud provider to create an external IP for the service. Check the status by running -`kubectl get services nginx`. If the `EXTERNAL-IP` field shows an address, the service is ready to be accessed externally. - -Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and synchronize -the Infoblox DNS records. - -## Verifying Infoblox DNS records - -Run the following command to view the A records for your Infoblox DNS zone: - -``` -$ curl -kl \ - -X GET \ - -u ${WAPI_USERNAME}:${WAPI_PASSWORD} \ - https://${GRID_HOST}:${WAPI_PORT}/wapi/v${WAPI_VERSION}/record:a?zone=example.com -``` - -Substitute the zone for the one created above if a different domain was used. - -This should show the external IP address of the service as the A record for your domain ('@' indicates the record is for the zone itself). - -## Clean up - -Now that we have verified that ExternalDNS will automatically manage Infoblox DNS records, we can delete the tutorial's -DNS zone: - -``` -$ curl -kl \ - -X DELETE \ - -u ${WAPI_USERNAME}:${WAPI_PASSWORD} \ - https://${GRID_HOST}:${WAPI_PORT}/wapi/v${WAPI_VERSION}/zone_auth?fqdn=example.com -``` - -## Ability to filter results from the zone auth API using a regular expression - -There is also the ability to filter results from the Infoblox zone_auth service based upon a regular expression. See the [Infoblox API document](https://www.infoblox.com/wp-content/uploads/infoblox-deployment-infoblox-rest-api.pdf) for examples. To use this feature for the zone_auth service, set the parameter infoblox-fqdn-regex for external-dns to a regular expression that makes sense for you. For instance, to only return hosted zones that start with staging in the test.com domain (like staging.beta.test.com, or staging.test.com), use the following command line option when starting external-dns - -``` ---infoblox-fqdn-regex=^staging.*test.com$ -``` - -## Ability to filter A, Host, CNAME and TXT records from the by name using a regular expression - -Infoblox supports filtering records by name using a regular expression. See the [Infoblox API document](https://www.infoblox.com/wp-content/uploads/infoblox-deployment-infoblox-rest-api.pdf) for examples. To use this feature, set the parameter infoblox-name-regex for external-dns to a regular expression that makes sense for you. For instance, if all your dns records end with `cluster1.example.com`, you can fetch records matching this style by setting the following: - -``` ---infoblox-name-regex=cluster1.example.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/go.mod b/go.mod index 9a41805214..6fbd67abfa 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,6 @@ require ( github.com/google/uuid v1.6.0 github.com/gophercloud/gophercloud v1.12.0 github.com/hooklift/gowsdl v0.5.0 - github.com/infobloxopen/infoblox-go-client/v2 v2.6.0 github.com/linki/instrumented_http v0.3.0 github.com/linode/linodego v1.34.0 github.com/maxatome/go-testdeep v1.14.0 diff --git a/go.sum b/go.sum index aad94219a3..76d4a4c33a 100644 --- a/go.sum +++ b/go.sum @@ -655,8 +655,6 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/infobloxopen/infoblox-go-client/v2 v2.6.0 h1:nwdGhQ5XRheGybEdUQ4cSl1Vw2UsSQKKi+HEleguQug= -github.com/infobloxopen/infoblox-go-client/v2 v2.6.0/go.mod h1:Zu7c+X0mTB6ahIYm7p9LlvfcH814ZUEP+eXGPEYLDU4= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= diff --git a/main.go b/main.go index 96e79c7400..a84a4df0b0 100644 --- a/main.go +++ b/main.go @@ -61,7 +61,6 @@ import ( "sigs.k8s.io/external-dns/provider/godaddy" "sigs.k8s.io/external-dns/provider/google" "sigs.k8s.io/external-dns/provider/ibmcloud" - "sigs.k8s.io/external-dns/provider/infoblox" "sigs.k8s.io/external-dns/provider/inmemory" "sigs.k8s.io/external-dns/provider/linode" "sigs.k8s.io/external-dns/provider/ns1" @@ -280,26 +279,6 @@ func main() { p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version) case "dnsimple": p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun) - case "infoblox": - p, err = infoblox.NewInfobloxProvider( - infoblox.StartupConfig{ - DomainFilter: domainFilter, - ZoneIDFilter: zoneIDFilter, - Host: cfg.InfobloxGridHost, - Port: cfg.InfobloxWapiPort, - Username: cfg.InfobloxWapiUsername, - Password: cfg.InfobloxWapiPassword, - Version: cfg.InfobloxWapiVersion, - SSLVerify: cfg.InfobloxSSLVerify, - View: cfg.InfobloxView, - MaxResults: cfg.InfobloxMaxResults, - DryRun: cfg.DryRun, - FQDNRegEx: cfg.InfobloxFQDNRegEx, - NameRegEx: cfg.InfobloxNameRegEx, - CreatePTR: cfg.InfobloxCreatePTR, - CacheDuration: cfg.InfobloxCacheDuration, - }, - ) case "dyn": p, err = dyn.NewDynProvider( dyn.DynConfig{ diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 2bc5dc08a8..e88dabf659 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -120,18 +120,6 @@ type Config struct { AkamaiAccessToken string AkamaiEdgercPath string AkamaiEdgercSection string - InfobloxGridHost string - InfobloxWapiPort int - InfobloxWapiUsername string - InfobloxWapiPassword string `secure:"yes"` - InfobloxWapiVersion string - InfobloxSSLVerify bool - InfobloxView string - InfobloxMaxResults int - InfobloxFQDNRegEx string - InfobloxNameRegEx string - InfobloxCreatePTR bool - InfobloxCacheDuration int DynCustomerName string DynUsername string DynPassword string `secure:"yes"` @@ -292,17 +280,6 @@ var defaultConfig = &Config{ AkamaiAccessToken: "", AkamaiEdgercSection: "", AkamaiEdgercPath: "", - InfobloxGridHost: "", - InfobloxWapiPort: 443, - InfobloxWapiUsername: "admin", - InfobloxWapiPassword: "", - InfobloxWapiVersion: "2.3.1", - InfobloxSSLVerify: true, - InfobloxView: "", - InfobloxMaxResults: 0, - InfobloxFQDNRegEx: "", - InfobloxCreatePTR: false, - InfobloxCacheDuration: 0, OCIConfigFile: "/etc/kubernetes/oci.yaml", OCIZoneScope: "GLOBAL", OCIZoneCacheDuration: 0 * time.Second, @@ -475,7 +452,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("traefik-disable-new", "Disable listeners on Resources under the traefik.io API Group").Default(strconv.FormatBool(defaultConfig.TraefikDisableNew)).BoolVar(&cfg.TraefikDisableNew) // Flags related to providers - providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "bluecat", "civo", "cloudflare", "coredns", "designate", "digitalocean", "dnsimple", "dyn", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "infoblox", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rcodezero", "rdns", "rfc2136", "safedns", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "vinyldns", "vultr", "webhook"} + providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "bluecat", "civo", "cloudflare", "coredns", "designate", "digitalocean", "dnsimple", "dyn", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rcodezero", "rdns", "rfc2136", "safedns", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "vinyldns", "vultr", "webhook"} app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: "+strings.Join(providers, ", ")+")").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, providers...) app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter) app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains) @@ -529,18 +506,6 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("akamai-access-token", "When using the Akamai provider, specify the access token (required when --provider=akamai and edgerc-path not specified)").Default(defaultConfig.AkamaiAccessToken).StringVar(&cfg.AkamaiAccessToken) app.Flag("akamai-edgerc-path", "When using the Akamai provider, specify the .edgerc file path. Path must be reachable form invocation environment. (required when --provider=akamai and *-token, secret serviceconsumerdomain not specified)").Default(defaultConfig.AkamaiEdgercPath).StringVar(&cfg.AkamaiEdgercPath) app.Flag("akamai-edgerc-section", "When using the Akamai provider, specify the .edgerc file path (Optional when edgerc-path is specified)").Default(defaultConfig.AkamaiEdgercSection).StringVar(&cfg.AkamaiEdgercSection) - app.Flag("infoblox-grid-host", "When using the Infoblox provider, specify the Grid Manager host (required when --provider=infoblox)").Default(defaultConfig.InfobloxGridHost).StringVar(&cfg.InfobloxGridHost) - app.Flag("infoblox-wapi-port", "When using the Infoblox provider, specify the WAPI port (default: 443)").Default(strconv.Itoa(defaultConfig.InfobloxWapiPort)).IntVar(&cfg.InfobloxWapiPort) - app.Flag("infoblox-wapi-username", "When using the Infoblox provider, specify the WAPI username (default: admin)").Default(defaultConfig.InfobloxWapiUsername).StringVar(&cfg.InfobloxWapiUsername) - app.Flag("infoblox-wapi-password", "When using the Infoblox provider, specify the WAPI password (required when --provider=infoblox)").Default(defaultConfig.InfobloxWapiPassword).StringVar(&cfg.InfobloxWapiPassword) - app.Flag("infoblox-wapi-version", "When using the Infoblox provider, specify the WAPI version (default: 2.3.1)").Default(defaultConfig.InfobloxWapiVersion).StringVar(&cfg.InfobloxWapiVersion) - app.Flag("infoblox-ssl-verify", "When using the Infoblox provider, specify whether to verify the SSL certificate (default: true, disable with --no-infoblox-ssl-verify)").Default(strconv.FormatBool(defaultConfig.InfobloxSSLVerify)).BoolVar(&cfg.InfobloxSSLVerify) - 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-name-regex", "Apply this regular expression as a filter on the name field for obtaining infoblox records. This is disabled by default.").Default(defaultConfig.InfobloxNameRegEx).StringVar(&cfg.InfobloxNameRegEx) - 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("infoblox-cache-duration", "When using the Infoblox provider, set the record TTL (0s to disable).").Default(strconv.Itoa(defaultConfig.InfobloxCacheDuration)).IntVar(&cfg.InfobloxCacheDuration) 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/types_test.go b/pkg/apis/externaldns/types_test.go index 987fd5ef37..68f22fa936 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -88,14 +88,6 @@ var ( AkamaiAccessToken: "", AkamaiEdgercPath: "", AkamaiEdgercSection: "", - InfobloxGridHost: "", - InfobloxWapiPort: 443, - InfobloxWapiUsername: "admin", - InfobloxWapiPassword: "", - InfobloxWapiVersion: "2.3.1", - InfobloxView: "", - InfobloxSSLVerify: true, - InfobloxMaxResults: 0, OCIConfigFile: "/etc/kubernetes/oci.yaml", OCIZoneScope: "GLOBAL", OCIZoneCacheDuration: 0 * time.Second, @@ -202,14 +194,6 @@ var ( AkamaiAccessToken: "o184671d5307a388180fbf7f11dbdf46", AkamaiEdgercPath: "/home/test/.edgerc", AkamaiEdgercSection: "default", - InfobloxGridHost: "127.0.0.1", - InfobloxWapiPort: 8443, - InfobloxWapiUsername: "infoblox", - InfobloxWapiPassword: "infoblox", - InfobloxWapiVersion: "2.6.1", - InfobloxView: "internal", - InfobloxSSLVerify: false, - InfobloxMaxResults: 2000, OCIConfigFile: "oci.yaml", OCIZoneScope: "PRIVATE", OCIZoneCacheDuration: 30 * time.Second, @@ -320,13 +304,6 @@ func TestParseFlags(t *testing.T) { "--akamai-access-token=o184671d5307a388180fbf7f11dbdf46", "--akamai-edgerc-path=/home/test/.edgerc", "--akamai-edgerc-section=default", - "--infoblox-grid-host=127.0.0.1", - "--infoblox-wapi-port=8443", - "--infoblox-wapi-username=infoblox", - "--infoblox-wapi-password=infoblox", - "--infoblox-wapi-version=2.6.1", - "--infoblox-view=internal", - "--infoblox-max-results=2000", "--inmemory-zone=example.org", "--inmemory-zone=company.com", "--ovh-endpoint=ovh-ca", @@ -340,7 +317,6 @@ func TestParseFlags(t *testing.T) { "--tls-ca=/path/to/ca.crt", "--tls-client-cert=/path/to/cert.pem", "--tls-client-cert-key=/path/to/key.pem", - "--no-infoblox-ssl-verify", "--domain-filter=example.org", "--domain-filter=company.com", "--exclude-domains=xapi.example.org", @@ -451,14 +427,6 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_AKAMAI_ACCESS_TOKEN": "o184671d5307a388180fbf7f11dbdf46", "EXTERNAL_DNS_AKAMAI_EDGERC_PATH": "/home/test/.edgerc", "EXTERNAL_DNS_AKAMAI_EDGERC_SECTION": "default", - "EXTERNAL_DNS_INFOBLOX_GRID_HOST": "127.0.0.1", - "EXTERNAL_DNS_INFOBLOX_WAPI_PORT": "8443", - "EXTERNAL_DNS_INFOBLOX_WAPI_USERNAME": "infoblox", - "EXTERNAL_DNS_INFOBLOX_WAPI_PASSWORD": "infoblox", - "EXTERNAL_DNS_INFOBLOX_WAPI_VERSION": "2.6.1", - "EXTERNAL_DNS_INFOBLOX_VIEW": "internal", - "EXTERNAL_DNS_INFOBLOX_SSL_VERIFY": "0", - "EXTERNAL_DNS_INFOBLOX_MAX_RESULTS": "2000", "EXTERNAL_DNS_OCI_CONFIG_FILE": "oci.yaml", "EXTERNAL_DNS_OCI_ZONE_SCOPE": "PRIVATE", "EXTERNAL_DNS_OCI_ZONES_CACHE_DURATION": "30s", @@ -564,7 +532,6 @@ func restoreEnv(t *testing.T, originalEnv map[string]string) { func TestPasswordsNotLogged(t *testing.T) { cfg := Config{ DynPassword: "dyn-pass", - InfobloxWapiPassword: "infoblox-pass", PDNSAPIKey: "pdns-api-key", RFC2136TSIGSecret: "tsig-secret", } @@ -572,7 +539,6 @@ func TestPasswordsNotLogged(t *testing.T) { s := cfg.String() assert.False(t, strings.Contains(s, "dyn-pass")) - assert.False(t, strings.Contains(s, "infoblox-pass")) assert.False(t, strings.Contains(s, "pdns-api-key")) assert.False(t, strings.Contains(s, "tsig-secret")) } diff --git a/pkg/apis/externaldns/validation/validation.go b/pkg/apis/externaldns/validation/validation.go index c5ebc38451..6af44ab125 100644 --- a/pkg/apis/externaldns/validation/validation.go +++ b/pkg/apis/externaldns/validation/validation.go @@ -61,16 +61,6 @@ func ValidateConfig(cfg *externaldns.Config) error { } } - // Infoblox provider specific validations - if cfg.Provider == "infoblox" { - if cfg.InfobloxGridHost == "" { - return errors.New("no Infoblox Grid Manager host specified") - } - if cfg.InfobloxWapiPassword == "" { - return errors.New("no Infoblox WAPI password specified") - } - } - if cfg.Provider == "dyn" { if cfg.DynUsername == "" { return errors.New("no Dyn username specified") diff --git a/provider/infoblox/infoblox.go b/provider/infoblox/infoblox.go deleted file mode 100644 index 841cca9ae6..0000000000 --- a/provider/infoblox/infoblox.go +++ /dev/null @@ -1,744 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package infoblox - -import ( - "context" - "fmt" - "net" - "net/http" - "os" - "sort" - "strconv" - "strings" - - ibclient "github.com/infobloxopen/infoblox-go-client/v2" - "github.com/sirupsen/logrus" - - "sigs.k8s.io/external-dns/endpoint" - "sigs.k8s.io/external-dns/pkg/rfc2317" - "sigs.k8s.io/external-dns/plan" - "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" -) - -func isNotFoundError(err error) bool { - _, ok := err.(*ibclient.NotFoundError) - return ok -} - -// StartupConfig clarifies the method signature -type StartupConfig struct { - DomainFilter endpoint.DomainFilter - ZoneIDFilter provider.ZoneIDFilter - Host string - Port int - Username string - Password string - Version string - SSLVerify bool - DryRun bool - View string - MaxResults int - FQDNRegEx string - NameRegEx string - CreatePTR bool - CacheDuration int -} - -// ProviderConfig implements the DNS provider for Infoblox. -type ProviderConfig struct { - provider.BaseProvider - client ibclient.IBConnector - domainFilter endpoint.DomainFilter - zoneIDFilter provider.ZoneIDFilter - view string - dryRun bool - fqdnRegEx string - createPTR bool - cacheDuration int -} - -type infobloxRecordSet struct { - obj ibclient.IBObject - res interface{} -} - -// ExtendedRequestBuilder implements a HttpRequestBuilder which sets -// additional query parameter on all get requests -type ExtendedRequestBuilder struct { - fqdnRegEx string - nameRegEx string - maxResults int - ibclient.WapiRequestBuilder -} - -// NewExtendedRequestBuilder returns a ExtendedRequestBuilder which adds -// _max_results query parameter to all GET requests -func NewExtendedRequestBuilder(maxResults int, fqdnRegEx string, nameRegEx string) *ExtendedRequestBuilder { - return &ExtendedRequestBuilder{ - fqdnRegEx: fqdnRegEx, - nameRegEx: nameRegEx, - maxResults: maxResults, - } -} - -// BuildRequest prepares the api request. it uses BuildRequest of -// WapiRequestBuilder and then add the _max_requests parameter -func (mrb *ExtendedRequestBuilder) BuildRequest(t ibclient.RequestType, obj ibclient.IBObject, ref string, queryParams *ibclient.QueryParams) (req *http.Request, err error) { - req, err = mrb.WapiRequestBuilder.BuildRequest(t, obj, ref, queryParams) - if req.Method == "GET" { - query := req.URL.Query() - if mrb.maxResults > 0 { - query.Set("_max_results", strconv.Itoa(mrb.maxResults)) - } - _, zoneAuthQuery := obj.(*ibclient.ZoneAuth) - if zoneAuthQuery && t == ibclient.GET && mrb.fqdnRegEx != "" { - query.Set("fqdn~", mrb.fqdnRegEx) - } - - // if we are not doing a ZoneAuth query, support the name filter - if !zoneAuthQuery && mrb.nameRegEx != "" { - query.Set("name~", mrb.nameRegEx) - } - - req.URL.RawQuery = query.Encode() - } - return -} - -// NewInfobloxProvider creates a new Infoblox provider. -func NewInfobloxProvider(ibStartupCfg StartupConfig) (*ProviderConfig, error) { - hostCfg := ibclient.HostConfig{ - Host: ibStartupCfg.Host, - Port: strconv.Itoa(ibStartupCfg.Port), - Version: ibStartupCfg.Version, - } - - authCfg := ibclient.AuthConfig{ - Username: ibStartupCfg.Username, - Password: ibStartupCfg.Password, - } - - httpPoolConnections := lookupEnvAtoi("EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS", 10) - httpRequestTimeout := lookupEnvAtoi("EXTERNAL_DNS_INFOBLOX_HTTP_REQUEST_TIMEOUT", 60) - - transportConfig := ibclient.NewTransportConfig( - strconv.FormatBool(ibStartupCfg.SSLVerify), - httpRequestTimeout, - httpPoolConnections, - ) - - var ( - requestBuilder ibclient.HttpRequestBuilder - err error - ) - if ibStartupCfg.MaxResults != 0 || ibStartupCfg.FQDNRegEx != "" || ibStartupCfg.NameRegEx != "" { - // use our own HttpRequestBuilder which sets _max_results parameter on GET requests - requestBuilder = NewExtendedRequestBuilder(ibStartupCfg.MaxResults, ibStartupCfg.FQDNRegEx, ibStartupCfg.NameRegEx) - } else { - // use the default HttpRequestBuilder of the infoblox client - requestBuilder, err = ibclient.NewWapiRequestBuilder(hostCfg, authCfg) - if err != nil { - return nil, err - } - } - - requestor := &ibclient.WapiHttpRequestor{} - - client, err := ibclient.NewConnector(hostCfg, authCfg, transportConfig, requestBuilder, requestor) - if err != nil { - return nil, err - } - - providerCfg := &ProviderConfig{ - client: client, - domainFilter: ibStartupCfg.DomainFilter, - zoneIDFilter: ibStartupCfg.ZoneIDFilter, - dryRun: ibStartupCfg.DryRun, - view: ibStartupCfg.View, - fqdnRegEx: ibStartupCfg.FQDNRegEx, - createPTR: ibStartupCfg.CreatePTR, - cacheDuration: ibStartupCfg.CacheDuration, - } - - return providerCfg, nil -} - -func recordQueryParams(zone string, view string) *ibclient.QueryParams { - searchFields := map[string]string{} - if zone != "" { - searchFields["zone"] = zone - } - - if view != "" { - searchFields["view"] = view - } - return ibclient.NewQueryParams(false, searchFields) -} - -// Records gets the current records. -func (p *ProviderConfig) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, err error) { - zones, err := p.zones() - if err != nil { - return nil, fmt.Errorf("could not fetch zones: %w", err) - } - - for _, zone := range zones { - logrus.Debugf("fetch records from zone '%s'", zone.Fqdn) - searchParams := recordQueryParams(zone.Fqdn, p.view) - var resA []ibclient.RecordA - objA := ibclient.NewEmptyRecordA() - err = p.client.GetObject(objA, "", searchParams, &resA) - if err != nil && !isNotFoundError(err) { - return nil, fmt.Errorf("could not fetch A records from zone '%s': %w", zone.Fqdn, err) - } - for _, res := range resA { - // Check if endpoint already exists and add to existing endpoint if it does - foundExisting := false - for _, ep := range endpoints { - if ep.DNSName == *res.Name && ep.RecordType == endpoint.RecordTypeA { - foundExisting = true - duplicateTarget := false - - for _, t := range ep.Targets { - if t == *res.Ipv4Addr { - duplicateTarget = true - break - } - } - - if duplicateTarget { - logrus.Debugf("A duplicate target '%s' found for existing A record '%s'", *res.Ipv4Addr, ep.DNSName) - } else { - logrus.Debugf("Adding target '%s' to existing A record '%s'", *res.Ipv4Addr, *res.Name) - ep.Targets = append(ep.Targets, *res.Ipv4Addr) - } - break - } - } - if !foundExisting { - newEndpoint := endpoint.NewEndpoint(*res.Name, endpoint.RecordTypeA, *res.Ipv4Addr) - if p.createPTR { - newEndpoint.WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") - } - endpoints = append(endpoints, newEndpoint) - } - } - // sort targets so that they are always in same order, as infoblox might return them in different order - for _, ep := range endpoints { - sort.Sort(ep.Targets) - } - - // Include Host records since they should be treated synonymously with A records - var resH []ibclient.HostRecord - objH := ibclient.NewEmptyHostRecord() - err = p.client.GetObject(objH, "", searchParams, &resH) - if err != nil && !isNotFoundError(err) { - return nil, fmt.Errorf("could not fetch host records from zone '%s': %w", zone.Fqdn, err) - } - for _, res := range resH { - for _, ip := range res.Ipv4Addrs { - logrus.Debugf("Record='%s' A(H):'%s'", *res.Name, *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) - } - } - - var resC []ibclient.RecordCNAME - objC := ibclient.NewEmptyRecordCNAME() - err = p.client.GetObject(objC, "", searchParams, &resC) - if err != nil && !isNotFoundError(err) { - return nil, fmt.Errorf("could not fetch CNAME records from zone '%s': %w", zone.Fqdn, err) - } - for _, res := range resC { - logrus.Debugf("Record='%s' CNAME:'%s'", *res.Name, *res.Canonical) - 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 := rfc2317.CidrToInAddr(zone.Fqdn) - if err == nil { - var resP []ibclient.RecordPTR - objP := ibclient.NewEmptyRecordPTR() - err = p.client.GetObject(objP, "", recordQueryParams(arpaZone, p.view), &resP) - if err != nil && !isNotFoundError(err) { - return nil, fmt.Errorf("could not fetch PTR records from zone '%s': %w", zone.Fqdn, err) - } - for _, res := range resP { - endpoints = append(endpoints, endpoint.NewEndpoint(*res.PtrdName, endpoint.RecordTypePTR, *res.Ipv4Addr)) - } - } - } - - var resT []ibclient.RecordTXT - objT := ibclient.NewEmptyRecordTXT() - err = p.client.GetObject(objT, "", searchParams, &resT) - if err != nil && !isNotFoundError(err) { - return nil, fmt.Errorf("could not fetch TXT records from zone '%s': %w", zone.Fqdn, err) - } - for _, res := range resT { - // The Infoblox API strips enclosing double quotes from TXT records lacking whitespace. - // Unhandled, the missing double quotes would break the extractOwnerID method of the registry package. - if _, err := strconv.Unquote(*res.Text); err != nil { - quoted := strconv.Quote(*res.Text) - res.Text = "ed - } - - foundExisting := false - - for _, ep := range endpoints { - if ep.DNSName == *res.Name && ep.RecordType == endpoint.RecordTypeTXT { - foundExisting = true - duplicateTarget := false - - for _, t := range ep.Targets { - if t == *res.Text { - duplicateTarget = true - break - } - } - - if duplicateTarget { - logrus.Debugf("A duplicate target '%s' found for existing TXT record '%s'", *res.Text, ep.DNSName) - } else { - logrus.Debugf("Adding target '%s' to existing TXT record '%s'", *res.Text, *res.Name) - ep.Targets = append(ep.Targets, *res.Text) - } - break - } - } - if !foundExisting { - logrus.Debugf("Record='%s' TXT:'%s'", *res.Name, *res.Text) - newEndpoint := endpoint.NewEndpoint(*res.Name, endpoint.RecordTypeTXT, *res.Text) - endpoints = append(endpoints, newEndpoint) - } - } - } - - // 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 *ProviderConfig) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpoint.Endpoint, error) { - // Update user specified TTL (0 == disabled) - for i := range endpoints { - endpoints[i].RecordTTL = endpoint.TTL(p.cacheDuration) - } - - if !p.createPTR { - return endpoints, nil - } - - // 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, nil -} - -// ApplyChanges applies the given changes. -func (p *ProviderConfig) ApplyChanges(ctx context.Context, changes *plan.Changes) error { - zones, err := p.zones() - if err != nil { - return err - } - - created, deleted := p.mapChanges(zones, changes) - p.deleteRecords(deleted) - p.createRecords(created) - return nil -} - -func (p *ProviderConfig) zones() ([]ibclient.ZoneAuth, error) { - var res, result []ibclient.ZoneAuth - obj := ibclient.NewZoneAuth(ibclient.ZoneAuth{}) - queryParams := recordQueryParams("", p.view) - err := p.client.GetObject(obj, "", queryParams, &res) - if err != nil && !isNotFoundError(err) { - return nil, err - } - - for _, zone := range res { - if !p.domainFilter.Match(zone.Fqdn) { - continue - } - - if !p.zoneIDFilter.Match(zone.Ref) { - continue - } - - result = append(result, zone) - } - - return result, nil -} - -type infobloxChangeMap map[string][]*endpoint.Endpoint - -func (p *ProviderConfig) mapChanges(zones []ibclient.ZoneAuth, changes *plan.Changes) (infobloxChangeMap, infobloxChangeMap) { - created := infobloxChangeMap{} - deleted := infobloxChangeMap{} - - mapChange := func(changeMap infobloxChangeMap, change *endpoint.Endpoint) { - zone := p.findZone(zones, change.DNSName) - if zone == nil { - logrus.Debugf("Ignoring changes to '%s' because a suitable Infoblox DNS zone was not found.", change.DNSName) - return - } - // 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 { - mapChange(deleted, change) - } - for _, change := range changes.UpdateOld { - mapChange(deleted, change) - } - for _, change := range changes.Create { - mapChange(created, change) - } - for _, change := range changes.UpdateNew { - mapChange(created, change) - } - - return created, deleted -} - -func (p *ProviderConfig) findZone(zones []ibclient.ZoneAuth, name string) *ibclient.ZoneAuth { - var result *ibclient.ZoneAuth - - // Go through every zone looking for the longest name (i.e. most specific) as a matching suffix - for idx := range zones { - zone := &zones[idx] - if strings.HasSuffix(name, "."+zone.Fqdn) { - if result == nil || len(zone.Fqdn) > len(result.Fqdn) { - result = zone - } - } else if strings.EqualFold(name, zone.Fqdn) { - if result == nil || len(zone.Fqdn) > len(result.Fqdn) { - result = zone - } - } - } - return result -} - -func (p *ProviderConfig) findReverseZone(zones []ibclient.ZoneAuth, name string) *ibclient.ZoneAuth { - ip := net.ParseIP(name) - networks := map[int]*ibclient.ZoneAuth{} - maxMask := 0 - - for i, zone := range zones { - _, rZoneNet, err := net.ParseCIDR(zone.Fqdn) - if err != nil { - logrus.WithError(err).Debugf("fqdn %s is no cidr", zone.Fqdn) - } else { - if rZoneNet.Contains(ip) { - _, mask := rZoneNet.Mask.Size() - networks[mask] = &zones[i] - if mask > maxMask { - maxMask = mask - } - } - } - } - return networks[maxMask] -} - -func (p *ProviderConfig) recordSet(ep *endpoint.Endpoint, getObject bool, targetIndex int) (recordSet infobloxRecordSet, err error) { - switch ep.RecordType { - case endpoint.RecordTypeA: - var res []ibclient.RecordA - obj := ibclient.NewEmptyRecordA() - obj.Name = &ep.DNSName - obj.Ipv4Addr = &ep.Targets[targetIndex] - obj.View = p.view - if getObject { - queryParams := ibclient.NewQueryParams(false, map[string]string{"name": *obj.Name}) - err = p.client.GetObject(obj, "", queryParams, &res) - if err != nil && !isNotFoundError(err) { - return - } - } - recordSet = infobloxRecordSet{ - obj: obj, - res: &res, - } - case endpoint.RecordTypePTR: - var res []ibclient.RecordPTR - obj := ibclient.NewEmptyRecordPTR() - obj.PtrdName = &ep.DNSName - obj.Ipv4Addr = &ep.Targets[targetIndex] - obj.View = p.view - if getObject { - queryParams := ibclient.NewQueryParams(false, map[string]string{"name": *obj.PtrdName}) - err = p.client.GetObject(obj, "", queryParams, &res) - if err != nil && !isNotFoundError(err) { - return - } - } - recordSet = infobloxRecordSet{ - obj: obj, - res: &res, - } - case endpoint.RecordTypeCNAME: - var res []ibclient.RecordCNAME - obj := ibclient.NewEmptyRecordCNAME() - obj.Name = &ep.DNSName - obj.Canonical = &ep.Targets[0] - obj.View = &p.view - if getObject { - queryParams := ibclient.NewQueryParams(false, map[string]string{"name": *obj.Name}) - err = p.client.GetObject(obj, "", queryParams, &res) - if err != nil && !isNotFoundError(err) { - return - } - } - recordSet = infobloxRecordSet{ - obj: obj, - res: &res, - } - case endpoint.RecordTypeTXT: - var res []ibclient.RecordTXT - // The Infoblox API strips enclosing double quotes from TXT records lacking whitespace. - // Here we reconcile that fact by making this state match that reality. - if target, err2 := strconv.Unquote(ep.Targets[0]); err2 == nil && !strings.Contains(ep.Targets[0], " ") { - ep.Targets = endpoint.Targets{target} - } - obj := ibclient.NewEmptyRecordTXT() - obj.Name = &ep.DNSName - obj.Text = &ep.Targets[0] - obj.View = &p.view - if getObject { - queryParams := ibclient.NewQueryParams(false, map[string]string{"name": *obj.Name}) - err = p.client.GetObject(obj, "", queryParams, &res) - if err != nil && !isNotFoundError(err) { - return - } - } - recordSet = infobloxRecordSet{ - obj: obj, - res: &res, - } - } - return -} - -func (p *ProviderConfig) createRecords(created infobloxChangeMap) { - for zone, endpoints := range created { - for _, ep := range endpoints { - for targetIndex := range ep.Targets { - if p.dryRun { - logrus.Infof( - - "Would create %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", - ep.RecordType, - ep.DNSName, - ep.Targets[targetIndex], - zone, - ) - continue - } - - logrus.Infof( - "Creating %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", - ep.RecordType, - ep.DNSName, - ep.Targets[targetIndex], - zone, - ) - - recordSet, err := p.recordSet(ep, false, targetIndex) - if err != nil && !isNotFoundError(err) { - logrus.Errorf( - "Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v", - ep.RecordType, - ep.DNSName, - ep.Targets[targetIndex], - zone, - err, - ) - continue - } - _, err = p.client.CreateObject(recordSet.obj) - if err != nil { - logrus.Errorf( - "Failed to create %s record named '%s' to '%s' for DNS zone '%s': %v", - ep.RecordType, - ep.DNSName, - ep.Targets[targetIndex], - zone, - err, - ) - } - } - } - } -} - -func (p *ProviderConfig) deleteRecords(deleted infobloxChangeMap) { - // Delete records first - for zone, endpoints := range deleted { - for _, ep := range endpoints { - for targetIndex := range ep.Targets { - recordSet, err := p.recordSet(ep, true, targetIndex) - if err != nil && !isNotFoundError(err) { - logrus.Errorf( - "Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v", - ep.RecordType, - ep.DNSName, - ep.Targets[targetIndex], - zone, - err, - ) - continue - } - switch ep.RecordType { - case endpoint.RecordTypeA: - for _, record := range *recordSet.res.(*[]ibclient.RecordA) { - if p.dryRun { - logrus.Infof("Would delete %s record named '%p' to '%p' for Infoblox DNS zone '%s'.", "A", record.Name, record.Ipv4Addr, record.Zone) - } else { - logrus.Infof("Deleting %s record named '%p' to '%p' for Infoblox DNS zone '%s'.", "A", record.Name, record.Ipv4Addr, record.Zone) - _, err = p.client.DeleteObject(record.Ref) - } - } - case endpoint.RecordTypePTR: - for _, record := range *recordSet.res.(*[]ibclient.RecordPTR) { - if p.dryRun { - logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "PTR", *record.PtrdName, *record.Ipv4Addr, record.Zone) - } else { - logrus.Infof("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "PTR", *record.PtrdName, *record.Ipv4Addr, record.Zone) - _, err = p.client.DeleteObject(record.Ref) - } - } - case endpoint.RecordTypeCNAME: - for _, record := range *recordSet.res.(*[]ibclient.RecordCNAME) { - if p.dryRun { - logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "CNAME", *record.Name, *record.Canonical, record.Zone) - } else { - logrus.Infof("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "CNAME", *record.Name, *record.Canonical, record.Zone) - _, err = p.client.DeleteObject(record.Ref) - } - } - case endpoint.RecordTypeTXT: - for _, record := range *recordSet.res.(*[]ibclient.RecordTXT) { - if p.dryRun { - logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "TXT", *record.Name, *record.Text, record.Zone) - } else { - logrus.Infof("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "TXT", *record.Name, *record.Text, record.Zone) - _, err = p.client.DeleteObject(record.Ref) - } - } - } - if err != nil && !isNotFoundError(err) { - logrus.Errorf( - "Failed to delete %s record named '%s' to '%s' for Infoblox DNS zone '%s': %v", - ep.RecordType, - ep.DNSName, - ep.Targets[targetIndex], - zone, - err, - ) - } - } - } - } -} - -func lookupEnvAtoi(key string, fallback int) (i int) { - val, ok := os.LookupEnv(key) - if !ok { - i = fallback - return - } - i, err := strconv.Atoi(val) - if err != nil { - i = fallback - return - } - return -} diff --git a/provider/infoblox/infoblox_test.go b/provider/infoblox/infoblox_test.go deleted file mode 100644 index b2d0a256e2..0000000000 --- a/provider/infoblox/infoblox_test.go +++ /dev/null @@ -1,939 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package infoblox - -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - "net/http" - "net/url" - "regexp" - "strings" - "testing" - - ibclient "github.com/infobloxopen/infoblox-go-client/v2" - "github.com/miekg/dns" - "github.com/stretchr/testify/assert" - - "sigs.k8s.io/external-dns/endpoint" - "sigs.k8s.io/external-dns/internal/testutils" - "sigs.k8s.io/external-dns/plan" - "sigs.k8s.io/external-dns/provider" -) - -type mockIBConnector struct { - mockInfobloxZones *[]ibclient.ZoneAuth - mockInfobloxObjects *[]ibclient.IBObject - createdEndpoints []*endpoint.Endpoint - deletedEndpoints []*endpoint.Endpoint - updatedEndpoints []*endpoint.Endpoint - getObjectRequests []*getObjectRequest - requestBuilder ExtendedRequestBuilder -} - -type getObjectRequest struct { - obj string - ref string - queryParams string - url url.URL - verified bool -} - -func (req *getObjectRequest) ExpectRequestURLQueryParam(t *testing.T, name string, value string) *getObjectRequest { - if req.url.Query().Get(name) != value { - t.Errorf("Expected GetObject Request URL to contain query parameter %s=%s, Got: %v", name, value, req.url.Query()) - } - - return req -} - -func (req *getObjectRequest) ExpectNotRequestURLQueryParam(t *testing.T, name string) *getObjectRequest { - if req.url.Query().Has(name) { - t.Errorf("Expected GetObject Request URL not to contain query parameter %s, Got: %v", name, req.url.Query()) - } - - return req -} - -func (client *mockIBConnector) verifyGetObjectRequest(t *testing.T, obj string, ref string, query *map[string]string) *getObjectRequest { - qp := "" - if query != nil { - qp = fmt.Sprint(ibclient.NewQueryParams(false, *query)) - } - - for _, req := range client.getObjectRequests { - if !req.verified && req.obj == obj && req.ref == ref && req.queryParams == qp { - req.verified = true - return req - } - } - - t.Errorf("Expected GetObject obj=%s, query=%s, ref=%s", obj, qp, ref) - return &getObjectRequest{} -} - -// verifyNoMoreGetObjectRequests will assert that all "GetObject" calls have been verified. -func (client *mockIBConnector) verifyNoMoreGetObjectRequests(t *testing.T) { - unverified := []getObjectRequest{} - for _, req := range client.getObjectRequests { - if !req.verified { - unverified = append(unverified, *req) - } - } - - if len(unverified) > 0 { - b := new(bytes.Buffer) - for _, req := range unverified { - fmt.Fprintf(b, "obj=%s, ref=%s, params=%s (url=%s)\n", req.obj, req.ref, req.queryParams, req.url.String()) - } - - t.Errorf("Unverified GetObject Requests: %v", unverified) - } -} - -func (client *mockIBConnector) CreateObject(obj ibclient.IBObject) (ref string, err error) { - switch obj.ObjectType() { - case "record:a": - client.createdEndpoints = append( - client.createdEndpoints, - endpoint.NewEndpoint( - *obj.(*ibclient.RecordA).Name, - endpoint.RecordTypeA, - *obj.(*ibclient.RecordA).Ipv4Addr, - ), - ) - ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(*obj.(*ibclient.RecordA).Name)), *obj.(*ibclient.RecordA).Name) - obj.(*ibclient.RecordA).Ref = ref - case "record:cname": - client.createdEndpoints = append( - client.createdEndpoints, - endpoint.NewEndpoint( - *obj.(*ibclient.RecordCNAME).Name, - endpoint.RecordTypeCNAME, - *obj.(*ibclient.RecordCNAME).Canonical, - ), - ) - ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(*obj.(*ibclient.RecordCNAME).Name)), *obj.(*ibclient.RecordCNAME).Name) - obj.(*ibclient.RecordCNAME).Ref = ref - case "record:host": - for _, i := range obj.(*ibclient.HostRecord).Ipv4Addrs { - client.createdEndpoints = append( - client.createdEndpoints, - endpoint.NewEndpoint( - *obj.(*ibclient.HostRecord).Name, - endpoint.RecordTypeA, - *i.Ipv4Addr, - ), - ) - } - ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(*obj.(*ibclient.HostRecord).Name)), *obj.(*ibclient.HostRecord).Name) - obj.(*ibclient.HostRecord).Ref = ref - case "record:txt": - client.createdEndpoints = append( - client.createdEndpoints, - endpoint.NewEndpoint( - *obj.(*ibclient.RecordTXT).Name, - endpoint.RecordTypeTXT, - *obj.(*ibclient.RecordTXT).Text, - ), - ) - 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, - obj, - ) - return ref, nil -} - -func (client *mockIBConnector) GetObject(obj ibclient.IBObject, ref string, queryParams *ibclient.QueryParams, res interface{}) (err error) { - req := getObjectRequest{ - obj: obj.ObjectType(), - ref: ref, - } - if queryParams != nil { - req.queryParams = fmt.Sprint(queryParams) - } - r, _ := client.requestBuilder.BuildRequest(ibclient.GET, obj, ref, queryParams) - if r != nil { - req.url = *r.URL - } - client.getObjectRequests = append(client.getObjectRequests, &req) - switch obj.ObjectType() { - case "record:a": - var result []ibclient.RecordA - for _, object := range *client.mockInfobloxObjects { - if object.ObjectType() == "record:a" { - if ref != "" && - ref != object.(*ibclient.RecordA).Ref { - continue - } - if obj.(*ibclient.RecordA).Name != nil && - *obj.(*ibclient.RecordA).Name != *object.(*ibclient.RecordA).Name { - continue - } - result = append(result, *object.(*ibclient.RecordA)) - } - } - *res.(*[]ibclient.RecordA) = result - case "record:cname": - var result []ibclient.RecordCNAME - for _, object := range *client.mockInfobloxObjects { - if object.ObjectType() == "record:cname" { - if ref != "" && - ref != object.(*ibclient.RecordCNAME).Ref { - continue - } - if obj.(*ibclient.RecordCNAME).Name != nil && - *obj.(*ibclient.RecordCNAME).Name != *object.(*ibclient.RecordCNAME).Name { - continue - } - result = append(result, *object.(*ibclient.RecordCNAME)) - } - } - *res.(*[]ibclient.RecordCNAME) = result - case "record:host": - var result []ibclient.HostRecord - for _, object := range *client.mockInfobloxObjects { - if object.ObjectType() == "record:host" { - if ref != "" && - ref != object.(*ibclient.HostRecord).Ref { - continue - } - if obj.(*ibclient.HostRecord).Name != nil && - *obj.(*ibclient.HostRecord).Name != *object.(*ibclient.HostRecord).Name { - continue - } - result = append(result, *object.(*ibclient.HostRecord)) - } - } - *res.(*[]ibclient.HostRecord) = result - case "record:txt": - var result []ibclient.RecordTXT - for _, object := range *client.mockInfobloxObjects { - if object.ObjectType() == "record:txt" { - if ref != "" && - ref != object.(*ibclient.RecordTXT).Ref { - continue - } - if obj.(*ibclient.RecordTXT).Name != nil && - *obj.(*ibclient.RecordTXT).Name != *object.(*ibclient.RecordTXT).Name { - continue - } - result = append(result, *object.(*ibclient.RecordTXT)) - } - } - *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 != nil && - *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 - } - return -} - -func (client *mockIBConnector) DeleteObject(ref string) (refRes string, err error) { - re := regexp.MustCompile(`([^/]+)/[^:]+:([^/]+)/default`) - result := re.FindStringSubmatch(ref) - - switch result[1] { - case "record:a": - var records []ibclient.RecordA - obj := ibclient.NewEmptyRecordA() - obj.Name = &result[2] - client.GetObject(obj, ref, nil, &records) - for _, record := range records { - client.deletedEndpoints = append( - client.deletedEndpoints, - endpoint.NewEndpoint( - *record.Name, - endpoint.RecordTypeA, - "", - ), - ) - } - case "record:cname": - var records []ibclient.RecordCNAME - obj := ibclient.NewEmptyRecordCNAME() - obj.Name = &result[2] - client.GetObject(obj, ref, nil, &records) - for _, record := range records { - client.deletedEndpoints = append( - client.deletedEndpoints, - endpoint.NewEndpoint( - *record.Name, - endpoint.RecordTypeCNAME, - "", - ), - ) - } - case "record:host": - var records []ibclient.HostRecord - obj := ibclient.NewEmptyHostRecord() - obj.Name = &result[2] - client.GetObject(obj, ref, nil, &records) - for _, record := range records { - client.deletedEndpoints = append( - client.deletedEndpoints, - endpoint.NewEndpoint( - *record.Name, - endpoint.RecordTypeA, - "", - ), - ) - } - case "record:txt": - var records []ibclient.RecordTXT - obj := ibclient.NewEmptyRecordTXT() - obj.Name = &result[2] - client.GetObject(obj, ref, nil, &records) - for _, record := range records { - client.deletedEndpoints = append( - client.deletedEndpoints, - endpoint.NewEndpoint( - *record.Name, - endpoint.RecordTypeTXT, - "", - ), - ) - } - case "record:ptr": - var records []ibclient.RecordPTR - obj := ibclient.NewEmptyRecordPTR() - obj.Name = &result[2] - client.GetObject(obj, ref, nil, &records) - for _, record := range records { - client.deletedEndpoints = append( - client.deletedEndpoints, - endpoint.NewEndpoint( - *record.PtrdName, - endpoint.RecordTypePTR, - "", - ), - ) - } - } - return "", nil -} - -func (client *mockIBConnector) UpdateObject(obj ibclient.IBObject, ref string) (refRes string, err error) { - switch obj.ObjectType() { - case "record:a": - client.updatedEndpoints = append( - client.updatedEndpoints, - endpoint.NewEndpoint( - *obj.(*ibclient.RecordA).Name, - *obj.(*ibclient.RecordA).Ipv4Addr, - endpoint.RecordTypeA, - ), - ) - case "record:cname": - client.updatedEndpoints = append( - client.updatedEndpoints, - endpoint.NewEndpoint( - *obj.(*ibclient.RecordCNAME).Name, - *obj.(*ibclient.RecordCNAME).Canonical, - endpoint.RecordTypeCNAME, - ), - ) - case "record:host": - for _, i := range obj.(*ibclient.HostRecord).Ipv4Addrs { - client.updatedEndpoints = append( - client.updatedEndpoints, - endpoint.NewEndpoint( - *obj.(*ibclient.HostRecord).Name, - *i.Ipv4Addr, - endpoint.RecordTypeA, - ), - ) - } - case "record:txt": - client.updatedEndpoints = append( - client.updatedEndpoints, - endpoint.NewEndpoint( - *obj.(*ibclient.RecordTXT).Name, - *obj.(*ibclient.RecordTXT).Text, - endpoint.RecordTypeTXT, - ), - ) - } - return "", nil -} - -func createMockInfobloxZone(fqdn string) ibclient.ZoneAuth { - return ibclient.ZoneAuth{ - Fqdn: fqdn, - } -} - -func createMockInfobloxObject(name, recordType, value string) ibclient.IBObject { - ref := fmt.Sprintf("record:%s/%s:%s/default", strings.ToLower(recordType), base64.StdEncoding.EncodeToString([]byte(name)), name) - switch recordType { - case endpoint.RecordTypeA: - obj := ibclient.NewEmptyRecordA() - obj.Name = &name - obj.Ref = ref - obj.Ipv4Addr = &value - return obj - case endpoint.RecordTypeCNAME: - obj := ibclient.NewEmptyRecordCNAME() - obj.Name = &name - obj.Ref = ref - obj.Canonical = &value - return obj - case endpoint.RecordTypeTXT: - obj := ibclient.NewEmptyRecordTXT() - obj.Name = &name - obj.Ref = ref - obj.Text = &value - return obj - case "HOST": - obj := ibclient.NewEmptyHostRecord() - obj.Name = &name - obj.Ref = ref - obj.Ipv4Addrs = []ibclient.HostRecordIpv4Addr{ - { - Ipv4Addr: &value, - }, - } - return obj - case endpoint.RecordTypePTR: - obj := ibclient.NewEmptyRecordPTR() - obj.PtrdName = &name - obj.Ref = ref - obj.Ipv4Addr = &value - return obj - } - - return nil -} - -func newInfobloxProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, view string, dryRun bool, createPTR bool, client ibclient.IBConnector) *ProviderConfig { - return &ProviderConfig{ - client: client, - domainFilter: domainFilter, - zoneIDFilter: zoneIDFilter, - dryRun: dryRun, - createPTR: createPTR, - view: view, - } -} - -func TestInfobloxRecords(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("nginx.example.com", endpoint.RecordTypeA, "123.123.123.123"), - createMockInfobloxObject("nginx.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), - createMockInfobloxObject("whitespace.example.com", endpoint.RecordTypeA, "123.123.123.124"), - createMockInfobloxObject("whitespace.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=white space"), - createMockInfobloxObject("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), - createMockInfobloxObject("multiple.example.com", endpoint.RecordTypeA, "123.123.123.122"), - createMockInfobloxObject("multiple.example.com", endpoint.RecordTypeA, "123.123.123.121"), - createMockInfobloxObject("multiple.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), - createMockInfobloxObject("existing.example.com", endpoint.RecordTypeA, "124.1.1.1"), - createMockInfobloxObject("existing.example.com", endpoint.RecordTypeA, "124.1.1.2"), - createMockInfobloxObject("existing.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=existing"), - createMockInfobloxObject("host.example.com", "HOST", "125.1.1.1"), - }, - } - - providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), "", true, false, &client) - actual, err := providerCfg.Records(context.Background()) - if err != nil { - t.Fatal(err) - } - expected := []*endpoint.Endpoint{ - endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122"), - endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""), - endpoint.NewEndpoint("nginx.example.com", endpoint.RecordTypeA, "123.123.123.123"), - endpoint.NewEndpoint("nginx.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""), - endpoint.NewEndpoint("whitespace.example.com", endpoint.RecordTypeA, "123.123.123.124"), - endpoint.NewEndpoint("whitespace.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=white space\""), - endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), - endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeA, "123.123.123.122", "123.123.123.121"), - endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""), - endpoint.NewEndpoint("existing.example.com", endpoint.RecordTypeA, "124.1.1.1", "124.1.1.2"), - endpoint.NewEndpoint("existing.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=existing\""), - endpoint.NewEndpoint("host.example.com", endpoint.RecordTypeA, "125.1.1.1"), - } - validateEndpoints(t, actual, expected) - client.verifyGetObjectRequest(t, "zone_auth", "", &map[string]string{}). - ExpectNotRequestURLQueryParam(t, "view"). - ExpectNotRequestURLQueryParam(t, "zone") - client.verifyGetObjectRequest(t, "record:a", "", &map[string]string{"zone": "example.com"}). - ExpectRequestURLQueryParam(t, "zone", "example.com") - client.verifyGetObjectRequest(t, "record:host", "", &map[string]string{"zone": "example.com"}). - ExpectRequestURLQueryParam(t, "zone", "example.com") - client.verifyGetObjectRequest(t, "record:cname", "", &map[string]string{"zone": "example.com"}). - ExpectRequestURLQueryParam(t, "zone", "example.com") - client.verifyGetObjectRequest(t, "record:txt", "", &map[string]string{"zone": "example.com"}). - ExpectRequestURLQueryParam(t, "zone", "example.com") - client.verifyNoMoreGetObjectRequests(t) -} - -func TestInfobloxRecordsWithView(t *testing.T) { - client := mockIBConnector{ - mockInfobloxZones: &[]ibclient.ZoneAuth{ - createMockInfobloxZone("foo.example.com"), - createMockInfobloxZone("bar.example.com"), - }, - mockInfobloxObjects: &[]ibclient.IBObject{ - createMockInfobloxObject("cat.foo.example.com", endpoint.RecordTypeA, "123.123.123.122"), - createMockInfobloxObject("dog.bar.example.com", endpoint.RecordTypeA, "123.123.123.123"), - }, - } - - providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"foo.example.com", "bar.example.com"}), provider.NewZoneIDFilter([]string{""}), "Inside", true, false, &client) - actual, err := providerCfg.Records(context.Background()) - if err != nil { - t.Fatal(err) - } - expected := []*endpoint.Endpoint{ - endpoint.NewEndpoint("cat.foo.example.com", endpoint.RecordTypeA, "123.123.123.122"), - endpoint.NewEndpoint("dog.bar.example.com", endpoint.RecordTypeA, "123.123.123.123"), - } - validateEndpoints(t, actual, expected) - client.verifyGetObjectRequest(t, "zone_auth", "", &map[string]string{"view": "Inside"}). - ExpectRequestURLQueryParam(t, "view", "Inside"). - ExpectNotRequestURLQueryParam(t, "zone") - client.verifyGetObjectRequest(t, "record:a", "", &map[string]string{"zone": "foo.example.com", "view": "Inside"}). - ExpectRequestURLQueryParam(t, "zone", "foo.example.com"). - ExpectRequestURLQueryParam(t, "view", "Inside") - client.verifyGetObjectRequest(t, "record:host", "", &map[string]string{"zone": "foo.example.com", "view": "Inside"}). - ExpectRequestURLQueryParam(t, "zone", "foo.example.com"). - ExpectRequestURLQueryParam(t, "view", "Inside") - client.verifyGetObjectRequest(t, "record:cname", "", &map[string]string{"zone": "foo.example.com", "view": "Inside"}). - ExpectRequestURLQueryParam(t, "zone", "foo.example.com"). - ExpectRequestURLQueryParam(t, "view", "Inside") - client.verifyGetObjectRequest(t, "record:txt", "", &map[string]string{"zone": "foo.example.com", "view": "Inside"}). - ExpectRequestURLQueryParam(t, "zone", "foo.example.com"). - ExpectRequestURLQueryParam(t, "view", "Inside") - client.verifyGetObjectRequest(t, "record:a", "", &map[string]string{"zone": "bar.example.com", "view": "Inside"}). - ExpectRequestURLQueryParam(t, "zone", "bar.example.com"). - ExpectRequestURLQueryParam(t, "view", "Inside") - client.verifyGetObjectRequest(t, "record:host", "", &map[string]string{"zone": "bar.example.com", "view": "Inside"}). - ExpectRequestURLQueryParam(t, "zone", "bar.example.com"). - ExpectRequestURLQueryParam(t, "view", "Inside") - client.verifyGetObjectRequest(t, "record:cname", "", &map[string]string{"zone": "bar.example.com", "view": "Inside"}). - ExpectRequestURLQueryParam(t, "zone", "bar.example.com"). - ExpectRequestURLQueryParam(t, "view", "Inside") - client.verifyGetObjectRequest(t, "record:txt", "", &map[string]string{"zone": "bar.example.com", "view": "Inside"}). - ExpectRequestURLQueryParam(t, "zone", "bar.example.com"). - ExpectRequestURLQueryParam(t, "view", "Inside") - client.verifyNoMoreGetObjectRequests(t) -} - -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"), - }, - } - - providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), "", true, true, &client) - actual, err := providerCfg.Records(context.Background()) - if err != nil { - t.Fatal(err) - } - providerCfg.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"), - }, - } - - providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"10.0.0.0/24"}), provider.NewZoneIDFilter([]string{""}), "", true, true, &client) - actual, err := providerCfg.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, false, &client) - - validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{ - endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "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.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("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, ""), - }) - - validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{}) -} - -func TestInfobloxApplyChangesDryRun(t *testing.T) { - client := mockIBConnector{ - mockInfobloxObjects: &[]ibclient.IBObject{}, - } - - testInfobloxApplyChangesInternal(t, true, false, &client) - - validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{}) - - validateEndpoints(t, client.deletedEndpoints, []*endpoint.Endpoint{}) - - validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{}) -} - -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"), - } - - providerCfg := newInfobloxProvider( - endpoint.NewDomainFilter([]string{""}), - provider.NewZoneIDFilter([]string{""}), - "", - dryRun, - createPTR, - client, - ) - - createRecords := []*endpoint.Endpoint{ - endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "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.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("nope.com", endpoint.RecordTypeA, "4.4.4.4"), - endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"), - 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"), - } - - updateOldRecords := []*endpoint.Endpoint{ - endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"), - endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"), - endpoint.NewEndpoint("old.nope.com", endpoint.RecordTypeA, "121.212.121.212"), - } - - updateNewRecords := []*endpoint.Endpoint{ - endpoint.NewEndpoint("new.example.com", endpoint.RecordTypeA, "111.222.111.222"), - endpoint.NewEndpoint("newcname.example.com", endpoint.RecordTypeCNAME, "other.com"), - endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeA, "222.111.222.111"), - } - - deleteRecords := []*endpoint.Endpoint{ - endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, "121.212.121.212"), - 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, - UpdateOld: updateOldRecords, - Delete: deleteRecords, - } - - if err := providerCfg.ApplyChanges(context.Background(), changes); err != nil { - t.Fatal(err) - } -} - -func TestInfobloxZones(t *testing.T) { - client := mockIBConnector{ - mockInfobloxZones: &[]ibclient.ZoneAuth{ - createMockInfobloxZone("example.com"), - createMockInfobloxZone("lvl1-1.example.com"), - createMockInfobloxZone("lvl2-1.lvl1-1.example.com"), - createMockInfobloxZone("1.2.3.0/24"), - }, - mockInfobloxObjects: &[]ibclient.IBObject{}, - } - - providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com", "1.2.3.0/24"}), provider.NewZoneIDFilter([]string{""}), "", true, false, &client) - zones, _ := providerCfg.zones() - var emptyZoneAuth *ibclient.ZoneAuth - assert.Equal(t, providerCfg.findZone(zones, "example.com").Fqdn, "example.com") - assert.Equal(t, providerCfg.findZone(zones, "nomatch-example.com"), emptyZoneAuth) - assert.Equal(t, providerCfg.findZone(zones, "nginx.example.com").Fqdn, "example.com") - assert.Equal(t, providerCfg.findZone(zones, "lvl1-1.example.com").Fqdn, "lvl1-1.example.com") - assert.Equal(t, providerCfg.findZone(zones, "lvl1-2.example.com").Fqdn, "example.com") - assert.Equal(t, providerCfg.findZone(zones, "lvl2-1.lvl1-1.example.com").Fqdn, "lvl2-1.lvl1-1.example.com") - assert.Equal(t, providerCfg.findZone(zones, "lvl2-2.lvl1-1.example.com").Fqdn, "lvl1-1.example.com") - assert.Equal(t, providerCfg.findZone(zones, "lvl2-2.lvl1-2.example.com").Fqdn, "example.com") - assert.Equal(t, providerCfg.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{}, - } - - providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com", "1.2.3.0/24", "10.0.0.0/8"}), provider.NewZoneIDFilter([]string{""}), "", true, false, &client) - zones, _ := providerCfg.zones() - var emptyZoneAuth *ibclient.ZoneAuth - assert.Equal(t, providerCfg.findReverseZone(zones, "nomatch-example.com"), emptyZoneAuth) - assert.Equal(t, providerCfg.findReverseZone(zones, "192.168.0.1"), emptyZoneAuth) - assert.Equal(t, providerCfg.findReverseZone(zones, "1.2.3.4").Fqdn, "1.2.3.0/24") - assert.Equal(t, providerCfg.findReverseZone(zones, "10.28.29.30").Fqdn, "10.0.0.0/8") -} - -func TestExtendedRequestFDQDRegExBuilder(t *testing.T) { - hostCfg := ibclient.HostConfig{ - Host: "localhost", - Port: "8080", - Version: "2.3.1", - } - - authCfg := ibclient.AuthConfig{ - Username: "user", - Password: "abcd", - } - - requestBuilder := NewExtendedRequestBuilder(0, "^staging.*test.com$", "") - requestBuilder.Init(hostCfg, authCfg) - - obj := ibclient.NewZoneAuth(ibclient.ZoneAuth{}) - - req, _ := requestBuilder.BuildRequest(ibclient.GET, obj, "", &ibclient.QueryParams{}) - - assert.True(t, req.URL.Query().Get("fqdn~") == "^staging.*test.com$") - - req, _ = requestBuilder.BuildRequest(ibclient.CREATE, obj, "", &ibclient.QueryParams{}) - - assert.True(t, req.URL.Query().Get("fqdn~") == "") -} - -func TestExtendedRequestNameRegExBuilder(t *testing.T) { - hostCfg := ibclient.HostConfig{ - Host: "localhost", - Port: "8080", - Version: "2.3.1", - } - - authCfg := ibclient.AuthConfig{ - Username: "user", - Password: "abcd", - } - - requestBuilder := NewExtendedRequestBuilder(0, "", "^staging.*test.com$") - requestBuilder.Init(hostCfg, authCfg) - - obj := ibclient.NewEmptyRecordCNAME() - - req, _ := requestBuilder.BuildRequest(ibclient.GET, obj, "", &ibclient.QueryParams{}) - - assert.True(t, req.URL.Query().Get("name~") == "^staging.*test.com$") - - req, _ = requestBuilder.BuildRequest(ibclient.CREATE, obj, "", &ibclient.QueryParams{}) - - assert.True(t, req.URL.Query().Get("name~") == "") -} - -func TestExtendedRequestMaxResultsBuilder(t *testing.T) { - hostCfg := ibclient.HostConfig{ - Host: "localhost", - Port: "8080", - Version: "2.3.1", - } - - authCfg := ibclient.AuthConfig{ - Username: "user", - Password: "abcd", - } - - requestBuilder := NewExtendedRequestBuilder(54321, "", "") - requestBuilder.Init(hostCfg, authCfg) - - obj := ibclient.NewEmptyRecordCNAME() - obj.Zone = "foo.bar.com" - - req, _ := requestBuilder.BuildRequest(ibclient.GET, obj, "", &ibclient.QueryParams{}) - - assert.True(t, req.URL.Query().Get("_max_results") == "54321") - - req, _ = requestBuilder.BuildRequest(ibclient.CREATE, obj, "", &ibclient.QueryParams{}) - - assert.True(t, req.URL.Query().Get("_max_results") == "") -} - -func TestGetObject(t *testing.T) { - hostCfg := ibclient.HostConfig{} - authCfg := ibclient.AuthConfig{} - transportConfig := ibclient.TransportConfig{} - requestBuilder := NewExtendedRequestBuilder(1000, "mysite.com", "") - requestor := mockRequestor{} - client, _ := ibclient.NewConnector(hostCfg, authCfg, transportConfig, requestBuilder, &requestor) - - providerConfig := newInfobloxProvider(endpoint.NewDomainFilter([]string{"mysite.com"}), provider.NewZoneIDFilter([]string{""}), "", true, true, client) - - providerConfig.deleteRecords(infobloxChangeMap{ - "myzone.com": []*endpoint.Endpoint{ - endpoint.NewEndpoint("deletethisrecord.com", endpoint.RecordTypeA, "1.2.3.4"), - }, - }) - - requestQuery := requestor.request.URL.Query() - assert.True(t, requestQuery.Has("name"), "Expected the request to filter objects by name") -} - -// Mock requestor that doesn't send request -type mockRequestor struct { - request *http.Request -} - -func (r *mockRequestor) Init(ibclient.AuthConfig, ibclient.TransportConfig) {} -func (r *mockRequestor) SendRequest(req *http.Request) (res []byte, err error) { - res = []byte("[{}]") - r.request = req - return -} - -func validateEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) { - assert.True(t, testutils.SameEndpoints(endpoints, expected), "actual and expected endpoints don't match. %s:%s", endpoints, expected) -}