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: steps:
- name: Set up Go 1.x - name: Set up Go 1.x
uses: actions/setup-go@v3 uses: actions/setup-go@v4
with: with:
go-version: 1.19 go-version: 1.19
id: go id: go

View File

@ -18,13 +18,13 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-python@v2 - uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.10"
cache: "pip" cache: "pip"
cache-dependency-path: "./docs/scripts/requirements.txt" cache-dependency-path: "./docs/scripts/requirements.txt"
- uses: actions/setup-go@v3 - uses: actions/setup-go@v4
with: with:
go-version: ^1.19 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: steps:
- name: Set up Go 1.x - name: Set up Go 1.x
uses: actions/setup-go@v3 uses: actions/setup-go@v4
with: with:
go-version: 1.19 go-version: 1.19
id: go 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 RUN go mod download
COPY . . COPY . .
RUN make test build.$ARCH
# final image FROM alpine:3.18
FROM $ARCH/alpine:3.17
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 /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 COPY --from=builder /sigs.k8s.io/external-dns/build/external-dns /bin/external-dns

View File

@ -19,7 +19,7 @@
cover: cover:
go get github.com/wadey/gocovmerge go get github.com/wadey/gocovmerge
$(eval PKGS := $(shell go list ./... | grep -v /vendor/)) $(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 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 gocovmerge `ls *.coverprofile` > cover.out
rm *.coverprofile rm *.coverprofile
@ -90,8 +90,11 @@ IMAGE ?= us.gcr.io/k8s-artifacts-prod/external-dns/$(BINARY)
VERSION ?= $(shell git describe --tags --always --dirty) VERSION ?= $(shell git describe --tags --always --dirty)
BUILD_FLAGS ?= -v BUILD_FLAGS ?= -v
LDFLAGS ?= -X sigs.k8s.io/external-dns/pkg/apis/externaldns.Version=$(VERSION) -w -s LDFLAGS ?= -X sigs.k8s.io/external-dns/pkg/apis/externaldns.Version=$(VERSION) -w -s
ARCHS = amd64 arm64v8 arm32v7 ARCHS = amd64 arm64 arm/v7
SHELL = /bin/bash ARCH ?= amd64
DEFAULT_ARCH = amd64
SHELL = /bin/bash
OUTPUT_TYPE ?= docker
build: build/$(BINARY) build: build/$(BINARY)
@ -99,37 +102,65 @@ build: build/$(BINARY)
build/$(BINARY): $(SOURCES) build/$(BINARY): $(SOURCES)
CGO_ENABLED=0 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" . 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=() arch_specific_tags=()
for arch in $(ARCHS); do \ for arch in $(ARCHS); do \
image="$(IMAGE):$(VERSION)-$${arch}" ;\ image="$(IMAGE):$(VERSION)-$$(echo $$arch | tr / -)" ;\
# pre-pull due to https://github.com/kubernetes-sigs/cluster-addons/pull/84/files ;\ arch_specific_tags+=( " $${image}" ) ;\
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}" ) ;\
done ;\ done ;\
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create "$(IMAGE):$(VERSION)" $${arch_specific_tags[@]} ;\ echo $${arch_specific_tags[@]} ;\
for arch in $(ARCHS); do \ DOCKER_CLI_EXPERIMENTAL=enabled docker buildx imagetools create --tag "$(IMAGE):$(VERSION)" $${arch_specific_tags[@]} ;\
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate --arch $${arch} "$(IMAGE):$(VERSION)" "$(IMAGE):$(VERSION)-$${arch}" ;\
done;\
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(IMAGE):$(VERSION)" \
build.push: build.docker build.image/multiarch: $(addprefix build.image-,$(ARCHS))
docker push "$(IMAGE):$(VERSION)"
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)" . CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.amd64: build.amd64:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" . 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)" . CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.docker: build.setup:
docker build --rm --tag "$(IMAGE):$(VERSION)" --build-arg VERSION="$(VERSION)" --build-arg ARCH="amd64" . 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: build.mini:
docker build --rm --tag "$(IMAGE):$(VERSION)-mini" --build-arg VERSION="$(VERSION)" -f Dockerfile.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. # Builds and push container images to the staging bucket.
.PHONY: release.staging .PHONY: release.staging
release.staging: release.staging: test
IMAGE=$(IMAGE_STAGING) $(MAKE) build.push/multiarch IMAGE=$(IMAGE_STAGING) $(MAKE) build.push/multiarch
release.prod: release.prod: test
$(MAKE) build.push/multiarch $(MAKE) build.push/multiarch

View File

