mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 09:36:58 +02:00
Merge pull request #2359 from assureddt/rjh_safedns_provider
Create SafeDNS provider
This commit is contained in:
commit
c8ef36567c
@ -52,6 +52,7 @@ ExternalDNS' allows you to keep selected zones (via `--domain-filter`) synchroni
|
||||
* [Akamai Edge DNS](https://learn.akamai.com/en-us/products/cloud_security/edge_dns.html)
|
||||
* [GoDaddy](https://www.godaddy.com)
|
||||
* [Gandi](https://www.gandi.net)
|
||||
* [UKFast SafeDNS](https://my.ukfast.co.uk/safedns/)
|
||||
|
||||
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.
|
||||
|
||||
@ -109,6 +110,7 @@ The following table clarifies the current status of the providers according to t
|
||||
| UltraDNS | Alpha | |
|
||||
| GoDaddy | Alpha | |
|
||||
| Gandi | Alpha | @packi |
|
||||
| SafeDNS | Alpha | @assureddt |
|
||||
|
||||
## Kubernetes version compatibility
|
||||
|
||||
@ -171,6 +173,7 @@ The following tutorials are provided:
|
||||
* [UltraDNS](docs/tutorials/ultradns.md)
|
||||
* [GoDaddy](docs/tutorials/godaddy.md)
|
||||
* [Gandi](docs/tutorials/gandi.md)
|
||||
* [SafeDNS](docs/tutorials/safedns.md)
|
||||
|
||||
### Running Locally
|
||||
|
||||
|
210
docs/tutorials/UKFast_SafeDNS.md
Normal file
210
docs/tutorials/UKFast_SafeDNS.md
Normal file
@ -0,0 +1,210 @@
|
||||
# Setting up ExternalDNS for Services on UKFast's SafeDNS
|
||||
|
||||
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using SafeDNS.
|
||||
|
||||
Make sure to use **>=0.11.0** version of ExternalDNS for this tutorial.
|
||||
|
||||
## Managing DNS with SafeDNS
|
||||
|
||||
If you want to learn about how to use the SafeDNS service read the following tutorials:
|
||||
To learn more about the use of SafeDNS in general, see the following page:
|
||||
|
||||
[UKFast's SafeDNS documentation](https://docs.ukfast.co.uk/domains/safedns/index.html).
|
||||
|
||||
## Creating SafeDNS credentials
|
||||
|
||||
Generate a fresh API token for use with ExternalDNS, following the instructions
|
||||
at the UKFast developer [Getting-Started](https://developers.ukfast.io/getting-started)
|
||||
page. You will need to grant read/write access to the SafeDNS API. No access to
|
||||
any other UKFast service is required.
|
||||
|
||||
The environment variable `SAFEDNS_TOKEN` must have a value of this token to run
|
||||
ExternalDNS with SafeDNS integration.
|
||||
|
||||
## Deploy ExternalDNS
|
||||
|
||||
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
|
||||
Then apply one of the following manifests file to deploy ExternalDNS.
|
||||
|
||||
### Manifest (for clusters without RBAC enabled)
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
# You will need to check what the latest version is yourself:
|
||||
# https://github.com/kubernetes-sigs/external-dns/releases
|
||||
image: k8s.gcr.io/external-dns/external-dns:vX.Y.Z
|
||||
args:
|
||||
- --source=service # ingress is also possible
|
||||
# (optional) limit to only example.com domains; change to match the
|
||||
# zone created above.
|
||||
- --domain-filter=example.com
|
||||
- --provider=safedns
|
||||
env:
|
||||
- name: SAFEDNS_TOKEN
|
||||
value: "SAFEDNSTOKENSAFEDNSTOKEN"
|
||||
```
|
||||
|
||||
### Manifest (for clusters with RBAC enabled)
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: external-dns
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: external-dns
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services","endpoints","pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions","networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["list"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: external-dns-viewer
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: external-dns
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: external-dns
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: k8s.gcr.io/external-dns/external-dns:v0.11.0
|
||||
args:
|
||||
- --source=service # ingress is also possible
|
||||
# (optional) limit to only example.com domains; change to match the
|
||||
# zone created above.
|
||||
- --domain-filter=example.com
|
||||
- --provider=safedns
|
||||
env:
|
||||
- name: SAFEDNS_TOKEN
|
||||
value: "SAFEDNSTOKENSAFEDNSTOKEN"
|
||||
```
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
Note the annotation on the service; use a hostname that matches the domain
|
||||
filter specified 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 SafeDNS records.
|
||||
|
||||
## Verifying SafeDNS records
|
||||
|
||||
Check your [SafeDNS UI](https://my.ukfast.co.uk/safedns/index.php) and select
|
||||
the appropriate domain to view the records for your SafeDNS zone.
|
||||
|
||||
This should show the external IP address of the service as the A record for your
|
||||
domain.
|
||||
|
||||
Alternatively, you can perform a DNS lookup for the hostname specified:
|
||||
```console
|
||||
$ dig +short my-app.example.com
|
||||
an.ip.addr.ess
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
Now that we have verified that ExternalDNS will automatically manage SafeDNS
|
||||
records, we can delete the tutorial's example:
|
||||
|
||||
```
|
||||
$ kubectl delete service -f nginx.yaml
|
||||
$ kubectl delete service -f externaldns.yaml
|
||||
```
|
1
go.mod
1
go.mod
@ -56,6 +56,7 @@ require (
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/terra-farm/udnssdk v1.3.5 // indirect
|
||||
github.com/transip/gotransip/v6 v6.6.2
|
||||
github.com/ukfast/sdk-go v1.4.23 // indirect
|
||||
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556
|
||||
github.com/vultr/govultr/v2 v2.9.0
|
||||
|
15
go.sum
15
go.sum
@ -52,6 +52,7 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
||||
git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d h1:d6sdozgfqtgaOhjUn++lbo5siX3HELjcOUnbtrvVQi4=
|
||||
git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d/go.mod h1:n26Twiii5jhkMC+Ocz/s8R73cBBcXRIwyTqQ+6bOZGo=
|
||||
git.lukeshu.com/go/libsystemd v0.5.3/go.mod h1:FfDoP0i92r4p5Vn4NCLxvjkd7rCOe6otPa4L6hZg9WM=
|
||||
github.com/0x4c6565/genie v1.0.0/go.mod h1:fDOjW0hFamMWOIkh4irf2D/TZpXXWMFtpP8MfgK0N3c=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v46.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v46.4.0+incompatible h1:fCN6Pi+tEiEwFa8RSmtVlFHRXEZ+DJm9gfx/MKqYWw4=
|
||||
@ -270,6 +271,7 @@ github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2
|
||||
github.com/datawire/ambassador v1.6.0 h1:4KduhY/wqtv0jK8sMVQNtENHy9fmoXugsuFp/UrM0Ts=
|
||||
github.com/datawire/ambassador v1.6.0/go.mod h1:mV5EhoG/NnHBsffmLnjrq+x4ZNkYDWFZXW9R+AueUiE=
|
||||
github.com/datawire/pf v0.0.0-20180510150411-31a823f9495a/go.mod h1:H8uUmE8qqo7z9u30MYB9riLyRckPHOPBk9ZdCuH+dQQ=
|
||||
github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -431,6 +433,10 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
||||
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
|
||||
github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
|
||||
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY=
|
||||
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
@ -742,6 +748,8 @@ github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4F
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
@ -1082,6 +1090,10 @@ github.com/transip/gotransip/v6 v6.6.2 h1:+d3QO5Cyfh9n/J5OZxz8roer4JQIdmYvHVHExO
|
||||
github.com/transip/gotransip/v6 v6.6.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ukfast/go-durationstring v1.0.0 h1:kgPuA7XjLjgLDfkG8j0MpolxcZh/eMdiVoOIFD/uc5I=
|
||||
github.com/ukfast/go-durationstring v1.0.0/go.mod h1:Ci81n51kfxlKUIaLY9cINIKRO94VTqV+iCGbOMTb0V8=
|
||||
github.com/ukfast/sdk-go v1.4.23 h1:dLZmHW2jgV0QQ2TGGdbL2tYVdtQPcuUub7Rzh+6Cqic=
|
||||
github.com/ukfast/sdk-go v1.4.23/go.mod h1:tspweEP77MHhVEYgEEieKAKGITFgwkYl1q5fLh4HZAo=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60 h1:n7unetnX8WWTc0U85h/0+dJoLWLqoaJwowXB9RkBdxU=
|
||||
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60/go.mod h1:43vmy6GEvRuVMpGEWfJ/JoEM6RIqUQI1/tb8JqZR1zI=
|
||||
@ -1694,6 +1706,9 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.27.0 h1:wCg/0hk9RzcB0CYw8pYV6FiBYug1on0cpco9YZF8jqA=
|
||||
gopkg.in/go-playground/validator.v9 v9.27.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
|
3
main.go
3
main.go
@ -62,6 +62,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/safedns"
|
||||
"sigs.k8s.io/external-dns/provider/scaleway"
|
||||
"sigs.k8s.io/external-dns/provider/transip"
|
||||
"sigs.k8s.io/external-dns/provider/ultradns"
|
||||
@ -324,6 +325,8 @@ func main() {
|
||||
p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun)
|
||||
case "gandi":
|
||||
p, err = gandi.NewGandiProvider(ctx, domainFilter, cfg.DryRun)
|
||||
case "safedns":
|
||||
p, err = safedns.NewSafeDNSProvider(domainFilter, cfg.DryRun)
|
||||
default:
|
||||
log.Fatalf("unknown dns provider: %s", cfg.Provider)
|
||||
}
|
||||
|
@ -387,7 +387,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("default-targets", "Set globally default IP address that will apply as a target instead of source addresses. Specify multiple times for multiple targets (optional)").StringsVar(&cfg.DefaultTargets)
|
||||
|
||||
// Flags related to providers
|
||||
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, 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, gandi)").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", "godaddy", "bluecat", "gandi")
|
||||
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, 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, gandi, safedns)").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", "godaddy", "bluecat", "gandi", "safedns")
|
||||
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("regex-domain-filter", "Limit possible domains and target zones by a Regex filter; Overrides domain-filter (optional)").Default(defaultConfig.RegexDomainFilter.String()).RegexpVar(&cfg.RegexDomainFilter)
|
||||
|
242
provider/safedns/safedns.go
Normal file
242
provider/safedns/safedns.go
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
Copyright 2021 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 safedns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
ukfClient "github.com/ukfast/sdk-go/pkg/client"
|
||||
ukfConnection "github.com/ukfast/sdk-go/pkg/connection"
|
||||
"github.com/ukfast/sdk-go/pkg/service/safedns"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// SafeDNS is an interface that is a subset of the SafeDNS service API that are actually used.
|
||||
// Signatures must match exactly.
|
||||
type SafeDNS interface {
|
||||
CreateZoneRecord(zoneName string, req safedns.CreateRecordRequest) (int, error)
|
||||
DeleteZoneRecord(zoneName string, recordID int) error
|
||||
GetZone(zoneName string) (safedns.Zone, error)
|
||||
GetZoneRecord(zoneName string, recordID int) (safedns.Record, error)
|
||||
GetZoneRecords(zoneName string, parameters ukfConnection.APIRequestParameters) ([]safedns.Record, error)
|
||||
GetZones(parameters ukfConnection.APIRequestParameters) ([]safedns.Zone, error)
|
||||
PatchZoneRecord(zoneName string, recordID int, patch safedns.PatchRecordRequest) (int, error)
|
||||
UpdateZoneRecord(zoneName string, record safedns.Record) (int, error)
|
||||
}
|
||||
|
||||
// SafeDNSProvider implements the DNS provider spec for UKFast SafeDNS.
|
||||
type SafeDNSProvider struct {
|
||||
provider.BaseProvider
|
||||
Client SafeDNS
|
||||
// Only consider hosted zones managing domains ending in this suffix
|
||||
domainFilter endpoint.DomainFilter
|
||||
DryRun bool
|
||||
APIRequestParams ukfConnection.APIRequestParameters
|
||||
}
|
||||
|
||||
// ZoneRecord is a datatype to simplify management of a record in a zone.
|
||||
type ZoneRecord struct {
|
||||
ID int
|
||||
Name string
|
||||
Type safedns.RecordType
|
||||
TTL safedns.RecordTTL
|
||||
Zone string
|
||||
Content string
|
||||
}
|
||||
|
||||
func NewSafeDNSProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*SafeDNSProvider, error) {
|
||||
token, ok := os.LookupEnv("SAFEDNS_TOKEN")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no SAFEDNS_TOKEN found in environment")
|
||||
}
|
||||
|
||||
ukfAPIConnection := ukfConnection.NewAPIKeyCredentialsAPIConnection(token)
|
||||
ukfClient := ukfClient.NewClient(ukfAPIConnection)
|
||||
safeDNS := ukfClient.SafeDNSService()
|
||||
|
||||
provider := &SafeDNSProvider{
|
||||
Client: safeDNS,
|
||||
domainFilter: domainFilter,
|
||||
DryRun: dryRun,
|
||||
APIRequestParams: *ukfConnection.NewAPIRequestParameters(),
|
||||
}
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// Zones returns the list of hosted zones in the SafeDNS account
|
||||
func (p *SafeDNSProvider) Zones(ctx context.Context) ([]safedns.Zone, error) {
|
||||
var zones []safedns.Zone
|
||||
|
||||
allZones, err := p.Client.GetZones(p.APIRequestParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check each found zone to see whether they match the domain filter provided. If they do, append it to the array of
|
||||
// zones defined above. If not, continue to the next item in the loop.
|
||||
for _, zone := range allZones {
|
||||
if p.domainFilter.Match(zone.Name) {
|
||||
zones = append(zones, zone)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
func (p *SafeDNSProvider) ZoneRecords(ctx context.Context) ([]ZoneRecord, error) {
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var zoneRecords []ZoneRecord
|
||||
for _, zone := range zones {
|
||||
// For each zone in the zonelist, get all records of an ExternalDNS supported type.
|
||||
records, err := p.Client.GetZoneRecords(zone.Name, p.APIRequestParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range records {
|
||||
zoneRecord := ZoneRecord{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Type: r.Type,
|
||||
TTL: r.TTL,
|
||||
Zone: zone.Name,
|
||||
Content: r.Content,
|
||||
}
|
||||
zoneRecords = append(zoneRecords, zoneRecord)
|
||||
}
|
||||
}
|
||||
return zoneRecords, nil
|
||||
}
|
||||
|
||||
// Records returns a list of Endpoint resources created from all records in supported zones.
|
||||
func (p *SafeDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
zoneRecords, err := p.ZoneRecords(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range zoneRecords {
|
||||
if provider.SupportedRecordType(string(r.Type)) {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, string(r.Type), endpoint.TTL(r.TTL), r.Content))
|
||||
}
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *SafeDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
// Identify the zone name for each record
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, zone := range zones {
|
||||
zoneNameIDMapper.Add(zone.Name, zone.Name)
|
||||
}
|
||||
|
||||
zoneRecords, err := p.ZoneRecords(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range changes.Create {
|
||||
_, ZoneName := zoneNameIDMapper.FindZone(endpoint.DNSName)
|
||||
for _, target := range endpoint.Targets {
|
||||
request := safedns.CreateRecordRequest{
|
||||
Name: endpoint.DNSName,
|
||||
Type: endpoint.RecordType,
|
||||
Content: target,
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"zoneID": ZoneName,
|
||||
"dnsName": endpoint.DNSName,
|
||||
"recordType": endpoint.RecordType,
|
||||
"Value": target,
|
||||
}).Info("Creating record")
|
||||
_, err := p.Client.CreateZoneRecord(ZoneName, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, endpoint := range changes.UpdateNew {
|
||||
// Currently iterates over each zoneRecord in ZoneRecords for each Endpoint
|
||||
// in UpdateNew; the same will go for Delete. As it's double-iteration,
|
||||
// that's O(n^2), which isn't great. No performance issues have been noted
|
||||
// thus far.
|
||||
var zoneRecord ZoneRecord
|
||||
for _, target := range endpoint.Targets {
|
||||
for _, zr := range zoneRecords {
|
||||
if zr.Name == endpoint.DNSName && zr.Content == target {
|
||||
zoneRecord = zr
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
newTTL := safedns.RecordTTL(int(endpoint.RecordTTL))
|
||||
newRecord := safedns.PatchRecordRequest{
|
||||
Name: endpoint.DNSName,
|
||||
Content: target,
|
||||
TTL: &newTTL,
|
||||
Type: endpoint.RecordType,
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"zoneID": zoneRecord.Zone,
|
||||
"dnsName": newRecord.Name,
|
||||
"recordType": newRecord.Type,
|
||||
"Value": newRecord.Content,
|
||||
"Priority": newRecord.Priority,
|
||||
}).Info("Patching record")
|
||||
_, err = p.Client.PatchZoneRecord(zoneRecord.Zone, zoneRecord.ID, newRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, endpoint := range changes.Delete {
|
||||
// As above, currently iterates in O(n^2). May be a good start for optimisations.
|
||||
var zoneRecord ZoneRecord
|
||||
for _, zr := range zoneRecords {
|
||||
if zr.Name == endpoint.DNSName && string(zr.Type) == endpoint.RecordType {
|
||||
zoneRecord = zr
|
||||
break
|
||||
}
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"zoneID": zoneRecord.Zone,
|
||||
"dnsName": zoneRecord.Name,
|
||||
"recordType": zoneRecord.Type,
|
||||
}).Info("Deleting record")
|
||||
err := p.Client.DeleteZoneRecord(zoneRecord.Zone, zoneRecord.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
366
provider/safedns/safedns_test.go
Normal file
366
provider/safedns/safedns_test.go
Normal file
@ -0,0 +1,366 @@
|
||||
/*
|
||||
Copyright 2021 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 safedns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
ukfConnection "github.com/ukfast/sdk-go/pkg/connection"
|
||||
"github.com/ukfast/sdk-go/pkg/service/safedns"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
)
|
||||
|
||||
// Create an implementation of the SafeDNS interface for Mocking
|
||||
type MockSafeDNSService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) CreateZoneRecord(zoneName string, req safedns.CreateRecordRequest) (int, error) {
|
||||
args := m.Called(zoneName, req)
|
||||
return args.Int(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) DeleteZoneRecord(zoneName string, recordID int) error {
|
||||
args := m.Called(zoneName, recordID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) GetZone(zoneName string) (safedns.Zone, error) {
|
||||
args := m.Called(zoneName)
|
||||
return args.Get(0).(safedns.Zone), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) GetZoneRecord(zoneName string, recordID int) (safedns.Record, error) {
|
||||
args := m.Called(zoneName, recordID)
|
||||
return args.Get(0).(safedns.Record), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) GetZoneRecords(zoneName string, parameters ukfConnection.APIRequestParameters) ([]safedns.Record, error) {
|
||||
args := m.Called(zoneName, parameters)
|
||||
return args.Get(0).([]safedns.Record), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) GetZones(parameters ukfConnection.APIRequestParameters) ([]safedns.Zone, error) {
|
||||
args := m.Called(parameters)
|
||||
return args.Get(0).([]safedns.Zone), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) PatchZoneRecord(zoneName string, recordID int, patch safedns.PatchRecordRequest) (int, error) {
|
||||
args := m.Called(zoneName, recordID, patch)
|
||||
return args.Int(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) UpdateZoneRecord(zoneName string, record safedns.Record) (int, error) {
|
||||
args := m.Called(zoneName, record)
|
||||
return args.Int(0), args.Error(1)
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
func createZones() []safedns.Zone {
|
||||
return []safedns.Zone{
|
||||
{Name: "foo.com", Description: "Foo dot com"},
|
||||
{Name: "bar.io", Description: ""},
|
||||
{Name: "baz.org", Description: "Org"},
|
||||
}
|
||||
}
|
||||
|
||||
func createFooRecords() []safedns.Record {
|
||||
return []safedns.Record{
|
||||
{
|
||||
ID: 11,
|
||||
Type: safedns.RecordTypeA,
|
||||
Name: "foo.com",
|
||||
Content: "targetFoo",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 12,
|
||||
Type: safedns.RecordTypeTXT,
|
||||
Name: "foo.com",
|
||||
Content: "text",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 13,
|
||||
Type: safedns.RecordTypeCAA,
|
||||
Name: "foo.com",
|
||||
Content: "",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createBarRecords() []safedns.Record {
|
||||
return []safedns.Record{}
|
||||
}
|
||||
|
||||
func createBazRecords() []safedns.Record {
|
||||
return []safedns.Record{
|
||||
{
|
||||
ID: 31,
|
||||
Type: safedns.RecordTypeA,
|
||||
Name: "baz.org",
|
||||
Content: "targetBaz",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 32,
|
||||
Type: safedns.RecordTypeTXT,
|
||||
Name: "baz.org",
|
||||
Content: "text",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 33,
|
||||
Type: safedns.RecordTypeA,
|
||||
Name: "api.baz.org",
|
||||
Content: "targetBazAPI",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 34,
|
||||
Type: safedns.RecordTypeTXT,
|
||||
Name: "api.baz.org",
|
||||
Content: "text",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Actual tests
|
||||
func TestNewSafeDNSProvider(t *testing.T) {
|
||||
_ = os.Setenv("SAFEDNS_TOKEN", "DUMMYVALUE")
|
||||
_, err := NewSafeDNSProvider(endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true)
|
||||
require.NoError(t, err)
|
||||
|
||||
_ = os.Unsetenv("SAFEDNS_TOKEN")
|
||||
_, err = NewSafeDNSProvider(endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRecords(t *testing.T) {
|
||||
mockSafeDNSService := MockSafeDNSService{}
|
||||
|
||||
provider := &SafeDNSProvider{
|
||||
Client: &mockSafeDNSService,
|
||||
domainFilter: endpoint.NewDomainFilter([]string{}),
|
||||
DryRun: false,
|
||||
}
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZones",
|
||||
mock.Anything,
|
||||
).Return(createZones(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"foo.com",
|
||||
mock.Anything,
|
||||
).Return(createFooRecords(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"bar.io",
|
||||
mock.Anything,
|
||||
).Return(createBarRecords(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"baz.org",
|
||||
mock.Anything,
|
||||
).Return(createBazRecords(), nil).Once()
|
||||
|
||||
actual, err := provider.Records(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
Targets: []string{"targetFoo"},
|
||||
RecordType: "A",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
Targets: []string{"text"},
|
||||
RecordType: "TXT",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "baz.org",
|
||||
Targets: []string{"targetBaz"},
|
||||
RecordType: "A",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "baz.org",
|
||||
Targets: []string{"text"},
|
||||
RecordType: "TXT",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "api.baz.org",
|
||||
Targets: []string{"targetBazAPI"},
|
||||
RecordType: "A",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "api.baz.org",
|
||||
Targets: []string{"text"},
|
||||
RecordType: "TXT",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
}
|
||||
|
||||
mockSafeDNSService.AssertExpectations(t)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestSafeDNSApplyChanges(t *testing.T) {
|
||||
mockSafeDNSService := MockSafeDNSService{}
|
||||
|
||||
provider := &SafeDNSProvider{
|
||||
Client: &mockSafeDNSService,
|
||||
domainFilter: endpoint.NewDomainFilter([]string{}),
|
||||
DryRun: false,
|
||||
}
|
||||
|
||||
// Dummy data
|
||||
mockSafeDNSService.On(
|
||||
"GetZones",
|
||||
mock.Anything,
|
||||
).Return(createZones(), nil).Once()
|
||||
mockSafeDNSService.On(
|
||||
"GetZones",
|
||||
mock.Anything,
|
||||
).Return(createZones(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"foo.com",
|
||||
mock.Anything,
|
||||
).Return(createFooRecords(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"bar.io",
|
||||
mock.Anything,
|
||||
).Return(createBarRecords(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"baz.org",
|
||||
mock.Anything,
|
||||
).Return(createBazRecords(), nil).Once()
|
||||
|
||||
// Apply actions
|
||||
mockSafeDNSService.On(
|
||||
"DeleteZoneRecord",
|
||||
"baz.org",
|
||||
33,
|
||||
).Return(nil).Once()
|
||||
mockSafeDNSService.On(
|
||||
"DeleteZoneRecord",
|
||||
"baz.org",
|
||||
34,
|
||||
).Return(nil).Once()
|
||||
|
||||
TTL300 := safedns.RecordTTL(300)
|
||||
mockSafeDNSService.On(
|
||||
"PatchZoneRecord",
|
||||
"foo.com",
|
||||
11,
|
||||
safedns.PatchRecordRequest{
|
||||
Type: "A",
|
||||
Name: "foo.com",
|
||||
Content: "targetFoo",
|
||||
TTL: &TTL300,
|
||||
},
|
||||
).Return(123, nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"CreateZoneRecord",
|
||||
"bar.io",
|
||||
safedns.CreateRecordRequest{
|
||||
Type: "A",
|
||||
Name: "create.bar.io",
|
||||
Content: "targetBar",
|
||||
},
|
||||
).Return(246, nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"CreateZoneRecord",
|
||||
"bar.io",
|
||||
safedns.CreateRecordRequest{
|
||||
Type: "A",
|
||||
Name: "bar.io",
|
||||
Content: "targetBar",
|
||||
},
|
||||
).Return(369, nil).Once()
|
||||
|
||||
err := provider.ApplyChanges(context.Background(), &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "create.bar.io",
|
||||
RecordType: "A",
|
||||
Targets: []string{"targetBar"},
|
||||
RecordTTL: 3600,
|
||||
},
|
||||
{
|
||||
DNSName: "bar.io",
|
||||
RecordType: "A",
|
||||
Targets: []string{"targetBar"},
|
||||
RecordTTL: 3600,
|
||||
},
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "api.baz.org",
|
||||
RecordType: "A",
|
||||
},
|
||||
{
|
||||
DNSName: "api.baz.org",
|
||||
RecordType: "TXT",
|
||||
},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
RecordType: "A",
|
||||
RecordTTL: 300,
|
||||
Targets: []string{"targetFoo"},
|
||||
},
|
||||
},
|
||||
UpdateOld: []*endpoint.Endpoint{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockSafeDNSService.AssertExpectations(t)
|
||||
}
|
Loading…
Reference in New Issue
Block a user