Merge remote-tracking branch 'origin/master' into ingress-class-filtering

This commit is contained in:
Dave Salisbury 2021-12-28 14:27:30 +11:00
commit 097df5c458
43 changed files with 981 additions and 406 deletions

View File

@ -7,6 +7,7 @@ on:
jobs: jobs:
lint-test: lint-test:
if: github.repository == 'kubernetes-sigs/external-dns'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
@ -36,7 +37,7 @@ jobs:
fi fi
- name: Run chart-testing (lint) - name: Run chart-testing (lint)
run: ct lint run: ct lint --check-version-increment=false
- name: Create Kind cluster - name: Create Kind cluster
uses: helm/kind-action@v1.2.0 uses: helm/kind-action@v1.2.0

View File

@ -5,10 +5,11 @@ on:
branches: branches:
- master - master
paths: paths:
- "charts/external-dns/**" - "charts/external-dns/Chart.yaml"
jobs: jobs:
release: release:
if: github.repository == 'kubernetes-sigs/external-dns'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout

View File

@ -1,8 +1,6 @@
name: trivy vulnerability scanner name: trivy vulnerability scanner
on: on:
push: push:
branches:
- master
jobs: jobs:
build: build:
name: Build name: Build
@ -10,18 +8,10 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Build an image from Dockerfile - name: Build an image from Dockerfile
run: | run: |
make build.docker make build.docker
- name: Run trivy
- uses: cachix/install-nix-action@v13 run: |
with: ./scripts/run-trivy.sh
nix_path: nixpkgs=channel:nixos-unstable
- uses: workflow/nix-shell-action@v1
with:
packages: trivy
script: |
make build.docker
./scripts/run-trivy.sh

1
OWNERS
View File

@ -4,6 +4,7 @@
approvers: approvers:
- raffo - raffo
- njuettner - njuettner
- seanmalloy
reviewers: reviewers:
- njuettner - njuettner

View File

@ -110,6 +110,13 @@ The following table clarifies the current status of the providers according to t
| GoDaddy | Alpha | | | GoDaddy | Alpha | |
| Gandi | Alpha | @packi | | Gandi | Alpha | @packi |
## Kubernetes version compatibility
| ExternalDNS | <= 0.9.x | >= 0.10.0 |
| ------------------ | :----------------: | :----------------: |
| Kubernetes <= 1.18 | :white_check_mark: | :x: |
| Kubernetes >= 1.19 | :x: | :white_check_mark: |
## Running ExternalDNS: ## Running ExternalDNS:
The are two ways of running ExternalDNS: The are two ways of running ExternalDNS:

6
charts/OWNERS Normal file
View File

@ -0,0 +1,6 @@
labels:
- chart
approvers:
- stevehipwell
reviewers:
- stevehipwell

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.4.0 version: 1.7.0
appVersion: 0.10.1 appVersion: 0.10.2
keywords: keywords:
- kubernetes - kubernetes
- external-dns - external-dns
@ -17,5 +17,9 @@ maintainers:
email: steve.hipwell@gmail.com email: steve.hipwell@gmail.com
annotations: annotations:
artifacthub.io/changes: | artifacthub.io/changes: |
- kind: added
description: "Allow custom ClusterRole rules to be specified for sources without defaults."
- kind: changed - kind: changed
description: "Update image to v0.10.1" description: "Update ExternalDNS version to v0.10.2."
- kind: changed
description: "Set ClusterRole rules based more enabled sources."

View File

@ -21,7 +21,7 @@ helm upgrade --install external-dns/external-dns
The following table lists the configurable parameters of the _ExternalDNS_ chart and their default values. The following table lists the configurable parameters of the _ExternalDNS_ chart and their default values.
| Parameter | Description | Default | | Parameter | Description | Default |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | |-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|
| `image.repository` | Image repository. | `k8s.gcr.io/external-dns/external-dns` | | `image.repository` | Image repository. | `k8s.gcr.io/external-dns/external-dns` |
| `image.tag` | Image tag, will override the default tag derived from the chart app version. | `""` | | `image.tag` | Image tag, will override the default tag derived from the chart app version. | `""` |
| `image.pullPolicy` | Image pull policy. | `IfNotPresent` | | `image.pullPolicy` | Image pull policy. | `IfNotPresent` |
@ -32,6 +32,7 @@ The following table lists the configurable parameters of the _ExternalDNS_ chart
| `serviceAccount.annotations` | Annotations to add to the service account. | `{}` | | `serviceAccount.annotations` | Annotations to add to the service account. | `{}` |
| `serviceAccount.name` | Service account to be used. If not set and `serviceAccount.create` is `true`, a name is generated using the full name template. | `""` | | `serviceAccount.name` | Service account to be used. If not set and `serviceAccount.create` is `true`, a name is generated using the full name template. | `""` |
| `rbac.create` | If `true`, create the RBAC resources. | `true` | | `rbac.create` | If `true`, create the RBAC resources. | `true` |
| `rbac.additionalPermissions` | Additional permissions to be added to the cluster role. | `{}` |
| `podLabels` | Labels to add to the pod. | `{}` | | `podLabels` | Labels to add to the pod. | `{}` |
| `podAnnotations` | Annotations to add to the pod. | `{}` | | `podAnnotations` | Annotations to add to the pod. | `{}` |
| `podSecurityContext` | Security context for the pod, this supports the full [PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podsecuritycontext-v1-core) API. | _see values.yaml_ | | `podSecurityContext` | Security context for the pod, this supports the full [PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podsecuritycontext-v1-core) API. | _see values.yaml_ |
@ -45,6 +46,7 @@ The following table lists the configurable parameters of the _ExternalDNS_ chart
| `env` | [Environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for the _external-dns_ container, this supports the full [EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvar-v1-core) API including secrets and configmaps. | `[]` | | `env` | [Environment variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) for the _external-dns_ container, this supports the full [EnvVar](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#envvar-v1-core) API including secrets and configmaps. | `[]` |
| `livenessProbe` | [Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ | | `livenessProbe` | [Liveness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ |
| `readinessProbe` | [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ | | `readinessProbe` | [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) for the _external-dns_ container, this supports the full [Probe](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#probe-v1-core) API. | See _values.yaml_ |
| `service.annotations` | Annotations to add to the service. | `{}` |
| `service.port` | Port to expose via the service. | `7979` | | `service.port` | Port to expose via the service. | `7979` |
| `extraVolumes` | Additional volumes for the pod, this supports the full [VolumeDevice](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumedevice-v1-core) API. | `[]` | | `extraVolumes` | Additional volumes for the pod, this supports the full [VolumeDevice](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumedevice-v1-core) API. | `[]` |
| `extraVolumeMounts` | Additional volume mounts for the _external-dns_ container, this supports the full [VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumemount-v1-core) API. | `[]` | | `extraVolumeMounts` | Additional volume mounts for the _external-dns_ container, this supports the full [VolumeMount](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#volumemount-v1-core) API. | `[]` |

View File

@ -6,13 +6,98 @@ metadata:
labels: labels:
{{- include "external-dns.labels" . | nindent 4 }} {{- include "external-dns.labels" . | nindent 4 }}
rules: rules:
- apiGroups: [""] {{- 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) }}
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""] - apiGroups: [""]
resources: ["nodes"] resources: ["nodes"]
verbs: ["list","watch"] verbs: ["list","watch"]
{{- end }} {{- end }}
{{- if or (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","watch","list"]
{{- end }}
{{- if or (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "istio-gateway" .Values.sources) (has "istio-virtualservice" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
- apiGroups: [""]
resources: ["services","endpoints"]
verbs: ["get","watch","list"]
{{- end }}
{{- if or (has "ingress" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "istio-gateway" .Values.sources }}
- apiGroups: ["networking.istio.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "istio-virtualservice" .Values.sources }}
- apiGroups: ["networking.istio.io"]
resources: ["virtualservices"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "ambassador-host" .Values.sources }}
- apiGroups: ["getambassador.io"]
resources: ["hosts","ingresses"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "contour-httpproxy" .Values.sources }}
- apiGroups: ["projectcontour.io"]
resources: ["httpproxies"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "contour-ingressroute" .Values.sources }}
- apiGroups: ["contour.heptio.com"]
resources: ["ingressroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "crd" .Values.sources }}
- apiGroups: ["externaldns.k8s.io"]
resources: ["dnsendpoints"]
verbs: ["get","watch","list"]
- apiGroups: ["externaldns.k8s.io"]
resources: ["dnsendpoints/status"]
verbs: ["*"]
{{- end }}
{{- if has "gloo-proxy" .Values.sources }}
- apiGroups: ["gloo.solo.io","gateway.solo.io"]
resources: ["proxies","virtualservices"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "kong-tcpingress" .Values.sources }}
- apiGroups: ["configuration.konghq.com"]
resources: ["tcpingresses"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "openshift-route" .Values.sources }}
- apiGroups: ["route.openshift.io"]
resources: ["routes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "skipper-routegroup" .Values.sources }}
- apiGroups: ["zalando.org"]
resources: ["routegroups"]
verbs: ["get","watch","list"]
- apiGroups: ["zalando.org"]
resources: ["routegroups/status"]
verbs: ["patch","update"]
{{- end }}
{{- with .Values.rbac.additionalPermissions }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- end }}

View File

@ -4,6 +4,10 @@ metadata:
name: {{ include "external-dns.fullname" . }} name: {{ include "external-dns.fullname" . }}
labels: labels:
{{- include "external-dns.labels" . | nindent 4 }} {{- include "external-dns.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec: spec:
type: ClusterIP type: ClusterIP
selector: selector:

View File

@ -24,6 +24,7 @@ serviceAccount:
rbac: rbac:
# Specifies whether RBAC resources should be created # Specifies whether RBAC resources should be created
create: true create: true
additionalPermissions: {}
podLabels: {} podLabels: {}
@ -73,6 +74,7 @@ readinessProbe:
service: service:
port: 7979 port: 7979
annotations: {}
extraVolumes: [] extraVolumes: []

View File

@ -3,7 +3,7 @@ timeout: 5000s
options: options:
substitution_option: ALLOW_LOOSE substitution_option: ALLOW_LOOSE
steps: steps:
- name: "gcr.io/k8s-testimages/gcb-docker-gcloud:v20200824-5d057db" - name: "gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90"
entrypoint: make entrypoint: make
env: env:
- DOCKER_CLI_EXPERIMENTAL=enabled - DOCKER_CLI_EXPERIMENTAL=enabled

View File

@ -94,6 +94,30 @@ var (
Help: "Number of Source errors.", Help: "Number of Source errors.",
}, },
) )
registryARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "registry",
Name: "a_records",
Help: "Number of Registry A records.",
},
)
sourceARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "source",
Name: "a_records",
Help: "Number of Source A records.",
},
)
verifiedARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "controller",
Name: "verified_a_records",
Help: "Number of DNS A-records that exists both in source and registry.",
},
)
) )
func init() { func init() {
@ -105,6 +129,9 @@ func init() {
prometheus.MustRegister(deprecatedRegistryErrors) prometheus.MustRegister(deprecatedRegistryErrors)
prometheus.MustRegister(deprecatedSourceErrors) prometheus.MustRegister(deprecatedSourceErrors)
prometheus.MustRegister(controllerNoChangesTotal) prometheus.MustRegister(controllerNoChangesTotal)
prometheus.MustRegister(registryARecords)
prometheus.MustRegister(sourceARecords)
prometheus.MustRegister(verifiedARecords)
} }
// Controller is responsible for orchestrating the different components. // Controller is responsible for orchestrating the different components.
@ -141,7 +168,8 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return err return err
} }
registryEndpointsTotal.Set(float64(len(records))) registryEndpointsTotal.Set(float64(len(records)))
regARecords := filterARecords(records)
registryARecords.Set(float64(len(regARecords)))
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)
@ -151,7 +179,10 @@ 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)
sourceARecords.Set(float64(len(srcARecords)))
vRecords := fetchMatchingARecords(endpoints, records)
verifiedARecords.Set(float64(len(vRecords)))
endpoints = c.Registry.AdjustEndpoints(endpoints) endpoints = c.Registry.AdjustEndpoints(endpoints)
plan := &plan.Plan{ plan := &plan.Plan{
@ -181,6 +212,32 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return nil return nil
} }
// Checks and returns the intersection of A records in endpoint and registry.
func fetchMatchingARecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) []string {
aRecords := filterARecords(endpoints)
recordsMap := make(map[string]struct{})
for _, regRecord := range registryRecords {
recordsMap[regRecord.DNSName] = struct{}{}
}
var cm []string
for _, sourceRecord := range aRecords {
if _, found := recordsMap[sourceRecord]; found {
cm = append(cm, sourceRecord)
}
}
return cm
}
func filterARecords(endpoints []*endpoint.Endpoint) []string {
var aRecords []string
for _, endPoint := range endpoints {
if endPoint.RecordType == endpoint.RecordTypeA {
aRecords = append(aRecords, endPoint.DNSName)
}
}
return aRecords
}
// ScheduleRunOnce makes sure execution happens at most once per interval. // ScheduleRunOnce makes sure execution happens at most once per interval.
func (c *Controller) ScheduleRunOnce(now time.Time) { func (c *Controller) ScheduleRunOnce(now time.Time) {
c.nextRunAtMux.Lock() c.nextRunAtMux.Lock()

View File

@ -19,6 +19,8 @@ package controller
import ( import (
"context" "context"
"errors" "errors"
"github.com/prometheus/client_golang/prometheus"
"math"
"reflect" "reflect"
"testing" "testing"
"time" "time"
@ -49,6 +51,10 @@ type filteredMockProvider struct {
ApplyChangesCalls []*plan.Changes ApplyChangesCalls []*plan.Changes
} }
type errorMockProvider struct {
mockProvider
}
func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilterInterface { func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilterInterface {
return p.domainFilter return p.domainFilter
} }
@ -70,6 +76,10 @@ func (p *mockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error
return p.RecordsStore, nil return p.RecordsStore, nil
} }
func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
return nil, errors.New("error for testing")
}
// 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 len(changes.Create) != len(p.ExpectChanges.Create) {
@ -180,6 +190,13 @@ func TestRunOnce(t *testing.T) {
// Validate that the mock source was called. // Validate that the mock source was called.
source.AssertExpectations(t) source.AssertExpectations(t)
// check the verified records
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords))
}
func valueFromMetric(metric prometheus.Gauge) uint64 {
ref := reflect.ValueOf(metric)
return reflect.Indirect(ref).FieldByName("valBits").Uint()
} }
func TestShouldRunOnce(t *testing.T) { func TestShouldRunOnce(t *testing.T) {
@ -376,3 +393,127 @@ func TestWhenMultipleControllerConsidersAllFilteredComain(t *testing.T) {
}, },
) )
} }
func TestVerifyARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
[]*plan.Changes{},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords))
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"24.24.24.24"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"24.24.24.24"},
},
},
}},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords))
}
func TestARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
{
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.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
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.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
}},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceARecords))
assert.Equal(t, math.Float64bits(1), valueFromMetric(registryARecords))
}

