Merge remote-tracking branch 'kubernetes-sigs/master'

This commit is contained in:
Thomas Kosiewski 2023-06-01 18:49:38 +02:00
commit 95abe994c4
No known key found for this signature in database
GPG Key ID: F61C90EB9A479069
134 changed files with 4705 additions and 1408 deletions

View File

@ -21,7 +21,7 @@ jobs:
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.19
id: go

View File

@ -18,13 +18,13 @@ jobs:
with:
fetch-depth: 0
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: "pip"
cache-dependency-path: "./docs/scripts/requirements.txt"
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: ^1.19

View File

@ -0,0 +1,23 @@
name: json-yaml-validate
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
pull-requests: write # enable write permissions for pull requests
jobs:
json-yaml-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: json-yaml-validate
uses: GrantBirki/json-yaml-validate@v1.2.0
with:
comment: "true" # enable comment mode
yaml_exclude_regex: "(charts/external-dns/templates.*|mkdocs.yml)"

View File

@ -21,7 +21,7 @@ jobs:
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.19
id: go

View File

@ -0,0 +1,37 @@
name: Build all images
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
permissions:
contents: read # to fetch code (actions/checkout)
checks: write # to create a new check based on the results (shogo82148/actions-goveralls)
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v4
with:
go-version: 1.19
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Install CI
run: |
go get -v -t -d ./...
- name: Test
run: make build.image/multiarch

View File