@ -5,7 +5,7 @@ hide:
--- ---
<p align="center"> <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> </p>
# ExternalDNS # ExternalDNS
@ -176,6 +176,7 @@ The following tutorials are provided:
* [Nginx Ingress Controller](docs/tutorials/nginx-ingress.md) * [Nginx Ingress Controller](docs/tutorials/nginx-ingress.md)
* [NS1](docs/tutorials/ns1.md) * [NS1](docs/tutorials/ns1.md)
* [NS Record Creation with CRD Source](docs/tutorials/ns-record.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) * [OpenStack Designate](docs/tutorials/designate.md)
* [Oracle Cloud Infrastructure (OCI) DNS](docs/tutorials/oracle.md) * [Oracle Cloud Infrastructure (OCI) DNS](docs/tutorials/oracle.md)
* [PowerDNS](docs/tutorials/pdns.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 ## [UNRELEASED]
### Highlights
### All Changes ### All Changes
- Added
- Updated ## [v1.12.2] - UNRELEASED
- Changed
- Fixed ### All Changes
- Deprecated
- Removed --> - 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 ## [v1.12.1] - 2023-02-06

View File

@ -2,8 +2,8 @@ apiVersion: v2
name: external-dns name: external-dns
description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers. description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
type: application type: application
version: 1.12.1 version: 1.12.2
appVersion: 0.13.2 appVersion: 0.13.4
keywords: keywords:
- kubernetes - kubernetes
- externaldns - externaldns
@ -12,7 +12,7 @@ keywords:
- service - service
- ingress - ingress
home: https://github.com/kubernetes-sigs/external-dns/ 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: sources:
- https://github.com/kubernetes-sigs/external-dns/ - https://github.com/kubernetes-sigs/external-dns/
maintainers: maintainers:
@ -20,9 +20,15 @@ maintainers:
email: steve.hipwell@gmail.com email: steve.hipwell@gmail.com
annotations: annotations:
artifacthub.io/changes: | artifacthub.io/changes: |
- kind: changed
description: "Updated ExternalDNS version to v0.13.2."
- kind: added - kind: added
description: "Added secretConfiguration.subPath to mount specific files from secret as a sub-path." description: "Added support for ServiceMonitor relabelling."
- kind: changed - 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` | | `logFormat` | Formats of the logs, available values are: `text`, `json`. | `text` |
| `interval` | The interval for DNS updates. | `1m` | | `interval` | The interval for DNS updates. | `1m` |
| `triggerLoopOnEvent` | When enabled, triggers run loop on create/update/delete events in addition of regular interval. | `false` | | `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_ | | `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` | | `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` | | `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.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.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). | `""` | | `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 -}} {{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: {{ .Values.namespaced | ternary "Role" "ClusterRole" }}
metadata: metadata:
name: {{ template "external-dns.fullname" . }} name: {{ template "external-dns.fullname" . }}
labels: labels:
{{- include "external-dns.labels" . | nindent 4 }} {{- include "external-dns.labels" . | nindent 4 }}
rules: 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: [""] - apiGroups: [""]
resources: ["nodes"] resources: ["nodes"]
verbs: ["list","watch"] verbs: ["list","watch"]
@ -60,6 +60,41 @@ rules:
resources: ["dnsendpoints/status"] resources: ["dnsendpoints/status"]
verbs: ["*"] verbs: ["*"]
{{- end }} {{- 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 }} {{- if has "gloo-proxy" .Values.sources }}
- apiGroups: ["gloo.solo.io","gateway.solo.io"] - apiGroups: ["gloo.solo.io","gateway.solo.io"]
resources: ["proxies","virtualservices"] resources: ["proxies","virtualservices"]
@ -83,6 +118,11 @@ rules:
resources: ["routegroups/status"] resources: ["routegroups/status"]
verbs: ["patch","update"] verbs: ["patch","update"]
{{- end }} {{- end }}
{{- if has "f5-virtualserver" .Values.sources }}
- apiGroups: ["cis.f5.com"]
resources: ["virtualservers"]
verbs: ["get","watch","list"]
{{- end }}
{{- with .Values.rbac.additionalPermissions }} {{- with .Values.rbac.additionalPermissions }}
{{- toYaml . | nindent 2 }} {{- toYaml . | nindent 2 }}
{{- end }} {{- end }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,6 +21,7 @@ import (
"errors" "errors"
"math" "math"
"reflect" "reflect"
"sort"
"testing" "testing"
"time" "time"
@ -83,32 +84,20 @@ func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
// ApplyChanges validates that the passed in changes satisfy the assumptions. // ApplyChanges validates that the passed in changes satisfy the assumptions.
func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
if len(changes.Create) != len(p.ExpectChanges.Create) { if err := verifyEndpoints(changes.Create, p.ExpectChanges.Create); err != nil {
return errors.New("number of created records is wrong") return err
} }
for i := range changes.Create { if err := verifyEndpoints(changes.UpdateNew, p.ExpectChanges.UpdateNew); err != nil {
if changes.Create[i].DNSName != p.ExpectChanges.Create[i].DNSName || !changes.Create[i].Targets.Same(p.ExpectChanges.Create[i].Targets) { return err
return errors.New("created record is wrong")
}
} }
for i := range changes.UpdateNew { if err := verifyEndpoints(changes.UpdateOld, p.ExpectChanges.UpdateOld); err != nil {
if changes.UpdateNew[i].DNSName != p.ExpectChanges.UpdateNew[i].DNSName || !changes.UpdateNew[i].Targets.Same(p.ExpectChanges.UpdateNew[i].Targets) { return err
return errors.New("delete record is wrong")
}
} }
for i := range changes.UpdateOld { if err := verifyEndpoints(changes.Delete, p.ExpectChanges.Delete); err != nil {
if changes.UpdateOld[i].DNSName != p.ExpectChanges.UpdateOld[i].DNSName || !changes.UpdateOld[i].Targets.Same(p.ExpectChanges.UpdateOld[i].Targets) { return err
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 !reflect.DeepEqual(ctx.Value(provider.RecordsContextKey), p.RecordsStore) { 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 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. // newMockProvider creates a new mockProvider returning the given endpoints and validating the desired changes.
func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) provider.Provider { func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) provider.Provider {
dnsProvider := &mockProvider{ dnsProvider := &mockProvider{
@ -132,7 +136,7 @@ func TestRunOnce(t *testing.T) {
// Fake some desired endpoints coming from our source. // Fake some desired endpoints coming from our source.
source := new(testutils.MockSource) source := new(testutils.MockSource)
cfg := externaldns.NewConfig() 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{ source.On("Endpoints").Return([]*endpoint.Endpoint{
{ {
DNSName: "create-record", DNSName: "create-record",
@ -144,6 +148,16 @@ func TestRunOnce(t *testing.T) {
RecordType: endpoint.RecordTypeA, RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.4.4"}, 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) }, nil)
// Fake some existing records in our DNS provider and validate some desired changes. // 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, RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"4.3.2.1"}, 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{ &plan.Changes{
Create: []*endpoint.Endpoint{ 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"}}, {DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
}, },
UpdateNew: []*endpoint.Endpoint{ 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"}}, {DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}},
}, },
UpdateOld: []*endpoint.Endpoint{ 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"}}, {DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}},
}, },
Delete: []*endpoint.Endpoint{ 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"}}, {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) source.AssertExpectations(t)
// check the verified records // check the verified records
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords)) assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedAAAARecords))
} }
func valueFromMetric(metric prometheus.Gauge) uint64 { 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) { func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter endpoint.DomainFilterInterface, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
t.Helper() t.Helper()
cfg := externaldns.NewConfig() cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME} cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
source := new(testutils.MockSource) source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil) 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(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) { 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 recordType: A
targets: targets:
- 192.168.99.216 - 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: Here is the full list of available metrics provided by ExternalDNS:
| Name | Description | Type | | Name | Description | Type |
| --------------------------------------------------- | ------------------------------------------------------- | ------- | | --------------------------------------------------- | ------------------------------------------------------------------ | ------- |
| external_dns_controller_last_sync_timestamp_seconds | Timestamp of last successful sync with the DNS provider | Gauge | | 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_endpoints_total | Number of Endpoints in all sources | Gauge |
| external_dns_registry_errors_total | Number of Registry errors | Counter | | 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_endpoints_total | Number of Endpoints in the registry | Gauge |
| external_dns_source_errors_total | Number of Source errors | Counter | | 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 | | external_dns_controller_verified_aaaa_records | Number of DNS AAAA-records that exists both in source and registry | Gauge |
| | source & registry | | | 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_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 | | 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? ### 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_SOURCE=$'service\ningress' \
-e EXTERNAL_DNS_PROVIDER=google \ -e EXTERNAL_DNS_PROVIDER=google \
-e EXTERNAL_DNS_DOMAIN_FILTER=$'foo.com\nbar.com' \ -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: ... 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 ### Running an internal and external dns service
Sometimes you need to run an internal and an 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 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.
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 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.
an instance of an ingress controller. Let's assume you have two ingress controllers `nginx-internal` and `nginx-external` Let's assume you have two ingress controllers, `internal` and `external`.
then you can start two ExternalDNS providers one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-internal)` You can then start two ExternalDNS providers, one with `--ingress-class=internal` and one with `--ingress-class=external`.
and one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)`.
If you need to search for multiple values of said annotation, you can provide a comma separated list, like so: If you need to search for multiple ingress classes, you can specify the flag multiple times, like so:
`--annotation-filter=kubernetes.io/ingress.class in (nginx-internal, alb-ingress-internal)`. `--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. The `--ingress-class` flag will check both the `spec.ingressClassName` field and the deprecated `kubernetes.io/ingress.class` annotation.
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 `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. **Backward compatibility**
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 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)`.
in `--label-filter` will be passed to the controller.
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? ### 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>). 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. 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 - name: external-dns
# You will need to check what the latest version is yourself: # You will need to check what the latest version is yourself:
# https://github.com/kubernetes-sigs/external-dns/releases # 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
# (optional) limit to only example.com domains; change to match the # (optional) limit to only example.com domains; change to match the
@ -114,7 +114,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
# (optional) limit to only example.com domains; change to match the # (optional) limit to only example.com domains; change to match the

View File

@ -57,7 +57,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # or ingress or both - --source=service # or ingress or both
- --provider=akamai - --provider=akamai
@ -143,7 +143,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # or ingress or both - --source=service # or ingress or both
- --provider=akamai - --provider=akamai

View File

@ -113,7 +113,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -187,7 +187,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -233,9 +233,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: foo name: foo
annotations:
kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller.
spec: spec:
ingressClassName: nginx # use the one that corresponds to your ingress controller.
rules: rules:
- host: foo.external-dns-test.com - host: foo.external-dns-test.com
http: 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 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 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 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 ## Deploy an example application
@ -80,7 +80,6 @@ kind: Ingress
metadata: metadata:
annotations: annotations:
alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/scheme: internet-facing
kubernetes.io/ingress.class: alb
name: echoserver name: echoserver
spec: spec:
ingressClassName: alb ingressClassName: alb
@ -120,7 +119,6 @@ metadata:
annotations: annotations:
alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/scheme: internet-facing
external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org
kubernetes.io/ingress.class: alb
name: echoserver name: echoserver
spec: spec:
ingressClassName: alb ingressClassName: alb
@ -159,7 +157,6 @@ metadata:
annotations: annotations:
alb.ingress.kubernetes.io/scheme: internet-facing alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/ip-address-type: dualstack alb.ingress.kubernetes.io/ip-address-type: dualstack
kubernetes.io/ingress.class: alb
name: echoserver name: echoserver
spec: spec:
ingressClassName: alb ingressClassName: alb

View File

@ -81,7 +81,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: env:
- name: AWS_REGION - name: AWS_REGION
value: us-east-1 # put your CloudMap NameSpace region value: us-east-1 # put your CloudMap NameSpace region
@ -148,7 +148,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: env:
- name: AWS_REGION - name: AWS_REGION
value: us-east-1 # put your CloudMap NameSpace region value: us-east-1 # put your CloudMap NameSpace region

View File

@ -413,7 +413,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -508,7 +508,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -739,9 +739,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: nginx name: nginx
annotations:
kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller.
spec: spec:
ingressClassName: nginx
rules: rules:
- host: server.example.com - host: server.example.com
http: 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 * `--source=ingress --source=service` - specify multiple times for multiple sources
* `--namespace=my-app` * `--namespace=my-app`
* `--label-filter=app in (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) * Limit services watched by type (not applicable to ingress or other types)
* `--service-type-filter=LoadBalancer` default `all` * `--service-type-filter=LoadBalancer` default `all`
* Limit the hosted zones considered * Limit the hosted zones considered
@ -962,7 +961,7 @@ A simple way to implement randomised startup is with an init container:
spec: spec:
initContainers: initContainers:
- name: init-jitter - 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: command:
- /bin/sh - /bin/sh
- -c - -c

View File

@ -130,7 +130,7 @@ spec:
spec: spec:
containers: containers:
- name: externaldns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -201,7 +201,7 @@ spec:
serviceAccountName: externaldns serviceAccountName: externaldns
containers: containers:
- name: externaldns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -272,7 +272,7 @@ spec:
serviceAccountName: externaldns serviceAccountName: externaldns
containers: containers:
- name: externaldns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -416,9 +416,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: nginx name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec: spec:
ingressClassName: nginx
rules: rules:
- host: server.example.com - host: server.example.com
http: http:

View File

@ -356,7 +356,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -424,7 +424,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -495,7 +495,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -560,9 +560,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: nginx name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec: spec:
ingressClassName: nginx
rules: rules:
- host: server.example.com - host: server.example.com
http: http:
@ -649,3 +648,9 @@ resource group:
```bash ```bash
$ az group delete --name "MyDnsResourceGroup" $ 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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --log-level=debug - --log-level=debug
- --source=service - --source=service
@ -136,7 +136,7 @@ spec:
secretName: bluecatconfig secretName: bluecatconfig
containers: containers:
- name: external-dns - 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: volumeMounts:
- name: bluecatconfig - name: bluecatconfig
mountPath: "/etc/external-dns/" mountPath: "/etc/external-dns/"

View File

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

View File

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

View File

@ -59,7 +59,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone you create in DNSimple. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone you create in DNSimple. - --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: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=ingress - --source=ingress
- --txt-prefix=_d - --txt-prefix=_d

View File

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

View File

@ -27,7 +27,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --log-level=debug - --log-level=debug
- --source=service - --source=service

View File

@ -39,7 +39,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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: metadata:
name: external-dns name: external-dns
--- ---
apiVersion: rbac.authorization.k8s.io/v1beta1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:
name: external-dns name: external-dns
@ -71,7 +71,7 @@ rules:
resources: ["nodes"] resources: ["nodes"]
verbs: ["list","watch"] verbs: ["list","watch"]
--- ---
apiVersion: rbac.authorization.k8s.io/v1beta1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding kind: ClusterRoleBinding
metadata: metadata:
name: external-dns-viewer name: external-dns-viewer
@ -103,7 +103,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
# Add desired Gateway API Route sources. # Add desired Gateway API Route sources.
- --source=gateway-httproute - --source=gateway-httproute

View File

@ -319,7 +319,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress

View File

@ -22,7 +22,7 @@ spec:
containers: containers:
- name: external-dns - name: external-dns
# update this to the desired external-dns version # 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: args:
- --source=gloo-proxy - --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system) - --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system)
@ -90,7 +90,7 @@ spec:
containers: containers:
- name: external-dns - name: external-dns
# update this to the desired external-dns version # 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: args:
- --source=gloo-proxy - --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system) - --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system)

View File

@ -44,7 +44,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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: spec:
containers: containers:
- name: external-dns - 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: args:
- --log-level=debug - --log-level=debug
- --source=service - --source=service
@ -96,7 +96,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --log-level=debug - --log-level=debug
- --source=service - --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` First lets deploy a Kafka Stateful set, a simple example(a lot of stuff is missing) with a headless service called `ksvc`
```yaml ```yaml
apiVersion: apps/v1beta1 apiVersion: apps/v1
kind: StatefulSet kind: StatefulSet
metadata: metadata:
name: kafka name: kafka

View File

@ -69,7 +69,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains. - --domain-filter=example.com # (optional) limit to only example.com domains.
@ -150,7 +150,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains. - --domain-filter=example.com # (optional) limit to only example.com domains.

View File

@ -28,7 +28,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -98,7 +98,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress

View File

@ -22,7 +22,7 @@ spec:
containers: containers:
- name: external-dns - name: external-dns
# update this to the desired external-dns version # 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: args:
- --source=kong-tcpingress - --source=kong-tcpingress
- --provider=aws - --provider=aws
@ -86,7 +86,7 @@ spec:
containers: containers:
- name: external-dns - name: external-dns
# update this to the desired external-dns version # 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: args:
- --source=kong-tcpingress - --source=kong-tcpingress
- --provider=aws - --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 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
annotations:
kubernetes.io/ingress.class: skipper
name: echoserver name: echoserver
spec: spec:
ingressClassName: skipper ingressClassName: skipper
@ -181,7 +179,6 @@ kind: Ingress
metadata: metadata:
annotations: annotations:
external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org
kubernetes.io/ingress.class: skipper
name: echoserver name: echoserver
spec: spec:
ingressClassName: skipper ingressClassName: skipper
@ -218,7 +215,6 @@ kind: Ingress
metadata: metadata:
annotations: annotations:
alb.ingress.kubernetes.io/ip-address-type: dualstack alb.ingress.kubernetes.io/ip-address-type: dualstack
kubernetes.io/ingress.class: skipper
name: echoserver name: echoserver
spec: spec:
ingressClassName: skipper ingressClassName: skipper
@ -256,7 +252,6 @@ kind: Ingress
metadata: metadata:
annotations: annotations:
zalando.org/aws-load-balancer-type: nlb zalando.org/aws-load-balancer-type: nlb
kubernetes.io/ingress.class: skipper
name: echoserver name: echoserver
spec: spec:
ingressClassName: skipper ingressClassName: skipper

View File

@ -41,7 +41,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=ingress - --source=ingress
- --domain-filter=external-dns-test.gcp.zalan.do - --domain-filter=external-dns-test.gcp.zalan.do
@ -294,8 +294,6 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: nginx name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec: spec:
ingressClassName: nginx ingressClassName: nginx
rules: rules:
@ -570,7 +568,7 @@ spec:
- --google-project=zalando-external-dns-test - --google-project=zalando-external-dns-test
- --registry=txt - --registry=txt
- --txt-owner-id=my-identifier - --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 name: external-dns
securityContext: securityContext:
fsGroup: 65534 fsGroup: 65534
@ -595,8 +593,6 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: nginx name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec: spec:
ingressClassName: nginx ingressClassName: nginx
rules: rules:

View File

@ -3,8 +3,9 @@
This tutorial describes how to configure ExternalDNS to use the cluster nodes as source. 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. 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 node source adds an `A` record per each node `externalIP` (if not found, any IPv4 `internalIP` is used instead).
The TTL record can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation. 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) ## Manifest (for cluster without RBAC enabled)
@ -28,7 +29,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=node # will use nodes as source - --source=node # will use nodes as source
- --provider=aws - --provider=aws
@ -99,7 +100,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=node # will use nodes as source - --source=node # will use nodes as source
- --provider=aws - --provider=aws

View File

@ -61,7 +61,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=openshift-route - --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 - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=openshift-route - --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 - --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 ## 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]. For more information about OCI DNS see the documentation [here][1].
## Deploy ExternalDNS ## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with. 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. 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 ```yaml
auth: auth:
@ -37,7 +46,29 @@ Create a secret using the config file above:
$ kubectl create secret generic external-dns-config --from-file=oci.yaml $ 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. Apply the following manifest to deploy ExternalDNS.
@ -93,7 +124,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress
@ -159,3 +190,6 @@ $ kubectl apply -f nginx.yaml
``` ```
[1]: https://docs.cloud.oracle.com/iaas/Content/DNS/Concepts/dnszonemanagement.htm [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: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 # serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # or ingress or both - --source=service # or ingress or both
- --provider=pdns - --provider=pdns

View File

@ -78,7 +78,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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 # If authentication is disabled and/or you didn't create
# a secret, you can remove this block. # a secret, you can remove this block.
envFrom: envFrom:

View File

@ -35,7 +35,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -105,7 +105,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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. 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 ```yaml
apiVersion: apps/v1beta2 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
labels: labels:
@ -241,9 +241,9 @@ spec:
- --provider=aws - --provider=aws
- --registry=txt - --registry=txt
- --txt-owner-id=external-dns - --txt-owner-id=external-dns
- --annotation-filter=kubernetes.io/ingress.class in (external-ingress) - --ingress-class=external-ingress
- --aws-zone-type=public - --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 name: external-dns-public
``` ```
@ -251,10 +251,10 @@ spec:
Consult [AWS ExternalDNS setup docs](aws.md) for installation guidelines. 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 ```yaml
apiVersion: apps/v1beta2 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
labels: labels:
@ -279,28 +279,27 @@ spec:
- --provider=aws - --provider=aws
- --registry=txt - --registry=txt
- --txt-owner-id=dev.k8s.nexus - --txt-owner-id=dev.k8s.nexus
- --annotation-filter=kubernetes.io/ingress.class in (internal-ingress) - --ingress-class=internal-ingress
- --aws-zone-type=private - --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 name: external-dns-private
``` ```
## Create application Service definitions ## 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 ```yaml
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
annotations:
kubernetes.io/ingress.class: "external-ingress"
labels: labels:
app: app app: app
name: app-public name: app-public
spec: spec:
ingressClassName: external-ingress
rules: rules:
- host: app.domain.com - host: app.domain.com
http: http:
@ -313,18 +312,17 @@ spec:
pathType: Prefix pathType: Prefix
``` ```
Then create private Service definition: Then create a private Ingress definition:
```yaml ```yaml
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
annotations:
kubernetes.io/ingress.class: "internal-ingress"
labels: labels:
app: app app: app
name: app-private name: app-private
spec: spec:
ingressClassName: internal-ingress
rules: rules:
- host: app.domain.com - host: app.domain.com
http: http:
@ -347,12 +345,12 @@ metadata:
certmanager.k8s.io/acme-challenge-type: "dns01" certmanager.k8s.io/acme-challenge-type: "dns01"
certmanager.k8s.io/acme-dns01-provider: "route53" certmanager.k8s.io/acme-dns01-provider: "route53"
certmanager.k8s.io/cluster-issuer: "letsencrypt-production" certmanager.k8s.io/cluster-issuer: "letsencrypt-production"
kubernetes.io/ingress.class: "external-ingress"
kubernetes.io/tls-acme: "true" kubernetes.io/tls-acme: "true"
labels: labels:
app: app app: app
name: app-public name: app-public
spec: spec:
ingressClassName: "external-ingress"
rules: rules:
- host: app.domain.com - host: app.domain.com
http: http:
@ -375,12 +373,11 @@ And reuse the requested certificate in private Service definition:
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
annotations:
kubernetes.io/ingress.class: "internal-ingress"
labels: labels:
app: app app: app
name: app-private name: app-private
spec: spec:
ingressClassName: "internal-ingress"
rules: rules:
- host: app.domain.com - host: app.domain.com
http: http:

View File

@ -53,7 +53,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=ingress - --source=ingress
- --provider=rdns - --provider=rdns
@ -123,7 +123,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=ingress - --source=ingress
- --provider=rdns - --provider=rdns
@ -142,9 +142,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: nginx name: nginx
annotations:
kubernetes.io/ingress.class: "nginx"
spec: spec:
ingressClassName: nginx
rules: rules:
- host: nginx.lb.rancher.cloud - host: nginx.lb.rancher.cloud
http: http:

View File

@ -218,7 +218,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --registry=txt - --registry=txt
- --txt-prefix=external-dns- - --txt-prefix=external-dns-
@ -260,7 +260,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --registry=txt - --registry=txt
- --txt-prefix=external-dns- - --txt-prefix=external-dns-

View File

@ -53,7 +53,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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: spec:
containers: containers:
- name: external-dns - 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: args:
- ... # your arguments here - ... # your arguments here
securityContext: securityContext:

View File

@ -129,7 +129,7 @@ spec:
- --policy=sync # set `upsert-only` would prevent ExternalDNS from deleting any records - --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-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 - --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 imagePullPolicy: Always
name: external-dns name: external-dns
resources: {} resources: {}

View File

@ -36,7 +36,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains - --domain-filter=example.com # (optional) limit to only example.com domains
@ -107,7 +107,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains - --domain-filter=example.com # (optional) limit to only example.com domains

View File

@ -44,7 +44,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress # ingress is also possible - --source=ingress # ingress is also possible
@ -116,7 +116,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service - --source=service
- --source=ingress - --source=ingress

View File

@ -66,7 +66,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --provider=vinyldns - --provider=vinyldns
- --source=service - --source=service
@ -137,7 +137,7 @@ spec:
serviceAccountName: external-dns serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --provider=vinyldns - --provider=vinyldns
- --source=service - --source=service

View File

@ -42,7 +42,7 @@ spec:
spec: spec:
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 serviceAccountName: external-dns
containers: containers:
- name: external-dns - 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: args:
- --source=service # ingress is also possible - --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above. - --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 ( const (
// RecordTypeA is a RecordType enum value // RecordTypeA is a RecordType enum value
RecordTypeA = "A" RecordTypeA = "A"
// RecordTypeAAAA is a RecordType enum value
RecordTypeAAAA = "AAAA"
// RecordTypeCNAME is a RecordType enum value // RecordTypeCNAME is a RecordType enum value
RecordTypeCNAME = "CNAME" RecordTypeCNAME = "CNAME"
// RecordTypeTXT is a RecordType enum value // RecordTypeTXT is a RecordType enum value
@ -40,6 +42,8 @@ const (
RecordTypeNS = "NS" RecordTypeNS = "NS"
// RecordTypePTR is a RecordType enum value // RecordTypePTR is a RecordType enum value
RecordTypePTR = "PTR" RecordTypePTR = "PTR"
// RecordTypeMX is a RecordType enum value
RecordTypeMX = "MX"
) )
// TTL is a structure defining the TTL of a DNS record // TTL is a structure defining the TTL of a DNS record
@ -164,7 +168,7 @@ type Endpoint struct {
DNSName string `json:"dnsName,omitempty"` DNSName string `json:"dnsName,omitempty"`
// The targets the DNS record points to // The targets the DNS record points to
Targets Targets `json:"targets,omitempty"` 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"` 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') // 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"` SetIdentifier string `json:"setIdentifier,omitempty"`

View File

@ -17,6 +17,8 @@ limitations under the License.
package endpoint package endpoint
import ( import (
log "github.com/sirupsen/logrus"
"errors" "errors"
"fmt" "fmt"
"sort" "sort"
@ -41,6 +43,9 @@ const (
// DualstackLabelKey is the name of the label that identifies dualstack endpoints // DualstackLabelKey is the name of the label that identifies dualstack endpoints
DualstackLabelKey = "dualstack" 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 // Labels store metadata related to the endpoint
@ -55,7 +60,7 @@ func NewLabels() Labels {
// NewLabelsFromString constructs endpoints labels from a provided format string // NewLabelsFromString constructs endpoints labels from a provided format string
// if heritage set to another value is found then error is returned // 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 // 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{} endpointLabels := map[string]string{}
labelText = strings.Trim(labelText, "\"") // drop quotes labelText = strings.Trim(labelText, "\"") // drop quotes
tokens := strings.Split(labelText, ",") tokens := strings.Split(labelText, ",")
@ -85,9 +90,26 @@ func NewLabelsFromString(labelText string) (Labels, error) {
return endpointLabels, nil 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 // withQuotes adds additional quotes
func (l Labels) Serialize(withQuotes bool) string { func (l Labels) SerializePlain(withQuotes bool) string {
var tokens []string var tokens []string
tokens = append(tokens, fmt.Sprintf("heritage=%s", heritage)) tokens = append(tokens, fmt.Sprintf("heritage=%s", heritage))
var keys []string var keys []string
@ -104,3 +126,31 @@ func (l Labels) Serialize(withQuotes bool) string {
} }
return strings.Join(tokens, ",") 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 { type LabelsSuite struct {
suite.Suite suite.Suite
foo Labels aesKey []byte
fooAsText string foo Labels
fooAsTextWithQuotes string fooAsText string
barText string fooAsTextWithQuotes string
barTextAsMap Labels fooAsTextEncrypted string
noHeritageText string fooAsTextWithQuotesEncrypted string
wrongHeritageText string barText string
multipleHeritageText string // considered invalid barTextEncrypted string
barTextAsMap Labels
noHeritageText string
wrongHeritageText string
multipleHeritageText string // considered invalid
} }
func (suite *LabelsSuite) SetupTest() { func (suite *LabelsSuite) SetupTest() {
@ -40,48 +44,79 @@ func (suite *LabelsSuite) SetupTest() {
"owner": "foo-owner", "owner": "foo-owner",
"resource": "foo-resource", "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.fooAsText = "heritage=external-dns,external-dns/owner=foo-owner,external-dns/resource=foo-resource"
suite.fooAsTextWithQuotes = fmt.Sprintf(`"%s"`, suite.fooAsText) 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{ suite.barTextAsMap = map[string]string{
"owner": "bar-owner", "owner": "bar-owner",
"resource": "bar-resource", "resource": "bar-resource",
"new-key": "bar-new-key", "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.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.noHeritageText = "external-dns/owner=random-owner"
suite.wrongHeritageText = "heritage=mate,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" suite.multipleHeritageText = "heritage=mate,heritage=external-dns,external-dns/owner=random-owner"
} }
func (suite *LabelsSuite) TestSerialize() { func (suite *LabelsSuite) TestSerialize() {
suite.Equal(suite.fooAsText, suite.foo.Serialize(false), "should serializeLabel") suite.Equal(suite.fooAsText, suite.foo.SerializePlain(false), "should serializeLabel")
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.Serialize(true), "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() { func (suite *LabelsSuite) TestDeserialize() {
foo, err := NewLabelsFromString(suite.fooAsText) foo, err := NewLabelsFromStringPlain(suite.fooAsText)
suite.NoError(err, "should succeed for valid label text") suite.NoError(err, "should succeed for valid label text")
suite.Equal(suite.foo, foo, "should reconstruct original label map") 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.NoError(err, "should succeed for valid label text")
suite.Equal(suite.foo, foo, "should reconstruct original label map") 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.NoError(err, "should succeed for valid label text")
suite.Equal(suite.barTextAsMap, bar, "should reconstruct original label map") 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.Equal(ErrInvalidHeritage, err, "should fail if no heritage is found")
suite.Nil(noHeritage, "should return nil") 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.Equal(ErrInvalidHeritage, err, "should fail if wrong heritage is found")
suite.Nil(wrongHeritage, "if error should return nil") 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.Equal(ErrInvalidHeritage, err, "should fail if multiple heritage is found")
suite.Nil(multipleHeritage, "if error should return nil") 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/onsi/ginkgo v1.16.5
github.com/openshift/api v0.0.0-20210315202829-4b79815405ec github.com/openshift/api v0.0.0-20210315202829-4b79815405ec
github.com/openshift/client-go v0.0.0-20210112165513-ebc401615f47 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/ovh/go-ovh v1.1.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pluralsh/gqlclient v1.1.6 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/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556 github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556
github.com/vultr/govultr/v2 v2.17.2 github.com/vultr/govultr/v2 v2.17.2
go.etcd.io/etcd/api/v3 v3.5.5 go.etcd.io/etcd/api/v3 v3.5.8
go.etcd.io/etcd/client/v3 v3.5.5 go.etcd.io/etcd/client/v3 v3.5.8
go.uber.org/ratelimit v0.2.0 go.uber.org/ratelimit v0.2.0
golang.org/x/net v0.7.0 golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.5.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-playground/universal-translator v0.18.0 // indirect
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // 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/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/gofrs/uuid v4.0.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.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/schollz/progressbar/v3 v3.8.6 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/smartystreets/gunit v1.3.4 // 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/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/objx v0.5.0 // indirect
github.com/terra-farm/udnssdk v1.3.5 // indirect github.com/terra-farm/udnssdk v1.3.5 // indirect
github.com/vektah/gqlparser/v2 v2.5.0 // 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.mongodb.org/mongo-driver v1.5.1 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
@ -176,7 +178,7 @@ require (
go.uber.org/zap v1.19.1 // indirect go.uber.org/zap v1.19.1 // indirect
golang.org/x/crypto v0.5.0 // indirect golang.org/x/crypto v0.5.0 // indirect
golang.org/x/mod v0.7.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/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // 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/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 h1:wZzojt99wtVIEHs8zNQzp1Xhqme5tD5NqMM1VLmG6xQ=
github.com/ans-group/sdk-go v1.10.4/go.mod h1:XSKXEDfKobnDtZoyia5DhJxxaDMcCjr76e1KJ9dU/xc= 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/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.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.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 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-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-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/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/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= 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.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.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.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.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/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= 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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= 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.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 h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 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= 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/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.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.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 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= 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= 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.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.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/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/v65 v65.35.0 h1:zvDsEuGs0qf6hPZVbrDnnfPJYQP7CwAgidTr4Pch6E4=
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/go.mod h1:MXMLMzHnnd9wlpgadPkdlkZ9YrwQmCOmbX5kjVEJodw=
github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk= 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/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= 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.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.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.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.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 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= 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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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 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.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.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/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/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/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.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/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.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 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/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.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 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.8 h1:Zf44zJszoU7zRV0X/nStPenegNXoFDWcB/MwrJbA+L4=
go.etcd.io/etcd/api/v3 v3.5.5/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8= go.etcd.io/etcd/api/v3 v3.5.8/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
go.etcd.io/etcd/client/pkg/v3 v3.5.5 h1:9S0JUVvmrVl7wCF39iTQthdaaNIiAaQbmK75ogO6GU8= go.etcd.io/etcd/client/pkg/v3 v3.5.8 h1:tPp9YRn/UBFAHdhOQUII9eUs7aOK35eulpMhX4YBd+M=
go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ= go.etcd.io/etcd/client/pkg/v3 v3.5.8/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
go.etcd.io/etcd/client/v3 v3.5.5 h1:q++2WTJbUgpQu4B6hCuT7VkdwaTP7Qz6Daak3WzbrlI= go.etcd.io/etcd/client/v3 v3.5.8 h1:B6ngTKZSWWowHEoaucOKHQR/AtZKaoHLiUpWxOLG4l4=
go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c= 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.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.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2/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.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 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.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.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 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/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.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 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 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 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= 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-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-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-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-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/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= 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-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-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-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-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-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.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.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/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.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.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= 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-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-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-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-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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 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-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-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-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 h1:ijGwO+0vL2hJt5gaygqP2j6PfflOBrRot0IczKbmtio=
google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= 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= 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.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.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.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.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 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= 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= 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.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.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.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.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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/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: images:
- name: registry.k8s.io/external-dns/external-dns - name: registry.k8s.io/external-dns/external-dns
newTag: v0.13.2 newTag: v0.13.5
resources: resources:
- ./external-dns-deployment.yaml - ./external-dns-deployment.yaml

20
main.go
View File

@ -18,6 +18,7 @@ package main
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
@ -113,6 +114,7 @@ func main() {
Namespace: cfg.Namespace, Namespace: cfg.Namespace,
AnnotationFilter: cfg.AnnotationFilter, AnnotationFilter: cfg.AnnotationFilter,
LabelFilter: labelSelector, LabelFilter: labelSelector,
IngressClassNames: cfg.IngressClassNames,
FQDNTemplate: cfg.FQDNTemplate, FQDNTemplate: cfg.FQDNTemplate,
CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation, CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation,
IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation, IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation,
@ -139,6 +141,8 @@ func main() {
RequestTimeout: cfg.RequestTimeout, RequestTimeout: cfg.RequestTimeout,
DefaultTargets: cfg.DefaultTargets, DefaultTargets: cfg.DefaultTargets,
OCPRouterName: cfg.OCPRouterName, OCPRouterName: cfg.OCPRouterName,
UpdateEvents: cfg.UpdateEvents,
ResolveLoadBalancerHostname: cfg.ResolveServiceLoadBalancerHostname,
} }
// Lookup all the selected sources by names and pass them the desired configuration. // Lookup all the selected sources by names and pass them the desired configuration.
@ -312,7 +316,19 @@ func main() {
) )
case "oci": case "oci":
var config *oci.OCIConfig 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 { if err == nil {
p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun) p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
} }
@ -367,7 +383,7 @@ func main() {
case "noop": case "noop":
r, err = registry.NewNoopRegistry(p) r, err = registry.NewNoopRegistry(p)
case "txt": case "txt":
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.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": case "aws-sd":
r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID) r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID)
default: default:

View File

@ -43,162 +43,168 @@ var Version = "unknown"
// Config is a project-wide configuration // Config is a project-wide configuration
type Config struct { type Config struct {
APIServerURL string APIServerURL string
KubeConfig string KubeConfig string
RequestTimeout time.Duration RequestTimeout time.Duration
DefaultTargets []string DefaultTargets []string
ContourLoadBalancerService string ContourLoadBalancerService string
GlooNamespace string GlooNamespace string
SkipperRouteGroupVersion string SkipperRouteGroupVersion string
Sources []string Sources []string
Namespace string Namespace string
AnnotationFilter string AnnotationFilter string
LabelFilter string LabelFilter string
FQDNTemplate string IngressClassNames []string
CombineFQDNAndAnnotation bool FQDNTemplate string
IgnoreHostnameAnnotation bool CombineFQDNAndAnnotation bool
IgnoreIngressTLSSpec bool IgnoreHostnameAnnotation bool
IgnoreIngressRulesSpec bool IgnoreIngressTLSSpec bool
GatewayNamespace string IgnoreIngressRulesSpec bool
GatewayLabelFilter string GatewayNamespace string
Compatibility string GatewayLabelFilter string
PublishInternal bool Compatibility string
PublishHostIP bool PublishInternal bool
AlwaysPublishNotReadyAddresses bool PublishHostIP bool
ConnectorSourceServer string AlwaysPublishNotReadyAddresses bool
Provider string ConnectorSourceServer string
GoogleProject string Provider string
GoogleBatchChangeSize int GoogleProject string
GoogleBatchChangeInterval time.Duration GoogleBatchChangeSize int
GoogleZoneVisibility string GoogleBatchChangeInterval time.Duration
DomainFilter []string GoogleZoneVisibility string
ExcludeDomains []string DomainFilter []string
RegexDomainFilter *regexp.Regexp ExcludeDomains []string
RegexDomainExclusion *regexp.Regexp RegexDomainFilter *regexp.Regexp
ZoneNameFilter []string RegexDomainExclusion *regexp.Regexp
ZoneIDFilter []string ZoneNameFilter []string
TargetNetFilter []string ZoneIDFilter []string
ExcludeTargetNets []string TargetNetFilter []string
AlibabaCloudConfigFile string ExcludeTargetNets []string
AlibabaCloudZoneType string AlibabaCloudConfigFile string
AWSZoneType string AlibabaCloudZoneType string
AWSZoneTagFilter []string AWSZoneType string
AWSAssumeRole string AWSZoneTagFilter []string
AWSAssumeRoleExternalID string AWSAssumeRole string
AWSBatchChangeSize int AWSAssumeRoleExternalID string
AWSBatchChangeInterval time.Duration AWSBatchChangeSize int
AWSEvaluateTargetHealth bool AWSBatchChangeInterval time.Duration
AWSAPIRetries int AWSEvaluateTargetHealth bool
AWSPreferCNAME bool AWSAPIRetries int
AWSZoneCacheDuration time.Duration AWSPreferCNAME bool
AWSSDServiceCleanup bool AWSZoneCacheDuration time.Duration
AzureConfigFile string AWSSDServiceCleanup bool
AzureResourceGroup string AzureConfigFile string
AzureSubscriptionID string AzureResourceGroup string
AzureUserAssignedIdentityClientID string AzureSubscriptionID string
BluecatDNSConfiguration string AzureUserAssignedIdentityClientID string
BluecatConfigFile string BluecatDNSConfiguration string
BluecatDNSView string BluecatConfigFile string
BluecatGatewayHost string BluecatDNSView string
BluecatRootZone string BluecatGatewayHost string
BluecatDNSServerName string BluecatRootZone string
BluecatDNSDeployType string BluecatDNSServerName string
BluecatSkipTLSVerify bool BluecatDNSDeployType string
CloudflareProxied bool BluecatSkipTLSVerify bool
CloudflareDNSRecordsPerPage int CloudflareProxied bool
CoreDNSPrefix string CloudflareDNSRecordsPerPage int
RcodezeroTXTEncrypt bool CoreDNSPrefix string
AkamaiServiceConsumerDomain string RcodezeroTXTEncrypt bool
AkamaiClientToken string AkamaiServiceConsumerDomain string
AkamaiClientSecret string AkamaiClientToken string
AkamaiAccessToken string AkamaiClientSecret string
AkamaiEdgercPath string AkamaiAccessToken string
AkamaiEdgercSection string AkamaiEdgercPath string
InfobloxGridHost string AkamaiEdgercSection string
InfobloxWapiPort int InfobloxGridHost string
InfobloxWapiUsername string InfobloxWapiPort int
InfobloxWapiPassword string `secure:"yes"` InfobloxWapiUsername string
InfobloxWapiVersion string InfobloxWapiPassword string `secure:"yes"`
InfobloxSSLVerify bool InfobloxWapiVersion string
InfobloxView string InfobloxSSLVerify bool
InfobloxMaxResults int InfobloxView string
InfobloxFQDNRegEx string InfobloxMaxResults int
InfobloxNameRegEx string InfobloxFQDNRegEx string
InfobloxCreatePTR bool InfobloxNameRegEx string
InfobloxCacheDuration int InfobloxCreatePTR bool
DynCustomerName string InfobloxCacheDuration int
DynUsername string DynCustomerName string
DynPassword string `secure:"yes"` DynUsername string
DynMinTTLSeconds int DynPassword string `secure:"yes"`
OCIConfigFile string DynMinTTLSeconds int
InMemoryZones []string OCIConfigFile string
OVHEndpoint string OCICompartmentOCID string
OVHApiRateLimit int OCIAuthInstancePrincipal bool
PDNSServer string InMemoryZones []string
PDNSAPIKey string `secure:"yes"` OVHEndpoint string
PDNSTLSEnabled bool OVHApiRateLimit int
TLSCA string PDNSServer string
TLSClientCert string PDNSAPIKey string `secure:"yes"`
TLSClientCertKey string PDNSTLSEnabled bool
Policy string TLSCA string
Registry string TLSClientCert string
TXTOwnerID string TLSClientCertKey string
TXTPrefix string Policy string
TXTSuffix string Registry string
Interval time.Duration TXTOwnerID string
MinEventSyncInterval time.Duration TXTPrefix string
Once bool TXTSuffix string
DryRun bool TXTEncryptEnabled bool
UpdateEvents bool TXTEncryptAESKey string
LogFormat string Interval time.Duration
MetricsAddress string MinEventSyncInterval time.Duration
LogLevel string Once bool
TXTCacheInterval time.Duration DryRun bool
TXTWildcardReplacement string UpdateEvents bool
ExoscaleEndpoint string LogFormat string
ExoscaleAPIKey string `secure:"yes"` MetricsAddress string
ExoscaleAPISecret string `secure:"yes"` LogLevel string
CRDSourceAPIVersion string TXTCacheInterval time.Duration
CRDSourceKind string TXTWildcardReplacement string
ServiceTypeFilter []string ExoscaleEndpoint string
CFAPIEndpoint string ExoscaleAPIKey string `secure:"yes"`
CFUsername string ExoscaleAPISecret string `secure:"yes"`
CFPassword string CRDSourceAPIVersion string
RFC2136Host string CRDSourceKind string
RFC2136Port int ServiceTypeFilter []string
RFC2136Zone string CFAPIEndpoint string
RFC2136Insecure bool CFUsername string
RFC2136GSSTSIG bool CFPassword string
RFC2136KerberosRealm string ResolveServiceLoadBalancerHostname bool
RFC2136KerberosUsername string RFC2136Host string
RFC2136KerberosPassword string `secure:"yes"` RFC2136Port int
RFC2136TSIGKeyName string RFC2136Zone string
RFC2136TSIGSecret string `secure:"yes"` RFC2136Insecure bool
RFC2136TSIGSecretAlg string RFC2136GSSTSIG bool
RFC2136TAXFR bool RFC2136KerberosRealm string
RFC2136MinTTL time.Duration RFC2136KerberosUsername string
RFC2136BatchChangeSize int RFC2136KerberosPassword string `secure:"yes"`
NS1Endpoint string RFC2136TSIGKeyName string
NS1IgnoreSSL bool RFC2136TSIGSecret string `secure:"yes"`
NS1MinTTLSeconds int RFC2136TSIGSecretAlg string
TransIPAccountName string RFC2136TAXFR bool
TransIPPrivateKeyFile string RFC2136MinTTL time.Duration
DigitalOceanAPIPageSize int RFC2136BatchChangeSize int
ManagedDNSRecordTypes []string NS1Endpoint string
GoDaddyAPIKey string `secure:"yes"` NS1IgnoreSSL bool
GoDaddySecretKey string `secure:"yes"` NS1MinTTLSeconds int
GoDaddyTTL int64 TransIPAccountName string
GoDaddyOTE bool TransIPPrivateKeyFile string
OCPRouterName string DigitalOceanAPIPageSize int
IBMCloudProxied bool ManagedDNSRecordTypes []string
IBMCloudConfigFile string GoDaddyAPIKey string `secure:"yes"`
TencentCloudConfigFile string GoDaddySecretKey string `secure:"yes"`
TencentCloudZoneType string GoDaddyTTL int64
PiholeServer string GoDaddyOTE bool
PiholePassword string `secure:"yes"` OCPRouterName string
PiholeTLSInsecureSkipVerify bool IBMCloudProxied bool
PluralCluster string IBMCloudConfigFile string
PluralProvider string TencentCloudConfigFile string
TencentCloudZoneType string
PiholeServer string
PiholePassword string `secure:"yes"`
PiholeTLSInsecureSkipVerify bool
PluralCluster string
PluralProvider string
} }
var defaultConfig = &Config{ var defaultConfig = &Config{
@ -213,6 +219,7 @@ var defaultConfig = &Config{
Namespace: "", Namespace: "",
AnnotationFilter: "", AnnotationFilter: "",
LabelFilter: labels.Everything().String(), LabelFilter: labels.Everything().String(),
IngressClassNames: nil,
FQDNTemplate: "", FQDNTemplate: "",
CombineFQDNAndAnnotation: false, CombineFQDNAndAnnotation: false,
IgnoreHostnameAnnotation: false, IgnoreHostnameAnnotation: false,
@ -292,6 +299,8 @@ var defaultConfig = &Config{
TXTCacheInterval: 0, TXTCacheInterval: 0,
TXTWildcardReplacement: "", TXTWildcardReplacement: "",
MinEventSyncInterval: 5 * time.Second, MinEventSyncInterval: 5 * time.Second,
TXTEncryptEnabled: false,
TXTEncryptAESKey: "",
Interval: time.Minute, Interval: time.Minute,
Once: false, Once: false,
DryRun: false, DryRun: false,
@ -327,7 +336,7 @@ var defaultConfig = &Config{
TransIPAccountName: "", TransIPAccountName: "",
TransIPPrivateKeyFile: "", TransIPPrivateKeyFile: "",
DigitalOceanAPIPageSize: 50, DigitalOceanAPIPageSize: 50,
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
GoDaddyAPIKey: "", GoDaddyAPIKey: "",
GoDaddySecretKey: "", GoDaddySecretKey: "",
GoDaddyTTL: 600, 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("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("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("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 // 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) 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("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("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("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("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("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) 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-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("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("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("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("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) 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-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("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-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("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("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) 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-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-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-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 // Flags related to the main control loop
app.Flag("txt-cache-interval", "The interval between cache synchronizations in duration format (default: disabled)").Default(defaultConfig.TXTCacheInterval.String()).DurationVar(&cfg.TXTCacheInterval) app.Flag("txt-cache-interval", "The interval between cache synchronizations in duration format (default: disabled)").Default(defaultConfig.TXTCacheInterval.String()).DurationVar(&cfg.TXTCacheInterval)

View File

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

View File

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

View File

@ -35,6 +35,9 @@ type PlanTestSuite struct {
fooV2CnameNoLabel *endpoint.Endpoint fooV2CnameNoLabel *endpoint.Endpoint
fooV3CnameSameResource *endpoint.Endpoint fooV3CnameSameResource *endpoint.Endpoint
fooA5 *endpoint.Endpoint fooA5 *endpoint.Endpoint
fooAAAA *endpoint.Endpoint
dsA *endpoint.Endpoint
dsAAAA *endpoint.Endpoint
bar127A *endpoint.Endpoint bar127A *endpoint.Endpoint
bar127AWithTTL *endpoint.Endpoint bar127AWithTTL *endpoint.Endpoint
bar127AWithProviderSpecificTrue *endpoint.Endpoint bar127AWithProviderSpecificTrue *endpoint.Endpoint
@ -106,6 +109,30 @@ func (suite *PlanTestSuite) SetupTest() {
endpoint.ResourceLabelKey: "ingress/default/foo-5", 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{ suite.bar127A = &endpoint.Endpoint{
DNSName: "bar", DNSName: "bar",
Targets: endpoint.Targets{"127.0.0.1"}, Targets: endpoint.Targets{"127.0.0.1"},
@ -438,9 +465,9 @@ func (suite *PlanTestSuite) TestIdempotency() {
func (suite *PlanTestSuite) TestDifferentTypes() { func (suite *PlanTestSuite) TestDifferentTypes() {
current := []*endpoint.Endpoint{suite.fooV1Cname} current := []*endpoint.Endpoint{suite.fooV1Cname}
desired := []*endpoint.Endpoint{suite.fooV2Cname, suite.fooA5} desired := []*endpoint.Endpoint{suite.fooV2Cname, suite.fooA5}
expectedCreate := []*endpoint.Endpoint{} expectedCreate := []*endpoint.Endpoint{suite.fooA5}
expectedUpdateOld := []*endpoint.Endpoint{suite.fooV1Cname} expectedUpdateOld := []*endpoint.Endpoint{suite.fooV1Cname}
expectedUpdateNew := []*endpoint.Endpoint{suite.fooA5} expectedUpdateNew := []*endpoint.Endpoint{suite.fooV2Cname}
expectedDelete := []*endpoint.Endpoint{} expectedDelete := []*endpoint.Endpoint{}
p := &Plan{ p := &Plan{
@ -544,52 +571,6 @@ func (suite *PlanTestSuite) TestRemoveEndpointWithUpsert() {
validateEntries(suite.T(), changes.Delete, expectedDelete) 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() { func (suite *PlanTestSuite) TestMultipleRecordsSameNameDifferentSetIdentifier() {
current := []*endpoint.Endpoint{suite.multiple1} current := []*endpoint.Endpoint{suite.multiple1}
desired := []*endpoint.Endpoint{suite.multiple2, suite.multiple3} desired := []*endpoint.Endpoint{suite.multiple2, suite.multiple3}
@ -695,6 +676,39 @@ func (suite *PlanTestSuite) TestMissing() {
validateEntries(suite.T(), changes.Create, expectedCreate) 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) { func TestPlan(t *testing.T) {
suite.Run(t, new(PlanTestSuite)) suite.Run(t, new(PlanTestSuite))
} }

View File

@ -86,6 +86,7 @@ var canonicalHostedZones = map[string]string{
"eu-west-3.elb.amazonaws.com": "Z3Q77PNBQS71R4", "eu-west-3.elb.amazonaws.com": "Z3Q77PNBQS71R4",
"eu-north-1.elb.amazonaws.com": "Z23TAZ6LKFMNIO", "eu-north-1.elb.amazonaws.com": "Z23TAZ6LKFMNIO",
"eu-south-1.elb.amazonaws.com": "Z3ULH7SSC9OV64", "eu-south-1.elb.amazonaws.com": "Z3ULH7SSC9OV64",
"eu-south-2.elb.amazonaws.com": "Z0956581394HF5D5LXGAP",
"sa-east-1.elb.amazonaws.com": "Z2P70J7HTTTPLU", "sa-east-1.elb.amazonaws.com": "Z2P70J7HTTTPLU",
"cn-north-1.elb.amazonaws.com.cn": "Z1GDH35T77C1KE", "cn-north-1.elb.amazonaws.com.cn": "Z1GDH35T77C1KE",
"cn-northwest-1.elb.amazonaws.com.cn": "ZM7IZAIOVVDZF", "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-west-3.amazonaws.com": "Z1CMS0P5QUZ6D5",
"elb.eu-north-1.amazonaws.com": "Z1UDT6IFJ4EJM", "elb.eu-north-1.amazonaws.com": "Z1UDT6IFJ4EJM",
"elb.eu-south-1.amazonaws.com": "Z23146JA1KNAFP", "elb.eu-south-1.amazonaws.com": "Z23146JA1KNAFP",
"elb.eu-south-2.amazonaws.com": "Z1011216NVTVYADP1SSV",
"elb.sa-east-1.amazonaws.com": "ZTK26PT1VY4CU", "elb.sa-east-1.amazonaws.com": "ZTK26PT1VY4CU",
"elb.cn-north-1.amazonaws.com.cn": "Z3QFB96KMJ7ED6", "elb.cn-north-1.amazonaws.com.cn": "Z3QFB96KMJ7ED6",
"elb.cn-northwest-1.amazonaws.com.cn": "ZQEIKTCZ8352D", "elb.cn-northwest-1.amazonaws.com.cn": "ZQEIKTCZ8352D",
@ -373,7 +375,7 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*route53.Hos
for _, r := range resp.ResourceRecordSets { for _, r := range resp.ResourceRecordSets {
newEndpoints := make([]*endpoint.Endpoint, 0) newEndpoints := make([]*endpoint.Endpoint, 0)
if !provider.SupportedRecordType(aws.StringValue(r.Type)) { if !p.SupportedRecordType(aws.StringValue(r.Type)) {
continue continue
} }
@ -1059,3 +1061,12 @@ func canonicalHostedZone(hostname string) string {
func cleanZoneID(id string) string { func cleanZoneID(id string) string {
return strings.TrimPrefix(id, "/hostedzone/") 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 // convert ownerID string to service description format
label := endpoint.NewLabels() label := endpoint.NewLabels()
label[endpoint.OwnerLabelKey] = p.ownerID 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]) { if strings.HasPrefix(aws.StringValue(service.Description), label[endpoint.AWSSDDescriptionLabel]) {
log.Infof("Deleting service \"%s\"", *service.Name) 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. 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 package azure
import ( import (
@ -109,7 +109,7 @@ func (p *AzureProvider) Records(ctx context.Context) (endpoints []*endpoint.Endp
return true return true
} }
recordType := strings.TrimPrefix(*recordSet.Type, "Microsoft.Network/dnszones/") recordType := strings.TrimPrefix(*recordSet.Type, "Microsoft.Network/dnszones/")
if !provider.SupportedRecordType(recordType) { if !p.SupportedRecordType(recordType) {
return true return true
} }
name := formatAzureDNSName(*recordSet.Name, *zone.Name) name := formatAzureDNSName(*recordSet.Name, *zone.Name)
@ -190,6 +190,15 @@ func (p *AzureProvider) zones(ctx context.Context) ([]dns.Zone, error) {
return zones, nil 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 { 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) 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) mapChange(deleted, change)
} }
for _, change := range changes.UpdateOld {
mapChange(deleted, change)
}
for _, change := range changes.Create { for _, change := range changes.Create {
mapChange(updated, change) mapChange(updated, change)
} }
@ -377,6 +382,21 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet
}, },
}, },
}, nil }, 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: case dns.TXT:
return dns.RecordSet{ return dns.RecordSet{
RecordSetProperties: &dns.RecordSetProperties{ RecordSetProperties: &dns.RecordSetProperties{
@ -425,6 +445,16 @@ func extractAzureTargets(recordSet *dns.RecordSet) []string {
return []string{*cnameRecord.Cname} 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 // Check for TXT records
txtRecords := properties.TxtRecords txtRecords := properties.TxtRecords
if txtRecords != nil && len(*txtRecords) > 0 && (*txtRecords)[0].Value != nil { 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. 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 package azure
import ( import (
@ -237,10 +237,6 @@ func (p *AzurePrivateDNSProvider) mapChanges(zones []privatedns.PrivateZone, cha
mapChange(deleted, change) mapChange(deleted, change)
} }
for _, change := range changes.UpdateOld {
mapChange(deleted, change)
}
for _, change := range changes.Create { for _, change := range changes.Create {
mapChange(updated, change) mapChange(updated, change)
} }
@ -367,6 +363,21 @@ func (p *AzurePrivateDNSProvider) newRecordSet(endpoint *endpoint.Endpoint) (pri
}, },
}, },
}, nil }, 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: case privatedns.TXT:
return privatedns.RecordSet{ return privatedns.RecordSet{
RecordSetProperties: &privatedns.RecordSetProperties{ RecordSetProperties: &privatedns.RecordSetProperties{
@ -407,6 +418,16 @@ func extractAzurePrivateDNSTargets(recordSet *privatedns.RecordSet) []string {
return []string{*cnameRecord.Cname} 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 // Check for TXT records
txtRecords := properties.TxtRecords txtRecords := properties.TxtRecords
if txtRecords != nil && len(*txtRecords) > 0 && (*txtRecords)[0].Value != nil { 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 { func privateTxtRecordSetPropertiesGetter(values []string, ttl int64) *privatedns.RecordSetProperties {
return &privatedns.RecordSetProperties{ return &privatedns.RecordSetProperties{
TTL: to.Int64Ptr(ttl), TTL: to.Int64Ptr(ttl),
@ -156,6 +167,8 @@ func createPrivateMockRecordSetMultiWithTTL(name, recordType string, ttl int64,
getterFunc = privateARecordSetPropertiesGetter getterFunc = privateARecordSetPropertiesGetter
case endpoint.RecordTypeCNAME: case endpoint.RecordTypeCNAME:
getterFunc = privateCNameRecordSetPropertiesGetter getterFunc = privateCNameRecordSetPropertiesGetter
case endpoint.RecordTypeMX:
getterFunc = privateMXRecordSetPropertiesGetter
case endpoint.RecordTypeTXT: case endpoint.RecordTypeTXT:
getterFunc = privateTxtRecordSetPropertiesGetter getterFunc = privateTxtRecordSetPropertiesGetter
default: default:
@ -266,6 +279,7 @@ func TestAzurePrivateDNSRecord(t *testing.T) {
createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeA, "123.123.123.123", 3600), createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeA, "123.123.123.123", 3600),
createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL), createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
createPrivateMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10), createPrivateMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
createPrivateMockRecordSetWithTTL("mail", endpoint.RecordTypeMX, "10 example.com", 4000),
}) })
if err != nil { if err != nil {
t.Fatal(err) 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.RecordTypeA, 3600, "123.123.123.123"),
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"), 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("hack.example.com", endpoint.RecordTypeCNAME, 10, "hack.azurewebsites.net"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, 4000, "10 example.com"),
} }
validateAzureEndpoints(t, actual, expected) 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"), 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("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
createPrivateMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10), createPrivateMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
createPrivateMockRecordSetMultiWithTTL("mail", endpoint.RecordTypeMX, 4000, "10 example.com", "20 backup.example.com"),
}) })
if err != nil { if err != nil {
t.Fatal(err) 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.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("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("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) validateAzureEndpoints(t, actual, expected)
@ -325,8 +342,6 @@ func TestAzurePrivateDNSApplyChanges(t *testing.T) {
testAzurePrivateDNSApplyChangesInternal(t, false, &recordsClient) testAzurePrivateDNSApplyChangesInternal(t, false, &recordsClient)
validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{ 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("deleted.example.com", endpoint.RecordTypeA, ""),
endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""), 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("other.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"), endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"), 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("other.com", endpoint.RecordTypeTXT, "tag"),
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"), endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"),
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"), 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{ currentRecords := []*endpoint.Endpoint{
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"), endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"),
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"), endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
endpoint.NewEndpoint("old.nope.com", endpoint.RecordTypeA, "121.212.121.212"), 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{ updatedRecords := []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"), endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"), endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeA, "222.111.222.111"), 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{ 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 { func txtRecordSetPropertiesGetter(values []string, ttl int64) *dns.RecordSetProperties {
return &dns.RecordSetProperties{ return &dns.RecordSetProperties{
TTL: to.Int64Ptr(ttl), TTL: to.Int64Ptr(ttl),
@ -155,6 +166,8 @@ func createMockRecordSetMultiWithTTL(name, recordType string, ttl int64, values
getterFunc = aRecordSetPropertiesGetter getterFunc = aRecordSetPropertiesGetter
case endpoint.RecordTypeCNAME: case endpoint.RecordTypeCNAME:
getterFunc = cNameRecordSetPropertiesGetter getterFunc = cNameRecordSetPropertiesGetter
case endpoint.RecordTypeMX:
getterFunc = mxRecordSetPropertiesGetter
case endpoint.RecordTypeTXT: case endpoint.RecordTypeTXT:
getterFunc = txtRecordSetPropertiesGetter getterFunc = txtRecordSetPropertiesGetter
default: default:
@ -271,6 +284,7 @@ func TestAzureRecord(t *testing.T) {
createMockRecordSetWithTTL("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("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
createMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10), createMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
createMockRecordSetMultiWithTTL("mail", endpoint.RecordTypeMX, 4000, "10 example.com"),
}) })
if err != nil { if err != nil {
t.Fatal(err) 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.RecordTypeA, 3600, "123.123.123.123"),
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"), 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("hack.example.com", endpoint.RecordTypeCNAME, 10, "hack.azurewebsites.net"),
endpoint.NewEndpointWithTTL("mail.example.com", endpoint.RecordTypeMX, 4000, "10 example.com"),
} }
validateAzureEndpoints(t, actual, expected) 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"), 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("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
createMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10), createMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
createMockRecordSetMultiWithTTL("mail", endpoint.RecordTypeMX, 4000, "10 example.com", "20 backup.example.com"),
}) })
if err != nil { if err != nil {
t.Fatal(err) 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.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("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("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) validateAzureEndpoints(t, actual, expected)
@ -332,8 +349,6 @@ func TestAzureApplyChanges(t *testing.T) {
testAzureApplyChangesInternal(t, false, &recordsClient) testAzureApplyChangesInternal(t, false, &recordsClient)
validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{ 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("deleted.example.com", endpoint.RecordTypeA, ""),
endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""), 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("other.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"), endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"), 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("other.com", endpoint.RecordTypeTXT, "tag"),
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"), endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"),
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"), 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{ currentRecords := []*endpoint.Endpoint{
endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"), endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"),
endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"), endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"),
endpoint.NewEndpoint("old.nope.com", endpoint.RecordTypeA, "121.212.121.212"), 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{ updatedRecords := []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"), endpoint.NewEndpointWithTTL("new.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"), endpoint.NewEndpointWithTTL("newcname.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeA, "222.111.222.111"), 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{ deleteRecords := []*endpoint.Endpoint{
@ -455,6 +477,7 @@ func TestAzureNameFilter(t *testing.T) {
createMockRecordSetWithTTL("test.nginx", endpoint.RecordTypeA, "123.123.123.123", 3600), createMockRecordSetWithTTL("test.nginx", endpoint.RecordTypeA, "123.123.123.123", 3600),
createMockRecordSetWithTTL("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("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), createMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
}) })
if err != nil { 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("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.RecordTypeA, 3600, "123.123.123.123"),
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"), 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) validateAzureEndpoints(t, actual, expected)
@ -481,8 +505,6 @@ func TestAzureApplyChangesZoneName(t *testing.T) {
testAzureApplyChangesInternalZoneName(t, false, &recordsClient) testAzureApplyChangesInternalZoneName(t, false, &recordsClient)
validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{ 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("deleted.foo.example.com", endpoint.RecordTypeA, ""),
endpoint.NewEndpoint("deletedcname.foo.example.com", endpoint.RecordTypeCNAME, ""), 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" "fmt"
"os" "os"
"strconv" "strconv"
"strings"
cloudflare "github.com/cloudflare/cloudflare-go" cloudflare "github.com/cloudflare/cloudflare-go"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -155,7 +156,15 @@ func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter prov
err error err error
) )
if os.Getenv("CF_API_TOKEN") != "" { 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 { } else {
config, err = cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL")) 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 { if err != nil {
t.Errorf("should not fail, %s", err) 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.Unsetenv("CF_API_TOKEN")
_ = os.Setenv("CF_API_KEY", "xxxxxxxxxxxxxxxxx") _ = os.Setenv("CF_API_KEY", "xxxxxxxxxxxxxxxxx")
_ = os.Setenv("CF_API_EMAIL", "test@test.com") _ = os.Setenv("CF_API_EMAIL", "test@test.com")
@ -689,6 +706,7 @@ func TestCloudflareProvider(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("should not fail, %s", err) t.Errorf("should not fail, %s", err)
} }
_ = os.Unsetenv("CF_API_KEY") _ = os.Unsetenv("CF_API_KEY")
_ = os.Unsetenv("CF_API_EMAIL") _ = os.Unsetenv("CF_API_EMAIL")
_, err = NewCloudFlareProvider( _, 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 { if recordSet.Type != endpoint.RecordTypeA && recordSet.Type != endpoint.RecordTypeTXT && recordSet.Type != endpoint.RecordTypeCNAME {
return nil return nil
} }
for _, record := range recordSet.Records {
ep := endpoint.NewEndpoint(recordSet.Name, recordSet.Type, record) ep := endpoint.NewEndpoint(recordSet.Name, recordSet.Type, recordSet.Records...)
ep.Labels[designateRecordSetID] = recordSet.ID ep.Labels[designateRecordSetID] = recordSet.ID
ep.Labels[designateZoneID] = recordSet.ZoneID ep.Labels[designateZoneID] = recordSet.ZoneID
ep.Labels[designateOriginalRecords] = strings.Join(recordSet.Records, "\000") ep.Labels[designateOriginalRecords] = strings.Join(recordSet.Records, "\000")
result = append(result, ep) result = append(result, ep)
}
return nil return nil
}, },
) )
@ -358,7 +358,7 @@ type recordSet struct {
} }
// adds endpoint into recordset aggregation, loading original values from endpoint labels first // 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) key := fmt.Sprintf("%s/%s", ep.DNSName, ep.RecordType)
rs := recordSets[key] rs := recordSets[key]
if rs == nil { if rs == nil {
@ -368,6 +368,9 @@ func addEndpoint(ep *endpoint.Endpoint, recordSets map[string]*recordSet, delete
names: make(map[string]bool), names: make(map[string]bool),
} }
} }
addDesignateIDLabelsFromExistingEndpoints(oldEndpoints, ep)
if rs.zoneID == "" { if rs.zoneID == "" {
rs.zoneID = ep.Labels[designateZoneID] rs.zoneID = ep.Labels[designateZoneID]
} }
@ -389,25 +392,55 @@ func addEndpoint(ep *endpoint.Endpoint, recordSets map[string]*recordSet, delete
recordSets[key] = rs 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. // ApplyChanges applies a given set of changes in a given zone.
func (p designateProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { func (p designateProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
managedZones, err := p.getZones() managedZones, err := p.getZones()
if err != nil { if err != nil {
return err return err
} }
endpoints, err := p.Records(ctx)
if err != nil {
return fmt.Errorf("failed to fetch active records: %w", err)
}
recordSets := map[string]*recordSet{} recordSets := map[string]*recordSet{}
for _, ep := range changes.Create { for _, ep := range changes.Create {
addEndpoint(ep, recordSets, false) addEndpoint(ep, recordSets, endpoints, false)
}
for _, ep := range changes.UpdateNew {
addEndpoint(ep, recordSets, false)
} }
for _, ep := range changes.UpdateOld { 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 { for _, ep := range changes.Delete {
addEndpoint(ep, recordSets, true) addEndpoint(ep, recordSets, endpoints, true)
} }
for _, rs := range recordSets { for _, rs := range recordSets {
if err2 := p.upsertRecordSet(rs, managedZones); err == nil { if err2 := p.upsertRecordSet(rs, managedZones); err == nil {
err = err2 err = err2

View File

@ -274,17 +274,7 @@ func TestDesignateRecords(t *testing.T) {
{ {
DNSName: "srv.test.net", DNSName: "srv.test.net",
RecordType: endpoint.RecordTypeA, RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"10.2.1.1"}, Targets: endpoint.Targets{"10.2.1.1", "10.2.1.2"},
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"},
Labels: map[string]string{ Labels: map[string]string{
designateRecordSetID: rs21ID, designateRecordSetID: rs21ID,
designateZoneID: zone2ID, designateZoneID: zone2ID,
@ -336,6 +326,19 @@ func testDesignateCreateRecords(t *testing.T, client *fakeDesignateClient) []*re
Status: "ACTIVE", 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{ endpoints := []*endpoint.Endpoint{
{ {
DNSName: "www.example.com", DNSName: "www.example.com",
@ -409,7 +412,7 @@ func testDesignateCreateRecords(t *testing.T, client *fakeDesignateClient) []*re
expectedCopy := make([]*recordsets.RecordSet, len(expected)) expectedCopy := make([]*recordsets.RecordSet, len(expected))
copy(expectedCopy, 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

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