View File

@ -0,0 +1,5 @@
# Helm Chart
## Chart Changes
When contributing chart changes please follow the same process as when contributing other content but also please **DON'T** modify _Chart.yaml_ in the PR as this would result in a chart release when merged and will mean that your PR will need modifying before it can be accepted. The chart version will be updated as part of the PR to release the chart.

View File

@ -185,6 +185,10 @@ Here is the full list of available metrics provided by ExternalDNS:
| 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 |
| | source & registry | |
| external_dns_registry_a_records | Number of A records in registry | 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?

View File

@ -24,10 +24,21 @@ You must be an official maintainer of the project to be able to do a release.
### Steps ### Steps
- Run `scripts/releaser.sh` to create a new GitHub release. - Run `scripts/releaser.sh` to create a new GitHub release. Alternatively you can create a release in the GitHub UI making sure to click on the autogenerate release node feature.
- The step above will trigger the Kubernetes based CI/CD system [Prow](https://prow.k8s.io/?repo=kubernetes-sigs%2Fexternal-dns). Verify that a new image was built and uploaded to `gcr.io/k8s-staging-external-dns/external-dns`. - The step above will trigger the Kubernetes based CI/CD system [Prow](https://prow.k8s.io/?repo=kubernetes-sigs%2Fexternal-dns). Verify that a new image was built and uploaded to `gcr.io/k8s-staging-external-dns/external-dns`.
- Create a PR in the [k8s.io repo](https://github.com/kubernetes/k8s.io) (see https://github.com/kubernetes/k8s.io/pull/540 for reference) by taking the current staging image using the sha256 digest. Once the PR is merged, the image will be live with the corresponding tag specified in the PR. - Create a PR in the [k8s.io repo](https://github.com/kubernetes/k8s.io) (see https://github.com/kubernetes/k8s.io/pull/540 for reference) by taking the current staging image using the sha256 digest. Once the PR is merged, the image will be live with the corresponding tag specified in the PR.
- Verify that the image is pullable with the given tag (i.e. `v0.7.5`). - Verify that the image is pullable with the given tag (i.e. `v0.7.5`).
- Branch out from the default branch and run `scripts/kustomize-version-udapter.sh` to update the image tag used in the kustomization.yaml. - Branch out from the default branch and run `scripts/kustomize-version-udapter.sh` to update the image tag used in the kustomization.yaml.
- Create an issue to release the corresponding Helm chart via the chart release process (below) assigned to a chart maintainer
- Create a PR with the kustomize change. - Create a PR with the kustomize change.
- Once the PR is merged, all is done :-) - Once the PR is merged, all is done :-)
## How to release a new chart version
The chart needs to be released in response to an ExternalDNS image release or on an as-needed basis; this should be triggered by an issue to release the chart.
### Steps
- Create a PR to update _Chart.yaml_ with the ExternalDNS version in `appVersion`, agreed on chart release version in `version` and `annotations` showing the changes
- Validate that the chart linting is successful
- Merge the PR to trigger a GitHub action to release the chart

View File

@ -83,14 +83,18 @@ metadata:
kubernetes.io/ingress.class: alb kubernetes.io/ingress.class: alb
name: echoserver name: echoserver
spec: spec:
ingressClassName: alb
rules: rules:
- host: echoserver.mycluster.example.org - host: echoserver.mycluster.example.org
http: &echoserver_root http: &echoserver_root
paths: paths:
- backend: - path: /
serviceName: echoserver backend:
servicePort: 80 service:
path: / name: echoserver
port:
number: 80
pathType: Prefix
- host: echoserver.example.org - host: echoserver.example.org
http: *echoserver_root http: *echoserver_root
``` ```
@ -119,13 +123,17 @@ metadata:
kubernetes.io/ingress.class: alb kubernetes.io/ingress.class: alb
name: echoserver name: echoserver
spec: spec:
ingressClassName: alb
rules: rules:
- http: - http:
paths: paths:
- backend: - path: /
serviceName: echoserver backend:
servicePort: 80 service:
path: / name: echoserver
port:
number: 80
pathType: Prefix
``` ```
In the above example we create a default path that works for any hostname, and In the above example we create a default path that works for any hostname, and
@ -154,14 +162,18 @@ metadata:
kubernetes.io/ingress.class: alb kubernetes.io/ingress.class: alb
name: echoserver name: echoserver
spec: spec:
ingressClassName: alb
rules: rules:
- host: echoserver.example.org - host: echoserver.example.org
http: http:
paths: paths:
- backend: - path: /
serviceName: echoserver backend:
servicePort: 80 service:
path: / name: echoserver
port:
number: 80
pathType: Prefix
``` ```
The above Ingress object will result in the creation of an ALB with a dualstack The above Ingress object will result in the creation of an ALB with a dualstack

View File

