mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-11-28 16:31:23 +01:00
Merge branch 'master' into master
This commit is contained in:
commit
00da3a130f
@ -1,23 +1,70 @@
|
||||
run:
|
||||
concurrency: 4
|
||||
|
||||
modules-download-mode: readonly
|
||||
|
||||
linters-settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: false
|
||||
goimports:
|
||||
local-prefixes: github.com/kubernetes-sigs/external-dns
|
||||
golint:
|
||||
min-confidence: 0.9
|
||||
|
||||
gocyclo:
|
||||
min-complexity: 15
|
||||
maligned:
|
||||
suggest-new: true
|
||||
misspell:
|
||||
locale: US
|
||||
|
||||
linters:
|
||||
# please, do not use `enable-all`: it's deprecated and will be removed soon.
|
||||
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
|
||||
disable-all: true
|
||||
enable:
|
||||
- deadcode
|
||||
- depguard
|
||||
- dogsled
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- goprintffuncname
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- golint
|
||||
- goimports
|
||||
- misspell
|
||||
- unconvert
|
||||
- megacheck
|
||||
- interfacer
|
||||
- misspell
|
||||
- rowserrcheck
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- varcheck
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- deadcode
|
||||
- depguard
|
||||
- dogsled
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- goprintffuncname
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- interfacer
|
||||
- misspell
|
||||
- nolintlint
|
||||
- rowserrcheck
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unused
|
||||
- varcheck
|
||||
- whitespace
|
||||
|
||||
run:
|
||||
skip-files:
|
||||
- endpoint/zz_generated.deepcopy.go
|
||||
|
||||
28
.travis.yml
28
.travis.yml
@ -6,23 +6,35 @@ os:
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.13.x"
|
||||
- "1.14.x"
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
env:
|
||||
- GOLANGCI_RELEASE="v1.23.1"
|
||||
|
||||
before_install:
|
||||
- GO111MODULE=off go get github.com/mattn/goveralls
|
||||
- GO111MODULE=off go get github.com/lawrencewoodman/roveralls
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_RELEASE}
|
||||
cache:
|
||||
directories:
|
||||
- $GOPATH/pkg/mod
|
||||
|
||||
script:
|
||||
- make test
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: "Linting"
|
||||
go: "1.14.x"
|
||||
env:
|
||||
- GOLANGCI_RELEASE="v1.26.0"
|
||||
before_install:
|
||||
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_RELEASE}
|
||||
script:
|
||||
- make lint
|
||||
- name: "Coverage"
|
||||
go: "1.14.x"
|
||||
before_install:
|
||||
- GO111MODULE=off go get github.com/mattn/goveralls
|
||||
- GO111MODULE=off go get github.com/lawrencewoodman/roveralls
|
||||
script:
|
||||
- travis_wait 20 roveralls
|
||||
- goveralls -coverprofile=roveralls.coverprofile -service=travis-ci
|
||||
|
||||
45
CHANGELOG.md
45
CHANGELOG.md
@ -1,3 +1,48 @@
|
||||
## v0.7.2 - 2020-06-03
|
||||
|
||||
- Update blogpost in README (#1610) @vanhumbeecka
|
||||
- Support for AWS Route53 in China (#1603) @greenu
|
||||
- Update Govcloud provider hosted zones (#1592) @clhuang
|
||||
- Fix issue with too large DNS messages (#1590) @dmayle
|
||||
- use the latest linode go version (#1587) @tariq1890
|
||||
- use istio client-go and clean up k8s deps (#1584) @tariq1890
|
||||
- Add owners for cloudflare and coredns providers (#1582) @Raffo
|
||||
- remove some code duplication in gateway source (#1575) @tariq1890
|
||||
- update Contour IngressRoute deps (#1569) @stevesloka
|
||||
- Make tests faster (#1568) @sheerun
|
||||
- Fix scheduling of reconciliation (#1567) @sheerun
|
||||
- fix minor typos in istio gateway source docs (#1566) @tariq1890
|
||||
- Provider structure refactor (#1565) @Raffo
|
||||
- Fix typo in ttl.md (#1564) @rtnpro
|
||||
- Fix goreportcard warnings (#1561) @squat
|
||||
- Use consistent headless service name in example (#1559) @lowkeyliesmyth
|
||||
- Update go versions to 1.14.x that were missed in commit 99cebfcf from PR #1476 (#1554) @stealthybox
|
||||
- Remove duplicate selector from DigitalOcean manifest (#1553) @ggordan
|
||||
- Upgrade DNSimple client and add support for contexts (#1551) @weppos
|
||||
- Upgrade github.com/miekg/dns to v1.1.25 (#1545) @davidcollom
|
||||
- Fix updates in CloudFlare provider (#1542) @sheerun
|
||||
- update readme for latest version (#1539) @elsesiy
|
||||
- Improve Cloudflare tests in preparation to fix other issues (#1537) @sheerun
|
||||
- Allow for custom property comparators (#1536) @sheerun
|
||||
- fix typo (#1535) @tmatias
|
||||
- Bump github.com/pkg/errors from 0.8.1 to 0.9.1 (#1531) @njuettner
|
||||
- Bump github.com/digitalocean/godo from 1.19.0 to 1.34.0 (#1530) @njuettner
|
||||
- Bump github.com/prometheus/client_golang from 1.0.0 to 1.5.1 (#1529) @njuettner
|
||||
- Bump github.com/akamai/AkamaiOPEN-edgegrid-golang from 0.9.10 to 0.9.11 (#1528) @njuettner
|
||||
- Fix RFC2316 Windows Documentation (#1516) @scottd018
|
||||
- remove dependency on kubernetes/kubernetes (#1513) @tariq1890
|
||||
- update akamai openapi dependency (#1511) @tariq1890
|
||||
- Vultr Provider (#1509) @ddymko
|
||||
- Add AWS region ap-east-1(HK) (#1497) @lovemai073
|
||||
- Fix: file coredns.go is not `goimports`-ed (#1496) @njuettner
|
||||
- Allow ZoneIDFilter for Cloudflare (#1494) @james-callahan
|
||||
- update etcd dependency to latest version (#1485) @tariq1890
|
||||
- Support for openshift routes (#1484) @jgrumboe
|
||||
- add --txt-suffix feature (#1483) @jgrumboe
|
||||
- update to go 1.14 (#1476) @jochen42
|
||||
- Multiple A records support for the same FQDN (#1475) @ytsarev
|
||||
- Implement annotation filter for CRD source (#1399) @ytsarev
|
||||
|
||||
## v0.7.1 - 2020-04-01
|
||||
|
||||
- Prometheus metric: timestamp of last successful sync with the DNS provider (#1480) @njuettner
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM golang:1.13 as builder
|
||||
FROM golang:1.14 as builder
|
||||
|
||||
WORKDIR /sigs.k8s.io/external-dns
|
||||
|
||||
|
||||
4
Makefile
4
Makefile
@ -31,14 +31,14 @@ cover-html: cover
|
||||
|
||||
# Run all the linters
|
||||
lint:
|
||||
golangci-lint run --timeout=5m ./...
|
||||
golangci-lint run --timeout=15m ./...
|
||||
|
||||
|
||||
# The verify target runs tasks similar to the CI tasks, but without code coverage
|
||||
.PHONY: verify test
|
||||
|
||||
test:
|
||||
go test -v -race $(shell go list ./... | grep -v /vendor/)
|
||||
go test -race ./...
|
||||
|
||||
# The build targets allow to build the binary and docker image
|
||||
.PHONY: build build.docker build.mini
|
||||
|
||||
@ -19,7 +19,7 @@ In a broader sense, ExternalDNS allows you to control DNS records dynamically vi
|
||||
|
||||
The [FAQ](docs/faq.md) contains additional information and addresses several questions about key concepts of ExternalDNS.
|
||||
|
||||
To see ExternalDNS in action, have a look at this [video](https://www.youtube.com/watch?v=9HQ2XgL9YVI) or read this [blogpost](https://medium.com/wearetheledger/deploying-test-environments-with-azure-devops-eks-and-externaldns-67abe647e4e).
|
||||
To see ExternalDNS in action, have a look at this [video](https://www.youtube.com/watch?v=9HQ2XgL9YVI) or read this [blogpost](https://codemine.be/posts/20190125-devops-eks-externaldns/).
|
||||
|
||||
## The Latest Release: v0.7
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
@ -112,6 +113,10 @@ type Controller struct {
|
||||
Interval time.Duration
|
||||
// The DomainFilter defines which DNS records to keep or exclude
|
||||
DomainFilter endpoint.DomainFilter
|
||||
// The nextRunAt used for throttling and batching reconciliation
|
||||
nextRunAt time.Time
|
||||
// The nextRunAtMux is for atomic updating of nextRunAt
|
||||
nextRunAtMux sync.Mutex
|
||||
}
|
||||
|
||||
// RunOnce runs a single iteration of a reconciliation loop.
|
||||
@ -139,6 +144,7 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
||||
Current: records,
|
||||
Desired: endpoints,
|
||||
DomainFilter: c.DomainFilter,
|
||||
PropertyComparator: c.Registry.PropertyValuesEqual,
|
||||
}
|
||||
|
||||
plan = plan.Calculate()
|
||||
@ -154,18 +160,39 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run runs RunOnce in a loop with a delay until stopChan receives a value.
|
||||
func (c *Controller) Run(ctx context.Context, stopChan <-chan struct{}) {
|
||||
ticker := time.NewTicker(c.Interval)
|
||||
// MinInterval is used as window for batching events
|
||||
const MinInterval = 5 * time.Second
|
||||
|
||||
// RunOnceThrottled makes sure execution happens at most once per interval.
|
||||
func (c *Controller) ScheduleRunOnce(now time.Time) {
|
||||
c.nextRunAtMux.Lock()
|
||||
defer c.nextRunAtMux.Unlock()
|
||||
c.nextRunAt = now.Add(MinInterval)
|
||||
}
|
||||
|
||||
func (c *Controller) ShouldRunOnce(now time.Time) bool {
|
||||
c.nextRunAtMux.Lock()
|
||||
defer c.nextRunAtMux.Unlock()
|
||||
if now.Before(c.nextRunAt) {
|
||||
return false
|
||||
}
|
||||
c.nextRunAt = now.Add(c.Interval)
|
||||
return true
|
||||
}
|
||||
|
||||
// Run runs RunOnce in a loop with a delay until context is canceled
|
||||
func (c *Controller) Run(ctx context.Context) {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
err := c.RunOnce(ctx)
|
||||
if err != nil {
|
||||
if c.ShouldRunOnce(time.Now()) {
|
||||
if err := c.RunOnce(ctx); err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-stopChan:
|
||||
case <-ctx.Done():
|
||||
log.Info("Terminating main controller loop")
|
||||
return
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ import (
|
||||
|
||||
// mockProvider returns mock endpoints and validates changes.
|
||||
type mockProvider struct {
|
||||
provider.BaseProvider
|
||||
RecordsStore []*endpoint.Endpoint
|
||||
ExpectChanges *plan.Changes
|
||||
}
|
||||
@ -153,43 +154,41 @@ func TestRunOnce(t *testing.T) {
|
||||
source.AssertExpectations(t)
|
||||
}
|
||||
|
||||
// TestSourceEventHandler tests that the Controller can use a Source's registered handler as a callback.
|
||||
func TestSourceEventHandler(t *testing.T) {
|
||||
source := new(testutils.MockSource)
|
||||
func TestShouldRunOnce(t *testing.T) {
|
||||
ctrl := &Controller{Interval: 10 * time.Minute}
|
||||
|
||||
handlerCh := make(chan bool)
|
||||
timeoutCh := make(chan bool, 1)
|
||||
stopChan := make(chan struct{}, 1)
|
||||
now := time.Now()
|
||||
|
||||
ctrl := &Controller{
|
||||
Source: source,
|
||||
Registry: nil,
|
||||
Policy: &plan.SyncPolicy{},
|
||||
}
|
||||
|
||||
// Define and register a simple handler that sends a message to a channel to show it was called.
|
||||
handler := func() error {
|
||||
handlerCh <- true
|
||||
return nil
|
||||
}
|
||||
// Example of preventing handler from being called more than once every 5 seconds.
|
||||
ctrl.Source.AddEventHandler(handler, stopChan, 5*time.Second)
|
||||
|
||||
// Send timeout message after 10 seconds to fail test if handler is not called.
|
||||
go func() {
|
||||
time.Sleep(10 * time.Second)
|
||||
timeoutCh <- true
|
||||
}()
|
||||
|
||||
// Wait until we either receive a message from handlerCh or timeoutCh channel after 10 seconds.
|
||||
select {
|
||||
case msg := <-handlerCh:
|
||||
assert.True(t, msg)
|
||||
case <-timeoutCh:
|
||||
assert.Fail(t, "timed out waiting for event handler to be called")
|
||||
}
|
||||
|
||||
close(stopChan)
|
||||
close(handlerCh)
|
||||
close(timeoutCh)
|
||||
// First run of Run loop should execute RunOnce
|
||||
assert.True(t, ctrl.ShouldRunOnce(now))
|
||||
|
||||
// Second run should not
|
||||
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||
|
||||
now = now.Add(10 * time.Second)
|
||||
// Changes happen in ingresses or services
|
||||
ctrl.ScheduleRunOnce(now)
|
||||
ctrl.ScheduleRunOnce(now)
|
||||
|
||||
// Because we batch changes, ShouldRunOnce returns False at first
|
||||
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||
assert.False(t, ctrl.ShouldRunOnce(now.Add(100*time.Microsecond)))
|
||||
|
||||
// But after MinInterval we should run reconciliation
|
||||
now = now.Add(MinInterval)
|
||||
assert.True(t, ctrl.ShouldRunOnce(now))
|
||||
|
||||
// But just one time
|
||||
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||
|
||||
// We should wait maximum possible time after last reconciliation started
|
||||
now = now.Add(10*time.Minute - time.Second)
|
||||
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||
|
||||
// After exactly Interval it's OK again to reconcile
|
||||
now = now.Add(time.Second)
|
||||
assert.True(t, ctrl.ShouldRunOnce(now))
|
||||
|
||||
// But not two times
|
||||
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ PRs welcome!
|
||||
|
||||
Notes
|
||||
=====
|
||||
When the `external-dns.alpha.kubernetes.io/ttl` annotation is not provided, the TTL will default to 0 seconds and `enpoint.TTL.isConfigured()` will be false.
|
||||
When the `external-dns.alpha.kubernetes.io/ttl` annotation is not provided, the TTL will default to 0 seconds and `endpoint.TTL.isConfigured()` will be false.
|
||||
|
||||
### AWS Provider
|
||||
The AWS Provider overrides the value to 300s when the TTL is 0.
|
||||
|
||||
@ -36,9 +36,6 @@ spec:
|
||||
app: external-dns
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
@ -102,9 +99,6 @@ spec:
|
||||
app: external-dns
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
@ -195,3 +189,13 @@ Now that we have verified that ExternalDNS will automatically manage DigitalOcea
|
||||
$ kubectl delete service -f nginx.yaml
|
||||
$ kubectl delete service -f externaldns.yaml
|
||||
```
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### API Page Size
|
||||
|
||||
If you have a large number of domains and/or records within a domain, you may encounter API
|
||||
rate limiting because of the number of API calls that external-dns must make to the DigitalOcean API to retrieve
|
||||
the current DNS configuration during every reconciliation loop. If this is the case, use the
|
||||
`--digitalocean-api-page-size` option to increase the size of the pages used when querying the DigitalOcean API.
|
||||
(Note: external-dns uses a default of 50.)
|
||||
|
||||
@ -9,7 +9,7 @@ The main use cases that inspired this feature is the necessity for fixed address
|
||||
|
||||
We will go through a small example of deploying a simple Kafka with use of a headless service.
|
||||
|
||||
### Exernal DNS
|
||||
### External DNS
|
||||
|
||||
A simple deploy could look like this:
|
||||
### Manifest (for clusters without RBAC enabled)
|
||||
@ -17,7 +17,7 @@ A simple deploy could look like this:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: exeternal-dns
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
@ -81,7 +81,7 @@ subjects:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: exeternal-dns
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
@ -111,7 +111,7 @@ spec:
|
||||
|
||||
### Kafka Stateful Set
|
||||
|
||||
First lets deploy a Kafka Stateful set, a simple example(a lot of stuff is missing) with a headless service called `kafka-hsvc`
|
||||
First lets deploy a Kafka Stateful set, a simple example(a lot of stuff is missing) with a headless service called `ksvc`
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1beta1
|
||||
@ -155,7 +155,7 @@ spec:
|
||||
requests:
|
||||
storage: 500Gi
|
||||
```
|
||||
Very important here, is to set the `hostport`(only works if the PodSecurityPolicy allows it)! and in case your app requires an actual hostname inside the container, unlike Kafka, which can advertise on another address, you have to set the hostname yourself.
|
||||
Very important here, is to set the `hostPort`(only works if the PodSecurityPolicy allows it)! and in case your app requires an actual hostname inside the container, unlike Kafka, which can advertise on another address, you have to set the hostname yourself.
|
||||
|
||||
### Headless Service
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ It is meant to supplement the other provider-specific setup tutorials.
|
||||
|
||||
* Manifest (for clusters without RBAC enabled)
|
||||
* Manifest (for clusters with RBAC enabled)
|
||||
* Update existing Existing DNS Deployment
|
||||
* Update existing ExternalDNS Deployment
|
||||
|
||||
### Manifest (for clusters without RBAC enabled)
|
||||
|
||||
@ -48,7 +48,7 @@ kind: ServiceAccount
|
||||
metadata:
|
||||
name: external-dns
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: external-dns
|
||||
@ -66,7 +66,7 @@ rules:
|
||||
resources: ["gateways"]
|
||||
verbs: ["get","watch","list"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: external-dns-viewer
|
||||
@ -110,7 +110,7 @@ spec:
|
||||
- --txt-owner-id=my-identifier
|
||||
```
|
||||
|
||||
### Update existing Existing DNS Deployment
|
||||
### Update existing ExternalDNS Deployment
|
||||
|
||||
* For clusters with running `external-dns`, you can just update the deployment.
|
||||
* With access to the `kube-system` namespace, update the existing `external-dns` deployment.
|
||||
@ -217,7 +217,7 @@ transfer-encoding: chunked
|
||||
|
||||
**Note:** The `-H` flag in the original Istio tutorial is no longer necessary in the `curl` commands.
|
||||
|
||||
### Debug External-DNS
|
||||
### Debug ExternalDNS
|
||||
|
||||
* Look for the deployment pod to see the status
|
||||
|
||||
@ -239,8 +239,8 @@ At this point, you can `create` or `update` any `Istio Gateway` object with `hos
|
||||
|
||||
```console
|
||||
time="2020-01-17T06:08:08Z" level=info msg="Desired change: CREATE httpbin.example.com A"
|
||||
time="2020-01-17T06:08:08Z" level=info msg="Desired change: CREATE httpbin.example.comm TXT"
|
||||
time="2020-01-17T06:08:08Z" level=info msg="2 record(s) in zone example.comm. were successfully updated"
|
||||
time="2020-01-17T06:08:08Z" level=info msg="Desired change: CREATE httpbin.example.com TXT"
|
||||
time="2020-01-17T06:08:08Z" level=info msg="2 record(s) in zone example.com. were successfully updated"
|
||||
time="2020-01-17T06:09:08Z" level=info msg="All records are already up to date, there are no changes for the matching hosted zones"
|
||||
```
|
||||
|
||||
|
||||
@ -159,6 +159,7 @@ func NewEndpointWithTTL(dnsName, recordType string, ttl TTL, targets ...string)
|
||||
}
|
||||
}
|
||||
|
||||
// WithSetIdentifier applies the given set identifier to the endpoint.
|
||||
func (e *Endpoint) WithSetIdentifier(setIdentifier string) *Endpoint {
|
||||
e.SetIdentifier = setIdentifier
|
||||
return e
|
||||
|
||||
57
go.mod
57
go.mod
@ -3,46 +3,46 @@ module sigs.k8s.io/external-dns
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.44.3
|
||||
cloud.google.com/go v0.50.0
|
||||
github.com/Azure/azure-sdk-for-go v36.0.0+incompatible
|
||||
github.com/Azure/go-autorest/autorest v0.9.0
|
||||
github.com/Azure/go-autorest/autorest/adal v0.6.0
|
||||
github.com/Azure/go-autorest/autorest v0.9.4
|
||||
github.com/Azure/go-autorest/autorest/adal v0.8.3
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.0.0-00010101000000-000000000000
|
||||
github.com/Azure/go-autorest/autorest/to v0.3.0
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
|
||||
github.com/alecthomas/colour v0.1.0 // indirect
|
||||
github.com/alecthomas/kingpin v2.2.5+incompatible
|
||||
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 // indirect
|
||||
github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20180828111155-cad214d7d71f
|
||||
github.com/aws/aws-sdk-go v1.27.4
|
||||
github.com/aws/aws-sdk-go v1.31.4
|
||||
github.com/cloudflare/cloudflare-go v0.10.1
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/denverdino/aliyungo v0.0.0-20180815121905-69560d9530f5
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba
|
||||
github.com/digitalocean/godo v1.34.0
|
||||
github.com/dnaeon/go-vcr v1.0.1 // indirect
|
||||
github.com/dnsimple/dnsimple-go v0.14.0
|
||||
github.com/dnsimple/dnsimple-go v0.60.0
|
||||
github.com/exoscale/egoscale v0.18.1
|
||||
github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99
|
||||
github.com/go-resty/resty v1.8.0 // indirect
|
||||
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b // indirect
|
||||
github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||
github.com/gophercloud/gophercloud v0.1.0
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/heptio/contour v0.15.0
|
||||
github.com/gorilla/mux v1.7.4 // indirect
|
||||
github.com/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65
|
||||
github.com/linki/instrumented_http v0.2.0
|
||||
github.com/linode/linodego v0.3.0
|
||||
github.com/mattn/go-isatty v0.0.11 // indirect
|
||||
github.com/linode/linodego v0.15.0
|
||||
github.com/maxatome/go-testdeep v1.4.0
|
||||
github.com/miekg/dns v1.1.25
|
||||
github.com/nesv/go-dynect v0.6.0
|
||||
github.com/nic-at/rc0go v1.1.0
|
||||
github.com/openshift/api v0.0.0-20190322043348-8741ff068a47
|
||||
github.com/openshift/client-go v3.9.0+incompatible
|
||||
github.com/openshift/api v0.0.0-20200302134843-001335d6cc34
|
||||
github.com/openshift/client-go v0.0.0-20200116145930-eb24d03d8420
|
||||
github.com/oracle/oci-go-sdk v1.8.0
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.0.0
|
||||
github.com/projectcontour/contour v1.4.0
|
||||
github.com/prometheus/client_golang v1.1.0
|
||||
github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
@ -50,22 +50,27 @@ require (
|
||||
github.com/smartystreets/gunit v1.1.1 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/terra-farm/udnssdk v1.3.5 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/smartystreets/gunit v1.3.4 // indirect
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/transip/gotransip v5.8.2+incompatible
|
||||
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20190611170422-7119fe55ed92
|
||||
github.com/vultr/govultr v0.3.2
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
google.golang.org/api v0.9.0
|
||||
google.golang.org/api v0.15.0
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1
|
||||
gopkg.in/yaml.v2 v2.2.5
|
||||
istio.io/api v0.0.0-20190820204432-483f2547d882
|
||||
istio.io/istio v0.0.0-20190322063008-2b1331886076
|
||||
k8s.io/api v0.0.0-20190620084959-7cf5895f2711
|
||||
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719
|
||||
k8s.io/client-go v10.0.0+incompatible
|
||||
k8s.io/klog v0.3.1
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
istio.io/api v0.0.0-20200324230725-4b064f75ad8f
|
||||
istio.io/client-go v0.0.0-20200324231043-96a582576da1
|
||||
k8s.io/api v0.17.5
|
||||
k8s.io/apimachinery v0.17.5
|
||||
k8s.io/client-go v0.17.5
|
||||
k8s.io/klog v1.0.0
|
||||
)
|
||||
|
||||
replace (
|
||||
@ -74,11 +79,5 @@ replace (
|
||||
github.com/Azure/go-autorest/autorest/adal => github.com/Azure/go-autorest/autorest/adal v0.6.0
|
||||
github.com/Azure/go-autorest/autorest/azure/auth => github.com/Azure/go-autorest/autorest/azure/auth v0.3.0
|
||||
github.com/golang/glog => github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d
|
||||
istio.io/api => istio.io/api v0.0.0-20190820204432-483f2547d882
|
||||
istio.io/istio => istio.io/istio v0.0.0-20190911205955-c2bd59595ce6
|
||||
k8s.io/api => k8s.io/api v0.0.0-20190817221950-ebce17126a01
|
||||
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190919022157-e8460a76b3ad
|
||||
k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190817221809-bf4de9df677c
|
||||
k8s.io/client-go => k8s.io/client-go v0.0.0-20190817222206-ee6c071a42cf
|
||||
k8s.io/klog => github.com/mikkeloscar/knolog v0.0.0-20190326191552-80742771eb6b
|
||||
)
|
||||
|
||||
4
internal/config/config.go
Normal file
4
internal/config/config.go
Normal file
@ -0,0 +1,4 @@
|
||||
package config
|
||||
|
||||
// FastPoll used for fast testing
|
||||
var FastPoll = false
|
||||
@ -39,7 +39,6 @@ func (b byAllFields) Less(i, j int) bool {
|
||||
return b[i].RecordType <= b[j].RecordType
|
||||
}
|
||||
return b[i].Targets.String() <= b[j].Targets.String()
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
23
internal/testutils/init.go
Normal file
23
internal/testutils/init.go
Normal file
@ -0,0 +1,23 @@
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"sigs.k8s.io/external-dns/internal/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config.FastPoll = true
|
||||
if os.Getenv("DEBUG") == "" {
|
||||
logrus.SetOutput(ioutil.Discard)
|
||||
log.SetOutput(ioutil.Discard)
|
||||
} else {
|
||||
if level, err := logrus.ParseLevel(os.Getenv("DEBUG")); err == nil {
|
||||
logrus.SetLevel(level)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
@ -40,21 +41,18 @@ func (m *MockSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
||||
return endpoints.([]*endpoint.Endpoint), args.Error(1)
|
||||
}
|
||||
|
||||
// AddEventHandler adds an event handler function that's called when sources that support such a thing have changed.
|
||||
func (m *MockSource) AddEventHandler(handler func() error, stopChan <-chan struct{}, minInterval time.Duration) {
|
||||
// Execute callback handler no more than once per minInterval, until a message on stopChan is received.
|
||||
// AddEventHandler adds an event handler that should be triggered if something in source changes
|
||||
func (m *MockSource) AddEventHandler(ctx context.Context, handler func()) {
|
||||
go func() {
|
||||
var lastCallbackTime time.Time
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stopChan:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
now := time.Now()
|
||||
if now.After(lastCallbackTime.Add(minInterval)) {
|
||||
case <-ticker.C:
|
||||
handler()
|
||||
lastCallbackTime = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
132
main.go
132
main.go
@ -28,6 +28,32 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||
"sigs.k8s.io/external-dns/provider/akamai"
|
||||
"sigs.k8s.io/external-dns/provider/alibabacloud"
|
||||
"sigs.k8s.io/external-dns/provider/aws"
|
||||
"sigs.k8s.io/external-dns/provider/awssd"
|
||||
"sigs.k8s.io/external-dns/provider/azure"
|
||||
"sigs.k8s.io/external-dns/provider/cloudflare"
|
||||
"sigs.k8s.io/external-dns/provider/coredns"
|
||||
"sigs.k8s.io/external-dns/provider/designate"
|
||||
"sigs.k8s.io/external-dns/provider/digitalocean"
|
||||
"sigs.k8s.io/external-dns/provider/dnsimple"
|
||||
"sigs.k8s.io/external-dns/provider/dyn"
|
||||
"sigs.k8s.io/external-dns/provider/exoscale"
|
||||
"sigs.k8s.io/external-dns/provider/google"
|
||||
"sigs.k8s.io/external-dns/provider/infoblox"
|
||||
"sigs.k8s.io/external-dns/provider/inmemory"
|
||||
"sigs.k8s.io/external-dns/provider/linode"
|
||||
"sigs.k8s.io/external-dns/provider/ns1"
|
||||
"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/rcode0"
|
||||
"sigs.k8s.io/external-dns/provider/rdns"
|
||||
"sigs.k8s.io/external-dns/provider/rfc2136"
|
||||
"sigs.k8s.io/external-dns/provider/transip"
|
||||
"sigs.k8s.io/external-dns/provider/vinyldns"
|
||||
"sigs.k8s.io/external-dns/provider/vultr"
|
||||
|
||||
"sigs.k8s.io/external-dns/controller"
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
@ -63,12 +89,10 @@ func main() {
|
||||
}
|
||||
log.SetLevel(ll)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
stopChan := make(chan struct{}, 1)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go serveMetrics(cfg.MetricsAddress)
|
||||
go handleSigterm(stopChan)
|
||||
go handleSigterm(cancel)
|
||||
|
||||
// Create a source.Config from the flags passed by the user.
|
||||
sourceCfg := &source.Config{
|
||||
@ -123,8 +147,8 @@ func main() {
|
||||
var p provider.Provider
|
||||
switch cfg.Provider {
|
||||
case "akamai":
|
||||
p = provider.NewAkamaiProvider(
|
||||
provider.AkamaiConfig{
|
||||
p = akamai.NewAkamaiProvider(
|
||||
akamai.AkamaiConfig{
|
||||
DomainFilter: domainFilter,
|
||||
ZoneIDFilter: zoneIDFilter,
|
||||
ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain,
|
||||
@ -135,10 +159,10 @@ func main() {
|
||||
},
|
||||
)
|
||||
case "alibabacloud":
|
||||
p, err = provider.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
|
||||
p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
|
||||
case "aws":
|
||||
p, err = provider.NewAWSProvider(
|
||||
provider.AWSConfig{
|
||||
p, err = aws.NewAWSProvider(
|
||||
aws.AWSConfig{
|
||||
DomainFilter: domainFilter,
|
||||
ZoneIDFilter: zoneIDFilter,
|
||||
ZoneTypeFilter: zoneTypeFilter,
|
||||
@ -158,36 +182,34 @@ func main() {
|
||||
log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry)
|
||||
cfg.Registry = "aws-sd"
|
||||
}
|
||||
p, err = provider.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.AWSAssumeRole, cfg.DryRun)
|
||||
p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.AWSAssumeRole, cfg.DryRun)
|
||||
case "azure-dns", "azure":
|
||||
p, err = provider.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
||||
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
||||
case "azure-private-dns":
|
||||
p, err = provider.NewAzurePrivateDNSProvider(domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureSubscriptionID, cfg.DryRun)
|
||||
p, err = azure.NewAzurePrivateDNSProvider(domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureSubscriptionID, cfg.DryRun)
|
||||
case "vinyldns":
|
||||
p, err = provider.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
case "vultr":
|
||||
p, err = provider.NewVultrProvider(domainFilter, cfg.DryRun)
|
||||
|
||||
case "ultradns":
|
||||
p, err = provider.NewUltraDNSProvider(domainFilter, cfg.DryRun )
|
||||
|
||||
case "cloudflare":
|
||||
p, err = provider.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareZonesPerPage, cfg.CloudflareProxied, cfg.DryRun)
|
||||
p, err = cloudflare.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareZonesPerPage, cfg.CloudflareProxied, cfg.DryRun)
|
||||
case "rcodezero":
|
||||
p, err = provider.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt)
|
||||
p, err = rcode0.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt)
|
||||
case "google":
|
||||
p, err = provider.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.DryRun)
|
||||
p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.DryRun)
|
||||
case "digitalocean":
|
||||
p, err = provider.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun)
|
||||
p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
|
||||
case "ovh":
|
||||
p, err = provider.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.DryRun)
|
||||
p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.DryRun)
|
||||
case "linode":
|
||||
p, err = provider.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version)
|
||||
p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version)
|
||||
case "dnsimple":
|
||||
p, err = provider.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
case "infoblox":
|
||||
p, err = provider.NewInfobloxProvider(
|
||||
provider.InfobloxConfig{
|
||||
p, err = infoblox.NewInfobloxProvider(
|
||||
infoblox.InfobloxConfig{
|
||||
DomainFilter: domainFilter,
|
||||
ZoneIDFilter: zoneIDFilter,
|
||||
Host: cfg.InfobloxGridHost,
|
||||
@ -202,8 +224,8 @@ func main() {
|
||||
},
|
||||
)
|
||||
case "dyn":
|
||||
p, err = provider.NewDynProvider(
|
||||
provider.DynConfig{
|
||||
p, err = dyn.NewDynProvider(
|
||||
dyn.DynConfig{
|
||||
DomainFilter: domainFilter,
|
||||
ZoneIDFilter: zoneIDFilter,
|
||||
DryRun: cfg.DryRun,
|
||||
@ -215,29 +237,29 @@ func main() {
|
||||
},
|
||||
)
|
||||
case "coredns", "skydns":
|
||||
p, err = provider.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
|
||||
p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
|
||||
case "rdns":
|
||||
p, err = provider.NewRDNSProvider(
|
||||
provider.RDNSConfig{
|
||||
p, err = rdns.NewRDNSProvider(
|
||||
rdns.RDNSConfig{
|
||||
DomainFilter: domainFilter,
|
||||
DryRun: cfg.DryRun,
|
||||
},
|
||||
)
|
||||
case "exoscale":
|
||||
p, err = provider.NewExoscaleProvider(cfg.ExoscaleEndpoint, cfg.ExoscaleAPIKey, cfg.ExoscaleAPISecret, cfg.DryRun, provider.ExoscaleWithDomain(domainFilter), provider.ExoscaleWithLogging()), nil
|
||||
p, err = exoscale.NewExoscaleProvider(cfg.ExoscaleEndpoint, cfg.ExoscaleAPIKey, cfg.ExoscaleAPISecret, cfg.DryRun, exoscale.ExoscaleWithDomain(domainFilter), exoscale.ExoscaleWithLogging()), nil
|
||||
case "inmemory":
|
||||
p, err = provider.NewInMemoryProvider(provider.InMemoryInitZones(cfg.InMemoryZones), provider.InMemoryWithDomain(domainFilter), provider.InMemoryWithLogging()), nil
|
||||
p, err = inmemory.NewInMemoryProvider(inmemory.InMemoryInitZones(cfg.InMemoryZones), inmemory.InMemoryWithDomain(domainFilter), inmemory.InMemoryWithLogging()), nil
|
||||
case "designate":
|
||||
p, err = provider.NewDesignateProvider(domainFilter, cfg.DryRun)
|
||||
p, err = designate.NewDesignateProvider(domainFilter, cfg.DryRun)
|
||||
case "pdns":
|
||||
p, err = provider.NewPDNSProvider(
|
||||
p, err = pdns.NewPDNSProvider(
|
||||
ctx,
|
||||
provider.PDNSConfig{
|
||||
pdns.PDNSConfig{
|
||||
DomainFilter: domainFilter,
|
||||
DryRun: cfg.DryRun,
|
||||
Server: cfg.PDNSServer,
|
||||
APIKey: cfg.PDNSAPIKey,
|
||||
TLSConfig: provider.TLSConfig{
|
||||
TLSConfig: pdns.TLSConfig{
|
||||
TLSEnabled: cfg.PDNSTLSEnabled,
|
||||
CAFilePath: cfg.TLSCA,
|
||||
ClientCertFilePath: cfg.TLSClientCert,
|
||||
@ -246,16 +268,16 @@ func main() {
|
||||
},
|
||||
)
|
||||
case "oci":
|
||||
var config *provider.OCIConfig
|
||||
config, err = provider.LoadOCIConfig(cfg.OCIConfigFile)
|
||||
var config *oci.OCIConfig
|
||||
config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
|
||||
if err == nil {
|
||||
p, err = provider.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
}
|
||||
case "rfc2136":
|
||||
p, err = provider.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, nil)
|
||||
p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, nil)
|
||||
case "ns1":
|
||||
p, err = provider.NewNS1Provider(
|
||||
provider.NS1Config{
|
||||
p, err = ns1.NewNS1Provider(
|
||||
ns1.NS1Config{
|
||||
DomainFilter: domainFilter,
|
||||
ZoneIDFilter: zoneIDFilter,
|
||||
NS1Endpoint: cfg.NS1Endpoint,
|
||||
@ -264,7 +286,7 @@ func main() {
|
||||
},
|
||||
)
|
||||
case "transip":
|
||||
p, err = provider.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun)
|
||||
p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun)
|
||||
default:
|
||||
log.Fatalf("unknown dns provider: %s", cfg.Provider)
|
||||
}
|
||||
@ -277,9 +299,9 @@ func main() {
|
||||
case "noop":
|
||||
r, err = registry.NewNoopRegistry(p)
|
||||
case "txt":
|
||||
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTOwnerID, cfg.TXTCacheInterval)
|
||||
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval)
|
||||
case "aws-sd":
|
||||
r, err = registry.NewAWSSDRegistry(p.(*provider.AWSSDProvider), cfg.TXTOwnerID)
|
||||
r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID)
|
||||
default:
|
||||
log.Fatalf("unknown registry: %s", cfg.Registry)
|
||||
}
|
||||
@ -301,13 +323,6 @@ func main() {
|
||||
DomainFilter: domainFilter,
|
||||
}
|
||||
|
||||
if cfg.UpdateEvents {
|
||||
// Add RunOnce as the handler function that will be called when ingress/service sources have changed.
|
||||
// Note that k8s Informers will perform an initial list operation, which results in the handler
|
||||
// function initially being called for every Service/Ingress that exists limted by minInterval.
|
||||
ctrl.Source.AddEventHandler(func() error { return ctrl.RunOnce(ctx) }, stopChan, 1*time.Minute)
|
||||
}
|
||||
|
||||
if cfg.Once {
|
||||
err := ctrl.RunOnce(ctx)
|
||||
if err != nil {
|
||||
@ -316,15 +331,24 @@ func main() {
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
ctrl.Run(ctx, stopChan)
|
||||
|
||||
if cfg.UpdateEvents {
|
||||
// Add RunOnce as the handler function that will be called when ingress/service sources have changed.
|
||||
// Note that k8s Informers will perform an initial list operation, which results in the handler
|
||||
// function initially being called for every Service/Ingress that exists
|
||||
ctrl.Source.AddEventHandler(ctx, func() { ctrl.ScheduleRunOnce(time.Now()) })
|
||||
}
|
||||
|
||||
func handleSigterm(stopChan chan struct{}) {
|
||||
ctrl.ScheduleRunOnce(time.Now())
|
||||
ctrl.Run(ctx)
|
||||
}
|
||||
|
||||
func handleSigterm(cancel func()) {
|
||||
signals := make(chan os.Signal, 1)
|
||||
signal.Notify(signals, syscall.SIGTERM)
|
||||
<-signals
|
||||
log.Info("Received SIGTERM. Terminating...")
|
||||
close(stopChan)
|
||||
cancel()
|
||||
}
|
||||
|
||||
func serveMetrics(address string) {
|
||||
|
||||
@ -109,6 +109,7 @@ type Config struct {
|
||||
Registry string
|
||||
TXTOwnerID string
|
||||
TXTPrefix string
|
||||
TXTSuffix string
|
||||
Interval time.Duration
|
||||
Once bool
|
||||
DryRun bool
|
||||
@ -139,6 +140,7 @@ type Config struct {
|
||||
NS1IgnoreSSL bool
|
||||
TransIPAccountName string
|
||||
TransIPPrivateKeyFile string
|
||||
DigitalOceanAPIPageSize int
|
||||
}
|
||||
|
||||
var defaultConfig = &Config{
|
||||
@ -205,6 +207,7 @@ var defaultConfig = &Config{
|
||||
Registry: "txt",
|
||||
TXTOwnerID: "default",
|
||||
TXTPrefix: "",
|
||||
TXTSuffix: "",
|
||||
TXTCacheInterval: 0,
|
||||
Interval: time.Minute,
|
||||
Once: false,
|
||||
@ -235,6 +238,7 @@ var defaultConfig = &Config{
|
||||
NS1IgnoreSSL: false,
|
||||
TransIPAccountName: "",
|
||||
TransIPPrivateKeyFile: "",
|
||||
DigitalOceanAPIPageSize: 50,
|
||||
}
|
||||
|
||||
// NewConfig returns new Config object
|
||||
@ -361,6 +365,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("pdns-tls-enabled", "When using the PowerDNS/PDNS provider, specify whether to use TLS (default: false, requires --tls-ca, optionally specify --tls-client-cert and --tls-client-cert-key)").Default(strconv.FormatBool(defaultConfig.PDNSTLSEnabled)).BoolVar(&cfg.PDNSTLSEnabled)
|
||||
app.Flag("ns1-endpoint", "When using the NS1 provider, specify the URL of the API endpoint to target (default: https://api.nsone.net/v1/)").Default(defaultConfig.NS1Endpoint).StringVar(&cfg.NS1Endpoint)
|
||||
app.Flag("ns1-ignoressl", "When using the NS1 provider, specify whether to verify the SSL certificate (default: false)").Default(strconv.FormatBool(defaultConfig.NS1IgnoreSSL)).BoolVar(&cfg.NS1IgnoreSSL)
|
||||
app.Flag("digitalocean-api-page-size", "Configure the page size used when querying the DigitalOcean API.").Default(strconv.Itoa(defaultConfig.DigitalOceanAPIPageSize)).IntVar(&cfg.DigitalOceanAPIPageSize)
|
||||
|
||||
// Flags related to TLS communication
|
||||
app.Flag("tls-ca", "When using TLS communication, the path to the certificate authority to verify server communications (optionally specify --tls-client-cert for two-way TLS)").Default(defaultConfig.TLSCA).StringVar(&cfg.TLSCA)
|
||||
@ -392,7 +397,8 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
// Flags related to the registry
|
||||
app.Flag("registry", "The registry implementation to use to keep track of DNS record ownership (default: txt, options: txt, noop, aws-sd)").Default(defaultConfig.Registry).EnumVar(&cfg.Registry, "txt", "noop", "aws-sd")
|
||||
app.Flag("txt-owner-id", "When using the TXT registry, a name that identifies this instance of ExternalDNS (default: default)").Default(defaultConfig.TXTOwnerID).StringVar(&cfg.TXTOwnerID)
|
||||
app.Flag("txt-prefix", "When using the TXT registry, a custom string that's prefixed to each ownership DNS record (optional)").Default(defaultConfig.TXTPrefix).StringVar(&cfg.TXTPrefix)
|
||||
app.Flag("txt-prefix", "When using the TXT registry, a custom string that's prefixed to each ownership DNS record (optional). Mutual exclusive with txt-suffix!").Default(defaultConfig.TXTPrefix).StringVar(&cfg.TXTPrefix)
|
||||
app.Flag("txt-suffix", "When using the TXT registry, a custom string that's suffixed to the host portion of each ownership DNS record (optional). Mutual exclusive with txt-prefix!").Default(defaultConfig.TXTSuffix).StringVar(&cfg.TXTSuffix)
|
||||
|
||||
// Flags related to the main control loop
|
||||
app.Flag("txt-cache-interval", "The interval between cache synchronizations in duration format (default: disabled)").Default(defaultConfig.TXTCacheInterval.String()).DurationVar(&cfg.TXTCacheInterval)
|
||||
|
||||
@ -98,6 +98,7 @@ var (
|
||||
RcodezeroTXTEncrypt: false,
|
||||
TransIPAccountName: "",
|
||||
TransIPPrivateKeyFile: "",
|
||||
DigitalOceanAPIPageSize: 50,
|
||||
}
|
||||
|
||||
overriddenConfig = &Config{
|
||||
@ -177,6 +178,7 @@ var (
|
||||
NS1IgnoreSSL: true,
|
||||
TransIPAccountName: "transip",
|
||||
TransIPPrivateKeyFile: "/path/to/transip.key",
|
||||
DigitalOceanAPIPageSize: 100,
|
||||
}
|
||||
)
|
||||
|
||||
@ -280,6 +282,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"--ns1-ignoressl",
|
||||
"--transip-account=transip",
|
||||
"--transip-keyfile=/path/to/transip.key",
|
||||
"--digitalocean-api-page-size=100",
|
||||
},
|
||||
envVars: map[string]string{},
|
||||
expected: overriddenConfig,
|
||||
@ -364,6 +367,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_NS1_IGNORESSL": "1",
|
||||
"EXTERNAL_DNS_TRANSIP_ACCOUNT": "transip",
|
||||
"EXTERNAL_DNS_TRANSIP_KEYFILE": "/path/to/transip.key",
|
||||
"EXTERNAL_DNS_DIGITALOCEAN_API_PAGE_SIZE": "100",
|
||||
},
|
||||
expected: overriddenConfig,
|
||||
},
|
||||
|
||||
@ -91,5 +91,10 @@ func ValidateConfig(cfg *externaldns.Config) error {
|
||||
if cfg.IgnoreHostnameAnnotation && cfg.FQDNTemplate == "" {
|
||||
return errors.New("FQDN Template must be set if ignoring annotations")
|
||||
}
|
||||
|
||||
if len(cfg.TXTPrefix) > 0 && len(cfg.TXTSuffix) > 0 {
|
||||
return errors.New("txt-prefix and txt-suffix are mutual exclusive")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,238 +0,0 @@
|
||||
/*
|
||||
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 async
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// BoundedFrequencyRunner manages runs of a user-provided function.
|
||||
// See NewBoundedFrequencyRunner for examples.
|
||||
type BoundedFrequencyRunner struct {
|
||||
name string // the name of this instance
|
||||
minInterval time.Duration // the min time between runs, modulo bursts
|
||||
maxInterval time.Duration // the max time between runs
|
||||
|
||||
run chan struct{} // try an async run
|
||||
|
||||
mu sync.Mutex // guards runs of fn and all mutations
|
||||
fn func() // function to run
|
||||
lastRun time.Time // time of last run
|
||||
timer timer // timer for deferred runs
|
||||
limiter rateLimiter // rate limiter for on-demand runs
|
||||
}
|
||||
|
||||
// designed so that flowcontrol.RateLimiter satisfies
|
||||
type rateLimiter interface {
|
||||
TryAccept() bool
|
||||
Stop()
|
||||
}
|
||||
|
||||
type nullLimiter struct{}
|
||||
|
||||
func (nullLimiter) TryAccept() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (nullLimiter) Stop() {}
|
||||
|
||||
var _ rateLimiter = nullLimiter{}
|
||||
|
||||
// for testing
|
||||
type timer interface {
|
||||
// C returns the timer's selectable channel.
|
||||
C() <-chan time.Time
|
||||
|
||||
// See time.Timer.Reset.
|
||||
Reset(d time.Duration) bool
|
||||
|
||||
// See time.Timer.Stop.
|
||||
Stop() bool
|
||||
|
||||
// See time.Now.
|
||||
Now() time.Time
|
||||
|
||||
// See time.Since.
|
||||
Since(t time.Time) time.Duration
|
||||
|
||||
// See time.Sleep.
|
||||
Sleep(d time.Duration)
|
||||
}
|
||||
|
||||
// implement our timer in terms of std time.Timer.
|
||||
type realTimer struct {
|
||||
*time.Timer
|
||||
}
|
||||
|
||||
func (rt realTimer) C() <-chan time.Time {
|
||||
return rt.Timer.C
|
||||
}
|
||||
|
||||
func (rt realTimer) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (rt realTimer) Since(t time.Time) time.Duration {
|
||||
return time.Since(t)
|
||||
}
|
||||
|
||||
func (rt realTimer) Sleep(d time.Duration) {
|
||||
time.Sleep(d)
|
||||
}
|
||||
|
||||
var _ timer = realTimer{}
|
||||
|
||||
// NewBoundedFrequencyRunner creates a new BoundedFrequencyRunner instance,
|
||||
// which will manage runs of the specified function.
|
||||
//
|
||||
// All runs will be async to the caller of BoundedFrequencyRunner.Run, but
|
||||
// multiple runs are serialized. If the function needs to hold locks, it must
|
||||
// take them internally.
|
||||
//
|
||||
// Runs of the function will have at least minInterval between them (from
|
||||
// completion to next start), except that up to bursts may be allowed. Burst
|
||||
// runs are "accumulated" over time, one per minInterval up to burstRuns total.
|
||||
// This can be used, for example, to mitigate the impact of expensive operations
|
||||
// being called in response to user-initiated operations. Run requests that
|
||||
// would violate the minInterval are coallesced and run at the next opportunity.
|
||||
//
|
||||
// The function will be run at least once per maxInterval. For example, this can
|
||||
// force periodic refreshes of state in the absence of anyone calling Run.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// NewBoundedFrequencyRunner("name", fn, time.Second, 5*time.Second, 1)
|
||||
// - fn will have at least 1 second between runs
|
||||
// - fn will have no more than 5 seconds between runs
|
||||
//
|
||||
// NewBoundedFrequencyRunner("name", fn, 3*time.Second, 10*time.Second, 3)
|
||||
// - fn will have at least 3 seconds between runs, with up to 3 burst runs
|
||||
// - fn will have no more than 10 seconds between runs
|
||||
//
|
||||
// The maxInterval must be greater than or equal to the minInterval, If the
|
||||
// caller passes a maxInterval less than minInterval, this function will panic.
|
||||
func NewBoundedFrequencyRunner(name string, fn func(), minInterval, maxInterval time.Duration, burstRuns int) *BoundedFrequencyRunner {
|
||||
timer := realTimer{Timer: time.NewTimer(0)} // will tick immediately
|
||||
<-timer.C() // consume the first tick
|
||||
return construct(name, fn, minInterval, maxInterval, burstRuns, timer)
|
||||
}
|
||||
|
||||
// Make an instance with dependencies injected.
|
||||
func construct(name string, fn func(), minInterval, maxInterval time.Duration, burstRuns int, timer timer) *BoundedFrequencyRunner {
|
||||
if maxInterval < minInterval {
|
||||
panic(fmt.Sprintf("%s: maxInterval (%v) must be >= minInterval (%v)", name, minInterval, maxInterval))
|
||||
}
|
||||
if timer == nil {
|
||||
panic(fmt.Sprintf("%s: timer must be non-nil", name))
|
||||
}
|
||||
|
||||
bfr := &BoundedFrequencyRunner{
|
||||
name: name,
|
||||
fn: fn,
|
||||
minInterval: minInterval,
|
||||
maxInterval: maxInterval,
|
||||
run: make(chan struct{}, 1),
|
||||
timer: timer,
|
||||
}
|
||||
if minInterval == 0 {
|
||||
bfr.limiter = nullLimiter{}
|
||||
} else {
|
||||
// allow burst updates in short succession
|
||||
qps := float32(time.Second) / float32(minInterval)
|
||||
bfr.limiter = flowcontrol.NewTokenBucketRateLimiterWithClock(qps, burstRuns, timer)
|
||||
}
|
||||
return bfr
|
||||
}
|
||||
|
||||
// Loop handles the periodic timer and run requests. This is expected to be
|
||||
// called as a goroutine.
|
||||
func (bfr *BoundedFrequencyRunner) Loop(stop <-chan struct{}) {
|
||||
klog.V(3).Infof("%s Loop running", bfr.name)
|
||||
bfr.timer.Reset(bfr.maxInterval)
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
bfr.stop()
|
||||
klog.V(3).Infof("%s Loop stopping", bfr.name)
|
||||
return
|
||||
case <-bfr.timer.C():
|
||||
bfr.tryRun()
|
||||
case <-bfr.run:
|
||||
bfr.tryRun()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the function as soon as possible. If this is called while Loop is not
|
||||
// running, the call may be deferred indefinitely.
|
||||
// If there is already a queued request to call the underlying function, it
|
||||
// may be dropped - it is just guaranteed that we will try calling the
|
||||
// underlying function as soon as possible starting from now.
|
||||
func (bfr *BoundedFrequencyRunner) Run() {
|
||||
// If it takes a lot of time to run the underlying function, noone is really
|
||||
// processing elements from <run> channel. So to avoid blocking here on the
|
||||
// putting element to it, we simply skip it if there is already an element
|
||||
// in it.
|
||||
select {
|
||||
case bfr.run <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// assumes the lock is not held
|
||||
func (bfr *BoundedFrequencyRunner) stop() {
|
||||
bfr.mu.Lock()
|
||||
defer bfr.mu.Unlock()
|
||||
bfr.limiter.Stop()
|
||||
bfr.timer.Stop()
|
||||
}
|
||||
|
||||
// assumes the lock is not held
|
||||
func (bfr *BoundedFrequencyRunner) tryRun() {
|
||||
bfr.mu.Lock()
|
||||
defer bfr.mu.Unlock()
|
||||
|
||||
if bfr.limiter.TryAccept() {
|
||||
// We're allowed to run the function right now.
|
||||
bfr.fn()
|
||||
bfr.lastRun = bfr.timer.Now()
|
||||
bfr.timer.Stop()
|
||||
bfr.timer.Reset(bfr.maxInterval)
|
||||
klog.V(3).Infof("%s: ran, next possible in %v, periodic in %v", bfr.name, bfr.minInterval, bfr.maxInterval)
|
||||
return
|
||||
}
|
||||
|
||||
// It can't run right now, figure out when it can run next.
|
||||
|
||||
elapsed := bfr.timer.Since(bfr.lastRun) // how long since last run
|
||||
nextPossible := bfr.minInterval - elapsed // time to next possible run
|
||||
nextScheduled := bfr.maxInterval - elapsed // time to next periodic run
|
||||
klog.V(4).Infof("%s: %v since last run, possible in %v, scheduled in %v", bfr.name, elapsed, nextPossible, nextScheduled)
|
||||
|
||||
if nextPossible < nextScheduled {
|
||||
// Set the timer for ASAP, but don't drain here. Assuming Loop is running,
|
||||
// it might get a delivery in the mean time, but that is OK.
|
||||
bfr.timer.Stop()
|
||||
bfr.timer.Reset(nextPossible)
|
||||
klog.V(3).Infof("%s: throttled, scheduling run in %v", bfr.name, nextPossible)
|
||||
}
|
||||
}
|
||||
74
plan/plan.go
74
plan/plan.go
@ -18,11 +18,15 @@ package plan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
)
|
||||
|
||||
// PropertyComparator is used in Plan for comparing the previous and current custom annotations.
|
||||
type PropertyComparator func(name string, previous string, current string) bool
|
||||
|
||||
// Plan can convert a list of desired and current records to a series of create,
|
||||
// update and delete actions.
|
||||
type Plan struct {
|
||||
@ -37,6 +41,8 @@ type Plan struct {
|
||||
Changes *Changes
|
||||
// DomainFilter matches DNS names
|
||||
DomainFilter endpoint.DomainFilter
|
||||
// Property comparator compares custom properties of providers
|
||||
PropertyComparator PropertyComparator
|
||||
}
|
||||
|
||||
// Changes holds lists of actions to be executed by dns providers
|
||||
@ -135,7 +141,7 @@ func (p *Plan) Calculate() *Plan {
|
||||
if row.current != nil && len(row.candidates) > 0 { //dns name is taken
|
||||
update := t.resolver.ResolveUpdate(row.current, row.candidates)
|
||||
// compare "update" to "current" to figure out if actual update is required
|
||||
if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || shouldUpdateProviderSpecific(update, row.current) {
|
||||
if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || p.shouldUpdateProviderSpecific(update, row.current) {
|
||||
inheritOwner(row.current, update)
|
||||
changes.UpdateNew = append(changes.UpdateNew, update)
|
||||
changes.UpdateOld = append(changes.UpdateOld, row.current)
|
||||
@ -178,10 +184,15 @@ func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool {
|
||||
return desired.RecordTTL != current.RecordTTL
|
||||
}
|
||||
|
||||
func shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool {
|
||||
if current.ProviderSpecific == nil && len(desired.ProviderSpecific) == 0 {
|
||||
return false
|
||||
func (p *Plan) shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool {
|
||||
desiredProperties := map[string]endpoint.ProviderSpecificProperty{}
|
||||
|
||||
if desired.ProviderSpecific != nil {
|
||||
for _, d := range desired.ProviderSpecific {
|
||||
desiredProperties[d.Name] = d
|
||||
}
|
||||
}
|
||||
if current.ProviderSpecific != nil {
|
||||
for _, c := range current.ProviderSpecific {
|
||||
// don't consider target health when detecting changes
|
||||
// see: https://github.com/kubernetes-sigs/external-dns/issues/869#issuecomment-458576954
|
||||
@ -189,33 +200,23 @@ func shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool {
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, d := range desired.ProviderSpecific {
|
||||
if d.Name == c.Name {
|
||||
if d.Value != c.Value {
|
||||
// provider-specific attribute updated
|
||||
if d, ok := desiredProperties[c.Name]; ok {
|
||||
if p.PropertyComparator != nil {
|
||||
if !p.PropertyComparator(c.Name, c.Value, d.Value) {
|
||||
return true
|
||||
}
|
||||
found = true
|
||||
break
|
||||
} else if c.Value != d.Value {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if p.PropertyComparator != nil {
|
||||
if !p.PropertyComparator(c.Name, c.Value, "") {
|
||||
return true
|
||||
}
|
||||
if !found {
|
||||
// provider-specific attribute deleted
|
||||
} else if c.Value != "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, d := range desired.ProviderSpecific {
|
||||
found := false
|
||||
for _, c := range current.ProviderSpecific {
|
||||
if d.Name == c.Name {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// provider-specific attribute added
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@ -260,3 +261,28 @@ func normalizeDNSName(dnsName string) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// CompareBoolean is an implementation of PropertyComparator for comparing boolean-line values
|
||||
// For example external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"
|
||||
// If value doesn't parse as boolean, the defaultValue is used
|
||||
func CompareBoolean(defaultValue bool, name, current, previous string) bool {
|
||||
var err error
|
||||
|
||||
v1, v2 := defaultValue, defaultValue
|
||||
|
||||
if previous != "" {
|
||||
v1, err = strconv.ParseBool(previous)
|
||||
if err != nil {
|
||||
v1 = defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
if current != "" {
|
||||
v2, err = strconv.ParseBool(current)
|
||||
if err != nil {
|
||||
v2 = defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
return v1 == v2
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ type PlanTestSuite struct {
|
||||
bar127AWithTTL *endpoint.Endpoint
|
||||
bar127AWithProviderSpecificTrue *endpoint.Endpoint
|
||||
bar127AWithProviderSpecificFalse *endpoint.Endpoint
|
||||
bar127AWithProviderSpecificUnset *endpoint.Endpoint
|
||||
bar192A *endpoint.Endpoint
|
||||
multiple1 *endpoint.Endpoint
|
||||
multiple2 *endpoint.Endpoint
|
||||
@ -138,6 +139,15 @@ func (suite *PlanTestSuite) SetupTest() {
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.bar127AWithProviderSpecificUnset = &endpoint.Endpoint{
|
||||
DNSName: "bar",
|
||||
Targets: endpoint.Targets{"127.0.0.1"},
|
||||
RecordType: "A",
|
||||
Labels: map[string]string{
|
||||
endpoint.ResourceLabelKey: "ingress/default/bar-127",
|
||||
},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{},
|
||||
}
|
||||
suite.bar192A = &endpoint.Endpoint{
|
||||
DNSName: "bar",
|
||||
Targets: endpoint.Targets{"192.168.0.1"},
|
||||
@ -291,6 +301,54 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificChange() {
|
||||
validateEntries(suite.T(), changes.Delete, expectedDelete)
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificDefaultFalse() {
|
||||
current := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificFalse}
|
||||
desired := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificUnset}
|
||||
expectedCreate := []*endpoint.Endpoint{}
|
||||
expectedUpdateOld := []*endpoint.Endpoint{}
|
||||
expectedUpdateNew := []*endpoint.Endpoint{}
|
||||
expectedDelete := []*endpoint.Endpoint{}
|
||||
|
||||
p := &Plan{
|
||||
Policies: []Policy{&SyncPolicy{}},
|
||||
Current: current,
|
||||
Desired: desired,
|
||||
PropertyComparator: func(name, previous, current string) bool {
|
||||
return CompareBoolean(false, name, previous, current)
|
||||
},
|
||||
}
|
||||
|
||||
changes := p.Calculate().Changes
|
||||
validateEntries(suite.T(), changes.Create, expectedCreate)
|
||||
validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
|
||||
validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
|
||||
validateEntries(suite.T(), changes.Delete, expectedDelete)
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificDefualtTrue() {
|
||||
current := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificTrue}
|
||||
desired := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificUnset}
|
||||
expectedCreate := []*endpoint.Endpoint{}
|
||||
expectedUpdateOld := []*endpoint.Endpoint{}
|
||||
expectedUpdateNew := []*endpoint.Endpoint{}
|
||||
expectedDelete := []*endpoint.Endpoint{}
|
||||
|
||||
p := &Plan{
|
||||
Policies: []Policy{&SyncPolicy{}},
|
||||
Current: current,
|
||||
Desired: desired,
|
||||
PropertyComparator: func(name, previous, current string) bool {
|
||||
return CompareBoolean(true, name, previous, current)
|
||||
},
|
||||
}
|
||||
|
||||
changes := p.Calculate().Changes
|
||||
validateEntries(suite.T(), changes.Create, expectedCreate)
|
||||
validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
|
||||
validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
|
||||
validateEntries(suite.T(), changes.Delete, expectedDelete)
|
||||
}
|
||||
|
||||
func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() {
|
||||
current := []*endpoint.Endpoint{suite.fooV1Cname}
|
||||
desired := []*endpoint.Endpoint{suite.fooV2Cname}
|
||||
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package akamai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -30,6 +30,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type akamaiClient interface {
|
||||
@ -50,7 +51,7 @@ func (*akamaiOpenClient) Do(config edgegrid.Config, req *http.Request) (*http.Re
|
||||
// AkamaiConfig clarifies the method signature
|
||||
type AkamaiConfig struct {
|
||||
DomainFilter endpoint.DomainFilter
|
||||
ZoneIDFilter ZoneIDFilter
|
||||
ZoneIDFilter provider.ZoneIDFilter
|
||||
ServiceConsumerDomain string
|
||||
ClientToken string
|
||||
ClientSecret string
|
||||
@ -60,8 +61,9 @@ type AkamaiConfig struct {
|
||||
|
||||
// AkamaiProvider implements the DNS provider for Akamai.
|
||||
type AkamaiProvider struct {
|
||||
provider.BaseProvider
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
config edgegrid.Config
|
||||
dryRun bool
|
||||
client akamaiClient
|
||||
@ -226,7 +228,7 @@ func (p *AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoin
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *AkamaiProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
zones, err := p.fetchZones()
|
||||
if err != nil {
|
||||
log.Warnf("No zones to fetch endpoints from!")
|
||||
@ -289,9 +291,8 @@ func (p *AkamaiProvider) newAkamaiRecord(dnsName, recordType string, targets ...
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AkamaiProvider) createRecords(zoneNameIDMapper zoneIDName, endpoints []*endpoint.Endpoint) (created []*endpoint.Endpoint, failed []*endpoint.Endpoint) {
|
||||
func (p *AkamaiProvider) createRecords(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) (created []*endpoint.Endpoint, failed []*endpoint.Endpoint) {
|
||||
for _, endpoint := range endpoints {
|
||||
|
||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
||||
log.Debugf("Skipping creation at Akamai of endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
|
||||
continue
|
||||
@ -320,9 +321,8 @@ func (p *AkamaiProvider) createRecords(zoneNameIDMapper zoneIDName, endpoints []
|
||||
return created, failed
|
||||
}
|
||||
|
||||
func (p *AkamaiProvider) deleteRecords(zoneNameIDMapper zoneIDName, endpoints []*endpoint.Endpoint) (deleted []*endpoint.Endpoint, failed []*endpoint.Endpoint) {
|
||||
func (p *AkamaiProvider) deleteRecords(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) (deleted []*endpoint.Endpoint, failed []*endpoint.Endpoint) {
|
||||
for _, endpoint := range endpoints {
|
||||
|
||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
||||
log.Debugf("Skipping deletion at Akamai of endpoint: '%s' type: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
|
||||
continue
|
||||
@ -349,9 +349,8 @@ func (p *AkamaiProvider) deleteRecords(zoneNameIDMapper zoneIDName, endpoints []
|
||||
return deleted, failed
|
||||
}
|
||||
|
||||
func (p *AkamaiProvider) updateNewRecords(zoneNameIDMapper zoneIDName, endpoints []*endpoint.Endpoint) (updated []*endpoint.Endpoint, failed []*endpoint.Endpoint) {
|
||||
func (p *AkamaiProvider) updateNewRecords(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) (updated []*endpoint.Endpoint, failed []*endpoint.Endpoint) {
|
||||
for _, endpoint := range endpoints {
|
||||
|
||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
||||
log.Debugf("Skipping update at Akamai of endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
|
||||
continue
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package akamai
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -32,6 +32,7 @@ import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type mockAkamaiClient struct {
|
||||
@ -92,7 +93,7 @@ func TestRequestError(t *testing.T) {
|
||||
|
||||
func TestFetchZonesZoneIDFilter(t *testing.T) {
|
||||
config := AkamaiConfig{
|
||||
ZoneIDFilter: NewZoneIDFilter([]string{"Test"}),
|
||||
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Test"}),
|
||||
}
|
||||
|
||||
client := &mockAkamaiClient{}
|
||||
@ -109,7 +110,7 @@ func TestFetchZonesZoneIDFilter(t *testing.T) {
|
||||
func TestFetchZonesEmpty(t *testing.T) {
|
||||
config := AkamaiConfig{
|
||||
DomainFilter: endpoint.NewDomainFilter([]string{"Nonexistent"}),
|
||||
ZoneIDFilter: NewZoneIDFilter([]string{"Nonexistent"}),
|
||||
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Nonexistent"}),
|
||||
}
|
||||
|
||||
client := &mockAkamaiClient{}
|
||||
@ -171,7 +172,7 @@ func TestAkamaiRecords(t *testing.T) {
|
||||
|
||||
func TestAkamaiRecordsEmpty(t *testing.T) {
|
||||
config := AkamaiConfig{
|
||||
ZoneIDFilter: NewZoneIDFilter([]string{"Nonexistent"}),
|
||||
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Nonexistent"}),
|
||||
}
|
||||
|
||||
client := &mockAkamaiClient{}
|
||||
@ -185,7 +186,7 @@ func TestAkamaiRecordsEmpty(t *testing.T) {
|
||||
func TestAkamaiRecordsFilters(t *testing.T) {
|
||||
config := AkamaiConfig{
|
||||
DomainFilter: endpoint.NewDomainFilter([]string{"www.exclude.me"}),
|
||||
ZoneIDFilter: NewZoneIDFilter([]string{"Exclude-Me"}),
|
||||
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Exclude-Me"}),
|
||||
}
|
||||
|
||||
client := &mockAkamaiClient{}
|
||||
@ -208,7 +209,7 @@ func TestCreateRecords(t *testing.T) {
|
||||
c := NewAkamaiProvider(config)
|
||||
c.client = client
|
||||
|
||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
||||
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||
endpoints := make([]*endpoint.Endpoint, 0)
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
|
||||
@ -228,7 +229,7 @@ func TestCreateRecordsDomainFilter(t *testing.T) {
|
||||
c := NewAkamaiProvider(config)
|
||||
c.client = client
|
||||
|
||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
||||
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||
endpoints := make([]*endpoint.Endpoint, 0)
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
|
||||
@ -247,7 +248,7 @@ func TestDeleteRecords(t *testing.T) {
|
||||
c := NewAkamaiProvider(config)
|
||||
c.client = client
|
||||
|
||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
||||
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||
endpoints := make([]*endpoint.Endpoint, 0)
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
|
||||
@ -267,7 +268,7 @@ func TestDeleteRecordsDomainFilter(t *testing.T) {
|
||||
c := NewAkamaiProvider(config)
|
||||
c.client = client
|
||||
|
||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
||||
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||
endpoints := make([]*endpoint.Endpoint, 0)
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
|
||||
@ -286,7 +287,7 @@ func TestUpdateRecords(t *testing.T) {
|
||||
c := NewAkamaiProvider(config)
|
||||
c.client = client
|
||||
|
||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
||||
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||
endpoints := make([]*endpoint.Endpoint, 0)
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
|
||||
@ -306,7 +307,7 @@ func TestUpdateRecordsDomainFilter(t *testing.T) {
|
||||
c := NewAkamaiProvider(config)
|
||||
c.client = client
|
||||
|
||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
||||
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||
endpoints := make([]*endpoint.Endpoint, 0)
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeA, "10.0.0.2", "10.0.0.3"))
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint("www.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"))
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package alibabacloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -33,6 +33,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -66,8 +67,9 @@ type AlibabaCloudPrivateZoneAPI interface {
|
||||
|
||||
// AlibabaCloudProvider implements the DNS provider for Alibaba Cloud.
|
||||
type AlibabaCloudProvider struct {
|
||||
provider.BaseProvider
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter // Private Zone only
|
||||
zoneIDFilter provider.ZoneIDFilter // Private Zone only
|
||||
MaxChangeCount int
|
||||
EvaluateTargetHealth bool
|
||||
AssumeRole string
|
||||
@ -93,22 +95,22 @@ type alibabaCloudConfig struct {
|
||||
// NewAlibabaCloudProvider creates a new Alibaba Cloud provider.
|
||||
//
|
||||
// Returns the provider or an error if a provider could not be created.
|
||||
func NewAlibabaCloudProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFileter ZoneIDFilter, zoneType string, dryRun bool) (*AlibabaCloudProvider, error) {
|
||||
func NewAlibabaCloudProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFileter provider.ZoneIDFilter, zoneType string, dryRun bool) (*AlibabaCloudProvider, error) {
|
||||
cfg := alibabaCloudConfig{}
|
||||
if configFile != "" {
|
||||
contents, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to read Alibaba Cloud config file '%s': %v", configFile, err)
|
||||
return nil, fmt.Errorf("failed to read Alibaba Cloud config file '%s': %v", configFile, err)
|
||||
}
|
||||
err = yaml.Unmarshal(contents, &cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to parse Alibaba Cloud config file '%s': %v", configFile, err)
|
||||
return nil, fmt.Errorf("failed to parse Alibaba Cloud config file '%s': %v", configFile, err)
|
||||
}
|
||||
} else {
|
||||
var tmpError error
|
||||
cfg, tmpError = getCloudConfigFromStsToken()
|
||||
if tmpError != nil {
|
||||
return nil, fmt.Errorf("Failed to getCloudConfigFromStsToken: %v", tmpError)
|
||||
return nil, fmt.Errorf("failed to getCloudConfigFromStsToken: %v", tmpError)
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,19 +182,19 @@ func getCloudConfigFromStsToken() (alibabaCloudConfig, error) {
|
||||
roleName := ""
|
||||
var err error
|
||||
if roleName, err = m.RoleName(); err != nil {
|
||||
return cfg, fmt.Errorf("Failed to get role name from Metadata Service: %v", err)
|
||||
return cfg, fmt.Errorf("failed to get role name from Metadata Service: %v", err)
|
||||
}
|
||||
vpcID, err := m.VpcID()
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("Failed to get VPC ID from Metadata Service: %v", err)
|
||||
return cfg, fmt.Errorf("failed to get VPC ID from Metadata Service: %v", err)
|
||||
}
|
||||
regionID, err := m.Region()
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("Failed to get Region ID from Metadata Service: %v", err)
|
||||
return cfg, fmt.Errorf("failed to get Region ID from Metadata Service: %v", err)
|
||||
}
|
||||
role, err := m.RamRoleToken(roleName)
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("Failed to get STS Token from Metadata Service: %v", err)
|
||||
return cfg, fmt.Errorf("failed to get STS Token from Metadata Service: %v", err)
|
||||
}
|
||||
cfg.RegionID = regionID
|
||||
cfg.RoleName = roleName
|
||||
@ -315,7 +317,6 @@ func (p *AlibabaCloudProvider) getDNSName(rr, domain string) string {
|
||||
//
|
||||
// Returns the current records or an error if the operation failed.
|
||||
func (p *AlibabaCloudProvider) recordsForDNS() (endpoints []*endpoint.Endpoint, _ error) {
|
||||
|
||||
records, err := p.records()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -344,7 +345,6 @@ func (p *AlibabaCloudProvider) recordsForDNS() (endpoints []*endpoint.Endpoint,
|
||||
}
|
||||
|
||||
func getNextPageNumber(pageNumber, pageSize, totalCount int) int {
|
||||
|
||||
if pageNumber*pageSize >= totalCount {
|
||||
return 0
|
||||
}
|
||||
@ -363,18 +363,13 @@ func (p *AlibabaCloudProvider) getRecordKeyByEndpoint(endpoint *endpoint.Endpoin
|
||||
}
|
||||
|
||||
func (p *AlibabaCloudProvider) groupRecords(records []alidns.Record) (endpointMap map[string][]alidns.Record) {
|
||||
|
||||
endpointMap = make(map[string][]alidns.Record)
|
||||
|
||||
for _, record := range records {
|
||||
|
||||
key := p.getRecordKey(record)
|
||||
|
||||
recordList := endpointMap[key]
|
||||
endpointMap[key] = append(recordList, record)
|
||||
|
||||
}
|
||||
|
||||
return endpointMap
|
||||
}
|
||||
|
||||
@ -449,18 +444,15 @@ func (p *AlibabaCloudProvider) getDomainRecords(domainName string) ([]alidns.Rec
|
||||
}
|
||||
|
||||
for _, record := range response.DomainRecords.Record {
|
||||
|
||||
domainName := record.DomainName
|
||||
recordType := record.Type
|
||||
|
||||
if !p.domainFilter.Match(domainName) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !supportedRecordType(recordType) {
|
||||
if !provider.SupportedRecordType(recordType) {
|
||||
continue
|
||||
}
|
||||
|
||||
//TODO filter Locked record
|
||||
results = append(results, record)
|
||||
}
|
||||
@ -622,7 +614,6 @@ func (p *AlibabaCloudProvider) equals(record alidns.Record, endpoint *endpoint.E
|
||||
}
|
||||
|
||||
func (p *AlibabaCloudProvider) updateRecords(recordMap map[string][]alidns.Record, endpoints []*endpoint.Endpoint) error {
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
key := p.getRecordKeyByEndpoint(endpoint)
|
||||
records := recordMap[key]
|
||||
@ -667,7 +658,6 @@ func (p *AlibabaCloudProvider) updateRecords(recordMap map[string][]alidns.Recor
|
||||
}
|
||||
|
||||
func (p *AlibabaCloudProvider) splitDNSName(endpoint *endpoint.Endpoint) (rr string, domain string) {
|
||||
|
||||
name := strings.TrimSuffix(endpoint.DNSName, ".")
|
||||
|
||||
found := false
|
||||
@ -727,7 +717,6 @@ func (p *AlibabaCloudProvider) matchVPC(zoneID string) bool {
|
||||
}
|
||||
|
||||
func (p *AlibabaCloudProvider) privateZones() ([]pvtz.Zone, error) {
|
||||
|
||||
var zones []pvtz.Zone
|
||||
|
||||
request := pvtz.CreateDescribeZonesRequest()
|
||||
@ -782,7 +771,6 @@ func (p *AlibabaCloudProvider) getPrivateZones() (map[string]*alibabaPrivateZone
|
||||
}
|
||||
|
||||
for _, zone := range zones {
|
||||
|
||||
request := pvtz.CreateDescribeZoneRecordsRequest()
|
||||
request.ZoneId = zone.ZoneId
|
||||
request.PageSize = requests.NewInteger(defaultAlibabaCloudPageSize)
|
||||
@ -799,10 +787,9 @@ func (p *AlibabaCloudProvider) getPrivateZones() (map[string]*alibabaPrivateZone
|
||||
}
|
||||
|
||||
for _, record := range response.Records.Record {
|
||||
|
||||
recordType := record.Type
|
||||
|
||||
if !supportedRecordType(recordType) {
|
||||
if !provider.SupportedRecordType(recordType) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -829,7 +816,6 @@ func (p *AlibabaCloudProvider) getPrivateZones() (map[string]*alibabaPrivateZone
|
||||
}
|
||||
|
||||
func (p *AlibabaCloudProvider) groupPrivateZoneRecords(zone *alibabaPrivateZone) (endpointMap map[string][]pvtz.Record) {
|
||||
|
||||
endpointMap = make(map[string][]pvtz.Record)
|
||||
|
||||
for _, record := range zone.records {
|
||||
@ -845,7 +831,6 @@ func (p *AlibabaCloudProvider) groupPrivateZoneRecords(zone *alibabaPrivateZone)
|
||||
//
|
||||
// Returns the current records or an error if the operation failed.
|
||||
func (p *AlibabaCloudProvider) privateZoneRecords() (endpoints []*endpoint.Endpoint, _ error) {
|
||||
|
||||
zones, err := p.getPrivateZones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -879,7 +864,7 @@ func (p *AlibabaCloudProvider) createPrivateZoneRecord(zones map[string]*alibaba
|
||||
rr, domain := p.splitDNSName(endpoint)
|
||||
zone := zones[domain]
|
||||
if zone == nil {
|
||||
err := fmt.Errorf("Failed to find private zone '%s'", domain)
|
||||
err := fmt.Errorf("failed to find private zone '%s'", domain)
|
||||
log.Errorf("Failed to create %s record named '%s' to '%s' for Alibaba Cloud Private Zone: %v", endpoint.RecordType, endpoint.DNSName, target, err)
|
||||
return err
|
||||
}
|
||||
@ -925,7 +910,6 @@ func (p *AlibabaCloudProvider) createPrivateZoneRecords(zones map[string]*alibab
|
||||
}
|
||||
|
||||
func (p *AlibabaCloudProvider) deletePrivateZoneRecord(recordID int) error {
|
||||
|
||||
if p.dryRun {
|
||||
log.Infof("Dry run: Delete record id '%d' in Alibaba Cloud Private Zone", recordID)
|
||||
}
|
||||
@ -949,7 +933,7 @@ func (p *AlibabaCloudProvider) deletePrivateZoneRecords(zones map[string]*alibab
|
||||
|
||||
zone := zones[domain]
|
||||
if zone == nil {
|
||||
err := fmt.Errorf("Failed to find private zone '%s'", domain)
|
||||
err := fmt.Errorf("failed to find private zone '%s'", domain)
|
||||
log.Errorf("Failed to delete %s record named '%s' for Alibaba Cloud Private Zone: %v", endpoint.RecordType, endpoint.DNSName, err)
|
||||
continue
|
||||
}
|
||||
@ -1033,12 +1017,11 @@ func (p *AlibabaCloudProvider) equalsPrivateZone(record pvtz.Record, endpoint *e
|
||||
}
|
||||
|
||||
func (p *AlibabaCloudProvider) updatePrivateZoneRecords(zones map[string]*alibabaPrivateZone, endpoints []*endpoint.Endpoint) error {
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
rr, domain := p.splitDNSName(endpoint)
|
||||
zone := zones[domain]
|
||||
if zone == nil {
|
||||
err := fmt.Errorf("Failed to find private zone '%s'", domain)
|
||||
err := fmt.Errorf("failed to find private zone '%s'", domain)
|
||||
log.Errorf("Failed to update %s record named '%s' for Alibaba Cloud Private Zone: %v", endpoint.RecordType, endpoint.DNSName, err)
|
||||
continue
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package alibabacloud
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -34,6 +34,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -51,8 +52,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// see: https://docs.aws.amazon.com/general/latest/gr/rande.html#elb_region
|
||||
// and: https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/using-govcloud-endpoints.html
|
||||
// see: https://docs.aws.amazon.com/general/latest/gr/elb.html
|
||||
canonicalHostedZones = map[string]string{
|
||||
// Application Load Balancers and Classic Load Balancers
|
||||
"us-east-2.elb.amazonaws.com": "Z3AADJGX6KTTL2",
|
||||
@ -73,9 +73,10 @@ var (
|
||||
"eu-west-3.elb.amazonaws.com": "Z3Q77PNBQS71R4",
|
||||
"eu-north-1.elb.amazonaws.com": "Z23TAZ6LKFMNIO",
|
||||
"sa-east-1.elb.amazonaws.com": "Z2P70J7HTTTPLU",
|
||||
"cn-north-1.elb.amazonaws.com.cn": "Z3BX2TMKNYI13Y",
|
||||
"cn-northwest-1.elb.amazonaws.com.cn": "Z3BX2TMKNYI13Y",
|
||||
"us-gov-west-1.amazonaws.com": "Z1K6XKP9SAGWDV",
|
||||
"cn-north-1.elb.amazonaws.com.cn": "Z1GDH35T77C1KE",
|
||||
"cn-northwest-1.elb.amazonaws.com.cn": "ZM7IZAIOVVDZF",
|
||||
"us-gov-west-1.elb.amazonaws.com": "Z33AYJ8TM3BH4J",
|
||||
"us-gov-east-1.elb.amazonaws.com": "Z166TLBEWOO7G0",
|
||||
"me-south-1.elb.amazonaws.com": "ZS929ML54UICD",
|
||||
// Network Load Balancers
|
||||
"elb.us-east-2.amazonaws.com": "ZLMOA37VPKANP",
|
||||
@ -97,6 +98,8 @@ var (
|
||||
"elb.sa-east-1.amazonaws.com": "ZTK26PT1VY4CU",
|
||||
"elb.cn-north-1.amazonaws.com.cn": "Z3QFB96KMJ7ED6",
|
||||
"elb.cn-northwest-1.amazonaws.com.cn": "ZQEIKTCZ8352D",
|
||||
"elb.us-gov-west-1.amazonaws.com": "ZMG1MZ2THAWF1",
|
||||
"elb.us-gov-east-1.amazonaws.com": "Z1ZSMQQ6Q24QQ8",
|
||||
"elb.me-south-1.amazonaws.com": "Z3QSRYVP46NYYV",
|
||||
}
|
||||
)
|
||||
@ -113,6 +116,7 @@ type Route53API interface {
|
||||
|
||||
// AWSProvider is an implementation of Provider for AWS Route53.
|
||||
type AWSProvider struct {
|
||||
provider.BaseProvider
|
||||
client Route53API
|
||||
dryRun bool
|
||||
batchChangeSize int
|
||||
@ -121,20 +125,20 @@ type AWSProvider struct {
|
||||
// only consider hosted zones managing domains ending in this suffix
|
||||
domainFilter endpoint.DomainFilter
|
||||
// filter hosted zones by id
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
// filter hosted zones by type (e.g. private or public)
|
||||
zoneTypeFilter ZoneTypeFilter
|
||||
zoneTypeFilter provider.ZoneTypeFilter
|
||||
// filter hosted zones by tags
|
||||
zoneTagFilter ZoneTagFilter
|
||||
zoneTagFilter provider.ZoneTagFilter
|
||||
preferCNAME bool
|
||||
}
|
||||
|
||||
// AWSConfig contains configuration to create a new AWS provider.
|
||||
type AWSConfig struct {
|
||||
DomainFilter endpoint.DomainFilter
|
||||
ZoneIDFilter ZoneIDFilter
|
||||
ZoneTypeFilter ZoneTypeFilter
|
||||
ZoneTagFilter ZoneTagFilter
|
||||
ZoneIDFilter provider.ZoneIDFilter
|
||||
ZoneTypeFilter provider.ZoneTypeFilter
|
||||
ZoneTagFilter provider.ZoneTagFilter
|
||||
BatchChangeSize int
|
||||
BatchChangeInterval time.Duration
|
||||
EvaluateTargetHealth bool
|
||||
@ -266,7 +270,7 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*route53.Hos
|
||||
// TODO(linki, ownership): Remove once ownership system is in place.
|
||||
// See: https://github.com/kubernetes-sigs/external-dns/pull/122/files/74e2c3d3e237411e619aefc5aab694742001cdec#r109863370
|
||||
|
||||
if !supportedRecordType(aws.StringValue(r.Type)) {
|
||||
if !provider.SupportedRecordType(aws.StringValue(r.Type)) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -377,7 +381,7 @@ func (p *AWSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) e
|
||||
return err
|
||||
}
|
||||
|
||||
records, ok := ctx.Value(RecordsContextKey).([]*endpoint.Endpoint)
|
||||
records, ok := ctx.Value(provider.RecordsContextKey).([]*endpoint.Endpoint)
|
||||
if !ok {
|
||||
var err error
|
||||
records, err = p.records(ctx, zones)
|
||||
@ -449,7 +453,7 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes []*route53.Chan
|
||||
}
|
||||
|
||||
if len(failedZones) > 0 {
|
||||
return fmt.Errorf("Failed to submit all changes for the following zones: %v", failedZones)
|
||||
return fmt.Errorf("failed to submit all changes for the following zones: %v", failedZones)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -588,7 +592,8 @@ func (p *AWSProvider) tagsForZone(ctx context.Context, zoneID string) (map[strin
|
||||
|
||||
func batchChangeSet(cs []*route53.Change, batchSize int) [][]*route53.Change {
|
||||
if len(cs) <= batchSize {
|
||||
return [][]*route53.Change{cs}
|
||||
res := sortChangesByActionNameType(cs)
|
||||
return [][]*route53.Change{res}
|
||||
}
|
||||
|
||||
batchChanges := make([][]*route53.Change, 0)
|
||||
@ -635,10 +640,10 @@ func batchChangeSet(cs []*route53.Change, batchSize int) [][]*route53.Change {
|
||||
|
||||
func sortChangesByActionNameType(cs []*route53.Change) []*route53.Change {
|
||||
sort.SliceStable(cs, func(i, j int) bool {
|
||||
if *cs[i].Action < *cs[j].Action {
|
||||
if *cs[i].Action > *cs[j].Action {
|
||||
return true
|
||||
}
|
||||
if *cs[i].Action > *cs[j].Action {
|
||||
if *cs[i].Action < *cs[j].Action {
|
||||
return false
|
||||
}
|
||||
if *cs[i].ResourceRecordSet.Name < *cs[j].ResourceRecordSet.Name {
|
||||
@ -662,7 +667,7 @@ func changesByZone(zones map[string]*route53.HostedZone, changeSet []*route53.Ch
|
||||
}
|
||||
|
||||
for _, c := range changeSet {
|
||||
hostname := ensureTrailingDot(aws.StringValue(c.ResourceRecordSet.Name))
|
||||
hostname := provider.EnsureTrailingDot(aws.StringValue(c.ResourceRecordSet.Name))
|
||||
|
||||
zones := suitableZones(hostname, zones)
|
||||
if len(zones) == 0 {
|
||||
@ -733,7 +738,6 @@ func isAWSAlias(ep *endpoint.Endpoint, addrs []*endpoint.Endpoint) string {
|
||||
if hostedZone := canonicalHostedZone(addr.Targets[0]); hostedZone != "" {
|
||||
return hostedZone
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -35,6 +35,7 @@ import (
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -178,17 +179,17 @@ func (r *Route53APIStub) ChangeResourceRecordSetsWithContext(ctx context.Context
|
||||
}
|
||||
}
|
||||
|
||||
change.ResourceRecordSet.Name = aws.String(wildcardEscape(ensureTrailingDot(aws.StringValue(change.ResourceRecordSet.Name))))
|
||||
change.ResourceRecordSet.Name = aws.String(wildcardEscape(provider.EnsureTrailingDot(aws.StringValue(change.ResourceRecordSet.Name))))
|
||||
|
||||
if change.ResourceRecordSet.AliasTarget != nil {
|
||||
change.ResourceRecordSet.AliasTarget.DNSName = aws.String(wildcardEscape(ensureTrailingDot(aws.StringValue(change.ResourceRecordSet.AliasTarget.DNSName))))
|
||||
change.ResourceRecordSet.AliasTarget.DNSName = aws.String(wildcardEscape(provider.EnsureTrailingDot(aws.StringValue(change.ResourceRecordSet.AliasTarget.DNSName))))
|
||||
}
|
||||
|
||||
setId := ""
|
||||
setID := ""
|
||||
if change.ResourceRecordSet.SetIdentifier != nil {
|
||||
setId = aws.StringValue(change.ResourceRecordSet.SetIdentifier)
|
||||
setID = aws.StringValue(change.ResourceRecordSet.SetIdentifier)
|
||||
}
|
||||
key := aws.StringValue(change.ResourceRecordSet.Name) + "::" + aws.StringValue(change.ResourceRecordSet.Type) + "::" + setId
|
||||
key := aws.StringValue(change.ResourceRecordSet.Name) + "::" + aws.StringValue(change.ResourceRecordSet.Type) + "::" + setID
|
||||
switch aws.StringValue(change.Action) {
|
||||
case route53.ChangeActionCreate:
|
||||
if _, found := recordSets[key]; found {
|
||||
@ -287,17 +288,17 @@ func TestAWSZones(t *testing.T) {
|
||||
|
||||
for _, ti := range []struct {
|
||||
msg string
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneTypeFilter ZoneTypeFilter
|
||||
zoneTagFilter ZoneTagFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
zoneTypeFilter provider.ZoneTypeFilter
|
||||
zoneTagFilter provider.ZoneTagFilter
|
||||
expectedZones map[string]*route53.HostedZone
|
||||
}{
|
||||
{"no filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), NewZoneTagFilter([]string{}), allZones},
|
||||
{"public filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter("public"), NewZoneTagFilter([]string{}), publicZones},
|
||||
{"private filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter("private"), NewZoneTagFilter([]string{}), privateZones},
|
||||
{"unknown filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter("unknown"), NewZoneTagFilter([]string{}), noZones},
|
||||
{"zone id filter", NewZoneIDFilter([]string{"/hostedzone/zone-3.ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), NewZoneTagFilter([]string{}), privateZones},
|
||||
{"tag filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), NewZoneTagFilter([]string{"zone=3"}), privateZones},
|
||||
{"no filter", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), provider.NewZoneTagFilter([]string{}), allZones},
|
||||
{"public filter", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter("public"), provider.NewZoneTagFilter([]string{}), publicZones},
|
||||
{"private filter", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter("private"), provider.NewZoneTagFilter([]string{}), privateZones},
|
||||
{"unknown filter", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter("unknown"), provider.NewZoneTagFilter([]string{}), noZones},
|
||||
{"zone id filter", provider.NewZoneIDFilter([]string{"/hostedzone/zone-3.ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneTypeFilter(""), provider.NewZoneTagFilter([]string{}), privateZones},
|
||||
{"tag filter", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), provider.NewZoneTagFilter([]string{"zone=3"}), privateZones},
|
||||
} {
|
||||
provider, _ := newAWSProviderWithTagFilter(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), ti.zoneIDFilter, ti.zoneTypeFilter, ti.zoneTagFilter, defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
|
||||
@ -309,7 +310,7 @@ func TestAWSZones(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSRecords(t *testing.T) {
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, false, []*endpoint.Endpoint{
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), false, false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("*.wildcard-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
@ -351,7 +352,7 @@ func TestAWSRecords(t *testing.T) {
|
||||
|
||||
func TestAWSCreateRecords(t *testing.T) {
|
||||
customTTL := endpoint.TTL(60)
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
|
||||
records := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
@ -376,7 +377,7 @@ func TestAWSCreateRecords(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSUpdateRecords(t *testing.T) {
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
@ -419,7 +420,7 @@ func TestAWSDeleteRecords(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("delete-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
}
|
||||
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, false, originalEndpoints)
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), false, false, originalEndpoints)
|
||||
|
||||
require.NoError(t, provider.DeleteRecords(context.Background(), originalEndpoints))
|
||||
|
||||
@ -441,12 +442,12 @@ func TestAWSApplyChanges(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
records, err := p.Records(ctx)
|
||||
require.NoError(t, err)
|
||||
return context.WithValue(ctx, RecordsContextKey, records)
|
||||
return context.WithValue(ctx, provider.RecordsContextKey, records)
|
||||
}, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
@ -538,7 +539,7 @@ func TestAWSApplyChangesDryRun(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("delete-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4", "4.3.2.1"),
|
||||
}
|
||||
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, true, originalEndpoints)
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, true, originalEndpoints)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
@ -686,7 +687,7 @@ func TestAWSChangesByZones(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSsubmitChanges(t *testing.T) {
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
const subnets = 16
|
||||
const hosts = defaultBatchChangeSize / subnets
|
||||
|
||||
@ -715,7 +716,7 @@ func TestAWSsubmitChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSsubmitChangesError(t *testing.T) {
|
||||
provider, clientStub := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
provider, clientStub := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
clientStub.MockMethod("ChangeResourceRecordSets", mock.Anything).Return(nil, fmt.Errorf("Mock route53 failure"))
|
||||
|
||||
ctx := context.Background()
|
||||
@ -851,7 +852,7 @@ func validateAWSChangeRecord(t *testing.T, record *route53.Change, expected *rou
|
||||
}
|
||||
|
||||
func TestAWSCreateRecordsWithCNAME(t *testing.T) {
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
|
||||
records := []*endpoint.Endpoint{
|
||||
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Targets: endpoint.Targets{"foo.example.org"}, RecordType: endpoint.RecordTypeCNAME},
|
||||
@ -881,7 +882,7 @@ func TestAWSCreateRecordsWithALIAS(t *testing.T) {
|
||||
"false": false,
|
||||
"": false,
|
||||
} {
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{})
|
||||
|
||||
// Test dualstack and ipv4 load balancer targets
|
||||
records := []*endpoint.Endpoint{
|
||||
@ -1023,8 +1024,8 @@ func TestAWSCanonicalHostedZone(t *testing.T) {
|
||||
{"foo.eu-west-2.elb.amazonaws.com", "ZHURV8PSTC4K8"},
|
||||
{"foo.eu-west-3.elb.amazonaws.com", "Z3Q77PNBQS71R4"},
|
||||
{"foo.sa-east-1.elb.amazonaws.com", "Z2P70J7HTTTPLU"},
|
||||
{"foo.cn-north-1.elb.amazonaws.com.cn", "Z3BX2TMKNYI13Y"},
|
||||
{"foo.cn-northwest-1.elb.amazonaws.com.cn", "Z3BX2TMKNYI13Y"},
|
||||
{"foo.cn-north-1.elb.amazonaws.com.cn", "Z1GDH35T77C1KE"},
|
||||
{"foo.cn-northwest-1.elb.amazonaws.com.cn", "ZM7IZAIOVVDZF"},
|
||||
// Network Load Balancers
|
||||
{"foo.elb.us-east-2.amazonaws.com", "ZLMOA37VPKANP"},
|
||||
{"foo.elb.us-east-1.amazonaws.com", "Z26RNL4JYFTOTI"},
|
||||
@ -1182,11 +1183,11 @@ func escapeAWSRecords(t *testing.T, provider *AWSProvider, zone string) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
func newAWSProvider(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) {
|
||||
return newAWSProviderWithTagFilter(t, domainFilter, zoneIDFilter, zoneTypeFilter, NewZoneTagFilter([]string{}), evaluateTargetHealth, dryRun, records)
|
||||
func newAWSProvider(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) {
|
||||
return newAWSProviderWithTagFilter(t, domainFilter, zoneIDFilter, zoneTypeFilter, provider.NewZoneTagFilter([]string{}), evaluateTargetHealth, dryRun, records)
|
||||
}
|
||||
|
||||
func newAWSProviderWithTagFilter(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, zoneTagFilter ZoneTagFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) {
|
||||
func newAWSProviderWithTagFilter(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, zoneTagFilter provider.ZoneTagFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) {
|
||||
client := NewRoute53APIStub()
|
||||
|
||||
provider := &AWSProvider{
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package awssd
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -37,6 +37,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -73,6 +74,7 @@ type AWSSDClient interface {
|
||||
|
||||
// AWSSDProvider is an implementation of Provider for AWS Cloud Map.
|
||||
type AWSSDProvider struct {
|
||||
provider.BaseProvider
|
||||
client AWSSDClient
|
||||
dryRun bool
|
||||
// only consider namespaces ending in this suffix
|
||||
@ -14,13 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package awssd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -51,7 +52,7 @@ type AWSSDClientStub struct {
|
||||
func (s *AWSSDClientStub) CreateService(input *sd.CreateServiceInput) (*sd.CreateServiceOutput, error) {
|
||||
|
||||
srv := &sd.Service{
|
||||
Id: aws.String(string(rand.Intn(10000))),
|
||||
Id: aws.String(strconv.Itoa(rand.Intn(10000))),
|
||||
DnsConfig: input.DnsConfig,
|
||||
Name: input.Name,
|
||||
Description: input.Description,
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -34,6 +34,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -66,8 +67,9 @@ type RecordSetsClient interface {
|
||||
|
||||
// AzureProvider implements the DNS provider for Microsoft's Azure cloud platform.
|
||||
type AzureProvider struct {
|
||||
provider.BaseProvider
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
dryRun bool
|
||||
resourceGroup string
|
||||
userAssignedIdentityClientID string
|
||||
@ -78,7 +80,7 @@ type AzureProvider struct {
|
||||
// NewAzureProvider creates a new Azure provider.
|
||||
//
|
||||
// Returns the provider or an error if a provider could not be created.
|
||||
func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, resourceGroup string, userAssignedIdentityClientID string, dryRun bool) (*AzureProvider, error) {
|
||||
func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, resourceGroup string, userAssignedIdentityClientID string, dryRun bool) (*AzureProvider, error) {
|
||||
contents, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||
@ -199,7 +201,7 @@ func (p *AzureProvider) Records(ctx context.Context) (endpoints []*endpoint.Endp
|
||||
return true
|
||||
}
|
||||
recordType := strings.TrimPrefix(*recordSet.Type, "Microsoft.Network/dnszones/")
|
||||
if !supportedRecordType(recordType) {
|
||||
if !provider.SupportedRecordType(recordType) {
|
||||
return true
|
||||
}
|
||||
name := formatAzureDNSName(*recordSet.Name, *zone.Name)
|
||||
@ -300,7 +302,7 @@ func (p *AzureProvider) mapChanges(zones []dns.Zone, changes *plan.Changes) (azu
|
||||
ignored := map[string]bool{}
|
||||
deleted := azureChangeMap{}
|
||||
updated := azureChangeMap{}
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, z := range zones {
|
||||
if z.Name != nil {
|
||||
zoneNameIDMapper.Add(*z.Name, *z.Name)
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -29,6 +29,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// PrivateZonesClient is an interface of privatedns.PrivateZoneClient that can be stubbed for testing.
|
||||
@ -45,8 +46,9 @@ type PrivateRecordSetsClient interface {
|
||||
|
||||
// AzurePrivateDNSProvider implements the DNS provider for Microsoft's Azure Private DNS service
|
||||
type AzurePrivateDNSProvider struct {
|
||||
provider.BaseProvider
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
dryRun bool
|
||||
subscriptionID string
|
||||
resourceGroup string
|
||||
@ -57,7 +59,7 @@ type AzurePrivateDNSProvider struct {
|
||||
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
|
||||
//
|
||||
// Returns the provider or an error if a provider could not be created.
|
||||
func NewAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, resourceGroup string, subscriptionID string, dryRun bool) (*AzurePrivateDNSProvider, error) {
|
||||
func NewAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, resourceGroup string, subscriptionID string, dryRun bool) (*AzurePrivateDNSProvider, error) {
|
||||
authorizer, err := auth.NewAuthorizerFromEnvironment()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -208,7 +210,7 @@ func (p *AzurePrivateDNSProvider) mapChanges(zones []privatedns.PrivateZone, cha
|
||||
ignored := map[string]bool{}
|
||||
deleted := azurePrivateDNSChangeMap{}
|
||||
updated := azurePrivateDNSChangeMap{}
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, z := range zones {
|
||||
if z.Name != nil {
|
||||
zoneNameIDMapper.Add(*z.Name, *z.Name)
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -27,6 +27,11 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
recordTTL = 300
|
||||
)
|
||||
|
||||
// mockPrivateZonesClient implements the methods of the Azure Private DNS Zones Client which are used in the Azure Private DNS Provider
|
||||
@ -203,7 +208,7 @@ func (client *mockPrivateRecordSetsClient) CreateOrUpdate(ctx context.Context, r
|
||||
}
|
||||
|
||||
// newMockedAzurePrivateDNSProvider creates an AzureProvider comprising the mocked clients for zones and recordsets
|
||||
func newMockedAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, resourceGroup string, zones *[]privatedns.PrivateZone, recordSets *[]privatedns.RecordSet) (*AzurePrivateDNSProvider, error) {
|
||||
func newMockedAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, zones *[]privatedns.PrivateZone, recordSets *[]privatedns.RecordSet) (*AzurePrivateDNSProvider, error) {
|
||||
// init zone-related parts of the mock-client
|
||||
pageIterator := mockPrivateZoneListResultPageIterator{
|
||||
results: []privatedns.PrivateZoneListResult{
|
||||
@ -236,7 +241,7 @@ func newMockedAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneID
|
||||
return newAzurePrivateDNSProvider(domainFilter, zoneIDFilter, dryRun, resourceGroup, &zonesClient, &recordSetsClient), nil
|
||||
}
|
||||
|
||||
func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, resourceGroup string, privateZonesClient PrivateZonesClient, privateRecordsClient PrivateRecordSetsClient) *AzurePrivateDNSProvider {
|
||||
func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, privateZonesClient PrivateZonesClient, privateRecordsClient PrivateRecordSetsClient) *AzurePrivateDNSProvider {
|
||||
return &AzurePrivateDNSProvider{
|
||||
domainFilter: domainFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
@ -248,7 +253,7 @@ func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter
|
||||
}
|
||||
|
||||
func TestAzurePrivateDNSRecord(t *testing.T) {
|
||||
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, "k8s",
|
||||
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
|
||||
&[]privatedns.PrivateZone{
|
||||
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
|
||||
},
|
||||
@ -284,7 +289,7 @@ func TestAzurePrivateDNSRecord(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAzurePrivateDNSMultiRecord(t *testing.T) {
|
||||
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, "k8s",
|
||||
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
|
||||
&[]privatedns.PrivateZone{
|
||||
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
|
||||
},
|
||||
@ -383,7 +388,7 @@ func testAzurePrivateDNSApplyChangesInternal(t *testing.T, dryRun bool, client P
|
||||
|
||||
provider := newAzurePrivateDNSProvider(
|
||||
endpoint.NewDomainFilter([]string{""}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
provider.NewZoneIDFilter([]string{""}),
|
||||
dryRun,
|
||||
"group",
|
||||
&zonesClient,
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -30,6 +30,7 @@ import (
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// mockZonesClient implements the methods of the Azure DNS Zones Client which are used in the Azure Provider
|
||||
@ -206,7 +207,7 @@ func (client *mockRecordSetsClient) CreateOrUpdate(ctx context.Context, resource
|
||||
}
|
||||
|
||||
// newMockedAzureProvider creates an AzureProvider comprising the mocked clients for zones and recordsets
|
||||
func newMockedAzureProvider(domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, resourceGroup string, userAssignedIdentityClientID string, zones *[]dns.Zone, recordSets *[]dns.RecordSet) (*AzureProvider, error) {
|
||||
func newMockedAzureProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, userAssignedIdentityClientID string, zones *[]dns.Zone, recordSets *[]dns.RecordSet) (*AzureProvider, error) {
|
||||
// init zone-related parts of the mock-client
|
||||
pageIterator := mockZoneListResultPageIterator{
|
||||
results: []dns.ZoneListResult{
|
||||
@ -239,7 +240,7 @@ func newMockedAzureProvider(domainFilter endpoint.DomainFilter, zoneIDFilter Zon
|
||||
return newAzureProvider(domainFilter, zoneIDFilter, dryRun, resourceGroup, userAssignedIdentityClientID, &zonesClient, &recordSetsClient), nil
|
||||
}
|
||||
|
||||
func newAzureProvider(domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, resourceGroup string, userAssignedIdentityClientID string, zonesClient ZonesClient, recordsClient RecordSetsClient) *AzureProvider {
|
||||
func newAzureProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, userAssignedIdentityClientID string, zonesClient ZonesClient, recordsClient RecordSetsClient) *AzureProvider {
|
||||
return &AzureProvider{
|
||||
domainFilter: domainFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
@ -256,7 +257,7 @@ func validateAzureEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expect
|
||||
}
|
||||
|
||||
func TestAzureRecord(t *testing.T) {
|
||||
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, "k8s", "",
|
||||
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "",
|
||||
&[]dns.Zone{
|
||||
createMockZone("example.com", "/dnszones/example.com"),
|
||||
},
|
||||
@ -293,7 +294,7 @@ func TestAzureRecord(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAzureMultiRecord(t *testing.T) {
|
||||
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, "k8s", "",
|
||||
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "",
|
||||
&[]dns.Zone{
|
||||
createMockZone("example.com", "/dnszones/example.com"),
|
||||
},
|
||||
@ -393,7 +394,7 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordSetsC
|
||||
|
||||
provider := newAzureProvider(
|
||||
endpoint.NewDomainFilter([]string{""}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
provider.NewZoneIDFilter([]string{""}),
|
||||
dryRun,
|
||||
"group",
|
||||
"",
|
||||
2
provider/cloudflare/OWNERS
Normal file
2
provider/cloudflare/OWNERS
Normal file
@ -0,0 +1,2 @@
|
||||
approvers:
|
||||
- sheerun
|
||||
@ -14,13 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -29,6 +28,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
"sigs.k8s.io/external-dns/source"
|
||||
)
|
||||
|
||||
@ -58,6 +58,7 @@ type cloudFlareDNS interface {
|
||||
ZoneIDByName(zoneName string) (string, error)
|
||||
ListZones(zoneID ...string) ([]cloudflare.Zone, error)
|
||||
ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error)
|
||||
ZoneDetails(zoneID string) (cloudflare.Zone, error)
|
||||
DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error)
|
||||
CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error)
|
||||
DeleteDNSRecord(zoneID, recordID string) error
|
||||
@ -98,12 +99,17 @@ func (z zoneService) ListZonesContext(ctx context.Context, opts ...cloudflare.Re
|
||||
return z.service.ListZonesContext(ctx, opts...)
|
||||
}
|
||||
|
||||
func (z zoneService) ZoneDetails(zoneID string) (cloudflare.Zone, error) {
|
||||
return z.service.ZoneDetails(zoneID)
|
||||
}
|
||||
|
||||
// CloudFlareProvider is an implementation of Provider for CloudFlare DNS.
|
||||
type CloudFlareProvider struct {
|
||||
provider.BaseProvider
|
||||
Client cloudFlareDNS
|
||||
// only consider hosted zones managing domains ending in this suffix
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
proxiedByDefault bool
|
||||
DryRun bool
|
||||
PaginationOptions cloudflare.PaginationOptions
|
||||
@ -112,11 +118,11 @@ type CloudFlareProvider struct {
|
||||
// cloudFlareChange differentiates between ChangActions
|
||||
type cloudFlareChange struct {
|
||||
Action string
|
||||
ResourceRecordSet []cloudflare.DNSRecord
|
||||
ResourceRecord cloudflare.DNSRecord
|
||||
}
|
||||
|
||||
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
|
||||
func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, zonesPerPage int, proxiedByDefault bool, dryRun bool) (*CloudFlareProvider, error) {
|
||||
func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zonesPerPage int, proxiedByDefault bool, dryRun bool) (*CloudFlareProvider, error) {
|
||||
// initialize via chosen auth method and returns new API object
|
||||
var (
|
||||
config *cloudflare.API
|
||||
@ -150,6 +156,27 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
|
||||
result := []cloudflare.Zone{}
|
||||
p.PaginationOptions.Page = 1
|
||||
|
||||
// if there is a zoneIDfilter configured
|
||||
// && if the filter isnt just a blank string (used in tests)
|
||||
if len(p.zoneIDFilter.ZoneIDs) > 0 && p.zoneIDFilter.ZoneIDs[0] != "" {
|
||||
log.Debugln("zoneIDFilter configured. only looking up zone IDs defined")
|
||||
for _, zoneID := range p.zoneIDFilter.ZoneIDs {
|
||||
log.Debugf("looking up zone %s", zoneID)
|
||||
detailResponse, err := p.Client.ZoneDetails(zoneID)
|
||||
if err != nil {
|
||||
log.Errorf("zone %s lookup failed, %v", zoneID, err)
|
||||
continue
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"zoneName": detailResponse.Name,
|
||||
"zoneID": detailResponse.ID,
|
||||
}).Debugln("adding zone for consideration")
|
||||
result = append(result, detailResponse)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
log.Debugln("no zoneIDFilter configured, looking at all zones")
|
||||
for {
|
||||
zonesResponse, err := p.Client.ListZonesContext(ctx, cloudflare.WithPagination(p.PaginationOptions))
|
||||
if err != nil {
|
||||
@ -158,10 +185,7 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
|
||||
|
||||
for _, zone := range zonesResponse.Result {
|
||||
if !p.domainFilter.Match(zone.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !p.zoneIDFilter.Match(zone.ID) {
|
||||
log.Debugf("zone %s not in domain filter", zone.Name)
|
||||
continue
|
||||
}
|
||||
result = append(result, zone)
|
||||
@ -199,15 +223,47 @@ func (p *CloudFlareProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *CloudFlareProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
proxiedByDefault := p.proxiedByDefault
|
||||
cloudflareChanges := []*cloudFlareChange{}
|
||||
|
||||
combinedChanges := make([]*cloudFlareChange, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
||||
for _, endpoint := range changes.Create {
|
||||
for _, target := range endpoint.Targets {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareCreate, endpoint, target))
|
||||
}
|
||||
}
|
||||
|
||||
combinedChanges = append(combinedChanges, newCloudFlareChanges(cloudFlareCreate, changes.Create, proxiedByDefault)...)
|
||||
combinedChanges = append(combinedChanges, newCloudFlareChanges(cloudFlareUpdate, changes.UpdateNew, proxiedByDefault)...)
|
||||
combinedChanges = append(combinedChanges, newCloudFlareChanges(cloudFlareDelete, changes.Delete, proxiedByDefault)...)
|
||||
for i, desired := range changes.UpdateNew {
|
||||
current := changes.UpdateOld[i]
|
||||
|
||||
return p.submitChanges(ctx, combinedChanges)
|
||||
add, remove, leave := provider.Difference(current.Targets, desired.Targets)
|
||||
|
||||
for _, a := range add {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareCreate, desired, a))
|
||||
}
|
||||
|
||||
for _, a := range leave {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareUpdate, desired, a))
|
||||
}
|
||||
|
||||
for _, a := range remove {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareDelete, current, a))
|
||||
}
|
||||
}
|
||||
|
||||
for _, endpoint := range changes.Delete {
|
||||
for _, target := range endpoint.Targets {
|
||||
cloudflareChanges = append(cloudflareChanges, p.newCloudFlareChange(cloudFlareDelete, endpoint, target))
|
||||
}
|
||||
}
|
||||
|
||||
return p.submitChanges(ctx, cloudflareChanges)
|
||||
}
|
||||
|
||||
func (p *CloudFlareProvider) PropertyValuesEqual(name string, previous string, current string) bool {
|
||||
if name == source.CloudflareProxiedKey {
|
||||
return plan.CompareBoolean(p.proxiedByDefault, name, previous, current)
|
||||
}
|
||||
|
||||
return p.BaseProvider.PropertyValuesEqual(name, previous, current)
|
||||
}
|
||||
|
||||
// submitChanges takes a zone and a collection of Changes and sends them as a single transaction.
|
||||
@ -231,10 +287,9 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
|
||||
}
|
||||
for _, change := range changes {
|
||||
logFields := log.Fields{
|
||||
"record": change.ResourceRecordSet[0].Name,
|
||||
"type": change.ResourceRecordSet[0].Type,
|
||||
"ttl": change.ResourceRecordSet[0].TTL,
|
||||
"targets": len(change.ResourceRecordSet),
|
||||
"record": change.ResourceRecord.Name,
|
||||
"type": change.ResourceRecord.Type,
|
||||
"ttl": change.ResourceRecord.TTL,
|
||||
"action": change.Action,
|
||||
"zone": zoneID,
|
||||
}
|
||||
@ -245,35 +300,41 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
|
||||
continue
|
||||
}
|
||||
|
||||
recordIDs := p.getRecordIDs(records, change.ResourceRecordSet[0])
|
||||
|
||||
// to simplify bookkeeping for multiple records, an update is executed as delete+create
|
||||
if change.Action == cloudFlareDelete || change.Action == cloudFlareUpdate {
|
||||
for _, recordID := range recordIDs {
|
||||
if change.Action == cloudFlareUpdate {
|
||||
recordID := p.getRecordID(records, change.ResourceRecord)
|
||||
if recordID == "" {
|
||||
log.WithFields(logFields).Errorf("failed to find previous record: %v", change.ResourceRecord)
|
||||
continue
|
||||
}
|
||||
err := p.Client.UpdateDNSRecord(zoneID, recordID, change.ResourceRecord)
|
||||
if err != nil {
|
||||
log.WithFields(logFields).Errorf("failed to delete record: %v", err)
|
||||
}
|
||||
} else if change.Action == cloudFlareDelete {
|
||||
recordID := p.getRecordID(records, change.ResourceRecord)
|
||||
if recordID == "" {
|
||||
log.WithFields(logFields).Errorf("failed to find previous record: %v", change.ResourceRecord)
|
||||
continue
|
||||
}
|
||||
err := p.Client.DeleteDNSRecord(zoneID, recordID)
|
||||
if err != nil {
|
||||
log.WithFields(logFields).Errorf("failed to delete record: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if change.Action == cloudFlareCreate || change.Action == cloudFlareUpdate {
|
||||
for _, record := range change.ResourceRecordSet {
|
||||
_, err := p.Client.CreateDNSRecord(zoneID, record)
|
||||
} else if change.Action == cloudFlareCreate {
|
||||
_, err := p.Client.CreateDNSRecord(zoneID, change.ResourceRecord)
|
||||
if err != nil {
|
||||
log.WithFields(logFields).Errorf("failed to create record: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// changesByZone separates a multi-zone change into a single change per zone.
|
||||
func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []*cloudFlareChange) map[string][]*cloudFlareChange {
|
||||
changes := make(map[string][]*cloudFlareChange)
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
|
||||
for _, z := range zones {
|
||||
zoneNameIDMapper.Add(z.ID, z.Name)
|
||||
@ -281,9 +342,9 @@ func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []
|
||||
}
|
||||
|
||||
for _, c := range changeSet {
|
||||
zoneID, _ := zoneNameIDMapper.FindZone(c.ResourceRecordSet[0].Name)
|
||||
zoneID, _ := zoneNameIDMapper.FindZone(c.ResourceRecord.Name)
|
||||
if zoneID == "" {
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", c.ResourceRecordSet[0].Name)
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", c.ResourceRecord.Name)
|
||||
continue
|
||||
}
|
||||
changes[zoneID] = append(changes[zoneID], c)
|
||||
@ -292,51 +353,36 @@ func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []
|
||||
return changes
|
||||
}
|
||||
|
||||
func (p *CloudFlareProvider) getRecordIDs(records []cloudflare.DNSRecord, record cloudflare.DNSRecord) []string {
|
||||
recordIDs := make([]string, 0)
|
||||
func (p *CloudFlareProvider) getRecordID(records []cloudflare.DNSRecord, record cloudflare.DNSRecord) string {
|
||||
for _, zoneRecord := range records {
|
||||
if zoneRecord.Name == record.Name && zoneRecord.Type == record.Type {
|
||||
recordIDs = append(recordIDs, zoneRecord.ID)
|
||||
if zoneRecord.Name == record.Name && zoneRecord.Type == record.Type && zoneRecord.Content == record.Content {
|
||||
return zoneRecord.ID
|
||||
}
|
||||
}
|
||||
sort.Strings(recordIDs)
|
||||
return recordIDs
|
||||
return ""
|
||||
}
|
||||
|
||||
// newCloudFlareChanges returns a collection of Changes based on the given records and action.
|
||||
func newCloudFlareChanges(action string, endpoints []*endpoint.Endpoint, proxiedByDefault bool) []*cloudFlareChange {
|
||||
changes := make([]*cloudFlareChange, 0, len(endpoints))
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
changes = append(changes, newCloudFlareChange(action, endpoint, proxiedByDefault))
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
func newCloudFlareChange(action string, endpoint *endpoint.Endpoint, proxiedByDefault bool) *cloudFlareChange {
|
||||
func (p *CloudFlareProvider) newCloudFlareChange(action string, endpoint *endpoint.Endpoint, target string) *cloudFlareChange {
|
||||
ttl := defaultCloudFlareRecordTTL
|
||||
proxied := shouldBeProxied(endpoint, proxiedByDefault)
|
||||
proxied := shouldBeProxied(endpoint, p.proxiedByDefault)
|
||||
|
||||
if endpoint.RecordTTL.IsConfigured() {
|
||||
ttl = int(endpoint.RecordTTL)
|
||||
}
|
||||
|
||||
resourceRecordSet := make([]cloudflare.DNSRecord, len(endpoint.Targets))
|
||||
|
||||
for i := range endpoint.Targets {
|
||||
resourceRecordSet[i] = cloudflare.DNSRecord{
|
||||
Name: endpoint.DNSName,
|
||||
TTL: ttl,
|
||||
Proxied: proxied,
|
||||
Type: endpoint.RecordType,
|
||||
Content: endpoint.Targets[i],
|
||||
}
|
||||
if len(endpoint.Targets) > 1 {
|
||||
log.Errorf("Updates should have just one target")
|
||||
}
|
||||
|
||||
return &cloudFlareChange{
|
||||
Action: action,
|
||||
ResourceRecordSet: resourceRecordSet,
|
||||
ResourceRecord: cloudflare.DNSRecord{
|
||||
Name: endpoint.DNSName,
|
||||
TTL: ttl,
|
||||
Proxied: proxied,
|
||||
Type: endpoint.RecordType,
|
||||
Content: target,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,7 +414,7 @@ func groupByNameAndType(records []cloudflare.DNSRecord) []*endpoint.Endpoint {
|
||||
groups := map[string][]cloudflare.DNSRecord{}
|
||||
|
||||
for _, r := range records {
|
||||
if !supportedRecordType(r.Type) {
|
||||
if !provider.SupportedRecordType(r.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
1135
provider/cloudflare/cloudflare_test.go
Normal file
1135
provider/cloudflare/cloudflare_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,607 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
cloudflare "github.com/cloudflare/cloudflare-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
)
|
||||
|
||||
type mockCloudFlareClient struct{}
|
||||
|
||||
func (m *mockCloudFlareClient) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
|
||||
if zoneID == "1234567890" {
|
||||
return []cloudflare.DNSRecord{
|
||||
{ID: "1234567890", Name: "foobar.ext-dns-test.zalando.to.", Type: endpoint.RecordTypeA, TTL: 120},
|
||||
{ID: "1231231233", Name: "foo.bar.com", TTL: 1}},
|
||||
nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) DeleteDNSRecord(zoneID, recordID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) UserDetails() (cloudflare.User, error) {
|
||||
return cloudflare.User{ID: "xxxxxxxxxxxxxxxxxxx"}, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) ZoneIDByName(zoneName string) (string, error) {
|
||||
return "1234567890", nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
|
||||
return []cloudflare.Zone{{ID: "1234567890", Name: "ext-dns-test.zalando.to."}, {ID: "1234567891", Name: "foo.com."}}, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareClient) ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) {
|
||||
return cloudflare.ZonesResponse{
|
||||
Result: []cloudflare.Zone{
|
||||
{ID: "1234567890", Name: "ext-dns-test.zalando.to."},
|
||||
{ID: "1234567891", Name: "foo.com."}},
|
||||
ResultInfo: cloudflare.ResultInfo{
|
||||
Page: 1,
|
||||
TotalPages: 1,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type mockCloudFlareDNSRecordsFail struct{}
|
||||
|
||||
func (m *mockCloudFlareDNSRecordsFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareDNSRecordsFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
|
||||
return []cloudflare.DNSRecord{}, fmt.Errorf("can not get records from zone")
|
||||
}
|
||||
func (m *mockCloudFlareDNSRecordsFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareDNSRecordsFail) DeleteDNSRecord(zoneID, recordID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareDNSRecordsFail) UserDetails() (cloudflare.User, error) {
|
||||
return cloudflare.User{ID: "xxxxxxxxxxxxxxxxxxx"}, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareDNSRecordsFail) ZoneIDByName(zoneName string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareDNSRecordsFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
|
||||
return []cloudflare.Zone{{Name: "ext-dns-test.zalando.to."}}, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareDNSRecordsFail) ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) {
|
||||
return cloudflare.ZonesResponse{
|
||||
Result: []cloudflare.Zone{
|
||||
{ID: "1234567890", Name: "ext-dns-test.zalando.to."},
|
||||
{ID: "1234567891", Name: "foo.com."}},
|
||||
ResultInfo: cloudflare.ResultInfo{
|
||||
TotalPages: 1,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type mockCloudFlareListZonesFail struct{}
|
||||
|
||||
func (m *mockCloudFlareListZonesFail) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareListZonesFail) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
|
||||
return []cloudflare.DNSRecord{}, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareListZonesFail) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareListZonesFail) DeleteDNSRecord(zoneID, recordID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareListZonesFail) UserDetails() (cloudflare.User, error) {
|
||||
return cloudflare.User{}, nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareListZonesFail) ZoneIDByName(zoneName string) (string, error) {
|
||||
return "1234567890", nil
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareListZonesFail) ListZones(zoneID ...string) ([]cloudflare.Zone, error) {
|
||||
return []cloudflare.Zone{{}}, fmt.Errorf("no zones available")
|
||||
}
|
||||
|
||||
func (m *mockCloudFlareListZonesFail) ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) {
|
||||
return cloudflare.ZonesResponse{}, fmt.Errorf("no zones available")
|
||||
}
|
||||
|
||||
func TestNewCloudFlareChanges(t *testing.T) {
|
||||
expect := []struct {
|
||||
Name string
|
||||
TTL int
|
||||
}{
|
||||
{
|
||||
"CustomRecordTTL",
|
||||
120,
|
||||
},
|
||||
{
|
||||
"DefaultRecordTTL",
|
||||
1,
|
||||
},
|
||||
}
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{DNSName: "new", Targets: endpoint.Targets{"target"}, RecordTTL: 120},
|
||||
{DNSName: "new2", Targets: endpoint.Targets{"target2"}},
|
||||
}
|
||||
changes := newCloudFlareChanges(cloudFlareCreate, endpoints, true)
|
||||
for i, change := range changes {
|
||||
assert.Equal(
|
||||
t,
|
||||
change.ResourceRecordSet[0].TTL,
|
||||
expect[i].TTL,
|
||||
expect[i].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCloudFlareChangeNoProxied(t *testing.T) {
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Targets: endpoint.Targets{"target"}}, false)
|
||||
assert.False(t, change.ResourceRecordSet[0].Proxied)
|
||||
}
|
||||
|
||||
func TestNewCloudFlareProxiedAnnotationTrue(t *testing.T) {
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Targets: endpoint.Targets{"target"}, ProviderSpecific: endpoint.ProviderSpecific{
|
||||
endpoint.ProviderSpecificProperty{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "true",
|
||||
},
|
||||
}}, false)
|
||||
assert.True(t, change.ResourceRecordSet[0].Proxied)
|
||||
}
|
||||
|
||||
func TestNewCloudFlareProxiedAnnotationFalse(t *testing.T) {
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Targets: endpoint.Targets{"target"}, ProviderSpecific: endpoint.ProviderSpecific{
|
||||
endpoint.ProviderSpecificProperty{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "false",
|
||||
},
|
||||
}}, true)
|
||||
assert.False(t, change.ResourceRecordSet[0].Proxied)
|
||||
}
|
||||
|
||||
func TestNewCloudFlareProxiedAnnotationIllegalValue(t *testing.T) {
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Targets: endpoint.Targets{"target"}, ProviderSpecific: endpoint.ProviderSpecific{
|
||||
endpoint.ProviderSpecificProperty{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "asdaslkjndaslkdjals",
|
||||
},
|
||||
}}, false)
|
||||
assert.False(t, change.ResourceRecordSet[0].Proxied)
|
||||
}
|
||||
|
||||
func TestNewCloudFlareChangeProxiable(t *testing.T) {
|
||||
var cloudFlareTypes = []struct {
|
||||
recordType string
|
||||
proxiable bool
|
||||
}{
|
||||
{"A", true},
|
||||
{"CNAME", true},
|
||||
{"LOC", false},
|
||||
{"MX", false},
|
||||
{"NS", false},
|
||||
{"SPF", false},
|
||||
{"TXT", false},
|
||||
{"SRV", false},
|
||||
}
|
||||
|
||||
for _, cloudFlareType := range cloudFlareTypes {
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: cloudFlareType.recordType, Targets: endpoint.Targets{"target"}}, true)
|
||||
|
||||
if cloudFlareType.proxiable {
|
||||
assert.True(t, change.ResourceRecordSet[0].Proxied)
|
||||
} else {
|
||||
assert.False(t, change.ResourceRecordSet[0].Proxied)
|
||||
}
|
||||
}
|
||||
|
||||
change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "*.foo", RecordType: "A", Targets: endpoint.Targets{"target"}}, true)
|
||||
assert.False(t, change.ResourceRecordSet[0].Proxied)
|
||||
}
|
||||
|
||||
func TestCloudFlareZones(t *testing.T) {
|
||||
provider := &CloudFlareProvider{
|
||||
Client: &mockCloudFlareClient{},
|
||||
domainFilter: endpoint.NewDomainFilter([]string{"zalando.to."}),
|
||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
||||
}
|
||||
|
||||
zones, err := provider.Zones(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validateCloudFlareZones(t, zones, []cloudflare.Zone{
|
||||
{Name: "ext-dns-test.zalando.to."},
|
||||
})
|
||||
}
|
||||
|
||||
func TestRecords(t *testing.T) {
|
||||
provider := &CloudFlareProvider{
|
||||
Client: &mockCloudFlareClient{},
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
records, err := provider.Records(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(records))
|
||||
provider.Client = &mockCloudFlareDNSRecordsFail{}
|
||||
_, err = provider.Records(ctx)
|
||||
if err == nil {
|
||||
t.Errorf("expected to fail")
|
||||
}
|
||||
provider.Client = &mockCloudFlareListZonesFail{}
|
||||
_, err = provider.Records(ctx)
|
||||
if err == nil {
|
||||
t.Errorf("expected to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCloudFlareProvider(t *testing.T) {
|
||||
_ = os.Setenv("CF_API_TOKEN", "abc123def")
|
||||
_, err := NewCloudFlareProvider(
|
||||
endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
25,
|
||||
false,
|
||||
true)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
_ = os.Unsetenv("CF_API_TOKEN")
|
||||
_ = os.Setenv("CF_API_KEY", "xxxxxxxxxxxxxxxxx")
|
||||
_ = os.Setenv("CF_API_EMAIL", "test@test.com")
|
||||
_, err = NewCloudFlareProvider(
|
||||
endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
1,
|
||||
false,
|
||||
true)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
_ = os.Unsetenv("CF_API_KEY")
|
||||
_ = os.Unsetenv("CF_API_EMAIL")
|
||||
_, err = NewCloudFlareProvider(
|
||||
endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
50,
|
||||
false,
|
||||
true)
|
||||
if err == nil {
|
||||
t.Errorf("expected to fail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyChanges(t *testing.T) {
|
||||
changes := &plan.Changes{}
|
||||
provider := &CloudFlareProvider{
|
||||
Client: &mockCloudFlareClient{},
|
||||
}
|
||||
changes.Create = []*endpoint.Endpoint{{DNSName: "new.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target"}}, {DNSName: "new.ext-dns-test.unrelated.to.", Targets: endpoint.Targets{"target"}}}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target"}}}
|
||||
changes.UpdateOld = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target-old"}}}
|
||||
changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "foobar.ext-dns-test.zalando.to.", Targets: endpoint.Targets{"target-new"}}}
|
||||
err := provider.ApplyChanges(context.Background(), changes)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
|
||||
// empty changes
|
||||
changes.Create = []*endpoint.Endpoint{}
|
||||
changes.Delete = []*endpoint.Endpoint{}
|
||||
changes.UpdateOld = []*endpoint.Endpoint{}
|
||||
changes.UpdateNew = []*endpoint.Endpoint{}
|
||||
|
||||
err = provider.ApplyChanges(context.Background(), changes)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudFlareGetRecordID(t *testing.T) {
|
||||
p := &CloudFlareProvider{}
|
||||
records := []cloudflare.DNSRecord{
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
ID: "1",
|
||||
},
|
||||
{
|
||||
Name: "bar.de",
|
||||
Type: endpoint.RecordTypeA,
|
||||
ID: "2",
|
||||
},
|
||||
}
|
||||
|
||||
assert.Len(t, p.getRecordIDs(records, cloudflare.DNSRecord{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
}), 0)
|
||||
assert.Len(t, p.getRecordIDs(records, cloudflare.DNSRecord{
|
||||
Name: "bar.de",
|
||||
Type: endpoint.RecordTypeA,
|
||||
}), 1)
|
||||
assert.Equal(t, "2", p.getRecordIDs(records, cloudflare.DNSRecord{
|
||||
Name: "bar.de",
|
||||
Type: endpoint.RecordTypeA,
|
||||
})[0])
|
||||
}
|
||||
|
||||
func validateCloudFlareZones(t *testing.T, zones []cloudflare.Zone, expected []cloudflare.Zone) {
|
||||
require.Len(t, zones, len(expected))
|
||||
|
||||
for i, zone := range zones {
|
||||
assert.Equal(t, expected[i].Name, zone.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupByNameAndType(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Records []cloudflare.DNSRecord
|
||||
ExpectedEndpoints []*endpoint.Endpoint
|
||||
}{
|
||||
{
|
||||
Name: "empty",
|
||||
Records: []cloudflare.DNSRecord{},
|
||||
ExpectedEndpoints: []*endpoint.Endpoint{},
|
||||
},
|
||||
{
|
||||
Name: "single record - single target",
|
||||
Records: []cloudflare.DNSRecord{
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.1",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
},
|
||||
ExpectedEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
Targets: endpoint.Targets{"10.10.10.1"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultCloudFlareRecordTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "single record - multiple targets",
|
||||
Records: []cloudflare.DNSRecord{
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.1",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.2",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
},
|
||||
ExpectedEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
Targets: endpoint.Targets{"10.10.10.1", "10.10.10.2"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultCloudFlareRecordTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "multiple record - multiple targets",
|
||||
Records: []cloudflare.DNSRecord{
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.1",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.2",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
{
|
||||
Name: "bar.de",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.1",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
{
|
||||
Name: "bar.de",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.2",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
},
|
||||
ExpectedEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
Targets: endpoint.Targets{"10.10.10.1", "10.10.10.2"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultCloudFlareRecordTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "bar.de",
|
||||
Targets: endpoint.Targets{"10.10.10.1", "10.10.10.2"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultCloudFlareRecordTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "multiple record - mixed single/multiple targets",
|
||||
Records: []cloudflare.DNSRecord{
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.1",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.2",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
{
|
||||
Name: "bar.de",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.1",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
},
|
||||
ExpectedEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
Targets: endpoint.Targets{"10.10.10.1", "10.10.10.2"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultCloudFlareRecordTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "bar.de",
|
||||
Targets: endpoint.Targets{"10.10.10.1"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultCloudFlareRecordTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "unsupported record type",
|
||||
Records: []cloudflare.DNSRecord{
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.1",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Content: "10.10.10.2",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
{
|
||||
Name: "bar.de",
|
||||
Type: "NOT SUPPORTED",
|
||||
Content: "10.10.10.1",
|
||||
TTL: defaultCloudFlareRecordTTL,
|
||||
},
|
||||
},
|
||||
ExpectedEndpoints: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
Targets: endpoint.Targets{"10.10.10.1", "10.10.10.2"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: endpoint.TTL(defaultCloudFlareRecordTTL),
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "false",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
assert.ElementsMatch(t, groupByNameAndType(tc.Records), tc.ExpectedEndpoints)
|
||||
}
|
||||
}
|
||||
2
provider/coredns/OWNERS
Normal file
2
provider/coredns/OWNERS
Normal file
@ -0,0 +1,2 @@
|
||||
approvers:
|
||||
- ytsarev
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package coredns
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -35,6 +35,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -56,6 +57,7 @@ type coreDNSClient interface {
|
||||
}
|
||||
|
||||
type coreDNSProvider struct {
|
||||
provider.BaseProvider
|
||||
dryRun bool
|
||||
coreDNSPrefix string
|
||||
domainFilter endpoint.DomainFilter
|
||||
@ -84,7 +86,7 @@ type Service struct {
|
||||
// answer.
|
||||
Group string `json:"group,omitempty"`
|
||||
|
||||
// Etcd key where we found this service and ignored from json un-/marshalling
|
||||
// Etcd key where we found this service and ignored from json un-/marshaling
|
||||
Key string `json:"-"`
|
||||
}
|
||||
|
||||
@ -244,7 +246,7 @@ func newETCDClient() (coreDNSClient, error) {
|
||||
}
|
||||
|
||||
// NewCoreDNSProvider is a CoreDNS provider constructor
|
||||
func NewCoreDNSProvider(domainFilter endpoint.DomainFilter, prefix string, dryRun bool) (Provider, error) {
|
||||
func NewCoreDNSProvider(domainFilter endpoint.DomainFilter, prefix string, dryRun bool) (provider.Provider, error) {
|
||||
client, err := newETCDClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -395,7 +397,6 @@ func (p coreDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
index := 0
|
||||
for _, ep := range group {
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package coredns
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package designate
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -35,6 +35,7 @@ import (
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/pkg/tlsutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -226,6 +227,7 @@ func (c designateClient) DeleteRecordSet(zoneID, recordSetID string) error {
|
||||
|
||||
// designate provider type
|
||||
type designateProvider struct {
|
||||
provider.BaseProvider
|
||||
client designateClientInterface
|
||||
|
||||
// only consider hosted zones managing domains ending in this suffix
|
||||
@ -234,7 +236,7 @@ type designateProvider struct {
|
||||
}
|
||||
|
||||
// NewDesignateProvider is a factory function for OpenStack designate providers
|
||||
func NewDesignateProvider(domainFilter endpoint.DomainFilter, dryRun bool) (Provider, error) {
|
||||
func NewDesignateProvider(domainFilter endpoint.DomainFilter, dryRun bool) (provider.Provider, error) {
|
||||
client, err := newDesignateClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package designate
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -34,6 +34,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
var lastGeneratedDesignateID int32
|
||||
@ -130,7 +131,7 @@ func (c fakeDesignateClient) DeleteRecordSet(zoneID, recordSetID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c fakeDesignateClient) ToProvider() Provider {
|
||||
func (c fakeDesignateClient) ToProvider() provider.Provider {
|
||||
return &designateProvider{client: c}
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -28,6 +28,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -44,9 +45,12 @@ const (
|
||||
|
||||
// DigitalOceanProvider is an implementation of Provider for Digital Ocean's DNS.
|
||||
type DigitalOceanProvider struct {
|
||||
provider.BaseProvider
|
||||
Client godo.DomainsService
|
||||
// only consider hosted zones managing domains ending in this suffix
|
||||
domainFilter endpoint.DomainFilter
|
||||
// page size when querying paginated APIs
|
||||
apiPageSize int
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
@ -57,10 +61,10 @@ type DigitalOceanChange struct {
|
||||
}
|
||||
|
||||
// NewDigitalOceanProvider initializes a new DigitalOcean DNS based Provider.
|
||||
func NewDigitalOceanProvider(ctx context.Context, domainFilter endpoint.DomainFilter, dryRun bool) (*DigitalOceanProvider, error) {
|
||||
func NewDigitalOceanProvider(ctx context.Context, domainFilter endpoint.DomainFilter, dryRun bool, apiPageSize int) (*DigitalOceanProvider, error) {
|
||||
token, ok := os.LookupEnv("DO_TOKEN")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("No token found")
|
||||
return nil, fmt.Errorf("no token found")
|
||||
}
|
||||
oauthClient := oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{
|
||||
AccessToken: token,
|
||||
@ -70,6 +74,7 @@ func NewDigitalOceanProvider(ctx context.Context, domainFilter endpoint.DomainFi
|
||||
provider := &DigitalOceanProvider{
|
||||
Client: client.Domains,
|
||||
domainFilter: domainFilter,
|
||||
apiPageSize: apiPageSize,
|
||||
DryRun: dryRun,
|
||||
}
|
||||
return provider, nil
|
||||
@ -107,7 +112,7 @@ func (p *DigitalOceanProvider) Records(ctx context.Context) ([]*endpoint.Endpoin
|
||||
}
|
||||
|
||||
for _, r := range records {
|
||||
if supportedRecordType(r.Type) {
|
||||
if provider.SupportedRecordType(r.Type) {
|
||||
name := r.Name + "." + zone.Name
|
||||
|
||||
// root name is identified by @ and should be
|
||||
@ -126,7 +131,7 @@ func (p *DigitalOceanProvider) Records(ctx context.Context) ([]*endpoint.Endpoin
|
||||
|
||||
func (p *DigitalOceanProvider) fetchRecords(ctx context.Context, zoneName string) ([]godo.DomainRecord, error) {
|
||||
allRecords := []godo.DomainRecord{}
|
||||
listOptions := &godo.ListOptions{}
|
||||
listOptions := &godo.ListOptions{PerPage: p.apiPageSize}
|
||||
for {
|
||||
records, resp, err := p.Client.Records(ctx, zoneName, listOptions)
|
||||
if err != nil {
|
||||
@ -151,7 +156,7 @@ func (p *DigitalOceanProvider) fetchRecords(ctx context.Context, zoneName string
|
||||
|
||||
func (p *DigitalOceanProvider) fetchZones(ctx context.Context) ([]godo.Domain, error) {
|
||||
allZones := []godo.Domain{}
|
||||
listOptions := &godo.ListOptions{}
|
||||
listOptions := &godo.ListOptions{PerPage: p.apiPageSize}
|
||||
for {
|
||||
zones, resp, err := p.Client.List(ctx, listOptions)
|
||||
if err != nil {
|
||||
@ -314,7 +319,7 @@ func (p *DigitalOceanProvider) getRecordID(records []godo.DomainRecord, record g
|
||||
// digitalOceanchangesByZone separates a multi-zone change into a single change per zone.
|
||||
func digitalOceanChangesByZone(zones []godo.Domain, changeSet []*DigitalOceanChange) map[string][]*DigitalOceanChange {
|
||||
changes := make(map[string][]*DigitalOceanChange)
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, z := range zones {
|
||||
zoneNameIDMapper.Add(z.Name, z.Name)
|
||||
changes[z.Name] = []*DigitalOceanChange{}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -187,12 +187,12 @@ func TestDigitalOceanApplyChanges(t *testing.T) {
|
||||
|
||||
func TestNewDigitalOceanProvider(t *testing.T) {
|
||||
_ = os.Setenv("DO_TOKEN", "xxxxxxxxxxxxxxxxx")
|
||||
_, err := NewDigitalOceanProvider(context.Background(), endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true)
|
||||
_, err := NewDigitalOceanProvider(context.Background(), endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true, 50)
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
_ = os.Unsetenv("DO_TOKEN")
|
||||
_, err = NewDigitalOceanProvider(context.Background(), endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true)
|
||||
_, err = NewDigitalOceanProvider(context.Background(), endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true, 50)
|
||||
if err == nil {
|
||||
t.Errorf("expected to fail")
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package dnsimple
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -25,70 +25,64 @@ import (
|
||||
|
||||
"github.com/dnsimple/dnsimple-go/dnsimple"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const dnsimpleRecordTTL = 3600 // Default TTL of 1 hour if not set (DNSimple's default)
|
||||
|
||||
type identityService struct {
|
||||
type dnsimpleIdentityService struct {
|
||||
service *dnsimple.IdentityService
|
||||
}
|
||||
|
||||
func (i identityService) Whoami() (*dnsimple.WhoamiResponse, error) {
|
||||
return i.service.Whoami()
|
||||
func (i dnsimpleIdentityService) Whoami(ctx context.Context) (*dnsimple.WhoamiResponse, error) {
|
||||
return i.service.Whoami(ctx)
|
||||
}
|
||||
|
||||
// Returns the account ID given dnsimple credentials
|
||||
func (p *dnsimpleProvider) GetAccountID(credentials dnsimple.Credentials, client dnsimple.Client) (accountID string, err error) {
|
||||
// get DNSimple client accountID
|
||||
whoamiResponse, err := client.Identity.Whoami()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strconv.Itoa(whoamiResponse.Data.Account.ID), nil
|
||||
}
|
||||
|
||||
// dnsimpleZoneServiceInterface is an interface that contains all necessary zone services from dnsimple
|
||||
// dnsimpleZoneServiceInterface is an interface that contains all necessary zone services from DNSimple
|
||||
type dnsimpleZoneServiceInterface interface {
|
||||
ListZones(accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error)
|
||||
ListRecords(accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error)
|
||||
CreateRecord(accountID string, zoneID string, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error)
|
||||
DeleteRecord(accountID string, zoneID string, recordID int) (*dnsimple.ZoneRecordResponse, error)
|
||||
UpdateRecord(accountID string, zoneID string, recordID int, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error)
|
||||
ListZones(ctx context.Context, accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error)
|
||||
ListRecords(ctx context.Context, accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error)
|
||||
CreateRecord(ctx context.Context, accountID string, zoneID string, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error)
|
||||
DeleteRecord(ctx context.Context, accountID string, zoneID string, recordID int64) (*dnsimple.ZoneRecordResponse, error)
|
||||
UpdateRecord(ctx context.Context, accountID string, zoneID string, recordID int64, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error)
|
||||
}
|
||||
|
||||
type dnsimpleZoneService struct {
|
||||
service *dnsimple.ZonesService
|
||||
}
|
||||
|
||||
func (z dnsimpleZoneService) ListZones(accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error) {
|
||||
return z.service.ListZones(accountID, options)
|
||||
func (z dnsimpleZoneService) ListZones(ctx context.Context, accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error) {
|
||||
return z.service.ListZones(ctx, accountID, options)
|
||||
}
|
||||
|
||||
func (z dnsimpleZoneService) ListRecords(accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error) {
|
||||
return z.service.ListRecords(accountID, zoneID, options)
|
||||
func (z dnsimpleZoneService) ListRecords(ctx context.Context, accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error) {
|
||||
return z.service.ListRecords(ctx, accountID, zoneID, options)
|
||||
}
|
||||
|
||||
func (z dnsimpleZoneService) CreateRecord(accountID string, zoneID string, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error) {
|
||||
return z.service.CreateRecord(accountID, zoneID, recordAttributes)
|
||||
func (z dnsimpleZoneService) CreateRecord(ctx context.Context, accountID string, zoneID string, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
|
||||
return z.service.CreateRecord(ctx, accountID, zoneID, recordAttributes)
|
||||
}
|
||||
|
||||
func (z dnsimpleZoneService) DeleteRecord(accountID string, zoneID string, recordID int) (*dnsimple.ZoneRecordResponse, error) {
|
||||
return z.service.DeleteRecord(accountID, zoneID, recordID)
|
||||
func (z dnsimpleZoneService) DeleteRecord(ctx context.Context, accountID string, zoneID string, recordID int64) (*dnsimple.ZoneRecordResponse, error) {
|
||||
return z.service.DeleteRecord(ctx, accountID, zoneID, recordID)
|
||||
}
|
||||
|
||||
func (z dnsimpleZoneService) UpdateRecord(accountID string, zoneID string, recordID int, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error) {
|
||||
return z.service.UpdateRecord(accountID, zoneID, recordID, recordAttributes)
|
||||
func (z dnsimpleZoneService) UpdateRecord(ctx context.Context, accountID string, zoneID string, recordID int64, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
|
||||
return z.service.UpdateRecord(ctx, accountID, zoneID, recordID, recordAttributes)
|
||||
}
|
||||
|
||||
type dnsimpleProvider struct {
|
||||
provider.BaseProvider
|
||||
client dnsimpleZoneServiceInterface
|
||||
identity identityService
|
||||
identity dnsimpleIdentityService
|
||||
accountID string
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
dryRun bool
|
||||
}
|
||||
|
||||
@ -104,35 +98,52 @@ const (
|
||||
)
|
||||
|
||||
// NewDnsimpleProvider initializes a new Dnsimple based provider
|
||||
func NewDnsimpleProvider(domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) (Provider, error) {
|
||||
func NewDnsimpleProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool) (provider.Provider, error) {
|
||||
oauthToken := os.Getenv("DNSIMPLE_OAUTH")
|
||||
if len(oauthToken) == 0 {
|
||||
return nil, fmt.Errorf("No dnsimple oauth token provided")
|
||||
return nil, fmt.Errorf("no dnsimple oauth token provided")
|
||||
}
|
||||
client := dnsimple.NewClient(dnsimple.NewOauthTokenCredentials(oauthToken))
|
||||
|
||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: oauthToken})
|
||||
tc := oauth2.NewClient(context.Background(), ts)
|
||||
|
||||
client := dnsimple.NewClient(tc)
|
||||
client.SetUserAgent(fmt.Sprintf("Kubernetes ExternalDNS/%s", externaldns.Version))
|
||||
|
||||
provider := &dnsimpleProvider{
|
||||
client: dnsimpleZoneService{service: client.Zones},
|
||||
identity: identityService{service: client.Identity},
|
||||
identity: dnsimpleIdentityService{service: client.Identity},
|
||||
domainFilter: domainFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
dryRun: dryRun,
|
||||
}
|
||||
whoamiResponse, err := provider.identity.service.Whoami()
|
||||
|
||||
whoamiResponse, err := provider.identity.Whoami(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provider.accountID = strconv.Itoa(whoamiResponse.Data.Account.ID)
|
||||
provider.accountID = int64ToString(whoamiResponse.Data.Account.ID)
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// GetAccountID returns the account ID given DNSimple credentials.
|
||||
func (p *dnsimpleProvider) GetAccountID(ctx context.Context) (accountID string, err error) {
|
||||
// get DNSimple client accountID
|
||||
whoamiResponse, err := p.identity.Whoami(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return int64ToString(whoamiResponse.Data.Account.ID), nil
|
||||
}
|
||||
|
||||
// Returns a list of filtered Zones
|
||||
func (p *dnsimpleProvider) Zones() (map[string]dnsimple.Zone, error) {
|
||||
func (p *dnsimpleProvider) Zones(ctx context.Context) (map[string]dnsimple.Zone, error) {
|
||||
zones := make(map[string]dnsimple.Zone)
|
||||
page := 1
|
||||
listOptions := &dnsimple.ZoneListOptions{}
|
||||
for {
|
||||
listOptions.Page = page
|
||||
zonesResponse, err := p.client.ListZones(p.accountID, listOptions)
|
||||
listOptions.Page = &page
|
||||
zonesResponse, err := p.client.ListZones(ctx, p.accountID, listOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -141,11 +152,11 @@ func (p *dnsimpleProvider) Zones() (map[string]dnsimple.Zone, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !p.zoneIDFilter.Match(strconv.Itoa(zone.ID)) {
|
||||
if !p.zoneIDFilter.Match(int64ToString(zone.ID)) {
|
||||
continue
|
||||
}
|
||||
|
||||
zones[strconv.Itoa(zone.ID)] = zone
|
||||
zones[int64ToString(zone.ID)] = zone
|
||||
}
|
||||
|
||||
page++
|
||||
@ -158,7 +169,7 @@ func (p *dnsimpleProvider) Zones() (map[string]dnsimple.Zone, error) {
|
||||
|
||||
// Records returns a list of endpoints in a given zone
|
||||
func (p *dnsimpleProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, _ error) {
|
||||
zones, err := p.Zones()
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -166,8 +177,8 @@ func (p *dnsimpleProvider) Records(ctx context.Context) (endpoints []*endpoint.E
|
||||
page := 1
|
||||
listOptions := &dnsimple.ZoneRecordListOptions{}
|
||||
for {
|
||||
listOptions.Page = page
|
||||
records, err := p.client.ListRecords(p.accountID, zone.Name, listOptions)
|
||||
listOptions.Page = &page
|
||||
records, err := p.client.ListRecords(ctx, p.accountID, zone.Name, listOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -224,12 +235,12 @@ func newDnsimpleChanges(action string, endpoints []*endpoint.Endpoint) []*dnsimp
|
||||
}
|
||||
|
||||
// submitChanges takes a zone and a collection of changes and makes all changes from the collection
|
||||
func (p *dnsimpleProvider) submitChanges(changes []*dnsimpleChange) error {
|
||||
func (p *dnsimpleProvider) submitChanges(ctx context.Context, changes []*dnsimpleChange) error {
|
||||
if len(changes) == 0 {
|
||||
log.Infof("All records are already up to date")
|
||||
return nil
|
||||
}
|
||||
zones, err := p.Zones()
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -248,28 +259,35 @@ func (p *dnsimpleProvider) submitChanges(changes []*dnsimpleChange) error {
|
||||
change.ResourceRecordSet.Name = strings.TrimSuffix(change.ResourceRecordSet.Name, fmt.Sprintf(".%s", zone.Name))
|
||||
}
|
||||
|
||||
recordAttributes := dnsimple.ZoneRecordAttributes{
|
||||
Name: &change.ResourceRecordSet.Name,
|
||||
Type: change.ResourceRecordSet.Type,
|
||||
Content: change.ResourceRecordSet.Content,
|
||||
TTL: change.ResourceRecordSet.TTL,
|
||||
}
|
||||
|
||||
if !p.dryRun {
|
||||
switch change.Action {
|
||||
case dnsimpleCreate:
|
||||
_, err := p.client.CreateRecord(p.accountID, zone.Name, change.ResourceRecordSet)
|
||||
_, err := p.client.CreateRecord(ctx, p.accountID, zone.Name, recordAttributes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case dnsimpleDelete:
|
||||
recordID, err := p.GetRecordID(zone.Name, change.ResourceRecordSet.Name)
|
||||
recordID, err := p.GetRecordID(ctx, zone.Name, *recordAttributes.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = p.client.DeleteRecord(p.accountID, zone.Name, recordID)
|
||||
_, err = p.client.DeleteRecord(ctx, p.accountID, zone.Name, recordID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case dnsimpleUpdate:
|
||||
recordID, err := p.GetRecordID(zone.Name, change.ResourceRecordSet.Name)
|
||||
recordID, err := p.GetRecordID(ctx, zone.Name, *recordAttributes.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = p.client.UpdateRecord(p.accountID, zone.Name, recordID, change.ResourceRecordSet)
|
||||
_, err = p.client.UpdateRecord(ctx, p.accountID, zone.Name, recordID, recordAttributes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -279,13 +297,13 @@ func (p *dnsimpleProvider) submitChanges(changes []*dnsimpleChange) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns the record ID for a given record name and zone
|
||||
func (p *dnsimpleProvider) GetRecordID(zone string, recordName string) (recordID int, err error) {
|
||||
// GetRecordID returns the record ID for a given record name and zone.
|
||||
func (p *dnsimpleProvider) GetRecordID(ctx context.Context, zone string, recordName string) (recordID int64, err error) {
|
||||
page := 1
|
||||
listOptions := &dnsimple.ZoneRecordListOptions{Name: recordName}
|
||||
listOptions := &dnsimple.ZoneRecordListOptions{Name: &recordName}
|
||||
for {
|
||||
listOptions.Page = page
|
||||
records, err := p.client.ListRecords(p.accountID, zone, listOptions)
|
||||
listOptions.Page = &page
|
||||
records, err := p.client.ListRecords(ctx, p.accountID, zone, listOptions)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -301,7 +319,7 @@ func (p *dnsimpleProvider) GetRecordID(zone string, recordName string) (recordID
|
||||
break
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("No record id found")
|
||||
return 0, fmt.Errorf("no record id found")
|
||||
}
|
||||
|
||||
// dnsimpleSuitableZone returns the most suitable zone for a given hostname and a set of zones.
|
||||
@ -319,18 +337,18 @@ func dnsimpleSuitableZone(hostname string, zones map[string]dnsimple.Zone) *dnsi
|
||||
}
|
||||
|
||||
// CreateRecords creates records for a given slice of endpoints
|
||||
func (p *dnsimpleProvider) CreateRecords(endpoints []*endpoint.Endpoint) error {
|
||||
return p.submitChanges(newDnsimpleChanges(dnsimpleCreate, endpoints))
|
||||
func (p *dnsimpleProvider) CreateRecords(ctx context.Context, endpoints []*endpoint.Endpoint) error {
|
||||
return p.submitChanges(ctx, newDnsimpleChanges(dnsimpleCreate, endpoints))
|
||||
}
|
||||
|
||||
// DeleteRecords deletes records for a given slice of endpoints
|
||||
func (p *dnsimpleProvider) DeleteRecords(endpoints []*endpoint.Endpoint) error {
|
||||
return p.submitChanges(newDnsimpleChanges(dnsimpleDelete, endpoints))
|
||||
func (p *dnsimpleProvider) DeleteRecords(ctx context.Context, endpoints []*endpoint.Endpoint) error {
|
||||
return p.submitChanges(ctx, newDnsimpleChanges(dnsimpleDelete, endpoints))
|
||||
}
|
||||
|
||||
// UpdateRecords updates records for a given slice of endpoints
|
||||
func (p *dnsimpleProvider) UpdateRecords(endpoints []*endpoint.Endpoint) error {
|
||||
return p.submitChanges(newDnsimpleChanges(dnsimpleUpdate, endpoints))
|
||||
func (p *dnsimpleProvider) UpdateRecords(ctx context.Context, endpoints []*endpoint.Endpoint) error {
|
||||
return p.submitChanges(ctx, newDnsimpleChanges(dnsimpleUpdate, endpoints))
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes
|
||||
@ -341,5 +359,9 @@ func (p *dnsimpleProvider) ApplyChanges(ctx context.Context, changes *plan.Chang
|
||||
combinedChanges = append(combinedChanges, newDnsimpleChanges(dnsimpleUpdate, changes.UpdateNew)...)
|
||||
combinedChanges = append(combinedChanges, newDnsimpleChanges(dnsimpleDelete, changes.Delete)...)
|
||||
|
||||
return p.submitChanges(combinedChanges)
|
||||
return p.submitChanges(ctx, combinedChanges)
|
||||
}
|
||||
|
||||
func int64ToString(i int64) string {
|
||||
return strconv.FormatInt(i, 10)
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package dnsimple
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -22,8 +22,6 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"strconv"
|
||||
|
||||
"github.com/dnsimple/dnsimple-go/dnsimple"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@ -31,6 +29,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
var mockProvider dnsimpleProvider
|
||||
@ -102,15 +101,17 @@ func TestDnsimpleServices(t *testing.T) {
|
||||
}
|
||||
|
||||
// Setup mock services
|
||||
// Note: AnythingOfType doesn't work with interfaces https://github.com/stretchr/testify/issues/519
|
||||
mockDNS := &mockDnsimpleZoneServiceInterface{}
|
||||
mockDNS.On("ListZones", "1", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimpleListZonesResponse, nil)
|
||||
mockDNS.On("ListZones", "2", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(nil, fmt.Errorf("Account ID not found"))
|
||||
mockDNS.On("ListRecords", "1", "example.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimpleListRecordsResponse, nil)
|
||||
mockDNS.On("ListRecords", "1", "example-beta.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimple.ZoneRecordsResponse{Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}}}, nil)
|
||||
mockDNS.On("ListZones", mock.AnythingOfType("*context.emptyCtx"), "1", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(&dnsimpleListZonesResponse, nil)
|
||||
mockDNS.On("ListZones", mock.AnythingOfType("*context.emptyCtx"), "2", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(nil, fmt.Errorf("Account ID not found"))
|
||||
mockDNS.On("ListRecords", mock.AnythingOfType("*context.emptyCtx"), "1", "example.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(&dnsimpleListRecordsResponse, nil)
|
||||
mockDNS.On("ListRecords", mock.AnythingOfType("*context.emptyCtx"), "1", "example-beta.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(&dnsimple.ZoneRecordsResponse{Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}}}, nil)
|
||||
|
||||
for _, record := range records {
|
||||
simpleRecord := dnsimple.ZoneRecord{
|
||||
Name: record.Name,
|
||||
recordName := record.Name
|
||||
simpleRecord := dnsimple.ZoneRecordAttributes{
|
||||
Name: &recordName,
|
||||
Type: record.Type,
|
||||
Content: record.Content,
|
||||
TTL: record.TTL,
|
||||
@ -121,10 +122,10 @@ func TestDnsimpleServices(t *testing.T) {
|
||||
Data: []dnsimple.ZoneRecord{record},
|
||||
}
|
||||
|
||||
mockDNS.On("ListRecords", "1", record.ZoneID, &dnsimple.ZoneRecordListOptions{Name: record.Name, ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimpleRecordResponse, nil)
|
||||
mockDNS.On("CreateRecord", "1", record.ZoneID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
mockDNS.On("DeleteRecord", "1", record.ZoneID, record.ID).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
mockDNS.On("UpdateRecord", "1", record.ZoneID, record.ID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
mockDNS.On("ListRecords", mock.AnythingOfType("*context.emptyCtx"), "1", record.ZoneID, &dnsimple.ZoneRecordListOptions{Name: &recordName, ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(1)}}).Return(&dnsimpleRecordResponse, nil)
|
||||
mockDNS.On("CreateRecord", mock.AnythingOfType("*context.emptyCtx"), "1", record.ZoneID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
mockDNS.On("DeleteRecord", mock.AnythingOfType("*context.emptyCtx"), "1", record.ZoneID, record.ID).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
mockDNS.On("UpdateRecord", mock.AnythingOfType("*context.emptyCtx"), "1", record.ZoneID, record.ID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||
}
|
||||
|
||||
mockProvider = dnsimpleProvider{client: mockDNS}
|
||||
@ -139,13 +140,14 @@ func TestDnsimpleServices(t *testing.T) {
|
||||
}
|
||||
|
||||
func testDnsimpleProviderZones(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockProvider.accountID = "1"
|
||||
result, err := mockProvider.Zones()
|
||||
result, err := mockProvider.Zones(ctx)
|
||||
assert.Nil(t, err)
|
||||
validateDnsimpleZones(t, result, dnsimpleListZonesResponse.Data)
|
||||
|
||||
mockProvider.accountID = "2"
|
||||
_, err = mockProvider.Zones()
|
||||
_, err = mockProvider.Zones(ctx)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
@ -160,13 +162,16 @@ func testDnsimpleProviderRecords(t *testing.T) {
|
||||
_, err = mockProvider.Records(ctx)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func testDnsimpleProviderApplyChanges(t *testing.T) {
|
||||
changes := &plan.Changes{}
|
||||
changes.Create = []*endpoint.Endpoint{
|
||||
{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
|
||||
{DNSName: "custom-ttl.example.com", RecordTTL: 60, Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
|
||||
}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "example-beta.example.com", Targets: endpoint.Targets{"127.0.0.1"}, RecordType: endpoint.RecordTypeA}}
|
||||
changes.Delete = []*endpoint.Endpoint{
|
||||
{DNSName: "example-beta.example.com", Targets: endpoint.Targets{"127.0.0.1"}, RecordType: endpoint.RecordTypeA},
|
||||
}
|
||||
changes.UpdateNew = []*endpoint.Endpoint{
|
||||
{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
|
||||
{DNSName: "example.com", Targets: endpoint.Targets{"127.0.0.1"}, RecordType: endpoint.RecordTypeA},
|
||||
@ -193,8 +198,9 @@ func testDnsimpleProviderApplyChangesSkipsUnknown(t *testing.T) {
|
||||
}
|
||||
|
||||
func testDnsimpleSuitableZone(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
mockProvider.accountID = "1"
|
||||
zones, err := mockProvider.Zones()
|
||||
zones, err := mockProvider.Zones(ctx)
|
||||
assert.Nil(t, err)
|
||||
|
||||
zone := dnsimpleSuitableZone("example-beta.example.com", zones)
|
||||
@ -203,7 +209,7 @@ func testDnsimpleSuitableZone(t *testing.T) {
|
||||
|
||||
func TestNewDnsimpleProvider(t *testing.T) {
|
||||
os.Setenv("DNSIMPLE_OAUTH", "xxxxxxxxxxxxxxxxxxxxxxxxxx")
|
||||
_, err := NewDnsimpleProvider(endpoint.NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true)
|
||||
_, err := NewDnsimpleProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true)
|
||||
if err == nil {
|
||||
t.Errorf("Expected to fail new provider on bad token")
|
||||
}
|
||||
@ -214,21 +220,24 @@ func TestNewDnsimpleProvider(t *testing.T) {
|
||||
}
|
||||
|
||||
func testDnsimpleGetRecordID(t *testing.T) {
|
||||
mockProvider.accountID = "1"
|
||||
result, err := mockProvider.GetRecordID("example.com", "example")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, result)
|
||||
var result int64
|
||||
var err error
|
||||
|
||||
result, err = mockProvider.GetRecordID("example.com", "example-beta")
|
||||
mockProvider.accountID = "1"
|
||||
result, err = mockProvider.GetRecordID(context.Background(), "example.com", "example")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, result)
|
||||
assert.Equal(t, int64(2), result)
|
||||
|
||||
result, err = mockProvider.GetRecordID(context.Background(), "example.com", "example-beta")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(1), result)
|
||||
}
|
||||
|
||||
func validateDnsimpleZones(t *testing.T, zones map[string]dnsimple.Zone, expected []dnsimple.Zone) {
|
||||
require.Len(t, zones, len(expected))
|
||||
|
||||
for _, e := range expected {
|
||||
assert.Equal(t, zones[strconv.Itoa(e.ID)].Name, e.Name)
|
||||
assert.Equal(t, zones[int64ToString(e.ID)].Name, e.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,8 +245,8 @@ type mockDnsimpleZoneServiceInterface struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (_m *mockDnsimpleZoneServiceInterface) CreateRecord(accountID string, zoneID string, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error) {
|
||||
args := _m.Called(accountID, zoneID, recordAttributes)
|
||||
func (_m *mockDnsimpleZoneServiceInterface) CreateRecord(ctx context.Context, accountID string, zoneID string, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
|
||||
args := _m.Called(ctx, accountID, zoneID, recordAttributes)
|
||||
var r0 *dnsimple.ZoneRecordResponse
|
||||
|
||||
if args.Get(0) != nil {
|
||||
@ -247,8 +256,8 @@ func (_m *mockDnsimpleZoneServiceInterface) CreateRecord(accountID string, zoneI
|
||||
return r0, args.Error(1)
|
||||
}
|
||||
|
||||
func (_m *mockDnsimpleZoneServiceInterface) DeleteRecord(accountID string, zoneID string, recordID int) (*dnsimple.ZoneRecordResponse, error) {
|
||||
args := _m.Called(accountID, zoneID, recordID)
|
||||
func (_m *mockDnsimpleZoneServiceInterface) DeleteRecord(ctx context.Context, accountID string, zoneID string, recordID int64) (*dnsimple.ZoneRecordResponse, error) {
|
||||
args := _m.Called(ctx, accountID, zoneID, recordID)
|
||||
var r0 *dnsimple.ZoneRecordResponse
|
||||
|
||||
if args.Get(0) != nil {
|
||||
@ -258,8 +267,8 @@ func (_m *mockDnsimpleZoneServiceInterface) DeleteRecord(accountID string, zoneI
|
||||
return r0, args.Error(1)
|
||||
}
|
||||
|
||||
func (_m *mockDnsimpleZoneServiceInterface) ListRecords(accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error) {
|
||||
args := _m.Called(accountID, zoneID, options)
|
||||
func (_m *mockDnsimpleZoneServiceInterface) ListRecords(ctx context.Context, accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error) {
|
||||
args := _m.Called(ctx, accountID, zoneID, options)
|
||||
var r0 *dnsimple.ZoneRecordsResponse
|
||||
|
||||
if args.Get(0) != nil {
|
||||
@ -269,8 +278,8 @@ func (_m *mockDnsimpleZoneServiceInterface) ListRecords(accountID string, zoneID
|
||||
return r0, args.Error(1)
|
||||
}
|
||||
|
||||
func (_m *mockDnsimpleZoneServiceInterface) ListZones(accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error) {
|
||||
args := _m.Called(accountID, options)
|
||||
func (_m *mockDnsimpleZoneServiceInterface) ListZones(ctx context.Context, accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error) {
|
||||
args := _m.Called(ctx, accountID, options)
|
||||
var r0 *dnsimple.ZonesResponse
|
||||
|
||||
if args.Get(0) != nil {
|
||||
@ -280,8 +289,8 @@ func (_m *mockDnsimpleZoneServiceInterface) ListZones(accountID string, options
|
||||
return r0, args.Error(1)
|
||||
}
|
||||
|
||||
func (_m *mockDnsimpleZoneServiceInterface) UpdateRecord(accountID string, zoneID string, recordID int, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error) {
|
||||
args := _m.Called(accountID, zoneID, recordID, recordAttributes)
|
||||
func (_m *mockDnsimpleZoneServiceInterface) UpdateRecord(ctx context.Context, accountID string, zoneID string, recordID int64, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
|
||||
args := _m.Called(ctx, accountID, zoneID, recordID, recordAttributes)
|
||||
var r0 *dnsimple.ZoneRecordResponse
|
||||
|
||||
if args.Get(0) != nil {
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package dyn
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -31,6 +31,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -57,7 +58,7 @@ func unixNow() int64 {
|
||||
// DynConfig hold connection parameters to dyn.com and internal state
|
||||
type DynConfig struct {
|
||||
DomainFilter endpoint.DomainFilter
|
||||
ZoneIDFilter ZoneIDFilter
|
||||
ZoneIDFilter provider.ZoneIDFilter
|
||||
DryRun bool
|
||||
CustomerName string
|
||||
Username string
|
||||
@ -103,6 +104,7 @@ func (snap *ZoneSnapshot) StoreRecordsForSerial(zone string, serial int, records
|
||||
|
||||
// DynProvider is the actual interface impl.
|
||||
type dynProviderState struct {
|
||||
provider.BaseProvider
|
||||
DynConfig
|
||||
LastLoginErrorTime int64
|
||||
|
||||
@ -141,7 +143,7 @@ type ZonePublishResponse struct {
|
||||
}
|
||||
|
||||
// NewDynProvider initializes a new Dyn Provider.
|
||||
func NewDynProvider(config DynConfig) (Provider, error) {
|
||||
func NewDynProvider(config DynConfig) (provider.Provider, error) {
|
||||
return &dynProviderState{
|
||||
DynConfig: config,
|
||||
ZoneSnapshot: &ZoneSnapshot{
|
||||
@ -156,7 +158,6 @@ func NewDynProvider(config DynConfig) (Provider, error) {
|
||||
func filterAndFixLinks(links []string, filter endpoint.DomainFilter) []string {
|
||||
var result []string
|
||||
for _, link := range links {
|
||||
|
||||
// link looks like /REST/CNAMERecord/acme.com/exchange.acme.com/349386875
|
||||
|
||||
// strip /REST/
|
||||
@ -290,7 +291,6 @@ func (d *dynProviderState) allRecordsToEndpoints(records *dynectsoap.GetAllRecor
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
func errorOrValue(err error, value interface{}) interface{} {
|
||||
@ -392,7 +392,6 @@ func (d *dynProviderState) fetchAllRecordsInZone(zone string) (*dynectsoap.GetAl
|
||||
}
|
||||
|
||||
return &records, nil
|
||||
|
||||
}
|
||||
|
||||
// buildLinkToRecord build a resource link. The symmetry of the dyn API is used to save
|
||||
@ -404,7 +403,7 @@ func (d *dynProviderState) buildLinkToRecord(ep *endpoint.Endpoint) string {
|
||||
return ""
|
||||
}
|
||||
var matchingZone = ""
|
||||
for _, zone := range d.ZoneIDFilter.zoneIDs {
|
||||
for _, zone := range d.ZoneIDFilter.ZoneIDs {
|
||||
if strings.HasSuffix(ep.DNSName, zone) {
|
||||
matchingZone = zone
|
||||
break
|
||||
@ -460,7 +459,7 @@ func (d *dynProviderState) login() (*dynect.Client, error) {
|
||||
// the zones we are allowed to touch. Currently only exact matches are considered, not all
|
||||
// zones with the given suffix
|
||||
func (d *dynProviderState) zones(client *dynect.Client) []string {
|
||||
return d.ZoneIDFilter.zoneIDs
|
||||
return d.ZoneIDFilter.ZoneIDs
|
||||
}
|
||||
|
||||
func (d *dynProviderState) buildRecordRequest(ep *endpoint.Endpoint) (string, *dynect.RecordRequest) {
|
||||
@ -568,7 +567,7 @@ func (d *dynProviderState) commit(client *dynect.Client) error {
|
||||
case 1:
|
||||
return errs[0]
|
||||
default:
|
||||
return fmt.Errorf("Multiple errors committing: %+v", errs)
|
||||
return fmt.Errorf("multiple errors committing: %+v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
@ -679,7 +678,7 @@ func (d *dynProviderState) ApplyChanges(ctx context.Context, changes *plan.Chang
|
||||
case 1:
|
||||
return errs[0]
|
||||
default:
|
||||
return fmt.Errorf("Multiple errors committing: %+v", errs)
|
||||
return fmt.Errorf("multiple errors committing: %+v", errs)
|
||||
}
|
||||
|
||||
if needsCommit {
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package dyn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -25,6 +25,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
func TestDynMerge_NoUpdateOnTTL0Changes(t *testing.T) {
|
||||
@ -187,7 +188,7 @@ func TestDyn_endpointToRecord(t *testing.T) {
|
||||
func TestDyn_buildLinkToRecord(t *testing.T) {
|
||||
provider := &dynProviderState{
|
||||
DynConfig: DynConfig{
|
||||
ZoneIDFilter: NewZoneIDFilter([]string{"example.com"}),
|
||||
ZoneIDFilter: provider.NewZoneIDFilter([]string{"example.com"}),
|
||||
DomainFilter: endpoint.NewDomainFilter([]string{"the-target.example.com"}),
|
||||
},
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package exoscale
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -25,6 +25,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// EgoscaleClientI for replaceable implementation
|
||||
@ -38,6 +39,7 @@ type EgoscaleClientI interface {
|
||||
|
||||
// ExoscaleProvider initialized as dns provider with no records
|
||||
type ExoscaleProvider struct {
|
||||
provider.BaseProvider
|
||||
domain endpoint.DomainFilter
|
||||
client EgoscaleClientI
|
||||
filter *zoneFilter
|
||||
@ -257,3 +259,38 @@ func (f *zoneFilter) EndpointZoneID(endpoint *endpoint.Endpoint, zones map[int64
|
||||
}
|
||||
return matchZoneID, name
|
||||
}
|
||||
|
||||
func merge(updateOld, updateNew []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
findMatch := func(template *endpoint.Endpoint) *endpoint.Endpoint {
|
||||
for _, new := range updateNew {
|
||||
if template.DNSName == new.DNSName &&
|
||||
template.RecordType == new.RecordType {
|
||||
return new
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var result []*endpoint.Endpoint
|
||||
for _, old := range updateOld {
|
||||
matchingNew := findMatch(old)
|
||||
if matchingNew == nil {
|
||||
// no match, shouldn't happen
|
||||
continue
|
||||
}
|
||||
|
||||
if !matchingNew.Targets.Same(old.Targets) {
|
||||
// new target: always update, TTL will be overwritten too if necessary
|
||||
result = append(result, matchingNew)
|
||||
continue
|
||||
}
|
||||
|
||||
if matchingNew.RecordTTL != 0 && matchingNew.RecordTTL != old.RecordTTL {
|
||||
// same target, but new non-zero TTL set in k8s, must update
|
||||
// probably would happen only if there is a bug in the code calling the provider
|
||||
result = append(result, matchingNew)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package exoscale
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -189,3 +189,144 @@ func TestExoscaleApplyChanges(t *testing.T) {
|
||||
assert.Equal(t, "foo.com", updateExoscale[0].name)
|
||||
assert.Equal(t, int64(1), updateExoscale[0].updateDNSRecord.ID)
|
||||
}
|
||||
|
||||
func TestExoscaleMerge_NoUpdateOnTTL0Changes(t *testing.T) {
|
||||
updateOld := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
},
|
||||
}
|
||||
|
||||
updateNew := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(0),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(0),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, 0, len(merge(updateOld, updateNew)))
|
||||
}
|
||||
|
||||
func TestExoscaleMerge_UpdateOnTTLChanges(t *testing.T) {
|
||||
updateOld := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
|
||||
updateNew := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(77),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(10),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
|
||||
merged := merge(updateOld, updateNew)
|
||||
assert.Equal(t, 2, len(merged))
|
||||
assert.Equal(t, "name1", merged[0].DNSName)
|
||||
}
|
||||
|
||||
func TestExoscaleMerge_AlwaysUpdateTarget(t *testing.T) {
|
||||
updateOld := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(1),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
|
||||
updateNew := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Targets: endpoint.Targets{"target1-changed"},
|
||||
RecordTTL: endpoint.TTL(0),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(0),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
|
||||
merged := merge(updateOld, updateNew)
|
||||
assert.Equal(t, 1, len(merged))
|
||||
assert.Equal(t, "target1-changed", merged[0].Targets[0])
|
||||
}
|
||||
|
||||
func TestExoscaleMerge_NoUpdateIfTTLUnchanged(t *testing.T) {
|
||||
updateOld := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(55),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(55),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
|
||||
updateNew := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "name1",
|
||||
Targets: endpoint.Targets{"target1"},
|
||||
RecordTTL: endpoint.TTL(55),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
{
|
||||
DNSName: "name2",
|
||||
Targets: endpoint.Targets{"target2"},
|
||||
RecordTTL: endpoint.TTL(55),
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
},
|
||||
}
|
||||
|
||||
merged := merge(updateOld, updateNew)
|
||||
assert.Equal(t, 0, len(merged))
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package google
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -33,6 +33,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -98,6 +99,7 @@ func (c changesService) Create(project string, managedZone string, change *dns.C
|
||||
|
||||
// GoogleProvider is an implementation of Provider for Google CloudDNS.
|
||||
type GoogleProvider struct {
|
||||
provider.BaseProvider
|
||||
// The Google project to work in
|
||||
project string
|
||||
// Enabled dry-run will print any modifying actions rather than execute them.
|
||||
@ -109,7 +111,7 @@ type GoogleProvider struct {
|
||||
// only consider hosted zones managing domains ending in this suffix
|
||||
domainFilter endpoint.DomainFilter
|
||||
// only consider hosted zones ending with this zone id
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
// A client for managing resource record sets
|
||||
resourceRecordSetsClient resourceRecordSetsClientInterface
|
||||
// A client for managing hosted zones
|
||||
@ -121,7 +123,7 @@ type GoogleProvider struct {
|
||||
}
|
||||
|
||||
// NewGoogleProvider initializes a new Google CloudDNS based Provider.
|
||||
func NewGoogleProvider(ctx context.Context, project string, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, batchChangeSize int, batchChangeInterval time.Duration, dryRun bool) (*GoogleProvider, error) {
|
||||
func NewGoogleProvider(ctx context.Context, project string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, batchChangeSize int, batchChangeInterval time.Duration, dryRun bool) (*GoogleProvider, error) {
|
||||
gcloud, err := google.DefaultClient(ctx, dns.NdevClouddnsReadwriteScope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -209,7 +211,7 @@ func (p *GoogleProvider) Records(ctx context.Context) (endpoints []*endpoint.End
|
||||
|
||||
f := func(resp *dns.ResourceRecordSetsListResponse) error {
|
||||
for _, r := range resp.Rrsets {
|
||||
if !supportedRecordType(r.Type) {
|
||||
if !provider.SupportedRecordType(r.Type) {
|
||||
continue
|
||||
}
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, r.Type, endpoint.TTL(r.Ttl), r.Rrdatas...))
|
||||
@ -398,7 +400,7 @@ func batchChange(change *dns.Change, batchSize int) []*dns.Change {
|
||||
// separateChange separates a multi-zone change into a single change per zone.
|
||||
func separateChange(zones map[string]*dns.ManagedZone, change *dns.Change) map[string]*dns.Change {
|
||||
changes := make(map[string]*dns.Change)
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, z := range zones {
|
||||
zoneNameIDMapper[z.Name] = z.DnsName
|
||||
changes[z.Name] = &dns.Change{
|
||||
@ -407,7 +409,7 @@ func separateChange(zones map[string]*dns.ManagedZone, change *dns.Change) map[s
|
||||
}
|
||||
}
|
||||
for _, a := range change.Additions {
|
||||
if zoneName, _ := zoneNameIDMapper.FindZone(ensureTrailingDot(a.Name)); zoneName != "" {
|
||||
if zoneName, _ := zoneNameIDMapper.FindZone(provider.EnsureTrailingDot(a.Name)); zoneName != "" {
|
||||
changes[zoneName].Additions = append(changes[zoneName].Additions, a)
|
||||
} else {
|
||||
log.Warnf("No matching zone for record addition: %s %s %s %d", a.Name, a.Type, a.Rrdatas, a.Ttl)
|
||||
@ -415,7 +417,7 @@ func separateChange(zones map[string]*dns.ManagedZone, change *dns.Change) map[s
|
||||
}
|
||||
|
||||
for _, d := range change.Deletions {
|
||||
if zoneName, _ := zoneNameIDMapper.FindZone(ensureTrailingDot(d.Name)); zoneName != "" {
|
||||
if zoneName, _ := zoneNameIDMapper.FindZone(provider.EnsureTrailingDot(d.Name)); zoneName != "" {
|
||||
changes[zoneName].Deletions = append(changes[zoneName].Deletions, d)
|
||||
} else {
|
||||
log.Warnf("No matching zone for record deletion: %s %s %s %d", d.Name, d.Type, d.Rrdatas, d.Ttl)
|
||||
@ -440,7 +442,7 @@ func newRecord(ep *endpoint.Endpoint) *dns.ResourceRecordSet {
|
||||
targets := make([]string, len(ep.Targets))
|
||||
copy(targets, []string(ep.Targets))
|
||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||
targets[0] = ensureTrailingDot(targets[0])
|
||||
targets[0] = provider.EnsureTrailingDot(targets[0])
|
||||
}
|
||||
|
||||
// no annotation results in a Ttl of 0, default to 300 for backwards-compatibility
|
||||
@ -450,7 +452,7 @@ func newRecord(ep *endpoint.Endpoint) *dns.ResourceRecordSet {
|
||||
}
|
||||
|
||||
return &dns.ResourceRecordSet{
|
||||
Name: ensureTrailingDot(ep.DNSName),
|
||||
Name: provider.EnsureTrailingDot(ep.DNSName),
|
||||
Rrdatas: targets,
|
||||
Ttl: ttl,
|
||||
Type: ep.RecordType,
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -30,7 +30,9 @@ import (
|
||||
"google.golang.org/api/googleapi"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -192,7 +194,7 @@ func hasTrailingDot(target string) bool {
|
||||
}
|
||||
|
||||
func TestGoogleZonesIDFilter(t *testing.T) {
|
||||
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), NewZoneIDFilter([]string{"10002"}), false, []*endpoint.Endpoint{})
|
||||
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"10002"}), false, []*endpoint.Endpoint{})
|
||||
|
||||
zones, err := provider.Zones(context.Background())
|
||||
require.NoError(t, err)
|
||||
@ -203,7 +205,7 @@ func TestGoogleZonesIDFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGoogleZonesNameFilter(t *testing.T) {
|
||||
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), NewZoneIDFilter([]string{"internal-2"}), false, []*endpoint.Endpoint{})
|
||||
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"internal-2"}), false, []*endpoint.Endpoint{})
|
||||
|
||||
zones, err := provider.Zones(context.Background())
|
||||
require.NoError(t, err)
|
||||
@ -214,7 +216,7 @@ func TestGoogleZonesNameFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGoogleZones(t *testing.T) {
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
|
||||
zones, err := provider.Zones(context.Background())
|
||||
require.NoError(t, err)
|
||||
@ -233,7 +235,7 @@ func TestGoogleRecords(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("list-test-alias.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(3), "foo.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, originalEndpoints)
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, originalEndpoints)
|
||||
|
||||
records, err := provider.Records(context.Background())
|
||||
require.NoError(t, err)
|
||||
@ -261,7 +263,7 @@ func TestGoogleRecordsFilter(t *testing.T) {
|
||||
"zone-0.ext-dns-test-2.gcp.zalan.do.",
|
||||
// there exists a third zone "zone-3" that we want to exclude from being managed.
|
||||
}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
provider.NewZoneIDFilter([]string{""}),
|
||||
false,
|
||||
originalEndpoints,
|
||||
)
|
||||
@ -286,7 +288,7 @@ func TestGoogleRecordsFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGoogleCreateRecords(t *testing.T) {
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
|
||||
records := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
@ -312,7 +314,7 @@ func TestGoogleUpdateRecords(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("update-test-ttl.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, endpoint.TTL(15), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, googleRecordTTL, "foo.elb.amazonaws.com"),
|
||||
}
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, currentRecords)
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, currentRecords)
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-ttl.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, endpoint.TTL(25), "4.3.2.1"),
|
||||
@ -338,7 +340,7 @@ func TestGoogleDeleteRecords(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, googleRecordTTL, "baz.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, originalEndpoints)
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, originalEndpoints)
|
||||
|
||||
require.NoError(t, provider.DeleteRecords(originalEndpoints))
|
||||
|
||||
@ -359,7 +361,7 @@ func TestGoogleApplyChanges(t *testing.T) {
|
||||
"zone-0.ext-dns-test-2.gcp.zalan.do.",
|
||||
// there exists a third zone "zone-3" that we want to exclude from being managed.
|
||||
}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
provider.NewZoneIDFilter([]string{""}),
|
||||
false,
|
||||
[]*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, googleRecordTTL, "8.8.8.8"),
|
||||
@ -433,7 +435,7 @@ func TestGoogleApplyChangesDryRun(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, googleRecordTTL, "qux.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), true, originalEndpoints)
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), true, originalEndpoints)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
@ -475,12 +477,12 @@ func TestGoogleApplyChangesDryRun(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGoogleApplyChangesEmpty(t *testing.T) {
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
assert.NoError(t, provider.ApplyChanges(context.Background(), &plan.Changes{}))
|
||||
}
|
||||
|
||||
func TestNewFilteredRecords(t *testing.T) {
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
|
||||
records := provider.newFilteredRecords([]*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, 1, "8.8.4.4"),
|
||||
@ -670,7 +672,7 @@ func validateChangeRecord(t *testing.T, record *dns.ResourceRecordSet, expected
|
||||
assert.Equal(t, expected.Type, record.Type)
|
||||
}
|
||||
|
||||
func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
|
||||
func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
|
||||
provider := &GoogleProvider{
|
||||
project: "zalando-external-dns-test",
|
||||
dryRun: false,
|
||||
@ -705,7 +707,7 @@ func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilt
|
||||
|
||||
}
|
||||
|
||||
func newGoogleProvider(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
|
||||
func newGoogleProvider(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
|
||||
provider := &GoogleProvider{
|
||||
project: "zalando-external-dns-test",
|
||||
dryRun: false,
|
||||
@ -792,3 +794,7 @@ func clearGoogleRecords(t *testing.T, provider *GoogleProvider, zone string) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func validateEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) {
|
||||
assert.True(t, testutils.SameEndpoints(endpoints, expected), "actual and expected endpoints don't match. %s:%s", endpoints, expected)
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package infoblox
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -29,12 +29,13 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// InfobloxConfig clarifies the method signature
|
||||
type InfobloxConfig struct {
|
||||
DomainFilter endpoint.DomainFilter
|
||||
ZoneIDFilter ZoneIDFilter
|
||||
ZoneIDFilter provider.ZoneIDFilter
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
@ -48,9 +49,10 @@ type InfobloxConfig struct {
|
||||
|
||||
// InfobloxProvider implements the DNS provider for Infoblox.
|
||||
type InfobloxProvider struct {
|
||||
provider.BaseProvider
|
||||
client ibclient.IBConnector
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
view string
|
||||
dryRun bool
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package infoblox
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -28,7 +28,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type mockIBConnector struct {
|
||||
@ -329,7 +331,7 @@ func createMockInfobloxObject(name, recordType, value string) ibclient.IBObject
|
||||
return nil
|
||||
}
|
||||
|
||||
func newInfobloxProvider(domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, client ibclient.IBConnector) *InfobloxProvider {
|
||||
func newInfobloxProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, client ibclient.IBConnector) *InfobloxProvider {
|
||||
return &InfobloxProvider{
|
||||
client: client,
|
||||
domainFilter: domainFilter,
|
||||
@ -354,7 +356,7 @@ func TestInfobloxRecords(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, &client)
|
||||
provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, &client)
|
||||
actual, err := provider.Records(context.Background())
|
||||
|
||||
if err != nil {
|
||||
@ -428,7 +430,7 @@ func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient
|
||||
|
||||
provider := newInfobloxProvider(
|
||||
endpoint.NewDomainFilter([]string{""}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
provider.NewZoneIDFilter([]string{""}),
|
||||
dryRun,
|
||||
client,
|
||||
)
|
||||
@ -486,7 +488,7 @@ func TestInfobloxZones(t *testing.T) {
|
||||
mockInfobloxObjects: &[]ibclient.IBObject{},
|
||||
}
|
||||
|
||||
provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, &client)
|
||||
provider := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, &client)
|
||||
zones, _ := provider.zones()
|
||||
var emptyZoneAuth *ibclient.ZoneAuth
|
||||
assert.Equal(t, provider.findZone(zones, "example.com").Fqdn, "example.com")
|
||||
@ -521,3 +523,7 @@ func TestMaxResultsRequestBuilder(t *testing.T) {
|
||||
|
||||
assert.True(t, req.URL.Query().Get("_max_results") == "")
|
||||
}
|
||||
|
||||
func validateEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) {
|
||||
assert.True(t, testutils.SameEndpoints(endpoints, expected), "actual and expected endpoints don't match. %s:%s", endpoints, expected)
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package inmemory
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -25,6 +25,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -43,6 +44,7 @@ var (
|
||||
// InMemoryProvider - dns provider only used for testing purposes
|
||||
// initialized as dns provider with no records
|
||||
type InMemoryProvider struct {
|
||||
provider.BaseProvider
|
||||
domain endpoint.DomainFilter
|
||||
client *inMemoryClient
|
||||
filter *filter
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package inmemory
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -26,10 +26,11 @@ import (
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Provider = &InMemoryProvider{}
|
||||
_ provider.Provider = &InMemoryProvider{}
|
||||
)
|
||||
|
||||
func TestInMemoryProvider(t *testing.T) {
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package linode
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -30,12 +30,13 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// LinodeDomainClient interface to ease testing
|
||||
type LinodeDomainClient interface {
|
||||
ListDomainRecords(ctx context.Context, domainID int, opts *linodego.ListOptions) ([]*linodego.DomainRecord, error)
|
||||
ListDomains(ctx context.Context, opts *linodego.ListOptions) ([]*linodego.Domain, error)
|
||||
ListDomainRecords(ctx context.Context, domainID int, opts *linodego.ListOptions) ([]linodego.DomainRecord, error)
|
||||
ListDomains(ctx context.Context, opts *linodego.ListOptions) ([]linodego.Domain, error)
|
||||
CreateDomainRecord(ctx context.Context, domainID int, domainrecord linodego.DomainRecordCreateOptions) (*linodego.DomainRecord, error)
|
||||
DeleteDomainRecord(ctx context.Context, domainID int, id int) error
|
||||
UpdateDomainRecord(ctx context.Context, domainID int, id int, domainrecord linodego.DomainRecordUpdateOptions) (*linodego.DomainRecord, error)
|
||||
@ -43,6 +44,7 @@ type LinodeDomainClient interface {
|
||||
|
||||
// LinodeProvider is an implementation of Provider for Digital Ocean's DNS.
|
||||
type LinodeProvider struct {
|
||||
provider.BaseProvider
|
||||
Client LinodeDomainClient
|
||||
domainFilter endpoint.DomainFilter
|
||||
DryRun bool
|
||||
@ -50,28 +52,28 @@ type LinodeProvider struct {
|
||||
|
||||
// LinodeChanges All API calls calculated from the plan
|
||||
type LinodeChanges struct {
|
||||
Creates []*LinodeChangeCreate
|
||||
Deletes []*LinodeChangeDelete
|
||||
Updates []*LinodeChangeUpdate
|
||||
Creates []LinodeChangeCreate
|
||||
Deletes []LinodeChangeDelete
|
||||
Updates []LinodeChangeUpdate
|
||||
}
|
||||
|
||||
// LinodeChangeCreate Linode Domain Record Creates
|
||||
type LinodeChangeCreate struct {
|
||||
Domain *linodego.Domain
|
||||
Domain linodego.Domain
|
||||
Options linodego.DomainRecordCreateOptions
|
||||
}
|
||||
|
||||
// LinodeChangeUpdate Linode Domain Record Updates
|
||||
type LinodeChangeUpdate struct {
|
||||
Domain *linodego.Domain
|
||||
DomainRecord *linodego.DomainRecord
|
||||
Domain linodego.Domain
|
||||
DomainRecord linodego.DomainRecord
|
||||
Options linodego.DomainRecordUpdateOptions
|
||||
}
|
||||
|
||||
// LinodeChangeDelete Linode Domain Record Deletes
|
||||
type LinodeChangeDelete struct {
|
||||
Domain *linodego.Domain
|
||||
DomainRecord *linodego.DomainRecord
|
||||
Domain linodego.Domain
|
||||
DomainRecord linodego.DomainRecord
|
||||
}
|
||||
|
||||
// NewLinodeProvider initializes a new Linode DNS based Provider.
|
||||
@ -101,7 +103,7 @@ func NewLinodeProvider(domainFilter endpoint.DomainFilter, dryRun bool, appVersi
|
||||
}
|
||||
|
||||
// Zones returns the list of hosted zones.
|
||||
func (p *LinodeProvider) Zones(ctx context.Context) ([]*linodego.Domain, error) {
|
||||
func (p *LinodeProvider) Zones(ctx context.Context) ([]linodego.Domain, error) {
|
||||
zones, err := p.fetchZones(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -126,7 +128,7 @@ func (p *LinodeProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, err
|
||||
}
|
||||
|
||||
for _, r := range records {
|
||||
if supportedRecordType(string(r.Type)) {
|
||||
if provider.SupportedRecordType(string(r.Type)) {
|
||||
name := fmt.Sprintf("%s.%s", r.Name, zone.Domain)
|
||||
|
||||
// root name is identified by the empty string and should be
|
||||
@ -143,7 +145,7 @@ func (p *LinodeProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, err
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (p *LinodeProvider) fetchRecords(ctx context.Context, domainID int) ([]*linodego.DomainRecord, error) {
|
||||
func (p *LinodeProvider) fetchRecords(ctx context.Context, domainID int) ([]linodego.DomainRecord, error) {
|
||||
records, err := p.Client.ListDomainRecords(ctx, domainID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -152,8 +154,8 @@ func (p *LinodeProvider) fetchRecords(ctx context.Context, domainID int) ([]*lin
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (p *LinodeProvider) fetchZones(ctx context.Context) ([]*linodego.Domain, error) {
|
||||
var zones []*linodego.Domain
|
||||
func (p *LinodeProvider) fetchZones(ctx context.Context) ([]linodego.Domain, error) {
|
||||
var zones []linodego.Domain
|
||||
|
||||
allZones, err := p.Client.ListDomains(ctx, linodego.NewListOptions(0, ""))
|
||||
|
||||
@ -257,7 +259,7 @@ func getPriority() *int {
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
recordsByZoneID := make(map[string][]*linodego.DomainRecord)
|
||||
recordsByZoneID := make(map[string][]linodego.DomainRecord)
|
||||
|
||||
zones, err := p.fetchZones(ctx)
|
||||
|
||||
@ -265,9 +267,9 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
return err
|
||||
}
|
||||
|
||||
zonesByID := make(map[string]*linodego.Domain)
|
||||
zonesByID := make(map[string]linodego.Domain)
|
||||
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
|
||||
for _, z := range zones {
|
||||
zoneNameIDMapper.Add(strconv.Itoa(z.ID), z.Domain)
|
||||
@ -289,9 +291,9 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
updatesByZone := endpointsByZone(zoneNameIDMapper, changes.UpdateNew)
|
||||
deletesByZone := endpointsByZone(zoneNameIDMapper, changes.Delete)
|
||||
|
||||
var linodeCreates []*LinodeChangeCreate
|
||||
var linodeUpdates []*LinodeChangeUpdate
|
||||
var linodeDeletes []*LinodeChangeDelete
|
||||
var linodeCreates []LinodeChangeCreate
|
||||
var linodeUpdates []LinodeChangeUpdate
|
||||
var linodeDeletes []LinodeChangeDelete
|
||||
|
||||
// Generate Creates
|
||||
for zoneID, creates := range createsByZone {
|
||||
@ -326,7 +328,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
}
|
||||
|
||||
for _, target := range ep.Targets {
|
||||
linodeCreates = append(linodeCreates, &LinodeChangeCreate{
|
||||
linodeCreates = append(linodeCreates, LinodeChangeCreate{
|
||||
Domain: zone,
|
||||
Options: linodego.DomainRecordCreateOptions{
|
||||
Target: target,
|
||||
@ -374,7 +376,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
return err
|
||||
}
|
||||
|
||||
matchedRecordsByTarget := make(map[string]*linodego.DomainRecord)
|
||||
matchedRecordsByTarget := make(map[string]linodego.DomainRecord)
|
||||
|
||||
for _, record := range matchedRecords {
|
||||
matchedRecordsByTarget[record.Target] = record
|
||||
@ -390,7 +392,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
"target": target,
|
||||
}).Warn("Updating Existing Target")
|
||||
|
||||
linodeUpdates = append(linodeUpdates, &LinodeChangeUpdate{
|
||||
linodeUpdates = append(linodeUpdates, LinodeChangeUpdate{
|
||||
Domain: zone,
|
||||
DomainRecord: record,
|
||||
Options: linodego.DomainRecordUpdateOptions{
|
||||
@ -415,7 +417,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
"target": target,
|
||||
}).Warn("Creating New Target")
|
||||
|
||||
linodeCreates = append(linodeCreates, &LinodeChangeCreate{
|
||||
linodeCreates = append(linodeCreates, LinodeChangeCreate{
|
||||
Domain: zone,
|
||||
Options: linodego.DomainRecordCreateOptions{
|
||||
Target: target,
|
||||
@ -440,12 +442,11 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
"target": record.Target,
|
||||
}).Warn("Deleting Target")
|
||||
|
||||
linodeDeletes = append(linodeDeletes, &LinodeChangeDelete{
|
||||
linodeDeletes = append(linodeDeletes, LinodeChangeDelete{
|
||||
Domain: zone,
|
||||
DomainRecord: record,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -476,7 +477,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
}
|
||||
|
||||
for _, record := range matchedRecords {
|
||||
linodeDeletes = append(linodeDeletes, &LinodeChangeDelete{
|
||||
linodeDeletes = append(linodeDeletes, LinodeChangeDelete{
|
||||
Domain: zone,
|
||||
DomainRecord: record,
|
||||
})
|
||||
@ -491,8 +492,8 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
})
|
||||
}
|
||||
|
||||
func endpointsByZone(zoneNameIDMapper zoneIDName, endpoints []*endpoint.Endpoint) map[string][]*endpoint.Endpoint {
|
||||
endpointsByZone := make(map[string][]*endpoint.Endpoint)
|
||||
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)
|
||||
@ -500,7 +501,7 @@ func endpointsByZone(zoneNameIDMapper zoneIDName, endpoints []*endpoint.Endpoint
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", ep.DNSName)
|
||||
continue
|
||||
}
|
||||
endpointsByZone[zoneID] = append(endpointsByZone[zoneID], ep)
|
||||
endpointsByZone[zoneID] = append(endpointsByZone[zoneID], *ep)
|
||||
}
|
||||
|
||||
return endpointsByZone
|
||||
@ -523,7 +524,7 @@ func convertRecordType(recordType string) (linodego.DomainRecordType, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func getStrippedRecordName(zone *linodego.Domain, ep *endpoint.Endpoint) string {
|
||||
func getStrippedRecordName(zone linodego.Domain, ep endpoint.Endpoint) string {
|
||||
// Handle root
|
||||
if ep.DNSName == zone.Domain {
|
||||
return ""
|
||||
@ -532,8 +533,8 @@ func getStrippedRecordName(zone *linodego.Domain, ep *endpoint.Endpoint) string
|
||||
return strings.TrimSuffix(ep.DNSName, "."+zone.Domain)
|
||||
}
|
||||
|
||||
func getRecordID(records []*linodego.DomainRecord, zone *linodego.Domain, ep *endpoint.Endpoint) []*linodego.DomainRecord {
|
||||
var matchedRecords []*linodego.DomainRecord
|
||||
func getRecordID(records []linodego.DomainRecord, zone linodego.Domain, ep endpoint.Endpoint) []linodego.DomainRecord {
|
||||
var matchedRecords []linodego.DomainRecord
|
||||
|
||||
for _, record := range records {
|
||||
if record.Name == getStrippedRecordName(zone, ep) && string(record.Type) == ep.RecordType {
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package linode
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -34,14 +34,14 @@ type MockDomainClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockDomainClient) ListDomainRecords(ctx context.Context, domainID int, opts *linodego.ListOptions) ([]*linodego.DomainRecord, error) {
|
||||
func (m *MockDomainClient) ListDomainRecords(ctx context.Context, domainID int, opts *linodego.ListOptions) ([]linodego.DomainRecord, error) {
|
||||
args := m.Called(ctx, domainID, opts)
|
||||
return args.Get(0).([]*linodego.DomainRecord), args.Error(1)
|
||||
return args.Get(0).([]linodego.DomainRecord), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockDomainClient) ListDomains(ctx context.Context, opts *linodego.ListOptions) ([]*linodego.Domain, error) {
|
||||
func (m *MockDomainClient) ListDomains(ctx context.Context, opts *linodego.ListOptions) ([]linodego.Domain, error) {
|
||||
args := m.Called(ctx, opts)
|
||||
return args.Get(0).([]*linodego.Domain), args.Error(1)
|
||||
return args.Get(0).([]linodego.Domain), args.Error(1)
|
||||
}
|
||||
func (m *MockDomainClient) CreateDomainRecord(ctx context.Context, domainID int, opts linodego.DomainRecordCreateOptions) (*linodego.DomainRecord, error) {
|
||||
args := m.Called(ctx, domainID, opts)
|
||||
@ -56,16 +56,16 @@ func (m *MockDomainClient) UpdateDomainRecord(ctx context.Context, domainID int,
|
||||
return args.Get(0).(*linodego.DomainRecord), args.Error(1)
|
||||
}
|
||||
|
||||
func createZones() []*linodego.Domain {
|
||||
return []*linodego.Domain{
|
||||
func createZones() []linodego.Domain {
|
||||
return []linodego.Domain{
|
||||
{ID: 1, Domain: "foo.com"},
|
||||
{ID: 2, Domain: "bar.io"},
|
||||
{ID: 3, Domain: "baz.com"},
|
||||
}
|
||||
}
|
||||
|
||||
func createFooRecords() []*linodego.DomainRecord {
|
||||
return []*linodego.DomainRecord{{
|
||||
func createFooRecords() []linodego.DomainRecord {
|
||||
return []linodego.DomainRecord{{
|
||||
ID: 11,
|
||||
Type: linodego.RecordTypeA,
|
||||
Name: "",
|
||||
@ -83,12 +83,12 @@ func createFooRecords() []*linodego.DomainRecord {
|
||||
}}
|
||||
}
|
||||
|
||||
func createBarRecords() []*linodego.DomainRecord {
|
||||
return []*linodego.DomainRecord{}
|
||||
func createBarRecords() []linodego.DomainRecord {
|
||||
return []linodego.DomainRecord{}
|
||||
}
|
||||
|
||||
func createBazRecords() []*linodego.DomainRecord {
|
||||
return []*linodego.DomainRecord{{
|
||||
func createBazRecords() []linodego.DomainRecord {
|
||||
return []linodego.DomainRecord{{
|
||||
ID: 31,
|
||||
Type: linodego.RecordTypeA,
|
||||
Name: "",
|
||||
@ -147,15 +147,15 @@ func TestNewLinodeProvider(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLinodeStripRecordName(t *testing.T) {
|
||||
assert.Equal(t, "api", getStrippedRecordName(&linodego.Domain{
|
||||
assert.Equal(t, "api", getStrippedRecordName(linodego.Domain{
|
||||
Domain: "example.com",
|
||||
}, &endpoint.Endpoint{
|
||||
}, endpoint.Endpoint{
|
||||
DNSName: "api.example.com",
|
||||
}))
|
||||
|
||||
assert.Equal(t, "", getStrippedRecordName(&linodego.Domain{
|
||||
assert.Equal(t, "", getStrippedRecordName(linodego.Domain{
|
||||
Domain: "example.com",
|
||||
}, &endpoint.Endpoint{
|
||||
}, endpoint.Endpoint{
|
||||
DNSName: "example.com",
|
||||
}))
|
||||
}
|
||||
@ -198,7 +198,7 @@ func TestLinodeFetchZonesWithFilter(t *testing.T) {
|
||||
mock.Anything,
|
||||
).Return(createZones(), nil).Once()
|
||||
|
||||
expected := []*linodego.Domain{
|
||||
expected := []linodego.Domain{
|
||||
{ID: 1, Domain: "foo.com"},
|
||||
{ID: 3, Domain: "baz.com"},
|
||||
}
|
||||
@ -210,15 +210,15 @@ func TestLinodeFetchZonesWithFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLinodeGetStrippedRecordName(t *testing.T) {
|
||||
assert.Equal(t, "", getStrippedRecordName(&linodego.Domain{
|
||||
assert.Equal(t, "", getStrippedRecordName(linodego.Domain{
|
||||
Domain: "foo.com",
|
||||
}, &endpoint.Endpoint{
|
||||
}, endpoint.Endpoint{
|
||||
DNSName: "foo.com",
|
||||
}))
|
||||
|
||||
assert.Equal(t, "api", getStrippedRecordName(&linodego.Domain{
|
||||
assert.Equal(t, "api", getStrippedRecordName(linodego.Domain{
|
||||
Domain: "foo.com",
|
||||
}, &endpoint.Endpoint{
|
||||
}, endpoint.Endpoint{
|
||||
DNSName: "api.foo.com",
|
||||
}))
|
||||
}
|
||||
@ -398,14 +398,14 @@ func TestLinodeApplyChangesTargetAdded(t *testing.T) {
|
||||
"ListDomains",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return([]*linodego.Domain{{Domain: "example.com", ID: 1}}, nil).Once()
|
||||
).Return([]linodego.Domain{{Domain: "example.com", ID: 1}}, nil).Once()
|
||||
|
||||
mockDomainClient.On(
|
||||
"ListDomainRecords",
|
||||
mock.Anything,
|
||||
1,
|
||||
mock.Anything,
|
||||
).Return([]*linodego.DomainRecord{{ID: 11, Name: "", Type: "A", Target: "targetA"}}, nil).Once()
|
||||
).Return([]linodego.DomainRecord{{ID: 11, Name: "", Type: "A", Target: "targetA"}}, nil).Once()
|
||||
|
||||
// Apply Actions
|
||||
mockDomainClient.On(
|
||||
@ -457,14 +457,14 @@ func TestLinodeApplyChangesTargetRemoved(t *testing.T) {
|
||||
"ListDomains",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return([]*linodego.Domain{{Domain: "example.com", ID: 1}}, nil).Once()
|
||||
).Return([]linodego.Domain{{Domain: "example.com", ID: 1}}, nil).Once()
|
||||
|
||||
mockDomainClient.On(
|
||||
"ListDomainRecords",
|
||||
mock.Anything,
|
||||
1,
|
||||
mock.Anything,
|
||||
).Return([]*linodego.DomainRecord{{ID: 11, Name: "", Type: "A", Target: "targetA"}, {ID: 12, Type: "A", Name: "", Target: "targetB"}}, nil).Once()
|
||||
).Return([]linodego.DomainRecord{{ID: 11, Name: "", Type: "A", Target: "targetA"}, {ID: 12, Type: "A", Name: "", Target: "targetB"}}, nil).Once()
|
||||
|
||||
// Apply Actions
|
||||
mockDomainClient.On(
|
||||
@ -513,14 +513,14 @@ func TestLinodeApplyChangesNoChanges(t *testing.T) {
|
||||
"ListDomains",
|
||||
mock.Anything,
|
||||
mock.Anything,
|
||||
).Return([]*linodego.Domain{{Domain: "example.com", ID: 1}}, nil).Once()
|
||||
).Return([]linodego.Domain{{Domain: "example.com", ID: 1}}, nil).Once()
|
||||
|
||||
mockDomainClient.On(
|
||||
"ListDomainRecords",
|
||||
mock.Anything,
|
||||
1,
|
||||
mock.Anything,
|
||||
).Return([]*linodego.DomainRecord{{ID: 11, Name: "", Type: "A", Target: "targetA"}}, nil).Once()
|
||||
).Return([]linodego.DomainRecord{{ID: 11, Name: "", Type: "A", Target: "targetA"}}, nil).Once()
|
||||
|
||||
err := provider.ApplyChanges(context.Background(), &plan.Changes{})
|
||||
require.NoError(t, err)
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package ns1
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -30,6 +30,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -85,7 +86,7 @@ func (n NS1DomainService) ListZones() ([]*dns.Zone, *http.Response, error) {
|
||||
// NS1Config passes cli args to the NS1Provider
|
||||
type NS1Config struct {
|
||||
DomainFilter endpoint.DomainFilter
|
||||
ZoneIDFilter ZoneIDFilter
|
||||
ZoneIDFilter provider.ZoneIDFilter
|
||||
NS1Endpoint string
|
||||
NS1IgnoreSSL bool
|
||||
DryRun bool
|
||||
@ -93,9 +94,10 @@ type NS1Config struct {
|
||||
|
||||
// NS1Provider is the NS1 provider
|
||||
type NS1Provider struct {
|
||||
provider.BaseProvider
|
||||
client NS1DomainClient
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
dryRun bool
|
||||
}
|
||||
|
||||
@ -150,7 +152,6 @@ func (p *NS1Provider) Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
for _, zone := range zones {
|
||||
|
||||
// TODO handle Header Codes
|
||||
zoneData, _, err := p.client.GetZone(zone.String())
|
||||
if err != nil {
|
||||
@ -158,7 +159,7 @@ func (p *NS1Provider) Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||
}
|
||||
|
||||
for _, record := range zoneData.Records {
|
||||
if supportedRecordType(record.Type) {
|
||||
if provider.SupportedRecordType(record.Type) {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(
|
||||
record.Domain,
|
||||
record.Type,
|
||||
@ -299,7 +300,7 @@ func newNS1Changes(action string, endpoints []*endpoint.Endpoint) []*ns1Change {
|
||||
// ns1ChangesByZone separates a multi-zone change into a single change per zone.
|
||||
func ns1ChangesByZone(zones []*dns.Zone, changeSets []*ns1Change) map[string][]*ns1Change {
|
||||
changes := make(map[string][]*ns1Change)
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, z := range zones {
|
||||
zoneNameIDMapper.Add(z.Zone, z.Zone)
|
||||
changes[z.Zone] = []*ns1Change{}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package ns1
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -31,6 +31,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type MockNS1DomainClient struct {
|
||||
@ -130,7 +131,7 @@ func TestNS1Records(t *testing.T) {
|
||||
provider := &NS1Provider{
|
||||
client: &MockNS1DomainClient{},
|
||||
domainFilter: endpoint.NewDomainFilter([]string{"foo.com."}),
|
||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
||||
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
@ -151,7 +152,7 @@ func TestNewNS1Provider(t *testing.T) {
|
||||
_ = os.Setenv("NS1_APIKEY", "xxxxxxxxxxxxxxxxx")
|
||||
testNS1Config := NS1Config{
|
||||
DomainFilter: endpoint.NewDomainFilter([]string{"foo.com."}),
|
||||
ZoneIDFilter: NewZoneIDFilter([]string{""}),
|
||||
ZoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||
DryRun: false,
|
||||
}
|
||||
_, err := NewNS1Provider(testNS1Config)
|
||||
@ -166,7 +167,7 @@ func TestNS1Zones(t *testing.T) {
|
||||
provider := &NS1Provider{
|
||||
client: &MockNS1DomainClient{},
|
||||
domainFilter: endpoint.NewDomainFilter([]string{"foo.com."}),
|
||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
||||
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||
}
|
||||
|
||||
zones, err := provider.zonesFiltered()
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -29,6 +29,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const ociRecordTTL = 300
|
||||
@ -52,11 +53,12 @@ type OCIConfig struct {
|
||||
// OCIProvider is an implementation of Provider for Oracle Cloud Infrastructure
|
||||
// (OCI) DNS.
|
||||
type OCIProvider struct {
|
||||
provider.BaseProvider
|
||||
client ociDNSClient
|
||||
cfg OCIConfig
|
||||
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
dryRun bool
|
||||
}
|
||||
|
||||
@ -82,8 +84,8 @@ func LoadOCIConfig(path string) (*OCIConfig, error) {
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// NewOCIProvider initialises a new OCI DNS based Provider.
|
||||
func NewOCIProvider(cfg OCIConfig, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) (*OCIProvider, error) {
|
||||
// NewOCIProvider initializes a new OCI DNS based Provider.
|
||||
func NewOCIProvider(cfg OCIConfig, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool) (*OCIProvider, error) {
|
||||
var client ociDNSClient
|
||||
client, err := dns.NewDnsClientWithConfigurationProvider(common.NewRawConfigurationProvider(
|
||||
cfg.Auth.TenancyID,
|
||||
@ -94,7 +96,7 @@ func NewOCIProvider(cfg OCIConfig, domainFilter endpoint.DomainFilter, zoneIDFil
|
||||
&cfg.Auth.Passphrase,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "initialising OCI DNS API client")
|
||||
return nil, errors.Wrap(err, "initializing OCI DNS API client")
|
||||
}
|
||||
|
||||
return &OCIProvider{
|
||||
@ -177,7 +179,7 @@ func (p *OCIProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||
}
|
||||
|
||||
for _, record := range resp.Items {
|
||||
if !supportedRecordType(*record.Rtype) {
|
||||
if !provider.SupportedRecordType(*record.Rtype) {
|
||||
continue
|
||||
}
|
||||
endpoints = append(endpoints,
|
||||
@ -252,7 +254,7 @@ func newRecordOperation(ep *endpoint.Endpoint, opType dns.RecordOperationOperati
|
||||
targets := make([]string, len(ep.Targets))
|
||||
copy(targets, []string(ep.Targets))
|
||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||
targets[0] = ensureTrailingDot(targets[0])
|
||||
targets[0] = provider.EnsureTrailingDot(targets[0])
|
||||
}
|
||||
rdata := strings.Join(targets, " ")
|
||||
|
||||
@ -274,7 +276,7 @@ func newRecordOperation(ep *endpoint.Endpoint, opType dns.RecordOperationOperati
|
||||
func operationsByZone(zones map[string]dns.ZoneSummary, ops []dns.RecordOperation) map[string][]dns.RecordOperation {
|
||||
changes := make(map[string][]dns.RecordOperation)
|
||||
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, z := range zones {
|
||||
zoneNameIDMapper.Add(*z.Id, *z.Name)
|
||||
changes[*z.Id] = []dns.RecordOperation{}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package oci
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -28,6 +28,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type mockOCIDNSClient struct{}
|
||||
@ -100,7 +101,7 @@ func (c *mockOCIDNSClient) PatchZoneRecords(ctx context.Context, request dns.Pat
|
||||
}
|
||||
|
||||
// newOCIProvider creates an OCI provider with API calls mocked out.
|
||||
func newOCIProvider(client ociDNSClient, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) *OCIProvider {
|
||||
func newOCIProvider(client ociDNSClient, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool) *OCIProvider {
|
||||
return &OCIProvider{
|
||||
client: client,
|
||||
cfg: OCIConfig{
|
||||
@ -176,7 +177,7 @@ hKRtDhmSdWBo3tJK12RrAe4t7CUe8gMgTvU7ExlcA3xQkseFPx9K
|
||||
`,
|
||||
},
|
||||
},
|
||||
err: errors.New("initialising OCI DNS API client: can not create client, bad configuration: PEM data was not found in buffer"),
|
||||
err: errors.New("initializing OCI DNS API client: can not create client, bad configuration: PEM data was not found in buffer"),
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
@ -184,7 +185,7 @@ hKRtDhmSdWBo3tJK12RrAe4t7CUe8gMgTvU7ExlcA3xQkseFPx9K
|
||||
_, err := NewOCIProvider(
|
||||
tc.config,
|
||||
endpoint.NewDomainFilter([]string{"com"}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
provider.NewZoneIDFilter([]string{""}),
|
||||
false,
|
||||
)
|
||||
if err == nil {
|
||||
@ -200,13 +201,13 @@ func TestOCIZones(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
expected map[string]dns.ZoneSummary
|
||||
}{
|
||||
{
|
||||
name: "DomainFilter_com",
|
||||
domainFilter: endpoint.NewDomainFilter([]string{"com"}),
|
||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
||||
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||
expected: map[string]dns.ZoneSummary{
|
||||
"foo.com": {
|
||||
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
|
||||
@ -220,7 +221,7 @@ func TestOCIZones(t *testing.T) {
|
||||
}, {
|
||||
name: "DomainFilter_foo.com",
|
||||
domainFilter: endpoint.NewDomainFilter([]string{"foo.com"}),
|
||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
||||
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||
expected: map[string]dns.ZoneSummary{
|
||||
"foo.com": {
|
||||
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
|
||||
@ -230,7 +231,7 @@ func TestOCIZones(t *testing.T) {
|
||||
}, {
|
||||
name: "ZoneIDFilter_ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959",
|
||||
domainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||
zoneIDFilter: NewZoneIDFilter([]string{"ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"}),
|
||||
zoneIDFilter: provider.NewZoneIDFilter([]string{"ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"}),
|
||||
expected: map[string]dns.ZoneSummary{
|
||||
"foo.com": {
|
||||
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
|
||||
@ -253,13 +254,13 @@ func TestOCIRecords(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter ZoneIDFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
expected []*endpoint.Endpoint
|
||||
}{
|
||||
{
|
||||
name: "unfiltered",
|
||||
domainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
||||
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||
expected: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
|
||||
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeTXT, endpoint.TTL(ociRecordTTL), "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/my-svc"),
|
||||
@ -269,7 +270,7 @@ func TestOCIRecords(t *testing.T) {
|
||||
}, {
|
||||
name: "DomainFilter_foo.com",
|
||||
domainFilter: endpoint.NewDomainFilter([]string{"foo.com"}),
|
||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
||||
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||
expected: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
|
||||
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeTXT, endpoint.TTL(ociRecordTTL), "heritage=external-dns,external-dns/owner=default,external-dns/resource=service/default/my-svc"),
|
||||
@ -278,7 +279,7 @@ func TestOCIRecords(t *testing.T) {
|
||||
}, {
|
||||
name: "ZoneIDFilter_ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404",
|
||||
domainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||
zoneIDFilter: NewZoneIDFilter([]string{"ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404"}),
|
||||
zoneIDFilter: provider.NewZoneIDFilter([]string{"ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404"}),
|
||||
expected: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("foo.bar.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
|
||||
},
|
||||
@ -826,7 +827,7 @@ func TestOCIApplyChanges(t *testing.T) {
|
||||
provider := newOCIProvider(
|
||||
client,
|
||||
endpoint.NewDomainFilter([]string{""}),
|
||||
NewZoneIDFilter([]string{""}),
|
||||
provider.NewZoneIDFilter([]string{""}),
|
||||
tc.dryRun,
|
||||
)
|
||||
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package ovh
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -28,6 +28,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -45,6 +46,8 @@ var (
|
||||
|
||||
// OVHProvider is an implementation of Provider for OVH DNS.
|
||||
type OVHProvider struct {
|
||||
provider.BaseProvider
|
||||
|
||||
client ovhClient
|
||||
|
||||
domainFilter endpoint.DomainFilter
|
||||
@ -94,7 +97,6 @@ func NewOVHProvider(ctx context.Context, domainFilter endpoint.DomainFilter, end
|
||||
|
||||
// Records returns the list of records in all relevant zones.
|
||||
func (p *OVHProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
|
||||
_, records, err := p.zonesRecords(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -237,7 +239,7 @@ func (p *OVHProvider) record(zone *string, id uint64, records chan<- ovhRecord)
|
||||
if err := p.client.Get(fmt.Sprintf("/domain/zone/%s/record/%d", *zone, id), &record); err != nil {
|
||||
return err
|
||||
}
|
||||
if supportedRecordType(record.FieldType) {
|
||||
if provider.SupportedRecordType(record.FieldType) {
|
||||
log.Debugf("OVH: Record %d for %s is %+v", id, *zone, record)
|
||||
records <- record
|
||||
}
|
||||
@ -278,7 +280,7 @@ func ovhGroupByNameAndType(records []ovhRecord) []*endpoint.Endpoint {
|
||||
}
|
||||
|
||||
func newOvhChange(action int, endpoints []*endpoint.Endpoint, zones []string, records []ovhRecord) []ovhChange {
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
ovhChanges := make([]ovhChange, 0, countTargets(endpoints))
|
||||
for _, zone := range zones {
|
||||
zoneNameIDMapper.Add(zone, zone)
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package ovh
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -142,9 +142,9 @@ func TestOvhRecords(t *testing.T) {
|
||||
sort.Strings(endoint.Targets)
|
||||
}
|
||||
assert.ElementsMatch(endpoints, []*endpoint.Endpoint{
|
||||
&endpoint.Endpoint{DNSName: "example.org", RecordType: "A", RecordTTL: 10, Labels: endpoint.NewLabels(), Targets: []string{"203.0.113.42"}},
|
||||
&endpoint.Endpoint{DNSName: "www.example.org", RecordType: "CNAME", RecordTTL: 10, Labels: endpoint.NewLabels(), Targets: []string{"example.org"}},
|
||||
&endpoint.Endpoint{DNSName: "ovh.example.net", RecordType: "A", RecordTTL: 10, Labels: endpoint.NewLabels(), Targets: []string{"203.0.113.42", "203.0.113.43"}},
|
||||
{DNSName: "example.org", RecordType: "A", RecordTTL: 10, Labels: endpoint.NewLabels(), Targets: []string{"203.0.113.42"}},
|
||||
{DNSName: "www.example.org", RecordType: "CNAME", RecordTTL: 10, Labels: endpoint.NewLabels(), Targets: []string{"example.org"}},
|
||||
{DNSName: "ovh.example.net", RecordType: "A", RecordTTL: 10, Labels: endpoint.NewLabels(), Targets: []string{"203.0.113.42", "203.0.113.43"}},
|
||||
})
|
||||
client.AssertExpectations(t)
|
||||
|
||||
@ -283,10 +283,10 @@ func TestOvhCountTargets(t *testing.T) {
|
||||
endpoints [][]*endpoint.Endpoint
|
||||
count int
|
||||
}{
|
||||
{[][]*endpoint.Endpoint{[]*endpoint.Endpoint{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}}}, 1},
|
||||
{[][]*endpoint.Endpoint{[]*endpoint.Endpoint{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}, {DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}}}, 2},
|
||||
{[][]*endpoint.Endpoint{[]*endpoint.Endpoint{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target", "target"}}}}, 3},
|
||||
{[][]*endpoint.Endpoint{[]*endpoint.Endpoint{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target"}}}, []*endpoint.Endpoint{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target"}}}}, 4},
|
||||
{[][]*endpoint.Endpoint{{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}}}, 1},
|
||||
{[][]*endpoint.Endpoint{{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}, {DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}}}, 2},
|
||||
{[][]*endpoint.Endpoint{{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target", "target"}}}}, 3},
|
||||
{[][]*endpoint.Endpoint{{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target"}}}, {{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target"}}}}, 4},
|
||||
}
|
||||
for _, test := range cases {
|
||||
count := countTargets(test.endpoints...)
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package pdns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -35,6 +35,7 @@ import (
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/pkg/tlsutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type pdnsChangeType string
|
||||
@ -116,7 +117,6 @@ func (tlsConfig *TLSConfig) setHTTPClient(pdnsClientConfig *pgo.Configuration) e
|
||||
|
||||
// Function for debug printing
|
||||
func stringifyHTTPResponseBody(r *http.Response) (body string) {
|
||||
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
@ -125,7 +125,6 @@ func stringifyHTTPResponseBody(r *http.Response) (body string) {
|
||||
buf.ReadFrom(r.Body)
|
||||
body = buf.String()
|
||||
return body
|
||||
|
||||
}
|
||||
|
||||
// PDNSAPIProvider : Interface used and extended by the PDNSAPIClient struct as
|
||||
@ -161,7 +160,6 @@ func (c *PDNSAPIClient) ListZones() (zones []pgo.Zone, resp *http.Response, err
|
||||
|
||||
log.Errorf("Unable to fetch zones. %v", err)
|
||||
return zones, resp, err
|
||||
|
||||
}
|
||||
|
||||
// PartitionZones : Method returns a slice of zones that adhere to the domain filter and a slice of ones that does not adhere to the filter
|
||||
@ -190,14 +188,12 @@ func (c *PDNSAPIClient) ListZone(zoneID string) (zone pgo.Zone, resp *http.Respo
|
||||
log.Debugf("Retrying ListZone() ... %d", i)
|
||||
time.Sleep(retryAfterTime * (1 << uint(i)))
|
||||
continue
|
||||
|
||||
}
|
||||
return zone, resp, err
|
||||
}
|
||||
|
||||
log.Errorf("Unable to list zone. %v", err)
|
||||
return zone, resp, err
|
||||
|
||||
}
|
||||
|
||||
// PatchZone : Method used to update the contents of a particular zone from PowerDNS
|
||||
@ -210,7 +206,6 @@ func (c *PDNSAPIClient) PatchZone(zoneID string, zoneStruct pgo.Zone) (resp *htt
|
||||
log.Debugf("Retrying PatchZone() ... %d", i)
|
||||
time.Sleep(retryAfterTime * (1 << uint(i)))
|
||||
continue
|
||||
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
@ -221,16 +216,16 @@ func (c *PDNSAPIClient) PatchZone(zoneID string, zoneStruct pgo.Zone) (resp *htt
|
||||
|
||||
// PDNSProvider is an implementation of the Provider interface for PowerDNS
|
||||
type PDNSProvider struct {
|
||||
provider.BaseProvider
|
||||
client PDNSAPIProvider
|
||||
}
|
||||
|
||||
// NewPDNSProvider initializes a new PowerDNS based Provider.
|
||||
func NewPDNSProvider(ctx context.Context, config PDNSConfig) (*PDNSProvider, error) {
|
||||
|
||||
// Do some input validation
|
||||
|
||||
if config.APIKey == "" {
|
||||
return nil, errors.New("Missing API Key for PDNS. Specify using --pdns-api-key=")
|
||||
return nil, errors.New("missing API Key for PDNS. Specify using --pdns-api-key=")
|
||||
}
|
||||
|
||||
// We do not support dry running, exit safely instead of surprising the user
|
||||
@ -257,7 +252,6 @@ func NewPDNSProvider(ctx context.Context, config PDNSConfig) (*PDNSProvider, err
|
||||
domainFilter: config.DomainFilter,
|
||||
},
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
@ -270,13 +264,11 @@ func (p *PDNSProvider) convertRRSetToEndpoints(rr pgo.RrSet) (endpoints []*endpo
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(rr.Name, rr.Type_, endpoint.TTL(rr.Ttl), record.Content))
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// ConvertEndpointsToZones marshals endpoints into pdns compatible Zone structs
|
||||
func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changetype pdnsChangeType) (zonelist []pgo.Zone, _ error) {
|
||||
|
||||
zonelist = []pgo.Zone{}
|
||||
endpoints := make([]*endpoint.Endpoint, len(eps))
|
||||
copy(endpoints, eps)
|
||||
@ -310,15 +302,15 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
|
||||
zone.Rrsets = []pgo.RrSet{}
|
||||
for i := 0; i < len(endpoints); {
|
||||
ep := endpoints[i]
|
||||
dnsname := ensureTrailingDot(ep.DNSName)
|
||||
dnsname := provider.EnsureTrailingDot(ep.DNSName)
|
||||
if dnsname == zone.Name || strings.HasSuffix(dnsname, "."+zone.Name) {
|
||||
// The assumption here is that there will only ever be one target
|
||||
// per (ep.DNSName, ep.RecordType) tuple, which holds true for
|
||||
// external-dns v5.0.0-alpha onwards
|
||||
records := []pgo.Record{}
|
||||
for _, t := range ep.Targets {
|
||||
if "CNAME" == ep.RecordType {
|
||||
t = ensureTrailingDot(t)
|
||||
if ep.RecordType == "CNAME" {
|
||||
t = provider.EnsureTrailingDot(t)
|
||||
}
|
||||
|
||||
records = append(records, pgo.Record{Content: t})
|
||||
@ -333,7 +325,7 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
|
||||
// DELETEs explicitly forbid a TTL, therefore only PATCHes need the TTL
|
||||
if changetype == PdnsReplace {
|
||||
if int64(ep.RecordTTL) > int64(math.MaxInt32) {
|
||||
return nil, errors.New("Value of record TTL overflows, limited to int32")
|
||||
return nil, errors.New("value of record TTL overflows, limited to int32")
|
||||
}
|
||||
if ep.RecordTTL == 0 {
|
||||
// No TTL was specified for the record, we use the default
|
||||
@ -351,13 +343,10 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
|
||||
// If we didn't pop anything, we move to the next item in the list
|
||||
i++
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if len(zone.Rrsets) > 0 {
|
||||
zonelist = append(zonelist, zone)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// residualZones is unsorted by name length like its counterpart
|
||||
@ -365,7 +354,7 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
|
||||
for _, zone := range residualZones {
|
||||
for i := 0; i < len(endpoints); {
|
||||
ep := endpoints[i]
|
||||
dnsname := ensureTrailingDot(ep.DNSName)
|
||||
dnsname := provider.EnsureTrailingDot(ep.DNSName)
|
||||
if dnsname == zone.Name || strings.HasSuffix(dnsname, "."+zone.Name) {
|
||||
// "pop" endpoint if it's matched to a residual zone... essentially a no-op
|
||||
log.Debugf("Ignoring Endpoint because it was matched to a zone that was not specified within Domain Filter(s): %s", dnsname)
|
||||
@ -375,7 +364,6 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we still have some endpoints left, it means we couldn't find a matching zone (filtered or residual) for them
|
||||
// We warn instead of hard fail here because we don't want a misconfig to cause everything to go down
|
||||
if len(endpoints) > 0 {
|
||||
@ -400,20 +388,17 @@ func (p *PDNSProvider) mutateRecords(endpoints []*endpoint.Endpoint, changetype
|
||||
} else {
|
||||
log.Debugf("Struct for PatchZone:\n%s", string(jso))
|
||||
}
|
||||
|
||||
resp, err := p.client.PatchZone(zone.Id, zone)
|
||||
if err != nil {
|
||||
log.Debugf("PDNS API response: %s", stringifyHTTPResponseBody(resp))
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Records returns all DNS records controlled by the configured PDNS server (for all zones)
|
||||
func (p *PDNSProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, _ error) {
|
||||
|
||||
zones, _, err := p.client.ListZones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -443,7 +428,6 @@ func (p *PDNSProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpo
|
||||
// ApplyChanges takes a list of changes (endpoints) and updates the PDNS server
|
||||
// by sending the correct HTTP PATCH requests to a matching zone
|
||||
func (p *PDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
// Create
|
||||
@ -491,7 +475,6 @@ func (p *PDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Changes pushed out to PowerDNS in %s\n", time.Since(startTime))
|
||||
return nil
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package pdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -735,7 +735,7 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreateTLS() {
|
||||
DomainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||
TLSConfig: TLSConfig{
|
||||
TLSEnabled: true,
|
||||
CAFilePath: "../internal/testresources/ca.pem",
|
||||
CAFilePath: "../../internal/testresources/ca.pem",
|
||||
},
|
||||
})
|
||||
assert.Nil(suite.T(), err, "Enabled TLS Config with --tls-ca should raise no error")
|
||||
@ -748,8 +748,8 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreateTLS() {
|
||||
DomainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||
TLSConfig: TLSConfig{
|
||||
TLSEnabled: true,
|
||||
CAFilePath: "../internal/testresources/ca.pem",
|
||||
ClientCertFilePath: "../internal/testresources/client-cert.pem",
|
||||
CAFilePath: "../../internal/testresources/ca.pem",
|
||||
ClientCertFilePath: "../../internal/testresources/client-cert.pem",
|
||||
},
|
||||
})
|
||||
assert.Error(suite.T(), err, "Enabled TLS Config with --tls-client-cert only should raise an error")
|
||||
@ -762,8 +762,8 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreateTLS() {
|
||||
DomainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||
TLSConfig: TLSConfig{
|
||||
TLSEnabled: true,
|
||||
CAFilePath: "../internal/testresources/ca.pem",
|
||||
ClientCertKeyFilePath: "../internal/testresources/client-cert-key.pem",
|
||||
CAFilePath: "../../internal/testresources/ca.pem",
|
||||
ClientCertKeyFilePath: "../../internal/testresources/client-cert-key.pem",
|
||||
},
|
||||
})
|
||||
assert.Error(suite.T(), err, "Enabled TLS Config with --tls-client-cert-key only should raise an error")
|
||||
@ -776,9 +776,9 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreateTLS() {
|
||||
DomainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||
TLSConfig: TLSConfig{
|
||||
TLSEnabled: true,
|
||||
CAFilePath: "../internal/testresources/ca.pem",
|
||||
ClientCertFilePath: "../internal/testresources/client-cert.pem",
|
||||
ClientCertKeyFilePath: "../internal/testresources/client-cert-key.pem",
|
||||
CAFilePath: "../../internal/testresources/ca.pem",
|
||||
ClientCertFilePath: "../../internal/testresources/client-cert.pem",
|
||||
ClientCertKeyFilePath: "../../internal/testresources/client-cert-key.pem",
|
||||
},
|
||||
})
|
||||
assert.Nil(suite.T(), err, "Enabled TLS Config with all flags should raise no error")
|
||||
@ -29,6 +29,14 @@ import (
|
||||
type Provider interface {
|
||||
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||
ApplyChanges(ctx context.Context, changes *plan.Changes) error
|
||||
PropertyValuesEqual(name string, previous string, current string) bool
|
||||
}
|
||||
|
||||
type BaseProvider struct {
|
||||
}
|
||||
|
||||
func (b BaseProvider) PropertyValuesEqual(name, previous, current string) bool {
|
||||
return previous == current
|
||||
}
|
||||
|
||||
type contextKey struct {
|
||||
@ -42,11 +50,34 @@ func (k *contextKey) String() string { return "provider context value " + k.name
|
||||
// type []*endpoint.Endpoint.
|
||||
var RecordsContextKey = &contextKey{"records"}
|
||||
|
||||
// ensureTrailingDot ensures that the hostname receives a trailing dot if it hasn't already.
|
||||
func ensureTrailingDot(hostname string) string {
|
||||
// EnsureTrailingDot ensures that the hostname receives a trailing dot if it hasn't already.
|
||||
func EnsureTrailingDot(hostname string) string {
|
||||
if net.ParseIP(hostname) != nil {
|
||||
return hostname
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(hostname, ".") + "."
|
||||
}
|
||||
|
||||
// Difference tells which entries need to be respectively
|
||||
// added, removed, or left untouched for "current" to be transformed to "desired"
|
||||
func Difference(current, desired []string) ([]string, []string, []string) {
|
||||
add, remove, leave := []string{}, []string{}, []string{}
|
||||
index := make(map[string]struct{}, len(current))
|
||||
for _, x := range current {
|
||||
index[x] = struct{}{}
|
||||
}
|
||||
for _, x := range desired {
|
||||
if _, found := index[x]; found {
|
||||
leave = append(leave, x)
|
||||
delete(index, x)
|
||||
} else {
|
||||
add = append(add, x)
|
||||
delete(index, x)
|
||||
}
|
||||
}
|
||||
for x := range index {
|
||||
remove = append(remove, x)
|
||||
}
|
||||
return add, remove, leave
|
||||
}
|
||||
|
||||
@ -17,9 +17,19 @@ limitations under the License.
|
||||
package provider
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestEnsureTrailingDot(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
input, expected string
|
||||
@ -28,10 +38,28 @@ func TestEnsureTrailingDot(t *testing.T) {
|
||||
{"example.org.", "example.org."},
|
||||
{"8.8.8.8", "8.8.8.8"},
|
||||
} {
|
||||
output := ensureTrailingDot(tc.input)
|
||||
output := EnsureTrailingDot(tc.input)
|
||||
|
||||
if output != tc.expected {
|
||||
t.Errorf("expected %s, got %s", tc.expected, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference(t *testing.T) {
|
||||
current := []string{"foo", "bar"}
|
||||
desired := []string{"bar", "baz"}
|
||||
add, remove, leave := Difference(current, desired)
|
||||
assert.Equal(t, add, []string{"baz"})
|
||||
assert.Equal(t, remove, []string{"foo"})
|
||||
assert.Equal(t, leave, []string{"bar"})
|
||||
}
|
||||
|
||||
func TestBaseProviderPropertyEquality(t *testing.T) {
|
||||
p := BaseProvider{}
|
||||
assert.True(t, p.PropertyValuesEqual("some.property", "", ""), "Both properties not present")
|
||||
assert.False(t, p.PropertyValuesEqual("some.property", "", "Foo"), "First property missing")
|
||||
assert.False(t, p.PropertyValuesEqual("some.property", "Foo", ""), "Second property missing")
|
||||
assert.True(t, p.PropertyValuesEqual("some.property", "Foo", "Foo"), "Properties the same")
|
||||
assert.False(t, p.PropertyValuesEqual("some.property", "Foo", "Bar"), "Attributes differ")
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package rcode0
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -28,10 +28,12 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// RcodeZeroProvider implements the DNS provider for RcodeZero Anycast DNS.
|
||||
type RcodeZeroProvider struct {
|
||||
provider.BaseProvider
|
||||
Client *rc0.Client
|
||||
|
||||
DomainFilter endpoint.DomainFilter
|
||||
@ -44,7 +46,6 @@ type RcodeZeroProvider struct {
|
||||
//
|
||||
// Returns the provider or an error if a provider could not be created.
|
||||
func NewRcodeZeroProvider(domainFilter endpoint.DomainFilter, dryRun bool, txtEnc bool) (*RcodeZeroProvider, error) {
|
||||
|
||||
client, err := rc0.NewClient(os.Getenv("RC0_API_KEY"))
|
||||
|
||||
if err != nil {
|
||||
@ -76,7 +77,6 @@ func NewRcodeZeroProvider(domainFilter endpoint.DomainFilter, dryRun bool, txtEn
|
||||
|
||||
// Zones returns filtered zones if filter is set
|
||||
func (p *RcodeZeroProvider) Zones() ([]*rc0.Zone, error) {
|
||||
|
||||
var result []*rc0.Zone
|
||||
|
||||
zones, err := p.fetchZones()
|
||||
@ -97,7 +97,6 @@ func (p *RcodeZeroProvider) Zones() ([]*rc0.Zone, error) {
|
||||
//
|
||||
// Decrypts TXT records if TXT-Encrypt flag is set and key is provided
|
||||
func (p *RcodeZeroProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
|
||||
zones, err := p.Zones()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -106,7 +105,6 @@ func (p *RcodeZeroProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
for _, zone := range zones {
|
||||
|
||||
rrset, err := p.fetchRecords(zone.Domain)
|
||||
|
||||
if err != nil {
|
||||
@ -114,25 +112,19 @@ func (p *RcodeZeroProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
|
||||
}
|
||||
|
||||
for _, r := range rrset {
|
||||
|
||||
if supportedRecordType(r.Type) {
|
||||
|
||||
if provider.SupportedRecordType(r.Type) {
|
||||
if p.TXTEncrypt && (p.Key != nil) && strings.EqualFold(r.Type, "TXT") {
|
||||
p.Client.RRSet.DecryptTXT(p.Key, r)
|
||||
}
|
||||
|
||||
if len(r.Records) > 1 {
|
||||
|
||||
for _, _r := range r.Records {
|
||||
if !_r.Disabled {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, r.Type, endpoint.TTL(r.TTL), _r.Content))
|
||||
}
|
||||
}
|
||||
|
||||
} else if !r.Records[0].Disabled {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, r.Type, endpoint.TTL(r.TTL), r.Records[0].Content))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -142,7 +134,6 @@ func (p *RcodeZeroProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *RcodeZeroProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
|
||||
combinedChanges := make([]*rc0.RRSetChange, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
||||
|
||||
combinedChanges = append(combinedChanges, p.NewRcodezeroChanges(rc0.ChangeTypeADD, changes.Create)...)
|
||||
@ -154,9 +145,8 @@ func (p *RcodeZeroProvider) ApplyChanges(ctx context.Context, changes *plan.Chan
|
||||
|
||||
// Helper function
|
||||
func rcodezeroChangesByZone(zones []*rc0.Zone, changeSet []*rc0.RRSetChange) map[string][]*rc0.RRSetChange {
|
||||
|
||||
changes := make(map[string][]*rc0.RRSetChange)
|
||||
zoneNameIDMapper := zoneIDName{}
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, z := range zones {
|
||||
zoneNameIDMapper.Add(z.Domain, z.Domain)
|
||||
changes[z.Domain] = []*rc0.RRSetChange{}
|
||||
@ -176,7 +166,6 @@ func rcodezeroChangesByZone(zones []*rc0.Zone, changeSet []*rc0.RRSetChange) map
|
||||
|
||||
// Helper function
|
||||
func (p *RcodeZeroProvider) fetchRecords(zoneName string) ([]*rc0.RRType, error) {
|
||||
|
||||
var allRecords []*rc0.RRType
|
||||
|
||||
listOptions := rc0.NewListOptions()
|
||||
@ -202,7 +191,6 @@ func (p *RcodeZeroProvider) fetchRecords(zoneName string) ([]*rc0.RRType, error)
|
||||
|
||||
// Helper function
|
||||
func (p *RcodeZeroProvider) fetchZones() ([]*rc0.Zone, error) {
|
||||
|
||||
var allZones []*rc0.Zone
|
||||
|
||||
listOptions := rc0.NewListOptions()
|
||||
@ -228,7 +216,6 @@ func (p *RcodeZeroProvider) fetchZones() ([]*rc0.Zone, error) {
|
||||
//
|
||||
// Changes are submitted by change type.
|
||||
func (p *RcodeZeroProvider) submitChanges(changes []*rc0.RRSetChange) error {
|
||||
|
||||
if len(changes) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -240,11 +227,8 @@ func (p *RcodeZeroProvider) submitChanges(changes []*rc0.RRSetChange) error {
|
||||
|
||||
// separate into per-zone change sets to be passed to the API.
|
||||
changesByZone := rcodezeroChangesByZone(zones, changes)
|
||||
|
||||
for zoneName, changes := range changesByZone {
|
||||
|
||||
for _, change := range changes {
|
||||
|
||||
logFields := log.Fields{
|
||||
"record": change.Name,
|
||||
"content": change.Records[0].Content,
|
||||
@ -306,7 +290,6 @@ func (p *RcodeZeroProvider) submitChanges(changes []*rc0.RRSetChange) error {
|
||||
|
||||
// NewRcodezeroChanges returns a RcodeZero specific array with rrset change objects.
|
||||
func (p *RcodeZeroProvider) NewRcodezeroChanges(action string, endpoints []*endpoint.Endpoint) []*rc0.RRSetChange {
|
||||
|
||||
changes := make([]*rc0.RRSetChange, 0, len(endpoints))
|
||||
|
||||
for _, _endpoint := range endpoints {
|
||||
@ -318,7 +301,6 @@ func (p *RcodeZeroProvider) NewRcodezeroChanges(action string, endpoints []*endp
|
||||
|
||||
// NewRcodezeroChange returns a RcodeZero specific rrset change object.
|
||||
func (p *RcodeZeroProvider) NewRcodezeroChange(action string, endpoint *endpoint.Endpoint) *rc0.RRSetChange {
|
||||
|
||||
change := &rc0.RRSetChange{
|
||||
Type: endpoint.RecordType,
|
||||
ChangeType: action,
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package rcode0
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package rdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -35,9 +35,11 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
etcdTimeout = 5 * time.Second
|
||||
rdnsMaxHosts = 10
|
||||
rdnsOriginalLabel = "originalText"
|
||||
rdnsPrefix = "/rdnsv3"
|
||||
@ -65,6 +67,7 @@ type RDNSConfig struct {
|
||||
|
||||
// RDNSProvider is an implementation of Provider for Rancher DNS(RDNS).
|
||||
type RDNSProvider struct {
|
||||
provider.BaseProvider
|
||||
client RDNSClient
|
||||
dryRun bool
|
||||
domainFilter endpoint.DomainFilter
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package rdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -16,9 +16,9 @@ limitations under the License.
|
||||
|
||||
package provider
|
||||
|
||||
// supportedRecordType returns true only for supported record types.
|
||||
// SupportedRecordType returns true only for supported record types.
|
||||
// Currently A, CNAME, SRV, and TXT record types are supported.
|
||||
func supportedRecordType(recordType string) bool {
|
||||
func SupportedRecordType(recordType string) bool {
|
||||
switch recordType {
|
||||
case "A", "CNAME", "SRV", "TXT":
|
||||
return true
|
||||
|
||||
@ -41,7 +41,7 @@ func TestRecordTypeFilter(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, r := range records {
|
||||
got := supportedRecordType(r.rtype)
|
||||
got := SupportedRecordType(r.rtype)
|
||||
if r.expect != got {
|
||||
t.Errorf("wrong record type %s: expect %v, but got %v", r.rtype, r.expect, got)
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package rfc2136
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -30,10 +30,17 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
// maximum size of a UDP transport message in DNS protocol
|
||||
udpMaxMsgSize = 512
|
||||
)
|
||||
|
||||
// rfc2136 provider type
|
||||
type rfc2136Provider struct {
|
||||
provider.BaseProvider
|
||||
nameserver string
|
||||
zoneName string
|
||||
tsigKeyName string
|
||||
@ -65,7 +72,7 @@ type rfc2136Actions interface {
|
||||
}
|
||||
|
||||
// NewRfc2136Provider is a factory function for OpenStack rfc2136 providers
|
||||
func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, keyName string, secret string, secretAlg string, axfr bool, domainFilter endpoint.DomainFilter, dryRun bool, minTTL time.Duration, actions rfc2136Actions) (Provider, error) {
|
||||
func NewRfc2136Provider(host string, port int, zoneName string, insecure bool, keyName string, secret string, secretAlg string, axfr bool, domainFilter endpoint.DomainFilter, dryRun bool, minTTL time.Duration, actions rfc2136Actions) (provider.Provider, error) {
|
||||
secretAlgChecked, ok := tsigAlgs[secretAlg]
|
||||
if !ok && !insecure {
|
||||
return nil, errors.Errorf("%s is not supported TSIG algorithm", secretAlg)
|
||||
@ -206,7 +213,6 @@ func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
m.SetUpdate(r.zoneName)
|
||||
|
||||
for _, ep := range changes.Create {
|
||||
|
||||
if !r.domainFilter.Match(ep.DNSName) {
|
||||
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||
continue
|
||||
@ -214,17 +220,15 @@ func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
|
||||
r.AddRecord(m, ep)
|
||||
}
|
||||
for _, ep := range changes.UpdateNew {
|
||||
|
||||
for i, ep := range changes.UpdateNew {
|
||||
if !r.domainFilter.Match(ep.DNSName) {
|
||||
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||
continue
|
||||
}
|
||||
|
||||
r.UpdateRecord(m, ep)
|
||||
r.UpdateRecord(m, changes.UpdateOld[i], ep)
|
||||
}
|
||||
for _, ep := range changes.Delete {
|
||||
|
||||
if !r.domainFilter.Match(ep.DNSName) {
|
||||
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||
continue
|
||||
@ -244,13 +248,13 @@ func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r rfc2136Provider) UpdateRecord(m *dns.Msg, ep *endpoint.Endpoint) error {
|
||||
err := r.RemoveRecord(m, ep)
|
||||
func (r rfc2136Provider) UpdateRecord(m *dns.Msg, oldEp *endpoint.Endpoint, newEp *endpoint.Endpoint) error {
|
||||
err := r.RemoveRecord(m, oldEp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.AddRecord(m, ep)
|
||||
return r.AddRecord(m, newEp)
|
||||
}
|
||||
|
||||
func (r rfc2136Provider) AddRecord(m *dns.Msg, ep *endpoint.Endpoint) error {
|
||||
@ -308,6 +312,10 @@ func (r rfc2136Provider) SendMessage(msg *dns.Msg) error {
|
||||
msg.SetTsig(r.tsigKeyName, r.tsigSecretAlg, 300, time.Now().Unix())
|
||||
}
|
||||
|
||||
if msg.Len() > udpMaxMsgSize {
|
||||
c.Net = "tcp"
|
||||
}
|
||||
|
||||
resp, _, err := c.Exchange(msg, r.nameserver)
|
||||
if err != nil {
|
||||
log.Infof("error in dns.Client.Exchange: %s", err)
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package rfc2136
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -29,6 +29,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type rfc2136Stub struct {
|
||||
@ -93,7 +94,7 @@ func (r *rfc2136Stub) IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Envelo
|
||||
return outChan, nil
|
||||
}
|
||||
|
||||
func createRfc2136StubProvider(stub *rfc2136Stub) (Provider, error) {
|
||||
func createRfc2136StubProvider(stub *rfc2136Stub) (provider.Provider, error) {
|
||||
return NewRfc2136Provider("", 0, "", false, "key", "secret", "hmac-sha512", true, endpoint.DomainFilter{}, false, 300*time.Second, stub)
|
||||
}
|
||||
|
||||
@ -246,3 +247,90 @@ func TestRfc2136ApplyChangesWithDifferentTTLs(t *testing.T) {
|
||||
assert.True(t, strings.Contains(createRecords[2], "300"))
|
||||
|
||||
}
|
||||
|
||||
func TestRfc2136ApplyChangesWithUpdate(t *testing.T) {
|
||||
stub := newStub()
|
||||
|
||||
provider, err := createRfc2136StubProvider(stub)
|
||||
assert.NoError(t, err)
|
||||
|
||||
p := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "v1.foo.com",
|
||||
RecordType: "A",
|
||||
Targets: []string{"1.2.3.4"},
|
||||
RecordTTL: endpoint.TTL(400),
|
||||
},
|
||||
{
|
||||
DNSName: "v1.foobar.com",
|
||||
RecordType: "TXT",
|
||||
Targets: []string{"boom"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = provider.ApplyChanges(context.Background(), p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
p = &plan.Changes{
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "v1.foo.com",
|
||||
RecordType: "A",
|
||||
Targets: []string{"1.2.3.4"},
|
||||
RecordTTL: endpoint.TTL(400),
|
||||
},
|
||||
{
|
||||
DNSName: "v1.foobar.com",
|
||||
RecordType: "TXT",
|
||||
Targets: []string{"boom"},
|
||||
},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "v1.foo.com",
|
||||
RecordType: "A",
|
||||
Targets: []string{"1.2.3.5"},
|
||||
RecordTTL: endpoint.TTL(400),
|
||||
},
|
||||
{
|
||||
DNSName: "v1.foobar.com",
|
||||
RecordType: "TXT",
|
||||
Targets: []string{"kablui"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = provider.ApplyChanges(context.Background(), p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 4, len(stub.createMsgs))
|
||||
assert.Equal(t, 2, len(stub.updateMsgs))
|
||||
|
||||
assert.True(t, strings.Contains(stub.createMsgs[0].String(), "v1.foo.com"))
|
||||
assert.True(t, strings.Contains(stub.createMsgs[0].String(), "1.2.3.4"))
|
||||
assert.True(t, strings.Contains(stub.createMsgs[2].String(), "v1.foo.com"))
|
||||
assert.True(t, strings.Contains(stub.createMsgs[2].String(), "1.2.3.5"))
|
||||
|
||||
assert.True(t, strings.Contains(stub.updateMsgs[0].String(), "v1.foo.com"))
|
||||
assert.True(t, strings.Contains(stub.updateMsgs[0].String(), "1.2.3.4"))
|
||||
|
||||
assert.True(t, strings.Contains(stub.createMsgs[1].String(), "v1.foobar.com"))
|
||||
assert.True(t, strings.Contains(stub.createMsgs[1].String(), "boom"))
|
||||
assert.True(t, strings.Contains(stub.createMsgs[3].String(), "v1.foobar.com"))
|
||||
assert.True(t, strings.Contains(stub.createMsgs[3].String(), "kablui"))
|
||||
|
||||
assert.True(t, strings.Contains(stub.updateMsgs[1].String(), "v1.foobar.com"))
|
||||
assert.True(t, strings.Contains(stub.updateMsgs[1].String(), "boom"))
|
||||
|
||||
}
|
||||
|
||||
func contains(arr []*endpoint.Endpoint, name string) bool {
|
||||
for _, a := range arr {
|
||||
if a.DNSName == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -1,4 +1,20 @@
|
||||
package provider
|
||||
/*
|
||||
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 transip
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -12,6 +28,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -22,6 +39,7 @@ const (
|
||||
|
||||
// TransIPProvider is an implementation of Provider for TransIP.
|
||||
type TransIPProvider struct {
|
||||
provider.BaseProvider
|
||||
client gotransip.SOAPClient
|
||||
domainFilter endpoint.DomainFilter
|
||||
dryRun bool
|
||||
@ -72,7 +90,7 @@ func (p *TransIPProvider) ApplyChanges(ctx context.Context, changes *plan.Change
|
||||
return err
|
||||
}
|
||||
|
||||
zoneNameMapper := zoneIDName{}
|
||||
zoneNameMapper := provider.ZoneIDName{}
|
||||
zonesByName := make(map[string]transip.Domain)
|
||||
updatedZones := make(map[string]bool)
|
||||
for _, zone := range zones {
|
||||
@ -232,7 +250,7 @@ func (p *TransIPProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, er
|
||||
// go over all zones and their DNS entries and create endpoints for them
|
||||
for _, zone := range zones {
|
||||
for _, r := range zone.DNSEntries {
|
||||
if !supportedRecordType(string(r.Type)) {
|
||||
if !provider.SupportedRecordType(string(r.Type)) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -360,7 +378,7 @@ func (p *TransIPProvider) addEndpointToEntries(ep *endpoint.Endpoint, zone trans
|
||||
|
||||
// zoneForZoneName returns the zone mapped to given name or error if zone could
|
||||
// not be found
|
||||
func (p *TransIPProvider) zoneForZoneName(name string, m zoneIDName, z map[string]transip.Domain) (transip.Domain, error) {
|
||||
func (p *TransIPProvider) zoneForZoneName(name string, m provider.ZoneIDName, z map[string]transip.Domain) (transip.Domain, error) {
|
||||
_, zoneName := m.FindZone(name)
|
||||
if zoneName == "" {
|
||||
return transip.Domain{}, fmt.Errorf("could not find zoneName for %s", name)
|
||||
@ -1,4 +1,20 @@
|
||||
package provider
|
||||
/*
|
||||
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 transip
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package vinyldns
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -27,6 +27,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -47,8 +48,9 @@ type vinyldnsZoneInterface interface {
|
||||
}
|
||||
|
||||
type vinyldnsProvider struct {
|
||||
provider.BaseProvider
|
||||
client vinyldnsZoneInterface
|
||||
zoneFilter ZoneIDFilter
|
||||
zoneFilter provider.ZoneIDFilter
|
||||
domainFilter endpoint.DomainFilter
|
||||
dryRun bool
|
||||
}
|
||||
@ -59,7 +61,7 @@ type vinyldnsChange struct {
|
||||
}
|
||||
|
||||
// NewVinylDNSProvider provides support for VinylDNS records
|
||||
func NewVinylDNSProvider(domainFilter endpoint.DomainFilter, zoneFilter ZoneIDFilter, dryRun bool) (Provider, error) {
|
||||
func NewVinylDNSProvider(domainFilter endpoint.DomainFilter, zoneFilter provider.ZoneIDFilter, dryRun bool) (provider.Provider, error) {
|
||||
_, ok := os.LookupEnv("VINYLDNS_ACCESS_KEY")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no vinyldns access key found")
|
||||
@ -97,7 +99,7 @@ func (p *vinyldnsProvider) Records(ctx context.Context) (endpoints []*endpoint.E
|
||||
}
|
||||
|
||||
for _, r := range records {
|
||||
if supportedRecordType(r.Type) {
|
||||
if provider.SupportedRecordType(r.Type) {
|
||||
recordsCount := len(r.Records)
|
||||
log.Debugf(fmt.Sprintf("%s.%s.%d.%s", r.Name, r.Type, recordsCount, zone.Name))
|
||||
|
||||
@ -204,7 +206,7 @@ func (p *vinyldnsProvider) findRecordSetID(zoneID string, recordSetName string)
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Record not found")
|
||||
return "", fmt.Errorf("record not found")
|
||||
}
|
||||
|
||||
func (p *vinyldnsProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package vinyldns
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -28,6 +28,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type mockVinyldnsZoneInterface struct {
|
||||
@ -97,12 +98,12 @@ func testVinylDNSProviderRecords(t *testing.T) {
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(vinylDNSRecords), len(result))
|
||||
|
||||
mockVinylDNSProvider.zoneFilter = NewZoneIDFilter([]string{"0"})
|
||||
mockVinylDNSProvider.zoneFilter = provider.NewZoneIDFilter([]string{"0"})
|
||||
result, err = mockVinylDNSProvider.Records(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, len(vinylDNSRecords), len(result))
|
||||
|
||||
mockVinylDNSProvider.zoneFilter = NewZoneIDFilter([]string{"1"})
|
||||
mockVinylDNSProvider.zoneFilter = provider.NewZoneIDFilter([]string{"1"})
|
||||
result, err = mockVinylDNSProvider.Records(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(result))
|
||||
@ -118,7 +119,7 @@ func testVinylDNSProviderApplyChanges(t *testing.T) {
|
||||
}
|
||||
changes.Delete = []*endpoint.Endpoint{{DNSName: "example.com", Targets: endpoint.Targets{"vinyldns.com"}, RecordType: endpoint.RecordTypeCNAME}}
|
||||
|
||||
mockVinylDNSProvider.zoneFilter = NewZoneIDFilter([]string{"1"})
|
||||
mockVinylDNSProvider.zoneFilter = provider.NewZoneIDFilter([]string{"1"})
|
||||
err := mockVinylDNSProvider.ApplyChanges(context.Background(), changes)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to apply changes: %v", err)
|
||||
@ -126,7 +127,7 @@ func testVinylDNSProviderApplyChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
func testVinylDNSSuitableZone(t *testing.T) {
|
||||
mockVinylDNSProvider.zoneFilter = NewZoneIDFilter([]string{"0"})
|
||||
mockVinylDNSProvider.zoneFilter = provider.NewZoneIDFilter([]string{"0"})
|
||||
|
||||
zone := vinyldnsSuitableZone("example.com", vinylDNSZones)
|
||||
assert.Equal(t, zone.Name, "example.com.")
|
||||
@ -134,11 +135,11 @@ func testVinylDNSSuitableZone(t *testing.T) {
|
||||
|
||||
func TestNewVinylDNSProvider(t *testing.T) {
|
||||
os.Setenv("VINYLDNS_ACCESS_KEY", "xxxxxxxxxxxxxxxxxxxxxxxxxx")
|
||||
_, err := NewVinylDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{"0"}), true)
|
||||
_, err := NewVinylDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{"0"}), true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
os.Unsetenv("VINYLDNS_ACCESS_KEY")
|
||||
_, err = NewVinylDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{"0"}), true)
|
||||
_, err = NewVinylDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{"0"}), true)
|
||||
assert.NotNil(t, err)
|
||||
if err == nil {
|
||||
t.Errorf("Expected to fail new provider on empty token")
|
||||
@ -146,7 +147,7 @@ func TestNewVinylDNSProvider(t *testing.T) {
|
||||
}
|
||||
|
||||
func testVinylDNSFindRecordSetID(t *testing.T) {
|
||||
mockVinylDNSProvider.zoneFilter = NewZoneIDFilter([]string{"0"})
|
||||
mockVinylDNSProvider.zoneFilter = provider.NewZoneIDFilter([]string{"0"})
|
||||
result, err := mockVinylDNSProvider.findRecordSetID("0", "example.com.")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "", result)
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package vultr
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -27,6 +27,7 @@ import (
|
||||
"github.com/vultr/govultr"
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -36,13 +37,16 @@ const (
|
||||
vultrTTL = 3600
|
||||
)
|
||||
|
||||
// VultrProvider is an implementation of Provider for Vultr DNS.
|
||||
type VultrProvider struct {
|
||||
provider.BaseProvider
|
||||
client govultr.Client
|
||||
|
||||
domainFilter endpoint.DomainFilter
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
// VultrChanges differentiates between ChangActions.
|
||||
type VultrChanges struct {
|
||||
Action string
|
||||
|
||||
@ -78,6 +82,7 @@ func (p *VultrProvider) Zones(ctx context.Context) ([]govultr.DNSDomain, error)
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
// Records returns the list of records.
|
||||
func (p *VultrProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
@ -93,7 +98,7 @@ func (p *VultrProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
}
|
||||
|
||||
for _, r := range records {
|
||||
if supportedRecordType(r.Type) {
|
||||
if provider.SupportedRecordType(r.Type) {
|
||||
name := fmt.Sprintf("%s.%s", r.Name, zone.Domain)
|
||||
|
||||
// root name is identified by the empty string and should be
|
||||
@ -151,7 +156,6 @@ func (p *VultrProvider) submitChanges(ctx context.Context, changes []*VultrChang
|
||||
|
||||
for zoneName, changes := range zoneChanges {
|
||||
for _, change := range changes {
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"record": change.ResourceRecordSet.Name,
|
||||
"type": change.ResourceRecordSet.Type,
|
||||
@ -197,10 +201,10 @@ func (p *VultrProvider) submitChanges(ctx context.Context, changes []*VultrChang
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *VultrProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
combinedChanges := make([]*VultrChanges, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
||||
|
||||
@ -215,7 +219,6 @@ func newVultrChanges(action string, endpoints []*endpoint.Endpoint) []*VultrChan
|
||||
changes := make([]*VultrChanges, 0, len(endpoints))
|
||||
ttl := vultrTTL
|
||||
for _, e := range endpoints {
|
||||
|
||||
if e.RecordTTL.IsConfigured() {
|
||||
ttl = int(e.RecordTTL)
|
||||
}
|
||||
@ -236,7 +239,7 @@ func newVultrChanges(action string, endpoints []*endpoint.Endpoint) []*VultrChan
|
||||
|
||||
func seperateChangesByZone(zones []govultr.DNSDomain, changes []*VultrChanges) map[string][]*VultrChanges {
|
||||
change := make(map[string][]*VultrChanges)
|
||||
zoneNameID := zoneIDName{}
|
||||
zoneNameID := provider.ZoneIDName{}
|
||||
|
||||
for _, z := range zones {
|
||||
zoneNameID.Add(z.Domain, z.Domain)
|
||||
@ -250,7 +253,6 @@ func seperateChangesByZone(zones []govultr.DNSDomain, changes []*VultrChanges) m
|
||||
continue
|
||||
}
|
||||
change[zone] = append(change[zone], c)
|
||||
|
||||
}
|
||||
return change
|
||||
}
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package provider
|
||||
package vultr
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -20,7 +20,7 @@ import "strings"
|
||||
|
||||
// ZoneIDFilter holds a list of zone ids to filter by
|
||||
type ZoneIDFilter struct {
|
||||
zoneIDs []string
|
||||
ZoneIDs []string
|
||||
}
|
||||
|
||||
// NewZoneIDFilter returns a new ZoneIDFilter given a list of zone ids
|
||||
@ -31,11 +31,11 @@ func NewZoneIDFilter(zoneIDs []string) ZoneIDFilter {
|
||||
// Match checks whether a zone matches one of the provided zone ids
|
||||
func (f ZoneIDFilter) Match(zoneID string) bool {
|
||||
// An empty filter includes all zones.
|
||||
if len(f.zoneIDs) == 0 {
|
||||
if len(f.ZoneIDs) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, id := range f.zoneIDs {
|
||||
for _, id := range f.ZoneIDs {
|
||||
if strings.HasSuffix(zoneID, id) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -18,13 +18,13 @@ package provider
|
||||
|
||||
import "strings"
|
||||
|
||||
type zoneIDName map[string]string
|
||||
type ZoneIDName map[string]string
|
||||
|
||||
func (z zoneIDName) Add(zoneID, zoneName string) {
|
||||
func (z ZoneIDName) Add(zoneID, zoneName string) {
|
||||
z[zoneID] = zoneName
|
||||
}
|
||||
|
||||
func (z zoneIDName) FindZone(hostname string) (suitableZoneID, suitableZoneName string) {
|
||||
func (z ZoneIDName) FindZone(hostname string) (suitableZoneID, suitableZoneName string) {
|
||||
for zoneID, zoneName := range z {
|
||||
if hostname == zoneName || strings.HasSuffix(hostname, "."+zoneName) {
|
||||
if suitableZoneName == "" || len(zoneName) > len(suitableZoneName) {
|
||||
|
||||
@ -23,12 +23,12 @@ import (
|
||||
)
|
||||
|
||||
func TestZoneIDName(t *testing.T) {
|
||||
z := zoneIDName{}
|
||||
z := ZoneIDName{}
|
||||
z.Add("123456", "foo.bar")
|
||||
z.Add("123456", "qux.baz")
|
||||
z.Add("654321", "foo.qux.baz")
|
||||
|
||||
assert.Equal(t, zoneIDName{
|
||||
assert.Equal(t, ZoneIDName{
|
||||
"123456": "qux.baz",
|
||||
"654321": "foo.qux.baz",
|
||||
}, z)
|
||||
|
||||
@ -87,3 +87,7 @@ func (sdr *AWSSDRegistry) updateLabels(endpoints []*endpoint.Endpoint) {
|
||||
ep.Labels[endpoint.AWSSDDescriptionLabel] = ep.Labels.Serialize(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (sdr *AWSSDRegistry) PropertyValuesEqual(name string, previous string, current string) bool {
|
||||
return sdr.provider.PropertyValuesEqual(name, previous, current)
|
||||
}
|
||||
|
||||
@ -26,9 +26,11 @@ import (
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
type inMemoryProvider struct {
|
||||
provider.BaseProvider
|
||||
endpoints []*endpoint.Endpoint
|
||||
onApplyChanges func(changes *plan.Changes)
|
||||
}
|
||||
|
||||
@ -45,3 +45,8 @@ func (im *NoopRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
func (im *NoopRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
return im.provider.ApplyChanges(ctx, changes)
|
||||
}
|
||||
|
||||
// PropertyValuesEqual compares two property values for equality
|
||||
func (im *NoopRegistry) PropertyValuesEqual(attribute string, previous string, current string) bool {
|
||||
return im.provider.PropertyValuesEqual(attribute, previous, current)
|
||||
}
|
||||
|
||||
@ -26,7 +26,7 @@ import (
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
"sigs.k8s.io/external-dns/provider/inmemory"
|
||||
)
|
||||
|
||||
var _ Registry = &NoopRegistry{}
|
||||
@ -38,7 +38,7 @@ func TestNoopRegistry(t *testing.T) {
|
||||
}
|
||||
|
||||
func testNoopInit(t *testing.T) {
|
||||
p := provider.NewInMemoryProvider()
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
r, err := NewNoopRegistry(p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, p, r.provider)
|
||||
@ -46,9 +46,9 @@ func testNoopInit(t *testing.T) {
|
||||
|
||||
func testNoopRecords(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := provider.NewInMemoryProvider()
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone("org")
|
||||
providerRecords := []*endpoint.Endpoint{
|
||||
inmemoryRecords := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Targets: endpoint.Targets{"example-lb.com"},
|
||||
@ -56,21 +56,21 @@ func testNoopRecords(t *testing.T) {
|
||||
},
|
||||
}
|
||||
p.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: providerRecords,
|
||||
Create: inmemoryRecords,
|
||||
})
|
||||
|
||||
r, _ := NewNoopRegistry(p)
|
||||
|
||||
eps, err := r.Records(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, testutils.SameEndpoints(eps, providerRecords))
|
||||
assert.True(t, testutils.SameEndpoints(eps, inmemoryRecords))
|
||||
}
|
||||
|
||||
func testNoopApplyChanges(t *testing.T) {
|
||||
// do some prep
|
||||
p := provider.NewInMemoryProvider()
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone("org")
|
||||
providerRecords := []*endpoint.Endpoint{
|
||||
inmemoryRecords := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
Targets: endpoint.Targets{"old-lb.com"},
|
||||
@ -92,7 +92,7 @@ func testNoopApplyChanges(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
p.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: providerRecords,
|
||||
Create: inmemoryRecords,
|
||||
})
|
||||
|
||||
// wrong changes
|
||||
@ -106,7 +106,7 @@ func testNoopApplyChanges(t *testing.T) {
|
||||
},
|
||||
},
|
||||
})
|
||||
assert.EqualError(t, err, provider.ErrRecordAlreadyExists.Error())
|
||||
assert.EqualError(t, err, inmemory.ErrRecordAlreadyExists.Error())
|
||||
|
||||
//correct changes
|
||||
require.NoError(t, r.ApplyChanges(ctx, &plan.Changes{
|
||||
|
||||
@ -32,6 +32,7 @@ import (
|
||||
type Registry interface {
|
||||
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||
ApplyChanges(ctx context.Context, changes *plan.Changes) error
|
||||
PropertyValuesEqual(attribute string, previous string, current string) bool
|
||||
}
|
||||
|
||||
//TODO(ideahitme): consider moving this to Plan
|
||||
|
||||
@ -43,12 +43,16 @@ type TXTRegistry struct {
|
||||
}
|
||||
|
||||
// NewTXTRegistry returns new TXTRegistry object
|
||||
func NewTXTRegistry(provider provider.Provider, txtPrefix, ownerID string, cacheInterval time.Duration) (*TXTRegistry, error) {
|
||||
func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration) (*TXTRegistry, error) {
|
||||
if ownerID == "" {
|
||||
return nil, errors.New("owner id cannot be empty")
|
||||
}
|
||||
|
||||
mapper := newPrefixNameMapper(txtPrefix)
|
||||
if len(txtPrefix) > 0 && len(txtSuffix) > 0 {
|
||||
return nil, errors.New("txt-prefix and txt-suffix are mutual exclusive")
|
||||
}
|
||||
|
||||
mapper := newaffixNameMapper(txtPrefix, txtSuffix)
|
||||
|
||||
return &TXTRegistry{
|
||||
provider: provider,
|
||||
@ -187,6 +191,11 @@ func (im *TXTRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
||||
return im.provider.ApplyChanges(ctx, filteredChanges)
|
||||
}
|
||||
|
||||
// PropertyValuesEqual compares two attribute values for equality
|
||||
func (im *TXTRegistry) PropertyValuesEqual(name string, previous string, current string) bool {
|
||||
return im.provider.PropertyValuesEqual(name, previous, current)
|
||||
}
|
||||
|
||||
/**
|
||||
TXT registry specific private methods
|
||||
*/
|
||||
@ -201,26 +210,35 @@ type nameMapper interface {
|
||||
toTXTName(string) string
|
||||
}
|
||||
|
||||
type prefixNameMapper struct {
|
||||
type affixNameMapper struct {
|
||||
prefix string
|
||||
suffix string
|
||||
}
|
||||
|
||||
var _ nameMapper = prefixNameMapper{}
|
||||
var _ nameMapper = affixNameMapper{}
|
||||
|
||||
func newPrefixNameMapper(prefix string) prefixNameMapper {
|
||||
return prefixNameMapper{prefix: strings.ToLower(prefix)}
|
||||
func newaffixNameMapper(prefix string, suffix string) affixNameMapper {
|
||||
return affixNameMapper{prefix: strings.ToLower(prefix), suffix: strings.ToLower(suffix)}
|
||||
}
|
||||
|
||||
func (pr prefixNameMapper) toEndpointName(txtDNSName string) string {
|
||||
func (pr affixNameMapper) toEndpointName(txtDNSName string) string {
|
||||
lowerDNSName := strings.ToLower(txtDNSName)
|
||||
if strings.HasPrefix(lowerDNSName, pr.prefix) {
|
||||
if strings.HasPrefix(lowerDNSName, pr.prefix) && len(pr.suffix) == 0 {
|
||||
return strings.TrimPrefix(lowerDNSName, pr.prefix)
|
||||
}
|
||||
|
||||
if len(pr.suffix) > 0 {
|
||||
DNSName := strings.SplitN(lowerDNSName, ".", 2)
|
||||
if strings.HasSuffix(DNSName[0], pr.suffix) {
|
||||
return strings.TrimSuffix(DNSName[0], pr.suffix) + "." + DNSName[1]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (pr prefixNameMapper) toTXTName(endpointDNSName string) string {
|
||||
return pr.prefix + endpointDNSName
|
||||
func (pr affixNameMapper) toTXTName(endpointDNSName string) string {
|
||||
DNSName := strings.SplitN(endpointDNSName, ".", 2)
|
||||
return pr.prefix + DNSName[0] + pr.suffix + "." + DNSName[1]
|
||||
}
|
||||
|
||||
func (im *TXTRegistry) addToCache(ep *endpoint.Endpoint) {
|
||||
|
||||
@ -29,6 +29,7 @@ import (
|
||||
"sigs.k8s.io/external-dns/internal/testutils"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
"sigs.k8s.io/external-dns/provider/inmemory"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -42,33 +43,44 @@ func TestTXTRegistry(t *testing.T) {
|
||||
}
|
||||
|
||||
func testTXTRegistryNew(t *testing.T) {
|
||||
p := provider.NewInMemoryProvider()
|
||||
_, err := NewTXTRegistry(p, "txt", "", time.Hour)
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
_, err := NewTXTRegistry(p, "txt", "", "", time.Hour)
|
||||
require.Error(t, err)
|
||||
|
||||
r, err := NewTXTRegistry(p, "txt", "owner", time.Hour)
|
||||
_, err = NewTXTRegistry(p, "", "txt", "", time.Hour)
|
||||
require.Error(t, err)
|
||||
|
||||
r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, p, r.provider)
|
||||
|
||||
r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok := r.mapper.(prefixNameMapper)
|
||||
_, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour)
|
||||
require.Error(t, err)
|
||||
|
||||
_, ok := r.mapper.(affixNameMapper)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "owner", r.ownerID)
|
||||
assert.Equal(t, p, r.provider)
|
||||
|
||||
r, err = NewTXTRegistry(p, "", "owner", time.Hour)
|
||||
r, err = NewTXTRegistry(p, "", "", "owner", time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok = r.mapper.(prefixNameMapper)
|
||||
_, ok = r.mapper.(affixNameMapper)
|
||||
assert.True(t, ok)
|
||||
}
|
||||
|
||||
func testTXTRegistryRecords(t *testing.T) {
|
||||
t.Run("With prefix", testTXTRegistryRecordsPrefixed)
|
||||
t.Run("With suffix", testTXTRegistryRecordsSuffixed)
|
||||
t.Run("No prefix", testTXTRegistryRecordsNoPrefix)
|
||||
}
|
||||
|
||||
func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := provider.NewInMemoryProvider()
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
p.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -159,20 +171,125 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt.", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour)
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
// Ensure prefix is case-insensitive
|
||||
r, _ = NewTXTRegistry(p, "TxT.", "owner", time.Hour)
|
||||
r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour)
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
|
||||
}
|
||||
|
||||
func testTXTRegistryRecordsSuffixed(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
p.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerAndLabels("foo.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, "", endpoint.Labels{"foo": "somefoo"}),
|
||||
newEndpointWithOwnerAndLabels("bar.test-zone.example.org", "my-domain.com", endpoint.RecordTypeCNAME, "", endpoint.Labels{"bar": "somebar"}),
|
||||
newEndpointWithOwner("bar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("bar-txt.test-zone.example.org", "baz.test-zone.example.org", endpoint.RecordTypeCNAME, ""),
|
||||
newEndpointWithOwner("qux.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwnerAndLabels("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "", endpoint.Labels{"tar": "sometar"}),
|
||||
newEndpointWithOwner("tar-TxT.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner-2\"", endpoint.RecordTypeTXT, ""), // case-insensitive TXT prefix
|
||||
newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
|
||||
newEndpointWithOwner("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-1"),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-2"),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
})
|
||||
expectedRecords := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"foo.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
"foo": "somefoo",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "bar.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"my-domain.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
"bar": "somebar",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "bar-txt.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"baz.test-zone.example.org"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "qux.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"random"},
|
||||
RecordType: endpoint.RecordTypeTXT,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "tar.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"tar.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner-2",
|
||||
"tar": "sometar",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "foobar.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"foobar.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "multiple.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"lb1.loadbalancer.com"},
|
||||
SetIdentifier: "test-set-1",
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "multiple.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"lb2.loadbalancer.com"},
|
||||
SetIdentifier: "test-set-2",
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour)
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
// Ensure prefix is case-insensitive
|
||||
r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour)
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
|
||||
}
|
||||
|
||||
func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
p := provider.NewInMemoryProvider()
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
ctx := context.Background()
|
||||
p.CreateZone(testZone)
|
||||
p.ApplyChanges(ctx, &plan.Changes{
|
||||
@ -240,7 +357,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour)
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
@ -248,11 +365,12 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
|
||||
func testTXTRegistryApplyChanges(t *testing.T) {
|
||||
t.Run("With Prefix", testTXTRegistryApplyChangesWithPrefix)
|
||||
t.Run("With Suffix", testTXTRegistryApplyChangesWithSuffix)
|
||||
t.Run("No prefix", testTXTRegistryApplyChangesNoPrefix)
|
||||
}
|
||||
|
||||
func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
|
||||
p := provider.NewInMemoryProvider()
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
ctxEndpoints := []*endpoint.Endpoint{}
|
||||
ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
|
||||
@ -276,7 +394,7 @@ func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
|
||||
newEndpointWithOwner("txt.multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "txt.", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour)
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -342,8 +460,99 @@ func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func testTXTRegistryApplyChangesWithSuffix(t *testing.T) {
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
ctxEndpoints := []*endpoint.Endpoint{}
|
||||
ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
|
||||
p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
|
||||
assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey))
|
||||
}
|
||||
p.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwner("foo.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
|
||||
newEndpointWithOwner("bar.test-zone.example.org", "my-domain.com", endpoint.RecordTypeCNAME, ""),
|
||||
newEndpointWithOwner("bar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("bar-txt.test-zone.example.org", "baz.test-zone.example.org", endpoint.RecordTypeCNAME, ""),
|
||||
newEndpointWithOwner("qux.test-zone.example.org", "random", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
|
||||
newEndpointWithOwner("tar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
|
||||
newEndpointWithOwner("foobar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-1"),
|
||||
newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-2"),
|
||||
newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour)
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", "", "", "ingress/default/my-ingress"),
|
||||
newEndpointWithOwnerResource("multiple.test-zone.example.org", "lb3.loadbalancer.com", "", "", "ingress/default/my-ingress").WithSetIdentifier("test-set-3"),
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-1"),
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerResource("tar.test-zone.example.org", "new-tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2"),
|
||||
newEndpointWithOwnerResource("multiple.test-zone.example.org", "new.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
}
|
||||
expected := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", "", "owner", "ingress/default/my-ingress"),
|
||||
newEndpointWithOwner("new-record-1-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwnerResource("multiple.test-zone.example.org", "lb3.loadbalancer.com", "", "owner", "ingress/default/my-ingress").WithSetIdentifier("test-set-3"),
|
||||
newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-3"),
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
|
||||
newEndpointWithOwner("foobar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "lb1.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-1"),
|
||||
newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerResource("tar.test-zone.example.org", "new-tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2"),
|
||||
newEndpointWithOwner("tar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwnerResource("multiple.test-zone.example.org", "new.loadbalancer.com", endpoint.RecordTypeCNAME, "owner", "ingress/default/my-ingress-2").WithSetIdentifier("test-set-2"),
|
||||
newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress-2\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
UpdateOld: []*endpoint.Endpoint{
|
||||
newEndpointWithOwner("tar.test-zone.example.org", "tar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
|
||||
newEndpointWithOwner("tar-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "owner").WithSetIdentifier("test-set-2"),
|
||||
newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
}
|
||||
p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
|
||||
mExpected := map[string][]*endpoint.Endpoint{
|
||||
"Create": expected.Create,
|
||||
"UpdateNew": expected.UpdateNew,
|
||||
"UpdateOld": expected.UpdateOld,
|
||||
"Delete": expected.Delete,
|
||||
}
|
||||
mGot := map[string][]*endpoint.Endpoint{
|
||||
"Create": got.Create,
|
||||
"UpdateNew": got.UpdateNew,
|
||||
"UpdateOld": got.UpdateOld,
|
||||
"Delete": got.Delete,
|
||||
}
|
||||
assert.True(t, testutils.SamePlanChanges(mGot, mExpected))
|
||||
assert.Equal(t, nil, ctx.Value(provider.RecordsContextKey))
|
||||
}
|
||||
err := r.ApplyChanges(ctx, changes)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func testTXTRegistryApplyChangesNoPrefix(t *testing.T) {
|
||||
p := provider.NewInMemoryProvider()
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
ctxEndpoints := []*endpoint.Endpoint{}
|
||||
ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
|
||||
@ -363,7 +572,7 @@ func testTXTRegistryApplyChangesNoPrefix(t *testing.T) {
|
||||
newEndpointWithOwner("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour)
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -485,11 +694,9 @@ func newEndpointWithOwner(dnsName, target, recordType, ownerID string) *endpoint
|
||||
func newEndpointWithOwnerAndLabels(dnsName, target, recordType, ownerID string, labels endpoint.Labels) *endpoint.Endpoint {
|
||||
e := endpoint.NewEndpoint(dnsName, recordType, target)
|
||||
e.Labels[endpoint.OwnerLabelKey] = ownerID
|
||||
if labels != nil {
|
||||
for k, v := range labels {
|
||||
e.Labels[k] = v
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
|
||||
@ -17,8 +17,8 @@ limitations under the License.
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
cfclient "github.com/cloudfoundry-community/go-cfclient"
|
||||
|
||||
@ -36,7 +36,7 @@ func NewCloudFoundrySource(cfClient *cfclient.Client) (Source, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rs *cloudfoundrySource) AddEventHandler(handler func() error, stopChan <-chan struct{}, minInterval time.Duration) {
|
||||
func (rs *cloudfoundrySource) AddEventHandler(ctx context.Context, handler func()) {
|
||||
}
|
||||
|
||||
// Endpoints returns endpoint objects
|
||||
|
||||
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"net"
|
||||
"time"
|
||||
@ -65,5 +66,5 @@ func (cs *connectorSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (cs *connectorSource) AddEventHandler(handler func() error, stopChan <-chan struct{}, minInterval time.Duration) {
|
||||
func (cs *connectorSource) AddEventHandler(ctx context.Context, handler func()) {
|
||||
}
|
||||
|
||||
@ -17,10 +17,10 @@ limitations under the License.
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -92,7 +92,7 @@ func NewCRDClientForAPIVersionKind(client kubernetes.Interface, kubeConfig, kube
|
||||
|
||||
config.ContentConfig.GroupVersion = &groupVersion
|
||||
config.APIPath = "/apis"
|
||||
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}
|
||||
config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}
|
||||
|
||||
crdClient, err := rest.UnversionedRESTClientFor(config)
|
||||
if err != nil {
|
||||
@ -112,7 +112,7 @@ func NewCRDSource(crdClient rest.Interface, namespace, kind string, annotationFi
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cs *crdSource) AddEventHandler(handler func() error, stopChan <-chan struct{}, minInterval time.Duration) {
|
||||
func (cs *crdSource) AddEventHandler(ctx context.Context, handler func()) {
|
||||
}
|
||||
|
||||
// Endpoints returns endpoint objects.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user