diff --git a/README.md b/README.md index 34d757253..1f2d71882 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ ExternalDNS' current release is `v0.7`. This version allows you to keep selected * [TransIP](https://www.transip.eu/domain-name/) * [VinylDNS](https://www.vinyldns.io) * [OVH](https://www.ovh.com) +* [Scaleway](https://www.scaleway.com) From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.5` (or greater) with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API. @@ -97,6 +98,7 @@ The following table clarifies the current status of the providers according to t | RancherDNS | Alpha | | | Akamai FastDNS | Alpha | | | OVH | Alpha | | +| Scaleway DNS | Alpha | @Sh4d1 | | Vultr | Alpha | | | UltraDNS | Alpha | | @@ -147,6 +149,7 @@ The following tutorials are provided: * [TransIP](docs/tutorials/transip.md) * [VinylDNS](docs/tutorials/vinyldns.md) * [OVH](docs/tutorials/ovh.md) +* [Scaleway](docs/tutorials/scaleway.md) * [Vultr](docs/tutorials/vultr.md) * [UltraDNS](docs/tutorials/ultradns.md) diff --git a/docs/tutorials/scaleway.md b/docs/tutorials/scaleway.md new file mode 100644 index 000000000..7afb7c5ab --- /dev/null +++ b/docs/tutorials/scaleway.md @@ -0,0 +1,209 @@ +# Setting up ExternalDNS for Services on Scaleway + +This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Scaleway DNS. + +Make sure to use **>=0.7.3** version of ExternalDNS for this tutorial. + +**Warning**: Scaleway DNS is currently in Public Beta and may not be suited for production usage. + +## Importing a Domain into Scaleway DNS + +In order to use your domain, you need to import it into Scaleway DNS. If it's not already done, you can follow [this documentation](https://www.scaleway.com/en/docs/scaleway-dns/) + +Once the domain is imported you can either use the root zone, or create a subzone to use. + +In this example we will use `example.com` as an example. + +## Creating Scaleway Credentials + +To use ExternalDNS with Scaleway DNS, you need to create an API token (composed of the Access Key and the Secret Key). +You can either use existing ones or you can create a new token, as explained in [How to generate an API token](https://www.scaleway.com/en/docs/generate-an-api-token/) or directly by going to the [credentials page](https://console.scaleway.com/account/organization/credentials). + +Note that you will also need to the Organization ID, which can be retrieve on the same page. + +Three environment variables are needed to run ExternalDNS with Scaleway DNS: +- `SCW_ACCESS_KEY` which is the Access Key. +- `SCW_SECRET_KEY` which is the Secret Key. +- `SCW_DEFAULT_ORGANIZATION_ID` which is your Organization ID. + +## 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. + +The following example are suited for development. For a production usage, prefer secrets over environment, and use a [tagged release](https://github.com/kubernetes-sigs/external-dns/releases). + +### Manifest (for clusters without RBAC enabled) +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + replicas: 1 + selector: + matchLabels: + app: external-dns + strategy: + type: Recreate + template: + metadata: + labels: + app: external-dns + spec: + containers: + - name: external-dns + image: registry.opensource.zalan.do/teapot/external-dns:latest + args: + - --source=service # ingress is also possible + - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. + - --provider=scaleway + env: + - name: SCW_ACCESS_KEY + value: "" + - name: SCW_SECRET_KEY + value: "" + - name: SCW_DEFAULT_ORGANIZATION_ID + 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: + replicas: 1 + selector: + matchLabels: + app: external-dns + strategy: + type: Recreate + 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 # ingress is also possible + - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. + - --provider=scaleway + env: + - name: SCW_ACCESS_KEY + value: "" + - name: SCW_SECRET_KEY + value: "" + - name: SCW_DEFAULT_ORGANIZATION_ID + 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: + replicas: 1 + 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 +``` + +Note the annotation on the service; use the same hostname as the Scaleway DNS 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. + +Create the deployment and service: + +```console +$ kubectl create -f nginx.yaml +``` + +Depending where you run your service it can take a little while 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 synchronize the Scaleway DNS records. + +## Verifying Scaleway DNS records + +Check your [Scaleway DNS UI](https://console.scaleway.com/domains/external) to view the records for your Scaleway DNS zone. + +Click on 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. + +## Cleanup + +Now that we have verified that ExternalDNS will automatically manage Scaleway DNS records, we can delete the tutorial's example: + +``` +$ kubectl delete service -f nginx.yaml +$ kubectl delete service -f externaldns.yaml +``` diff --git a/go.mod b/go.mod index bef0c8f38..b469728b1 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/projectcontour/contour v1.5.0 github.com/prometheus/client_golang v1.7.1 github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0 + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200623155123-84df6c4b5301 github.com/sirupsen/logrus v1.6.0 github.com/smartystreets/gunit v1.3.4 // indirect github.com/stretchr/testify v1.5.1 diff --git a/go.sum b/go.sum index a6720d155..5f6ca441c 100644 --- a/go.sum +++ b/go.sum @@ -25,7 +25,6 @@ github.com/Azure/go-autorest/autorest v0.11.0/go.mod h1:JFgpikqFJ/MleTTxwepExTKn github.com/Azure/go-autorest/autorest v0.11.4 h1:iWJqGEvip7mjibEqC/srXNdo+4wLEPiwlP/7dZLtoPc= github.com/Azure/go-autorest/autorest v0.11.4/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.9.0 h1:SigMbuFNuKgc1xcGhaeapbh+8fgsu+GxgDRFyg7f5lM= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.2 h1:Aze/GQeAN1RRbGmnUJvUj+tFGBzFdIg3293/A9rbxC4= github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= @@ -38,17 +37,14 @@ github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8K github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.4.0 h1:z20OWOSG5aCye0HEkDp6TPmP17ZcfeMxPi6HnSALa8c= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= @@ -63,8 +59,6 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/ahmetb/gen-crd-api-reference-docs v0.1.5 h1:OU+AFpBEhyclrQGx4I6zpCx5WvXiKqvFeeOASOmhKCY= github.com/ahmetb/gen-crd-api-reference-docs v0.1.5/go.mod h1:P/XzJ+c2+khJKNKABcm2biRwk2QAuwbLf8DlXuaL7WM= github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11 h1:QGjNHMwoPYxE5NpOAc8kpd2KTY293/oFk5BWdjkza+k= github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11/go.mod h1:L+HB2uBoDgi3+r1pJEJcbGwyyHhd2QXaGsKLbDwtm8Q= @@ -85,7 +79,6 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/aliyun/alibaba-cloud-sdk-go v1.61.357 h1:3ynCSeUh9OtJLd/OzLapM1DLDv2g+0yyDdkLqSfZCaQ= github.com/aliyun/alibaba-cloud-sdk-go v1.61.357/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= @@ -97,11 +90,9 @@ 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/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/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/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/cloudflare-go v0.10.1 h1:d2CL6F9k2O0Ux0w27LgogJ5UOzZRj6a/hDPFqPP68d8= github.com/cloudflare/cloudflare-go v0.10.1/go.mod h1:C0Y6eWnTJPMK2ceuOxx2pjh78UUHihcXeTTHb8r7QjU= @@ -152,15 +143,12 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3 github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s= -github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= @@ -262,9 +250,6 @@ github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= -github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -283,13 +268,10 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= -github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -305,7 +287,6 @@ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= @@ -356,8 +337,6 @@ github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0 github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -372,7 +351,6 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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= @@ -388,8 +366,6 @@ github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d/go.mod h1:C github.com/linki/instrumented_http v0.2.0 h1:zLhcB3Q/McQQqml3qd5kzdZ0cGnL3vquPFIW2338f5Y= github.com/linki/instrumented_http v0.2.0/go.mod h1:pjYbItoegfuVi2GUOMhEqzvm/SJKuEL3H0tc8QRLRFk= github.com/linode/linodego v0.19.0 h1:JxYBTxUcXcOlCwLMuugc7Il0RMtJ7riaddqz6gG/ACA= -github.com/linode/linodego v0.19.0 h1:JxYBTxUcXcOlCwLMuugc7Il0RMtJ7riaddqz6gG/ACA= -github.com/linode/linodego v0.19.0/go.mod h1:XOWXRHjqeU2uPS84tKLgfWIfTlv3TYzCS0io4GOQzEI= github.com/linode/linodego v0.19.0/go.mod h1:XOWXRHjqeU2uPS84tKLgfWIfTlv3TYzCS0io4GOQzEI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -440,7 +416,6 @@ github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXW github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= @@ -452,16 +427,10 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae h1:cRqNH6AtRQwEqCpymMkaR2ePp08FBIYLkU7YusJeZJ8= github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae h1:cRqNH6AtRQwEqCpymMkaR2ePp08FBIYLkU7YusJeZJ8= github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae/go.mod h1:l6TGeqJ92DrZBuWMNKcot1iZUHfbYSJyBWHGgg6Dn6s= -github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae/go.mod h1:l6TGeqJ92DrZBuWMNKcot1iZUHfbYSJyBWHGgg6Dn6s= -github.com/openshift/build-machinery-go v0.0.0-20200424080330-082bf86082cc/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc= github.com/openshift/build-machinery-go v0.0.0-20200424080330-082bf86082cc/go.mod h1:1CkcsT3aVebzRBzVTSbiKSkJMsC/CASqxesfqEMfJEc= github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73 h1:JePLt9EpNLF/30KsSsArrzxGWPaUIvYUt8Fwnw9wlgM= -github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73 h1:JePLt9EpNLF/30KsSsArrzxGWPaUIvYUt8Fwnw9wlgM= -github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73/go.mod h1:+66gk3dEqw9e+WoiXjJFzWlS1KGhj9ZRHi/RI/YG/ZM= github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73/go.mod h1:+66gk3dEqw9e+WoiXjJFzWlS1KGhj9ZRHi/RI/YG/ZM= github.com/oracle/oci-go-sdk v21.4.0+incompatible h1:ORX+RXBuG/INBs+rgx6S3qoShEZ5+rwEEyRn2s6bPiw= github.com/oracle/oci-go-sdk v21.4.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= @@ -486,30 +455,23 @@ github.com/projectcontour/contour v1.5.0 h1:4zz4XWKKb1Nk2zXVQ27ZpoTivjG2DQvYLwrS github.com/projectcontour/contour v1.5.0/go.mod h1:y1MEsorL/Q8lBG5BZz8Gzryi9L5ryVALOuHicmAdfW8= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -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.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/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.0.0-20190812154241-14fe0d1b01d4/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.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 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.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -520,17 +482,17 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0 h1:vOcHdR1nu7DO4BAx1rwzdHV7jQTzW3gqcBT5qxHSc6A= github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0/go.mod h1:FeplEtXXejBYC4NPAFTrs5L7KuK+5RL9bf5nB2vZe9o= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200623155123-84df6c4b5301 h1:qj0du14RIOnmePII/eTlw1aHKDYL6zxDIk/Dq7Tef9k= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200623155123-84df6c4b5301/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -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= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= @@ -571,7 +533,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/terra-farm/udnssdk v1.3.5 h1:MNR3adfuuEK/l04+jzo8WW/0fnorY+nW515qb3vEr6I= github.com/terra-farm/udnssdk v1.3.5/go.mod h1:8RnM56yZTR7mYyUIvrDgXzdRaEyFIzqdEi7+um26Sv8= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/transip/gotransip v5.8.2+incompatible h1:aNJhw/w/3QBqFcHAIPz1ytoK5FexeMzbUCGrrhWr3H0= @@ -600,10 +561,7 @@ go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mI go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875 h1:C7kWARE8r64ppRadl40yfNo6pag+G6ocvGU2xZ6yNes= go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= @@ -631,7 +589,6 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -721,11 +678,8 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII= golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -846,15 +800,9 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -istio.io/api v0.0.0-20200529165953-72dad51d4ffc h1:cR9GmbIBAz3FnY3tgs1SRn/uiznhtvG+mZBfD1p2vIA= istio.io/api v0.0.0-20200529165953-72dad51d4ffc h1:cR9GmbIBAz3FnY3tgs1SRn/uiznhtvG+mZBfD1p2vIA= istio.io/api v0.0.0-20200529165953-72dad51d4ffc/go.mod h1:kyq3g5w42zl/AKlbzDGppYpGMQYMYMyZKeq0/eexML8= -istio.io/api v0.0.0-20200529165953-72dad51d4ffc/go.mod h1:kyq3g5w42zl/AKlbzDGppYpGMQYMYMyZKeq0/eexML8= istio.io/client-go v0.0.0-20200529172309-31c16ea3f751 h1:yH62fTmV+5l1XVTWcomsc1jjH/oH9u/tTgn5NVmdIac= -istio.io/client-go v0.0.0-20200529172309-31c16ea3f751 h1:yH62fTmV+5l1XVTWcomsc1jjH/oH9u/tTgn5NVmdIac= -istio.io/client-go v0.0.0-20200529172309-31c16ea3f751/go.mod h1:4SGvmmus5HNFdqQsIL+uQO1PbAhjQKtSjMTqwsvYHlg= istio.io/client-go v0.0.0-20200529172309-31c16ea3f751/go.mod h1:4SGvmmus5HNFdqQsIL+uQO1PbAhjQKtSjMTqwsvYHlg= istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a h1:w7zILua2dnYo9CxImhpNW4NE/8ZxEoc/wfBfHrhUhrE= istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a/go.mod h1:OzpAts7jljZceG4Vqi5/zXy/pOg1b209T3jb7Nv5wIs= @@ -866,7 +814,6 @@ k8s.io/api v0.18.3 h1:2AJaUQdgUZLoDZHrun21PW2Nx9+ll6cUzvn3IKhSIn0= k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY= k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8= -k8s.io/apiextensions-apiserver v0.17.0/go.mod h1:XiIFUakZywkUl54fVXa7QTEHcqQz9HG55nHd1DCoHj8= k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.18.1/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= @@ -875,41 +822,25 @@ k8s.io/apimachinery v0.18.3 h1:pOGcbVAhxADgUYnjS08EFXs9QMl8qaH5U4fr5LGUrSk= k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg= k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= -k8s.io/apiserver v0.17.0/go.mod h1:ABM+9x/prjINN6iiffRVNCBR2Wk7uY4z+EtEGZD48cg= k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk= -k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk= -k8s.io/client-go v0.0.0-20190918160344-1fbdaa4c8d90/go.mod h1:J69/JveO6XESwVgG53q3Uz5OSfgsv4uxpScmmyYOOlk= -k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= -k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= k8s.io/client-go v0.18.1/go.mod h1:iCikYRiXOj/yRRFE/aWqrpPtDt4P2JVWhtHkmESTcfY= -k8s.io/client-go v0.18.1/go.mod h1:iCikYRiXOj/yRRFE/aWqrpPtDt4P2JVWhtHkmESTcfY= -k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= k8s.io/client-go v0.18.3 h1:QaJzz92tsN67oorwzmoB0a9r9ZVHuD5ryjbCKP0U22k= -k8s.io/client-go v0.18.3 h1:QaJzz92tsN67oorwzmoB0a9r9ZVHuD5ryjbCKP0U22k= k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= -k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= -k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269 h1:d8Fm55A+7HOczX58+x9x+nJnJ1Devt1aCrWVIPaw/Vg= k8s.io/code-generator v0.0.0-20190912054826-cd179ad6a269/go.mod h1:V5BD6M4CyaN5m+VthcclXWsVcT1Hu+glwa1bi3MIsyE= k8s.io/code-generator v0.17.0/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s= k8s.io/code-generator v0.18.3/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= k8s.io/component-base v0.0.0-20190918160511-547f6c5d7090/go.mod h1:933PBGtQFJky3TEwYx4aEPZ4IxqhWh3R6DCmzqIn1hA= k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= -k8s.io/component-base v0.17.0/go.mod h1:rKuRAokNMY2nn2A6LP/MiwpoaMRHpfRnrPaUJJj1Yoc= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20191120174120-e74f70b9b27e/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJDVK9qPLhM+sRHYanNKw0EQ= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= diff --git a/main.go b/main.go index 60bf72977..b6e92118c 100644 --- a/main.go +++ b/main.go @@ -58,6 +58,7 @@ import ( "sigs.k8s.io/external-dns/provider/rcode0" "sigs.k8s.io/external-dns/provider/rdns" "sigs.k8s.io/external-dns/provider/rfc2136" + "sigs.k8s.io/external-dns/provider/scaleway" "sigs.k8s.io/external-dns/provider/transip" "sigs.k8s.io/external-dns/provider/ultradns" "sigs.k8s.io/external-dns/provider/vinyldns" @@ -289,6 +290,8 @@ func main() { ) case "transip": p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun) + case "scaleway": + p, err = scaleway.NewScalewayProvider(ctx, domainFilter, cfg.DryRun) default: log.Fatalf("unknown dns provider: %s", cfg.Provider) } diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index be557ef1e..9ef1840af 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -317,7 +317,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, hetzner, 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", "hetzner", "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("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, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "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", "scaleway", "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/scaleway/interface.go b/provider/scaleway/interface.go new file mode 100644 index 000000000..a8a5d17cd --- /dev/null +++ b/provider/scaleway/interface.go @@ -0,0 +1,53 @@ +package scaleway + +import ( + domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2alpha2" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +// DomainAPI is an interface matching the domain.API struct +type DomainAPI interface { + ListTasks(req *domain.ListTasksRequest, opts ...scw.RequestOption) (*domain.ListTasksResponse, error) + BuyDomain(req *domain.BuyDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) + RenewDomain(req *domain.RenewDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) + TransferDomain(req *domain.TransferDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) + TradeDomain(req *domain.TradeDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) + RegisterExternalDomain(req *domain.RegisterExternalDomainRequest, opts ...scw.RequestOption) (*domain.RegisterExternalDomainResponse, error) + DeleteExternalDomain(req *domain.DeleteExternalDomainRequest, opts ...scw.RequestOption) (*domain.DeleteExternalDomainResponse, error) + ListContacts(req *domain.ListContactsRequest, opts ...scw.RequestOption) (*domain.ListContactsResponse, error) + GetContact(req *domain.GetContactRequest, opts ...scw.RequestOption) (*domain.Contact, error) + UpdateContact(req *domain.UpdateContactRequest, opts ...scw.RequestOption) (*domain.Contact, error) + ListDomains(req *domain.ListDomainsRequest, opts ...scw.RequestOption) (*domain.ListDomainsResponse, error) + GetDomain(req *domain.GetDomainRequest, opts ...scw.RequestOption) (*domain.GetDomainResponse, error) + UpdateDomain(req *domain.UpdateDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) + LockDomainTransfer(req *domain.LockDomainTransferRequest, opts ...scw.RequestOption) (*domain.Domain, error) + UnlockDomainTransfer(req *domain.UnlockDomainTransferRequest, opts ...scw.RequestOption) (*domain.Domain, error) + EnableDomainAutoRenew(req *domain.EnableDomainAutoRenewRequest, opts ...scw.RequestOption) (*domain.Domain, error) + DisableDomainAutoRenew(req *domain.DisableDomainAutoRenewRequest, opts ...scw.RequestOption) (*domain.Domain, error) + GetDomainAuthCode(req *domain.GetDomainAuthCodeRequest, opts ...scw.RequestOption) (*domain.GetDomainAuthCodeResponse, error) + EnableDomainDNSSEC(req *domain.EnableDomainDNSSECRequest, opts ...scw.RequestOption) (*domain.Domain, error) + DisableDomainDNSSEC(req *domain.DisableDomainDNSSECRequest, opts ...scw.RequestOption) (*domain.Domain, error) + CreateDNSZone(req *domain.CreateDNSZoneRequest, opts ...scw.RequestOption) (*domain.DNSZone, error) + UpdateDNSZone(req *domain.UpdateDNSZoneRequest, opts ...scw.RequestOption) (*domain.DNSZone, error) + CopyDNSZone(req *domain.CopyDNSZoneRequest, opts ...scw.RequestOption) (*domain.DNSZone, error) + DeleteDNSZone(req *domain.DeleteDNSZoneRequest, opts ...scw.RequestOption) (*domain.DeleteDNSZoneResponse, error) + ListDNSZoneNameservers(req *domain.ListDNSZoneNameserversRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneNameserversResponse, error) + UpdateDNSZoneNameservers(req *domain.UpdateDNSZoneNameserversRequest, opts ...scw.RequestOption) (*domain.UpdateDNSZoneNameserversResponse, error) + ClearDNSZoneRecords(req *domain.ClearDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.ClearDNSZoneRecordsResponse, error) + ExportRawDNSZone(req *domain.ExportRawDNSZoneRequest, opts ...scw.RequestOption) (*scw.File, error) + ImportRawDNSZone(req *domain.ImportRawDNSZoneRequest, opts ...scw.RequestOption) (*domain.ImportRawDNSZoneResponse, error) + ImportProviderDNSZone(req *domain.ImportProviderDNSZoneRequest, opts ...scw.RequestOption) (*domain.ImportProviderDNSZoneResponse, error) + RefreshDNSZone(req *domain.RefreshDNSZoneRequest, opts ...scw.RequestOption) (*domain.RefreshDNSZoneResponse, error) + ListDNSZoneVersions(req *domain.ListDNSZoneVersionsRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneVersionsResponse, error) + ListDNSZoneVersionRecords(req *domain.ListDNSZoneVersionRecordsRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneVersionRecordsResponse, error) + GetDNSZoneVersionDiff(req *domain.GetDNSZoneVersionDiffRequest, opts ...scw.RequestOption) (*domain.GetDNSZoneVersionDiffResponse, error) + RestoreDNSZoneVersion(req *domain.RestoreDNSZoneVersionRequest, opts ...scw.RequestOption) (*domain.RestoreDNSZoneVersionResponse, error) + CreateSSLCertificate(req *domain.CreateSSLCertificateRequest, opts ...scw.RequestOption) (*domain.ZoneSSL, error) + ListSSLCertificates(req *domain.ListSSLCertificatesRequest, opts ...scw.RequestOption) (*domain.ListSSLCertificatesResponse, error) + DeleteSSLCertificate(req *domain.DeleteSSLCertificateRequest, opts ...scw.RequestOption) (*domain.DeleteSSLCertificateResponse, error) + GetDNSZoneTsigKey(req *domain.GetDNSZoneTsigKeyRequest, opts ...scw.RequestOption) (*domain.GetDNSZoneTsigKeyResponse, error) + DeleteDNSZoneTsigKey(req *domain.DeleteDNSZoneTsigKeyRequest, opts ...scw.RequestOption) error + ListDNSZones(req *domain.ListDNSZonesRequest, opts ...scw.RequestOption) (*domain.ListDNSZonesResponse, error) + ListDNSZoneRecords(req *domain.ListDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneRecordsResponse, error) + UpdateDNSZoneRecords(req *domain.UpdateDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.UpdateDNSZoneRecordsResponse, error) +} diff --git a/provider/scaleway/scaleway.go b/provider/scaleway/scaleway.go new file mode 100644 index 000000000..ab3102701 --- /dev/null +++ b/provider/scaleway/scaleway.go @@ -0,0 +1,341 @@ +/* +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 scaleway + +import ( + "context" + "fmt" + "os" + "strconv" + "strings" + + domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2alpha2" + "github.com/scaleway/scaleway-sdk-go/scw" + log "github.com/sirupsen/logrus" + "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" + "sigs.k8s.io/external-dns/provider" +) + +const ( + scalewyRecordTTL uint32 = 300 + scalewayDefaultPriority uint32 = 0 + scalewayPriorityKey string = "scw/priority" +) + +// ScalewayProvider implements the DNS provider for Scaleway DNS +type ScalewayProvider struct { + provider.BaseProvider + domainAPI DomainAPI + dryRun bool + // only consider hosted zones managing domains ending in this suffix + domainFilter endpoint.DomainFilter +} + +// ScalewayChange differentiates between ChangActions +type ScalewayChange struct { + Action string + Record []domain.Record +} + +// NewScalewayProvider initializes a new Scaleway DNS provider +func NewScalewayProvider(ctx context.Context, domainFilter endpoint.DomainFilter, dryRun bool) (*ScalewayProvider, error) { + accessKey, ok := os.LookupEnv(scw.ScwAccessKeyEnv) + if !ok { + return nil, fmt.Errorf("environment variable %s not found", scw.ScwAccessKeyEnv) + } + secretKey, ok := os.LookupEnv(scw.ScwSecretKeyEnv) + if !ok { + return nil, fmt.Errorf("environment variable %s not found", scw.ScwSecretKeyEnv) + } + organizationID, ok := os.LookupEnv(scw.ScwDefaultOrganizationIDEnv) + if !ok { + return nil, fmt.Errorf("environment variable %s not found", scw.ScwDefaultOrganizationIDEnv) + } + + scwClient, err := scw.NewClient( + scw.WithEnv(), + scw.WithAuth(accessKey, secretKey), + scw.WithDefaultOrganizationID(organizationID), + scw.WithUserAgent("ExternalDNS"), + ) + if err != nil { + return nil, err + } + + domainAPI := domain.NewAPI(scwClient) + + return &ScalewayProvider{ + domainAPI: domainAPI, + dryRun: dryRun, + domainFilter: domainFilter, + }, nil +} + +// Zones returns the list of hosted zones. +func (p *ScalewayProvider) Zones(ctx context.Context) ([]*domain.DNSZone, error) { + res := []*domain.DNSZone{} + + dnsZones, err := p.domainAPI.ListDNSZones(&domain.ListDNSZonesRequest{}, scw.WithAllPages(), scw.WithContext(ctx)) + if err != nil { + return nil, err + } + + for _, dnsZone := range dnsZones.DNSZones { + if p.domainFilter.Match(getCompleteZoneName(dnsZone)) { + res = append(res, dnsZone) + } + } + + return res, nil +} + +// Records returns the list of records in a given zone. +func (p *ScalewayProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { + endpoints := map[string]*endpoint.Endpoint{} + dnsZones, err := p.Zones(ctx) + if err != nil { + return nil, err + } + + for _, zone := range dnsZones { + recordsResp, err := p.domainAPI.ListDNSZoneRecords(&domain.ListDNSZoneRecordsRequest{ + DNSZone: getCompleteZoneName(zone), + }, scw.WithAllPages()) + if err != nil { + return nil, err + } + + for _, record := range recordsResp.Records { + name := record.Name + "." + if record.Name == "" { + name = "" + } + // trim any leading or ending dot + fullRecordName := strings.Trim(name+getCompleteZoneName(zone), ".") + + if !provider.SupportedRecordType(record.Type.String()) { + log.Infof("Skipping record %s because type %s is not supported", fullRecordName, record.Type.String()) + continue + } + + // in external DNS, same endpoint have the same ttl and same priority + // it's not the case in Scaleway DNS. It should never happen, but if + // the record is modified without going through ExternalDNS, we could have + // different priorities of ttls for a same name. + // In this case, we juste take the first one. + if existingEndpoint, ok := endpoints[record.Type.String()+"/"+fullRecordName]; ok { + existingEndpoint.Targets = append(existingEndpoint.Targets, record.Data) + log.Infof("Appending target %s to record %s, using TTL and priotiry of target %s", record.Data, fullRecordName, existingEndpoint.Targets[0]) + } else { + ep := endpoint.NewEndpointWithTTL(fullRecordName, record.Type.String(), endpoint.TTL(record.TTL), record.Data) + ep = ep.WithProviderSpecific(scalewayPriorityKey, fmt.Sprintf("%d", record.Priority)) + endpoints[record.Type.String()+"/"+fullRecordName] = ep + } + } + } + returnedEndpoints := []*endpoint.Endpoint{} + for _, ep := range endpoints { + returnedEndpoints = append(returnedEndpoints, ep) + } + + return returnedEndpoints, nil +} + +// ApplyChanges applies a set of changes in a zone. +func (p *ScalewayProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { + requests, err := p.generateApplyRequests(ctx, changes) + if err != nil { + return err + } + for _, req := range requests { + logChanges(req) + if p.dryRun { + log.Info("Running in dry run mode") + continue + } + _, err := p.domainAPI.UpdateDNSZoneRecords(req, scw.WithContext(ctx)) + if err != nil { + return err + } + } + return nil +} + +func (p *ScalewayProvider) generateApplyRequests(ctx context.Context, changes *plan.Changes) ([]*domain.UpdateDNSZoneRecordsRequest, error) { + returnedRequests := []*domain.UpdateDNSZoneRecordsRequest{} + recordsToAdd := map[string]*domain.RecordChangeAdd{} + recordsToDelete := map[string][]*domain.RecordChange{} + + dnsZones, err := p.Zones(ctx) + if err != nil { + return nil, err + } + + zoneNameMapper := provider.ZoneIDName{} + for _, zone := range dnsZones { + zoneName := getCompleteZoneName(zone) + zoneNameMapper.Add(zoneName, zoneName) + recordsToAdd[zoneName] = &domain.RecordChangeAdd{ + Records: []*domain.Record{}, + } + recordsToDelete[zoneName] = []*domain.RecordChange{} + } + + for _, c := range changes.UpdateOld { + zone, _ := zoneNameMapper.FindZone(c.DNSName) + if zone == "" { + log.Infof("Ignore record %s since it's not handled by ExternalDNS", c.DNSName) + continue + } + recordsToDelete[zone] = append(recordsToDelete[zone], endpointToScalewayRecordsChangeDelete(zone, c)...) + } + + for _, c := range changes.Delete { + zone, _ := zoneNameMapper.FindZone(c.DNSName) + if zone == "" { + log.Infof("Ignore record %s since it's not handled by ExternalDNS", c.DNSName) + continue + } + recordsToDelete[zone] = append(recordsToDelete[zone], endpointToScalewayRecordsChangeDelete(zone, c)...) + } + + for _, c := range changes.Create { + zone, _ := zoneNameMapper.FindZone(c.DNSName) + if zone == "" { + log.Infof("Ignore record %s since it's not handled by ExternalDNS", c.DNSName) + continue + } + recordsToAdd[zone].Records = append(recordsToAdd[zone].Records, endpointToScalewayRecords(zone, c)...) + } + for _, c := range changes.UpdateNew { + zone, _ := zoneNameMapper.FindZone(c.DNSName) + if zone == "" { + log.Infof("Ignore record %s since it's not handled by ExternalDNS", c.DNSName) + continue + } + recordsToAdd[zone].Records = append(recordsToAdd[zone].Records, endpointToScalewayRecords(zone, c)...) + } + + for _, zone := range dnsZones { + zoneName := getCompleteZoneName(zone) + req := &domain.UpdateDNSZoneRecordsRequest{ + DNSZone: zoneName, + Changes: recordsToDelete[zoneName], + } + req.Changes = append(req.Changes, &domain.RecordChange{ + Add: recordsToAdd[zoneName], + }) + returnedRequests = append(returnedRequests, req) + } + + return returnedRequests, nil +} + +func getCompleteZoneName(zone *domain.DNSZone) string { + subdomain := zone.Subdomain + "." + if zone.Subdomain == "" { + subdomain = "" + } + return subdomain + zone.Domain +} + +func endpointToScalewayRecords(zoneName string, ep *endpoint.Endpoint) []*domain.Record { + // no annotation results in a TTL of 0, default to 300 for consistency with other providers + var ttl = scalewyRecordTTL + if ep.RecordTTL.IsConfigured() { + ttl = uint32(ep.RecordTTL) + } + var priority = scalewayDefaultPriority + if prop, ok := ep.GetProviderSpecificProperty(scalewayPriorityKey); ok { + prio, err := strconv.ParseUint(prop.Value, 10, 64) + if err != nil { + log.Errorf("Failed parsing value of %s: %s: %v; using priority of %d", scalewayPriorityKey, prop.Value, err, scalewayDefaultPriority) + } else { + priority = uint32(prio) + } + } + + records := []*domain.Record{} + + for _, target := range ep.Targets { + records = append(records, &domain.Record{ + Data: target, + Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "), + Priority: priority, + TTL: ttl, + Type: domain.RecordType(ep.RecordType), + }) + } + + return records +} + +func endpointToScalewayRecordsChangeDelete(zoneName string, ep *endpoint.Endpoint) []*domain.RecordChange { + records := []*domain.RecordChange{} + + for _, target := range ep.Targets { + records = append(records, &domain.RecordChange{ + Delete: &domain.RecordChangeDelete{ + Data: target, + Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "), + Type: domain.RecordType(ep.RecordType), + }, + }) + } + + return records +} + +func logChanges(req *domain.UpdateDNSZoneRecordsRequest) { + log.Infof("Updating zone %s", req.DNSZone) + if !log.IsLevelEnabled(log.InfoLevel) { + return + } + for _, change := range req.Changes { + if change.Add != nil { + for _, add := range change.Add.Records { + name := add.Name + "." + if add.Name == "" { + name = "" + } + + logFields := log.Fields{ + "record": name + req.DNSZone, + "type": add.Type.String(), + "ttl": add.TTL, + "priority": add.Priority, + "data": add.Data, + } + log.WithFields(logFields).Info("Adding record") + } + } else if change.Delete != nil { + name := change.Delete.Name + "." + if change.Delete.Name == "" { + name = "" + } + + logFields := log.Fields{ + "record": name + req.DNSZone, + "type": change.Delete.Type.String(), + "data": change.Delete.Data, + } + + log.WithFields(logFields).Info("Deleting record") + } + } +} diff --git a/provider/scaleway/scaleway_test.go b/provider/scaleway/scaleway_test.go new file mode 100644 index 000000000..9a434f021 --- /dev/null +++ b/provider/scaleway/scaleway_test.go @@ -0,0 +1,623 @@ +package scaleway + +import ( + "context" + "os" + "reflect" + "testing" + + domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2alpha2" + "github.com/scaleway/scaleway-sdk-go/scw" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" +) + +type mockScalewayDomain struct { + *domain.API +} + +// Need to implement all these useless methods +func (m *mockScalewayDomain) ListTasks(req *domain.ListTasksRequest, opts ...scw.RequestOption) (*domain.ListTasksResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) BuyDomain(req *domain.BuyDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) RenewDomain(req *domain.RenewDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) TransferDomain(req *domain.TransferDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) TradeDomain(req *domain.TradeDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) RegisterExternalDomain(req *domain.RegisterExternalDomainRequest, opts ...scw.RequestOption) (*domain.RegisterExternalDomainResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) DeleteExternalDomain(req *domain.DeleteExternalDomainRequest, opts ...scw.RequestOption) (*domain.DeleteExternalDomainResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) ListContacts(req *domain.ListContactsRequest, opts ...scw.RequestOption) (*domain.ListContactsResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) GetContact(req *domain.GetContactRequest, opts ...scw.RequestOption) (*domain.Contact, error) { + return nil, nil +} +func (m *mockScalewayDomain) UpdateContact(req *domain.UpdateContactRequest, opts ...scw.RequestOption) (*domain.Contact, error) { + return nil, nil +} +func (m *mockScalewayDomain) ListDomains(req *domain.ListDomainsRequest, opts ...scw.RequestOption) (*domain.ListDomainsResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) GetDomain(req *domain.GetDomainRequest, opts ...scw.RequestOption) (*domain.GetDomainResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) UpdateDomain(req *domain.UpdateDomainRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) LockDomainTransfer(req *domain.LockDomainTransferRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) UnlockDomainTransfer(req *domain.UnlockDomainTransferRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) EnableDomainAutoRenew(req *domain.EnableDomainAutoRenewRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) DisableDomainAutoRenew(req *domain.DisableDomainAutoRenewRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) GetDomainAuthCode(req *domain.GetDomainAuthCodeRequest, opts ...scw.RequestOption) (*domain.GetDomainAuthCodeResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) EnableDomainDNSSEC(req *domain.EnableDomainDNSSECRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) DisableDomainDNSSEC(req *domain.DisableDomainDNSSECRequest, opts ...scw.RequestOption) (*domain.Domain, error) { + return nil, nil +} +func (m *mockScalewayDomain) CreateDNSZone(req *domain.CreateDNSZoneRequest, opts ...scw.RequestOption) (*domain.DNSZone, error) { + return nil, nil +} +func (m *mockScalewayDomain) UpdateDNSZone(req *domain.UpdateDNSZoneRequest, opts ...scw.RequestOption) (*domain.DNSZone, error) { + return nil, nil +} +func (m *mockScalewayDomain) CopyDNSZone(req *domain.CopyDNSZoneRequest, opts ...scw.RequestOption) (*domain.DNSZone, error) { + return nil, nil +} +func (m *mockScalewayDomain) DeleteDNSZone(req *domain.DeleteDNSZoneRequest, opts ...scw.RequestOption) (*domain.DeleteDNSZoneResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) ListDNSZoneNameservers(req *domain.ListDNSZoneNameserversRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneNameserversResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) UpdateDNSZoneNameservers(req *domain.UpdateDNSZoneNameserversRequest, opts ...scw.RequestOption) (*domain.UpdateDNSZoneNameserversResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) ClearDNSZoneRecords(req *domain.ClearDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.ClearDNSZoneRecordsResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) ExportRawDNSZone(req *domain.ExportRawDNSZoneRequest, opts ...scw.RequestOption) (*scw.File, error) { + return nil, nil +} +func (m *mockScalewayDomain) ImportRawDNSZone(req *domain.ImportRawDNSZoneRequest, opts ...scw.RequestOption) (*domain.ImportRawDNSZoneResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) ImportProviderDNSZone(req *domain.ImportProviderDNSZoneRequest, opts ...scw.RequestOption) (*domain.ImportProviderDNSZoneResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) RefreshDNSZone(req *domain.RefreshDNSZoneRequest, opts ...scw.RequestOption) (*domain.RefreshDNSZoneResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) ListDNSZoneVersions(req *domain.ListDNSZoneVersionsRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneVersionsResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) ListDNSZoneVersionRecords(req *domain.ListDNSZoneVersionRecordsRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneVersionRecordsResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) GetDNSZoneVersionDiff(req *domain.GetDNSZoneVersionDiffRequest, opts ...scw.RequestOption) (*domain.GetDNSZoneVersionDiffResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) RestoreDNSZoneVersion(req *domain.RestoreDNSZoneVersionRequest, opts ...scw.RequestOption) (*domain.RestoreDNSZoneVersionResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) CreateSSLCertificate(req *domain.CreateSSLCertificateRequest, opts ...scw.RequestOption) (*domain.ZoneSSL, error) { + return nil, nil +} +func (m *mockScalewayDomain) ListSSLCertificates(req *domain.ListSSLCertificatesRequest, opts ...scw.RequestOption) (*domain.ListSSLCertificatesResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) DeleteSSLCertificate(req *domain.DeleteSSLCertificateRequest, opts ...scw.RequestOption) (*domain.DeleteSSLCertificateResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) GetDNSZoneTsigKey(req *domain.GetDNSZoneTsigKeyRequest, opts ...scw.RequestOption) (*domain.GetDNSZoneTsigKeyResponse, error) { + return nil, nil +} +func (m *mockScalewayDomain) DeleteDNSZoneTsigKey(req *domain.DeleteDNSZoneTsigKeyRequest, opts ...scw.RequestOption) error { + return nil +} + +func (m *mockScalewayDomain) ListDNSZones(req *domain.ListDNSZonesRequest, opts ...scw.RequestOption) (*domain.ListDNSZonesResponse, error) { + return &domain.ListDNSZonesResponse{ + DNSZones: []*domain.DNSZone{ + { + Domain: "example.com", + Subdomain: "", + }, + { + Domain: "example.com", + Subdomain: "test", + }, + { + Domain: "dummy.me", + Subdomain: "", + }, + { + Domain: "dummy.me", + Subdomain: "test", + }, + }, + }, nil +} + +func (m *mockScalewayDomain) ListDNSZoneRecords(req *domain.ListDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneRecordsResponse, error) { + records := []*domain.Record{} + if req.DNSZone == "example.com" { + records = []*domain.Record{ + { + Data: "1.1.1.1", + Name: "one", + TTL: 300, + Priority: 0, + Type: domain.RecordTypeA, + }, + { + Data: "1.1.1.2", + Name: "two", + TTL: 300, + Priority: 0, + Type: domain.RecordTypeA, + }, + { + Data: "1.1.1.3", + Name: "two", + TTL: 300, + Priority: 0, + Type: domain.RecordTypeA, + }, + } + } else if req.DNSZone == "test.example.com" { + records = []*domain.Record{ + { + Data: "1.1.1.1", + Name: "", + TTL: 300, + Priority: 0, + Type: domain.RecordTypeA, + }, + { + Data: "test.example.com", + Name: "two", + TTL: 600, + Priority: 30, + Type: domain.RecordTypeCNAME, + }, + } + } + return &domain.ListDNSZoneRecordsResponse{ + Records: records, + }, nil +} + +func (m *mockScalewayDomain) UpdateDNSZoneRecords(req *domain.UpdateDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.UpdateDNSZoneRecordsResponse, error) { + return nil, nil +} + +func TestScalewayProvider_NewScalewayProvider(t *testing.T) { + _ = os.Setenv(scw.ScwAccessKeyEnv, "SCWXXXXXXXXXXXXXXXXX") + _ = os.Setenv(scw.ScwSecretKeyEnv, "11111111-1111-1111-1111-111111111111") + _ = os.Setenv(scw.ScwDefaultOrganizationIDEnv, "11111111-1111-1111-1111-111111111111") + _, err := NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true) + if err != nil { + t.Errorf("failed : %s", err) + } + + _ = os.Unsetenv(scw.ScwDefaultOrganizationIDEnv) + _, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true) + if err == nil { + t.Errorf("expected to fail") + } + + _ = os.Setenv(scw.ScwDefaultOrganizationIDEnv, "dummy") + _, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true) + if err == nil { + t.Errorf("expected to fail") + } + + _ = os.Unsetenv(scw.ScwSecretKeyEnv) + _ = os.Setenv(scw.ScwDefaultOrganizationIDEnv, "11111111-1111-1111-1111-111111111111") + _, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true) + if err == nil { + t.Errorf("expected to fail") + } + + _ = os.Setenv(scw.ScwSecretKeyEnv, "dummy") + _, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true) + if err == nil { + t.Errorf("expected to fail") + } + + _ = os.Unsetenv(scw.ScwAccessKeyEnv) + _ = os.Setenv(scw.ScwSecretKeyEnv, "11111111-1111-1111-1111-111111111111") + _, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true) + if err == nil { + t.Errorf("expected to fail") + } + + _ = os.Setenv(scw.ScwAccessKeyEnv, "dummy") + _, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true) + if err == nil { + t.Errorf("expected to fail") + } +} + +func TestScalewayProvider_Zones(t *testing.T) { + mocked := mockScalewayDomain{nil} + provider := &ScalewayProvider{ + domainAPI: &mocked, + domainFilter: endpoint.NewDomainFilter([]string{"example.com"}), + } + + expected := []*domain.DNSZone{ + { + Domain: "example.com", + Subdomain: "", + }, + { + Domain: "example.com", + Subdomain: "test", + }, + } + zones, err := provider.Zones(context.Background()) + if err != nil { + t.Fatal(err) + } + require.Len(t, zones, len(expected)) + for i, zone := range zones { + assert.Equal(t, expected[i], zone) + } +} + +func TestScalewayProvider_Records(t *testing.T) { + mocked := mockScalewayDomain{nil} + provider := &ScalewayProvider{ + domainAPI: &mocked, + domainFilter: endpoint.NewDomainFilter([]string{"example.com"}), + } + + expected := []*endpoint.Endpoint{ + { + DNSName: "one.example.com", + RecordTTL: 300, + RecordType: "A", + Targets: []string{"1.1.1.1"}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: scalewayPriorityKey, + Value: "0", + }, + }, + }, + { + DNSName: "two.example.com", + RecordTTL: 300, + RecordType: "A", + Targets: []string{"1.1.1.2", "1.1.1.3"}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: scalewayPriorityKey, + Value: "0", + }, + }, + }, + { + DNSName: "test.example.com", + RecordTTL: 300, + RecordType: "A", + Targets: []string{"1.1.1.1"}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: scalewayPriorityKey, + Value: "0", + }, + }, + }, + { + DNSName: "two.test.example.com", + RecordTTL: 600, + RecordType: "CNAME", + Targets: []string{"test.example.com"}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: scalewayPriorityKey, + Value: "30", + }, + }, + }, + } + + records, err := provider.Records(context.TODO()) + if err != nil { + t.Fatal(err) + } + + require.Len(t, records, len(expected)) + for _, record := range records { + found := false + for _, expectedRecord := range expected { + if checkRecordEquality(record, expectedRecord) { + found = true + } + } + assert.Equal(t, true, found) + } +} + +// this test is really ugly since we are working on maps, so array are randomly sorted +// feel free to modify if you have a better idea +func TestScalewayProvider_generateApplyRequests(t *testing.T) { + mocked := mockScalewayDomain{nil} + provider := &ScalewayProvider{ + domainAPI: &mocked, + domainFilter: endpoint.NewDomainFilter([]string{"example.com"}), + } + + expected := []*domain.UpdateDNSZoneRecordsRequest{ + { + DNSZone: "example.com", + Changes: []*domain.RecordChange{ + { + Add: &domain.RecordChangeAdd{ + Records: []*domain.Record{ + { + Data: "1.1.1.1", + Name: "", + TTL: 300, + Type: domain.RecordTypeA, + Priority: 0, + }, + { + Data: "1.1.1.2", + Name: "", + TTL: 300, + Type: domain.RecordTypeA, + Priority: 0, + }, + { + Data: "2.2.2.2", + Name: "me", + TTL: 600, + Type: domain.RecordTypeA, + Priority: 30, + }, + }, + }, + }, + { + Delete: &domain.RecordChangeDelete{ + Data: "3.3.3.3", + Name: "me", + Type: domain.RecordTypeA, + }, + }, + { + Delete: &domain.RecordChangeDelete{ + Data: "1.1.1.1", + Name: "here", + Type: domain.RecordTypeA, + }, + }, + { + Delete: &domain.RecordChangeDelete{ + Data: "1.1.1.2", + Name: "here", + Type: domain.RecordTypeA, + }, + }, + }, + }, + { + DNSZone: "test.example.com", + Changes: []*domain.RecordChange{ + { + Add: &domain.RecordChangeAdd{ + Records: []*domain.Record{ + { + Data: "example.com", + Name: "", + TTL: 600, + Type: domain.RecordTypeCNAME, + Priority: 20, + }, + { + Data: "1.2.3.4", + Name: "my", + TTL: 300, + Type: domain.RecordTypeA, + Priority: 0, + }, + { + Data: "5.6.7.8", + Name: "my", + TTL: 300, + Type: domain.RecordTypeA, + Priority: 0, + }, + }, + }, + }, + { + Delete: &domain.RecordChangeDelete{ + Data: "1.1.1.1", + Name: "here.is.my", + Type: domain.RecordTypeA, + }, + }, + { + Delete: &domain.RecordChangeDelete{ + Data: "4.4.4.4", + Name: "my", + Type: domain.RecordTypeA, + }, + }, + { + Delete: &domain.RecordChangeDelete{ + Data: "5.5.5.5", + Name: "my", + Type: domain.RecordTypeA, + }, + }, + }, + }, + } + + changes := &plan.Changes{ + Create: []*endpoint.Endpoint{ + { + DNSName: "example.com", + RecordType: "A", + Targets: []string{"1.1.1.1", "1.1.1.2"}, + }, + { + DNSName: "test.example.com", + RecordType: "CNAME", + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: scalewayPriorityKey, + Value: "20", + }, + }, + RecordTTL: 600, + Targets: []string{"example.com"}, + }, + }, + Delete: []*endpoint.Endpoint{ + { + DNSName: "here.example.com", + RecordType: "A", + Targets: []string{"1.1.1.1", "1.1.1.2"}, + }, + { + DNSName: "here.is.my.test.example.com", + RecordType: "A", + Targets: []string{"1.1.1.1"}, + }, + }, + UpdateNew: []*endpoint.Endpoint{ + { + DNSName: "me.example.com", + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: scalewayPriorityKey, + Value: "30", + }, + }, + RecordType: "A", + RecordTTL: 600, + Targets: []string{"2.2.2.2"}, + }, + { + DNSName: "my.test.example.com", + RecordType: "A", + Targets: []string{"1.2.3.4", "5.6.7.8"}, + }, + }, + UpdateOld: []*endpoint.Endpoint{ + { + DNSName: "me.example.com", + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: scalewayPriorityKey, + Value: "1234", + }, + }, + RecordType: "A", + Targets: []string{"3.3.3.3"}, + }, + { + DNSName: "my.test.example.com", + RecordType: "A", + Targets: []string{"4.4.4.4", "5.5.5.5"}, + }, + }, + } + + requests, err := provider.generateApplyRequests(context.TODO(), changes) + if err != nil { + t.Fatal(err) + } + + require.Len(t, requests, len(expected)) + total := int(len(expected)) + for _, req := range requests { + for _, exp := range expected { + if checkScalewayReqChanges(req, exp) { + total-- + } + } + } + assert.Equal(t, 0, total) +} + +func checkRecordEquality(record1, record2 *endpoint.Endpoint) bool { + return record1.Targets.Same(record2.Targets) && + record1.DNSName == record2.DNSName && + record1.RecordTTL == record2.RecordTTL && + record1.RecordType == record2.RecordType && + reflect.DeepEqual(record1.ProviderSpecific, record2.ProviderSpecific) +} + +func checkScalewayReqChanges(r1, r2 *domain.UpdateDNSZoneRecordsRequest) bool { + if r1.DNSZone != r2.DNSZone { + return false + } + if len(r1.Changes) != len(r2.Changes) { + return false + } + total := int(len(r1.Changes)) + for _, c1 := range r1.Changes { + for _, c2 := range r2.Changes { + // we only have 1 add per request + if c1.Add != nil && c2.Add != nil && checkScalewayRecords(c1.Add.Records, c2.Add.Records) { + total-- + } else if c1.Delete != nil && c2.Delete != nil { + if c1.Delete.Data == c2.Delete.Data && c1.Delete.Name == c2.Delete.Name && c1.Delete.Type == c2.Delete.Type { + total-- + } + } + } + } + return total == 0 +} + +func checkScalewayRecords(rs1, rs2 []*domain.Record) bool { + if len(rs1) != len(rs2) { + return false + } + total := int(len(rs1)) + for _, r1 := range rs1 { + for _, r2 := range rs2 { + if r1.Data == r2.Data && + r1.Name == r2.Name && + r1.Priority == r2.Priority && + r1.TTL == r2.TTL && + r1.Type == r2.Type { + total-- + } + } + } + return total == 0 +}