@ -55,7 +55,7 @@ If your EKS-managed cluster is >= 1.13 and was created after 2019-09-04, refer
to the [Amazon EKS to the [Amazon EKS
documentation](https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html) documentation](https://docs.aws.amazon.com/eks/latest/userguide/create-service-account-iam-policy-and-role.html)
for instructions on how to create the IAM Role. Otherwise, you will need to use for instructions on how to create the IAM Role. Otherwise, you will need to use
kiam or kube2iam. kiam or kube2iam or set the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY on the deployment.
### kiam ### kiam
@ -464,6 +464,58 @@ $ aws route53 delete-hosted-zone --id /hostedzone/ZEWFWZ4R16P7IB
## Throttling ## Throttling
Route53 has a [5 API requests per second per account hard quota](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-route-53). Route53 has a [5 API requests per second per account hard quota](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-route-53).
Running several fast polling ExternalDNS instances in a given account can easily hit that limit. Some ways to circumvent that issue includes: Running several fast polling ExternalDNS instances in a given account can easily hit that limit. Some ways to reduce the request rate include:
* Augment the synchronization interval (`--interval`), at the cost of slower changes propagation. * Reduce the polling loop's synchronization interval at the possible cost of slower change propagation (but see `--events` below to reduce the impact).
* If the ExternalDNS managed zones list doesn't change frequently, set `--aws-zones-cache-duration` (zones list cache time-to-live) to a larger value. Note that zones list cache can be disabled with `--aws-zones-cache-duration=0s`. * `--interval=5m` (default `1m`)
* Trigger the polling loop on changes to K8s objects, rather than only at `interval`, to have responsive updates with long poll intervals
* `--events`
* Limit the [sources watched](https://github.com/kubernetes-sigs/external-dns/blob/master/pkg/apis/externaldns/types.go#L364) when the `--events` flag is specified to specific types, namespaces, labels, or annotations
* `--source=ingress --source=service` - specify multiple times for multiple sources
* `--namespace=my-app`
* `--label-filter=app in (my-app)`
* `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)` - note that this filter would apply to services too..
* Limit services watched by type (not applicable to ingress or other types)
* `--service-type-filter=LoadBalancer` default `all`
* Limit the hosted zones considered
* `--zone-id-filter=ABCDEF12345678` - specify multiple times if needed
* `--domain-filter=example.com` by domain suffix - specify multiple times if needed
* `--regex-domain-filter=example*` by domain suffix but as a regex - overrides domain-filter
* `--exclude-domains=ignore.this.example.com` to exclude a domain or subdomain
* `--regex-domain-exclusion=ignore*` subtracts it's matches from `regex-domain-filter`'s matches
* `--aws-zone-type=public` only sync zones of this type `[public|private]`
* `--aws-zone-tags=owner=k8s` only sync zones with this tag
* If the list of zones managed by ExternalDNS doesn't change frequently, cache it by setting a TTL.
* `--aws-zones-cache-duration=3h` (default `0` - disabled)
* Increase the number of changes applied to Route53 in each batch
* `--aws-batch-change-size=4000` (default `1000`)
* Increase the interval between changes
* `--aws-batch-change-interval=10s` (default `1s`)
* Introducing some jitter to the pod initialization, so that when multiple instances of ExternalDNS are updated at the same time they do not make their requests on the same second.
A simple way to implement randomised startup is with an init container:
```
...
spec:
initContainers:
- name: init-jitter
image: k8s.gcr.io/external-dns/external-dns:v0.7.6
command:
- /bin/sh
- -c
- 'FOR=$((RANDOM % 10))s;echo "Sleeping for $FOR";sleep $FOR'
containers:
...
```
### EKS
An effective starting point for EKS with an ingress controller might look like:
```bash
--interval=5m
--events
--source=ingress
--domain-filter=example.com
--aws-zones-cache-duration=1h
```

View File

@ -20,6 +20,10 @@ BlueCat Gateway username and password can be supplied using the configuration fi
| rootZone | Yes | | rootZone | Yes |
| skipTLSVerify | No (default false) | | skipTLSVerify | No (default false) |
### HTTP proxy
BlueCat provider supports getting the proxy URL from the environment variables. The format is the one specified by golang's [http.ProxyFromEnvironment](https://pkg.go.dev/net/http#ProxyFromEnvironment).
## Deploy ## Deploy
Setup configuration file as k8s `Secret`. Setup configuration file as k8s `Secret`.
``` ```

View File

@ -145,14 +145,18 @@ metadata:
kubernetes.io/ingress.class: skipper kubernetes.io/ingress.class: skipper
name: echoserver name: echoserver
spec: spec:
ingressClassName: skipper
rules: rules:
- host: echoserver.mycluster.example.org - host: echoserver.mycluster.example.org
http: &echoserver_root http: &echoserver_root
paths: paths:
- backend: - path: /
serviceName: echoserver backend:
servicePort: 80 service:
path: / name: echoserver
port:
number: 80
pathType: Prefix
- host: echoserver.example.org - host: echoserver.example.org
http: *echoserver_root http: *echoserver_root
``` ```
@ -180,13 +184,17 @@ metadata:
kubernetes.io/ingress.class: skipper kubernetes.io/ingress.class: skipper
name: echoserver name: echoserver
spec: spec:
ingressClassName: skipper
rules: rules:
- http: - http:
paths: paths:
- backend: - path: /
serviceName: echoserver backend:
servicePort: 80 service:
path: / name: echoserver
port:
number: 80
pathType: Prefix
``` ```
In the above example we create a default path that works for any hostname, and In the above example we create a default path that works for any hostname, and
@ -213,14 +221,18 @@ metadata:
kubernetes.io/ingress.class: skipper kubernetes.io/ingress.class: skipper
name: echoserver name: echoserver
spec: spec:
ingressClassName: skipper
rules: rules:
- host: echoserver.example.org - host: echoserver.example.org
http: http:
paths: paths:
- backend: - path: /
serviceName: echoserver backend:
servicePort: 80 service:
path: / name: echoserver
port:
number: 80
pathType: Prefix
``` ```
The above Ingress object will result in the creation of an ALB with a dualstack The above Ingress object will result in the creation of an ALB with a dualstack
@ -247,14 +259,18 @@ metadata:
kubernetes.io/ingress.class: skipper kubernetes.io/ingress.class: skipper
name: echoserver name: echoserver
spec: spec:
ingressClassName: skipper
rules: rules:
- host: echoserver.example.org - host: echoserver.example.org
http: http:
paths: paths:
- backend: - path: /
serviceName: echoserver backend:
servicePort: 80 service:
path: / name: echoserver
port:
number: 80
pathType: Prefix
``` ```
The above Ingress object will result in the creation of an NLB. A The above Ingress object will result in the creation of an NLB. A

View File

@ -297,13 +297,18 @@ metadata:
annotations: annotations:
kubernetes.io/ingress.class: nginx kubernetes.io/ingress.class: nginx
spec: spec:
ingressClassName: nginx
rules: rules:
- host: via-ingress.external-dns-test.gcp.zalan.do - host: via-ingress.external-dns-test.gcp.zalan.do
http: http:
paths: paths:
- backend: - path: /
serviceName: nginx backend:
servicePort: 80 service:
name: nginx
port:
number: 80
pathType: Prefix
--- ---
@ -593,13 +598,18 @@ metadata:
annotations: annotations:
kubernetes.io/ingress.class: nginx kubernetes.io/ingress.class: nginx
spec: spec:
ingressClassName: nginx
rules: rules:
- host: via-ingress.external-dns-test.gcp.zalan.do - host: via-ingress.external-dns-test.gcp.zalan.do
http: http:
paths: paths:
- backend: - path: /
serviceName: nginx backend:
servicePort: 80 service:
name: nginx
port:
number: 80
pathType: Prefix
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service

View File

@ -2,6 +2,60 @@
This tutorial describes how to configure ExternalDNS to use the OpenShift Route source. This tutorial describes how to configure ExternalDNS to use the OpenShift Route source.
It is meant to supplement the other provider-specific setup tutorials. It is meant to supplement the other provider-specific setup tutorials.
### For OCP 4.x
In OCP 4.x, if you have multiple ingress controllers then you must specify an ingress controller name or a router name(you can get it from the route's Status.Ingress.RouterName field).
If you don't specify an ingress controller's or router name when you have multiple ingresscontrollers in your environment then the route gets populated with multiple entries of router canonical hostnames which causes external dns to create a CNAME record with multiple router canonical hostnames pointing to the route host which is a violation of RFC 1912 and is not allowed by Cloud Providers which leads to failure of record creation.
Once you specify the ingresscontroller or router name then that will be matched by the external-dns and the router canonical hostname corresponding to this routerName(which is present in route's Status.Ingress.RouterName field) is selected and a CNAME record of this route host pointing to this router canonical hostname is created.
Your externaldns CR shall be created as per the following example.
Replace names in the domain section and zone ID as per your environment.
This is example is for AWS environment.
```yaml
apiVersion: externaldns.olm.openshift.io/v1alpha1
kind: ExternalDNS
metadata:
name: sample1
spec:
domains:
- filterType: Include
matchType: Exact
names: apps.miheer.externaldns
provider:
type: AWS
source:
hostnameAnnotation: Allow
openshiftRouteOptions:
routerName: default
type: OpenShiftRoute
zones:
- Z05387772BD5723IZFRX3
```
This will create an externaldns pod with the following container args under spec in the external-dns namespace where `- --source=openshift-route` and `- --openshift-router-name=default` is added by the external-dns-operator.
```
spec:
containers:
- args:
- --domain-filter=apps.misalunk.externaldns
- --metrics-address=127.0.0.1:7979
- --txt-owner-id=external-dns-sample1
- --provider=aws
- --source=openshift-route
- --policy=sync
- --registry=txt
- --log-level=debug
- --zone-id-filter=Z05387772BD5723IZFRX3
- --openshift-router-name=default
- --txt-prefix=external-dns-
```
### For OCP 3.11 environment
### Prepare ROUTER_CANONICAL_HOSTNAME in default/router deployment ### Prepare ROUTER_CANONICAL_HOSTNAME in default/router deployment
Read and go through [Finding the Host Name of the Router](https://docs.openshift.com/container-platform/3.11/install_config/router/default_haproxy_router.html#finding-router-hostname). Read and go through [Finding the Host Name of the Router](https://docs.openshift.com/container-platform/3.11/install_config/router/default_haproxy_router.html#finding-router-hostname).
If no ROUTER_CANONICAL_HOSTNAME is set, you must annotate each route with external-dns.alpha.kubernetes.io/target! If no ROUTER_CANONICAL_HOSTNAME is set, you must annotate each route with external-dns.alpha.kubernetes.io/target!

8
go.mod
View File

@ -18,19 +18,19 @@ require (
github.com/aliyun/alibaba-cloud-sdk-go v1.61.357 github.com/aliyun/alibaba-cloud-sdk-go v1.61.357
github.com/aws/aws-sdk-go v1.40.53 github.com/aws/aws-sdk-go v1.40.53
github.com/bodgit/tsig v0.0.2 github.com/bodgit/tsig v0.0.2
github.com/cloudflare/cloudflare-go v0.13.2 github.com/cloudflare/cloudflare-go v0.25.0
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
github.com/datawire/ambassador v1.6.0 github.com/datawire/ambassador v1.6.0
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba
github.com/digitalocean/godo v1.69.1 github.com/digitalocean/godo v1.69.1
github.com/dnsimple/dnsimple-go v0.60.0 github.com/dnsimple/dnsimple-go v0.60.0
github.com/exoscale/egoscale v0.73.2 github.com/exoscale/egoscale v0.80.1
github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99 github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99
github.com/go-gandi/go-gandi v0.0.0-20200921091836-0d8a64b9cc09 github.com/go-gandi/go-gandi v0.0.0-20200921091836-0d8a64b9cc09
github.com/go-logr/logr v1.1.0 // indirect github.com/go-logr/logr v1.1.0 // indirect
github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f
github.com/google/go-cmp v0.5.6 github.com/google/go-cmp v0.5.6
github.com/gophercloud/gophercloud v0.21.0 github.com/gophercloud/gophercloud v0.22.0
github.com/hooklift/gowsdl v0.5.0 github.com/hooklift/gowsdl v0.5.0
github.com/infobloxopen/infoblox-go-client v1.1.1 github.com/infobloxopen/infoblox-go-client v1.1.1
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
@ -48,7 +48,7 @@ require (
github.com/oracle/oci-go-sdk v21.4.0+incompatible github.com/oracle/oci-go-sdk v21.4.0+incompatible
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/projectcontour/contour v1.18.1 github.com/projectcontour/contour v1.18.2
github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_golang v1.11.0
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1

26
go.sum
View File

@ -126,9 +126,9 @@ github.com/Raffo/knolog v0.0.0-20211016155154-e4d5e0cc970a/go.mod h1:AsBYmtPY5rg
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/Venafi/vcert/v4 v4.13.1/go.mod h1:Z3sJFoAurFNXPpoSUSHq46aIeHLiGQEMDhprfxlpofQ=
github.com/StackExchange/dnscontrol v0.2.8 h1:7jviqDH9cIqRSRpH0UxgmpT7a8CwEhs9mLHBhoYhXo8= github.com/StackExchange/dnscontrol v0.2.8 h1:7jviqDH9cIqRSRpH0UxgmpT7a8CwEhs9mLHBhoYhXo8=
github.com/StackExchange/dnscontrol v0.2.8/go.mod h1:BH+5nX50JxHDdb3+AD/z/UfYMCc7iaqEkRtQ+NjcFGE= github.com/StackExchange/dnscontrol v0.2.8/go.mod h1:BH+5nX50JxHDdb3+AD/z/UfYMCc7iaqEkRtQ+NjcFGE=
github.com/Venafi/vcert/v4 v4.13.1/go.mod h1:Z3sJFoAurFNXPpoSUSHq46aIeHLiGQEMDhprfxlpofQ=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
@ -212,8 +212,13 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.13.2 h1:bhMGoNhAg21DuqJjU9jQepRRft6vYfo6pejT3NN4V6A= github.com/cloudflare/cloudflare-go v0.10.1 h1:d2CL6F9k2O0Ux0w27LgogJ5UOzZRj6a/hDPFqPP68d8=
github.com/cloudflare/cloudflare-go v0.10.1/go.mod h1:C0Y6eWnTJPMK2ceuOxx2pjh78UUHihcXeTTHb8r7QjU=
github.com/cloudflare/cloudflare-go v0.13.2/go.mod h1:27kfc1apuifUmJhp069y0+hwlKDg4bd8LWlu7oKeZvM= github.com/cloudflare/cloudflare-go v0.13.2/go.mod h1:27kfc1apuifUmJhp069y0+hwlKDg4bd8LWlu7oKeZvM=
github.com/cloudflare/cloudflare-go v0.22.0 h1:3TYbkyz/IBbM6XozrTKWiNpv7RjJGamALy9yu2SBqTo=
github.com/cloudflare/cloudflare-go v0.22.0/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI=
github.com/cloudflare/cloudflare-go v0.25.0 h1:GwyKwGq8ciGNjKiTpjj6RvU3+uJNuPBNjlUkeQRx0yU=
github.com/cloudflare/cloudflare-go v0.25.0/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI=
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s= github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s=
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14= github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14=
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=
@ -328,8 +333,8 @@ github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exoscale/egoscale v0.73.2 h1:YGy0YufwuaRduaqTM6ptyZQpBxr2L9eA6kKfmGW8jKg= github.com/exoscale/egoscale v0.80.1 h1:+JR0RhGKjkgHIluWeRWTTnveSGIRE1ifbpnjB/Fkwlk=
github.com/exoscale/egoscale v0.73.2/go.mod h1:Fpy/cIVjiUkI0DntkoFl4fZpaeqRFQ6SVcX1A0APg0E= github.com/exoscale/egoscale v0.80.1/go.mod h1:wi0myUxPsV8SdEtdJHQJxFLL/wEw9fiw9Gs1PWRkvkM=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -571,8 +576,8 @@ github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/gookit/color v1.2.3/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gookit/color v1.2.3/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gophercloud/gophercloud v0.21.0 h1:21rxoQM7cSaZCPgfP45h71Vt1amZa942l7AtUWLOI2I= github.com/gophercloud/gophercloud v0.22.0 h1:9lFISNLafZcecT0xUveIMt3IafexC6DIV9ek1SZdSMw=
github.com/gophercloud/gophercloud v0.21.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= github.com/gophercloud/gophercloud v0.22.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -732,7 +737,6 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d h1:JV46OtdhH2vVt8mJ1EWUE94k99vbN9fZs1WQ8kcEapU=
github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d/go.mod h1:CHQ3o5KBH1PIS2Fb1mRLTIWO5YzP9kSUB3KoCICwlvA= github.com/kubermatic/glog-logrus v0.0.0-20180829085450-3fa5b9870d1d/go.mod h1:CHQ3o5KBH1PIS2Fb1mRLTIWO5YzP9kSUB3KoCICwlvA=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
@ -782,6 +786,7 @@ github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mN
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@ -858,6 +863,7 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -938,8 +944,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/projectcontour/contour v1.18.1 h1:J1MmqchDFuou066Gkr13eYtPaBnfb5j7h5ETH9OfIfQ= github.com/projectcontour/contour v1.18.2 h1:q16Q0f8mb14DATpCus/qBONawBy1Zn4vrvBj1bXh3hc=
github.com/projectcontour/contour v1.18.1/go.mod h1:prL5IyPK2ek6MUWwm8C5S1pHsWOE/2Y8Ym7ak3feVpo= github.com/projectcontour/contour v1.18.2/go.mod h1:prL5IyPK2ek6MUWwm8C5S1pHsWOE/2Y8Ym7ak3feVpo=
github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
@ -1084,6 +1090,7 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
@ -1277,6 +1284,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo=

View File

@ -3,7 +3,7 @@ kind: Kustomization
images: images:
- name: k8s.gcr.io/external-dns/external-dns - name: k8s.gcr.io/external-dns/external-dns
newTag: v0.10.0 newTag: v0.10.2
resources: resources:
- ./external-dns-deployment.yaml - ./external-dns-deployment.yaml

View File

@ -132,6 +132,7 @@ func main() {
SkipperRouteGroupVersion: cfg.SkipperRouteGroupVersion, SkipperRouteGroupVersion: cfg.SkipperRouteGroupVersion,
RequestTimeout: cfg.RequestTimeout, RequestTimeout: cfg.RequestTimeout,
DefaultTargets: cfg.DefaultTargets, DefaultTargets: cfg.DefaultTargets,
OCPRouterName: cfg.OCPRouterName,
} }
// 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.

View File

@ -176,6 +176,7 @@ type Config struct {
GoDaddySecretKey string `secure:"yes"` GoDaddySecretKey string `secure:"yes"`
GoDaddyTTL int64 GoDaddyTTL int64
GoDaddyOTE bool GoDaddyOTE bool
OCPRouterName string
} }
var defaultConfig = &Config{ var defaultConfig = &Config{
@ -363,8 +364,9 @@ func (cfg *Config) ParseFlags(args []string) error {
// Flags related to Skipper RouteGroup // Flags related to Skipper RouteGroup
app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion) app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion)
// Flags related to processing sources // Flags related to processing source
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress") app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress")
app.Flag("openshift-router-name", "if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record.").StringVar(&cfg.OCPRouterName)
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)

View File

@ -115,6 +115,7 @@ var (
DigitalOceanAPIPageSize: 50, DigitalOceanAPIPageSize: 50,
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
RFC2136BatchChangeSize: 50, RFC2136BatchChangeSize: 50,
OCPRouterName: "default",
} }
overriddenConfig = &Config{ overriddenConfig = &Config{
@ -225,6 +226,7 @@ func TestParseFlags(t *testing.T) {
args: []string{ args: []string{
"--source=service", "--source=service",
"--provider=google", "--provider=google",
"--openshift-router-name=default",
}, },
envVars: map[string]string{}, envVars: map[string]string{},
expected: minimalConfig, expected: minimalConfig,

View File

@ -353,6 +353,7 @@ func TestAWSRecords(t *testing.T) {
endpoint.NewEndpointWithTTL("multi-value-answer-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set").WithProviderSpecific(providerSpecificMultiValueAnswer, ""), endpoint.NewEndpointWithTTL("multi-value-answer-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set").WithProviderSpecific(providerSpecificMultiValueAnswer, ""),
endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationContinentCode, "EU"), endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationContinentCode, "EU"),
endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificGeolocationCountryCode, "DE"), endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificGeolocationCountryCode, "DE"),
endpoint.NewEndpointWithTTL("geolocation-subdivision-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationSubdivisionCode, "NY"),
endpoint.NewEndpoint("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.example.com").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificWeight, "10").WithProviderSpecific(providerSpecificHealthCheckID, "foo-bar-healthcheck-id"), endpoint.NewEndpoint("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.example.com").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificWeight, "10").WithProviderSpecific(providerSpecificHealthCheckID, "foo-bar-healthcheck-id"),
endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificWeight, "20").WithProviderSpecific(providerSpecificHealthCheckID, "abc-def-healthcheck-id"), endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificWeight, "20").WithProviderSpecific(providerSpecificHealthCheckID, "abc-def-healthcheck-id"),
}) })
@ -376,6 +377,7 @@ func TestAWSRecords(t *testing.T) {
endpoint.NewEndpointWithTTL("multi-value-answer-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set").WithProviderSpecific(providerSpecificMultiValueAnswer, ""), endpoint.NewEndpointWithTTL("multi-value-answer-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set").WithProviderSpecific(providerSpecificMultiValueAnswer, ""),
endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationContinentCode, "EU"), endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationContinentCode, "EU"),
endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificGeolocationCountryCode, "DE"), endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificGeolocationCountryCode, "DE"),
endpoint.NewEndpointWithTTL("geolocation-subdivision-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationSubdivisionCode, "NY"),
endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.example.com").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificWeight, "10").WithProviderSpecific(providerSpecificHealthCheckID, "foo-bar-healthcheck-id"), endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.example.com").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificWeight, "10").WithProviderSpecific(providerSpecificHealthCheckID, "foo-bar-healthcheck-id"),
endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificWeight, "20").WithProviderSpecific(providerSpecificHealthCheckID, "abc-def-healthcheck-id"), endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificWeight, "20").WithProviderSpecific(providerSpecificHealthCheckID, "abc-def-healthcheck-id"),
}) })