@ -24,12 +24,10 @@ COPY go.sum .
RUN go mod download
COPY . .
RUN make test build.$ARCH
# final image
FROM $ARCH/alpine:3.17
FROM alpine:3.18
RUN apk update && apk add "libcrypto3>=3.0.8-r0" "libssl3>=3.0.8-r0" && rm -rf /var/cache/apt/*
RUN apk update && apk add "libcrypto3>=3.0.8-r4" "libssl3>=3.0.8-r4" && rm -rf /var/cache/apt/*
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /sigs.k8s.io/external-dns/build/external-dns /bin/external-dns

View File

@ -19,7 +19,7 @@
cover:
go get github.com/wadey/gocovmerge
$(eval PKGS := $(shell go list ./... | grep -v /vendor/))
$(eval PKGS_DELIM := $(shell echo $(PKGS) | sed -e 's/ /,/g'))
$(eval PKGS_DELIM := $(shell echo $(PKGS) | tr / -'))
go list -f '{{if or (len .TestGoFiles) (len .XTestGoFiles)}}go test -test.v -test.timeout=120s -covermode=count -coverprofile={{.Name}}_{{len .Imports}}_{{len .Deps}}.coverprofile -coverpkg $(PKGS_DELIM) {{.ImportPath}}{{end}}' $(PKGS) | xargs -0 sh -c
gocovmerge `ls *.coverprofile` > cover.out
rm *.coverprofile
@ -90,8 +90,11 @@ IMAGE ?= us.gcr.io/k8s-artifacts-prod/external-dns/$(BINARY)
VERSION ?= $(shell git describe --tags --always --dirty)
BUILD_FLAGS ?= -v
LDFLAGS ?= -X sigs.k8s.io/external-dns/pkg/apis/externaldns.Version=$(VERSION) -w -s
ARCHS = amd64 arm64v8 arm32v7
SHELL = /bin/bash
ARCHS = amd64 arm64 arm/v7
ARCH ?= amd64
DEFAULT_ARCH = amd64
SHELL = /bin/bash
OUTPUT_TYPE ?= docker
build: build/$(BINARY)
@ -99,37 +102,65 @@ build: build/$(BINARY)
build/$(BINARY): $(SOURCES)
CGO_ENABLED=0 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.push/multiarch:
build.push/multiarch: $(addprefix build.push-,$(ARCHS))
arch_specific_tags=()
for arch in $(ARCHS); do \
image="$(IMAGE):$(VERSION)-$${arch}" ;\
# pre-pull due to https://github.com/kubernetes-sigs/cluster-addons/pull/84/files ;\
docker pull $${arch}/alpine:3.17 ;\
docker pull golang:1.19 ;\
DOCKER_BUILDKIT=1 docker build --rm --tag $${image} --build-arg VERSION="$(VERSION)" --build-arg ARCH="$${arch}" . ;\
docker push $${image} ;\
arch_specific_tags+=( "--amend $${image}" ) ;\
image="$(IMAGE):$(VERSION)-$$(echo $$arch | tr / -)" ;\
arch_specific_tags+=( " $${image}" ) ;\
done ;\
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create "$(IMAGE):$(VERSION)" $${arch_specific_tags[@]} ;\
for arch in $(ARCHS); do \
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate --arch $${arch} "$(IMAGE):$(VERSION)" "$(IMAGE):$(VERSION)-$${arch}" ;\
done;\
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(IMAGE):$(VERSION)" \
echo $${arch_specific_tags[@]} ;\
DOCKER_CLI_EXPERIMENTAL=enabled docker buildx imagetools create --tag "$(IMAGE):$(VERSION)" $${arch_specific_tags[@]} ;\
build.push: build.docker
docker push "$(IMAGE):$(VERSION)"
build.image/multiarch: $(addprefix build.image-,$(ARCHS))
build.arm64v8:
build.image:
$(MAKE) ARCH=$(ARCH) OUTPUT_TYPE=docker build.docker
build.image-amd64:
$(MAKE) ARCH=amd64 build.image
build.image-arm64:
$(MAKE) ARCH=arm64 build.image
build.image-arm/v7:
$(MAKE) ARCH=arm/v7 build.image
build.push:
$(MAKE) ARCH=$(ARCH) OUTPUT_TYPE=registry build.docker
build.push-amd64:
$(MAKE) ARCH=amd64 build.push
build.push-arm64:
$(MAKE) ARCH=arm64 build.push
build.push-arm/v7:
$(MAKE) ARCH=arm/v7 build.push
build.arm64:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.amd64:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.arm32v7:
build.arm/v7:
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.docker:
docker build --rm --tag "$(IMAGE):$(VERSION)" --build-arg VERSION="$(VERSION)" --build-arg ARCH="amd64" .
build.setup:
docker buildx inspect img-builder > /dev/null || docker buildx create --name img-builder --use
build.docker: build.setup build.$(ARCH)
docker build --rm --tag "$(IMAGE):$(VERSION)" --build-arg VERSION="$(VERSION)" --build-arg ARCH="$(ARCH)" .
image="$(IMAGE):$(VERSION)-$(subst /,-,$(ARCH))"; \
docker buildx build \
--pull \
--provenance=false \
--sbom=false \
--output=type=$(OUTPUT_TYPE) \
--platform linux/$(ARCH) \
--build-arg ARCH="$(ARCH)" \
--build-arg VERSION="$(VERSION)" \
--tag $${image} .
build.mini:
docker build --rm --tag "$(IMAGE):$(VERSION)-mini" --build-arg VERSION="$(VERSION)" -f Dockerfile.mini .
@ -140,8 +171,8 @@ clean:
# Builds and push container images to the staging bucket.
.PHONY: release.staging
release.staging:
release.staging: test
IMAGE=$(IMAGE_STAGING) $(MAKE) build.push/multiarch
release.prod:
release.prod: test
$(MAKE) build.push/multiarch

View File

@ -5,7 +5,7 @@ hide:
---
<p align="center">
<img src="docs/img/external-dns.png" width="40%" align="center" alt="ExternalDNS">
<img src="img/external-dns.png" width="40%" align="center" alt="ExternalDNS">
</p>
# ExternalDNS
@ -176,6 +176,7 @@ The following tutorials are provided:
* [Nginx Ingress Controller](docs/tutorials/nginx-ingress.md)
* [NS1](docs/tutorials/ns1.md)
* [NS Record Creation with CRD Source](docs/tutorials/ns-record.md)
* [MX Record Creation with CRD Source](docs/tutorials/mx-record.md)
* [OpenStack Designate](docs/tutorials/designate.md)
* [Oracle Cloud Infrastructure (OCI) DNS](docs/tutorials/oracle.md)
* [PowerDNS](docs/tutorials/pdns.md)

View File

@ -7,15 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
<!-- ## [vX.Y.Z] - UNRELEASED
### Highlights
## [UNRELEASED]
### All Changes
- Added
- Updated
- Changed
- Fixed
- Deprecated
- Removed -->
## [v1.12.2] - UNRELEASED
### All Changes
- Added support for ServiceMonitor relabelling. ([#3366](https://github.com/kubernetes-sigs/external-dns/pull/3366)) [@jkroepke](https://github.com/jkroepke)
- Updated chart icon path. ([#3492](https://github.com/kubernetes-sigs/external-dns/pull/3494)) [kundan2707](https://github.com/kundan2707)
- Added RBAC for Gateway-API resources to ClusterRole. ([#3499](https://github.com/kubernetes-sigs/external-dns/pull/3499)) [@michaelvl](https://github.com/MichaelVL)
- Added RBAC for F5 VirtualServer to ClusterRole. ([#3503](https://github.com/kubernetes-sigs/external-dns/pull/3503)) [@mikejoh](https://github.com/mikejoh)
- Added support for running ExternalDNS with namespaced scope. ([#3403](https://github.com/kubernetes-sigs/external-dns/pull/3403)) [@jkroepke](https://github.com/jkroepke)
- Updated _ExternalDNS_ version to [v0.13.4](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.13.4). ([#3516](https://github.com/kubernetes-sigs/external-dns/pull/3516)) [@stevehipwell](https://github.com/stevehipwell)
## [v1.12.1] - 2023-02-06

View File

@ -2,8 +2,8 @@ apiVersion: v2
name: external-dns
description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
type: application
version: 1.12.1
appVersion: 0.13.2
version: 1.12.2
appVersion: 0.13.4
keywords:
- kubernetes
- externaldns
@ -12,7 +12,7 @@ keywords:
- service
- ingress
home: https://github.com/kubernetes-sigs/external-dns/
icon: https://github.com/kubernetes-sigs/external-dns/raw/master/img/external-dns.png
icon: https://github.com/kubernetes-sigs/external-dns/raw/master/docs/img/external-dns.png
sources:
- https://github.com/kubernetes-sigs/external-dns/
maintainers:
@ -20,9 +20,15 @@ maintainers:
email: steve.hipwell@gmail.com
annotations:
artifacthub.io/changes: |
- kind: changed
description: "Updated ExternalDNS version to v0.13.2."
- kind: added
description: "Added secretConfiguration.subPath to mount specific files from secret as a sub-path."
description: "Added support for ServiceMonitor relabelling."
- kind: changed
description: "Changed to use registry.k8s.io instead of k8s.gcr.io."
description: "Updated chart icon path."
- kind: added
description: "Added RBAC for Gateway-API resources to ClusterRole."
- kind: added
description: "Added RBAC for F5 VirtualServer to ClusterRole."
- kind: added
description: "Added support for running ExternalDNS with namespaced scope."
- kind: changed
description: "Updated _ExternalDNS_ version to [v0.13.4](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.13.4)."

View File

@ -69,6 +69,7 @@ The following table lists the configurable parameters of the _ExternalDNS_ chart
| `logFormat` | Formats of the logs, available values are: `text`, `json`. | `text` |
| `interval` | The interval for DNS updates. | `1m` |
| `triggerLoopOnEvent` | When enabled, triggers run loop on create/update/delete events in addition of regular interval. | `false` |
| `namespaced` | When enabled, external-dns runs on namespace scope. Additionally, Role and Rolebinding will be namespaced, too. | `false` |
| `sources` | K8s resources type to be observed for new DNS entries. | See _values.yaml_ |
| `policy` | How DNS records are synchronized between sources and providers, available values are: `sync`, `upsert-only`. | `upsert-only` |
| `registry` | Registry Type, available types are: `txt`, `noop`. | `txt` |
@ -82,3 +83,36 @@ The following table lists the configurable parameters of the _ExternalDNS_ chart
| `secretConfiguration.mountPath` | Mount path of secret configuration secret (this can be templated). | `""` |
| `secretConfiguration.data` | Secret configuration secret data. Could be used to store DNS provider credentials. | `{}` |
| `secretConfiguration.subPath` | Sub-path of secret configuration secret (this can be templated). | `""` |
## Namespaced scoped installation
external-dns supports running on a namespaced only scope, too.
If `namespaced=true` is defined, the helm chart will setup `Roles` and `RoleBindings` instead `ClusterRoles` and `ClusterRoleBindings`.
### Limited supported
Not all sources are supported in namespaced scope, since some sources depends on cluster-wide resources.
For example: Source `node` isn't supported, since `kind: Node` has scope `Cluster`.
Sources like `istio-virtualservice` only work, if all resources like `Gateway` and `VirtualService` are present in the same
namespaces as `external-dns`.
The annotation `external-dns.alpha.kubernetes.io/endpoints-type: NodeExternalIP` is not supported.
If `namespaced` is set to `true`, please ensure that `sources` my only contains supported sources (Default: `service,ingress`.
### Support matrix
| Source | Supported | Infos |
|------------------------|-----------|------------------------|
| `ingress` | ✅ | |
| `istio-gateway` | ✅ | |
| `istio-virtualservice` | ✅ | |
| `contour-ingressroute` | ✅ | |
| `crd` | ✅ | |
| `kong-tcpingress` | ✅ | |
| `openshift-route` | ✅ | |
| `skipper-routegroup` | ✅ | |
| `gloo-proxy` | ✅ | |
| `contour-httpproxy` | ✅ | |
| `service` | ⚠️️ | NodePort not supported |
| `node` | ❌ | |
| `pod` | ❌ | |

View File

@ -1,12 +1,12 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
kind: {{ .Values.namespaced | ternary "Role" "ClusterRole" }}
metadata:
name: {{ template "external-dns.fullname" . }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
rules:
{{- if or (has "node" .Values.sources) (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
{{- if and (not .Values.namespaced) (or (has "node" .Values.sources) (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources)) }}
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
@ -60,6 +60,41 @@ rules:
resources: ["dnsendpoints/status"]
verbs: ["*"]
{{- end }}
{{- if or (has "gateway-httproute" .Values.sources) (has "gateway-grpcroute" .Values.sources) (has "gateway-tlsroute" .Values.sources) (has "gateway-tcproute" .Values.sources) (has "gateway-udproute" .Values.sources) }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-httproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-httproute" .Values.sources }}
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-grpcroute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["grpcroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-tlsroute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tlsroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-tcproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tcproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-udproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["udproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gloo-proxy" .Values.sources }}
- apiGroups: ["gloo.solo.io","gateway.solo.io"]
resources: ["proxies","virtualservices"]
@ -83,6 +118,11 @@ rules:
resources: ["routegroups/status"]
verbs: ["patch","update"]
{{- end }}
{{- if has "f5-virtualserver" .Values.sources }}
- apiGroups: ["cis.f5.com"]
resources: ["virtualservers"]
verbs: ["get","watch","list"]
{{- end }}
{{- with .Values.rbac.additionalPermissions }}
{{- toYaml . | nindent 2 }}
{{- end }}

View File

@ -1,13 +1,13 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
kind: {{ .Values.namespaced | ternary "RoleBinding" "ClusterRoleBinding" }}
metadata:
name: {{ printf "%s-viewer" (include "external-dns.fullname" .) }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
kind: {{ .Values.namespaced | ternary "Role" "ClusterRole" }}
name: {{ template "external-dns.fullname" . }}
subjects:
- kind: ServiceAccount

View File

@ -89,6 +89,9 @@ spec:
- --txt-suffix={{ .Values.txtSuffix }}
{{- end }}
{{- end }}
{{- if .Values.namespaced }}
- --namespace={{ .Release.Namespace }}
{{- end }}
{{- range .Values.domainFilters }}
- --domain-filter={{ . }}
{{- end }}

View File

@ -151,6 +151,8 @@ logFormat: text
interval: 1m
triggerLoopOnEvent: false
namespaced: false
sources:
- service
- ingress

View File

@ -2,15 +2,22 @@
timeout: 5000s
options:
substitution_option: ALLOW_LOOSE
machineType: 'N1_HIGHCPU_8'
steps:
- name: "gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90"
entrypoint: make
- name: "gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20230206-8160eea68e"
entrypoint: bash
env:
- DOCKER_CLI_EXPERIMENTAL=enabled
- VERSION=$_GIT_TAG
- PULL_BASE_REF=$_PULL_BASE_REF
- HOME=/root
args:
- release.staging
- -c
- |
gcloud auth configure-docker
/buildx-entrypoint version
apk add musl-dev gcc
make release.staging
substitutions:
# _GIT_TAG will be filled with a git-based tag for the image, of the form vYYYYMMDD-hash, and
# can be used as a substitution

View File

@ -102,6 +102,14 @@ var (
Help: "Number of Registry A records.",
},
)
registryAAAARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "registry",
Name: "aaaa_records",
Help: "Number of Registry AAAA records.",
},
)
sourceARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
@ -110,6 +118,14 @@ var (
Help: "Number of Source A records.",
},
)
sourceAAAARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "source",
Name: "aaaa_records",
Help: "Number of Source AAAA records.",
},
)
verifiedARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
@ -118,6 +134,14 @@ var (
Help: "Number of DNS A-records that exists both in source and registry.",
},
)
verifiedAAAARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "controller",
Name: "verified_aaaa_records",
Help: "Number of DNS AAAA-records that exists both in source and registry.",
},
)
)
func init() {
@ -130,8 +154,11 @@ func init() {
prometheus.MustRegister(deprecatedSourceErrors)
prometheus.MustRegister(controllerNoChangesTotal)
prometheus.MustRegister(registryARecords)
prometheus.MustRegister(registryAAAARecords)
prometheus.MustRegister(sourceARecords)
prometheus.MustRegister(sourceAAAARecords)
prometheus.MustRegister(verifiedARecords)
prometheus.MustRegister(verifiedAAAARecords)
}
// Controller is responsible for orchestrating the different components.
@ -171,8 +198,9 @@ func (c *Controller) RunOnce(ctx context.Context) error {
missingRecords := c.Registry.MissingRecords()
registryEndpointsTotal.Set(float64(len(records)))
regARecords := filterARecords(records)
registryARecords.Set(float64(len(regARecords)))
regARecords, regAAAARecords := countAddressRecords(records)
registryARecords.Set(float64(regARecords))
registryAAAARecords.Set(float64(regAAAARecords))
ctx = context.WithValue(ctx, provider.RecordsContextKey, records)
endpoints, err := c.Source.Endpoints(ctx)
@ -182,10 +210,12 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return err
}
sourceEndpointsTotal.Set(float64(len(endpoints)))
srcARecords := filterARecords(endpoints)
sourceARecords.Set(float64(len(srcARecords)))
vRecords := fetchMatchingARecords(endpoints, records)
verifiedARecords.Set(float64(len(vRecords)))
srcARecords, srcAAAARecords := countAddressRecords(endpoints)
sourceARecords.Set(float64(srcARecords))
sourceAAAARecords.Set(float64(srcAAAARecords))
vARecords, vAAAARecords := countMatchingAddressRecords(endpoints, records)
verifiedARecords.Set(float64(vARecords))
verifiedAAAARecords.Set(float64(vAAAARecords))
endpoints = c.Registry.AdjustEndpoints(endpoints)
if len(missingRecords) > 0 {
@ -238,30 +268,44 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return nil
}
// Checks and returns the intersection of A records in endpoint and registry.
func fetchMatchingARecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) []string {
aRecords := filterARecords(endpoints)
recordsMap := make(map[string]struct{})
// Counts the intersections of A and AAAA records in endpoint and registry.
func countMatchingAddressRecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) (int, int) {
recordsMap := make(map[string]map[string]struct{})
for _, regRecord := range registryRecords {
recordsMap[regRecord.DNSName] = struct{}{}
if _, found := recordsMap[regRecord.DNSName]; !found {
recordsMap[regRecord.DNSName] = make(map[string]struct{})
}
recordsMap[regRecord.DNSName][regRecord.RecordType] = struct{}{}
}
var cm []string
for _, sourceRecord := range aRecords {
if _, found := recordsMap[sourceRecord]; found {
cm = append(cm, sourceRecord)
aCount := 0
aaaaCount := 0
for _, sourceRecord := range endpoints {
if _, found := recordsMap[sourceRecord.DNSName]; found {
if _, found := recordsMap[sourceRecord.DNSName][sourceRecord.RecordType]; found {
switch sourceRecord.RecordType {
case endpoint.RecordTypeA:
aCount++
case endpoint.RecordTypeAAAA:
aaaaCount++
}
}
}
}
return cm
return aCount, aaaaCount
}
func filterARecords(endpoints []*endpoint.Endpoint) []string {
var aRecords []string
func countAddressRecords(endpoints []*endpoint.Endpoint) (int, int) {
aCount := 0
aaaaCount := 0
for _, endPoint := range endpoints {
if endPoint.RecordType == endpoint.RecordTypeA {
aRecords = append(aRecords, endPoint.DNSName)
switch endPoint.RecordType {
case endpoint.RecordTypeA:
aCount++
case endpoint.RecordTypeAAAA:
aaaaCount++
}
}
return aRecords
return aCount, aaaaCount
}
// ScheduleRunOnce makes sure execution happens at most once per interval.
@ -292,7 +336,7 @@ func (c *Controller) Run(ctx context.Context) {
for {
if c.ShouldRunOnce(time.Now()) {
if err := c.RunOnce(ctx); err != nil {
log.Error(err)
log.Fatal(err)
}
}
select {

View File

@ -21,6 +21,7 @@ import (
"errors"
"math"
"reflect"
"sort"
"testing"
"time"
@ -83,32 +84,20 @@ func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
// ApplyChanges validates that the passed in changes satisfy the assumptions.
func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
if len(changes.Create) != len(p.ExpectChanges.Create) {
return errors.New("number of created records is wrong")
if err := verifyEndpoints(changes.Create, p.ExpectChanges.Create); err != nil {
return err
}
for i := range changes.Create {
if changes.Create[i].DNSName != p.ExpectChanges.Create[i].DNSName || !changes.Create[i].Targets.Same(p.ExpectChanges.Create[i].Targets) {
return errors.New("created record is wrong")
}
if err := verifyEndpoints(changes.UpdateNew, p.ExpectChanges.UpdateNew); err != nil {
return err
}
for i := range changes.UpdateNew {
if changes.UpdateNew[i].DNSName != p.ExpectChanges.UpdateNew[i].DNSName || !changes.UpdateNew[i].Targets.Same(p.ExpectChanges.UpdateNew[i].Targets) {
return errors.New("delete record is wrong")
}
if err := verifyEndpoints(changes.UpdateOld, p.ExpectChanges.UpdateOld); err != nil {
return err
}
for i := range changes.UpdateOld {
if changes.UpdateOld[i].DNSName != p.ExpectChanges.UpdateOld[i].DNSName || !changes.UpdateOld[i].Targets.Same(p.ExpectChanges.UpdateOld[i].Targets) {
return errors.New("delete record is wrong")
}
}
for i := range changes.Delete {
if changes.Delete[i].DNSName != p.ExpectChanges.Delete[i].DNSName || !changes.Delete[i].Targets.Same(p.ExpectChanges.Delete[i].Targets) {
return errors.New("delete record is wrong")
}
if err := verifyEndpoints(changes.Delete, p.ExpectChanges.Delete); err != nil {
return err
}
if !reflect.DeepEqual(ctx.Value(provider.RecordsContextKey), p.RecordsStore) {
@ -117,6 +106,21 @@ func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes)
return nil
}
func verifyEndpoints(actual, expected []*endpoint.Endpoint) error {
if len(actual) != len(expected) {
return errors.New("number of records is wrong")
}
sort.Slice(actual, func(i, j int) bool {
return actual[i].DNSName < actual[j].DNSName
})
for i := range actual {
if actual[i].DNSName != expected[i].DNSName || !actual[i].Targets.Same(expected[i].Targets) {
return errors.New("record is wrong")
}
}
return nil
}
// newMockProvider creates a new mockProvider returning the given endpoints and validating the desired changes.
func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) provider.Provider {
dnsProvider := &mockProvider{
@ -132,7 +136,7 @@ func TestRunOnce(t *testing.T) {
// Fake some desired endpoints coming from our source.
source := new(testutils.MockSource)
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
source.On("Endpoints").Return([]*endpoint.Endpoint{
{
DNSName: "create-record",
@ -144,6 +148,16 @@ func TestRunOnce(t *testing.T) {
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.4.4"},
},
{
DNSName: "create-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
}, nil)
// Fake some existing records in our DNS provider and validate some desired changes.
@ -159,18 +173,32 @@ func TestRunOnce(t *testing.T) {
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"4.3.2.1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
{
DNSName: "delete-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::4"},
},
},
&plan.Changes{
Create: []*endpoint.Endpoint{
{DNSName: "create-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
{DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
UpdateNew: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::2"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}},
},
UpdateOld: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::3"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}},
},
Delete: []*endpoint.Endpoint{
{DNSName: "delete-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::4"}},
{DNSName: "delete-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}},
},
},
@ -193,6 +221,7 @@ func TestRunOnce(t *testing.T) {
source.AssertExpectations(t)
// check the verified records
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedAAAARecords))
}
func valueFromMetric(metric prometheus.Gauge) uint64 {
@ -253,7 +282,7 @@ func TestShouldRunOnce(t *testing.T) {
func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter endpoint.DomainFilterInterface, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
t.Helper()
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil)
@ -526,6 +555,85 @@ func TestVerifyARecords(t *testing.T) {
}},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedAAAARecords))
}
func TestVerifyAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
},
[]*plan.Changes{},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
}},
)
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
}
func TestARecords(t *testing.T) {
@ -628,3 +736,50 @@ func TestMissingRecordsApply(t *testing.T) {
},
})
}
func TestAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
}},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceAAAARecords))
assert.Equal(t, math.Float64bits(1), valueFromMetric(registryAAAARecords))
}

View File

@ -0,0 +1,18 @@
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: examplednsrecord
spec:
endpoints:
- dnsName: subdomain.foo.bar.com
providerSpecific:
- name: "aws/failover"
value: "PRIMARY"
- name: "aws/health-check-id"
value: "asdf1234-as12-as12-as12-asdf12345678"
- name: "aws/evaluate-target-health"
value: "true"
recordType: CNAME
setIdentifier: some-unique-id
targets:
- other-subdomain.foo.bar.com

View File

@ -9,3 +9,7 @@ spec:
recordType: A
targets:
- 192.168.99.216
# Provider specific configurations are set like an annotation would on other sources
providerSpecific:
- name: external-dns.alpha.kubernetes.io/cloudflare-proxied
value: "true"

View File

@ -178,16 +178,18 @@ You can use the host label in the metric to figure out if the request was agains
Here is the full list of available metrics provided by ExternalDNS:
| Name | Description | Type |
| --------------------------------------------------- | ------------------------------------------------------- | ------- |
| external_dns_controller_last_sync_timestamp_seconds | Timestamp of last successful sync with the DNS provider | Gauge |
| external_dns_registry_endpoints_total | Number of Endpoints in all sources | Gauge |
| external_dns_registry_errors_total | Number of Registry errors | Counter |
| external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge |
| external_dns_source_errors_total | Number of Source errors | Counter |
| external_dns_controller_verified_records | Number of DNS A-records that exists both in | Gauge |
| | source & registry | |
| Name | Description | Type |
| --------------------------------------------------- | ------------------------------------------------------------------ | ------- |
| external_dns_controller_last_sync_timestamp_seconds | Timestamp of last successful sync with the DNS provider | Gauge |
| external_dns_registry_endpoints_total | Number of Endpoints in all sources | Gauge |
| external_dns_registry_errors_total | Number of Registry errors | Counter |
| external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge |
| external_dns_source_errors_total | Number of Source errors | Counter |
| external_dns_controller_verified_aaaa_records | Number of DNS AAAA-records that exists both in source and registry | Gauge |
| external_dns_controller_verified_a_records | Number of DNS A-records that exists both in source and registry | Gauge |
| external_dns_registry_aaaa_records | Number of AAAA records in registry | Gauge |
| external_dns_registry_a_records | Number of A records in registry | Gauge |
| external_dns_source_aaaa_records | Number of AAAA records in source | Gauge |
| external_dns_source_a_records | Number of A records in source | Gauge |
### How can I run ExternalDNS under a specific GCP Service Account, e.g. to access DNS records in other projects?
@ -205,7 +207,7 @@ $ docker run \
-e EXTERNAL_DNS_SOURCE=$'service\ningress' \
-e EXTERNAL_DNS_PROVIDER=google \
-e EXTERNAL_DNS_DOMAIN_FILTER=$'foo.com\nbar.com' \
registry.k8s.io/external-dns/external-dns:v0.13.2
registry.k8s.io/external-dns/external-dns:v0.13.5
time="2017-08-08T14:10:26Z" level=info msg="config: &{APIServerURL: KubeConfig: Sources:[service ingress] Namespace: ...
```
@ -255,24 +257,33 @@ spec:
### Running an internal and external dns service
Sometimes you need to run an internal and an external dns service.
The internal one should provision hostnames used on the internal network (perhaps inside a VPC), and the external
one to expose DNS to the internet.
The internal one should provision hostnames used on the internal network (perhaps inside a VPC), and the external one to expose DNS to the internet.
To do this with ExternalDNS you can use the `--annotation-filter` to specifically tie an instance of ExternalDNS to
an instance of an ingress controller. Let's assume you have two ingress controllers `nginx-internal` and `nginx-external`
then you can start two ExternalDNS providers one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-internal)`
and one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)`.
To do this with ExternalDNS you can use the `--ingress-class` flag to specifically tie an instance of ExternalDNS to an instance of a ingress controller.
Let's assume you have two ingress controllers, `internal` and `external`.
You can then start two ExternalDNS providers, one with `--ingress-class=internal` and one with `--ingress-class=external`.
If you need to search for multiple values of said annotation, you can provide a comma separated list, like so:
`--annotation-filter=kubernetes.io/ingress.class in (nginx-internal, alb-ingress-internal)`.
If you need to search for multiple ingress classes, you can specify the flag multiple times, like so:
`--ingress-class=internal --ingress-class=external`.
Beware when using multiple sources, e.g. `--source=service --source=ingress`, `--annotation-filter` will filter every given source objects.
If you need to filter only one specific source you have to run a separated external dns service containing only the wanted `--source` and `--annotation-filter`.
The `--ingress-class` flag will check both the `spec.ingressClassName` field and the deprecated `kubernetes.io/ingress.class` annotation.
The `spec.ingressClassName` tasks precedence over the annotation if both are supplied.
**Note:** Filtering based on annotation means that the external-dns controller will receive all resources of that kind and then filter on the client-side.
In larger clusters with many resources which change frequently this can cause performance issues. If only some resources need to be managed by an instance
of external-dns then label filtering can be used instead of annotation filtering. This means that only those resources which match the selector specified
in `--label-filter` will be passed to the controller.
**Backward compatibility**
The previous `--annotation-filter` flag can still be used to restrict which objects ExternalDNS considers; for example, `--annotation-filter=kubernetes.io/ingress.class in (public,dmz)`.
However, beware when using annotation filters with multiple sources, e.g. `--source=service --source=ingress`, since `--annotation-filter` will filter every given source object.
If you need to use annotation filters against a specific source you have to run a separated external dns service containing only the wanted `--source` and `--annotation-filter`.
Note: the `--ingress-class` flag cannot be used at the same time as the `--annotation-filter=kubernetes.io/ingress.class in (...)` flag; if you do this an error will be raised.
**Performance considerations**
Filtering based on ingress class name or annotations means that the external-dns controller will receive all resources of that kind and then filter on the client-side.
In larger clusters with many resources which change frequently this can cause performance issues.
If only some resources need to be managed by an instance of external-dns then label filtering can be used instead of ingress class filtering (or legacy annotation filtering).
This means that only those resources which match the selector specified in `--label-filter` will be passed to the controller.
### How do I specify that I want the DNS record to point to either the Node's public or private IP when it has both?

View File

@ -13,3 +13,68 @@ The controller will try to create the "new format" TXT records if they are not p
Later on, the old format will be dropped and only the new format will be kept (<record_type>-<endpoint_name>).
Cleanup will be done by controller itself.
### Encryption of TXT Records
TXT records may contain sensitive information, such as the internal ingress name or namespace, which attackers could exploit to gather information about your infrastructure.
By encrypting TXT records, you can protect this information from unauthorized access. It is strongly recommended to encrypt all TXT records to prevent potential security breaches.
To enable encryption of TXT records, you can use the following parameters:
- `--txt-encrypt-enabled=true`
- `--txt-encrypt-aes-key=32bytesKey` (used for AES-256-GCM encryption and should be exactly 32 bytes)
Note that the key used for encryption should be a secure key and properly managed to ensure the security of your TXT records.
### Generating TXT encryption AES key
Python
```python
python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'
```
Bash
```shell
dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d -- '\n' | tr -- '+/' '-_'; echo
```
OpenSSL
```shell
openssl rand -base64 32 | tr -- '+/' '-_'
```
PowerShell
```powershell
# Add System.Web assembly to session, just in case
Add-Type -AssemblyName System.Web
[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([System.Web.Security.Membership]::GeneratePassword(32,4))).Replace("+","-").Replace("/","_")
```
Terraform
```hcl
resource "random_password" "txt_key" {
length = 32
override_special = "-_"
}
```
### Manually Encrypt/Decrypt TXT Records
In some cases, you may need to edit labels generated by External-DNS, and in such cases, you can use simple Golang code to do that.
```go
package main
import (
"fmt"
"sigs.k8s.io/external-dns/endpoint"
)
func main() {
key := []byte("testtesttesttesttesttesttesttest")
encrypted, _ := endpoint.EncryptText(
"heritage=external-dns,external-dns/owner=example,external-dns/resource=ingress/default/example",
key,
nil,
)
decrypted, _, _ := endpoint.DecryptText(encrypted, key)
fmt.Println(decrypted)
}
```

View File

@ -48,7 +48,7 @@ spec:
- name: external-dns
# You will need to check what the latest version is yourself:
# https://github.com/kubernetes-sigs/external-dns/releases
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
# (optional) limit to only example.com domains; change to match the
@ -114,7 +114,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
# (optional) limit to only example.com domains; change to match the

View File

@ -57,7 +57,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # or ingress or both
- --provider=akamai
@ -143,7 +143,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # or ingress or both
- --provider=akamai

View File

@ -113,7 +113,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -187,7 +187,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -233,9 +233,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foo
annotations:
kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller.
spec:
ingressClassName: nginx # use the one that corresponds to your ingress controller.
rules:
- host: foo.external-dns-test.com
http:

View File

@ -24,7 +24,7 @@ as Kubernetes does with the AWS cloud provider.
In the examples that follow, it is assumed that you configured the ALB Ingress
Controller with the `ingress-class=alb` argument (not to be confused with the
same argument to ExternalDNS) so that the controller will only respect Ingress
objects with the `kubernetes.io/ingress.class` annotation set to "alb".
objects with the `ingressClassName` field set to "alb".
## Deploy an example application
@ -80,7 +80,6 @@ kind: Ingress
metadata:
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
kubernetes.io/ingress.class: alb
name: echoserver
spec:
ingressClassName: alb
@ -120,7 +119,6 @@ metadata:
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org
kubernetes.io/ingress.class: alb
name: echoserver
spec:
ingressClassName: alb
@ -159,7 +157,6 @@ metadata:
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/ip-address-type: dualstack
kubernetes.io/ingress.class: alb
name: echoserver
spec:
ingressClassName: alb

View File

@ -81,7 +81,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
env:
- name: AWS_REGION
value: us-east-1 # put your CloudMap NameSpace region
@ -148,7 +148,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
env:
- name: AWS_REGION
value: us-east-1 # put your CloudMap NameSpace region

View File

@ -413,7 +413,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -508,7 +508,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -739,9 +739,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller.
spec:
ingressClassName: nginx
rules:
- host: server.example.com
http:
@ -936,7 +935,7 @@ Running several fast polling ExternalDNS instances in a given account can easily
* `--source=ingress --source=service` - specify multiple times for multiple sources
* `--namespace=my-app`
* `--label-filter=app in (my-app)`
* `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)` - note that this filter would apply to services too..
* `--ingress-class=nginx-external`
* Limit services watched by type (not applicable to ingress or other types)
* `--service-type-filter=LoadBalancer` default `all`
* Limit the hosted zones considered
@ -962,7 +961,7 @@ A simple way to implement randomised startup is with an init container:
spec:
initContainers:
- name: init-jitter
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
command:
- /bin/sh
- -c

View File

@ -130,7 +130,7 @@ spec:
spec:
containers:
- name: externaldns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -201,7 +201,7 @@ spec:
serviceAccountName: externaldns
containers:
- name: externaldns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -272,7 +272,7 @@ spec:
serviceAccountName: externaldns
containers:
- name: externaldns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -416,9 +416,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:
- host: server.example.com
http:

View File

@ -356,7 +356,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -424,7 +424,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -495,7 +495,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -560,9 +560,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:
- host: server.example.com
http:
@ -649,3 +648,9 @@ resource group:
```bash
$ az group delete --name "MyDnsResourceGroup"
```
## More tutorials
A video explanantion is available here: https://www.youtube.com/watch?v=VSn6DPKIhM8&list=PLpbcUe4chE79sB7Jg7B4z3HytqUUEwcNE
![image](https://user-images.githubusercontent.com/6548359/235437721-87611869-75f2-4f32-bb35-9da585e46299.png)

View File

@ -46,7 +46,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --log-level=debug
- --source=service
@ -136,7 +136,7 @@ spec:
secretName: bluecatconfig
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
volumeMounts:
- name: bluecatconfig
mountPath: "/etc/external-dns/"

View File

@ -41,7 +41,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -59,7 +59,7 @@ kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
@ -74,7 +74,7 @@ rules:
resources: ["nodes"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
@ -105,7 +105,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -20,6 +20,8 @@ Snippet from [Cloudflare - Getting Started](https://api.cloudflare.com/#getting-
API Token will be preferred for authentication if `CF_API_TOKEN` environment variable is set.
Otherwise `CF_API_KEY` and `CF_API_EMAIL` should be set to run ExternalDNS with Cloudflare.
You may provide the Cloudflare API token through a file by setting the
`CF_API_TOKEN="file:/path/to/token"`.
When using API Token authentication, the token should be granted Zone `Read`, DNS `Edit` privileges, and access to `All zones`.
@ -54,7 +56,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -123,7 +125,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -26,7 +26,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -102,7 +102,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress

View File

@ -108,7 +108,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --provider=coredns
@ -175,7 +175,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --provider=coredns
@ -198,9 +198,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
ingressClassName: nginx
rules:
- host: nginx.example.org
http:

View File

@ -59,7 +59,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -136,7 +136,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -43,7 +43,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -107,7 +107,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -35,7 +35,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone you create in DNSimple.
@ -100,7 +100,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone you create in DNSimple.

View File

@ -43,7 +43,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --txt-prefix=_d

View File

@ -41,7 +41,7 @@ spec:
# serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress # or service or both
- --provider=exoscale
@ -109,9 +109,9 @@ kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
external-dns.alpha.kubernetes.io/target: {{ Elastic-IP-address }}
spec:
ingressClassName: nginx
rules:
- host: via-ingress.example.com
http:

View File

@ -27,7 +27,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --log-level=debug
- --source=service

View File

@ -39,7 +39,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -56,7 +56,7 @@ kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
@ -71,7 +71,7 @@ rules:
resources: ["nodes"]
verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
@ -103,7 +103,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -72,7 +72,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
# Add desired Gateway API Route sources.
- --source=gateway-httproute

View File

@ -319,7 +319,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress

View File

@ -22,7 +22,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system)
@ -90,7 +90,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system)

View File

@ -44,7 +44,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -115,7 +115,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -31,7 +31,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --log-level=debug
- --source=service
@ -96,7 +96,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --log-level=debug
- --source=service
@ -114,7 +114,7 @@ spec:
First lets deploy a Kafka Stateful set, a simple example(a lot of stuff is missing) with a headless service called `ksvc`
```yaml
apiVersion: apps/v1beta1
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka

View File

@ -69,7 +69,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -142,7 +142,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -69,7 +69,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains.
@ -150,7 +150,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains.

View File

@ -28,7 +28,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -98,7 +98,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress

View File

@ -22,7 +22,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=kong-tcpingress
- --provider=aws
@ -86,7 +86,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=kong-tcpingress
- --provider=aws

View File

@ -141,8 +141,6 @@ Create the following Ingress to expose the echoserver application to the Interne
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: skipper
name: echoserver
spec:
ingressClassName: skipper
@ -181,7 +179,6 @@ kind: Ingress
metadata:
annotations:
external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org
kubernetes.io/ingress.class: skipper
name: echoserver
spec:
ingressClassName: skipper
@ -218,7 +215,6 @@ kind: Ingress
metadata:
annotations:
alb.ingress.kubernetes.io/ip-address-type: dualstack
kubernetes.io/ingress.class: skipper
name: echoserver
spec:
ingressClassName: skipper
@ -256,7 +252,6 @@ kind: Ingress
metadata:
annotations:
zalando.org/aws-load-balancer-type: nlb
kubernetes.io/ingress.class: skipper
name: echoserver
spec:
ingressClassName: skipper

View File

@ -41,7 +41,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -105,7 +105,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -0,0 +1,28 @@
# Creating MX record with CRD source
You can create and manage MX records with the help of [CRD source](/docs/contributing/crd-source.md)
and `DNSEndpoint` CRD. Currently, this feature is only supported by `aws`, `azure`, and `google` providers.
In order to start managing MX records you need to set the `--managed-record-types MX` flag.
```console
external-dns --source crd --provider {aws|azure|google} --managed-record-types A --managed-record-types CNAME --managed-record-types MX
```
Targets within the CRD need to be specified according to the RFC 1034 (section 3.6.1). Below is an example of
`example.com` DNS MX record which specifies two separate targets with distinct priorities.
```yaml
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: examplemxrecord
spec:
endpoints:
- dnsName: example.com
recordTTL: 180
recordType: MX
targets:
- 10 mailhost1.example.com
- 20 mailhost2.example.com
```

View File

@ -273,7 +273,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --domain-filter=external-dns-test.gcp.zalan.do
@ -294,8 +294,6 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:
@ -570,7 +568,7 @@ spec:
- --google-project=zalando-external-dns-test
- --registry=txt
- --txt-owner-id=my-identifier
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
name: external-dns
securityContext:
fsGroup: 65534
@ -595,8 +593,6 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:

View File

@ -3,8 +3,9 @@
This tutorial describes how to configure ExternalDNS to use the cluster nodes as source.
Using nodes (`--source=node`) as source is possible to synchronize a DNS zone with the nodes of a cluster.
The node source adds an `A` record per each node `externalIP` (if not found, node's `internalIP` is used).
The TTL record can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation.
The node source adds an `A` record per each node `externalIP` (if not found, any IPv4 `internalIP` is used instead).
It also adds an `AAAA` record per each node IPv6 `internalIP`.
The TTL of the records can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation.
## Manifest (for cluster without RBAC enabled)
@ -28,7 +29,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=node # will use nodes as source
- --provider=aws
@ -99,7 +100,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=node # will use nodes as source
- --provider=aws

View File

@ -61,7 +61,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -125,7 +125,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -66,7 +66,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=openshift-route
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
@ -133,7 +133,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=openshift-route
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones

View File

@ -6,16 +6,25 @@ Make sure to use the latest version of ExternalDNS for this tutorial.
## Creating an OCI DNS Zone
Create a DNS zone which will contain the managed DNS records. Let's use `example.com` as an reference here.
Create a DNS zone which will contain the managed DNS records. Let's use
`example.com` as a reference here. Make note of the OCID of the compartment
in which you created the zone; you'll need to provide that later.
For more information about OCI DNS see the documentation [here][1].
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
The OCI provider supports two authentication options: key-based and instance
principals.
### Key-based
We first need to create a config file containing the information needed to connect with the OCI API.
Create a new file (oci.yaml) and modify the contents to match the example below. Be sure to adjust the values to match your own credentials:
Create a new file (oci.yaml) and modify the contents to match the example
below. Be sure to adjust the values to match your own credentials, and the OCID
of the compartment containing the zone:
```yaml
auth:
@ -37,7 +46,29 @@ Create a secret using the config file above:
$ kubectl create secret generic external-dns-config --from-file=oci.yaml
```
### Manifest (for clusters with RBAC enabled)
### OCI IAM Instance Principal
If you're running ExternalDNS within OCI, you can use OCI IAM instance
principals to authenticate with OCI. This obviates the need to create the
secret with your credentials. You'll need to ensure an OCI IAM policy exists
with a statement granting the `manage dns` permission on zones and records in
the target compartment to the dynamic group covering your instance running
ExternalDNS.
E.g.:
```
Allow dynamic-group <dynamic-group-name> to manage dns in compartment id <target-compartment-OCID>
```
You'll also need to add the `--oci-auth-instance-principal` flag to enable
this type of authentication. Finally, you'll need to add the
`--oci-compartment-ocid=ocid1.compartment.oc1...` flag to provide the OCID of
the compartment containing the zone to be managed.
For more information about OCI IAM instance principals, see the documentation [here][2].
For more information about OCI IAM policy details for the DNS service, see the documentation [here][3].
## Manifest (for clusters with RBAC enabled)
Apply the following manifest to deploy ExternalDNS.
@ -93,7 +124,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -159,3 +190,6 @@ $ kubectl apply -f nginx.yaml
```
[1]: https://docs.cloud.oracle.com/iaas/Content/DNS/Concepts/dnszonemanagement.htm
[2]: https://docs.cloud.oracle.com/iaas/Content/Identity/Reference/dnspolicyreference.htm
[3]: https://docs.cloud.oracle.com/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm

View File

@ -86,7 +86,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -160,7 +160,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -42,7 +42,7 @@ spec:
# serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # or ingress or both
- --provider=pdns

View File

@ -78,7 +78,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
# If authentication is disabled and/or you didn't create
# a secret, you can remove this block.
envFrom:

View File

@ -35,7 +35,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -105,7 +105,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -213,10 +213,10 @@ spec:
Consult [AWS ExternalDNS setup docs](aws.md) for installation guidelines.
In ExternalDNS containers args, make sure to specify `annotation-filter` and `aws-zone-type`:
In ExternalDNS containers args, make sure to specify `aws-zone-type` and `ingress-class`:
```yaml
apiVersion: apps/v1beta2
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
@ -241,9 +241,9 @@ spec:
- --provider=aws
- --registry=txt
- --txt-owner-id=external-dns
- --annotation-filter=kubernetes.io/ingress.class in (external-ingress)
- --ingress-class=external-ingress
- --aws-zone-type=public
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
name: external-dns-public
```
@ -251,10 +251,10 @@ spec:
Consult [AWS ExternalDNS setup docs](aws.md) for installation guidelines.
In ExternalDNS containers args, make sure to specify `annotation-filter` and `aws-zone-type`:
In ExternalDNS containers args, make sure to specify `aws-zone-type` and `ingress-class`:
```yaml
apiVersion: apps/v1beta2
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
@ -279,28 +279,27 @@ spec:
- --provider=aws
- --registry=txt
- --txt-owner-id=dev.k8s.nexus
- --annotation-filter=kubernetes.io/ingress.class in (internal-ingress)
- --ingress-class=internal-ingress
- --aws-zone-type=private
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
name: external-dns-private
```
## Create application Service definitions
For this setup to work, you've to create two Service definitions for your application.
For this setup to work, you need to create two Ingress definitions for your application.
At first, create public Service definition:
At first, create a public Ingress definition:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "external-ingress"
labels:
app: app
name: app-public
spec:
ingressClassName: external-ingress
rules:
- host: app.domain.com
http:
@ -313,18 +312,17 @@ spec:
pathType: Prefix
```
Then create private Service definition:
Then create a private Ingress definition:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "internal-ingress"
labels:
app: app
name: app-private
spec:
ingressClassName: internal-ingress
rules:
- host: app.domain.com
http:
@ -347,12 +345,12 @@ metadata:
certmanager.k8s.io/acme-challenge-type: "dns01"
certmanager.k8s.io/acme-dns01-provider: "route53"
certmanager.k8s.io/cluster-issuer: "letsencrypt-production"
kubernetes.io/ingress.class: "external-ingress"
kubernetes.io/tls-acme: "true"
labels:
app: app
name: app-public
spec:
ingressClassName: "external-ingress"
rules:
- host: app.domain.com
http:
@ -375,12 +373,11 @@ And reuse the requested certificate in private Service definition:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "internal-ingress"
labels:
app: app
name: app-private
spec:
ingressClassName: "internal-ingress"
rules:
- host: app.domain.com
http:

View File

@ -53,7 +53,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -120,7 +120,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -54,7 +54,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --provider=rdns
@ -123,7 +123,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --provider=rdns
@ -142,9 +142,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
ingressClassName: nginx
rules:
- host: nginx.lb.rancher.cloud
http:

View File

@ -218,7 +218,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --registry=txt
- --txt-prefix=external-dns-
@ -260,7 +260,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --registry=txt
- --txt-prefix=external-dns-

View File

@ -53,7 +53,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -121,7 +121,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -20,7 +20,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- ... # your arguments here
securityContext:

View File

@ -129,7 +129,7 @@ spec:
- --policy=sync # set `upsert-only` would prevent ExternalDNS from deleting any records
- --tencent-cloud-zone-type=private # only look at private hosted zones. set `public` to use the public dns service.
- --tencent-cloud-config-file=/etc/kubernetes/tencent-cloud.json
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
imagePullPolicy: Always
name: external-dns
resources: {}

View File

@ -36,7 +36,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains
@ -107,7 +107,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains

View File

@ -44,7 +44,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress # ingress is also possible
@ -116,7 +116,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress

View File

@ -66,7 +66,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --provider=vinyldns
- --source=service
@ -137,7 +137,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --provider=vinyldns
- --source=service

View File

@ -42,7 +42,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -106,7 +106,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

134
endpoint/crypto.go Normal file
View File

@ -0,0 +1,134 @@
/*
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 endpoint
import (
"bytes"
"compress/gzip"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
log "github.com/sirupsen/logrus"
)
// EncryptText gzip input data and encrypts it using the supplied AES key
func EncryptText(text string, aesKey []byte, nonceEncoded []byte) (string, error) {
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if nonceEncoded == nil {
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
} else {
if _, err = base64.StdEncoding.Decode(nonce, nonceEncoded); err != nil {
return "", err
}
}
data, err := compressData([]byte(text))
if err != nil {
return "", err
}
cipherData := gcm.Seal(nonce, nonce, data, nil)
return base64.StdEncoding.EncodeToString(cipherData), nil
}
// DecryptText decrypt gziped data using a supplied AES encryption key ang ungzip it
// in case of decryption failed, will return original input and decryption error
func DecryptText(text string, aesKey []byte) (decryptResult string, encryptNonce string, err error) {
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", "", err
}
nonceSize := gcm.NonceSize()
data, err := base64.StdEncoding.DecodeString(text)
if err != nil {
return "", "", err
}
if len(data) <= nonceSize {
return "", "", fmt.Errorf("the encoded data from text %#v is shorter than %#v bytes and can't be decoded", text, nonceSize)
}
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
plaindata, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", "", err
}
plaindata, err = decompressData(plaindata)
if err != nil {
log.Debugf("Failed to decompress data based on the base64 encoded text %#v. Got error %#v.", text, err)
return "", "", err
}
return string(plaindata), base64.StdEncoding.EncodeToString(nonce), nil
}
// decompressData gzip compressed data
func decompressData(data []byte) (resData []byte, err error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, err
}
defer gz.Close()
var b bytes.Buffer
if _, err = b.ReadFrom(gz); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// compressData by gzip, for minify data stored in registry
func compressData(data []byte) (compressedData []byte, err error) {
var b bytes.Buffer
gz, err := gzip.NewWriterLevel(&b, gzip.BestCompression)
if err != nil {
return nil, err
}
defer gz.Close()
if _, err = gz.Write(data); err != nil {
return nil, err
}
if err = gz.Flush(); err != nil {
return nil, err
}
if err = gz.Close(); err != nil {
return nil, err
}
return b.Bytes(), nil
}

58
endpoint/crypto_test.go Normal file
View File

@ -0,0 +1,58 @@
/*
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 endpoint
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestEncrypt(t *testing.T) {
// Verify that text encryption and decryption works
aesKey := []byte("s%zF`.*'5`9.AhI2!B,.~hmbs^.*TL?;")
plaintext := "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
encryptedtext, err := EncryptText(plaintext, aesKey, nil)
require.NoError(t, err)
decryptedtext, _, err := DecryptText(encryptedtext, aesKey)
require.NoError(t, err)
if plaintext != decryptedtext {
t.Errorf("Original plain text %#v differs from the resulting decrypted text %#v", plaintext, decryptedtext)
}
// Verify that decrypt returns an error and empty data if wrong AES encryption key is used
decryptedtext, _, err = DecryptText(encryptedtext, []byte("s'J!jD`].LC?g&Oa11AgTub,j48ts/96"))
require.Error(t, err)
if decryptedtext != "" {
t.Error("Data decryption failed, empty string should be as result")
}
// Verify that decrypt returns an error and empty data if unencrypted input is is supplied
decryptedtext, _, err = DecryptText(plaintext, aesKey)
require.Error(t, err)
if decryptedtext != "" {
t.Errorf("Data decryption failed, empty string should be as result")
}
// Verify that a known encrypted text is decrypted to what is expected
encryptedtext = "0Mfzf6wsN8llrfX0ucDZ6nlc2+QiQfKKedjPPLu5atb2I35L9nUZeJcCnuLVW7CVW3K0h94vSuBLdXnMrj8Vcm0M09shxaoF48IcCpD03XtQbKXqk2hPbsW6+JybvplHIQGr16/PcjUSObGmR9yjf38+qEltApkKvrPjsyw43BX4eE10rL0Bln33UJD7/w+zazRDPFlAcbGtkt0ETKHnvyB3/aCddLipvrhjCXj2ZY/ktRF6h716kJRgXU10dCIQHFYU45MIdxI+k10HK3yZqhI2V0Gp2xjrFV/LRQ7/OS9SFee4asPWUYxbCEsnOzp8qc0dCPFSo1dtADzWnUZnsAcbnjtudT4milfLJc5CxDk1v3ykqQ/ajejwHjWQ7b8U6AsTErbezfdcqrb5IzkLgHb5TosnfrdDmNc9GcKfpsrCHbVY8KgNwMVdtwavLv7d9WM6sooUlZ3t0sABGkzagXQmPRvwLnkSOlie5XrnzWo8/8/4UByLga29CaXO"
decryptedtext, _, err = DecryptText(encryptedtext, aesKey)
require.NoError(t, err)
if decryptedtext != plaintext {
t.Error("Decryption of text didn't result in expected plaintext result.")
}
}

View File

@ -30,6 +30,8 @@ import (
const (
// RecordTypeA is a RecordType enum value
RecordTypeA = "A"
// RecordTypeAAAA is a RecordType enum value
RecordTypeAAAA = "AAAA"
// RecordTypeCNAME is a RecordType enum value
RecordTypeCNAME = "CNAME"
// RecordTypeTXT is a RecordType enum value
@ -40,6 +42,8 @@ const (
RecordTypeNS = "NS"
// RecordTypePTR is a RecordType enum value
RecordTypePTR = "PTR"
// RecordTypeMX is a RecordType enum value
RecordTypeMX = "MX"
)
// TTL is a structure defining the TTL of a DNS record
@ -164,7 +168,7 @@ type Endpoint struct {
DNSName string `json:"dnsName,omitempty"`
// The targets the DNS record points to
Targets Targets `json:"targets,omitempty"`
// RecordType type of record, e.g. CNAME, A, SRV, TXT etc
// RecordType type of record, e.g. CNAME, A, AAAA, SRV, TXT etc
RecordType string `json:"recordType,omitempty"`
// Identifier to distinguish multiple records with the same name and type (e.g. Route53 records with routing policies other than 'simple')
SetIdentifier string `json:"setIdentifier,omitempty"`

View File

@ -17,6 +17,8 @@ limitations under the License.
package endpoint
import (
log "github.com/sirupsen/logrus"
"errors"
"fmt"
"sort"
@ -41,6 +43,9 @@ const (
// DualstackLabelKey is the name of the label that identifies dualstack endpoints
DualstackLabelKey = "dualstack"
// txtEncryptionNonce label for keep same nonce for same txt records, for prevent different result of encryption for same txt record, it can cause issues for some providers
txtEncryptionNonce = "txt-encryption-nonce"
)
// Labels store metadata related to the endpoint
@ -55,7 +60,7 @@ func NewLabels() Labels {
// NewLabelsFromString constructs endpoints labels from a provided format string
// if heritage set to another value is found then error is returned
// no heritage automatically assumes is not owned by external-dns and returns invalidHeritage error
func NewLabelsFromString(labelText string) (Labels, error) {
func NewLabelsFromStringPlain(labelText string) (Labels, error) {
endpointLabels := map[string]string{}
labelText = strings.Trim(labelText, "\"") // drop quotes
tokens := strings.Split(labelText, ",")
@ -85,9 +90,26 @@ func NewLabelsFromString(labelText string) (Labels, error) {
return endpointLabels, nil
}
// Serialize transforms endpoints labels into a external-dns recognizable format string
func NewLabelsFromString(labelText string, aesKey []byte) (Labels, error) {
if len(aesKey) != 0 {
decryptedText, encryptionNonce, err := DecryptText(strings.Trim(labelText, "\""), aesKey)
//in case if we have decryption error, just try process original text
//decryption errors should be ignored here, because we can already have plain-text labels in registry
if err == nil {
labels, err := NewLabelsFromStringPlain(decryptedText)
if err == nil {
labels[txtEncryptionNonce] = encryptionNonce
}
return labels, err
}
}
return NewLabelsFromStringPlain(labelText)
}
// SerializePlain transforms endpoints labels into a external-dns recognizable format string
// withQuotes adds additional quotes
func (l Labels) Serialize(withQuotes bool) string {
func (l Labels) SerializePlain(withQuotes bool) string {
var tokens []string
tokens = append(tokens, fmt.Sprintf("heritage=%s", heritage))
var keys []string
@ -104,3 +126,31 @@ func (l Labels) Serialize(withQuotes bool) string {
}
return strings.Join(tokens, ",")
}
// Serialize same to SerializePlain, but encrypt data, if encryption enabled
func (l Labels) Serialize(withQuotes bool, txtEncryptEnabled bool, aesKey []byte) string {
if !txtEncryptEnabled {
return l.SerializePlain(withQuotes)
}
var encryptionNonce []byte = nil
if extractedNonce, nonceExists := l[txtEncryptionNonce]; nonceExists {
encryptionNonce = []byte(extractedNonce)
delete(l, txtEncryptionNonce)
}
text := l.SerializePlain(false)
log.Debugf("Encrypt the serialized text %#v before returning it.", text)
var err error
text, err = EncryptText(text, aesKey, encryptionNonce)
if err != nil {
log.Fatalf("Failed to encrypt the text %#v using the encryption key %#v. Got error %#v.", text, aesKey, err)
}
if withQuotes {
text = fmt.Sprintf("\"%s\"", text)
}
log.Debugf("Serialized text after encryption is %#v.", text)
return text
}

View File

@ -25,14 +25,18 @@ import (
type LabelsSuite struct {
suite.Suite
foo Labels
fooAsText string
fooAsTextWithQuotes string
barText string
barTextAsMap Labels
noHeritageText string
wrongHeritageText string
multipleHeritageText string // considered invalid
aesKey []byte
foo Labels
fooAsText string
fooAsTextWithQuotes string
fooAsTextEncrypted string
fooAsTextWithQuotesEncrypted string
barText string
barTextEncrypted string
barTextAsMap Labels
noHeritageText string
wrongHeritageText string
multipleHeritageText string // considered invalid
}
func (suite *LabelsSuite) SetupTest() {
@ -40,48 +44,79 @@ func (suite *LabelsSuite) SetupTest() {
"owner": "foo-owner",
"resource": "foo-resource",
}
suite.aesKey = []byte(")K_Fy|?Z.64#UuHm`}[d!GC%WJM_fs{_")
suite.fooAsText = "heritage=external-dns,external-dns/owner=foo-owner,external-dns/resource=foo-resource"
suite.fooAsTextWithQuotes = fmt.Sprintf(`"%s"`, suite.fooAsText)
suite.fooAsTextEncrypted = `+lvP8q9KHJ6BS6O81i2Q6DLNdf2JSKy8j/gbZKviTZlGYj7q+yDoYMgkQ1hPn6urtGllM5bfFMcaaHto52otQtiOYrX8990J3kQqg4s47m3bH3Ejl8RSxSSuWJM3HJtPghQzYg0/LSOsdQ0=`
suite.fooAsTextWithQuotesEncrypted = fmt.Sprintf(`"%s"`, suite.fooAsTextEncrypted)
suite.barTextAsMap = map[string]string{
"owner": "bar-owner",
"resource": "bar-resource",
"new-key": "bar-new-key",
}
suite.barText = "heritage=external-dns,,external-dns/owner=bar-owner,external-dns/resource=bar-resource,external-dns/new-key=bar-new-key,random=stuff,no-equal-sign,," // also has some random gibberish
suite.barTextEncrypted = "yi6vVATlgYN0enXBIupVK2atNUKtajofWMroWtvZjUanFZXlWvqjJPpjmMd91kv86bZj+syQEP0uR3TK6eFVV7oKFh/NxYyh238FjZ+25zlXW9TgbLoMalUNOkhKFdfXkLeeaqJjePB59t+kQBYX+ZEryK652asPs6M+xTIvtg07N7WWZ6SjJujm0RRISg=="
suite.noHeritageText = "external-dns/owner=random-owner"
suite.wrongHeritageText = "heritage=mate,external-dns/owner=random-owner"
suite.multipleHeritageText = "heritage=mate,heritage=external-dns,external-dns/owner=random-owner"
}
func (suite *LabelsSuite) TestSerialize() {
suite.Equal(suite.fooAsText, suite.foo.Serialize(false), "should serializeLabel")
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.Serialize(true), "should serializeLabel")
suite.Equal(suite.fooAsText, suite.foo.SerializePlain(false), "should serializeLabel")
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.SerializePlain(true), "should serializeLabel")
suite.Equal(suite.fooAsText, suite.foo.Serialize(false, false, nil), "should serializeLabel")
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.Serialize(true, false, nil), "should serializeLabel")
suite.Equal(suite.fooAsText, suite.foo.Serialize(false, false, suite.aesKey), "should serializeLabel")
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.Serialize(true, false, suite.aesKey), "should serializeLabel")
suite.NotEqual(suite.fooAsText, suite.foo.Serialize(false, true, suite.aesKey), "should serializeLabel and encrypt")
suite.NotEqual(suite.fooAsTextWithQuotes, suite.foo.Serialize(true, true, suite.aesKey), "should serializeLabel and encrypt")
}
func (suite *LabelsSuite) TestEncryptionNonceReUsage() {
foo, err := NewLabelsFromString(suite.fooAsTextEncrypted, suite.aesKey)
suite.NoError(err, "should succeed for valid label text")
serialized := foo.Serialize(false, true, suite.aesKey)
suite.Equal(serialized, suite.fooAsTextEncrypted, "serialized result should be equal")
}
func (suite *LabelsSuite) TestDeserialize() {
foo, err := NewLabelsFromString(suite.fooAsText)
foo, err := NewLabelsFromStringPlain(suite.fooAsText)
suite.NoError(err, "should succeed for valid label text")
suite.Equal(suite.foo, foo, "should reconstruct original label map")
foo, err = NewLabelsFromString(suite.fooAsTextWithQuotes)
foo, err = NewLabelsFromStringPlain(suite.fooAsTextWithQuotes)
suite.NoError(err, "should succeed for valid label text")
suite.Equal(suite.foo, foo, "should reconstruct original label map")
bar, err := NewLabelsFromString(suite.barText)
foo, err = NewLabelsFromString(suite.fooAsTextEncrypted, suite.aesKey)
suite.NoError(err, "should succeed for valid encrypted label text")
for key, val := range suite.foo {
suite.Equal(val, foo[key], "should contains all keys from original label map")
}
foo, err = NewLabelsFromString(suite.fooAsTextWithQuotesEncrypted, suite.aesKey)
suite.NoError(err, "should succeed for valid encrypted label text")
for key, val := range suite.foo {
suite.Equal(val, foo[key], "should contains all keys from original label map")
}
bar, err := NewLabelsFromStringPlain(suite.barText)
suite.NoError(err, "should succeed for valid label text")
suite.Equal(suite.barTextAsMap, bar, "should reconstruct original label map")
noHeritage, err := NewLabelsFromString(suite.noHeritageText)
bar, err = NewLabelsFromString(suite.barText, suite.aesKey)
suite.NoError(err, "should succeed for valid encrypted label text")
suite.Equal(suite.barTextAsMap, bar, "should reconstruct original label map")
noHeritage, err := NewLabelsFromStringPlain(suite.noHeritageText)
suite.Equal(ErrInvalidHeritage, err, "should fail if no heritage is found")
suite.Nil(noHeritage, "should return nil")
wrongHeritage, err := NewLabelsFromString(suite.wrongHeritageText)
wrongHeritage, err := NewLabelsFromStringPlain(suite.wrongHeritageText)
suite.Equal(ErrInvalidHeritage, err, "should fail if wrong heritage is found")
suite.Nil(wrongHeritage, "if error should return nil")
multipleHeritage, err := NewLabelsFromString(suite.multipleHeritageText)
multipleHeritage, err := NewLabelsFromStringPlain(suite.multipleHeritageText)
suite.Equal(ErrInvalidHeritage, err, "should fail if multiple heritage is found")
suite.Nil(multipleHeritage, "if error should return nil")
}

12
go.mod
View File

@ -42,7 +42,7 @@ require (
github.com/onsi/ginkgo v1.16.5
github.com/openshift/api v0.0.0-20210315202829-4b79815405ec
github.com/openshift/client-go v0.0.0-20210112165513-ebc401615f47
github.com/oracle/oci-go-sdk v24.3.0+incompatible
github.com/oracle/oci-go-sdk/v65 v65.35.0
github.com/ovh/go-ovh v1.1.0
github.com/pkg/errors v0.9.1
github.com/pluralsh/gqlclient v1.1.6
@ -58,8 +58,8 @@ require (
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556
github.com/vultr/govultr/v2 v2.17.2
go.etcd.io/etcd/api/v3 v3.5.5
go.etcd.io/etcd/client/v3 v3.5.5
go.etcd.io/etcd/api/v3 v3.5.8
go.etcd.io/etcd/client/v3 v3.5.8
go.uber.org/ratelimit v0.2.0
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.5.0
@ -113,6 +113,7 @@ require (
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gofrs/uuid v4.0.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
@ -164,11 +165,12 @@ require (
github.com/schollz/progressbar/v3 v3.8.6 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/smartystreets/gunit v1.3.4 // indirect
github.com/sony/gobreaker v0.5.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/terra-farm/udnssdk v1.3.5 // indirect
github.com/vektah/gqlparser/v2 v2.5.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.8 // indirect
go.mongodb.org/mongo-driver v1.5.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
@ -176,7 +178,7 @@ require (
go.uber.org/zap v1.19.1 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect

45
go.sum
View File

@ -153,7 +153,6 @@ github.com/ans-group/go-durationstring v1.2.0 h1:UJIuQATkp0t1rBvZsHRwki33YHV9E+U
github.com/ans-group/go-durationstring v1.2.0/go.mod h1:QGF9Mdpq9058QXaut8r55QWu6lcHX6i/GvF1PZVkV6o=
github.com/ans-group/sdk-go v1.10.4 h1:wZzojt99wtVIEHs8zNQzp1Xhqme5tD5NqMM1VLmG6xQ=
github.com/ans-group/sdk-go v1.10.4/go.mod h1:XSKXEDfKobnDtZoyia5DhJxxaDMcCjr76e1KJ9dU/xc=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -218,8 +217,6 @@ github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
github.com/cncf/udpa v0.0.0-20200324003616-bae28a880fdb/go.mod h1:HNVadOiXCy7Jk3R2knJ+qm++zkncJxxBMpjdGgJ+UJc=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200324003616-bae28a880fdb/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
@ -314,9 +311,6 @@ github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4s
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.3.0-java.0.20200609174644-bd816e4522c1/go.mod h1:bjmEhrMDubXDd0uKxnWwRmgSsiEv2CkJliIHnj6ETm8=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@ -484,6 +478,8 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg=
github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
@ -623,7 +619,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
@ -950,8 +945,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/oracle/oci-go-sdk v24.3.0+incompatible h1:x4mcfb4agelf1O4/1/auGlZ1lr97jXRSSN5MxTgG/zU=
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/oracle/oci-go-sdk/v65 v65.35.0 h1:zvDsEuGs0qf6hPZVbrDnnfPJYQP7CwAgidTr4Pch6E4=
github.com/oracle/oci-go-sdk/v65 v65.35.0/go.mod h1:MXMLMzHnnd9wlpgadPkdlkZ9YrwQmCOmbX5kjVEJodw=
github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk=
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso=
@ -995,7 +990,6 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
@ -1039,7 +1033,6 @@ github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqn
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -1089,6 +1082,8 @@ github.com/smartystreets/gunit v1.3.4 h1:iHc8Rfhb/uCOc9a3KGuD3ut22L+hLIVaqR1o5fS
github.com/smartystreets/gunit v1.3.4/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@ -1201,12 +1196,12 @@ go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg=
go.etcd.io/etcd/api/v3 v3.5.5 h1:BX4JIbQ7hl7+jL+g+2j5UAr0o1bctCm6/Ct+ArBGkf0=
go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8=
go.etcd.io/etcd/client/pkg/v3 v3.5.5 h1:9S0JUVvmrVl7wCF39iTQthdaaNIiAaQbmK75ogO6GU8=
go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ=
go.etcd.io/etcd/client/v3 v3.5.5 h1:q++2WTJbUgpQu4B6hCuT7VkdwaTP7Qz6Daak3WzbrlI=
go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c=
go.etcd.io/etcd/api/v3 v3.5.8 h1:Zf44zJszoU7zRV0X/nStPenegNXoFDWcB/MwrJbA+L4=
go.etcd.io/etcd/api/v3 v3.5.8/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
go.etcd.io/etcd/client/pkg/v3 v3.5.8 h1:tPp9YRn/UBFAHdhOQUII9eUs7aOK35eulpMhX4YBd+M=
go.etcd.io/etcd/client/pkg/v3 v3.5.8/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
go.etcd.io/etcd/client/v3 v3.5.8 h1:B6ngTKZSWWowHEoaucOKHQR/AtZKaoHLiUpWxOLG4l4=
go.etcd.io/etcd/client/v3 v3.5.8/go.mod h1:idZYIPVkttBJBiRigkB5EM0MmEyx8jcl18zCV3F5noc=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
@ -1221,7 +1216,6 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -1239,7 +1233,6 @@ go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6m
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -1298,7 +1291,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@ -1467,7 +1459,6 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1487,8 +1478,8 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -1587,7 +1578,6 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM=
@ -1651,7 +1641,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@ -1660,7 +1649,6 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc h1:ijGwO+0vL2hJt5gaygqP2j6PfflOBrRot0IczKbmtio=
google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@ -1682,11 +1670,7 @@ google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@ -1745,7 +1729,6 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

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

20
main.go
View File

@ -18,6 +18,7 @@ package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
@ -113,6 +114,7 @@ func main() {
Namespace: cfg.Namespace,
AnnotationFilter: cfg.AnnotationFilter,
LabelFilter: labelSelector,
IngressClassNames: cfg.IngressClassNames,
FQDNTemplate: cfg.FQDNTemplate,
CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation,
IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation,
@ -139,6 +141,8 @@ func main() {
RequestTimeout: cfg.RequestTimeout,
DefaultTargets: cfg.DefaultTargets,
OCPRouterName: cfg.OCPRouterName,
UpdateEvents: cfg.UpdateEvents,
ResolveLoadBalancerHostname: cfg.ResolveServiceLoadBalancerHostname,
}
// Lookup all the selected sources by names and pass them the desired configuration.
@ -312,7 +316,19 @@ func main() {
)
case "oci":
var config *oci.OCIConfig
config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
// if the instance-principals flag was set, and a compartment OCID was provided, then ignore the
// OCI config file, and provide a config that uses instance principal authentication.
if cfg.OCIAuthInstancePrincipal {
if len(cfg.OCICompartmentOCID) == 0 {
err = fmt.Errorf("instance principal authentication requested, but no compartment OCID provided")
} else {
authConfig := oci.OCIAuthConfig{UseInstancePrincipal: true}
config = &oci.OCIConfig{Auth: authConfig, CompartmentID: cfg.OCICompartmentOCID}
}
} else {
config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
}
if err == nil {
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
}
@ -367,7 +383,7 @@ func main() {
case "noop":
r, err = registry.NewNoopRegistry(p)
case "txt":
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes)
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey))
case "aws-sd":
r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID)
default:

View File

@ -43,162 +43,168 @@ var Version = "unknown"
// Config is a project-wide configuration
type Config struct {
APIServerURL string
KubeConfig string
RequestTimeout time.Duration
DefaultTargets []string
ContourLoadBalancerService string
GlooNamespace string
SkipperRouteGroupVersion string
Sources []string
Namespace string
AnnotationFilter string
LabelFilter string
FQDNTemplate string
CombineFQDNAndAnnotation bool
IgnoreHostnameAnnotation bool
IgnoreIngressTLSSpec bool
IgnoreIngressRulesSpec bool
GatewayNamespace string
GatewayLabelFilter string
Compatibility string
PublishInternal bool
PublishHostIP bool
AlwaysPublishNotReadyAddresses bool
ConnectorSourceServer string
Provider string
GoogleProject string
GoogleBatchChangeSize int
GoogleBatchChangeInterval time.Duration
GoogleZoneVisibility string
DomainFilter []string
ExcludeDomains []string
RegexDomainFilter *regexp.Regexp
RegexDomainExclusion *regexp.Regexp
ZoneNameFilter []string
ZoneIDFilter []string
TargetNetFilter []string
ExcludeTargetNets []string
AlibabaCloudConfigFile string
AlibabaCloudZoneType string
AWSZoneType string
AWSZoneTagFilter []string
AWSAssumeRole string
AWSAssumeRoleExternalID string
AWSBatchChangeSize int
AWSBatchChangeInterval time.Duration
AWSEvaluateTargetHealth bool
AWSAPIRetries int
AWSPreferCNAME bool
AWSZoneCacheDuration time.Duration
AWSSDServiceCleanup bool
AzureConfigFile string
AzureResourceGroup string
AzureSubscriptionID string
AzureUserAssignedIdentityClientID string
BluecatDNSConfiguration string
BluecatConfigFile string
BluecatDNSView string
BluecatGatewayHost string
BluecatRootZone string
BluecatDNSServerName string
BluecatDNSDeployType string
BluecatSkipTLSVerify bool
CloudflareProxied bool
CloudflareDNSRecordsPerPage int
CoreDNSPrefix string
RcodezeroTXTEncrypt bool
AkamaiServiceConsumerDomain string
AkamaiClientToken string
AkamaiClientSecret string
AkamaiAccessToken string
AkamaiEdgercPath string
AkamaiEdgercSection string
InfobloxGridHost string
InfobloxWapiPort int
InfobloxWapiUsername string
InfobloxWapiPassword string `secure:"yes"`
InfobloxWapiVersion string
InfobloxSSLVerify bool
InfobloxView string
InfobloxMaxResults int
InfobloxFQDNRegEx string
InfobloxNameRegEx string
InfobloxCreatePTR bool
InfobloxCacheDuration int
DynCustomerName string
DynUsername string
DynPassword string `secure:"yes"`
DynMinTTLSeconds int
OCIConfigFile string
InMemoryZones []string
OVHEndpoint string
OVHApiRateLimit int
PDNSServer string
PDNSAPIKey string `secure:"yes"`
PDNSTLSEnabled bool
TLSCA string
TLSClientCert string
TLSClientCertKey string
Policy string
Registry string
TXTOwnerID string
TXTPrefix string
TXTSuffix string
Interval time.Duration
MinEventSyncInterval time.Duration
Once bool
DryRun bool
UpdateEvents bool
LogFormat string
MetricsAddress string
LogLevel string
TXTCacheInterval time.Duration
TXTWildcardReplacement string
ExoscaleEndpoint string
ExoscaleAPIKey string `secure:"yes"`
ExoscaleAPISecret string `secure:"yes"`
CRDSourceAPIVersion string
CRDSourceKind string
ServiceTypeFilter []string
CFAPIEndpoint string
CFUsername string
CFPassword string
RFC2136Host string
RFC2136Port int
RFC2136Zone string
RFC2136Insecure bool
RFC2136GSSTSIG bool
RFC2136KerberosRealm string
RFC2136KerberosUsername string
RFC2136KerberosPassword string `secure:"yes"`
RFC2136TSIGKeyName string
RFC2136TSIGSecret string `secure:"yes"`
RFC2136TSIGSecretAlg string
RFC2136TAXFR bool
RFC2136MinTTL time.Duration
RFC2136BatchChangeSize int
NS1Endpoint string
NS1IgnoreSSL bool
NS1MinTTLSeconds int
TransIPAccountName string
TransIPPrivateKeyFile string
DigitalOceanAPIPageSize int
ManagedDNSRecordTypes []string
GoDaddyAPIKey string `secure:"yes"`
GoDaddySecretKey string `secure:"yes"`
GoDaddyTTL int64
GoDaddyOTE bool
OCPRouterName string
IBMCloudProxied bool
IBMCloudConfigFile string
TencentCloudConfigFile string
TencentCloudZoneType string
PiholeServer string
PiholePassword string `secure:"yes"`
PiholeTLSInsecureSkipVerify bool
PluralCluster string
PluralProvider string
APIServerURL string
KubeConfig string
RequestTimeout time.Duration
DefaultTargets []string
ContourLoadBalancerService string
GlooNamespace string
SkipperRouteGroupVersion string
Sources []string
Namespace string
AnnotationFilter string
LabelFilter string
IngressClassNames []string
FQDNTemplate string
CombineFQDNAndAnnotation bool
IgnoreHostnameAnnotation bool
IgnoreIngressTLSSpec bool
IgnoreIngressRulesSpec bool
GatewayNamespace string
GatewayLabelFilter string
Compatibility string
PublishInternal bool
PublishHostIP bool
AlwaysPublishNotReadyAddresses bool
ConnectorSourceServer string
Provider string
GoogleProject string
GoogleBatchChangeSize int
GoogleBatchChangeInterval time.Duration
GoogleZoneVisibility string
DomainFilter []string
ExcludeDomains []string
RegexDomainFilter *regexp.Regexp
RegexDomainExclusion *regexp.Regexp
ZoneNameFilter []string
ZoneIDFilter []string
TargetNetFilter []string
ExcludeTargetNets []string
AlibabaCloudConfigFile string
AlibabaCloudZoneType string
AWSZoneType string
AWSZoneTagFilter []string
AWSAssumeRole string
AWSAssumeRoleExternalID string
AWSBatchChangeSize int
AWSBatchChangeInterval time.Duration
AWSEvaluateTargetHealth bool
AWSAPIRetries int
AWSPreferCNAME bool
AWSZoneCacheDuration time.Duration
AWSSDServiceCleanup bool
AzureConfigFile string
AzureResourceGroup string
AzureSubscriptionID string
AzureUserAssignedIdentityClientID string
BluecatDNSConfiguration string
BluecatConfigFile string
BluecatDNSView string
BluecatGatewayHost string
BluecatRootZone string
BluecatDNSServerName string
BluecatDNSDeployType string
BluecatSkipTLSVerify bool
CloudflareProxied bool
CloudflareDNSRecordsPerPage int
CoreDNSPrefix string
RcodezeroTXTEncrypt bool
AkamaiServiceConsumerDomain string
AkamaiClientToken string
AkamaiClientSecret string
AkamaiAccessToken string
AkamaiEdgercPath string
AkamaiEdgercSection string
InfobloxGridHost string
InfobloxWapiPort int
InfobloxWapiUsername string
InfobloxWapiPassword string `secure:"yes"`
InfobloxWapiVersion string
InfobloxSSLVerify bool
InfobloxView string
InfobloxMaxResults int
InfobloxFQDNRegEx string
InfobloxNameRegEx string
InfobloxCreatePTR bool
InfobloxCacheDuration int
DynCustomerName string
DynUsername string
DynPassword string `secure:"yes"`
DynMinTTLSeconds int
OCIConfigFile string
OCICompartmentOCID string
OCIAuthInstancePrincipal bool
InMemoryZones []string
OVHEndpoint string
OVHApiRateLimit int
PDNSServer string
PDNSAPIKey string `secure:"yes"`
PDNSTLSEnabled bool
TLSCA string
TLSClientCert string
TLSClientCertKey string
Policy string
Registry string
TXTOwnerID string
TXTPrefix string
TXTSuffix string
TXTEncryptEnabled bool
TXTEncryptAESKey string
Interval time.Duration
MinEventSyncInterval time.Duration
Once bool
DryRun bool
UpdateEvents bool
LogFormat string
MetricsAddress string
LogLevel string
TXTCacheInterval time.Duration
TXTWildcardReplacement string
ExoscaleEndpoint string
ExoscaleAPIKey string `secure:"yes"`
ExoscaleAPISecret string `secure:"yes"`
CRDSourceAPIVersion string
CRDSourceKind string
ServiceTypeFilter []string
CFAPIEndpoint string
CFUsername string
CFPassword string
ResolveServiceLoadBalancerHostname bool
RFC2136Host string
RFC2136Port int
RFC2136Zone string
RFC2136Insecure bool
RFC2136GSSTSIG bool
RFC2136KerberosRealm string
RFC2136KerberosUsername string
RFC2136KerberosPassword string `secure:"yes"`
RFC2136TSIGKeyName string
RFC2136TSIGSecret string `secure:"yes"`
RFC2136TSIGSecretAlg string
RFC2136TAXFR bool
RFC2136MinTTL time.Duration
RFC2136BatchChangeSize int
NS1Endpoint string
NS1IgnoreSSL bool
NS1MinTTLSeconds int
TransIPAccountName string
TransIPPrivateKeyFile string
DigitalOceanAPIPageSize int
ManagedDNSRecordTypes []string
GoDaddyAPIKey string `secure:"yes"`
GoDaddySecretKey string `secure:"yes"`
GoDaddyTTL int64
GoDaddyOTE bool
OCPRouterName string
IBMCloudProxied bool
IBMCloudConfigFile string
TencentCloudConfigFile string
TencentCloudZoneType string
PiholeServer string
PiholePassword string `secure:"yes"`
PiholeTLSInsecureSkipVerify bool
PluralCluster string
PluralProvider string
}
var defaultConfig = &Config{
@ -213,6 +219,7 @@ var defaultConfig = &Config{
Namespace: "",
AnnotationFilter: "",
LabelFilter: labels.Everything().String(),
IngressClassNames: nil,
FQDNTemplate: "",
CombineFQDNAndAnnotation: false,
IgnoreHostnameAnnotation: false,
@ -292,6 +299,8 @@ var defaultConfig = &Config{
TXTCacheInterval: 0,
TXTWildcardReplacement: "",
MinEventSyncInterval: 5 * time.Second,
TXTEncryptEnabled: false,
TXTEncryptAESKey: "",
Interval: time.Minute,
Once: false,
DryRun: false,
@ -327,7 +336,7 @@ var defaultConfig = &Config{
TransIPAccountName: "",
TransIPPrivateKeyFile: "",
DigitalOceanAPIPageSize: 50,
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
GoDaddyAPIKey: "",
GoDaddySecretKey: "",
GoDaddyTTL: 600,
@ -388,6 +397,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("server", "The Kubernetes API server to connect to (default: auto-detect)").Default(defaultConfig.APIServerURL).StringVar(&cfg.APIServerURL)
app.Flag("kubeconfig", "Retrieve target cluster configuration from a Kubernetes configuration file (default: auto-detect)").Default(defaultConfig.KubeConfig).StringVar(&cfg.KubeConfig)
app.Flag("request-timeout", "Request timeout when calling Kubernetes APIs. 0s means no timeout").Default(defaultConfig.RequestTimeout.String()).DurationVar(&cfg.RequestTimeout)
app.Flag("resolve-service-load-balancer-hostname", "Resolve the hostname of LoadBalancer-type Service object to IP addresses in order to create DNS A/AAAA records instead of CNAMEs").BoolVar(&cfg.ResolveServiceLoadBalancerHostname)
// Flags related to cloud foundry
app.Flag("cf-api-endpoint", "The fully-qualified domain name of the cloud foundry instance you are targeting").Default(defaultConfig.CFAPIEndpoint).StringVar(&cfg.CFAPIEndpoint)
@ -409,6 +419,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter)
app.Flag("label-filter", "Filter sources managed by external-dns via label selector when listing all resources; currently supported by source types CRD, ingress, service and openshift-route").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter)
app.Flag("ingress-class", "Require an ingress to have this class name (defaults to any class; specify multiple times to allow more than one class)").StringsVar(&cfg.IngressClassNames)
app.Flag("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate)
app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation)
app.Flag("ignore-hostname-annotation", "Ignore hostname annotation when generating DNS names, valid only when using fqdn-template is set (optional, default: false)").BoolVar(&cfg.IgnoreHostnameAnnotation)
@ -424,7 +435,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("crd-source-apiversion", "API version of the CRD for crd source, e.g. `externaldns.k8s.io/v1alpha1`, valid only when using crd source").Default(defaultConfig.CRDSourceAPIVersion).StringVar(&cfg.CRDSourceAPIVersion)
app.Flag("crd-source-kind", "Kind of the CRD for the crd source in API group and version specified by crd-source-apiversion").Default(defaultConfig.CRDSourceKind).StringVar(&cfg.CRDSourceKind)
app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter)
app.Flag("managed-record-types", "Comma separated list of record types to manage (default: A, CNAME) (supported records: CNAME, A, NS").Default("A", "CNAME").StringsVar(&cfg.ManagedDNSRecordTypes)
app.Flag("managed-record-types", "Record types to manage; specify multiple times to include many; (default: A, AAAA, CNAME) (supported records: CNAME, A, AAAA, NS").Default("A", "AAAA", "CNAME").StringsVar(&cfg.ManagedDNSRecordTypes)
app.Flag("default-targets", "Set globally default IP address that will apply as a target instead of source addresses. Specify multiple times for multiple targets (optional)").StringsVar(&cfg.DefaultTargets)
app.Flag("target-net-filter", "Limit possible targets by a net filter; specify multiple times for multiple possible nets (optional)").StringsVar(&cfg.TargetNetFilter)
app.Flag("exclude-target-net", "Exclude target nets (optional)").StringsVar(&cfg.ExcludeTargetNets)
@ -498,6 +509,8 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("dyn-password", "When using the Dyn provider, specify the password").Default("").StringVar(&cfg.DynPassword)
app.Flag("dyn-min-ttl", "Minimal TTL (in seconds) for records. This value will be used if the provided TTL for a service/ingress is lower than this.").IntVar(&cfg.DynMinTTLSeconds)
app.Flag("oci-config-file", "When using the OCI provider, specify the OCI configuration file (required when --provider=oci").Default(defaultConfig.OCIConfigFile).StringVar(&cfg.OCIConfigFile)
app.Flag("oci-compartment-ocid", "When using the OCI provider, specify the OCID of the OCI compartment containing all managed zones and records. Required when using OCI IAM instance principal authentication.").StringVar(&cfg.OCICompartmentOCID)
app.Flag("oci-auth-instance-principal", "When using the OCI provider, specify whether OCI IAM instance principal authentication should be used (instead of key-based auth via the OCI config file).").Default(strconv.FormatBool(defaultConfig.OCIAuthInstancePrincipal)).BoolVar(&cfg.OCIAuthInstancePrincipal)
app.Flag("rcodezero-txt-encrypt", "When using the Rcodezero provider with txt registry option, set if TXT rrs are encrypted (default: false)").Default(strconv.FormatBool(defaultConfig.RcodezeroTXTEncrypt)).BoolVar(&cfg.RcodezeroTXTEncrypt)
app.Flag("inmemory-zone", "Provide a list of pre-configured zones for the inmemory provider; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.InMemoryZones)
app.Flag("ovh-endpoint", "When using the OVH provider, specify the endpoint (default: ovh-eu)").Default(defaultConfig.OVHEndpoint).StringVar(&cfg.OVHEndpoint)
@ -564,6 +577,8 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("txt-prefix", "When using the TXT registry, a custom string that's prefixed to each ownership DNS record (optional). Could contain record type template like '%{record_type}-prefix-'. 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). Could contain record type template like '-%{record_type}-suffix'. Mutual exclusive with txt-prefix!").Default(defaultConfig.TXTSuffix).StringVar(&cfg.TXTSuffix)
app.Flag("txt-wildcard-replacement", "When using the TXT registry, a custom string that's used instead of an asterisk for TXT records corresponding to wildcard DNS records (optional)").Default(defaultConfig.TXTWildcardReplacement).StringVar(&cfg.TXTWildcardReplacement)
app.Flag("txt-encrypt-enabled", "When using the TXT registry, set if TXT records should be encrypted before stored (default: disabled)").BoolVar(&cfg.TXTEncryptEnabled)
app.Flag("txt-encrypt-aes-key", "When using the TXT registry, set TXT record decryption and encryption 32 byte aes key (required when --txt-encrypt=true)").Default(defaultConfig.TXTEncryptAESKey).StringVar(&cfg.TXTEncryptAESKey)
// 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)

View File

@ -122,7 +122,7 @@ var (
TransIPAccountName: "",
TransIPPrivateKeyFile: "",
DigitalOceanAPIPageSize: 50,
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
RFC2136BatchChangeSize: 50,
OCPRouterName: "default",
IBMCloudProxied: false,
@ -233,7 +233,7 @@ var (
TransIPAccountName: "transip",
TransIPPrivateKeyFile: "/path/to/transip.key",
DigitalOceanAPIPageSize: 100,
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS},
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS},
RFC2136BatchChangeSize: 100,
IBMCloudProxied: true,
IBMCloudConfigFile: "ibmcloud.json",
@ -372,6 +372,7 @@ func TestParseFlags(t *testing.T) {
"--transip-keyfile=/path/to/transip.key",
"--digitalocean-api-page-size=100",
"--managed-record-types=A",
"--managed-record-types=AAAA",
"--managed-record-types=CNAME",
"--managed-record-types=NS",
"--rfc2136-batch-change-size=100",
@ -488,7 +489,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_TRANSIP_ACCOUNT": "transip",
"EXTERNAL_DNS_TRANSIP_KEYFILE": "/path/to/transip.key",
"EXTERNAL_DNS_DIGITALOCEAN_API_PAGE_SIZE": "100",
"EXTERNAL_DNS_MANAGED_RECORD_TYPES": "A\nCNAME\nNS",
"EXTERNAL_DNS_MANAGED_RECORD_TYPES": "A\nAAAA\nCNAME\nNS",
"EXTERNAL_DNS_RFC2136_BATCH_CHANGE_SIZE": "100",
"EXTERNAL_DNS_IBMCLOUD_PROXIED": "1",
"EXTERNAL_DNS_IBMCLOUD_CONFIG_FILE": "ibmcloud.json",

View File

@ -64,8 +64,15 @@ type Changes struct {
Delete []*endpoint.Endpoint
}
// planKey is a key for a row in `planTable`.
type planKey struct {
dnsName string
setIdentifier string
recordType string
}
// planTable is a supplementary struct for Plan
// each row correspond to a dnsName -> (current record + all desired records)
// each row correspond to a planKey -> (current record + all desired records)
/*
planTable: (-> = target)
--------------------------------------------------------
@ -78,12 +85,12 @@ bar.com | | [->191.1.1.1, ->190.1.1.1] | = create (bar.com -> 1
"=", i.e. result of calculation relies on supplied ConflictResolver
*/
type planTable struct {
rows map[string]map[string]*planTableRow
rows map[planKey]*planTableRow
resolver ConflictResolver
}
func newPlanTable() planTable { // TODO: make resolver configurable
return planTable{map[string]map[string]*planTableRow{}, PerResource{}}
return planTable{map[planKey]*planTableRow{}, PerResource{}}
}
// planTableRow
@ -99,25 +106,25 @@ func (t planTableRow) String() string {
}
func (t planTable) addCurrent(e *endpoint.Endpoint) {
dnsName := normalizeDNSName(e.DNSName)
if _, ok := t.rows[dnsName]; !ok {
t.rows[dnsName] = make(map[string]*planTableRow)
}
if _, ok := t.rows[dnsName][e.SetIdentifier]; !ok {
t.rows[dnsName][e.SetIdentifier] = &planTableRow{}
}
t.rows[dnsName][e.SetIdentifier].current = e
key := t.newPlanKey(e)
t.rows[key].current = e
}
func (t planTable) addCandidate(e *endpoint.Endpoint) {
dnsName := normalizeDNSName(e.DNSName)
if _, ok := t.rows[dnsName]; !ok {
t.rows[dnsName] = make(map[string]*planTableRow)
key := t.newPlanKey(e)
t.rows[key].candidates = append(t.rows[key].candidates, e)
}
func (t *planTable) newPlanKey(e *endpoint.Endpoint) planKey {
key := planKey{
dnsName: normalizeDNSName(e.DNSName),
setIdentifier: e.SetIdentifier,
recordType: e.RecordType,
}
if _, ok := t.rows[dnsName][e.SetIdentifier]; !ok {
t.rows[dnsName][e.SetIdentifier] = &planTableRow{}
if _, ok := t.rows[key]; !ok {
t.rows[key] = &planTableRow{}
}
t.rows[dnsName][e.SetIdentifier].candidates = append(t.rows[dnsName][e.SetIdentifier].candidates, e)
return key
}
func (c *Changes) HasChanges() bool {
@ -146,26 +153,24 @@ func (p *Plan) Calculate() *Plan {
changes := &Changes{}
for _, topRow := range t.rows {
for _, row := range topRow {
if row.current == nil { // dns name not taken
changes.Create = append(changes.Create, t.resolver.ResolveCreate(row.candidates))
}
if row.current != nil && len(row.candidates) == 0 {
changes.Delete = append(changes.Delete, row.current)
}
for _, row := range t.rows {
if row.current == nil { // dns name not taken
changes.Create = append(changes.Create, t.resolver.ResolveCreate(row.candidates))
}
if row.current != nil && len(row.candidates) == 0 {
changes.Delete = append(changes.Delete, row.current)
}
// TODO: allows record type change, which might not be supported by all dns providers
if row.current != nil && len(row.candidates) > 0 { // dns name is taken
update := t.resolver.ResolveUpdate(row.current, row.candidates)
// compare "update" to "current" to figure out if actual update is required
if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || p.shouldUpdateProviderSpecific(update, row.current) {
inheritOwner(row.current, update)
changes.UpdateNew = append(changes.UpdateNew, update)
changes.UpdateOld = append(changes.UpdateOld, row.current)
}
continue
// TODO: allows record type change, which might not be supported by all dns providers
if row.current != nil && len(row.candidates) > 0 { // dns name is taken
update := t.resolver.ResolveUpdate(row.current, row.candidates)
// compare "update" to "current" to figure out if actual update is required
if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || p.shouldUpdateProviderSpecific(update, row.current) {
inheritOwner(row.current, update)
changes.UpdateNew = append(changes.UpdateNew, update)
changes.UpdateOld = append(changes.UpdateOld, row.current)
}
continue
}
}
for _, pol := range p.Policies {
@ -181,7 +186,7 @@ func (p *Plan) Calculate() *Plan {
Current: p.Current,
Desired: p.Desired,
Changes: changes,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
}
return plan

View File

@ -35,6 +35,9 @@ type PlanTestSuite struct {
fooV2CnameNoLabel *endpoint.Endpoint
fooV3CnameSameResource *endpoint.Endpoint
fooA5 *endpoint.Endpoint
fooAAAA *endpoint.Endpoint
dsA *endpoint.Endpoint
dsAAAA *endpoint.Endpoint
bar127A *endpoint.Endpoint
bar127AWithTTL *endpoint.Endpoint
bar127AWithProviderSpecificTrue *endpoint.Endpoint
@ -106,6 +109,30 @@ func (suite *PlanTestSuite) SetupTest() {
endpoint.ResourceLabelKey: "ingress/default/foo-5",
},
}
suite.fooAAAA = &endpoint.Endpoint{
DNSName: "foo",
Targets: endpoint.Targets{"2001:DB8::1"},
RecordType: "AAAA",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-AAAA",
},
}
suite.dsA = &endpoint.Endpoint{
DNSName: "ds",
Targets: endpoint.Targets{"1.1.1.1"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/ds",
},
}
suite.dsAAAA = &endpoint.Endpoint{
DNSName: "ds",
Targets: endpoint.Targets{"1.1.1.1"},
RecordType: "AAAA",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/ds-AAAAA",
},
}
suite.bar127A = &endpoint.Endpoint{
DNSName: "bar",
Targets: endpoint.Targets{"127.0.0.1"},
@ -438,9 +465,9 @@ func (suite *PlanTestSuite) TestIdempotency() {
func (suite *PlanTestSuite) TestDifferentTypes() {
current := []*endpoint.Endpoint{suite.fooV1Cname}
desired := []*endpoint.Endpoint{suite.fooV2Cname, suite.fooA5}
expectedCreate := []*endpoint.Endpoint{}
expectedCreate := []*endpoint.Endpoint{suite.fooA5}
expectedUpdateOld := []*endpoint.Endpoint{suite.fooV1Cname}
expectedUpdateNew := []*endpoint.Endpoint{suite.fooA5}
expectedUpdateNew := []*endpoint.Endpoint{suite.fooV2Cname}
expectedDelete := []*endpoint.Endpoint{}
p := &Plan{
@ -544,52 +571,6 @@ func (suite *PlanTestSuite) TestRemoveEndpointWithUpsert() {
validateEntries(suite.T(), changes.Delete, expectedDelete)
}
// TODO: remove once multiple-target per endpoint is supported
func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceReplace() {
current := []*endpoint.Endpoint{suite.fooV3CnameSameResource, suite.bar192A}
desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV3CnameSameResource}
expectedCreate := []*endpoint.Endpoint{}
expectedUpdateOld := []*endpoint.Endpoint{suite.fooV3CnameSameResource}
expectedUpdateNew := []*endpoint.Endpoint{suite.fooV1Cname}
expectedDelete := []*endpoint.Endpoint{suite.bar192A}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
}
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)
}
// TODO: remove once multiple-target per endpoint is supported
func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceRetain() {
current := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar192A}
desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV3CnameSameResource}
expectedCreate := []*endpoint.Endpoint{}
expectedUpdateOld := []*endpoint.Endpoint{}
expectedUpdateNew := []*endpoint.Endpoint{}
expectedDelete := []*endpoint.Endpoint{suite.bar192A}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
}
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) TestMultipleRecordsSameNameDifferentSetIdentifier() {
current := []*endpoint.Endpoint{suite.multiple1}
desired := []*endpoint.Endpoint{suite.multiple2, suite.multiple3}
@ -695,6 +676,39 @@ func (suite *PlanTestSuite) TestMissing() {
validateEntries(suite.T(), changes.Create, expectedCreate)
}
func (suite *PlanTestSuite) TestAAAARecords() {
current := []*endpoint.Endpoint{}
desired := []*endpoint.Endpoint{suite.fooAAAA}
expectedCreate := []*endpoint.Endpoint{suite.fooAAAA}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
}
changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
}
func (suite *PlanTestSuite) TestDualStackRecords() {
current := []*endpoint.Endpoint{}
desired := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
expectedCreate := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
}
changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
}
func TestPlan(t *testing.T) {
suite.Run(t, new(PlanTestSuite))
}

View File

@ -86,6 +86,7 @@ var canonicalHostedZones = map[string]string{
"eu-west-3.elb.amazonaws.com": "Z3Q77PNBQS71R4",
"eu-north-1.elb.amazonaws.com": "Z23TAZ6LKFMNIO",
"eu-south-1.elb.amazonaws.com": "Z3ULH7SSC9OV64",
"eu-south-2.elb.amazonaws.com": "Z0956581394HF5D5LXGAP",
"sa-east-1.elb.amazonaws.com": "Z2P70J7HTTTPLU",
"cn-north-1.elb.amazonaws.com.cn": "Z1GDH35T77C1KE",
"cn-northwest-1.elb.amazonaws.com.cn": "ZM7IZAIOVVDZF",
@ -115,6 +116,7 @@ var canonicalHostedZones = map[string]string{
"elb.eu-west-3.amazonaws.com": "Z1CMS0P5QUZ6D5",
"elb.eu-north-1.amazonaws.com": "Z1UDT6IFJ4EJM",
"elb.eu-south-1.amazonaws.com": "Z23146JA1KNAFP",
"elb.eu-south-2.amazonaws.com": "Z1011216NVTVYADP1SSV",
"elb.sa-east-1.amazonaws.com": "ZTK26PT1VY4CU",
"elb.cn-north-1.amazonaws.com.cn": "Z3QFB96KMJ7ED6",
"elb.cn-northwest-1.amazonaws.com.cn": "ZQEIKTCZ8352D",
@ -373,7 +375,7 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*route53.Hos
for _, r := range resp.ResourceRecordSets {
newEndpoints := make([]*endpoint.Endpoint, 0)
if !provider.SupportedRecordType(aws.StringValue(r.Type)) {
if !p.SupportedRecordType(aws.StringValue(r.Type)) {
continue
}
@ -1059,3 +1061,12 @@ func canonicalHostedZone(hostname string) string {
func cleanZoneID(id string) string {
return strings.TrimPrefix(id, "/hostedzone/")
}
func (p *AWSProvider) SupportedRecordType(recordType string) bool {
switch recordType {
case "MX":
return true
default:
return provider.SupportedRecordType(recordType)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -509,7 +509,7 @@ func (p *AWSSDProvider) DeleteService(service *sd.Service) error {
// convert ownerID string to service description format
label := endpoint.NewLabels()
label[endpoint.OwnerLabelKey] = p.ownerID
label[endpoint.AWSSDDescriptionLabel] = label.Serialize(false)
label[endpoint.AWSSDDescriptionLabel] = label.SerializePlain(false)
if strings.HasPrefix(aws.StringValue(service.Description), label[endpoint.AWSSDDescriptionLabel]) {
log.Infof("Deleting service \"%s\"", *service.Name)

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// nolint:staticcheck
//nolint:staticcheck // Required due to the current dependency on a deprecated version of azure-sdk-for-go
package azure
import (
@ -109,7 +109,7 @@ func (p *AzureProvider) Records(ctx context.Context) (endpoints []*endpoint.Endp
return true
}
recordType := strings.TrimPrefix(*recordSet.Type, "Microsoft.Network/dnszones/")
if !provider.SupportedRecordType(recordType) {
if !p.SupportedRecordType(recordType) {
return true
}
name := formatAzureDNSName(*recordSet.Name, *zone.Name)
@ -190,6 +190,15 @@ func (p *AzureProvider) zones(ctx context.Context) ([]dns.Zone, error) {
return zones, nil
}
func (p *AzureProvider) SupportedRecordType(recordType string) bool {
switch recordType {
case "MX":
return true
default:
return provider.SupportedRecordType(recordType)
}
}
func (p *AzureProvider) iterateRecords(ctx context.Context, zoneName string, callback func(dns.RecordSet) bool) error {
log.Debugf("Retrieving Azure DNS records for zone '%s'.", zoneName)
@ -241,10 +250,6 @@ func (p *AzureProvider) mapChanges(zones []dns.Zone, changes *plan.Changes) (azu
mapChange(deleted, change)
}
for _, change := range changes.UpdateOld {
mapChange(deleted, change)
}
for _, change := range changes.Create {
mapChange(updated, change)
}
@ -377,6 +382,21 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet
},
},
}, nil
case dns.MX:
mxRecords := make([]dns.MxRecord, len(endpoint.Targets))
for i, target := range endpoint.Targets {
mxRecord, err := parseMxTarget[dns.MxRecord](target)
if err != nil {
return dns.RecordSet{}, err
}
mxRecords[i] = mxRecord
}
return dns.RecordSet{
RecordSetProperties: &dns.RecordSetProperties{
TTL: to.Int64Ptr(ttl),
MxRecords: &mxRecords,
},
}, nil
case dns.TXT:
return dns.RecordSet{
RecordSetProperties: &dns.RecordSetProperties{
@ -425,6 +445,16 @@ func extractAzureTargets(recordSet *dns.RecordSet) []string {
return []string{*cnameRecord.Cname}
}
// Check for MX records
mxRecords := properties.MxRecords
if mxRecords != nil && len(*mxRecords) > 0 && (*mxRecords)[0].Exchange != nil {
targets := make([]string, len(*mxRecords))
for i, mxRecord := range *mxRecords {
targets[i] = fmt.Sprintf("%d %s", *mxRecord.Preference, *mxRecord.Exchange)
}
return targets
}
// Check for TXT records
txtRecords := properties.TxtRecords
if txtRecords != nil && len(*txtRecords) > 0 && (*txtRecords)[0].Value != nil {

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// nolint:staticcheck
//nolint:staticcheck // Required due to the current dependency on a deprecated version of azure-sdk-for-go
package azure
import (
@ -237,10 +237,6 @@ func (p *AzurePrivateDNSProvider) mapChanges(zones []privatedns.PrivateZone, cha
mapChange(deleted, change)
}
for _, change := range changes.UpdateOld {
mapChange(deleted, change)
}
for _, change := range changes.Create {
mapChange(updated, change)
}
@ -367,6 +363,21 @@ func (p *AzurePrivateDNSProvider) newRecordSet(endpoint *endpoint.Endpoint) (pri
},
},
}, nil
case privatedns.MX:
mxRecords := make([]privatedns.MxRecord, len(endpoint.Targets))
for i, target := range endpoint.Targets {
mxRecord, err := parseMxTarget[privatedns.MxRecord](target)
if err != nil {
return privatedns.RecordSet{}, err
}
mxRecords[i] = mxRecord
}
return privatedns.RecordSet{
RecordSetProperties: &privatedns.RecordSetProperties{
TTL: to.Int64Ptr(ttl),
MxRecords: &mxRecords,
},
}, nil
case privatedns.TXT:
return privatedns.RecordSet{
RecordSetProperties: &privatedns.RecordSetProperties{
@ -407,6 +418,16 @@ func extractAzurePrivateDNSTargets(recordSet *privatedns.RecordSet) []string {
return []string{*cnameRecord.Cname}
}
// Check for MX records
mxRecords := properties.MxRecords
if mxRecords != nil && len(*mxRecords) > 0 && (*mxRecords)[0].Exchange != nil {
targets := make([]string, len(*mxRecords))
for i, mxRecord := range *mxRecords {
targets[i] = fmt.Sprintf("%d %s", *mxRecord.Preference, *mxRecord.Exchange)
}
return targets
}
// Check for TXT records
txtRecords := properties.TxtRecords
if txtRecords != nil && len(*txtRecords) > 0 && (*txtRecords)[0].Value != nil {

View File

@ -123,6 +123,17 @@ func privateCNameRecordSetPropertiesGetter(values []string, ttl int64) *privated
}
}
func privateMXRecordSetPropertiesGetter(values []string, ttl int64) *privatedns.RecordSetProperties {
mxRecords := make([]privatedns.MxRecord, len(values))
for i, target := range values {
mxRecords[i], _ = parseMxTarget[privatedns.MxRecord](target)
}
return &privatedns.RecordSetProperties{
TTL: to.Int64Ptr(ttl),
MxRecords: &mxRecords,
}
}
func privateTxtRecordSetPropertiesGetter(values []string, ttl int64) *privatedns.RecordSetProperties {
return &privatedns.RecordSetProperties{
TTL: to.Int64Ptr(ttl),
@ -156,6 +167,8 @@ func createPrivateMockRecordSetMultiWithTTL(name, recordType string, ttl int64,
getterFunc = privateARecordSetPropertiesGetter
case endpoint.RecordTypeCNAME:
getterFunc = privateCNameRecordSetPropertiesGetter
case endpoint.RecordTypeMX:
getterFunc = privateMXRecordSetPropertiesGetter
case endpoint.RecordTypeTXT:
getterFunc = privateTxtRecordSetPropertiesGetter
default:
@ -266,6 +279,7 @@ func TestAzurePrivateDNSRecord(t *testing.T) {
createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeA, "123.123.123.123", 3600),
createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
createPrivateMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
createPrivateMockRecordSetWithTTL("mail", endpoint.RecordTypeMX, "10 example.com", 4000),
})
if err != nil {
t.Fatal(err)
@ -281,6 +295,7 @@ func TestAzurePrivateDNSRecord(t *testing.T) {
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123"),
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"),
endpoint.NewEndpointWithTTL("hack.example.com", endpoint.RecordTypeCNAME, 10, "hack.azurewebsites.net"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, 4000, "10 example.com"),
}
validateAzureEndpoints(t, actual, expected)
@ -299,6 +314,7 @@ func TestAzurePrivateDNSMultiRecord(t *testing.T) {
createPrivateMockRecordSetMultiWithTTL("nginx", endpoint.RecordTypeA, 3600, "123.123.123.123", "234.234.234.234"),
createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
createPrivateMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
createPrivateMockRecordSetMultiWithTTL("mail", endpoint.RecordTypeMX, 4000, "10 example.com", "20 backup.example.com"),
})
if err != nil {
t.Fatal(err)
@ -314,6 +330,7 @@ func TestAzurePrivateDNSMultiRecord(t *testing.T) {
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123", "234.234.234.234"),
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"),
endpoint.NewEndpointWithTTL("hack.example.com", endpoint.RecordTypeCNAME, 10, "hack.azurewebsites.net"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, 4000, "10 example.com", "20 backup.example.com"),
}
validateAzureEndpoints(t, actual, expected)
@ -325,8 +342,6 @@ func TestAzurePrivateDNSApplyChanges(t *testing.T) {
testAzurePrivateDNSApplyChangesInternal(t, false, &recordsClient)
validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""),
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""),
endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""),
endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""),
})
@ -342,6 +357,9 @@ func TestAzurePrivateDNSApplyChanges(t *testing.T) {
endpoint.NewEndpointWithTTL("other.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
endpoint.NewEndpointWithTTL("newmail.example.com", endpoint.RecordTypeMX, 7200, "40 bar.other.com"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, endpoint.TTL(recordTTL), "10 other.com"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
})
}
@ -401,17 +419,21 @@ func testAzurePrivateDNSApplyChangesInternal(t *testing.T, dryRun bool, client P
endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"),
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"),
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"),
endpoint.NewEndpoint("mail.example.com", endpoint.RecordTypeMX, "10 other.com"),
endpoint.NewEndpoint("mail.example.com", endpoint.RecordTypeTXT, "tag"),
}
currentRecords := []*endpoint.Endpoint{
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"),
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
endpoint.NewEndpoint("old.nope.com", endpoint.RecordTypeA, "121.212.121.212"),
endpoint.NewEndpoint("oldmail.example.com", endpoint.RecordTypeMX, "20 foo.other.com"),
}
updatedRecords := []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeA, "222.111.222.111"),
endpoint.NewEndpointWithTTL("newmail.example.com", endpoint.RecordTypeMX, 7200, "40 bar.other.com"),
}
deleteRecords := []*endpoint.Endpoint{

View File

@ -122,6 +122,17 @@ func cNameRecordSetPropertiesGetter(values []string, ttl int64) *dns.RecordSetPr
}
}
func mxRecordSetPropertiesGetter(values []string, ttl int64) *dns.RecordSetProperties {
mxRecords := make([]dns.MxRecord, len(values))
for i, target := range values {
mxRecords[i], _ = parseMxTarget[dns.MxRecord](target)
}
return &dns.RecordSetProperties{
TTL: to.Int64Ptr(ttl),
MxRecords: &mxRecords,
}
}
func txtRecordSetPropertiesGetter(values []string, ttl int64) *dns.RecordSetProperties {
return &dns.RecordSetProperties{
TTL: to.Int64Ptr(ttl),
@ -155,6 +166,8 @@ func createMockRecordSetMultiWithTTL(name, recordType string, ttl int64, values
getterFunc = aRecordSetPropertiesGetter
case endpoint.RecordTypeCNAME:
getterFunc = cNameRecordSetPropertiesGetter
case endpoint.RecordTypeMX:
getterFunc = mxRecordSetPropertiesGetter
case endpoint.RecordTypeTXT:
getterFunc = txtRecordSetPropertiesGetter
default:
@ -271,6 +284,7 @@ func TestAzureRecord(t *testing.T) {
createMockRecordSetWithTTL("nginx", endpoint.RecordTypeA, "123.123.123.123", 3600),
createMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
createMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
createMockRecordSetMultiWithTTL("mail", endpoint.RecordTypeMX, 4000, "10 example.com"),
})
if err != nil {
t.Fatal(err)
@ -287,6 +301,7 @@ func TestAzureRecord(t *testing.T) {
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123"),
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"),
endpoint.NewEndpointWithTTL("hack.example.com", endpoint.RecordTypeCNAME, 10, "hack.azurewebsites.net"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, 4000, "10 example.com"),
}
validateAzureEndpoints(t, actual, expected)
@ -305,6 +320,7 @@ func TestAzureMultiRecord(t *testing.T) {
createMockRecordSetMultiWithTTL("nginx", endpoint.RecordTypeA, 3600, "123.123.123.123", "234.234.234.234"),
createMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
createMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
createMockRecordSetMultiWithTTL("mail", endpoint.RecordTypeMX, 4000, "10 example.com", "20 backup.example.com"),
})
if err != nil {
t.Fatal(err)
@ -321,6 +337,7 @@ func TestAzureMultiRecord(t *testing.T) {
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123", "234.234.234.234"),
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"),
endpoint.NewEndpointWithTTL("hack.example.com", endpoint.RecordTypeCNAME, 10, "hack.azurewebsites.net"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, 4000, "10 example.com", "20 backup.example.com"),
}
validateAzureEndpoints(t, actual, expected)
@ -332,8 +349,6 @@ func TestAzureApplyChanges(t *testing.T) {
testAzureApplyChangesInternal(t, false, &recordsClient)
validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""),
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""),
endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""),
endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""),
})
@ -349,6 +364,9 @@ func TestAzureApplyChanges(t *testing.T) {
endpoint.NewEndpointWithTTL("other.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
endpoint.NewEndpointWithTTL("newmail.example.com", endpoint.RecordTypeMX, 7200, "40 bar.other.com"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, endpoint.TTL(recordTTL), "10 other.com"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
})
}
@ -410,17 +428,21 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordSetsC
endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"),
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"),
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"),
endpoint.NewEndpoint("mail.example.com", endpoint.RecordTypeMX, "10 other.com"),
endpoint.NewEndpoint("mail.example.com", endpoint.RecordTypeTXT, "tag"),
}
currentRecords := []*endpoint.Endpoint{
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"),
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
endpoint.NewEndpoint("old.nope.com", endpoint.RecordTypeA, "121.212.121.212"),
endpoint.NewEndpoint("oldmail.example.com", endpoint.RecordTypeMX, "20 foo.other.com"),
}
updatedRecords := []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeA, "222.111.222.111"),
endpoint.NewEndpointWithTTL("newmail.example.com", endpoint.RecordTypeMX, 7200, "40 bar.other.com"),
}
deleteRecords := []*endpoint.Endpoint{
@ -455,6 +477,7 @@ func TestAzureNameFilter(t *testing.T) {
createMockRecordSetWithTTL("test.nginx", endpoint.RecordTypeA, "123.123.123.123", 3600),
createMockRecordSetWithTTL("nginx", endpoint.RecordTypeA, "123.123.123.123", 3600),
createMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
createMockRecordSetWithTTL("mail.nginx", endpoint.RecordTypeMX, "20 example.com", recordTTL),
createMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
})
if err != nil {
@ -470,6 +493,7 @@ func TestAzureNameFilter(t *testing.T) {
endpoint.NewEndpointWithTTL("test.nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123"),
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123"),
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"),
endpoint.NewEndpointWithTTL("mail.nginx.example.com", endpoint.RecordTypeMX, recordTTL, "20 example.com"),
}
validateAzureEndpoints(t, actual, expected)
@ -481,8 +505,6 @@ func TestAzureApplyChangesZoneName(t *testing.T) {
testAzureApplyChangesInternalZoneName(t, false, &recordsClient)
validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{
endpoint.NewEndpoint("old.foo.example.com", endpoint.RecordTypeA, ""),
endpoint.NewEndpoint("oldcname.foo.example.com", endpoint.RecordTypeCNAME, ""),
endpoint.NewEndpoint("deleted.foo.example.com", endpoint.RecordTypeA, ""),
endpoint.NewEndpoint("deletedcname.foo.example.com", endpoint.RecordTypeCNAME, ""),
})

47
provider/azure/common.go Normal file
View File

@ -0,0 +1,47 @@
/*
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.
*/
//nolint:staticcheck // Required due to the current dependency on a deprecated version of azure-sdk-for-go
package azure
import (
"fmt"
"strconv"
"strings"
"github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2018-05-01/dns"
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
"github.com/Azure/go-autorest/autorest/to"
)
// Helper function (shared with test code)
func parseMxTarget[T dns.MxRecord | privatedns.MxRecord](mxTarget string) (T, error) {
targetParts := strings.SplitN(mxTarget, " ", 2)
if len(targetParts) != 2 {
return T{}, fmt.Errorf("mx target needs to be of form '10 example.com'")
}
preferenceRaw, exchange := targetParts[0], targetParts[1]
preference, err := strconv.ParseInt(preferenceRaw, 10, 32)
if err != nil {
return T{}, fmt.Errorf("invalid preference specified")
}
return T{
Preference: to.Int32Ptr(int32(preference)),
Exchange: to.StringPtr(exchange),
}, nil
}

View File

@ -0,0 +1,87 @@
/*
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 azure
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2018-05-01/dns"
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
"github.com/Azure/go-autorest/autorest/to"
"github.com/stretchr/testify/assert"
)
func Test_parseMxTarget(t *testing.T) {
type testCase[T interface {
dns.MxRecord | privatedns.MxRecord
}] struct {
name string
args string
want T
wantErr assert.ErrorAssertionFunc
}
tests := []testCase[dns.MxRecord]{
{
name: "valid mx target",
args: "10 example.com",
want: dns.MxRecord{
Preference: to.Int32Ptr(int32(10)),
Exchange: to.StringPtr("example.com"),
},
wantErr: assert.NoError,
},
{
name: "valid mx target with a subdomain",
args: "99 foo-bar.example.com",
want: dns.MxRecord{
Preference: to.Int32Ptr(int32(99)),
Exchange: to.StringPtr("foo-bar.example.com"),
},
wantErr: assert.NoError,
},
{
name: "invalid mx target with misplaced preference and exchange",
args: "example.com 10",
want: dns.MxRecord{},
wantErr: assert.Error,
},
{
name: "invalid mx target without preference",
args: "example.com",
want: dns.MxRecord{},
wantErr: assert.Error,
},
{
name: "invalid mx target with non numeric preference",
args: "aa example.com",
want: dns.MxRecord{},
wantErr: assert.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseMxTarget[dns.MxRecord](tt.args)
if !tt.wantErr(t, err, fmt.Sprintf("parseMxTarget(%v)", tt.args)) {
return
}
assert.Equalf(t, tt.want, got, "parseMxTarget(%v)", tt.args)
})
}
}

View File

@ -21,6 +21,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
cloudflare "github.com/cloudflare/cloudflare-go"
log "github.com/sirupsen/logrus"
@ -155,7 +156,15 @@ func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter prov
err error
)
if os.Getenv("CF_API_TOKEN") != "" {
config, err = cloudflare.NewWithAPIToken(os.Getenv("CF_API_TOKEN"))
token := os.Getenv("CF_API_TOKEN")
if strings.HasPrefix(token, "file:") {
tokenBytes, err := os.ReadFile(strings.TrimPrefix(token, "file:"))
if err != nil {
return nil, fmt.Errorf("failed to read CF_API_TOKEN from file: %w", err)
}
token = string(tokenBytes)
}
config, err = cloudflare.NewWithAPIToken(token)
} else {
config, err = cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL"))
}

View File

@ -677,6 +677,23 @@ func TestCloudflareProvider(t *testing.T) {
if err != nil {
t.Errorf("should not fail, %s", err)
}
_ = os.Unsetenv("CF_API_TOKEN")
tokenFile := "/tmp/cf_api_token"
if err := os.WriteFile(tokenFile, []byte("abc123def"), 0644); err != nil {
t.Errorf("failed to write token file, %s", err)
}
_ = os.Setenv("CF_API_TOKEN", tokenFile)
_, err = NewCloudFlareProvider(
endpoint.NewDomainFilter([]string{"bar.com"}),
provider.NewZoneIDFilter([]string{""}),
false,
true,
5000)
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")
@ -689,6 +706,7 @@ func TestCloudflareProvider(t *testing.T) {
if err != nil {
t.Errorf("should not fail, %s", err)
}
_ = os.Unsetenv("CF_API_KEY")
_ = os.Unsetenv("CF_API_EMAIL")
_, err = NewCloudFlareProvider(

View File

@ -322,13 +322,13 @@ func (p designateProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, e
if recordSet.Type != endpoint.RecordTypeA && recordSet.Type != endpoint.RecordTypeTXT && recordSet.Type != endpoint.RecordTypeCNAME {
return nil
}
for _, record := range recordSet.Records {
ep := endpoint.NewEndpoint(recordSet.Name, recordSet.Type, record)
ep.Labels[designateRecordSetID] = recordSet.ID
ep.Labels[designateZoneID] = recordSet.ZoneID
ep.Labels[designateOriginalRecords] = strings.Join(recordSet.Records, "\000")
result = append(result, ep)
}
ep := endpoint.NewEndpoint(recordSet.Name, recordSet.Type, recordSet.Records...)
ep.Labels[designateRecordSetID] = recordSet.ID
ep.Labels[designateZoneID] = recordSet.ZoneID
ep.Labels[designateOriginalRecords] = strings.Join(recordSet.Records, "\000")
result = append(result, ep)
return nil
},
)
@ -358,7 +358,7 @@ type recordSet struct {
}
// adds endpoint into recordset aggregation, loading original values from endpoint labels first
func addEndpoint(ep *endpoint.Endpoint, recordSets map[string]*recordSet, delete bool) {
func addEndpoint(ep *endpoint.Endpoint, recordSets map[string]*recordSet, oldEndpoints []*endpoint.Endpoint, delete bool) {
key := fmt.Sprintf("%s/%s", ep.DNSName, ep.RecordType)
rs := recordSets[key]
if rs == nil {
@ -368,6 +368,9 @@ func addEndpoint(ep *endpoint.Endpoint, recordSets map[string]*recordSet, delete
names: make(map[string]bool),
}
}
addDesignateIDLabelsFromExistingEndpoints(oldEndpoints, ep)
if rs.zoneID == "" {
rs.zoneID = ep.Labels[designateZoneID]
}
@ -389,25 +392,55 @@ func addEndpoint(ep *endpoint.Endpoint, recordSets map[string]*recordSet, delete
recordSets[key] = rs
}
// addDesignateIDLabelsFromExistingEndpoints adds the labels identified by the constants designateZoneID and designateRecordSetID
// to an Endpoint. Therefore, it searches all given existing endpoints for an endpoint with the same record type and record
// value. If the given Endpoint already has the labels set, they are left untouched. This fixes an issue with the
// TXTRegistry which generates new TXT entries instead of updating the old ones.
func addDesignateIDLabelsFromExistingEndpoints(existingEndpoints []*endpoint.Endpoint, ep *endpoint.Endpoint) {
_, hasZoneIDLabel := ep.Labels[designateZoneID]
_, hasRecordSetIDLabel := ep.Labels[designateRecordSetID]
if hasZoneIDLabel && hasRecordSetIDLabel {
return
}
for _, oep := range existingEndpoints {
if ep.RecordType == oep.RecordType && ep.DNSName == oep.DNSName {
if !hasZoneIDLabel {
ep.Labels[designateZoneID] = oep.Labels[designateZoneID]
}
if !hasRecordSetIDLabel {
ep.Labels[designateRecordSetID] = oep.Labels[designateRecordSetID]
}
return
}
}
}
// ApplyChanges applies a given set of changes in a given zone.
func (p designateProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
managedZones, err := p.getZones()
if err != nil {
return err
}
endpoints, err := p.Records(ctx)
if err != nil {
return fmt.Errorf("failed to fetch active records: %w", err)
}
recordSets := map[string]*recordSet{}
for _, ep := range changes.Create {
addEndpoint(ep, recordSets, false)
}
for _, ep := range changes.UpdateNew {
addEndpoint(ep, recordSets, false)
addEndpoint(ep, recordSets, endpoints, false)
}
for _, ep := range changes.UpdateOld {
addEndpoint(ep, recordSets, true)
addEndpoint(ep, recordSets, endpoints, true)
}
for _, ep := range changes.UpdateNew {
addEndpoint(ep, recordSets, endpoints, false)
}
for _, ep := range changes.Delete {
addEndpoint(ep, recordSets, true)
addEndpoint(ep, recordSets, endpoints, true)
}
for _, rs := range recordSets {
if err2 := p.upsertRecordSet(rs, managedZones); err == nil {
err = err2

View File

@ -274,17 +274,7 @@ func TestDesignateRecords(t *testing.T) {
{
DNSName: "srv.test.net",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"10.2.1.1"},
Labels: map[string]string{
designateRecordSetID: rs21ID,
designateZoneID: zone2ID,
designateOriginalRecords: "10.2.1.1\00010.2.1.2",
},
},
{
DNSName: "srv.test.net",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"10.2.1.2"},
Targets: endpoint.Targets{"10.2.1.1", "10.2.1.2"},
Labels: map[string]string{
designateRecordSetID: rs21ID,
designateZoneID: zone2ID,
@ -336,6 +326,19 @@ func testDesignateCreateRecords(t *testing.T, client *fakeDesignateClient) []*re
Status: "ACTIVE",
})
}
_, err := client.CreateRecordSet("zone-1", recordsets.CreateOpts{
Name: "www.example.com.",
Description: "",
Records: []string{"foo"},
TTL: 60,
Type: endpoint.RecordTypeTXT,
})
if err != nil {
t.Fatal("failed to prefil records")
}
endpoints := []*endpoint.Endpoint{
{
DNSName: "www.example.com",
@ -409,7 +412,7 @@ func testDesignateCreateRecords(t *testing.T, client *fakeDesignateClient) []*re
expectedCopy := make([]*recordsets.RecordSet, len(expected))
copy(expectedCopy, expected)
err := client.ToProvider().ApplyChanges(context.Background(), &plan.Changes{Create: endpoints})
err = client.ToProvider().ApplyChanges(context.Background(), &plan.Changes{Create: endpoints})
if err != nil {
t.Fatal(err)
}

Some files were not shown because too many files have changed in this diff Show More