diff --git a/.github/workflows/lint-test-chart.yaml b/.github/workflows/lint-test-chart.yaml index 7a06a393c..064d2de31 100644 --- a/.github/workflows/lint-test-chart.yaml +++ b/.github/workflows/lint-test-chart.yaml @@ -7,6 +7,7 @@ on: jobs: lint-test: + if: github.repository == 'kubernetes-sigs/external-dns' runs-on: ubuntu-latest steps: - name: Checkout @@ -36,7 +37,7 @@ jobs: fi - name: Run chart-testing (lint) - run: ct lint + run: ct lint --check-version-increment=false - name: Create Kind cluster uses: helm/kind-action@v1.2.0 diff --git a/.github/workflows/release-chart.yaml b/.github/workflows/release-chart.yaml index b72995986..34bd1e779 100644 --- a/.github/workflows/release-chart.yaml +++ b/.github/workflows/release-chart.yaml @@ -5,10 +5,11 @@ on: branches: - master paths: - - "charts/external-dns/**" + - "charts/external-dns/Chart.yaml" jobs: release: + if: github.repository == 'kubernetes-sigs/external-dns' runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index e170d95e0..f88ef1fc4 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -1,8 +1,6 @@ name: trivy vulnerability scanner on: push: - branches: - - master jobs: build: name: Build @@ -10,18 +8,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Build an image from Dockerfile run: | make build.docker - - - uses: cachix/install-nix-action@v13 - with: - nix_path: nixpkgs=channel:nixos-unstable - - uses: workflow/nix-shell-action@v1 - with: - packages: trivy - script: | - make build.docker - ./scripts/run-trivy.sh + - name: Run trivy + run: | + ./scripts/run-trivy.sh diff --git a/OWNERS b/OWNERS index 613f70f9d..50f90e684 100644 --- a/OWNERS +++ b/OWNERS @@ -4,6 +4,7 @@ approvers: - raffo - njuettner + - seanmalloy reviewers: - njuettner diff --git a/README.md b/README.md index 3271acfa4..a9b7d8cdb 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,13 @@ The following table clarifies the current status of the providers according to t | GoDaddy | Alpha | | | 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: The are two ways of running ExternalDNS: diff --git a/charts/OWNERS b/charts/OWNERS new file mode 100644 index 000000000..a5ec64441 --- /dev/null +++ b/charts/OWNERS @@ -0,0 +1,6 @@ +labels: + - chart +approvers: + - stevehipwell +reviewers: + - stevehipwell diff --git a/charts/external-dns/Chart.yaml b/charts/external-dns/Chart.yaml index fc0c979f6..a20666e1a 100644 --- a/charts/external-dns/Chart.yaml +++ b/charts/external-dns/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: external-dns description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers. type: application -version: 1.4.0 -appVersion: 0.10.1 +version: 1.7.0 +appVersion: 0.10.2 keywords: - kubernetes - external-dns @@ -17,5 +17,9 @@ maintainers: email: steve.hipwell@gmail.com annotations: artifacthub.io/changes: | + - kind: added + description: "Allow custom ClusterRole rules to be specified for sources without defaults." - 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." diff --git a/charts/external-dns/README.md b/charts/external-dns/README.md index e6b82ba5b..5bdb5c671 100644 --- a/charts/external-dns/README.md +++ b/charts/external-dns/README.md @@ -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. | Parameter | Description | Default | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | +|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------| | `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.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.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.additionalPermissions` | Additional permissions to be added to the cluster role. | `{}` | | `podLabels` | Labels 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_ | @@ -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. | `[]` | | `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_ | +| `service.annotations` | Annotations to add to the service. | `{}` | | `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. | `[]` | | `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. | `[]` | diff --git a/charts/external-dns/templates/clusterrole.yaml b/charts/external-dns/templates/clusterrole.yaml index e46a66dd7..7ad8da0b5 100644 --- a/charts/external-dns/templates/clusterrole.yaml +++ b/charts/external-dns/templates/clusterrole.yaml @@ -6,13 +6,98 @@ metadata: labels: {{- include "external-dns.labels" . | nindent 4 }} rules: - - apiGroups: [""] - resources: ["services","endpoints","pods"] - verbs: ["get","watch","list"] - - apiGroups: ["extensions","networking.k8s.io"] - resources: ["ingresses"] - verbs: ["get","watch","list"] +{{- 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) }} - apiGroups: [""] resources: ["nodes"] verbs: ["list","watch"] {{- 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 }} diff --git a/charts/external-dns/templates/service.yaml b/charts/external-dns/templates/service.yaml index 0c0c000bd..174e3dce0 100644 --- a/charts/external-dns/templates/service.yaml +++ b/charts/external-dns/templates/service.yaml @@ -4,6 +4,10 @@ metadata: name: {{ include "external-dns.fullname" . }} labels: {{- include "external-dns.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: type: ClusterIP selector: diff --git a/charts/external-dns/values.yaml b/charts/external-dns/values.yaml index d949d578c..c8730e365 100644 --- a/charts/external-dns/values.yaml +++ b/charts/external-dns/values.yaml @@ -24,6 +24,7 @@ serviceAccount: rbac: # Specifies whether RBAC resources should be created create: true + additionalPermissions: {} podLabels: {} @@ -73,6 +74,7 @@ readinessProbe: service: port: 7979 + annotations: {} extraVolumes: [] diff --git a/cloudbuild.yaml b/cloudbuild.yaml index c6629fd0c..52576cc5b 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -3,7 +3,7 @@ timeout: 5000s options: substitution_option: ALLOW_LOOSE 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 env: - DOCKER_CLI_EXPERIMENTAL=enabled diff --git a/controller/controller.go b/controller/controller.go index 7d083cce1..d609c089d 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -94,6 +94,30 @@ var ( 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() { @@ -105,6 +129,9 @@ func init() { prometheus.MustRegister(deprecatedRegistryErrors) prometheus.MustRegister(deprecatedSourceErrors) prometheus.MustRegister(controllerNoChangesTotal) + prometheus.MustRegister(registryARecords) + prometheus.MustRegister(sourceARecords) + prometheus.MustRegister(verifiedARecords) } // Controller is responsible for orchestrating the different components. @@ -141,7 +168,8 @@ func (c *Controller) RunOnce(ctx context.Context) error { return err } registryEndpointsTotal.Set(float64(len(records))) - + regARecords := filterARecords(records) + registryARecords.Set(float64(len(regARecords))) ctx = context.WithValue(ctx, provider.RecordsContextKey, records) endpoints, err := c.Source.Endpoints(ctx) @@ -151,7 +179,10 @@ func (c *Controller) RunOnce(ctx context.Context) error { return err } sourceEndpointsTotal.Set(float64(len(endpoints))) - + srcARecords := filterARecords(endpoints) + sourceARecords.Set(float64(len(srcARecords))) + vRecords := fetchMatchingARecords(endpoints, records) + verifiedARecords.Set(float64(len(vRecords))) endpoints = c.Registry.AdjustEndpoints(endpoints) plan := &plan.Plan{ @@ -181,6 +212,32 @@ func (c *Controller) RunOnce(ctx context.Context) error { return nil } +// Checks and returns the intersection of A records in endpoint and registry. +func fetchMatchingARecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) []string { + aRecords := filterARecords(endpoints) + recordsMap := make(map[string]struct{}) + 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. func (c *Controller) ScheduleRunOnce(now time.Time) { c.nextRunAtMux.Lock() diff --git a/controller/controller_test.go b/controller/controller_test.go index dff2a6f62..27d01777c 100644 --- a/controller/controller_test.go +++ b/controller/controller_test.go @@ -19,6 +19,8 @@ package controller import ( "context" "errors" + "github.com/prometheus/client_golang/prometheus" + "math" "reflect" "testing" "time" @@ -49,6 +51,10 @@ type filteredMockProvider struct { ApplyChangesCalls []*plan.Changes } +type errorMockProvider struct { + mockProvider +} + func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilterInterface { return p.domainFilter } @@ -70,6 +76,10 @@ func (p *mockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error 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. func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { if len(changes.Create) != len(p.ExpectChanges.Create) { @@ -180,6 +190,13 @@ func TestRunOnce(t *testing.T) { // Validate that the mock source was called. 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) { @@ -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)) +} diff --git a/docs/contributing/chart.md b/docs/contributing/chart.md new file mode 100644 index 000000000..da16c6d14 --- /dev/null +++ b/docs/contributing/chart.md @@ -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. diff --git a/docs/faq.md b/docs/faq.md index be9ac3b63..9cda9c59f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -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_source_endpoints_total | Number of Endpoints in the registry | Gauge | | external_dns_source_errors_total | Number of Source errors | Counter | +| external_dns_controller_verified_records | Number of DNS A-records that exists both in | Gauge | +| | source & registry | | +| 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? diff --git a/docs/release.md b/docs/release.md index dcfe0de5d..e4ce413b5 100644 --- a/docs/release.md +++ b/docs/release.md @@ -24,10 +24,21 @@ You must be an official maintainer of the project to be able to do a release. ### 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`. - 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`). - 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. - 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 diff --git a/docs/tutorials/alb-ingress.md b/docs/tutorials/alb-ingress.md index 0d818ac54..0feee763a 100644 --- a/docs/tutorials/alb-ingress.md +++ b/docs/tutorials/alb-ingress.md @@ -83,14 +83,18 @@ metadata: kubernetes.io/ingress.class: alb name: echoserver spec: + ingressClassName: alb rules: - host: echoserver.mycluster.example.org http: &echoserver_root paths: - - backend: - serviceName: echoserver - servicePort: 80 - path: / + - path: / + backend: + service: + name: echoserver + port: + number: 80 + pathType: Prefix - host: echoserver.example.org http: *echoserver_root ``` @@ -119,13 +123,17 @@ metadata: kubernetes.io/ingress.class: alb name: echoserver spec: + ingressClassName: alb rules: - http: paths: - - backend: - serviceName: echoserver - servicePort: 80 - path: / + - path: / + backend: + service: + name: echoserver + port: + number: 80 + pathType: Prefix ``` 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 name: echoserver spec: + ingressClassName: alb rules: - host: echoserver.example.org http: paths: - - backend: - serviceName: echoserver - servicePort: 80 - path: / + - path: / + backend: + service: + name: echoserver + port: + number: 80 + pathType: Prefix ``` The above Ingress object will result in the creation of an ALB with a dualstack diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index 75dfbfc6f..a7773ad4a 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -55,7 +55,7 @@ If your EKS-managed cluster is >= 1.13 and was created after 2019-09-04, refer to the [Amazon EKS 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 -kiam or kube2iam. +kiam or kube2iam or set the environment variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY on the deployment. ### kiam @@ -464,6 +464,58 @@ $ aws route53 delete-hosted-zone --id /hostedzone/ZEWFWZ4R16P7IB ## 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). -Running several fast polling ExternalDNS instances in a given account can easily hit that limit. Some ways to circumvent that issue includes: -* Augment the synchronization interval (`--interval`), at the cost of slower changes propagation. -* 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`. +Running several fast polling ExternalDNS instances in a given account can easily hit that limit. Some ways to reduce the request rate include: +* Reduce the polling loop's synchronization interval at the possible cost of slower change propagation (but see `--events` below to reduce the impact). + * `--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 +``` diff --git a/docs/tutorials/bluecat.md b/docs/tutorials/bluecat.md index 4046bdc04..a6b88f78a 100644 --- a/docs/tutorials/bluecat.md +++ b/docs/tutorials/bluecat.md @@ -20,6 +20,10 @@ BlueCat Gateway username and password can be supplied using the configuration fi | rootZone | Yes | | 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 Setup configuration file as k8s `Secret`. ``` diff --git a/docs/tutorials/kube-ingress-aws.md b/docs/tutorials/kube-ingress-aws.md index 1b893eb0e..bea5e56ac 100644 --- a/docs/tutorials/kube-ingress-aws.md +++ b/docs/tutorials/kube-ingress-aws.md @@ -145,14 +145,18 @@ metadata: kubernetes.io/ingress.class: skipper name: echoserver spec: + ingressClassName: skipper rules: - host: echoserver.mycluster.example.org http: &echoserver_root paths: - - backend: - serviceName: echoserver - servicePort: 80 - path: / + - path: / + backend: + service: + name: echoserver + port: + number: 80 + pathType: Prefix - host: echoserver.example.org http: *echoserver_root ``` @@ -180,13 +184,17 @@ metadata: kubernetes.io/ingress.class: skipper name: echoserver spec: + ingressClassName: skipper rules: - http: paths: - - backend: - serviceName: echoserver - servicePort: 80 - path: / + - path: / + backend: + service: + name: echoserver + port: + number: 80 + pathType: Prefix ``` 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 name: echoserver spec: + ingressClassName: skipper rules: - host: echoserver.example.org http: paths: - - backend: - serviceName: echoserver - servicePort: 80 - path: / + - path: / + backend: + service: + name: echoserver + port: + number: 80 + pathType: Prefix ``` 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 name: echoserver spec: + ingressClassName: skipper rules: - host: echoserver.example.org http: paths: - - backend: - serviceName: echoserver - servicePort: 80 - path: / + - path: / + backend: + service: + name: echoserver + port: + number: 80 + pathType: Prefix ``` The above Ingress object will result in the creation of an NLB. A diff --git a/docs/tutorials/nginx-ingress.md b/docs/tutorials/nginx-ingress.md index 842adb44e..66b068ee7 100644 --- a/docs/tutorials/nginx-ingress.md +++ b/docs/tutorials/nginx-ingress.md @@ -297,13 +297,18 @@ metadata: annotations: kubernetes.io/ingress.class: nginx spec: + ingressClassName: nginx rules: - host: via-ingress.external-dns-test.gcp.zalan.do http: paths: - - backend: - serviceName: nginx - servicePort: 80 + - path: / + backend: + service: + name: nginx + port: + number: 80 + pathType: Prefix --- @@ -593,13 +598,18 @@ metadata: annotations: kubernetes.io/ingress.class: nginx spec: + ingressClassName: nginx rules: - host: via-ingress.external-dns-test.gcp.zalan.do http: paths: - - backend: - serviceName: nginx - servicePort: 80 + - path: / + backend: + service: + name: nginx + port: + number: 80 + pathType: Prefix --- apiVersion: v1 kind: Service diff --git a/docs/tutorials/openshift.md b/docs/tutorials/openshift.md index c90e029a0..2fd983ecd 100644 --- a/docs/tutorials/openshift.md +++ b/docs/tutorials/openshift.md @@ -2,6 +2,60 @@ This tutorial describes how to configure ExternalDNS to use the OpenShift Route source. 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 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! diff --git a/go.mod b/go.mod index c41477dbb..c8def2cf2 100644 --- a/go.mod +++ b/go.mod @@ -18,19 +18,19 @@ require ( github.com/aliyun/alibaba-cloud-sdk-go v1.61.357 github.com/aws/aws-sdk-go v1.40.53 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/datawire/ambassador v1.6.0 github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba github.com/digitalocean/godo v1.69.1 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/go-gandi/go-gandi v0.0.0-20200921091836-0d8a64b9cc09 github.com/go-logr/logr v1.1.0 // indirect github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f 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/infobloxopen/infoblox-go-client v1.1.1 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/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 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/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f github.com/sirupsen/logrus v1.8.1 diff --git a/go.sum b/go.sum index e1c8538f9..1e7583981 100644 --- a/go.sum +++ b/go.sum @@ -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/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/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/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/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= 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/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/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.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/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14= 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.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= 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.73.2/go.mod h1:Fpy/cIVjiUkI0DntkoFl4fZpaeqRFQ6SVcX1A0APg0E= +github.com/exoscale/egoscale v0.80.1 h1:+JR0RhGKjkgHIluWeRWTTnveSGIRE1ifbpnjB/Fkwlk= +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/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= 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/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.21.0 h1:21rxoQM7cSaZCPgfP45h71Vt1amZa942l7AtUWLOI2I= -github.com/gophercloud/gophercloud v0.21.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= +github.com/gophercloud/gophercloud v0.22.0 h1:9lFISNLafZcecT0xUveIMt3IafexC6DIV9ek1SZdSMw= +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-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg= 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.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.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 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.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.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= 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 v1.4.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/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/projectcontour/contour v1.18.1 h1:J1MmqchDFuou066Gkr13eYtPaBnfb5j7h5ETH9OfIfQ= -github.com/projectcontour/contour v1.18.1/go.mod h1:prL5IyPK2ek6MUWwm8C5S1pHsWOE/2Y8Ym7ak3feVpo= +github.com/projectcontour/contour v1.18.2 h1:q16Q0f8mb14DATpCus/qBONawBy1Zn4vrvBj1bXh3hc= +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.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= @@ -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.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 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/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 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-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-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-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo= diff --git a/kustomize/kustomization.yaml b/kustomize/kustomization.yaml index ac086b49c..df5f4aa2a 100644 --- a/kustomize/kustomization.yaml +++ b/kustomize/kustomization.yaml @@ -3,7 +3,7 @@ kind: Kustomization images: - name: k8s.gcr.io/external-dns/external-dns - newTag: v0.10.0 + newTag: v0.10.2 resources: - ./external-dns-deployment.yaml diff --git a/main.go b/main.go index 4a461e028..6b8fbbbdb 100644 --- a/main.go +++ b/main.go @@ -132,6 +132,7 @@ func main() { SkipperRouteGroupVersion: cfg.SkipperRouteGroupVersion, RequestTimeout: cfg.RequestTimeout, DefaultTargets: cfg.DefaultTargets, + OCPRouterName: cfg.OCPRouterName, } // Lookup all the selected sources by names and pass them the desired configuration. diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index a43e3bafd..b840999ad 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -176,6 +176,7 @@ type Config struct { GoDaddySecretKey string `secure:"yes"` GoDaddyTTL int64 GoDaddyOTE bool + OCPRouterName string } var defaultConfig = &Config{ @@ -363,8 +364,9 @@ func (cfg *Config) ParseFlags(args []string) error { // Flags related to Skipper RouteGroup 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("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("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) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 5266b9b28..5ecde3765 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -115,6 +115,7 @@ var ( DigitalOceanAPIPageSize: 50, ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, RFC2136BatchChangeSize: 50, + OCPRouterName: "default", } overriddenConfig = &Config{ @@ -225,6 +226,7 @@ func TestParseFlags(t *testing.T) { args: []string{ "--source=service", "--provider=google", + "--openshift-router-name=default", }, envVars: map[string]string{}, expected: minimalConfig, diff --git a/provider/aws/aws_test.go b/provider/aws/aws_test.go index cba42201a..c87de29f2 100644 --- a/provider/aws/aws_test.go +++ b/provider/aws/aws_test.go @@ -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("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-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.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("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-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.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificWeight, "20").WithProviderSpecific(providerSpecificHealthCheckID, "abc-def-healthcheck-id"), }) diff --git a/provider/azure/azure_test.go b/provider/azure/azure_test.go index fa5e2341d..1c58b2c3d 100644 --- a/provider/azure/azure_test.go +++ b/provider/azure/azure_test.go @@ -18,12 +18,10 @@ package azure import ( "context" - "strings" "testing" "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/azure" "github.com/Azure/go-autorest/autorest/to" "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) { provider, err := newMockedAzureProvider(endpoint.NewDomainFilter([]string{"nginx.example.com"}), endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s", "", &[]dns.Zone{ diff --git a/provider/azure/config.go b/provider/azure/config.go index 8e4d3870e..5b282af8b 100644 --- a/provider/azure/config.go +++ b/provider/azure/config.go @@ -19,7 +19,6 @@ package azure import ( "fmt" "io/ioutil" - "os" "strings" "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. if cfg.UseManagedIdentityExtension { 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 != "" { log.Infof("Resolving to user assigned identity, client id is %s.", cfg.UserAssignedIdentityID) diff --git a/provider/bluecat/bluecat.go b/provider/bluecat/bluecat.go index dc3b12edb..de6badb25 100644 --- a/provider/bluecat/bluecat.go +++ b/provider/bluecat/bluecat.go @@ -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") } - c := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.SkipTLSVerify}, - }} + c := newHTTPClient(cfg.SkipTLSVerify) resp, err := c.Post(cfg.GatewayHost+"/rest_login", "application/json", bytes.NewBuffer(body)) if err != nil { @@ -622,12 +619,8 @@ func getBluecatGatewayToken(cfg bluecatConfig) (string, http.Cookie, error) { } func (c GatewayClientConfig) getBluecatZones(zoneName string) ([]BluecatZone, error) { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) + zonePath := expandZone(zoneName) url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + zonePath 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 { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) zonePath := expandZone(zone) @@ -692,12 +680,7 @@ func (c GatewayClientConfig) getHostRecords(zone string, records *[]BluecatHostR } func (c GatewayClientConfig) getCNAMERecords(zone string, records *[]BluecatCNAMERecord) error { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) zonePath := expandZone(zone) @@ -724,12 +707,7 @@ func (c GatewayClientConfig) getCNAMERecords(zone string, records *[]BluecatCNAM } func (c GatewayClientConfig) getTXTRecords(zone string, records *[]BluecatTXTRecord) error { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) zonePath := expandZone(zone) @@ -757,12 +735,7 @@ func (c GatewayClientConfig) getTXTRecords(zone string, records *[]BluecatTXTRec } func (c GatewayClientConfig) getHostRecord(name string, record *BluecatHostRecord) error { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + @@ -785,12 +758,7 @@ func (c GatewayClientConfig) getHostRecord(name string, record *BluecatHostRecor } func (c GatewayClientConfig) getCNAMERecord(name string, record *BluecatCNAMERecord) error { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + @@ -813,12 +781,7 @@ func (c GatewayClientConfig) getCNAMERecord(name string, record *BluecatCNAMERec } func (c GatewayClientConfig) getTXTRecord(name string, record *BluecatTXTRecord) error { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/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) { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) zonePath := expandZone(zone) // 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) { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) zonePath := expandZone(zone) // 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) { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) zonePath := expandZone(zone) // 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) { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/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) { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/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 { - transportCfg := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: c.SkipTLSVerify}, - } - client := &http.Client{ - Transport: transportCfg, - } + client := newHTTPClient(c.SkipTLSVerify) url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/views/" + c.View + "/" + @@ -1042,3 +975,15 @@ func extractOwnerfromTXTRecord(propString string) (string, error) { } 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, + }, + }, + } +} diff --git a/provider/cloudflare/cloudflare.go b/provider/cloudflare/cloudflare.go index 033a0084f..a7cb6fdd7 100644 --- a/provider/cloudflare/cloudflare.go +++ b/provider/cloudflare/cloudflare.go @@ -43,6 +43,15 @@ const ( 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{ "LOC": 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. type cloudFlareDNS interface { - UserDetails() (cloudflare.User, error) + UserDetails(ctx context.Context) (cloudflare.User, 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) - ZoneDetails(zoneID string) (cloudflare.Zone, error) - DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) - CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) - DeleteDNSRecord(zoneID, recordID string) error - UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error + ZoneDetails(ctx context.Context, zoneID string) (cloudflare.Zone, error) + DNSRecords(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) + CreateDNSRecord(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) + DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error + UpdateDNSRecord(ctx context.Context, zoneID, recordID string, rr cloudflare.DNSRecord) error } type zoneService struct { service *cloudflare.API } -func (z zoneService) UserDetails() (cloudflare.User, error) { - return z.service.UserDetails() +func (z zoneService) UserDetails(ctx context.Context) (cloudflare.User, error) { + return z.service.UserDetails(ctx) } -func (z zoneService) ListZones(zoneID ...string) ([]cloudflare.Zone, error) { - return z.service.ListZones(zoneID...) +func (z zoneService) ListZones(ctx context.Context, zoneID ...string) ([]cloudflare.Zone, error) { + return z.service.ListZones(ctx, zoneID...) } func (z zoneService) ZoneIDByName(zoneName string) (string, error) { return z.service.ZoneIDByName(zoneName) } -func (z zoneService) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) { - return z.service.CreateDNSRecord(zoneID, rr) +func (z zoneService) CreateDNSRecord(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) { + return z.service.CreateDNSRecord(ctx, zoneID, rr) } -func (z zoneService) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) { - return z.service.DNSRecords(zoneID, rr) +func (z zoneService) DNSRecords(ctx context.Context, zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) { + return z.service.DNSRecords(ctx, zoneID, rr) } -func (z zoneService) UpdateDNSRecord(zoneID, recordID string, rr cloudflare.DNSRecord) error { - return z.service.UpdateDNSRecord(zoneID, recordID, rr) +func (z zoneService) UpdateDNSRecord(ctx context.Context, zoneID, recordID string, rr cloudflare.DNSRecord) error { + return z.service.UpdateDNSRecord(ctx, zoneID, recordID, rr) } -func (z zoneService) DeleteDNSRecord(zoneID, recordID string) error { - return z.service.DeleteDNSRecord(zoneID, recordID) +func (z zoneService) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error { + return z.service.DeleteDNSRecord(ctx, zoneID, recordID) } func (z zoneService) ListZonesContext(ctx context.Context, opts ...cloudflare.ReqOption) (cloudflare.ZonesResponse, error) { return z.service.ListZonesContext(ctx, opts...) } -func (z zoneService) ZoneDetails(zoneID string) (cloudflare.Zone, error) { - return z.service.ZoneDetails(zoneID) +func (z zoneService) ZoneDetails(ctx context.Context, zoneID string) (cloudflare.Zone, error) { + return z.service.ZoneDetails(ctx, zoneID) } // 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") for _, zoneID := range p.zoneIDFilter.ZoneIDs { log.Debugf("looking up zone %s", zoneID) - detailResponse, err := p.Client.ZoneDetails(zoneID) + detailResponse, err := p.Client.ZoneDetails(ctx, zoneID) if err != nil { log.Errorf("zone %s lookup failed, %v", zoneID, err) continue @@ -177,24 +186,20 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro } 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 { - if !p.domainFilter.Match(zone.Name) { - log.Debugf("zone %s not in domain filter", zone.Name) - continue - } - result = append(result, zone) - } - if p.PaginationOptions.Page == zonesResponse.ResultInfo.TotalPages { - break - } - p.PaginationOptions.Page++ + zonesResponse, err := p.Client.ListZonesContext(ctx) + if err != nil { + return nil, err } + + 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 } @@ -207,7 +212,7 @@ func (p *CloudFlareProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, endpoints := []*endpoint.Endpoint{} 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 { return nil, err } @@ -281,7 +286,7 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud changesByZone := p.changesByZone(zones, changes) 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 { 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) continue } - err := p.Client.UpdateDNSRecord(zoneID, recordID, change.ResourceRecord) + err := p.Client.UpdateDNSRecord(ctx, zoneID, recordID, change.ResourceRecord) if err != nil { 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) continue } - err := p.Client.DeleteDNSRecord(zoneID, recordID) + err := p.Client.DeleteDNSRecord(ctx, zoneID, recordID) if err != nil { log.WithFields(logFields).Errorf("failed to delete record: %v", err) } } else if change.Action == cloudFlareCreate { - _, err := p.Client.CreateDNSRecord(zoneID, change.ResourceRecord) + _, err := p.Client.CreateDNSRecord(ctx, zoneID, change.ResourceRecord) if err != nil { 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) } - if len(endpoint.Targets) > 1 { - log.Errorf("Updates should have just one target") - } - return &cloudFlareChange{ Action: action, ResourceRecord: cloudflare.DNSRecord{ Name: endpoint.DNSName, TTL: ttl, - Proxied: proxied, + Proxied: &proxied, Type: endpoint.RecordType, Content: target, }, @@ -450,8 +451,15 @@ func groupByNameAndType(records []cloudflare.DNSRecord) []*endpoint.Endpoint { records[0].Type, endpoint.TTL(records[0].TTL), targets...). - WithProviderSpecific(source.CloudflareProxiedKey, strconv.FormatBool(records[0].Proxied))) + WithProviderSpecific(source.CloudflareProxiedKey, strconv.FormatBool(*records[0].Proxied)), + ) } 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 +} diff --git a/provider/cloudflare/cloudflare_test.go b/provider/cloudflare/cloudflare_test.go index fae840b2b..f3c0970fd 100644 --- a/provider/cloudflare/cloudflare_test.go +++ b/provider/cloudflare/cloudflare_test.go @@ -55,7 +55,7 @@ var ExampleDomain = []cloudflare.DNSRecord{ Type: endpoint.RecordTypeA, TTL: 120, Content: "1.2.3.4", - Proxied: false, + Proxied: proxyDisabled, }, { ID: "2345678901", @@ -64,7 +64,7 @@ var ExampleDomain = []cloudflare.DNSRecord{ Type: endpoint.RecordTypeA, TTL: 120, Content: "3.4.5.6", - Proxied: false, + Proxied: proxyDisabled, }, { ID: "1231231233", @@ -73,7 +73,7 @@ var ExampleDomain = []cloudflare.DNSRecord{ Type: endpoint.RecordTypeA, TTL: 1, Content: "2.3.4.5", - Proxied: false, + Proxied: proxyDisabled, }, } @@ -105,7 +105,7 @@ func NewMockCloudFlareClientWithRecords(records map[string][]cloudflare.DNSRecor 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{ Name: "Create", ZoneId: zoneID, @@ -118,7 +118,7 @@ func (m *mockCloudFlareClient) CreateDNSRecord(zoneID string, rr cloudflare.DNSR 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 { return nil, m.dnsRecordsError } @@ -132,7 +132,7 @@ func (m *mockCloudFlareClient) DNSRecords(zoneID string, rr cloudflare.DNSRecord 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{ Name: "Update", ZoneId: zoneID, @@ -147,7 +147,7 @@ func (m *mockCloudFlareClient) UpdateDNSRecord(zoneID, recordID string, rr cloud 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{ Name: "Delete", ZoneId: zoneID, @@ -162,7 +162,7 @@ func (m *mockCloudFlareClient) DeleteDNSRecord(zoneID, recordID string) error { return nil } -func (m *mockCloudFlareClient) UserDetails() (cloudflare.User, error) { +func (m *mockCloudFlareClient) UserDetails(ctx context.Context) (cloudflare.User, error) { return m.User, nil } @@ -176,7 +176,7 @@ func (m *mockCloudFlareClient) ZoneIDByName(zoneName string) (string, error) { 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 { return nil, m.listZonesError } @@ -216,7 +216,7 @@ func (m *mockCloudFlareClient) ListZonesContext(ctx context.Context, opts ...clo }, 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 { if zoneID == id { return cloudflare.Zone{ @@ -292,7 +292,7 @@ func TestCloudflareA(t *testing.T) { Name: "bar.com", Content: "127.0.0.1", TTL: 1, - Proxied: false, + Proxied: proxyDisabled, }, }, { @@ -303,7 +303,7 @@ func TestCloudflareA(t *testing.T) { Name: "bar.com", Content: "127.0.0.2", TTL: 1, - Proxied: false, + Proxied: proxyDisabled, }, }, }, @@ -330,7 +330,7 @@ func TestCloudflareCname(t *testing.T) { Name: "cname.bar.com", Content: "google.com", TTL: 1, - Proxied: false, + Proxied: proxyDisabled, }, }, { @@ -341,7 +341,7 @@ func TestCloudflareCname(t *testing.T) { Name: "cname.bar.com", Content: "facebook.com", TTL: 1, - Proxied: false, + Proxied: proxyDisabled, }, }, }, @@ -368,7 +368,7 @@ func TestCloudflareCustomTTL(t *testing.T) { Name: "ttl.bar.com", Content: "127.0.0.1", TTL: 120, - Proxied: false, + Proxied: proxyDisabled, }, }, }, @@ -394,7 +394,7 @@ func TestCloudflareProxiedDefault(t *testing.T) { Name: "bar.com", Content: "127.0.0.1", TTL: 1, - Proxied: true, + Proxied: proxyEnabled, }, }, }, @@ -426,7 +426,7 @@ func TestCloudflareProxiedOverrideTrue(t *testing.T) { Name: "bar.com", Content: "127.0.0.1", TTL: 1, - Proxied: true, + Proxied: proxyEnabled, }, }, }, @@ -458,7 +458,7 @@ func TestCloudflareProxiedOverrideFalse(t *testing.T) { Name: "bar.com", Content: "127.0.0.1", TTL: 1, - Proxied: false, + Proxied: proxyDisabled, }, }, }, @@ -490,7 +490,7 @@ func TestCloudflareProxiedOverrideIllegal(t *testing.T) { Name: "bar.com", Content: "127.0.0.1", TTL: 1, - Proxied: true, + Proxied: proxyEnabled, }, }, }, @@ -499,19 +499,21 @@ func TestCloudflareProxiedOverrideIllegal(t *testing.T) { } func TestCloudflareSetProxied(t *testing.T) { + var proxied *bool = proxyEnabled + var notProxied *bool = proxyDisabled var testCases = []struct { recordType string domain string - proxiable bool + proxiable *bool }{ - {"A", "bar.com", true}, - {"CNAME", "bar.com", true}, - {"TXT", "bar.com", false}, - {"MX", "bar.com", false}, - {"NS", "bar.com", false}, - {"SPF", "bar.com", false}, - {"SRV", "bar.com", false}, - {"A", "*.bar.com", false}, + {"A", "bar.com", proxied}, + {"CNAME", "bar.com", proxied}, + {"TXT", "bar.com", notProxied}, + {"MX", "bar.com", notProxied}, + {"NS", "bar.com", notProxied}, + {"SPF", "bar.com", notProxied}, + {"SRV", "bar.com", notProxied}, + {"A", "*.bar.com", notProxied}, } for _, testCase := range testCases { @@ -684,6 +686,7 @@ func TestCloudflareApplyChanges(t *testing.T) { Name: "new.bar.com", Content: "target", TTL: 1, + Proxied: proxyDisabled, }, }, { @@ -693,6 +696,7 @@ func TestCloudflareApplyChanges(t *testing.T) { Name: "foobar.bar.com", Content: "target-new", TTL: 1, + Proxied: proxyDisabled, }, }, }) @@ -779,6 +783,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Type: endpoint.RecordTypeA, Content: "10.10.10.1", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, }, ExpectedEndpoints: []*endpoint.Endpoint{ @@ -805,12 +810,14 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Type: endpoint.RecordTypeA, Content: "10.10.10.1", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, { Name: "foo.com", Type: endpoint.RecordTypeA, Content: "10.10.10.2", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, }, ExpectedEndpoints: []*endpoint.Endpoint{ @@ -837,24 +844,28 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Type: endpoint.RecordTypeA, Content: "10.10.10.1", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, { Name: "foo.com", Type: endpoint.RecordTypeA, Content: "10.10.10.2", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, { Name: "bar.de", Type: endpoint.RecordTypeA, Content: "10.10.10.1", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, { Name: "bar.de", Type: endpoint.RecordTypeA, Content: "10.10.10.2", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, }, ExpectedEndpoints: []*endpoint.Endpoint{ @@ -894,18 +905,21 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Type: endpoint.RecordTypeA, Content: "10.10.10.1", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, { Name: "foo.com", Type: endpoint.RecordTypeA, Content: "10.10.10.2", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, { Name: "bar.de", Type: endpoint.RecordTypeA, Content: "10.10.10.1", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, }, ExpectedEndpoints: []*endpoint.Endpoint{ @@ -945,18 +959,21 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Type: endpoint.RecordTypeA, Content: "10.10.10.1", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, { Name: "foo.com", Type: endpoint.RecordTypeA, Content: "10.10.10.2", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, { Name: "bar.de", Type: "NOT SUPPORTED", Content: "10.10.10.1", TTL: defaultCloudFlareRecordTTL, + Proxied: proxyDisabled, }, }, ExpectedEndpoints: []*endpoint.Endpoint{ @@ -984,94 +1001,101 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { func TestProviderPropertiesIdempotency(t *testing.T) { testCases := []struct { + Name string ProviderProxiedByDefault bool - RecordsAreProxied bool + RecordsAreProxied *bool ShouldBeUpdated bool }{ { + Name: "ProxyDefault: false, ShouldBeProxied: false, ExpectUpdates: false", ProviderProxiedByDefault: false, - RecordsAreProxied: false, + RecordsAreProxied: proxyDisabled, ShouldBeUpdated: false, }, { + Name: "ProxyDefault: true, ShouldBeProxied: true, ExpectUpdates: false", ProviderProxiedByDefault: true, - RecordsAreProxied: true, + RecordsAreProxied: proxyEnabled, ShouldBeUpdated: false, }, { + Name: "ProxyDefault: true, ShouldBeProxied: false, ExpectUpdates: true", ProviderProxiedByDefault: true, - RecordsAreProxied: false, + RecordsAreProxied: proxyDisabled, ShouldBeUpdated: true, }, { + Name: "ProxyDefault: false, ShouldBeProxied: true, ExpectUpdates: true", ProviderProxiedByDefault: false, - RecordsAreProxied: true, + RecordsAreProxied: proxyEnabled, ShouldBeUpdated: true, }, } for _, test := range testCases { - client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{ - "001": { - { - ID: "1234567890", - ZoneID: "001", - Name: "foobar.bar.com", - Type: endpoint.RecordTypeA, - TTL: 120, - Content: "1.2.3.4", - Proxied: test.RecordsAreProxied, + t.Run(test.Name, func(t *testing.T) { + client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{ + "001": { + { + ID: "1234567890", + ZoneID: "001", + Name: "foobar.bar.com", + Type: endpoint.RecordTypeA, + TTL: 120, + 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{ - Current: current, - Desired: desired, - PropertyComparator: provider.PropertyValuesEqual, - ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}, - } + provider := &CloudFlareProvider{ + Client: client, + proxiedByDefault: test.ProviderProxiedByDefault, + } + ctx := context.Background() - 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") + current, err := provider.Records(ctx) + if err != nil { + t.Errorf("should not fail, %s", err) + } + assert.Equal(t, 1, len(current)) - 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") - } + 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{ + 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", Content: "2.3.4.5", TTL: 1, - Proxied: true, + Proxied: proxyEnabled, }, }, MockAction{ @@ -1141,7 +1165,7 @@ func TestCloudflareComplexUpdate(t *testing.T) { Type: "A", Content: "1.2.3.4", TTL: 1, - Proxied: true, + Proxied: proxyEnabled, }, }, MockAction{ @@ -1162,7 +1186,7 @@ func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) { Type: endpoint.RecordTypeA, TTL: 1, Content: "1.2.3.4", - Proxied: true, + Proxied: proxyEnabled, }, }, }) diff --git a/provider/rfc2136/rfc2136.go b/provider/rfc2136/rfc2136.go index 50ee6e7b5..21fab5f88 100644 --- a/provider/rfc2136/rfc2136.go +++ b/provider/rfc2136/rfc2136.go @@ -176,6 +176,9 @@ OuterLoop: case dns.TypeTXT: rrValues = (rr.(*dns.TXT).Txt) rrType = "TXT" + case dns.TypeNS: + rrValues = []string{rr.(*dns.NS).Ns} + rrType = "NS" default: continue // Unhandled record type } diff --git a/provider/rfc2136/rfc2136_test.go b/provider/rfc2136/rfc2136_test.go index 89e3b9012..bc8a7106f 100644 --- a/provider/rfc2136/rfc2136_test.go +++ b/provider/rfc2136/rfc2136_test.go @@ -172,6 +172,11 @@ func TestRfc2136ApplyChanges(t *testing.T) { RecordType: "TXT", Targets: []string{"boom"}, }, + { + DNSName: "ns.foobar.com", + RecordType: "NS", + Targets: []string{"boom"}, + }, }, Delete: []*endpoint.Endpoint{ { @@ -190,13 +195,16 @@ func TestRfc2136ApplyChanges(t *testing.T) { err = provider.ApplyChanges(context.Background(), p) 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(), "1.2.3.4")) assert.True(t, strings.Contains(stub.createMsgs[1].String(), "v1.foobar.com")) assert.True(t, strings.Contains(stub.createMsgs[1].String(), "boom")) + assert.True(t, strings.Contains(stub.createMsgs[2].String(), "ns.foobar.com")) + assert.True(t, strings.Contains(stub.createMsgs[2].String(), "boom")) + 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[1].String(), "v2.foobar.com")) diff --git a/scripts/run-trivy.sh b/scripts/run-trivy.sh index 8fdcc791e..b84dad170 100755 --- a/scripts/run-trivy.sh +++ b/scripts/run-trivy.sh @@ -1,3 +1,13 @@ #! /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) diff --git a/source/openshift_route.go b/source/openshift_route.go index 4f27918fc..82cd7edff 100644 --- a/source/openshift_route.go +++ b/source/openshift_route.go @@ -49,6 +49,7 @@ type ocpRouteSource struct { ignoreHostnameAnnotation bool routeInformer routeInformer.RouteInformer labelSelector labels.Selector + ocpRouterName string } // NewOcpRouteSource creates a new ocpRouteSource with the given config. @@ -60,6 +61,7 @@ func NewOcpRouteSource( combineFQDNAnnotation bool, ignoreHostnameAnnotation bool, labelSelector labels.Selector, + ocpRouterName string, ) (Source, error) { tmpl, err := parseTemplate(fqdnTemplate) if err != nil { @@ -96,11 +98,16 @@ func NewOcpRouteSource( ignoreHostnameAnnotation: ignoreHostnameAnnotation, routeInformer: informer, labelSelector: labelSelector, + ocpRouterName: ocpRouterName, }, nil } -// TODO add a meaningful EventHandler 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. @@ -128,7 +135,7 @@ func (ors *ocpRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, continue } - orEndpoints := endpointsFromOcpRoute(ocpRoute, ors.ignoreHostnameAnnotation) + orEndpoints := ors.endpointsFromOcpRoute(ocpRoute, ors.ignoreHostnameAnnotation) // apply template if host is missing on OpenShift Route 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) if len(targets) == 0 { - targets = targetsFromOcpRouteStatus(ocpRoute.Status) + targets = ors.targetsFromOcpRouteStatus(ocpRoute.Status) } 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 -func endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation bool) []*endpoint.Endpoint { +func (ors *ocpRouteSource) endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation bool) []*endpoint.Endpoint { var endpoints []*endpoint.Endpoint ttl, err := getTTLFromAnnotations(ocpRoute.Annotations) @@ -234,7 +241,7 @@ func endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation boo targets := getTargetsFromTargetAnnotation(ocpRoute.Annotations) if len(targets) == 0 { - targets = targetsFromOcpRouteStatus(ocpRoute.Status) + targets = ors.targetsFromOcpRouteStatus(ocpRoute.Status) } providerSpecific, setIdentifier := getProviderSpecificAnnotations(ocpRoute.Annotations) @@ -253,14 +260,18 @@ func endpointsFromOcpRoute(ocpRoute *routev1.Route, ignoreHostnameAnnotation boo return endpoints } -func targetsFromOcpRouteStatus(status routev1.RouteStatus) endpoint.Targets { +func (ors *ocpRouteSource) targetsFromOcpRouteStatus(status routev1.RouteStatus) endpoint.Targets { var targets endpoint.Targets - 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) + return targets } } - return targets } diff --git a/source/openshift_route_test.go b/source/openshift_route_test.go index f2307a5e3..7dc851990 100644 --- a/source/openshift_route_test.go +++ b/source/openshift_route_test.go @@ -50,6 +50,7 @@ func (suite *OCPRouteSuite) SetupTest() { false, false, labels.Everything(), + "", ) suite.routeWithTargets = &routev1.Route{ @@ -147,6 +148,7 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) { false, false, labelSelector, + "", ) if ti.expectError { @@ -160,8 +162,6 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) { // testOcpRouteSourceEndpoints tests that various OCP routes generate the correct endpoints. func testOcpRouteSourceEndpoints(t *testing.T) { - t.Parallel() - for _, tc := range []struct { title string targetNamespace string @@ -172,6 +172,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) { expected []*endpoint.Endpoint expectError bool labelFilter string + ocpRouterName string }{ { title: "route with basic hostname and route status target", @@ -196,6 +197,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, }, }, + ocpRouterName: "", expected: []*endpoint.Endpoint{ { DNSName: "my-domain.com", @@ -206,6 +208,119 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, 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", targetNamespace: "", @@ -221,8 +336,9 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, }, }, - expected: []*endpoint.Endpoint{}, - expectError: false, + ocpRouterName: "", + expected: []*endpoint.Endpoint{}, + expectError: false, }, { title: "route with basic hostname and annotation target", @@ -242,6 +358,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, }, }, + ocpRouterName: "", expected: []*endpoint.Endpoint{ { DNSName: "my-annotation-domain.com", @@ -273,6 +390,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, }, }, + ocpRouterName: "", expected: []*endpoint.Endpoint{ { DNSName: "my-annotation-domain.com", @@ -304,17 +422,16 @@ func testOcpRouteSourceEndpoints(t *testing.T) { }, }, }, - expected: []*endpoint.Endpoint{}, - expectError: false, + ocpRouterName: "", + expected: []*endpoint.Endpoint{}, + expectError: false, }, } { tc := tc t.Run(tc.title, func(t *testing.T) { t.Parallel() - // Create a Kubernetes testing client fakeClient := fake.NewSimpleClientset() - _, err := fakeClient.RouteV1().Routes(tc.ocpRoute.Namespace).Create(context.Background(), tc.ocpRoute, metav1.CreateOptions{}) require.NoError(t, err) @@ -329,7 +446,9 @@ func testOcpRouteSourceEndpoints(t *testing.T) { false, false, labelSelector, + tc.ocpRouterName, ) + require.NoError(t, err) res, err := source.Endpoints(context.Background()) diff --git a/source/service.go b/source/service.go index b9707be34..e49ff2f49 100644 --- a/source/service.go +++ b/source/service.go @@ -223,6 +223,7 @@ func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e lastMergedEndpoint := len(mergedEndpoints) - 1 if mergedEndpoints[lastMergedEndpoint].DNSName == endpoints[i].DNSName && mergedEndpoints[lastMergedEndpoint].RecordType == endpoints[i].RecordType && + mergedEndpoints[lastMergedEndpoint].SetIdentifier == endpoints[i].SetIdentifier && mergedEndpoints[lastMergedEndpoint].RecordTTL == endpoints[i].RecordTTL { mergedEndpoints[lastMergedEndpoint].Targets = append(mergedEndpoints[lastMergedEndpoint].Targets, endpoints[i].Targets[0]) } else { diff --git a/source/service_test.go b/source/service_test.go index 38bf2a2d9..57a2056e8 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -1085,7 +1085,7 @@ func testMultipleServicesEndpoints(t *testing.T) { ignoreHostnameAnnotation bool labels map[string]string clusterIP string - hostnames map[string]string + services map[string]map[string]string serviceTypesFilter []string expected []*endpoint.Endpoint expectError bool @@ -1103,8 +1103,8 @@ func testMultipleServicesEndpoints(t *testing.T) { false, map[string]string{}, "", - map[string]string{ - "1.2.3.4": "foo.example.org", + map[string]map[string]string{ + "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"}, }, []string{}, []*endpoint.Endpoint{ @@ -1125,10 +1125,10 @@ func testMultipleServicesEndpoints(t *testing.T) { false, map[string]string{}, "", - map[string]string{ - "1.2.3.4": "foo.example.org", - "1.2.3.5": "foo.example.org", - "1.2.3.6": "foo.example.org", + map[string]map[string]string{ + "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"}, + "1.2.3.5": {hostnameAnnotationKey: "foo.example.org"}, + "1.2.3.6": {hostnameAnnotationKey: "foo.example.org"}, }, []string{}, []*endpoint.Endpoint{ @@ -1149,14 +1149,14 @@ func testMultipleServicesEndpoints(t *testing.T) { false, map[string]string{}, "", - map[string]string{ - "1.2.3.5": "foo.example.org", - "10.1.1.3": "bar.example.org", - "10.1.1.1": "bar.example.org", - "1.2.3.4": "foo.example.org", - "10.1.1.2": "bar.example.org", - "20.1.1.1": "foobar.example.org", - "1.2.3.6": "foo.example.org", + map[string]map[string]string{ + "1.2.3.5": {hostnameAnnotationKey: "foo.example.org"}, + "10.1.1.3": {hostnameAnnotationKey: "bar.example.org"}, + "10.1.1.1": {hostnameAnnotationKey: "bar.example.org"}, + "1.2.3.4": {hostnameAnnotationKey: "foo.example.org"}, + "10.1.1.2": {hostnameAnnotationKey: "bar.example.org"}, + "20.1.1.1": {hostnameAnnotationKey: "foobar.example.org"}, + "1.2.3.6": {hostnameAnnotationKey: "foo.example.org"}, }, []string{}, []*endpoint.Endpoint{ @@ -1166,6 +1166,30 @@ func testMultipleServicesEndpoints(t *testing.T) { }, 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 t.Run(tc.title, func(t *testing.T) { @@ -1175,12 +1199,9 @@ func testMultipleServicesEndpoints(t *testing.T) { kubernetes := fake.NewSimpleClientset() // Create services to test against - for serviceip, hostname := range tc.hostnames { + for lb, annotations := range tc.services { ingresses := []v1.LoadBalancerIngress{} - ingresses = append(ingresses, v1.LoadBalancerIngress{IP: serviceip}) - - annotations := make(map[string]string) - annotations[hostnameAnnotationKey] = hostname + ingresses = append(ingresses, v1.LoadBalancerIngress{IP: lb}) service := &v1.Service{ Spec: v1.ServiceSpec{ @@ -1189,7 +1210,7 @@ func testMultipleServicesEndpoints(t *testing.T) { }, ObjectMeta: metav1.ObjectMeta{ Namespace: tc.svcNamespace, - Name: tc.svcName + serviceip, + Name: tc.svcName + lb, Labels: tc.labels, Annotations: annotations, }, diff --git a/source/store.go b/source/store.go index b3dcf80b6..e2adfc4c4 100644 --- a/source/store.go +++ b/source/store.go @@ -68,6 +68,7 @@ type Config struct { SkipperRouteGroupVersion string RequestTimeout time.Duration DefaultTargets []string + OCPRouterName string } // ClientGenerator provides clients @@ -255,7 +256,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err if err != nil { 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": return NewFakeSource(cfg.FQDNTemplate) case "connector":