mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-11-29 17:01:01 +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:
|
linters-settings:
|
||||||
|
exhaustive:
|
||||||
|
default-signifies-exhaustive: false
|
||||||
|
goimports:
|
||||||
|
local-prefixes: github.com/kubernetes-sigs/external-dns
|
||||||
golint:
|
golint:
|
||||||
min-confidence: 0.9
|
min-confidence: 0.9
|
||||||
|
maligned:
|
||||||
gocyclo:
|
suggest-new: true
|
||||||
min-complexity: 15
|
misspell:
|
||||||
|
locale: US
|
||||||
|
|
||||||
linters:
|
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
|
disable-all: true
|
||||||
enable:
|
enable:
|
||||||
|
- deadcode
|
||||||
|
- depguard
|
||||||
|
- dogsled
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- golint
|
||||||
|
- goprintffuncname
|
||||||
|
- gosimple
|
||||||
- govet
|
- govet
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- golint
|
|
||||||
- goimports
|
|
||||||
- misspell
|
|
||||||
- unconvert
|
|
||||||
- megacheck
|
|
||||||
- interfacer
|
- 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
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- "1.13.x"
|
- "1.14.x"
|
||||||
- tip
|
- tip
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- go: tip
|
- go: tip
|
||||||
|
|
||||||
env:
|
cache:
|
||||||
- GOLANGCI_RELEASE="v1.23.1"
|
directories:
|
||||||
|
- $GOPATH/pkg/mod
|
||||||
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}
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- make test
|
- 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
|
- 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
|
- travis_wait 20 roveralls
|
||||||
- goveralls -coverprofile=roveralls.coverprofile -service=travis-ci
|
- 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
|
## v0.7.1 - 2020-04-01
|
||||||
|
|
||||||
- Prometheus metric: timestamp of last successful sync with the DNS provider (#1480) @njuettner
|
- 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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
FROM golang:1.13 as builder
|
FROM golang:1.14 as builder
|
||||||
|
|
||||||
WORKDIR /sigs.k8s.io/external-dns
|
WORKDIR /sigs.k8s.io/external-dns
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@ -31,14 +31,14 @@ cover-html: cover
|
|||||||
|
|
||||||
# Run all the linters
|
# Run all the linters
|
||||||
lint:
|
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
|
# The verify target runs tasks similar to the CI tasks, but without code coverage
|
||||||
.PHONY: verify test
|
.PHONY: verify test
|
||||||
|
|
||||||
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
|
# The build targets allow to build the binary and docker image
|
||||||
.PHONY: build build.docker build.mini
|
.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.
|
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
|
## The Latest Release: v0.7
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@ -112,6 +113,10 @@ type Controller struct {
|
|||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
// The DomainFilter defines which DNS records to keep or exclude
|
// The DomainFilter defines which DNS records to keep or exclude
|
||||||
DomainFilter endpoint.DomainFilter
|
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.
|
// RunOnce runs a single iteration of a reconciliation loop.
|
||||||
@ -139,6 +144,7 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
|||||||
Current: records,
|
Current: records,
|
||||||
Desired: endpoints,
|
Desired: endpoints,
|
||||||
DomainFilter: c.DomainFilter,
|
DomainFilter: c.DomainFilter,
|
||||||
|
PropertyComparator: c.Registry.PropertyValuesEqual,
|
||||||
}
|
}
|
||||||
|
|
||||||
plan = plan.Calculate()
|
plan = plan.Calculate()
|
||||||
@ -154,18 +160,39 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs RunOnce in a loop with a delay until stopChan receives a value.
|
// MinInterval is used as window for batching events
|
||||||
func (c *Controller) Run(ctx context.Context, stopChan <-chan struct{}) {
|
const MinInterval = 5 * time.Second
|
||||||
ticker := time.NewTicker(c.Interval)
|
|
||||||
|
// 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()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
err := c.RunOnce(ctx)
|
if c.ShouldRunOnce(time.Now()) {
|
||||||
if err != nil {
|
if err := c.RunOnce(ctx); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
case <-stopChan:
|
case <-ctx.Done():
|
||||||
log.Info("Terminating main controller loop")
|
log.Info("Terminating main controller loop")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import (
|
|||||||
|
|
||||||
// mockProvider returns mock endpoints and validates changes.
|
// mockProvider returns mock endpoints and validates changes.
|
||||||
type mockProvider struct {
|
type mockProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
RecordsStore []*endpoint.Endpoint
|
RecordsStore []*endpoint.Endpoint
|
||||||
ExpectChanges *plan.Changes
|
ExpectChanges *plan.Changes
|
||||||
}
|
}
|
||||||
@ -153,43 +154,41 @@ func TestRunOnce(t *testing.T) {
|
|||||||
source.AssertExpectations(t)
|
source.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSourceEventHandler tests that the Controller can use a Source's registered handler as a callback.
|
func TestShouldRunOnce(t *testing.T) {
|
||||||
func TestSourceEventHandler(t *testing.T) {
|
ctrl := &Controller{Interval: 10 * time.Minute}
|
||||||
source := new(testutils.MockSource)
|
|
||||||
|
|
||||||
handlerCh := make(chan bool)
|
now := time.Now()
|
||||||
timeoutCh := make(chan bool, 1)
|
|
||||||
stopChan := make(chan struct{}, 1)
|
|
||||||
|
|
||||||
ctrl := &Controller{
|
// First run of Run loop should execute RunOnce
|
||||||
Source: source,
|
assert.True(t, ctrl.ShouldRunOnce(now))
|
||||||
Registry: nil,
|
|
||||||
Policy: &plan.SyncPolicy{},
|
// Second run should not
|
||||||
}
|
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||||
|
|
||||||
// Define and register a simple handler that sends a message to a channel to show it was called.
|
now = now.Add(10 * time.Second)
|
||||||
handler := func() error {
|
// Changes happen in ingresses or services
|
||||||
handlerCh <- true
|
ctrl.ScheduleRunOnce(now)
|
||||||
return nil
|
ctrl.ScheduleRunOnce(now)
|
||||||
}
|
|
||||||
// Example of preventing handler from being called more than once every 5 seconds.
|
// Because we batch changes, ShouldRunOnce returns False at first
|
||||||
ctrl.Source.AddEventHandler(handler, stopChan, 5*time.Second)
|
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||||
|
assert.False(t, ctrl.ShouldRunOnce(now.Add(100*time.Microsecond)))
|
||||||
// Send timeout message after 10 seconds to fail test if handler is not called.
|
|
||||||
go func() {
|
// But after MinInterval we should run reconciliation
|
||||||
time.Sleep(10 * time.Second)
|
now = now.Add(MinInterval)
|
||||||
timeoutCh <- true
|
assert.True(t, ctrl.ShouldRunOnce(now))
|
||||||
}()
|
|
||||||
|
// But just one time
|
||||||
// Wait until we either receive a message from handlerCh or timeoutCh channel after 10 seconds.
|
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||||
select {
|
|
||||||
case msg := <-handlerCh:
|
// We should wait maximum possible time after last reconciliation started
|
||||||
assert.True(t, msg)
|
now = now.Add(10*time.Minute - time.Second)
|
||||||
case <-timeoutCh:
|
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||||
assert.Fail(t, "timed out waiting for event handler to be called")
|
|
||||||
}
|
// After exactly Interval it's OK again to reconcile
|
||||||
|
now = now.Add(time.Second)
|
||||||
close(stopChan)
|
assert.True(t, ctrl.ShouldRunOnce(now))
|
||||||
close(handlerCh)
|
|
||||||
close(timeoutCh)
|
// But not two times
|
||||||
|
assert.False(t, ctrl.ShouldRunOnce(now))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,7 +51,7 @@ PRs welcome!
|
|||||||
|
|
||||||
Notes
|
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
|
### AWS Provider
|
||||||
The AWS Provider overrides the value to 300s when the TTL is 0.
|
The AWS Provider overrides the value to 300s when the TTL is 0.
|
||||||
|
|||||||
@ -36,9 +36,6 @@ spec:
|
|||||||
app: external-dns
|
app: external-dns
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: Recreate
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: external-dns
|
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
@ -102,9 +99,6 @@ spec:
|
|||||||
app: external-dns
|
app: external-dns
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: Recreate
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: external-dns
|
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
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 nginx.yaml
|
||||||
$ kubectl delete service -f externaldns.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.
|
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:
|
A simple deploy could look like this:
|
||||||
### Manifest (for clusters without RBAC enabled)
|
### Manifest (for clusters without RBAC enabled)
|
||||||
@ -17,7 +17,7 @@ A simple deploy could look like this:
|
|||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: exeternal-dns
|
name: external-dns
|
||||||
spec:
|
spec:
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: Recreate
|
||||||
@ -81,7 +81,7 @@ subjects:
|
|||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: exeternal-dns
|
name: external-dns
|
||||||
spec:
|
spec:
|
||||||
strategy:
|
strategy:
|
||||||
type: Recreate
|
type: Recreate
|
||||||
@ -111,7 +111,7 @@ spec:
|
|||||||
|
|
||||||
### Kafka Stateful Set
|
### 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
|
```yaml
|
||||||
apiVersion: apps/v1beta1
|
apiVersion: apps/v1beta1
|
||||||
@ -155,7 +155,7 @@ spec:
|
|||||||
requests:
|
requests:
|
||||||
storage: 500Gi
|
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
|
### 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 without RBAC enabled)
|
||||||
* Manifest (for clusters with RBAC enabled)
|
* Manifest (for clusters with RBAC enabled)
|
||||||
* Update existing Existing DNS Deployment
|
* Update existing ExternalDNS Deployment
|
||||||
|
|
||||||
### Manifest (for clusters without RBAC enabled)
|
### Manifest (for clusters without RBAC enabled)
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ kind: ServiceAccount
|
|||||||
metadata:
|
metadata:
|
||||||
name: external-dns
|
name: external-dns
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
name: external-dns
|
name: external-dns
|
||||||
@ -66,7 +66,7 @@ rules:
|
|||||||
resources: ["gateways"]
|
resources: ["gateways"]
|
||||||
verbs: ["get","watch","list"]
|
verbs: ["get","watch","list"]
|
||||||
---
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
metadata:
|
metadata:
|
||||||
name: external-dns-viewer
|
name: external-dns-viewer
|
||||||
@ -110,7 +110,7 @@ spec:
|
|||||||
- --txt-owner-id=my-identifier
|
- --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.
|
* 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.
|
* 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.
|
**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
|
* 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
|
```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.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="Desired change: CREATE httpbin.example.com 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="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"
|
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 {
|
func (e *Endpoint) WithSetIdentifier(setIdentifier string) *Endpoint {
|
||||||
e.SetIdentifier = setIdentifier
|
e.SetIdentifier = setIdentifier
|
||||||
return e
|
return e
|
||||||
|
|||||||
57
go.mod
57
go.mod
@ -3,46 +3,46 @@ module sigs.k8s.io/external-dns
|
|||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
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/azure-sdk-for-go v36.0.0+incompatible
|
||||||
github.com/Azure/go-autorest/autorest v0.9.0
|
github.com/Azure/go-autorest/autorest v0.9.4
|
||||||
github.com/Azure/go-autorest/autorest/adal v0.6.0
|
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/azure/auth v0.0.0-00010101000000-000000000000
|
||||||
github.com/Azure/go-autorest/autorest/to v0.3.0
|
github.com/Azure/go-autorest/autorest/to v0.3.0
|
||||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11
|
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.11
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
|
||||||
github.com/alecthomas/colour v0.1.0 // indirect
|
github.com/alecthomas/colour v0.1.0 // indirect
|
||||||
github.com/alecthomas/kingpin v2.2.5+incompatible
|
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/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/cloudflare/cloudflare-go v0.10.1
|
||||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
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/digitalocean/godo v1.34.0
|
||||||
github.com/dnaeon/go-vcr v1.0.1 // indirect
|
github.com/dnsimple/dnsimple-go v0.60.0
|
||||||
github.com/dnsimple/dnsimple-go v0.14.0
|
|
||||||
github.com/exoscale/egoscale v0.18.1
|
github.com/exoscale/egoscale v0.18.1
|
||||||
github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99
|
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/gobs/pretty v0.0.0-20180724170744-09732c25a95b // indirect
|
||||||
github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f
|
github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||||
github.com/gophercloud/gophercloud v0.1.0
|
github.com/gophercloud/gophercloud v0.1.0
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
github.com/heptio/contour v0.15.0
|
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/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65
|
||||||
github.com/linki/instrumented_http v0.2.0
|
github.com/linki/instrumented_http v0.2.0
|
||||||
github.com/linode/linodego v0.3.0
|
github.com/linode/linodego v0.15.0
|
||||||
github.com/mattn/go-isatty v0.0.11 // indirect
|
github.com/maxatome/go-testdeep v1.4.0
|
||||||
github.com/miekg/dns v1.1.25
|
github.com/miekg/dns v1.1.25
|
||||||
github.com/nesv/go-dynect v0.6.0
|
github.com/nesv/go-dynect v0.6.0
|
||||||
github.com/nic-at/rc0go v1.1.0
|
github.com/nic-at/rc0go v1.1.0
|
||||||
github.com/openshift/api v0.0.0-20190322043348-8741ff068a47
|
github.com/openshift/api v0.0.0-20200302134843-001335d6cc34
|
||||||
github.com/openshift/client-go v3.9.0+incompatible
|
github.com/openshift/client-go v0.0.0-20200116145930-eb24d03d8420
|
||||||
github.com/oracle/oci-go-sdk v1.8.0
|
github.com/oracle/oci-go-sdk v1.8.0
|
||||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014
|
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014
|
||||||
github.com/pkg/errors v0.9.1
|
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/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0
|
||||||
github.com/sergi/go-diff v1.1.0 // indirect
|
github.com/sergi/go-diff v1.1.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.6.0
|
github.com/sirupsen/logrus v1.6.0
|
||||||
@ -50,22 +50,27 @@ require (
|
|||||||
github.com/smartystreets/gunit v1.1.1 // indirect
|
github.com/smartystreets/gunit v1.1.1 // indirect
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
github.com/terra-farm/udnssdk v1.3.5 // indirect
|
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/transip/gotransip v5.8.2+incompatible
|
||||||
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
|
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
|
||||||
github.com/vinyldns/go-vinyldns v0.0.0-20190611170422-7119fe55ed92
|
github.com/vinyldns/go-vinyldns v0.0.0-20190611170422-7119fe55ed92
|
||||||
github.com/vultr/govultr v0.3.2
|
github.com/vultr/govultr v0.3.2
|
||||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875
|
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
|
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/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1
|
||||||
gopkg.in/yaml.v2 v2.2.5
|
gopkg.in/yaml.v2 v2.2.8
|
||||||
istio.io/api v0.0.0-20190820204432-483f2547d882
|
istio.io/api v0.0.0-20200324230725-4b064f75ad8f
|
||||||
istio.io/istio v0.0.0-20190322063008-2b1331886076
|
istio.io/client-go v0.0.0-20200324231043-96a582576da1
|
||||||
k8s.io/api v0.0.0-20190620084959-7cf5895f2711
|
k8s.io/api v0.17.5
|
||||||
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719
|
k8s.io/apimachinery v0.17.5
|
||||||
k8s.io/client-go v10.0.0+incompatible
|
k8s.io/client-go v0.17.5
|
||||||
k8s.io/klog v0.3.1
|
k8s.io/klog v1.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
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/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/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
|
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
|
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].RecordType <= b[j].RecordType
|
||||||
}
|
}
|
||||||
return b[i].Targets.String() <= b[j].Targets.String()
|
return b[i].Targets.String() <= b[j].Targets.String()
|
||||||
|
|
||||||
}
|
}
|
||||||
return false
|
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
|
package testutils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
@ -40,21 +41,18 @@ func (m *MockSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
|||||||
return endpoints.([]*endpoint.Endpoint), args.Error(1)
|
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.
|
// AddEventHandler adds an event handler that should be triggered if something in source changes
|
||||||
func (m *MockSource) AddEventHandler(handler func() error, stopChan <-chan struct{}, minInterval time.Duration) {
|
func (m *MockSource) AddEventHandler(ctx context.Context, handler func()) {
|
||||||
// Execute callback handler no more than once per minInterval, until a message on stopChan is received.
|
|
||||||
go func() {
|
go func() {
|
||||||
var lastCallbackTime time.Time
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-stopChan:
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
default:
|
case <-ticker.C:
|
||||||
now := time.Now()
|
|
||||||
if now.After(lastCallbackTime.Add(minInterval)) {
|
|
||||||
handler()
|
handler()
|
||||||
lastCallbackTime = time.Now()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
132
main.go
132
main.go
@ -28,6 +28,32 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
_ "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/controller"
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
@ -63,12 +89,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
log.SetLevel(ll)
|
log.SetLevel(ll)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
stopChan := make(chan struct{}, 1)
|
|
||||||
|
|
||||||
go serveMetrics(cfg.MetricsAddress)
|
go serveMetrics(cfg.MetricsAddress)
|
||||||
go handleSigterm(stopChan)
|
go handleSigterm(cancel)
|
||||||
|
|
||||||
// Create a source.Config from the flags passed by the user.
|
// Create a source.Config from the flags passed by the user.
|
||||||
sourceCfg := &source.Config{
|
sourceCfg := &source.Config{
|
||||||
@ -123,8 +147,8 @@ func main() {
|
|||||||
var p provider.Provider
|
var p provider.Provider
|
||||||
switch cfg.Provider {
|
switch cfg.Provider {
|
||||||
case "akamai":
|
case "akamai":
|
||||||
p = provider.NewAkamaiProvider(
|
p = akamai.NewAkamaiProvider(
|
||||||
provider.AkamaiConfig{
|
akamai.AkamaiConfig{
|
||||||
DomainFilter: domainFilter,
|
DomainFilter: domainFilter,
|
||||||
ZoneIDFilter: zoneIDFilter,
|
ZoneIDFilter: zoneIDFilter,
|
||||||
ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain,
|
ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain,
|
||||||
@ -135,10 +159,10 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
case "alibabacloud":
|
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":
|
case "aws":
|
||||||
p, err = provider.NewAWSProvider(
|
p, err = aws.NewAWSProvider(
|
||||||
provider.AWSConfig{
|
aws.AWSConfig{
|
||||||
DomainFilter: domainFilter,
|
DomainFilter: domainFilter,
|
||||||
ZoneIDFilter: zoneIDFilter,
|
ZoneIDFilter: zoneIDFilter,
|
||||||
ZoneTypeFilter: zoneTypeFilter,
|
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)
|
log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry)
|
||||||
cfg.Registry = "aws-sd"
|
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":
|
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":
|
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":
|
case "vinyldns":
|
||||||
p, err = provider.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||||
case "vultr":
|
case "vultr":
|
||||||
p, err = provider.NewVultrProvider(domainFilter, cfg.DryRun)
|
p, err = provider.NewVultrProvider(domainFilter, cfg.DryRun)
|
||||||
|
|
||||||
case "ultradns":
|
case "ultradns":
|
||||||
p, err = provider.NewUltraDNSProvider(domainFilter, cfg.DryRun )
|
p, err = provider.NewUltraDNSProvider(domainFilter, cfg.DryRun )
|
||||||
|
|
||||||
case "cloudflare":
|
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":
|
case "rcodezero":
|
||||||
p, err = provider.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt)
|
p, err = rcode0.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt)
|
||||||
case "google":
|
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":
|
case "digitalocean":
|
||||||
p, err = provider.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun)
|
p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
|
||||||
case "ovh":
|
case "ovh":
|
||||||
p, err = provider.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.DryRun)
|
p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.DryRun)
|
||||||
case "linode":
|
case "linode":
|
||||||
p, err = provider.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version)
|
p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version)
|
||||||
case "dnsimple":
|
case "dnsimple":
|
||||||
p, err = provider.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||||
case "infoblox":
|
case "infoblox":
|
||||||
p, err = provider.NewInfobloxProvider(
|
p, err = infoblox.NewInfobloxProvider(
|
||||||
provider.InfobloxConfig{
|
infoblox.InfobloxConfig{
|
||||||
DomainFilter: domainFilter,
|
DomainFilter: domainFilter,
|
||||||
ZoneIDFilter: zoneIDFilter,
|
ZoneIDFilter: zoneIDFilter,
|
||||||
Host: cfg.InfobloxGridHost,
|
Host: cfg.InfobloxGridHost,
|
||||||
@ -202,8 +224,8 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
case "dyn":
|
case "dyn":
|
||||||
p, err = provider.NewDynProvider(
|
p, err = dyn.NewDynProvider(
|
||||||
provider.DynConfig{
|
dyn.DynConfig{
|
||||||
DomainFilter: domainFilter,
|
DomainFilter: domainFilter,
|
||||||
ZoneIDFilter: zoneIDFilter,
|
ZoneIDFilter: zoneIDFilter,
|
||||||
DryRun: cfg.DryRun,
|
DryRun: cfg.DryRun,
|
||||||
@ -215,29 +237,29 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
case "coredns", "skydns":
|
case "coredns", "skydns":
|
||||||
p, err = provider.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
|
p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
|
||||||
case "rdns":
|
case "rdns":
|
||||||
p, err = provider.NewRDNSProvider(
|
p, err = rdns.NewRDNSProvider(
|
||||||
provider.RDNSConfig{
|
rdns.RDNSConfig{
|
||||||
DomainFilter: domainFilter,
|
DomainFilter: domainFilter,
|
||||||
DryRun: cfg.DryRun,
|
DryRun: cfg.DryRun,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
case "exoscale":
|
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":
|
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":
|
case "designate":
|
||||||
p, err = provider.NewDesignateProvider(domainFilter, cfg.DryRun)
|
p, err = designate.NewDesignateProvider(domainFilter, cfg.DryRun)
|
||||||
case "pdns":
|
case "pdns":
|
||||||
p, err = provider.NewPDNSProvider(
|
p, err = pdns.NewPDNSProvider(
|
||||||
ctx,
|
ctx,
|
||||||
provider.PDNSConfig{
|
pdns.PDNSConfig{
|
||||||
DomainFilter: domainFilter,
|
DomainFilter: domainFilter,
|
||||||
DryRun: cfg.DryRun,
|
DryRun: cfg.DryRun,
|
||||||
Server: cfg.PDNSServer,
|
Server: cfg.PDNSServer,
|
||||||
APIKey: cfg.PDNSAPIKey,
|
APIKey: cfg.PDNSAPIKey,
|
||||||
TLSConfig: provider.TLSConfig{
|
TLSConfig: pdns.TLSConfig{
|
||||||
TLSEnabled: cfg.PDNSTLSEnabled,
|
TLSEnabled: cfg.PDNSTLSEnabled,
|
||||||
CAFilePath: cfg.TLSCA,
|
CAFilePath: cfg.TLSCA,
|
||||||
ClientCertFilePath: cfg.TLSClientCert,
|
ClientCertFilePath: cfg.TLSClientCert,
|
||||||
@ -246,16 +268,16 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
case "oci":
|
case "oci":
|
||||||
var config *provider.OCIConfig
|
var config *oci.OCIConfig
|
||||||
config, err = provider.LoadOCIConfig(cfg.OCIConfigFile)
|
config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
p, err = provider.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
|
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
|
||||||
}
|
}
|
||||||
case "rfc2136":
|
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":
|
case "ns1":
|
||||||
p, err = provider.NewNS1Provider(
|
p, err = ns1.NewNS1Provider(
|
||||||
provider.NS1Config{
|
ns1.NS1Config{
|
||||||
DomainFilter: domainFilter,
|
DomainFilter: domainFilter,
|
||||||
ZoneIDFilter: zoneIDFilter,
|
ZoneIDFilter: zoneIDFilter,
|
||||||
NS1Endpoint: cfg.NS1Endpoint,
|
NS1Endpoint: cfg.NS1Endpoint,
|
||||||
@ -264,7 +286,7 @@ func main() {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
case "transip":
|
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:
|
default:
|
||||||
log.Fatalf("unknown dns provider: %s", cfg.Provider)
|
log.Fatalf("unknown dns provider: %s", cfg.Provider)
|
||||||
}
|
}
|
||||||
@ -277,9 +299,9 @@ func main() {
|
|||||||
case "noop":
|
case "noop":
|
||||||
r, err = registry.NewNoopRegistry(p)
|
r, err = registry.NewNoopRegistry(p)
|
||||||
case "txt":
|
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":
|
case "aws-sd":
|
||||||
r, err = registry.NewAWSSDRegistry(p.(*provider.AWSSDProvider), cfg.TXTOwnerID)
|
r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("unknown registry: %s", cfg.Registry)
|
log.Fatalf("unknown registry: %s", cfg.Registry)
|
||||||
}
|
}
|
||||||
@ -301,13 +323,6 @@ func main() {
|
|||||||
DomainFilter: domainFilter,
|
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 {
|
if cfg.Once {
|
||||||
err := ctrl.RunOnce(ctx)
|
err := ctrl.RunOnce(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -316,15 +331,24 @@ func main() {
|
|||||||
|
|
||||||
os.Exit(0)
|
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)
|
signals := make(chan os.Signal, 1)
|
||||||
signal.Notify(signals, syscall.SIGTERM)
|
signal.Notify(signals, syscall.SIGTERM)
|
||||||
<-signals
|
<-signals
|
||||||
log.Info("Received SIGTERM. Terminating...")
|
log.Info("Received SIGTERM. Terminating...")
|
||||||
close(stopChan)
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveMetrics(address string) {
|
func serveMetrics(address string) {
|
||||||
|
|||||||
@ -109,6 +109,7 @@ type Config struct {
|
|||||||
Registry string
|
Registry string
|
||||||
TXTOwnerID string
|
TXTOwnerID string
|
||||||
TXTPrefix string
|
TXTPrefix string
|
||||||
|
TXTSuffix string
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
Once bool
|
Once bool
|
||||||
DryRun bool
|
DryRun bool
|
||||||
@ -139,6 +140,7 @@ type Config struct {
|
|||||||
NS1IgnoreSSL bool
|
NS1IgnoreSSL bool
|
||||||
TransIPAccountName string
|
TransIPAccountName string
|
||||||
TransIPPrivateKeyFile string
|
TransIPPrivateKeyFile string
|
||||||
|
DigitalOceanAPIPageSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultConfig = &Config{
|
var defaultConfig = &Config{
|
||||||
@ -205,6 +207,7 @@ var defaultConfig = &Config{
|
|||||||
Registry: "txt",
|
Registry: "txt",
|
||||||
TXTOwnerID: "default",
|
TXTOwnerID: "default",
|
||||||
TXTPrefix: "",
|
TXTPrefix: "",
|
||||||
|
TXTSuffix: "",
|
||||||
TXTCacheInterval: 0,
|
TXTCacheInterval: 0,
|
||||||
Interval: time.Minute,
|
Interval: time.Minute,
|
||||||
Once: false,
|
Once: false,
|
||||||
@ -235,6 +238,7 @@ var defaultConfig = &Config{
|
|||||||
NS1IgnoreSSL: false,
|
NS1IgnoreSSL: false,
|
||||||
TransIPAccountName: "",
|
TransIPAccountName: "",
|
||||||
TransIPPrivateKeyFile: "",
|
TransIPPrivateKeyFile: "",
|
||||||
|
DigitalOceanAPIPageSize: 50,
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns new Config object
|
// 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("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-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("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
|
// 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)
|
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
|
// 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("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-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
|
// 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)
|
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,
|
RcodezeroTXTEncrypt: false,
|
||||||
TransIPAccountName: "",
|
TransIPAccountName: "",
|
||||||
TransIPPrivateKeyFile: "",
|
TransIPPrivateKeyFile: "",
|
||||||
|
DigitalOceanAPIPageSize: 50,
|
||||||
}
|
}
|
||||||
|
|
||||||
overriddenConfig = &Config{
|
overriddenConfig = &Config{
|
||||||
@ -177,6 +178,7 @@ var (
|
|||||||
NS1IgnoreSSL: true,
|
NS1IgnoreSSL: true,
|
||||||
TransIPAccountName: "transip",
|
TransIPAccountName: "transip",
|
||||||
TransIPPrivateKeyFile: "/path/to/transip.key",
|
TransIPPrivateKeyFile: "/path/to/transip.key",
|
||||||
|
DigitalOceanAPIPageSize: 100,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -280,6 +282,7 @@ func TestParseFlags(t *testing.T) {
|
|||||||
"--ns1-ignoressl",
|
"--ns1-ignoressl",
|
||||||
"--transip-account=transip",
|
"--transip-account=transip",
|
||||||
"--transip-keyfile=/path/to/transip.key",
|
"--transip-keyfile=/path/to/transip.key",
|
||||||
|
"--digitalocean-api-page-size=100",
|
||||||
},
|
},
|
||||||
envVars: map[string]string{},
|
envVars: map[string]string{},
|
||||||
expected: overriddenConfig,
|
expected: overriddenConfig,
|
||||||
@ -364,6 +367,7 @@ func TestParseFlags(t *testing.T) {
|
|||||||
"EXTERNAL_DNS_NS1_IGNORESSL": "1",
|
"EXTERNAL_DNS_NS1_IGNORESSL": "1",
|
||||||
"EXTERNAL_DNS_TRANSIP_ACCOUNT": "transip",
|
"EXTERNAL_DNS_TRANSIP_ACCOUNT": "transip",
|
||||||
"EXTERNAL_DNS_TRANSIP_KEYFILE": "/path/to/transip.key",
|
"EXTERNAL_DNS_TRANSIP_KEYFILE": "/path/to/transip.key",
|
||||||
|
"EXTERNAL_DNS_DIGITALOCEAN_API_PAGE_SIZE": "100",
|
||||||
},
|
},
|
||||||
expected: overriddenConfig,
|
expected: overriddenConfig,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -91,5 +91,10 @@ func ValidateConfig(cfg *externaldns.Config) error {
|
|||||||
if cfg.IgnoreHostnameAnnotation && cfg.FQDNTemplate == "" {
|
if cfg.IgnoreHostnameAnnotation && cfg.FQDNTemplate == "" {
|
||||||
return errors.New("FQDN Template must be set if ignoring annotations")
|
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
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"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,
|
// Plan can convert a list of desired and current records to a series of create,
|
||||||
// update and delete actions.
|
// update and delete actions.
|
||||||
type Plan struct {
|
type Plan struct {
|
||||||
@ -37,6 +41,8 @@ type Plan struct {
|
|||||||
Changes *Changes
|
Changes *Changes
|
||||||
// DomainFilter matches DNS names
|
// DomainFilter matches DNS names
|
||||||
DomainFilter endpoint.DomainFilter
|
DomainFilter endpoint.DomainFilter
|
||||||
|
// Property comparator compares custom properties of providers
|
||||||
|
PropertyComparator PropertyComparator
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changes holds lists of actions to be executed by dns providers
|
// 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
|
if row.current != nil && len(row.candidates) > 0 { //dns name is taken
|
||||||
update := t.resolver.ResolveUpdate(row.current, row.candidates)
|
update := t.resolver.ResolveUpdate(row.current, row.candidates)
|
||||||
// compare "update" to "current" to figure out if actual update is required
|
// 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)
|
inheritOwner(row.current, update)
|
||||||
changes.UpdateNew = append(changes.UpdateNew, update)
|
changes.UpdateNew = append(changes.UpdateNew, update)
|
||||||
changes.UpdateOld = append(changes.UpdateOld, row.current)
|
changes.UpdateOld = append(changes.UpdateOld, row.current)
|
||||||
@ -178,10 +184,15 @@ func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool {
|
|||||||
return desired.RecordTTL != current.RecordTTL
|
return desired.RecordTTL != current.RecordTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool {
|
func (p *Plan) shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool {
|
||||||
if current.ProviderSpecific == nil && len(desired.ProviderSpecific) == 0 {
|
desiredProperties := map[string]endpoint.ProviderSpecificProperty{}
|
||||||
return false
|
|
||||||
|
if desired.ProviderSpecific != nil {
|
||||||
|
for _, d := range desired.ProviderSpecific {
|
||||||
|
desiredProperties[d.Name] = d
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if current.ProviderSpecific != nil {
|
||||||
for _, c := range current.ProviderSpecific {
|
for _, c := range current.ProviderSpecific {
|
||||||
// don't consider target health when detecting changes
|
// don't consider target health when detecting changes
|
||||||
// see: https://github.com/kubernetes-sigs/external-dns/issues/869#issuecomment-458576954
|
// see: https://github.com/kubernetes-sigs/external-dns/issues/869#issuecomment-458576954
|
||||||
@ -189,33 +200,23 @@ func shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
found := false
|
if d, ok := desiredProperties[c.Name]; ok {
|
||||||
for _, d := range desired.ProviderSpecific {
|
if p.PropertyComparator != nil {
|
||||||
if d.Name == c.Name {
|
if !p.PropertyComparator(c.Name, c.Value, d.Value) {
|
||||||
if d.Value != c.Value {
|
|
||||||
// provider-specific attribute updated
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
found = true
|
} else if c.Value != d.Value {
|
||||||
break
|
return true
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if p.PropertyComparator != nil {
|
||||||
|
if !p.PropertyComparator(c.Name, c.Value, "") {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
if !found {
|
} else if c.Value != "" {
|
||||||
// provider-specific attribute deleted
|
|
||||||
return true
|
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
|
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
|
bar127AWithTTL *endpoint.Endpoint
|
||||||
bar127AWithProviderSpecificTrue *endpoint.Endpoint
|
bar127AWithProviderSpecificTrue *endpoint.Endpoint
|
||||||
bar127AWithProviderSpecificFalse *endpoint.Endpoint
|
bar127AWithProviderSpecificFalse *endpoint.Endpoint
|
||||||
|
bar127AWithProviderSpecificUnset *endpoint.Endpoint
|
||||||
bar192A *endpoint.Endpoint
|
bar192A *endpoint.Endpoint
|
||||||
multiple1 *endpoint.Endpoint
|
multiple1 *endpoint.Endpoint
|
||||||
multiple2 *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{
|
suite.bar192A = &endpoint.Endpoint{
|
||||||
DNSName: "bar",
|
DNSName: "bar",
|
||||||
Targets: endpoint.Targets{"192.168.0.1"},
|
Targets: endpoint.Targets{"192.168.0.1"},
|
||||||
@ -291,6 +301,54 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificChange() {
|
|||||||
validateEntries(suite.T(), changes.Delete, expectedDelete)
|
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() {
|
func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() {
|
||||||
current := []*endpoint.Endpoint{suite.fooV1Cname}
|
current := []*endpoint.Endpoint{suite.fooV1Cname}
|
||||||
desired := []*endpoint.Endpoint{suite.fooV2Cname}
|
desired := []*endpoint.Endpoint{suite.fooV2Cname}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package akamai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -30,6 +30,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type akamaiClient interface {
|
type akamaiClient interface {
|
||||||
@ -50,7 +51,7 @@ func (*akamaiOpenClient) Do(config edgegrid.Config, req *http.Request) (*http.Re
|
|||||||
// AkamaiConfig clarifies the method signature
|
// AkamaiConfig clarifies the method signature
|
||||||
type AkamaiConfig struct {
|
type AkamaiConfig struct {
|
||||||
DomainFilter endpoint.DomainFilter
|
DomainFilter endpoint.DomainFilter
|
||||||
ZoneIDFilter ZoneIDFilter
|
ZoneIDFilter provider.ZoneIDFilter
|
||||||
ServiceConsumerDomain string
|
ServiceConsumerDomain string
|
||||||
ClientToken string
|
ClientToken string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
@ -60,8 +61,9 @@ type AkamaiConfig struct {
|
|||||||
|
|
||||||
// AkamaiProvider implements the DNS provider for Akamai.
|
// AkamaiProvider implements the DNS provider for Akamai.
|
||||||
type AkamaiProvider struct {
|
type AkamaiProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
config edgegrid.Config
|
config edgegrid.Config
|
||||||
dryRun bool
|
dryRun bool
|
||||||
client akamaiClient
|
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.
|
// ApplyChanges applies a given set of changes in a given zone.
|
||||||
func (p *AkamaiProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
func (p *AkamaiProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
zones, err := p.fetchZones()
|
zones, err := p.fetchZones()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("No zones to fetch endpoints from!")
|
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 {
|
for _, endpoint := range endpoints {
|
||||||
|
|
||||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
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)
|
log.Debugf("Skipping creation at Akamai of endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
|
||||||
continue
|
continue
|
||||||
@ -320,9 +321,8 @@ func (p *AkamaiProvider) createRecords(zoneNameIDMapper zoneIDName, endpoints []
|
|||||||
return created, failed
|
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 {
|
for _, endpoint := range endpoints {
|
||||||
|
|
||||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
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)
|
log.Debugf("Skipping deletion at Akamai of endpoint: '%s' type: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
|
||||||
continue
|
continue
|
||||||
@ -349,9 +349,8 @@ func (p *AkamaiProvider) deleteRecords(zoneNameIDMapper zoneIDName, endpoints []
|
|||||||
return deleted, failed
|
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 {
|
for _, endpoint := range endpoints {
|
||||||
|
|
||||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
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)
|
log.Debugf("Skipping update at Akamai of endpoint DNSName: '%s' RecordType: '%s', it does not match against Domain filters", endpoint.DNSName, endpoint.RecordType)
|
||||||
continue
|
continue
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package akamai
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockAkamaiClient struct {
|
type mockAkamaiClient struct {
|
||||||
@ -92,7 +93,7 @@ func TestRequestError(t *testing.T) {
|
|||||||
|
|
||||||
func TestFetchZonesZoneIDFilter(t *testing.T) {
|
func TestFetchZonesZoneIDFilter(t *testing.T) {
|
||||||
config := AkamaiConfig{
|
config := AkamaiConfig{
|
||||||
ZoneIDFilter: NewZoneIDFilter([]string{"Test"}),
|
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Test"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &mockAkamaiClient{}
|
client := &mockAkamaiClient{}
|
||||||
@ -109,7 +110,7 @@ func TestFetchZonesZoneIDFilter(t *testing.T) {
|
|||||||
func TestFetchZonesEmpty(t *testing.T) {
|
func TestFetchZonesEmpty(t *testing.T) {
|
||||||
config := AkamaiConfig{
|
config := AkamaiConfig{
|
||||||
DomainFilter: endpoint.NewDomainFilter([]string{"Nonexistent"}),
|
DomainFilter: endpoint.NewDomainFilter([]string{"Nonexistent"}),
|
||||||
ZoneIDFilter: NewZoneIDFilter([]string{"Nonexistent"}),
|
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Nonexistent"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &mockAkamaiClient{}
|
client := &mockAkamaiClient{}
|
||||||
@ -171,7 +172,7 @@ func TestAkamaiRecords(t *testing.T) {
|
|||||||
|
|
||||||
func TestAkamaiRecordsEmpty(t *testing.T) {
|
func TestAkamaiRecordsEmpty(t *testing.T) {
|
||||||
config := AkamaiConfig{
|
config := AkamaiConfig{
|
||||||
ZoneIDFilter: NewZoneIDFilter([]string{"Nonexistent"}),
|
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Nonexistent"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &mockAkamaiClient{}
|
client := &mockAkamaiClient{}
|
||||||
@ -185,7 +186,7 @@ func TestAkamaiRecordsEmpty(t *testing.T) {
|
|||||||
func TestAkamaiRecordsFilters(t *testing.T) {
|
func TestAkamaiRecordsFilters(t *testing.T) {
|
||||||
config := AkamaiConfig{
|
config := AkamaiConfig{
|
||||||
DomainFilter: endpoint.NewDomainFilter([]string{"www.exclude.me"}),
|
DomainFilter: endpoint.NewDomainFilter([]string{"www.exclude.me"}),
|
||||||
ZoneIDFilter: NewZoneIDFilter([]string{"Exclude-Me"}),
|
ZoneIDFilter: provider.NewZoneIDFilter([]string{"Exclude-Me"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &mockAkamaiClient{}
|
client := &mockAkamaiClient{}
|
||||||
@ -208,7 +209,7 @@ func TestCreateRecords(t *testing.T) {
|
|||||||
c := NewAkamaiProvider(config)
|
c := NewAkamaiProvider(config)
|
||||||
c.client = client
|
c.client = client
|
||||||
|
|
||||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||||
endpoints := make([]*endpoint.Endpoint, 0)
|
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.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"))
|
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 := NewAkamaiProvider(config)
|
||||||
c.client = client
|
c.client = client
|
||||||
|
|
||||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||||
endpoints := make([]*endpoint.Endpoint, 0)
|
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.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"))
|
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 := NewAkamaiProvider(config)
|
||||||
c.client = client
|
c.client = client
|
||||||
|
|
||||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||||
endpoints := make([]*endpoint.Endpoint, 0)
|
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.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"))
|
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 := NewAkamaiProvider(config)
|
||||||
c.client = client
|
c.client = client
|
||||||
|
|
||||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||||
endpoints := make([]*endpoint.Endpoint, 0)
|
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.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"))
|
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 := NewAkamaiProvider(config)
|
||||||
c.client = client
|
c.client = client
|
||||||
|
|
||||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||||
endpoints := make([]*endpoint.Endpoint, 0)
|
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.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"))
|
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 := NewAkamaiProvider(config)
|
||||||
c.client = client
|
c.client = client
|
||||||
|
|
||||||
zoneNameIDMapper := zoneIDName{"example.com": "example.com"}
|
zoneNameIDMapper := provider.ZoneIDName{"example.com": "example.com"}
|
||||||
endpoints := make([]*endpoint.Endpoint, 0)
|
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.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"))
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package alibabacloud
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -33,6 +33,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -66,8 +67,9 @@ type AlibabaCloudPrivateZoneAPI interface {
|
|||||||
|
|
||||||
// AlibabaCloudProvider implements the DNS provider for Alibaba Cloud.
|
// AlibabaCloudProvider implements the DNS provider for Alibaba Cloud.
|
||||||
type AlibabaCloudProvider struct {
|
type AlibabaCloudProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter // Private Zone only
|
zoneIDFilter provider.ZoneIDFilter // Private Zone only
|
||||||
MaxChangeCount int
|
MaxChangeCount int
|
||||||
EvaluateTargetHealth bool
|
EvaluateTargetHealth bool
|
||||||
AssumeRole string
|
AssumeRole string
|
||||||
@ -93,22 +95,22 @@ type alibabaCloudConfig struct {
|
|||||||
// NewAlibabaCloudProvider creates a new Alibaba Cloud provider.
|
// NewAlibabaCloudProvider creates a new Alibaba Cloud provider.
|
||||||
//
|
//
|
||||||
// Returns the provider or an error if a provider could not be created.
|
// 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{}
|
cfg := alibabaCloudConfig{}
|
||||||
if configFile != "" {
|
if configFile != "" {
|
||||||
contents, err := ioutil.ReadFile(configFile)
|
contents, err := ioutil.ReadFile(configFile)
|
||||||
if err != nil {
|
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)
|
err = yaml.Unmarshal(contents, &cfg)
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
var tmpError error
|
var tmpError error
|
||||||
cfg, tmpError = getCloudConfigFromStsToken()
|
cfg, tmpError = getCloudConfigFromStsToken()
|
||||||
if tmpError != nil {
|
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 := ""
|
roleName := ""
|
||||||
var err error
|
var err error
|
||||||
if roleName, err = m.RoleName(); err != nil {
|
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()
|
vpcID, err := m.VpcID()
|
||||||
if err != nil {
|
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()
|
regionID, err := m.Region()
|
||||||
if err != nil {
|
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)
|
role, err := m.RamRoleToken(roleName)
|
||||||
if err != nil {
|
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.RegionID = regionID
|
||||||
cfg.RoleName = roleName
|
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.
|
// Returns the current records or an error if the operation failed.
|
||||||
func (p *AlibabaCloudProvider) recordsForDNS() (endpoints []*endpoint.Endpoint, _ error) {
|
func (p *AlibabaCloudProvider) recordsForDNS() (endpoints []*endpoint.Endpoint, _ error) {
|
||||||
|
|
||||||
records, err := p.records()
|
records, err := p.records()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -344,7 +345,6 @@ func (p *AlibabaCloudProvider) recordsForDNS() (endpoints []*endpoint.Endpoint,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getNextPageNumber(pageNumber, pageSize, totalCount int) int {
|
func getNextPageNumber(pageNumber, pageSize, totalCount int) int {
|
||||||
|
|
||||||
if pageNumber*pageSize >= totalCount {
|
if pageNumber*pageSize >= totalCount {
|
||||||
return 0
|
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) {
|
func (p *AlibabaCloudProvider) groupRecords(records []alidns.Record) (endpointMap map[string][]alidns.Record) {
|
||||||
|
|
||||||
endpointMap = make(map[string][]alidns.Record)
|
endpointMap = make(map[string][]alidns.Record)
|
||||||
|
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
|
|
||||||
key := p.getRecordKey(record)
|
key := p.getRecordKey(record)
|
||||||
|
|
||||||
recordList := endpointMap[key]
|
recordList := endpointMap[key]
|
||||||
endpointMap[key] = append(recordList, record)
|
endpointMap[key] = append(recordList, record)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpointMap
|
return endpointMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,18 +444,15 @@ func (p *AlibabaCloudProvider) getDomainRecords(domainName string) ([]alidns.Rec
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, record := range response.DomainRecords.Record {
|
for _, record := range response.DomainRecords.Record {
|
||||||
|
|
||||||
domainName := record.DomainName
|
domainName := record.DomainName
|
||||||
recordType := record.Type
|
recordType := record.Type
|
||||||
|
|
||||||
if !p.domainFilter.Match(domainName) {
|
if !p.domainFilter.Match(domainName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !provider.SupportedRecordType(recordType) {
|
||||||
if !supportedRecordType(recordType) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO filter Locked record
|
//TODO filter Locked record
|
||||||
results = append(results, 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 {
|
func (p *AlibabaCloudProvider) updateRecords(recordMap map[string][]alidns.Record, endpoints []*endpoint.Endpoint) error {
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
key := p.getRecordKeyByEndpoint(endpoint)
|
key := p.getRecordKeyByEndpoint(endpoint)
|
||||||
records := recordMap[key]
|
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) {
|
func (p *AlibabaCloudProvider) splitDNSName(endpoint *endpoint.Endpoint) (rr string, domain string) {
|
||||||
|
|
||||||
name := strings.TrimSuffix(endpoint.DNSName, ".")
|
name := strings.TrimSuffix(endpoint.DNSName, ".")
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
@ -727,7 +717,6 @@ func (p *AlibabaCloudProvider) matchVPC(zoneID string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *AlibabaCloudProvider) privateZones() ([]pvtz.Zone, error) {
|
func (p *AlibabaCloudProvider) privateZones() ([]pvtz.Zone, error) {
|
||||||
|
|
||||||
var zones []pvtz.Zone
|
var zones []pvtz.Zone
|
||||||
|
|
||||||
request := pvtz.CreateDescribeZonesRequest()
|
request := pvtz.CreateDescribeZonesRequest()
|
||||||
@ -782,7 +771,6 @@ func (p *AlibabaCloudProvider) getPrivateZones() (map[string]*alibabaPrivateZone
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, zone := range zones {
|
for _, zone := range zones {
|
||||||
|
|
||||||
request := pvtz.CreateDescribeZoneRecordsRequest()
|
request := pvtz.CreateDescribeZoneRecordsRequest()
|
||||||
request.ZoneId = zone.ZoneId
|
request.ZoneId = zone.ZoneId
|
||||||
request.PageSize = requests.NewInteger(defaultAlibabaCloudPageSize)
|
request.PageSize = requests.NewInteger(defaultAlibabaCloudPageSize)
|
||||||
@ -799,10 +787,9 @@ func (p *AlibabaCloudProvider) getPrivateZones() (map[string]*alibabaPrivateZone
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, record := range response.Records.Record {
|
for _, record := range response.Records.Record {
|
||||||
|
|
||||||
recordType := record.Type
|
recordType := record.Type
|
||||||
|
|
||||||
if !supportedRecordType(recordType) {
|
if !provider.SupportedRecordType(recordType) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -829,7 +816,6 @@ func (p *AlibabaCloudProvider) getPrivateZones() (map[string]*alibabaPrivateZone
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *AlibabaCloudProvider) groupPrivateZoneRecords(zone *alibabaPrivateZone) (endpointMap map[string][]pvtz.Record) {
|
func (p *AlibabaCloudProvider) groupPrivateZoneRecords(zone *alibabaPrivateZone) (endpointMap map[string][]pvtz.Record) {
|
||||||
|
|
||||||
endpointMap = make(map[string][]pvtz.Record)
|
endpointMap = make(map[string][]pvtz.Record)
|
||||||
|
|
||||||
for _, record := range zone.records {
|
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.
|
// Returns the current records or an error if the operation failed.
|
||||||
func (p *AlibabaCloudProvider) privateZoneRecords() (endpoints []*endpoint.Endpoint, _ error) {
|
func (p *AlibabaCloudProvider) privateZoneRecords() (endpoints []*endpoint.Endpoint, _ error) {
|
||||||
|
|
||||||
zones, err := p.getPrivateZones()
|
zones, err := p.getPrivateZones()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -879,7 +864,7 @@ func (p *AlibabaCloudProvider) createPrivateZoneRecord(zones map[string]*alibaba
|
|||||||
rr, domain := p.splitDNSName(endpoint)
|
rr, domain := p.splitDNSName(endpoint)
|
||||||
zone := zones[domain]
|
zone := zones[domain]
|
||||||
if zone == nil {
|
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)
|
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
|
return err
|
||||||
}
|
}
|
||||||
@ -925,7 +910,6 @@ func (p *AlibabaCloudProvider) createPrivateZoneRecords(zones map[string]*alibab
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *AlibabaCloudProvider) deletePrivateZoneRecord(recordID int) error {
|
func (p *AlibabaCloudProvider) deletePrivateZoneRecord(recordID int) error {
|
||||||
|
|
||||||
if p.dryRun {
|
if p.dryRun {
|
||||||
log.Infof("Dry run: Delete record id '%d' in Alibaba Cloud Private Zone", recordID)
|
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]
|
zone := zones[domain]
|
||||||
if zone == nil {
|
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)
|
log.Errorf("Failed to delete %s record named '%s' for Alibaba Cloud Private Zone: %v", endpoint.RecordType, endpoint.DNSName, err)
|
||||||
continue
|
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 {
|
func (p *AlibabaCloudProvider) updatePrivateZoneRecords(zones map[string]*alibabaPrivateZone, endpoints []*endpoint.Endpoint) error {
|
||||||
|
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
rr, domain := p.splitDNSName(endpoint)
|
rr, domain := p.splitDNSName(endpoint)
|
||||||
zone := zones[domain]
|
zone := zones[domain]
|
||||||
if zone == nil {
|
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)
|
log.Errorf("Failed to update %s record named '%s' for Alibaba Cloud Private Zone: %v", endpoint.RecordType, endpoint.DNSName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package alibabacloud
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -34,6 +34,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -51,8 +52,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// see: https://docs.aws.amazon.com/general/latest/gr/rande.html#elb_region
|
// see: https://docs.aws.amazon.com/general/latest/gr/elb.html
|
||||||
// and: https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/using-govcloud-endpoints.html
|
|
||||||
canonicalHostedZones = map[string]string{
|
canonicalHostedZones = map[string]string{
|
||||||
// Application Load Balancers and Classic Load Balancers
|
// Application Load Balancers and Classic Load Balancers
|
||||||
"us-east-2.elb.amazonaws.com": "Z3AADJGX6KTTL2",
|
"us-east-2.elb.amazonaws.com": "Z3AADJGX6KTTL2",
|
||||||
@ -73,9 +73,10 @@ var (
|
|||||||
"eu-west-3.elb.amazonaws.com": "Z3Q77PNBQS71R4",
|
"eu-west-3.elb.amazonaws.com": "Z3Q77PNBQS71R4",
|
||||||
"eu-north-1.elb.amazonaws.com": "Z23TAZ6LKFMNIO",
|
"eu-north-1.elb.amazonaws.com": "Z23TAZ6LKFMNIO",
|
||||||
"sa-east-1.elb.amazonaws.com": "Z2P70J7HTTTPLU",
|
"sa-east-1.elb.amazonaws.com": "Z2P70J7HTTTPLU",
|
||||||
"cn-north-1.elb.amazonaws.com.cn": "Z3BX2TMKNYI13Y",
|
"cn-north-1.elb.amazonaws.com.cn": "Z1GDH35T77C1KE",
|
||||||
"cn-northwest-1.elb.amazonaws.com.cn": "Z3BX2TMKNYI13Y",
|
"cn-northwest-1.elb.amazonaws.com.cn": "ZM7IZAIOVVDZF",
|
||||||
"us-gov-west-1.amazonaws.com": "Z1K6XKP9SAGWDV",
|
"us-gov-west-1.elb.amazonaws.com": "Z33AYJ8TM3BH4J",
|
||||||
|
"us-gov-east-1.elb.amazonaws.com": "Z166TLBEWOO7G0",
|
||||||
"me-south-1.elb.amazonaws.com": "ZS929ML54UICD",
|
"me-south-1.elb.amazonaws.com": "ZS929ML54UICD",
|
||||||
// Network Load Balancers
|
// Network Load Balancers
|
||||||
"elb.us-east-2.amazonaws.com": "ZLMOA37VPKANP",
|
"elb.us-east-2.amazonaws.com": "ZLMOA37VPKANP",
|
||||||
@ -97,6 +98,8 @@ var (
|
|||||||
"elb.sa-east-1.amazonaws.com": "ZTK26PT1VY4CU",
|
"elb.sa-east-1.amazonaws.com": "ZTK26PT1VY4CU",
|
||||||
"elb.cn-north-1.amazonaws.com.cn": "Z3QFB96KMJ7ED6",
|
"elb.cn-north-1.amazonaws.com.cn": "Z3QFB96KMJ7ED6",
|
||||||
"elb.cn-northwest-1.amazonaws.com.cn": "ZQEIKTCZ8352D",
|
"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",
|
"elb.me-south-1.amazonaws.com": "Z3QSRYVP46NYYV",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -113,6 +116,7 @@ type Route53API interface {
|
|||||||
|
|
||||||
// AWSProvider is an implementation of Provider for AWS Route53.
|
// AWSProvider is an implementation of Provider for AWS Route53.
|
||||||
type AWSProvider struct {
|
type AWSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client Route53API
|
client Route53API
|
||||||
dryRun bool
|
dryRun bool
|
||||||
batchChangeSize int
|
batchChangeSize int
|
||||||
@ -121,20 +125,20 @@ type AWSProvider struct {
|
|||||||
// only consider hosted zones managing domains ending in this suffix
|
// only consider hosted zones managing domains ending in this suffix
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
// filter hosted zones by id
|
// filter hosted zones by id
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
// filter hosted zones by type (e.g. private or public)
|
// filter hosted zones by type (e.g. private or public)
|
||||||
zoneTypeFilter ZoneTypeFilter
|
zoneTypeFilter provider.ZoneTypeFilter
|
||||||
// filter hosted zones by tags
|
// filter hosted zones by tags
|
||||||
zoneTagFilter ZoneTagFilter
|
zoneTagFilter provider.ZoneTagFilter
|
||||||
preferCNAME bool
|
preferCNAME bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// AWSConfig contains configuration to create a new AWS provider.
|
// AWSConfig contains configuration to create a new AWS provider.
|
||||||
type AWSConfig struct {
|
type AWSConfig struct {
|
||||||
DomainFilter endpoint.DomainFilter
|
DomainFilter endpoint.DomainFilter
|
||||||
ZoneIDFilter ZoneIDFilter
|
ZoneIDFilter provider.ZoneIDFilter
|
||||||
ZoneTypeFilter ZoneTypeFilter
|
ZoneTypeFilter provider.ZoneTypeFilter
|
||||||
ZoneTagFilter ZoneTagFilter
|
ZoneTagFilter provider.ZoneTagFilter
|
||||||
BatchChangeSize int
|
BatchChangeSize int
|
||||||
BatchChangeInterval time.Duration
|
BatchChangeInterval time.Duration
|
||||||
EvaluateTargetHealth bool
|
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.
|
// TODO(linki, ownership): Remove once ownership system is in place.
|
||||||
// See: https://github.com/kubernetes-sigs/external-dns/pull/122/files/74e2c3d3e237411e619aefc5aab694742001cdec#r109863370
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,7 +381,7 @@ func (p *AWSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
records, ok := ctx.Value(RecordsContextKey).([]*endpoint.Endpoint)
|
records, ok := ctx.Value(provider.RecordsContextKey).([]*endpoint.Endpoint)
|
||||||
if !ok {
|
if !ok {
|
||||||
var err error
|
var err error
|
||||||
records, err = p.records(ctx, zones)
|
records, err = p.records(ctx, zones)
|
||||||
@ -449,7 +453,7 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes []*route53.Chan
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(failedZones) > 0 {
|
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
|
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 {
|
func batchChangeSet(cs []*route53.Change, batchSize int) [][]*route53.Change {
|
||||||
if len(cs) <= batchSize {
|
if len(cs) <= batchSize {
|
||||||
return [][]*route53.Change{cs}
|
res := sortChangesByActionNameType(cs)
|
||||||
|
return [][]*route53.Change{res}
|
||||||
}
|
}
|
||||||
|
|
||||||
batchChanges := make([][]*route53.Change, 0)
|
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 {
|
func sortChangesByActionNameType(cs []*route53.Change) []*route53.Change {
|
||||||
sort.SliceStable(cs, func(i, j int) bool {
|
sort.SliceStable(cs, func(i, j int) bool {
|
||||||
if *cs[i].Action < *cs[j].Action {
|
if *cs[i].Action > *cs[j].Action {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if *cs[i].Action > *cs[j].Action {
|
if *cs[i].Action < *cs[j].Action {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if *cs[i].ResourceRecordSet.Name < *cs[j].ResourceRecordSet.Name {
|
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 {
|
for _, c := range changeSet {
|
||||||
hostname := ensureTrailingDot(aws.StringValue(c.ResourceRecordSet.Name))
|
hostname := provider.EnsureTrailingDot(aws.StringValue(c.ResourceRecordSet.Name))
|
||||||
|
|
||||||
zones := suitableZones(hostname, zones)
|
zones := suitableZones(hostname, zones)
|
||||||
if len(zones) == 0 {
|
if len(zones) == 0 {
|
||||||
@ -733,7 +738,6 @@ func isAWSAlias(ep *endpoint.Endpoint, addrs []*endpoint.Endpoint) string {
|
|||||||
if hostedZone := canonicalHostedZone(addr.Targets[0]); hostedZone != "" {
|
if hostedZone := canonicalHostedZone(addr.Targets[0]); hostedZone != "" {
|
||||||
return hostedZone
|
return hostedZone
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -35,6 +35,7 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/internal/testutils"
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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 {
|
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 {
|
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) {
|
switch aws.StringValue(change.Action) {
|
||||||
case route53.ChangeActionCreate:
|
case route53.ChangeActionCreate:
|
||||||
if _, found := recordSets[key]; found {
|
if _, found := recordSets[key]; found {
|
||||||
@ -287,17 +288,17 @@ func TestAWSZones(t *testing.T) {
|
|||||||
|
|
||||||
for _, ti := range []struct {
|
for _, ti := range []struct {
|
||||||
msg string
|
msg string
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
zoneTypeFilter ZoneTypeFilter
|
zoneTypeFilter provider.ZoneTypeFilter
|
||||||
zoneTagFilter ZoneTagFilter
|
zoneTagFilter provider.ZoneTagFilter
|
||||||
expectedZones map[string]*route53.HostedZone
|
expectedZones map[string]*route53.HostedZone
|
||||||
}{
|
}{
|
||||||
{"no filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), NewZoneTagFilter([]string{}), allZones},
|
{"no filter", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), provider.NewZoneTagFilter([]string{}), allZones},
|
||||||
{"public filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter("public"), NewZoneTagFilter([]string{}), publicZones},
|
{"public filter", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter("public"), provider.NewZoneTagFilter([]string{}), publicZones},
|
||||||
{"private filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter("private"), NewZoneTagFilter([]string{}), privateZones},
|
{"private filter", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter("private"), provider.NewZoneTagFilter([]string{}), privateZones},
|
||||||
{"unknown filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter("unknown"), NewZoneTagFilter([]string{}), noZones},
|
{"unknown filter", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter("unknown"), provider.NewZoneTagFilter([]string{}), noZones},
|
||||||
{"zone id filter", NewZoneIDFilter([]string{"/hostedzone/zone-3.ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), NewZoneTagFilter([]string{}), privateZones},
|
{"zone id filter", provider.NewZoneIDFilter([]string{"/hostedzone/zone-3.ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneTypeFilter(""), provider.NewZoneTagFilter([]string{}), privateZones},
|
||||||
{"tag filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), NewZoneTagFilter([]string{"zone=3"}), 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{})
|
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) {
|
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-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("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"),
|
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) {
|
func TestAWSCreateRecords(t *testing.T) {
|
||||||
customTTL := endpoint.TTL(60)
|
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{
|
records := []*endpoint.Endpoint{
|
||||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
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) {
|
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-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.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"),
|
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"),
|
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))
|
require.NoError(t, provider.DeleteRecords(context.Background(), originalEndpoints))
|
||||||
|
|
||||||
@ -441,12 +442,12 @@ func TestAWSApplyChanges(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
records, err := p.Records(ctx)
|
records, err := p.Records(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return context.WithValue(ctx, RecordsContextKey, records)
|
return context.WithValue(ctx, provider.RecordsContextKey, records)
|
||||||
}, 0},
|
}, 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
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("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("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"),
|
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"),
|
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{
|
createRecords := []*endpoint.Endpoint{
|
||||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
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) {
|
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 subnets = 16
|
||||||
const hosts = defaultBatchChangeSize / subnets
|
const hosts = defaultBatchChangeSize / subnets
|
||||||
|
|
||||||
@ -715,7 +716,7 @@ func TestAWSsubmitChanges(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAWSsubmitChangesError(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"))
|
clientStub.MockMethod("ChangeResourceRecordSets", mock.Anything).Return(nil, fmt.Errorf("Mock route53 failure"))
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -851,7 +852,7 @@ func validateAWSChangeRecord(t *testing.T, record *route53.Change, expected *rou
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAWSCreateRecordsWithCNAME(t *testing.T) {
|
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{
|
records := []*endpoint.Endpoint{
|
||||||
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Targets: endpoint.Targets{"foo.example.org"}, RecordType: endpoint.RecordTypeCNAME},
|
{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": 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
|
// Test dualstack and ipv4 load balancer targets
|
||||||
records := []*endpoint.Endpoint{
|
records := []*endpoint.Endpoint{
|
||||||
@ -1023,8 +1024,8 @@ func TestAWSCanonicalHostedZone(t *testing.T) {
|
|||||||
{"foo.eu-west-2.elb.amazonaws.com", "ZHURV8PSTC4K8"},
|
{"foo.eu-west-2.elb.amazonaws.com", "ZHURV8PSTC4K8"},
|
||||||
{"foo.eu-west-3.elb.amazonaws.com", "Z3Q77PNBQS71R4"},
|
{"foo.eu-west-3.elb.amazonaws.com", "Z3Q77PNBQS71R4"},
|
||||||
{"foo.sa-east-1.elb.amazonaws.com", "Z2P70J7HTTTPLU"},
|
{"foo.sa-east-1.elb.amazonaws.com", "Z2P70J7HTTTPLU"},
|
||||||
{"foo.cn-north-1.elb.amazonaws.com.cn", "Z3BX2TMKNYI13Y"},
|
{"foo.cn-north-1.elb.amazonaws.com.cn", "Z1GDH35T77C1KE"},
|
||||||
{"foo.cn-northwest-1.elb.amazonaws.com.cn", "Z3BX2TMKNYI13Y"},
|
{"foo.cn-northwest-1.elb.amazonaws.com.cn", "ZM7IZAIOVVDZF"},
|
||||||
// Network Load Balancers
|
// Network Load Balancers
|
||||||
{"foo.elb.us-east-2.amazonaws.com", "ZLMOA37VPKANP"},
|
{"foo.elb.us-east-2.amazonaws.com", "ZLMOA37VPKANP"},
|
||||||
{"foo.elb.us-east-1.amazonaws.com", "Z26RNL4JYFTOTI"},
|
{"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)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func newAWSProvider(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) {
|
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, NewZoneTagFilter([]string{}), evaluateTargetHealth, dryRun, records)
|
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()
|
client := NewRoute53APIStub()
|
||||||
|
|
||||||
provider := &AWSProvider{
|
provider := &AWSProvider{
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package awssd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -37,6 +37,7 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -73,6 +74,7 @@ type AWSSDClient interface {
|
|||||||
|
|
||||||
// AWSSDProvider is an implementation of Provider for AWS Cloud Map.
|
// AWSSDProvider is an implementation of Provider for AWS Cloud Map.
|
||||||
type AWSSDProvider struct {
|
type AWSSDProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client AWSSDClient
|
client AWSSDClient
|
||||||
dryRun bool
|
dryRun bool
|
||||||
// only consider namespaces ending in this suffix
|
// 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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package awssd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ type AWSSDClientStub struct {
|
|||||||
func (s *AWSSDClientStub) CreateService(input *sd.CreateServiceInput) (*sd.CreateServiceOutput, error) {
|
func (s *AWSSDClientStub) CreateService(input *sd.CreateServiceInput) (*sd.CreateServiceOutput, error) {
|
||||||
|
|
||||||
srv := &sd.Service{
|
srv := &sd.Service{
|
||||||
Id: aws.String(string(rand.Intn(10000))),
|
Id: aws.String(strconv.Itoa(rand.Intn(10000))),
|
||||||
DnsConfig: input.DnsConfig,
|
DnsConfig: input.DnsConfig,
|
||||||
Name: input.Name,
|
Name: input.Name,
|
||||||
Description: input.Description,
|
Description: input.Description,
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -34,6 +34,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -66,8 +67,9 @@ type RecordSetsClient interface {
|
|||||||
|
|
||||||
// AzureProvider implements the DNS provider for Microsoft's Azure cloud platform.
|
// AzureProvider implements the DNS provider for Microsoft's Azure cloud platform.
|
||||||
type AzureProvider struct {
|
type AzureProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
resourceGroup string
|
resourceGroup string
|
||||||
userAssignedIdentityClientID string
|
userAssignedIdentityClientID string
|
||||||
@ -78,7 +80,7 @@ type AzureProvider struct {
|
|||||||
// NewAzureProvider creates a new Azure provider.
|
// NewAzureProvider creates a new Azure provider.
|
||||||
//
|
//
|
||||||
// Returns the provider or an error if a provider could not be created.
|
// 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)
|
contents, err := ioutil.ReadFile(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
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
|
return true
|
||||||
}
|
}
|
||||||
recordType := strings.TrimPrefix(*recordSet.Type, "Microsoft.Network/dnszones/")
|
recordType := strings.TrimPrefix(*recordSet.Type, "Microsoft.Network/dnszones/")
|
||||||
if !supportedRecordType(recordType) {
|
if !provider.SupportedRecordType(recordType) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
name := formatAzureDNSName(*recordSet.Name, *zone.Name)
|
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{}
|
ignored := map[string]bool{}
|
||||||
deleted := azureChangeMap{}
|
deleted := azureChangeMap{}
|
||||||
updated := azureChangeMap{}
|
updated := azureChangeMap{}
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
if z.Name != nil {
|
if z.Name != nil {
|
||||||
zoneNameIDMapper.Add(*z.Name, *z.Name)
|
zoneNameIDMapper.Add(*z.Name, *z.Name)
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -29,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"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.
|
// 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
|
// AzurePrivateDNSProvider implements the DNS provider for Microsoft's Azure Private DNS service
|
||||||
type AzurePrivateDNSProvider struct {
|
type AzurePrivateDNSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
subscriptionID string
|
subscriptionID string
|
||||||
resourceGroup string
|
resourceGroup string
|
||||||
@ -57,7 +59,7 @@ type AzurePrivateDNSProvider struct {
|
|||||||
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
|
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
|
||||||
//
|
//
|
||||||
// Returns the provider or an error if a provider could not be created.
|
// 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()
|
authorizer, err := auth.NewAuthorizerFromEnvironment()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -208,7 +210,7 @@ func (p *AzurePrivateDNSProvider) mapChanges(zones []privatedns.PrivateZone, cha
|
|||||||
ignored := map[string]bool{}
|
ignored := map[string]bool{}
|
||||||
deleted := azurePrivateDNSChangeMap{}
|
deleted := azurePrivateDNSChangeMap{}
|
||||||
updated := azurePrivateDNSChangeMap{}
|
updated := azurePrivateDNSChangeMap{}
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
if z.Name != nil {
|
if z.Name != nil {
|
||||||
zoneNameIDMapper.Add(*z.Name, *z.Name)
|
zoneNameIDMapper.Add(*z.Name, *z.Name)
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -27,6 +27,11 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"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
|
// 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
|
// 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
|
// init zone-related parts of the mock-client
|
||||||
pageIterator := mockPrivateZoneListResultPageIterator{
|
pageIterator := mockPrivateZoneListResultPageIterator{
|
||||||
results: []privatedns.PrivateZoneListResult{
|
results: []privatedns.PrivateZoneListResult{
|
||||||
@ -236,7 +241,7 @@ func newMockedAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneID
|
|||||||
return newAzurePrivateDNSProvider(domainFilter, zoneIDFilter, dryRun, resourceGroup, &zonesClient, &recordSetsClient), nil
|
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{
|
return &AzurePrivateDNSProvider{
|
||||||
domainFilter: domainFilter,
|
domainFilter: domainFilter,
|
||||||
zoneIDFilter: zoneIDFilter,
|
zoneIDFilter: zoneIDFilter,
|
||||||
@ -248,7 +253,7 @@ func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAzurePrivateDNSRecord(t *testing.T) {
|
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{
|
&[]privatedns.PrivateZone{
|
||||||
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
|
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
|
||||||
},
|
},
|
||||||
@ -284,7 +289,7 @@ func TestAzurePrivateDNSRecord(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAzurePrivateDNSMultiRecord(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{
|
&[]privatedns.PrivateZone{
|
||||||
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
|
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
|
||||||
},
|
},
|
||||||
@ -383,7 +388,7 @@ func testAzurePrivateDNSApplyChangesInternal(t *testing.T, dryRun bool, client P
|
|||||||
|
|
||||||
provider := newAzurePrivateDNSProvider(
|
provider := newAzurePrivateDNSProvider(
|
||||||
endpoint.NewDomainFilter([]string{""}),
|
endpoint.NewDomainFilter([]string{""}),
|
||||||
NewZoneIDFilter([]string{""}),
|
provider.NewZoneIDFilter([]string{""}),
|
||||||
dryRun,
|
dryRun,
|
||||||
"group",
|
"group",
|
||||||
&zonesClient,
|
&zonesClient,
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package azure
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -30,6 +30,7 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/internal/testutils"
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"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
|
// 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
|
// 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
|
// init zone-related parts of the mock-client
|
||||||
pageIterator := mockZoneListResultPageIterator{
|
pageIterator := mockZoneListResultPageIterator{
|
||||||
results: []dns.ZoneListResult{
|
results: []dns.ZoneListResult{
|
||||||
@ -239,7 +240,7 @@ func newMockedAzureProvider(domainFilter endpoint.DomainFilter, zoneIDFilter Zon
|
|||||||
return newAzureProvider(domainFilter, zoneIDFilter, dryRun, resourceGroup, userAssignedIdentityClientID, &zonesClient, &recordSetsClient), nil
|
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{
|
return &AzureProvider{
|
||||||
domainFilter: domainFilter,
|
domainFilter: domainFilter,
|
||||||
zoneIDFilter: zoneIDFilter,
|
zoneIDFilter: zoneIDFilter,
|
||||||
@ -256,7 +257,7 @@ func validateAzureEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expect
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAzureRecord(t *testing.T) {
|
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{
|
&[]dns.Zone{
|
||||||
createMockZone("example.com", "/dnszones/example.com"),
|
createMockZone("example.com", "/dnszones/example.com"),
|
||||||
},
|
},
|
||||||
@ -293,7 +294,7 @@ func TestAzureRecord(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAzureMultiRecord(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{
|
&[]dns.Zone{
|
||||||
createMockZone("example.com", "/dnszones/example.com"),
|
createMockZone("example.com", "/dnszones/example.com"),
|
||||||
},
|
},
|
||||||
@ -393,7 +394,7 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordSetsC
|
|||||||
|
|
||||||
provider := newAzureProvider(
|
provider := newAzureProvider(
|
||||||
endpoint.NewDomainFilter([]string{""}),
|
endpoint.NewDomainFilter([]string{""}),
|
||||||
NewZoneIDFilter([]string{""}),
|
provider.NewZoneIDFilter([]string{""}),
|
||||||
dryRun,
|
dryRun,
|
||||||
"group",
|
"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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package cloudflare
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -29,6 +28,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
"sigs.k8s.io/external-dns/source"
|
"sigs.k8s.io/external-dns/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,6 +58,7 @@ type cloudFlareDNS interface {
|
|||||||
ZoneIDByName(zoneName string) (string, error)
|
ZoneIDByName(zoneName string) (string, error)
|
||||||
ListZones(zoneID ...string) ([]cloudflare.Zone, error)
|
ListZones(zoneID ...string) ([]cloudflare.Zone, error)
|
||||||
ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, 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)
|
DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error)
|
||||||
CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error)
|
CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error)
|
||||||
DeleteDNSRecord(zoneID, recordID string) 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...)
|
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.
|
// CloudFlareProvider is an implementation of Provider for CloudFlare DNS.
|
||||||
type CloudFlareProvider struct {
|
type CloudFlareProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
Client cloudFlareDNS
|
Client cloudFlareDNS
|
||||||
// only consider hosted zones managing domains ending in this suffix
|
// only consider hosted zones managing domains ending in this suffix
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
proxiedByDefault bool
|
proxiedByDefault bool
|
||||||
DryRun bool
|
DryRun bool
|
||||||
PaginationOptions cloudflare.PaginationOptions
|
PaginationOptions cloudflare.PaginationOptions
|
||||||
@ -112,11 +118,11 @@ type CloudFlareProvider struct {
|
|||||||
// cloudFlareChange differentiates between ChangActions
|
// cloudFlareChange differentiates between ChangActions
|
||||||
type cloudFlareChange struct {
|
type cloudFlareChange struct {
|
||||||
Action string
|
Action string
|
||||||
ResourceRecordSet []cloudflare.DNSRecord
|
ResourceRecord cloudflare.DNSRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
|
// 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
|
// initialize via chosen auth method and returns new API object
|
||||||
var (
|
var (
|
||||||
config *cloudflare.API
|
config *cloudflare.API
|
||||||
@ -150,6 +156,27 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
|
|||||||
result := []cloudflare.Zone{}
|
result := []cloudflare.Zone{}
|
||||||
p.PaginationOptions.Page = 1
|
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 {
|
for {
|
||||||
zonesResponse, err := p.Client.ListZonesContext(ctx, cloudflare.WithPagination(p.PaginationOptions))
|
zonesResponse, err := p.Client.ListZonesContext(ctx, cloudflare.WithPagination(p.PaginationOptions))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -158,10 +185,7 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
|
|||||||
|
|
||||||
for _, zone := range zonesResponse.Result {
|
for _, zone := range zonesResponse.Result {
|
||||||
if !p.domainFilter.Match(zone.Name) {
|
if !p.domainFilter.Match(zone.Name) {
|
||||||
continue
|
log.Debugf("zone %s not in domain filter", zone.Name)
|
||||||
}
|
|
||||||
|
|
||||||
if !p.zoneIDFilter.Match(zone.ID) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
result = append(result, zone)
|
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.
|
// ApplyChanges applies a given set of changes in a given zone.
|
||||||
func (p *CloudFlareProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
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)...)
|
for i, desired := range changes.UpdateNew {
|
||||||
combinedChanges = append(combinedChanges, newCloudFlareChanges(cloudFlareUpdate, changes.UpdateNew, proxiedByDefault)...)
|
current := changes.UpdateOld[i]
|
||||||
combinedChanges = append(combinedChanges, newCloudFlareChanges(cloudFlareDelete, changes.Delete, proxiedByDefault)...)
|
|
||||||
|
|
||||||
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.
|
// 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 {
|
for _, change := range changes {
|
||||||
logFields := log.Fields{
|
logFields := log.Fields{
|
||||||
"record": change.ResourceRecordSet[0].Name,
|
"record": change.ResourceRecord.Name,
|
||||||
"type": change.ResourceRecordSet[0].Type,
|
"type": change.ResourceRecord.Type,
|
||||||
"ttl": change.ResourceRecordSet[0].TTL,
|
"ttl": change.ResourceRecord.TTL,
|
||||||
"targets": len(change.ResourceRecordSet),
|
|
||||||
"action": change.Action,
|
"action": change.Action,
|
||||||
"zone": zoneID,
|
"zone": zoneID,
|
||||||
}
|
}
|
||||||
@ -245,35 +300,41 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
recordIDs := p.getRecordIDs(records, change.ResourceRecordSet[0])
|
if change.Action == cloudFlareUpdate {
|
||||||
|
recordID := p.getRecordID(records, change.ResourceRecord)
|
||||||
// to simplify bookkeeping for multiple records, an update is executed as delete+create
|
if recordID == "" {
|
||||||
if change.Action == cloudFlareDelete || change.Action == cloudFlareUpdate {
|
log.WithFields(logFields).Errorf("failed to find previous record: %v", change.ResourceRecord)
|
||||||
for _, recordID := range recordIDs {
|
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)
|
err := p.Client.DeleteDNSRecord(zoneID, recordID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(logFields).Errorf("failed to delete record: %v", err)
|
log.WithFields(logFields).Errorf("failed to delete record: %v", err)
|
||||||
}
|
}
|
||||||
}
|
} else if change.Action == cloudFlareCreate {
|
||||||
}
|
_, err := p.Client.CreateDNSRecord(zoneID, change.ResourceRecord)
|
||||||
|
|
||||||
if change.Action == cloudFlareCreate || change.Action == cloudFlareUpdate {
|
|
||||||
for _, record := range change.ResourceRecordSet {
|
|
||||||
_, err := p.Client.CreateDNSRecord(zoneID, record)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(logFields).Errorf("failed to create record: %v", err)
|
log.WithFields(logFields).Errorf("failed to create record: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// changesByZone separates a multi-zone change into a single change per zone.
|
// changesByZone separates a multi-zone change into a single change per zone.
|
||||||
func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []*cloudFlareChange) map[string][]*cloudFlareChange {
|
func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []*cloudFlareChange) map[string][]*cloudFlareChange {
|
||||||
changes := make(map[string][]*cloudFlareChange)
|
changes := make(map[string][]*cloudFlareChange)
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
|
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
zoneNameIDMapper.Add(z.ID, z.Name)
|
zoneNameIDMapper.Add(z.ID, z.Name)
|
||||||
@ -281,9 +342,9 @@ func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range changeSet {
|
for _, c := range changeSet {
|
||||||
zoneID, _ := zoneNameIDMapper.FindZone(c.ResourceRecordSet[0].Name)
|
zoneID, _ := zoneNameIDMapper.FindZone(c.ResourceRecord.Name)
|
||||||
if zoneID == "" {
|
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
|
continue
|
||||||
}
|
}
|
||||||
changes[zoneID] = append(changes[zoneID], c)
|
changes[zoneID] = append(changes[zoneID], c)
|
||||||
@ -292,51 +353,36 @@ func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []
|
|||||||
return changes
|
return changes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *CloudFlareProvider) getRecordIDs(records []cloudflare.DNSRecord, record cloudflare.DNSRecord) []string {
|
func (p *CloudFlareProvider) getRecordID(records []cloudflare.DNSRecord, record cloudflare.DNSRecord) string {
|
||||||
recordIDs := make([]string, 0)
|
|
||||||
for _, zoneRecord := range records {
|
for _, zoneRecord := range records {
|
||||||
if zoneRecord.Name == record.Name && zoneRecord.Type == record.Type {
|
if zoneRecord.Name == record.Name && zoneRecord.Type == record.Type && zoneRecord.Content == record.Content {
|
||||||
recordIDs = append(recordIDs, zoneRecord.ID)
|
return zoneRecord.ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(recordIDs)
|
return ""
|
||||||
return recordIDs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCloudFlareChanges returns a collection of Changes based on the given records and action.
|
func (p *CloudFlareProvider) newCloudFlareChange(action string, endpoint *endpoint.Endpoint, target string) *cloudFlareChange {
|
||||||
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 {
|
|
||||||
ttl := defaultCloudFlareRecordTTL
|
ttl := defaultCloudFlareRecordTTL
|
||||||
proxied := shouldBeProxied(endpoint, proxiedByDefault)
|
proxied := shouldBeProxied(endpoint, p.proxiedByDefault)
|
||||||
|
|
||||||
if endpoint.RecordTTL.IsConfigured() {
|
if endpoint.RecordTTL.IsConfigured() {
|
||||||
ttl = int(endpoint.RecordTTL)
|
ttl = int(endpoint.RecordTTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceRecordSet := make([]cloudflare.DNSRecord, len(endpoint.Targets))
|
if len(endpoint.Targets) > 1 {
|
||||||
|
log.Errorf("Updates should have just one target")
|
||||||
for i := range endpoint.Targets {
|
|
||||||
resourceRecordSet[i] = cloudflare.DNSRecord{
|
|
||||||
Name: endpoint.DNSName,
|
|
||||||
TTL: ttl,
|
|
||||||
Proxied: proxied,
|
|
||||||
Type: endpoint.RecordType,
|
|
||||||
Content: endpoint.Targets[i],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cloudFlareChange{
|
return &cloudFlareChange{
|
||||||
Action: action,
|
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{}
|
groups := map[string][]cloudflare.DNSRecord{}
|
||||||
|
|
||||||
for _, r := range records {
|
for _, r := range records {
|
||||||
if !supportedRecordType(r.Type) {
|
if !provider.SupportedRecordType(r.Type) {
|
||||||
continue
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package coredns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -35,6 +35,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -56,6 +57,7 @@ type coreDNSClient interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type coreDNSProvider struct {
|
type coreDNSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
dryRun bool
|
dryRun bool
|
||||||
coreDNSPrefix string
|
coreDNSPrefix string
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
@ -84,7 +86,7 @@ type Service struct {
|
|||||||
// answer.
|
// answer.
|
||||||
Group string `json:"group,omitempty"`
|
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:"-"`
|
Key string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,7 +246,7 @@ func newETCDClient() (coreDNSClient, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewCoreDNSProvider is a CoreDNS provider constructor
|
// 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()
|
client, err := newETCDClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -395,7 +397,6 @@ func (p coreDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
index := 0
|
index := 0
|
||||||
for _, ep := range group {
|
for _, ep := range group {
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package coredns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package designate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -35,6 +35,7 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/pkg/tlsutils"
|
"sigs.k8s.io/external-dns/pkg/tlsutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -226,6 +227,7 @@ func (c designateClient) DeleteRecordSet(zoneID, recordSetID string) error {
|
|||||||
|
|
||||||
// designate provider type
|
// designate provider type
|
||||||
type designateProvider struct {
|
type designateProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client designateClientInterface
|
client designateClientInterface
|
||||||
|
|
||||||
// only consider hosted zones managing domains ending in this suffix
|
// 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
|
// 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()
|
client, err := newDesignateClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package designate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -34,6 +34,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
var lastGeneratedDesignateID int32
|
var lastGeneratedDesignateID int32
|
||||||
@ -130,7 +131,7 @@ func (c fakeDesignateClient) DeleteRecordSet(zoneID, recordSetID string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c fakeDesignateClient) ToProvider() Provider {
|
func (c fakeDesignateClient) ToProvider() provider.Provider {
|
||||||
return &designateProvider{client: c}
|
return &designateProvider{client: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package digitalocean
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -44,9 +45,12 @@ const (
|
|||||||
|
|
||||||
// DigitalOceanProvider is an implementation of Provider for Digital Ocean's DNS.
|
// DigitalOceanProvider is an implementation of Provider for Digital Ocean's DNS.
|
||||||
type DigitalOceanProvider struct {
|
type DigitalOceanProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
Client godo.DomainsService
|
Client godo.DomainsService
|
||||||
// only consider hosted zones managing domains ending in this suffix
|
// only consider hosted zones managing domains ending in this suffix
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
|
// page size when querying paginated APIs
|
||||||
|
apiPageSize int
|
||||||
DryRun bool
|
DryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,10 +61,10 @@ type DigitalOceanChange struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDigitalOceanProvider initializes a new DigitalOcean DNS based Provider.
|
// 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")
|
token, ok := os.LookupEnv("DO_TOKEN")
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("No token found")
|
return nil, fmt.Errorf("no token found")
|
||||||
}
|
}
|
||||||
oauthClient := oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{
|
oauthClient := oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{
|
||||||
AccessToken: token,
|
AccessToken: token,
|
||||||
@ -70,6 +74,7 @@ func NewDigitalOceanProvider(ctx context.Context, domainFilter endpoint.DomainFi
|
|||||||
provider := &DigitalOceanProvider{
|
provider := &DigitalOceanProvider{
|
||||||
Client: client.Domains,
|
Client: client.Domains,
|
||||||
domainFilter: domainFilter,
|
domainFilter: domainFilter,
|
||||||
|
apiPageSize: apiPageSize,
|
||||||
DryRun: dryRun,
|
DryRun: dryRun,
|
||||||
}
|
}
|
||||||
return provider, nil
|
return provider, nil
|
||||||
@ -107,7 +112,7 @@ func (p *DigitalOceanProvider) Records(ctx context.Context) ([]*endpoint.Endpoin
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range records {
|
for _, r := range records {
|
||||||
if supportedRecordType(r.Type) {
|
if provider.SupportedRecordType(r.Type) {
|
||||||
name := r.Name + "." + zone.Name
|
name := r.Name + "." + zone.Name
|
||||||
|
|
||||||
// root name is identified by @ and should be
|
// 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) {
|
func (p *DigitalOceanProvider) fetchRecords(ctx context.Context, zoneName string) ([]godo.DomainRecord, error) {
|
||||||
allRecords := []godo.DomainRecord{}
|
allRecords := []godo.DomainRecord{}
|
||||||
listOptions := &godo.ListOptions{}
|
listOptions := &godo.ListOptions{PerPage: p.apiPageSize}
|
||||||
for {
|
for {
|
||||||
records, resp, err := p.Client.Records(ctx, zoneName, listOptions)
|
records, resp, err := p.Client.Records(ctx, zoneName, listOptions)
|
||||||
if err != nil {
|
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) {
|
func (p *DigitalOceanProvider) fetchZones(ctx context.Context) ([]godo.Domain, error) {
|
||||||
allZones := []godo.Domain{}
|
allZones := []godo.Domain{}
|
||||||
listOptions := &godo.ListOptions{}
|
listOptions := &godo.ListOptions{PerPage: p.apiPageSize}
|
||||||
for {
|
for {
|
||||||
zones, resp, err := p.Client.List(ctx, listOptions)
|
zones, resp, err := p.Client.List(ctx, listOptions)
|
||||||
if err != nil {
|
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.
|
// digitalOceanchangesByZone separates a multi-zone change into a single change per zone.
|
||||||
func digitalOceanChangesByZone(zones []godo.Domain, changeSet []*DigitalOceanChange) map[string][]*DigitalOceanChange {
|
func digitalOceanChangesByZone(zones []godo.Domain, changeSet []*DigitalOceanChange) map[string][]*DigitalOceanChange {
|
||||||
changes := make(map[string][]*DigitalOceanChange)
|
changes := make(map[string][]*DigitalOceanChange)
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
zoneNameIDMapper.Add(z.Name, z.Name)
|
zoneNameIDMapper.Add(z.Name, z.Name)
|
||||||
changes[z.Name] = []*DigitalOceanChange{}
|
changes[z.Name] = []*DigitalOceanChange{}
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package digitalocean
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -187,12 +187,12 @@ func TestDigitalOceanApplyChanges(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewDigitalOceanProvider(t *testing.T) {
|
func TestNewDigitalOceanProvider(t *testing.T) {
|
||||||
_ = os.Setenv("DO_TOKEN", "xxxxxxxxxxxxxxxxx")
|
_ = 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 {
|
if err != nil {
|
||||||
t.Errorf("should not fail, %s", err)
|
t.Errorf("should not fail, %s", err)
|
||||||
}
|
}
|
||||||
_ = os.Unsetenv("DO_TOKEN")
|
_ = 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 {
|
if err == nil {
|
||||||
t.Errorf("expected to fail")
|
t.Errorf("expected to fail")
|
||||||
}
|
}
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package dnsimple
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -25,70 +25,64 @@ import (
|
|||||||
|
|
||||||
"github.com/dnsimple/dnsimple-go/dnsimple"
|
"github.com/dnsimple/dnsimple-go/dnsimple"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"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/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dnsimpleRecordTTL = 3600 // Default TTL of 1 hour if not set (DNSimple's default)
|
const dnsimpleRecordTTL = 3600 // Default TTL of 1 hour if not set (DNSimple's default)
|
||||||
|
|
||||||
type identityService struct {
|
type dnsimpleIdentityService struct {
|
||||||
service *dnsimple.IdentityService
|
service *dnsimple.IdentityService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i identityService) Whoami() (*dnsimple.WhoamiResponse, error) {
|
func (i dnsimpleIdentityService) Whoami(ctx context.Context) (*dnsimple.WhoamiResponse, error) {
|
||||||
return i.service.Whoami()
|
return i.service.Whoami(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the account ID given dnsimple credentials
|
// dnsimpleZoneServiceInterface is an interface that contains all necessary zone services from DNSimple
|
||||||
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
|
|
||||||
type dnsimpleZoneServiceInterface interface {
|
type dnsimpleZoneServiceInterface interface {
|
||||||
ListZones(accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error)
|
ListZones(ctx context.Context, accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error)
|
||||||
ListRecords(accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error)
|
ListRecords(ctx context.Context, accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error)
|
||||||
CreateRecord(accountID string, zoneID string, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error)
|
CreateRecord(ctx context.Context, accountID string, zoneID string, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error)
|
||||||
DeleteRecord(accountID string, zoneID string, recordID int) (*dnsimple.ZoneRecordResponse, error)
|
DeleteRecord(ctx context.Context, accountID string, zoneID string, recordID int64) (*dnsimple.ZoneRecordResponse, error)
|
||||||
UpdateRecord(accountID string, zoneID string, recordID int, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error)
|
UpdateRecord(ctx context.Context, accountID string, zoneID string, recordID int64, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dnsimpleZoneService struct {
|
type dnsimpleZoneService struct {
|
||||||
service *dnsimple.ZonesService
|
service *dnsimple.ZonesService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z dnsimpleZoneService) ListZones(accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error) {
|
func (z dnsimpleZoneService) ListZones(ctx context.Context, accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error) {
|
||||||
return z.service.ListZones(accountID, options)
|
return z.service.ListZones(ctx, accountID, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z dnsimpleZoneService) ListRecords(accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error) {
|
func (z dnsimpleZoneService) ListRecords(ctx context.Context, accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error) {
|
||||||
return z.service.ListRecords(accountID, zoneID, options)
|
return z.service.ListRecords(ctx, accountID, zoneID, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z dnsimpleZoneService) CreateRecord(accountID string, zoneID string, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error) {
|
func (z dnsimpleZoneService) CreateRecord(ctx context.Context, accountID string, zoneID string, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
|
||||||
return z.service.CreateRecord(accountID, zoneID, recordAttributes)
|
return z.service.CreateRecord(ctx, accountID, zoneID, recordAttributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z dnsimpleZoneService) DeleteRecord(accountID string, zoneID string, recordID int) (*dnsimple.ZoneRecordResponse, error) {
|
func (z dnsimpleZoneService) DeleteRecord(ctx context.Context, accountID string, zoneID string, recordID int64) (*dnsimple.ZoneRecordResponse, error) {
|
||||||
return z.service.DeleteRecord(accountID, zoneID, recordID)
|
return z.service.DeleteRecord(ctx, accountID, zoneID, recordID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (z dnsimpleZoneService) UpdateRecord(accountID string, zoneID string, recordID int, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error) {
|
func (z dnsimpleZoneService) UpdateRecord(ctx context.Context, accountID string, zoneID string, recordID int64, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
|
||||||
return z.service.UpdateRecord(accountID, zoneID, recordID, recordAttributes)
|
return z.service.UpdateRecord(ctx, accountID, zoneID, recordID, recordAttributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dnsimpleProvider struct {
|
type dnsimpleProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client dnsimpleZoneServiceInterface
|
client dnsimpleZoneServiceInterface
|
||||||
identity identityService
|
identity dnsimpleIdentityService
|
||||||
accountID string
|
accountID string
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,35 +98,52 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewDnsimpleProvider initializes a new Dnsimple based provider
|
// 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")
|
oauthToken := os.Getenv("DNSIMPLE_OAUTH")
|
||||||
if len(oauthToken) == 0 {
|
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{
|
provider := &dnsimpleProvider{
|
||||||
client: dnsimpleZoneService{service: client.Zones},
|
client: dnsimpleZoneService{service: client.Zones},
|
||||||
identity: identityService{service: client.Identity},
|
identity: dnsimpleIdentityService{service: client.Identity},
|
||||||
domainFilter: domainFilter,
|
domainFilter: domainFilter,
|
||||||
zoneIDFilter: zoneIDFilter,
|
zoneIDFilter: zoneIDFilter,
|
||||||
dryRun: dryRun,
|
dryRun: dryRun,
|
||||||
}
|
}
|
||||||
whoamiResponse, err := provider.identity.service.Whoami()
|
|
||||||
|
whoamiResponse, err := provider.identity.Whoami(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
provider.accountID = strconv.Itoa(whoamiResponse.Data.Account.ID)
|
provider.accountID = int64ToString(whoamiResponse.Data.Account.ID)
|
||||||
return provider, nil
|
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
|
// 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)
|
zones := make(map[string]dnsimple.Zone)
|
||||||
page := 1
|
page := 1
|
||||||
listOptions := &dnsimple.ZoneListOptions{}
|
listOptions := &dnsimple.ZoneListOptions{}
|
||||||
for {
|
for {
|
||||||
listOptions.Page = page
|
listOptions.Page = &page
|
||||||
zonesResponse, err := p.client.ListZones(p.accountID, listOptions)
|
zonesResponse, err := p.client.ListZones(ctx, p.accountID, listOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -141,11 +152,11 @@ func (p *dnsimpleProvider) Zones() (map[string]dnsimple.Zone, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.zoneIDFilter.Match(strconv.Itoa(zone.ID)) {
|
if !p.zoneIDFilter.Match(int64ToString(zone.ID)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
zones[strconv.Itoa(zone.ID)] = zone
|
zones[int64ToString(zone.ID)] = zone
|
||||||
}
|
}
|
||||||
|
|
||||||
page++
|
page++
|
||||||
@ -158,7 +169,7 @@ func (p *dnsimpleProvider) Zones() (map[string]dnsimple.Zone, error) {
|
|||||||
|
|
||||||
// Records returns a list of endpoints in a given zone
|
// Records returns a list of endpoints in a given zone
|
||||||
func (p *dnsimpleProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, _ error) {
|
func (p *dnsimpleProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, _ error) {
|
||||||
zones, err := p.Zones()
|
zones, err := p.Zones(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -166,8 +177,8 @@ func (p *dnsimpleProvider) Records(ctx context.Context) (endpoints []*endpoint.E
|
|||||||
page := 1
|
page := 1
|
||||||
listOptions := &dnsimple.ZoneRecordListOptions{}
|
listOptions := &dnsimple.ZoneRecordListOptions{}
|
||||||
for {
|
for {
|
||||||
listOptions.Page = page
|
listOptions.Page = &page
|
||||||
records, err := p.client.ListRecords(p.accountID, zone.Name, listOptions)
|
records, err := p.client.ListRecords(ctx, p.accountID, zone.Name, listOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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 {
|
if len(changes) == 0 {
|
||||||
log.Infof("All records are already up to date")
|
log.Infof("All records are already up to date")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
zones, err := p.Zones()
|
zones, err := p.Zones(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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))
|
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 {
|
if !p.dryRun {
|
||||||
switch change.Action {
|
switch change.Action {
|
||||||
case dnsimpleCreate:
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case dnsimpleDelete:
|
case dnsimpleDelete:
|
||||||
recordID, err := p.GetRecordID(zone.Name, change.ResourceRecordSet.Name)
|
recordID, err := p.GetRecordID(ctx, zone.Name, *recordAttributes.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = p.client.DeleteRecord(p.accountID, zone.Name, recordID)
|
_, err = p.client.DeleteRecord(ctx, p.accountID, zone.Name, recordID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case dnsimpleUpdate:
|
case dnsimpleUpdate:
|
||||||
recordID, err := p.GetRecordID(zone.Name, change.ResourceRecordSet.Name)
|
recordID, err := p.GetRecordID(ctx, zone.Name, *recordAttributes.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -279,13 +297,13 @@ func (p *dnsimpleProvider) submitChanges(changes []*dnsimpleChange) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the record ID for a given record name and zone
|
// GetRecordID returns the record ID for a given record name and zone.
|
||||||
func (p *dnsimpleProvider) GetRecordID(zone string, recordName string) (recordID int, err error) {
|
func (p *dnsimpleProvider) GetRecordID(ctx context.Context, zone string, recordName string) (recordID int64, err error) {
|
||||||
page := 1
|
page := 1
|
||||||
listOptions := &dnsimple.ZoneRecordListOptions{Name: recordName}
|
listOptions := &dnsimple.ZoneRecordListOptions{Name: &recordName}
|
||||||
for {
|
for {
|
||||||
listOptions.Page = page
|
listOptions.Page = &page
|
||||||
records, err := p.client.ListRecords(p.accountID, zone, listOptions)
|
records, err := p.client.ListRecords(ctx, p.accountID, zone, listOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -301,7 +319,7 @@ func (p *dnsimpleProvider) GetRecordID(zone string, recordName string) (recordID
|
|||||||
break
|
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.
|
// 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
|
// CreateRecords creates records for a given slice of endpoints
|
||||||
func (p *dnsimpleProvider) CreateRecords(endpoints []*endpoint.Endpoint) error {
|
func (p *dnsimpleProvider) CreateRecords(ctx context.Context, endpoints []*endpoint.Endpoint) error {
|
||||||
return p.submitChanges(newDnsimpleChanges(dnsimpleCreate, endpoints))
|
return p.submitChanges(ctx, newDnsimpleChanges(dnsimpleCreate, endpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRecords deletes records for a given slice of endpoints
|
// DeleteRecords deletes records for a given slice of endpoints
|
||||||
func (p *dnsimpleProvider) DeleteRecords(endpoints []*endpoint.Endpoint) error {
|
func (p *dnsimpleProvider) DeleteRecords(ctx context.Context, endpoints []*endpoint.Endpoint) error {
|
||||||
return p.submitChanges(newDnsimpleChanges(dnsimpleDelete, endpoints))
|
return p.submitChanges(ctx, newDnsimpleChanges(dnsimpleDelete, endpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRecords updates records for a given slice of endpoints
|
// UpdateRecords updates records for a given slice of endpoints
|
||||||
func (p *dnsimpleProvider) UpdateRecords(endpoints []*endpoint.Endpoint) error {
|
func (p *dnsimpleProvider) UpdateRecords(ctx context.Context, endpoints []*endpoint.Endpoint) error {
|
||||||
return p.submitChanges(newDnsimpleChanges(dnsimpleUpdate, endpoints))
|
return p.submitChanges(ctx, newDnsimpleChanges(dnsimpleUpdate, endpoints))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyChanges applies a given set of changes
|
// 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(dnsimpleUpdate, changes.UpdateNew)...)
|
||||||
combinedChanges = append(combinedChanges, newDnsimpleChanges(dnsimpleDelete, changes.Delete)...)
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package dnsimple
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -22,8 +22,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/dnsimple/dnsimple-go/dnsimple"
|
"github.com/dnsimple/dnsimple-go/dnsimple"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
@ -31,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mockProvider dnsimpleProvider
|
var mockProvider dnsimpleProvider
|
||||||
@ -102,15 +101,17 @@ func TestDnsimpleServices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup mock services
|
// Setup mock services
|
||||||
|
// Note: AnythingOfType doesn't work with interfaces https://github.com/stretchr/testify/issues/519
|
||||||
mockDNS := &mockDnsimpleZoneServiceInterface{}
|
mockDNS := &mockDnsimpleZoneServiceInterface{}
|
||||||
mockDNS.On("ListZones", "1", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimpleListZonesResponse, nil)
|
mockDNS.On("ListZones", mock.AnythingOfType("*context.emptyCtx"), "1", &dnsimple.ZoneListOptions{ListOptions: dnsimple.ListOptions{Page: dnsimple.Int(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("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", "1", "example.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimpleListRecordsResponse, nil)
|
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", "1", "example-beta.com", &dnsimple.ZoneRecordListOptions{ListOptions: dnsimple.ListOptions{Page: 1}}).Return(&dnsimple.ZoneRecordsResponse{Response: dnsimple.Response{Pagination: &dnsimple.Pagination{}}}, 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 {
|
for _, record := range records {
|
||||||
simpleRecord := dnsimple.ZoneRecord{
|
recordName := record.Name
|
||||||
Name: record.Name,
|
simpleRecord := dnsimple.ZoneRecordAttributes{
|
||||||
|
Name: &recordName,
|
||||||
Type: record.Type,
|
Type: record.Type,
|
||||||
Content: record.Content,
|
Content: record.Content,
|
||||||
TTL: record.TTL,
|
TTL: record.TTL,
|
||||||
@ -121,10 +122,10 @@ func TestDnsimpleServices(t *testing.T) {
|
|||||||
Data: []dnsimple.ZoneRecord{record},
|
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("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", "1", record.ZoneID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
mockDNS.On("CreateRecord", mock.AnythingOfType("*context.emptyCtx"), "1", record.ZoneID, simpleRecord).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||||
mockDNS.On("DeleteRecord", "1", record.ZoneID, record.ID).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
mockDNS.On("DeleteRecord", mock.AnythingOfType("*context.emptyCtx"), "1", record.ZoneID, record.ID).Return(&dnsimple.ZoneRecordResponse{}, nil)
|
||||||
mockDNS.On("UpdateRecord", "1", record.ZoneID, record.ID, simpleRecord).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}
|
mockProvider = dnsimpleProvider{client: mockDNS}
|
||||||
@ -139,13 +140,14 @@ func TestDnsimpleServices(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testDnsimpleProviderZones(t *testing.T) {
|
func testDnsimpleProviderZones(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
mockProvider.accountID = "1"
|
mockProvider.accountID = "1"
|
||||||
result, err := mockProvider.Zones()
|
result, err := mockProvider.Zones(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
validateDnsimpleZones(t, result, dnsimpleListZonesResponse.Data)
|
validateDnsimpleZones(t, result, dnsimpleListZonesResponse.Data)
|
||||||
|
|
||||||
mockProvider.accountID = "2"
|
mockProvider.accountID = "2"
|
||||||
_, err = mockProvider.Zones()
|
_, err = mockProvider.Zones(ctx)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,13 +162,16 @@ func testDnsimpleProviderRecords(t *testing.T) {
|
|||||||
_, err = mockProvider.Records(ctx)
|
_, err = mockProvider.Records(ctx)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDnsimpleProviderApplyChanges(t *testing.T) {
|
func testDnsimpleProviderApplyChanges(t *testing.T) {
|
||||||
changes := &plan.Changes{}
|
changes := &plan.Changes{}
|
||||||
changes.Create = []*endpoint.Endpoint{
|
changes.Create = []*endpoint.Endpoint{
|
||||||
{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
|
{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},
|
{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{
|
changes.UpdateNew = []*endpoint.Endpoint{
|
||||||
{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
|
{DNSName: "example.example.com", Targets: endpoint.Targets{"target"}, RecordType: endpoint.RecordTypeCNAME},
|
||||||
{DNSName: "example.com", Targets: endpoint.Targets{"127.0.0.1"}, RecordType: endpoint.RecordTypeA},
|
{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) {
|
func testDnsimpleSuitableZone(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
mockProvider.accountID = "1"
|
mockProvider.accountID = "1"
|
||||||
zones, err := mockProvider.Zones()
|
zones, err := mockProvider.Zones(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
zone := dnsimpleSuitableZone("example-beta.example.com", zones)
|
zone := dnsimpleSuitableZone("example-beta.example.com", zones)
|
||||||
@ -203,7 +209,7 @@ func testDnsimpleSuitableZone(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewDnsimpleProvider(t *testing.T) {
|
func TestNewDnsimpleProvider(t *testing.T) {
|
||||||
os.Setenv("DNSIMPLE_OAUTH", "xxxxxxxxxxxxxxxxxxxxxxxxxx")
|
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 {
|
if err == nil {
|
||||||
t.Errorf("Expected to fail new provider on bad token")
|
t.Errorf("Expected to fail new provider on bad token")
|
||||||
}
|
}
|
||||||
@ -214,21 +220,24 @@ func TestNewDnsimpleProvider(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testDnsimpleGetRecordID(t *testing.T) {
|
func testDnsimpleGetRecordID(t *testing.T) {
|
||||||
mockProvider.accountID = "1"
|
var result int64
|
||||||
result, err := mockProvider.GetRecordID("example.com", "example")
|
var err error
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, 2, result)
|
|
||||||
|
|
||||||
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.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) {
|
func validateDnsimpleZones(t *testing.T, zones map[string]dnsimple.Zone, expected []dnsimple.Zone) {
|
||||||
require.Len(t, zones, len(expected))
|
require.Len(t, zones, len(expected))
|
||||||
|
|
||||||
for _, e := range 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
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *mockDnsimpleZoneServiceInterface) CreateRecord(accountID string, zoneID string, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error) {
|
func (_m *mockDnsimpleZoneServiceInterface) CreateRecord(ctx context.Context, accountID string, zoneID string, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
|
||||||
args := _m.Called(accountID, zoneID, recordAttributes)
|
args := _m.Called(ctx, accountID, zoneID, recordAttributes)
|
||||||
var r0 *dnsimple.ZoneRecordResponse
|
var r0 *dnsimple.ZoneRecordResponse
|
||||||
|
|
||||||
if args.Get(0) != nil {
|
if args.Get(0) != nil {
|
||||||
@ -247,8 +256,8 @@ func (_m *mockDnsimpleZoneServiceInterface) CreateRecord(accountID string, zoneI
|
|||||||
return r0, args.Error(1)
|
return r0, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *mockDnsimpleZoneServiceInterface) DeleteRecord(accountID string, zoneID string, recordID int) (*dnsimple.ZoneRecordResponse, error) {
|
func (_m *mockDnsimpleZoneServiceInterface) DeleteRecord(ctx context.Context, accountID string, zoneID string, recordID int64) (*dnsimple.ZoneRecordResponse, error) {
|
||||||
args := _m.Called(accountID, zoneID, recordID)
|
args := _m.Called(ctx, accountID, zoneID, recordID)
|
||||||
var r0 *dnsimple.ZoneRecordResponse
|
var r0 *dnsimple.ZoneRecordResponse
|
||||||
|
|
||||||
if args.Get(0) != nil {
|
if args.Get(0) != nil {
|
||||||
@ -258,8 +267,8 @@ func (_m *mockDnsimpleZoneServiceInterface) DeleteRecord(accountID string, zoneI
|
|||||||
return r0, args.Error(1)
|
return r0, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *mockDnsimpleZoneServiceInterface) ListRecords(accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error) {
|
func (_m *mockDnsimpleZoneServiceInterface) ListRecords(ctx context.Context, accountID string, zoneID string, options *dnsimple.ZoneRecordListOptions) (*dnsimple.ZoneRecordsResponse, error) {
|
||||||
args := _m.Called(accountID, zoneID, options)
|
args := _m.Called(ctx, accountID, zoneID, options)
|
||||||
var r0 *dnsimple.ZoneRecordsResponse
|
var r0 *dnsimple.ZoneRecordsResponse
|
||||||
|
|
||||||
if args.Get(0) != nil {
|
if args.Get(0) != nil {
|
||||||
@ -269,8 +278,8 @@ func (_m *mockDnsimpleZoneServiceInterface) ListRecords(accountID string, zoneID
|
|||||||
return r0, args.Error(1)
|
return r0, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *mockDnsimpleZoneServiceInterface) ListZones(accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error) {
|
func (_m *mockDnsimpleZoneServiceInterface) ListZones(ctx context.Context, accountID string, options *dnsimple.ZoneListOptions) (*dnsimple.ZonesResponse, error) {
|
||||||
args := _m.Called(accountID, options)
|
args := _m.Called(ctx, accountID, options)
|
||||||
var r0 *dnsimple.ZonesResponse
|
var r0 *dnsimple.ZonesResponse
|
||||||
|
|
||||||
if args.Get(0) != nil {
|
if args.Get(0) != nil {
|
||||||
@ -280,8 +289,8 @@ func (_m *mockDnsimpleZoneServiceInterface) ListZones(accountID string, options
|
|||||||
return r0, args.Error(1)
|
return r0, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *mockDnsimpleZoneServiceInterface) UpdateRecord(accountID string, zoneID string, recordID int, recordAttributes dnsimple.ZoneRecord) (*dnsimple.ZoneRecordResponse, error) {
|
func (_m *mockDnsimpleZoneServiceInterface) UpdateRecord(ctx context.Context, accountID string, zoneID string, recordID int64, recordAttributes dnsimple.ZoneRecordAttributes) (*dnsimple.ZoneRecordResponse, error) {
|
||||||
args := _m.Called(accountID, zoneID, recordID, recordAttributes)
|
args := _m.Called(ctx, accountID, zoneID, recordID, recordAttributes)
|
||||||
var r0 *dnsimple.ZoneRecordResponse
|
var r0 *dnsimple.ZoneRecordResponse
|
||||||
|
|
||||||
if args.Get(0) != nil {
|
if args.Get(0) != nil {
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package dyn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -31,6 +31,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -57,7 +58,7 @@ func unixNow() int64 {
|
|||||||
// DynConfig hold connection parameters to dyn.com and internal state
|
// DynConfig hold connection parameters to dyn.com and internal state
|
||||||
type DynConfig struct {
|
type DynConfig struct {
|
||||||
DomainFilter endpoint.DomainFilter
|
DomainFilter endpoint.DomainFilter
|
||||||
ZoneIDFilter ZoneIDFilter
|
ZoneIDFilter provider.ZoneIDFilter
|
||||||
DryRun bool
|
DryRun bool
|
||||||
CustomerName string
|
CustomerName string
|
||||||
Username string
|
Username string
|
||||||
@ -103,6 +104,7 @@ func (snap *ZoneSnapshot) StoreRecordsForSerial(zone string, serial int, records
|
|||||||
|
|
||||||
// DynProvider is the actual interface impl.
|
// DynProvider is the actual interface impl.
|
||||||
type dynProviderState struct {
|
type dynProviderState struct {
|
||||||
|
provider.BaseProvider
|
||||||
DynConfig
|
DynConfig
|
||||||
LastLoginErrorTime int64
|
LastLoginErrorTime int64
|
||||||
|
|
||||||
@ -141,7 +143,7 @@ type ZonePublishResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDynProvider initializes a new Dyn Provider.
|
// NewDynProvider initializes a new Dyn Provider.
|
||||||
func NewDynProvider(config DynConfig) (Provider, error) {
|
func NewDynProvider(config DynConfig) (provider.Provider, error) {
|
||||||
return &dynProviderState{
|
return &dynProviderState{
|
||||||
DynConfig: config,
|
DynConfig: config,
|
||||||
ZoneSnapshot: &ZoneSnapshot{
|
ZoneSnapshot: &ZoneSnapshot{
|
||||||
@ -156,7 +158,6 @@ func NewDynProvider(config DynConfig) (Provider, error) {
|
|||||||
func filterAndFixLinks(links []string, filter endpoint.DomainFilter) []string {
|
func filterAndFixLinks(links []string, filter endpoint.DomainFilter) []string {
|
||||||
var result []string
|
var result []string
|
||||||
for _, link := range links {
|
for _, link := range links {
|
||||||
|
|
||||||
// link looks like /REST/CNAMERecord/acme.com/exchange.acme.com/349386875
|
// link looks like /REST/CNAMERecord/acme.com/exchange.acme.com/349386875
|
||||||
|
|
||||||
// strip /REST/
|
// strip /REST/
|
||||||
@ -290,7 +291,6 @@ func (d *dynProviderState) allRecordsToEndpoints(records *dynectsoap.GetAllRecor
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorOrValue(err error, value interface{}) interface{} {
|
func errorOrValue(err error, value interface{}) interface{} {
|
||||||
@ -392,7 +392,6 @@ func (d *dynProviderState) fetchAllRecordsInZone(zone string) (*dynectsoap.GetAl
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &records, nil
|
return &records, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildLinkToRecord build a resource link. The symmetry of the dyn API is used to save
|
// 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 ""
|
return ""
|
||||||
}
|
}
|
||||||
var matchingZone = ""
|
var matchingZone = ""
|
||||||
for _, zone := range d.ZoneIDFilter.zoneIDs {
|
for _, zone := range d.ZoneIDFilter.ZoneIDs {
|
||||||
if strings.HasSuffix(ep.DNSName, zone) {
|
if strings.HasSuffix(ep.DNSName, zone) {
|
||||||
matchingZone = zone
|
matchingZone = zone
|
||||||
break
|
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
|
// the zones we are allowed to touch. Currently only exact matches are considered, not all
|
||||||
// zones with the given suffix
|
// zones with the given suffix
|
||||||
func (d *dynProviderState) zones(client *dynect.Client) []string {
|
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) {
|
func (d *dynProviderState) buildRecordRequest(ep *endpoint.Endpoint) (string, *dynect.RecordRequest) {
|
||||||
@ -568,7 +567,7 @@ func (d *dynProviderState) commit(client *dynect.Client) error {
|
|||||||
case 1:
|
case 1:
|
||||||
return errs[0]
|
return errs[0]
|
||||||
default:
|
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:
|
case 1:
|
||||||
return errs[0]
|
return errs[0]
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("Multiple errors committing: %+v", errs)
|
return fmt.Errorf("multiple errors committing: %+v", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if needsCommit {
|
if needsCommit {
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package dyn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDynMerge_NoUpdateOnTTL0Changes(t *testing.T) {
|
func TestDynMerge_NoUpdateOnTTL0Changes(t *testing.T) {
|
||||||
@ -187,7 +188,7 @@ func TestDyn_endpointToRecord(t *testing.T) {
|
|||||||
func TestDyn_buildLinkToRecord(t *testing.T) {
|
func TestDyn_buildLinkToRecord(t *testing.T) {
|
||||||
provider := &dynProviderState{
|
provider := &dynProviderState{
|
||||||
DynConfig: DynConfig{
|
DynConfig: DynConfig{
|
||||||
ZoneIDFilter: NewZoneIDFilter([]string{"example.com"}),
|
ZoneIDFilter: provider.NewZoneIDFilter([]string{"example.com"}),
|
||||||
DomainFilter: endpoint.NewDomainFilter([]string{"the-target.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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package exoscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EgoscaleClientI for replaceable implementation
|
// EgoscaleClientI for replaceable implementation
|
||||||
@ -38,6 +39,7 @@ type EgoscaleClientI interface {
|
|||||||
|
|
||||||
// ExoscaleProvider initialized as dns provider with no records
|
// ExoscaleProvider initialized as dns provider with no records
|
||||||
type ExoscaleProvider struct {
|
type ExoscaleProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domain endpoint.DomainFilter
|
domain endpoint.DomainFilter
|
||||||
client EgoscaleClientI
|
client EgoscaleClientI
|
||||||
filter *zoneFilter
|
filter *zoneFilter
|
||||||
@ -257,3 +259,38 @@ func (f *zoneFilter) EndpointZoneID(endpoint *endpoint.Endpoint, zones map[int64
|
|||||||
}
|
}
|
||||||
return matchZoneID, name
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package exoscale
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -189,3 +189,144 @@ func TestExoscaleApplyChanges(t *testing.T) {
|
|||||||
assert.Equal(t, "foo.com", updateExoscale[0].name)
|
assert.Equal(t, "foo.com", updateExoscale[0].name)
|
||||||
assert.Equal(t, int64(1), updateExoscale[0].updateDNSRecord.ID)
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package google
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -33,6 +33,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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.
|
// GoogleProvider is an implementation of Provider for Google CloudDNS.
|
||||||
type GoogleProvider struct {
|
type GoogleProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
// The Google project to work in
|
// The Google project to work in
|
||||||
project string
|
project string
|
||||||
// Enabled dry-run will print any modifying actions rather than execute them.
|
// 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
|
// only consider hosted zones managing domains ending in this suffix
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
// only consider hosted zones ending with this zone id
|
// only consider hosted zones ending with this zone id
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
// A client for managing resource record sets
|
// A client for managing resource record sets
|
||||||
resourceRecordSetsClient resourceRecordSetsClientInterface
|
resourceRecordSetsClient resourceRecordSetsClientInterface
|
||||||
// A client for managing hosted zones
|
// A client for managing hosted zones
|
||||||
@ -121,7 +123,7 @@ type GoogleProvider struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGoogleProvider initializes a new Google CloudDNS based Provider.
|
// 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)
|
gcloud, err := google.DefaultClient(ctx, dns.NdevClouddnsReadwriteScope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -209,7 +211,7 @@ func (p *GoogleProvider) Records(ctx context.Context) (endpoints []*endpoint.End
|
|||||||
|
|
||||||
f := func(resp *dns.ResourceRecordSetsListResponse) error {
|
f := func(resp *dns.ResourceRecordSetsListResponse) error {
|
||||||
for _, r := range resp.Rrsets {
|
for _, r := range resp.Rrsets {
|
||||||
if !supportedRecordType(r.Type) {
|
if !provider.SupportedRecordType(r.Type) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, r.Type, endpoint.TTL(r.Ttl), r.Rrdatas...))
|
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.
|
// 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 {
|
func separateChange(zones map[string]*dns.ManagedZone, change *dns.Change) map[string]*dns.Change {
|
||||||
changes := make(map[string]*dns.Change)
|
changes := make(map[string]*dns.Change)
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
zoneNameIDMapper[z.Name] = z.DnsName
|
zoneNameIDMapper[z.Name] = z.DnsName
|
||||||
changes[z.Name] = &dns.Change{
|
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 {
|
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)
|
changes[zoneName].Additions = append(changes[zoneName].Additions, a)
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("No matching zone for record addition: %s %s %s %d", a.Name, a.Type, a.Rrdatas, a.Ttl)
|
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 {
|
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)
|
changes[zoneName].Deletions = append(changes[zoneName].Deletions, d)
|
||||||
} else {
|
} else {
|
||||||
log.Warnf("No matching zone for record deletion: %s %s %s %d", d.Name, d.Type, d.Rrdatas, d.Ttl)
|
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))
|
targets := make([]string, len(ep.Targets))
|
||||||
copy(targets, []string(ep.Targets))
|
copy(targets, []string(ep.Targets))
|
||||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
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
|
// 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{
|
return &dns.ResourceRecordSet{
|
||||||
Name: ensureTrailingDot(ep.DNSName),
|
Name: provider.EnsureTrailingDot(ep.DNSName),
|
||||||
Rrdatas: targets,
|
Rrdatas: targets,
|
||||||
Ttl: ttl,
|
Ttl: ttl,
|
||||||
Type: ep.RecordType,
|
Type: ep.RecordType,
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package google
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -30,7 +30,9 @@ import (
|
|||||||
"google.golang.org/api/googleapi"
|
"google.golang.org/api/googleapi"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -192,7 +194,7 @@ func hasTrailingDot(target string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGoogleZonesIDFilter(t *testing.T) {
|
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())
|
zones, err := provider.Zones(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -203,7 +205,7 @@ func TestGoogleZonesIDFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGoogleZonesNameFilter(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())
|
zones, err := provider.Zones(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -214,7 +216,7 @@ func TestGoogleZonesNameFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGoogleZones(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())
|
zones, err := provider.Zones(context.Background())
|
||||||
require.NoError(t, err)
|
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"),
|
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())
|
records, err := provider.Records(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -261,7 +263,7 @@ func TestGoogleRecordsFilter(t *testing.T) {
|
|||||||
"zone-0.ext-dns-test-2.gcp.zalan.do.",
|
"zone-0.ext-dns-test-2.gcp.zalan.do.",
|
||||||
// there exists a third zone "zone-3" that we want to exclude from being managed.
|
// there exists a third zone "zone-3" that we want to exclude from being managed.
|
||||||
}),
|
}),
|
||||||
NewZoneIDFilter([]string{""}),
|
provider.NewZoneIDFilter([]string{""}),
|
||||||
false,
|
false,
|
||||||
originalEndpoints,
|
originalEndpoints,
|
||||||
)
|
)
|
||||||
@ -286,7 +288,7 @@ func TestGoogleRecordsFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGoogleCreateRecords(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{
|
records := []*endpoint.Endpoint{
|
||||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
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-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"),
|
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{
|
updatedRecords := []*endpoint.Endpoint{
|
||||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
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"),
|
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"),
|
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))
|
require.NoError(t, provider.DeleteRecords(originalEndpoints))
|
||||||
|
|
||||||
@ -359,7 +361,7 @@ func TestGoogleApplyChanges(t *testing.T) {
|
|||||||
"zone-0.ext-dns-test-2.gcp.zalan.do.",
|
"zone-0.ext-dns-test-2.gcp.zalan.do.",
|
||||||
// there exists a third zone "zone-3" that we want to exclude from being managed.
|
// there exists a third zone "zone-3" that we want to exclude from being managed.
|
||||||
}),
|
}),
|
||||||
NewZoneIDFilter([]string{""}),
|
provider.NewZoneIDFilter([]string{""}),
|
||||||
false,
|
false,
|
||||||
[]*endpoint.Endpoint{
|
[]*endpoint.Endpoint{
|
||||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, googleRecordTTL, "8.8.8.8"),
|
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"),
|
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{
|
createRecords := []*endpoint.Endpoint{
|
||||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
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) {
|
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{}))
|
assert.NoError(t, provider.ApplyChanges(context.Background(), &plan.Changes{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFilteredRecords(t *testing.T) {
|
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{
|
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"),
|
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)
|
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{
|
provider := &GoogleProvider{
|
||||||
project: "zalando-external-dns-test",
|
project: "zalando-external-dns-test",
|
||||||
dryRun: false,
|
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{
|
provider := &GoogleProvider{
|
||||||
project: "zalando-external-dns-test",
|
project: "zalando-external-dns-test",
|
||||||
dryRun: false,
|
dryRun: false,
|
||||||
@ -792,3 +794,7 @@ func clearGoogleRecords(t *testing.T, provider *GoogleProvider, zone string) {
|
|||||||
require.NoError(t, err)
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package infoblox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -29,12 +29,13 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InfobloxConfig clarifies the method signature
|
// InfobloxConfig clarifies the method signature
|
||||||
type InfobloxConfig struct {
|
type InfobloxConfig struct {
|
||||||
DomainFilter endpoint.DomainFilter
|
DomainFilter endpoint.DomainFilter
|
||||||
ZoneIDFilter ZoneIDFilter
|
ZoneIDFilter provider.ZoneIDFilter
|
||||||
Host string
|
Host string
|
||||||
Port int
|
Port int
|
||||||
Username string
|
Username string
|
||||||
@ -48,9 +49,10 @@ type InfobloxConfig struct {
|
|||||||
|
|
||||||
// InfobloxProvider implements the DNS provider for Infoblox.
|
// InfobloxProvider implements the DNS provider for Infoblox.
|
||||||
type InfobloxProvider struct {
|
type InfobloxProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client ibclient.IBConnector
|
client ibclient.IBConnector
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
view string
|
view string
|
||||||
dryRun bool
|
dryRun bool
|
||||||
}
|
}
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package infoblox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -28,7 +28,9 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockIBConnector struct {
|
type mockIBConnector struct {
|
||||||
@ -329,7 +331,7 @@ func createMockInfobloxObject(name, recordType, value string) ibclient.IBObject
|
|||||||
return nil
|
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{
|
return &InfobloxProvider{
|
||||||
client: client,
|
client: client,
|
||||||
domainFilter: domainFilter,
|
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())
|
actual, err := provider.Records(context.Background())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -428,7 +430,7 @@ func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient
|
|||||||
|
|
||||||
provider := newInfobloxProvider(
|
provider := newInfobloxProvider(
|
||||||
endpoint.NewDomainFilter([]string{""}),
|
endpoint.NewDomainFilter([]string{""}),
|
||||||
NewZoneIDFilter([]string{""}),
|
provider.NewZoneIDFilter([]string{""}),
|
||||||
dryRun,
|
dryRun,
|
||||||
client,
|
client,
|
||||||
)
|
)
|
||||||
@ -486,7 +488,7 @@ func TestInfobloxZones(t *testing.T) {
|
|||||||
mockInfobloxObjects: &[]ibclient.IBObject{},
|
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()
|
zones, _ := provider.zones()
|
||||||
var emptyZoneAuth *ibclient.ZoneAuth
|
var emptyZoneAuth *ibclient.ZoneAuth
|
||||||
assert.Equal(t, provider.findZone(zones, "example.com").Fqdn, "example.com")
|
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") == "")
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package inmemory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -43,6 +44,7 @@ var (
|
|||||||
// InMemoryProvider - dns provider only used for testing purposes
|
// InMemoryProvider - dns provider only used for testing purposes
|
||||||
// initialized as dns provider with no records
|
// initialized as dns provider with no records
|
||||||
type InMemoryProvider struct {
|
type InMemoryProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
domain endpoint.DomainFilter
|
domain endpoint.DomainFilter
|
||||||
client *inMemoryClient
|
client *inMemoryClient
|
||||||
filter *filter
|
filter *filter
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package inmemory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -26,10 +26,11 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/internal/testutils"
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ Provider = &InMemoryProvider{}
|
_ provider.Provider = &InMemoryProvider{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInMemoryProvider(t *testing.T) {
|
func TestInMemoryProvider(t *testing.T) {
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package linode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -30,12 +30,13 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinodeDomainClient interface to ease testing
|
// LinodeDomainClient interface to ease testing
|
||||||
type LinodeDomainClient interface {
|
type LinodeDomainClient interface {
|
||||||
ListDomainRecords(ctx context.Context, domainID int, opts *linodego.ListOptions) ([]*linodego.DomainRecord, error)
|
ListDomainRecords(ctx context.Context, domainID int, opts *linodego.ListOptions) ([]linodego.DomainRecord, error)
|
||||||
ListDomains(ctx context.Context, opts *linodego.ListOptions) ([]*linodego.Domain, error)
|
ListDomains(ctx context.Context, opts *linodego.ListOptions) ([]linodego.Domain, error)
|
||||||
CreateDomainRecord(ctx context.Context, domainID int, domainrecord linodego.DomainRecordCreateOptions) (*linodego.DomainRecord, error)
|
CreateDomainRecord(ctx context.Context, domainID int, domainrecord linodego.DomainRecordCreateOptions) (*linodego.DomainRecord, error)
|
||||||
DeleteDomainRecord(ctx context.Context, domainID int, id int) error
|
DeleteDomainRecord(ctx context.Context, domainID int, id int) error
|
||||||
UpdateDomainRecord(ctx context.Context, domainID int, id int, domainrecord linodego.DomainRecordUpdateOptions) (*linodego.DomainRecord, 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.
|
// LinodeProvider is an implementation of Provider for Digital Ocean's DNS.
|
||||||
type LinodeProvider struct {
|
type LinodeProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
Client LinodeDomainClient
|
Client LinodeDomainClient
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
DryRun bool
|
DryRun bool
|
||||||
@ -50,28 +52,28 @@ type LinodeProvider struct {
|
|||||||
|
|
||||||
// LinodeChanges All API calls calculated from the plan
|
// LinodeChanges All API calls calculated from the plan
|
||||||
type LinodeChanges struct {
|
type LinodeChanges struct {
|
||||||
Creates []*LinodeChangeCreate
|
Creates []LinodeChangeCreate
|
||||||
Deletes []*LinodeChangeDelete
|
Deletes []LinodeChangeDelete
|
||||||
Updates []*LinodeChangeUpdate
|
Updates []LinodeChangeUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
// LinodeChangeCreate Linode Domain Record Creates
|
// LinodeChangeCreate Linode Domain Record Creates
|
||||||
type LinodeChangeCreate struct {
|
type LinodeChangeCreate struct {
|
||||||
Domain *linodego.Domain
|
Domain linodego.Domain
|
||||||
Options linodego.DomainRecordCreateOptions
|
Options linodego.DomainRecordCreateOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// LinodeChangeUpdate Linode Domain Record Updates
|
// LinodeChangeUpdate Linode Domain Record Updates
|
||||||
type LinodeChangeUpdate struct {
|
type LinodeChangeUpdate struct {
|
||||||
Domain *linodego.Domain
|
Domain linodego.Domain
|
||||||
DomainRecord *linodego.DomainRecord
|
DomainRecord linodego.DomainRecord
|
||||||
Options linodego.DomainRecordUpdateOptions
|
Options linodego.DomainRecordUpdateOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// LinodeChangeDelete Linode Domain Record Deletes
|
// LinodeChangeDelete Linode Domain Record Deletes
|
||||||
type LinodeChangeDelete struct {
|
type LinodeChangeDelete struct {
|
||||||
Domain *linodego.Domain
|
Domain linodego.Domain
|
||||||
DomainRecord *linodego.DomainRecord
|
DomainRecord linodego.DomainRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewLinodeProvider initializes a new Linode DNS based Provider.
|
// 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.
|
// 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)
|
zones, err := p.fetchZones(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -126,7 +128,7 @@ func (p *LinodeProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range records {
|
for _, r := range records {
|
||||||
if supportedRecordType(string(r.Type)) {
|
if provider.SupportedRecordType(string(r.Type)) {
|
||||||
name := fmt.Sprintf("%s.%s", r.Name, zone.Domain)
|
name := fmt.Sprintf("%s.%s", r.Name, zone.Domain)
|
||||||
|
|
||||||
// root name is identified by the empty string and should be
|
// 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
|
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)
|
records, err := p.Client.ListDomainRecords(ctx, domainID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -152,8 +154,8 @@ func (p *LinodeProvider) fetchRecords(ctx context.Context, domainID int) ([]*lin
|
|||||||
return records, nil
|
return records, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LinodeProvider) fetchZones(ctx context.Context) ([]*linodego.Domain, error) {
|
func (p *LinodeProvider) fetchZones(ctx context.Context) ([]linodego.Domain, error) {
|
||||||
var zones []*linodego.Domain
|
var zones []linodego.Domain
|
||||||
|
|
||||||
allZones, err := p.Client.ListDomains(ctx, linodego.NewListOptions(0, ""))
|
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.
|
// ApplyChanges applies a given set of changes in a given zone.
|
||||||
func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
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)
|
zones, err := p.fetchZones(ctx)
|
||||||
|
|
||||||
@ -265,9 +267,9 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zonesByID := make(map[string]*linodego.Domain)
|
zonesByID := make(map[string]linodego.Domain)
|
||||||
|
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
|
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
zoneNameIDMapper.Add(strconv.Itoa(z.ID), z.Domain)
|
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)
|
updatesByZone := endpointsByZone(zoneNameIDMapper, changes.UpdateNew)
|
||||||
deletesByZone := endpointsByZone(zoneNameIDMapper, changes.Delete)
|
deletesByZone := endpointsByZone(zoneNameIDMapper, changes.Delete)
|
||||||
|
|
||||||
var linodeCreates []*LinodeChangeCreate
|
var linodeCreates []LinodeChangeCreate
|
||||||
var linodeUpdates []*LinodeChangeUpdate
|
var linodeUpdates []LinodeChangeUpdate
|
||||||
var linodeDeletes []*LinodeChangeDelete
|
var linodeDeletes []LinodeChangeDelete
|
||||||
|
|
||||||
// Generate Creates
|
// Generate Creates
|
||||||
for zoneID, creates := range createsByZone {
|
for zoneID, creates := range createsByZone {
|
||||||
@ -326,7 +328,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, target := range ep.Targets {
|
for _, target := range ep.Targets {
|
||||||
linodeCreates = append(linodeCreates, &LinodeChangeCreate{
|
linodeCreates = append(linodeCreates, LinodeChangeCreate{
|
||||||
Domain: zone,
|
Domain: zone,
|
||||||
Options: linodego.DomainRecordCreateOptions{
|
Options: linodego.DomainRecordCreateOptions{
|
||||||
Target: target,
|
Target: target,
|
||||||
@ -374,7 +376,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
matchedRecordsByTarget := make(map[string]*linodego.DomainRecord)
|
matchedRecordsByTarget := make(map[string]linodego.DomainRecord)
|
||||||
|
|
||||||
for _, record := range matchedRecords {
|
for _, record := range matchedRecords {
|
||||||
matchedRecordsByTarget[record.Target] = record
|
matchedRecordsByTarget[record.Target] = record
|
||||||
@ -390,7 +392,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
"target": target,
|
"target": target,
|
||||||
}).Warn("Updating Existing Target")
|
}).Warn("Updating Existing Target")
|
||||||
|
|
||||||
linodeUpdates = append(linodeUpdates, &LinodeChangeUpdate{
|
linodeUpdates = append(linodeUpdates, LinodeChangeUpdate{
|
||||||
Domain: zone,
|
Domain: zone,
|
||||||
DomainRecord: record,
|
DomainRecord: record,
|
||||||
Options: linodego.DomainRecordUpdateOptions{
|
Options: linodego.DomainRecordUpdateOptions{
|
||||||
@ -415,7 +417,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
"target": target,
|
"target": target,
|
||||||
}).Warn("Creating New Target")
|
}).Warn("Creating New Target")
|
||||||
|
|
||||||
linodeCreates = append(linodeCreates, &LinodeChangeCreate{
|
linodeCreates = append(linodeCreates, LinodeChangeCreate{
|
||||||
Domain: zone,
|
Domain: zone,
|
||||||
Options: linodego.DomainRecordCreateOptions{
|
Options: linodego.DomainRecordCreateOptions{
|
||||||
Target: target,
|
Target: target,
|
||||||
@ -440,12 +442,11 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
"target": record.Target,
|
"target": record.Target,
|
||||||
}).Warn("Deleting Target")
|
}).Warn("Deleting Target")
|
||||||
|
|
||||||
linodeDeletes = append(linodeDeletes, &LinodeChangeDelete{
|
linodeDeletes = append(linodeDeletes, LinodeChangeDelete{
|
||||||
Domain: zone,
|
Domain: zone,
|
||||||
DomainRecord: record,
|
DomainRecord: record,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,7 +477,7 @@ func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, record := range matchedRecords {
|
for _, record := range matchedRecords {
|
||||||
linodeDeletes = append(linodeDeletes, &LinodeChangeDelete{
|
linodeDeletes = append(linodeDeletes, LinodeChangeDelete{
|
||||||
Domain: zone,
|
Domain: zone,
|
||||||
DomainRecord: record,
|
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 {
|
func endpointsByZone(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) map[string][]endpoint.Endpoint {
|
||||||
endpointsByZone := make(map[string][]*endpoint.Endpoint)
|
endpointsByZone := make(map[string][]endpoint.Endpoint)
|
||||||
|
|
||||||
for _, ep := range endpoints {
|
for _, ep := range endpoints {
|
||||||
zoneID, _ := zoneNameIDMapper.FindZone(ep.DNSName)
|
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)
|
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", ep.DNSName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
endpointsByZone[zoneID] = append(endpointsByZone[zoneID], ep)
|
endpointsByZone[zoneID] = append(endpointsByZone[zoneID], *ep)
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpointsByZone
|
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
|
// Handle root
|
||||||
if ep.DNSName == zone.Domain {
|
if ep.DNSName == zone.Domain {
|
||||||
return ""
|
return ""
|
||||||
@ -532,8 +533,8 @@ func getStrippedRecordName(zone *linodego.Domain, ep *endpoint.Endpoint) string
|
|||||||
return strings.TrimSuffix(ep.DNSName, "."+zone.Domain)
|
return strings.TrimSuffix(ep.DNSName, "."+zone.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRecordID(records []*linodego.DomainRecord, zone *linodego.Domain, ep *endpoint.Endpoint) []*linodego.DomainRecord {
|
func getRecordID(records []linodego.DomainRecord, zone linodego.Domain, ep endpoint.Endpoint) []linodego.DomainRecord {
|
||||||
var matchedRecords []*linodego.DomainRecord
|
var matchedRecords []linodego.DomainRecord
|
||||||
|
|
||||||
for _, record := range records {
|
for _, record := range records {
|
||||||
if record.Name == getStrippedRecordName(zone, ep) && string(record.Type) == ep.RecordType {
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package linode
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -34,14 +34,14 @@ type MockDomainClient struct {
|
|||||||
mock.Mock
|
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)
|
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)
|
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) {
|
func (m *MockDomainClient) CreateDomainRecord(ctx context.Context, domainID int, opts linodego.DomainRecordCreateOptions) (*linodego.DomainRecord, error) {
|
||||||
args := m.Called(ctx, domainID, opts)
|
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)
|
return args.Get(0).(*linodego.DomainRecord), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createZones() []*linodego.Domain {
|
func createZones() []linodego.Domain {
|
||||||
return []*linodego.Domain{
|
return []linodego.Domain{
|
||||||
{ID: 1, Domain: "foo.com"},
|
{ID: 1, Domain: "foo.com"},
|
||||||
{ID: 2, Domain: "bar.io"},
|
{ID: 2, Domain: "bar.io"},
|
||||||
{ID: 3, Domain: "baz.com"},
|
{ID: 3, Domain: "baz.com"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFooRecords() []*linodego.DomainRecord {
|
func createFooRecords() []linodego.DomainRecord {
|
||||||
return []*linodego.DomainRecord{{
|
return []linodego.DomainRecord{{
|
||||||
ID: 11,
|
ID: 11,
|
||||||
Type: linodego.RecordTypeA,
|
Type: linodego.RecordTypeA,
|
||||||
Name: "",
|
Name: "",
|
||||||
@ -83,12 +83,12 @@ func createFooRecords() []*linodego.DomainRecord {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBarRecords() []*linodego.DomainRecord {
|
func createBarRecords() []linodego.DomainRecord {
|
||||||
return []*linodego.DomainRecord{}
|
return []linodego.DomainRecord{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBazRecords() []*linodego.DomainRecord {
|
func createBazRecords() []linodego.DomainRecord {
|
||||||
return []*linodego.DomainRecord{{
|
return []linodego.DomainRecord{{
|
||||||
ID: 31,
|
ID: 31,
|
||||||
Type: linodego.RecordTypeA,
|
Type: linodego.RecordTypeA,
|
||||||
Name: "",
|
Name: "",
|
||||||
@ -147,15 +147,15 @@ func TestNewLinodeProvider(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLinodeStripRecordName(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",
|
Domain: "example.com",
|
||||||
}, &endpoint.Endpoint{
|
}, endpoint.Endpoint{
|
||||||
DNSName: "api.example.com",
|
DNSName: "api.example.com",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
assert.Equal(t, "", getStrippedRecordName(&linodego.Domain{
|
assert.Equal(t, "", getStrippedRecordName(linodego.Domain{
|
||||||
Domain: "example.com",
|
Domain: "example.com",
|
||||||
}, &endpoint.Endpoint{
|
}, endpoint.Endpoint{
|
||||||
DNSName: "example.com",
|
DNSName: "example.com",
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -198,7 +198,7 @@ func TestLinodeFetchZonesWithFilter(t *testing.T) {
|
|||||||
mock.Anything,
|
mock.Anything,
|
||||||
).Return(createZones(), nil).Once()
|
).Return(createZones(), nil).Once()
|
||||||
|
|
||||||
expected := []*linodego.Domain{
|
expected := []linodego.Domain{
|
||||||
{ID: 1, Domain: "foo.com"},
|
{ID: 1, Domain: "foo.com"},
|
||||||
{ID: 3, Domain: "baz.com"},
|
{ID: 3, Domain: "baz.com"},
|
||||||
}
|
}
|
||||||
@ -210,15 +210,15 @@ func TestLinodeFetchZonesWithFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLinodeGetStrippedRecordName(t *testing.T) {
|
func TestLinodeGetStrippedRecordName(t *testing.T) {
|
||||||
assert.Equal(t, "", getStrippedRecordName(&linodego.Domain{
|
assert.Equal(t, "", getStrippedRecordName(linodego.Domain{
|
||||||
Domain: "foo.com",
|
Domain: "foo.com",
|
||||||
}, &endpoint.Endpoint{
|
}, endpoint.Endpoint{
|
||||||
DNSName: "foo.com",
|
DNSName: "foo.com",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
assert.Equal(t, "api", getStrippedRecordName(&linodego.Domain{
|
assert.Equal(t, "api", getStrippedRecordName(linodego.Domain{
|
||||||
Domain: "foo.com",
|
Domain: "foo.com",
|
||||||
}, &endpoint.Endpoint{
|
}, endpoint.Endpoint{
|
||||||
DNSName: "api.foo.com",
|
DNSName: "api.foo.com",
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -398,14 +398,14 @@ func TestLinodeApplyChangesTargetAdded(t *testing.T) {
|
|||||||
"ListDomains",
|
"ListDomains",
|
||||||
mock.Anything,
|
mock.Anything,
|
||||||
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(
|
mockDomainClient.On(
|
||||||
"ListDomainRecords",
|
"ListDomainRecords",
|
||||||
mock.Anything,
|
mock.Anything,
|
||||||
1,
|
1,
|
||||||
mock.Anything,
|
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
|
// Apply Actions
|
||||||
mockDomainClient.On(
|
mockDomainClient.On(
|
||||||
@ -457,14 +457,14 @@ func TestLinodeApplyChangesTargetRemoved(t *testing.T) {
|
|||||||
"ListDomains",
|
"ListDomains",
|
||||||
mock.Anything,
|
mock.Anything,
|
||||||
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(
|
mockDomainClient.On(
|
||||||
"ListDomainRecords",
|
"ListDomainRecords",
|
||||||
mock.Anything,
|
mock.Anything,
|
||||||
1,
|
1,
|
||||||
mock.Anything,
|
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
|
// Apply Actions
|
||||||
mockDomainClient.On(
|
mockDomainClient.On(
|
||||||
@ -513,14 +513,14 @@ func TestLinodeApplyChangesNoChanges(t *testing.T) {
|
|||||||
"ListDomains",
|
"ListDomains",
|
||||||
mock.Anything,
|
mock.Anything,
|
||||||
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(
|
mockDomainClient.On(
|
||||||
"ListDomainRecords",
|
"ListDomainRecords",
|
||||||
mock.Anything,
|
mock.Anything,
|
||||||
1,
|
1,
|
||||||
mock.Anything,
|
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{})
|
err := provider.ApplyChanges(context.Background(), &plan.Changes{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package ns1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -30,6 +30,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -85,7 +86,7 @@ func (n NS1DomainService) ListZones() ([]*dns.Zone, *http.Response, error) {
|
|||||||
// NS1Config passes cli args to the NS1Provider
|
// NS1Config passes cli args to the NS1Provider
|
||||||
type NS1Config struct {
|
type NS1Config struct {
|
||||||
DomainFilter endpoint.DomainFilter
|
DomainFilter endpoint.DomainFilter
|
||||||
ZoneIDFilter ZoneIDFilter
|
ZoneIDFilter provider.ZoneIDFilter
|
||||||
NS1Endpoint string
|
NS1Endpoint string
|
||||||
NS1IgnoreSSL bool
|
NS1IgnoreSSL bool
|
||||||
DryRun bool
|
DryRun bool
|
||||||
@ -93,9 +94,10 @@ type NS1Config struct {
|
|||||||
|
|
||||||
// NS1Provider is the NS1 provider
|
// NS1Provider is the NS1 provider
|
||||||
type NS1Provider struct {
|
type NS1Provider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client NS1DomainClient
|
client NS1DomainClient
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +152,6 @@ func (p *NS1Provider) Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
|||||||
var endpoints []*endpoint.Endpoint
|
var endpoints []*endpoint.Endpoint
|
||||||
|
|
||||||
for _, zone := range zones {
|
for _, zone := range zones {
|
||||||
|
|
||||||
// TODO handle Header Codes
|
// TODO handle Header Codes
|
||||||
zoneData, _, err := p.client.GetZone(zone.String())
|
zoneData, _, err := p.client.GetZone(zone.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -158,7 +159,7 @@ func (p *NS1Provider) Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, record := range zoneData.Records {
|
for _, record := range zoneData.Records {
|
||||||
if supportedRecordType(record.Type) {
|
if provider.SupportedRecordType(record.Type) {
|
||||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(
|
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(
|
||||||
record.Domain,
|
record.Domain,
|
||||||
record.Type,
|
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.
|
// ns1ChangesByZone separates a multi-zone change into a single change per zone.
|
||||||
func ns1ChangesByZone(zones []*dns.Zone, changeSets []*ns1Change) map[string][]*ns1Change {
|
func ns1ChangesByZone(zones []*dns.Zone, changeSets []*ns1Change) map[string][]*ns1Change {
|
||||||
changes := make(map[string][]*ns1Change)
|
changes := make(map[string][]*ns1Change)
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
zoneNameIDMapper.Add(z.Zone, z.Zone)
|
zoneNameIDMapper.Add(z.Zone, z.Zone)
|
||||||
changes[z.Zone] = []*ns1Change{}
|
changes[z.Zone] = []*ns1Change{}
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package ns1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -31,6 +31,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MockNS1DomainClient struct {
|
type MockNS1DomainClient struct {
|
||||||
@ -130,7 +131,7 @@ func TestNS1Records(t *testing.T) {
|
|||||||
provider := &NS1Provider{
|
provider := &NS1Provider{
|
||||||
client: &MockNS1DomainClient{},
|
client: &MockNS1DomainClient{},
|
||||||
domainFilter: endpoint.NewDomainFilter([]string{"foo.com."}),
|
domainFilter: endpoint.NewDomainFilter([]string{"foo.com."}),
|
||||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
@ -151,7 +152,7 @@ func TestNewNS1Provider(t *testing.T) {
|
|||||||
_ = os.Setenv("NS1_APIKEY", "xxxxxxxxxxxxxxxxx")
|
_ = os.Setenv("NS1_APIKEY", "xxxxxxxxxxxxxxxxx")
|
||||||
testNS1Config := NS1Config{
|
testNS1Config := NS1Config{
|
||||||
DomainFilter: endpoint.NewDomainFilter([]string{"foo.com."}),
|
DomainFilter: endpoint.NewDomainFilter([]string{"foo.com."}),
|
||||||
ZoneIDFilter: NewZoneIDFilter([]string{""}),
|
ZoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||||
DryRun: false,
|
DryRun: false,
|
||||||
}
|
}
|
||||||
_, err := NewNS1Provider(testNS1Config)
|
_, err := NewNS1Provider(testNS1Config)
|
||||||
@ -166,7 +167,7 @@ func TestNS1Zones(t *testing.T) {
|
|||||||
provider := &NS1Provider{
|
provider := &NS1Provider{
|
||||||
client: &MockNS1DomainClient{},
|
client: &MockNS1DomainClient{},
|
||||||
domainFilter: endpoint.NewDomainFilter([]string{"foo.com."}),
|
domainFilter: endpoint.NewDomainFilter([]string{"foo.com."}),
|
||||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||||
}
|
}
|
||||||
|
|
||||||
zones, err := provider.zonesFiltered()
|
zones, err := provider.zonesFiltered()
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package oci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -29,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ociRecordTTL = 300
|
const ociRecordTTL = 300
|
||||||
@ -52,11 +53,12 @@ type OCIConfig struct {
|
|||||||
// OCIProvider is an implementation of Provider for Oracle Cloud Infrastructure
|
// OCIProvider is an implementation of Provider for Oracle Cloud Infrastructure
|
||||||
// (OCI) DNS.
|
// (OCI) DNS.
|
||||||
type OCIProvider struct {
|
type OCIProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client ociDNSClient
|
client ociDNSClient
|
||||||
cfg OCIConfig
|
cfg OCIConfig
|
||||||
|
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,8 +84,8 @@ func LoadOCIConfig(path string) (*OCIConfig, error) {
|
|||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOCIProvider initialises a new OCI DNS based Provider.
|
// NewOCIProvider initializes a new OCI DNS based Provider.
|
||||||
func NewOCIProvider(cfg OCIConfig, domainFilter endpoint.DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) (*OCIProvider, error) {
|
func NewOCIProvider(cfg OCIConfig, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool) (*OCIProvider, error) {
|
||||||
var client ociDNSClient
|
var client ociDNSClient
|
||||||
client, err := dns.NewDnsClientWithConfigurationProvider(common.NewRawConfigurationProvider(
|
client, err := dns.NewDnsClientWithConfigurationProvider(common.NewRawConfigurationProvider(
|
||||||
cfg.Auth.TenancyID,
|
cfg.Auth.TenancyID,
|
||||||
@ -94,7 +96,7 @@ func NewOCIProvider(cfg OCIConfig, domainFilter endpoint.DomainFilter, zoneIDFil
|
|||||||
&cfg.Auth.Passphrase,
|
&cfg.Auth.Passphrase,
|
||||||
))
|
))
|
||||||
if err != nil {
|
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{
|
return &OCIProvider{
|
||||||
@ -177,7 +179,7 @@ func (p *OCIProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, record := range resp.Items {
|
for _, record := range resp.Items {
|
||||||
if !supportedRecordType(*record.Rtype) {
|
if !provider.SupportedRecordType(*record.Rtype) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
endpoints = append(endpoints,
|
endpoints = append(endpoints,
|
||||||
@ -252,7 +254,7 @@ func newRecordOperation(ep *endpoint.Endpoint, opType dns.RecordOperationOperati
|
|||||||
targets := make([]string, len(ep.Targets))
|
targets := make([]string, len(ep.Targets))
|
||||||
copy(targets, []string(ep.Targets))
|
copy(targets, []string(ep.Targets))
|
||||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||||
targets[0] = ensureTrailingDot(targets[0])
|
targets[0] = provider.EnsureTrailingDot(targets[0])
|
||||||
}
|
}
|
||||||
rdata := strings.Join(targets, " ")
|
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 {
|
func operationsByZone(zones map[string]dns.ZoneSummary, ops []dns.RecordOperation) map[string][]dns.RecordOperation {
|
||||||
changes := make(map[string][]dns.RecordOperation)
|
changes := make(map[string][]dns.RecordOperation)
|
||||||
|
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
zoneNameIDMapper.Add(*z.Id, *z.Name)
|
zoneNameIDMapper.Add(*z.Id, *z.Name)
|
||||||
changes[*z.Id] = []dns.RecordOperation{}
|
changes[*z.Id] = []dns.RecordOperation{}
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package oci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockOCIDNSClient struct{}
|
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.
|
// 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{
|
return &OCIProvider{
|
||||||
client: client,
|
client: client,
|
||||||
cfg: OCIConfig{
|
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 {
|
for name, tc := range testCases {
|
||||||
@ -184,7 +185,7 @@ hKRtDhmSdWBo3tJK12RrAe4t7CUe8gMgTvU7ExlcA3xQkseFPx9K
|
|||||||
_, err := NewOCIProvider(
|
_, err := NewOCIProvider(
|
||||||
tc.config,
|
tc.config,
|
||||||
endpoint.NewDomainFilter([]string{"com"}),
|
endpoint.NewDomainFilter([]string{"com"}),
|
||||||
NewZoneIDFilter([]string{""}),
|
provider.NewZoneIDFilter([]string{""}),
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -200,13 +201,13 @@ func TestOCIZones(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
expected map[string]dns.ZoneSummary
|
expected map[string]dns.ZoneSummary
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "DomainFilter_com",
|
name: "DomainFilter_com",
|
||||||
domainFilter: endpoint.NewDomainFilter([]string{"com"}),
|
domainFilter: endpoint.NewDomainFilter([]string{"com"}),
|
||||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||||
expected: map[string]dns.ZoneSummary{
|
expected: map[string]dns.ZoneSummary{
|
||||||
"foo.com": {
|
"foo.com": {
|
||||||
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
|
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
|
||||||
@ -220,7 +221,7 @@ func TestOCIZones(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "DomainFilter_foo.com",
|
name: "DomainFilter_foo.com",
|
||||||
domainFilter: endpoint.NewDomainFilter([]string{"foo.com"}),
|
domainFilter: endpoint.NewDomainFilter([]string{"foo.com"}),
|
||||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||||
expected: map[string]dns.ZoneSummary{
|
expected: map[string]dns.ZoneSummary{
|
||||||
"foo.com": {
|
"foo.com": {
|
||||||
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
|
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
|
||||||
@ -230,7 +231,7 @@ func TestOCIZones(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "ZoneIDFilter_ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959",
|
name: "ZoneIDFilter_ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959",
|
||||||
domainFilter: endpoint.NewDomainFilter([]string{""}),
|
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{
|
expected: map[string]dns.ZoneSummary{
|
||||||
"foo.com": {
|
"foo.com": {
|
||||||
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
|
Id: common.String("ocid1.dns-zone.oc1..e1e042ef0bfbb5c251b9713fd7bf8959"),
|
||||||
@ -253,13 +254,13 @@ func TestOCIRecords(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
expected []*endpoint.Endpoint
|
expected []*endpoint.Endpoint
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "unfiltered",
|
name: "unfiltered",
|
||||||
domainFilter: endpoint.NewDomainFilter([]string{""}),
|
domainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||||
expected: []*endpoint.Endpoint{
|
expected: []*endpoint.Endpoint{
|
||||||
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
|
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"),
|
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",
|
name: "DomainFilter_foo.com",
|
||||||
domainFilter: endpoint.NewDomainFilter([]string{"foo.com"}),
|
domainFilter: endpoint.NewDomainFilter([]string{"foo.com"}),
|
||||||
zoneIDFilter: NewZoneIDFilter([]string{""}),
|
zoneIDFilter: provider.NewZoneIDFilter([]string{""}),
|
||||||
expected: []*endpoint.Endpoint{
|
expected: []*endpoint.Endpoint{
|
||||||
endpoint.NewEndpointWithTTL("foo.foo.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
|
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"),
|
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",
|
name: "ZoneIDFilter_ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404",
|
||||||
domainFilter: endpoint.NewDomainFilter([]string{""}),
|
domainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||||
zoneIDFilter: NewZoneIDFilter([]string{"ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404"}),
|
zoneIDFilter: provider.NewZoneIDFilter([]string{"ocid1.dns-zone.oc1..502aeddba262b92fd13ed7874f6f1404"}),
|
||||||
expected: []*endpoint.Endpoint{
|
expected: []*endpoint.Endpoint{
|
||||||
endpoint.NewEndpointWithTTL("foo.bar.com", endpoint.RecordTypeA, endpoint.TTL(ociRecordTTL), "127.0.0.1"),
|
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(
|
provider := newOCIProvider(
|
||||||
client,
|
client,
|
||||||
endpoint.NewDomainFilter([]string{""}),
|
endpoint.NewDomainFilter([]string{""}),
|
||||||
NewZoneIDFilter([]string{""}),
|
provider.NewZoneIDFilter([]string{""}),
|
||||||
tc.dryRun,
|
tc.dryRun,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package ovh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -45,6 +46,8 @@ var (
|
|||||||
|
|
||||||
// OVHProvider is an implementation of Provider for OVH DNS.
|
// OVHProvider is an implementation of Provider for OVH DNS.
|
||||||
type OVHProvider struct {
|
type OVHProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
|
|
||||||
client ovhClient
|
client ovhClient
|
||||||
|
|
||||||
domainFilter endpoint.DomainFilter
|
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.
|
// Records returns the list of records in all relevant zones.
|
||||||
func (p *OVHProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
func (p *OVHProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||||
|
|
||||||
_, records, err := p.zonesRecords(ctx)
|
_, records, err := p.zonesRecords(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err := p.client.Get(fmt.Sprintf("/domain/zone/%s/record/%d", *zone, id), &record); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if supportedRecordType(record.FieldType) {
|
if provider.SupportedRecordType(record.FieldType) {
|
||||||
log.Debugf("OVH: Record %d for %s is %+v", id, *zone, record)
|
log.Debugf("OVH: Record %d for %s is %+v", id, *zone, record)
|
||||||
records <- 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 {
|
func newOvhChange(action int, endpoints []*endpoint.Endpoint, zones []string, records []ovhRecord) []ovhChange {
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
ovhChanges := make([]ovhChange, 0, countTargets(endpoints))
|
ovhChanges := make([]ovhChange, 0, countTargets(endpoints))
|
||||||
for _, zone := range zones {
|
for _, zone := range zones {
|
||||||
zoneNameIDMapper.Add(zone, zone)
|
zoneNameIDMapper.Add(zone, zone)
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package ovh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -142,9 +142,9 @@ func TestOvhRecords(t *testing.T) {
|
|||||||
sort.Strings(endoint.Targets)
|
sort.Strings(endoint.Targets)
|
||||||
}
|
}
|
||||||
assert.ElementsMatch(endpoints, []*endpoint.Endpoint{
|
assert.ElementsMatch(endpoints, []*endpoint.Endpoint{
|
||||||
&endpoint.Endpoint{DNSName: "example.org", RecordType: "A", RecordTTL: 10, Labels: endpoint.NewLabels(), Targets: []string{"203.0.113.42"}},
|
{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"}},
|
{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: "ovh.example.net", RecordType: "A", RecordTTL: 10, Labels: endpoint.NewLabels(), Targets: []string{"203.0.113.42", "203.0.113.43"}},
|
||||||
})
|
})
|
||||||
client.AssertExpectations(t)
|
client.AssertExpectations(t)
|
||||||
|
|
||||||
@ -283,10 +283,10 @@ func TestOvhCountTargets(t *testing.T) {
|
|||||||
endpoints [][]*endpoint.Endpoint
|
endpoints [][]*endpoint.Endpoint
|
||||||
count int
|
count int
|
||||||
}{
|
}{
|
||||||
{[][]*endpoint.Endpoint{[]*endpoint.Endpoint{{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target"}}}}, 1},
|
{[][]*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{{{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{{{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", "target"}}}, {{DNSName: "ovh.example.net", Targets: endpoint.Targets{"target", "target"}}}}, 4},
|
||||||
}
|
}
|
||||||
for _, test := range cases {
|
for _, test := range cases {
|
||||||
count := countTargets(test.endpoints...)
|
count := countTargets(test.endpoints...)
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package pdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -35,6 +35,7 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/pkg/tlsutils"
|
"sigs.k8s.io/external-dns/pkg/tlsutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pdnsChangeType string
|
type pdnsChangeType string
|
||||||
@ -116,7 +117,6 @@ func (tlsConfig *TLSConfig) setHTTPClient(pdnsClientConfig *pgo.Configuration) e
|
|||||||
|
|
||||||
// Function for debug printing
|
// Function for debug printing
|
||||||
func stringifyHTTPResponseBody(r *http.Response) (body string) {
|
func stringifyHTTPResponseBody(r *http.Response) (body string) {
|
||||||
|
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -125,7 +125,6 @@ func stringifyHTTPResponseBody(r *http.Response) (body string) {
|
|||||||
buf.ReadFrom(r.Body)
|
buf.ReadFrom(r.Body)
|
||||||
body = buf.String()
|
body = buf.String()
|
||||||
return body
|
return body
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PDNSAPIProvider : Interface used and extended by the PDNSAPIClient struct as
|
// 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)
|
log.Errorf("Unable to fetch zones. %v", err)
|
||||||
return zones, resp, 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
|
// 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)
|
log.Debugf("Retrying ListZone() ... %d", i)
|
||||||
time.Sleep(retryAfterTime * (1 << uint(i)))
|
time.Sleep(retryAfterTime * (1 << uint(i)))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
return zone, resp, err
|
return zone, resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Errorf("Unable to list zone. %v", err)
|
log.Errorf("Unable to list zone. %v", err)
|
||||||
return zone, resp, err
|
return zone, resp, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PatchZone : Method used to update the contents of a particular zone from PowerDNS
|
// 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)
|
log.Debugf("Retrying PatchZone() ... %d", i)
|
||||||
time.Sleep(retryAfterTime * (1 << uint(i)))
|
time.Sleep(retryAfterTime * (1 << uint(i)))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
}
|
}
|
||||||
return resp, err
|
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
|
// PDNSProvider is an implementation of the Provider interface for PowerDNS
|
||||||
type PDNSProvider struct {
|
type PDNSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client PDNSAPIProvider
|
client PDNSAPIProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPDNSProvider initializes a new PowerDNS based Provider.
|
// NewPDNSProvider initializes a new PowerDNS based Provider.
|
||||||
func NewPDNSProvider(ctx context.Context, config PDNSConfig) (*PDNSProvider, error) {
|
func NewPDNSProvider(ctx context.Context, config PDNSConfig) (*PDNSProvider, error) {
|
||||||
|
|
||||||
// Do some input validation
|
// Do some input validation
|
||||||
|
|
||||||
if config.APIKey == "" {
|
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
|
// 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,
|
domainFilter: config.DomainFilter,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return provider, nil
|
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))
|
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(rr.Name, rr.Type_, endpoint.TTL(rr.Ttl), record.Content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return endpoints, nil
|
return endpoints, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConvertEndpointsToZones marshals endpoints into pdns compatible Zone structs
|
// ConvertEndpointsToZones marshals endpoints into pdns compatible Zone structs
|
||||||
func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changetype pdnsChangeType) (zonelist []pgo.Zone, _ error) {
|
func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changetype pdnsChangeType) (zonelist []pgo.Zone, _ error) {
|
||||||
|
|
||||||
zonelist = []pgo.Zone{}
|
zonelist = []pgo.Zone{}
|
||||||
endpoints := make([]*endpoint.Endpoint, len(eps))
|
endpoints := make([]*endpoint.Endpoint, len(eps))
|
||||||
copy(endpoints, eps)
|
copy(endpoints, eps)
|
||||||
@ -310,15 +302,15 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
|
|||||||
zone.Rrsets = []pgo.RrSet{}
|
zone.Rrsets = []pgo.RrSet{}
|
||||||
for i := 0; i < len(endpoints); {
|
for i := 0; i < len(endpoints); {
|
||||||
ep := endpoints[i]
|
ep := endpoints[i]
|
||||||
dnsname := ensureTrailingDot(ep.DNSName)
|
dnsname := provider.EnsureTrailingDot(ep.DNSName)
|
||||||
if dnsname == zone.Name || strings.HasSuffix(dnsname, "."+zone.Name) {
|
if dnsname == zone.Name || strings.HasSuffix(dnsname, "."+zone.Name) {
|
||||||
// The assumption here is that there will only ever be one target
|
// The assumption here is that there will only ever be one target
|
||||||
// per (ep.DNSName, ep.RecordType) tuple, which holds true for
|
// per (ep.DNSName, ep.RecordType) tuple, which holds true for
|
||||||
// external-dns v5.0.0-alpha onwards
|
// external-dns v5.0.0-alpha onwards
|
||||||
records := []pgo.Record{}
|
records := []pgo.Record{}
|
||||||
for _, t := range ep.Targets {
|
for _, t := range ep.Targets {
|
||||||
if "CNAME" == ep.RecordType {
|
if ep.RecordType == "CNAME" {
|
||||||
t = ensureTrailingDot(t)
|
t = provider.EnsureTrailingDot(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
records = append(records, pgo.Record{Content: 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
|
// DELETEs explicitly forbid a TTL, therefore only PATCHes need the TTL
|
||||||
if changetype == PdnsReplace {
|
if changetype == PdnsReplace {
|
||||||
if int64(ep.RecordTTL) > int64(math.MaxInt32) {
|
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 {
|
if ep.RecordTTL == 0 {
|
||||||
// No TTL was specified for the record, we use the default
|
// 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
|
// If we didn't pop anything, we move to the next item in the list
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(zone.Rrsets) > 0 {
|
if len(zone.Rrsets) > 0 {
|
||||||
zonelist = append(zonelist, zone)
|
zonelist = append(zonelist, zone)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// residualZones is unsorted by name length like its counterpart
|
// 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 _, zone := range residualZones {
|
||||||
for i := 0; i < len(endpoints); {
|
for i := 0; i < len(endpoints); {
|
||||||
ep := endpoints[i]
|
ep := endpoints[i]
|
||||||
dnsname := ensureTrailingDot(ep.DNSName)
|
dnsname := provider.EnsureTrailingDot(ep.DNSName)
|
||||||
if dnsname == zone.Name || strings.HasSuffix(dnsname, "."+zone.Name) {
|
if dnsname == zone.Name || strings.HasSuffix(dnsname, "."+zone.Name) {
|
||||||
// "pop" endpoint if it's matched to a residual zone... essentially a no-op
|
// "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)
|
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
|
// 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
|
// We warn instead of hard fail here because we don't want a misconfig to cause everything to go down
|
||||||
if len(endpoints) > 0 {
|
if len(endpoints) > 0 {
|
||||||
@ -400,20 +388,17 @@ func (p *PDNSProvider) mutateRecords(endpoints []*endpoint.Endpoint, changetype
|
|||||||
} else {
|
} else {
|
||||||
log.Debugf("Struct for PatchZone:\n%s", string(jso))
|
log.Debugf("Struct for PatchZone:\n%s", string(jso))
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := p.client.PatchZone(zone.Id, zone)
|
resp, err := p.client.PatchZone(zone.Id, zone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("PDNS API response: %s", stringifyHTTPResponseBody(resp))
|
log.Debugf("PDNS API response: %s", stringifyHTTPResponseBody(resp))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Records returns all DNS records controlled by the configured PDNS server (for all zones)
|
// 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) {
|
func (p *PDNSProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, _ error) {
|
||||||
|
|
||||||
zones, _, err := p.client.ListZones()
|
zones, _, err := p.client.ListZones()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// ApplyChanges takes a list of changes (endpoints) and updates the PDNS server
|
||||||
// by sending the correct HTTP PATCH requests to a matching zone
|
// by sending the correct HTTP PATCH requests to a matching zone
|
||||||
func (p *PDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
func (p *PDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
@ -491,7 +475,6 @@ func (p *PDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Changes pushed out to PowerDNS in %s\n", time.Since(startTime))
|
log.Debugf("Changes pushed out to PowerDNS in %s\n", time.Since(startTime))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package pdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -735,7 +735,7 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreateTLS() {
|
|||||||
DomainFilter: endpoint.NewDomainFilter([]string{""}),
|
DomainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||||
TLSConfig: TLSConfig{
|
TLSConfig: TLSConfig{
|
||||||
TLSEnabled: true,
|
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")
|
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{""}),
|
DomainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||||
TLSConfig: TLSConfig{
|
TLSConfig: TLSConfig{
|
||||||
TLSEnabled: true,
|
TLSEnabled: true,
|
||||||
CAFilePath: "../internal/testresources/ca.pem",
|
CAFilePath: "../../internal/testresources/ca.pem",
|
||||||
ClientCertFilePath: "../internal/testresources/client-cert.pem",
|
ClientCertFilePath: "../../internal/testresources/client-cert.pem",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.Error(suite.T(), err, "Enabled TLS Config with --tls-client-cert only should raise an error")
|
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{""}),
|
DomainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||||
TLSConfig: TLSConfig{
|
TLSConfig: TLSConfig{
|
||||||
TLSEnabled: true,
|
TLSEnabled: true,
|
||||||
CAFilePath: "../internal/testresources/ca.pem",
|
CAFilePath: "../../internal/testresources/ca.pem",
|
||||||
ClientCertKeyFilePath: "../internal/testresources/client-cert-key.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")
|
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{""}),
|
DomainFilter: endpoint.NewDomainFilter([]string{""}),
|
||||||
TLSConfig: TLSConfig{
|
TLSConfig: TLSConfig{
|
||||||
TLSEnabled: true,
|
TLSEnabled: true,
|
||||||
CAFilePath: "../internal/testresources/ca.pem",
|
CAFilePath: "../../internal/testresources/ca.pem",
|
||||||
ClientCertFilePath: "../internal/testresources/client-cert.pem",
|
ClientCertFilePath: "../../internal/testresources/client-cert.pem",
|
||||||
ClientCertKeyFilePath: "../internal/testresources/client-cert-key.pem",
|
ClientCertKeyFilePath: "../../internal/testresources/client-cert-key.pem",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.Nil(suite.T(), err, "Enabled TLS Config with all flags should raise no error")
|
assert.Nil(suite.T(), err, "Enabled TLS Config with all flags should raise no error")
|
||||||
@ -29,6 +29,14 @@ import (
|
|||||||
type Provider interface {
|
type Provider interface {
|
||||||
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||||
ApplyChanges(ctx context.Context, changes *plan.Changes) 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 {
|
type contextKey struct {
|
||||||
@ -42,11 +50,34 @@ func (k *contextKey) String() string { return "provider context value " + k.name
|
|||||||
// type []*endpoint.Endpoint.
|
// type []*endpoint.Endpoint.
|
||||||
var RecordsContextKey = &contextKey{"records"}
|
var RecordsContextKey = &contextKey{"records"}
|
||||||
|
|
||||||
// ensureTrailingDot ensures that the hostname receives a trailing dot if it hasn't already.
|
// EnsureTrailingDot ensures that the hostname receives a trailing dot if it hasn't already.
|
||||||
func ensureTrailingDot(hostname string) string {
|
func EnsureTrailingDot(hostname string) string {
|
||||||
if net.ParseIP(hostname) != nil {
|
if net.ParseIP(hostname) != nil {
|
||||||
return hostname
|
return hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.TrimSuffix(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
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"testing"
|
"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) {
|
func TestEnsureTrailingDot(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
input, expected string
|
input, expected string
|
||||||
@ -28,10 +38,28 @@ func TestEnsureTrailingDot(t *testing.T) {
|
|||||||
{"example.org.", "example.org."},
|
{"example.org.", "example.org."},
|
||||||
{"8.8.8.8", "8.8.8.8"},
|
{"8.8.8.8", "8.8.8.8"},
|
||||||
} {
|
} {
|
||||||
output := ensureTrailingDot(tc.input)
|
output := EnsureTrailingDot(tc.input)
|
||||||
|
|
||||||
if output != tc.expected {
|
if output != tc.expected {
|
||||||
t.Errorf("expected %s, got %s", tc.expected, output)
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package rcode0
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -28,10 +28,12 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RcodeZeroProvider implements the DNS provider for RcodeZero Anycast DNS.
|
// RcodeZeroProvider implements the DNS provider for RcodeZero Anycast DNS.
|
||||||
type RcodeZeroProvider struct {
|
type RcodeZeroProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
Client *rc0.Client
|
Client *rc0.Client
|
||||||
|
|
||||||
DomainFilter endpoint.DomainFilter
|
DomainFilter endpoint.DomainFilter
|
||||||
@ -44,7 +46,6 @@ type RcodeZeroProvider struct {
|
|||||||
//
|
//
|
||||||
// Returns the provider or an error if a provider could not be created.
|
// Returns the provider or an error if a provider could not be created.
|
||||||
func NewRcodeZeroProvider(domainFilter endpoint.DomainFilter, dryRun bool, txtEnc bool) (*RcodeZeroProvider, error) {
|
func NewRcodeZeroProvider(domainFilter endpoint.DomainFilter, dryRun bool, txtEnc bool) (*RcodeZeroProvider, error) {
|
||||||
|
|
||||||
client, err := rc0.NewClient(os.Getenv("RC0_API_KEY"))
|
client, err := rc0.NewClient(os.Getenv("RC0_API_KEY"))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -76,7 +77,6 @@ func NewRcodeZeroProvider(domainFilter endpoint.DomainFilter, dryRun bool, txtEn
|
|||||||
|
|
||||||
// Zones returns filtered zones if filter is set
|
// Zones returns filtered zones if filter is set
|
||||||
func (p *RcodeZeroProvider) Zones() ([]*rc0.Zone, error) {
|
func (p *RcodeZeroProvider) Zones() ([]*rc0.Zone, error) {
|
||||||
|
|
||||||
var result []*rc0.Zone
|
var result []*rc0.Zone
|
||||||
|
|
||||||
zones, err := p.fetchZones()
|
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
|
// Decrypts TXT records if TXT-Encrypt flag is set and key is provided
|
||||||
func (p *RcodeZeroProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
func (p *RcodeZeroProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||||
|
|
||||||
zones, err := p.Zones()
|
zones, err := p.Zones()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -106,7 +105,6 @@ func (p *RcodeZeroProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
|
|||||||
var endpoints []*endpoint.Endpoint
|
var endpoints []*endpoint.Endpoint
|
||||||
|
|
||||||
for _, zone := range zones {
|
for _, zone := range zones {
|
||||||
|
|
||||||
rrset, err := p.fetchRecords(zone.Domain)
|
rrset, err := p.fetchRecords(zone.Domain)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -114,25 +112,19 @@ func (p *RcodeZeroProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range rrset {
|
for _, r := range rrset {
|
||||||
|
if provider.SupportedRecordType(r.Type) {
|
||||||
if supportedRecordType(r.Type) {
|
|
||||||
|
|
||||||
if p.TXTEncrypt && (p.Key != nil) && strings.EqualFold(r.Type, "TXT") {
|
if p.TXTEncrypt && (p.Key != nil) && strings.EqualFold(r.Type, "TXT") {
|
||||||
p.Client.RRSet.DecryptTXT(p.Key, r)
|
p.Client.RRSet.DecryptTXT(p.Key, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.Records) > 1 {
|
if len(r.Records) > 1 {
|
||||||
|
|
||||||
for _, _r := range r.Records {
|
for _, _r := range r.Records {
|
||||||
if !_r.Disabled {
|
if !_r.Disabled {
|
||||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, r.Type, endpoint.TTL(r.TTL), _r.Content))
|
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, r.Type, endpoint.TTL(r.TTL), _r.Content))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if !r.Records[0].Disabled {
|
} else if !r.Records[0].Disabled {
|
||||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, r.Type, endpoint.TTL(r.TTL), r.Records[0].Content))
|
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.
|
// ApplyChanges applies a given set of changes in a given zone.
|
||||||
func (p *RcodeZeroProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
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 := make([]*rc0.RRSetChange, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
||||||
|
|
||||||
combinedChanges = append(combinedChanges, p.NewRcodezeroChanges(rc0.ChangeTypeADD, changes.Create)...)
|
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
|
// Helper function
|
||||||
func rcodezeroChangesByZone(zones []*rc0.Zone, changeSet []*rc0.RRSetChange) map[string][]*rc0.RRSetChange {
|
func rcodezeroChangesByZone(zones []*rc0.Zone, changeSet []*rc0.RRSetChange) map[string][]*rc0.RRSetChange {
|
||||||
|
|
||||||
changes := make(map[string][]*rc0.RRSetChange)
|
changes := make(map[string][]*rc0.RRSetChange)
|
||||||
zoneNameIDMapper := zoneIDName{}
|
zoneNameIDMapper := provider.ZoneIDName{}
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
zoneNameIDMapper.Add(z.Domain, z.Domain)
|
zoneNameIDMapper.Add(z.Domain, z.Domain)
|
||||||
changes[z.Domain] = []*rc0.RRSetChange{}
|
changes[z.Domain] = []*rc0.RRSetChange{}
|
||||||
@ -176,7 +166,6 @@ func rcodezeroChangesByZone(zones []*rc0.Zone, changeSet []*rc0.RRSetChange) map
|
|||||||
|
|
||||||
// Helper function
|
// Helper function
|
||||||
func (p *RcodeZeroProvider) fetchRecords(zoneName string) ([]*rc0.RRType, error) {
|
func (p *RcodeZeroProvider) fetchRecords(zoneName string) ([]*rc0.RRType, error) {
|
||||||
|
|
||||||
var allRecords []*rc0.RRType
|
var allRecords []*rc0.RRType
|
||||||
|
|
||||||
listOptions := rc0.NewListOptions()
|
listOptions := rc0.NewListOptions()
|
||||||
@ -202,7 +191,6 @@ func (p *RcodeZeroProvider) fetchRecords(zoneName string) ([]*rc0.RRType, error)
|
|||||||
|
|
||||||
// Helper function
|
// Helper function
|
||||||
func (p *RcodeZeroProvider) fetchZones() ([]*rc0.Zone, error) {
|
func (p *RcodeZeroProvider) fetchZones() ([]*rc0.Zone, error) {
|
||||||
|
|
||||||
var allZones []*rc0.Zone
|
var allZones []*rc0.Zone
|
||||||
|
|
||||||
listOptions := rc0.NewListOptions()
|
listOptions := rc0.NewListOptions()
|
||||||
@ -228,7 +216,6 @@ func (p *RcodeZeroProvider) fetchZones() ([]*rc0.Zone, error) {
|
|||||||
//
|
//
|
||||||
// Changes are submitted by change type.
|
// Changes are submitted by change type.
|
||||||
func (p *RcodeZeroProvider) submitChanges(changes []*rc0.RRSetChange) error {
|
func (p *RcodeZeroProvider) submitChanges(changes []*rc0.RRSetChange) error {
|
||||||
|
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
return nil
|
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.
|
// separate into per-zone change sets to be passed to the API.
|
||||||
changesByZone := rcodezeroChangesByZone(zones, changes)
|
changesByZone := rcodezeroChangesByZone(zones, changes)
|
||||||
|
|
||||||
for zoneName, changes := range changesByZone {
|
for zoneName, changes := range changesByZone {
|
||||||
|
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
|
|
||||||
logFields := log.Fields{
|
logFields := log.Fields{
|
||||||
"record": change.Name,
|
"record": change.Name,
|
||||||
"content": change.Records[0].Content,
|
"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.
|
// NewRcodezeroChanges returns a RcodeZero specific array with rrset change objects.
|
||||||
func (p *RcodeZeroProvider) NewRcodezeroChanges(action string, endpoints []*endpoint.Endpoint) []*rc0.RRSetChange {
|
func (p *RcodeZeroProvider) NewRcodezeroChanges(action string, endpoints []*endpoint.Endpoint) []*rc0.RRSetChange {
|
||||||
|
|
||||||
changes := make([]*rc0.RRSetChange, 0, len(endpoints))
|
changes := make([]*rc0.RRSetChange, 0, len(endpoints))
|
||||||
|
|
||||||
for _, _endpoint := range 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.
|
// NewRcodezeroChange returns a RcodeZero specific rrset change object.
|
||||||
func (p *RcodeZeroProvider) NewRcodezeroChange(action string, endpoint *endpoint.Endpoint) *rc0.RRSetChange {
|
func (p *RcodeZeroProvider) NewRcodezeroChange(action string, endpoint *endpoint.Endpoint) *rc0.RRSetChange {
|
||||||
|
|
||||||
change := &rc0.RRSetChange{
|
change := &rc0.RRSetChange{
|
||||||
Type: endpoint.RecordType,
|
Type: endpoint.RecordType,
|
||||||
ChangeType: action,
|
ChangeType: action,
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package rcode0
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package rdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -35,9 +35,11 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
etcdTimeout = 5 * time.Second
|
||||||
rdnsMaxHosts = 10
|
rdnsMaxHosts = 10
|
||||||
rdnsOriginalLabel = "originalText"
|
rdnsOriginalLabel = "originalText"
|
||||||
rdnsPrefix = "/rdnsv3"
|
rdnsPrefix = "/rdnsv3"
|
||||||
@ -65,6 +67,7 @@ type RDNSConfig struct {
|
|||||||
|
|
||||||
// RDNSProvider is an implementation of Provider for Rancher DNS(RDNS).
|
// RDNSProvider is an implementation of Provider for Rancher DNS(RDNS).
|
||||||
type RDNSProvider struct {
|
type RDNSProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client RDNSClient
|
client RDNSClient
|
||||||
dryRun bool
|
dryRun bool
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package rdns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -16,9 +16,9 @@ limitations under the License.
|
|||||||
|
|
||||||
package provider
|
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.
|
// Currently A, CNAME, SRV, and TXT record types are supported.
|
||||||
func supportedRecordType(recordType string) bool {
|
func SupportedRecordType(recordType string) bool {
|
||||||
switch recordType {
|
switch recordType {
|
||||||
case "A", "CNAME", "SRV", "TXT":
|
case "A", "CNAME", "SRV", "TXT":
|
||||||
return true
|
return true
|
||||||
|
|||||||
@ -41,7 +41,7 @@ func TestRecordTypeFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, r := range records {
|
for _, r := range records {
|
||||||
got := supportedRecordType(r.rtype)
|
got := SupportedRecordType(r.rtype)
|
||||||
if r.expect != got {
|
if r.expect != got {
|
||||||
t.Errorf("wrong record type %s: expect %v, but got %v", r.rtype, 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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package rfc2136
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -30,10 +30,17 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"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
|
// rfc2136 provider type
|
||||||
type rfc2136Provider struct {
|
type rfc2136Provider struct {
|
||||||
|
provider.BaseProvider
|
||||||
nameserver string
|
nameserver string
|
||||||
zoneName string
|
zoneName string
|
||||||
tsigKeyName string
|
tsigKeyName string
|
||||||
@ -65,7 +72,7 @@ type rfc2136Actions interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewRfc2136Provider is a factory function for OpenStack rfc2136 providers
|
// 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]
|
secretAlgChecked, ok := tsigAlgs[secretAlg]
|
||||||
if !ok && !insecure {
|
if !ok && !insecure {
|
||||||
return nil, errors.Errorf("%s is not supported TSIG algorithm", secretAlg)
|
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)
|
m.SetUpdate(r.zoneName)
|
||||||
|
|
||||||
for _, ep := range changes.Create {
|
for _, ep := range changes.Create {
|
||||||
|
|
||||||
if !r.domainFilter.Match(ep.DNSName) {
|
if !r.domainFilter.Match(ep.DNSName) {
|
||||||
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||||
continue
|
continue
|
||||||
@ -214,17 +220,15 @@ func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
|
|
||||||
r.AddRecord(m, ep)
|
r.AddRecord(m, ep)
|
||||||
}
|
}
|
||||||
for _, ep := range changes.UpdateNew {
|
for i, ep := range changes.UpdateNew {
|
||||||
|
|
||||||
if !r.domainFilter.Match(ep.DNSName) {
|
if !r.domainFilter.Match(ep.DNSName) {
|
||||||
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
r.UpdateRecord(m, ep)
|
r.UpdateRecord(m, changes.UpdateOld[i], ep)
|
||||||
}
|
}
|
||||||
for _, ep := range changes.Delete {
|
for _, ep := range changes.Delete {
|
||||||
|
|
||||||
if !r.domainFilter.Match(ep.DNSName) {
|
if !r.domainFilter.Match(ep.DNSName) {
|
||||||
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||||
continue
|
continue
|
||||||
@ -244,13 +248,13 @@ func (r rfc2136Provider) ApplyChanges(ctx context.Context, changes *plan.Changes
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r rfc2136Provider) UpdateRecord(m *dns.Msg, ep *endpoint.Endpoint) error {
|
func (r rfc2136Provider) UpdateRecord(m *dns.Msg, oldEp *endpoint.Endpoint, newEp *endpoint.Endpoint) error {
|
||||||
err := r.RemoveRecord(m, ep)
|
err := r.RemoveRecord(m, oldEp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.AddRecord(m, ep)
|
return r.AddRecord(m, newEp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r rfc2136Provider) AddRecord(m *dns.Msg, ep *endpoint.Endpoint) error {
|
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())
|
msg.SetTsig(r.tsigKeyName, r.tsigSecretAlg, 300, time.Now().Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if msg.Len() > udpMaxMsgSize {
|
||||||
|
c.Net = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
resp, _, err := c.Exchange(msg, r.nameserver)
|
resp, _, err := c.Exchange(msg, r.nameserver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Infof("error in dns.Client.Exchange: %s", err)
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package rfc2136
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -29,6 +29,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rfc2136Stub struct {
|
type rfc2136Stub struct {
|
||||||
@ -93,7 +94,7 @@ func (r *rfc2136Stub) IncomeTransfer(m *dns.Msg, a string) (env chan *dns.Envelo
|
|||||||
return outChan, nil
|
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)
|
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"))
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -12,6 +28,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -22,6 +39,7 @@ const (
|
|||||||
|
|
||||||
// TransIPProvider is an implementation of Provider for TransIP.
|
// TransIPProvider is an implementation of Provider for TransIP.
|
||||||
type TransIPProvider struct {
|
type TransIPProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client gotransip.SOAPClient
|
client gotransip.SOAPClient
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
@ -72,7 +90,7 @@ func (p *TransIPProvider) ApplyChanges(ctx context.Context, changes *plan.Change
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
zoneNameMapper := zoneIDName{}
|
zoneNameMapper := provider.ZoneIDName{}
|
||||||
zonesByName := make(map[string]transip.Domain)
|
zonesByName := make(map[string]transip.Domain)
|
||||||
updatedZones := make(map[string]bool)
|
updatedZones := make(map[string]bool)
|
||||||
for _, zone := range zones {
|
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
|
// go over all zones and their DNS entries and create endpoints for them
|
||||||
for _, zone := range zones {
|
for _, zone := range zones {
|
||||||
for _, r := range zone.DNSEntries {
|
for _, r := range zone.DNSEntries {
|
||||||
if !supportedRecordType(string(r.Type)) {
|
if !provider.SupportedRecordType(string(r.Type)) {
|
||||||
continue
|
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
|
// zoneForZoneName returns the zone mapped to given name or error if zone could
|
||||||
// not be found
|
// 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)
|
_, zoneName := m.FindZone(name)
|
||||||
if zoneName == "" {
|
if zoneName == "" {
|
||||||
return transip.Domain{}, fmt.Errorf("could not find zoneName for %s", name)
|
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 (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package vinyldns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -27,6 +27,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -47,8 +48,9 @@ type vinyldnsZoneInterface interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type vinyldnsProvider struct {
|
type vinyldnsProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client vinyldnsZoneInterface
|
client vinyldnsZoneInterface
|
||||||
zoneFilter ZoneIDFilter
|
zoneFilter provider.ZoneIDFilter
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
}
|
}
|
||||||
@ -59,7 +61,7 @@ type vinyldnsChange struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewVinylDNSProvider provides support for VinylDNS records
|
// 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")
|
_, ok := os.LookupEnv("VINYLDNS_ACCESS_KEY")
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("no vinyldns access key found")
|
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 {
|
for _, r := range records {
|
||||||
if supportedRecordType(r.Type) {
|
if provider.SupportedRecordType(r.Type) {
|
||||||
recordsCount := len(r.Records)
|
recordsCount := len(r.Records)
|
||||||
log.Debugf(fmt.Sprintf("%s.%s.%d.%s", r.Name, r.Type, recordsCount, zone.Name))
|
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 {
|
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.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package vinyldns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -28,6 +28,7 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockVinyldnsZoneInterface struct {
|
type mockVinyldnsZoneInterface struct {
|
||||||
@ -97,12 +98,12 @@ func testVinylDNSProviderRecords(t *testing.T) {
|
|||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, len(vinylDNSRecords), len(result))
|
assert.Equal(t, len(vinylDNSRecords), len(result))
|
||||||
|
|
||||||
mockVinylDNSProvider.zoneFilter = NewZoneIDFilter([]string{"0"})
|
mockVinylDNSProvider.zoneFilter = provider.NewZoneIDFilter([]string{"0"})
|
||||||
result, err = mockVinylDNSProvider.Records(ctx)
|
result, err = mockVinylDNSProvider.Records(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, len(vinylDNSRecords), len(result))
|
assert.Equal(t, len(vinylDNSRecords), len(result))
|
||||||
|
|
||||||
mockVinylDNSProvider.zoneFilter = NewZoneIDFilter([]string{"1"})
|
mockVinylDNSProvider.zoneFilter = provider.NewZoneIDFilter([]string{"1"})
|
||||||
result, err = mockVinylDNSProvider.Records(ctx)
|
result, err = mockVinylDNSProvider.Records(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, 0, len(result))
|
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}}
|
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)
|
err := mockVinylDNSProvider.ApplyChanges(context.Background(), changes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to apply changes: %v", err)
|
t.Errorf("Failed to apply changes: %v", err)
|
||||||
@ -126,7 +127,7 @@ func testVinylDNSProviderApplyChanges(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testVinylDNSSuitableZone(t *testing.T) {
|
func testVinylDNSSuitableZone(t *testing.T) {
|
||||||
mockVinylDNSProvider.zoneFilter = NewZoneIDFilter([]string{"0"})
|
mockVinylDNSProvider.zoneFilter = provider.NewZoneIDFilter([]string{"0"})
|
||||||
|
|
||||||
zone := vinyldnsSuitableZone("example.com", vinylDNSZones)
|
zone := vinyldnsSuitableZone("example.com", vinylDNSZones)
|
||||||
assert.Equal(t, zone.Name, "example.com.")
|
assert.Equal(t, zone.Name, "example.com.")
|
||||||
@ -134,11 +135,11 @@ func testVinylDNSSuitableZone(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewVinylDNSProvider(t *testing.T) {
|
func TestNewVinylDNSProvider(t *testing.T) {
|
||||||
os.Setenv("VINYLDNS_ACCESS_KEY", "xxxxxxxxxxxxxxxxxxxxxxxxxx")
|
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)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
os.Unsetenv("VINYLDNS_ACCESS_KEY")
|
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)
|
assert.NotNil(t, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("Expected to fail new provider on empty token")
|
t.Errorf("Expected to fail new provider on empty token")
|
||||||
@ -146,7 +147,7 @@ func TestNewVinylDNSProvider(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testVinylDNSFindRecordSetID(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.")
|
result, err := mockVinylDNSProvider.findRecordSetID("0", "example.com.")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "", result)
|
assert.Equal(t, "", result)
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package vultr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/vultr/govultr"
|
"github.com/vultr/govultr"
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -36,13 +37,16 @@ const (
|
|||||||
vultrTTL = 3600
|
vultrTTL = 3600
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// VultrProvider is an implementation of Provider for Vultr DNS.
|
||||||
type VultrProvider struct {
|
type VultrProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
client govultr.Client
|
client govultr.Client
|
||||||
|
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
DryRun bool
|
DryRun bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VultrChanges differentiates between ChangActions.
|
||||||
type VultrChanges struct {
|
type VultrChanges struct {
|
||||||
Action string
|
Action string
|
||||||
|
|
||||||
@ -78,6 +82,7 @@ func (p *VultrProvider) Zones(ctx context.Context) ([]govultr.DNSDomain, error)
|
|||||||
return zones, nil
|
return zones, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Records returns the list of records.
|
||||||
func (p *VultrProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
func (p *VultrProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||||
zones, err := p.Zones(ctx)
|
zones, err := p.Zones(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -93,7 +98,7 @@ func (p *VultrProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range records {
|
for _, r := range records {
|
||||||
if supportedRecordType(r.Type) {
|
if provider.SupportedRecordType(r.Type) {
|
||||||
name := fmt.Sprintf("%s.%s", r.Name, zone.Domain)
|
name := fmt.Sprintf("%s.%s", r.Name, zone.Domain)
|
||||||
|
|
||||||
// root name is identified by the empty string and should be
|
// 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 zoneName, changes := range zoneChanges {
|
||||||
for _, change := range changes {
|
for _, change := range changes {
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"record": change.ResourceRecordSet.Name,
|
"record": change.ResourceRecordSet.Name,
|
||||||
"type": change.ResourceRecordSet.Type,
|
"type": change.ResourceRecordSet.Type,
|
||||||
@ -197,10 +201,10 @@ func (p *VultrProvider) submitChanges(ctx context.Context, changes []*VultrChang
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplyChanges applies a given set of changes in a given zone.
|
||||||
func (p *VultrProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
func (p *VultrProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||||
combinedChanges := make([]*VultrChanges, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
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))
|
changes := make([]*VultrChanges, 0, len(endpoints))
|
||||||
ttl := vultrTTL
|
ttl := vultrTTL
|
||||||
for _, e := range endpoints {
|
for _, e := range endpoints {
|
||||||
|
|
||||||
if e.RecordTTL.IsConfigured() {
|
if e.RecordTTL.IsConfigured() {
|
||||||
ttl = int(e.RecordTTL)
|
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 {
|
func seperateChangesByZone(zones []govultr.DNSDomain, changes []*VultrChanges) map[string][]*VultrChanges {
|
||||||
change := make(map[string][]*VultrChanges)
|
change := make(map[string][]*VultrChanges)
|
||||||
zoneNameID := zoneIDName{}
|
zoneNameID := provider.ZoneIDName{}
|
||||||
|
|
||||||
for _, z := range zones {
|
for _, z := range zones {
|
||||||
zoneNameID.Add(z.Domain, z.Domain)
|
zoneNameID.Add(z.Domain, z.Domain)
|
||||||
@ -250,7 +253,6 @@ func seperateChangesByZone(zones []govultr.DNSDomain, changes []*VultrChanges) m
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
change[zone] = append(change[zone], c)
|
change[zone] = append(change[zone], c)
|
||||||
|
|
||||||
}
|
}
|
||||||
return change
|
return change
|
||||||
}
|
}
|
||||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package provider
|
package vultr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -20,7 +20,7 @@ import "strings"
|
|||||||
|
|
||||||
// ZoneIDFilter holds a list of zone ids to filter by
|
// ZoneIDFilter holds a list of zone ids to filter by
|
||||||
type ZoneIDFilter struct {
|
type ZoneIDFilter struct {
|
||||||
zoneIDs []string
|
ZoneIDs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewZoneIDFilter returns a new ZoneIDFilter given a list of zone ids
|
// 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
|
// Match checks whether a zone matches one of the provided zone ids
|
||||||
func (f ZoneIDFilter) Match(zoneID string) bool {
|
func (f ZoneIDFilter) Match(zoneID string) bool {
|
||||||
// An empty filter includes all zones.
|
// An empty filter includes all zones.
|
||||||
if len(f.zoneIDs) == 0 {
|
if len(f.ZoneIDs) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, id := range f.zoneIDs {
|
for _, id := range f.ZoneIDs {
|
||||||
if strings.HasSuffix(zoneID, id) {
|
if strings.HasSuffix(zoneID, id) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,13 +18,13 @@ package provider
|
|||||||
|
|
||||||
import "strings"
|
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
|
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 {
|
for zoneID, zoneName := range z {
|
||||||
if hostname == zoneName || strings.HasSuffix(hostname, "."+zoneName) {
|
if hostname == zoneName || strings.HasSuffix(hostname, "."+zoneName) {
|
||||||
if suitableZoneName == "" || len(zoneName) > len(suitableZoneName) {
|
if suitableZoneName == "" || len(zoneName) > len(suitableZoneName) {
|
||||||
|
|||||||
@ -23,12 +23,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestZoneIDName(t *testing.T) {
|
func TestZoneIDName(t *testing.T) {
|
||||||
z := zoneIDName{}
|
z := ZoneIDName{}
|
||||||
z.Add("123456", "foo.bar")
|
z.Add("123456", "foo.bar")
|
||||||
z.Add("123456", "qux.baz")
|
z.Add("123456", "qux.baz")
|
||||||
z.Add("654321", "foo.qux.baz")
|
z.Add("654321", "foo.qux.baz")
|
||||||
|
|
||||||
assert.Equal(t, zoneIDName{
|
assert.Equal(t, ZoneIDName{
|
||||||
"123456": "qux.baz",
|
"123456": "qux.baz",
|
||||||
"654321": "foo.qux.baz",
|
"654321": "foo.qux.baz",
|
||||||
}, z)
|
}, z)
|
||||||
|
|||||||
@ -87,3 +87,7 @@ func (sdr *AWSSDRegistry) updateLabels(endpoints []*endpoint.Endpoint) {
|
|||||||
ep.Labels[endpoint.AWSSDDescriptionLabel] = ep.Labels.Serialize(false)
|
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/endpoint"
|
||||||
"sigs.k8s.io/external-dns/internal/testutils"
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
|
"sigs.k8s.io/external-dns/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inMemoryProvider struct {
|
type inMemoryProvider struct {
|
||||||
|
provider.BaseProvider
|
||||||
endpoints []*endpoint.Endpoint
|
endpoints []*endpoint.Endpoint
|
||||||
onApplyChanges func(changes *plan.Changes)
|
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 {
|
func (im *NoopRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||||
return im.provider.ApplyChanges(ctx, changes)
|
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/endpoint"
|
||||||
"sigs.k8s.io/external-dns/internal/testutils"
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
"sigs.k8s.io/external-dns/provider"
|
"sigs.k8s.io/external-dns/provider/inmemory"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Registry = &NoopRegistry{}
|
var _ Registry = &NoopRegistry{}
|
||||||
@ -38,7 +38,7 @@ func TestNoopRegistry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testNoopInit(t *testing.T) {
|
func testNoopInit(t *testing.T) {
|
||||||
p := provider.NewInMemoryProvider()
|
p := inmemory.NewInMemoryProvider()
|
||||||
r, err := NewNoopRegistry(p)
|
r, err := NewNoopRegistry(p)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, p, r.provider)
|
assert.Equal(t, p, r.provider)
|
||||||
@ -46,9 +46,9 @@ func testNoopInit(t *testing.T) {
|
|||||||
|
|
||||||
func testNoopRecords(t *testing.T) {
|
func testNoopRecords(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
p := provider.NewInMemoryProvider()
|
p := inmemory.NewInMemoryProvider()
|
||||||
p.CreateZone("org")
|
p.CreateZone("org")
|
||||||
providerRecords := []*endpoint.Endpoint{
|
inmemoryRecords := []*endpoint.Endpoint{
|
||||||
{
|
{
|
||||||
DNSName: "example.org",
|
DNSName: "example.org",
|
||||||
Targets: endpoint.Targets{"example-lb.com"},
|
Targets: endpoint.Targets{"example-lb.com"},
|
||||||
@ -56,21 +56,21 @@ func testNoopRecords(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
p.ApplyChanges(ctx, &plan.Changes{
|
p.ApplyChanges(ctx, &plan.Changes{
|
||||||
Create: providerRecords,
|
Create: inmemoryRecords,
|
||||||
})
|
})
|
||||||
|
|
||||||
r, _ := NewNoopRegistry(p)
|
r, _ := NewNoopRegistry(p)
|
||||||
|
|
||||||
eps, err := r.Records(ctx)
|
eps, err := r.Records(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.True(t, testutils.SameEndpoints(eps, providerRecords))
|
assert.True(t, testutils.SameEndpoints(eps, inmemoryRecords))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNoopApplyChanges(t *testing.T) {
|
func testNoopApplyChanges(t *testing.T) {
|
||||||
// do some prep
|
// do some prep
|
||||||
p := provider.NewInMemoryProvider()
|
p := inmemory.NewInMemoryProvider()
|
||||||
p.CreateZone("org")
|
p.CreateZone("org")
|
||||||
providerRecords := []*endpoint.Endpoint{
|
inmemoryRecords := []*endpoint.Endpoint{
|
||||||
{
|
{
|
||||||
DNSName: "example.org",
|
DNSName: "example.org",
|
||||||
Targets: endpoint.Targets{"old-lb.com"},
|
Targets: endpoint.Targets{"old-lb.com"},
|
||||||
@ -92,7 +92,7 @@ func testNoopApplyChanges(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
p.ApplyChanges(ctx, &plan.Changes{
|
p.ApplyChanges(ctx, &plan.Changes{
|
||||||
Create: providerRecords,
|
Create: inmemoryRecords,
|
||||||
})
|
})
|
||||||
|
|
||||||
// wrong changes
|
// 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
|
//correct changes
|
||||||
require.NoError(t, r.ApplyChanges(ctx, &plan.Changes{
|
require.NoError(t, r.ApplyChanges(ctx, &plan.Changes{
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import (
|
|||||||
type Registry interface {
|
type Registry interface {
|
||||||
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||||
ApplyChanges(ctx context.Context, changes *plan.Changes) error
|
ApplyChanges(ctx context.Context, changes *plan.Changes) error
|
||||||
|
PropertyValuesEqual(attribute string, previous string, current string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO(ideahitme): consider moving this to Plan
|
//TODO(ideahitme): consider moving this to Plan
|
||||||
|
|||||||
@ -43,12 +43,16 @@ type TXTRegistry struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTXTRegistry returns new TXTRegistry object
|
// 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 == "" {
|
if ownerID == "" {
|
||||||
return nil, errors.New("owner id cannot be empty")
|
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{
|
return &TXTRegistry{
|
||||||
provider: provider,
|
provider: provider,
|
||||||
@ -187,6 +191,11 @@ func (im *TXTRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
|||||||
return im.provider.ApplyChanges(ctx, filteredChanges)
|
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
|
TXT registry specific private methods
|
||||||
*/
|
*/
|
||||||
@ -201,26 +210,35 @@ type nameMapper interface {
|
|||||||
toTXTName(string) string
|
toTXTName(string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
type prefixNameMapper struct {
|
type affixNameMapper struct {
|
||||||
prefix string
|
prefix string
|
||||||
|
suffix string
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ nameMapper = prefixNameMapper{}
|
var _ nameMapper = affixNameMapper{}
|
||||||
|
|
||||||
func newPrefixNameMapper(prefix string) prefixNameMapper {
|
func newaffixNameMapper(prefix string, suffix string) affixNameMapper {
|
||||||
return prefixNameMapper{prefix: strings.ToLower(prefix)}
|
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)
|
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)
|
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 ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pr prefixNameMapper) toTXTName(endpointDNSName string) string {
|
func (pr affixNameMapper) toTXTName(endpointDNSName string) string {
|
||||||
return pr.prefix + endpointDNSName
|
DNSName := strings.SplitN(endpointDNSName, ".", 2)
|
||||||
|
return pr.prefix + DNSName[0] + pr.suffix + "." + DNSName[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *TXTRegistry) addToCache(ep *endpoint.Endpoint) {
|
func (im *TXTRegistry) addToCache(ep *endpoint.Endpoint) {
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import (
|
|||||||
"sigs.k8s.io/external-dns/internal/testutils"
|
"sigs.k8s.io/external-dns/internal/testutils"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
"sigs.k8s.io/external-dns/provider"
|
"sigs.k8s.io/external-dns/provider"
|
||||||
|
"sigs.k8s.io/external-dns/provider/inmemory"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,33 +43,44 @@ func TestTXTRegistry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testTXTRegistryNew(t *testing.T) {
|
func testTXTRegistryNew(t *testing.T) {
|
||||||
p := provider.NewInMemoryProvider()
|
p := inmemory.NewInMemoryProvider()
|
||||||
_, err := NewTXTRegistry(p, "txt", "", time.Hour)
|
_, err := NewTXTRegistry(p, "txt", "", "", time.Hour)
|
||||||
require.Error(t, err)
|
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)
|
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)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "owner", r.ownerID)
|
assert.Equal(t, "owner", r.ownerID)
|
||||||
assert.Equal(t, p, r.provider)
|
assert.Equal(t, p, r.provider)
|
||||||
|
|
||||||
r, err = NewTXTRegistry(p, "", "owner", time.Hour)
|
r, err = NewTXTRegistry(p, "", "", "owner", time.Hour)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, ok = r.mapper.(prefixNameMapper)
|
_, ok = r.mapper.(affixNameMapper)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTXTRegistryRecords(t *testing.T) {
|
func testTXTRegistryRecords(t *testing.T) {
|
||||||
t.Run("With prefix", testTXTRegistryRecordsPrefixed)
|
t.Run("With prefix", testTXTRegistryRecordsPrefixed)
|
||||||
|
t.Run("With suffix", testTXTRegistryRecordsSuffixed)
|
||||||
t.Run("No prefix", testTXTRegistryRecordsNoPrefix)
|
t.Run("No prefix", testTXTRegistryRecordsNoPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
p := provider.NewInMemoryProvider()
|
p := inmemory.NewInMemoryProvider()
|
||||||
p.CreateZone(testZone)
|
p.CreateZone(testZone)
|
||||||
p.ApplyChanges(ctx, &plan.Changes{
|
p.ApplyChanges(ctx, &plan.Changes{
|
||||||
Create: []*endpoint.Endpoint{
|
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)
|
records, _ := r.Records(ctx)
|
||||||
|
|
||||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||||
|
|
||||||
// Ensure prefix is case-insensitive
|
// 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)
|
records, _ = r.Records(ctx)
|
||||||
|
|
||||||
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
|
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||||
p := provider.NewInMemoryProvider()
|
p := inmemory.NewInMemoryProvider()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
p.CreateZone(testZone)
|
p.CreateZone(testZone)
|
||||||
p.ApplyChanges(ctx, &plan.Changes{
|
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)
|
records, _ := r.Records(ctx)
|
||||||
|
|
||||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||||
@ -248,11 +365,12 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
|||||||
|
|
||||||
func testTXTRegistryApplyChanges(t *testing.T) {
|
func testTXTRegistryApplyChanges(t *testing.T) {
|
||||||
t.Run("With Prefix", testTXTRegistryApplyChangesWithPrefix)
|
t.Run("With Prefix", testTXTRegistryApplyChangesWithPrefix)
|
||||||
|
t.Run("With Suffix", testTXTRegistryApplyChangesWithSuffix)
|
||||||
t.Run("No prefix", testTXTRegistryApplyChangesNoPrefix)
|
t.Run("No prefix", testTXTRegistryApplyChangesNoPrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
|
func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
|
||||||
p := provider.NewInMemoryProvider()
|
p := inmemory.NewInMemoryProvider()
|
||||||
p.CreateZone(testZone)
|
p.CreateZone(testZone)
|
||||||
ctxEndpoints := []*endpoint.Endpoint{}
|
ctxEndpoints := []*endpoint.Endpoint{}
|
||||||
ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
|
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"),
|
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{
|
changes := &plan.Changes{
|
||||||
Create: []*endpoint.Endpoint{
|
Create: []*endpoint.Endpoint{
|
||||||
@ -342,8 +460,99 @@ func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
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) {
|
func testTXTRegistryApplyChangesNoPrefix(t *testing.T) {
|
||||||
p := provider.NewInMemoryProvider()
|
p := inmemory.NewInMemoryProvider()
|
||||||
p.CreateZone(testZone)
|
p.CreateZone(testZone)
|
||||||
ctxEndpoints := []*endpoint.Endpoint{}
|
ctxEndpoints := []*endpoint.Endpoint{}
|
||||||
ctx := context.WithValue(context.Background(), provider.RecordsContextKey, ctxEndpoints)
|
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, ""),
|
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{
|
changes := &plan.Changes{
|
||||||
Create: []*endpoint.Endpoint{
|
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 {
|
func newEndpointWithOwnerAndLabels(dnsName, target, recordType, ownerID string, labels endpoint.Labels) *endpoint.Endpoint {
|
||||||
e := endpoint.NewEndpoint(dnsName, recordType, target)
|
e := endpoint.NewEndpoint(dnsName, recordType, target)
|
||||||
e.Labels[endpoint.OwnerLabelKey] = ownerID
|
e.Labels[endpoint.OwnerLabelKey] = ownerID
|
||||||
if labels != nil {
|
|
||||||
for k, v := range labels {
|
for k, v := range labels {
|
||||||
e.Labels[k] = v
|
e.Labels[k] = v
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,8 +17,8 @@ limitations under the License.
|
|||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
|
||||||
|
|
||||||
cfclient "github.com/cloudfoundry-community/go-cfclient"
|
cfclient "github.com/cloudfoundry-community/go-cfclient"
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ func NewCloudFoundrySource(cfClient *cfclient.Client) (Source, error) {
|
|||||||
}, nil
|
}, 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
|
// Endpoints returns endpoint objects
|
||||||
|
|||||||
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
@ -65,5 +66,5 @@ func (cs *connectorSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
|||||||
return endpoints, nil
|
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
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
@ -92,7 +92,7 @@ func NewCRDClientForAPIVersionKind(client kubernetes.Interface, kubeConfig, kube
|
|||||||
|
|
||||||
config.ContentConfig.GroupVersion = &groupVersion
|
config.ContentConfig.GroupVersion = &groupVersion
|
||||||
config.APIPath = "/apis"
|
config.APIPath = "/apis"
|
||||||
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}
|
config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}
|
||||||
|
|
||||||
crdClient, err := rest.UnversionedRESTClientFor(config)
|
crdClient, err := rest.UnversionedRESTClientFor(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -112,7 +112,7 @@ func NewCRDSource(crdClient rest.Interface, namespace, kind string, annotationFi
|
|||||||
}, nil
|
}, 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.
|
// 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