Merge branch 'master' into fix-alibaba-domain-filter

This commit is contained in:
Stanley Chen 2022-11-06 09:49:10 +08:00 committed by GitHub
commit 29ce78decd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1706 additions and 61 deletions

View File

@ -6,9 +6,16 @@ on:
pull_request:
branches: [ master ]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
permissions:
contents: read # to fetch code (actions/checkout)
checks: write # to create a new check based on the results (shogo82148/actions-goveralls)
name: Build
runs-on: ubuntu-latest
steps:

View File

@ -5,8 +5,12 @@ on:
tags:
- "v*"
permissions: {}
jobs:
release_docs:
permissions:
contents: write # for mike to push
name: Release Docs
runs-on: ubuntu-latest
steps:

View File

@ -38,7 +38,7 @@ jobs:
python-version: "3.x"
- name: Set-up chart-testing
uses: helm/chart-testing-action@dae259e86a35ff09145c0805e2d7dd3f7207064a
uses: helm/chart-testing-action@afea100a513515fbd68b0e72a7bb0ae34cb62aec
- name: Run chart-testing (list-changed)
id: list-changed

View File

@ -7,8 +7,13 @@ on:
paths:
- "charts/external-dns/Chart.yaml"
permissions: {}
jobs:
release:
permissions:
contents: write # to push chart release and create a release (helm/chart-releaser-action)
if: github.repository == 'kubernetes-sigs/external-dns'
runs-on: ubuntu-latest
defaults:
@ -53,7 +58,7 @@ jobs:
version: latest
- name: Run chart-releaser
uses: helm/chart-releaser-action@a3454e46a6f5ac4811069a381e646961dda2e1bf
uses: helm/chart-releaser-action@98bccfd32b0f76149d188912ac8e45ddd3f8695f
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
CR_RELEASE_NAME_TEMPLATE: "external-dns-helm-chart-{{ .Version }}"

View File

@ -1,6 +1,10 @@
name: trivy vulnerability scanner
on:
push:
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
name: Build

View File

