mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
Merge pull request #2923 from pluralsh/plural-provider-rebase
feat: Add Plural DNS provider
This commit is contained in:
commit
a763843764
@ -59,6 +59,7 @@ ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchroniz
|
||||
* [IBM Cloud DNS](https://www.ibm.com/cloud/dns)
|
||||
* [TencentCloud PrivateDNS](https://cloud.tencent.com/product/privatedns)
|
||||
* [TencentCloud DNSPod](https://cloud.tencent.com/product/cns)
|
||||
* [Plural](https://www.plural.sh/)
|
||||
|
||||
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.
|
||||
|
||||
@ -118,6 +119,7 @@ The following table clarifies the current status of the providers according to t
|
||||
| SafeDNS | Alpha | @assureddt |
|
||||
| IBMCloud | Alpha | @hughhuangzh |
|
||||
| TencentCloud | Alpha | @Hyzhou |
|
||||
| Plural | Alpha | @michaeljguarino |
|
||||
|
||||
## Kubernetes version compatibility
|
||||
|
||||
@ -187,6 +189,7 @@ The following tutorials are provided:
|
||||
* [IBM Cloud](docs/tutorials/ibmcloud.md)
|
||||
* [Nodes as source](docs/tutorials/nodes.md)
|
||||
* [TencentCloud](docs/tutorials/tencentcloud.md)
|
||||
* [Plural](docs/tutorials/plural.md)
|
||||
|
||||
### Running Locally
|
||||
|
||||
|
197
docs/tutorials/plural.md
Normal file
197
docs/tutorials/plural.md
Normal file
@ -0,0 +1,197 @@
|
||||
# Setting up ExternalDNS for Services on Plural
|
||||
|
||||
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Plural DNS.
|
||||
|
||||
Make sure to use **>=0.12.3** version of ExternalDNS for this tutorial.
|
||||
|
||||
## Creating Plural Credentials
|
||||
|
||||
A secret containing the a Plural access token is needed for this provider. You can get a token for your user [here](https://app.plural.sh/profile/tokens).
|
||||
|
||||
To create the secret you can run `kubectl create secret generic plural-env --from-literal=PLURAL_ACCESS_TOKEN=<replace-with-your-access-token>`.
|
||||
|
||||
## Deploy ExternalDNS
|
||||
|
||||
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
|
||||
Then apply one of the following manifests file to deploy ExternalDNS.
|
||||
|
||||
### Manifest (for clusters without RBAC enabled)
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
|
||||
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=plural
|
||||
- --plural-cluster=example-plural-cluster
|
||||
- --plural-provider=aws # gcp, azure, equinix and kind are also possible
|
||||
env:
|
||||
- name: PLURAL_ACCESS_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: PLURAL_ACCESS_TOKEN
|
||||
name: plural-env
|
||||
- name: PLURAL_ENDPOINT # (optional) use an alternative endpoint for Plural; defaults to https://app.plural.sh
|
||||
value: https://app.plural.sh
|
||||
```
|
||||
|
||||
### 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", "watch"]
|
||||
---
|
||||
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:
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
|
||||
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=plural
|
||||
- --plural-cluster=example-plural-cluster
|
||||
- --plural-provider=aws # gcp, azure, equinix and kind are also possible
|
||||
env:
|
||||
- name: PLURAL_ACCESS_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
key: PLURAL_ACCESS_TOKEN
|
||||
name: plural-env
|
||||
- name: PLURAL_ENDPOINT # (optional) use an alternative endpoint for Plural; defaults to https://app.plural.sh
|
||||
value: https://app.plural.sh
|
||||
```
|
||||
|
||||
## Deploying an Nginx Service
|
||||
|
||||
Create a service file called 'nginx.yaml' with the following contents:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/hostname: example.com
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
```
|
||||
|
||||
Note the annotation on the service; use the same hostname as the Plural DNS zone created above. The annotation may also be a subdomain
|
||||
of the DNS zone (e.g. 'www.example.com').
|
||||
|
||||
By setting the TTL annotation on the service, you have to pass a valid TTL, which must be 120 or above.
|
||||
This annotation is optional, if you won't set it, it will be 1 (automatic) which is 300.
|
||||
|
||||
ExternalDNS uses this annotation to determine what services should be registered with DNS. Removing the annotation
|
||||
will cause ExternalDNS to remove the corresponding DNS records.
|
||||
|
||||
Create the deployment and service:
|
||||
|
||||
```
|
||||
$ kubectl create -f nginx.yaml
|
||||
```
|
||||
|
||||
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 Plural DNS records.
|
||||
|
||||
## Verifying Plural DNS records
|
||||
|
||||
Check your [Plural domain overview](https://app.plural.sh/account/domains) to view the domains associated with your Plural account. There you can view the records for each domain.
|
||||
|
||||
The records 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 Plural DNS records, we can delete the tutorial's example:
|
||||
|
||||
```
|
||||
$ kubectl delete -f nginx.yaml
|
||||
$ kubectl delete -f externaldns.yaml
|
17
go.mod
17
go.mod
@ -60,7 +60,7 @@ require (
|
||||
go.uber.org/ratelimit v0.2.0
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
|
||||
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
|
||||
google.golang.org/api v0.93.0
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@ -153,13 +153,12 @@ require (
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.19.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220804142021-4e6b2dfa6612 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
@ -181,9 +180,12 @@ require (
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
require github.com/pluralsh/gqlclient v1.1.6
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/Yamashou/gqlgenc v0.11.0 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
@ -192,7 +194,12 @@ require (
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.8.6 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.0 // indirect
|
||||
)
|
||||
|
||||
replace k8s.io/klog/v2 => github.com/Raffo/knolog v0.0.0-20211016155154-e4d5e0cc970a
|
||||
|
32
go.sum
32
go.sum
@ -160,6 +160,8 @@ github.com/StackExchange/dnscontrol v0.2.8 h1:7jviqDH9cIqRSRpH0UxgmpT7a8CwEhs9mL
|
||||
github.com/StackExchange/dnscontrol v0.2.8/go.mod h1:BH+5nX50JxHDdb3+AD/z/UfYMCc7iaqEkRtQ+NjcFGE=
|
||||
github.com/Venafi/vcert/v4 v4.14.3/go.mod h1:IL+6LA8QRWZbmcMzIr/vRhf9Aa6XDM2cQO50caWevjA=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/Yamashou/gqlgenc v0.11.0 h1:y6I7CDrUdY4JBxfwss9168HTP5k/CdExLV5+YPG/3nY=
|
||||
github.com/Yamashou/gqlgenc v0.11.0/go.mod h1:OeQhghEgvGWvRwzx9XjMeg3FUQOHnTo5/12iuJSJxLg=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/ahmetb/gen-crd-api-reference-docs v0.2.1-0.20201224172655-df869c1245d4/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8=
|
||||
@ -190,6 +192,7 @@ github.com/aliyun/alibaba-cloud-sdk-go v1.61.1742 h1:1pBiWcgrwB5LN52SXAn6rHN3dIB
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1742/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
|
||||
github.com/ans-group/go-durationstring v1.2.0 h1:UJIuQATkp0t1rBvZsHRwki33YHV9E+Ulro+3NbMB7MM=
|
||||
@ -912,6 +915,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
|
||||
github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
@ -998,6 +1002,8 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
@ -1019,6 +1025,8 @@ github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
|
||||
github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
@ -1178,6 +1186,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pluralsh/gqlclient v1.1.6 h1:lj6KVfUET9DPyB95+T+hXtCGqqOm+TXOB3jYzuBX1Zc=
|
||||
github.com/pluralsh/gqlclient v1.1.6/go.mod h1:qcE4KD7hBGl/JFCoXXy8zgUP0mWHJsAM6bhDAf592AE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
@ -1236,6 +1246,8 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@ -1263,6 +1275,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f h1:WSnaD0/cvbKJgSTYbjAPf4RJXVvNNDAwVm+W8wEmnGE=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
|
||||
github.com/schollz/progressbar/v3 v3.8.6 h1:QruMUdzZ1TbEP++S1m73OqRJk20ON11m6Wqv4EoGg8c=
|
||||
github.com/schollz/progressbar/v3 v3.8.6/go.mod h1:W5IEwbJecncFGBvuEh4A7HT1nZZ6WNIL2i3qbnI0WKY=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
@ -1379,6 +1393,8 @@ github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/vektah/gqlparser/v2 v2.5.0 h1:GwEwy7AJsqPWrey0bHnn+3JLaHLZVT66wY/+O+Tf9SU=
|
||||
github.com/vektah/gqlparser/v2 v2.5.0/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs=
|
||||
github.com/vektra/mockery/v2 v2.10.0/go.mod h1:m/WO2UzWzqgVX3nvqpRQq70I4Z7jbSCRhdmkgtp+Ab4=
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556 h1:UbVjBjgJUYGD8MlobEdOR+yTeNqaNa2Gf1/nskVNCSE=
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg=
|
||||
@ -1524,6 +1540,7 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
@ -1578,8 +1595,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -1695,8 +1712,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180425194835-bb9c189858d9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -1822,8 +1840,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@ -1942,8 +1960,8 @@ golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyj
|
||||
golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
5
main.go
5
main.go
@ -59,6 +59,7 @@ import (
|
||||
"sigs.k8s.io/external-dns/provider/oci"
|
||||
"sigs.k8s.io/external-dns/provider/ovh"
|
||||
"sigs.k8s.io/external-dns/provider/pdns"
|
||||
"sigs.k8s.io/external-dns/provider/plural"
|
||||
"sigs.k8s.io/external-dns/provider/rcode0"
|
||||
"sigs.k8s.io/external-dns/provider/rdns"
|
||||
"sigs.k8s.io/external-dns/provider/rfc2136"
|
||||
@ -335,7 +336,9 @@ func main() {
|
||||
p, err = ibmcloud.NewIBMCloudProvider(cfg.IBMCloudConfigFile, domainFilter, zoneIDFilter, endpointsSource, cfg.IBMCloudProxied, cfg.DryRun)
|
||||
case "safedns":
|
||||
p, err = safedns.NewSafeDNSProvider(domainFilter, cfg.DryRun)
|
||||
case "tencentcloud":
|
||||
case "plural":
|
||||
p, err = plural.NewPluralProvider(cfg.PluralCluster, cfg.PluralProvider)
|
||||
case "tencentcloud":
|
||||
p, err = tencentcloud.NewTencentCloudProvider(domainFilter, zoneIDFilter, cfg.TencentCloudConfigFile, cfg.TencentCloudZoneType, cfg.DryRun)
|
||||
default:
|
||||
log.Fatalf("unknown dns provider: %s", cfg.Provider)
|
||||
|
@ -194,6 +194,8 @@ type Config struct {
|
||||
IBMCloudConfigFile string
|
||||
TencentCloudConfigFile string
|
||||
TencentCloudZoneType string
|
||||
PluralCluster string
|
||||
PluralProvider string
|
||||
}
|
||||
|
||||
var defaultConfig = &Config{
|
||||
@ -331,6 +333,8 @@ var defaultConfig = &Config{
|
||||
IBMCloudConfigFile: "/etc/kubernetes/ibmcloud.json",
|
||||
TencentCloudConfigFile: "/etc/kubernetes/tencent-cloud.json",
|
||||
TencentCloudZoneType: "",
|
||||
PluralCluster: "",
|
||||
PluralProvider: "",
|
||||
}
|
||||
|
||||
// NewConfig returns new Config object
|
||||
@ -420,7 +424,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("exclude-target-net", "Exclude target nets (optional)").StringsVar(&cfg.ExcludeTargetNets)
|
||||
|
||||
// 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, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, ibmcloud, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns, tencentcloud)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "ibmcloud", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns", "tencentcloud")
|
||||
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, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, ibmcloud, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns, tencentcloud)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "ibmcloud", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns", "tencentcloud", "plural")
|
||||
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)
|
||||
@ -534,6 +538,10 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("transip-account", "When using the TransIP provider, specify the account name (required when --provider=transip)").Default(defaultConfig.TransIPAccountName).StringVar(&cfg.TransIPAccountName)
|
||||
app.Flag("transip-keyfile", "When using the TransIP provider, specify the path to the private key file (required when --provider=transip)").Default(defaultConfig.TransIPPrivateKeyFile).StringVar(&cfg.TransIPPrivateKeyFile)
|
||||
|
||||
// Flags related to the Plural provider
|
||||
app.Flag("plural-cluster", "When using the plural provider, specify the cluster name you're running with").Default(defaultConfig.PluralCluster).StringVar(&cfg.PluralCluster)
|
||||
app.Flag("plural-provider", "When using the plural provider, specify the provider name you're running with").Default(defaultConfig.PluralProvider).StringVar(&cfg.PluralProvider)
|
||||
|
||||
// Flags related to policies
|
||||
app.Flag("policy", "Modify how DNS records are synchronized between sources and providers (default: sync, options: sync, upsert-only, create-only)").Default(defaultConfig.Policy).EnumVar(&cfg.Policy, "sync", "upsert-only", "create-only")
|
||||
|
||||
|
130
provider/plural/client.go
Normal file
130
provider/plural/client.go
Normal file
@ -0,0 +1,130 @@
|
||||
package plural
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/pluralsh/gqlclient"
|
||||
"github.com/pluralsh/gqlclient/pkg/utils"
|
||||
)
|
||||
|
||||
type authedTransport struct {
|
||||
key string
|
||||
wrapped http.RoundTripper
|
||||
}
|
||||
|
||||
func (t *authedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req.Header.Set("Authorization", "Bearer "+t.key)
|
||||
return t.wrapped.RoundTrip(req)
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
DnsRecords() ([]*DnsRecord, error)
|
||||
CreateRecord(record *DnsRecord) (*DnsRecord, error)
|
||||
DeleteRecord(name, ttype string) error
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Token string
|
||||
Endpoint string
|
||||
Cluster string
|
||||
Provider string
|
||||
}
|
||||
|
||||
type client struct {
|
||||
ctx context.Context
|
||||
pluralClient *gqlclient.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
type DnsRecord struct {
|
||||
Type string
|
||||
Name string
|
||||
Records []string
|
||||
}
|
||||
|
||||
func NewClient(conf *Config) (Client, error) {
|
||||
base, err := conf.BaseUrl()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpClient := http.Client{
|
||||
Transport: &authedTransport{
|
||||
key: conf.Token,
|
||||
wrapped: http.DefaultTransport,
|
||||
},
|
||||
}
|
||||
endpoint := base + "/gql"
|
||||
return &client{
|
||||
ctx: context.Background(),
|
||||
pluralClient: gqlclient.NewClient(&httpClient, endpoint),
|
||||
config: conf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Config) BaseUrl() (string, error) {
|
||||
host := "https://app.plural.sh"
|
||||
if c.Endpoint != "" {
|
||||
host = fmt.Sprintf("https://%s", c.Endpoint)
|
||||
if _, err := url.Parse(host); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return host, nil
|
||||
}
|
||||
|
||||
func (client *client) DnsRecords() ([]*DnsRecord, error) {
|
||||
resp, err := client.pluralClient.GetDNSRecords(client.ctx, client.config.Cluster, gqlclient.Provider(strings.ToUpper(client.config.Provider)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]*DnsRecord, 0)
|
||||
for _, edge := range resp.DNSRecords.Edges {
|
||||
if edge.Node != nil {
|
||||
record := &DnsRecord{
|
||||
Type: string(edge.Node.Type),
|
||||
Name: edge.Node.Name,
|
||||
Records: utils.ConvertStringArrayPointer(edge.Node.Records),
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (client *client) CreateRecord(record *DnsRecord) (*DnsRecord, error) {
|
||||
provider := gqlclient.Provider(strings.ToUpper(client.config.Provider))
|
||||
cluster := client.config.Cluster
|
||||
attr := gqlclient.DNSRecordAttributes{
|
||||
Name: record.Name,
|
||||
Type: gqlclient.DNSRecordType(record.Type),
|
||||
Records: []*string{},
|
||||
}
|
||||
|
||||
for _, record := range record.Records {
|
||||
attr.Records = append(attr.Records, &record)
|
||||
}
|
||||
|
||||
resp, err := client.pluralClient.CreateDNSRecord(client.ctx, cluster, provider, attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DnsRecord{
|
||||
Type: string(resp.CreateDNSRecord.Type),
|
||||
Name: resp.CreateDNSRecord.Name,
|
||||
Records: utils.ConvertStringArrayPointer(resp.CreateDNSRecord.Records),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (client *client) DeleteRecord(name, ttype string) error {
|
||||
if _, err := client.pluralClient.DeleteDNSRecord(client.ctx, name, gqlclient.DNSRecordType(ttype)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
144
provider/plural/plural.go
Normal file
144
provider/plural/plural.go
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
Copyright 2022 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 plural
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
CreateAction = "c"
|
||||
DeleteAction = "d"
|
||||
)
|
||||
|
||||
type PluralProvider struct {
|
||||
provider.BaseProvider
|
||||
Client Client
|
||||
}
|
||||
|
||||
type RecordChange struct {
|
||||
Action string
|
||||
Record *DnsRecord
|
||||
}
|
||||
|
||||
func NewPluralProvider(cluster, provider string) (*PluralProvider, error) {
|
||||
token := os.Getenv("PLURAL_ACCESS_TOKEN")
|
||||
endpoint := os.Getenv("PLURAL_ENDPOINT")
|
||||
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("No plural access token provided, you must set the PLURAL_ACCESS_TOKEN env var")
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Token: token,
|
||||
Endpoint: endpoint,
|
||||
Cluster: cluster,
|
||||
Provider: provider,
|
||||
}
|
||||
|
||||
client, err := NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prov := &PluralProvider{
|
||||
Client: client,
|
||||
}
|
||||
|
||||
return prov, nil
|
||||
}
|
||||
|
||||
func (p *PluralProvider) Records(_ context.Context) (endpoints []*endpoint.Endpoint, err error) {
|
||||
records, err := p.Client.DnsRecords()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
endpoints = make([]*endpoint.Endpoint, len(records))
|
||||
for i, record := range records {
|
||||
endpoints[i] = endpoint.NewEndpoint(record.Name, record.Type, record.Records...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *PluralProvider) PropertyValuesEqual(name, previous, current string) bool {
|
||||
return p.BaseProvider.PropertyValuesEqual(name, previous, current)
|
||||
}
|
||||
|
||||
func (p *PluralProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func (p *PluralProvider) ApplyChanges(_ context.Context, diffs *plan.Changes) error {
|
||||
var changes []*RecordChange
|
||||
for _, endpoint := range diffs.Create {
|
||||
changes = append(changes, makeChange(CreateAction, endpoint.Targets, endpoint))
|
||||
}
|
||||
|
||||
for _, desired := range diffs.UpdateNew {
|
||||
changes = append(changes, makeChange(CreateAction, desired.Targets, desired))
|
||||
}
|
||||
|
||||
for _, deleted := range diffs.Delete {
|
||||
changes = append(changes, makeChange(DeleteAction, []string{}, deleted))
|
||||
}
|
||||
|
||||
return p.applyChanges(changes)
|
||||
}
|
||||
|
||||
func makeChange(change string, target []string, endpoint *endpoint.Endpoint) *RecordChange {
|
||||
return &RecordChange{
|
||||
Action: change,
|
||||
Record: &DnsRecord{
|
||||
Name: endpoint.DNSName,
|
||||
Type: endpoint.RecordType,
|
||||
Records: target,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PluralProvider) applyChanges(changes []*RecordChange) error {
|
||||
for _, change := range changes {
|
||||
logFields := log.Fields{
|
||||
"name": change.Record.Name,
|
||||
"type": change.Record.Type,
|
||||
"action": change.Action,
|
||||
}
|
||||
log.WithFields(logFields).Info("Changing record.")
|
||||
|
||||
if change.Action == CreateAction {
|
||||
_, err := p.Client.CreateRecord(change.Record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if change.Action == DeleteAction {
|
||||
if err := p.Client.DeleteRecord(change.Record.Name, change.Record.Type); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
381
provider/plural/plural_test.go
Normal file
381
provider/plural/plural_test.go
Normal file
@ -0,0 +1,381 @@
|
||||
/*
|
||||
Copyright 2022 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 plural
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type ClientStub struct {
|
||||
mockDnsRecords []*DnsRecord
|
||||
}
|
||||
|
||||
// CreateRecord provides a mock function with given fields: record
|
||||
func (c *ClientStub) CreateRecord(record *DnsRecord) (*DnsRecord, error) {
|
||||
c.mockDnsRecords = append(c.mockDnsRecords, record)
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// DeleteRecord provides a mock function with given fields: name, ttype
|
||||
func (c *ClientStub) DeleteRecord(name string, ttype string) error {
|
||||
newRecords := make([]*DnsRecord, 0)
|
||||
for _, record := range c.mockDnsRecords {
|
||||
if record.Name == name && record.Type == ttype {
|
||||
continue
|
||||
}
|
||||
newRecords = append(newRecords, record)
|
||||
}
|
||||
c.mockDnsRecords = newRecords
|
||||
return nil
|
||||
}
|
||||
|
||||
// DnsRecords provides a mock function with given fields:
|
||||
func (c *ClientStub) DnsRecords() ([]*DnsRecord, error) {
|
||||
return c.mockDnsRecords, nil
|
||||
}
|
||||
|
||||
func newPluralProvider(pluralDNSRecord []*DnsRecord) *PluralProvider {
|
||||
if pluralDNSRecord == nil {
|
||||
pluralDNSRecord = make([]*DnsRecord, 0)
|
||||
}
|
||||
return &PluralProvider{
|
||||
BaseProvider: provider.BaseProvider{},
|
||||
Client: &ClientStub{
|
||||
mockDnsRecords: pluralDNSRecord,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluralRecords(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedEndpoints []*endpoint.Endpoint
|
||||
records []*DnsRecord
|
||||
}{
|
||||
{
|
||||
name: "check records",
|
||||
records: []*DnsRecord{
|
||||
{
|
||||
Type: endpoint.RecordTypeA,
|
||||
Name: "example.com",
|
||||
Records: []string{"123.123.123.122"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeA,
|
||||
Name: "nginx.example.com",
|
||||
Records: []string{"123.123.123.123"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Name: "hack.example.com",
|
||||
Records: []string{"bluecatnetworks.com"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Name: "kdb.example.com",
|
||||
Records: []string{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
||||
},
|
||||
},
|
||||
expectedEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.122"},
|
||||
},
|
||||
{
|
||||
DNSName: "nginx.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.123"},
|
||||
},
|
||||
{
|
||||
DNSName: "hack.example.com",
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Targets: endpoint.Targets{"bluecatnetworks.com"},
|
||||
},
|
||||
{
|
||||
DNSName: "kdb.example.com",
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
provider := newPluralProvider(test.records)
|
||||
|
||||
actual, err := provider.Records(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
validateEndpoints(t, actual, test.expectedEndpoints)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPluralApplyChangesCreate(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedEndpoints []*endpoint.Endpoint
|
||||
}{
|
||||
{
|
||||
name: "create new endpoints",
|
||||
expectedEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.122"},
|
||||
},
|
||||
{
|
||||
DNSName: "nginx.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.123"},
|
||||
},
|
||||
{
|
||||
DNSName: "hack.example.com",
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Targets: endpoint.Targets{"bluecatnetworks.com"},
|
||||
},
|
||||
{
|
||||
DNSName: "kdb.example.com",
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
provider := newPluralProvider(nil)
|
||||
|
||||
// no records
|
||||
actual, err := provider.Records(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, len(actual), 0, "expected no entries")
|
||||
|
||||
err = provider.ApplyChanges(context.Background(), &plan.Changes{Create: test.expectedEndpoints})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actual, err = provider.Records(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
validateEndpoints(t, actual, test.expectedEndpoints)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPluralApplyChangesDelete(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
records []*DnsRecord
|
||||
deleteEndpoints []*endpoint.Endpoint
|
||||
expectedEndpoints []*endpoint.Endpoint
|
||||
}{
|
||||
{
|
||||
name: "delete not existing record",
|
||||
records: []*DnsRecord{
|
||||
{
|
||||
Type: endpoint.RecordTypeA,
|
||||
Name: "example.com",
|
||||
Records: []string{"123.123.123.122"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeA,
|
||||
Name: "nginx.example.com",
|
||||
Records: []string{"123.123.123.123"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Name: "hack.example.com",
|
||||
Records: []string{"bluecatnetworks.com"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Name: "kdb.example.com",
|
||||
Records: []string{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
||||
},
|
||||
},
|
||||
deleteEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "fake.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
},
|
||||
},
|
||||
expectedEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.122"},
|
||||
},
|
||||
{
|
||||
DNSName: "nginx.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.123"},
|
||||
},
|
||||
{
|
||||
DNSName: "hack.example.com",
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Targets: endpoint.Targets{"bluecatnetworks.com"},
|
||||
},
|
||||
{
|
||||
DNSName: "kdb.example.com",
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete one record",
|
||||
records: []*DnsRecord{
|
||||
{
|
||||
Type: endpoint.RecordTypeA,
|
||||
Name: "example.com",
|
||||
Records: []string{"123.123.123.122"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeA,
|
||||
Name: "nginx.example.com",
|
||||
Records: []string{"123.123.123.123"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Name: "hack.example.com",
|
||||
Records: []string{"bluecatnetworks.com"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Name: "kdb.example.com",
|
||||
Records: []string{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
||||
},
|
||||
},
|
||||
deleteEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "kdb.example.com",
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
||||
},
|
||||
},
|
||||
expectedEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.122"},
|
||||
},
|
||||
{
|
||||
DNSName: "nginx.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.123"},
|
||||
},
|
||||
{
|
||||
DNSName: "hack.example.com",
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Targets: endpoint.Targets{"bluecatnetworks.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "delete all records",
|
||||
records: []*DnsRecord{
|
||||
{
|
||||
Type: endpoint.RecordTypeA,
|
||||
Name: "example.com",
|
||||
Records: []string{"123.123.123.122"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeA,
|
||||
Name: "nginx.example.com",
|
||||
Records: []string{"123.123.123.123"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Name: "hack.example.com",
|
||||
Records: []string{"bluecatnetworks.com"},
|
||||
},
|
||||
{
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Name: "kdb.example.com",
|
||||
Records: []string{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
||||
},
|
||||
},
|
||||
deleteEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "kdb.example.com",
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Targets: endpoint.Targets{"heritage=external-dns,external-dns/owner=default,external-dns/resource=service/openshift-ingress/router-default"},
|
||||
},
|
||||
{
|
||||
DNSName: "example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.122"},
|
||||
},
|
||||
{
|
||||
DNSName: "nginx.example.com",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"123.123.123.123"},
|
||||
},
|
||||
{
|
||||
DNSName: "hack.example.com",
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Targets: endpoint.Targets{"bluecatnetworks.com"},
|
||||
},
|
||||
},
|
||||
expectedEndpoints: []*endpoint.Endpoint{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
provider := newPluralProvider(test.records)
|
||||
|
||||
err := provider.ApplyChanges(context.Background(), &plan.Changes{Delete: test.deleteEndpoints})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
actual, err := provider.Records(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
validateEndpoints(t, actual, test.expectedEndpoints)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func validateEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) {
|
||||
assert.True(t, testutils.SameEndpoints(endpoints, expected), "expected and actual endpoints don't match. %s:%s", endpoints, expected)
|
||||
}
|
Loading…
Reference in New Issue
Block a user