View File

@ -18,12 +18,10 @@ package azure
import ( import (
"context" "context"
"strings"
"testing" "testing"
"github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2018-05-01/dns" "github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2018-05-01/dns"
"github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/Azure/go-autorest/autorest/to" "github.com/Azure/go-autorest/autorest/to"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -446,59 +444,6 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordSetsC
} }
} }
func TestAzureGetAccessToken(t *testing.T) {
env := azure.PublicCloud
cfg := config{
ClientID: "",
ClientSecret: "",
TenantID: "",
UseManagedIdentityExtension: false,
}
_, err := getAccessToken(cfg, env)
if err == nil {
t.Fatalf("expected to fail, but got no error")
}
// Expect to use managed identity in this case
cfg = config{
ClientID: "msi",
ClientSecret: "msi",
TenantID: "cefe8aef-5127-4d65-a299-012053f81f60",
UserAssignedIdentityID: "userAssignedIdentityClientID",
UseManagedIdentityExtension: true,
}
token, err := getAccessToken(cfg, env)
if err != nil {
t.Fatalf("expected to construct a token successfully, but got error %v", err)
}
_, err = token.MarshalJSON()
if err == nil ||
!strings.Contains(err.Error(), "marshalling ServicePrincipalMSISecret is not supported") {
t.Fatalf("expected to fail to marshal token, but got %v", err)
}
// Expect to use SPN in this case
cfg = config{
ClientID: "SPNClientID",
ClientSecret: "SPNSecret",
TenantID: "cefe8aef-5127-4d65-a299-012053f81f60",
UserAssignedIdentityID: "userAssignedIdentityClientID",
UseManagedIdentityExtension: true,
}
token, err = getAccessToken(cfg, env)
if err != nil {
t.Fatalf("expected to construct a token successfully, but got error %v", err)
}
innerToken, err := token.MarshalJSON()
if err != nil {
t.Fatalf("expected to marshal token successfully, but got error %v", err)
}
if !strings.Contains(string(innerToken), "SPNClientID") {
t.Fatalf("expect the clientID of the token is SPNClientID, but got token %s", string(innerToken))
}
}
func TestAzureNameFilter(t *testing.T) { func TestAzureNameFilter(t *testing.T) {
provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"nginx.example.com"}), endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "", provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"nginx.example.com"}), endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "",
&[]dns.Zone{ &[]dns.Zone{

View File

@ -19,7 +19,6 @@ package azure
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"strings" "strings"
"github.com/Azure/go-autorest/autorest/adal" "github.com/Azure/go-autorest/autorest/adal"
@ -104,10 +103,6 @@ func getAccessToken(cfg config, environment azure.Environment) (*adal.ServicePri
// Try to retrieve token with MSI. // Try to retrieve token with MSI.
if cfg.UseManagedIdentityExtension { if cfg.UseManagedIdentityExtension {
log.Info("Using managed identity extension to retrieve access token for Azure API.") log.Info("Using managed identity extension to retrieve access token for Azure API.")
os.Setenv("MSI_ENDPOINT", "http://dummy")
defer func() {
os.Unsetenv("MSI_ENDPOINT")
}()
if cfg.UserAssignedIdentityID != "" { if cfg.UserAssignedIdentityID != "" {
log.Infof("Resolving to user assigned identity, client id is %s.", cfg.UserAssignedIdentityID) log.Infof("Resolving to user assigned identity, client id is %s.", cfg.UserAssignedIdentityID)

View File

@ -587,10 +587,7 @@ func getBluecatGatewayToken(cfg bluecatConfig) (string, http.Cookie, error) {
return "", http.Cookie{}, errors.Wrap(err, "could not unmarshal credentials for bluecat gateway config") return "", http.Cookie{}, errors.Wrap(err, "could not unmarshal credentials for bluecat gateway config")
} }
c := &http.Client{ c := newHTTPClient(cfg.SkipTLSVerify)
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.SkipTLSVerify},
}}
resp, err := c.Post(cfg.GatewayHost+"/rest_login", "application/json", bytes.NewBuffer(body)) resp, err := c.Post(cfg.GatewayHost+"/rest_login", "application/json", bytes.NewBuffer(body))
if err != nil { if err != nil {
@ -622,12 +619,8 @@ func getBluecatGatewayToken(cfg bluecatConfig) (string, http.Cookie, error) {
} }
func (c GatewayClientConfig) getBluecatZones(zoneName string) ([]BluecatZone, error) { func (c GatewayClientConfig) getBluecatZones(zoneName string) ([]BluecatZone, error) {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
zonePath := expandZone(zoneName) zonePath := expandZone(zoneName)
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath
req, err := c.buildHTTPRequest("GET", url, nil) req, err := c.buildHTTPRequest("GET", url, nil)
@ -660,12 +653,7 @@ func (c GatewayClientConfig) getBluecatZones(zoneName string) ([]BluecatZone, er
} }
func (c GatewayClientConfig) getHostRecords(zone string, records *[]BluecatHostRecord) error { func (c GatewayClientConfig) getHostRecords(zone string, records *[]BluecatHostRecord) error {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
zonePath := expandZone(zone) zonePath := expandZone(zone)
@ -692,12 +680,7 @@ func (c GatewayClientConfig) getHostRecords(zone string, records *[]BluecatHostR
} }
func (c GatewayClientConfig) getCNAMERecords(zone string, records *[]BluecatCNAMERecord) error { func (c GatewayClientConfig) getCNAMERecords(zone string, records *[]BluecatCNAMERecord) error {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
zonePath := expandZone(zone) zonePath := expandZone(zone)
@ -724,12 +707,7 @@ func (c GatewayClientConfig) getCNAMERecords(zone string, records *[]BluecatCNAM
} }
func (c GatewayClientConfig) getTXTRecords(zone string, records *[]BluecatTXTRecord) error { func (c GatewayClientConfig) getTXTRecords(zone string, records *[]BluecatTXTRecord) error {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
zonePath := expandZone(zone) zonePath := expandZone(zone)
@ -757,12 +735,7 @@ func (c GatewayClientConfig) getTXTRecords(zone string, records *[]BluecatTXTRec
} }
func (c GatewayClientConfig) getHostRecord(name string, record *BluecatHostRecord) error { func (c GatewayClientConfig) getHostRecord(name string, record *BluecatHostRecord) error {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
"/views/" + c.View + "/" + "/views/" + c.View + "/" +
@ -785,12 +758,7 @@ func (c GatewayClientConfig) getHostRecord(name string, record *BluecatHostRecor
} }
func (c GatewayClientConfig) getCNAMERecord(name string, record *BluecatCNAMERecord) error { func (c GatewayClientConfig) getCNAMERecord(name string, record *BluecatCNAMERecord) error {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
"/views/" + c.View + "/" + "/views/" + c.View + "/" +
@ -813,12 +781,7 @@ func (c GatewayClientConfig) getCNAMERecord(name string, record *BluecatCNAMERec
} }
func (c GatewayClientConfig) getTXTRecord(name string, record *BluecatTXTRecord) error { func (c GatewayClientConfig) getTXTRecord(name string, record *BluecatTXTRecord) error {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
"/views/" + c.View + "/" + "/views/" + c.View + "/" +
@ -842,12 +805,7 @@ func (c GatewayClientConfig) getTXTRecord(name string, record *BluecatTXTRecord)
} }
func (c GatewayClientConfig) createHostRecord(zone string, req *bluecatCreateHostRecordRequest) (res interface{}, err error) { func (c GatewayClientConfig) createHostRecord(zone string, req *bluecatCreateHostRecordRequest) (res interface{}, err error) {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
zonePath := expandZone(zone) zonePath := expandZone(zone)
// Remove the trailing 'zones/' // Remove the trailing 'zones/'
@ -866,12 +824,7 @@ func (c GatewayClientConfig) createHostRecord(zone string, req *bluecatCreateHos
} }
func (c GatewayClientConfig) createCNAMERecord(zone string, req *bluecatCreateCNAMERecordRequest) (res interface{}, err error) { func (c GatewayClientConfig) createCNAMERecord(zone string, req *bluecatCreateCNAMERecordRequest) (res interface{}, err error) {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
zonePath := expandZone(zone) zonePath := expandZone(zone)
// Remove the trailing 'zones/' // Remove the trailing 'zones/'
@ -892,12 +845,7 @@ func (c GatewayClientConfig) createCNAMERecord(zone string, req *bluecatCreateCN
} }
func (c GatewayClientConfig) createTXTRecord(zone string, req *bluecatCreateTXTRecordRequest) (interface{}, error) { func (c GatewayClientConfig) createTXTRecord(zone string, req *bluecatCreateTXTRecordRequest) (interface{}, error) {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
zonePath := expandZone(zone) zonePath := expandZone(zone)
// Remove the trailing 'zones/' // Remove the trailing 'zones/'
@ -917,12 +865,7 @@ func (c GatewayClientConfig) createTXTRecord(zone string, req *bluecatCreateTXTR
} }
func (c GatewayClientConfig) deleteHostRecord(name string, zone string) (err error) { func (c GatewayClientConfig) deleteHostRecord(name string, zone string) (err error) {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
"/views/" + c.View + "/" + "/views/" + c.View + "/" +
@ -941,12 +884,7 @@ func (c GatewayClientConfig) deleteHostRecord(name string, zone string) (err err
} }
func (c GatewayClientConfig) deleteCNAMERecord(name string, zone string) (err error) { func (c GatewayClientConfig) deleteCNAMERecord(name string, zone string) (err error) {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
"/views/" + c.View + "/" + "/views/" + c.View + "/" +
@ -965,12 +903,7 @@ func (c GatewayClientConfig) deleteCNAMERecord(name string, zone string) (err er
} }
func (c GatewayClientConfig) deleteTXTRecord(name string, zone string) error { func (c GatewayClientConfig) deleteTXTRecord(name string, zone string) error {
transportCfg := &http.Transport{ client := newHTTPClient(c.SkipTLSVerify)
TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify},
}
client := &http.Client{
Transport: transportCfg,
}
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration +
"/views/" + c.View + "/" + "/views/" + c.View + "/" +
@ -1042,3 +975,15 @@ func extractOwnerfromTXTRecord(propString string) (string, error) {
} }
return strings.Split(match[0], "=")[1], nil return strings.Split(match[0], "=")[1], nil
} }
// newHTTPClient returns an instance of http client
func newHTTPClient(skipTLSVerify bool) *http.Client {
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: skipTLSVerify,
},
},
}
}

View File

@ -43,6 +43,15 @@ const (
defaultCloudFlareRecordTTL = 1 defaultCloudFlareRecordTTL = 1
) )
// We have to use pointers to bools now, as the upstream cloudflare-go library requires them
// see: https://github.com/cloudflare/cloudflare-go/pull/595
// proxyEnabled is a pointer to a bool true showing the record should be proxied through cloudflare
var proxyEnabled *bool = boolPtr(true)
// proxyDisabled is a pointer to a bool false showing the record should not be proxied through cloudflare
var proxyDisabled *bool = boolPtr(false)
var cloudFlareTypeNotSupported = map[string]bool{ var cloudFlareTypeNotSupported = map[string]bool{
"LOC": true, "LOC": true,
"MX": true, "MX": true,
@ -54,53 +63,53 @@ var cloudFlareTypeNotSupported = map[string]bool{
// cloudFlareDNS is the subset of the CloudFlare API that we actually use. Add methods as required. Signatures must match exactly. // cloudFlareDNS is the subset of the CloudFlare API that we actually use. Add methods as required. Signatures must match exactly.
type cloudFlareDNS interface { type cloudFlareDNS interface {
UserDetails() (cloudflare.User, error) UserDetails(ctx context.Context) (cloudflare.User, error)
ZoneIDByName(zoneName string) (string, error) ZoneIDByName(zoneName string) (string, error)
ListZones(zoneID ...string) ([]cloudflare.Zone, error) ListZones(ctx context.Context, zoneID ...string) ([]cloudflare.Zone, error)
ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error)
ZoneDetails(zoneID string) (cloudflare.Zone, error) ZoneDetails(ctx context.Context, zoneID string) (cloudflare.Zone, error)
DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) DNSRecords(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error)
CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) CreateDNSRecord(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error)
DeleteDNSRecord(zoneID, recordID string) error DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error
UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error UpdateDNSRecord(ctx context.Context, zoneID, recordID string, rr cloudflare.DNSRecord) error
} }
type zoneService struct { type zoneService struct {
service *cloudflare.API service *cloudflare.API
} }
func (z zoneService) UserDetails() (cloudflare.User, error) { func (z zoneService) UserDetails(ctx context.Context) (cloudflare.User, error) {
return z.service.UserDetails() return z.service.UserDetails(ctx)
} }
func (z zoneService) ListZones(zoneID ...string) ([]cloudflare.Zone, error) { func (z zoneService) ListZones(ctx context.Context, zoneID ...string) ([]cloudflare.Zone, error) {
return z.service.ListZones(zoneID...) return z.service.ListZones(ctx, zoneID...)
} }
func (z zoneService) ZoneIDByName(zoneName string) (string, error) { func (z zoneService) ZoneIDByName(zoneName string) (string, error) {
return z.service.ZoneIDByName(zoneName) return z.service.ZoneIDByName(zoneName)
} }
func (z zoneService) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) { func (z zoneService) CreateDNSRecord(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
return z.service.CreateDNSRecord(zoneID, rr) return z.service.CreateDNSRecord(ctx, zoneID, rr)
} }
func (z zoneService) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) { func (z zoneService) DNSRecords(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
return z.service.DNSRecords(zoneID, rr) return z.service.DNSRecords(ctx, zoneID, rr)
} }
func (z zoneService) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error { func (z zoneService) UpdateDNSRecord(ctx context.Context, zoneID, recordID string, rr cloudflare.DNSRecord) error {
return z.service.UpdateDNSRecord(zoneID, recordID, rr) return z.service.UpdateDNSRecord(ctx, zoneID, recordID, rr)
} }
func (z zoneService) DeleteDNSRecord(zoneID, recordID string) error { func (z zoneService) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error {
return z.service.DeleteDNSRecord(zoneID, recordID) return z.service.DeleteDNSRecord(ctx, zoneID, recordID)
} }
func (z zoneService) ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) { func (z zoneService) ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) {
return z.service.ListZonesContext(ctx, opts...) return z.service.ListZonesContext(ctx, opts...)
} }
func (z zoneService) ZoneDetails(zoneID string) (cloudflare.Zone, error) { func (z zoneService) ZoneDetails(ctx context.Context, zoneID string) (cloudflare.Zone, error) {
return z.service.ZoneDetails(zoneID) return z.service.ZoneDetails(ctx, zoneID)
} }
// CloudFlareProvider is an implementation of Provider for CloudFlare DNS. // CloudFlareProvider is an implementation of Provider for CloudFlare DNS.
@ -162,7 +171,7 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
log.Debugln("zoneIDFilter configured. only looking up zone IDs defined") log.Debugln("zoneIDFilter configured. only looking up zone IDs defined")
for _, zoneID := range p.zoneIDFilter.ZoneIDs { for _, zoneID := range p.zoneIDFilter.ZoneIDs {
log.Debugf("looking up zone %s", zoneID) log.Debugf("looking up zone %s", zoneID)
detailResponse, err := p.Client.ZoneDetails(zoneID) detailResponse, err := p.Client.ZoneDetails(ctx, zoneID)
if err != nil { if err != nil {
log.Errorf("zone %s lookup failed, %v", zoneID, err) log.Errorf("zone %s lookup failed, %v", zoneID, err)
continue continue
@ -177,24 +186,20 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
} }
log.Debugln("no zoneIDFilter configured, looking at all zones") log.Debugln("no zoneIDFilter configured, looking at all zones")
for {
zonesResponse, err := p.Client.ListZonesContext(ctx, cloudflare.WithPagination(p.PaginationOptions))
if err != nil {
return nil, err
}
for _, zone := range zonesResponse.Result { zonesResponse, err := p.Client.ListZonesContext(ctx)
if !p.domainFilter.Match(zone.Name) { if err != nil {
log.Debugf("zone %s not in domain filter", zone.Name) return nil, err
continue
}
result = append(result, zone)
}
if p.PaginationOptions.Page == zonesResponse.ResultInfo.TotalPages {
break
}
p.PaginationOptions.Page++
} }
for _, zone := range zonesResponse.Result {
if !p.domainFilter.Match(zone.Name) {
log.Debugf("zone %s not in domain filter", zone.Name)
continue
}
result = append(result, zone)
}
return result, nil return result, nil
} }
@ -207,7 +212,7 @@ func (p *CloudFlareProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
endpoints := []*endpoint.Endpoint{} endpoints := []*endpoint.Endpoint{}
for _, zone := range zones { for _, zone := range zones {
records, err := p.Client.DNSRecords(zone.ID, cloudflare.DNSRecord{}) records, err := p.Client.DNSRecords(ctx, zone.ID, cloudflare.DNSRecord{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -281,7 +286,7 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
changesByZone := p.changesByZone(zones, changes) changesByZone := p.changesByZone(zones, changes)
for zoneID, changes := range changesByZone { for zoneID, changes := range changesByZone {
records, err := p.Client.DNSRecords(zoneID, cloudflare.DNSRecord{}) records, err := p.Client.DNSRecords(ctx, zoneID, cloudflare.DNSRecord{})
if err != nil { if err != nil {
return fmt.Errorf("could not fetch records from zone, %v", err) return fmt.Errorf("could not fetch records from zone, %v", err)
} }
@ -306,7 +311,7 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
log.WithFields(logFields).Errorf("failed to find previous record: %v", change.ResourceRecord) log.WithFields(logFields).Errorf("failed to find previous record: %v", change.ResourceRecord)
continue continue
} }
err := p.Client.UpdateDNSRecord(zoneID, recordID, change.ResourceRecord) err := p.Client.UpdateDNSRecord(ctx, zoneID, recordID, change.ResourceRecord)
if err != nil { if err != nil {
log.WithFields(logFields).Errorf("failed to update record: %v", err) log.WithFields(logFields).Errorf("failed to update record: %v", err)
} }
@ -316,12 +321,12 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
log.WithFields(logFields).Errorf("failed to find previous record: %v", change.ResourceRecord) log.WithFields(logFields).Errorf("failed to find previous record: %v", change.ResourceRecord)
continue continue
} }
err := p.Client.DeleteDNSRecord(zoneID, recordID) err := p.Client.DeleteDNSRecord(ctx, zoneID, recordID)
if err != nil { if err != nil {
log.WithFields(logFields).Errorf("failed to delete record: %v", err) log.WithFields(logFields).Errorf("failed to delete record: %v", err)
} }
} else if change.Action == cloudFlareCreate { } else if change.Action == cloudFlareCreate {
_, err := p.Client.CreateDNSRecord(zoneID, change.ResourceRecord) _, err := p.Client.CreateDNSRecord(ctx, zoneID, change.ResourceRecord)
if err != nil { if err != nil {
log.WithFields(logFields).Errorf("failed to create record: %v", err) log.WithFields(logFields).Errorf("failed to create record: %v", err)
} }
@ -382,16 +387,12 @@ func (p *CloudFlareProvider) newCloudFlareChange(action string, endpoint *endpoi
ttl = int(endpoint.RecordTTL) ttl = int(endpoint.RecordTTL)
} }
if len(endpoint.Targets) > 1 {
log.Errorf("Updates should have just one target")
}
return &cloudFlareChange{ return &cloudFlareChange{
Action: action, Action: action,
ResourceRecord: cloudflare.DNSRecord{ ResourceRecord: cloudflare.DNSRecord{
Name: endpoint.DNSName, Name: endpoint.DNSName,
TTL: ttl, TTL: ttl,
Proxied: proxied, Proxied: &proxied,
Type: endpoint.RecordType, Type: endpoint.RecordType,
Content: target, Content: target,
}, },
@ -450,8 +451,15 @@ func groupByNameAndType(records []cloudflare.DNSRecord) []*endpoint.Endpoint {
records[0].Type, records[0].Type,
endpoint.TTL(records[0].TTL), endpoint.TTL(records[0].TTL),
targets...). targets...).
WithProviderSpecific(source.CloudflareProxiedKey, strconv.FormatBool(records[0].Proxied))) WithProviderSpecific(source.CloudflareProxiedKey, strconv.FormatBool(*records[0].Proxied)),
)
} }
return endpoints return endpoints
} }
// boolPtr is used as a helper function to return a pointer to a boolean
// Needed because some parameters require a pointer.
func boolPtr(b bool) *bool {
return &b
}

View File

@ -55,7 +55,7 @@ var ExampleDomain = []cloudflare.DNSRecord{
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
TTL: 120, TTL: 120,
Content: "1.2.3.4", Content: "1.2.3.4",
Proxied: false, Proxied: proxyDisabled,
}, },
{ {
ID: "2345678901", ID: "2345678901",
@ -64,7 +64,7 @@ var ExampleDomain = []cloudflare.DNSRecord{
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
TTL: 120, TTL: 120,
Content: "3.4.5.6", Content: "3.4.5.6",
Proxied: false, Proxied: proxyDisabled,
}, },
{ {
ID: "1231231233", ID: "1231231233",
@ -73,7 +73,7 @@ var ExampleDomain = []cloudflare.DNSRecord{
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
TTL: 1, TTL: 1,
Content: "2.3.4.5", Content: "2.3.4.5",
Proxied: false, Proxied: proxyDisabled,
}, },
} }
@ -105,7 +105,7 @@ func NewMockCloudFlareClientWithRecords(records map[string][]cloudflare.DNSRecor
return m return m
} }
func (m *mockCloudFlareClient) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) { func (m *mockCloudFlareClient) CreateDNSRecord(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
m.Actions = append(m.Actions, MockAction{ m.Actions = append(m.Actions, MockAction{
Name: "Create", Name: "Create",
ZoneId: zoneID, ZoneId: zoneID,
@ -118,7 +118,7 @@ func (m *mockCloudFlareClient) CreateDNSRecord(zoneID string, rr cloudflare.DNSR
return nil, nil return nil, nil
} }
func (m *mockCloudFlareClient) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) { func (m *mockCloudFlareClient) DNSRecords(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
if m.dnsRecordsError != nil { if m.dnsRecordsError != nil {
return nil, m.dnsRecordsError return nil, m.dnsRecordsError
} }
@ -132,7 +132,7 @@ func (m *mockCloudFlareClient) DNSRecords(zoneID string, rr cloudflare.DNSRecord
return result, nil return result, nil
} }
func (m *mockCloudFlareClient) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error { func (m *mockCloudFlareClient) UpdateDNSRecord(ctx context.Context, zoneID, recordID string, rr cloudflare.DNSRecord) error {
m.Actions = append(m.Actions, MockAction{ m.Actions = append(m.Actions, MockAction{
Name: "Update", Name: "Update",
ZoneId: zoneID, ZoneId: zoneID,
@ -147,7 +147,7 @@ func (m *mockCloudFlareClient) UpdateDNSRecord(zoneID, recordID string, rr cloud
return nil return nil
} }
func (m *mockCloudFlareClient) DeleteDNSRecord(zoneID, recordID string) error { func (m *mockCloudFlareClient) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error {
m.Actions = append(m.Actions, MockAction{ m.Actions = append(m.Actions, MockAction{
Name: "Delete", Name: "Delete",
ZoneId: zoneID, ZoneId: zoneID,
@ -162,7 +162,7 @@ func (m *mockCloudFlareClient) DeleteDNSRecord(zoneID, recordID string) error {
return nil return nil
} }
func (m *mockCloudFlareClient) UserDetails() (cloudflare.User, error) { func (m *mockCloudFlareClient) UserDetails(ctx context.Context) (cloudflare.User, error) {
return m.User, nil return m.User, nil
} }
@ -176,7 +176,7 @@ func (m *mockCloudFlareClient) ZoneIDByName(zoneName string) (string, error) {
return "", errors.New("Unknown zone: " + zoneName) return "", errors.New("Unknown zone: " + zoneName)
} }
func (m *mockCloudFlareClient) ListZones(zoneID ...string) ([]cloudflare.Zone, error) { func (m *mockCloudFlareClient) ListZones(ctx context.Context, zoneID ...string) ([]cloudflare.Zone, error) {
if m.listZonesError != nil { if m.listZonesError != nil {
return nil, m.listZonesError return nil, m.listZonesError
} }
@ -216,7 +216,7 @@ func (m *mockCloudFlareClient) ListZonesContext(ctx context.Context, opts ...clo
}, nil }, nil
} }
func (m *mockCloudFlareClient) ZoneDetails(zoneID string) (cloudflare.Zone, error) { func (m *mockCloudFlareClient) ZoneDetails(ctx context.Context, zoneID string) (cloudflare.Zone, error) {
for id, zoneName := range m.Zones { for id, zoneName := range m.Zones {
if zoneID == id { if zoneID == id {
return cloudflare.Zone{ return cloudflare.Zone{
@ -292,7 +292,7 @@ func TestCloudflareA(t *testing.T) {
Name: "bar.com", Name: "bar.com",
Content: "127.0.0.1", Content: "127.0.0.1",
TTL: 1, TTL: 1,
Proxied: false, Proxied: proxyDisabled,
}, },
}, },
{ {
@ -303,7 +303,7 @@ func TestCloudflareA(t *testing.T) {
Name: "bar.com", Name: "bar.com",
Content: "127.0.0.2", Content: "127.0.0.2",
TTL: 1, TTL: 1,
Proxied: false, Proxied: proxyDisabled,
}, },
}, },
}, },
@ -330,7 +330,7 @@ func TestCloudflareCname(t *testing.T) {
Name: "cname.bar.com", Name: "cname.bar.com",
Content: "google.com", Content: "google.com",
TTL: 1, TTL: 1,
Proxied: false, Proxied: proxyDisabled,
}, },
}, },
{ {
@ -341,7 +341,7 @@ func TestCloudflareCname(t *testing.T) {
Name: "cname.bar.com", Name: "cname.bar.com",
Content: "facebook.com", Content: "facebook.com",
TTL: 1, TTL: 1,
Proxied: false, Proxied: proxyDisabled,
}, },
}, },
}, },
@ -368,7 +368,7 @@ func TestCloudflareCustomTTL(t *testing.T) {
Name: "ttl.bar.com", Name: "ttl.bar.com",
Content: "127.0.0.1", Content: "127.0.0.1",
TTL: 120, TTL: 120,
Proxied: false, Proxied: proxyDisabled,
}, },
}, },
}, },
@ -394,7 +394,7 @@ func TestCloudflareProxiedDefault(t *testing.T) {
Name: "bar.com", Name: "bar.com",
Content: "127.0.0.1", Content: "127.0.0.1",
TTL: 1, TTL: 1,
Proxied: true, Proxied: proxyEnabled,
}, },
}, },
}, },
@ -426,7 +426,7 @@ func TestCloudflareProxiedOverrideTrue(t *testing.T) {
Name: "bar.com", Name: "bar.com",
Content: "127.0.0.1", Content: "127.0.0.1",
TTL: 1, TTL: 1,
Proxied: true, Proxied: proxyEnabled,
}, },
}, },
}, },
@ -458,7 +458,7 @@ func TestCloudflareProxiedOverrideFalse(t *testing.T) {
Name: "bar.com", Name: "bar.com",
Content: "127.0.0.1", Content: "127.0.0.1",
TTL: 1, TTL: 1,
Proxied: false, Proxied: proxyDisabled,
}, },
}, },
}, },
@ -490,7 +490,7 @@ func TestCloudflareProxiedOverrideIllegal(t *testing.T) {
Name: "bar.com", Name: "bar.com",
Content: "127.0.0.1", Content: "127.0.0.1",
TTL: 1, TTL: 1,
Proxied: true, Proxied: proxyEnabled,
}, },
}, },
}, },
@ -499,19 +499,21 @@ func TestCloudflareProxiedOverrideIllegal(t *testing.T) {
} }
func TestCloudflareSetProxied(t *testing.T) { func TestCloudflareSetProxied(t *testing.T) {
var proxied *bool = proxyEnabled
var notProxied *bool = proxyDisabled
var testCases = []struct { var testCases = []struct {
recordType string recordType string
domain string domain string
proxiable bool proxiable *bool
}{ }{
{"A", "bar.com", true}, {"A", "bar.com", proxied},
{"CNAME", "bar.com", true}, {"CNAME", "bar.com", proxied},
{"TXT", "bar.com", false}, {"TXT", "bar.com", notProxied},
{"MX", "bar.com", false}, {"MX", "bar.com", notProxied},
{"NS", "bar.com", false}, {"NS", "bar.com", notProxied},
{"SPF", "bar.com", false}, {"SPF", "bar.com", notProxied},
{"SRV", "bar.com", false}, {"SRV", "bar.com", notProxied},
{"A", "*.bar.com", false}, {"A", "*.bar.com", notProxied},
} }
for _, testCase := range testCases { for _, testCase := range testCases {
@ -684,6 +686,7 @@ func TestCloudflareApplyChanges(t *testing.T) {
Name: "new.bar.com", Name: "new.bar.com",
Content: "target", Content: "target",
TTL: 1, TTL: 1,
Proxied: proxyDisabled,
}, },
}, },
{ {
@ -693,6 +696,7 @@ func TestCloudflareApplyChanges(t *testing.T) {
Name: "foobar.bar.com", Name: "foobar.bar.com",
Content: "target-new", Content: "target-new",
TTL: 1, TTL: 1,
Proxied: proxyDisabled,
}, },
}, },
}) })
@ -779,6 +783,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) {
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.1", Content: "10.10.10.1",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
}, },
ExpectedEndpoints: []*endpoint.Endpoint{ ExpectedEndpoints: []*endpoint.Endpoint{
@ -805,12 +810,14 @@ func TestCloudflareGroupByNameAndType(t *testing.T) {
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.1", Content: "10.10.10.1",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
{ {
Name: "foo.com", Name: "foo.com",
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.2", Content: "10.10.10.2",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
}, },
ExpectedEndpoints: []*endpoint.Endpoint{ ExpectedEndpoints: []*endpoint.Endpoint{
@ -837,24 +844,28 @@ func TestCloudflareGroupByNameAndType(t *testing.T) {
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.1", Content: "10.10.10.1",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
{ {
Name: "foo.com", Name: "foo.com",
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.2", Content: "10.10.10.2",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
{ {
Name: "bar.de", Name: "bar.de",
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.1", Content: "10.10.10.1",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
{ {
Name: "bar.de", Name: "bar.de",
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.2", Content: "10.10.10.2",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
}, },
ExpectedEndpoints: []*endpoint.Endpoint{ ExpectedEndpoints: []*endpoint.Endpoint{
@ -894,18 +905,21 @@ func TestCloudflareGroupByNameAndType(t *testing.T) {
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.1", Content: "10.10.10.1",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
{ {
Name: "foo.com", Name: "foo.com",
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.2", Content: "10.10.10.2",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
{ {
Name: "bar.de", Name: "bar.de",
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.1", Content: "10.10.10.1",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
}, },
ExpectedEndpoints: []*endpoint.Endpoint{ ExpectedEndpoints: []*endpoint.Endpoint{
@ -945,18 +959,21 @@ func TestCloudflareGroupByNameAndType(t *testing.T) {
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.1", Content: "10.10.10.1",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
{ {
Name: "foo.com", Name: "foo.com",
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
Content: "10.10.10.2", Content: "10.10.10.2",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
{ {
Name: "bar.de", Name: "bar.de",
Type: "NOT SUPPORTED", Type: "NOT SUPPORTED",
Content: "10.10.10.1", Content: "10.10.10.1",
TTL: defaultCloudFlareRecordTTL, TTL: defaultCloudFlareRecordTTL,
Proxied: proxyDisabled,
}, },
}, },
ExpectedEndpoints: []*endpoint.Endpoint{ ExpectedEndpoints: []*endpoint.Endpoint{
@ -984,94 +1001,101 @@ func TestCloudflareGroupByNameAndType(t *testing.T) {
func TestProviderPropertiesIdempotency(t *testing.T) { func TestProviderPropertiesIdempotency(t *testing.T) {
testCases := []struct { testCases := []struct {
Name string
ProviderProxiedByDefault bool ProviderProxiedByDefault bool
RecordsAreProxied bool RecordsAreProxied *bool
ShouldBeUpdated bool ShouldBeUpdated bool
}{ }{
{ {
Name: "ProxyDefault: false, ShouldBeProxied: false, ExpectUpdates: false",
ProviderProxiedByDefault: false, ProviderProxiedByDefault: false,
RecordsAreProxied: false, RecordsAreProxied: proxyDisabled,
ShouldBeUpdated: false, ShouldBeUpdated: false,
}, },
{ {
Name: "ProxyDefault: true, ShouldBeProxied: true, ExpectUpdates: false",
ProviderProxiedByDefault: true, ProviderProxiedByDefault: true,
RecordsAreProxied: true, RecordsAreProxied: proxyEnabled,
ShouldBeUpdated: false, ShouldBeUpdated: false,
}, },
{ {
Name: "ProxyDefault: true, ShouldBeProxied: false, ExpectUpdates: true",
ProviderProxiedByDefault: true, ProviderProxiedByDefault: true,
RecordsAreProxied: false, RecordsAreProxied: proxyDisabled,
ShouldBeUpdated: true, ShouldBeUpdated: true,
}, },
{ {
Name: "ProxyDefault: false, ShouldBeProxied: true, ExpectUpdates: true",
ProviderProxiedByDefault: false, ProviderProxiedByDefault: false,
RecordsAreProxied: true, RecordsAreProxied: proxyEnabled,
ShouldBeUpdated: true, ShouldBeUpdated: true,
}, },
} }
for _, test := range testCases { for _, test := range testCases {
client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{ t.Run(test.Name, func(t *testing.T) {
"001": { client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{
{ "001": {
ID: "1234567890", {
ZoneID: "001", ID: "1234567890",
Name: "foobar.bar.com", ZoneID: "001",
Type: endpoint.RecordTypeA, Name: "foobar.bar.com",
TTL: 120, Type: endpoint.RecordTypeA,
Content: "1.2.3.4", TTL: 120,
Proxied: test.RecordsAreProxied, Content: "1.2.3.4",
Proxied: test.RecordsAreProxied,
},
}, },
},
})
provider := &CloudFlareProvider{
Client: client,
proxiedByDefault: test.ProviderProxiedByDefault,
}
ctx := context.Background()
current, err := provider.Records(ctx)
if err != nil {
t.Errorf("should not fail, %s", err)
}
assert.Equal(t, 1, len(current))
desired := []*endpoint.Endpoint{}
for _, c := range current {
// Copy all except ProviderSpecific fields
desired = append(desired, &endpoint.Endpoint{
DNSName: c.DNSName,
Targets: c.Targets,
RecordType: c.RecordType,
SetIdentifier: c.SetIdentifier,
RecordTTL: c.RecordTTL,
Labels: c.Labels,
}) })
}
plan := plan.Plan{ provider := &CloudFlareProvider{
Current: current, Client: client,
Desired: desired, proxiedByDefault: test.ProviderProxiedByDefault,
PropertyComparator: provider.PropertyValuesEqual, }
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, ctx := context.Background()
}
plan = *plan.Calculate() current, err := provider.Records(ctx)
assert.NotNil(t, plan.Changes, "should have plan") if err != nil {
if plan.Changes == nil { t.Errorf("should not fail, %s", err)
return }
} assert.Equal(t, 1, len(current))
assert.Equal(t, 0, len(plan.Changes.Create), "should not have creates")
assert.Equal(t, 0, len(plan.Changes.Delete), "should not have deletes")
if test.ShouldBeUpdated { desired := []*endpoint.Endpoint{}
assert.Equal(t, 1, len(plan.Changes.UpdateNew), "should not have new updates") for _, c := range current {
assert.Equal(t, 1, len(plan.Changes.UpdateOld), "should not have old updates") // Copy all except ProviderSpecific fields
} else { desired = append(desired, &endpoint.Endpoint{
assert.Equal(t, 0, len(plan.Changes.UpdateNew), "should not have new updates") DNSName: c.DNSName,
assert.Equal(t, 0, len(plan.Changes.UpdateOld), "should not have old updates") Targets: c.Targets,
} RecordType: c.RecordType,
SetIdentifier: c.SetIdentifier,
RecordTTL: c.RecordTTL,
Labels: c.Labels,
})
}
plan := plan.Plan{
Current: current,
Desired: desired,
PropertyComparator: provider.PropertyValuesEqual,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
}
plan = *plan.Calculate()
assert.NotNil(t, plan.Changes, "should have plan")
if plan.Changes == nil {
return
}
assert.Equal(t, 0, len(plan.Changes.Create), "should not have creates")
assert.Equal(t, 0, len(plan.Changes.Delete), "should not have deletes")
if test.ShouldBeUpdated {
assert.Equal(t, 1, len(plan.Changes.UpdateNew), "should not have new updates")
assert.Equal(t, 1, len(plan.Changes.UpdateOld), "should not have old updates")
} else {
assert.Equal(t, 0, len(plan.Changes.UpdateNew), "should not have new updates")
assert.Equal(t, 0, len(plan.Changes.UpdateOld), "should not have old updates")
}
})
} }
} }
@ -1129,7 +1153,7 @@ func TestCloudflareComplexUpdate(t *testing.T) {
Type: "A", Type: "A",
Content: "2.3.4.5", Content: "2.3.4.5",
TTL: 1, TTL: 1,
Proxied: true, Proxied: proxyEnabled,
}, },
}, },
MockAction{ MockAction{
@ -1141,7 +1165,7 @@ func TestCloudflareComplexUpdate(t *testing.T) {
Type: "A", Type: "A",
Content: "1.2.3.4", Content: "1.2.3.4",
TTL: 1, TTL: 1,
Proxied: true, Proxied: proxyEnabled,
}, },
}, },
MockAction{ MockAction{
@ -1162,7 +1186,7 @@ func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) {
Type: endpoint.RecordTypeA, Type: endpoint.RecordTypeA,
TTL: 1, TTL: 1,
Content: "1.2.3.4", Content: "1.2.3.4",
Proxied: true, Proxied: proxyEnabled,
}, },
}, },
}) })

View File

@ -176,6 +176,9 @@ OuterLoop:
case dns.TypeTXT: case dns.TypeTXT:
rrValues = (rr.(*dns.TXT).Txt) rrValues = (rr.(*dns.TXT).Txt)
rrType = "TXT" rrType = "TXT"
case dns.TypeNS:
rrValues = []string{rr.(*dns.NS).Ns}
rrType = "NS"
default: default:
continue // Unhandled record type continue // Unhandled record type
} }

View File

@ -172,6 +172,11 @@ func TestRfc2136ApplyChanges(t *testing.T) {
RecordType: "TXT", RecordType: "TXT",
Targets: []string{"boom"}, Targets: []string{"boom"},
}, },
{
DNSName: "ns.foobar.com",
RecordType: "NS",
Targets: []string{"boom"},
},
}, },
Delete: []*endpoint.Endpoint{ Delete: []*endpoint.Endpoint{
{ {
@ -190,13 +195,16 @@ func TestRfc2136ApplyChanges(t *testing.T) {
err = provider.ApplyChanges(context.Background(), p) err = provider.ApplyChanges(context.Background(), p)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 2, len(stub.createMsgs)) assert.Equal(t, 3, len(stub.createMsgs))
assert.True(t, strings.Contains(stub.createMsgs[0].String(), "v1.foo.com")) assert.True(t, strings.Contains(stub.createMsgs[0].String(), "v1.foo.com"))
assert.True(t, strings.Contains(stub.createMsgs[0].String(), "1.2.3.4")) assert.True(t, strings.Contains(stub.createMsgs[0].String(), "1.2.3.4"))
assert.True(t, strings.Contains(stub.createMsgs[1].String(), "v1.foobar.com")) assert.True(t, strings.Contains(stub.createMsgs[1].String(), "v1.foobar.com"))
assert.True(t, strings.Contains(stub.createMsgs[1].String(), "boom")) assert.True(t, strings.Contains(stub.createMsgs[1].String(), "boom"))
assert.True(t, strings.Contains(stub.createMsgs[2].String(), "ns.foobar.com"))
assert.True(t, strings.Contains(stub.createMsgs[2].String(), "boom"))
assert.Equal(t, 2, len(stub.updateMsgs)) assert.Equal(t, 2, len(stub.updateMsgs))
assert.True(t, strings.Contains(stub.updateMsgs[0].String(), "v2.foo.com")) assert.True(t, strings.Contains(stub.updateMsgs[0].String(), "v2.foo.com"))
assert.True(t, strings.Contains(stub.updateMsgs[1].String(), "v2.foobar.com")) assert.True(t, strings.Contains(stub.updateMsgs[1].String(), "v2.foobar.com"))

View File

@ -1,3 +1,13 @@
#! /bin/bash #! /bin/bash
set -e
trivy image --exit-code 1 us.gcr.io/k8s-artifacts-prod/external-dns/external-dns:$(git describe --tags --always --dirty) # install trivy
cd /tmp
curl -LO https://github.com/aquasecurity/trivy/releases/download/v0.20.2/trivy_0.20.2_Linux-64bit.tar.gz
echo "38a6de48e21a34e0fa0d2cf63439c0afcbbae0e78fb3feada7a84a9cf6e7f60c trivy_0.20.2_Linux-64bit.tar.gz" | sha256sum -c
tar -xvf trivy_0.20.2_Linux-64bit.tar.gz
chmod +x trivy
# run trivy
cd -
/tmp/trivy image --exit-code 1 us.gcr.io/k8s-artifacts-prod/external-dns/external-dns:$(git describe --tags --always --dirty)

View File

@ -49,6 +49,7 @@ type ocpRouteSource struct {
ignoreHostnameAnnotation bool ignoreHostnameAnnotation bool
routeInformer routeInformer.RouteInformer routeInformer routeInformer.RouteInformer
labelSelector labels.Selector labelSelector labels.Selector
ocpRouterName string
} }
// NewOcpRouteSource creates a new ocpRouteSource with the given config. // NewOcpRouteSource creates a new ocpRouteSource with the given config.
@ -60,6 +61,7 @@ func NewOcpRouteSource(
combineFQDNAnnotation bool, combineFQDNAnnotation bool,
ignoreHostnameAnnotation bool, ignoreHostnameAnnotation bool,
labelSelector labels.Selector, labelSelector labels.Selector,
ocpRouterName string,
) (Source, error) { ) (Source, error) {
tmpl, err := parseTemplate(fqdnTemplate) tmpl, err := parseTemplate(fqdnTemplate)
if err != nil { if err != nil {
@ -96,11 +98,16 @@ func NewOcpRouteSource(
ignoreHostnameAnnotation: ignoreHostnameAnnotation, ignoreHostnameAnnotation: ignoreHostnameAnnotation,
routeInformer: informer, routeInformer: informer,
labelSelector: labelSelector, labelSelector: labelSelector,
ocpRouterName: ocpRouterName,
}, nil }, nil
} }
// TODO add a meaningful EventHandler
func (ors *ocpRouteSource) AddEventHandler(ctx context.Context, handler func()) { func (ors *ocpRouteSource) AddEventHandler(ctx context.Context, handler func()) {
log.Debug("Adding event handler for openshift route")
// Right now there is no way to remove event handler from informer, see:
// https://github.com/kubernetes/kubernetes/issues/79610
ors.routeInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
} }
// Endpoints returns endpoint objects for each host-target combination that should be processed. // Endpoints returns endpoint objects for each host-target combination that should be processed.
@ -128,7 +135,7 @@ func (ors *ocpRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint,
continue continue
} }
orEndpoints := endpointsFromOcpRoute(ocpRoute, ors.ignoreHostnameAnnotation) orEndpoints := ors.endpointsFromOcpRoute(ocpRoute, ors.ignoreHostnameAnnotation)
// apply template if host is missing on OpenShift Route // apply template if host is missing on OpenShift Route
if (ors.combineFQDNAnnotation || len(orEndpoints) == 0) && ors.fqdnTemplate != nil { if (ors.combineFQDNAnnotation || len(orEndpoints) == 0) && ors.fqdnTemplate != nil {
@ -174,7 +181,7 @@ func (ors *ocpRouteSource) endpointsFromTemplate(ocpRoute *routev1.Route) ([]*en
targets := getTargetsFromTargetAnnotation(ocpRoute.Annotations) targets := getTargetsFromTargetAnnotation(ocpRoute.Annotations)
if len(targets) == 0 { if len(targets) == 0 {
targets = targetsFromOcpRouteStatus(ocpRoute.Status) targets = ors.targetsFromOcpRouteStatus(ocpRoute.Status)
} }
providerSpecific, setIdentifier := getProviderSpecificAnnotations(ocpRoute.Annotations) providerSpecific, setIdentifier := getProviderSpecificAnnotations(ocpRoute.Annotations)
@ -223,7 +230,7 @@ func (ors *ocpRouteSource) setResourceLabel(ocpRoute *routev1.Route, endpoints [
} }
// endpointsFromOcpRoute extracts the endpoints from a OpenShift Route object // endpointsFromOcpRoute extracts the endpoints from a OpenShift Route object
func endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation bool) []*endpoint.Endpoint { func (ors *ocpRouteSource) endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation bool) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint var endpoints []*endpoint.Endpoint
ttl, err := getTTLFromAnnotations(ocpRoute.Annotations) ttl, err := getTTLFromAnnotations(ocpRoute.Annotations)
@ -234,7 +241,7 @@ func endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation boo
targets := getTargetsFromTargetAnnotation(ocpRoute.Annotations) targets := getTargetsFromTargetAnnotation(ocpRoute.Annotations)
if len(targets) == 0 { if len(targets) == 0 {
targets = targetsFromOcpRouteStatus(ocpRoute.Status) targets = ors.targetsFromOcpRouteStatus(ocpRoute.Status)
} }
providerSpecific, setIdentifier := getProviderSpecificAnnotations(ocpRoute.Annotations) providerSpecific, setIdentifier := getProviderSpecificAnnotations(ocpRoute.Annotations)
@ -253,14 +260,18 @@ func endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation boo
return endpoints return endpoints
} }
func targetsFromOcpRouteStatus(status routev1.RouteStatus) endpoint.Targets { func (ors *ocpRouteSource) targetsFromOcpRouteStatus(status routev1.RouteStatus) endpoint.Targets {
var targets endpoint.Targets var targets endpoint.Targets
for _, ing := range status.Ingress { for _, ing := range status.Ingress {
if ing.RouterCanonicalHostname != "" { if len(ors.ocpRouterName) != 0 {
if ing.RouterName == ors.ocpRouterName {
targets = append(targets, ing.RouterCanonicalHostname)
return targets
}
} else if ing.RouterCanonicalHostname != "" {
targets = append(targets, ing.RouterCanonicalHostname) targets = append(targets, ing.RouterCanonicalHostname)
return targets
} }
} }
return targets return targets
} }

View File

@ -50,6 +50,7 @@ func (suite *OCPRouteSuite) SetupTest() {
false, false,
false, false,
labels.Everything(), labels.Everything(),
"",
) )
suite.routeWithTargets = &routev1.Route{ suite.routeWithTargets = &routev1.Route{
@ -147,6 +148,7 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) {
false, false,
false, false,
labelSelector, labelSelector,
"",
) )
if ti.expectError { if ti.expectError {
@ -160,8 +162,6 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) {
// testOcpRouteSourceEndpoints tests that various OCP routes generate the correct endpoints. // testOcpRouteSourceEndpoints tests that various OCP routes generate the correct endpoints.
func testOcpRouteSourceEndpoints(t *testing.T) { func testOcpRouteSourceEndpoints(t *testing.T) {
t.Parallel()
for _, tc := range []struct { for _, tc := range []struct {
title string title string
targetNamespace string targetNamespace string
@ -172,6 +172,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) {
expected []*endpoint.Endpoint expected []*endpoint.Endpoint
expectError bool expectError bool
labelFilter string labelFilter string
ocpRouterName string
}{ }{
{ {
title: "route with basic hostname and route status target", title: "route with basic hostname and route status target",
@ -196,6 +197,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) {
}, },
}, },
}, },
ocpRouterName: "",
expected: []*endpoint.Endpoint{ expected: []*endpoint.Endpoint{
{ {
DNSName: "my-domain.com", DNSName: "my-domain.com",
@ -206,6 +208,119 @@ func testOcpRouteSourceEndpoints(t *testing.T) {
}, },
expectError: false, expectError: false,
}, },
{
title: "route with basic hostname and route status target with one RouterCanonicalHostname and one ocpRouterNames defined",
targetNamespace: "",
annotationFilter: "",
fqdnTemplate: "",
ignoreHostnameAnnotation: false,
ocpRoute: &routev1.Route{
Spec: routev1.RouteSpec{
Host: "my-domain.com",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "route-with-target",
Annotations: map[string]string{},
},
Status: routev1.RouteStatus{
Ingress: []routev1.RouteIngress{
{
RouterName: "default",
RouterCanonicalHostname: "router-default.my-domain.com",
},
},
},
},
ocpRouterName: "default",
expected: []*endpoint.Endpoint{
{
DNSName: "my-domain.com",
Targets: []string{
"router-default.my-domain.com",
},
},
},
expectError: false,
},
{
title: "route with basic hostname and route status target with one RouterCanonicalHostname and one ocpRouterNames defined and two router canonical names",
targetNamespace: "",
annotationFilter: "",
fqdnTemplate: "",
ignoreHostnameAnnotation: false,
ocpRoute: &routev1.Route{
Spec: routev1.RouteSpec{
Host: "my-domain.com",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "route-with-target",
Annotations: map[string]string{},
},
Status: routev1.RouteStatus{
Ingress: []routev1.RouteIngress{
{
RouterName: "default",
RouterCanonicalHostname: "router-default.my-domain.com",
},
{
RouterName: "test",
RouterCanonicalHostname: "router-test.my-domain.com",
},
},
},
},
ocpRouterName: "default",
expected: []*endpoint.Endpoint{
{
DNSName: "my-domain.com",
Targets: []string{
"router-default.my-domain.com",
},
},
},
expectError: false,
},
{
title: "route with basic hostname and route status target with one RouterCanonicalHostname and one ocpRouterName defined and two router canonical names",
targetNamespace: "",
annotationFilter: "",
fqdnTemplate: "",
ignoreHostnameAnnotation: false,
ocpRoute: &routev1.Route{
Spec: routev1.RouteSpec{
Host: "my-domain.com",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "route-with-target",
Annotations: map[string]string{},
},
Status: routev1.RouteStatus{
Ingress: []routev1.RouteIngress{
{
RouterName: "default",
RouterCanonicalHostname: "router-default.my-domain.com",
},
{
RouterName: "test",
RouterCanonicalHostname: "router-test.my-domain.com",
},
},
},
},
ocpRouterName: "default",
expected: []*endpoint.Endpoint{
{
DNSName: "my-domain.com",
Targets: []string{
"router-default.my-domain.com",
},
},
},
expectError: false,
},
{ {
title: "route with incorrect externalDNS controller annotation", title: "route with incorrect externalDNS controller annotation",
targetNamespace: "", targetNamespace: "",
@ -221,8 +336,9 @@ func testOcpRouteSourceEndpoints(t *testing.T) {
}, },
}, },
}, },
expected: []*endpoint.Endpoint{}, ocpRouterName: "",
expectError: false, expected: []*endpoint.Endpoint{},
expectError: false,
}, },
{ {
title: "route with basic hostname and annotation target", title: "route with basic hostname and annotation target",
@ -242,6 +358,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) {
}, },
}, },
}, },
ocpRouterName: "",
expected: []*endpoint.Endpoint{ expected: []*endpoint.Endpoint{
{ {
DNSName: "my-annotation-domain.com", DNSName: "my-annotation-domain.com",
@ -273,6 +390,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) {
}, },
}, },
}, },
ocpRouterName: "",
expected: []*endpoint.Endpoint{ expected: []*endpoint.Endpoint{
{ {
DNSName: "my-annotation-domain.com", DNSName: "my-annotation-domain.com",
@ -304,17 +422,16 @@ func testOcpRouteSourceEndpoints(t *testing.T) {
}, },
}, },
}, },
expected: []*endpoint.Endpoint{}, ocpRouterName: "",
expectError: false, expected: []*endpoint.Endpoint{},
expectError: false,
}, },
} { } {
tc := tc tc := tc
t.Run(tc.title, func(t *testing.T) { t.Run(tc.title, func(t *testing.T) {
t.Parallel() t.Parallel()
// Create a Kubernetes testing client // Create a Kubernetes testing client
fakeClient := fake.NewSimpleClientset() fakeClient := fake.NewSimpleClientset()
_, err := fakeClient.RouteV1().Routes(tc.ocpRoute.Namespace).Create(context.Background(), tc.ocpRoute, metav1.CreateOptions{}) _, err := fakeClient.RouteV1().Routes(tc.ocpRoute.Namespace).Create(context.Background(), tc.ocpRoute, metav1.CreateOptions{})
require.NoError(t, err) require.NoError(t, err)
@ -329,7 +446,9 @@ func testOcpRouteSourceEndpoints(t *testing.T) {
false, false,
false, false,
labelSelector, labelSelector,
tc.ocpRouterName,
) )
require.NoError(t, err) require.NoError(t, err)
res, err := source.Endpoints(context.Background()) res, err := source.Endpoints(context.Background())

View File

@ -223,6 +223,7 @@ func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e
lastMergedEndpoint := len(mergedEndpoints) - 1 lastMergedEndpoint := len(mergedEndpoints) - 1
if mergedEndpoints[lastMergedEndpoint].DNSName == endpoints[i].DNSName && if mergedEndpoints[lastMergedEndpoint].DNSName == endpoints[i].DNSName &&
mergedEndpoints[lastMergedEndpoint].RecordType == endpoints[i].RecordType && mergedEndpoints[lastMergedEndpoint].RecordType == endpoints[i].RecordType &&
mergedEndpoints[lastMergedEndpoint].SetIdentifier == endpoints[i].SetIdentifier &&
mergedEndpoints[lastMergedEndpoint].RecordTTL == endpoints[i].RecordTTL { mergedEndpoints[lastMergedEndpoint].RecordTTL == endpoints[i].RecordTTL {
mergedEndpoints[lastMergedEndpoint].Targets = append(mergedEndpoints[lastMergedEndpoint].Targets, endpoints[i].Targets[0]) mergedEndpoints[lastMergedEndpoint].Targets = append(mergedEndpoints[lastMergedEndpoint].Targets, endpoints[i].Targets[0])
} else { } else {

View File

@ -1085,7 +1085,7 @@ func testMultipleServicesEndpoints(t *testing.T) {
ignoreHostnameAnnotation bool ignoreHostnameAnnotation bool
labels map[string]string labels map[string]string
clusterIP string clusterIP string
hostnames map[string]string services map[string]map[string]string
serviceTypesFilter []string serviceTypesFilter []string
expected []*endpoint.Endpoint expected []*endpoint.Endpoint
expectError bool expectError bool
@ -1103,8 +1103,8 @@ func testMultipleServicesEndpoints(t *testing.T) {
false, false,
map[string]string{}, map[string]string{},
"", "",
map[string]string{ map[string]map[string]string{
"1.2.3.4": "foo.example.org", "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"},
}, },
[]string{}, []string{},
[]*endpoint.Endpoint{ []*endpoint.Endpoint{
@ -1125,10 +1125,10 @@ func testMultipleServicesEndpoints(t *testing.T) {
false, false,
map[string]string{}, map[string]string{},
"", "",
map[string]string{ map[string]map[string]string{
"1.2.3.4": "foo.example.org", "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"},
"1.2.3.5": "foo.example.org", "1.2.3.5": {hostnameAnnotationKey: "foo.example.org"},
"1.2.3.6": "foo.example.org", "1.2.3.6": {hostnameAnnotationKey: "foo.example.org"},
}, },
[]string{}, []string{},
[]*endpoint.Endpoint{ []*endpoint.Endpoint{
@ -1149,14 +1149,14 @@ func testMultipleServicesEndpoints(t *testing.T) {
false, false,
map[string]string{}, map[string]string{},
"", "",
map[string]string{ map[string]map[string]string{
"1.2.3.5": "foo.example.org", "1.2.3.5": {hostnameAnnotationKey: "foo.example.org"},
"10.1.1.3": "bar.example.org", "10.1.1.3": {hostnameAnnotationKey: "bar.example.org"},
"10.1.1.1": "bar.example.org", "10.1.1.1": {hostnameAnnotationKey: "bar.example.org"},
"1.2.3.4": "foo.example.org", "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"},
"10.1.1.2": "bar.example.org", "10.1.1.2": {hostnameAnnotationKey: "bar.example.org"},
"20.1.1.1": "foobar.example.org", "20.1.1.1": {hostnameAnnotationKey: "foobar.example.org"},
"1.2.3.6": "foo.example.org", "1.2.3.6": {hostnameAnnotationKey: "foo.example.org"},
}, },
[]string{}, []string{},
[]*endpoint.Endpoint{ []*endpoint.Endpoint{
@ -1166,6 +1166,30 @@ func testMultipleServicesEndpoints(t *testing.T) {
}, },
false, false,
}, },
{
"test that services with different set-identifier do not get merged together",
"",
"",
"testing",
"foo",
v1.ServiceTypeLoadBalancer,
"",
"",
false,
false,
map[string]string{},
"",
map[string]map[string]string{
"a.elb.com": {hostnameAnnotationKey: "foo.example.org", SetIdentifierKey: "a"},
"b.elb.com": {hostnameAnnotationKey: "foo.example.org", SetIdentifierKey: "b"},
},
[]string{},
[]*endpoint.Endpoint{
{DNSName: "foo.example.org", Targets: endpoint.Targets{"a.elb.com"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/fooa.elb.com"}, SetIdentifier: "a"},
{DNSName: "foo.example.org", Targets: endpoint.Targets{"b.elb.com"}, Labels: map[string]string{endpoint.ResourceLabelKey: "service/testing/foob.elb.com"}, SetIdentifier: "b"},
},
false,
},
} { } {
tc := tc tc := tc
t.Run(tc.title, func(t *testing.T) { t.Run(tc.title, func(t *testing.T) {
@ -1175,12 +1199,9 @@ func testMultipleServicesEndpoints(t *testing.T) {
kubernetes := fake.NewSimpleClientset() kubernetes := fake.NewSimpleClientset()
// Create services to test against // Create services to test against
for serviceip, hostname := range tc.hostnames { for lb, annotations := range tc.services {
ingresses := []v1.LoadBalancerIngress{} ingresses := []v1.LoadBalancerIngress{}
ingresses = append(ingresses, v1.LoadBalancerIngress{IP: serviceip}) ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb})
annotations := make(map[string]string)
annotations[hostnameAnnotationKey] = hostname
service := &v1.Service{ service := &v1.Service{
Spec: v1.ServiceSpec{ Spec: v1.ServiceSpec{
@ -1189,7 +1210,7 @@ func testMultipleServicesEndpoints(t *testing.T) {
}, },
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Namespace: tc.svcNamespace, Namespace: tc.svcNamespace,
Name: tc.svcName + serviceip, Name: tc.svcName + lb,
Labels: tc.labels, Labels: tc.labels,
Annotations: annotations, Annotations: annotations,
}, },

View File

@ -68,6 +68,7 @@ type Config struct {
SkipperRouteGroupVersion string SkipperRouteGroupVersion string
RequestTimeout time.Duration RequestTimeout time.Duration
DefaultTargets []string DefaultTargets []string
OCPRouterName string
} }
// ClientGenerator provides clients // ClientGenerator provides clients
@ -255,7 +256,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewOcpRouteSource(ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter) return NewOcpRouteSource(ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter, cfg.OCPRouterName)
case "fake": case "fake":
return NewFakeSource(cfg.FQDNTemplate) return NewFakeSource(cfg.FQDNTemplate)
case "connector": case "connector":