diff --git a/.github/labeler.yml b/.github/labeler.yml index e0dc68118..f831d8422 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -65,5 +65,5 @@ provider/vinyldns: provider/vinyldns* # Add 'provider/vultr' in file which starts with vultr provider/vultr: provider/vultr* -# Add 'provider/ultradns' in file which starts with vultr -provider/vultr: provider/ultradns* +# Add 'provider/ultradns' in file which starts with ultradns +provider/ultradns: provider/ultradns* diff --git a/docs/tutorials/ultradns.md b/docs/tutorials/ultradns.md new file mode 100644 index 000000000..86d39d9ef --- /dev/null +++ b/docs/tutorials/ultradns.md @@ -0,0 +1,619 @@ +# Setting up ExternalDNS for Services on UltraDNS + +This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using UltraDNS. + +For this tutorial, please make sure that you are using a version **> 0.7.2** of ExternalDNS. + +## Managing DNS with UltraDNS + +If you would like to read-up on the UltraDNS service, you can find additional details here: [Introduction to UltraDNS](https://docs.ultradns.neustar) + +Before proceeding, please create a new DNS Zone that you will create your records in for this tutorial process. For the examples in this tutorial, we will be using `example.com` as our Zone. + +## Setting Up UltraDNS Credentials + +The following environment variables will be needed to run ExternalDNS with UltraDNS. + +`ULTRADNS_USERNAME`,`ULTRADNS_PASSWORD`, &`ULTRADNS_BASEURL` +`ULTRADNS_ACCOUNTNAME`(optional variable). + +## Deploying 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. + +- Note: We are assuming the zone is already present within UltraDNS. +- Note: While creating CNAMES as target endpoints, the `--txt-prefix` option is mandatory. +### 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.opensource.zalan.do/teapot/external-dns:latest + args: + - --source=service + - --source=ingress # ingress is also possible + - --domain-filter=example.com # (Recommended) We recommend to use this filter as it minimize the time to propagate changes, as there are less number of zones to look into.. + - --provider=ultradns + - --txt-prefix=txt- + env: + - name: ULTRADNS_USERNAME + value: "" + - name: ULTRADNS_PASSWORD # The password is required to be BASE64 encrypted. + value: "" + - name: ULTRADNS_BASEURL + value: "https://api.ultradns.com/" + - name: ULTRADNS_ACCOUNTNAME + value: "" +``` + +### Manifest (for clusters with RBAC enabled) + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: external-dns +rules: +- apiGroups: [""] + resources: ["services","endpoints","pods"] + verbs: ["get","watch","list"] +- apiGroups: ["extensions"] + resources: ["ingresses"] + verbs: ["get","watch","list"] +- apiGroups: [""] + resources: ["nodes"] + verbs: ["list"] +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +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.opensource.zalan.do/teapot/external-dns:latest + args: + - --source=service + - --source=ingress + - --domain-filter=example.com #(Recommended) We recommend to use this filter as it minimize the time to propagate changes, as there are less number of zones to look into.. + - --provider=ultradns + - --txt-prefix=txt- + env: + - name: ULTRADNS_USERNAME + value: "" + - name: ULTRADNS_PASSWORD # The password is required to be BASE64 encrypted. + value: "" + - name: ULTRADNS_BASEURL + value: "https://api.ultradns.com/" + - name: ULTRADNS_ACCOUNTNAME + value: "" +``` + +## 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: my-app.example.com. +spec: + selector: + app: nginx + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 +``` + +Please note the annotation on the service. Use the same hostname as the UltraDNS zone created above. + +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. + +## Creating the Deployment and Service: + +```console +$ kubectl create -f nginx.yaml +$ kubectl create -f external-dns.yaml +``` + +Depending on where you run your service from, it can take a few minutes for your cloud provider to create an external IP for the service. + +Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and will synchronize the UltraDNS records. + +## Verifying UltraDNS Records + +Please verify on the [UltraDNS UI](https://portal.ultradns.neustar) that the records are created under the zone "example.com". + +For more information on UltraDNS UI, refer to (https://docs.ultradns.neustar/mspuserguide.html). + +Select the zone that was created above (or select the appropriate zone if a different zone was used.) + +The external IP address will be displayed as a CNAME record for your zone. + +## Cleaning Up the Deployment and Service + +Now that we have verified that ExternalDNS will automatically manage your UltraDNS records, you can delete example zones that you created in this tutorial: + +``` +$ kubectl delete service -f nginx.yaml +$ kubectl delete service -f externaldns.yaml +``` +## Creating Multiple A Records Target +- First, you want to create a service file called 'apple-banana-echo.yaml' +```yaml +--- +kind: Pod +apiVersion: v1 +metadata: + name: apple-app + labels: + app: apple +spec: + containers: + - name: apple-app + image: hashicorp/http-echo + args: + - "-text=apple" +--- +kind: Service +apiVersion: v1 +metadata: + name: apple-service +spec: + selector: + app: apple + ports: + - port: 5678 # Default port for image +``` +- Then, create service file called 'expose-apple-banana-app.yaml' to expose the services. For more information to deploy ingress controller, refer to (https://kubernetes.github.io/ingress-nginx/deploy/) +```yaml +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: example-ingress + annotations: + ingress.kubernetes.io/rewrite-target: / + ingress.kubernetes.io/scheme: internet-facing + external-dns.alpha.kubernetes.io/hostname: apple.example.com. + external-dns.alpha.kubernetes.io/target: 10.10.10.1,10.10.10.23 +spec: + rules: + - http: + paths: + - path: /apple + backend: + serviceName: apple-service + servicePort: 5678 +``` +- Then, create the deployment and service: +```console +$ kubectl create -f apple-banana-echo.yaml +$ kubectl create -f expose-apple-banana-app.yaml +$ kubectl create -f external-dns.yaml +``` +- Depending on where you run your service from, it can take a few minutes for your cloud provider to create an external IP for the service. +- Please verify on the [UltraDNS UI](https://portal.ultradns.neustar) that the records have been created under the zone "example.com". +- Finally, you will need to clean up the deployment and service. Please verify on the UI afterwards that the records have been deleted from the zone ‘example.com’: +```console +$ kubectl delete -f apple-banana-echo.yaml +$ kubectl delete -f expose-apple-banana-app.yaml +$ kubectl delete -f external-dns.yaml +``` +## Creating CNAME Record +- Please note, that prior to deploying the external-dns service, you will need to add the option –txt-prefix=txt- into external-dns.yaml. If this not provided, your records will not be created. +- First, create a service file called 'apple-banana-echo.yaml' + - _Config File Example – kubernetes cluster is on-premise not on cloud_ + ```yaml + --- + kind: Pod + apiVersion: v1 + metadata: + name: apple-app + labels: + app: apple + spec: + containers: + - name: apple-app + image: hashicorp/http-echo + args: + - "-text=apple" + --- + kind: Service + apiVersion: v1 + metadata: + name: apple-service + spec: + selector: + app: apple + ports: + - port: 5678 # Default port for image + --- + apiVersion: extensions/v1beta1 + kind: Ingress + metadata: + name: example-ingress + annotations: + ingress.kubernetes.io/rewrite-target: / + ingress.kubernetes.io/scheme: internet-facing + external-dns.alpha.kubernetes.io/hostname: apple.example.com. + external-dns.alpha.kubernetes.io/target: apple.cname.com. + spec: + rules: + - http: + paths: + - path: /apple + backend: + serviceName: apple-service + servicePort: 5678 + ``` + - _Config File Example – Kubernetes cluster service from different cloud vendors_ + ```yaml + --- + kind: Pod + apiVersion: v1 + metadata: + name: apple-app + labels: + app: apple + spec: + containers: + - name: apple-app + image: hashicorp/http-echo + args: + - "-text=apple" + --- + kind: Service + apiVersion: v1 + metadata: + name: apple-service + annotations: + external-dns.alpha.kubernetes.io/hostname: my-app.example.com. + spec: + selector: + app: apple + type: LoadBalancer + ports: + - protocol: TCP + port: 5678 + targetPort: 5678 + ``` +- Then, create the deployment and service: +```console +$ kubectl create -f apple-banana-echo.yaml +$ kubectl create -f external-dns.yaml +``` +- Depending on where you run your service from, it can take a few minutes for your cloud provider to create an external IP for the service. +- Please verify on the [UltraDNS UI](https://portal.ultradns.neustar), that the records have been created under the zone "example.com". +- Finally, you will need to clean up the deployment and service. Please verify on the UI afterwards that the records have been deleted from the zone "example.com": +```console +$ kubectl delete -f apple-banana-echo.yaml +$ kubectl delete -f external-dns.yaml +``` +## Create Multiple Types Of Records +- Please note, that prior to deploying the external-dns service, you will need to add the option –txt-prefix=txt- into external-dns.yaml. Since you will also be created a CNAME record, If this not provided, your records will not be created. +- First, create a service file called 'apple-banana-echo.yaml' + - _Config File Example – kubernetes cluster is on-premise not on cloud_ + ```yaml + --- + kind: Pod + apiVersion: v1 + metadata: + name: apple-app + labels: + app: apple + spec: + containers: + - name: apple-app + image: hashicorp/http-echo + args: + - "-text=apple" + --- + kind: Service + apiVersion: v1 + metadata: + name: apple-service + spec: + selector: + app: apple + ports: + - port: 5678 # Default port for image + --- + kind: Pod + apiVersion: v1 + metadata: + name: apple-app1 + labels: + app: apple1 + spec: + containers: + - name: apple-app1 + image: hashicorp/http-echo + args: + - "-text=apple" + --- + kind: Service + apiVersion: v1 + metadata: + name: apple-service1 + spec: + selector: + app: apple1 + ports: + - port: 5679 # Default port for image + --- + kind: Pod + apiVersion: v1 + metadata: + name: apple-app2 + labels: + app: apple2 + spec: + containers: + - name: apple-app2 + image: hashicorp/http-echo + args: + - "-text=apple" + --- + kind: Service + apiVersion: v1 + metadata: + name: apple-service2 + spec: + selector: + app: apple2 + ports: + - port: 5680 # Default port for image + apiVersion: extensions/v1beta1 + --- + apiVersion: extensions/v1beta1 + kind: Ingress + metadata: + name: example-ingress + annotations: + ingress.kubernetes.io/rewrite-target: / + ingress.kubernetes.io/scheme: internet-facing + external-dns.alpha.kubernetes.io/hostname: apple.example.com. + external-dns.alpha.kubernetes.io/target: apple.cname.com. + spec: + rules: + - http: + paths: + - path: /apple + backend: + serviceName: apple-service + servicePort: 5678 + --- + apiVersion: extensions/v1beta1 + kind: Ingress + metadata: + name: example-ingress1 + annotations: + ingress.kubernetes.io/rewrite-target: / + ingress.kubernetes.io/scheme: internet-facing + external-dns.alpha.kubernetes.io/hostname: apple-banana.example.com. + external-dns.alpha.kubernetes.io/target: 10.10.10.3 + spec: + rules: + - http: + paths: + - path: /apple + backend: + serviceName: apple-service1 + servicePort: 5679 + --- + apiVersion: extensions/v1beta1 + kind: Ingress + metadata: + name: example-ingress2 + annotations: + ingress.kubernetes.io/rewrite-target: / + ingress.kubernetes.io/scheme: internet-facing + external-dns.alpha.kubernetes.io/hostname: banana.example.com. + external-dns.alpha.kubernetes.io/target: 10.10.10.3,10.10.10.20 + spec: + rules: + - http: + paths: + - path: /apple + backend: + serviceName: apple-service2 + servicePort: 5680 + ``` + - _Config File Example – Kubernetes cluster service from different cloud vendors_ + ```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: my-app.example.com. + spec: + selector: + app: nginx + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 + --- + kind: Pod + apiVersion: v1 + metadata: + name: apple-app + labels: + app: apple + spec: + containers: + - name: apple-app + image: hashicorp/http-echo + args: + - "-text=apple" + --- + kind: Service + apiVersion: v1 + metadata: + name: apple-service + spec: + selector: + app: apple + ports: + - port: 5678 # Default port for image + --- + kind: Pod + apiVersion: v1 + metadata: + name: apple-app1 + labels: + app: apple1 + spec: + containers: + - name: apple-app1 + image: hashicorp/http-echo + args: + - "-text=apple" + --- + kind: Service + apiVersion: v1 + metadata: + name: apple-service1 + spec: + selector: + app: apple1 + ports: + - port: 5679 # Default port for image + --- + kind: Ingress + metadata: + name: example-ingress + annotations: + ingress.kubernetes.io/rewrite-target: / + ingress.kubernetes.io/scheme: internet-facing + external-dns.alpha.kubernetes.io/hostname: apple.example.com. + external-dns.alpha.kubernetes.io/target: 10.10.10.3,10.10.10.25 + spec: + rules: + - http: + paths: + - path: /apple + backend: + serviceName: apple-service + servicePort: 5678 + --- + apiVersion: extensions/v1beta1 + kind: Ingress + metadata: + name: example-ingress1 + annotations: + ingress.kubernetes.io/rewrite-target: / + ingress.kubernetes.io/scheme: internet-facing + external-dns.alpha.kubernetes.io/hostname: apple-banana.example.com. + external-dns.alpha.kubernetes.io/target: 10.10.10.3 + spec: + rules: + - http: + paths: + - path: /apple + backend: + serviceName: apple-service1 + servicePort: 5679 + ``` +- Then, create the deployment and service: +```console +$ kubectl create -f apple-banana-echo.yaml +$ kubectl create -f external-dns.yaml +``` +- Depending on where you run your service from, it can take a few minutes for your cloud provider to create an external IP for the service. +-o Please verify on the [UltraDNS UI](https://portal.ultradns.neustar), that the records have been created under the zone "example.com". +- Finally, you will need to clean up the deployment and service. Please verify on the UI afterwards that the records have been deleted from the zone "example.com": +```console +$ kubectl delete -f apple-banana-echo.yaml +$ kubectl delete -f external-dns.yaml``` diff --git a/go.mod b/go.mod index 88d165710..b017db91a 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/alecthomas/colour v0.1.0 // indirect github.com/alecthomas/kingpin v2.2.5+incompatible github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 // indirect + github.com/ultradns/ultradns-sdk-go v1.3.7 github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20180828111155-cad214d7d71f github.com/aws/aws-sdk-go v1.27.4 github.com/cloudflare/cloudflare-go v0.10.1 @@ -28,6 +29,7 @@ require ( github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b // indirect github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f github.com/gophercloud/gophercloud v0.1.0 + github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/heptio/contour v0.15.0 github.com/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65 github.com/linki/instrumented_http v0.2.0 @@ -44,7 +46,7 @@ require ( github.com/prometheus/client_golang v1.0.0 github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0 github.com/sergi/go-diff v1.1.0 // indirect - github.com/sirupsen/logrus v1.4.2 + github.com/sirupsen/logrus v1.6.0 github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/smartystreets/gunit v1.1.1 // indirect github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum index 1215b0df5..7608ffbe3 100644 --- a/go.sum +++ b/go.sum @@ -71,12 +71,10 @@ github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAK github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/ultradns/ultradns-sdk-go v1.3.7 h1:sGeLtpu5atyi4aXEM18aEz0DpTFyhREhCSfkx4RojfU= +github.com/ultradns/ultradns-sdk-go v1.3.7/go.mod h1:vCC5SBZUcMRpcfma80Aw0Xk11WxOgbDA071AUban7ws= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis v0.0.0-20180201100744-9d52b1fc8da9/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20180828111155-cad214d7d71f h1:hinXH9rcBjRoIih5tl4f1BCbNjOmPJ2UnZwcYDhEHR0= @@ -96,8 +94,6 @@ github.com/aws/aws-sdk-go v1.27.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= @@ -105,8 +101,6 @@ github.com/cactus/go-statsd-client v3.1.1+incompatible/go.mod h1:cMRcwZDklk7hXp+ github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY= github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= @@ -194,6 +188,7 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL github.com/facebookgo/stackerr v0.0.0-20150612192056-c2fcf88613f4/go.mod h1:SBHk9aNQtiw4R4bEuzHjVmZikkUKCnO1v3lPQ21HZGk= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99 h1:jmwW6QWvUO2OPe22YfgFvBaaZlSr8Rlrac5lZvG6IdM= github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99/go.mod h1:4mP9w9+vYGw2jUx2+2v03IA+phyQQjNRR4AL3uxlNrs= @@ -206,7 +201,6 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-ini/ini v1.33.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -263,8 +257,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v15.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -345,6 +337,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= @@ -374,8 +368,6 @@ github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwK github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jteeuwen/go-bindata v0.0.0-20180305030458-6025e8de665b/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -394,6 +386,8 @@ github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -511,22 +505,16 @@ github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4 github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= -github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -534,8 +522,6 @@ github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/prom2json v1.1.0/go.mod h1:v7OY1795b9fEUZgq4UU2+15YjRv0LfpxKejIQCy3L7o= github.com/prometheus/prom2json v1.2.1/go.mod h1:yIcXOj/TLPdtZ12qRyhswPnu+02sfDoqatDjj0WGSvo= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -561,6 +547,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -699,8 +687,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -732,8 +718,6 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -764,8 +748,6 @@ golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDq golang.org/x/tools v0.0.0-20190822000311-fc82fb2afd64/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/main.go b/main.go index e8c0ef5d8..0cff80cda 100644 --- a/main.go +++ b/main.go @@ -169,7 +169,7 @@ func main() { p, err = provider.NewVultrProvider(domainFilter, cfg.DryRun) case "ultradns": - p, err = provider.NewUltraDNSProvider(domainFilter, cfg.DryRun) + p, err = provider.NewUltraDNSProvider(domainFilter, cfg.DryRun ) case "cloudflare": p, err = provider.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareZonesPerPage, cfg.CloudflareProxied, cfg.DryRun) diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 7ffb95a17..a79794d06 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -312,7 +312,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter) // Flags related to providers - app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, vultr, ultradns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "vultr") + app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, vultr, ultradns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "vultr", "ultradns") 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) app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter) diff --git a/provider/ultradns.go b/provider/ultradns.go new file mode 100644 index 000000000..fb1846ebb --- /dev/null +++ b/provider/ultradns.go @@ -0,0 +1,511 @@ +/* +Copyright 2020 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 provider + +import ( + "context" + "encoding/base64" + "fmt" + "os" + "strconv" + "strings" + "time" + + udnssdk "github.com/ultradns/ultradns-sdk-go" + log "github.com/sirupsen/logrus" + "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" +) + +const ( + ultradnsDefaultTTL = 198 + ultradnsCreate = "CREATE" + ultradnsDelete = "DELETE" + ultradnsUpdate = "UPDATE" + sbPoolPriority = 1 + sbPoolOrder = "ROUND_ROBIN" + rdPoolOrder = "ROUND_ROBIN" +) + +// global variables +var sbPoolRunProbes = true +var sbPoolActOnProbes = true +var ultradnsPoolType = "rdpool" + +//Setting custom headers for ultradns api calls +var customHeader = []udnssdk.CustomHeader{ + udnssdk.CustomHeader { + Key: "UltraClient", + Value: "kube-client", + }, +} + +type UltraDNSProvider struct { + client udnssdk.Client + + domainFilter endpoint.DomainFilter + DryRun bool + AccountName string +} + +type UltraDNSChanges struct { + Action string + + ResourceRecordSetUltraDNS udnssdk.RRSet +} + +// NewUltraDNSProvider initializes a new UltraDNS DNS based provider +func NewUltraDNSProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*UltraDNSProvider, error) { + Username, ok := os.LookupEnv("ULTRADNS_USERNAME") + udnssdk.SetCustomHeader = customHeader + if !ok { + return nil, fmt.Errorf("no username found") + } + + Base64Password, ok := os.LookupEnv("ULTRADNS_PASSWORD") + if !ok { + return nil, fmt.Errorf("no password found") + } + + // Base64 Standard Decoding + Password, err := base64.StdEncoding.DecodeString(Base64Password) + if err != nil { + fmt.Printf("Error decoding string: %s ", err.Error()) + return nil, err + } + + BaseURL, ok := os.LookupEnv("ULTRADNS_BASEURL") + if !ok { + return nil, fmt.Errorf("no baseurl found") + } + AccountName, ok := os.LookupEnv("ULTRADNS_ACCOUNTNAME") + if !ok { + AccountName = "" + } + + probeValue, ok := os.LookupEnv("ULTRADNS_ENABLE_PROBING") + if ok { + if (probeValue != "true") && (probeValue != "false") { + return nil, fmt.Errorf("please set proper probe value, the values can be either true or false") + } else { + sbPoolRunProbes, _ = strconv.ParseBool(probeValue) + } + } + + actOnProbeValue, ok := os.LookupEnv("ULTRADNS_ENABLE_ACTONPROBE") + if ok { + if (actOnProbeValue != "true") && (actOnProbeValue != "false") { + return nil, fmt.Errorf("please set proper act on probe value, the values can be either true or false") + } else { + sbPoolActOnProbes, _ = strconv.ParseBool(actOnProbeValue) + } + } + + poolValue, ok := os.LookupEnv("ULTRADNS_POOL_TYPE") + if ok { + if (poolValue != "sbpool") && (poolValue != "rdpool") { + return nil, fmt.Errorf(" please set proper ULTRADNS_POOL_TYPE, supported types are sbpool or rdpool") + } + ultradnsPoolType = poolValue + } + + client, err := udnssdk.NewClient(Username, string(Password), BaseURL) + if err != nil { + + return nil, fmt.Errorf("Connection cannot be established") + } + + provider := &UltraDNSProvider{ + client: *client, + domainFilter: domainFilter, + DryRun: dryRun, + AccountName: AccountName, + } + + return provider, nil +} + +// Zones returns list of hosted zones +func (p *UltraDNSProvider) Zones(ctx context.Context) ([]udnssdk.Zone, error) { + zoneKey := &udnssdk.ZoneKey{} + if p.AccountName != "" { + zoneKey = &udnssdk.ZoneKey{ + Zone: "", + AccountName: p.AccountName, + } + } + + zones, err := p.fetchZones(ctx, zoneKey) + if err != nil { + return nil, err + } + + return zones, nil +} + +func (p *UltraDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { + var endpoints []*endpoint.Endpoint + + zones, err := p.Zones(ctx) + if err != nil { + return nil, err + } + + for _, zone := range zones { + log.Infof("zones : %v", zone) + rrsetType := "" + ownerName := "" + rrsetKey := udnssdk.RRSetKey{ + Zone: zone.Properties.Name, + Type: rrsetType, + Name: ownerName, + } + + if zone.Properties.ResourceRecordCount != 0 { + records, err := p.fetchRecords(ctx, rrsetKey) + if err != nil { + return nil, err + } + + for _, r := range records { + recordTypeArray := strings.Fields(r.RRType) + if supportedRecordType(recordTypeArray[0]) { + log.Infof("owner name %s", r.OwnerName) + name := fmt.Sprintf("%s", r.OwnerName) + + // root name is identified by the empty string and should be + // translated to zone name for the endpoint entry. + if r.OwnerName == "" { + name = zone.Properties.Name + } + + endPointTTL := endpoint.NewEndpointWithTTL(name, recordTypeArray[0], endpoint.TTL(r.TTL), r.RData...) + endpoints = append(endpoints, endPointTTL) + } + } + } + + } + log.Infof("endpoints %v", endpoints) + return endpoints, nil +} + +func (p *UltraDNSProvider) fetchRecords(ctx context.Context, k udnssdk.RRSetKey) ([]udnssdk.RRSet, error) { + // TODO: Sane Configuration for timeouts / retries + maxerrs := 5 + waittime := 5 * time.Second + + rrsets := []udnssdk.RRSet{} + errcnt := 0 + offset := 0 + limit := 1000 + + for { + reqRrsets, ri, res, err := p.client.RRSets.SelectWithOffsetWithLimit(k, offset, limit) + if err != nil { + if res != nil && res.StatusCode >= 500 { + errcnt = errcnt + 1 + if errcnt < maxerrs { + time.Sleep(waittime) + continue + } + } + return rrsets, err + } + + for _, rrset := range reqRrsets { + rrsets = append(rrsets, rrset) + } + if ri.ReturnedCount+ri.Offset >= ri.TotalCount { + return rrsets, nil + } + offset = ri.ReturnedCount + ri.Offset + continue + } +} + +func (p *UltraDNSProvider) fetchZones(ctx context.Context, zoneKey *udnssdk.ZoneKey) ([]udnssdk.Zone, error) { + // Select will list the zone rrsets, paginating through all available results + // TODO: Sane Configuration for timeouts / retries + maxerrs := 5 + waittime := 5 * time.Second + + zones := []udnssdk.Zone{} + + errcnt := 0 + offset := 0 + limit := 1000 + + for { + reqZones, ri, res, err := p.client.Zone.SelectWithOffsetWithLimit(zoneKey, offset, limit) + if err != nil { + if res != nil && res.StatusCode >= 500 { + errcnt = errcnt + 1 + if errcnt < maxerrs { + time.Sleep(waittime) + continue + } + } + return zones, err + } + + for _, zone := range reqZones { + + if p.domainFilter.IsConfigured() { + if p.domainFilter.Match(zone.Properties.Name) { + zones = append(zones, zone) + } + } else { + zones = append(zones, zone) + } + } + if ri.ReturnedCount+ri.Offset >= ri.TotalCount { + return zones, nil + } + offset = ri.ReturnedCount + ri.Offset + continue + } +} + +func (p *UltraDNSProvider) submitChanges(ctx context.Context, changes []*UltraDNSChanges) error { + cnameownerName := "cname" + txtownerName := "txt" + if len(changes) == 0 { + log.Infof("All records are already up to date") + return nil + } + + zones, err := p.Zones(ctx) + if err != nil { + return err + } + zoneChanges := seperateChangeByZone(zones, changes) + + for zoneName, changes := range zoneChanges { + + for _, change := range changes { + + if change.ResourceRecordSetUltraDNS.RRType == "CNAME" { + cnameownerName = change.ResourceRecordSetUltraDNS.OwnerName + } else if change.ResourceRecordSetUltraDNS.RRType == "TXT" { + txtownerName = change.ResourceRecordSetUltraDNS.OwnerName + } + + if cnameownerName == txtownerName { + rrsetKey := udnssdk.RRSetKey{ + Zone: zoneName, + Type: endpoint.RecordTypeCNAME, + Name: change.ResourceRecordSetUltraDNS.OwnerName, + } + err := p.getSpecificRecord(ctx, rrsetKey) + if err != nil { + return err + } + if p.DryRun != true { + _, err = p.client.RRSets.Delete(rrsetKey) + if err != nil { + return err + } + } + return fmt.Errorf("The CNAME and TXT Record name cannot be same please recreate external-dns with - --txt-prefix=") + } + rrsetKey := udnssdk.RRSetKey{ + Zone: zoneName, + Type: change.ResourceRecordSetUltraDNS.RRType, + Name: change.ResourceRecordSetUltraDNS.OwnerName, + } + record := udnssdk.RRSet{} + if ((change.ResourceRecordSetUltraDNS.RRType == "A" || change.ResourceRecordSetUltraDNS.RRType == "AAAA" ) && (len(change.ResourceRecordSetUltraDNS.RData) >= 2)) { + if ultradnsPoolType == "sbpool" && change.ResourceRecordSetUltraDNS.RRType == "A" { + sbPoolObject, _ := p.newSBPoolObjectCreation(ctx, change) + record = udnssdk.RRSet{ + RRType: change.ResourceRecordSetUltraDNS.RRType, + OwnerName: change.ResourceRecordSetUltraDNS.OwnerName, + RData: change.ResourceRecordSetUltraDNS.RData, + TTL: change.ResourceRecordSetUltraDNS.TTL, + Profile: sbPoolObject.RawProfile(), + } + } else if ultradnsPoolType == "rdpool" { + rdPoolObject, _ := p.newRDPoolObjectCreation(ctx, change) + record = udnssdk.RRSet{ + RRType: change.ResourceRecordSetUltraDNS.RRType, + OwnerName: change.ResourceRecordSetUltraDNS.OwnerName, + RData: change.ResourceRecordSetUltraDNS.RData, + TTL: change.ResourceRecordSetUltraDNS.TTL, + Profile: rdPoolObject.RawProfile(), + } + }else{ + return fmt.Errorf("We do not support Multiple target AAAA records in SB Pool please contact to Neustar for further details") + } + }else { + record = udnssdk.RRSet{ + RRType: change.ResourceRecordSetUltraDNS.RRType, + OwnerName: change.ResourceRecordSetUltraDNS.OwnerName, + RData: change.ResourceRecordSetUltraDNS.RData, + TTL: change.ResourceRecordSetUltraDNS.TTL, + } + } + + log.WithFields(log.Fields{ + "record": record.OwnerName, + "type": record.RRType, + "ttl": record.TTL, + "action": change.Action, + "zone": zoneName, + "profile": record.Profile, + }).Info("Changing record.") + + switch change.Action { + case ultradnsCreate: + if p.DryRun != true { + res, err := p.client.RRSets.Create(rrsetKey, record) + _ = res + if err != nil { + return err + } + } + + case ultradnsDelete: + err := p.getSpecificRecord(ctx, rrsetKey) + if err != nil { + return err + } + + if p.DryRun != true { + _, err = p.client.RRSets.Delete(rrsetKey) + if err != nil { + return err + } + } + case ultradnsUpdate: + err := p.getSpecificRecord(ctx, rrsetKey) + if err != nil { + return err + } + + if p.DryRun != true { + _, err = p.client.RRSets.Update(rrsetKey, record) + if err != nil { + return err + } + } + } + } + } + + return nil +} + +func (p *UltraDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { + combinedChanges := make([]*UltraDNSChanges, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete)) + log.Infof("value of changes %v,%v,%v", changes.Create, changes.UpdateNew, changes.Delete) + combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsCreate, changes.Create)...) + combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsUpdate, changes.UpdateNew)...) + combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsDelete, changes.Delete)...) + + return p.submitChanges(ctx, combinedChanges) +} + +func newUltraDNSChanges(action string, endpoints []*endpoint.Endpoint) []*UltraDNSChanges { + changes := make([]*UltraDNSChanges, 0, len(endpoints)) + ttl := ultradnsDefaultTTL + for _, e := range endpoints { + + if e.RecordTTL.IsConfigured() { + ttl = int(e.RecordTTL) + } + + // Adding suffix dot to the record name + recordName := fmt.Sprintf("%s.", e.DNSName) + change := &UltraDNSChanges{ + Action: action, + ResourceRecordSetUltraDNS: udnssdk.RRSet{ + RRType: e.RecordType, + OwnerName: recordName, + RData: e.Targets, + TTL: ttl, + }, + } + changes = append(changes, change) + } + return changes +} + +func seperateChangeByZone(zones []udnssdk.Zone, changes []*UltraDNSChanges) map[string][]*UltraDNSChanges { + change := make(map[string][]*UltraDNSChanges) + zoneNameID := zoneIDName{} + for _, z := range zones { + zoneNameID.Add(z.Properties.Name, z.Properties.Name) + change[z.Properties.Name] = []*UltraDNSChanges{} + } + + for _, c := range changes { + zone, _ := zoneNameID.FindZone(c.ResourceRecordSetUltraDNS.OwnerName) + if zone == "" { + log.Infof("Skipping record %s because no hosted zone matching record DNS Name was detected", c.ResourceRecordSetUltraDNS.OwnerName) + continue + } + change[zone] = append(change[zone], c) + + } + return change +} + +func (p *UltraDNSProvider) getSpecificRecord(ctx context.Context, rrsetKey udnssdk.RRSetKey) (err error) { + _, err = p.client.RRSets.Select(rrsetKey) + if err != nil { + return fmt.Errorf("no record was found for %v", rrsetKey) + } else { + return nil + } +} + +// Creation of SBPoolObject +func (p *UltraDNSProvider) newSBPoolObjectCreation(ctx context.Context, change *UltraDNSChanges) (sbPool udnssdk.SBPoolProfile, err error) { + + sbpoolRDataList := []udnssdk.SBRDataInfo{} + for _, _ = range change.ResourceRecordSetUltraDNS.RData { + + rrdataInfo := udnssdk.SBRDataInfo{ + RunProbes: sbPoolRunProbes, + Priority: sbPoolPriority, + State: "NORMAL", + Threshold: 1, + Weight: nil, + } + sbpoolRDataList = append(sbpoolRDataList, rrdataInfo) + } + sbPoolObject := udnssdk.SBPoolProfile{ + Context: udnssdk.SBPoolSchema, + Order: sbPoolOrder, + Description: change.ResourceRecordSetUltraDNS.OwnerName, + MaxActive: len(change.ResourceRecordSetUltraDNS.RData), + MaxServed: len(change.ResourceRecordSetUltraDNS.RData), + RDataInfo: sbpoolRDataList, + RunProbes: sbPoolRunProbes, + ActOnProbes: sbPoolActOnProbes, + } + return sbPoolObject, nil +} + +//Creation of RDPoolObject +func (p *UltraDNSProvider) newRDPoolObjectCreation(ctx context.Context, change *UltraDNSChanges) (rdPool udnssdk.RDPoolProfile, err error) { + + rdPoolObject := udnssdk.RDPoolProfile{ + Context: udnssdk.RDPoolSchema, + Order: rdPoolOrder, + Description: change.ResourceRecordSetUltraDNS.OwnerName, + } + return rdPoolObject, nil +} diff --git a/provider/ultradns_test.go b/provider/ultradns_test.go new file mode 100644 index 000000000..445e87432 --- /dev/null +++ b/provider/ultradns_test.go @@ -0,0 +1,686 @@ +/* +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 provider + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "reflect" + _ "strings" + "testing" + + udnssdk "github.com/ultradns/ultradns-sdk-go" + "github.com/stretchr/testify/assert" + "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" +) + +type mockUltraDNSZone struct { + client *udnssdk.Client +} + +func (m *mockUltraDNSZone) SelectWithOffsetWithLimit(k *udnssdk.ZoneKey, offset int, limit int) (zones []udnssdk.Zone, ResultInfo udnssdk.ResultInfo, resp *http.Response, err error) { + zones = []udnssdk.Zone{} + zone := udnssdk.Zone{} + zoneJson := ` + { + "properties": { + "name":"test-ultradns-provider.com.", + "accountName":"teamrest", + "type":"PRIMARY", + "dnssecStatus":"UNSIGNED", + "status":"ACTIVE", + "owner":"teamrest", + "resourceRecordCount":7, + "lastModifiedDateTime":"" + } + }` + if err := json.Unmarshal([]byte(zoneJson), &zone); err != nil { + log.Fatal(err) + } + + zones = append(zones, zone) + return zones, udnssdk.ResultInfo{}, nil, nil +} + +type mockUltraDNSRecord struct { + client *udnssdk.Client +} + +func (m *mockUltraDNSRecord) Create(k udnssdk.RRSetKey, rrset udnssdk.RRSet) (*http.Response, error) { + return nil, nil +} + +func (m *mockUltraDNSRecord) Select(k udnssdk.RRSetKey) ([]udnssdk.RRSet, error) { + return []udnssdk.RRSet{{ + OwnerName: "test-ultradns-provider.com.", + RRType: endpoint.RecordTypeA, + RData: []string{"1.1.1.1"}, + TTL: 86400, + }}, nil + +} + +func (m *mockUltraDNSRecord) SelectWithOffset(k udnssdk.RRSetKey, offset int) ([]udnssdk.RRSet, udnssdk.ResultInfo, *http.Response, error) { + return nil, udnssdk.ResultInfo{}, nil, nil +} + +func (m *mockUltraDNSRecord) Update(udnssdk.RRSetKey, udnssdk.RRSet) (*http.Response, error) { + return nil, nil +} + +func (m *mockUltraDNSRecord) Delete(k udnssdk.RRSetKey) (*http.Response, error) { + return nil, nil +} + +func (m *mockUltraDNSRecord) SelectWithOffsetWithLimit(k udnssdk.RRSetKey, offset int, limit int) (rrsets []udnssdk.RRSet, ResultInfo udnssdk.ResultInfo, resp *http.Response, err error) { + return []udnssdk.RRSet{{ + OwnerName: "test-ultradns-provider.com.", + RRType: endpoint.RecordTypeA, + RData: []string{"1.1.1.1"}, + TTL: 86400, + }}, udnssdk.ResultInfo{}, nil, nil +} + +// NewUltraDNSProvider Test scenario +func TestNewUltraDNSProvider(t *testing.T) { + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _, err := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.Nil(t,err) + + _ = os.Unsetenv("ULTRADNS_PASSWORD") + _ = os.Unsetenv("ULTRADNS_USERNAME") + _ = os.Unsetenv("ULTRADNS_BASEURL") + _ = os.Unsetenv("ULTRADNS_ACCOUNTNAME") + _, err = NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.NotNilf(t,err,"Expected to fail %s","formatted") +} + +//zones function test scenario +func TestUltraDNSProvider_Zones(t *testing.T) { + mocked := mockUltraDNSZone{} + provider := &UltraDNSProvider{ + client: udnssdk.Client{ + Zone: &mocked, + }, + } + + zoneKey := &udnssdk.ZoneKey{ + Zone: "", + AccountName: "teamrest", + } + + expected, _, _, err := provider.client.Zone.SelectWithOffsetWithLimit(zoneKey, 0, 1000) + assert.Nil(t,err) + zones, err := provider.Zones(context.Background()) + assert.Nil(t,err) + assert.Equal(t,reflect.DeepEqual(expected, zones),true) +} + +//Records function test case +func TestUltraDNSProvider_Records(t *testing.T) { + mocked := mockUltraDNSRecord{} + mockedDomain := mockUltraDNSZone{} + + provider := &UltraDNSProvider{ + client: udnssdk.Client{ + RRSets: &mocked, + Zone: &mockedDomain, + }, + } + rrsetKey := udnssdk.RRSetKey{} + expected, _, _, err := provider.client.RRSets.SelectWithOffsetWithLimit(rrsetKey, 0, 1000) + records, err := provider.Records(context.Background()) + assert.Nil(t,err) + for _, v := range records { + assert.Equal(t, fmt.Sprintf("%s.", v.DNSName), expected[0].OwnerName) + assert.Equal(t, v.RecordType, expected[0].RRType) + assert.Equal(t, int(v.RecordTTL), expected[0].TTL) + } + +} + +//ApplyChanges function testcase +func TestUltraDNSProvider_ApplyChanges(t *testing.T) { + changes := &plan.Changes{} + mocked := mockUltraDNSRecord{nil} + mockedDomain := mockUltraDNSZone{nil} + + provider := &UltraDNSProvider{ + client: udnssdk.Client{ + RRSets: &mocked, + Zone: &mockedDomain, + }, + } + + changes.Create = []*endpoint.Endpoint{ + {DNSName: "test-ultradns-provider.com", Targets: endpoint.Targets{"1.1.1.1"}, RecordType: "A"}, + {DNSName: "ttl.test-ultradns-provider.com", Targets: endpoint.Targets{"1.1.1.1"}, RecordType: "A", RecordTTL: 100}, + } + changes.Create = []*endpoint.Endpoint{{DNSName: "test-ultradns-provider.com", Targets: endpoint.Targets{"1.1.1.2"}, RecordType: "A"}} + changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test-ultradns-provider.com", Targets: endpoint.Targets{"1.1.2.2"}, RecordType: "A", RecordTTL: 100}} + changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test-ultradns-provider.com", Targets: endpoint.Targets{"1.1.2.2", "1.1.2.3", "1.1.2.4"}, RecordType: "A", RecordTTL: 100}} + changes.Delete = []*endpoint.Endpoint{{DNSName: "test-ultradns-provider.com", Targets: endpoint.Targets{"1.1.2.2", "1.1.2.3", "1.1.2.4"}, RecordType: "A", RecordTTL: 100}} + changes.Delete = []*endpoint.Endpoint{{DNSName: "ttl.test-ultradns-provider.com", Targets: endpoint.Targets{"1.1.1.1"}, RecordType: "A", RecordTTL: 100}} + err := provider.ApplyChanges(context.Background(), changes) + assert.Nilf(t,err,"Should not fail %s","formatted") +} + +// Testing function getSpecificRecord +func TestUltraDNSProvider_getSpecificRecord(t *testing.T) { + mocked := mockUltraDNSRecord{nil} + mockedDomain := mockUltraDNSZone{nil} + + provider := &UltraDNSProvider{ + client: udnssdk.Client{ + RRSets: &mocked, + Zone: &mockedDomain, + }, + } + + recordSetKey := udnssdk.RRSetKey{ + Zone: "test-ultradns-provider.com.", + Type: "A", + Name: "teamrest", + } + err := provider.getSpecificRecord(context.Background(), recordSetKey) + assert.Nil(t,err) +} + +//Fail case scenario testing where CNAME and TXT Record name are same +func TestUltraDNSProvider_ApplyChangesCNAME(t *testing.T) { + changes := &plan.Changes{} + mocked := mockUltraDNSRecord{nil} + mockedDomain := mockUltraDNSZone{nil} + + provider := &UltraDNSProvider{ + client: udnssdk.Client{ + RRSets: &mocked, + Zone: &mockedDomain, + }, + } + + changes.Create = []*endpoint.Endpoint{ + {DNSName: "test-ultradns-provider.com", Targets: endpoint.Targets{"1.1.1.1"}, RecordType: "CNAME"}, + {DNSName: "test-ultradns-provider.com", Targets: endpoint.Targets{"1.1.1.1"}, RecordType: "TXT"}, + } + + err := provider.ApplyChanges(context.Background(), changes) + assert.NotNil(t,err) +} + +// This will work if you would set the environment variables such as "ULTRADNS_INTEGRATION" and zone should be avaialble "kubernetes-ultradns-provider-test.com" +func TestUltraDNSProvider_ApplyChanges_Integration(t *testing.T) { + + _, ok := os.LookupEnv("ULTRADNS_INTEGRATION") + if !ok { + log.Printf("Skipping test") + + } else { + + providerUltradns, err := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false) + changes := &plan.Changes{} + changes.Create = []*endpoint.Endpoint{ + {DNSName: "kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.1.1"}, RecordType: "A"}, + {DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, RecordType: "AAAA", RecordTTL: 100}, + } + + err = providerUltradns.ApplyChanges(context.Background(), changes) + assert.Nil(t,err) + + rrsetKey := udnssdk.RRSetKey{ + Zone: "kubernetes-ultradns-provider-test.com.", + Name: "kubernetes-ultradns-provider-test.com.", + Type: "A", + } + + rrsets, _ := providerUltradns.client.RRSets.Select(rrsetKey) + assert.Equal(t, rrsets[0].RData[0], "1.1.1.1") + + rrsetKey = udnssdk.RRSetKey{ + Zone: "kubernetes-ultradns-provider-test.com.", + Name: "ttl.kubernetes-ultradns-provider-test.com.", + Type: "AAAA", + } + + rrsets, _ = providerUltradns.client.RRSets.Select(rrsetKey) + assert.Equal(t, rrsets[0].RData[0], "2001:db8:85a3:0:0:8a2e:370:7334") + + changes = &plan.Changes{} + changes.UpdateNew = []*endpoint.Endpoint{ + {DNSName: "kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.2.2"}, RecordType: "A", RecordTTL: 100}, + {DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"2001:0db8:85a3:0000:0000:8a2e:0370:7335"}, RecordType: "AAAA", RecordTTL: 100}} + err = providerUltradns.ApplyChanges(context.Background(), changes) + assert.Nil(t,err) + + rrsetKey = udnssdk.RRSetKey{ + Zone: "kubernetes-ultradns-provider-test.com.", + Name: "kubernetes-ultradns-provider-test.com.", + Type: "A", + } + + rrsets, _ = providerUltradns.client.RRSets.Select(rrsetKey) + assert.Equal(t, rrsets[0].RData[0], "1.1.2.2") + + rrsetKey = udnssdk.RRSetKey{ + Zone: "kubernetes-ultradns-provider-test.com.", + Name: "ttl.kubernetes-ultradns-provider-test.com.", + Type: "AAAA", + } + + rrsets, _ = providerUltradns.client.RRSets.Select(rrsetKey) + assert.Equal(t, rrsets[0].RData[0], "2001:db8:85a3:0:0:8a2e:370:7335") + + changes = &plan.Changes{} + changes.Delete = []*endpoint.Endpoint{ + {DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"2001:0db8:85a3:0000:0000:8a2e:0370:7335"}, RecordType: "AAAA", RecordTTL: 100}, + {DNSName: "kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.2.2"}, RecordType: "A", RecordTTL: 100}} + + err = providerUltradns.ApplyChanges(context.Background(), changes) + assert.Nil(t,err) + + resp, _ := providerUltradns.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/AAAA/ttl.kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "404 Not Found") + + resp, _ = providerUltradns.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/A/kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "404 Not Found") + + } + +} + +// This will work if you would set the environment variables such as "ULTRADNS_INTEGRATION" and zone should be avaialble "kubernetes-ultradns-provider-test.com" for multiple target +func TestUltraDNSProvider_ApplyChanges_MultipleTarget_integeration(t *testing.T) { + _, ok := os.LookupEnv("ULTRADNS_INTEGRATION") + if !ok { + log.Printf("Skipping test") + + } else { + + provider, err := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false) + changes := &plan.Changes{} + changes.Create = []*endpoint.Endpoint{ + {DNSName: "kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.1.1", "1.1.2.2"}, RecordType: "A"}} + + err = provider.ApplyChanges(context.Background(), changes) + assert.Nil(t,err) + + rrsetKey := udnssdk.RRSetKey{ + Zone: "kubernetes-ultradns-provider-test.com.", + Name: "kubernetes-ultradns-provider-test.com.", + Type: "A", + } + + rrsets, _ := provider.client.RRSets.Select(rrsetKey) + assert.Equal(t, rrsets[0].RData, []string{"1.1.1.1", "1.1.2.2"}) + + changes = &plan.Changes{} + changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.2.2", "192.168.0.24", "1.2.3.4"}, RecordType: "A", RecordTTL: 100}} + + err = provider.ApplyChanges(context.Background(), changes) + assert.Nil(t,err) + + rrsetKey = udnssdk.RRSetKey{ + Zone: "kubernetes-ultradns-provider-test.com.", + Name: "kubernetes-ultradns-provider-test.com.", + Type: "A", + } + + rrsets, _ = provider.client.RRSets.Select(rrsetKey) + assert.Equal(t, rrsets[0].RData, []string{"1.1.2.2", "192.168.0.24", "1.2.3.4"}) + + changes = &plan.Changes{} + changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.2.2"}, RecordType: "A", RecordTTL: 100}} + + err = provider.ApplyChanges(context.Background(), changes) + + assert.Nil(t,err) + + rrsetKey = udnssdk.RRSetKey{ + Zone: "kubernetes-ultradns-provider-test.com.", + Name: "kubernetes-ultradns-provider-test.com.", + Type: "A", + } + + rrsets, _ = provider.client.RRSets.Select(rrsetKey) + assert.Equal(t, rrsets[0].RData, []string{"1.1.2.2"}) + + changes = &plan.Changes{} + changes.Delete = []*endpoint.Endpoint{{DNSName: "kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.2.2", "192.168.0.24"}, RecordType: "A"}} + + err = provider.ApplyChanges(context.Background(), changes) + + assert.Nil(t,err) + + resp, _ := provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/A/kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "404 Not Found") + + } +} + +// Test case to check sbpool creation +func TestUltraDNSProvider_newSBPoolObjectCreation(t *testing.T) { + mocked := mockUltraDNSRecord{nil} + mockedDomain := mockUltraDNSZone{nil} + + provider := &UltraDNSProvider{ + client: udnssdk.Client{ + RRSets: &mocked, + Zone: &mockedDomain, + }, + } + sbpoolRDataList := []udnssdk.SBRDataInfo{} + changes := &plan.Changes{} + changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "kubernetes-ultradns-provider-test.com.", Targets: endpoint.Targets{"1.1.2.2", "192.168.0.24"}, RecordType: "A", RecordTTL: 100}} + changesList := &UltraDNSChanges{ + Action: "UPDATE", + ResourceRecordSetUltraDNS: udnssdk.RRSet{ + RRType: "A", + OwnerName: "kubernetes-ultradns-provider-test.com.", + RData: []string{"1.1.2.2", "192.168.0.24"}, + TTL: 100, + }, + } + + for _, _ = range changesList.ResourceRecordSetUltraDNS.RData { + + rrdataInfo := udnssdk.SBRDataInfo{ + RunProbes: true, + Priority: 1, + State: "NORMAL", + Threshold: 1, + Weight: nil, + } + sbpoolRDataList = append(sbpoolRDataList, rrdataInfo) + } + sbPoolObject := udnssdk.SBPoolProfile{ + Context: udnssdk.SBPoolSchema, + Order: "ROUND_ROBIN", + Description: "kubernetes-ultradns-provider-test.com.", + MaxActive: 2, + MaxServed: 2, + RDataInfo: sbpoolRDataList, + RunProbes: true, + ActOnProbes: true, + } + + actualSBPoolObject, _ := provider.newSBPoolObjectCreation(context.Background(), changesList) + assert.Equal(t, sbPoolObject, actualSBPoolObject) + +} + +//Testcase to check fail scenario for multiple AAAA targets +func TestUltraDNSProvider_MultipleTargetAAAA(t *testing.T) { + _, ok := os.LookupEnv("ULTRADNS_INTEGRATION") + if !ok { + log.Printf("Skipping test") + + } else { + _ = os.Setenv("ULTRADNS_POOL_TYPE","sbpool") + + provider, _ := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false) + changes := &plan.Changes{} + changes.Create = []*endpoint.Endpoint{ + {DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:0db8:85a3:0000:0000:8a2e:0370:7335"}, RecordType: "AAAA", RecordTTL: 100}, + } + err := provider.ApplyChanges(context.Background(), changes) + assert.NotNilf(t,err,"We wanted it to fail since multiple AAAA targets are not allowed %s","formatted") + + resp, _ := provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/AAAA/ttl.kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "404 Not Found") + _ = os.Unsetenv("ULTRADNS_POOL_TYPE") + } +} + +//Testcase to check fail scenario for multiple AAAA targets +func TestUltraDNSProvider_MultipleTargetAAAARDPool(t *testing.T) { + _, ok := os.LookupEnv("ULTRADNS_INTEGRATION") + if !ok { + log.Printf("Skipping test") + + } else { + _ = os.Setenv("ULTRADNS_POOL_TYPE","rdpool") + provider, _ := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false) + changes := &plan.Changes{} + changes.Create = []*endpoint.Endpoint{ + {DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:0db8:85a3:0000:0000:8a2e:0370:7335"}, RecordType: "AAAA", RecordTTL: 100}, + } + err := provider.ApplyChanges(context.Background(), changes) + assert.Nilf(t,err," multiple AAAA targets are allowed when pool is RDPool %s","formatted") + + resp, _ := provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/AAAA/ttl.kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "200 OK") + + changes = &plan.Changes{} + changes.Delete = []*endpoint.Endpoint{{DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"2001:0db8:85a3:0000:0000:8a2e:0370:7334", "2001:0db8:85a3:0000:0000:8a2e:0370:7335"}, RecordType: "AAAA"}} + + err = provider.ApplyChanges(context.Background(), changes) + + assert.Nil(t,err) + + resp, _ = provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/A/kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "404 Not Found") + + } +} + +// Test case to check multiple CNAME targets. +func TestUltraDNSProvider_MultipleTargetCNAME(t *testing.T) { + _, ok := os.LookupEnv("ULTRADNS_INTEGRATION") + if !ok { + log.Printf("Skipping test") + + } else { + provider, err := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false) + changes := &plan.Changes{} + + changes.Create = []*endpoint.Endpoint{ + {DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"nginx.loadbalancer.com.", "nginx1.loadbalancer.com."}, RecordType: "CNAME", RecordTTL: 100}, + } + err = provider.ApplyChanges(context.Background(), changes) + + assert.NotNilf(t,err,"We wanted it to fail since multiple CNAME targets are not allowed %s","formatted") + + + resp, _ := provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/CNAME/kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "404 Not Found") + } +} + +//Testing creation of RD Pool +func TestUltraDNSProvider_newRDPoolObjectCreation(t *testing.T) { + mocked := mockUltraDNSRecord{nil} + mockedDomain := mockUltraDNSZone{nil} + + provider := &UltraDNSProvider{ + client: udnssdk.Client{ + RRSets: &mocked, + Zone: &mockedDomain, + }, + } + changes := &plan.Changes{} + changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "kubernetes-ultradns-provider-test.com.", Targets: endpoint.Targets{"1.1.2.2", "192.168.0.24"}, RecordType: "A", RecordTTL: 100}} + changesList := &UltraDNSChanges{ + Action: "UPDATE", + ResourceRecordSetUltraDNS: udnssdk.RRSet{ + RRType: "A", + OwnerName: "kubernetes-ultradns-provider-test.com.", + RData: []string{"1.1.2.2", "192.168.0.24"}, + TTL: 100, + }, + } + rdPoolObject := udnssdk.RDPoolProfile{ + Context: udnssdk.RDPoolSchema, + Order: "ROUND_ROBIN", + Description: "kubernetes-ultradns-provider-test.com.", + } + + actualRDPoolObject, _ := provider.newRDPoolObjectCreation(context.Background(), changesList) + assert.Equal(t, rdPoolObject, actualRDPoolObject) + +} + +//Testing Failure scenarios over NewUltraDNS Provider +func TestNewUltraDNSProvider_FailCases(t *testing.T) { + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _ = os.Setenv("ULTRADNS_POOL_TYPE", "xyz") + _, err := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.NotNilf(t,err,"Pool Type other than given type not working %s","formatted") + + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _ = os.Setenv("ULTRADNS_ENABLE_PROBING", "adefg") + _, err = NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.NotNilf(t,err,"Probe value other than given values not working %s","formatted") + + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _ = os.Setenv("ULTRADNS_ENABLE_ACTONPROBE", "adefg") + _, err = NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.NotNilf(t,err,"ActOnProbe value other than given values not working %s","formatted") + + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Unsetenv("ULTRADNS_PASSWORD") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _, err = NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.NotNilf(t,err,"Expected to give error if password is not set %s","formatted") + + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "") + _ = os.Unsetenv("ULTRADNS_BASEURL") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _, err = NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.NotNilf(t,err,"Expected to give error if baseurl is not set %s","formatted") + + + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "") + _ = os.Unsetenv("ULTRADNS_ACCOUNTNAME") + _ = os.Unsetenv("ULTRADNS_ENABLE_ACTONPROBE") + _ = os.Unsetenv("ULTRADNS_ENABLE_PROBING") + _ = os.Unsetenv("ULTRADNS_POOL_TYPE") + _, accounterr := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.Nil(t,accounterr) + +} + +// Testing success scenarios for newly introduced environment variables +func TestNewUltraDNSProvider_NewEnvVariableSuccessCases(t *testing.T) { + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _ = os.Setenv("ULTRADNS_POOL_TYPE", "rdpool") + _, err := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.Nilf(t,err,"Pool Type not working in proper scenario %s","formatted") + + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _ = os.Setenv("ULTRADNS_ENABLE_PROBING", "false") + _, err1 := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.Nilf(t,err1,"Probe given value is not working %s","formatted") + + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _ = os.Setenv("ULTRADNS_ENABLE_ACTONPROBE", "true") + _, err2 := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.Nilf(t,err2,"ActOnProbe given value is not working %s","formatted") + + +} + +// Base64 Bad string decoding scenario +func TestNewUltraDNSProvider_Base64DecodeFailcase(t *testing.T) { + + _ = os.Setenv("ULTRADNS_USERNAME", "") + _ = os.Setenv("ULTRADNS_PASSWORD", "12345") + _ = os.Setenv("ULTRADNS_BASEURL", "") + _ = os.Setenv("ULTRADNS_ACCOUNTNAME", "") + _ = os.Setenv("ULTRADNS_ENABLE_ACTONPROBE", "true") + _, err := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"test-ultradns-provider.com"}), true) + assert.NotNilf(t,err,"Base64 decode should fail in this case %s","formatted") + +} + +func TestUltraDNSProvider_PoolConversionCase(t *testing.T){ + + _, ok := os.LookupEnv("ULTRADNS_INTEGRATION") + if !ok { + log.Printf("Skipping test") + + } else { + //Creating SBPool Record + _ = os.Setenv("ULTRADNS_POOL_TYPE","sbpool") + provider, _ := NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false) + changes := &plan.Changes{} + changes.Create = []*endpoint.Endpoint{{DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.1.1", "1.2.3.4"}, RecordType: "A", RecordTTL: 100}} + err := provider.ApplyChanges(context.Background(), changes) + assert.Nilf(t,err," multiple A record creation with SBPool %s","formatted") + + resp, _ := provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/A/ttl.kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "200 OK") + + //Coverting to RD Pool + _ = os.Setenv("ULTRADNS_POOL_TYPE","rdpool") + provider, _ = NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false) + changes = &plan.Changes{} + changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.1.1","1.2.3.5"}, RecordType: "A"}} + err = provider.ApplyChanges(context.Background(), changes) + assert.Nil(t,err) + resp, _ = provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/A/ttl.kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "200 OK") + + + //Coverting back to SB Pool + _ = os.Setenv("ULTRADNS_POOL_TYPE","sbpool") + provider, _ = NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false) + changes = &plan.Changes{} + changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.1.1","1.2.3.4"}, RecordType: "A"}} + err = provider.ApplyChanges(context.Background(), changes) + assert.Nil(t,err) + resp, _ = provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/A/ttl.kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "200 OK") + + //Deleting Record + changes = &plan.Changes{} + changes.Delete = []*endpoint.Endpoint{{DNSName: "ttl.kubernetes-ultradns-provider-test.com", Targets: endpoint.Targets{"1.1.1.1","1.2.3.4"}, RecordType: "A"}} + err = provider.ApplyChanges(context.Background(), changes) + assert.Nil(t,err) + resp, _ = provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/A/kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{}) + assert.Equal(t, resp.Status, "404 Not Found") + } +}