@ -41,6 +41,7 @@ ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchroniz
* [AWS Cloud Map](https://docs.aws.amazon.com/cloud-map/)
* [AzureDNS](https://azure.microsoft.com/en-us/services/dns)
* [BlueCat](https://bluecatnetworks.com)
* [Civo](https://www.civo.com)
* [CloudFlare](https://www.cloudflare.com/dns)
* [RcodeZero](https://www.rcodezero.at/)
* [DigitalOcean](https://www.digitalocean.com/products/networking)
@ -101,6 +102,7 @@ The following table clarifies the current status of the providers according to t
| Akamai Edge DNS | Beta | |
| AzureDNS | Beta | |
| BlueCat | Alpha | @seanmalloy @vinny-sabatini |
| Civo | Alpha | @alejandrojnm |
| CloudFlare | Beta | |
| RcodeZero | Alpha | |
| DigitalOcean | Alpha | |
@ -160,6 +162,7 @@ The following tutorials are provided:
* [Kube Ingress AWS Controller](docs/tutorials/kube-ingress-aws.md)
* [Azure DNS](docs/tutorials/azure.md)
* [Azure Private DNS](docs/tutorials/azure-private-dns.md)
* [Civo](docs/tutorials/civo.md)
* [Cloudflare](docs/tutorials/cloudflare.md)
* [BlueCat](docs/tutorials/bluecat.md)
* [CoreDNS](docs/tutorials/coredns.md)

187
docs/tutorials/civo.md Normal file
View File

@ -0,0 +1,187 @@
# Setting up ExternalDNS for Services on Civo
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Civo DNS Manager.
Make sure to use **>0.12.2** version of ExternalDNS for this tutorial.
## Managing DNS with Civo
If you want to learn about how to use Civo DNS Manager read the following tutorials:
[An Introduction to Managing DNS](https://www.civo.com/learn/configure-dns)
## Get Civo Token
Copy the token in the settings fo your account
The environment variable `CIVO_TOKEN` will be needed to run ExternalDNS with Civo.
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
Then apply one of the following manifests file to deploy ExternalDNS.
### Manifest (for clusters without RBAC enabled)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.12.2
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=civo
env:
- name: CIVO_TOKEN
value: "YOUR_CIVO_API_TOKEN"
```
### 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","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.12.2
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=civo
env:
- name: CIVO_TOKEN
value: "YOUR_CIVO_API_TOKEN"
```
## 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 the same hostname as the Civo 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 Civo DNS records.
## Verifying Civo DNS records
Check your [Civo UI](https://www.civo.com/account/dns) to view the records for your Civo 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 Civo DNS records, we can delete the tutorial's example:
```
$ kubectl delete service -f nginx.yaml
$ kubectl delete service -f externaldns.yaml
```

View File

@ -1,7 +1,7 @@
# Setting up ExternalDNS for Tencent Cloud
## External Dns Version
* Make sure to use **>=1.7.2** version of ExternalDNS for this tutorial
* Make sure to use **>=0.13.1** version of ExternalDNS for this tutorial
## Set up PrivateDns or DNSPod
@ -101,7 +101,8 @@ data:
"regionId": "ap-shanghai",
"secretId": "******",
"secretKey": "******",
"vpcId": "vpc-******"
"vpcId": "vpc-******",
"internetEndpoint": false # Default: false. Access the Tencent API through the intranet. If you need to deploy on the public network, you need to change to true
}
---
apiVersion: apps/v1
@ -128,7 +129,7 @@ spec:
- --policy=sync # set `upsert-only` would prevent ExternalDNS from deleting any records
- --tencent-cloud-zone-type=private # only look at private hosted zones. set `public` to use the public dns service.
- --tencent-cloud-config-file=/etc/kubernetes/tencent-cloud.json
image: k8s.gcr.io/external-dns/external-dns:v1.7.2
image: registry.k8s.io/external-dns/external-dns:v0.13.1
imagePullPolicy: Always
name: external-dns
resources: {}
@ -139,6 +140,11 @@ spec:
name: config-volume
readOnly: true
dnsPolicy: ClusterFirst
hostAliases:
- hostnames:
- privatedns.internal.tencentcloudapi.com
- dnspod.internal.tencentcloudapi.com
ip: 169.254.0.95
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}

View File

@ -73,9 +73,11 @@ type DomainFilter struct {
// prepareFilters provides consistent trimming for filters/exclude params
func prepareFilters(filters []string) []string {
fs := make([]string, len(filters))
for i, domain := range filters {
fs[i] = strings.ToLower(strings.TrimSuffix(strings.TrimSpace(domain), "."))
var fs []string
for _, filter := range filters {
if domain := strings.ToLower(strings.TrimSuffix(strings.TrimSpace(filter), ".")); domain != "" {
fs = append(fs, domain)
}
}
return fs
}
@ -98,7 +100,7 @@ func NewRegexDomainFilter(regexDomainFilter *regexp.Regexp, regexDomainExclusion
// Match checks whether a domain can be found in the DomainFilter.
// RegexFilter takes precedence over Filters
func (df DomainFilter) Match(domain string) bool {
if df.regex != nil && df.regex.String() != "" {
if df.regex != nil && df.regex.String() != "" || df.regexExclusion != nil && df.regexExclusion.String() != "" {
return matchRegex(df.regex, df.regexExclusion, domain)
}
@ -113,12 +115,13 @@ func matchFilter(filters []string, domain string, emptyval bool) bool {
return emptyval
}
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
for _, filter := range filters {
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
if filter == "" {
return emptyval
} else if strings.HasPrefix(filter, ".") && strings.HasSuffix(strippedDomain, filter) {
continue
}
if strings.HasPrefix(filter, ".") && strings.HasSuffix(strippedDomain, filter) {
return true
} else if strings.Count(strippedDomain, ".") == strings.Count(filter, ".") {
if strippedDomain == filter {
@ -146,35 +149,32 @@ func matchRegex(regex *regexp.Regexp, negativeRegex *regexp.Regexp, domain strin
// MatchParent checks wether DomainFilter matches a given parent domain.
func (df DomainFilter) MatchParent(domain string) bool {
if !df.IsConfigured() {
if matchFilter(df.exclude, domain, false) {
return false
}
if len(df.Filters) == 0 {
return true
}
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
for _, filter := range df.Filters {
if strings.HasPrefix(filter, ".") {
if filter == "" || strings.HasPrefix(filter, ".") {
// We don't check parents if the filter is prefixed with "."
continue
}
if filter == "" {
return true
}
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
if strings.HasSuffix(filter, "."+strippedDomain) && !matchFilter(df.exclude, domain, false) {
if strings.HasSuffix(filter, "."+strippedDomain) {
return true
}
}
return false
}
// IsConfigured returns true if DomainFilter is configured, false otherwise
// IsConfigured returns true if any inclusion or exclusion rules have been specified.
func (df DomainFilter) IsConfigured() bool {
if df.regex != nil && df.regex.String() != "" {
return true
} else if len(df.Filters) == 1 {
return df.Filters[0] != ""
} else if df.regexExclusion != nil && df.regexExclusion.String() != "" {
return true
}
return len(df.Filters) > 0
return len(df.Filters) > 0 || len(df.exclude) > 0
}

View File

@ -379,15 +379,15 @@ func TestPrepareFiltersStripsWhitespaceAndDotSuffix(t *testing.T) {
}{
{
[]string{},
[]string{},
nil,
},
{
[]string{""},
[]string{""},
nil,
},
{
[]string{" ", " ", ""},
[]string{"", "", ""},
nil,
},
{
[]string{" foo ", " bar. ", "baz."},
@ -429,7 +429,7 @@ func TestDomainFilterIsConfigured(t *testing.T) {
{
[]string{"", ""},
[]string{""},
true,
false,
},
{
[]string{" . "},
@ -446,6 +446,11 @@ func TestDomainFilterIsConfigured(t *testing.T) {
[]string{" thisdoesntmatter.com "},
true,
},
{
[]string{""},
[]string{" thisdoesntmatter.com "},
true,
},
} {
t.Run("test IsConfigured", func(t *testing.T) {
df := NewDomainFilterWithExclusions(tt.filters, tt.exclude)

17
go.mod
View File

@ -8,16 +8,17 @@ require (
github.com/Azure/go-autorest/autorest v0.11.27
github.com/Azure/go-autorest/autorest/adal v0.9.20
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/IBM-Cloud/ibm-cloud-cli-sdk v0.11.0
github.com/IBM-Cloud/ibm-cloud-cli-sdk v1.0.0
github.com/IBM/go-sdk-core/v5 v5.8.0
github.com/IBM/networking-go-sdk v0.32.0
github.com/StackExchange/dnscontrol v0.2.8
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.1
github.com/alecthomas/kingpin v2.2.5+incompatible
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1742
github.com/ans-group/sdk-go v1.8.1
github.com/aws/aws-sdk-go v1.44.81
github.com/ans-group/sdk-go v1.10.4
github.com/aws/aws-sdk-go v1.44.119
github.com/bodgit/tsig v1.2.0
github.com/civo/civogo v0.3.14
github.com/cloudflare/cloudflare-go v0.50.0
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
github.com/datawire/ambassador v1.6.0
@ -60,7 +61,7 @@ require (
go.etcd.io/etcd/api/v3 v3.5.4
go.etcd.io/etcd/client/v3 v3.5.2
go.uber.org/ratelimit v0.2.0
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
golang.org/x/net v0.1.0
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
google.golang.org/api v0.93.0
@ -128,7 +129,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.0.0 // indirect
@ -173,9 +174,9 @@ require (
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.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/sys v0.1.0 // indirect
golang.org/x/term v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect

26
go.sum
View File

@ -116,8 +116,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/IBM-Cloud/ibm-cloud-cli-sdk v0.11.0 h1:75KEvjN+5lXcFzvW7RBoZY1YALJSNctcXFEfUyFz5Vo=
github.com/IBM-Cloud/ibm-cloud-cli-sdk v0.11.0/go.mod h1:+GAqrO/rFsYnhzTIxYLXCHxHVZyrtzBLyKjV6hi73YQ=
github.com/IBM-Cloud/ibm-cloud-cli-sdk v1.0.0 h1:2gzVSELk4I4ncZNrsaKI6fvZ3to60iYnig+lTFcGCEM=
github.com/IBM-Cloud/ibm-cloud-cli-sdk v1.0.0/go.mod h1:P9YNyJaJazc49fLNFG4uQ61VZVptykWqNU2vWLWcxu0=
github.com/IBM/go-sdk-core/v5 v5.8.0 h1:Bn9BxTaKYKWpd+BDpVsL6XOOJl4QDgxux4gSdWi31vE=
github.com/IBM/go-sdk-core/v5 v5.8.0/go.mod h1:+YbdhrjCHC84ls4MeBp+Hj4NZCni+tDAc0XQUqRO9Jc=
github.com/IBM/networking-go-sdk v0.32.0 h1:QWd7CxC+Wzap+zWFfXMjbqB5LpvrB1KvNtIbKrWIkhA=
@ -200,8 +200,8 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo
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=
github.com/ans-group/go-durationstring v1.2.0/go.mod h1:QGF9Mdpq9058QXaut8r55QWu6lcHX6i/GvF1PZVkV6o=
github.com/ans-group/sdk-go v1.8.1 h1:PHPcYHujevWvCMiI7ujXvvrYHf5zpHIcZw7FfJ1rXw0=
github.com/ans-group/sdk-go v1.8.1/go.mod h1:XSKXEDfKobnDtZoyia5DhJxxaDMcCjr76e1KJ9dU/xc=
github.com/ans-group/sdk-go v1.10.4 h1:wZzojt99wtVIEHs8zNQzp1Xhqme5tD5NqMM1VLmG6xQ=
github.com/ans-group/sdk-go v1.10.4/go.mod h1:XSKXEDfKobnDtZoyia5DhJxxaDMcCjr76e1KJ9dU/xc=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
@ -231,6 +231,8 @@ github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/
github.com/aws/aws-sdk-go v1.40.14/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go v1.44.81 h1:C8oBZ+a+ka0qk3Q24MohQIFq0tkbO8IAu5tfpAMKVWE=
github.com/aws/aws-sdk-go v1.44.81/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.44.119 h1:TPkpDsanBMcZaF5wHwpKhjkapRV/b7d2qdC+a+IPbmY=
github.com/aws/aws-sdk-go v1.44.119/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
@ -275,6 +277,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/civo/civogo v0.3.14 h1:W+o+hFXtEhWyJOmZOm2C5s8OEorSXGP6eyPYOa69NA8=
github.com/civo/civogo v0.3.14/go.mod h1:SbS06e0JPgIF27r1sLC97gjU1xWmONQeHgzF1hfLpak=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.20.0/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI=
@ -863,8 +867,9 @@ github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/infobloxopen/infoblox-go-client/v2 v2.1.2-0.20220407114022-6f4c71443168 h1:EXKtVoP/44ckXpw3v2/vrtMEdKx/PA+YBl+REoV27XQ=
@ -1116,6 +1121,7 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@ -1128,6 +1134,7 @@ github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
@ -1681,6 +1688,8 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1848,12 +1857,16 @@ golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/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=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1865,6 +1878,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -2251,6 +2266,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=

View File

@ -16,7 +16,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: k8s.gcr.io/external-dns/external-dns
image: registry.k8s.io/external-dns/external-dns
args:
- --source=service
- --source=ingress

View File

@ -3,7 +3,7 @@ kind: Kustomization
images:
- name: registry.k8s.io/external-dns/external-dns
newTag: v0.12.2
newTag: v0.13.1
resources:
- ./external-dns-deployment.yaml

View File

@ -41,6 +41,7 @@ import (
"sigs.k8s.io/external-dns/provider/awssd"
"sigs.k8s.io/external-dns/provider/azure"
"sigs.k8s.io/external-dns/provider/bluecat"
"sigs.k8s.io/external-dns/provider/civo"
"sigs.k8s.io/external-dns/provider/cloudflare"
"sigs.k8s.io/external-dns/provider/coredns"
"sigs.k8s.io/external-dns/provider/designate"
@ -228,6 +229,8 @@ func main() {
p, err = vultr.NewVultrProvider(ctx, domainFilter, cfg.DryRun)
case "ultradns":
p, err = ultradns.NewUltraDNSProvider(domainFilter, cfg.DryRun)
case "civo":
p, err = civo.NewCivoProvider(domainFilter, cfg.DryRun)
case "cloudflare":
p, err = cloudflare.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareZonesPerPage, cfg.CloudflareProxied, cfg.DryRun)
case "rcodezero":

View File

@ -373,7 +373,7 @@ func (p *AlibabaCloudProvider) records() ([]alidns.Record, error) {
log.Infof("Retrieving Alibaba Cloud DNS Domain Records")
var results []alidns.Record
if (len(p.domainFilter.Filters) == 1 && p.domainFilter.Filters[0] == "") || len(p.domainFilter.Filters) == 0 {
if len(p.domainFilter.Filters) == 0 {
domainNames, tmpErr := p.getDomainList()
if tmpErr != nil {
log.Errorf("AlibabaCloudProvider getDomainList error %v", tmpErr)

538
provider/civo/civo.go Normal file
View File

@ -0,0 +1,538 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package civo
import (
"context"
"fmt"
"os"
"strings"
"github.com/civo/civogo"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
)
// CivoProvider is an implementation of Provider for Civo's DNS.
type CivoProvider struct {
provider.BaseProvider
Client civogo.Client
domainFilter endpoint.DomainFilter
DryRun bool
}
// CivoChanges All API calls calculated from the plan
type CivoChanges struct {
Creates []*CivoChangeCreate
Deletes []*CivoChangeDelete
Updates []*CivoChangeUpdate
}
// CivoChangeCreate Civo Domain Record Creates
type CivoChangeCreate struct {
Domain civogo.DNSDomain
Options *civogo.DNSRecordConfig
}
// CivoChangeUpdate Civo Domain Record Updates
type CivoChangeUpdate struct {
Domain civogo.DNSDomain
DomainRecord civogo.DNSRecord
Options civogo.DNSRecordConfig
}
// CivoChangeDelete Civo Domain Record Deletes
type CivoChangeDelete struct {
Domain civogo.DNSDomain
DomainRecord civogo.DNSRecord
}
// NewCivoProvider initializes a new Civo DNS based Provider.
func NewCivoProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*CivoProvider, error) {
token, ok := os.LookupEnv("CIVO_TOKEN")
if !ok {
return nil, fmt.Errorf("no token found")
}
// Declare a default region just for the client is not used for anything else
// as the DNS API is global and not region based
region := "LON1"
civoClient, err := civogo.NewClient(token, region)
if err != nil {
return nil, err
}
userAgent := &civogo.Component{
Name: "external-dns",
Version: externaldns.Version,
}
civoClient.SetUserAgent(userAgent)
provider := &CivoProvider{
Client: *civoClient,
domainFilter: domainFilter,
DryRun: dryRun,
}
return provider, nil
}
// Zones returns the list of hosted zones.
func (p *CivoProvider) Zones(ctx context.Context) ([]civogo.DNSDomain, error) {
zones, err := p.fetchZones(ctx)
if err != nil {
return nil, err
}
return zones, nil
}
// Records returns the list of records in a given zone.
func (p *CivoProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
zones, err := p.Zones(ctx)
if err != nil {
return nil, err
}
var endpoints []*endpoint.Endpoint
for _, zone := range zones {
records, err := p.fetchRecords(ctx, zone.ID)
if err != nil {
return nil, err
}
for _, r := range records {
toUpper := strings.ToUpper(string(r.Type))
if provider.SupportedRecordType(toUpper) {
name := fmt.Sprintf("%s.%s", r.Name, zone.Name)
// root name is identified by the empty string and should be
// translated to zone name for the endpoint entry.
if r.Name == "" {
name = zone.Name
}
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(name, toUpper, endpoint.TTL(r.TTL), r.Value))
}
}
}
return endpoints, nil
}
func (p *CivoProvider) fetchRecords(ctx context.Context, domainID string) ([]civogo.DNSRecord, error) {
records, err := p.Client.ListDNSRecords(domainID)
if err != nil {
return nil, err
}
return records, nil
}
func (p *CivoProvider) fetchZones(ctx context.Context) ([]civogo.DNSDomain, error) {
var zones []civogo.DNSDomain
allZones, err := p.Client.ListDNSDomains()
if err != nil {
return nil, err
}
for _, zone := range allZones {
if !p.domainFilter.Match(zone.Name) {
continue
}
zones = append(zones, zone)
}
return zones, nil
}
// submitChanges takes a zone and a collection of Changes and sends them as a single transaction.
func (p *CivoProvider) submitChanges(ctx context.Context, changes CivoChanges) error {
for _, change := range changes.Creates {
logFields := log.Fields{
"Type": change.Options.Type,
"Name": change.Options.Name,
"Value": change.Options.Value,
"Priority": change.Options.Priority,
"TTL": change.Options.TTL,
"action": "Create",
}
log.WithFields(logFields).Info("Creating record.")
if p.DryRun {
log.WithFields(logFields).Info("Would create record.")
} else if _, err := p.Client.CreateDNSRecord(change.Domain.ID, change.Options); err != nil {
log.WithFields(logFields).Errorf(
"Failed to Create record: %v",
err,
)
}
}
for _, change := range changes.Deletes {
logFields := log.Fields{
"Type": change.DomainRecord.Type,
"Name": change.DomainRecord.Name,
"Value": change.DomainRecord.Value,
"Priority": change.DomainRecord.Priority,
"TTL": change.DomainRecord.TTL,
"action": "Delete",
}
log.WithFields(logFields).Info("Deleting record.")
if p.DryRun {
log.WithFields(logFields).Info("Would delete record.")
} else if _, err := p.Client.DeleteDNSRecord(&change.DomainRecord); err != nil {
log.WithFields(logFields).Errorf(
"Failed to Delete record: %v",
err,
)
}
}
for _, change := range changes.Updates {
logFields := log.Fields{
"Type": change.DomainRecord.Type,
"Name": change.DomainRecord.Name,
"Value": change.DomainRecord.Value,
"Priority": change.DomainRecord.Priority,
"TTL": change.DomainRecord.TTL,
"action": "Update",
}
log.WithFields(logFields).Info("Updating record.")
if p.DryRun {
log.WithFields(logFields).Info("Would update record.")
} else if _, err := p.Client.UpdateDNSRecord(&change.DomainRecord, &change.Options); err != nil {
log.WithFields(logFields).Errorf(
"Failed to Update record: %v",
err,
)
}
}
return nil
}
// processCreateActions return a list of changes to create records.
func processCreateActions(zonesByID map[string]civogo.DNSDomain, recordsByZoneID map[string][]civogo.DNSRecord, createsByZone map[string][]*endpoint.Endpoint, civoChange *CivoChanges) error {
for zoneID, creates := range createsByZone {
zone := zonesByID[zoneID]
if len(creates) == 0 {
log.WithFields(log.Fields{
"zoneID": zoneID,
"zoneName": zone.Name,
}).Info("Skipping Zone, no creates found.")
continue
}
records := recordsByZoneID[zoneID]
// Generate Create
for _, ep := range creates {
matchedRecords := getRecordID(records, zone, *ep)
if len(matchedRecords) != 0 {
log.WithFields(log.Fields{
"zoneID": zoneID,
"zoneName": zone.Name,
"dnsName": ep.DNSName,
"recordType": ep.RecordType,
}).Warn("Records found which should not exist")
}
recordType, err := convertRecordType(ep.RecordType)
if err != nil {
return err
}
for _, target := range ep.Targets {
civoChange.Creates = append(civoChange.Creates, &CivoChangeCreate{
Domain: zone,
Options: &civogo.DNSRecordConfig{
Value: target,
Name: getStrippedRecordName(zone, *ep),
Type: recordType,
Priority: 0,
TTL: int(ep.RecordTTL),
},
})
}
}
}
return nil
}
// processUpdateActions return a list of changes to update records.
func processUpdateActions(zonesByID map[string]civogo.DNSDomain, recordsByZoneID map[string][]civogo.DNSRecord, updatesByZone map[string][]*endpoint.Endpoint, civoChange *CivoChanges) error {
for zoneID, updates := range updatesByZone {
zone := zonesByID[zoneID]
if len(updates) == 0 {
log.WithFields(log.Fields{
"zoneID": zoneID,
"zoneName": zone.Name,
}).Debug("Skipping Zone, no updates found.")
continue
}
records := recordsByZoneID[zoneID]
for _, ep := range updates {
matchedRecords := getRecordID(records, zone, *ep)
if len(matchedRecords) == 0 {
log.WithFields(log.Fields{
"zoneID": zoneID,
"dnsName": ep.DNSName,
"zoneName": zone.Name,
"recordType": ep.RecordType,
}).Warn("Update Records not found.")
}
recordType, err := convertRecordType(ep.RecordType)
if err != nil {
return err
}
matchedRecordsByTarget := make(map[string]civogo.DNSRecord)
for _, record := range matchedRecords {
matchedRecordsByTarget[record.Value] = record
}
for _, target := range ep.Targets {
if record, ok := matchedRecordsByTarget[target]; ok {
log.WithFields(log.Fields{
"zoneID": zoneID,
"dnsName": ep.DNSName,
"zoneName": zone.Name,
"recordType": ep.RecordType,
"target": target,
}).Warn("Updating Existing Target")
civoChange.Updates = append(civoChange.Updates, &CivoChangeUpdate{
Domain: zone,
DomainRecord: record,
Options: civogo.DNSRecordConfig{
Value: target,
Name: getStrippedRecordName(zone, *ep),
Type: recordType,
Priority: 0,
TTL: int(ep.RecordTTL),
},
})
delete(matchedRecordsByTarget, target)
} else {
// Record did not previously exist, create new 'target'
log.WithFields(log.Fields{
"zoneID": zoneID,
"dnsName": ep.DNSName,
"zoneName": zone.Name,
"recordType": ep.RecordType,
"target": target,
}).Warn("Creating New Target")
civoChange.Creates = append(civoChange.Creates, &CivoChangeCreate{
Domain: zone,
Options: &civogo.DNSRecordConfig{
Value: target,
Name: getStrippedRecordName(zone, *ep),
Type: recordType,
Priority: 0,
TTL: int(ep.RecordTTL),
},
})
}
}
// Any remaining records have been removed, delete them
for _, record := range matchedRecordsByTarget {
log.WithFields(log.Fields{
"zoneID": zoneID,
"dnsName": ep.DNSName,
"recordType": ep.RecordType,
"target": record.Value,
}).Warn("Deleting target")
civoChange.Deletes = append(civoChange.Deletes, &CivoChangeDelete{
Domain: zone,
DomainRecord: record,
})
}
}
}
return nil
}
// processDeleteActions return a list of changes to delete records.
func processDeleteActions(zonesByID map[string]civogo.DNSDomain, recordsByZoneID map[string][]civogo.DNSRecord, deletesByZone map[string][]*endpoint.Endpoint, civoChange *CivoChanges) error {
for zoneID, deletes := range deletesByZone {
zone := zonesByID[zoneID]
if len(deletes) == 0 {
log.WithFields(log.Fields{
"zoneID": zoneID,
"zoneName": zone.Name,
}).Debug("Skipping Zone, no deletes found.")
continue
}
records := recordsByZoneID[zoneID]
for _, ep := range deletes {
matchedRecords := getRecordID(records, zone, *ep)
if len(matchedRecords) == 0 {
log.WithFields(log.Fields{
"zoneID": zoneID,
"dnsName": ep.DNSName,
"zoneName": zone.Name,
"recordType": ep.RecordType,
}).Warn("Records to Delete not found.")
}
for _, record := range matchedRecords {
civoChange.Deletes = append(civoChange.Deletes, &CivoChangeDelete{
Domain: zone,
DomainRecord: record,
})
}
}
}
return nil
}
// ApplyChanges applies a given set of changes in a given zone.
func (p *CivoProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
var civoChange CivoChanges
recordsByZoneID := make(map[string][]civogo.DNSRecord)
zones, err := p.fetchZones(ctx)
if err != nil {
return err
}
zonesByID := make(map[string]civogo.DNSDomain)
zoneNameIDMapper := provider.ZoneIDName{}
for _, z := range zones {
zoneNameIDMapper.Add(z.ID, z.Name)
zonesByID[z.ID] = z
}
// Fetch records for each zone
for _, zone := range zones {
records, err := p.fetchRecords(ctx, zone.ID)
if err != nil {
return err
}
recordsByZoneID[zone.ID] = append(recordsByZoneID[zone.ID], records...)
}
createsByZone := endpointsByZone(zoneNameIDMapper, changes.Create)
updatesByZone := endpointsByZone(zoneNameIDMapper, changes.UpdateNew)
deletesByZone := endpointsByZone(zoneNameIDMapper, changes.Delete)
// Generate Creates
err = processCreateActions(zonesByID, recordsByZoneID, createsByZone, &civoChange)
if err != nil {
return err
}
// Generate Updates
err = processUpdateActions(zonesByID, recordsByZoneID, updatesByZone, &civoChange)
if err != nil {
return err
}
// Generate Deletes
err = processDeleteActions(zonesByID, recordsByZoneID, deletesByZone, &civoChange)
if err != nil {
return err
}
return p.submitChanges(ctx, civoChange)
}
func endpointsByZone(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) map[string][]*endpoint.Endpoint {
endpointsByZone := make(map[string][]*endpoint.Endpoint)
for _, ep := range endpoints {
zoneID, _ := zoneNameIDMapper.FindZone(ep.DNSName)
if zoneID == "" {
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", ep.DNSName)
continue
}
endpointsByZone[zoneID] = append(endpointsByZone[zoneID], ep)
}
return endpointsByZone
}
func convertRecordType(recordType string) (civogo.DNSRecordType, error) {
switch recordType {
case "A":
return civogo.DNSRecordTypeA, nil
case "CNAME":
return civogo.DNSRecordTypeCName, nil
case "TXT":
return civogo.DNSRecordTypeTXT, nil
case "SRV":
return civogo.DNSRecordTypeSRV, nil
default:
return "", fmt.Errorf("invalid Record Type: %s", recordType)
}
}
func getStrippedRecordName(zone civogo.DNSDomain, ep endpoint.Endpoint) string {
if ep.DNSName == zone.Name {
return ""
}
return strings.TrimSuffix(ep.DNSName, "."+zone.Name)
}
func getRecordID(records []civogo.DNSRecord, zone civogo.DNSDomain, ep endpoint.Endpoint) []civogo.DNSRecord {
var matchedRecords []civogo.DNSRecord
for _, record := range records {
stripedName := getStrippedRecordName(zone, ep)
toUpper := strings.ToUpper(string(record.Type))
if record.Name == stripedName && toUpper == ep.RecordType {
matchedRecords = append(matchedRecords, record)
}
}
return matchedRecords
}

874
provider/civo/civo_test.go Normal file
View File

@ -0,0 +1,874 @@
/*
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 civo
import (
"context"
"fmt"
"os"
"reflect"
"strings"
"testing"
"github.com/civo/civogo"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/plan"
)
func TestNewCivoProvider(t *testing.T) {
_ = os.Setenv("CIVO_TOKEN", "xxxxxxxxxxxxxxx")
_, err := NewCivoProvider(endpoint.NewDomainFilter([]string{"test.civo.com"}), true)
require.NoError(t, err)
_ = os.Unsetenv("CIVO_TOKEN")
}
func TestNewCivoProviderNoToken(t *testing.T) {
_, err := NewCivoProvider(endpoint.NewDomainFilter([]string{"test.civo.com"}), true)
assert.Error(t, err)
assert.Equal(t, "no token found", err.Error())
}
func TestCivoProviderZones(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns": `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
expected, err := client.ListDNSDomains()
assert.NoError(t, err)
zones, err := provider.Zones(context.Background())
assert.NoError(t, err)
// Check if the return is a DNSDomain type
assert.Equal(t, reflect.TypeOf(zones), reflect.TypeOf(expected))
assert.ElementsMatch(t, zones, expected)
}
func TestCivoProviderZonesWithError(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns-error": `[]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
_, err := provider.Zones(context.Background())
assert.Error(t, err)
}
func TestCivoProviderRecords(t *testing.T) {
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
{
Method: "GET",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: ``,
URL: "/v2/dns/12345/records",
ResponseBody: `[
{"id": "1", "domain_id":"12345", "account_id": "1", "name": "www", "type": "A", "value": "10.0.0.0", "ttl": 600},
{"id": "2", "account_id": "1", "domain_id":"12345", "name": "mail", "type": "A", "value": "10.0.0.1", "ttl": 600}
]`,
},
{
RequestBody: ``,
URL: "/v2/dns",
ResponseBody: `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
},
},
},
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
domainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
}
expected, err := client.ListDNSRecords("12345")
assert.NoError(t, err)
records, err := provider.Records(context.Background())
assert.NoError(t, err)
assert.Equal(t, strings.TrimSuffix(records[0].DNSName, ".example.com"), expected[0].Name)
assert.Equal(t, records[0].RecordType, string(expected[0].Type))
assert.Equal(t, int(records[0].RecordTTL), expected[0].TTL)
assert.Equal(t, strings.TrimSuffix(records[1].DNSName, ".example.com"), expected[1].Name)
assert.Equal(t, records[1].RecordType, string(expected[1].Type))
assert.Equal(t, int(records[1].RecordTTL), expected[1].TTL)
}
func TestCivoProviderWithoutRecords(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns/12345/records": `[]`,
"/v2/dns": `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
domainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
}
records, err := provider.Records(context.Background())
assert.NoError(t, err)
assert.Equal(t, len(records), 0)
}
func TestCivoProcessCreateActions(t *testing.T) {
zoneByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "12.12.12.1",
Type: "A",
TTL: 600,
},
},
}
createsByZone := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"),
endpoint.NewEndpoint("txt.example.com", endpoint.RecordTypeCNAME, "foo.example.com"),
},
}
var changes CivoChanges
err := processCreateActions(zoneByID, recordsByZoneID, createsByZone, &changes)
require.NoError(t, err)
assert.Equal(t, 2, len(changes.Creates))
assert.Equal(t, 0, len(changes.Updates))
assert.Equal(t, 0, len(changes.Deletes))
expectedCreates := []*CivoChangeCreate{
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "A",
Name: "foo",
Value: "1.2.3.4",
},
},
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "CNAME",
Name: "txt",
Value: "foo.example.com",
},
},
}
if !elementsMatch(t, expectedCreates, changes.Creates) {
assert.Failf(t, "diff: %s", cmp.Diff(expectedCreates, changes.Creates))
}
}
func TestCivoProcessCreateActionsWithError(t *testing.T) {
zoneByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "12.12.12.1",
Type: "A",
TTL: 600,
},
},
}
createsByZone := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("foo.example.com", "AAAA", "1.2.3.4"),
endpoint.NewEndpoint("txt.example.com", endpoint.RecordTypeCNAME, "foo.example.com"),
},
}
var changes CivoChanges
err := processCreateActions(zoneByID, recordsByZoneID, createsByZone, &changes)
require.Error(t, err)
assert.Equal(t, "invalid Record Type: AAAA", err.Error())
}
func TestCivoProcessUpdateActions(t *testing.T) {
zoneByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "1.2.3.4",
Type: "A",
TTL: 600,
},
{
ID: "2",
AccountID: "1",
DNSDomainID: "1",
Name: "foo",
Value: "foo.example.com",
Type: "CNAME",
TTL: 600,
},
{
ID: "3",
AccountID: "1",
DNSDomainID: "1",
Name: "bar",
Value: "10.10.10.1",
Type: "A",
TTL: 600,
},
},
}
updatesByZone := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("txt.example.com", endpoint.RecordTypeA, "10.20.30.40"),
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeCNAME, "bar.example.com"),
},
}
var changes CivoChanges
err := processUpdateActions(zoneByID, recordsByZoneID, updatesByZone, &changes)
require.NoError(t, err)
assert.Equal(t, 2, len(changes.Creates))
assert.Equal(t, 0, len(changes.Updates))
assert.Equal(t, 2, len(changes.Deletes))
expectedUpdate := []*CivoChangeCreate{
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "A",
Name: "txt",
Value: "10.20.30.40",
},
},
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "CNAME",
Name: "foo",
Value: "bar.example.com",
},
},
}
if !elementsMatch(t, expectedUpdate, changes.Creates) {
assert.Failf(t, "diff: %s", cmp.Diff(expectedUpdate, changes.Creates))
}
expectedDelete := []*CivoChangeDelete{
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "1.2.3.4",
Type: "A",
Priority: 0,
TTL: 600,
},
},
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "2",
AccountID: "1",
DNSDomainID: "1",
Name: "foo",
Value: "foo.example.com",
Type: "CNAME",
Priority: 0,
TTL: 600,
},
},
}
if !elementsMatch(t, expectedDelete, changes.Deletes) {
assert.Failf(t, "diff: %s", cmp.Diff(expectedDelete, changes.Deletes))
}
}
func TestCivoProcessDeleteAction(t *testing.T) {
zoneByID := map[string]civogo.DNSDomain{
"example.com": {
ID: "1",
AccountID: "1",
Name: "example.com",
},
}
recordsByZoneID := map[string][]civogo.DNSRecord{
"example.com": {
{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "1.2.3.4",
Type: "A",
TTL: 600,
},
{
ID: "2",
AccountID: "1",
DNSDomainID: "1",
Name: "foo",
Value: "5.6.7.8",
Type: "A",
TTL: 600,
},
{
ID: "3",
AccountID: "1",
DNSDomainID: "1",
Name: "bar",
Value: "10.10.10.1",
Type: "A",
TTL: 600,
},
},
}
deleteByDomain := map[string][]*endpoint.Endpoint{
"example.com": {
endpoint.NewEndpoint("txt.example.com", endpoint.RecordTypeA, "1.2.3.4"),
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "5.6.7.8"),
},
}
var changes CivoChanges
err := processDeleteActions(zoneByID, recordsByZoneID, deleteByDomain, &changes)
require.NoError(t, err)
assert.Equal(t, 0, len(changes.Creates))
assert.Equal(t, 0, len(changes.Updates))
assert.Equal(t, 2, len(changes.Deletes))
expectedDelete := []*CivoChangeDelete{
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "1",
AccountID: "1",
DNSDomainID: "1",
Name: "txt",
Value: "1.2.3.4",
Type: "A",
TTL: 600,
},
},
{
Domain: civogo.DNSDomain{
ID: "1",
AccountID: "1",
Name: "example.com",
},
DomainRecord: civogo.DNSRecord{
ID: "2",
AccountID: "1",
DNSDomainID: "1",
Type: "A",
Name: "foo",
Value: "5.6.7.8",
TTL: 600,
},
},
}
if !elementsMatch(t, expectedDelete, changes.Deletes) {
assert.Failf(t, "diff: %s", cmp.Diff(expectedDelete, changes.Deletes))
}
}
func TestCivoApplyChanges(t *testing.T) {
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
{
Method: "GET",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: "",
URL: "/v2/dns",
ResponseBody: `[{"id": "12345", "account_id": "1", "name": "example.com"}]`,
},
{
RequestBody: "",
URL: "/v2/dns/12345/records",
ResponseBody: `[]`,
},
},
},
})
defer server.Close()
changes := &plan.Changes{}
provider := &CivoProvider{
Client: *client,
}
changes.Create = []*endpoint.Endpoint{
{DNSName: "new.ext-dns-test.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeA},
{DNSName: "new.ext-dns-test-with-ttl.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeA, RecordTTL: 100},
}
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.example.com", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"target"}}}
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.example.de", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"target-old"}}}
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.foo.com", Targets: endpoint.Targets{"target-new"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 100}}
err := provider.ApplyChanges(context.Background(), changes)
assert.NoError(t, err)
}
func TestCivoProviderFetchZones(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns": `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
expected, err := client.ListDNSDomains()
if err != nil {
t.Errorf("should not fail, %s", err)
}
zones, err := provider.fetchZones(context.Background())
if err != nil {
t.Fatal(err)
}
assert.ElementsMatch(t, zones, expected)
}
func TestCivoProviderFetchZonesWithFilter(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns": `[
{"id": "12345", "account_id": "1", "name": "example.com"},
{"id": "12346", "account_id": "1", "name": "example.net"}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
domainFilter: endpoint.NewDomainFilter([]string{".com"}),
}
expected := []civogo.DNSDomain{
{ID: "12345", Name: "example.com", AccountID: "1"},
}
actual, err := provider.fetchZones(context.Background())
if err != nil {
t.Fatal(err)
}
assert.ElementsMatch(t, expected, actual)
}
func TestCivoProviderFetchRecords(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns/12345/records": `[
{"id": "1", "domain_id":"12345", "account_id": "1", "name": "www", "type": "A", "value": "10.0.0.0", "ttl": 600},
{"id": "2", "account_id": "1", "domain_id":"12345", "name": "mail", "type": "A", "value": "10.0.0.1", "ttl": 600}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
expected, err := client.ListDNSRecords("12345")
assert.NoError(t, err)
actual, err := provider.fetchRecords(context.Background(), "12345")
assert.NoError(t, err)
assert.ElementsMatch(t, expected, actual)
}
func TestCivoProviderFetchRecordsWithError(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns/12345/records": `[
{"id": "1", "domain_id":"12345", "account_id": "1", "name": "www", "type": "A", "value": "10.0.0.0", "ttl": 600},
{"id": "2", "account_id": "1", "domain_id":"12345", "name": "mail", "type": "A", "value": "10.0.0.1", "ttl": 600}
]`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
}
_, err := provider.fetchRecords(context.Background(), "235698")
assert.Error(t, err)
}
func TestCivo_getStrippedRecordName(t *testing.T) {
assert.Equal(t, "", getStrippedRecordName(civogo.DNSDomain{
Name: "foo.com",
}, endpoint.Endpoint{
DNSName: "foo.com",
}))
assert.Equal(t, "api", getStrippedRecordName(civogo.DNSDomain{
Name: "foo.com",
}, endpoint.Endpoint{
DNSName: "api.foo.com",
}))
}
func TestCivo_convertRecordType(t *testing.T) {
record, err := convertRecordType("A")
recordA := civogo.DNSRecordType(civogo.DNSRecordTypeA)
require.NoError(t, err)
assert.Equal(t, recordA, record)
record, err = convertRecordType("CNAME")
recordCName := civogo.DNSRecordType(civogo.DNSRecordTypeCName)
require.NoError(t, err)
assert.Equal(t, recordCName, record)
record, err = convertRecordType("TXT")
recordTXT := civogo.DNSRecordType(civogo.DNSRecordTypeTXT)
require.NoError(t, err)
assert.Equal(t, recordTXT, record)
record, err = convertRecordType("SRV")
recordSRV := civogo.DNSRecordType(civogo.DNSRecordTypeSRV)
require.NoError(t, err)
assert.Equal(t, recordSRV, record)
_, err = convertRecordType("INVALID")
require.Error(t, err)
assert.Equal(t, "invalid Record Type: INVALID", err.Error())
}
func TestCivoProviderGetRecordID(t *testing.T) {
zone := civogo.DNSDomain{
ID: "12345",
Name: "test.com",
}
record := []civogo.DNSRecord{{
ID: "1",
Type: "A",
Name: "www",
Value: "10.0.0.0",
DNSDomainID: "12345",
TTL: 600,
}, {
ID: "2",
Type: "A",
Name: "api",
Value: "10.0.0.1",
DNSDomainID: "12345",
TTL: 600,
}}
endPoint := endpoint.Endpoint{DNSName: "www.test.com", Targets: endpoint.Targets{"10.0.0.0"}, RecordType: "A"}
id := getRecordID(record, zone, endPoint)
assert.Equal(t, id[0].ID, record[0].ID)
}
func TestCivo_submitChangesCreate(t *testing.T) {
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
{
Method: "POST",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: `{"type":"MX","name":"mail","value":"10.0.0.1","priority":10,"ttl":600}`,
URL: "/v2/dns/12345/records",
ResponseBody: `{
"id": "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
"account_id": "1",
"domain_id": "12345",
"name": "mail",
"value": "10.0.0.1",
"type": "MX",
"priority": 10,
"ttl": 600
}`,
},
},
},
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
DryRun: false,
}
changes := CivoChanges{
Creates: []*CivoChangeCreate{
{
Domain: civogo.DNSDomain{
ID: "12345",
AccountID: "1",
Name: "example.com",
},
Options: &civogo.DNSRecordConfig{
Type: "MX",
Name: "mail",
Value: "10.0.0.1",
Priority: 10,
TTL: 600,
},
},
},
}
err := provider.submitChanges(context.Background(), changes)
assert.NoError(t, err)
}
func TestCivo_submitChangesUpdate(t *testing.T) {
client, server, _ := civogo.NewAdvancedClientForTesting([]civogo.ConfigAdvanceClientForTesting{
{
Method: "PUT",
Value: []civogo.ValueAdvanceClientForTesting{
{
RequestBody: `{"type":"MX","name":"mail","value":"10.0.0.2","priority":10,"ttl":600}`,
URL: "/v2/dns/12345/records/76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
ResponseBody: `{
"id": "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
"account_id": "1",
"domain_id": "12345",
"name": "mail",
"value": "10.0.0.2",
"type": "MX",
"priority": 10,
"ttl": 600
}`,
},
},
},
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
DryRun: false,
}
changes := CivoChanges{
Updates: []*CivoChangeUpdate{
{
Domain: civogo.DNSDomain{ID: "12345", AccountID: "1", Name: "example.com"},
DomainRecord: civogo.DNSRecord{
ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
AccountID: "1",
DNSDomainID: "12345",
Name: "mail",
Value: "10.0.0.1",
Type: "MX",
Priority: 10,
TTL: 600,
},
Options: civogo.DNSRecordConfig{
Type: "MX",
Name: "mail",
Value: "10.0.0.2",
Priority: 10,
TTL: 600,
},
},
},
}
err := provider.submitChanges(context.Background(), changes)
assert.NoError(t, err)
}
func TestCivo_submitChangesDelete(t *testing.T) {
client, server, _ := civogo.NewClientForTesting(map[string]string{
"/v2/dns/12345/records/76cc107f-fbef-4e2b-b97f-f5d34f4075d3": `{"result": "success"}`,
})
defer server.Close()
provider := &CivoProvider{
Client: *client,
DryRun: false,
}
changes := CivoChanges{
Deletes: []*CivoChangeDelete{
{
Domain: civogo.DNSDomain{ID: "12345", AccountID: "1", Name: "example.com"},
DomainRecord: civogo.DNSRecord{
ID: "76cc107f-fbef-4e2b-b97f-f5d34f4075d3",
AccountID: "1",
DNSDomainID: "12345",
Name: "mail",
Value: "10.0.0.2",
Type: "MX",
Priority: 10,
TTL: 600,
},
},
},
}
err := provider.submitChanges(context.Background(), changes)
assert.NoError(t, err)
}
// This function is an adapted copy of the testify package's ElementsMatch function with the
// call to ObjectsAreEqual replaced with cmp.Equal which better handles struct's with pointers to
// other structs. It also ignores ordering when comparing unlike cmp.Equal.
func elementsMatch(t *testing.T, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) {
if listA == nil && listB == nil {
return true
} else if listA == nil {
return isEmpty(listB)
} else if listB == nil {
return isEmpty(listA)
}
aKind := reflect.TypeOf(listA).Kind()
bKind := reflect.TypeOf(listB).Kind()
if aKind != reflect.Array && aKind != reflect.Slice {
return assert.Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...)
}
if bKind != reflect.Array && bKind != reflect.Slice {
return assert.Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...)
}
aValue := reflect.ValueOf(listA)
bValue := reflect.ValueOf(listB)
aLen := aValue.Len()
bLen := bValue.Len()
if aLen != bLen {
return assert.Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...)
}
// Mark indexes in bValue that we already used
visited := make([]bool, bLen)
for i := 0; i < aLen; i++ {
element := aValue.Index(i).Interface()
found := false
for j := 0; j < bLen; j++ {
if visited[j] {
continue
}
if cmp.Equal(bValue.Index(j).Interface(), element) {
visited[j] = true
found = true
break
}
}
if !found {
return assert.Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...)
}
}
return true
}
func isEmpty(xs interface{}) bool {
if xs != nil {
objValue := reflect.ValueOf(xs)
return objValue.Len() == 0
}
return true
}

View File

@ -188,17 +188,13 @@ func (p *GoogleProvider) Zones(ctx context.Context) (map[string]*dns.ManagedZone
return nil
}
log.Debugf("Matching zones against domain filters: %v", p.domainFilter.Filters)
log.Debugf("Matching zones against domain filters: %v", p.domainFilter)
if err := p.managedZonesClient.List(p.project).Pages(ctx, f); err != nil {
return nil, err
}
if len(zones) == 0 {
if p.domainFilter.IsConfigured() {
log.Warnf("No zones in the project, %s, match domain filters: %v", p.project, p.domainFilter.Filters)
} else {
log.Warnf("No zones found in the project, %s", p.project)
}
log.Warnf("No zones in the project, %s, match domain filters: %v", p.project, p.domainFilter)
}
for _, zone := range zones {

View File

@ -218,7 +218,7 @@ func (c *ibmcloudConfig) Validate(authenticator core.Authenticator, domainFilter
var service ibmcloudService
isPrivate := false
log.Debugf("filters: %v, %v", domainFilter.Filters, zoneIDFilter.ZoneIDs)
if domainFilter.Filters[0] == "" && zoneIDFilter.ZoneIDs[0] == "" {
if (len(domainFilter.Filters) == 0 || domainFilter.Filters[0] == "") && zoneIDFilter.ZoneIDs[0] == "" {
return service, isPrivate, fmt.Errorf("at lease one of filters: 'domain-filter', 'zone-id-filter' needed")
}
@ -253,7 +253,7 @@ func (c *ibmcloudConfig) Validate(authenticator core.Authenticator, domainFilter
}
for _, zone := range zonesResp.Result {
log.Debugf("zoneName: %s, zoneID: %s", *zone.Name, *zone.ID)
if len(domainFilter.Filters[0]) != 0 && domainFilter.Match(*zone.Name) {
if len(domainFilter.Filters) > 0 && domainFilter.Filters[0] != "" && domainFilter.Match(*zone.Name) {
log.Debugf("zone %s found.", *zone.ID)
zoneID = *zone.ID
break

View File

@ -138,11 +138,7 @@ func (p *OCIProvider) zones(ctx context.Context) (map[string]dns.ZoneSummary, er
}
if len(zones) == 0 {
if p.domainFilter.IsConfigured() {
log.Warnf("No zones in compartment %q match domain filters %v", p.cfg.CompartmentID, p.domainFilter.Filters)
} else {
log.Warnf("No zones found in compartment %q", p.cfg.CompartmentID)
}
log.Warnf("No zones in compartment %q match domain filters %v", p.cfg.CompartmentID, p.domainFilter)
}
return zones, nil

View File

@ -31,7 +31,7 @@ import (
"sigs.k8s.io/external-dns/endpoint"
)
// This is a compile-time validation that glooSource is a Source.
// This is a compile-time validation that kongTCPIngressSource is a Source.
var _ Source = &kongTCPIngressSource{}
const defaultKongNamespace = "kong"