Showing preview only (5,279K chars total). Download the full file or copy to clipboard to get everything.
Repository: kubernetes-sigs/external-dns
Branch: master
Commit: 1c9913bbdbdd
Files: 537
Total size: 4.9 MB
Directory structure:
gitextract_h6x703iq/
├── .editorconfig
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── ---bug-report.md
│ │ ├── --enhancement-request.md
│ │ ├── -support-request.md
│ │ └── create-release.md
│ ├── dependabot.yml
│ ├── labeler.yml
│ ├── pull_request_template.md
│ ├── renovate-config.js
│ ├── renovate.json
│ └── workflows/
│ ├── OWNERS
│ ├── ci.yaml
│ ├── codeql-analysis.yaml
│ ├── dependency-update.yaml
│ ├── docs.yaml
│ ├── end-to-end-tests.yml
│ ├── gh-workflow-approve.yaml
│ ├── json-yaml-validate.yml
│ ├── lint-test-chart.yaml
│ ├── lint.yaml
│ ├── release-chart.yaml
│ ├── staging-image-tester.yaml
│ └── validate-crd.yml
├── .gitignore
├── .golangci.yml
├── .ko.yaml
├── .markdownlint.json
├── .pre-commit-config.yaml
├── .spectral.yaml
├── .zappr.yaml
├── CONTRIBUTING.md
├── LICENSE.md
├── Makefile
├── OWNERS
├── README.md
├── SECURITY_CONTACTS
├── api/
│ └── webhook.yaml
├── apis/
│ ├── OWNERS
│ ├── api.go
│ └── v1alpha1/
│ ├── api.go
│ ├── dnsendpoint.go
│ ├── groupversion_info.go
│ └── zz_generated.deepcopy.go
├── charts/
│ └── OWNERS
├── cloudbuild.yaml
├── code-of-conduct.md
├── config/
│ └── crd/
│ └── standard/
│ └── dnsendpoints.externaldns.k8s.io.yaml
├── controller/
│ ├── OWNERS
│ ├── controller.go
│ ├── controller_test.go
│ ├── events.go
│ ├── events_test.go
│ ├── execute.go
│ ├── execute_test.go
│ ├── metrics.go
│ └── metrics_test.go
├── docs/
│ ├── 20190708-external-dns-incubator.md
│ ├── OWNERS
│ ├── advanced/
│ │ ├── configuration-precedence.md
│ │ ├── domain-filter.md
│ │ ├── events.md
│ │ ├── fqdn-templating.md
│ │ ├── import-records.md
│ │ ├── nat64.md
│ │ ├── operational-best-practices.md
│ │ ├── rate-limits.md
│ │ ├── split-horizon.md
│ │ └── ttl.md
│ ├── annotations/
│ │ └── annotations.md
│ ├── contributing/
│ │ ├── bug-report.md
│ │ ├── chart.md
│ │ ├── design.md
│ │ ├── dev-guide.md
│ │ ├── index.md
│ │ ├── source-wrappers.md
│ │ └── sources-and-providers.md
│ ├── deprecation.md
│ ├── faq.md
│ ├── flags.md
│ ├── initial-design.md
│ ├── monitoring/
│ │ ├── index.md
│ │ └── metrics.md
│ ├── overrides/
│ │ └── partials/
│ │ └── copyright.html
│ ├── proposal/
│ │ ├── 001-leader-election.md
│ │ ├── 002-internal-ipv6-handling-rollback.md
│ │ ├── 003-dnsendpoint-graduation-to-beta.md
│ │ ├── 004-gateway-api-annotation-placement.md
│ │ ├── design-template.md
│ │ └── multi-target.md
│ ├── providers.md
│ ├── registry/
│ │ ├── dynamodb.md
│ │ ├── registry.md
│ │ └── txt.md
│ ├── release.md
│ ├── scripts/
│ │ ├── index.html.gotmpl
│ │ └── requirements.txt
│ ├── snippets/
│ │ ├── contributing/
│ │ │ ├── collect-extdns-info.sh
│ │ │ └── collect-resources.sh
│ │ ├── exoscale/
│ │ │ ├── extdns.yaml
│ │ │ ├── how-to-test.yaml
│ │ │ └── rbac.yaml
│ │ ├── security-context/
│ │ │ └── extdns-limited-privilege.yaml
│ │ ├── traefik-proxy/
│ │ │ ├── ingress-route-default.yaml
│ │ │ ├── ingress-route-public-private.yaml
│ │ │ ├── traefik-public-private-config.yaml
│ │ │ ├── with-cluster-rbac.yaml
│ │ │ └── without-rbac.yaml
│ │ └── tutorials/
│ │ └── coredns/
│ │ ├── coredns-groups.yaml
│ │ ├── etcd.yaml
│ │ ├── fixtures.yaml
│ │ ├── kind.yaml
│ │ ├── values-coredns.yaml
│ │ └── values-extdns-coredns.yaml
│ ├── sources/
│ │ ├── about.md
│ │ ├── crd/
│ │ │ ├── dnsendpoint-aws-example.yaml
│ │ │ └── dnsendpoint-example.yaml
│ │ ├── crd.md
│ │ ├── f5-transportserver.md
│ │ ├── f5-virtualserver.md
│ │ ├── gateway-api.md
│ │ ├── gateway.md
│ │ ├── gloo-proxy.md
│ │ ├── index.md
│ │ ├── ingress.md
│ │ ├── istio.md
│ │ ├── kong.md
│ │ ├── mx-record.md
│ │ ├── nodes.md
│ │ ├── ns-record.md
│ │ ├── openshift.md
│ │ ├── pod.md
│ │ ├── service.md
│ │ ├── traefik-proxy.md
│ │ ├── txt-record.md
│ │ └── unstructured.md
│ ├── tutorials/
│ │ ├── akamai-edgedns.md
│ │ ├── alibabacloud.md
│ │ ├── anexia-engine.md
│ │ ├── aws-filters.md
│ │ ├── aws-load-balancer-controller.md
│ │ ├── aws-public-private-route53.md
│ │ ├── aws-sd.md
│ │ ├── aws.md
│ │ ├── azure-private-dns.md
│ │ ├── azure.md
│ │ ├── civo.md
│ │ ├── cloudflare.md
│ │ ├── contour.md
│ │ ├── coredns-etcd.md
│ │ ├── coredns.md
│ │ ├── crd.md
│ │ ├── dnsimple.md
│ │ ├── exoscale.md
│ │ ├── externalname.md
│ │ ├── gandi.md
│ │ ├── gke-nginx.md
│ │ ├── gke.md
│ │ ├── godaddy.md
│ │ ├── hostport.md
│ │ ├── ionoscloud.md
│ │ ├── kops-dns-controller.md
│ │ ├── kube-ingress-aws.md
│ │ ├── linode.md
│ │ ├── myra.md
│ │ ├── ns1.md
│ │ ├── oracle.md
│ │ ├── ovh.md
│ │ ├── pdns.md
│ │ ├── pihole.md
│ │ ├── plural.md
│ │ ├── rfc2136.md
│ │ ├── scaleway.md
│ │ ├── security-context.md
│ │ ├── transip.md
│ │ └── webhook-provider.md
│ └── version-update-playbook.md
├── e2e/
│ ├── deployment.yaml
│ ├── provider/
│ │ ├── coredns.yaml
│ │ └── etcd.yaml
│ └── service.yaml
├── endpoint/
│ ├── OWNERS
│ ├── crypto.go
│ ├── crypto_test.go
│ ├── domain_filter.go
│ ├── domain_filter_test.go
│ ├── endpoint.go
│ ├── endpoint_benchmark_test.go
│ ├── endpoint_test.go
│ ├── labels.go
│ ├── labels_test.go
│ ├── target_filter.go
│ ├── target_filter_test.go
│ ├── utils.go
│ ├── utils_test.go
│ └── zz_generated.deepcopy.go
├── external-dns.code-workspace
├── go.mod
├── go.sum
├── go.tool.mod
├── go.tool.sum
├── internal/
│ ├── OWNERS
│ ├── config/
│ │ └── config.go
│ ├── flags/
│ │ ├── binders.go
│ │ └── binders_test.go
│ ├── gen/
│ │ └── docs/
│ │ ├── flags/
│ │ │ ├── main.go
│ │ │ ├── main_test.go
│ │ │ └── templates/
│ │ │ └── flags.gotpl
│ │ ├── metrics/
│ │ │ ├── main.go
│ │ │ ├── main_test.go
│ │ │ └── templates/
│ │ │ └── metrics.gotpl
│ │ ├── render/
│ │ │ ├── render.go
│ │ │ └── render_test.go
│ │ └── sources/
│ │ ├── main.go
│ │ ├── main_test.go
│ │ └── templates/
│ │ └── sources.gotpl
│ ├── idna/
│ │ ├── idna.go
│ │ └── idna_test.go
│ ├── testresources/
│ │ ├── ca.pem
│ │ ├── client-cert-key.pem
│ │ └── client-cert.pem
│ └── testutils/
│ ├── endpoint.go
│ ├── endpoint_test.go
│ ├── env.go
│ ├── helpers.go
│ ├── helpers_test.go
│ ├── init.go
│ ├── log/
│ │ └── log.go
│ ├── metrics.go
│ └── mock_source.go
├── kustomize/
│ ├── OWNERS
│ ├── external-dns-clusterrole.yaml
│ ├── external-dns-clusterrolebinding.yaml
│ ├── external-dns-deployment.yaml
│ ├── external-dns-serviceaccount.yaml
│ └── kustomization.yaml
├── main.go
├── mkdocs.yml
├── pkg/
│ ├── OWNERS
│ ├── apis/
│ │ ├── OWNERS
│ │ └── externaldns/
│ │ ├── constants.go
│ │ ├── types.go
│ │ ├── types_test.go
│ │ ├── validation/
│ │ │ ├── validation.go
│ │ │ └── validation_test.go
│ │ ├── version.go
│ │ └── version_test.go
│ ├── client/
│ │ ├── OWNERS
│ │ ├── config.go
│ │ └── config_test.go
│ ├── events/
│ │ ├── OWNERS
│ │ ├── controller.go
│ │ ├── controller_test.go
│ │ ├── fake/
│ │ │ ├── fake.go
│ │ │ └── fake_test.go
│ │ ├── types.go
│ │ └── types_test.go
│ ├── http/
│ │ ├── drain.go
│ │ ├── drain_test.go
│ │ ├── http.go
│ │ ├── http_benchmark_test.go
│ │ └── http_test.go
│ ├── metrics/
│ │ ├── OWNERS
│ │ ├── labels.go
│ │ ├── metrics.go
│ │ ├── metrics_test.go
│ │ ├── models.go
│ │ └── models_test.go
│ ├── rfc2317/
│ │ ├── OWNERS
│ │ ├── arpa.go
│ │ └── arpa_test.go
│ └── tlsutils/
│ ├── OWNERS
│ ├── tlsconfig.go
│ └── tlsconfig_test.go
├── plan/
│ ├── OWNERS
│ ├── conflict.go
│ ├── conflict_test.go
│ ├── metrics.go
│ ├── metrics_test.go
│ ├── plan.go
│ ├── plan_test.go
│ ├── policy.go
│ └── policy_test.go
├── provider/
│ ├── OWNERS
│ ├── akamai/
│ │ ├── akamai.go
│ │ └── akamai_test.go
│ ├── alibabacloud/
│ │ ├── alibaba_cloud.go
│ │ └── alibaba_cloud_test.go
│ ├── aws/
│ │ ├── aws.go
│ │ ├── aws_fixtures_test.go
│ │ ├── aws_test.go
│ │ ├── aws_utils_test.go
│ │ ├── config.go
│ │ ├── config_test.go
│ │ ├── fixtures/
│ │ │ └── 160-plus-zones.yaml
│ │ ├── instrumented_config.go
│ │ └── instrumented_config_test.go
│ ├── awssd/
│ │ ├── aws_sd.go
│ │ ├── aws_sd_test.go
│ │ └── fixtures_test.go
│ ├── azure/
│ │ ├── azure.go
│ │ ├── azure_private_dns.go
│ │ ├── azure_privatedns_test.go
│ │ ├── azure_test.go
│ │ ├── common.go
│ │ ├── common_test.go
│ │ ├── config.go
│ │ ├── config_test.go
│ │ └── fixtures/
│ │ └── config_test.json
│ ├── blueprint/
│ │ ├── zone_cache.go
│ │ └── zone_cache_test.go
│ ├── cached_provider.go
│ ├── cached_provider_test.go
│ ├── civo/
│ │ ├── civo.go
│ │ └── civo_test.go
│ ├── cloudflare/
│ │ ├── OWNERS
│ │ ├── cloudflare.go
│ │ ├── cloudflare_batch.go
│ │ ├── cloudflare_batch_test.go
│ │ ├── cloudflare_custom_hostnames.go
│ │ ├── cloudflare_custom_hostnames_test.go
│ │ ├── cloudflare_regional.go
│ │ ├── cloudflare_regional_test.go
│ │ ├── cloudflare_test.go
│ │ ├── pagination.go
│ │ └── pagination_test.go
│ ├── coredns/
│ │ ├── OWNERS
│ │ ├── coredns.go
│ │ └── coredns_test.go
│ ├── dnsimple/
│ │ ├── dnsimple.go
│ │ └── dnsimple_test.go
│ ├── exoscale/
│ │ ├── exoscale.go
│ │ └── exoscale_test.go
│ ├── factory/
│ │ ├── provider.go
│ │ └── provider_test.go
│ ├── fakes/
│ │ └── provider.go
│ ├── gandi/
│ │ ├── client.go
│ │ ├── gandi.go
│ │ └── gandi_test.go
│ ├── godaddy/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── godaddy.go
│ │ └── godaddy_test.go
│ ├── google/
│ │ ├── google.go
│ │ └── google_test.go
│ ├── inmemory/
│ │ ├── inmemory.go
│ │ └── inmemory_test.go
│ ├── linode/
│ │ ├── linode.go
│ │ └── linode_test.go
│ ├── ns1/
│ │ ├── ns1.go
│ │ └── ns1_test.go
│ ├── oci/
│ │ ├── cache.go
│ │ ├── cache_test.go
│ │ ├── oci.go
│ │ └── oci_test.go
│ ├── ovh/
│ │ ├── ovh.go
│ │ └── ovh_test.go
│ ├── pdns/
│ │ ├── pdns.go
│ │ └── pdns_test.go
│ ├── pihole/
│ │ ├── client.go
│ │ ├── clientV6.go
│ │ ├── clientV6_test.go
│ │ ├── client_test.go
│ │ ├── pihole.go
│ │ ├── piholeV6_test.go
│ │ └── pihole_test.go
│ ├── plural/
│ │ ├── client.go
│ │ ├── plural.go
│ │ └── plural_test.go
│ ├── provider.go
│ ├── provider_test.go
│ ├── recordfilter.go
│ ├── recordfilter_test.go
│ ├── rfc2136/
│ │ ├── rfc2136.go
│ │ └── rfc2136_test.go
│ ├── scaleway/
│ │ ├── interface.go
│ │ ├── scaleway.go
│ │ └── scaleway_test.go
│ ├── transip/
│ │ ├── transip.go
│ │ └── transip_test.go
│ ├── webhook/
│ │ ├── api/
│ │ │ ├── httpapi.go
│ │ │ └── httpapi_test.go
│ │ ├── webhook.go
│ │ └── webhook_test.go
│ ├── zone_id_filter.go
│ ├── zone_id_filter_test.go
│ ├── zone_tag_filter.go
│ ├── zone_tag_filter_test.go
│ ├── zone_type_filter.go
│ ├── zone_type_filter_test.go
│ ├── zonefinder.go
│ └── zonefinder_test.go
├── registry/
│ ├── OWNERS
│ ├── awssd/
│ │ ├── OWNERS
│ │ ├── registry.go
│ │ └── registry_test.go
│ ├── dynamodb/
│ │ ├── OWNERS
│ │ ├── registry.go
│ │ └── registry_test.go
│ ├── factory/
│ │ ├── registry.go
│ │ └── registry_test.go
│ ├── mapper/
│ │ ├── mapper.go
│ │ └── mapper_test.go
│ ├── noop/
│ │ ├── OWNERS
│ │ ├── noop.go
│ │ └── noop_test.go
│ ├── registry.go
│ └── txt/
│ ├── OWNERS
│ ├── encryption_test.go
│ ├── registry.go
│ ├── registry_test.go
│ └── utils_test.go
├── scripts/
│ ├── OWNERS
│ ├── aws-cleanup-legacy-txt-records.py
│ ├── e2e-test.sh
│ ├── generate-crd.sh
│ ├── get-sha256.sh
│ ├── helm-tools.sh
│ ├── install-ko.sh
│ ├── install-tools.sh
│ ├── releaser.sh
│ ├── update_route53_k8s_txt_owner.py
│ └── version-updater.sh
├── source/
│ ├── OWNERS
│ ├── ambassador_host.go
│ ├── ambassador_host_test.go
│ ├── annotations/
│ │ ├── annotations.go
│ │ ├── annotations_test.go
│ │ ├── filter.go
│ │ ├── filter_test.go
│ │ ├── processors.go
│ │ ├── processors_test.go
│ │ ├── provider_specific.go
│ │ └── provider_specific_test.go
│ ├── compatibility.go
│ ├── connector.go
│ ├── connector_test.go
│ ├── contour_httpproxy.go
│ ├── contour_httpproxy_test.go
│ ├── crd.go
│ ├── crd_test.go
│ ├── empty.go
│ ├── empty_test.go
│ ├── endpoint_benchmark_test.go
│ ├── endpoints.go
│ ├── endpoints_test.go
│ ├── f5_transportserver.go
│ ├── f5_transportserver_test.go
│ ├── f5_virtualserver.go
│ ├── f5_virtualserver_test.go
│ ├── fake.go
│ ├── fake_test.go
│ ├── fqdn/
│ │ ├── fqdn.go
│ │ └── fqdn_test.go
│ ├── gateway.go
│ ├── gateway_grpcroute.go
│ ├── gateway_grpcroute_test.go
│ ├── gateway_hostname.go
│ ├── gateway_httproute.go
│ ├── gateway_httproute_test.go
│ ├── gateway_tcproute.go
│ ├── gateway_tcproute_test.go
│ ├── gateway_test.go
│ ├── gateway_tlsroute.go
│ ├── gateway_tlsroute_test.go
│ ├── gateway_udproute.go
│ ├── gateway_udproute_test.go
│ ├── gloo_proxy.go
│ ├── gloo_proxy_test.go
│ ├── informers/
│ │ ├── fake.go
│ │ ├── handlers.go
│ │ ├── handlers_test.go
│ │ ├── indexers.go
│ │ ├── indexers_test.go
│ │ ├── informers.go
│ │ ├── informers_test.go
│ │ ├── transfomers.go
│ │ └── transformers_test.go
│ ├── ingress.go
│ ├── ingress_fqdn_test.go
│ ├── ingress_test.go
│ ├── istio_gateway.go
│ ├── istio_gateway_fqdn_test.go
│ ├── istio_gateway_test.go
│ ├── istio_virtualservice.go
│ ├── istio_virtualservice_fqdn_test.go
│ ├── istio_virtualservice_test.go
│ ├── kong_tcpingress.go
│ ├── kong_tcpingress_test.go
│ ├── main_test.go
│ ├── node.go
│ ├── node_fqdn_test.go
│ ├── node_test.go
│ ├── openshift_route.go
│ ├── openshift_route_fqdn_test.go
│ ├── openshift_route_test.go
│ ├── pod.go
│ ├── pod_fqdn_test.go
│ ├── pod_indexer_test.go
│ ├── pod_test.go
│ ├── service.go
│ ├── service_fqdn_test.go
│ ├── service_test.go
│ ├── shared_test.go
│ ├── skipper_routegroup.go
│ ├── skipper_routegroup_test.go
│ ├── source.go
│ ├── source_test.go
│ ├── store.go
│ ├── store_test.go
│ ├── traefik_proxy.go
│ ├── traefik_proxy_test.go
│ ├── types/
│ │ └── types.go
│ ├── unstructured.go
│ ├── unstructured_converter.go
│ ├── unstructured_fqdn_test.go
│ ├── unstructured_test.go
│ ├── utils.go
│ ├── utils_test.go
│ └── wrappers/
│ ├── dedupsource.go
│ ├── dedupsource_test.go
│ ├── multisource.go
│ ├── multisource_test.go
│ ├── nat64source.go
│ ├── nat64source_test.go
│ ├── post_processor.go
│ ├── post_processor_test.go
│ ├── source_test.go
│ ├── targetfiltersource.go
│ ├── targetfiltersource_test.go
│ ├── types.go
│ └── types_test.go
└── tests/
└── integration/
├── OWNERS
├── scenarios/
│ └── tests.yaml
├── source_test.go
└── toolkit/
├── mocks.go
├── models.go
└── toolkit.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# https://github.com/editorconfig/editorconfig-core-go/blob/master/.editorconfig
[{Makefile,go.mod,go.sum,*.go}]
indent_style = tab
indent_size = 4
[*.py]
indent_style = space
indent_size = 4
================================================
FILE: .github/ISSUE_TEMPLATE/---bug-report.md
================================================
---
name: "\U0001F41E Bug report"
about: Report a bug encountered while operating external-dns
title: ''
labels: kind/bug
assignees: ''
---
<!--
Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!
Make sure to validate the behavior against latest release https://github.com/kubernetes-sigs/external-dns/releases as we don't support past versions.
Bug Report Guide: https://kubernetes-sigs.github.io/external-dns/latest/docs/contributing/bug-report/
-->
**What happened**:
**What you expected to happen**:
**How to reproduce it (as minimally and precisely as possible)**:
<!--
Please provide as much detail as possible, including Kubernetes manifests with spec.status, ExternalDNS arguments, and logs. A bug that cannot be reproduced won't be fixed.
Provide live objects from the API server — not Helm/Terraform/Flux templates. Include ALL fields. The status section is often critical.
kubectl get <resource> -o yaml # ingress, service, gateway, dnsendpoint, nodes, …
# before and after the change if reporting a regression
-->
**Anything else we need to know?**:
**Environment**:
- External-DNS version (use `external-dns --version`):
- DNS provider:
- Others:
## Checklist
- [ ] I have searched existing issues and tried to find a fix myself
- [ ] I am using the [latest release](https://github.com/kubernetes-sigs/external-dns/releases),
or have checked the [staging image](https://kubernetes-sigs.github.io/external-dns/latest/release/#staging-release-cycle) to confirm the bug is still reproducible
- [ ] I have provided the actual process flags (not Helm values)
- [ ] I have provided `kubectl get <resource> -o yaml` output including `status`
- [ ] I have provided full external-dns debug logs
- [ ] I have described what DNS records exist and what I expected
================================================
FILE: .github/ISSUE_TEMPLATE/--enhancement-request.md
================================================
---
name: "✨ Enhancement Request"
about: Suggest an enhancement to external-dns
title: ''
labels: kind/feature
assignees: ''
---
<!-- Please only use this template for submitting enhancement requests. This can be something like a new provider or a new gateway. -->
**What would you like to be added**:
**Why is this needed**:
================================================
FILE: .github/ISSUE_TEMPLATE/-support-request.md
================================================
---
name: "❓Support Request"
about: Support request or question relating to external-dns
title: ''
labels: kind/support
assignees: ''
---
<!--
STOP -- PLEASE READ!
GitHub is not the right place for support requests.
If you're looking for help, check our [docs](https://github.com/kubernetes-sigs/external-dns/tree/HEAD/docs).
You can also post your question on the [Kubernetes Slack #external-dns](https://kubernetes.slack.com/app_redirect?channel=external-dns).
-->
================================================
FILE: .github/ISSUE_TEMPLATE/create-release.md
================================================
---
name: Create Release
about: Release template to track the next release
title: Release x.y
labels: area/release
assignees: ''
---
This Issue tracks the next `external-dns` release. Please follow the guideline below. If anything is missing or unclear, please add a comment to this issue so this can be improved after the release.
## Preparation Tasks
- [ ] Release [steps](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/release.md#steps)
### Release Execution
- [ ] Branch out from the default branch and run scripts/version-updater.sh to update the image tag used in the kustomization.yaml and in documentation.
- [ ] Create the PR with this version change.
- [ ] Create an issue to release the corresponding Helm chart via the chart release process (below) assigned to a chart maintainer
### After Release Tasks
- [ ] Announce release on `#external-dns` in Slack
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
groups:
dev-dependencies:
patterns:
- "*"
ignore:
- dependency-name: "github.com/openshift/api"
- dependency-name: "github.com/openshift/client-go"
# miss tag on v1.19.0 in 2019
# See https://pkg.go.dev/github.com/exoscale/egoscale?tab=versions
- dependency-name: "github.com/exoscale/egoscale"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
dev-dependencies:
patterns:
- "*"
# Dependencies listed in requirements.txt
- package-ecosystem: "pip"
directory: "docs/scripts"
schedule:
interval: "monthly"
groups:
mkdocs-deps:
patterns:
- "*"
labels:
- python
- dependencies
- ok-to-test
- release-note-none
================================================
FILE: .github/labeler.yml
================================================
# Add 'docs' to any changes within 'docs' folder or any subfolders
docs:
- docs/**/*
# Add 'provider/alibaba' in file which starts with alibaba
provider/alibaba: provider/alibaba*
# Add 'provider/aws' in file which starts with aws
provider/aws: provider/aws*
# Add 'provider/azure' in file which starts with azure
provider/azure: provider/azure*
# Add 'provider/bluecat' in file which starts with bluecat
provider/bluecat: provider/bluecat*
# Add 'provider/cloudflare' in file which starts with cloudflare
provider/cloudflare: provider/cloudflare*
# Add 'provider/coredns' in file which starts with coredns
provider/coredns: provider/coredns*
# Add 'provider/designate' in file which starts with designate
provider/designate: provider/designate*
# Add 'provider/dnssimple' in file which starts with dnssimple
provider/dnssimple: provider/dnssimple*
# Add 'provider/dyn' in file which starts with dyn
provider/dyn: provider/dyn*
# Add 'provider/exoscale' in file which starts with exoscale
provider/exoscale: provider/exoscale*
# Add 'provider/transip' in file which starts with transip
provider/transip: provider/transip*
# Add 'provider/rfc2136' in file which starts with rfc2136
provider/rfc2136: provider/rfc2136*
# Add 'provider/rdns' in file which starts with rdns
provider/rdns: provider/rdns*
# Add 'provider/powerdns' in file which starts with pdns
provider/powerdns: provider/pdns*
# Add 'provider/google' in file which starts with google
provider/google: provider/google*
# Add 'provider/infoblox' in file which starts with infoblox
provider/infoblox: provider/infoblox*
# Add 'provider/linode' in file which starts with linode
provider/linode: provider/linode*
# Add 'provider/ns1' in file which starts with ns1
provider/ns1: provider/ns1*
# Add 'provider/oci' in file which starts with oci
provider/oci: provider/oci*
# Add 'provider/vinyldns' in file which starts with vinyldns
provider/vinyldns: provider/vinyldns*
# Add 'provider/vultr' in file which starts with vultr
provider/vultr: provider/vultr*
# Add 'provider/ultradns' in file which starts with ultradns
provider/ultradns: provider/ultradns*
================================================
FILE: .github/pull_request_template.md
================================================
## What does it do ?
<!-- A brief description of the change being made with this pull request. -->
## Motivation
<!-- What inspired you to submit this pull request? -->
## More
- [ ] Yes, this PR title follows [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
- [ ] Yes, I added unit tests
- [ ] Yes, I updated end user documentation accordingly
<!--
Please read https://github.com/kubernetes-sigs/external-dns#contributing before submitting
your pull request. Please fill in each section below to help us better prioritize your pull request. Thanks!
-->
================================================
FILE: .github/renovate-config.js
================================================
"use strict";
// https://github.com/renovatebot/github-action/blob/main/.github/renovate.json
// https://docs.renovatebot.com/configuration-options/
module.exports = {
"extends": [":disableRateLimiting", ":semanticCommits"],
"assigneesFromCodeOwners": true,
"gitAuthor": "Renovate Bot <bot@external-dns.com>",
"onboarding": false,
"platform": "github",
"repositories": [
"kubernetes-sigs/external-dns"
],
"printConfig": false,
"prConcurrentLimit": 0,
"prHourlyLimit": 0,
"stabilityDays": 3,
"pruneStaleBranches": true,
"recreateClosed": true,
"dependencyDashboard": false,
"requireConfig": false,
"rebaseWhen": "behind-base-branch",
"baseBranches": ["master", "main"],
"recreateWhen": "always",
"semanticCommits": "enabled",
"pre-commit": {
"enabled": true
},
"labels": ["{{depType}}", "datasource::{{datasource}}", "type::{{updateType}}", "manager::{{manager}}"], // can be overridden per packageRule
"addLabels": ["renovate-bot"], // cannot be overridden, any packageRule config extends this
"packageRules": [
{
"groupName": "pre-commit",
"matchManagers": ["pre-commit"],
"addLabels": ["pre-commit", "skip-release"]
},
],
"enabledManagers": [ // supported managers https://docs.renovatebot.com/modules/manager/
"regex",
"pre-commit"
],
"customManagers": [ // https://docs.renovatebot.com/modules/manager/regex/
{
// to capture registry.k8s.io/external-dns/external-dns:<version> in *.md files
"customType": "regex",
"fileMatch": [
".*\\.md$"
],
"matchStrings": [
"(?<depName>registry.k8s.io\/external-dns\/external-dns):(?<currentValue>.*)"
],
"depNameTemplate": "kubernetes-sigs/external-dns",
"datasourceTemplate": "github-releases",
"versioningTemplate": "semver"
},
{
"customType": "regex",
"fileMatch": [".*"],
"matchStrings": [
"datasource=(?<datasource>.*?) depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?\\s.*?_VERSION=(?<currentValue>.*)\\s"
],
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}",
},
]
};
================================================
FILE: .github/renovate.json
================================================
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}
================================================
FILE: .github/workflows/OWNERS
================================================
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- github_actions
================================================
FILE: .github/workflows/ci.yaml
================================================
name: Go
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
test:
permissions:
contents: read # to fetch code (actions/checkout)
checks: write # to create a new check based on the results (shogo82148/actions-goveralls)
name: Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
# tests for target OS
os: [ubuntu-latest, macos-latest]
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Go 1.x
uses: actions/setup-go@v6.3.0
with:
go-version-file: go.mod
check-latest: true
id: go
- name: Install Dependencies
run: |
go get -v -t -d ./...
- name: Test
env:
GOMAXPROCS: 4
GOMEMLIMIT: 8192MiB
run: make go-test
- name: Send coverage
uses: coverallsapp/github-action@v2
with:
file: profile.cov
format: golang
flag-name: run-${{ join(matrix.*, '-') }}
parallel: true
continue-on-error: true
finish:
needs: test
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@v2
with:
parallel-finished: true
carryforward: "run-ubuntu-latest,run-macos-latest"
continue-on-error: true
================================================
FILE: .github/workflows/codeql-analysis.yaml
================================================
name: "CodeQL analysis"
on:
push:
branches: [ master]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '35 13 * * 5'
workflow_dispatch:
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install go version
uses: actions/setup-go@v6.3.0
with:
go-version-file: go.mod
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
- run: |
make build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
================================================
FILE: .github/workflows/dependency-update.yaml
================================================
name: update-versions-with-renovate
on:
push:
branches: [main, master]
schedule:
# https://crontab.guru/
# once a day
- cron: '0 0 * * *'
jobs:
update-versions-with-renovate:
runs-on: ubuntu-latest
if: github.repository == 'kubernetes-sigs/external-dns'
steps:
- name: checkout
uses: actions/checkout@v6
# https://github.com/renovatebot/github-action
- name: self-hosted renovate
uses: renovatebot/github-action@v46.1.4
with:
# https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication
token: ${{ secrets.GITHUB_TOKEN }}
configurationFile: .github/renovate-config.js
env:
LOG_LEVEL: info
================================================
FILE: .github/workflows/docs.yaml
================================================
name: Release Docs
on:
push:
tags:
- "v*"
# See https://docs.github.com/fr/webhooks/webhook-events-and-payloads#workflow_dispatch
# Can be used to update doc with latest tag
workflow_dispatch:
permissions: {}
jobs:
release_docs:
permissions:
contents: write # for mike to push
name: Release Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: "./docs/scripts/requirements.txt"
- run: |
pip install -r docs/scripts/requirements.txt
- name: Configure Git user
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
- name: build and push
run: |
VERSION="${{ github.ref_name }}"
if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then
VERSION="latest"
fi
mike deploy $VERSION --push --update-aliases
mike set-default --push latest
================================================
FILE: .github/workflows/end-to-end-tests.yml
================================================
name: end to end test
on:
push:
branches:
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
e2e-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: e2e
run: |
./scripts/e2e-test.sh
================================================
FILE: .github/workflows/gh-workflow-approve.yaml
================================================
name: Approve GH Workflows
on:
pull_request_target:
types:
- labeled
- synchronize
branches:
- master
jobs:
approve:
name: Approve ok-to-test
if: contains(github.event.pull_request.labels.*.name, 'ok-to-test')
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Update PR
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
debug: ${{ secrets.ACTIONS_RUNNER_DEBUG == 'true' }}
script: |
const result = await github.rest.actions.listWorkflowRunsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
event: "pull_request",
status: "action_required",
head_sha: context.payload.pull_request.head.sha,
per_page: 100
});
for (var run of result.data.workflow_runs) {
await github.rest.actions.approveWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: run.id
});
}
================================================
FILE: .github/workflows/json-yaml-validate.yml
================================================
name: json-yaml-validate
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
pull-requests: write # enable write permissions for pull requests
jobs:
json-yaml-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: json-yaml-validate
uses: GrantBirki/json-yaml-validate@v4.0.0
with:
# ref: https://github.com/GrantBirki/json-yaml-validate?tab=readme-ov-file#inputs-
comment: "true" # enable comment mode
yaml_exclude_regex: "(charts/external-dns/templates.*|mkdocs.yml)"
allow_multiple_documents: "true"
================================================
FILE: .github/workflows/lint-test-chart.yaml
================================================
name: Lint and Test Chart
on:
pull_request:
branches:
- master
paths:
- "charts/external-dns/**"
concurrency:
group: chart-pr-${{ github.ref }}
cancel-in-progress: true
permissions: read-all
jobs:
lint-test:
name: Lint and Test
if: github.repository == 'kubernetes-sigs/external-dns'
runs-on: ubuntu-latest
permissions:
contents: read
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
with:
version: latest
- name: Configure Helm
run: |
set -euo pipefail
helm plugin install https://github.com/losisin/helm-values-schema-json.git --verify=false
helm plugin install https://github.com/helm-unittest/helm-unittest.git --verify=false
- name: Run Helm Schema check
working-directory: charts/external-dns
run: |
set -euo pipefail
helm schema
if [[ -n "$(git status --porcelain --untracked-files=no)" ]]
then
echo "Schema not up to date. Please run helm schema and commit changes!" >&2
exit 1
fi
- name: Install Helm Docs
uses: action-stars/install-tool-from-github-release@1fa61c3bea52eca3bcdb1f5c961a3b113fe7fa54 # v0.2.6
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
owner: norwoodj
repository: helm-docs
arch_amd64: x86_64
os_linux: Linux
check_command: helm-docs --version
version: latest
- name: Run Helm Docs check
run: |
set -euo pipefail
helm-docs
if [[ -n "$(git status --porcelain --untracked-files=no)" ]]
then
echo "Documentation not up to date. Please run helm-docs and commit changes!" >&2
exit 1
fi
- name: Run Helm Unit Tests
run: |
set -euo pipefail
helm unittest -f 'tests/*_test.yaml' charts/external-dns
- name: Install YQ
uses: action-stars/install-tool-from-github-release@1fa61c3bea52eca3bcdb1f5c961a3b113fe7fa54 # v0.2.6
with:
github_token: ${{ github.token }}
owner: mikefarah
repository: yq
extract: false
filename_format: "{name}_{os}_{arch}"
check_command: yq --version
version: latest
- name: Install MDQ
uses: action-stars/install-tool-from-github-release@1fa61c3bea52eca3bcdb1f5c961a3b113fe7fa54 # v0.2.6
with:
github_token: ${{ github.token }}
owner: yshavit
repository: mdq
arch_amd64: x64
filename_format: "{name}-{os}-{arch}.{ext}"
check_command: mdq --version
version: latest
- name: Run CHANGELOG check
run: |
set -euo pipefail
chart_file_path="./charts/external-dns/Chart.yaml"
changelog_file_path="./charts/external-dns/CHANGELOG.md"
version="$(yq eval '.version' "${chart_file_path}")"
entry="$(mdq --no-br --link-format inline "# v${version}" <"${changelog_file_path}" || true)"
if [[ -z "${entry}" ]]
then
echo "No CHANGELOG entry for ${chart} version ${version}!" >&2
exit 1
fi
added="$(mdq --output plain "# v${version} | # Added | -" <"${changelog_file_path}" || true)"
changed="$(mdq --output plain "# v${version} | # Changed | -" <"${changelog_file_path}" || true)"
deprecated="$(mdq --output plain "# v${version} | # Deprecated | -" <"${changelog_file_path}" || true)"
removed="$(mdq --output plain "# v${version} | # Removed | -" <"${changelog_file_path}" || true)"
fixed="$(mdq --output plain "# v${version} | # Fixed | -" <"${changelog_file_path}" || true)"
security="$(mdq --output plain "# v${version} | # Security | -" <"${changelog_file_path}" || true)"
changes_path="./charts/external-dns/changes.txt"
rm -f "${changes_path}"
old_ifs="${IFS}"
IFS=$'\n'
for item in ${added}; do
printf -- '- kind: added\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${changed}; do
printf -- '- kind: changed\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${deprecated}; do
printf -- '- kind: deprecated\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${removed}; do
printf -- '- kind: removed\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${fixed}; do
printf -- '- kind: fixed\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${security}; do
printf -- '- kind: security\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
IFS="${old_ifs}"
if [[ -f "${changes_path}" ]]; then
echo "::group::Changes"
cat "${changes_path}"
echo "::endgroup::"
changes="$(cat "${changes_path}")" yq eval --inplace '.annotations["artifacthub.io/changes"] |= strenv(changes)' "${chart_file_path}"
rm -f "${changes_path}"
fi
- name: Install Artifact Hub CLI
uses: action-stars/install-tool-from-github-release@1fa61c3bea52eca3bcdb1f5c961a3b113fe7fa54 # v0.2.6
with:
github_token: ${{ github.token }}
owner: artifacthub
repository: hub
name: ah
check_command: ah version
version: latest
- name: Run Artifact Hub lint
run: ah lint --kind helm --path ./charts/external-dns || exit 1
- name: Install Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
token: ${{ github.token }}
python-version: "3.x"
- name: Set-up chart-testing
uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0
- name: Run chart-testing lint
run: ct lint --charts=./charts/external-dns --target-branch=${{ github.event.repository.default_branch }} --check-version-increment=false
- name: Create Kind cluster
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
wait: 120s
- name: Run chart-testing install
run: ct install --charts=./charts/external-dns --target-branch=${{ github.event.repository.default_branch }}
================================================
FILE: .github/workflows/lint.yaml
================================================
name: Lint
on:
pull_request:
branches: [ master ]
jobs:
lint:
name: Markdown and Go
runs-on: ubuntu-latest
permissions:
# Required: allow read access to the content for analysis.
contents: read
# For go lang linter
pull-requests: read
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Lint markdown
uses: nosborn/github-action-markdown-cli@v3.5.0
with:
files: '.'
config_file: ".markdownlint.json"
- name: Set up Go
uses: actions/setup-go@v6.3.0
with:
go-version-file: go.mod
- name: Go formatting
run: |
if [ -z "$(gofmt -l .)" ]; then
echo -e "All '*.go' files are properly formatted."
else
echo -e "Please run 'make go-lint' to fix. Some files need formatting:"
gofmt -d -l .
exit 1
fi
# https://github.com/golangci/golangci-lint-action?tab=readme-ov-file#verify
- name: Verify linter configuration and Lint go code
uses: golangci/golangci-lint-action@v9
with:
verify: true
args: --timeout=30m
version: v2.7
- uses: actions/setup-python@v6
# https://github.com/pre-commit/action
- name: Verify with pre-commit
uses: pre-commit/action@v3.0.1
================================================
FILE: .github/workflows/release-chart.yaml
================================================
name: Release Chart
on:
push:
branches:
- master
paths:
- "charts/external-dns/Chart.yaml"
concurrency:
group: chart-release
cancel-in-progress: false
permissions: read-all
jobs:
release:
name: Release
if: github.repository == 'kubernetes-sigs/external-dns'
runs-on: ubuntu-latest
permissions:
contents: write
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: Install YQ
uses: action-stars/install-tool-from-github-release@1fa61c3bea52eca3bcdb1f5c961a3b113fe7fa54 # v0.2.6
with:
github_token: ${{ github.token }}
owner: mikefarah
repository: yq
extract: false
filename_format: "{name}_{os}_{arch}"
check_command: yq --version
version: latest
- name: Install MDQ
uses: action-stars/install-tool-from-github-release@1fa61c3bea52eca3bcdb1f5c961a3b113fe7fa54 # v0.2.6
with:
github_token: ${{ github.token }}
owner: yshavit
repository: mdq
arch_amd64: x64
filename_format: "{name}-{os}-{arch}.{ext}"
check_command: mdq --version
version: latest
- name: Get chart version
id: chart_version
run: |
set -euo pipefail
chart_version="$(grep -Po "(?<=^version: ).+" charts/external-dns/Chart.yaml)"
echo "version=${chart_version}" >> $GITHUB_OUTPUT
- name: Get changelog entry
id: changelog_reader
uses: mindsers/changelog-reader-action@32aa5b4c155d76c94e4ec883a223c947b2f02656 # v2.2.3
with:
path: charts/external-dns/CHANGELOG.md
version: "v${{ steps.chart_version.outputs.version }}"
- name: Process changelog
id: changelog
run: |
set -euo pipefail
package_dir="./.cr-release-packages"
mkdir -p "${package_dir}"
release_notes_file="RELEASE.md"
release_notes_path="./charts/external-dns/${release_notes_file}"
cat <<"EOF" > "${release_notes_path}"
${{ steps.changelog_reader.outputs.changes }}
EOF
added="$(mdq --output plain '# Added | -' <"${release_notes_path}" || true)"
changed="$(mdq --output plain '# Changed | -' <"${release_notes_path}" || true)"
deprecated="$(mdq --output plain '# Deprecated | -' <"${release_notes_path}" || true)"
removed="$(mdq --output plain '# Removed | -' <"${release_notes_path}" || true)"
fixed="$(mdq --output plain '# Fixed | -' <"${release_notes_path}" || true)"
security="$(mdq --output plain '# Security | -' <"${release_notes_path}" || true)"
changes_path="./charts/external-dns/changes.txt"
rm -f "${changes_path}"
old_ifs="${IFS}"
IFS=$'\n'
for item in ${added}; do
printf -- '- kind: added\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${changed}; do
printf -- '- kind: changed\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${deprecated}; do
printf -- '- kind: deprecated\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${removed}; do
printf -- '- kind: removed\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${fixed}; do
printf -- '- kind: fixed\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
for item in ${security}; do
printf -- '- kind: security\n description: "%s"\n' "${item%.*}." >> "${changes_path}"
done
IFS="${old_ifs}"
if [[ -f "${changes_path}" ]]; then
changes="$(cat "${changes_path}")" yq eval --inplace '.annotations["artifacthub.io/changes"] |= strenv(changes)' ./charts/external-dns/Chart.yaml
rm -f "${changes_path}"
fi
echo "release_notes_file=${release_notes_file}" >> "${GITHUB_OUTPUT}"
- name: Install Artifact Hub CLI
uses: action-stars/install-tool-from-github-release@1fa61c3bea52eca3bcdb1f5c961a3b113fe7fa54 # v0.2.6
with:
github_token: ${{ github.token }}
owner: artifacthub
repository: hub
name: ah
check_command: ah version
version: latest
- name: Run Artifact Hub lint
run: ah lint --kind helm --path ./charts/external-dns || exit 1
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Install Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1
with:
version: latest
- name: Run chart-releaser
uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0
env:
CR_TOKEN: "${{ github.token }}"
CR_RELEASE_NAME_TEMPLATE: "external-dns-helm-chart-{{ .Version }}"
CR_RELEASE_NOTES_FILE: "${{ steps.changelog.outputs.release_notes_file }}"
CR_MAKE_RELEASE_LATEST: "false"
================================================
FILE: .github/workflows/staging-image-tester.yaml
================================================
name: Build all images
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
permissions:
contents: read # to fetch code (actions/checkout)
checks: write # to create a new check based on the results (shogo82148/actions-goveralls)
name: Build
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Go 1.x
uses: actions/setup-go@v6.3.0
with:
go-version-file: go.mod
id: go
- name: Install CI
run: |
go get -v -t -d ./...
- name: Test
run: make build.image/multiarch
================================================
FILE: .github/workflows/validate-crd.yml
================================================
name: Validate CRD Generation
# This workflow validates that generated CRD files are up-to-date when tool
# dependencies change. It ensures that if go.tool.mod or go.tool.sum are updated,
# the corresponding generated files (CRDs and deepcopy code) are also regenerated
# and committed in the same PR.
#
# Why this is needed:
# - controller-gen (from go.tool.mod) generates CRD YAML and deepcopy Go code
# - Different versions of controller-gen may produce different output
# - When tool versions change, generated code must be regenerated
# - This prevents CI failures and runtime issues from stale generated code
on:
pull_request:
paths:
- 'go.tool.mod'
- 'go.tool.sum'
- 'scripts/generate-crd.sh'
- '**/dnsendpoints.externaldns.k8s.io.yaml'
permissions:
contents: read
jobs:
validate-crd:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Set up Go
uses: actions/setup-go@def8c394e3ad351a79bc93815e4a585520fe993b # v6.2.0
with:
go-version-file: 'go.mod'
- name: Regenerate CRDs
run: ./scripts/generate-crd.sh
- name: Check for uncommitted changes
id: check_changes
run: |
# Check if there are any changes to generated files
if ! git diff --quiet; then
echo "::error::Generated CRD files are out of sync with go.tool.mod"
echo ""
echo "The following files have uncommitted changes after running 'make crd':"
git diff .
echo ""
echo "This usually means:"
echo "1. go.tool.mod or go.tool.sum was updated (new controller-gen version)"
echo "2. The generated CRD files were not regenerated"
echo ""
echo "To fix this:"
echo " make crd"
echo " git diff ."
echo " commit, push and update your PR:"
exit 1
fi
- name: Success
if: success()
run: |
echo "✅ Generated CRD files are up-to-date"
================================================
FILE: .gitignore
================================================
# OSX leaves these everywhere on SMB shares
._*
# OSX trash
.DS_Store
# Eclipse files
.classpath
.project
.settings/**
# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
.idea/
*.iml
# Vscode files
.vscode
__debug_*
# This is where the result of the go build goes
/output*/
/_output*/
/_output
/build
# Emacs save files
*~
\#*\#
.\#*
# Vim-related files
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
# cscope-related files
cscope.*
/bazel-*
# coverage output
cover.out
coverage.html
*.coverprofile
external-dns
# vendor dir
vendor/
profile.cov
# github codespaces
.venv/
# Helm charts
!/charts/external-dns/
docs/LICENSE.md
docs/code-of-conduct.md
docs/CONTRIBUTING.md
docs/index.md
docs/redirect
site
_scratch
Pipfile
================================================
FILE: .golangci.yml
================================================
# https://golangci-lint.run/docs/configuration/
version: "2"
linters:
default: none
enable: # golangci-lint help linters
- copyloopvar # A linter detects places where loop variables are copied. https://golangci-lint.run/docs/linters/configuration/#copyloopvar
- dogsled # Checks assignments with too many blank identifiers. https://golangci-lint.run/docs/linters/configuration/#dogsled
- dupword # Duplicate word. https://golangci-lint.run/docs/linters/configuration/#dupword
- goprintffuncname
- govet
- ineffassign
- misspell
- revive # https://golangci-lint.run/docs/linters/configuration/#revive
- recvcheck # Checks for receiver type consistency. https://golangci-lint.run/docs/linters/configuration/#recvcheck
- rowserrcheck # Checks whether Rows.Err of rows is checked successfully.
- errchkjson # Checks types passed to the json encoding functions. ref: https://golangci-lint.run/docs/linters/configuration/#errchkjson
- errorlint # Checking for unchecked errors in Go code https://golangci-lint.run/docs/linters/configuration/#errorlint
- staticcheck
- unconvert
- unused # https://golangci-lint.run/docs/linters/configuration/#unused
- unparam # https://golangci-lint.run/docs/linters/configuration/#unparam
- usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. https://golangci-lint.run/docs/linters/configuration/#usestdlibvars
- whitespace
- decorder # Check declaration order and count of types, constants, variables and functions. https://golangci-lint.run/docs/linters/configuration/#decorder
- tagalign # Check that struct tags are well aligned. https://golangci-lint.run/docs/linters/configuration/#tagalign
- predeclared # Find code that shadows one of Go's predeclared identifiers
- sloglint # Ensure consistent code style when using log/slog
- asciicheck # Checks that all code identifiers does not have non-ASCII symbols in the name
- nilnil # Checks that there is no simultaneous return of nil error and an nil value. ref: https://golangci-lint.run/docs/linters/configuration/#nilnil
- nonamedreturns # Checks that functions with named return values do not return named values. https://golangci-lint.run/docs/linters/configuration/#nonamedreturns
- cyclop # Checks function and package cyclomatic complexity. https://golangci-lint.run/docs/linters/configuration/#cyclop
- gocritic # Analyze source code for various issues, including bugs, performance hiccups, and non-idiomatic coding practices. https://golangci-lint.run/docs/linters/configuration/#gocritic
- gochecknoinits # Checks that there are no init() functions in the code. https://golangci-lint.run/docs/linters/configuration/#gochecknoinits
- goconst # Finds repeated strings that could be replaced by a constant. https://golangci-lint.run/docs/linters/configuration/#goconst
- modernize # A suite of analyzers that suggest simplifications to Go code, using modern language and library features. https://golangci-lint.run/docs/linters/configuration/#modernize
# tests
- testifylint # Checks usage of github.com/stretchr/testify. https://golangci-lint.run/docs/linters/configuration/#testifylint
- usetesting # Reports uses of functions with replacement inside the testing package.
settings:
exhaustive:
default-signifies-exhaustive: false
misspell:
locale: US
revive:
rules:
- name: confusing-naming
disabled: true
- name: unused-parameter # https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#unused-parameter
disabled: false
cyclop: # Lower cyclomatic complexity threshold after the max complexity is lowered
max-complexity: 32 # See https://github.com/kubernetes-sigs/external-dns/issues/5419
goconst:
min-occurrences: 3
# Ignore well-known DNS record types, boolean strings, and common values
ignore-string-values:
- "^(A|AAAA|ALIAS|CNAME|MX|NS|PTR|SRV|TXT)$" # DNS record types
- "^(true|false)$" # Boolean strings
- "^none$" # Common null/empty indicator
- "^(aws-sd|noop)$" # Registry types - can be ignored for consistency
testifylint:
# Enable all checkers (https://github.com/Antonboom/testifylint#checkers).
# Default: false
enable-all: true
# Disable checkers by name
# (in addition to default
# suite-thelper
# ).
# TODO: enable in follow-up
disable:
- require-error
usetesting:
# Enable/disable `context.Background()` detections.
context-background: true
# Enable/disable `context.TODO()` detections.
context-todo: true
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
rules:
- linters:
- deadcode
- depguard
- dogsled
- goprintffuncname
- govet
- ineffassign
- misspell
- nolintlint
- rowserrcheck
- staticcheck
- structcheck
- unconvert
- varcheck
- whitespace
- goconst
path: _test\.go
# TODO: skiip as will require design changes
- linters:
- nilnil
path: istio_virtualservice.go|fqdn.go|cloudflare_custom_hostnames.go
- linters:
- gochecknoinits
path: ^(internal/.*/init\.go|.*/metrics\.go|.*/webhook\.go|.*/http\.go|apis/.*\.go|.*/cached_provider\.go)$
- linters:
- modernize
path: ^(apis/.*\.go)$
paths:
- endpoint/zz_generated.deepcopy.go
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- goimports
settings:
goimports:
local-prefixes:
- sigs.k8s.io/external-dns
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
================================================
FILE: .ko.yaml
================================================
defaultBaseImage: gcr.io/distroless/static-debian12:latest
builds:
- env:
- CGO_ENABLED=0
flags:
- -v
ldflags:
- -s
- -w
- -X sigs.k8s.io/external-dns/pkg/apis/externaldns.Version={{.Env.VERSION}}
================================================
FILE: .markdownlint.json
================================================
{
"default": true,
"MD010": { "code_blocks": false },
"MD013": { "line_length": "300" },
"MD033": false,
"MD036": false,
"MD024": false,
"MD041": false,
"MD029": false,
"MD034": false,
"MD038": false,
"MD046": false
}
================================================
FILE: .pre-commit-config.yaml
================================================
---
default_language_version:
node: system
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: check-shebang-scripts-are-executable
- id: check-symlinks
- id: destroyed-symlinks
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: forbid-new-submodules
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.47.0
hooks:
- id: markdownlint
args: ["--fix"]
minimum_pre_commit_version: !!str 3.2
================================================
FILE: .spectral.yaml
================================================
extends: ["spectral:oas"]
================================================
FILE: .zappr.yaml
================================================
X-Zalando-Team: teapot
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guidelines
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
_In the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or other activities._
## Getting Started
We have full documentation on how to get started contributing here:
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
- [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing)
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers
## Developer Documentation
For more detailed contribution guides, see [Developer Documentation](docs/contributing) which includes:
- [Development Guide](docs/contributing/dev-guide.md) - Setting up development environment, building, and testing
- [Chart Development](docs/contributing/chart.md) - Working with Helm charts
- [Design Documentation](docs/contributing/design.md) - Architecture and design decisions
- [Sources and Providers](docs/contributing/sources-and-providers.md) - Adding new sources and providers
- [Source Wrappers](docs/contributing/source-wrappers.md) - Source wrapper implementation details
This project follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification on PR title. The explicit commit history is used, among other things, to provide a readable changelog in release notes.
## How to test a PR
On Linux (or WSL), a PR can be tested following this instruction with [gh](https://cli.github.com/) and [golang](https://go.dev/):
```bash
gh repo clone kubernetes-sigs/external-dns
cd external-dns
gh pr checkout XXX # <=== Set PR number here
go run main.go \
--kubeconfig=<kubeconfig_path> \
--log-format=text \
--log-level=debug \
--interval=1m
--provider=xxx
--source=yyy
```
## Mentorship
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
## Contact Information
- [Slack channel](https://kubernetes.slack.com/messages/external-dns)
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-network)
================================================
FILE: LICENSE.md
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: Makefile
================================================
# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#? cover: Creates coverage report for whole project excluding vendor and opens result in the default browser
.PHONY: cover cover-html
.DEFAULT_GOAL := build
cover:
@go test -cover -coverprofile=cover.out -v ./...
#? cover-html: Run tests with coverage and open coverage report in the browser
cover-html: cover
@go tool cover -html=cover.out
#? go-tools: list installed go tools
go-tools:
@echo ">> go tools installed in go.mod"
@go tool -n
@echo ">> go tools installed in go.tool.mod"
@go tool -modfile=go.tool.mod
#? golangci-lint-install: Install golangci-lint tool
golangci-lint-install:
@scripts/install-tools.sh --golangci
#? go-lint: Run the golangci-lint tool
.PHONY: go-lint
go-lint: golangci-lint-install
golangci-lint config verify
gofmt -l -s -w .
golangci-lint run --timeout=30m --fix ./...
#? licensecheck: Run the to check for license headers
.PHONY: licensecheck
licensecheck:
@echo ">> checking license header"
@licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \
awk 'NR<=5' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \
done); \
if [ -n "$${licRes}" ]; then \
echo "license header checking failed:"; echo "$${licRes}"; \
exit 1; \
fi
#? lint: Run all the linters
.PHONY: lint
lint: licensecheck go-lint
#? crd: Generates CRD using controller-gen and copy it into chart
.PHONY: crd
crd:
@./scripts/generate-crd.sh
# Required as long as dependabot does not support go.tool.mod https://github.com/dependabot/dependabot-core/issues/12050
#? update-tools-deps: Update go tools defined in go.tool.mod to latest versions
update-tools-deps:
@go get -modfile=go.tool.mod tool
#? test: The verify target runs tasks similar to the CI tasks, but without code coverage
.PHONY: test
test:
go test -race ./...
.PHONY: test
go-test:
go test -race -coverprofile=profile.cov ./...
go tool cover -func=profile.cov > coverage.summary
@tail -n 1 coverage.summary
#? build: The build targets allow to build the binary and container image
.PHONY: build
BINARY ?= external-dns
SOURCES = $(shell find . -name '*.go')
IMAGE_STAGING = gcr.io/k8s-staging-external-dns/$(BINARY)
REGISTRY ?= us.gcr.io/k8s-artifacts-prod/external-dns
IMAGE ?= $(REGISTRY)/$(BINARY)
VERSION ?= $(shell git describe --tags --always --dirty --match "v*")
GIT_COMMIT ?= $(shell git rev-parse --short HEAD)
BUILD_FLAGS ?= -v
LDFLAGS ?= -X sigs.k8s.io/external-dns/pkg/apis/externaldns.Version=$(VERSION) -w -s
LDFLAGS += -X sigs.k8s.io/external-dns/pkg/apis/externaldns.GitCommit=$(GIT_COMMIT)
ARCH ?= amd64
SHELL = /bin/bash
IMG_PLATFORM ?= linux/amd64,linux/arm64,linux/arm/v7
IMG_PUSH ?= true
IMG_SBOM ?= none
build: build/$(BINARY)
build/$(BINARY): $(SOURCES)
CGO_ENABLED=0 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.push/multiarch: ko
KO_DOCKER_REPO=${IMAGE} \
VERSION=${VERSION} \
ko build --tags ${VERSION} --bare --sbom ${IMG_SBOM} \
--image-label org.opencontainers.image.source="https://github.com/kubernetes-sigs/external-dns" \
--image-label org.opencontainers.image.revision=$(shell git rev-parse HEAD) \
--platform=${IMG_PLATFORM} --push=${IMG_PUSH} .
build.image/multiarch:
$(MAKE) IMG_PUSH=false build.push/multiarch
build.image:
$(MAKE) IMG_PLATFORM=linux/$(ARCH) build.image/multiarch
build.image-amd64:
$(MAKE) ARCH=amd64 build.image
build.image-arm64:
$(MAKE) ARCH=arm64 build.image
build.image-arm/v7:
$(MAKE) ARCH=arm/v7 build.image
build.push:
$(MAKE) IMG_PLATFORM=linux/$(ARCH) build.push/multiarch
build.push-amd64:
$(MAKE) ARCH=amd64 build.push
build.push-arm64:
$(MAKE) ARCH=arm64 build.push
build.push-arm/v7:
$(MAKE) ARCH=arm/v7 build.push
build.arm64:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.amd64:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.arm/v7:
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
clean:
@rm -rf build
@go clean -cache
.PHONY: release.staging
#? release.staging: Builds and push container images to the staging bucket.
release.staging: test
IMAGE=$(IMAGE_STAGING) $(MAKE) build.push/multiarch
release.prod: test
$(MAKE) build.push/multiarch
.PHONY: ko
ko:
scripts/install-ko.sh
.PHONY: generate-flags-documentation
#? generate-flags-documentation: Generate documentation (docs/flags.md)
generate-flags-documentation:
go run internal/gen/docs/flags/main.go
.PHONY: generate-metrics-documentation
#? generate-metrics-documentation: Generate documentation (docs/monitoring/metrics.md)
generate-metrics-documentation:
go run internal/gen/docs/metrics/main.go
.PHONY: generate-sources-documentation
#? generate-sources-documentation: Generate documentation (docs/sources/index.md)
generate-sources-documentation:
go run internal/gen/docs/sources/main.go
#? pre-commit-install: Install pre-commit hooks
pre-commit-install:
@pre-commit install
@pre-commit gc
#? pre-commit-uninstall: Uninstall pre-commit hooks
pre-commit-uninstall:
@pre-commit uninstall
#? pre-commit-validate: Validate files with pre-commit hooks
pre-commit-validate:
@pre-commit run --all-files
.PHONY: help
#? help: Get more info on available commands
help: Makefile
@sed -n 's/^#?//p' $< | column -t -s ':' | sort | sed -e 's/^/ /'
#? helm-test: Run unit tests
helm-test:
scripts/helm-tools.sh --helm-unittest
#? helm-template: Run helm template
helm-template:
scripts/helm-tools.sh --helm-template
#? helm-lint: Run helm linting (schema,docs)
helm-lint:
scripts/helm-tools.sh --schema
scripts/helm-tools.sh --docs
.PHONY: go-dependency
#? go-dependency: Dependency maintanance
go-dependency:
go mod tidy
.PHONY: mkdocs-serve
#? mkdocs-serve: Run the builtin development server for mkdocs
mkdocs-serve:
@$(info "contribute to documentation docs/contributing/dev-guide.md")
@mkdocs serve
================================================
FILE: OWNERS
================================================
# See the OWNERS file documentation:
# https://github.com/kubernetes/community/blob/HEAD/contributors/guide/owners.md
## These OWNERS files should stay in sync:
# https://github.com/kubernetes/test-infra/blob/master/config/jobs/kubernetes-sigs/external-dns/OWNERS
# https://github.com/kubernetes/k8s.io/blob/master/registry.k8s.io/images/k8s-staging-external-dns/OWNERS
## with this GitHub teams file:
# https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-network/teams.yaml
approvers:
- ivankatliarchuk
- mloiseleur
- raffo
- szuecs
reviewers:
- ivankatliarchuk
- mloiseleur
- raffo
- szuecs
- vflaux
emeritus_approvers:
- hjacobs
- johngmyers
- linki
- njuettner
- seanmalloy
================================================
FILE: README.md
================================================
---
hide:
- toc
- navigation
---
<p align="center">
<img src="docs/img/external-dns.png" width="40%" align="center" alt="ExternalDNS">
</p>
# ExternalDNS
[](https://github.com/kubernetes-sigs/external-dns/actions)
[](https://coveralls.io/github/kubernetes-sigs/external-dns)
[](https://github.com/kubernetes-sigs/external-dns/releases)
[](https://godoc.org/github.com/kubernetes-sigs/external-dns)
[](https://goreportcard.com/report/github.com/kubernetes-sigs/external-dns)
[](https://kubernetes-sigs.github.io/external-dns/)
[](https://deepwiki.com/kubernetes-sigs/external-dns)
ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
## Documentation
This README is a part of the complete [documentation, available here](https://kubernetes-sigs.github.io/external-dns/) and [DeepWiki](https://deepwiki.com/kubernetes-sigs/external-dns).
## What It Does
Inspired by [Kubernetes DNS](https://github.com/kubernetes/dns), Kubernetes' cluster-internal DNS server, ExternalDNS makes Kubernetes resources discoverable via public DNS servers.
Like KubeDNS, it retrieves a list of resources (Services, Ingresses, etc.) from the [Kubernetes API](https://kubernetes.io/docs/api/) to determine a desired list of DNS records.
_Unlike_ KubeDNS, however, it's not a DNS server itself, but merely configures other DNS providers accordingly—e.g. [AWS Route 53](https://aws.amazon.com/route53/) or [Google Cloud DNS](https://cloud.google.com/dns/docs/).
In a broader sense, ExternalDNS allows you to control DNS records dynamically via Kubernetes resources in a DNS provider-agnostic way.
The [FAQ](docs/faq.md) contains additional information and addresses several questions about key concepts of ExternalDNS.
To see ExternalDNS in action, have a look at this [video](https://www.youtube.com/watch?v=9HQ2XgL9YVI) or read this [blogpost](https://codemine.be/posts/20190125-devops-eks-externaldns/).
## The Latest Release
- [current release process](./docs/release.md)
ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchronized with Ingresses and Services of `type=LoadBalancer` and nodes in various DNS providers:
- [Google Cloud DNS](https://cloud.google.com/dns/docs/)
- [AWS Route 53](https://aws.amazon.com/route53/)
- [AWS Cloud Map](https://docs.aws.amazon.com/cloud-map/)
- [AzureDNS](https://azure.microsoft.com/en-us/services/dns)
- [Civo](https://www.civo.com)
- [CloudFlare](https://www.cloudflare.com/dns)
- [DNSimple](https://dnsimple.com/)
- [PowerDNS](https://www.powerdns.com/)
- [CoreDNS](https://coredns.io/)
- [Exoscale](https://www.exoscale.com/dns/)
- [Oracle Cloud Infrastructure DNS](https://docs.cloud.oracle.com/iaas/Content/DNS/Concepts/dnszonemanagement.htm)
- [Linode DNS](https://www.linode.com/docs/networking/dns/)
- [RFC2136](https://tools.ietf.org/html/rfc2136)
- [NS1](https://ns1.com/)
- [TransIP](https://www.transip.eu/domain-name/)
- [OVHcloud](https://www.ovhcloud.com)
- [Scaleway](https://www.scaleway.com)
- [Akamai Edge DNS](https://learn.akamai.com/en-us/products/cloud_security/edge_dns.html)
- [GoDaddy](https://www.godaddy.com)
- [Gandi](https://www.gandi.net)
- [Plural](https://www.plural.sh/)
- [Pi-hole](https://pi-hole.net/)
- [Alibaba Cloud DNS](https://www.alibabacloud.com/help/en/dns)
- [Myra Security DNS](https://www.myrasecurity.com/en/saasp/application-security/secure-dns/)
ExternalDNS is, by default, aware of the records it is managing, therefore it can safely manage non-empty hosted zones.
We strongly encourage you to set `--txt-owner-id` to a unique value that doesn't change for the lifetime of your cluster.
You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API.
Note that all flags can be replaced with environment variables; for instance,
`--dry-run` could be replaced with `EXTERNAL_DNS_DRY_RUN=1`.
## New providers
No new provider will be added to ExternalDNS _in-tree_.
ExternalDNS has introduced a webhook system, which can be used to add a new provider.
See PR #3063 for all the discussions about it.
Some known providers using webhooks are the ones in the table below.
**NOTE**: The maintainers of ExternalDNS have not reviewed those providers, use them at your own risk and following the license
and usage recommendations provided by the respective projects. The maintainers of ExternalDNS take no responsibility for any issue or damage
from the usage of any externally developed webhook.
| Provider | Repo |
| --------------------- | -------------------------------------------------------------------- |
| Abion | https://github.com/abiondevelopment/external-dns-webhook-abion |
| Adguard Home Provider | https://github.com/muhlba91/external-dns-provider-adguard |
| Anexia | https://github.com/anexia/k8s-external-dns-webhook |
| Bizfly Cloud | https://github.com/bizflycloud/external-dns-bizflycloud-webhook |
| ClouDNS | https://github.com/rwunderer/external-dns-cloudns-webhook |
| deSEC | https://github.com/michelangelomo/external-dns-desec-provider |
| DigitalOcean | https://github.com/amoniacou/external-dns-digitalocean-webhook |
| Dreamhost | https://github.com/asymingt/external-dns-dreamhost-webhook |
| Efficient IP | https://github.com/EfficientIP-Labs/external-dns-efficientip-webhook |
| Gcore | https://github.com/G-Core/external-dns-gcore-webhook |
| GleSYS | https://github.com/glesys/external-dns-glesys |
| Hetzner | https://github.com/mconfalonieri/external-dns-hetzner-webhook |
| Huawei Cloud | https://github.com/setoru/external-dns-huaweicloud-webhook |
| IONOS | https://github.com/ionos-cloud/external-dns-ionos-webhook |
| Infoblox | https://github.com/AbsaOSS/external-dns-infoblox-webhook |
| Infomaniak | https://github.com/M0NsTeRRR/external-dns-webhook-infomaniak |
| Mikrotik | https://github.com/mirceanton/external-dns-provider-mikrotik |
| Myra Security | https://github.com/Myra-Security-GmbH/external-dns-myrasec-webhook |
| Netcup | https://github.com/mrueg/external-dns-netcup-webhook |
| Netic | https://github.com/neticdk/external-dns-tidydns-webhook |
| OpenStack Designate | https://github.com/inovex/external-dns-designate-webhook |
| OpenWRT | https://github.com/renanqts/external-dns-openwrt-webhook |
| PS Cloud Services | https://github.com/supervillain3000/external-dns-pscloud-webhook |
| SAKURA Cloud | https://github.com/sacloud/external-dns-sacloud-webhook |
| Simply | https://github.com/uozalp/external-dns-simply-webhook |
| STACKIT | https://github.com/stackitcloud/external-dns-stackit-webhook |
| Unbound | https://github.com/guillomep/external-dns-unbound-webhook |
| Unifi | https://github.com/kashalls/external-dns-unifi-webhook |
| UniFi | https://github.com/lexfrei/external-dns-unifios-webhook |
| Volcengine Cloud | https://github.com/volcengine/external-dns-volcengine-webhook |
| Vultr | https://github.com/vultr/external-dns-vultr-webhook |
| Yandex Cloud | https://github.com/ismailbaskin/external-dns-yandex-webhook/ |
## Status of in-tree providers
ExternalDNS supports multiple DNS providers which have been implemented by the [ExternalDNS contributors](https://github.com/kubernetes-sigs/external-dns/graphs/contributors).
Maintaining all of those in a central repository is a challenge, which introduces lots of toil and potential risks.
This mean that `external-dns` has begun the process to move providers out of tree. See #4347 for more details.
Those who are interested can create a webhook provider based on an _in-tree_ provider and after submit a PR to reference it here.
We define the following stability levels for providers:
- **Stable**: Used for smoke tests before a release, used in production and maintainers are active.
- **Beta**: Community supported, well tested, but maintainers have no access to resources to execute integration tests on the real platform and/or are not using it in production.
- **Alpha**: Community provided with no support from the maintainers apart from reviewing PRs.
The following table clarifies the current status of the providers according to the aforementioned stability levels:
| Provider | Status | Maintainers |
|---------------------------------| ------ |------------------|
| Google Cloud DNS | Stable | |
| AWS Route 53 | Stable | |
| AWS Cloud Map | Beta | |
| Akamai Edge DNS | Beta | |
| AzureDNS | Stable | |
| Civo | Alpha | @alejandrojnm |
| CloudFlare | Beta | |
| DNSimple | Alpha | |
| PowerDNS | Alpha | |
| CoreDNS | Alpha | |
| Exoscale | Alpha | |
| Oracle Cloud Infrastructure DNS | Alpha | |
| Linode DNS | Alpha | |
| RFC2136 | Alpha | |
| NS1 | Alpha | |
| TransIP | Alpha | |
| OVHcloud | Beta | @rbeuque74 |
| Scaleway DNS | Alpha | @Sh4d1 |
| GoDaddy | Alpha | |
| Gandi | Alpha | @packi |
| Plural | Alpha | @michaeljguarino |
| Pi-hole | Alpha | @tinyzimmer |
| Alibaba Cloud DNS | Alpha | |
## Kubernetes version compatibility
Breaking changes were introduced in external-dns in the following versions:
- [`v0.10.0`](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.10.0): use of `networking.k8s.io/ingresses` instead of `extensions/ingresses` (see [#2281](https://github.com/kubernetes-sigs/external-dns/pull/2281))
- [`v0.18.0`](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.18.0): use of `discovery.k8s.io/endpointslices` instead of `endpoints` (see [#5493](https://github.com/kubernetes-sigs/external-dns/pull/5493))
- [`v0.19.0`](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.19.0): don't expose internal ipv6 by default (see [#5575](https://github.com/kubernetes-sigs/external-dns/pull/5575)) and disable legacy listeners on `traefik.containo.us` API Group (see [#5565](https://github.com/kubernetes-sigs/external-dns/pull/5565))
| ExternalDNS | ≤ 0.9.x | ≥ 0.10.x and ≤ 0.17.x | ≥ 0.18.x |
| ---------------------------- | :----------------: | :-------------------: | :----------------: |
| Kubernetes ≤ 1.18 | :white_check_mark: | :x: | :x: |
| Kubernetes 1.19 and 1.20 | :white_check_mark: | :white_check_mark: | :x: |
| Kubernetes 1.21 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Kubernetes ≥ 1.22 and ≤ 1.32 | :x: | :white_check_mark: | :white_check_mark: |
| Kubernetes ≥ 1.33 | :x: | :x: | :white_check_mark: |
## Running ExternalDNS
There are two ways of running ExternalDNS:
- Deploying to a Cluster
- Running Locally
### Deploying to a Cluster
The following tutorials are provided:
- [Akamai Edge DNS](docs/tutorials/akamai-edgedns.md)
- [Alibaba Cloud](docs/tutorials/alibabacloud.md)
- AWS
- [AWS Load Balancer Controller](docs/tutorials/aws-load-balancer-controller.md)
- [Route53](docs/tutorials/aws.md)
- [Same domain for public and private Route53 zones](docs/tutorials/aws-public-private-route53.md)
- [Cloud Map](docs/tutorials/aws-sd.md)
- [Kube Ingress AWS Controller](docs/tutorials/kube-ingress-aws.md)
- [Azure DNS](docs/tutorials/azure.md)
- [Azure Private DNS](docs/tutorials/azure-private-dns.md)
- [Civo](docs/tutorials/civo.md)
- [Cloudflare](docs/tutorials/cloudflare.md)
- [CoreDNS](docs/tutorials/coredns.md)
- [DNSimple](docs/tutorials/dnsimple.md)
- [Exoscale](docs/tutorials/exoscale.md)
- [ExternalName Services](docs/tutorials/externalname.md)
- Google Kubernetes Engine
- [Using Google's Default Ingress Controller](docs/tutorials/gke.md)
- [Using the Nginx Ingress Controller](docs/tutorials/gke-nginx.md)
- [Headless Services](docs/tutorials/hostport.md)
- [IONOS Cloud](docs/tutorials/ionoscloud.md)
- [Istio Gateway Source](docs/sources/istio.md)
- [Linode](docs/tutorials/linode.md)
- [Myra Security](docs/tutorials/myra.md)
- [NS1](docs/tutorials/ns1.md)
- [NS Record Creation with CRD Source](docs/sources/ns-record.md)
- [MX Record Creation with CRD Source](docs/sources/mx-record.md)
- [TXT Record Creation with CRD Source](docs/sources/txt-record.md)
- [Oracle Cloud Infrastructure (OCI) DNS](docs/tutorials/oracle.md)
- [PowerDNS](docs/tutorials/pdns.md)
- [RFC2136](docs/tutorials/rfc2136.md)
- [TransIP](docs/tutorials/transip.md)
- [OVHcloud](docs/tutorials/ovh.md)
- [Scaleway](docs/tutorials/scaleway.md)
- [GoDaddy](docs/tutorials/godaddy.md)
- [Gandi](docs/tutorials/gandi.md)
- [Nodes as source](docs/sources/nodes.md)
- [Plural](docs/tutorials/plural.md)
- [Pi-hole](docs/tutorials/pihole.md)
### Running Locally
See the [contributor guide](docs/contributing/dev-guide.md) for details on compiling
from source.
#### Setup Steps
Next, run an application and expose it via a Kubernetes Service:
```console
kubectl run nginx --image=nginx --port=80
kubectl expose pod nginx --port=80 --target-port=80 --type=LoadBalancer
```
Annotate the Service with your desired external DNS name. Make sure to change `example.org` to your domain.
```console
kubectl annotate service nginx "external-dns.alpha.kubernetes.io/hostname=nginx.example.org."
```
Optionally, you can customize the TTL value of the resulting DNS record by using the `external-dns.alpha.kubernetes.io/ttl` annotation:
```console
kubectl annotate service nginx "external-dns.alpha.kubernetes.io/ttl=10"
```
For more details on configuring TTL, see [advanced ttl](docs/advanced/ttl.md).
Use the internal-hostname annotation to create DNS records with ClusterIP as the target.
```console
kubectl annotate service nginx "external-dns.alpha.kubernetes.io/internal-hostname=nginx.internal.example.org."
```
If the service is not of type Loadbalancer you need the --publish-internal-services flag.
Locally run a single sync loop of ExternalDNS.
```console
external-dns --txt-owner-id my-cluster-id --provider google --google-project example-project --source service --once --dry-run
```
This should output the DNS records it will modify to match the managed zone with the DNS records you desire.
It also assumes you are running in the `default` namespace. See the [FAQ](docs/faq.md) for more information regarding namespaces.
Note: TXT records will have the `my-cluster-id` value embedded. Those are used to ensure that ExternalDNS is aware of the records it manages.
Once you're satisfied with the result, you can run ExternalDNS like you would run it in your cluster: as a control loop, and **not in dry-run** mode:
```console
external-dns --txt-owner-id my-cluster-id --provider google --google-project example-project --source service
```
Check that ExternalDNS has created the desired DNS record for your Service and that it points to its load balancer's IP. Then try to resolve it:
```console
dig +short nginx.example.org.
104.155.60.49
```
Now you can experiment and watch how ExternalDNS makes sure that your DNS records are configured as desired. Here are a couple of things you can try out:
- Change the desired hostname by modifying the Service's annotation.
- Recreate the Service and see that the DNS record will be updated to point to the new load balancer IP.
- Add another Service to create more DNS records.
- Remove Services to clean up your managed zone.
The **tutorials** section contains examples, including Ingress resources, and shows you how to set up ExternalDNS in different environments such as other cloud providers and alternative Ingress controllers.
# Note
If using a txt registry and attempting to use a CNAME the `--txt-prefix` must be set to avoid conflicts. Changing `--txt-prefix` will result in lost ownership over previously created records.
If `externalIPs` list is defined for a `LoadBalancer` service, this list will be used instead of an assigned load balancer IP to create a DNS record.
It's useful when you run bare metal Kubernetes clusters behind NAT or in a similar setup, where a load balancer IP differs from a public IP (e.g. with [MetalLB](https://metallb.universe.tf)).
## Contributing
Are you interested in contributing to external-dns? We, the maintainers and community, would love your
suggestions, contributions, and help! Also, the maintainers can be contacted at any time to learn more
about how to get involved.
We also encourage ALL active community participants to act as if they are maintainers, even if you don't have
"official" write permissions. This is a community effort, we are here to serve the Kubernetes community. If you
have an active interest and you want to get involved, you have real power! Don't assume that the only people who
can get things done around here are the "maintainers". We also would love to add more "official" maintainers, so
show us what you can do!
The external-dns project is currently in need of maintainers for specific DNS providers. Ideally each provider
would have at least two maintainers. It would be nice if the maintainers run the provider in production, but it
is not strictly required. Provider listed [status](https://github.com/kubernetes-sigs/external-dns#status-of-in-tree-providers)
that do not have a maintainer listed are in need of assistance.
Read the [contributing guidelines](CONTRIBUTING.md) and have a look at [the contributing docs](docs/contributing/dev-guide.md) to learn about building the project, the project structure, and the purpose of each package.
For an overview on how to write new Sources and Providers check out [Sources and Providers](docs/contributing/sources-and-providers.md).
## Heritage
ExternalDNS is an effort to unify the following similar projects in order to bring the Kubernetes community an easy and predictable way of managing DNS records across cloud providers based on their Kubernetes resources:
- Kops' [DNS Controller](https://github.com/kubernetes/kops/tree/HEAD/dns-controller)
- Zalando's [Mate](https://github.com/linki/mate)
- Molecule Software's [route53-kubernetes](https://github.com/wearemolecule/route53-kubernetes)
### User Demo How-To Blogs and Examples
- A full demo on GKE Kubernetes. See [How-to Kubernetes with DNS management (ssl-manager pre-req)](https://medium.com/@jpantjsoha/how-to-kubernetes-with-dns-management-for-gitops-31239ea75d8d)
- Run external-dns on GKE with workload identity. See [Kubernetes, ingress-nginx, cert-manager & external-dns](https://blog.atomist.com/kubernetes-ingress-nginx-cert-manager-external-dns/)
- [ExternalDNS integration with Azure DNS using workload identity](https://cloudchronicles.blog/blog/ExternalDNS-integration-with-Azure-DNS-using-workload-identity/)
================================================
FILE: SECURITY_CONTACTS
================================================
# Defined below are the security contacts for this repo.
#
# They are the contact point for the Product Security Team to reach out
# to for triaging and handling of incoming issues.
#
# The below names agree to abide by the
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/HEAD/security-release-process-documentation/security-release-process.md#embargo-policy)
# and will be removed and replaced if they violate that agreement.
#
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/
njuettner
hjacobs
raffo
================================================
FILE: api/webhook.yaml
================================================
---
openapi: "3.0.0"
info:
version: v0.15.0
title: External DNS Webhook Server
description: >-
Implements the external DNS webhook endpoints.
contact:
url: https://github.com/kubernetes-sigs/external-dns
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
tags:
- name: initialization
description: Endpoints for initial negotiation.
- name: listing
description: Endpoints to get listings of DNS records.
- name: update
description: Endpoints to update DNS records.
servers:
- url: http://localhost:8888
description: Server url for a Kubernetes deployment.
paths:
/:
get:
summary: >-
Initialisation and negotiates headers and returns domain
filter.
description: |
Initialisation and negotiates headers and returns domain
filter.
operationId: negotiate
tags: [initialization]
responses:
'200':
description: |
The list of domains this DNS provider serves.
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/filters'
example:
filters:
- example.com
'500':
description: |
Negotiation failed.
/records:
get:
summary: Returns the current records.
description: |
Get the current records from the DNS provider and return them.
operationId: getRecords
tags: [listing]
responses:
'200':
description: |
Provided the list of DNS records successfully.
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/endpoints'
example:
- dnsName: "test.example.com"
recordTTL: 10
recordType: 'A'
targets:
- "1.2.3.4"
'500':
description: |
Failed to provide the list of DNS records.
post:
summary: Applies the changes.
description: |
Set the records in the DNS provider based on those supplied here.
operationId: setRecords
tags: [update]
requestBody:
description: |
This is the list of changes that need to be applied. There are
four lists of endpoints. The `create` and `delete` lists are lists
of records to create and delete respectively. The `updateOld` and
`updateNew` lists are paired. For each entry there's the old version
of the record and a new version of the record.
required: true
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/changes'
example:
create:
- dnsName: "test.example.com"
recordTTL: 10
recordType: 'A'
responses:
'204':
description: |
Changes were accepted.
'500':
description: |
Changes were not accepted.
/adjustendpoints:
post:
summary: Executes the AdjustEndpoints method.
description: |
Adjusts the records in the provider based on those supplied here.
operationId: adjustRecords
tags: [update]
requestBody:
description: |
This is the list of changes to be applied.
required: true
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/endpoints'
example:
- dnsName: "test.example.com"
recordTTL: 10
recordType: 'A'
targets:
- "1.2.3.4"
responses:
'200':
description: |
Adjustments were accepted.
content:
application/external.dns.webhook+json;version=1:
schema:
$ref: '#/components/schemas/endpoints'
example:
- dnsName: "test.example.com"
recordTTL: 0
recordType: 'A'
targets:
- "1.2.3.4"
'500':
description: |
Adjustments were not accepted.
components:
schemas:
filters:
description: |
external-dns will only create DNS records for host names (specified in ingress objects and services with the external-dns annotation) related to zones that match filters. They can set in external-dns deployment manifest.
type: object
properties:
filters:
type: array
items:
type: string
example: "foo.example.com"
example:
- ".example.com"
example:
filters:
- ".example.com"
- ".example.org"
endpoints:
description: |
This is a list of DNS records.
type: array
items:
$ref: '#/components/schemas/endpoint'
example:
- dnsName: foo.example.com
recordType: A
recordTTL: 60
endpoint:
description: |
This is a DNS record.
type: object
properties:
dnsName:
type: string
example: "foo.example.org"
targets:
$ref: '#/components/schemas/targets'
recordType:
type: string
example: "CNAME"
setIdentifier:
type: string
example: "v1"
recordTTL:
type: integer
format: int64
example: 60
labels:
type: object
additionalProperties:
type: string
example: "foo"
example:
foo: bar
providerSpecific:
type: array
items:
$ref: '#/components/schemas/providerSpecificProperty'
example:
- name: foo
value: bar
example:
dnsName: foo.example.com
recordType: A
recordTTL: 60
targets:
description: |
This is the list of targets that this DNS record points to.
So for an A record it will be a list of IP addresses.
type: array
items:
type: string
example: "::1"
example:
- "1.2.3.4"
- "test.example.org"
providerSpecificProperty:
description: |
Allows provider to pass property specific to their implementation.
type: object
properties:
name:
type: string
example: foo
value:
type: string
example: bar
example:
name: foo
value: bar
changes:
description: |
This is the list of changes send by `external-dns` that need to
be applied. There are four lists of endpoints. The `create`
and `delete` lists are lists of records to create and delete
respectively. The `updateOld` and `updateNew` lists are paired.
For each entry there's the old version of the record and a new
version of the record.
type: object
properties:
create:
$ref: '#/components/schemas/endpoints'
updateOld:
$ref: '#/components/schemas/endpoints'
updateNew:
$ref: '#/components/schemas/endpoints'
delete:
$ref: '#/components/schemas/endpoints'
example:
create:
- dnsName: foo.example.com
recordType: A
recordTTL: 60
delete:
- dnsName: foo.example.org
recordType: CNAME
================================================
FILE: apis/OWNERS
================================================
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- apis
================================================
FILE: apis/api.go
================================================
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apis
================================================
FILE: apis/v1alpha1/api.go
================================================
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1alpha1 contains API Schema definitions for the externaldns.k8s.io v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=externaldns.k8s.io
package v1alpha1
================================================
FILE: apis/v1alpha1/dnsendpoint.go
================================================
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/external-dns/endpoint"
)
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// DNSEndpoint is a contract that a user-specified CRD must implement to be used as a source for external-dns.
// The user-specified CRD should also have the status sub-resource.
// +k8s:openapi-gen=true
// +groupName=externaldns.k8s.io
// +kubebuilder:resource:path=dnsendpoints
// +kubebuilder:subresource:status
// +kubebuilder:metadata:annotations="api-approved.kubernetes.io=https://github.com/kubernetes-sigs/external-dns/pull/2007"
// +versionName=v1alpha1
type DNSEndpoint struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec DNSEndpointSpec `json:"spec,omitempty"`
Status DNSEndpointStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// DNSEndpointList is a list of DNSEndpoint objects
type DNSEndpointList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []DNSEndpoint `json:"items"`
}
// DNSEndpointSpec defines the desired state of DNSEndpoint
type DNSEndpointSpec struct {
Endpoints []*endpoint.Endpoint `json:"endpoints,omitempty"`
}
// DNSEndpointStatus defines the observed state of DNSEndpoint
type DNSEndpointStatus struct {
// The generation observed by the external-dns controller.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}
================================================
FILE: apis/v1alpha1/groupversion_info.go
================================================
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1alpha1 contains API Schema definitions for the externaldns.k8s.io v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=externaldns.k8s.io
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
const (
// DNSEndpointKind is the kind name for DNSEndpoint resources
DNSEndpointKind = "DNSEndpoint"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "externaldns.k8s.io", Version: "v1alpha1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
func init() {
SchemeBuilder.Register(&DNSEndpoint{}, &DNSEndpointList{})
}
================================================
FILE: apis/v1alpha1/zz_generated.deepcopy.go
================================================
//go:build !ignore_autogenerated
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/external-dns/endpoint"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DNSEndpoint) DeepCopyInto(out *DNSEndpoint) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpoint.
func (in *DNSEndpoint) DeepCopy() *DNSEndpoint {
if in == nil {
return nil
}
out := new(DNSEndpoint)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *DNSEndpoint) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DNSEndpointList) DeepCopyInto(out *DNSEndpointList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]DNSEndpoint, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpointList.
func (in *DNSEndpointList) DeepCopy() *DNSEndpointList {
if in == nil {
return nil
}
out := new(DNSEndpointList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *DNSEndpointList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DNSEndpointSpec) DeepCopyInto(out *DNSEndpointSpec) {
*out = *in
if in.Endpoints != nil {
in, out := &in.Endpoints, &out.Endpoints
*out = make([]*endpoint.Endpoint, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(endpoint.Endpoint)
(*in).DeepCopyInto(*out)
}
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpointSpec.
func (in *DNSEndpointSpec) DeepCopy() *DNSEndpointSpec {
if in == nil {
return nil
}
out := new(DNSEndpointSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DNSEndpointStatus) DeepCopyInto(out *DNSEndpointStatus) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSEndpointStatus.
func (in *DNSEndpointStatus) DeepCopy() *DNSEndpointStatus {
if in == nil {
return nil
}
out := new(DNSEndpointStatus)
in.DeepCopyInto(out)
return out
}
================================================
FILE: charts/OWNERS
================================================
labels:
- chart
approvers:
- stevehipwell
reviewers:
- stevehipwell
================================================
FILE: cloudbuild.yaml
================================================
# See https://cloud.google.com/cloud-build/docs/build-config
timeout: 5000s
options:
substitution_option: ALLOW_LOOSE
machineType: 'N1_HIGHCPU_8'
steps:
- name: 'docker.io/library/golang:1.25-bookworm'
entrypoint: make
env:
- VERSION=$_GIT_TAG
- PULL_BASE_REF=$_PULL_BASE_REF
args:
- release.staging
substitutions:
# _GIT_TAG will be filled with a git-based tag for the image, of the form vYYYYMMDD-hash, and
# can be used as a substitution
_GIT_TAG: "12345"
_PULL_BASE_REF: 'master'
================================================
FILE: code-of-conduct.md
================================================
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
================================================
FILE: config/crd/standard/dnsendpoints.externaldns.k8s.io.yaml
================================================
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
api-approved.kubernetes.io: https://github.com/kubernetes-sigs/external-dns/pull/2007
controller-gen.kubebuilder.io/version: v0.20.1
name: dnsendpoints.externaldns.k8s.io
spec:
group: externaldns.k8s.io
names:
kind: DNSEndpoint
listKind: DNSEndpointList
plural: dnsendpoints
singular: dnsendpoint
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: |-
DNSEndpoint is a contract that a user-specified CRD must implement to be used as a source for external-dns.
The user-specified CRD should also have the status sub-resource.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: DNSEndpointSpec defines the desired state of DNSEndpoint
properties:
endpoints:
items:
description: Endpoint is a high-level way of a connection between a service and an IP
properties:
dnsName:
description: The hostname of the DNS record
type: string
labels:
additionalProperties:
type: string
description: Labels stores labels defined for the Endpoint
type: object
providerSpecific:
description: ProviderSpecific stores provider specific config
items:
description: ProviderSpecificProperty holds the name and value of a configuration which is specific to individual DNS providers
properties:
name:
type: string
value:
type: string
type: object
type: array
recordTTL:
description: TTL for the record
format: int64
type: integer
recordType:
description: RecordType type of record, e.g. CNAME, A, AAAA, SRV, TXT etc
type: string
setIdentifier:
description: Identifier to distinguish multiple records with the same name and type (e.g. Route53 records with routing policies other than 'simple')
type: string
targets:
description: The targets the DNS record points to
items:
type: string
type: array
type: object
type: array
type: object
status:
description: DNSEndpointStatus defines the observed state of DNSEndpoint
properties:
observedGeneration:
description: The generation observed by the external-dns controller.
format: int64
type: integer
type: object
type: object
served: true
storage: true
subresources:
status: {}
================================================
FILE: controller/OWNERS
================================================
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- controller
================================================
FILE: controller/controller.go
================================================
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"errors"
"fmt"
"sync"
"time"
log "github.com/sirupsen/logrus"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/events"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/registry"
"sigs.k8s.io/external-dns/source"
)
// Controller is responsible for orchestrating the different components.
// It works in the following way:
// * Ask the DNS provider for the current list of endpoints.
// * Ask the Source for the desired list of endpoints.
// * Take both lists and calculate a Plan to move current towards the desired state.
// * Tell the DNS provider to apply the changes calculated by the Plan.
type Controller struct {
Source source.Source
Registry registry.Registry
// The policy that defines which change to DNS records is allowed
Policy plan.Policy
// The interval between individual synchronizations
Interval time.Duration
// The DomainFilter defines which DNS records to keep or exclude
DomainFilter endpoint.DomainFilterInterface
// The nextRunAt used for throttling and batching reconciliation
nextRunAt time.Time
// The runAtMutex is for atomic updating of nextRunAt and lastRunAt
runAtMutex sync.Mutex
// The lastRunAt used for throttling and batching reconciliation
lastRunAt time.Time
EventEmitter events.EventEmitter
// MangedRecordTypes are DNS record types that will be considered for management.
ManagedRecordTypes []string
// ExcludeRecordTypes are DNS record types that will be excluded from management.
ExcludeRecordTypes []string
// MinEventSyncInterval is used as a window for batching events
MinEventSyncInterval time.Duration
// Old txt-owner value we need to migrate from
TXTOwnerOld string
}
// RunOnce runs a single iteration of a reconciliation loop.
func (c *Controller) RunOnce(ctx context.Context) error {
lastReconcileTimestamp.Gauge.SetToCurrentTime()
c.runAtMutex.Lock()
c.lastRunAt = time.Now()
c.runAtMutex.Unlock()
regRecords, err := c.Registry.Records(ctx)
if err != nil {
registryErrorsTotal.Counter.Inc()
deprecatedRegistryErrors.Counter.Inc()
return err
}
registryEndpointsTotal.Gauge.Set(float64(len(regRecords)))
countAddressRecords(regRecords, registryRecords)
ctx = context.WithValue(ctx, provider.RecordsContextKey, regRecords)
sourceEndpoints, err := c.Source.Endpoints(ctx)
if err != nil {
sourceErrorsTotal.Counter.Inc()
deprecatedSourceErrors.Counter.Inc()
return err
}
sourceEndpointsTotal.Gauge.Set(float64(len(sourceEndpoints)))
countAddressRecords(sourceEndpoints, sourceRecords)
countMatchingAddressRecords(sourceEndpoints, regRecords, verifiedRecords)
endpoints, err := c.Registry.AdjustEndpoints(sourceEndpoints)
if err != nil {
return fmt.Errorf("adjusting endpoints: %w", err)
}
registryFilter := c.Registry.GetDomainFilter()
plan := &plan.Plan{
Policies: []plan.Policy{c.Policy},
Current: regRecords,
Desired: endpoints,
DomainFilter: endpoint.MatchAllDomainFilters{c.DomainFilter, registryFilter},
ManagedRecords: c.ManagedRecordTypes,
ExcludeRecords: c.ExcludeRecordTypes,
OwnerID: c.Registry.OwnerID(),
OldOwnerID: c.TXTOwnerOld,
}
plan = plan.Calculate()
if plan.Changes.HasChanges() {
err = c.Registry.ApplyChanges(ctx, plan.Changes)
if err != nil {
registryErrorsTotal.Counter.Inc()
deprecatedRegistryErrors.Counter.Inc()
emitChangeEvent(c.EventEmitter, plan.Changes, events.RecordError)
return err
}
emitChangeEvent(c.EventEmitter, plan.Changes, events.RecordReady)
} else {
controllerNoChangesTotal.Counter.Inc()
log.Info("All records are already up to date")
}
lastSyncTimestamp.Gauge.SetToCurrentTime()
return nil
}
func earliest(r time.Time, times ...time.Time) time.Time {
for _, t := range times {
if t.Before(r) {
r = t
}
}
return r
}
func latest(r time.Time, times ...time.Time) time.Time {
for _, t := range times {
if t.After(r) {
r = t
}
}
return r
}
// ScheduleRunOnce makes sure execution happens at most once per interval.
func (c *Controller) ScheduleRunOnce(now time.Time) {
c.runAtMutex.Lock()
defer c.runAtMutex.Unlock()
c.nextRunAt = latest(
c.lastRunAt.Add(c.MinEventSyncInterval),
earliest(
now.Add(5*time.Second),
c.nextRunAt,
),
)
}
func (c *Controller) ShouldRunOnce(now time.Time) bool {
c.runAtMutex.Lock()
defer c.runAtMutex.Unlock()
if now.Before(c.nextRunAt) {
return false
}
c.nextRunAt = now.Add(c.Interval)
return true
}
// Run runs RunOnce in a loop with a delay until context is canceled
func (c *Controller) Run(ctx context.Context) {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
var softErrorCount int
for {
if c.ShouldRunOnce(time.Now()) {
if err := c.RunOnce(ctx); err != nil {
if errors.Is(err, provider.SoftError) {
softErrorCount++
consecutiveSoftErrors.Gauge.Set(float64(softErrorCount))
log.Errorf("Failed to do run once: %v (consecutive soft errors: %d)", err, softErrorCount)
} else {
log.Fatalf("Failed to do run once: %v", err) // nolint: gocritic // exitAfterDefer
}
} else {
if softErrorCount > 0 {
log.Infof("Reconciliation succeeded after %d consecutive soft errors", softErrorCount)
}
softErrorCount = 0
consecutiveSoftErrors.Gauge.Set(0)
}
}
select {
case <-ticker.C:
case <-ctx.Done():
log.Info("Terminating main controller loop")
return
}
}
}
================================================
FILE: controller/controller_test.go
================================================
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"errors"
"reflect"
"sort"
"sync"
"testing"
"time"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/internal/testutils"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/pkg/events"
"sigs.k8s.io/external-dns/pkg/events/fake"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/provider/fakes"
registryfactory "sigs.k8s.io/external-dns/registry/factory"
"sigs.k8s.io/external-dns/registry/noop"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
// mockProvider returns mock endpoints and validates changes.
type mockProvider struct {
provider.BaseProvider
RecordsStore []*endpoint.Endpoint
ExpectChanges *plan.Changes
}
type filteredMockProvider struct {
provider.BaseProvider
domainFilter *endpoint.DomainFilter
RecordsStore []*endpoint.Endpoint
RecordsCallCount int
ApplyChangesCalls []*plan.Changes
}
func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilterInterface {
return p.domainFilter
}
// Records returns the desired mock endpoints.
func (p *filteredMockProvider) Records(_ context.Context) ([]*endpoint.Endpoint, error) {
p.RecordsCallCount++
return p.RecordsStore, nil
}
// ApplyChanges stores all calls for later check
func (p *filteredMockProvider) ApplyChanges(_ context.Context, changes *plan.Changes) error {
p.ApplyChangesCalls = append(p.ApplyChangesCalls, changes)
return nil
}
// Records returns the desired mock endpoints.
func (p *mockProvider) Records(_ context.Context) ([]*endpoint.Endpoint, error) {
return p.RecordsStore, nil
}
// ApplyChanges validates that the passed in changes satisfy the assumptions.
func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
if err := verifyEndpoints(changes.Create, p.ExpectChanges.Create); err != nil {
return err
}
if err := verifyEndpoints(changes.UpdateNew, p.ExpectChanges.UpdateNew); err != nil {
return err
}
if err := verifyEndpoints(changes.UpdateOld, p.ExpectChanges.UpdateOld); err != nil {
return err
}
if err := verifyEndpoints(changes.Delete, p.ExpectChanges.Delete); err != nil {
return err
}
if !reflect.DeepEqual(ctx.Value(provider.RecordsContextKey), p.RecordsStore) {
return errors.New("context is wrong")
}
return nil
}
func verifyEndpoints(actual, expected []*endpoint.Endpoint) error {
if len(actual) != len(expected) {
return errors.New("number of records is wrong")
}
sort.Slice(actual, func(i, j int) bool {
return actual[i].DNSName < actual[j].DNSName
})
for i := range actual {
if actual[i].DNSName != expected[i].DNSName || !actual[i].Targets.Same(expected[i].Targets) {
return errors.New("record is wrong")
}
}
return nil
}
// newMockProvider creates a new mockProvider returning the given endpoints and validating the desired changes.
func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) provider.Provider {
dnsProvider := &mockProvider{
RecordsStore: endpoints,
ExpectChanges: changes,
}
return dnsProvider
}
func getTestSource() *testutils.MockSource {
// Fake some desired endpoints coming from our source.
source := new(testutils.MockSource)
source.On("Endpoints").Return([]*endpoint.Endpoint{
{
DNSName: "create-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "update-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.4.4"},
},
{
DNSName: "create-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
}, nil)
return source
}
func getTestConfig() *externaldns.Config {
cfg := externaldns.NewConfig()
cfg.Registry = externaldns.RegistryNoop
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
return cfg
}
func getTestProvider() provider.Provider {
// Fake some existing records in our DNS provider and validate some desired changes.
return newMockProvider(
[]*endpoint.Endpoint{
{
DNSName: "update-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "delete-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"4.3.2.1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
{
DNSName: "delete-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::4"},
},
},
&plan.Changes{
Create: []*endpoint.Endpoint{
{DNSName: "create-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
{DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
UpdateNew: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::2"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}},
},
UpdateOld: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::3"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}},
},
Delete: []*endpoint.Endpoint{
{DNSName: "delete-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::4"}},
{DNSName: "delete-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}},
},
},
)
}
// TestRunOnce tests that RunOnce correctly orchestrates the different components.
func TestRunOnce(t *testing.T) {
source := getTestSource()
cfg := getTestConfig()
provider := getTestProvider()
emitter := fake.NewFakeEventEmitter()
r, err := registryfactory.Select(cfg, provider)
require.NoError(t, err)
// Run our controller once to trigger the validation.
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
EventEmitter: emitter,
}
assert.NoError(t, ctrl.RunOnce(t.Context()))
// Validate that the mock source was called.
source.AssertExpectations(t)
// check the verified records
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
emitter.AssertNumberOfCalls(t, "Add", 6)
}
// TestRun tests that Run correctly starts and stops
func TestRun(t *testing.T) {
source := getTestSource()
cfg := getTestConfig()
provider := getTestProvider()
r, err := registryfactory.Select(cfg, provider)
require.NoError(t, err)
// Run our controller once to trigger the validation.
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
}
ctrl.nextRunAt = time.Now().Add(-time.Millisecond)
ctx, cancel := context.WithCancel(t.Context())
stopped := make(chan struct{})
go func() {
ctrl.Run(ctx)
close(stopped)
}()
time.Sleep(1500 * time.Millisecond)
cancel() // start shutdown
<-stopped
// Validate that the mock source was called.
source.AssertExpectations(t)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
func TestShouldRunOnce(t *testing.T) {
ctrl := &Controller{Interval: 10 * time.Minute, MinEventSyncInterval: 15 * time.Second}
now := time.Now()
// First run of Run loop should execute RunOnce
assert.True(t, ctrl.ShouldRunOnce(now))
assert.Equal(t, now.Add(10*time.Minute), ctrl.nextRunAt)
// Second run should not
assert.False(t, ctrl.ShouldRunOnce(now))
ctrl.lastRunAt = now
now = now.Add(10 * time.Second)
// Changes happen in ingresses or services
ctrl.ScheduleRunOnce(now)
ctrl.ScheduleRunOnce(now)
// Because we batch changes, ShouldRunOnce returns False at first
assert.False(t, ctrl.ShouldRunOnce(now))
assert.False(t, ctrl.ShouldRunOnce(now.Add(100*time.Microsecond)))
// But after MinInterval we should run reconciliation
now = now.Add(5 * time.Second)
assert.True(t, ctrl.ShouldRunOnce(now))
// But just one time
assert.False(t, ctrl.ShouldRunOnce(now))
// We should wait maximum possible time after last reconciliation started
now = now.Add(10*time.Minute - time.Second)
assert.False(t, ctrl.ShouldRunOnce(now))
// After exactly Interval it's OK again to reconcile
now = now.Add(time.Second)
assert.True(t, ctrl.ShouldRunOnce(now))
// But not two times
assert.False(t, ctrl.ShouldRunOnce(now))
// Multiple ingresses or services changes, closer than MinInterval from each other
ctrl.lastRunAt = now
firstChangeTime := now
secondChangeTime := firstChangeTime.Add(time.Second)
// First change
ctrl.ScheduleRunOnce(firstChangeTime)
// Second change
ctrl.ScheduleRunOnce(secondChangeTime)
// Executions should be spaced by at least MinEventSyncInterval
assert.False(t, ctrl.ShouldRunOnce(now.Add(5*time.Second)))
// Should not postpone the reconciliation further than firstChangeTime + MinInterval
now = now.Add(ctrl.MinEventSyncInterval)
assert.True(t, ctrl.ShouldRunOnce(now))
}
func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter *endpoint.DomainFilter, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
t.Helper()
cfg := externaldns.NewConfig()
cfg.Registry = externaldns.RegistryNoop
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil)
// Fake some existing records in our DNS provider and validate some desired changes.
provider := &filteredMockProvider{
RecordsStore: providerEndpoints,
}
r, err := registryfactory.Select(cfg, provider)
require.NoError(t, err)
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
DomainFilter: domainFilter,
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
}
assert.NoError(t, ctrl.RunOnce(t.Context()))
assert.Equal(t, 1, provider.RecordsCallCount)
require.Len(t, provider.ApplyChangesCalls, len(expectedChanges))
for i, change := range expectedChanges {
assert.Equal(t, *change, *provider.ApplyChangesCalls[i])
}
}
func TestControllerSkipsEmptyChanges(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.other.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"},
},
},
[]*plan.Changes{},
)
}
func TestWhenNoFilterControllerConsidersAllDomains(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.other.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.DomainFilter{},
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
[]*plan.Changes{
{
Create: []*endpoint.Endpoint{
{
DNSName: "create-record.other.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
},
},
)
}
func TestWhenMultipleControllerConsidersAllFilteredComain(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.other.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.1.1.1"},
},
{
DNSName: "create-record.unused.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
endpoint.NewDomainFilter([]string{"used.tld", "other.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
[]*plan.Changes{
{
Create: []*endpoint.Endpoint{
{
DNSName: "create-record.other.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
},
UpdateOld: []*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
Labels: endpoint.Labels{},
},
},
UpdateNew: []*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.1.1.1"},
Labels: endpoint.Labels{
"owner": "",
},
},
},
},
},
)
}
type toggleRegistry struct {
noop.NoopRegistry
failCount int
failCountMu sync.Mutex // protects failCount
}
const toggleRegistryFailureCount = 3
func (r *toggleRegistry) Records(_ context.Context) ([]*endpoint.Endpoint, error) {
r.failCountMu.Lock()
defer r.failCountMu.Unlock()
if r.failCount < toggleRegistryFailureCount {
r.failCount++
return nil, provider.SoftError
}
return []*endpoint.Endpoint{}, nil
}
func (r *toggleRegistry) ApplyChanges(_ context.Context, _ *plan.Changes) error {
return nil
}
func TestToggleRegistry(t *testing.T) {
source := getTestSource()
cfg := getTestConfig()
r := &toggleRegistry{}
interval := 10 * time.Millisecond
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
Interval: interval,
}
ctrl.nextRunAt = time.Now().Add(-time.Millisecond)
ctx, cancel := context.WithCancel(t.Context())
stopped := make(chan struct{})
go func() {
ctrl.Run(ctx)
close(stopped)
}()
// Wait up to 1 minute for failCount to reach at least 3
// The timeout serves as a safety net against infinite loops while being
// sufficiently large to accommodate slow CI environments
deadline := time.Now().Add(15 * time.Second)
for {
r.failCountMu.Lock()
count := r.failCount
r.failCountMu.Unlock()
if count >= toggleRegistryFailureCount {
break
}
if time.Now().After(deadline) {
break
}
// Sleep for the controller interval to avoid busy waiting
// since the controller won't run again until the interval passes
time.Sleep(interval)
}
cancel()
<-stopped
r.failCountMu.Lock()
finalCount := r.failCount
r.failCountMu.Unlock()
assert.Equal(t, toggleRegistryFailureCount, finalCount, "failCount should be at least %d", toggleRegistryFailureCount)
}
func TestRunOnce_EmitChangeEvent(t *testing.T) {
tests := []struct {
name string
applyErr error
expectedReason events.Reason
expectErr bool
}{
{
name: "emits RecordReady on success",
expectedReason: events.RecordReady,
},
{
name: "emits RecordError on failure",
applyErr: errors.New("apply failed"),
expectedReason: events.RecordError,
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
source := new(testutils.MockSource)
source.On("Endpoints").Return([]*endpoint.Endpoint{
endpoint.NewEndpoint("dot.com", endpoint.RecordTypeA, "1.2.3.4").
WithRefObject(&events.ObjectReference{}),
}, nil)
r, err := registryfactory.Select(getTestConfig(), &fakes.MockProvider{ApplyChangesErr: tt.applyErr})
require.NoError(t, err)
emitter := fake.NewFakeEventEmitter()
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
ManagedRecordTypes: []string{endpoint.RecordTypeA},
EventEmitter: emitter,
}
err = ctrl.RunOnce(t.Context())
assert.Equal(t, tt.expectErr, err != nil)
emitter.AssertCalled(t, "Add", mock.MatchedBy(func(e events.Event) bool {
return e.Reason() == tt.expectedReason
}))
})
}
}
================================================
FILE: controller/events.go
================================================
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"sigs.k8s.io/external-dns/pkg/events"
"sigs.k8s.io/external-dns/plan"
)
// emitChangeEvent emits a Kubernetes event for each DNS record change.
// Deletes use RecordDeleted on success and RecordError on failure.
func emitChangeEvent(e events.EventEmitter, ch *plan.Changes, reason events.Reason) {
if e == nil {
return
}
for _, ep := range ch.Create {
e.Add(events.NewEventFromEndpoint(ep, events.ActionCreate, reason))
}
for _, ep := range ch.UpdateNew {
e.Add(events.NewEventFromEndpoint(ep, events.ActionUpdate, reason))
}
deleteReason := events.RecordDeleted
if reason == events.RecordError {
deleteReason = events.RecordError
}
for _, ep := range ch.Delete {
e.Add(events.NewEventFromEndpoint(ep, events.ActionDelete, deleteReason))
}
}
================================================
FILE: controller/events_test.go
================================================
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/events"
"sigs.k8s.io/external-dns/pkg/events/fake"
"sigs.k8s.io/external-dns/plan"
)
func TestEmit_RecordReady(t *testing.T) {
refObj := &events.ObjectReference{}
tests := []struct {
name string
changes plan.Changes
asserts func(em *fake.EventEmitter, ch plan.Changes)
}{
{
name: "create, update and delete endpoints",
changes: plan.Changes{
Create: []*endpoint.Endpoint{
endpoint.NewEndpoint("one.example.com", endpoint.RecordTypeA, "10.10.10.0").WithRefObject(refObj),
endpoint.NewEndpoint("two.example.com", endpoint.RecordTypeA, "10.10.10.1").WithRefObject(refObj),
},
UpdateNew: []*endpoint.Endpoint{
endpoint.NewEndpoint("three.example.com", endpoint.RecordTypeA, "10.10.10.2").WithRefObject(refObj),
endpoint.NewEndpoint("four.example.com", endpoint.RecordTypeA, "10.10.10.3").WithRefObject(refObj),
},
Delete: []*endpoint.Endpoint{
endpoint.NewEndpoint("five.example.com", endpoint.RecordTypeA, "192.10.10.0").WithRefObject(refObj),
},
},
asserts: func(em *fake.EventEmitter, ch plan.Changes) {
for _, ep := range ch.Create {
em.AssertCalled(t, "Add", events.NewEventFromEndpoint(ep, events.ActionCreate, events.RecordReady))
}
for _, ep := range ch.Delete {
em.AssertCalled(t, "Add", events.NewEventFromEndpoint(ep, events.ActionDelete, events.RecordDeleted))
}
em.AssertNotCalled(t, "Add", mock.MatchedBy(func(e events.Event) bool {
return e.EventType() == events.EventTypeWarning
}))
em.AssertNumberOfCalls(t, "Add", 5)
},
},
{
name: "delete endpoints",
changes: plan.Changes{
Create: []*endpoint.Endpoint{},
UpdateNew: []*endpoint.Endpoint{},
Delete: []*endpoint.Endpoint{
endpoint.NewEndpoint("five.example.com", endpoint.RecordTypeA, "192.10.10.0").WithRefObject(refObj),
},
},
asserts: func(em *fake.EventEmitter, ch plan.Changes) {
for _, ep := range ch.Delete {
em.AssertCalled(t, "Add", events.NewEventFromEndpoint(ep, events.ActionDelete, events.RecordDeleted))
}
em.AssertCalled(t, "Add", mock.MatchedBy(func(e events.Event) bool {
return e.EventType() == events.EventTypeNormal &&
e.Action() == events.ActionDelete &&
e.Reason() == events.RecordDeleted
}))
em.AssertNumberOfCalls(t, "Add", 1)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
emitter := fake.NewFakeEventEmitter()
emitChangeEvent(emitter, &tt.changes, events.RecordReady)
tt.asserts(emitter, tt.changes)
mock.AssertExpectationsForObjects(t, emitter)
})
}
}
func TestEmit_NilEmitter(t *testing.T) {
assert.NotPanics(t, func() {
emitChangeEvent(nil, &plan.Changes{}, events.RecordError)
})
}
func TestEmit_RecordError(t *testing.T) {
refObj := &events.ObjectReference{}
tests := []struct {
name string
changes plan.Changes
asserts func(em *fake.EventEmitter, ch plan.Changes)
}{
{
name: "create, update and delete endpoints",
changes: plan.Changes{
Create: []*endpoint.Endpoint{
endpoint.NewEndpoint("one.example.com", endpoint.RecordTypeA, "10.10.10.0").WithRefObject(refObj),
},
UpdateNew: []*endpoint.Endpoint{
endpoint.NewEndpoint("two.example.com", endpoint.RecordTypeA, "10.10.10.1").WithRefObject(refObj),
},
Delete: []*endpoint.Endpoint{
endpoint.NewEndpoint("three.example.com", endpoint.RecordTypeA, "10.10.10.2").WithRefObject(refObj),
},
},
asserts: func(em *fake.EventEmitter, ch plan.Changes) {
em.AssertCalled(t, "Add", events.NewEventFromEndpoint(ch.Create[0], events.ActionCreate, events.RecordError))
em.AssertCalled(t, "Add", events.NewEventFromEndpoint(ch.UpdateNew[0], events.ActionUpdate, events.RecordError))
em.AssertCalled(t, "Add", events.NewEventFromEndpoint(ch.Delete[0], events.ActionDelete, events.RecordError))
em.AssertNumberOfCalls(t, "Add", 3)
},
},
{
name: "delete endpoints emit RecordError not RecordDeleted",
changes: plan.Changes{
Create: []*endpoint.Endpoint{},
UpdateNew: []*endpoint.Endpoint{},
Delete: []*endpoint.Endpoint{
endpoint.NewEndpoint("five.example.com", endpoint.RecordTypeA, "192.10.10.0").WithRefObject(refObj),
},
},
asserts: func(em *fake.EventEmitter, ch plan.Changes) {
em.AssertCalled(t, "Add", events.NewEventFromEndpoint(ch.Delete[0], events.ActionDelete, events.RecordError))
em.AssertNotCalled(t, "Add", mock.MatchedBy(func(e events.Event) bool {
return e.Reason() == events.RecordDeleted
}))
em.AssertNumberOfCalls(t, "Add", 1)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
emitter := fake.NewFakeEventEmitter()
emitChangeEvent(emitter, &tt.changes, events.RecordError)
tt.asserts(emitter, tt.changes)
mock.AssertExpectationsForObjects(t, emitter)
})
}
}
================================================
FILE: controller/execute.go
================================================
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
"k8s.io/klog/v2"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/pkg/apis/externaldns/validation"
"sigs.k8s.io/external-dns/pkg/events"
"sigs.k8s.io/external-dns/pkg/metrics"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
providerfactory "sigs.k8s.io/external-dns/provider/factory"
webhookapi "sigs.k8s.io/external-dns/provider/webhook/api"
registryfactory "sigs.k8s.io/external-dns/registry/factory"
"sigs.k8s.io/external-dns/source"
"sigs.k8s.io/external-dns/source/annotations"
"sigs.k8s.io/external-dns/source/wrappers"
)
func Execute() {
cfg := externaldns.NewConfig()
if err := cfg.ParseFlags(os.Args[1:]); err != nil {
log.Fatalf("flag parsing error: %v", err)
}
log.Infof("config: %s", cfg)
if err := validation.ValidateConfig(cfg); err != nil {
log.Fatalf("config validation failed: %v", err)
}
// Set annotation prefix (required since init() was removed)
annotations.SetAnnotationPrefix(cfg.AnnotationPrefix)
if cfg.AnnotationPrefix != annotations.DefaultAnnotationPrefix {
log.Infof("Using custom annotation prefix: %s", cfg.AnnotationPrefix)
}
configureLogger(cfg)
if cfg.DryRun {
log.Info("running in dry-run mode. No changes to DNS records will be made.")
}
if log.GetLevel() < log.DebugLevel {
// Klog V2 is used by k8s.io/apimachinery/pkg/labels and can throw (a lot) of irrelevant logs
// See https://github.com/kubernetes-sigs/external-dns/issues/2348
defer klog.ClearLogger()
klog.SetLogger(logr.Discard())
}
log.Info(externaldns.Banner())
ctx, cancel := context.WithCancel(context.Background())
go serveMetrics(cfg.MetricsAddress)
go handleSigterm(cancel)
sCfg := source.NewSourceConfig(cfg)
endpointsSource, err := buildSource(ctx, sCfg)
if err != nil {
log.Fatal(err) // nolint: gocritic // exitAfterDefer
}
domainFilter := endpoint.NewDomainFilterWithOptions(
endpoint.WithDomainFilter(cfg.DomainFilter),
endpoint.WithDomainExclude(cfg.DomainExclude),
endpoint.WithRegexDomainFilter(cfg.RegexDomainFilter),
endpoint.WithRegexDomainExclude(cfg.RegexDomainExclude),
)
prvdr, err := providerfactory.Select(ctx, cfg, domainFilter)
if err != nil {
log.Fatal(err)
}
if cfg.WebhookServer {
webhookapi.StartHTTPApi(prvdr, nil, cfg.WebhookProviderReadTimeout, cfg.WebhookProviderWriteTimeout, "127.0.0.1:8888")
os.Exit(0)
}
ctrl, err := buildController(ctx, cfg, sCfg, endpointsSource, prvdr, domainFilter)
if err != nil {
log.Fatal(err)
}
if cfg.Once {
err := ctrl.RunOnce(ctx)
if err != nil {
log.Fatal(err)
}
os.Exit(0)
}
if cfg.UpdateEvents {
// Add RunOnce as the handler function that will be called when ingress/service sources have changed.
// Note that k8s Informers will perform an initial list operation, which results in the handler
// function initially being called for every Service/Ingress that exists
ctrl.Source.AddEventHandler(ctx, func() { ctrl.ScheduleRunOnce(time.Now()) })
}
ctrl.ScheduleRunOnce(time.Now())
ctrl.Run(ctx)
}
func buildController(
ctx context.Context,
cfg *externaldns.Config,
sCfg *source.Config,
src source.Source,
p provider.Provider,
filter *endpoint.DomainFilter,
) (*Controller, error) {
policy, ok := plan.Policies[cfg.Policy]
if !ok {
return nil, fmt.Errorf("unknown policy: %s", cfg.Policy)
}
reg, err := registryfactory.Select(cfg, p)
if err != nil {
return nil, err
}
eventsCfg := events.NewConfig(
events.WithEmitEvents(cfg.EmitEvents),
events.WithDryRun(cfg.DryRun))
var eventEmitter events.EventEmitter
if eventsCfg.IsEnabled() {
kubeClient, err := sCfg.ClientGenerator().KubeClient()
if err != nil {
return nil, err
}
eventCtrl, err := events.NewEventController(kubeClient.EventsV1(), eventsCfg)
if err != nil {
return nil, err
}
eventCtrl.Run(ctx)
eventEmitter = eventCtrl
}
return &Controller{
Source: src,
Registry: reg,
Policy: policy,
Interval: cfg.Interval,
DomainFilter: filter,
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
ExcludeRecordTypes: cfg.ExcludeDNSRecordTypes,
MinEventSyncInterval: cfg.MinEventSyncInterval,
TXTOwnerOld: cfg.TXTOwnerOld,
EventEmitter: eventEmitter,
}, nil
}
// This function configures the logger format and level based on the provided configuration.
func configureLogger(cfg *externaldns.Config) {
if cfg.LogFormat == "json" {
log.SetFormatter(&log.JSONFormatter{})
}
ll, err := log.ParseLevel(cfg.LogLevel)
if err != nil {
log.Fatalf("failed to parse log level: %v", err)
}
log.SetLevel(ll)
}
// buildSource creates and configures the source(s) for endpoint discovery based on the provided configuration.
// It initializes the source configuration, generates the required sources, and combines them into a single,
// deduplicated source. Returns the combined source or an error if source creation fails.
func buildSource(ctx context.Context, cfg *source.Config) (source.Source, error) {
sources, err := source.ByNames(ctx, cfg, cfg.ClientGenerator())
if err != nil {
return nil, err
}
opts := wrappers.NewConfig(
wrappers.WithDefaultTargets(cfg.DefaultTargets),
wrappers.WithForceDefaultTargets(cfg.ForceDefaultTargets),
wrappers.WithNAT64Networks(cfg.NAT64Networks),
wrappers.WithTargetNetFilter(cfg.TargetNetFilter),
wrappers.WithExcludeTargetNets(cfg.ExcludeTargetNets),
wrappers.WithMinTTL(cfg.MinTTL),
wrappers.WithProvider(cfg.Provider),
wrappers.WithPreferAlias(cfg.PreferAlias))
return wrappers.WrapSources(sources, opts)
}
// handleSigterm listens for a SIGTERM signal and triggers the provided cancel function
// to gracefully terminate the application. It logs a message when the signal is received.
func handleSigterm(cancel func()) {
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGTERM)
<-signals
log.Info("Received SIGTERM. Terminating...")
cancel()
}
// serveMetrics starts an HTTP server that serves health and metrics endpoints.
// The /healthz endpoint returns a 200 OK status to indicate the service is healthy.
// The /metrics endpoint serves Prometheus metrics.
// The server listens on the specified address and logs debug information about the endpoints.
func serveMetrics(address string) {
http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
})
log.Debugf("serving 'healthz' on '%s/healthz'", address)
log.Debugf("serving 'metrics' on '%s/metrics'", address)
log.Debugf("registered '%d' metrics", len(metrics.RegisterMetric.Metrics))
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(address, nil))
}
================================================
FILE: controller/execute_test.go
================================================
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"bytes"
"context"
"errors"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"testing"
"time"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
provider "sigs.k8s.io/external-dns/provider/factory"
"sigs.k8s.io/external-dns/source"
)
// Logger
func TestConfigureLogger(t *testing.T) {
tests := []struct {
name string
cfg *externaldns.Config
wantLevel log.Level
wantJSON bool
wantErr bool
wantErrMsg string
}{
{
name: "Default log format and level",
cfg: &externaldns.Config{
LogLevel: "info",
LogFormat: "text",
},
wantLevel: log.InfoLevel,
},
{
name: "JSON log format",
cfg: &externaldns.Config{
LogLevel: "debug",
LogFormat: "json",
},
wantLevel: log.DebugLevel,
wantJSON: true,
},
{
name: "Invalid log level",
cfg: &externaldns.Config{
LogLevel: "invalid",
LogFormat: "text",
},
wantLevel: log.InfoLevel,
wantErr: true,
wantErrMsg: "failed to parse log level",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.wantErr {
// Capture and suppress fatal exit; restore logger after test
logger := log.StandardLogger()
prevOut := logger.Out
prevExit := logger.ExitFunc
b := new(bytes.Buffer)
var captureLogFatal bool
logger.ExitFunc = func(int) { captureLogFatal = true }
logger.SetOutput(b)
t.Cleanup(func() {
logger.SetOutput(prevOut)
logger.ExitFunc = prevExit
})
configureLogger(tt.cfg)
assert.True(t, captureLogFatal)
assert.Contains(t, b.String(), tt.wantErrMsg)
} else {
// Save and restore logger state to avoid leaking between tests
logger := log.StandardLogger()
prevFormatter := logger.Formatter
prevLevel := log.GetLevel()
t.Cleanup(func() {
log.SetLevel(prevLevel)
logger.SetFormatter(prevFormatter)
})
configureLogger(tt.cfg)
assert.Equal(t, tt.wantLevel, log.GetLevel())
if tt.wantJSON {
assert.IsType(t, &log.JSONFormatter{}, log.StandardLogger().Formatter)
} else {
assert.IsType(t, &log.TextFormatter{}, log.StandardLogger().Formatter)
}
}
})
}
}
func TestBuildSourceWithWrappers(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
}))
defer svr.Close()
tests := []struct {
name string
cfg *externaldns.Config
asserts func(*testing.T, *externaldns.Config)
}{
{
name: "configuration with target filter wrapper",
cfg: &externaldns.Config{
APIServerURL: svr.URL,
Sources: []string{"fake"},
TargetNetFilter: []string{"10.0.0.0/8"},
},
},
{
name: "configuration with nat64 networks",
cfg: &externaldns.Config{
APIServerURL: svr.URL,
Sources: []string{"fake"},
NAT64Networks: []string{"2001:db8::/96"},
},
},
{
name: "default configuration",
cfg: &externaldns.Config{
APIServerURL: svr.URL,
Sources: []string{"fake"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := buildSource(t.Context(), source.NewSourceConfig(tt.cfg))
require.NoError(t, err)
})
}
}
// Helper used by runExecuteSubprocess.
func TestHelperProcess(_ *testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
// Parse args after the "--" sentinel.
idx := -1
for i, a := range os.Args {
if a == "--" {
idx = i
break
}
}
var args []string
if idx >= 0 {
args = os.Args[idx+1:]
}
os.Args = append([]string{"external-dns"}, args...)
Execute()
}
// runExecuteSubprocess runs Execute in a separate process and returns exit code.
func runExecuteSubprocess(t *testing.T, args []string) (int, error) {
t.Helper()
// make sure the subprocess does not run forever
ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second)
defer cancel()
// TODO: investigate why -test.run=TestHelperProcess
cmdArgs := append([]string{"-test.run=TestHelperProcess", "--"}, args...)
cmd := exec.CommandContext(ctx, os.Args[0], cmdArgs...)
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
err := cmd.Run()
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return -1, ctx.Err()
}
if err == nil {
return 0, nil
}
ee := &exec.ExitError{}
if errors.As(err, &ee) {
return ee.ExitCode(), nil
}
return -1, err
}
func TestExecuteOnceDryRunExitsZero(t *testing.T) {
// Use :0 for an ephemeral metrics port.
code, err := runExecuteSubprocess(t, []string{
"--source", "fake",
"--provider", "inmemory",
"--once",
"--dry-run",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.Equal(t, 0, code)
}
func TestExecuteUnknownProviderExitsNonZero(t *testing.T) {
code, err := runExecuteSubprocess(t, []string{
"--source", "fake",
"--provider", "unknown",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
func TestExecuteValidationErrorNoSources(t *testing.T) {
code, err := runExecuteSubprocess(t, []string{
"--provider", "inmemory",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
func TestExecuteFlagParsingErrorInvalidLogFormat(t *testing.T) {
code, err := runExecuteSubprocess(t, []string{
"--log-format", "invalid",
// Provide minimal required flags to keep errors focused on parsing
"--source", "fake",
"--provider", "inmemory",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// Config validation failure triggers log.Fatalf.
func TestExecuteConfigValidationErrorExitsNonZero(t *testing.T) {
code, err := runExecuteSubprocess(t, []string{
"--source", "fake",
// Choose a provider with validation that fails without required flags
"--provider", "azure",
// No --azure-config-file provided
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// buildSource failure triggers log.Fatal.
func TestExecuteBuildSourceErrorExitsNonZero(t *testing.T) {
// Use a valid source name (ingress) and an invalid kubeconfig path to
// force client creation failure inside buildSource.
code, err := runExecuteSubprocess(t, []string{
"--source", "ingress",
"--kubeconfig", "this/path/does/not/exist",
"--provider", "inmemory",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// RunOnce error exits non-zero.
func TestExecuteRunOnceErrorExitsNonZero(t *testing.T) {
// Connector source dials a TCP server; use a closed port to fail.
code, err := runExecuteSubprocess(t, []string{
"--source", "connector",
"--connector-source-server", "127.0.0.1:1",
"--provider", "inmemory",
"--once",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// Run loop error exits non-zero.
func TestExecuteRunLoopErrorExitsNonZero(t *testing.T) {
code, err := runExecuteSubprocess(t, []string{
"--source", "connector",
"--connector-source-server", "127.0.0.1:1",
"--provider", "inmemory",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// buildController registry-creation failure triggers log.Fatal.
func TestExecuteBuildControllerErrorExitsNonZero(t *testing.T) {
code, err := runExecuteSubprocess(t, []string{
"--source", "fake",
"--provider", "inmemory",
"--registry", "dynamodb",
// Force NewDynamoDBRegistry to fail validation by using empty owner id
"--txt-owner-id", "",
"--metrics-address", ":0",
})
require.NoError(t, err)
assert.NotEqual(t, 0, code)
}
// Controller run loop stops on context cancel.
func TestControllerRunCancelContextStopsLoop(t *testing.T) {
// Minimal controller using fake source and inmemory provider.
cfg := &externaldns.Config{
Sources: []string{"fake"},
Provider: "inmemory",
LogLevel: "error",
LogFormat: "text",
Policy: "sync",
Registry: "txt",
TXTOwnerID: "test-owner",
}
sCfg := source.NewSourceConfig(cfg)
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
src, err := buildSource(ctx, sCfg)
require.NoError(t, err)
domainFilter := endpoint.NewDomainFilterWithOptions(
endpoint.WithDomainFilter(cfg.DomainFilter),
endpoint.WithDomainExclude(cfg.DomainExclude),
endpoint.WithRegexDomainFilter(cfg.RegexDomainFilter),
endpoint.WithRegexDomainExclude(cfg.RegexDomainExclude),
)
p, err := provider.Select(ctx, cfg, domainFilter)
require.NoError(t, err)
ctrl, err := buildController(ctx, cfg, sCfg, src, p, domainFilter)
require.NoError(t, err)
done := make(chan struct{})
go func() {
ctrl.Run(ctx)
close(done)
}()
cancel()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Fatal("controller did not stop after context cancellation")
}
}
================================================
FILE: controller/metrics.go
================================================
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"github.com/prometheus/client_golang/prometheus"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/metrics"
)
var (
registryErrorsTotal = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "registry",
Name: "errors_total",
Help: "Number of Registry errors.",
},
)
sourceErrorsTotal = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "source",
Name: "errors_total",
Help: "Number of Source errors.",
},
)
sourceEndpointsTotal = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "source",
Name: "endpoints_total",
Help: "Number of Endpoints in all sources",
},
)
registryEndpointsTotal = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "registry",
Name: "endpoints_total",
Help: "Number of Endpoints in the registry",
},
)
lastSyncTimestamp = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "controller",
Name: "last_sync_timestamp_seconds",
Help: "Timestamp of last successful sync with the DNS provider",
},
)
lastReconcileTimestamp = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "controller",
Name: "last_reconcile_timestamp_seconds",
Help: "Timestamp of last attempted sync with the DNS provider",
},
)
controllerNoChangesTotal = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "controller",
Name: "no_op_runs_total",
Help: "Number of reconcile loops ending up with no changes on the DNS provider side.",
},
)
deprecatedRegistryErrors = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "registry",
Name: "errors_total",
Help: "Number of Registry errors.",
},
)
deprecatedSourceErrors = metrics.NewCounterWithOpts(
prometheus.CounterOpts{
Subsystem: "source",
Name: "errors_total",
Help: "Number of Source errors.",
},
)
registryRecords = metrics.NewGaugedVectorOpts(
prometheus.GaugeOpts{
Subsystem: "registry",
Name: "records",
Help: "Number of registry records partitioned by label name (vector).",
},
[]string{"record_type"},
)
sourceRecords = metrics.NewGaugedVectorOpts(
prometheus.GaugeOpts{
Subsystem: "source",
Name: "records",
Help: "Number of source records partitioned by label name (vector).",
},
[]string{"record_type"},
)
verifiedRecords = metrics.NewGaugedVectorOpts(
prometheus.GaugeOpts{
Subsystem: "controller",
Name: "verified_records",
Help: "Number of DNS records that exists both in source and registry (vector).",
},
[]string{"record_type"},
)
consecutiveSoftErrors = metrics.NewGaugeWithOpts(
prometheus.GaugeOpts{
Subsystem: "controller",
Name: "consecutive_soft_errors",
Help: "Number of consecutive soft errors in reconciliation loop.",
},
)
)
func init() {
metrics.RegisterMetric.MustRegister(registryErrorsTotal)
metrics.RegisterMetric.MustRegister(sourceErrorsTotal)
metrics.RegisterMetric.MustRegister(sourceEndpointsTotal)
metrics.RegisterMetric.MustRegister(registryEndpointsTotal)
metrics.RegisterMetric.MustRegister(lastSyncTimestamp)
metrics.RegisterMetric.MustRegister(lastReconcileTimestamp)
metrics.RegisterMetric.MustRegister(deprecatedRegistryErrors)
metrics.RegisterMetric.MustRegister(deprecatedSourceErrors)
metrics.RegisterMetric.MustRegister(controllerNoChangesTotal)
metrics.RegisterMetric.MustRegister(registryRecords)
metrics.RegisterMetric.MustRegister(sourceRecords)
metrics.RegisterMetric.MustRegister(verifiedRecords)
metrics.RegisterMetric.MustRegister(consecutiveSoftErrors)
}
type dnsKey struct {
name string
recordType string
}
// countMatchingAddressRecords counts records that exist in both endpoints and registry.
func countMatchingAddressRecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint, metric metrics.GaugeVecMetric) {
metric.Gauge.Reset()
registry := make(map[dnsKey]struct{}, len(registryRecords))
for _, r := range registryRecords {
registry[dnsKey{r.DNSName, r.RecordType}] = struct{}{}
}
counts := make(map[string]float64)
for _, ep := range endpoints {
if _, found := registry[dnsKey{ep.DNSName, ep.RecordType}]; found {
counts[ep.RecordType]++
}
}
for recordType, count := range counts {
metric.AddWithLabels(count, recordType)
}
}
// countAddressRecords counts each record type in the provided endpoints slice.
func countAddressRecords(endpoints []*endpoint.Endpoint, metric metrics.GaugeVecMetric) {
metric.Gauge.Reset()
counts := make(map[string]float64)
for _, ep := range endpoints {
counts[ep.RecordType]++
}
for recordType, count := range counts {
metric.AddWithLabels(count, recordType)
}
}
================================================
FILE: controller/metrics_test.go
================================================
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/internal/testutils"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/plan"
registryfactory "sigs.k8s.io/external-dns/registry/factory"
)
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{},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
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"},
},
},
}},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
func TestVerifyAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
},
[]*plan.Changes{},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
}},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
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"},
},
},
}},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
func TestAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
}},
)
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, sourceRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, sourceRecords.Gauge, map[string]string{"record_type": "aaaa"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, verifiedRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 1, verifiedRecords.Gauge, map[string]string{"record_type": "aaaa"})
}
func TestGaugeMetricsWithMixedRecords(t *testing.T) {
ctrl := newMixedRecordsFixture()
assert.NoError(t, ctrl.RunOnce(t.Context()))
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 534, sourceRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 324, sourceRecords.Gauge, map[string]string{"record_type": "aaaa"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 2, sourceRecords.Gauge, map[string]string{"record_type": "cname"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 11, sourceRecords.Gauge, map[string]string{"record_type": "srv"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 5334, registryRecords.Gauge, map[string]string{"record_type": "a"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 324, registryRecords.Gauge, map[string]string{"record_type": "aaaa"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 0, registryRecords.Gauge, map[string]string{"record_type": "mx"})
testutils.TestHelperVerifyMetricsGaugeVectorWithLabels(t, 43, registryRecords.Gauge, map[string]string{"record_type": "ptr"})
}
func newMixedRecordsFixture() *Controller {
configuredEndpoints := testutils.GenerateTestEndpointsByType(map[string]int{
endpoint.RecordTypeA: 534,
endpoint.RecordTypeAAAA: 324,
endpoint.RecordTypeCNAME: 2,
endpoint.RecordTypeTXT: 56,
endpoint.RecordTypeSRV: 11,
endpoint.RecordTypeNS: 3,
})
providerEndpoints := testutils.GenerateTestEndpointsByType(map[string]int{
endpoint.RecordTypeA: 5334,
endpoint.RecordTypeAAAA: 324,
endpoint.RecordTypeCNAME: 23,
endpoint.RecordTypeTXT: 6,
endpoint.RecordTypeSRV: 25,
endpoint.RecordTypeNS: 1,
endpoint.RecordTypePTR: 43,
})
cfg := externaldns.NewConfig()
cfg.Registry = externaldns.RegistryNoop
cfg.ManagedDNSRecordTypes = endpoint.KnownRecordTypes
source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil)
provider := &filteredMockProvider{
RecordsStore: providerEndpoints,
}
r, _ := registryfactory.Select(cfg, provider)
return &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
DomainFilter: endpoint.NewDomainFilter([]string{}),
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
}
}
func BenchmarkGaugeMetricsWithMixedRecords(b *testing.B) {
ctrl := newMixedRecordsFixture()
for b.Loop() {
if err := ctrl.RunOnce(b.Context()); err != nil {
b.Fatal(err)
}
}
}
================================================
FILE: docs/20190708-external-dns-incubator.md
================================================
# Move ExternalDNS out of Kubernetes incubator
<!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 -->
- [Summary](#summary)
- [Motivation](#motivation)
- [Goals](#goals)
- [Proposal](#proposal)
- [Details](#details)
- [Graduation Criteria](#graduation-criteria)
- [Maintainers](#maintainers)
- [Release process, artifacts](#release-process-artifacts)
- [Risks and Mitigations](#risks-and-mitigations)
<!-- /TOC -->
## Summary
[ExternalDNS](https://github.com/kubernetes-sigs/external-dns) is a project that synchronizes Kubernetes' Services, Ingresses and other Kubernetes resources to DNS backends for several DNS providers.
The project was started as a Kubernetes Incubator project in February 2017 and being the Kubernetes incubation initiative officially over, the maintainers want to propose the project to be moved to the kubernetes GitHub organization or to kubernetes-sigs, under the sponsorship of sig-network.
## Motivation
ExternalDNS started as a community project with the goal of unifying several existing projects that were trying to solve the same problem: create DNS records for Kubernetes resources on several DNS backends.
When the project was proposed (see the [original discussion](https://github.com/kubernetes/kubernetes/issues/28525#issuecomment-270766227)), there were at least 3 existing implementations of the same functionality:
- Mate - [https://github.com/linki/mate](https://github.com/linki/mate)
- DNS-controller from kops - [https://github.com/kubernetes/kops/tree/HEAD/dns-controller](https://github.com/kubernetes/kops/tree/HEAD/dns-controller)
- Route53-kubernetes - [https://github.com/wearemolecule/route53-kubernetes](https://github.com/wearemolecule/route53-kubernetes)
ExternalDNS' goal from the beginning was to provide an officially supported solution to those problems.
After two years of development, the project is still in the kubernetes-sigs.
The incubation has been officially discontinued and to quote @thockin "Incubator projects should either become real projects in Kubernetes,
shut themselves down, or move elsewhere" (see original thread [google group](https://groups.google.com/forum/#!topic/kubernetes-sig-network/fvpDC_nxtEM)).
This KEP proposes to move ExternalDNS to the main Kubernetes organization or kubernetes-sigs. The "Proposal" section details the reasons behind it.
### Goals
The only goal of this KEP is to establish consensus regarding the future of the ExternalDNS project and determine where it belongs.
## Proposal
This KEP is about moving External DNS out of the Kubernetes incubator. This section will cover the reasons why External DNS is useful and what the community would miss in case the project would be discontinued or moved under another organization.
External DNS...
- Is the de facto solution to create DNS records for several Kubernetes resources.
- Is a vital component to achieve an experience close to a PaaS that many Kubernetes users try to replicate on top of Kubernetes, by allowing to automatically create DNS records for web applications.
- Supports already 18 different DNS providers including all major public clouds (AWS, Azure, GCP).
Given that the kubernetes-sigs organization will eventually be shut down, the possible alternatives to moving to be an official Kubernetes project are the following:
- Shut down the project
- Move the project elsewhere
We believe that those alternatives would result in a worse outcome for the community compared to moving the project to any of the other official Kubernetes organizations.
In fact, shutting down ExternalDNS can cause:
- The community to rebuild the same solution as already happened multiple times before the project was launched. Currently ExternalDNS is easy to be found, referenced in many articles/tutorials and for that reason not exposed to that risk.
- Existing users of the projects to be left without a future proof working solution.
Moving the ExternalDNS project outside of Kubernetes projects would cause:
- Problems (re-)establishing user trust which could eventually lead to fragmentation and duplication.
- It would be hard to establish in which organization the project should be moved to.
- Lack of resources to test, lack of issue management via automation.
For those reasons, we propose to move ExternalDNS out of the Kubernetes incubator, to live either under the kubernetes or kubernetes-sigs organization to keep being a vital part of the Kubernetes ecosystem.
## Details
### Graduation Criteria
ExternalDNS is a two years old project widely used in production by many companies. The implementation for the three major cloud providers (AWS, Azure, GCP) is stable, not changing its logic and the project is being used in production by many company using Kubernetes.
We have evidence that many companies are using ExternalDNS in production, but it is out of scope for this proposal to collect a comprehensive list of companies.
The project was quoted by a number of tutorials on the web, including the [official tutorials from AWS](https://aws.amazon.com/blogs/opensource/unified-service-discovery-ecs-kubernetes/).
ExternalDNS can't be considered to be "done": while the core functionality has been implemented, there is lack of integration testing and structural changes that are needed.
Those are identified in the project roadmap, which is roughly made of the following items:
- Decoupling of the providers
- Implementation proposal
- Development
- Bug fixing and performance optimization (i.e. rate limiting on cloud providers)
- Integration testing suite, to be implemented at least for the "stable" providers
For those reasons, we consider ExternalDNS to be in Beta state as a project. We believe that once the items mentioned above will be implemented, the project can reach a declared GA status.
There are a number of other factors that need to be covered to fully describe the state of the project, including who are the maintainers, the way we release and manage the project and so on.
#### Maintainers
The project has the following maintainers:
- hjacobs
- Raffo
- linki
- njuettner
The list of maintainers shrunk over time as people moved out of the original development team (all the team members were working at Zalando at the time of project creation) and the project required less work.
The high number of providers contributed to the project pose a maintainability challenge: it is hard to bring the providers forward in terms of functionalities or even test them.
The maintainers believe that the plan to transform the current Provider interface from a Go interface to an API will allow for enough decoupling and to hand over the maintenance of those plugins to the contributors themselves, see the risk and mitigations section for further details.
### Release process, artifacts
The project uses the free quota of TravisCI to run tests for the project.
The release pipeline for the project is currently fully owned by Zalando. It runs on the internal system of the company (closed source) which external maintainers/users can't access and that pushes images to the publicly accessible docker registry available at the URL `registry.opensource.zalan.do`.
The docker registry service is provided as best effort with no sort of SLA and the maintainers team openly suggests the users to build and maintain their own docker image based on the provided Dockerfiles.
Providing a vanity URL for the docker images was considered a non goal till now, but the community seems to be wanting official images from a GCR domain, similarly to what is available for other parts of official Kubernetes projects.
ExternalDNS does not follow a specific release cycle. Releases are made often when there are major contributions (i.e. new providers) or important bug fixes. That said, the default branch is considered stable and can be used as well to build images.
### Risks and Mitigations
The following are risks that were identified:
- Low number of maintainers: we are currently facing issues keeping up with the number of pull requests and issues giving the low number of maintainers. The list of maintainers already shrunk from 8 maintainers to 4.
- Issues maintaining community contributed providers: we often lack access to external providers (i.e. InfoBlox, etc.) and this means that we cannot verify the implementations and/or run regression tests that go beyond unit testing.
- Somewhat low quality of releases due to lack of integration testing.
We think that the following actions will constitute appropriate mitigations:
- Decoupling the providers via an API will allow us to resolve the problem of the providers. Being the project already more than 2 years old and given that there are 18 providers implemented, we possess enough information to define an API that we can be stable in a short timeframe.
- Once this is stable, the problem of testing the providers can be deferred to be a provider's responsibility. This will also reduce the scope of External DNS core code, which means that there will be no need for a further increase of the maintaining team.
- We added integration testing for the main cloud providers to the roadmap for the 1.0 release to make sure that we cover the mostly used ones.
- We believe that this item should be tackled independently from the decoupling of providers as it would be capable of generating value independently from the result of the decoupling efforts.
- With the move to the Kubernetes incubation, we hope that we will be able to access the testing resources of the Kubernetes project.
================================================
FILE: docs/OWNERS
================================================
# See the OWNERS docs at https://go.k8s.io/owners
labels:
- docs
================================================
FILE: docs/advanced/configuration-precedence.md
================================================
## Annotations vs. CLI Flags Precedence
ExternalDNS configuration can come from these sources: resource annotations, CLI flags, environment variables, and defaults.
The effective value is determined by the following precedence order:
```mermaid
flowchart TD
A[1. Resource Annotations] -->|Override| Result
B[2. CLI Flags] -->|Used if no annotation| Result
C[3. Environment Variables] -->|May override defaults<br/>and in some cases flags/annotations| Result
D[4. Defaults] -->|Fallback| Result
subgraph Flags
B1[Filter Flags: --flag-with-filter] -->|Define scope<br/>Annotations outside scope ignored| B
B2[Non-filter Flags] -->|Apply if no annotation| B
end
Result[Effective ExternalDNS Configuration]
A --> Result
B --> Result
D --> Result
```
1. **Annotations**
- Most configuration options can be set via annotations on supported resources.
- When present, annotations override the corresponding CLI flags and defaults.
- Exception: should be documented.
- Exception: ignored when applied to `kind: DNSEndpoint`
- Exception: filter flags (e.g. `--service-type-filter`, `--source`) define the *scope* of resources considered.
2. **CLI Flags**
- Non-filter flags apply if no annotation overrides them.
- Filter flags (`--source`, `--service-type-filter`, `--*-filter`) limit which resources are processed.
- Annotations outside the defined scope are ignored.
- If a resource is excluded by a filter, annotations configured on the resource or defaults will not be applied.
3. **Environment Variables**
- May override defaults, and in some cases may take precedence over CLI flags and annotations.
- Behavior depends on how the variable is mapped in the code. Whether or not it replicates CLI flag or provider specific. Example: `kubectl` or `cloudflare`.
4. **Defaults**
- If none of the above specify a value, ExternalDNS falls back to its defaults.
================================================
FILE: docs/advanced/domain-filter.md
================================================
---
tags:
- advanced
- area/domain-filter
- domain-filter
---
# Domain Filter
> **Important:** Domain filter flags express application-level intent — they are not an
> enforcement boundary. Credentials (IAM policies, API token scopes, ACLs) are the real
> enforcement boundary. A misconfigured or missing flag will expose all zones the credentials
> can reach. Always scope API keys or IAM roles to only the specific zones external-dns manages;
> use these flags to complement that boundary, not replace it.
ExternalDNS has two modes for selecting which domains it manages: **plain domain filter** and **regex domain filter**.
Using any of the regex filter flags enables the **regex domain filter** mode, which overrides and ignores the **plain domain filter** flags.
**Domain filter flags**:
| Flag | Mode | Semantics |
| -------------------------- | ----- | -------------------------------------------------------------------------------------- |
| `--domain-filter` | plain | Suffix match — includes a domain and all its subdomains |
| `--exclude-domains` | plain | Suffix match — excludes a domain or subdomain from `--domain-filter` |
| `--regex-domain-filter` | regex | Full regex match — **overrides `--domain-filter`** when set |
| `--regex-domain-exclusion` | regex | Regex that removes matches from `--regex-domain-filter`; can also be used standalone |
All of these flags are applied to DNS record names. Providers that partition zones before managing records
(e.g., PowerDNS) also apply the filter to zone names.
## Plain domain filter
Specify one or more domain suffixes. ExternalDNS will manage any record whose name ends with one of
the provided values.
```sh
--domain-filter=example.com
--domain-filter=other.org
```
To exclude specific subdomains use `--exclude-domains`:
```sh
--domain-filter=example.com
--exclude-domains=staging.example.com
```
## Regex domain filter
`--regex-domain-filter` accepts a Go RE2 regular expression. Use it when suffix matching is not
expressive enough — for example, to select zones by region name pattern.
```sh
--regex-domain-filter='\.org$'
```
Use `--regex-domain-exclusion` to reject zones that would otherwise match:
```sh
--regex-domain-filter='^([\w-]+\.)*example\.com$'
--regex-domain-exclusion='^staging\.'
```
### Matching logic
Exclusion is always checked first:
1. If `--regex-domain-exclusion` matches → **rejected**
2. If `--regex-domain-filter` matches → **accepted**
3. If only `--regex-domain-exclusion` is set (the domain did not match) → **accepted** (exclusion-only mode)
4. If `--regex-domain-filter` is set (the domain did not match) → **rejected**
```mermaid
flowchart TD
A["Domain candidate"] --> B{"Is regex filter<br/>or exclusion set?"}
B -- "No (use plain filters)" --> C{"Matches<br/> --domain-filter?"}
C -- "No" --> REJECT["❌ Rejected"]
C -- "Yes" --> D{"Matches<br/> --exclude-domains?"}
D -- "Yes" --> REJECT
D -- "No" --> ACCEPT["✅ Accepted"]
B -- "Yes (regex mode)" --> E{"Matches<br/>--regex-domain-exclusion?"}
E -- "Yes" --> REJECT
E -- "No" --> F{"--regex-domain-filter set?"}
F -- "No (exclusion-only mode)" --> ACCEPT
F -- "Yes" --> G{"Matches<br/>--regex-domain-filter?"}
G -- "Yes" --> ACCEPT
G -- "No" --> REJECT
```
### Examples
**Include only `.org` domains:**
```sh
--regex-domain-filter='\.org$'
```
**Include a specific set of domains:**
```sh
--regex-domain-filter='(?:foo|bar)\.org$'
```
**Include with exclusion:**
```sh
# foo.org, bar.org, a.example.foo.org → accepted
# example.foo.org, example.bar.org → rejected
--regex-domain-filter='(?:foo|bar)\.org$'
--regex-domain-exclusion='^example\.(?:foo|bar)\.org$'
```
**Production environment with temp exclusion:**
```sh
--regex-domain-filter='\.prod\.example\.com$'
--regex-domain-exclusion='^temp-'
```
**Exclusion-only (accept everything except a pattern):**
```sh
--regex-domain-exclusion='test-v1\.3\.example-test\.in'
```
**Exclude a complex pattern:**
```sh
--regex-domain-exclusion='^(internal|private)-.*\.example\.com$'
```
### Zone-partitioning pitfall: `+` vs `*`
The most common misconfiguration when filtering zones (not just records) is using `[\w-]+` (one or
more) instead of `([\w-]+\.)*` (zero or more) for the label-prefix group. Because `+` requires at
least one repetition:
- The **apex zone** (`example.com`) has no label prefix and will never match.
- **Multi-label subdomain zones** (`long.sub.example.com`) contain dots that `[\w-]+` cannot span.
Both zone types end up unmanaged, causing ExternalDNS to log `Ignoring Endpoint` for every record
they contain with no other indication of what went wrong.
| Regex | Matches | Misses |
|-----------------------------|----------------------------------------------------------|---------------------------------------|
| `^[\w-]+\.example\.com$` | `sub.example.com` | `example.com`, `long.sub.example.com` |
| `^([\w-]+\.)*example\.com$` | `example.com`, `sub.example.com`, `long.sub.example.com` | — |
Always use `*` so the apex matches on zero repetitions and subdomain zones match on one or more.
### Multi-region example
```sh
--regex-domain-filter='^([\w-]+\.)*(?:us-east-1|eu-central-1)\.example\.com$'
--regex-domain-exclusion='^staging\.'
```
| Zone | Result |
|---------------------------------|-------------|
| `us-east-1.example.com` | managed |
| `prod.us-east-1.example.com` | managed |
| `eu-central-1.example.com` | managed |
| `staging.us-east-1.example.com` | excluded |
| `other.com` | not managed |
## Notes
- **Regex syntax**: Standard Go RE2. Escape dots (`\.`) and use anchors (`^`, `$`) where precision matters.
- **Case sensitivity**: Matching is case-sensitive. Domains are lowercased and trailing dots stripped before matching.
- **IDN / Unicode**: Domains are converted to Unicode form (IDNA) before matching, so patterns against emoji or Unicode labels work as expected.
- **Mutual exclusivity**: Once a regex flag is non-empty, list-based filters are ignored entirely.
## Debugging
If records are silently dropped, look for `Ignoring Endpoint` in the logs — this means no managed
zone matched the record. To isolate whether the domain filter is the cause, temporarily switch to
`--domain-filter` with the plain suffix; if records reappear, the regex is the problem.
## Testing your regex
Before deploying, validate the regex against real zone names:
- [regex101.com](https://regex101.com/) — interactive tester; select the **Golang** flavour to match Go's RE2 engine exactly. Paste each zone name on a separate line and enable the **global** flag.
- AI assistants (ChatGPT, Claude, DeepWiki, etc.) — describe the zones you want to match/exclude and ask for a regex; always verify the output in regex101 before use.
## See Also
- [Flags reference](../flags.md) — `--domain-filter`, `--exclude-domains`, `--regex-domain-filter`, `--regex-domain-exclusion`
- [AWS filters tutorial](../tutorials/aws-filters.md) — filter flag interaction table
- [FAQ](../faq.md) — general configuration questions
================================================
FILE: docs/advanced/events.md
================================================
---
tags: ["advanced", "area/events", "events"]
---
# Kubernetes Events in External-DNS
External-DNS manages DNS records dynamically based on Kubernetes resources like Services and Ingresses.
Emitting Kubernetes Events provides a lightweight observable way for users and systems to understand what External-DNS is doing, especially in production environments where DNS correctness is essential.
> Note; events is currently alpha feature. Functionality is limited and subject to change
## ✨ Why Events Matter
Kubernetes Events enable External-DNS to provide real-time feedback to users and controllers, complementing logs with a simpler way to track DNS changes. This enhances debugging, monitoring, and automation around DNS operations.
### Use Cases of Emitting Events
| Use Case | Description |
|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| **DNS Record Visibility** | Events show what DNS records were created, updated, or deleted (e.g., `Created A record "api.example.com"`). |
| **Developer Feedback** | Users deploying Ingresses or Services can see if External-DNS processed their resource. |
| **Surface Errors, Debugging and Troubleshooting** | Easily identify resource misannotations, sync failures, or IAM permission issues. |
| **Error Reporting** | Emit warning events when record sync fails due to provider issues, duplicate records, or misconfigurations. |
| **Integration with Alerting/Automation/Auditing** | This enables automated remediation or notifications when DNS sync fails or changes unexpectedly. |
| **Observability** | Trace why a DNS record wasn’t created. |
| **Alerting/automation** | Trigger actions based on failed events. |
| **Operator and Developer feedback** | It removes the black-box feeling of "I deployed an Ingress, but why doesn’t the DNS work?" |
## Consuming Events
You can observe External-DNS events using:
```sh
kubectl describe service <name>
kubectl get events --field-selector involvedObject.kind=S
gitextract_h6x703iq/
├── .editorconfig
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── ---bug-report.md
│ │ ├── --enhancement-request.md
│ │ ├── -support-request.md
│ │ └── create-release.md
│ ├── dependabot.yml
│ ├── labeler.yml
│ ├── pull_request_template.md
│ ├── renovate-config.js
│ ├── renovate.json
│ └── workflows/
│ ├── OWNERS
│ ├── ci.yaml
│ ├── codeql-analysis.yaml
│ ├── dependency-update.yaml
│ ├── docs.yaml
│ ├── end-to-end-tests.yml
│ ├── gh-workflow-approve.yaml
│ ├── json-yaml-validate.yml
│ ├── lint-test-chart.yaml
│ ├── lint.yaml
│ ├── release-chart.yaml
│ ├── staging-image-tester.yaml
│ └── validate-crd.yml
├── .gitignore
├── .golangci.yml
├── .ko.yaml
├── .markdownlint.json
├── .pre-commit-config.yaml
├── .spectral.yaml
├── .zappr.yaml
├── CONTRIBUTING.md
├── LICENSE.md
├── Makefile
├── OWNERS
├── README.md
├── SECURITY_CONTACTS
├── api/
│ └── webhook.yaml
├── apis/
│ ├── OWNERS
│ ├── api.go
│ └── v1alpha1/
│ ├── api.go
│ ├── dnsendpoint.go
│ ├── groupversion_info.go
│ └── zz_generated.deepcopy.go
├── charts/
│ └── OWNERS
├── cloudbuild.yaml
├── code-of-conduct.md
├── config/
│ └── crd/
│ └── standard/
│ └── dnsendpoints.externaldns.k8s.io.yaml
├── controller/
│ ├── OWNERS
│ ├── controller.go
│ ├── controller_test.go
│ ├── events.go
│ ├── events_test.go
│ ├── execute.go
│ ├── execute_test.go
│ ├── metrics.go
│ └── metrics_test.go
├── docs/
│ ├── 20190708-external-dns-incubator.md
│ ├── OWNERS
│ ├── advanced/
│ │ ├── configuration-precedence.md
│ │ ├── domain-filter.md
│ │ ├── events.md
│ │ ├── fqdn-templating.md
│ │ ├── import-records.md
│ │ ├── nat64.md
│ │ ├── operational-best-practices.md
│ │ ├── rate-limits.md
│ │ ├── split-horizon.md
│ │ └── ttl.md
│ ├── annotations/
│ │ └── annotations.md
│ ├── contributing/
│ │ ├── bug-report.md
│ │ ├── chart.md
│ │ ├── design.md
│ │ ├── dev-guide.md
│ │ ├── index.md
│ │ ├── source-wrappers.md
│ │ └── sources-and-providers.md
│ ├── deprecation.md
│ ├── faq.md
│ ├── flags.md
│ ├── initial-design.md
│ ├── monitoring/
│ │ ├── index.md
│ │ └── metrics.md
│ ├── overrides/
│ │ └── partials/
│ │ └── copyright.html
│ ├── proposal/
│ │ ├── 001-leader-election.md
│ │ ├── 002-internal-ipv6-handling-rollback.md
│ │ ├── 003-dnsendpoint-graduation-to-beta.md
│ │ ├── 004-gateway-api-annotation-placement.md
│ │ ├── design-template.md
│ │ └── multi-target.md
│ ├── providers.md
│ ├── registry/
│ │ ├── dynamodb.md
│ │ ├── registry.md
│ │ └── txt.md
│ ├── release.md
│ ├── scripts/
│ │ ├── index.html.gotmpl
│ │ └── requirements.txt
│ ├── snippets/
│ │ ├── contributing/
│ │ │ ├── collect-extdns-info.sh
│ │ │ └── collect-resources.sh
│ │ ├── exoscale/
│ │ │ ├── extdns.yaml
│ │ │ ├── how-to-test.yaml
│ │ │ └── rbac.yaml
│ │ ├── security-context/
│ │ │ └── extdns-limited-privilege.yaml
│ │ ├── traefik-proxy/
│ │ │ ├── ingress-route-default.yaml
│ │ │ ├── ingress-route-public-private.yaml
│ │ │ ├── traefik-public-private-config.yaml
│ │ │ ├── with-cluster-rbac.yaml
│ │ │ └── without-rbac.yaml
│ │ └── tutorials/
│ │ └── coredns/
│ │ ├── coredns-groups.yaml
│ │ ├── etcd.yaml
│ │ ├── fixtures.yaml
│ │ ├── kind.yaml
│ │ ├── values-coredns.yaml
│ │ └── values-extdns-coredns.yaml
│ ├── sources/
│ │ ├── about.md
│ │ ├── crd/
│ │ │ ├── dnsendpoint-aws-example.yaml
│ │ │ └── dnsendpoint-example.yaml
│ │ ├── crd.md
│ │ ├── f5-transportserver.md
│ │ ├── f5-virtualserver.md
│ │ ├── gateway-api.md
│ │ ├── gateway.md
│ │ ├── gloo-proxy.md
│ │ ├── index.md
│ │ ├── ingress.md
│ │ ├── istio.md
│ │ ├── kong.md
│ │ ├── mx-record.md
│ │ ├── nodes.md
│ │ ├── ns-record.md
│ │ ├── openshift.md
│ │ ├── pod.md
│ │ ├── service.md
│ │ ├── traefik-proxy.md
│ │ ├── txt-record.md
│ │ └── unstructured.md
│ ├── tutorials/
│ │ ├── akamai-edgedns.md
│ │ ├── alibabacloud.md
│ │ ├── anexia-engine.md
│ │ ├── aws-filters.md
│ │ ├── aws-load-balancer-controller.md
│ │ ├── aws-public-private-route53.md
│ │ ├── aws-sd.md
│ │ ├── aws.md
│ │ ├── azure-private-dns.md
│ │ ├── azure.md
│ │ ├── civo.md
│ │ ├── cloudflare.md
│ │ ├── contour.md
│ │ ├── coredns-etcd.md
│ │ ├── coredns.md
│ │ ├── crd.md
│ │ ├── dnsimple.md
│ │ ├── exoscale.md
│ │ ├── externalname.md
│ │ ├── gandi.md
│ │ ├── gke-nginx.md
│ │ ├── gke.md
│ │ ├── godaddy.md
│ │ ├── hostport.md
│ │ ├── ionoscloud.md
│ │ ├── kops-dns-controller.md
│ │ ├── kube-ingress-aws.md
│ │ ├── linode.md
│ │ ├── myra.md
│ │ ├── ns1.md
│ │ ├── oracle.md
│ │ ├── ovh.md
│ │ ├── pdns.md
│ │ ├── pihole.md
│ │ ├── plural.md
│ │ ├── rfc2136.md
│ │ ├── scaleway.md
│ │ ├── security-context.md
│ │ ├── transip.md
│ │ └── webhook-provider.md
│ └── version-update-playbook.md
├── e2e/
│ ├── deployment.yaml
│ ├── provider/
│ │ ├── coredns.yaml
│ │ └── etcd.yaml
│ └── service.yaml
├── endpoint/
│ ├── OWNERS
│ ├── crypto.go
│ ├── crypto_test.go
│ ├── domain_filter.go
│ ├── domain_filter_test.go
│ ├── endpoint.go
│ ├── endpoint_benchmark_test.go
│ ├── endpoint_test.go
│ ├── labels.go
│ ├── labels_test.go
│ ├── target_filter.go
│ ├── target_filter_test.go
│ ├── utils.go
│ ├── utils_test.go
│ └── zz_generated.deepcopy.go
├── external-dns.code-workspace
├── go.mod
├── go.sum
├── go.tool.mod
├── go.tool.sum
├── internal/
│ ├── OWNERS
│ ├── config/
│ │ └── config.go
│ ├── flags/
│ │ ├── binders.go
│ │ └── binders_test.go
│ ├── gen/
│ │ └── docs/
│ │ ├── flags/
│ │ │ ├── main.go
│ │ │ ├── main_test.go
│ │ │ └── templates/
│ │ │ └── flags.gotpl
│ │ ├── metrics/
│ │ │ ├── main.go
│ │ │ ├── main_test.go
│ │ │ └── templates/
│ │ │ └── metrics.gotpl
│ │ ├── render/
│ │ │ ├── render.go
│ │ │ └── render_test.go
│ │ └── sources/
│ │ ├── main.go
│ │ ├── main_test.go
│ │ └── templates/
│ │ └── sources.gotpl
│ ├── idna/
│ │ ├── idna.go
│ │ └── idna_test.go
│ ├── testresources/
│ │ ├── ca.pem
│ │ ├── client-cert-key.pem
│ │ └── client-cert.pem
│ └── testutils/
│ ├── endpoint.go
│ ├── endpoint_test.go
│ ├── env.go
│ ├── helpers.go
│ ├── helpers_test.go
│ ├── init.go
│ ├── log/
│ │ └── log.go
│ ├── metrics.go
│ └── mock_source.go
├── kustomize/
│ ├── OWNERS
│ ├── external-dns-clusterrole.yaml
│ ├── external-dns-clusterrolebinding.yaml
│ ├── external-dns-deployment.yaml
│ ├── external-dns-serviceaccount.yaml
│ └── kustomization.yaml
├── main.go
├── mkdocs.yml
├── pkg/
│ ├── OWNERS
│ ├── apis/
│ │ ├── OWNERS
│ │ └── externaldns/
│ │ ├── constants.go
│ │ ├── types.go
│ │ ├── types_test.go
│ │ ├── validation/
│ │ │ ├── validation.go
│ │ │ └── validation_test.go
│ │ ├── version.go
│ │ └── version_test.go
│ ├── client/
│ │ ├── OWNERS
│ │ ├── config.go
│ │ └── config_test.go
│ ├── events/
│ │ ├── OWNERS
│ │ ├── controller.go
│ │ ├── controller_test.go
│ │ ├── fake/
│ │ │ ├── fake.go
│ │ │ └── fake_test.go
│ │ ├── types.go
│ │ └── types_test.go
│ ├── http/
│ │ ├── drain.go
│ │ ├── drain_test.go
│ │ ├── http.go
│ │ ├── http_benchmark_test.go
│ │ └── http_test.go
│ ├── metrics/
│ │ ├── OWNERS
│ │ ├── labels.go
│ │ ├── metrics.go
│ │ ├── metrics_test.go
│ │ ├── models.go
│ │ └── models_test.go
│ ├── rfc2317/
│ │ ├── OWNERS
│ │ ├── arpa.go
│ │ └── arpa_test.go
│ └── tlsutils/
│ ├── OWNERS
│ ├── tlsconfig.go
│ └── tlsconfig_test.go
├── plan/
│ ├── OWNERS
│ ├── conflict.go
│ ├── conflict_test.go
│ ├── metrics.go
│ ├── metrics_test.go
│ ├── plan.go
│ ├── plan_test.go
│ ├── policy.go
│ └── policy_test.go
├── provider/
│ ├── OWNERS
│ ├── akamai/
│ │ ├── akamai.go
│ │ └── akamai_test.go
│ ├── alibabacloud/
│ │ ├── alibaba_cloud.go
│ │ └── alibaba_cloud_test.go
│ ├── aws/
│ │ ├── aws.go
│ │ ├── aws_fixtures_test.go
│ │ ├── aws_test.go
│ │ ├── aws_utils_test.go
│ │ ├── config.go
│ │ ├── config_test.go
│ │ ├── fixtures/
│ │ │ └── 160-plus-zones.yaml
│ │ ├── instrumented_config.go
│ │ └── instrumented_config_test.go
│ ├── awssd/
│ │ ├── aws_sd.go
│ │ ├── aws_sd_test.go
│ │ └── fixtures_test.go
│ ├── azure/
│ │ ├── azure.go
│ │ ├── azure_private_dns.go
│ │ ├── azure_privatedns_test.go
│ │ ├── azure_test.go
│ │ ├── common.go
│ │ ├── common_test.go
│ │ ├── config.go
│ │ ├── config_test.go
│ │ └── fixtures/
│ │ └── config_test.json
│ ├── blueprint/
│ │ ├── zone_cache.go
│ │ └── zone_cache_test.go
│ ├── cached_provider.go
│ ├── cached_provider_test.go
│ ├── civo/
│ │ ├── civo.go
│ │ └── civo_test.go
│ ├── cloudflare/
│ │ ├── OWNERS
│ │ ├── cloudflare.go
│ │ ├── cloudflare_batch.go
│ │ ├── cloudflare_batch_test.go
│ │ ├── cloudflare_custom_hostnames.go
│ │ ├── cloudflare_custom_hostnames_test.go
│ │ ├── cloudflare_regional.go
│ │ ├── cloudflare_regional_test.go
│ │ ├── cloudflare_test.go
│ │ ├── pagination.go
│ │ └── pagination_test.go
│ ├── coredns/
│ │ ├── OWNERS
│ │ ├── coredns.go
│ │ └── coredns_test.go
│ ├── dnsimple/
│ │ ├── dnsimple.go
│ │ └── dnsimple_test.go
│ ├── exoscale/
│ │ ├── exoscale.go
│ │ └── exoscale_test.go
│ ├── factory/
│ │ ├── provider.go
│ │ └── provider_test.go
│ ├── fakes/
│ │ └── provider.go
│ ├── gandi/
│ │ ├── client.go
│ │ ├── gandi.go
│ │ └── gandi_test.go
│ ├── godaddy/
│ │ ├── client.go
│ │ ├── client_test.go
│ │ ├── godaddy.go
│ │ └── godaddy_test.go
│ ├── google/
│ │ ├── google.go
│ │ └── google_test.go
│ ├── inmemory/
│ │ ├── inmemory.go
│ │ └── inmemory_test.go
│ ├── linode/
│ │ ├── linode.go
│ │ └── linode_test.go
│ ├── ns1/
│ │ ├── ns1.go
│ │ └── ns1_test.go
│ ├── oci/
│ │ ├── cache.go
│ │ ├── cache_test.go
│ │ ├── oci.go
│ │ └── oci_test.go
│ ├── ovh/
│ │ ├── ovh.go
│ │ └── ovh_test.go
│ ├── pdns/
│ │ ├── pdns.go
│ │ └── pdns_test.go
│ ├── pihole/
│ │ ├── client.go
│ │ ├── clientV6.go
│ │ ├── clientV6_test.go
│ │ ├── client_test.go
│ │ ├── pihole.go
│ │ ├── piholeV6_test.go
│ │ └── pihole_test.go
│ ├── plural/
│ │ ├── client.go
│ │ ├── plural.go
│ │ └── plural_test.go
│ ├── provider.go
│ ├── provider_test.go
│ ├── recordfilter.go
│ ├── recordfilter_test.go
│ ├── rfc2136/
│ │ ├── rfc2136.go
│ │ └── rfc2136_test.go
│ ├── scaleway/
│ │ ├── interface.go
│ │ ├── scaleway.go
│ │ └── scaleway_test.go
│ ├── transip/
│ │ ├── transip.go
│ │ └── transip_test.go
│ ├── webhook/
│ │ ├── api/
│ │ │ ├── httpapi.go
│ │ │ └── httpapi_test.go
│ │ ├── webhook.go
│ │ └── webhook_test.go
│ ├── zone_id_filter.go
│ ├── zone_id_filter_test.go
│ ├── zone_tag_filter.go
│ ├── zone_tag_filter_test.go
│ ├── zone_type_filter.go
│ ├── zone_type_filter_test.go
│ ├── zonefinder.go
│ └── zonefinder_test.go
├── registry/
│ ├── OWNERS
│ ├── awssd/
│ │ ├── OWNERS
│ │ ├── registry.go
│ │ └── registry_test.go
│ ├── dynamodb/
│ │ ├── OWNERS
│ │ ├── registry.go
│ │ └── registry_test.go
│ ├── factory/
│ │ ├── registry.go
│ │ └── registry_test.go
│ ├── mapper/
│ │ ├── mapper.go
│ │ └── mapper_test.go
│ ├── noop/
│ │ ├── OWNERS
│ │ ├── noop.go
│ │ └── noop_test.go
│ ├── registry.go
│ └── txt/
│ ├── OWNERS
│ ├── encryption_test.go
│ ├── registry.go
│ ├── registry_test.go
│ └── utils_test.go
├── scripts/
│ ├── OWNERS
│ ├── aws-cleanup-legacy-txt-records.py
│ ├── e2e-test.sh
│ ├── generate-crd.sh
│ ├── get-sha256.sh
│ ├── helm-tools.sh
│ ├── install-ko.sh
│ ├── install-tools.sh
│ ├── releaser.sh
│ ├── update_route53_k8s_txt_owner.py
│ └── version-updater.sh
├── source/
│ ├── OWNERS
│ ├── ambassador_host.go
│ ├── ambassador_host_test.go
│ ├── annotations/
│ │ ├── annotations.go
│ │ ├── annotations_test.go
│ │ ├── filter.go
│ │ ├── filter_test.go
│ │ ├── processors.go
│ │ ├── processors_test.go
│ │ ├── provider_specific.go
│ │ └── provider_specific_test.go
│ ├── compatibility.go
│ ├── connector.go
│ ├── connector_test.go
│ ├── contour_httpproxy.go
│ ├── contour_httpproxy_test.go
│ ├── crd.go
│ ├── crd_test.go
│ ├── empty.go
│ ├── empty_test.go
│ ├── endpoint_benchmark_test.go
│ ├── endpoints.go
│ ├── endpoints_test.go
│ ├── f5_transportserver.go
│ ├── f5_transportserver_test.go
│ ├── f5_virtualserver.go
│ ├── f5_virtualserver_test.go
│ ├── fake.go
│ ├── fake_test.go
│ ├── fqdn/
│ │ ├── fqdn.go
│ │ └── fqdn_test.go
│ ├── gateway.go
│ ├── gateway_grpcroute.go
│ ├── gateway_grpcroute_test.go
│ ├── gateway_hostname.go
│ ├── gateway_httproute.go
│ ├── gateway_httproute_test.go
│ ├── gateway_tcproute.go
│ ├── gateway_tcproute_test.go
│ ├── gateway_test.go
│ ├── gateway_tlsroute.go
│ ├── gateway_tlsroute_test.go
│ ├── gateway_udproute.go
│ ├── gateway_udproute_test.go
│ ├── gloo_proxy.go
│ ├── gloo_proxy_test.go
│ ├── informers/
│ │ ├── fake.go
│ │ ├── handlers.go
│ │ ├── handlers_test.go
│ │ ├── indexers.go
│ │ ├── indexers_test.go
│ │ ├── informers.go
│ │ ├── informers_test.go
│ │ ├── transfomers.go
│ │ └── transformers_test.go
│ ├── ingress.go
│ ├── ingress_fqdn_test.go
│ ├── ingress_test.go
│ ├── istio_gateway.go
│ ├── istio_gateway_fqdn_test.go
│ ├── istio_gateway_test.go
│ ├── istio_virtualservice.go
│ ├── istio_virtualservice_fqdn_test.go
│ ├── istio_virtualservice_test.go
│ ├── kong_tcpingress.go
│ ├── kong_tcpingress_test.go
│ ├── main_test.go
│ ├── node.go
│ ├── node_fqdn_test.go
│ ├── node_test.go
│ ├── openshift_route.go
│ ├── openshift_route_fqdn_test.go
│ ├── openshift_route_test.go
│ ├── pod.go
│ ├── pod_fqdn_test.go
│ ├── pod_indexer_test.go
│ ├── pod_test.go
│ ├── service.go
│ ├── service_fqdn_test.go
│ ├── service_test.go
│ ├── shared_test.go
│ ├── skipper_routegroup.go
│ ├── skipper_routegroup_test.go
│ ├── source.go
│ ├── source_test.go
│ ├── store.go
│ ├── store_test.go
│ ├── traefik_proxy.go
│ ├── traefik_proxy_test.go
│ ├── types/
│ │ └── types.go
│ ├── unstructured.go
│ ├── unstructured_converter.go
│ ├── unstructured_fqdn_test.go
│ ├── unstructured_test.go
│ ├── utils.go
│ ├── utils_test.go
│ └── wrappers/
│ ├── dedupsource.go
│ ├── dedupsource_test.go
│ ├── multisource.go
│ ├── multisource_test.go
│ ├── nat64source.go
│ ├── nat64source_test.go
│ ├── post_processor.go
│ ├── post_processor_test.go
│ ├── source_test.go
│ ├── targetfiltersource.go
│ ├── targetfiltersource_test.go
│ ├── types.go
│ └── types_test.go
└── tests/
└── integration/
├── OWNERS
├── scenarios/
│ └── tests.yaml
├── source_test.go
└── toolkit/
├── mocks.go
├── models.go
└── toolkit.go
Showing preview only (377K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3730 symbols across 316 files)
FILE: apis/v1alpha1/dnsendpoint.go
type DNSEndpoint (line 36) | type DNSEndpoint struct
type DNSEndpointList (line 46) | type DNSEndpointList struct
type DNSEndpointSpec (line 53) | type DNSEndpointSpec struct
type DNSEndpointStatus (line 58) | type DNSEndpointStatus struct
FILE: apis/v1alpha1/groupversion_info.go
constant DNSEndpointKind (line 29) | DNSEndpointKind = "DNSEndpoint"
function init (line 43) | func init() {
FILE: apis/v1alpha1/zz_generated.deepcopy.go
method DeepCopyInto (line 13) | func (in *DNSEndpoint) DeepCopyInto(out *DNSEndpoint) {
method DeepCopy (line 22) | func (in *DNSEndpoint) DeepCopy() *DNSEndpoint {
method DeepCopyObject (line 32) | func (in *DNSEndpoint) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 40) | func (in *DNSEndpointList) DeepCopyInto(out *DNSEndpointList) {
method DeepCopy (line 54) | func (in *DNSEndpointList) DeepCopy() *DNSEndpointList {
method DeepCopyObject (line 64) | func (in *DNSEndpointList) DeepCopyObject() runtime.Object {
method DeepCopyInto (line 72) | func (in *DNSEndpointSpec) DeepCopyInto(out *DNSEndpointSpec) {
method DeepCopy (line 88) | func (in *DNSEndpointSpec) DeepCopy() *DNSEndpointSpec {
method DeepCopyInto (line 98) | func (in *DNSEndpointStatus) DeepCopyInto(out *DNSEndpointStatus) {
method DeepCopy (line 103) | func (in *DNSEndpointStatus) DeepCopy() *DNSEndpointStatus {
FILE: controller/controller.go
type Controller (line 42) | type Controller struct
method RunOnce (line 69) | func (c *Controller) RunOnce(ctx context.Context) error {
method ScheduleRunOnce (line 158) | func (c *Controller) ScheduleRunOnce(now time.Time) {
method ShouldRunOnce (line 170) | func (c *Controller) ShouldRunOnce(now time.Time) bool {
method Run (line 181) | func (c *Controller) Run(ctx context.Context) {
function earliest (line 139) | func earliest(r time.Time, times ...time.Time) time.Time {
function latest (line 148) | func latest(r time.Time, times ...time.Time) time.Time {
FILE: controller/controller_test.go
type mockProvider (line 45) | type mockProvider struct
method Records (line 76) | func (p *mockProvider) Records(_ context.Context) ([]*endpoint.Endpoin...
method ApplyChanges (line 81) | func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan...
type filteredMockProvider (line 51) | type filteredMockProvider struct
method GetDomainFilter (line 59) | func (p *filteredMockProvider) GetDomainFilter() endpoint.DomainFilter...
method Records (line 64) | func (p *filteredMockProvider) Records(_ context.Context) ([]*endpoint...
method ApplyChanges (line 70) | func (p *filteredMockProvider) ApplyChanges(_ context.Context, changes...
function verifyEndpoints (line 104) | func verifyEndpoints(actual, expected []*endpoint.Endpoint) error {
function newMockProvider (line 120) | func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Chang...
function getTestSource (line 129) | func getTestSource() *testutils.MockSource {
function getTestConfig (line 158) | func getTestConfig() *externaldns.Config {
function getTestProvider (line 165) | func getTestProvider() provider.Provider {
function TestRunOnce (line 212) | func TestRunOnce(t *testing.T) {
function TestRun (line 244) | func TestRun(t *testing.T) {
function TestShouldRunOnce (line 277) | func TestShouldRunOnce(t *testing.T) {
function testControllerFiltersDomains (line 334) | func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*e...
function TestControllerSkipsEmptyChanges (line 366) | func TestControllerSkipsEmptyChanges(t *testing.T) {
function TestWhenNoFilterControllerConsidersAllDomains (line 393) | func TestWhenNoFilterControllerConsidersAllDomains(t *testing.T) {
function TestWhenMultipleControllerConsidersAllFilteredComain (line 430) | func TestWhenMultipleControllerConsidersAllFilteredComain(t *testing.T) {
type toggleRegistry (line 490) | type toggleRegistry struct
method Records (line 498) | func (r *toggleRegistry) Records(_ context.Context) ([]*endpoint.Endpo...
method ApplyChanges (line 508) | func (r *toggleRegistry) ApplyChanges(_ context.Context, _ *plan.Chang...
constant toggleRegistryFailureCount (line 496) | toggleRegistryFailureCount = 3
function TestToggleRegistry (line 512) | func TestToggleRegistry(t *testing.T) {
function TestRunOnce_EmitChangeEvent (line 560) | func TestRunOnce_EmitChangeEvent(t *testing.T) {
FILE: controller/events.go
function emitChangeEvent (line 26) | func emitChangeEvent(e events.EventEmitter, ch *plan.Changes, reason eve...
FILE: controller/events_test.go
function TestEmit_RecordReady (line 31) | func TestEmit_RecordReady(t *testing.T) {
function TestEmit_NilEmitter (line 103) | func TestEmit_NilEmitter(t *testing.T) {
function TestEmit_RecordError (line 109) | func TestEmit_RecordError(t *testing.T) {
FILE: controller/execute.go
function Execute (line 48) | func Execute() {
function buildController (line 132) | func buildController(
function configureLogger (line 180) | func configureLogger(cfg *externaldns.Config) {
function buildSource (line 194) | func buildSource(ctx context.Context, cfg *source.Config) (source.Source...
function handleSigterm (line 213) | func handleSigterm(cancel func()) {
function serveMetrics (line 225) | func serveMetrics(address string) {
FILE: controller/execute_test.go
function TestConfigureLogger (line 41) | func TestConfigureLogger(t *testing.T) {
function TestBuildSourceWithWrappers (line 122) | func TestBuildSourceWithWrappers(t *testing.T) {
function TestHelperProcess (line 167) | func TestHelperProcess(_ *testing.T) {
function runExecuteSubprocess (line 188) | func runExecuteSubprocess(t *testing.T, args []string) (int, error) {
function TestExecuteOnceDryRunExitsZero (line 215) | func TestExecuteOnceDryRunExitsZero(t *testing.T) {
function TestExecuteUnknownProviderExitsNonZero (line 228) | func TestExecuteUnknownProviderExitsNonZero(t *testing.T) {
function TestExecuteValidationErrorNoSources (line 238) | func TestExecuteValidationErrorNoSources(t *testing.T) {
function TestExecuteFlagParsingErrorInvalidLogFormat (line 247) | func TestExecuteFlagParsingErrorInvalidLogFormat(t *testing.T) {
function TestExecuteConfigValidationErrorExitsNonZero (line 260) | func TestExecuteConfigValidationErrorExitsNonZero(t *testing.T) {
function TestExecuteBuildSourceErrorExitsNonZero (line 273) | func TestExecuteBuildSourceErrorExitsNonZero(t *testing.T) {
function TestExecuteRunOnceErrorExitsNonZero (line 287) | func TestExecuteRunOnceErrorExitsNonZero(t *testing.T) {
function TestExecuteRunLoopErrorExitsNonZero (line 301) | func TestExecuteRunLoopErrorExitsNonZero(t *testing.T) {
function TestExecuteBuildControllerErrorExitsNonZero (line 313) | func TestExecuteBuildControllerErrorExitsNonZero(t *testing.T) {
function TestControllerRunCancelContextStopsLoop (line 327) | func TestControllerRunCancelContextStopsLoop(t *testing.T) {
FILE: controller/metrics.go
function init (line 127) | func init() {
type dnsKey (line 145) | type dnsKey struct
function countMatchingAddressRecords (line 151) | func countMatchingAddressRecords(endpoints []*endpoint.Endpoint, registr...
function countAddressRecords (line 172) | func countAddressRecords(endpoints []*endpoint.Endpoint, metric metrics....
FILE: controller/metrics_test.go
function TestVerifyARecords (line 31) | func TestVerifyARecords(t *testing.T) {
function TestVerifyAAAARecords (line 111) | func TestVerifyAAAARecords(t *testing.T) {
function TestARecords (line 191) | func TestARecords(t *testing.T) {
function TestAAAARecords (line 238) | func TestAAAARecords(t *testing.T) {
function TestGaugeMetricsWithMixedRecords (line 289) | func TestGaugeMetricsWithMixedRecords(t *testing.T) {
function newMixedRecordsFixture (line 305) | func newMixedRecordsFixture() *Controller {
function BenchmarkGaugeMetricsWithMixedRecords (line 346) | func BenchmarkGaugeMetricsWithMixedRecords(b *testing.B) {
FILE: endpoint/crypto.go
constant standardGcmNonceSize (line 30) | standardGcmNonceSize = 12
function GenerateNonce (line 33) | func GenerateNonce() (string, error) {
function EncryptText (line 43) | func EncryptText(text string, aesKey []byte, nonceEncoded string) (strin...
function DecryptText (line 74) | func DecryptText(text string, aesKey []byte) (string, string, error) {
function decompressData (line 104) | func decompressData(data []byte) ([]byte, error) {
function compressData (line 119) | func compressData(data []byte) ([]byte, error) {
FILE: endpoint/crypto_test.go
function TestEncrypt (line 29) | func TestEncrypt(t *testing.T) {
function TestGenerateNonceSuccess (line 70) | func TestGenerateNonceSuccess(t *testing.T) {
function TestGenerateNonceError (line 81) | func TestGenerateNonceError(t *testing.T) {
type faultyReader (line 94) | type faultyReader struct
method Read (line 96) | func (f *faultyReader) Read(_ []byte) (int, error) {
FILE: endpoint/domain_filter.go
type MatchAllDomainFilters (line 32) | type MatchAllDomainFilters
method Match (line 34) | func (f MatchAllDomainFilters) Match(domain string) bool {
type DomainFilterInterface (line 46) | type DomainFilterInterface interface
type DomainFilter (line 51) | type DomainFilter struct
method Match (line 120) | func (df *DomainFilter) Match(domain string) bool {
method IsConfigured (line 184) | func (df *DomainFilter) IsConfigured() bool {
method MarshalJSON (line 196) | func (df *DomainFilter) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 225) | func (df *DomainFilter) UnmarshalJSON(b []byte) error {
method MatchParent (line 258) | func (df *DomainFilter) MatchParent(domain string) bool {
type domainFilterSerde (line 65) | type domainFilterSerde struct
function prepareFilters (line 73) | func prepareFilters(filters []string) []string {
function NewDomainFilterWithExclusions (line 84) | func NewDomainFilterWithExclusions(domainFilters []string, excludeDomain...
function NewDomainFilter (line 89) | func NewDomainFilter(domainFilters []string) *DomainFilter {
function NewRegexDomainFilter (line 94) | func NewRegexDomainFilter(regexDomainFilter *regexp.Regexp, regexDomainE...
function NewDomainFilterWithOptions (line 107) | func NewDomainFilterWithOptions(opts ...DomainFilterOption) *DomainFilter {
function matchFilter (line 134) | func matchFilter(filters []string, domain string, emptyval bool) bool {
function matchRegex (line 163) | func matchRegex(regex *regexp.Regexp, negativeRegex *regexp.Regexp, doma...
function normalizeDomain (line 284) | func normalizeDomain(domain string) string {
type DomainFilterOption (line 292) | type DomainFilterOption
type domainFilterConfig (line 293) | type domainFilterConfig struct
function WithDomainFilter (line 301) | func WithDomainFilter(filters []string) DomainFilterOption {
function WithDomainExclude (line 307) | func WithDomainExclude(exclude []string) DomainFilterOption {
function WithRegexDomainFilter (line 313) | func WithRegexDomainFilter(regex *regexp.Regexp) DomainFilterOption {
function WithRegexDomainExclude (line 322) | func WithRegexDomainExclude(regex *regexp.Regexp) DomainFilterOption {
FILE: endpoint/domain_filter_test.go
type domainFilterTest (line 30) | type domainFilterTest struct
type regexDomainFilterTest (line 38) | type regexDomainFilterTest struct
function TestDomainFilterMatch (line 480) | func TestDomainFilterMatch(t *testing.T) {
function TestDomainFilterWithExclusions (line 505) | func TestDomainFilterWithExclusions(t *testing.T) {
function TestDomainFilterMatchWithEmptyFilter (line 532) | func TestDomainFilterMatchWithEmptyFilter(t *testing.T) {
function TestNewDomainFilterWithExclusionsHandlesEmptyInputs (line 542) | func TestNewDomainFilterWithExclusionsHandlesEmptyInputs(t *testing.T) {
function TestRegexDomainFilter (line 577) | func TestRegexDomainFilter(t *testing.T) {
function TestPrepareFiltersStripsWhitespaceAndDotSuffix (line 600) | func TestPrepareFiltersStripsWhitespaceAndDotSuffix(t *testing.T) {
function TestMatchFilterReturnsProperEmptyVal (line 632) | func TestMatchFilterReturnsProperEmptyVal(t *testing.T) {
function TestDomainFilterIsConfigured (line 638) | func TestDomainFilterIsConfigured(t *testing.T) {
function TestRegexDomainFilterIsConfigured (line 687) | func TestRegexDomainFilterIsConfigured(t *testing.T) {
function TestDomainFilterDeserializeError (line 723) | func TestDomainFilterDeserializeError(t *testing.T) {
function assertSerializes (line 792) | func assertSerializes[T any](t *testing.T, domainFilter *DomainFilter, e...
function deserialize (line 800) | func deserialize[T any](t *testing.T, serialized map[string]T) *DomainFi...
function TestDomainFilterMatchParent (line 810) | func TestDomainFilterMatchParent(t *testing.T) {
function TestSimpleDomainFilterWithExclusion (line 930) | func TestSimpleDomainFilterWithExclusion(t *testing.T) {
function TestDomainFilterNormalizeDomain (line 979) | func TestDomainFilterNormalizeDomain(t *testing.T) {
function TestMatchTargetFilterReturnsProperEmptyVal (line 1043) | func TestMatchTargetFilterReturnsProperEmptyVal(t *testing.T) {
function TestNewDomainFilterFromConfig (line 1049) | func TestNewDomainFilterFromConfig(t *testing.T) {
function TestRegexDomainFilterZoneNames (line 1174) | func TestRegexDomainFilterZoneNames(t *testing.T) {
FILE: endpoint/endpoint.go
constant RecordTypeA (line 37) | RecordTypeA = "A"
constant RecordTypeAAAA (line 39) | RecordTypeAAAA = "AAAA"
constant RecordTypeCNAME (line 41) | RecordTypeCNAME = "CNAME"
constant RecordTypeTXT (line 43) | RecordTypeTXT = "TXT"
constant RecordTypeSRV (line 45) | RecordTypeSRV = "SRV"
constant RecordTypeNS (line 47) | RecordTypeNS = "NS"
constant RecordTypePTR (line 49) | RecordTypePTR = "PTR"
constant RecordTypeMX (line 51) | RecordTypeMX = "MX"
constant RecordTypeNAPTR (line 53) | RecordTypeNAPTR = "NAPTR"
constant providerSpecificAlias (line 57) | providerSpecificAlias = "alias"
constant ProviderSpecificRecordType (line 61) | ProviderSpecificRecordType = "record-type"
type TTL (line 79) | type TTL
method IsConfigured (line 82) | func (ttl TTL) IsConfigured() bool {
type Targets (line 87) | type Targets
method String (line 101) | func (t Targets) String() string {
method Len (line 105) | func (t Targets) Len() int {
method Less (line 109) | func (t Targets) Less(i, j int) bool {
method Swap (line 123) | func (t Targets) Swap(i, j int) {
method Same (line 128) | func (t Targets) Same(o Targets) bool {
method IsLess (line 169) | func (t Targets) IsLess(o Targets) bool {
method ValidateIPRecord (line 600) | func (t Targets) ValidateIPRecord(recordType string) bool {
method ValidateMXRecord (line 619) | func (t Targets) ValidateMXRecord() bool {
method ValidateSRVRecord (line 631) | func (t Targets) ValidateSRVRecord() bool {
type MXTarget (line 90) | type MXTarget struct
method GetPriority (line 591) | func (m *MXTarget) GetPriority() *uint16 {
method GetHost (line 596) | func (m *MXTarget) GetHost() *string {
function NewTargets (line 97) | func NewTargets(target ...string) Targets {
type ProviderSpecificProperty (line 222) | type ProviderSpecificProperty struct
type ProviderSpecific (line 228) | type ProviderSpecific
type EndpointKey (line 231) | type EndpointKey struct
type Endpoint (line 243) | type Endpoint struct
method WithSetIdentifier (line 303) | func (e *Endpoint) WithSetIdentifier(setIdentifier string) *Endpoint {
method WithProviderSpecific (line 313) | func (e *Endpoint) WithProviderSpecific(key, value string) *Endpoint {
method GetProviderSpecificProperty (line 319) | func (e *Endpoint) GetProviderSpecificProperty(key string) (string, bo...
method GetBoolProviderSpecificProperty (line 332) | func (e *Endpoint) GetBoolProviderSpecificProperty(key string) (bool, ...
method SetProviderSpecificProperty (line 348) | func (e *Endpoint) SetProviderSpecificProperty(key string, value strin...
method DeleteProviderSpecificProperty (line 370) | func (e *Endpoint) DeleteProviderSpecificProperty(key string) {
method RetainProviderProperties (line 390) | func (e *Endpoint) RetainProviderProperties(provider string) {
method WithLabel (line 410) | func (e *Endpoint) WithLabel(key, value string) *Endpoint {
method WithRefObject (line 420) | func (e *Endpoint) WithRefObject(obj *events.ObjectReference) *Endpoint {
method RefObject (line 425) | func (e *Endpoint) RefObject() *events.ObjectReference {
method Key (line 430) | func (e *Endpoint) Key() EndpointKey {
method IsOwnedBy (line 439) | func (e *Endpoint) IsOwnedBy(ownerID string) bool {
method GetNakedDomain (line 447) | func (e *Endpoint) GetNakedDomain() string {
method String (line 469) | func (e *Endpoint) String() string {
method Describe (line 473) | func (e *Endpoint) Describe() string {
method RequestedRecordType (line 519) | func (e *Endpoint) RequestedRecordType() (string, bool) {
method CheckEndpoint (line 525) | func (e *Endpoint) CheckEndpoint() bool {
method isAlias (line 549) | func (e *Endpoint) isAlias() bool {
method supportsAlias (line 554) | func (e *Endpoint) supportsAlias() bool {
method WithMinTTL (line 564) | func (e *Endpoint) WithMinTTL(ttl int64) {
method ValidatePTRRecord (line 658) | func (e *Endpoint) ValidatePTRRecord() bool {
method GetDNSName (line 693) | func (e *Endpoint) GetDNSName() string {
method GetRecordType (line 698) | func (e *Endpoint) GetRecordType() string {
method GetRecordTTL (line 703) | func (e *Endpoint) GetRecordTTL() int64 {
method GetTargets (line 708) | func (e *Endpoint) GetTargets() []string {
method GetOwner (line 713) | func (e *Endpoint) GetOwner() string {
function NewEndpoint (line 267) | func NewEndpoint(dnsName, recordType string, targets ...string) *Endpoint {
function NewEndpointWithTTL (line 272) | func NewEndpointWithTTL(dnsName, recordType string, ttl TTL, targets ......
function NewPTREndpoint (line 460) | func NewPTREndpoint(target string, ttl TTL, hostnames ...string) (*Endpo...
function FilterEndpointsByOwnerID (line 479) | func FilterEndpointsByOwnerID(ownerID string, eps []*Endpoint) []*Endpoi...
function RemoveDuplicates (line 499) | func RemoveDuplicates(endpoints []*Endpoint) []*Endpoint {
function NewMXRecord (line 573) | func NewMXRecord(target string) (*MXTarget, error) {
function isReverseDNSName (line 683) | func isReverseDNSName(name string) bool {
FILE: endpoint/endpoint_benchmark_test.go
function TestEndpointGeneration (line 49) | func TestEndpointGeneration(t *testing.T) {
function BenchmarkProviderSpecificRandomAccess (line 64) | func BenchmarkProviderSpecificRandomAccess(b *testing.B) {
function BenchmarkProviderSpecificDelete (line 94) | func BenchmarkProviderSpecificDelete(b *testing.B) {
function generateBenchmarkEndpoints (line 125) | func generateBenchmarkEndpoints(count, setPropsCount int) []*Endpoint {
FILE: endpoint/endpoint_test.go
function TestNewEndpoint (line 33) | func TestNewEndpoint(t *testing.T) {
function TestNewTargets (line 48) | func TestNewTargets(t *testing.T) {
function TestTargetsSame (line 80) | func TestTargetsSame(t *testing.T) {
function TestSameSuccess (line 96) | func TestSameSuccess(t *testing.T) {
function TestSameFailures (line 127) | func TestSameFailures(t *testing.T) {
function TestIsLess (line 158) | func TestIsLess(t *testing.T) {
function TestGetProviderSpecificProperty (line 197) | func TestGetProviderSpecificProperty(t *testing.T) {
function TestSetProviderSpecficProperty (line 228) | func TestSetProviderSpecficProperty(t *testing.T) {
function TestDeleteProviderSpecificProperty (line 367) | func TestDeleteProviderSpecificProperty(t *testing.T) {
function TestRetainProviderProperties (line 453) | func TestRetainProviderProperties(t *testing.T) {
function TestFilterEndpointsByOwnerIDWithRecordTypeA (line 589) | func TestFilterEndpointsByOwnerIDWithRecordTypeA(t *testing.T) {
function TestFilterEndpointsByOwnerIDWithRecordTypeCNAME (line 645) | func TestFilterEndpointsByOwnerIDWithRecordTypeCNAME(t *testing.T) {
function TestFilterEndpointsByOwnerID_Logs (line 701) | func TestFilterEndpointsByOwnerID_Logs(t *testing.T) {
function TestIsOwnedBy (line 754) | func TestIsOwnedBy(t *testing.T) {
function TestDuplicatedEndpointsWithSimpleZone (line 798) | func TestDuplicatedEndpointsWithSimpleZone(t *testing.T) {
function TestDuplicatedEndpointsWithOverlappingZones (line 852) | func TestDuplicatedEndpointsWithOverlappingZones(t *testing.T) {
function TestPDNScheckEndpoint (line 931) | func TestPDNScheckEndpoint(t *testing.T) {
function TestNewMXTarget (line 1035) | func TestNewMXTarget(t *testing.T) {
function TestCheckEndpoint (line 1084) | func TestCheckEndpoint(t *testing.T) {
function TestCheckEndpoint_AliasWarningLog (line 1387) | func TestCheckEndpoint_AliasWarningLog(t *testing.T) {
function TestCheckEndpoint_PTRValidationLog (line 1440) | func TestCheckEndpoint_PTRValidationLog(t *testing.T) {
function TestEndpoint_WithRefObject (line 1508) | func TestEndpoint_WithRefObject(t *testing.T) {
function TestTargets_UniqueOrdered (line 1521) | func TestTargets_UniqueOrdered(t *testing.T) {
function TestEndpoint_WithMinTTL (line 1572) | func TestEndpoint_WithMinTTL(t *testing.T) {
function TestNewEndpointWithTTLPreservesDotsInTXTRecords (line 1620) | func TestNewEndpointWithTTLPreservesDotsInTXTRecords(t *testing.T) {
function TestGetBoolProviderSpecificProperty (line 1645) | func TestGetBoolProviderSpecificProperty(t *testing.T) {
function TestGetOwnerId (line 1739) | func TestGetOwnerId(t *testing.T) {
function TestGetNakedDomain (line 1806) | func TestGetNakedDomain(t *testing.T) {
function TestRequestedRecordType (line 1865) | func TestRequestedRecordType(t *testing.T) {
function TestNewPTREndpoint (line 1877) | func TestNewPTREndpoint(t *testing.T) {
FILE: endpoint/labels.go
constant heritage (line 32) | heritage = "external-dns"
constant OwnerLabelKey (line 34) | OwnerLabelKey = "owner"
constant ResourceLabelKey (line 36) | ResourceLabelKey = "resource"
constant OwnedRecordLabelKey (line 38) | OwnedRecordLabelKey = "ownedRecord"
constant AWSSDDescriptionLabel (line 42) | AWSSDDescriptionLabel = "aws-sd-description"
constant txtEncryptionNonce (line 45) | txtEncryptionNonce = "txt-encryption-nonce"
type Labels (line 50) | type Labels
method SerializePlain (line 109) | func (l Labels) SerializePlain(withQuotes bool) string {
method Serialize (line 131) | func (l Labels) Serialize(withQuotes bool, txtEncryptEnabled bool, aes...
function NewLabels (line 53) | func NewLabels() Labels {
function NewLabelsFromStringPlain (line 60) | func NewLabelsFromStringPlain(labelText string) (Labels, error) {
function NewLabelsFromString (line 90) | func NewLabelsFromString(labelText string, aesKey []byte) (Labels, error) {
FILE: endpoint/labels_test.go
type LabelsSuite (line 29) | type LabelsSuite struct
method SetupTest (line 45) | func (suite *LabelsSuite) SetupTest() {
method TestSerialize (line 67) | func (suite *LabelsSuite) TestSerialize() {
method TestEncryptionNonceReUsage (line 78) | func (suite *LabelsSuite) TestEncryptionNonceReUsage() {
method TestEncryptionKeyChanged (line 85) | func (suite *LabelsSuite) TestEncryptionKeyChanged() {
method TestEncryptionFailed (line 93) | func (suite *LabelsSuite) TestEncryptionFailed() {
method TestEncryptionFailedFaultyReader (line 111) | func (suite *LabelsSuite) TestEncryptionFailedFaultyReader() {
method TestDeserialize (line 139) | func (suite *LabelsSuite) TestDeserialize() {
function TestLabels (line 181) | func TestLabels(t *testing.T) {
FILE: endpoint/target_filter.go
type TargetFilterInterface (line 27) | type TargetFilterInterface interface
type TargetNetFilter (line 33) | type TargetNetFilter struct
method Match (line 63) | func (tf TargetNetFilter) Match(target string) bool {
method IsEnabled (line 68) | func (tf TargetNetFilter) IsEnabled() bool {
function prepareTargetFilters (line 41) | func prepareTargetFilters(filters []string) []*net.IPNet {
function NewTargetNetFilterWithExclusions (line 58) | func NewTargetNetFilterWithExclusions(targetFilterNets []string, exclude...
function matchTargetNetFilter (line 75) | func matchTargetNetFilter(filters []*net.IPNet, target string, emptyval ...
FILE: endpoint/target_filter_test.go
type targetFilterTest (line 25) | type targetFilterTest struct
function TestTargetFilterWithExclusions (line 83) | func TestTargetFilterWithExclusions(t *testing.T) {
function TestTargetFilterMatchWithEmptyFilter (line 95) | func TestTargetFilterMatchWithEmptyFilter(t *testing.T) {
function TestTargetNetFilter_IsEnabled (line 104) | func TestTargetNetFilter_IsEnabled(t *testing.T) {
FILE: endpoint/utils.go
constant msg (line 29) | msg = "No endpoints could be generated from '%s/%s/%s'"
function SuitableType (line 34) | func SuitableType(target string) string {
function HasNoEmptyEndpoints (line 51) | func HasNoEmptyEndpoints(
function EndpointsForHostname (line 64) | func EndpointsForHostname(hostname string, targets Targets, ttl TTL, pro...
function AttachRefObject (line 93) | func AttachRefObject(eps []*Endpoint, ref *events.ObjectReference) {
FILE: endpoint/utils_test.go
type mockObjectMetaAccessor (line 26) | type mockObjectMetaAccessor struct
method GetObjectMeta (line 31) | func (m *mockObjectMetaAccessor) GetObjectMeta() metav1.Object {
function TestSuitableType (line 38) | func TestSuitableType(t *testing.T) {
function TestHasEmptyEndpoints (line 69) | func TestHasEmptyEndpoints(t *testing.T) {
function TestEndpointsForHostname (line 121) | func TestEndpointsForHostname(t *testing.T) {
FILE: endpoint/zz_generated.deepcopy.go
method DeepCopyInto (line 8) | func (in *Endpoint) DeepCopyInto(out *Endpoint) {
method DeepCopy (line 35) | func (in *Endpoint) DeepCopy() *Endpoint {
FILE: internal/flags/binders.go
type FlagBinder (line 29) | type FlagBinder interface
type KingpinBinder (line 47) | type KingpinBinder struct
method StringVar (line 56) | func (b *KingpinBinder) StringVar(name, help, def string, target *stri...
method BoolVar (line 60) | func (b *KingpinBinder) BoolVar(name, help string, def bool, target *b...
method DurationVar (line 68) | func (b *KingpinBinder) DurationVar(name, help string, def time.Durati...
method IntVar (line 72) | func (b *KingpinBinder) IntVar(name, help string, def int, target *int) {
method Int64Var (line 76) | func (b *KingpinBinder) Int64Var(name, help string, def int64, target ...
method StringsVar (line 80) | func (b *KingpinBinder) StringsVar(name, help string, def []string, ta...
method EnumVar (line 88) | func (b *KingpinBinder) EnumVar(name, help, def string, target *string...
method StringsEnumVar (line 92) | func (b *KingpinBinder) StringsEnumVar(name, help string, def []string...
method StringMapVar (line 100) | func (b *KingpinBinder) StringMapVar(name, help string, target *map[st...
method RegexpVar (line 104) | func (b *KingpinBinder) RegexpVar(name, help string, def *regexp.Regex...
function NewKingpinBinder (line 52) | func NewKingpinBinder(app *kingpin.Application) *KingpinBinder {
type regexpValue (line 112) | type regexpValue struct
method String (line 116) | func (rv *regexpValue) String() string {
method Set (line 123) | func (rv *regexpValue) Set(s string) error {
method Type (line 132) | func (rv *regexpValue) Type() string { return "regexp" }
type regexpSetter (line 134) | type regexpSetter interface
function setRegexpDefault (line 138) | func setRegexpDefault(rs regexpSetter, def *regexp.Regexp, name string) {
FILE: internal/flags/binders_test.go
type badSetter (line 30) | type badSetter struct
method Set (line 32) | func (b *badSetter) Set(_ string) error { return errors.New("bad defau...
function TestKingpinBinderParsesAllTypes (line 34) | func TestKingpinBinderParsesAllTypes(t *testing.T) {
function TestKingpinBinderEnumValidation (line 68) | func TestKingpinBinderEnumValidation(t *testing.T) {
function TestKingpinBinderStringsVarNoDefaultAndBoolDefaultFalse (line 79) | func TestKingpinBinderStringsVarNoDefaultAndBoolDefaultFalse(t *testing....
function TestCobraRegexValueSetStringType (line 98) | func TestCobraRegexValueSetStringType(t *testing.T) {
function TestKingpinRegexpVarDefaultAndParse (line 118) | func TestKingpinRegexpVarDefaultAndParse(t *testing.T) {
function TestKingpinStringsEnumVarWithAndWithoutDefault (line 141) | func TestKingpinStringsEnumVarWithAndWithoutDefault(t *testing.T) {
function TestSetRegexDefaultPanicsOnInvalidDefault (line 161) | func TestSetRegexDefaultPanicsOnInvalidDefault(t *testing.T) {
FILE: internal/gen/docs/flags/main.go
type Flag (line 34) | type Flag struct
type Flags (line 38) | type Flags
method addFlag (line 41) | func (f *Flags) addFlag(name, description string) {
method generateMarkdownTable (line 105) | func (f *Flags) generateMarkdownTable() (string, error) {
function main (line 48) | func main() {
function computeFlags (line 62) | func computeFlags() Flags {
type columnWidths (line 88) | type columnWidths struct
function computeFlagColumnWidths (line 93) | func computeFlagColumnWidths(flags Flags) columnWidths {
type templateData (line 100) | type templateData struct
FILE: internal/gen/docs/flags/main_test.go
constant pathToDocs (line 29) | pathToDocs = "%s/../../../../docs"
function TestComputeFlags (line 31) | func TestComputeFlags(t *testing.T) {
function TestGenerateMarkdownTableRenderer (line 45) | func TestGenerateMarkdownTableRenderer(t *testing.T) {
function TestFlagsMdExists (line 57) | func TestFlagsMdExists(t *testing.T) {
function TestFlagsMdUpToDate (line 66) | func TestFlagsMdUpToDate(t *testing.T) {
function TestFlagsMdExtraFlagAdded (line 80) | func TestFlagsMdExtraFlagAdded(t *testing.T) {
FILE: internal/gen/docs/metrics/main.go
function main (line 43) | func main() {
function generateMarkdownTable (line 57) | func generateMarkdownTable(m *metrics.MetricRegistry, withRuntime bool) ...
function sortMetrics (line 83) | func sortMetrics(metrics []*metrics.Metric) {
function getRuntimeMetrics (line 93) | func getRuntimeMetrics(gatherer prometheus.Gatherer) []string {
type templateData (line 109) | type templateData struct
type columnWidths (line 116) | type columnWidths struct
function computeColumnWidths (line 123) | func computeColumnWidths(ms []*metrics.Metric) columnWidths {
FILE: internal/gen/docs/metrics/main_test.go
constant pathToDocs (line 33) | pathToDocs = "%s/../../../../docs/monitoring"
constant knownMetricsCount (line 34) | knownMetricsCount = 22
function TestComputeMetrics (line 37) | func TestComputeMetrics(t *testing.T) {
function TestGenerateMarkdownTableRenderer (line 47) | func TestGenerateMarkdownTableRenderer(t *testing.T) {
function TestGenerateMarkdownTableWithSingleMetric (line 57) | func TestGenerateMarkdownTableWithSingleMetric(t *testing.T) {
function TestMetricsMdUpToDate (line 76) | func TestMetricsMdUpToDate(t *testing.T) {
function TestMetricsMdExtraMetricAdded (line 89) | func TestMetricsMdExtraMetricAdded(t *testing.T) {
function TestGetRuntimeMetricsForNewRegistry (line 115) | func TestGetRuntimeMetricsForNewRegistry(t *testing.T) {
function TestGetRuntimeMetricsForDefaultRegistry (line 134) | func TestGetRuntimeMetricsForDefaultRegistry(t *testing.T) {
FILE: internal/gen/docs/render/render.go
function WriteToFile (line 32) | func WriteToFile(filename string, content string) error {
function RenderTemplate (line 37) | func RenderTemplate(fsys fs.FS, name string, data any) (string, error) {
function FuncMap (line 49) | func FuncMap() template.FuncMap {
function ComputeColumnWidth (line 72) | func ComputeColumnWidth(header string, values []string) int {
function MapColumn (line 77) | func MapColumn[T any](header string, items []T, fn func(T) string) int {
FILE: internal/gen/docs/render/render_test.go
function TestWriteToFile (line 31) | func TestWriteToFile(t *testing.T) {
function TestComputeColumnWidth (line 52) | func TestComputeColumnWidth(t *testing.T) {
function TestMapColumn (line 111) | func TestMapColumn(t *testing.T) {
function TestFuncs (line 161) | func TestFuncs(t *testing.T) {
function TestRenderTemplate (line 191) | func TestRenderTemplate(t *testing.T) {
function TestRenderTemplateWithFuncMap (line 203) | func TestRenderTemplateWithFuncMap(t *testing.T) {
FILE: internal/gen/docs/sources/main.go
constant annotationPrefix (line 35) | annotationPrefix = "+externaldns:source:"
constant annotationName (line 36) | annotationName = annotationPrefix + "name="
constant annotationCategory (line 37) | annotationCategory = annotationPrefix + "category="
constant annotationDesc (line 38) | annotationDesc = annotationPrefix + "description="
constant annotationResources (line 39) | annotationResources = annotationPrefix + "resources="
constant annotationFilters (line 40) | annotationFilters = annotationPrefix + "filters="
constant annotationNamespace (line 41) | annotationNamespace = annotationPrefix + "namespace="
constant annotationFQDNTemplate (line 42) | annotationFQDNTemplate = annotationPrefix + "fqdn-template="
constant annotationEvents (line 43) | annotationEvents = annotationPrefix + "events="
constant annotationProviderSpecific (line 44) | annotationProviderSpecific = annotationPrefix + "provider-specific="
type Source (line 55) | type Source struct
type Sources (line 69) | type Sources
method generateMarkdown (line 138) | func (s *Sources) generateMarkdown() (string, error) {
function main (line 74) | func main() {
function discoverSources (line 94) | func discoverSources(dir string) (Sources, error) {
type columnWidths (line 109) | type columnWidths struct
function computeColumnWidths (line 120) | func computeColumnWidths(sources Sources) columnWidths {
type templateData (line 133) | type templateData struct
function parseSourceAnnotations (line 147) | func parseSourceAnnotations(sourceDir string) (Sources, error) {
function parseFile (line 184) | func parseFile(filePath, baseDir string) (Sources, error) {
function extractSourcesFromComments (line 264) | func extractSourcesFromComments(comments, typeName, filePath string) (So...
FILE: internal/gen/docs/sources/main_test.go
constant pathToDocs (line 31) | pathToDocs = "%s/../../../../docs/sources"
constant fileName (line 32) | fileName = "index.md"
function TestIndexMdExists (line 35) | func TestIndexMdExists(t *testing.T) {
function TestIndexMdUpToDate (line 43) | func TestIndexMdUpToDate(t *testing.T) {
function TestDiscoverSources (line 57) | func TestDiscoverSources(t *testing.T) {
function TestGenerateMarkdown (line 75) | func TestGenerateMarkdown(t *testing.T) {
function TestParseSourceAnnotations (line 98) | func TestParseSourceAnnotations(t *testing.T) {
function TestParseSourceAnnotations_SkipsTestFiles (line 135) | func TestParseSourceAnnotations_SkipsTestFiles(t *testing.T) {
function TestParseFile_MultipleSourcesInOneFile (line 155) | func TestParseFile_MultipleSourcesInOneFile(t *testing.T) {
function TestParseFile_IgnoresNonSourceTypes (line 190) | func TestParseFile_IgnoresNonSourceTypes(t *testing.T) {
function TestParseSourceAnnotations_ErrorOnInvalidFile (line 211) | func TestParseSourceAnnotations_ErrorOnInvalidFile(t *testing.T) {
function TestParseFile_InvalidGoFile (line 227) | func TestParseFile_InvalidGoFile(t *testing.T) {
function TestParseSourceAnnotations_WithSubdirectories (line 240) | func TestParseSourceAnnotations_WithSubdirectories(t *testing.T) {
function TestGenerateMarkdown_WithMultipleCategories (line 268) | func TestGenerateMarkdown_WithMultipleCategories(t *testing.T) {
function TestExtractSourcesFromComments (line 306) | func TestExtractSourcesFromComments(t *testing.T) {
FILE: internal/idna/idna.go
function NormalizeDNSName (line 36) | func NormalizeDNSName(dnsName string) string {
FILE: internal/idna/idna_test.go
function TestProfileWithDefault (line 26) | func TestProfileWithDefault(t *testing.T) {
function TestNormalizeDNSName (line 61) | func TestNormalizeDNSName(tt *testing.T) {
FILE: internal/testutils/endpoint.go
type byNames (line 39) | type byNames
method Len (line 41) | func (p byNames) Len() int { return len(p) }
method Swap (line 42) | func (p byNames) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
method Less (line 43) | func (p byNames) Less(i, j int) bool { return p[i].Name < p[j].Name }
type byAllFields (line 45) | type byAllFields
method Len (line 47) | func (b byAllFields) Len() int { return len(b) }
method Swap (line 48) | func (b byAllFields) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
method Less (line 49) | func (b byAllFields) Less(i, j int) bool {
function SameEndpoint (line 72) | func SameEndpoint(a, b *endpoint.Endpoint) bool {
function SameEndpoints (line 88) | func SameEndpoints(a, b []*endpoint.Endpoint) bool {
function SameEndpointLabels (line 107) | func SameEndpointLabels(a, b []*endpoint.Endpoint) bool {
function SamePlanChanges (line 126) | func SamePlanChanges(a, b map[string][]*endpoint.Endpoint) bool {
function SameProviderSpecific (line 132) | func SameProviderSpecific(a, b endpoint.ProviderSpecific) bool {
function NewTargetsFromAddr (line 141) | func NewTargetsFromAddr(targets []netip.Addr) endpoint.Targets {
function GenerateTestEndpointsByType (line 154) | func GenerateTestEndpointsByType(typeCounts map[string]int) []*endpoint....
function GenerateTestEndpointsWithDistribution (line 174) | func GenerateTestEndpointsWithDistribution(
function NewEndpointWithRef (line 235) | func NewEndpointWithRef(dns, target string, obj ctrlclient.Object, sourc...
function AssertEndpointsHaveRefObject (line 242) | func AssertEndpointsHaveRefObject(
function distributeByWeight (line 258) | func distributeByWeight(keys []string, weights map[string]int, n int) []...
FILE: internal/testutils/endpoint_test.go
function TestExampleSameEndpoints (line 34) | func TestExampleSameEndpoints(t *testing.T) {
function makeEndpoint (line 94) | func makeEndpoint(DNSName string) *endpoint.Endpoint { // nolint: gocrit...
function TestSameEndpoint (line 112) | func TestSameEndpoint(t *testing.T) {
function TestSameEndpoints (line 224) | func TestSameEndpoints(t *testing.T) {
function TestSameEndpointLabel (line 276) | func TestSameEndpointLabel(t *testing.T) {
function TestSamePlanChanges (line 341) | func TestSamePlanChanges(t *testing.T) {
function TestNewTargetsFromAddr (line 437) | func TestNewTargetsFromAddr(t *testing.T) {
function TestWithLabel (line 482) | func TestWithLabel(t *testing.T) {
function TestGenerateTestEndpointsWithDistribution (line 502) | func TestGenerateTestEndpointsWithDistribution(t *testing.T) {
function TestFilterEndpointsByOwnerIDLogging (line 576) | func TestFilterEndpointsByOwnerIDLogging(t *testing.T) {
FILE: internal/testutils/env.go
function TestHelperEnvSetter (line 23) | func TestHelperEnvSetter(t *testing.T, envs map[string]string) {
FILE: internal/testutils/helpers.go
function ToPtr (line 25) | func ToPtr[T any](v T) *T {
FILE: internal/testutils/helpers_test.go
function TestStringPtr (line 25) | func TestStringPtr(t *testing.T) {
function TestIsPointer (line 42) | func TestIsPointer(t *testing.T) {
FILE: internal/testutils/init.go
function init (line 29) | func init() {
FILE: internal/testutils/log/log.go
function LogsUnderTestWithLogLevel (line 38) | func LogsUnderTestWithLogLevel(level log.Level, t *testing.T) *test.Hook {
function TestHelperLogContains (line 60) | func TestHelperLogContains(msg string, hook *test.Hook, t *testing.T) {
function TestHelperLogNotContains (line 83) | func TestHelperLogNotContains(msg string, hook *test.Hook, t *testing.T) {
function TestHelperLogContainsWithLogLevel (line 107) | func TestHelperLogContainsWithLogLevel(msg string, level log.Level, hook...
function TestHelperWithLogExitFunc (line 120) | func TestHelperWithLogExitFunc(exitFunc func(int)) func() {
FILE: internal/testutils/metrics.go
function TestHelperVerifyMetricsGaugeVectorWithLabels (line 42) | func TestHelperVerifyMetricsGaugeVectorWithLabels(t *testing.T, expected...
function TestHelperVerifyMetricsGaugeVectorWithLabelsFunc (line 48) | func TestHelperVerifyMetricsGaugeVectorWithLabelsFunc(t *testing.T, expe...
function collectAll (line 60) | func collectAll(metric *prometheus.GaugeVec) []*dto.Metric {
function sumMetricsWithLabels (line 79) | func sumMetricsWithLabels(metric *prometheus.GaugeVec, matchLabels map[s...
function collectGaugeVecMetrics (line 106) | func collectGaugeVecMetrics(metric *prometheus.GaugeVec) string {
FILE: internal/testutils/mock_source.go
type MockSource (line 29) | type MockSource struct
method Endpoints (line 44) | func (m *MockSource) Endpoints(_ context.Context) ([]*endpoint.Endpoin...
method AddEventHandler (line 56) | func (m *MockSource) AddEventHandler(ctx context.Context, handler func...
function NewMockSource (line 34) | func NewMockSource(endpoints ...*endpoint.Endpoint) *MockSource {
FILE: main.go
function main (line 25) | func main() {
FILE: pkg/apis/externaldns/constants.go
constant RegistryTXT (line 20) | RegistryTXT = "txt"
constant RegistryNoop (line 21) | RegistryNoop = "noop"
constant RegistryDynamoDB (line 22) | RegistryDynamoDB = "dynamodb"
constant RegistryAWSSD (line 23) | RegistryAWSSD = "aws-sd"
constant ProviderAkamai (line 25) | ProviderAkamai = "akamai"
constant ProviderAlibabaCloud (line 26) | ProviderAlibabaCloud = "alibabacloud"
constant ProviderAWS (line 27) | ProviderAWS = "aws"
constant ProviderAWSSD (line 28) | ProviderAWSSD = "aws-sd"
constant ProviderAzure (line 29) | ProviderAzure = "azure"
constant ProviderAzureDNS (line 30) | ProviderAzureDNS = "azure-dns"
constant ProviderAzurePrivate (line 31) | ProviderAzurePrivate = "azure-private-dns"
constant ProviderCivo (line 32) | ProviderCivo = "civo"
constant ProviderCloudflare (line 33) | ProviderCloudflare = "cloudflare"
constant ProviderCoreDNS (line 34) | ProviderCoreDNS = "coredns"
constant ProviderSkyDNS (line 35) | ProviderSkyDNS = "skydns"
constant ProviderDNSimple (line 36) | ProviderDNSimple = "dnsimple"
constant ProviderExoscale (line 37) | ProviderExoscale = "exoscale"
constant ProviderGandi (line 38) | ProviderGandi = "gandi"
constant ProviderGoDaddy (line 39) | ProviderGoDaddy = "godaddy"
constant ProviderGoogle (line 40) | ProviderGoogle = "google"
constant ProviderInMemory (line 41) | ProviderInMemory = "inmemory"
constant ProviderLinode (line 42) | ProviderLinode = "linode"
constant ProviderNS1 (line 43) | ProviderNS1 = "ns1"
constant ProviderOCI (line 44) | ProviderOCI = "oci"
constant ProviderOVH (line 45) | ProviderOVH = "ovh"
constant ProviderPDNS (line 46) | ProviderPDNS = "pdns"
constant ProviderPihole (line 47) | ProviderPihole = "pihole"
constant ProviderPlural (line 48) | ProviderPlural = "plural"
constant ProviderRFC2136 (line 49) | ProviderRFC2136 = "rfc2136"
constant ProviderScaleway (line 50) | ProviderScaleway = "scaleway"
constant ProviderTransip (line 51) | ProviderTransip = "transip"
constant ProviderWebhook (line 52) | ProviderWebhook = "webhook"
FILE: pkg/apis/externaldns/types.go
constant passwordMask (line 38) | passwordMask = "******"
type Config (line 42) | type Config struct
method String (line 464) | func (cfg *Config) String() string {
method ParseFlags (line 495) | func (cfg *Config) ParseFlags(args []string) error {
function NewConfig (line 457) | func NewConfig() *Config {
function allLogLevelsAsStrings (line 486) | func allLogLevelsAsStrings() []string {
function bindFlags (line 502) | func bindFlags(b flags.FlagBinder, cfg *Config) {
function App (line 719) | func App(cfg *Config) *kingpin.Application {
FILE: pkg/apis/externaldns/types_test.go
function TestParseFlags (line 257) | func TestParseFlags(t *testing.T) {
function TestParseFlagsCobraExecuteError (line 568) | func TestParseFlagsCobraExecuteError(t *testing.T) {
function TestParseFlagsKingpinParseError (line 574) | func TestParseFlagsKingpinParseError(t *testing.T) {
function TestConfigStringMasksSecureFields (line 580) | func TestConfigStringMasksSecureFields(t *testing.T) {
function TestParseFlagsDefaultKingpin (line 592) | func TestParseFlagsDefaultKingpin(t *testing.T) {
function TestParseFlagsCobraSwitchParitySubset (line 623) | func TestParseFlagsCobraSwitchParitySubset(t *testing.T) {
function TestParseFlagsCliFlagOverridesEnv (line 657) | func TestParseFlagsCliFlagOverridesEnv(t *testing.T) {
function TestParseFlagsCliFlagSeparatedValue (line 674) | func TestParseFlagsCliFlagSeparatedValue(t *testing.T) {
function TestPasswordsNotLogged (line 686) | func TestPasswordsNotLogged(t *testing.T) {
function parseCfg (line 701) | func parseCfg(t *testing.T, extra ...string) *Config {
function TestParseFlagsAlibabaCloud (line 709) | func TestParseFlagsAlibabaCloud(t *testing.T) {
function TestParseFlagsPublishingAndFilters (line 719) | func TestParseFlagsPublishingAndFilters(t *testing.T) {
function TestParseFlagsGateway (line 761) | func TestParseFlagsGateway(t *testing.T) {
function TestParseFlagsAzure (line 773) | func TestParseFlagsAzure(t *testing.T) {
function TestParseFlagsCloudflare (line 783) | func TestParseFlagsCloudflare(t *testing.T) {
function TestParseFlagsNS1 (line 789) | func TestParseFlagsNS1(t *testing.T) {
function TestParseFlagsOVH (line 795) | func TestParseFlagsOVH(t *testing.T) {
function TestParseFlagsPihole (line 801) | func TestParseFlagsPihole(t *testing.T) {
function TestParseFlagsOCI (line 813) | func TestParseFlagsOCI(t *testing.T) {
function TestParseFlagsPlural (line 823) | func TestParseFlagsPlural(t *testing.T) {
function TestParseFlagsProviderCacheAndDynamoDB (line 833) | func TestParseFlagsProviderCacheAndDynamoDB(t *testing.T) {
function TestParseFlagsGoDaddy (line 843) | func TestParseFlagsGoDaddy(t *testing.T) {
function TestParseFlagsRFC2136 (line 857) | func TestParseFlagsRFC2136(t *testing.T) {
function TestParseFlagsTraefik (line 894) | func TestParseFlagsTraefik(t *testing.T) {
function TestParseFlagsTXTRegistry (line 904) | func TestParseFlagsTXTRegistry(t *testing.T) {
function TestParseFlagsWebhookProvider (line 918) | func TestParseFlagsWebhookProvider(t *testing.T) {
function TestParseFlagsMiscListeners (line 932) | func TestParseFlagsMiscListeners(t *testing.T) {
function runWithKingpin (line 939) | func runWithKingpin(t *testing.T, args []string) *Config {
function TestBinderParityRepeatable (line 951) | func TestBinderParityRepeatable(t *testing.T) {
function TestBinderParityMapAndRegexp (line 957) | func TestBinderParityMapAndRegexp(t *testing.T) {
function TestBinderEnumValidationDifference (line 967) | func TestBinderEnumValidationDifference(t *testing.T) {
FILE: pkg/apis/externaldns/validation/validation.go
function ValidateConfig (line 30) | func ValidateConfig(cfg *externaldns.Config) error {
function preValidateConfig (line 64) | func preValidateConfig(cfg *externaldns.Config) error {
function validateConfigForProvider (line 77) | func validateConfigForProvider(cfg *externaldns.Config) error {
function validateConfigForAzure (line 90) | func validateConfigForAzure(cfg *externaldns.Config) error {
function validateConfigForAkamai (line 97) | func validateConfigForAkamai(cfg *externaldns.Config) error {
function validateConfigForRfc2136 (line 113) | func validateConfigForRfc2136(cfg *externaldns.Config) error {
FILE: pkg/apis/externaldns/validation/validation_test.go
function TestValidateFlags (line 28) | func TestValidateFlags(t *testing.T) {
function newValidConfig (line 93) | func newValidConfig(t *testing.T) *externaldns.Config {
function TestValidateBadIgnoreHostnameAnnotationsConfig (line 105) | func TestValidateBadIgnoreHostnameAnnotationsConfig(t *testing.T) {
function TestValidateBadRfc2136Config (line 113) | func TestValidateBadRfc2136Config(t *testing.T) {
function TestValidateBadRfc2136Batch (line 128) | func TestValidateBadRfc2136Batch(t *testing.T) {
function TestValidateGoodRfc2136Config (line 142) | func TestValidateGoodRfc2136Config(t *testing.T) {
function TestValidateBadRfc2136GssTsigConfig (line 156) | func TestValidateBadRfc2136GssTsigConfig(t *testing.T) {
function TestValidateGoodRfc2136GssTsigConfig (line 253) | func TestValidateGoodRfc2136GssTsigConfig(t *testing.T) {
function TestValidateBadAkamaiConfig (line 277) | func TestValidateBadAkamaiConfig(t *testing.T) {
function TestValidateGoodAkamaiConfig (line 331) | func TestValidateGoodAkamaiConfig(t *testing.T) {
function TestValidateBadAzureConfig (line 359) | func TestValidateBadAzureConfig(t *testing.T) {
function TestValidateGoodAzureConfig (line 373) | func TestValidateGoodAzureConfig(t *testing.T) {
FILE: pkg/apis/externaldns/version.go
constant bannerTemplate (line 25) | bannerTemplate = `GitCommitShort=%s, GoVersion=%s, Platform=%s, UserAgen...
function UserAgent (line 35) | func UserAgent() string {
function Banner (line 39) | func Banner() string {
FILE: pkg/apis/externaldns/version_test.go
function TestBanner (line 25) | func TestBanner(t *testing.T) {
FILE: pkg/client/config.go
function GetRestConfig (line 41) | func GetRestConfig(kubeConfig, apiServerURL string) (*rest.Config, error) {
function InstrumentedRESTConfig (line 76) | func InstrumentedRESTConfig(kubeConfig, apiServerURL string, requestTime...
function NewKubeClient (line 91) | func NewKubeClient(kubeConfig, apiServerURL string, requestTimeout time....
FILE: pkg/client/config_test.go
function TestGetRestConfig_WithKubeConfig (line 33) | func TestGetRestConfig_WithKubeConfig(t *testing.T) {
function TestInstrumentedRESTConfig_AddsMetrics (line 68) | func TestInstrumentedRESTConfig_AddsMetrics(t *testing.T) {
function TestGetRestConfig_RecommendedHomeFile (line 106) | func TestGetRestConfig_RecommendedHomeFile(t *testing.T) {
FILE: pkg/events/controller.go
constant workers (line 35) | workers = 1
constant controllerName (line 36) | controllerName = "external-dns"
constant maxTriesPerEvent (line 37) | maxTriesPerEvent = 3
constant maxQueuedEvents (line 38) | maxQueuedEvents = 100
type EventEmitter (line 41) | type EventEmitter interface
type Controller (line 45) | type Controller struct
method Run (line 70) | func (ec *Controller) Run(ctx context.Context) {
method run (line 77) | func (ec *Controller) run(ctx context.Context) {
method processNextWorkItem (line 93) | func (ec *Controller) processNextWorkItem(ctx context.Context) bool {
method Add (line 123) | func (ec *Controller) Add(events ...Event) {
method emit (line 137) | func (ec *Controller) emit(event *eventsv1.Event) {
function NewEventController (line 54) | func NewEventController(client v1.EventsV1Interface, cfg *Config) (*Cont...
FILE: pkg/events/controller_test.go
function TestNewEventController_Success (line 45) | func TestNewEventController_Success(t *testing.T) {
function TestController_Run_NoEmitEvents (line 89) | func TestController_Run_NoEmitEvents(t *testing.T) {
function TestController_Run_EmitEvents (line 101) | func TestController_Run_EmitEvents(t *testing.T) {
function TestController_Queue_EmitEvents (line 147) | func TestController_Queue_EmitEvents(t *testing.T) {
function TestController_ProcessNextWorkItem_RequeuesOnError (line 186) | func TestController_ProcessNextWorkItem_RequeuesOnError(t *testing.T) {
FILE: pkg/events/fake/fake.go
type EventEmitter (line 25) | type EventEmitter struct
method Add (line 29) | func (m *EventEmitter) Add(events ...events.Event) {
function NewFakeEventEmitter (line 33) | func NewFakeEventEmitter() *EventEmitter {
FILE: pkg/events/fake/fake_test.go
function TestNewFakeEventEmitter (line 28) | func TestNewFakeEventEmitter(t *testing.T) {
function TestEventEmitter_Add_SingleEvent (line 35) | func TestEventEmitter_Add_SingleEvent(t *testing.T) {
function TestEventEmitter_Add_MultipleEvents (line 45) | func TestEventEmitter_Add_MultipleEvents(t *testing.T) {
function TestEventEmitter_Add_WithDifferentEventTypes (line 57) | func TestEventEmitter_Add_WithDifferentEventTypes(t *testing.T) {
function TestEventEmitter_Add_VerifyMockCalled (line 97) | func TestEventEmitter_Add_VerifyMockCalled(t *testing.T) {
function TestEventEmitter_Add_VerifyMockCalledWithAnyEvent (line 109) | func TestEventEmitter_Add_VerifyMockCalledWithAnyEvent(t *testing.T) {
function TestEventEmitter_Add_EmptyEventsPanics (line 120) | func TestEventEmitter_Add_EmptyEventsPanics(t *testing.T) {
FILE: pkg/events/types.go
constant ActionCreate (line 39) | ActionCreate Action = "Created"
constant ActionUpdate (line 40) | ActionUpdate Action = "Updated"
constant ActionDelete (line 41) | ActionDelete Action = "Deleted"
constant ActionFailed (line 42) | ActionFailed Action = "FailedSync"
constant RecordReady (line 43) | RecordReady Reason = "RecordReady"
constant RecordDeleted (line 44) | RecordDeleted Reason = "RecordDeleted"
constant RecordError (line 45) | RecordError Reason = "RecordError"
constant EventTypeNormal (line 47) | EventTypeNormal EventType = EventType(apiv1.EventTypeNormal)
constant EventTypeWarning (line 48) | EventTypeWarning EventType = EventType(apiv1.EventTypeWarning)
type Action (line 59) | type Action
type Reason (line 61) | type Reason
type EventType (line 63) | type EventType
type ConfigOption (line 64) | type ConfigOption
type Event (line 66) | type Event struct
method description (line 150) | func (e *Event) description() string {
method Action (line 154) | func (e *Event) Action() Action {
method Reason (line 158) | func (e *Event) Reason() Reason {
method EventType (line 162) | func (e *Event) EventType() EventType {
method event (line 166) | func (e *Event) event() *eventsv1.Event {
type ObjectReference (line 77) | type ObjectReference struct
method objectRef (line 261) | func (r *ObjectReference) objectRef() *apiv1.ObjectReference {
type Config (line 86) | type Config struct
method IsEnabled (line 257) | func (c *Config) IsEnabled() bool {
type EndpointInfo (line 92) | type EndpointInfo interface
function NewObjectReference (line 102) | func NewObjectReference(obj runtime.Object, source string) *ObjectRefere...
function NewEvent (line 125) | func NewEvent(obj *ObjectReference, msg string, a Action, r Reason) Event {
function NewEventFromEndpoint (line 140) | func NewEventFromEndpoint(ep EndpointInfo, a Action, r Reason) Event {
function sanitize (line 208) | func sanitize(input string) string {
function WithDryRun (line 230) | func WithDryRun(dryRun bool) ConfigOption {
function WithEmitEvents (line 236) | func WithEmitEvents(events []string) ConfigOption {
function NewConfig (line 249) | func NewConfig(opts ...ConfigOption) *Config {
FILE: pkg/events/types_test.go
function TestNewObjectReference_DoesNotMutateObject (line 33) | func TestNewObjectReference_DoesNotMutateObject(t *testing.T) {
function TestSanitize (line 48) | func TestSanitize(t *testing.T) {
function TestEvent_Reference (line 72) | func TestEvent_Reference(t *testing.T) {
function TestEvent_NewEvents (line 97) | func TestEvent_NewEvents(t *testing.T) {
function TestEvent_Transpose (line 158) | func TestEvent_Transpose(t *testing.T) {
function TestWithEmitEvents (line 185) | func TestWithEmitEvents(t *testing.T) {
type mockEndpointInfo (line 241) | type mockEndpointInfo struct
method GetDNSName (line 250) | func (m *mockEndpointInfo) GetDNSName() string { return m.dns...
method GetRecordType (line 251) | func (m *mockEndpointInfo) GetRecordType() string { return m.rec...
method GetRecordTTL (line 252) | func (m *mockEndpointInfo) GetRecordTTL() int64 { return m.rec...
method GetTargets (line 253) | func (m *mockEndpointInfo) GetTargets() []string { return m.tar...
method GetOwner (line 254) | func (m *mockEndpointInfo) GetOwner() string { return m.own...
method RefObject (line 255) | func (m *mockEndpointInfo) RefObject() *ObjectReference { return m.ref...
function TestNewEventFromEndpoint (line 257) | func TestNewEventFromEndpoint(t *testing.T) {
function TestNewObjectReference (line 383) | func TestNewObjectReference(t *testing.T) {
type customObject (line 504) | type customObject struct
method DeepCopyObject (line 509) | func (c *customObject) DeepCopyObject() runtime.Object {
function TestNewObjectReference_ReflectionFallback (line 516) | func TestNewObjectReference_ReflectionFallback(t *testing.T) {
FILE: pkg/http/drain.go
constant drainMaxBytes (line 30) | drainMaxBytes = 1 << 20
function DrainAndClose (line 38) | func DrainAndClose(body io.ReadCloser) {
FILE: pkg/http/drain_test.go
type trackingReadCloser (line 28) | type trackingReadCloser struct
method Close (line 33) | func (t *trackingReadCloser) Close() error {
function TestDrainAndClose_DrainsThenCloses (line 38) | func TestDrainAndClose_DrainsThenCloses(t *testing.T) {
function TestDrainAndClose_EmptyBody (line 49) | func TestDrainAndClose_EmptyBody(t *testing.T) {
function TestDrainAndClose_OversizedBody (line 55) | func TestDrainAndClose_OversizedBody(t *testing.T) {
FILE: pkg/http/http.go
function init (line 44) | func init() {
type CustomRoundTripper (line 48) | type CustomRoundTripper struct
method CancelRequest (line 54) | func (r *CustomRoundTripper) CancelRequest(_ *http.Request) {
method RoundTrip (line 57) | func (r *CustomRoundTripper) RoundTrip(req *http.Request) (*http.Respo...
function NewInstrumentedClient (line 77) | func NewInstrumentedClient(next *http.Client) *http.Client {
function NewInstrumentedTransport (line 87) | func NewInstrumentedTransport(next http.RoundTripper) http.RoundTripper {
FILE: pkg/http/http_benchmark_test.go
type roundTripFunc (line 30) | type roundTripFunc
method RoundTrip (line 32) | func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, e...
function newTestClient (line 37) | func newTestClient(fn roundTripFunc) *http.Client {
type apiUnderTest (line 43) | type apiUnderTest struct
method doStuff (line 48) | func (api *apiUnderTest) doStuff() ([]byte, error) {
function BenchmarkRoundTripper (line 57) | func BenchmarkRoundTripper(b *testing.B) {
function TestRoundTripper_Concurrent (line 74) | func TestRoundTripper_Concurrent(t *testing.T) {
FILE: pkg/http/http_test.go
type dummyTransport (line 30) | type dummyTransport struct
method RoundTrip (line 32) | func (d *dummyTransport) RoundTrip(_ *http.Request) (*http.Response, e...
function TestNewInstrumentedTransport (line 36) | func TestNewInstrumentedTransport(t *testing.T) {
function TestNewInstrumentedClient (line 50) | func TestNewInstrumentedClient(t *testing.T) {
function TestCancelRequest (line 64) | func TestCancelRequest(t *testing.T) {
type mockRoundTripper (line 82) | type mockRoundTripper struct
method RoundTrip (line 87) | func (mrt mockRoundTripper) RoundTrip(*http.Request) (*http.Response, ...
function TestRoundTrip (line 91) | func TestRoundTrip(t *testing.T) {
FILE: pkg/metrics/labels.go
constant LabelScheme (line 24) | LabelScheme = "scheme"
constant LabelHost (line 25) | LabelHost = "host"
constant LabelPath (line 26) | LabelPath = "path"
constant LabelMethod (line 27) | LabelMethod = "method"
constant LabelStatus (line 28) | LabelStatus = "status"
FILE: pkg/metrics/metrics.go
constant Namespace (line 30) | Namespace = "external_dns"
function init (line 37) | func init() {
function NewMetricsRegister (line 55) | func NewMetricsRegister() *MetricRegistry {
method MustRegister (line 74) | func (m *MetricRegistry) MustRegister(cs IMetric) {
FILE: pkg/metrics/metrics_test.go
type MockMetric (line 29) | type MockMetric struct
method Get (line 33) | func (m *MockMetric) Get() *Metric {
function TestMustRegister (line 37) | func TestMustRegister(t *testing.T) {
function TestUnsupportedMetricWarning (line 97) | func TestUnsupportedMetricWarning(t *testing.T) {
function TestNewMetricsRegister (line 107) | func TestNewMetricsRegister(t *testing.T) {
FILE: pkg/metrics/models.go
type MetricRegistry (line 26) | type MetricRegistry struct
type Metric (line 32) | type Metric struct
type IMetric (line 41) | type IMetric interface
type GaugeMetric (line 45) | type GaugeMetric struct
method Get (line 50) | func (g GaugeMetric) Get() *Metric {
type CounterMetric (line 54) | type CounterMetric struct
method Get (line 59) | func (g CounterMetric) Get() *Metric {
type CounterVecMetric (line 63) | type CounterVecMetric struct
method Get (line 68) | func (g CounterVecMetric) Get() *Metric {
type GaugeVecMetric (line 72) | type GaugeVecMetric struct
method Get (line 77) | func (g GaugeVecMetric) Get() *Metric {
method SetWithLabels (line 83) | func (g GaugeVecMetric) SetWithLabels(value float64, lvs ...string) {
method AddWithLabels (line 92) | func (g GaugeVecMetric) AddWithLabels(value float64, lvs ...string) {
function NewGaugeWithOpts (line 96) | func NewGaugeWithOpts(opts prometheus.GaugeOpts) GaugeMetric {
function NewGaugedVectorOpts (line 113) | func NewGaugedVectorOpts(opts prometheus.GaugeOpts, labelNames []string)...
function NewCounterWithOpts (line 128) | func NewCounterWithOpts(opts prometheus.CounterOpts) CounterMetric {
function NewCounterVecWithOpts (line 143) | func NewCounterVecWithOpts(opts prometheus.CounterOpts, labelNames []str...
type GaugeFuncMetric (line 158) | type GaugeFuncMetric struct
method Get (line 163) | func (g GaugeFuncMetric) Get() *Metric {
function NewGaugeFuncMetric (line 167) | func NewGaugeFuncMetric(opts prometheus.GaugeOpts) GaugeFuncMetric {
type SummaryVecMetric (line 186) | type SummaryVecMetric struct
method Get (line 191) | func (s SummaryVecMetric) Get() *Metric {
method SetWithLabels (line 195) | func (s SummaryVecMetric) SetWithLabels(value float64, labels promethe...
function NewSummaryVecWithOpts (line 199) | func NewSummaryVecWithOpts(opts prometheus.SummaryOpts, labels []string)...
function PathProcessor (line 214) | func PathProcessor(path string) string {
function toLower (line 222) | func toLower(lvs []string) []string {
FILE: pkg/metrics/models_test.go
function TestNewGaugeWithOpts (line 30) | func TestNewGaugeWithOpts(t *testing.T) {
function TestNewCounterWithOpts (line 48) | func TestNewCounterWithOpts(t *testing.T) {
function TestNewCounterVecWithOpts (line 66) | func TestNewCounterVecWithOpts(t *testing.T) {
function TestGaugeV_SetWithLabels (line 87) | func TestGaugeV_SetWithLabels(t *testing.T) {
function TestNewGaugeFuncMetric (line 117) | func TestNewGaugeFuncMetric(t *testing.T) {
function TestSummaryV_SetWithLabels (line 188) | func TestSummaryV_SetWithLabels(t *testing.T) {
function TestPathProcessor (line 223) | func TestPathProcessor(t *testing.T) {
function TestGaugeV_AddWithLabels (line 245) | func TestGaugeV_AddWithLabels(t *testing.T) {
FILE: pkg/rfc2317/arpa.go
function CidrToInAddr (line 30) | func CidrToInAddr(cidr string) (string, error) {
function reverseaddr (line 101) | func reverseaddr(addr string) (string, error) {
constant hexDigit (line 124) | hexDigit = "0123456789abcdef"
function Uitoa (line 126) | func Uitoa(val uint) string {
FILE: pkg/rfc2317/arpa_test.go
function TestCidrToInAddr (line 24) | func TestCidrToInAddr(t *testing.T) {
FILE: pkg/tlsutils/tlsconfig.go
constant defaultMinVersion (line 30) | defaultMinVersion = tls.VersionTLS12
function CreateTLSConfig (line 34) | func CreateTLSConfig(prefix string) (*tls.Config, error) {
function NewTLSConfig (line 45) | func NewTLSConfig(certPath, keyPath, caPath, serverName string, insecure...
function loadRoots (line 78) | func loadRoots(caPath string) (*x509.CertPool, error) {
FILE: pkg/tlsutils/tlsconfig_test.go
function testingKey (line 57) | func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING...
function writeTempFile (line 59) | func writeTempFile(t *testing.T, dir, name, content, envKey string) {
function TestCreateTLSConfig (line 66) | func TestCreateTLSConfig(t *testing.T) {
FILE: plan/conflict.go
type ConflictResolver (line 29) | type ConflictResolver interface
type PerResource (line 36) | type PerResource struct
method ResolveCreate (line 40) | func (s PerResource) ResolveCreate(candidates []*endpoint.Endpoint) *e...
method ResolveUpdate (line 47) | func (s PerResource) ResolveUpdate(current *endpoint.Endpoint, candida...
method ResolveRecordTypes (line 65) | func (s PerResource) ResolveRecordTypes(key planKey, row *planTableRow...
function compareEndpoints (line 103) | func compareEndpoints(a, b *endpoint.Endpoint) int {
FILE: plan/conflict_test.go
type ResolverSuite (line 33) | type ResolverSuite struct
method SetupTest (line 49) | func (suite *ResolverSuite) SetupTest() {
method TestStrictResolver (line 123) | func (suite *ResolverSuite) TestStrictResolver() {
method TestPerResource_ResolveRecordTypes (line 149) | func (suite *ResolverSuite) TestPerResource_ResolveRecordTypes() {
function TestPerResource_ResolveRecordTypes_LogsWarning (line 299) | func TestPerResource_ResolveRecordTypes_LogsWarning(t *testing.T) {
function TestConflictResolver (line 367) | func TestConflictResolver(t *testing.T) {
FILE: plan/metrics.go
function init (line 41) | func init() {
function recordOwnerMismatch (line 49) | func recordOwnerMismatch(owner string, current *endpoint.Endpoint) {
FILE: plan/metrics_test.go
function TestOwnerMismatchMetric (line 31) | func TestOwnerMismatchMetric(t *testing.T) {
function TestCalculateOwnerMismatchDetection (line 75) | func TestCalculateOwnerMismatchDetection(t *testing.T) {
function TestOwnerMismatchMetricDistribution (line 107) | func TestOwnerMismatchMetricDistribution(t *testing.T) {
function BenchmarkOwnerMismatchMetricDistribution (line 123) | func BenchmarkOwnerMismatchMetricDistribution(b *testing.B) {
function newOwnerMismatchFixture (line 131) | func newOwnerMismatchFixture(scale ...int) *Plan {
function TestFlushOwnerMismatch (line 170) | func TestFlushOwnerMismatch(t *testing.T) {
FILE: plan/plan.go
type Plan (line 32) | type Plan struct
method Calculate (line 166) | func (p *Plan) Calculate() *Plan {
method calculateChanges (line 201) | func (p *Plan) calculateChanges(t planTable) *Changes {
method appendTakenDNSNameChanges (line 240) | func (p *Plan) appendTakenDNSNameChanges(
method calculatePlanTableRowChanges (line 273) | func (p *Plan) calculatePlanTableRowChanges(t planTable, key planKey, ...
method appendEndpointUpdates (line 300) | func (p *Plan) appendEndpointUpdates(t planTable, changes *Changes, cu...
method isOldOwnerIDSetAndDifferent (line 311) | func (p *Plan) isOldOwnerIDSetAndDifferent(current *endpoint.Endpoint)...
method providerSpecificChanged (line 336) | func (p *Plan) providerSpecificChanged(desired, current *endpoint.Endp...
type Changes (line 57) | type Changes struct
method HasChanges (line 156) | func (c *Changes) HasChanges() bool {
type planKey (line 69) | type planKey struct
type planTable (line 92) | type planTable struct
method addCurrent (line 124) | func (t *planTable) addCurrent(e *endpoint.Endpoint) {
method addCandidate (line 130) | func (t *planTable) addCandidate(e *endpoint.Endpoint) {
method newPlanKey (line 137) | func (t *planTable) newPlanKey(e *endpoint.Endpoint) planKey {
function newPlanTable (line 97) | func newPlanTable() planTable { // TODO: make resolver configurable
type planTableRow (line 102) | type planTableRow struct
type domainEndpoints (line 117) | type domainEndpoints struct
function inheritOwner (line 315) | func inheritOwner(from, to *endpoint.Endpoint) {
function targetChanged (line 325) | func targetChanged(desired, current *endpoint.Endpoint) bool {
function shouldUpdateTTL (line 329) | func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool {
function filterRecordsForPlan (line 363) | func filterRecordsForPlan(records []*endpoint.Endpoint, domainFilter end...
function IsManagedRecord (line 380) | func IsManagedRecord(record string, managedRecords, excludeRecords []str...
FILE: plan/plan_test.go
type PlanTestSuite (line 35) | type PlanTestSuite struct
method SetupTest (line 62) | func (suite *PlanTestSuite) SetupTest() {
method TestSyncFirstRound (line 296) | func (suite *PlanTestSuite) TestSyncFirstRound() {
method TestSyncSecondRound (line 318) | func (suite *PlanTestSuite) TestSyncSecondRound() {
method TestSyncSecondRoundMigration (line 340) | func (suite *PlanTestSuite) TestSyncSecondRoundMigration() {
method TestSyncSecondRoundWithTTLChange (line 362) | func (suite *PlanTestSuite) TestSyncSecondRoundWithTTLChange() {
method TestSyncSecondRoundWithProviderSpecificChange (line 384) | func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificCha...
method TestSyncSecondRoundWithProviderSpecificNoChange (line 406) | func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificNoC...
method TestHasChangesCreate (line 421) | func (suite *PlanTestSuite) TestHasChangesCreate() {
method TestHasChangesDelete (line 428) | func (suite *PlanTestSuite) TestHasChangesDelete() {
method TestHasChanges (line 435) | func (suite *PlanTestSuite) TestHasChanges() {
method TestSyncSecondRoundWithProviderSpecificRemoval (line 450) | func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificRem...
method TestSyncSecondRoundWithProviderSpecificAddition (line 472) | func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificAdd...
method TestSyncSecondRoundWithOwnerInherited (line 494) | func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() {
method TestIdempotency (line 526) | func (suite *PlanTestSuite) TestIdempotency() {
method TestRecordTypeChange (line 547) | func (suite *PlanTestSuite) TestRecordTypeChange() {
method TestExistingCNameWithDualStackDesired (line 570) | func (suite *PlanTestSuite) TestExistingCNameWithDualStackDesired() {
method TestExistingDualStackWithCNameDesired (line 593) | func (suite *PlanTestSuite) TestExistingDualStackWithCNameDesired() {
method TestExistingOwnerNotMatchingDualStackDesired (line 622) | func (suite *PlanTestSuite) TestExistingOwnerNotMatchingDualStackDesir...
method TestConflictingCurrentNonConflictingDesired (line 650) | func (suite *PlanTestSuite) TestConflictingCurrentNonConflictingDesire...
method TestConflictingCurrentNoDesired (line 678) | func (suite *PlanTestSuite) TestConflictingCurrentNoDesired() {
method TestCurrentWithConflictingDesired (line 705) | func (suite *PlanTestSuite) TestCurrentWithConflictingDesired() {
method TestNoCurrentWithConflictingDesired (line 732) | func (suite *PlanTestSuite) TestNoCurrentWithConflictingDesired() {
method TestIgnoreTXT (line 754) | func (suite *PlanTestSuite) TestIgnoreTXT() {
method TestExcludeTXT (line 776) | func (suite *PlanTestSuite) TestExcludeTXT() {
method TestIgnoreTargetCase (line 799) | func (suite *PlanTestSuite) TestIgnoreTargetCase() {
method TestRemoveEndpoint (line 820) | func (suite *PlanTestSuite) TestRemoveEndpoint() {
method TestRemoveEndpointWithUpsert (line 842) | func (suite *PlanTestSuite) TestRemoveEndpointWithUpsert() {
method TestMultipleRecordsSameNameDifferentSetIdentifier (line 864) | func (suite *PlanTestSuite) TestMultipleRecordsSameNameDifferentSetIde...
method TestSetIdentifierUpdateCreatesAndDeletes (line 886) | func (suite *PlanTestSuite) TestSetIdentifierUpdateCreatesAndDeletes() {
method TestDomainFiltersInitial (line 908) | func (suite *PlanTestSuite) TestDomainFiltersInitial() {
method TestDomainFiltersUpdate (line 932) | func (suite *PlanTestSuite) TestDomainFiltersUpdate() {
method TestAAAARecords (line 956) | func (suite *PlanTestSuite) TestAAAARecords() {
method TestDualStackRecords (line 976) | func (suite *PlanTestSuite) TestDualStackRecords() {
method TestDualStackRecordsDelete (line 996) | func (suite *PlanTestSuite) TestDualStackRecordsDelete() {
method TestDualStackToSingleStack (line 1016) | func (suite *PlanTestSuite) TestDualStackToSingleStack() {
method TestRecordOwnerIdMigration (line 1036) | func (suite *PlanTestSuite) TestRecordOwnerIdMigration() {
function TestPlan_ChangesJson_DecodeEncode (line 254) | func TestPlan_ChangesJson_DecodeEncode(t *testing.T) {
function TestPlan_ChangesJson_DecodeMixedCase (line 288) | func TestPlan_ChangesJson_DecodeMixedCase(t *testing.T) {
function TestPlan (line 1062) | func TestPlan(t *testing.T) {
function validateEntries (line 1067) | func validateEntries(t *testing.T, entries, expected []*endpoint.Endpoin...
function TestShouldUpdateProviderSpecific (line 1073) | func TestShouldUpdateProviderSpecific(tt *testing.T) {
function TestOwnerMismatchLogsDebug (line 1151) | func TestOwnerMismatchLogsDebug(t *testing.T) {
FILE: plan/policy.go
type Policy (line 20) | type Policy interface
type SyncPolicy (line 32) | type SyncPolicy struct
method Apply (line 35) | func (p *SyncPolicy) Apply(changes *Changes) *Changes {
type UpsertOnlyPolicy (line 40) | type UpsertOnlyPolicy struct
method Apply (line 43) | func (p *UpsertOnlyPolicy) Apply(changes *Changes) *Changes {
type CreateOnlyPolicy (line 52) | type CreateOnlyPolicy struct
method Apply (line 55) | func (p *CreateOnlyPolicy) Apply(changes *Changes) *Changes {
FILE: plan/policy_test.go
function TestApply (line 27) | func TestApply(t *testing.T) {
function TestPolicies (line 74) | func TestPolicies(t *testing.T) {
function validatePolicy (line 81) | func validatePolicy(t *testing.T, policy, expected Policy) {
FILE: provider/akamai/akamai.go
constant defaultTTL (line 40) | defaultTTL = 600
constant maxUint (line 41) | maxUint = ^uint(0)
constant maxInt (line 42) | maxInt = int(maxUint >> 1)
type AkamaiDNSService (line 46) | type AkamaiDNSService interface
type AkamaiConfig (line 55) | type AkamaiConfig struct
type AkamaiProvider (line 70) | type AkamaiProvider struct
method ListZones (line 174) | func (p AkamaiProvider) ListZones(queryArgs dns.ZoneListQueryArgs) (*d...
method GetRecordsets (line 178) | func (p AkamaiProvider) GetRecordsets(zone string, queryArgs dns.Recor...
method CreateRecordsets (line 182) | func (p AkamaiProvider) CreateRecordsets(recordsets *dns.Recordsets, z...
method GetRecord (line 186) | func (p AkamaiProvider) GetRecord(zone string, name string, recordtype...
method DeleteRecord (line 190) | func (p AkamaiProvider) DeleteRecord(record *dns.RecordBody, zone stri...
method UpdateRecord (line 194) | func (p AkamaiProvider) UpdateRecord(record *dns.RecordBody, zone stri...
method fetchZones (line 199) | func (p AkamaiProvider) fetchZones() (akamaiZones, error) {
method Records (line 229) | func (p AkamaiProvider) Records(context.Context) ([]*endpoint.Endpoint...
method ApplyChanges (line 276) | func (p AkamaiProvider) ApplyChanges(_ context.Context, changes *plan....
method createRecordsets (line 384) | func (p AkamaiProvider) createRecordsets(zoneNameIDMapper provider.Zon...
method deleteRecordsets (line 425) | func (p AkamaiProvider) deleteRecordsets(zoneNameIDMapper provider.Zon...
method updateNewRecordsets (line 458) | func (p AkamaiProvider) updateNewRecordsets(zoneNameIDMapper provider....
type akamaiZones (line 83) | type akamaiZones struct
type akamaiZone (line 87) | type akamaiZone struct
function New (line 93) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 109) | func newProvider(akamaiConfig AkamaiConfig, akaService AkamaiDNSService)...
function newAkamaiRecordset (line 324) | func newAkamaiRecordset(dnsName, recordType string, ttl int, targets []s...
function cleanTargets (line 334) | func cleanTargets(rtype string, targets ...string) []string {
function trimTxtRdata (line 359) | func trimTxtRdata(rdata []string, rtype string) []string {
function ttlAsInt (line 372) | func ttlAsInt(src endpoint.TTL) int {
function edgeChangesByZone (line 489) | func edgeChangesByZone(zoneMap provider.ZoneIDName, endpoints []*endpoin...
FILE: provider/akamai/akamai_test.go
type edgednsStubData (line 34) | type edgednsStubData struct
type edgednsStub (line 39) | type edgednsStub struct
method createStubDataEntry (line 64) | func (r *edgednsStub) createStubDataEntry(objtype string) {
method setOutput (line 73) | func (r *edgednsStub) setOutput(objtype string, output []any) {
method ListZones (line 83) | func (r *edgednsStub) ListZones(_ dns.ZoneListQueryArgs) (*dns.ZoneLis...
method GetRecordsets (line 98) | func (r *edgednsStub) GetRecordsets(_ string, _ dns.RecordsetQueryArgs...
method CreateRecordsets (line 112) | func (r *edgednsStub) CreateRecordsets(_ *dns.Recordsets, _ string, _ ...
method GetRecord (line 116) | func (r *edgednsStub) GetRecord(_ string, _ string, _ string) (*dns.Re...
method DeleteRecord (line 122) | func (r *edgednsStub) DeleteRecord(_ *dns.RecordBody, _ string, _ bool...
method UpdateRecord (line 126) | func (r *edgednsStub) UpdateRecord(_ *dns.RecordBody, _ string, _ bool...
function newStub (line 43) | func newStub() *edgednsStub {
function createAkamaiStubProvider (line 49) | func createAkamaiStubProvider(stub *edgednsStub, domfilter *endpoint.Dom...
function TestFetchZonesZoneIDFilter (line 131) | func TestFetchZonesZoneIDFilter(t *testing.T) {
function TestFetchZonesEmpty (line 147) | func TestFetchZonesEmpty(t *testing.T) {
function TestAkamaiRecords (line 164) | func TestAkamaiRecords(t *testing.T) {
function TestAkamaiRecordsEmpty (line 199) | func TestAkamaiRecordsEmpty(t *testing.T) {
function TestAkamaiRecordsFilters (line 213) | func TestAkamaiRecordsFilters(t *testing.T) {
function TestCreateRecords (line 243) | func TestCreateRecords(t *testing.T) {
function TestCreateRecordsDomainFilter (line 259) | func TestCreateRecordsDomainFilter(t *testing.T) {
function TestDeleteRecords (line 278) | func TestDeleteRecords(t *testing.T) {
function TestDeleteRecordsDomainFilter (line 294) | func TestDeleteRecordsDomainFilter(t *testing.T) {
function TestUpdateRecords (line 313) | func TestUpdateRecords(t *testing.T) {
function TestUpdateRecordsDomainFilter (line 329) | func TestUpdateRecordsDomainFilter(t *testing.T) {
function TestAkamaiApplyChanges (line 347) | func TestAkamaiApplyChanges(t *testing.T) {
FILE: provider/alibabacloud/alibaba_cloud.go
constant defaultTTL (line 44) | defaultTTL = 600
constant defaultAlibabaCloudPrivateZoneRecordTTL (line 45) | defaultAlibabaCloudPrivateZoneRecordTTL = 60
constant defaultAlibabaCloudPageSize (line 46) | defaultAlibabaCloudPageSize = 50
constant nullHostAlibabaCloud (line 47) | nullHostAlibabaCloud = "@"
constant pVTZDoamin (line 48) | pVTZDoamin = "pvtz.aliyuncs.com"
constant defaultAlibabaCloudRequestScheme (line 49) | defaultAlibabaCloudRequestScheme = "https"
type AlibabaCloudDNSAPI (line 54) | type AlibabaCloudDNSAPI interface
type AlibabaCloudPrivateZoneAPI (line 64) | type AlibabaCloudPrivateZoneAPI interface
type AlibabaCloudProvider (line 74) | type AlibabaCloudProvider struct
method getDNSClient (line 219) | func (p *AlibabaCloudProvider) getDNSClient() AlibabaCloudDNSAPI {
method getPvtzClient (line 225) | func (p *AlibabaCloudProvider) getPvtzClient() AlibabaCloudPrivateZone...
method setNextExpire (line 231) | func (p *AlibabaCloudProvider) setNextExpire(expireTime time.Time) {
method refreshStsToken (line 237) | func (p *AlibabaCloudProvider) refreshStsToken(sleepTime time.Duration) {
method Records (line 295) | func (p *AlibabaCloudProvider) Records(_ context.Context) ([]*endpoint...
method ApplyChanges (line 306) | func (p *AlibabaCloudProvider) ApplyChanges(_ context.Context, changes...
method getDNSName (line 318) | func (p *AlibabaCloudProvider) getDNSName(rr, domain string) string {
method recordsForDNS (line 328) | func (p *AlibabaCloudProvider) recordsForDNS() ([]*endpoint.Endpoint, ...
method getRecordKey (line 360) | func (p *AlibabaCloudProvider) getRecordKey(record alidns.Record) stri...
method getRecordKeyByEndpoint (line 367) | func (p *AlibabaCloudProvider) getRecordKeyByEndpoint(endpoint *endpoi...
method groupRecords (line 371) | func (p *AlibabaCloudProvider) groupRecords(records []alidns.Record) m...
method records (line 382) | func (p *AlibabaCloudProvider) records() ([]alidns.Record, error) {
method getDomainList (line 412) | func (p *AlibabaCloudProvider) getDomainList() ([]string, error) {
method getDomainRecords (line 437) | func (p *AlibabaCloudProvider) getDomainRecords(domainName string) ([]...
method applyChangesForDNS (line 475) | func (p *AlibabaCloudProvider) applyChangesForDNS(changes *plan.Change...
method escapeTXTRecordValue (line 496) | func (p *AlibabaCloudProvider) escapeTXTRecordValue(value string) stri...
method unescapeTXTRecordValue (line 501) | func (p *AlibabaCloudProvider) unescapeTXTRecordValue(value string) st...
method createRecord (line 508) | func (p *AlibabaCloudProvider) createRecord(endpoint *endpoint.Endpoin...
method createRecords (line 554) | func (p *AlibabaCloudProvider) createRecords(endpoints []*endpoint.End...
method deleteRecord (line 562) | func (p *AlibabaCloudProvider) deleteRecord(recordID string) error {
method updateRecord (line 580) | func (p *AlibabaCloudProvider) updateRecord(record alidns.Record, endp...
method deleteRecords (line 600) | func (p *AlibabaCloudProvider) deleteRecords(recordMap map[string][]al...
method equals (line 622) | func (p *AlibabaCloudProvider) equals(record alidns.Record, endpoint *...
method updateRecords (line 636) | func (p *AlibabaCloudProvider) updateRecords(recordMap map[string][]al...
method splitDNSName (line 679) | func (p *AlibabaCloudProvider) splitDNSName(dnsName string, hostedZone...
method matchVPC (line 706) | func (p *AlibabaCloudProvider) matchVPC(zoneID string) bool {
method privateZones (line 726) | func (p *AlibabaCloudProvider) privateZones() ([]pvtz.Zone, error) {
method getPrivateZones (line 769) | func (p *AlibabaCloudProvider) getPrivateZones() (map[string]*alibabaP...
method groupPrivateZoneRecords (line 825) | func (p *AlibabaCloudProvider) groupPrivateZoneRecords(zone *alibabaPr...
method privateZoneRecords (line 840) | func (p *AlibabaCloudProvider) privateZoneRecords() ([]*endpoint.Endpo...
method createPrivateZoneRecord (line 872) | func (p *AlibabaCloudProvider) createPrivateZoneRecord(zones map[strin...
method createPrivateZoneRecords (line 913) | func (p *AlibabaCloudProvider) createPrivateZoneRecords(zones map[stri...
method deletePrivateZoneRecord (line 921) | func (p *AlibabaCloudProvider) deletePrivateZoneRecord(recordID int64)...
method deletePrivateZoneRecords (line 940) | func (p *AlibabaCloudProvider) deletePrivateZoneRecords(zones map[stri...
method applyChangesForPrivateZone (line 972) | func (p *AlibabaCloudProvider) applyChangesForPrivateZone(changes *pla...
method updatePrivateZoneRecord (line 990) | func (p *AlibabaCloudProvider) updatePrivateZoneRecord(record pvtz.Rec...
method equalsPrivateZone (line 1011) | func (p *AlibabaCloudProvider) equalsPrivateZone(record pvtz.Record, e...
method updatePrivateZoneRecords (line 1025) | func (p *AlibabaCloudProvider) updatePrivateZoneRecords(zones map[stri...
type alibabaCloudConfig (line 90) | type alibabaCloudConfig struct
function New (line 101) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 108) | func newProvider(configFile string, domainFilter *endpoint.DomainFilter,...
function getCloudConfigFromStsToken (line 188) | func getCloudConfigFromStsToken() (alibabaCloudConfig, error) {
function getNextPageNumber (line 353) | func getNextPageNumber(pageNumber, totalCount int64) int64 {
type alibabaPrivateZone (line 764) | type alibabaPrivateZone struct
function keys (line 1075) | func keys[T any](value map[string]T) []string {
FILE: provider/alibabacloud/alibaba_cloud_test.go
type MockAlibabaCloudDNSAPI (line 30) | type MockAlibabaCloudDNSAPI struct
method AddDomainRecord (line 57) | func (m *MockAlibabaCloudDNSAPI) AddDomainRecord(request *alidns.AddDo...
method DeleteDomainRecord (line 70) | func (m *MockAlibabaCloudDNSAPI) DeleteDomainRecord(request *alidns.De...
method UpdateDomainRecord (line 83) | func (m *MockAlibabaCloudDNSAPI) UpdateDomainRecord(request *alidns.Up...
method DescribeDomains (line 95) | func (m *MockAlibabaCloudDNSAPI) DescribeDomains(_ *alidns.DescribeDom...
method DescribeDomainRecords (line 109) | func (m *MockAlibabaCloudDNSAPI) DescribeDomainRecords(request *alidns...
function NewMockAlibabaCloudDNSAPI (line 34) | func NewMockAlibabaCloudDNSAPI() *MockAlibabaCloudDNSAPI {
type MockAlibabaCloudPrivateZoneAPI (line 121) | type MockAlibabaCloudPrivateZoneAPI struct
method AddZoneRecord (line 159) | func (m *MockAlibabaCloudPrivateZoneAPI) AddZoneRecord(request *pvtz.A...
method DeleteZoneRecord (line 171) | func (m *MockAlibabaCloudPrivateZoneAPI) DeleteZoneRecord(request *pvt...
method UpdateZoneRecord (line 184) | func (m *MockAlibabaCloudPrivateZoneAPI) UpdateZoneRecord(request *pvt...
method DescribeZoneRecords (line 195) | func (m *MockAlibabaCloudPrivateZoneAPI) DescribeZoneRecords(_ *pvtz.D...
method DescribeZones (line 201) | func (m *MockAlibabaCloudPrivateZoneAPI) DescribeZones(_ *pvtz.Describ...
method DescribeZoneInfo (line 207) | func (m *MockAlibabaCloudPrivateZoneAPI) DescribeZoneInfo(_ *pvtz.Desc...
function NewMockAlibabaCloudPrivateZoneAPI (line 126) | func NewMockAlibabaCloudPrivateZoneAPI() *MockAlibabaCloudPrivateZoneAPI {
function newTestAlibabaCloudProvider (line 218) | func newTestAlibabaCloudProvider(private bool) *AlibabaCloudProvider {
function TestAlibabaCloudPrivateProvider_Records (line 235) | func TestAlibabaCloudPrivateProvider_Records(t *testing.T) {
function TestAlibabaCloudProvider_Records (line 250) | func TestAlibabaCloudProvider_Records(t *testing.T) {
function TestAlibabaCloudProvider_ApplyChanges (line 265) | func TestAlibabaCloudProvider_ApplyChanges(t *testing.T) {
function TestAlibabaCloudProvider_ApplyChanges_HaveNoDefinedZoneDomain (line 323) | func TestAlibabaCloudProvider_ApplyChanges_HaveNoDefinedZoneDomain(t *te...
function TestAlibabaCloudProvider_Records_PrivateZone (line 381) | func TestAlibabaCloudProvider_Records_PrivateZone(t *testing.T) {
function TestAlibabaCloudProvider_ApplyChanges_PrivateZone (line 396) | func TestAlibabaCloudProvider_ApplyChanges_PrivateZone(t *testing.T) {
function TestAlibabaCloudProvider_splitDNSName (line 440) | func TestAlibabaCloudProvider_splitDNSName(t *testing.T) {
function TestAlibabaCloudProvider_TXTEndpoint (line 508) | func TestAlibabaCloudProvider_TXTEndpoint(t *testing.T) {
function TestAlibabaCloudProvider_TXTEndpoint_PrivateZone (line 522) | func TestAlibabaCloudProvider_TXTEndpoint_PrivateZone(t *testing.T) {
FILE: provider/aws/aws.go
constant defaultAWSProfile (line 44) | defaultAWSProfile = "default"
constant defaultTTL (line 45) | defaultTTL = 300
constant route53PageSize (line 52) | route53PageSize int32 = 300
constant providerSpecificAlias (line 54) | providerSpecificAlias = "alias"
constant providerSpecificTargetHostedZone (line 55) | providerSpecificTargetHostedZone = "aws/target-hosted-zone"
constant providerSpecificEvaluateTargetHealth (line 59) | providerSpecificEvaluateTargetHealth = "aws/evaluate-targe...
constant providerSpecificWeight (line 60) | providerSpecificWeight = "aws/weight"
constant providerSpecificRegion (line 61) | providerSpecificRegion = "aws/region"
constant providerSpecificFailover (line 62) | providerSpecificFailover = "aws/failover"
constant providerSpecificGeolocationContinentCode (line 63) | providerSpecificGeolocationContinentCode = "aws/geolocation-co...
constant providerSpecificGeolocationCountryCode (line 64) | providerSpecificGeolocationCountryCode = "aws/geolocation-co...
constant providerSpecificGeolocationSubdivisionCode (line 65) | providerSpecificGeolocationSubdivisionCode = "aws/geolocation-su...
constant providerSpecificGeoProximityLocationAWSRegion (line 66) | providerSpecificGeoProximityLocationAWSRegion = "aws/geoproximity-r...
constant providerSpecificGeoProximityLocationBias (line 67) | providerSpecificGeoProximityLocationBias = "aws/geoproximity-b...
constant providerSpecificGeoProximityLocationCoordinates (line 68) | providerSpecificGeoProximityLocationCoordinates = "aws/geoproximity-c...
constant providerSpecificGeoProximityLocationLocalZoneGroup (line 69) | providerSpecificGeoProximityLocationLocalZoneGroup = "aws/geoproximity-l...
constant providerSpecificMultiValueAnswer (line 70) | providerSpecificMultiValueAnswer = "aws/multi-value-an...
constant providerSpecificHealthCheckID (line 71) | providerSpecificHealthCheckID = "aws/health-check-id"
constant sameZoneAlias (line 72) | sameZoneAlias = "same-zone"
constant batchSize (line 75) | batchSize = 10
constant minLatitude (line 76) | minLatitude = -90.0
constant maxLatitude (line 77) | maxLatitude = 90.0
constant minLongitude (line 78) | minLongitude = -180.0
constant maxLongitude (line 79) | maxLongitude = 180.0
type Route53API (line 228) | type Route53API interface
type Route53Change (line 237) | type Route53Change struct
type Route53Changes (line 244) | type Route53Changes
method Route53Changes (line 257) | func (cs Route53Changes) Route53Changes() []route53types.Change {
type profiledZone (line 246) | type profiledZone struct
type geoProximity (line 251) | type geoProximity struct
method withAWSRegion (line 1068) | func (gp *geoProximity) withAWSRegion() *geoProximity {
method withLocalZoneGroup (line 1077) | func (gp *geoProximity) withLocalZoneGroup() *geoProximity {
method withBias (line 1086) | func (gp *geoProximity) withBias() *geoProximity {
method withCoordinates (line 1114) | func (gp *geoProximity) withCoordinates() *geoProximity {
method build (line 1136) | func (gp *geoProximity) build() *route53types.GeoProximityLocation {
type zoneTags (line 265) | type zoneTags
method filterZonesByTags (line 269) | func (z zoneTags) filterZonesByTags(p *AWSProvider, zones map[string]*...
method append (line 278) | func (z zoneTags) append(id string, tags []route53types.Tag) {
type AWSProvider (line 289) | type AWSProvider struct
method Zones (line 381) | func (p *AWSProvider) Zones(ctx context.Context) (map[string]*route53t...
method zones (line 395) | func (p *AWSProvider) zones(ctx context.Context) (map[string]*profiled...
method Records (line 497) | func (p *AWSProvider) Records(ctx context.Context) ([]*endpoint.Endpoi...
method records (line 506) | func (p *AWSProvider) records(ctx context.Context, zones map[string]*p...
method requiresDeleteCreate (line 625) | func (p *AWSProvider) requiresDeleteCreate(old *endpoint.Endpoint, new...
method createUpdateChanges (line 660) | func (p *AWSProvider) createUpdateChanges(newEndpoints, oldEndpoints [...
method GetDomainFilter (line 688) | func (p *AWSProvider) GetDomainFilter() endpoint.DomainFilterInterface {
method ApplyChanges (line 703) | func (p *AWSProvider) ApplyChanges(ctx context.Context, changes *plan....
method submitChanges (line 720) | func (p *AWSProvider) submitChanges(ctx context.Context, changes Route...
method newChanges (line 828) | func (p *AWSProvider) newChanges(action route53types.ChangeAction, end...
method AdjustEndpoints (line 844) | func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ...
method adjustEndpointAndNewAaaaIfNeeded (line 857) | func (p *AWSProvider) adjustEndpointAndNewAaaaIfNeeded(ep *endpoint.En...
method adjustAliasRecord (line 877) | func (p *AWSProvider) adjustAliasRecord(ep *endpoint.Endpoint) {
method adjustAandAAAARecord (line 892) | func (p *AWSProvider) adjustAandAAAARecord(ep *endpoint.Endpoint) {
method adjustCNAMERecord (line 902) | func (p *AWSProvider) adjustCNAMERecord(ep *endpoint.Endpoint) {
method adjustOtherRecord (line 928) | func (p *AWSProvider) adjustOtherRecord(ep *endpoint.Endpoint) {
method newChange (line 962) | func (p *AWSProvider) newChange(action route53types.ChangeAction, ep *...
method tagsForZone (line 1190) | func (p *AWSProvider) tagsForZone(ctx context.Context, zoneIDs []strin...
method SupportedRecordType (line 1445) | func (p *AWSProvider) SupportedRecordType(recordType route53types.RRTy...
type AWSConfig (line 315) | type AWSConfig struct
function New (line 332) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 359) | func newProvider(cfg AWSConfig, clients map[string]Route53API) *AWSProvi...
function wildcardUnescape (line 470) | func wildcardUnescape(s string) string {
function convertOctalToAscii (line 477) | func convertOctalToAscii(input string) string {
function containsOctalSequence (line 489) | func containsOctalSequence(domain string) bool {
function handleGeoProximityLocationRecord (line 605) | func handleGeoProximityLocationRecord(r *route53types.ResourceRecordSet,...
function adjustGeoProximityLocationEndpoint (line 942) | func adjustGeoProximityLocationEndpoint(ep *endpoint.Endpoint) {
function newGeoProximity (line 1060) | func newGeoProximity(ep *endpoint.Endpoint) *geoProximity {
function validateCoordinates (line 1100) | func validateCoordinates(lat, long string) error {
function withChangeForGeoProximityEndpoint (line 1143) | func withChangeForGeoProximityEndpoint(change *Route53Change, ep *endpoi...
function findChangesInQueue (line 1154) | func findChangesInQueue(changes Route53Changes, queue Route53Changes) (R...
function groupChangesByNameAndOwnershipRelation (line 1178) | func groupChangesByNameAndOwnershipRelation(cs Route53Changes) map[strin...
function countChangeBytes (line 1216) | func countChangeBytes(cs Route53Changes) int {
function countChangeValues (line 1225) | func countChangeValues(cs Route53Changes) int {
function batchChangeSet (line 1233) | func batchChangeSet(cs Route53Changes, batchSize int, batchSizeBytes int...
function sortChangesByActionNameType (line 1287) | func sortChangesByActionNameType(cs Route53Changes) Route53Changes {
function changesByZone (line 1308) | func changesByZone(zones map[string]*profiledZone, changeSet Route53Chan...
function suitableZones (line 1356) | func suitableZones(hostname string, zones map[string]*profiledZone) []*p...
function useAlias (line 1382) | func useAlias(ep *endpoint.Endpoint, preferCNAME bool) bool {
function isAWSAlias (line 1396) | func isAWSAlias(ep *endpoint.Endpoint) string {
function canonicalHostedZone (line 1418) | func canonicalHostedZone(hostname string) string {
function cleanZoneID (line 1441) | func cleanZoneID(id string) string {
FILE: provider/aws/aws_fixtures_test.go
function TestAWSRecordsV1 (line 30) | func TestAWSRecordsV1(t *testing.T) {
function TestAWSZonesFilterWithTags (line 49) | func TestAWSZonesFilterWithTags(t *testing.T) {
function TestAWSZonesFiltersWithTags (line 65) | func TestAWSZonesFiltersWithTags(t *testing.T) {
function TestAWSZonesSecondRequestHitsTheCache (line 94) | func TestAWSZonesSecondRequestHitsTheCache(t *testing.T) {
FILE: provider/aws/aws_test.go
constant defaultBatchChangeSize (line 48) | defaultBatchChangeSize = 4000
constant defaultBatchChangeSizeBytes (line 49) | defaultBatchChangeSizeBytes = 32000
constant defaultBatchChangeSizeValues (line 50) | defaultBatchChangeSizeValues = 1000
constant defaultBatchChangeInterval (line 51) | defaultBatchChangeInterval = time.Second
constant defaultEvaluateTargetHealth (line 52) | defaultEvaluateTargetHealth = true
type Route53APIStub (line 62) | type Route53APIStub struct
method MockMethod (line 74) | func (r *Route53APIStub) MockMethod(method string, args ...any) *mock....
method ListResourceRecordSets (line 88) | func (r *Route53APIStub) ListResourceRecordSets(ctx context.Context, i...
method ListTagsForResources (line 167) | func (r *Route53APIStub) ListTagsForResources(_ context.Context, input...
method ChangeResourceRecordSets (line 189) | func (r *Route53APIStub) ChangeResourceRecordSets(_ context.Context, i...
method ListHostedZones (line 248) | func (r *Route53APIStub) ListHostedZones(_ context.Context, _ *route53...
method CreateHostedZone (line 256) | func (r *Route53APIStub) CreateHostedZone(_ context.Context, input *ro...
function NewRoute53APIStub (line 79) | func NewRoute53APIStub(t *testing.T) *Route53APIStub {
type Route53APICounter (line 108) | type Route53APICounter struct
method ListResourceRecordSets (line 120) | func (c *Route53APICounter) ListResourceRecordSets(ctx context.Context...
method ChangeResourceRecordSets (line 125) | func (c *Route53APICounter) ChangeResourceRecordSets(ctx context.Conte...
method CreateHostedZone (line 130) | func (c *Route53APICounter) CreateHostedZone(ctx context.Context, inpu...
method ListHostedZones (line 135) | func (c *Route53APICounter) ListHostedZones(ctx context.Context, input...
method ListTagsForResources (line 140) | func (c *Route53APICounter) ListTagsForResources(ctx context.Context, ...
function NewRoute53APICounter (line 113) | func NewRoute53APICounter(w Route53API) *Route53APICounter {
function wildcardEscape (line 146) | func wildcardEscape(s string) string {
function specialCharactersEscape (line 154) | func specialCharactersEscape(s string) string {
type dynamicMock (line 270) | type dynamicMock struct
method ListResourceRecordSets (line 274) | func (m *dynamicMock) ListResourceRecordSets(_ context.Context, input ...
method ChangeResourceRecordSets (line 282) | func (m *dynamicMock) ChangeResourceRecordSets(input *route53.ChangeRe...
method isMocked (line 290) | func (m *dynamicMock) isMocked(method string, arguments ...any) bool {
function TestAWSZones (line 302) | func TestAWSZones(t *testing.T) {
function TestAWSZonesWithTagFilterError (line 351) | func TestAWSZonesWithTagFilterError(t *testing.T) {
function TestAWSRecordsFilter (line 374) | func TestAWSRecordsFilter(t *testing.T) {
function TestAWSRecords (line 397) | func TestAWSRecords(t *testing.T) {
function TestAWSRecordsSoftError (line 690) | func TestAWSRecordsSoftError(t *testing.T) {
function TestAWSAdjustEndpoints (line 706) | func TestAWSAdjustEndpoints(t *testing.T) {
function TestAWSApplyChanges (line 740) | func TestAWSApplyChanges(t *testing.T) {
function TestAWSApplyChangesDryRun (line 1335) | func TestAWSApplyChangesDryRun(t *testing.T) {
function TestAWSChangesByZones (line 1474) | func TestAWSChangesByZones(t *testing.T) {
function TestAWSsubmitChanges (line 1603) | func TestAWSsubmitChanges(t *testing.T) {
function TestAWSsubmitChangesError (line 1632) | func TestAWSsubmitChangesError(t *testing.T) {
function TestAWSsubmitChangesRetryOnError (line 1646) | func TestAWSsubmitChangesRetryOnError(t *testing.T) {
function TestAWSBatchChangeSet (line 1705) | func TestAWSBatchChangeSet(t *testing.T) {
function TestAWSBatchChangeSetExceeding (line 1737) | func TestAWSBatchChangeSetExceeding(t *testing.T) {
function TestAWSBatchChangeSetExceedingNameChange (line 1777) | func TestAWSBatchChangeSetExceedingNameChange(t *testing.T) {
function TestAWSBatchChangeSetExceedingBytesLimit (line 1810) | func TestAWSBatchChangeSetExceedingBytesLimit(t *testing.T) {
function TestAWSBatchChangeSetExceedingBytesLimitUpsert (line 1869) | func TestAWSBatchChangeSetExceedingBytesLimitUpsert(t *testing.T) {
function TestAWSBatchChangeSetExceedingValuesLimit (line 1928) | func TestAWSBatchChangeSetExceedingValuesLimit(t *testing.T) {
function TestAWSBatchChangeSetExceedingValuesLimitUpsert (line 1987) | func TestAWSBatchChangeSetExceedingValuesLimitUpsert(t *testing.T) {
function validateEndpoints (line 2046) | func validateEndpoints(t *testing.T, provider *AWSProvider, endpoints []...
function validateAWSZones (line 2054) | func validateAWSZones(t *testing.T, zones map[string]*route53types.Hoste...
function validateAWSZone (line 2062) | func validateAWSZone(t *testing.T, zone *route53types.HostedZone, expect...
function validateAWSChangeRecords (line 2067) | func validateAWSChangeRecords(t *testing.T, records Route53Changes, expe...
function validateAWSChangeRecord (line 2075) | func validateAWSChangeRecord(t *testing.T, record *Route53Change, expect...
function TestAWSCreateRecordsWithCNAME (line 2081) | func TestAWSCreateRecordsWithCNAME(t *testing.T) {
function TestAWSCreateRecordsWithALIAS (line 2110) | func TestAWSCreateRecordsWithALIAS(t *testing.T) {
function TestAWSisLoadBalancer (line 2180) | func TestAWSisLoadBalancer(t *testing.T) {
function TestAWSisAWSAlias (line 2200) | func TestAWSisAWSAlias(t *testing.T) {
function TestAWSCanonicalHostedZone (line 2228) | func TestAWSCanonicalHostedZone(t *testing.T) {
function TestAWSCanonicalHostedZoneNotExist (line 2238) | func TestAWSCanonicalHostedZoneNotExist(t *testing.T) {
function BenchmarkTestAWSCanonicalHostedZone (line 2246) | func BenchmarkTestAWSCanonicalHostedZone(b *testing.B) {
function BenchmarkTestAWSNonCanonicalHostedZone (line 2254) | func BenchmarkTestAWSNonCanonicalHostedZone(b *testing.B) {
function TestAWSSuitableZones (line 2262) | func TestAWSSuitableZones(t *testing.T) {
function createAWSZone (line 2302) | func createAWSZone(t *testing.T, provider *AWSProvider, zone *route53typ...
function setAWSRecords (line 2315) | func setAWSRecords(t *testing.T, provider *AWSProvider, records []route5...
function listAWSRecords (line 2347) | func listAWSRecords(t *testing.T, client Route53API, zone string) []rout...
function newAWSProvider (line 2357) | func newAWSProvider(t *testing.T, domainFilter *endpoint.DomainFilter, z...
function newAWSProviderWithTagFilter (line 2361) | func newAWSProviderWithTagFilter(t *testing.T, domainFilter *endpoint.Do...
function setupZoneTags (line 2415) | func setupZoneTags(client *Route53APIStub) {
function addZoneTags (line 2438) | func addZoneTags(tagMap map[string][]route53types.Tag, zoneID string, ta...
function validateRecords (line 2449) | func validateRecords(t *testing.T, records []route53types.ResourceRecord...
function containsRecordWithDNSName (line 2453) | func containsRecordWithDNSName(records []*endpoint.Endpoint, dnsName str...
function TestRequiresDeleteCreate (line 2462) | func TestRequiresDeleteCreate(t *testing.T) {
function TestConvertOctalToAscii (line 2490) | func TestConvertOctalToAscii(t *testing.T) {
function TestGeoProximityWithAWSRegion (line 2521) | func TestGeoProximityWithAWSRegion(t *testing.T) {
function TestGeoProximityWithLocalZoneGroup (line 2602) | func TestGeoProximityWithLocalZoneGroup(t *testing.T) {
function TestGeoProximityWithCoordinates (line 2662) | func TestGeoProximityWithCoordinates(t *testing.T) {
function TestGeoProximityWithBias (line 2756) | func TestGeoProximityWithBias(t *testing.T) {
function TestAWSProvider_createUpdateChanges_NewMoreThanOld (line 2858) | func TestAWSProvider_createUpdateChanges_NewMoreThanOld(t *testing.T) {
function TestAWSProvider_adjustEndpointAndNewAaaaIfNeeded (line 2891) | func TestAWSProvider_adjustEndpointAndNewAaaaIfNeeded(t *testing.T) {
FILE: provider/aws/aws_utils_test.go
type HostedZones (line 35) | type HostedZones struct
type HostedZone (line 39) | type HostedZone struct
type Route53APIFixtureStub (line 47) | type Route53APIFixtureStub struct
method ListResourceRecordSets (line 105) | func (r Route53APIFixtureStub) ListResourceRecordSets(_ context.Contex...
method ChangeResourceRecordSets (line 110) | func (r Route53APIFixtureStub) ChangeResourceRecordSets(_ context.Cont...
method CreateHostedZone (line 115) | func (r Route53APIFixtureStub) CreateHostedZone(_ context.Context, _ *...
method ListHostedZones (line 120) | func (r Route53APIFixtureStub) ListHostedZones(_ context.Context, _ *r...
method ListTagsForResources (line 129) | func (r Route53APIFixtureStub) ListTagsForResources(_ context.Context,...
function providerFilters (line 53) | func providerFilters(client *Route53APIFixtureStub, options ...func(awsP...
function WithDomainFilters (line 70) | func WithDomainFilters(filters ...string) func(awsProvider *AWSProvider) {
function WithZoneIDFilters (line 76) | func WithZoneIDFilters(filters ...string) func(awsProvider *AWSProvider) {
function WithZoneTagFilters (line 82) | func WithZoneTagFilters(filters []string) func(awsProvider *AWSProvider) {
function NewRoute53APIFixtureStub (line 88) | func NewRoute53APIFixtureStub(zones *HostedZones) *Route53APIFixtureStub {
function unmarshalZonesFixture (line 146) | func unmarshalZonesFixture(obj any, t *testing.T) {
FILE: provider/aws/config.go
type AWSSessionConfig (line 35) | type AWSSessionConfig struct
function CreateDefaultV2Config (line 42) | func CreateDefaultV2Config(cfg *externaldns.Config) awsv2.Config {
function CreateV2Configs (line 56) | func CreateV2Configs(cfg *externaldns.Config) map[string]awsv2.Config {
function newV2Config (line 80) | func newV2Config(awsConfig AWSSessionConfig) (awsv2.Config, error) {
FILE: provider/aws/config_test.go
function Test_newV2Config (line 33) | func Test_newV2Config(t *testing.T) {
function prepareCredentialsFile (line 150) | func prepareCredentialsFile(t *testing.T) (*os.File, error) {
function TestCreateV2Configs (line 160) | func TestCreateV2Configs(t *testing.T) {
function TestCreateConfigFatalOnError (line 217) | func TestCreateConfigFatalOnError(t *testing.T) {
FILE: provider/aws/instrumented_config.go
type requestMetrics (line 31) | type requestMetrics struct
type requestMetricsKey (line 35) | type requestMetricsKey struct
function getRequestMetric (line 37) | func getRequestMetric(ctx context.Context) requestMetrics {
function setRequestMetric (line 42) | func setRequestMetric(ctx context.Context, requestMetrics requestMetrics...
function GetInstrumentationMiddlewares (line 84) | func GetInstrumentationMiddlewares() []func(*middleware.Stack) error {
FILE: provider/aws/instrumented_config_test.go
function Test_GetInstrumentationMiddlewares (line 33) | func Test_GetInstrumentationMiddlewares(t *testing.T) {
type MockInitializeHandler (line 54) | type MockInitializeHandler struct
method HandleInitialize (line 58) | func (mock *MockInitializeHandler) HandleInitialize(ctx context.Contex...
function Test_InitializedTimedOperationMiddleware (line 64) | func Test_InitializedTimedOperationMiddleware(t *testing.T) {
type MockDeserializeHandler (line 75) | type MockDeserializeHandler struct
method HandleDeserialize (line 78) | func (mock *MockDeserializeHandler) HandleDeserialize(_ context.Contex...
function Test_ExtractAWSRequestParameters (line 82) | func Test_ExtractAWSRequestParameters(t *testing.T) {
FILE: provider/awssd/aws_sd.go
constant defaultTTL (line 41) | defaultTTL = 300
constant maxResults (line 44) | maxResults = 100
constant sdNamespaceTypePublic (line 46) | sdNamespaceTypePublic = "public"
constant sdNamespaceTypePrivate (line 47) | sdNamespaceTypePrivate = "private"
constant sdInstanceAttrIPV4 (line 49) | sdInstanceAttrIPV4 = "AWS_INSTANCE_IPV4"
constant sdInstanceAttrIPV6 (line 50) | sdInstanceAttrIPV6 = "AWS_INSTANCE_IPV6"
constant sdInstanceAttrCname (line 51) | sdInstanceAttrCname = "AWS_INSTANCE_CNAME"
constant sdInstanceAttrAlias (line 52) | sdInstanceAttrAlias = "AWS_ALIAS_DNS_NAME"
type AWSSDClient (line 65) | type AWSSDClient interface
type AWSSDProvider (line 77) | type AWSSDProvider struct
method Records (line 153) | func (p *AWSSDProvider) Records(ctx context.Context) ([]*endpoint.Endp...
method instancesToEndpoint (line 195) | func (p *AWSSDProvider) instancesToEndpoint(ns *sdtypes.NamespaceSumma...
method ApplyChanges (line 237) | func (p *AWSSDProvider) ApplyChanges(ctx context.Context, changes *pla...
method updatesToCreates (line 267) | func (p *AWSSDProvider) updatesToCreates(changes *plan.Changes) ([]*en...
method submitCreates (line 303) | func (p *AWSSDProvider) submitCreates(ctx context.Context, namespaces ...
method submitDeletes (line 342) | func (p *AWSSDProvider) submitDeletes(ctx context.Context, namespaces ...
method ListNamespaces (line 371) | func (p *AWSSDProvider) ListNamespaces(ctx context.Context) ([]*sdtype...
method ListServicesByNamespaceID (line 395) | func (p *AWSSDProvider) ListServicesByNamespaceID(ctx context.Context,...
method CreateService (line 435) | func (p *AWSSDProvider) CreateService(ctx context.Context, namespaceID...
method UpdateService (line 472) | func (p *AWSSDProvider) UpdateService(ctx context.Context, service *sd...
method DeleteService (line 502) | func (p *AWSSDProvider) DeleteService(ctx context.Context, service *sd...
method RegisterInstance (line 532) | func (p *AWSSDProvider) RegisterInstance(ctx context.Context, service ...
method DeregisterInstance (line 569) | func (p *AWSSDProvider) DeregisterInstance(ctx context.Context, servic...
method targetToInstanceID (line 589) | func (p *AWSSDProvider) targetToInstanceID(target string) string {
method changesByNamespaceID (line 598) | func (p *AWSSDProvider) changesByNamespaceID(namespaces []*sdtypes.Nam...
method parseHostname (line 644) | func (p *AWSSDProvider) parseHostname(hostname string) (string, string) {
method routingPolicyFromEndpoint (line 650) | func (p *AWSSDProvider) routingPolicyFromEndpoint(ep *endpoint.Endpoin...
method serviceTypeFromEndpoint (line 659) | func (p *AWSSDProvider) serviceTypeFromEndpoint(ep *endpoint.Endpoint)...
method isAWSLoadBalancer (line 677) | func (p *AWSSDProvider) isAWSLoadBalancer(hostname string) bool {
function New (line 94) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 104) | func newProvider(domainFilter *endpoint.DomainFilter, namespaceType stri...
function newSdNamespaceFilter (line 122) | func newSdNamespaceFilter(namespaceTypeConfig string) []sdtypes.Namespac...
function awsTags (line 144) | func awsTags(tags map[string]string) []sdtypes.Tag {
function matchingNamespaces (line 631) | func matchingNamespaces(hostname string, namespaces []*sdtypes.Namespace...
FILE: provider/awssd/aws_sd_test.go
function TestAWSSDProvider_Records (line 35) | func TestAWSSDProvider_Records(t *testing.T) {
function TestAWSSDProvider_ApplyChanges (line 175) | func TestAWSSDProvider_ApplyChanges(t *testing.T) {
function TestAWSSDProvider_ApplyChanges_Update (line 229) | func TestAWSSDProvider_ApplyChanges_Update(t *testing.T) {
function TestAWSSDProvider_ListNamespaces (line 283) | func TestAWSSDProvider_ListNamespaces(t *testing.T) {
function TestAWSSDProvider_ListServicesByNamespace (line 333) | func TestAWSSDProvider_ListServicesByNamespace(t *testing.T) {
function TestAWSSDProvider_CreateService (line 387) | func TestAWSSDProvider_CreateService(t *testing.T) {
function TestAWSSDProvider_CreateServiceDryRun (line 501) | func TestAWSSDProvider_CreateServiceDryRun(t *testing.T) {
function TestAWSSDProvider_CreateService_LabelNotSet (line 532) | func TestAWSSDProvider_CreateService_LabelNotSet(t *testing.T) {
function TestAWSSDProvider_UpdateService (line 562) | func TestAWSSDProvider_UpdateService(t *testing.T) {
function TestAWSSDProvider_UpdateService_DryRun (line 606) | func TestAWSSDProvider_UpdateService_DryRun(t *testing.T) {
function TestAWSSDProvider_DeleteService (line 653) | func TestAWSSDProvider_DeleteService(t *testing.T) {
function TestAWSSDProvider_DeleteServiceEmptyDescription_Logging (line 730) | func TestAWSSDProvider_DeleteServiceEmptyDescription_Logging(t *testing....
function TestAWSSDProvider_DeleteServiceDryRun (line 767) | func TestAWSSDProvider_DeleteServiceDryRun(t *testing.T) {
function TestAWSSDProvider_RegisterInstance (line 801) | func TestAWSSDProvider_RegisterInstance(t *testing.T) {
function TestAWSSDProvider_DeregisterInstance (line 968) | func TestAWSSDProvider_DeregisterInstance(t *testing.T) {
function TestAWSSDProvider_awsTags (line 1010) | func TestAWSSDProvider_awsTags(t *testing.T) {
FILE: provider/awssd/fixtures_test.go
type AWSSDClientStub (line 44) | type AWSSDClientStub struct
method CreateService (line 58) | func (s *AWSSDClientStub) CreateService(_ context.Context, input *serv...
method DeregisterInstance (line 80) | func (s *AWSSDClientStub) DeregisterInstance(_ context.Context, input ...
method GetService (line 88) | func (s *AWSSDClientStub) GetService(_ context.Context, input *service...
method DiscoverInstances (line 101) | func (s *AWSSDClientStub) DiscoverInstances(_ context.Context, input *...
method ListNamespaces (line 128) | func (s *AWSSDClientStub) ListNamespaces(_ context.Context, input *sd....
method ListServices (line 146) | func (s *AWSSDClientStub) ListServices(_ context.Context, input *sd.Li...
method RegisterInstance (line 164) | func (s *AWSSDClientStub) RegisterInstance(_ context.Context, input *s...
method UpdateService (line 180) | func (s *AWSSDClientStub) UpdateService(ctx context.Context, input *sd...
method DeleteService (line 195) | func (s *AWSSDClientStub) DeleteService(ctx context.Context, input *sd...
function newTestAWSSDProvider (line 208) | func newTestAWSSDProvider(api AWSSDClient, domainFilter *endpoint.Domain...
function instanceToHTTPInstanceSummary (line 219) | func instanceToHTTPInstanceSummary(instance *sdtypes.Instance) *sdtypes....
function namespaceToNamespaceSummary (line 230) | func namespaceToNamespaceSummary(namespace *sdtypes.Namespace) *sdtypes....
function serviceToServiceSummary (line 243) | func serviceToServiceSummary(service *sdtypes.Service) *sdtypes.ServiceS...
function testHelperAWSSDServicesMapsEqual (line 262) | func testHelperAWSSDServicesMapsEqual(t *testing.T, expected map[string]...
function testHelperAWSSDServicesEqual (line 270) | func testHelperAWSSDServicesEqual(t *testing.T, expected *sdtypes.Servic...
FILE: provider/azure/azure.go
constant defaultTTL (line 42) | defaultTTL = 300
type ZonesClient (line 46) | type ZonesClient interface
type RecordSetsClient (line 51) | type RecordSetsClient interface
type AzureProvider (line 58) | type AzureProvider struct
method Records (line 130) | func (p *AzureProvider) Records(ctx context.Context) ([]*endpoint.Endp...
method ApplyChanges (line 185) | func (p *AzureProvider) ApplyChanges(ctx context.Context, changes *pla...
method zones (line 197) | func (p *AzureProvider) zones(ctx context.Context) ([]dns.Zone, error) {
method SupportedRecordType (line 224) | func (p *AzureProvider) SupportedRecordType(recordType string) bool {
method mapChanges (line 235) | func (p *AzureProvider) mapChanges(zones []dns.Zone, changes *plan.Cha...
method deleteRecords (line 272) | func (p *AzureProvider) deleteRecords(ctx context.Context, deleted azu...
method updateRecords (line 299) | func (p *AzureProvider) updateRecords(ctx context.Context, updated azu...
method recordSetNameForZone (line 352) | func (p *AzureProvider) recordSetNameForZone(zone string, endpoint *en...
method newRecordSet (line 365) | func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns...
function New (line 74) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 93) | func newProvider(configFile string, domainFilter, zoneNameFilter *endpoi...
type azureChangeMap (line 233) | type azureChangeMap
function formatAzureDNSName (line 452) | func formatAzureDNSName(recordName, zoneName string) string {
function extractAzureTargets (line 460) | func extractAzureTargets(recordSet *dns.RecordSet) []string {
FILE: provider/azure/azure_private_dns.go
type PrivateZonesClient (line 40) | type PrivateZonesClient interface
type PrivateRecordSetsClient (line 45) | type PrivateRecordSetsClient interface
type AzurePrivateDNSProvider (line 52) | type AzurePrivateDNSProvider struct
method Records (line 124) | func (p *AzurePrivateDNSProvider) Records(ctx context.Context) ([]*end...
method ApplyChanges (line 191) | func (p *AzurePrivateDNSProvider) ApplyChanges(ctx context.Context, ch...
method zones (line 205) | func (p *AzurePrivateDNSProvider) zones(ctx context.Context) ([]privat...
method mapChanges (line 236) | func (p *AzurePrivateDNSProvider) mapChanges(zones []privatedns.Privat...
method deleteRecords (line 273) | func (p *AzurePrivateDNSProvider) deleteRecords(ctx context.Context, d...
method updateRecords (line 301) | func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, u...
method recordSetNameForZone (line 355) | func (p *AzurePrivateDNSProvider) recordSetNameForZone(zone string, en...
method newRecordSet (line 368) | func (p *AzurePrivateDNSProvider) newRecordSet(endpoint *endpoint.Endp...
function newPrivateDNSProvider (line 70) | func newPrivateDNSProvider(configFile string, domainFilter, zoneNameFilt...
function NewPrivate (line 105) | func NewPrivate(_ context.Context, cfg *externaldns.Config, domainFilter...
type azurePrivateDNSChangeMap (line 234) | type azurePrivateDNSChangeMap
function extractAzurePrivateDNSTargets (line 442) | func extractAzurePrivateDNSTargets(recordSet *privatedns.RecordSet) []st...
FILE: provider/azure/azure_privatedns_test.go
constant recordTTL (line 35) | recordTTL = 300
type mockPrivateZonesClient (line 40) | type mockPrivateZonesClient struct
method NewListByResourceGroupPager (line 62) | func (client *mockPrivateZonesClient) NewListByResourceGroupPager(_ st...
function newMockPrivateZonesClient (line 44) | func newMockPrivateZonesClient(zones []*privatedns.PrivateZone) mockPriv...
type mockPrivateRecordSetsClient (line 68) | type mockPrivateRecordSetsClient struct
method NewListPager (line 92) | func (client *mockPrivateRecordSetsClient) NewListPager(_ string, _ st...
method Delete (line 96) | func (client *mockPrivateRecordSetsClient) Delete(_ context.Context, _...
method CreateOrUpdate (line 108) | func (client *mockPrivateRecordSetsClient) CreateOrUpdate(_ context.Co...
function newMockPrivateRecordSectsClient (line 74) | func newMockPrivateRecordSectsClient(recordSets []*privatedns.RecordSet)...
function createMockPrivateZone (line 125) | func createMockPrivateZone(zone string, id string) *privatedns.PrivateZo...
function privateARecordSetPropertiesGetter (line 132) | func privateARecordSetPropertiesGetter(values []string, ttl int64) *priv...
function privateAAAARecordSetPropertiesGetter (line 145) | func privateAAAARecordSetPropertiesGetter(values []string, ttl int64) *p...
function privateCNameRecordSetPropertiesGetter (line 158) | func privateCNameRecordSetPropertiesGetter(values []string, ttl int64) *...
function privateMXRecordSetPropertiesGetter (line 167) | func privateMXRecordSetPropertiesGetter(values []string, ttl int64) *pri...
function privateTxtRecordSetPropertiesGetter (line 179) | func privateTxtRecordSetPropertiesGetter(values []string, ttl int64) *pr...
function privateOthersRecordSetPropertiesGetter (line 190) | func privateOthersRecordSetPropertiesGetter(_ []string, ttl int64) *priv...
function createPrivateMockRecordSet (line 196) | func createPrivateMockRecordSet(recordType string, values ...string) *pr...
function createPrivateMockRecordSetWithNameAndTTL (line 200) | func createPrivateMockRecordSetWithNameAndTTL(name, recordType, value st...
function createPrivateMockRecordSetMultiWithTTL (line 204) | func createPrivateMockRecordSetMultiWithTTL(name, recordType string, ttl...
function newMockedAzurePrivateDNSProvider (line 229) | func newMockedAzurePrivateDNSProvider(domainFilter *endpoint.DomainFilte...
function newAzurePrivateDNSProvider (line 235) | func newAzurePrivateDNSProvider(domainFilter *endpoint.DomainFilter, zon...
function TestAzurePrivateDNSRecord (line 249) | func TestAzurePrivateDNSRecord(t *testing.T) {
function TestAzurePrivateDNSMultiRecord (line 285) | func TestAzurePrivateDNSMultiRecord(t *testing.T) {
function TestAzurePrivateDNSApplyChanges (line 321) | func TestAzurePrivateDNSApplyChanges(t *testing.T) {
function TestAzurePrivateDNSApplyChangesDryRun (line 353) | func TestAzurePrivateDNSApplyChangesDryRun(t *testing.T) {
function testAzurePrivateDNSApplyChangesInternal (line 363) | func testAzurePrivateDNSApplyChangesInternal(t *testing.T, dryRun bool, ...
function TestAzurePrivateDNSNameFilter (line 435) | func TestAzurePrivateDNSNameFilter(t *testing.T) {
function TestAzurePrivateDNSApplyChangesZoneName (line 468) | func TestAzurePrivateDNSApplyChangesZoneName(t *testing.T) {
function testAzurePrivateDNSApplyChangesInternalZoneName (line 489) | func testAzurePrivateDNSApplyChangesInternalZoneName(t *testing.T, dryRu...
FILE: provider/azure/azure_test.go
type mockZonesClient (line 38) | type mockZonesClient struct
method NewListByResourceGroupPager (line 60) | func (client *mockZonesClient) NewListByResourceGroupPager(_ string, _...
function newMockZonesClient (line 42) | func newMockZonesClient(zones []*dns.Zone) mockZonesClient {
type mockRecordSetsClient (line 66) | type mockRecordSetsClient struct
method NewListAllByDNSZonePager (line 90) | func (client *mockRecordSetsClient) NewListAllByDNSZonePager(_ string,...
method Delete (line 94) | func (client *mockRecordSetsClient) Delete(_ context.Context, _ string...
method CreateOrUpdate (line 106) | func (client *mockRecordSetsClient) CreateOrUpdate(_ context.Context, ...
function newMockRecordSetsClient (line 72) | func newMockRecordSetsClient(recordSets []*dns.RecordSet) mockRecordSets...
function createMockZone (line 123) | func createMockZone(zone string, id string) *dns.Zone {
function aRecordSetPropertiesGetter (line 130) | func aRecordSetPropertiesGetter(values []string, ttl int64) *dns.RecordS...
function aaaaRecordSetPropertiesGetter (line 143) | func aaaaRecordSetPropertiesGetter(values []string, ttl int64) *dns.Reco...
function cNameRecordSetPropertiesGetter (line 156) | func cNameRecordSetPropertiesGetter(values []string, ttl int64) *dns.Rec...
function mxRecordSetPropertiesGetter (line 165) | func mxRecordSetPropertiesGetter(values []string, ttl int64) *dns.Record...
function nsRecordSetPropertiesGetter (line 177) | func nsRecordSetPropertiesGetter(values []string, ttl int64) *dns.Record...
function txtRecordSetPropertiesGetter (line 190) | func txtRecordSetPropertiesGetter(values []string, ttl int64) *dns.Recor...
function othersRecordSetPropertiesGetter (line 201) | func othersRecordSetPropertiesGetter(_ []string, ttl int64) *dns.RecordS...
function createMockRecordSet (line 207) | func createMockRecordSet(name, recordType string, values ...string) *dns...
function createMockRecordSetWithTTL (line 211) | func createMockRecordSetWithTTL(name, recordType, value string, ttl int6...
function createMockRecordSetMultiWithTTL (line 215) | func createMockRecordSetMultiWithTTL(name, recordType string, ttl int64,...
function newMockedAzureProvider (line 242) | func newMockedAzureProvider(domainFilter *endpoint.DomainFilter, zoneNam...
function newAzureProvider (line 248) | func newAzureProvider(domainFilter *endpoint.DomainFilter, zoneNameFilte...
function validateAzureEndpoints (line 264) | func validateAzureEndpoints(t *testing.T, endpoints []*endpoint.Endpoint...
function TestAzureRecord (line 268) | func TestAzureRecord(t *testing.T) {
function TestAzureMultiRecord (line 310) | func TestAzureMultiRecord(t *testing.T) {
function TestAzureApplyChanges (line 352) | func TestAzureApplyChanges(t *testing.T) {
function TestAzureApplyChangesDryRun (line 388) | func TestAzureApplyChangesDryRun(t *testing.T) {
function testAzureApplyChangesInternal (line 398) | func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client Rec...
function TestAzureNameFilter (line 480) | func TestAzureNameFilter(t *testing.T) {
function TestAzureApplyChangesZoneName (line 516) | func TestAzureApplyChangesZoneName(t *testing.T) {
function testAzureApplyChangesInternalZoneName (line 540) | func testAzureApplyChangesInternalZoneName(t *testing.T, dryRun bool, cl...
FILE: provider/azure/common.go
function parseMxTarget (line 31) | func parseMxTarget[T dns.MxRecord | privatedns.MxRecord](mxTarget string...
FILE: provider/azure/common_test.go
function Test_parseMxTarget (line 30) | func Test_parseMxTarget(t *testing.T) {
FILE: provider/azure/config.go
type config (line 37) | type config struct
function getConfig (line 53) | func getConfig(configFile, subscriptionID, resourceGroup, userAssignedId...
type ctxKey (line 83) | type ctxKey
constant clientRequestIDKey (line 87) | clientRequestIDKey ctxKey = "client-request-id"
constant msRequestIDHeader (line 89) | msRequestIDHeader = "x-ms-request-id"
constant msCorrelationRequestHeader (line 90) | msCorrelationRequestHeader = "x-ms-correlation-request-id"
constant msClientRequestIDHeader (line 91) | msClientRequestIDHeader = "x-ms-client-request-id"
type customHeaderPolicy (line 95) | type customHeaderPolicy struct
method Do (line 97) | func (p *customHeaderPolicy) Do(req *policy.Request) (*http.Response, ...
function CustomHeaderPolicynew (line 107) | func CustomHeaderPolicynew() policy.Policy { return &customHeaderPolicy{} }
function getCredentials (line 110) | func getCredentials(cfg config, maxRetries int) (azcore.TokenCredential,...
function getCloudConfiguration (line 198) | func getCloudConfiguration(cfg config) (cloud.Configuration, error) {
FILE: provider/azure/config_test.go
function TestGetCloudConfiguration (line 35) | func TestGetCloudConfiguration(t *testing.T) {
function TestOverrideConfiguration (line 59) | func TestOverrideConfiguration(t *testing.T) {
type transportFunc (line 72) | type transportFunc
method Do (line 74) | func (f transportFunc) Do(req *http.Request) (*http.Response, error) {
function TestCustomHeaderPolicyWithRetries (line 78) | func TestCustomHeaderPolicyWithRetries(t *testing.T) {
function TestMaxRetriesCount (line 194) | func TestMaxRetriesCount(t *testing.T) {
function parseMaxRetries (line 307) | func parseMaxRetries(value string) (int, error) {
FILE: provider/blueprint/zone_cache.go
type ZoneCache (line 28) | type ZoneCache struct
function NewZoneCache (line 37) | func NewZoneCache[T any](duration time.Duration) *ZoneCache[T] {
method Get (line 43) | func (c *ZoneCache[T]) Get() T {
method Reset (line 51) | func (c *ZoneCache[T]) Reset(data T) {
method Expired (line 64) | func (c *ZoneCache[T]) Expired() bool {
FILE: provider/blueprint/zone_cache_test.go
function TestZoneCache_SliceCache (line 29) | func TestZoneCache_SliceCache(t *testing.T) {
function TestZoneCache_MapCache (line 41) | func TestZoneCache_MapCache(t *testing.T) {
function TestZoneCache_Expiration (line 53) | func TestZoneCache_Expiration(t *testing.T) {
function TestZoneCache_CachingDisabled (line 65) | func TestZoneCache_CachingDisabled(t *testing.T) {
function TestZoneCache_Expiration_Synctest (line 75) | func TestZoneCache_Expiration_Synctest(t *testing.T) {
function TestZoneCache_ThreadSafety (line 101) | func TestZoneCache_ThreadSafety(t *testing.T) {
FILE: provider/cached_provider.go
function init (line 51) | func init() {
type CachedProvider (line 56) | type CachedProvider struct
method Records (line 70) | func (c *CachedProvider) Records(ctx context.Context) ([]*endpoint.End...
method ApplyChanges (line 87) | func (c *CachedProvider) ApplyChanges(ctx context.Context, changes *pl...
method Reset (line 97) | func (c *CachedProvider) Reset() {
method needRefresh (line 102) | func (c *CachedProvider) needRefresh() bool {
function NewCachedProvider (line 63) | func NewCachedProvider(provider Provider, refreshDelay time.Duration) *C...
FILE: provider/cached_provider_test.go
type testProviderFunc (line 31) | type testProviderFunc struct
method Records (line 39) | func (p *testProviderFunc) Records(ctx context.Context) ([]*endpoint.E...
method ApplyChanges (line 43) | func (p *testProviderFunc) ApplyChanges(ctx context.Context, changes *...
method PropertyValuesEqual (line 47) | func (p *testProviderFunc) PropertyValuesEqual(name string, previous s...
method AdjustEndpoints (line 51) | func (p *testProviderFunc) AdjustEndpoints(endpoints []*endpoint.Endpo...
method GetDomainFilter (line 55) | func (p *testProviderFunc) GetDomainFilter() endpoint.DomainFilterInte...
function recordsNotCalled (line 59) | func recordsNotCalled(t *testing.T) func(ctx context.Context) ([]*endpoi...
function applyChangesNotCalled (line 66) | func applyChangesNotCalled(t *testing.T) func(_ context.Context, _ *plan...
function propertyValuesEqualNotCalled (line 73) | func propertyValuesEqualNotCalled(t *testing.T) func(name string, previo...
function adjustEndpointsNotCalled (line 80) | func adjustEndpointsNotCalled(t *testing.T) func(endpoints []*endpoint.E...
function newTestProviderFunc (line 87) | func newTestProviderFunc(t *testing.T) *testProviderFunc {
function TestCachedProviderCallsProviderOnFirstCall (line 96) | func TestCachedProviderCallsProviderOnFirstCall(t *testing.T) {
function TestCachedProviderUsesCacheWhileValid (line 112) | func TestCachedProviderUsesCacheWhileValid(t *testing.T) {
function TestCachedProviderForcesCacheRefreshOnUpdate (line 148) | func TestCachedProviderForcesCacheRefreshOnUpdate(t *testing.T) {
FILE: provider/civo/civo.go
type CivoProvider (line 35) | type CivoProvider struct
method Zones (line 109) | func (p *CivoProvider) Zones(_ context.Context) ([]civogo.DNSDomain, e...
method Records (line 119) | func (p *CivoProvider) Records(ctx context.Context) ([]*endpoint.Endpo...
method fetchRecords (line 152) | func (p *CivoProvider) fetchRecords(domainID string) ([]civogo.DNSReco...
method fetchZones (line 161) | func (p *CivoProvider) fetchZones() ([]civogo.DNSDomain, error) {
method submitChanges (line 181) | func (p *CivoProvider) submitChanges(changes CivoChanges) error {
method ApplyChanges (line 448) | func (p *CivoProvider) ApplyChanges(_ context.Context, changes *plan.C...
type CivoChanges (line 43) | type CivoChanges struct
method Empty (line 50) | func (c *CivoChanges) Empty() bool {
type CivoChangeCreate (line 55) | type CivoChangeCreate struct
type CivoChangeUpdate (line 61) | type CivoChangeUpdate struct
type CivoChangeDelete (line 68) | type CivoChangeDelete struct
function New (line 74) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 79) | func newProvider(domainFilter *endpoint.DomainFilter, dryRun bool) (*Civ...
function processCreateActions (line 257) | func processCreateActions(zonesByID map[string]civogo.DNSDomain, records...
function processUpdateActions (line 308) | func processUpdateActions(zonesByID map[string]civogo.DNSDomain, records...
function processDeleteActions (line 410) | func processDeleteActions(zonesByID map[string]civogo.DNSDomain, records...
function endpointsByZone (line 503) | func endpointsByZone(zoneNameIDMapper provider.ZoneIDName, endpoints []*...
function convertRecordType (line 518) | func convertRecordType(recordType string) (civogo.DNSRecordType, error) {
function getStrippedRecordName (line 533) | func getStrippedRecordName(zone civogo.DNSDomain, ep endpoint.Endpoint) ...
function getRecordID (line 541) | func getRecordID(records []civogo.DNSRecord, zone civogo.DNSDomain, ep e...
FILE: provider/civo/civo_test.go
function TestNewProvider (line 36) | func TestNewProvider(t *testing.T) {
function TestNewCivoProviderNoToken (line 44) | func TestNewCivoProviderNoToken(t *testing.T) {
function TestCivoProviderZones (line 51) | func TestCivoProviderZones(t *testing.T) {
function TestCivoProviderZonesWithError (line 73) | func TestCivoProviderZonesWithError(t *testing.T) {
function TestCivoProviderRecords (line 86) | func TestCivoProviderRecords(t *testing.T) {
function TestCivoProviderRecordsWithError (line 132) | func TestCivoProviderRecordsWithError(t *testing.T) {
function TestCivoProviderWithoutRecords (line 170) | func TestCivoProviderWithoutRecords(t *testing.T) {
function TestCivoProcessCreateActionsLogs (line 190) | func TestCivoProcessCreateActionsLogs(t *testing.T) {
function TestCivoProcessCreateActions (line 253) | func TestCivoProcessCreateActions(t *testing.T) {
function TestCivoProcessCreateActionsWithError (line 323) | func TestCivoProcessCreateActionsWithError(t *testing.T) {
function TestCivoProcessUpdateActionsWithError (line 359) | func TestCivoProcessUpdateActionsWithError(t *testing.T) {
function TestCivoProcessUpdateActions (line 395) | func TestCivoProcessUpdateActions(t *testing.T) {
function TestCivoProcessDeleteAction (line 524) | func TestCivoProcessDeleteAction(t *testing.T) {
function TestCivoApplyChanges (line 620) | func TestCivoApplyChanges(t *testing.T) {
function TestCivoApplyChangesError (line 656) | func TestCivoApplyChangesError(t *testing.T) {
function TestCivoProviderFetchZones (line 715) | func TestCivoProviderFetchZones(t *testing.T) {
function TestCivoProviderFetchZonesWithFilter (line 737) | func TestCivoProviderFetchZonesWithFilter(t *testing.T) {
function TestCivoProviderFetchRecords (line 761) | func TestCivoProviderFetchRecords(t *testing.T) {
function TestCivoProviderFetchRecordsWithError (line 782) | func TestCivoProviderFetchRecordsWithError(t *testing.T) {
function TestCivo_getStrippedRecordName (line 799) | func TestCivo_getStrippedRecordName(t *testing.T) {
function TestCivo_convertRecordType (line 813) | func TestCivo_convertRecordType(t *testing.T) {
function TestCivoProviderGetRecordID (line 840) | func TestCivoProviderGetRecordID(t *testing.T) {
function TestCivo_submitChangesCreate (line 868) | func TestCivo_submitChangesCreate(t *testing.T) {
function TestCivo_submitChangesDelete (line 994) | func TestCivo_submitChangesDelete(t *testing.T) {
function TestCivoChangesEmpty (line 1027) | func TestCivoChangesEmpty(t *testing.T) {
function elementsMatch (line 1178) | func elementsMatch(t *testing.T, listA, listB any) bool {
function isEmpty (line 1232) | func isEmpty(xs any) bool {
FILE: provider/cloudflare/cloudflare.go
type changeAction (line 47) | type changeAction
method String (line 75) | func (action changeAction) String() string {
constant cfAPIEmailEnvKey (line 51) | cfAPIEmailEnvKey = "CF_API_EMAIL"
constant cfAPIKeyEnvKey (line 52) | cfAPIKeyEnvKey = "CF_API_KEY"
constant cfAPITokenEnvKey (line 53) | cfAPITokenEnvKey = "CF_API_TOKEN"
constant cloudFlareCreate (line 56) | cloudFlareCreate changeAction = iota
constant cloudFlareDelete (line 58) | cloudFlareDelete
constant cloudFlareUpdate (line 60) | cloudFlareUpdate
constant defaultTTL (line 62) | defaultTTL = 1
constant freeZoneMaxCommentLength (line 65) | freeZoneMaxCommentLength = 100
constant paidZoneMaxCommentLength (line 66) | paidZoneMaxCommentLength = 500
type DNSRecordIndex (line 79) | type DNSRecordIndex struct
type DNSRecordsMap (line 85) | type DNSRecordsMap
type cloudFlareDNS (line 97) | type cloudFlareDNS interface
type zoneService (line 115) | type zoneService struct
method ZoneIDByName (line 119) | func (z zoneService) ZoneIDByName(zoneName string) (string, error) {
method CreateDNSRecord (line 139) | func (z zoneService) CreateDNSRecord(ctx context.Context, params dns.R...
method ListDNSRecords (line 143) | func (z zoneService) ListDNSRecords(ctx context.Context, params dns.Re...
method UpdateDNSRecord (line 147) | func (z zoneService) UpdateDNSRecord(ctx context.Context, recordID str...
method DeleteDNSRecord (line 151) | func (z zoneService) DeleteDNSRecord(ctx context.Context, recordID str...
method ListZones (line 156) | func (z zoneService) ListZones(ctx context.Context, params zones.ZoneL...
method GetZone (line 160) | func (z zoneService) GetZone(ctx context.Context, zoneID string) (*zon...
function listZonesV4Params (line 165) | func listZonesV4Params() zones.ZoneListParams {
type DNSRecordsConfig (line 169) | type DNSRecordsConfig struct
method trimAndValidateComment (line 176) | func (c *DNSRecordsConfig) trimAndValidateComment(dnsName, comment str...
type CloudFlareProvider (line 216) | type CloudFlareProvider struct
method ZoneHasPaidPlan (line 194) | func (p *CloudFlareProvider) ZoneHasPaidPlan(hostname string) bool {
method Zones (line 355) | func (p *CloudFlareProvider) Zones(ctx context.Context) ([]zones.Zone,...
method Records (line 401) | func (p *CloudFlareProvider) Records(ctx context.Context) ([]*endpoint...
method ApplyChanges (line 436) | func (p *CloudFlareProvider) ApplyChanges(ctx context.Context, changes...
method submitChanges (line 515) | func (p *CloudFlareProvider) submitChanges(ctx context.Context, change...
method AdjustEndpoints (line 635) | func (p *CloudFlareProvider) AdjustEndpoints(endpoints []*endpoint.End...
method changesByZone (line 674) | func (p *CloudFlareProvider) changesByZone(zones []zones.Zone, changeS...
method getRecordID (line 695) | func (p *CloudFlareProvider) getRecordID(records DNSRecordsMap, record...
method newCloudFlareChange (line 702) | func (p *CloudFlareProvider) newCloudFlareChange(action changeAction, ...
method getDNSRecordsMap (line 771) | func (p *CloudFlareProvider) getDNSRecordsMap(ctx context.Context, zon...
method groupByNameAndTypeWithCustomHostnames (line 819) | func (p *CloudFlareProvider) groupByNameAndTypeWithCustomHostnames(rec...
method SupportedAdditionalRecordTypes (line 891) | func (p *CloudFlareProvider) SupportedAdditionalRecordTypes(recordType...
type cloudFlareChange (line 230) | type cloudFlareChange struct
function convertCloudflareError (line 238) | func convertCloudflareError(err error) error {
function newProvider (line 276) | func newProvider(
function New (line 330) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function parseTagsAnnotation (line 621) | func parseTagsAnnotation(tagString string) []string {
function newDNSRecordIndex (line 766) | func newDNSRecordIndex(r dns.RecordResponse) DNSRecordIndex {
function shouldBeProxied (line 788) | func shouldBeProxied(ep *endpoint.Endpoint, proxiedByDefault bool) bool {
function getEndpointCustomHostnames (line 809) | func getEndpointCustomHostnames(ep *endpoint.Endpoint) []string {
FILE: provider/cloudflare/cloudflare_batch.go
constant defaultBatchChangeSize (line 31) | defaultBatchChangeSize = 200
type batchCollections (line 39) | type batchCollections struct
type batchChunk (line 59) | type batchChunk struct
method BatchDNSRecords (line 67) | func (z zoneService) BatchDNSRecords(ctx context.Context, params dns.Rec...
function getUpdateDNSRecordParam (line 72) | func getUpdateDNSRecordParam(zoneID string, cfc cloudFlareChange) dns.Re...
function getCreateDNSRecordParam (line 89) | func getCreateDNSRecordParam(zoneID string, cfc *cloudFlareChange) dns.R...
function chunkBatchChanges (line 110) | func chunkBatchChanges(zoneID string, bc batchCollections, limit int) []...
function tagsFromResponse (line 152) | func tagsFromResponse(tags any) []dns.RecordTagsParam {
function buildBatchPostParam (line 160) | func buildBatchPostParam(r dns.RecordResponse) dns.RecordBatchParamsPost {
function buildBatchPutParam (line 176) | func buildBatchPutParam(id string, r dns.RecordResponse) (dns.BatchPutUn...
method buildBatchCollections (line 269) | func (p *CloudFlareProvider) buildBatchCollections(
method submitDNSRecordChanges (line 323) | func (p *CloudFlareProvider) submitDNSRecordChanges(
method fallbackIndividualChanges (line 382) | func (p *CloudFlareProvider) fallbackIndividualChanges(
FILE: provider/cloudflare/cloudflare_batch_test.go
method BatchDNSRecords (line 36) | func (m *mockCloudFlareClient) BatchDNSRecords(_ context.Context, params...
function extractBatchPutData (line 138) | func extractBatchPutData(put dns.BatchPutUnionParam) (string, dns.Record...
function generateDNSRecordID (line 201) | func generateDNSRecordID(rrtype string, name string, content string) str...
function TestBatchFallbackIndividual (line 205) | func TestBatchFallbackIndividual(t *testing.T) {
function TestChunkBatchChanges (line 291) | func TestChunkBatchChanges(t *testing.T) {
function TestTagsFromResponse (line 412) | func TestTagsFromResponse(t *testing.T) {
function TestBuildBatchPutParam (line 425) | func TestBuildBatchPutParam(t *testing.T) {
function TestBuildBatchCollections_EdgeCases (line 516) | func TestBuildBatchCollections_EdgeCases(t *testing.T) {
function TestSubmitDNSRecordChanges_BatchInterval (line 577) | func TestSubmitDNSRecordChanges_BatchInterval(t *testing.T) {
function TestSubmitDNSRecordChanges_FallbackUpdates (line 616) | func TestSubmitDNSRecordChanges_FallbackUpdates(t *testing.T) {
function TestFallbackIndividualChanges_MissingRecord (line 669) | func TestFallbackIndividualChanges_MissingRecord(t *testing.T) {
FILE: provider/cloudflare/cloudflare_custom_hostnames.go
type customHostname (line 35) | type customHostname struct
type customHostnameSSL (line 44) | type customHostnameSSL struct
type customHostnameSSLSettings (line 53) | type customHostnameSSLSettings struct
type customHostnameIndex (line 58) | type customHostnameIndex struct
type customHostnamesMap (line 62) | type customHostnamesMap
type CustomHostnamesConfig (line 64) | type CustomHostnamesConfig struct
method CustomHostnames (line 75) | func (z zoneService) CustomHostnames(ctx context.Context, zoneID string)...
method DeleteCustomHostname (line 82) | func (z zoneService) DeleteCustomHostname(ctx context.Context, customHos...
method CreateCustomHostname (line 87) | func (z zoneService) CreateCustomHostname(ctx context.Context, zoneID st...
function buildCustomHostnameNewParams (line 95) | func buildCustomHostnameNewParams(zoneID string, ch customHostname) cust...
method submitCustomHostnameChanges (line 125) | func (p *CloudFlareProvider) submitCustomHostnameChanges(ctx context.Con...
method processCustomHostnameUpdate (line 143) | func (p *CloudFlareProvider) processCustomHostnameUpdate(ctx context.Con...
method processCustomHostnameDelete (line 175) | func (p *CloudFlareProvider) processCustomHostnameDelete(ctx context.Con...
method processCustomHostnameCreate (line 196) | func (p *CloudFlareProvider) processCustomHostnameCreate(ctx context.Con...
function getCustomHostname (line 220) | func getCustomHostname(chs customHostnamesMap, chName string) (customHos...
method newCustomHostname (line 230) | func (p *CloudFlareProvider) newCustomHostname(hostname string, origin s...
function getCustomHostnamesSSLOptions (line 238) | func getCustomHostnamesSSLOptions(customHostnamesConfig CustomHostnamesC...
function newCustomHostnameIndex (line 255) | func newCustomHostnameIndex(ch customHostname) customHostnameIndex {
method listCustomHostnamesWithPagination (line 260) | func (p *CloudFlareProvider) listCustomHostnamesWithPagination(ctx conte...
method processCustomHostnameChanges (line 282) | func (p *CloudFlareProvider) processCustomHostnameChanges(
function listAllCustomHostnames (line 305) | func listAllCustomHostnames(iter autoPager[custom_hostnames.CustomHostna...
FILE: provider/cloudflare/cloudflare_custom_hostnames_test.go
method CustomHostnames (line 37) | func (m *mockCloudFlareClient) CustomHostnames(ctx context.Context, zone...
method CreateCustomHostname (line 66) | func (m *mockCloudFlareClient) CreateCustomHostname(_ context.Context, z...
method DeleteCustomHostname (line 79) | func (m *mockCloudFlareClient) DeleteCustomHostname(_ context.Context, c...
function getCustomHostnameIdxByID (line 94) | func getCustomHostnameIdxByID(chs []customHostname, customHostnameID str...
function TestCloudflareCustomHostnameOperations (line 103) | func TestCloudflareCustomHostnameOperations(t *testing.T) {
function TestCloudflareDisabledCustomHostnameOperations (line 159) | func TestCloudflareDisabledCustomHostnameOperations(t *testing.T) {
function TestCloudflareCustomHostnameNotFoundOnRecordDeletion (line 281) | func TestCloudflareCustomHostnameNotFoundOnRecordDeletion(t *testing.T) {
function TestCloudflareListCustomHostnamesWithPagionation (line 411) | func TestCloudflareListCustomHostnamesWithPagionation(t *testing.T) {
function TestBuildCustomHostnameNewParams (line 470) | func TestBuildCustomHostnameNewParams(t *testing.T) {
function TestSubmitCustomHostnameChanges (line 592) | func TestSubmitCustomHostnameChanges(t *testing.T) {
FILE: provider/cloudflare/cloudflare_regional.go
type RegionalServicesConfig (line 34) | type RegionalServicesConfig struct
type regionalHostname (line 45) | type regionalHostname struct
type regionalHostnamesMap (line 51) | type regionalHostnamesMap
type regionalHostnameChange (line 53) | type regionalHostnameChange struct
method ListDataLocalizationRegionalHostnames (line 58) | func (z zoneService) ListDataLocalizationRegionalHostnames(ctx context.C...
method CreateDataLocalizationRegionalHostname (line 62) | func (z zoneService) CreateDataLocalizationRegionalHostname(ctx context....
method UpdateDataLocalizationRegionalHostname (line 67) | func (z zoneService) UpdateDataLocalizationRegionalHostname(ctx context....
method DeleteDataLocalizationRegionalHostname (line 72) | func (z zoneService) DeleteDataLocalizationRegionalHostname(ctx context....
function listDataLocalizationRegionalHostnamesParams (line 78) | func listDataLocalizationRegionalHostnamesParams(zoneID string) addressi...
function createDataLocalizationRegionalHostnameParams (line 85) | func createDataLocalizationRegionalHostnameParams(zoneID string, rhc reg...
function updateDataLocalizationRegionalHostnameParams (line 94) | func updateDataLocalizationRegionalHostnameParams(zoneID string, rhc reg...
function deleteDataLocalizationRegionalHostnameParams (line 102) | func deleteDataLocalizationRegionalHostnameParams(zoneID string) address...
method submitRegionalHostnameChanges (line 109) | func (p *CloudFlareProvider) submitRegionalHostnameChanges(ctx context.C...
method submitRegionalHostnameChange (line 122) | func (p *CloudFlareProvider) submitRegionalHostnameChange(ctx context.Co...
method listDataLocalisationRegionalHostnames (line 162) | func (p *CloudFlareProvider) listDataLocalisationRegionalHostnames(ctx c...
method regionalHostname (line 183) | func (p *CloudFlareProvider) regionalHostname(ep *endpoint.Endpoint) reg...
method addEnpointsProviderSpecificRegionKeyProperty (line 202) | func (p *CloudFlareProvider) addEnpointsProviderSpecificRegionKeyPropert...
method adjustEndpointProviderSpecificRegionKeyProperty (line 242) | func (p *CloudFlareProvider) adjustEndpointProviderSpecificRegionKeyProp...
function desiredRegionalHostnames (line 258) | func desiredRegionalHostnames(changes []*cloudFlareChange) ([]regionalHo...
function regionalHostnamesChanges (line 293) | func regionalHostnamesChanges(desired []regionalHostname, regionalHostna...
FILE: provider/cloudflare/cloudflare_regional_test.go
method ListDataLocalizationRegionalHostnames (line 40) | func (m *mockCloudFlareClient) ListDataLocalizationRegionalHostnames(_ c...
method CreateDataLocalizationRegionalHostname (line 57) | func (m *mockCloudFlareClient) CreateDataLocalizationRegionalHostname(_ ...
method UpdateDataLocalizationRegionalHostname (line 74) | func (m *mockCloudFlareClient) UpdateDataLocalizationRegionalHostname(_ ...
method DeleteDataLocalizationRegionalHostname (line 91) | func (m *mockCloudFlareClient) DeleteDataLocalizationRegionalHostname(_ ...
function TestCloudflareRegionalHostnameActions (line 106) | func TestCloudflareRegionalHostnameActions(t *testing.T) {
function TestCloudflareRegionalHostnameDefaults (line 301) | func TestCloudflareRegionalHostnameDefaults(t *testing.T) {
function Test_regionalHostname (line 350) | func Test_regionalHostname(t *testing.T) {
function Test_desiredDataLocalizationRegionalHostnames (line 492) | func Test_desiredDataLocalizationRegionalHostnames(t *testing.T) {
function Test_dataLocalizationRegionalHostnamesChanges (line 745) | func Test_dataLocalizationRegionalHostnamesChanges(t *testing.T) {
function TestRecordsWithListRegionalHostnameFaillure (line 830) | func TestRecordsWithListRegionalHostnameFaillure(t *testing.T) {
function TestApplyChangesWithRegionalHostnamesFaillures (line 847) | func TestApplyChangesWithRegionalHostnamesFaillures(t *testing.T) {
function TestApplyChangesWithRegionalHostnamesDryRun (line 1047) | func TestApplyChangesWithRegionalHostnamesDryRun(t *testing.T) {
function TestCloudflareAdjustEndpointsRegionalServices (line 1193) | func TestCloudflareAdjustEndpointsRegionalServices(t *testing.T) {
function TestSubmitChanges_DryRun_RegionalErrors (line 1308) | func TestSubmitChanges_DryRun_RegionalErrors(t *testing.T) {
FILE: provider/cloudflare/cloudflare_test.go
function newCloudflareError (line 52) | func newCloudflareError(statusCode int) *cloudflare.Error {
type MockAction (line 94) | type MockAction struct
type mockCloudFlareClient (line 102) | type mockCloudFlareClient struct
method CreateDNSRecord (line 144) | func (m *mockCloudFlareClient) CreateDNSRecord(_ context.Context, para...
method ListDNSRecords (line 173) | func (m *mockCloudFlareClient) ListDNSRecords(ctx context.Context, par...
method UpdateDNSRecord (line 192) | func (m *mockCloudFlareClient) UpdateDNSRecord(_ context.Context, reco...
method DeleteDNSRecord (line 223) | func (m *mockCloudFlareClient) DeleteDNSRecord(_ context.Context, reco...
method ZoneIDByName (line 243) | func (m *mockCloudFlareClient) ZoneIDByName(zoneName string) (string, ...
method ListZones (line 259) | func (m *mockCloudFlareClient) ListZones(_ context.Context, _ zones.Zo...
method GetZone (line 281) | func (m *mockCloudFlareClient) GetZone(_ context.Context, zoneID strin...
function NewMockCloudFlareClient (line 115) | func NewMockCloudFlareClient() *mockCloudFlareClient {
function NewMockCloudFlareClientWithRecords (line 130) | func NewMockCloudFlareClientWithRecords(records map[string][]dns.RecordR...
function AssertActions (line 299) | func AssertActions(t *testing.T, provider *CloudFlareProvider, endpoints...
function TestCloudflareA (line 345) | func TestCloudflareA(t *testing.T) {
function TestCloudflareCname (line 386) | func TestCloudflareCname(t *testing.T) {
function TestCloudflareMx (line 427) | func TestCloudflareMx(t *testing.T) {
function TestCloudflareTxt (line 470) | func TestCloudflareTxt(t *testing.T) {
function TestCloudflareCustomTTL (line 498) | func TestCloudflareCustomTTL(t *testing.T) {
function TestCloudflareProxiedDefault (line 527) | func TestCloudflareProxiedDefault(t *testing.T) {
function TestCloudflareProxiedOverrideTrue (line 555) | func TestCloudflareProxiedOverrideTrue(t *testing.T) {
function TestCloudflareProxiedOverrideFalse (line 589) | func TestCloudflareProxiedOverrideFalse(t *testing.T) {
function TestCloudflareProxiedOverrideIllegal (line 623) | func TestCloudflareProxiedOverrideIllegal(t *testing.T) {
function TestCloudflareSetProxied (line 657) | func TestCloudflareSetProxied(t *testing.T) {
function TestCloudflareZones (line 726) | func TestCloudflareZones(t *testing.T) {
function TestCloudflareZonesFailed (line 743) | func TestCloudflareZonesFailed(t *testing.T) {
function TestCloudFlareZonesWithIDFilter (line 760) | func TestCloudFlareZonesWithIDFilter(t *testing.T) {
function TestCloudflareListZonesRateLimited (line 779) | func TestCloudflareListZonesRateLimited(t *testing.T) {
function TestCloudflareListZonesRateLimitedStringError (line 794) | func TestCloudflareListZonesRateLimitedStringError(t *testing.T) {
function TestCloudflareListZoneInternalErrors (line 807) | func TestCloudflareListZoneInternalErrors(t *testing.T) {
function TestCloudflareRecords (line 823) | func TestCloudflareRecords(t *testing.T) {
function TestGetDNSRecordsMapWithPerPage (line 867) | func TestGetDNSRecordsMapWithPerPage(t *testing.T) {
function TestCloudflareProvider (line 896) | func TestCloudflareProvider(t *testing.T) {
function TestCloudflareApplyChanges (line 990) | func TestCloudflareApplyChanges(t *testing.T) {
function TestCloudflareDryRunApplyChanges (line 1059) | func TestCloudflareDryRunApplyChanges(t *testing.T) {
function TestCloudflareApplyChangesError (line 1083) | func TestCloudflareApplyChangesError(t *testing.T) {
function TestCloudflareGetRecordID (line 1099) | func TestCloudflareGetRecordID(t *testing.T) {
function TestCloudflareGroupByNameAndTypeWithCustomHostnames (line 1150) | func TestCloudflareGroupByNameAndTypeWithCustomHostnames(t *testing.T) {
function TestGroupByNameAndTypeWithCustomHostnames_MX (line 1404) | func TestGroupByNameAndTypeWithCustomHostnames_MX(t *testing.T) {
function TestProviderPropertiesIdempotency (line 1443) | func TestProviderPropertiesIdempotency(t *testing.T) {
function TestCloudflareComplexUpdate (line 1635) | func TestCloudflareComplexUpdate(t *testing.T) {
function TestCustomTTLWithEnabledProxyNotChanged (line 1716) | func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) {
function TestCloudFlareProvider_Region (line 1773) | func TestCloudFlareProvider_Region(t *testing.T) {
function TestCloudFlareProvider_newCloudFlareChange (line 1791) | func TestCloudFlareProvider_newCloudFlareChange(t *testing.T) {
function TestCloudFlareProvider_submitChangesCNAME (line 1907) | func TestCloudFlareProvider_submitChangesCNAME(t *testing.T) {
function TestCloudFlareProvider_submitChangesApex (line 1967) | func TestCloudFlareProvider_submitChangesApex(t *testing.T) {
function TestCloudflareZoneRecordsFail (line 2030) | func TestCloudflareZoneRecordsFail(t *testing.T) {
function TestCloudflareLongRecordsErrorLog (line 2052) | func TestCloudflareLongRecordsErrorLog(t *testing.T) {
function checkFailed (line 2078) | func checkFailed(name string, err error, shouldFail bool) error {
function TestCloudflareDNSRecordsOperationsFail (line 2088) | func TestCloudflareDNSRecordsOperationsFail(t *testing.T) {
function TestZoneHasPaidPlan (line 2206) | func TestZoneHasPaidPlan(t *testing.T) {
function TestCloudflareApplyChanges_AllErrorLogPaths (line 2227) | func TestCloudflareApplyChanges_AllErrorLogPaths(t *testing.T) {
function TestCloudFlareProvider_SupportedAdditionalRecordTypes (line 2372) | func TestCloudFlareProvider_SupportedAdditionalRecordTypes(t *testing.T) {
function TestCloudflareZoneChanges (line 2398) | func TestCloudflareZoneChanges(t *testing.T) {
function TestCloudflareZoneErrors (line 2458) | func TestCloudflareZoneErrors(t *testing.T) {
function TestCloudflareZoneFiltering (line 2482) | func TestCloudflareZoneFiltering(t *testing.T) {
function TestCloudflareZonePlanDetection (line 2511) | func TestCloudflareZonePlanDetection(t *testing.T) {
function TestCloudflareChangesByZone (line 2542) | func TestCloudflareChangesByZone(t *testing.T) {
function TestConvertCloudflareError (line 2597) | func TestConvertCloudflareError(t *testing.T) {
function TestConvertCloudflareErrorInContext (line 2718) | func TestConvertCloudflareErrorInContext(t *testing.T) {
function TestCloudFlareZonesDomainFilter (line 2817) | func TestCloudFlareZonesDomainFilter(t *testing.T) {
function TestZoneIDByNameIteratorError (line 2844) | func TestZoneIDByNameIteratorError(t *testing.T) {
function TestZoneIDByNameZoneNotFound (line 2860) | func TestZoneIDByNameZoneNotFound(t *testing.T) {
function TestGetUpdateDNSRecordParam (line 2879) | func TestGetUpdateDNSRecordParam(t *testing.T) {
function TestZoneService (line 2906) | func TestZoneService(t *testing.T) {
function TestSubmitChanges_ErrorPaths (line 3013) | func TestSubmitChanges_ErrorPaths(t *testing.T) {
function TestParseTagsAnnotation (line 3105) | func TestParseTagsAnnotation(t *testing.T) {
function TestAdjustEndpoints_TagsAnnotation (line 3124) | func TestAdjustEndpoints_TagsAnnotation(t *testing.T) {
function TestZoneServiceZoneIDByName (line 3149) | func TestZoneServiceZoneIDByName(t *testing.T) {
FILE: provider/cloudflare/pagination.go
type autoPager (line 19) | type autoPager interface
function autoPagerIterator (line 26) | func autoPagerIterator[T any](iter autoPager[T]) func(yield func(T) bool) {
FILE: provider/cloudflare/pagination_test.go
type mockAutoPager (line 27) | type mockAutoPager struct
method Next (line 34) | func (m *mockAutoPager[T]) Next() bool {
method Current (line 39) | func (m *mockAutoPager[T]) Current() T {
method Err (line 47) | func (m *mockAutoPager[T]) Err() error {
method hasError (line 51) | func (m *mockAutoPager[T]) hasError() bool {
method hasNext (line 55) | func (m *mockAutoPager[T]) hasNext() bool {
function TestAutoPagerIterator (line 59) | func TestAutoPagerIterator(t *testing.T) {
FILE: provider/coredns/coredns.go
constant priority (line 45) | priority = 10
constant etcdTimeout (line 46) | etcdTimeout = 5 * time.Second
constant randomPrefixLabel (line 48) | randomPrefixLabel = "prefix"
constant providerSpecificGroup (line 49) | providerSpecificGroup = "coredns/group"
type coreDNSClient (line 58) | type coreDNSClient interface
type coreDNSProvider (line 64) | type coreDNSProvider struct
method Records (line 306) | func (p coreDNSProvider) Records(ctx context.Context) ([]*endpoint.End...
method ApplyChanges (line 362) | func (p coreDNSProvider) ApplyChanges(ctx context.Context, changes *pl...
method groupEndpoints (line 378) | func (p coreDNSProvider) groupEndpoints(changes *plan.Changes) map[str...
method applyGroup (line 391) | func (p coreDNSProvider) applyGroup(ctx context.Context, dnsName strin...
method createServicesForEndpoint (line 419) | func (p coreDNSProvider) createServicesForEndpoint(ctx context.Context...
method updateTXTRecords (line 464) | func (p coreDNSProvider) updateTXTRecords(dnsName string, group []*end...
method deleteEndpoints (line 491) | func (p coreDNSProvider) deleteEndpoints(ctx context.Context, endpoint...
method etcdKeyFor (line 509) | func (p coreDNSProvider) etcdKeyFor(dnsName string) string {
type Service (line 74) | type Service struct
type etcdClient (line 102) | type etcdClient struct
method GetServices (line 111) | func (c etcdClient) GetServices(ctx context.Context, prefix string) ([...
method SaveService (line 156) | func (c etcdClient) SaveService(ctx context.Context, service *Service)...
method DeleteService (line 191) | func (c etcdClient) DeleteService(ctx context.Context, key string) err...
method unmarshalService (line 221) | func (c etcdClient) unmarshalService(n *mvccpb.KeyValue) (*Service, er...
function getETCDConfig (line 230) | func getETCDConfig() (*etcdcv3.Config, error) {
function newETCDClient (line 260) | func newETCDClient(owner string, strictlyOwned bool) (coreDNSClient, err...
function New (line 273) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 278) | func newProvider(domainFilter *endpoint.DomainFilter, prefix, owner stri...
function findEp (line 295) | func findEp(slice []*endpoint.Endpoint, dnsName string) (*endpoint.Endpo...
function guessRecordType (line 515) | func guessRecordType(target string) string {
function reverse (line 522) | func reverse(slice []string) {
FILE: provider/coredns/coredns_test.go
constant defaultCoreDNSPrefix (line 41) | defaultCoreDNSPrefix = "/skydns/"
type fakeETCDClient (line 43) | type fakeETCDClient struct
method GetServices (line 47) | func (c fakeETCDClient) GetServices(_ context.Context, prefix string) ...
method SaveService (line 59) | func (c fakeETCDClient) SaveService(_ context.Context, service *Servic...
method DeleteService (line 64) | func (c fakeETCDClient) DeleteService(_ context.Context, key string) e...
type MockEtcdKV (line 69) | type MockEtcdKV struct
method Put (line 74) | func (m *MockEtcdKV) Put(ctx context.Context, key, input string, _ ......
method Get (line 79) | func (m *MockEtcdKV) Get(ctx context.Context, key string, opts ...etcd...
method Delete (line 89) | func (m *MockEtcdKV) Delete(ctx context.Context, key string, opts ...e...
function TestETCDConfig (line 99) | func TestETCDConfig(t *testing.T) {
function TestEtcdHttpsProtocol (line 137) | func TestEtcdHttpsProtocol(t *testing.T) {
function TestEtcdHttpsIncorrectConfigError (line 148) | func TestEtcdHttpsIncorrectConfigError(t *testing.T) {
function TestEtcdUnsupportedProtocolError (line 159) | func TestEtcdUnsupportedProtocolError(t *testing.T) {
function TestAServiceTranslation (line 169) | func TestAServiceTranslation(t *testing.T) {
function TestCNAMEServiceTranslation (line 199) | func TestCNAMEServiceTranslation(t *testing.T) {
function TestTXTServiceTranslation (line 229) | func TestTXTServiceTranslation(t *testing.T) {
function TestAWithTXTServiceTranslation (line 259) | func TestAWithTXTServiceTranslation(t *testing.T) {
function TestCNAMEWithTXTServiceTranslation (line 299) | func TestCNAMEWithTXTServiceTranslation(t *testing.T) {
function TestCoreDNSApplyChanges (line 339) | func TestCoreDNSApplyChanges(t *testing.T) {
function TestCoreDNSApplyChanges_DomainDoNotMatch (line 422) | func TestCoreDNSApplyChanges_DomainDoNotMatch(t *testing.T) {
function applyServiceChanges (line 447) | func applyServiceChanges(provider coreDNSProvider, changes *plan.Changes...
function validateServices (line 462) | func validateServices(services map[string]Service, expectedServices map[...
function mergeLabels (line 495) | func mergeLabels(e *endpoint.Endpoint, labels map[string]string) {
function TestGetServices_Success (line 503) | func TestGetServices_Success(t *testing.T) {
function TestGetServices_Duplicate (line 530) | func TestGetServices_Duplicate(t *testing.T) {
function TestGetServices_Multiple (line 561) | func TestGetServices_Multiple(t *testing.T) {
function TestGetServices_FilterOutOtherServicesOwnerSetButNothingChanged (line 596) | func TestGetServices_FilterOutOtherServicesOwnerSetButNothingChanged(t *...
function TestGetServices_FilterOutOtherServicesWithStrictlyOwned (line 639) | func TestGetServices_FilterOutOtherServicesWithStrictlyOwned(t *testing....
function TestGetServices_UnmarshalError (line 683) | func TestGetServices_UnmarshalError(t *testing.T) {
function TestGetServices_GetError (line 710) | func TestGetServices_GetError(t *testing.T) {
function TestDeleteService (line 726) | func TestDeleteService(t *testing.T) {
function TestDeleteServiceWithStrictlyOwned (line 770) | func TestDeleteServiceWithStrictlyOwned(t *testing.T) {
function TestSaveService (line 904) | func TestSaveService(t *testing.T) {
function TestNewProvider (line 1165) | func TestNewProvider(t *testing.T) {
function TestFindEp (line 1204) | func TestFindEp(t *testing.T) {
function TestCoreDNSProvider_updateTXTRecords_WithEdpoints (line 1253) | func TestCoreDNSProvider_updateTXTRecords_WithEdpoints(t *testing.T) {
function TestCoreDNSProvider_updateTXTRecords_ClearsExtraText (line 1278) | func TestCoreDNSProvider_updateTXTRecords_ClearsExtraText(t *testing.T) {
function TestApplyChangesAWithGroupServiceTranslation (line 1303) | func TestApplyChangesAWithGroupServiceTranslation(t *testing.T) {
function TestRecordsAWithGroupServiceTranslation (line 1329) | func TestRecordsAWithGroupServiceTranslation(t *testing.T) {
function TestRecordsIncludeLabelOwnerWithStrictlyOwned (line 1348) | func TestRecordsIncludeLabelOwnerWithStrictlyOwned(t *testing.T) {
function TestRecordsIncludeOwnerASLabelWithoutStrictlyOwned (line 1367) | func TestRecordsIncludeOwnerASLabelWithoutStrictlyOwned(t *testing.T) {
FILE: provider/dnsimple/dnsimple.go
constant dnsimpleCreate (line 37) | dnsimpleCreate = "CREATE"
constant dnsimpleDelete (line 38) | dnsimpleDelete = "DELETE"
constant dnsimpleUpdate (line 39) | dnsimpleUpdate = "UPDATE"
constant defaultTTL (line 41) | defaultTTL = 3600
type dnsimpleIdentityService (line 44) | type dnsimpleIdentityService struct
method Whoami (line 48) | func (i dnsimpleIdentityService) Whoami(ctx context.Context) (*dnsimpl...
type dnsimpleZoneServiceInterface (line 53) | type dnsimpleZoneServiceInterface interface
type dnsimpleZoneService (line 61) | type dnsimpleZoneService struct
method ListZones (line 65) | func (z dnsimpleZoneService) ListZones(ctx context.Context, accountID ...
method ListRecords (line 69) | func (z dnsimpleZoneService) ListRecords(ctx context.Context, accountI...
method CreateRecord (line 73) | func (z dnsimpleZoneService) CreateRecord(ctx context.Context, account...
method DeleteRecord (line 77) | func (z dnsimpleZoneService) DeleteRecord(ctx context.Context, account...
method UpdateRecord (line 81) | func (z dnsimpleZoneService) UpdateRecord(ctx context.Context, account...
type dnsimpleProvider (line 85) | type dnsimpleProvider struct
method GetAccountID (line 138) | func (p *dnsimpleProvider) GetAccountID(ctx context.Context) (string, ...
method Zones (line 158) | func (p *dnsimpleProvider) Zones(ctx context.Context) (map[string]dnsi...
method Records (line 199) | func (p *dnsimpleProvider) Records(ctx context.Context) ([]*endpoint.E...
method submitChanges (line 264) | func (p *dnsimpleProvider) submitChanges(ctx context.Context, changes ...
method GetRecordID (line 327) | func (p *dnsimpleProvider) GetRecordID(ctx context.Context, zone strin...
method ApplyChanges (line 366) | func (p *dnsimpleProvider) ApplyChanges(ctx context.Context, changes *...
type dnsimpleChange (line 95) | type dnsimpleChange struct
function New (line 101) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 106) | func newProvider(domainFilter *endpoint.DomainFilter, zoneIDFilter provi...
function ZonesFromZoneString (line 147) | func ZonesFromZoneString(zonestring string) map[string]dnsimple.Zone {
function newDnsimpleChange (line 236) | func newDnsimpleChange(action string, e *endpoint.Endpoint) *dnsimpleCha...
function newDnsimpleChanges (line 255) | func newDnsimpleChanges(action string, endpoints []*endpoint.Endpoint) [...
function dnsimpleSuitableZone (line 352) | func dnsimpleSuitableZone(hostname string, zones map[string]dnsimple.Zon...
function int64ToString (line 376) | func int64ToString(i int64) string {
FILE: provider/dnsimple/dnsimple_test.go
function TestDnsimpleServices (line 42) | func TestDnsimpleServices(t *testing.T) {
function testDnsimpleProviderZones (line 155) | func testDnsimpleProviderZones(t *testing.T) {
function testDnsimpleProviderRecords (line 176) | func testDnsimpleProviderRecords(t *testing.T) {
function testDnsimpleProviderApplyChanges (line 188) | func testDnsimpleProviderApplyChanges(t *testing.T) {
function testDnsimpleProviderApplyChangesSkipsUnknown (line 209) | func testDnsimpleProviderApplyChangesSkipsUnknown(t *testing.T) {
function testDnsimpleSuitableZone (line 222) | func testDnsimpleSuitableZone(t *testing.T) {
function TestNewProvider (line 243) | func TestNewProvider(t *testing.T) {
function testDnsimpleGetRecordID (line 268) | func testDnsimpleGetRecordID(t *testing.T) {
function validateDnsimpleZones (line 282) | func validateDnsimpleZones(t *testing.T, zones map[string]dnsimple.Zone,...
type mockDnsimpleZoneServiceInterface (line 290) | type mockDnsimpleZoneServiceInterface struct
method CreateRecord (line 294) | func (_m *mockDnsimpleZoneServiceInterface) CreateRecord(ctx context.C...
method DeleteRecord (line 305) | func (_m *mockDnsimpleZoneServiceInterface) DeleteRecord(ctx context.C...
method ListRecords (line 316) | func (_m *mockDnsimpleZoneServiceInterface) ListRecords(ctx context.Co...
method ListZones (line 327) | func (_m *mockDnsimpleZoneServiceInterface) ListZones(ctx context.Cont...
method UpdateRecord (line 338) | func (_m *mockDnsimpleZoneServiceInterface) UpdateRecord(ctx context.C...
FILE: provider/exoscale/exoscale.go
type EgoscaleClientI (line 34) | type EgoscaleClientI interface
type ExoscaleProvider (line 43) | type ExoscaleProvider struct
method getZones (line 100) | func (ep *ExoscaleProvider) getZones(ctx context.Context) (map[string]...
method ApplyChanges (line 116) | func (ep *ExoscaleProvider) ApplyChanges(ctx context.Context, changes ...
method Records (line 236) | func (ep *ExoscaleProvider) Records(ctx context.Context) ([]*endpoint....
type ExoscaleOption (line 55) | type ExoscaleOption
function New (line 58) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 71) | func newProvider(env, zone, key, secret string, dryRun bool, opts ...Exo...
function NewExoscaleProviderWithClient (line 84) | func NewExoscaleProviderWithClient(client EgoscaleClientI, env, zone str...
function ExoscaleWithDomain (line 266) | func ExoscaleWithDomain(domainFilter *endpoint.DomainFilter) ExoscaleOpt...
function ExoscaleWithLogging (line 273) | func ExoscaleWithLogging() ExoscaleOption {
type zoneFilter (line 292) | type zoneFilter struct
method Zones (line 297) | func (f *zoneFilter) Zones(zones map[string]string) map[string]string {
method EndpointZoneID (line 309) | func (f *zoneFilter) EndpointZoneID(endpoint *endpoint.Endpoint, zones...
function merge (line 321) | func merge(updateOld, updateNew []*endpoint.Endpoint) []*endpoint.Endpoi...
FILE: provider/exoscale/exoscale_test.go
type createRecordExoscale (line 35) | type createRecordExoscale struct
type deleteRecordExoscale (line 40) | type deleteRecordExoscale struct
type updateRecordExoscale (line 45) | type updateRecordExoscale struct
function strPtr (line 75) | func strPtr(s string) *string {
type ExoscaleClientStub (line 79) | type ExoscaleClientStub struct
method ListDNSDomains (line 86) | func (ep *ExoscaleClientStub) ListDNSDomains(_ context.Context, _ stri...
method ListDNSDomainRecords (line 94) | func (ep *ExoscaleClientStub) ListDNSDomainRecords(_ context.Context, ...
method CreateDNSDomainRecord (line 98) | func (ep *ExoscaleClientStub) CreateDNSDomainRecord(_ context.Context,...
method DeleteDNSDomainRecord (line 103) | func (ep *ExoscaleClientStub) DeleteDNSDomainRecord(_ context.Context,...
method UpdateDNSDomainRecord (line 108) | func (ep *ExoscaleClientStub) UpdateDNSDomainRecord(_ context.Context,...
function NewExoscaleClientStub (line 81) | func NewExoscaleClientStub() EgoscaleClientI {
function contains (line 113) | func contains(arr []*endpoint.Endpoint, name string) bool {
function TestExoscaleGetRecords (line 122) | func TestExoscaleGetRecords(t *testing.T) {
function TestExoscaleApplyChanges (line 138) | func TestExoscaleApplyChanges(t *testing.T) {
function TestExoscaleMerge_NoUpdateOnTTL0Changes (line 209) | func TestExoscaleMerge_NoUpdateOnTTL0Changes(t *testing.T) {
function TestExoscaleMerge_UpdateOnTTLChanges (line 243) | func TestExoscaleMerge_UpdateOnTTLChanges(t *testing.T) {
function TestExoscaleMerge_AlwaysUpdateTarget (line 279) | func TestExoscaleMerge_AlwaysUpdateTarget(t *testing.T) {
function TestExoscaleMerge_NoUpdateIfTTLUnchanged (line 315) | func TestExoscaleMerge_NoUpdateIfTTLUnchanged(t *testing.T) {
function TestZones (line 350) | func TestZones(t *testing.T) {
function TestExoscaleWithDomain_SetsDomain (line 431) | func TestExoscaleWithDomain_SetsDomain(t *testing.T) {
function TestInMemoryWithLogging_LogsChanges (line 453) | func TestInMemoryWithLogging_LogsChanges(t *testing.T) {
FILE: provider/factory/provider.go
type ProviderConstructor (line 54) | type ProviderConstructor
function Select (line 61) | func Select(
function providers (line 80) | func providers(selector string) (ProviderConstructor, bool) {
FILE: provider/factory/provider_test.go
function TestSelectProvider (line 33) | func TestSelectProvider(t *testing.T) {
function TestKnownProviders (line 144) | func TestKnownProviders(t *testing.T) {
function TestSelectProvider_Webhook (line 156) | func TestSelectProvider_Webhook(t *testing.T) {
FILE: provider/fakes/provider.go
type MockProvider (line 26) | type MockProvider struct
method Records (line 31) | func (m *MockProvider) Records(_ context.Context) ([]*endpoint.Endpoin...
method ApplyChanges (line 35) | func (m *MockProvider) ApplyChanges(_ context.Context, _ *plan.Changes...
method AdjustEndpoints (line 39) | func (m *MockProvider) AdjustEndpoints(eps []*endpoint.Endpoint) ([]*e...
method GetDomainFilter (line 43) | func (m *MockProvider) GetDomainFilter() endpoint.DomainFilterInterface {
FILE: provider/gandi/client.go
type DomainClientAdapter (line 21) | type DomainClientAdapter interface
type domainClient (line 25) | type domainClient struct
method ListDomains (line 29) | func (p *domainClient) ListDomains() ([]domain.ListResponse, error) {
function NewDomainClient (line 33) | func NewDomainClient(client *domain.Domain) DomainClientAdapter {
type standardResponse (line 38) | type standardResponse struct
type standardError (line 49) | type standardError struct
type LiveDNSClientAdapter (line 55) | type LiveDNSClientAdapter interface
type LiveDNSClient (line 62) | type LiveDNSClient struct
method GetDomainRecords (line 70) | func (p *LiveDNSClient) GetDomainRecords(fqdn string) ([]livedns.Domai...
method CreateDomainRecord (line 74) | func (p *LiveDNSClient) CreateDomainRecord(fqdn, name, recordtype stri...
method DeleteDomainRecord (line 96) | func (p *LiveDNSClient) DeleteDomainRecord(fqdn, name, recordtype stri...
method UpdateDomainRecordByNameAndType (line 100) | func (p *LiveDNSClient) UpdateDomainRecordByNameAndType(fqdn, name, re...
function NewLiveDNSClient (line 66) | func NewLiveDNSClient(client *livedns.LiveDNS) LiveDNSClientAdapter {
FILE: provider/gandi/gandi.go
constant gandiCreate (line 34) | gandiCreate = "CREATE"
constant gandiDelete (line 35) | gandiDelete = "DELETE"
constant gandiUpdate (line 36) | gandiUpdate = "UPDATE"
constant defaultTTL (line 37) | defaultTTL = 600
constant gandiLiveDNSProvider (line 38) | gandiLiveDNSProvider = "livedns"
type GandiChanges (line 41) | type GandiChanges struct
type GandiProvider (line 47) | type GandiProvider struct
method Zones (line 92) | func (p *GandiProvider) Zones() ([]string, error) {
method Records (line 114) | func (p *GandiProvider) Records(_ context.Context) ([]*endpoint.Endpoi...
method ApplyChanges (line 155) | func (p *GandiProvider) ApplyChanges(ctx context.Context, changes *pla...
method submitChanges (line 165) | func (p *GandiProvider) submitChanges(_ context.Context, changes []*Ga...
method newGandiChanges (line 262) | func (p *GandiProvider) newGandiChanges(action string, endpoints []*en...
method groupAndFilterByZone (line 283) | func (p *GandiProvider) groupAndFilterByZone(zones []string, changes [...
function newProvider (line 55) | func newProvider(domainFilter *endpoint.DomainFilter, dryRun bool) (*Gan...
function New (line 88) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
FILE: provider/gandi/gandi_test.go
type MockAction (line 31) | type MockAction struct
type mockGandiClient (line 37) | type mockGandiClient struct
method GetDomainRecords (line 51) | func (m *mockGandiClient) GetDomainRecords(fqdn string) ([]livedns.Dom...
method CreateDomainRecord (line 64) | func (m *mockGandiClient) CreateDomainRecord(fqdn, name, recordtype st...
method DeleteDomainRecord (line 83) | func (m *mockGandiClient) DeleteDomainRecord(fqdn, name, recordtype st...
method UpdateDomainRecordByNameAndType (line 100) | func (m *mockGandiClient) UpdateDomainRecordByNameAndType(fqdn, name, ...
method ListDomains (line 119) | func (m *mockGandiClient) ListDomains() ([]domain.ListResponse, error) {
constant domainUriPrefix (line 44) | domainUriPrefix = "https://api.gandi.net/v5/domain/domains/"
constant exampleDotComUri (line 45) | exampleDotComUri = domainUriPrefix + "example.com"
constant exampleDotNetUri (line 46) | exampleDotNetUri = domainUriPrefix + "example.net"
function TestNewProvider (line 156) | func TestNewProvider(t *testing.T) {
function TestGandiProvider_RecordsReturnsCorrectEndpoints (line 192) | func TestGandiProvider_RecordsReturnsCorrectEndpoints(t *testing.T) {
function TestGandiProvider_RecordsOnFilteredDomainsShouldYieldNoEndpoints (line 260) | func TestGandiProvider_RecordsOnFilteredDomainsShouldYieldNoEndpoints(t ...
function TestGandiProvider_RecordsWithUnsupportedTypesAreNotReturned (line 283) | func TestGandiProvider_RecordsWithUnsupportedTypesAreNotReturned(t *test...
function TestGandiProvider_ApplyChangesMakesExpectedAPICalls (line 305) | func TestGandiProvider_ApplyChangesMakesExpectedAPICalls(t *testing.T) {
function TestGandiProvider_ApplyChangesRespectsDryRun (line 393) | func TestGandiProvider_ApplyChangesRespectsDryRun(t *testing.T) {
function TestGandiProvider_ApplyChangesWithEmptyResultDoesNothing (line 415) | func TestGandiProvider_ApplyChangesWithEmptyResultDoesNothing(t *testing...
function TestGandiProvider_ApplyChangesWithUnknownDomainDoesNoUpdate (line 428) | func TestGandiProvider_ApplyChangesWithUnknownDomainDoesNoUpdate(t *test...
function TestGandiProvider_ApplyChangesConvertsApexDomain (line 454) | func TestGandiProvider_ApplyChangesConvertsApexDomain(t *testing.T) {
function TestGandiProvider_FailingCases (line 494) | func TestGandiProvider_FailingCases(t *testing.T) {
FILE: provider/godaddy/client.go
constant ErrCodeQuotaExceeded (line 38) | ErrCodeQuotaExceeded = "QUOTA_EXCEEDED"
constant DefaultTimeout (line 41) | DefaultTimeout = 180 * time.Second
type APIError (line 50) | type APIError struct
method Error (line 55) | func (err *APIError) Error() string {
type Logger (line 61) | type Logger interface
type Client (line 70) | type Client struct
method Get (line 153) | func (c *Client) Get(url string, resType any) error {
method Patch (line 158) | func (c *Client) Patch(url string, reqBody, resType any) error {
method Post (line 163) | func (c *Client) Post(url string, reqBody, resType any) error {
method Put (line 168) | func (c *Client) Put(url string, reqBody, resType any) error {
method Delete (line 173) | func (c *Client) Delete(url string, resType any) error {
method GetWithContext (line 178) | func (c *Client) GetWithContext(ctx context.Context, url string, resTy...
method PatchWithContext (line 183) | func (c *Client) PatchWithContext(ctx context.Context, url string, req...
method PostWithContext (line 188) | func (c *Client) PostWithContext(ctx context.Context, url string, reqB...
method PutWithContext (line 193) | func (c *Client) PutWithContext(ctx context.Context, url string, reqBo...
method DeleteWithContext (line 198) | func (c *Client) DeleteWithContext(ctx context.Context, url string, re...
method NewRequest (line 203) | func (c *Client) NewRequest(method, path string, reqBody any) (*http.R...
method Do (line 235) | func (c *Client) Do(req *http.Request) (*http.Response, error) {
method CallAPI (line 289) | func (c *Client) CallAPI(method, path string, reqBody, resType any) er...
method CallAPIWithContext (line 313) | func (c *Client) CallAPIWithContext(ctx context.Context, method, path ...
method UnmarshalResponse (line 328) | func (c *Client) UnmarshalResponse(response *http.Response, resType an...
method validate (line 357) | func (c *Client) validate() error {
type GDErrorField (line 93) | type GDErrorField struct
type GDErrorResponse (line 101) | type GDErrorResponse struct
method String (line 107) | func (r GDErrorResponse) String() string {
function NewClient (line 116) | func NewClient(useOTE bool, apiKey, apiSecret string) (*Client, error) {
FILE: provider/godaddy/client_test.go
function TestClient_DoWhenQuotaExceeded (line 31) | func TestClient_DoWhenQuotaExceeded(t *testing.T) {
FILE: provider/godaddy/godaddy.go
constant defaultTTL (line 36) | defaultTTL = 600
constant gdCreate (line 37) | gdCreate = 0
constant gdReplace (line 38) | gdReplace = 1
constant gdDelete (line 39) | gdDelete = 2
constant domainsURI (line 41) | domainsURI = "/v1/domains?statuses=ACTIVE,PENDING_DNS_ACTIVE"
type gdClient (line 50) | type gdClient interface
type GDProvider (line 59) | type GDProvider struct
method zones (line 160) | func (p *GDProvider) zones() ([]string, error) {
method zonesRecords (line 180) | func (p *GDProvider) zonesRecords(ctx context.Context, all bool) ([]st...
method records (line 230) | func (p *GDProvider) records(_ *context.Context, zone string, all bool...
method groupByNameAndType (line 264) | func (p *GDProvider) groupByNameAndType(zoneRecords []gdRecords) []*en...
method Records (line 318) | func (p *GDProvider) Records(ctx context.Context) ([]*endpoint.Endpoin...
method appendChange (line 331) | func (p *GDProvider) appendChange(action int, endpoints []*endpoint.En...
method changeAllRecords (line 342) | func (p *GDProvider) changeAllRecords(endpoints []gdEndpoint, zoneReco...
method ApplyChanges (line 379) | func (p *GDProvider) ApplyChanges(ctx context.Context, changes *plan.C...
type gdEndpoint (line 68) | type gdEndpoint struct
type gdRecordField (line 73) | type gdRecordField struct
method String (line 584) | func (c gdRecordField) String() string {
type gdReplaceRecordField (line 85) | type gdReplaceRecordField struct
type gdRecords (line 95) | type gdRecords struct
method addRecord (line 449) | func (p *gdRecords) addRecord(client gdClient, endpoint endpoint.Endpo...
method replaceRecord (line 475) | func (p *gdRecords) replaceRecord(client gdClient, endpoint endpoint.E...
method deleteRecord (line 524) | func (p *gdRecords) deleteRecord(client gdClient, endpoint endpoint.En...
method applyEndpoint (line 571) | func (p *gdRecords) applyEndpoint(action int, client gdClient, endpoin...
type gdZone (line 101) | type gdZone struct
type gdZoneIDName (line 119) | type gdZoneIDName
method add (line 121) | func (z gdZoneIDName) add(zoneID string, zoneRecord *gdRecords) {
method findZoneRecord (line 125) | func (z gdZoneIDName) findZoneRecord(hostname string) (string, *gdReco...
function newProvider (line 141) | func newProvider(domainFilter *endpoint.DomainFilter, ttl int64, apiKey,...
function New (line 156) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function countTargets (line 588) | func countTargets(p *plan.Changes) int {
function maxOf (line 601) | func maxOf(vars ...int64) int64 {
function toString (line 605) | func toString(obj any) string {
FILE: provider/godaddy/godaddy_test.go
type mockGoDaddyClient (line 34) | type mockGoDaddyClient struct
method Post (line 50) | func (c *mockGoDaddyClient) Post(endpoint string, input any, output an...
method Patch (line 60) | func (c *mockGoDaddyClient) Patch(endpoint string, input any, output a...
method Put (line 70) | func (c *mockGoDaddyClient) Put(endpoint string, input any, output any...
method Get (line 80) | func (c *mockGoDaddyClient) Get(endpoint string, output any) error {
method Delete (line 90) | func (c *mockGoDaddyClient) Delete(endpoint string, output any) error {
function newMockGoDaddyClient (line 39) | func newMockGoDaddyClient(t *testing.T) *mockGoDaddyClient {
function TestGoDaddyZones (line 100) | func TestGoDaddyZones(t *testing.T) {
function TestGoDaddyZoneRecords (line 134) | func TestGoDaddyZoneRecords(t *testing.T) {
function TestGoDaddyRecords (line 233) | func TestGoDaddyRecords(t *testing.T) {
function TestGoDaddyChange (line 329) | func TestGoDaddyChange(t *testing.T) {
constant operationFailedTestErrCode (line 394) | operationFailedTestErrCode = "GD500"
constant operationFailedTestReason (line 395) | operationFailedTestReason = "Could not apply request"
constant recordNotFoundErrCode (line 396) | recordNotFoundErrCode = "GD404"
constant recordNotFoundReason (line 397) | recordNotFoundReason = "The requested record is not found in DNS z...
function TestGoDaddyErrorResponse (line 400) | func TestGoDaddyErrorResponse(t *testing.T) {
FILE: provider/google/google.go
constant defaultTTL (line 41) | defaultTTL = 300
type managedZonesCreateCallInterface (line 44) | type managedZonesCreateCallInterface interface
type managedZonesListCallInterface (line 48) | type managedZonesListCallInterface interface
type managedZonesServiceInterface (line 52) | type managedZonesServiceInterface interface
type resourceRecordSetsListCallInterface (line 57) | type resourceRecordSetsListCallInterface interface
type resourceRecordSetsClientInterface (line 61) | type resourceRecordSetsClientInterface interface
type changesCreateCallInterface (line 65) | type changesCreateCallInterface interface
type changesServiceInterface (line 69) | type changesServiceInterface interface
type resourceRecordSetsService (line 73) | type resourceRecordSetsService struct
method List (line 77) | func (r resourceRecordSetsService) List(project string, managedZone st...
type managedZonesService (line 81) | type managedZonesService struct
method Create (line 85) | func (m managedZonesService) Create(project string, managedzone *dns.M...
method List (line 89) | func (m managedZonesService) List(project string) managedZonesListCall...
type changesService (line 93) | type changesService struct
method Create (line 97) | func (c changesService) Create(project string, managedZone string, cha...
type GoogleProvider (line 102) | type GoogleProvider struct
method Zones (line 174) | func (p *GoogleProvider) Zones(ctx context.Context) (map[string]*dns.M...
method Records (line 211) | func (p *GoogleProvider) Records(ctx context.Context) ([]*endpoint.End...
method ApplyChanges (line 240) | func (p *GoogleProvider) ApplyChanges(ctx context.Context, changes *pl...
method SupportedRecordType (line 254) | func (p *GoogleProvider) SupportedRecordType(recordType string) bool {
method newFilteredRecords (line 264) | func (p *GoogleProvider) newFilteredRecords(endpoints []*endpoint.Endp...
method submitChange (line 277) | func (p *GoogleProvider) submitChange(ctx context.Context, change *dns...
function New (line 129) | func New(ctx context.Context, cfg *externaldns.Config, domainFilter *end...
function newProvider (line 134) | func newProvider(ctx context.Context, project string, domainFilter *endp...
function batchChange (line 317) | func batchChange(change *dns.Change, batchSize int) []*dns.Change {
function separateChange (line 390) | func separateChange(zones map[string]*dns.ManagedZone, change *dns.Chang...
function newRecord (line 427) | func newRecord(ep *endpoint.Endpoint) *dns.ResourceRecordSet {
FILE: provider/google/google_test.go
type mockManagedZonesCreateCall (line 46) | type mockManagedZonesCreateCall struct
method Do (line 51) | func (m *mockManagedZonesCreateCall) Do(_ ...googleapi.CallOption) (*d...
type mockManagedZonesListCall (line 63) | type mockManagedZonesListCall struct
method Pages (line 68) | func (m *mockManagedZonesListCall) Pages(_ context.Context, f func(*dn...
type mockManagedZonesClient (line 84) | type mockManagedZonesClient struct
method Create (line 88) | func (m *mockManagedZonesClient) Create(project string, managedZone *d...
method List (line 92) | func (m *mockManagedZonesClient) List(project string) managedZonesList...
type mockResourceRecordSetsListCall (line 96) | type mockResourceRecordSetsListCall struct
method Pages (line 102) | func (m *mockResourceRecordSetsListCall) Pages(_ context.Context, f fu...
type mockResourceRecordSetsClient (line 122) | type mockResourceRecordSetsClient struct
method List (line 126) | func (m *mockResourceRecordSetsClient) List(project string, managedZon...
type mockChangesCreateCall (line 130) | type mockChangesCreateCall struct
method Do (line 136) | func (m *mockChangesCreateCall) Do(_ ...googleapi.CallOption) (*dns.Ch...
type mockChangesClient (line 169) | type mockChangesClient struct
method Create (line 171) | func (m *mockChangesClient) Create(project string, managedZone string,...
function zoneKey (line 175) | func zoneKey(project, zoneName string) string {
function recordKey (line 179) | func recordKey(recordType, recordName string) string {
function isValidRecordSet (line 183) | func isValidRecordSet(recordSet *dns.ResourceRecordSet) bool {
function hasTrailingDot (line 206) | func hasTrailingDot(target string) bool {
function TestGoogleZonesIDFilter (line 210) | func TestGoogleZonesIDFilter(t *testing.T) {
function TestGoogleZonesNameFilter (line 221) | func TestGoogleZonesNameFilter(t *testing.T) {
function TestGoogleZonesVisibilityFilterPublic (line 232) | func TestGoogleZonesVisibilityFilterPublic(t *testing.T) {
function TestGoogleZonesVisibilityFilterPrivate (line 243) | func TestGoogleZonesVisibilityFilterPrivate(t *testing.T) {
function TestGoogleZonesVisibilityFilterPrivatePeering (line 254) | func TestGoogleZonesVisibilityFilterPrivatePeering(t *testing.T) {
function TestGoogleRecords (line 265) | func TestGoogleRecords(t *testing.T) {
function TestGoogleRecordsFilter (line 280) | func TestGoogleRecordsFilter(t *testing.T) {
function TestGoogleApplyChanges (line 328) | func TestGoogleApplyChanges(t *testing.T) {
function TestGoogleApplyChangesDryRun (line 405) | func TestGoogleApplyChangesDryRun(t *testing.T) {
function TestGoogleApplyChangesEmpty (line 456) | func TestGoogleApplyChangesEmpty(t *testing.T) {
function TestNewFilteredRecords (line 461) | func TestNewFilteredRecords(t *testing.T) {
function TestSeparateChanges (line 490) | func TestSeparateChanges(t *testing.T) {
function TestGoogleBatchChangeSet (line 539) | func TestGoogleBatchChangeSet(t *testing.T) {
function TestGoogleBatchChangeSetExceeding (line 561) | func TestGoogleBatchChangeSetExceeding(t *testing.T) {
function TestGoogleBatchChangeSetExceedingNameChange (line 597) | func TestGoogleBatchChangeSetExceedingNameChange(t *testing.T) {
function TestSoftErrListZonesConflict (line 615) | func TestSoftErrListZonesConflict(t *testing.T) {
function TestSoftErrListRecordsConflict (line 625) | func TestSoftErrListRecordsConflict(t *testing.T) {
function sortChangesByName (line 635) | func sortChangesByName(cs *dns.Change) {
function validateZones (line 645) | func validateZones(t *testing.T, zones map[string]*dns.ManagedZone, expe...
function validateZone (line 653) | func validateZone(t *testing.T, zone *dns.ManagedZone, expected *dns.Man...
function validateChange (line 659) | func validateChange(t *testing.T, change *dns.Change, expected *dns.Chan...
function validateChangeRecords (line 664) | func validateChangeRecords(t *testing.T, records []*dns.ResourceRecordSe...
function validateChangeRecord (line 672) | func validateChangeRecord(t *testing.T, record *dns.ResourceRecordSet, e...
function newGoogleProviderZoneOverlap (line 679) | func newGoogleProviderZoneOverlap(t *testing.T, domainFilter *endpoint.D...
function newGoogleProvider (line 744) | func newGoogleProvider(t *testing.T, domainFilter *endpoint.DomainFilter...
function createZone (line 787) | func createZone(t *testing.T, p *GoogleProvider, zone *dns.ManagedZone) {
function setupGoogleRecords (line 798) | func setupGoogleRecords(t *testing.T, provider *GoogleProvider, endpoint...
function clearGoogleRecords (line 817) | func clearGoogleRecords(t *testing.T, provider *GoogleProvider, zone str...
function validateEndpoints (line 838) | func validateEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, exp...
FILE: provider/inmemory/inmemory.go
type InMemoryProvider (line 50) | type InMemoryProvider struct
method CreateZone (line 129) | func (im *InMemoryProvider) CreateZone(newZone string) error {
method Zones (line 134) | func (im *InMemoryProvider) Zones() map[string]string {
method Records (line 139) | func (im *InMemoryProvider) Records(_ context.Context) ([]*endpoint.En...
method ApplyChanges (line 161) | func (im *InMemoryProvider) ApplyChanges(ctx context.Context, changes ...
function New (line 60) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
type InMemoryOption (line 66) | type InMemoryOption
function InMemoryWithLogging (line 69) | func InMemoryWithLogging() InMemoryOption {
function InMemoryWithDomain (line 89) | func InMemoryWithDomain(domainFilter *endpoint.DomainFilter) InMemoryOpt...
function InMemoryInitZones (line 96) | func InMemoryInitZones(zones []string) InMemoryOption {
function NewInMemoryProvider (line 107) | func NewInMemoryProvider(opts ...InMemoryOption) *InMemoryProvider {
function newProvider (line 112) | func newProvider(opts ...InMemoryOption) *InMemoryProvider {
function copyEndpoints (line 216) | func copyEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
type filter (line 228) | type filter struct
method Zones (line 233) | func (f *filter) Zones(zones map[string]string) map[string]string {
method EndpointZoneID (line 245) | func (f *filter) EndpointZoneID(endpoint *endpoint.Endpoint, zones map...
type zone (line 256) | type zone
type inMemoryClient (line 258) | type inMemoryClient struct
method Records (line 266) | func (c *inMemoryClient) Records(zone string) ([]*endpoint.Endpoint, e...
method Zones (line 278) | func (c *inMemoryClient) Zones() map[string]string {
method CreateZone (line 286) | func (c *inMemoryClient) CreateZone(zone string) error {
method ApplyChanges (line 295) | func (c *inMemoryClient) ApplyChanges(_ context.Context, zoneID string...
method updateMesh (line 311) | func (c *inMemoryClient) updateMesh(mesh sets.Set[endpoint.EndpointKey...
method validateChangeBatch (line 320) | func (c *inMemoryClient) validateChangeBatch(zone string, changes *pla...
function newInMemoryClient (line 262) | func newInMemoryClient() *inMemoryClient {
FILE: provider/inmemory/inmemory_test.go
function TestInMemoryProvider (line 33) | func TestInMemoryProvider(t *testing.T) {
function testInMemoryRecords (line 41) | func testInMemoryRecords(t *testing.T) {
function testInMemoryValidateChangeBatch (line 114) | func testInMemoryValidateChangeBatch(t *testing.T) {
function getInitData (line 419) | func getInitData() map[string]zone {
function testInMemoryApplyChanges (line 430) | func testInMemoryApplyChanges(t *testing.T) {
function testNewInMemoryProvider (line 563) | func testNewInMemoryProvider(t *testing.T) {
function testInMemoryCreateZone (line 568) | func testInMemoryCreateZone(t *testing.T) {
function makeZone (line 576) | func makeZone(s ...string) map[endpoint.EndpointKey]*endpoint.Endpoint {
FILE: provider/linode/linode.go
type LinodeDomainClient (line 39) | type LinodeDomainClient interface
type LinodeProvider (line 48) | type LinodeProvider struct
method Zones (line 112) | func (p *LinodeProvider) Zones(ctx context.Context) ([]linodego.Domain...
method Records (line 122) | func (p *LinodeProvider) Records(ctx context.Context) ([]*endpoint.End...
method fetchRecords (line 154) | func (p *LinodeProvider) fetchRecords(ctx context.Context, domainID in...
method fetchZones (line 163) | func (p *LinodeProvider) fetchZones(ctx context.Context) ([]linodego.D...
method submitChanges (line 183) | func (p *LinodeProvider) submitChanges(ctx context.Context, changes Li...
method ApplyChanges (line 271) | func (p *LinodeProvider) ApplyChanges(ctx context.Context, changes *pl...
type LinodeChanges (line 56) | type LinodeChanges struct
type LinodeChangeCreate (line 63) | type LinodeChangeCreate struct
type LinodeChangeUpdate (line 69) | type LinodeChangeUpdate struct
type LinodeChangeDelete (line 76) | type LinodeChangeDelete struct
function New (line 82) | func New(_ context.Context, cfg *externaldns.Config, domainFilter *endpo...
function newProvider (line 87) | func newProvider(domainFilter *endpoint.DomainFilter, dryRun bool) (*Lin...
function getWeight (line 250) | func getWeight(recordType linodego.DomainRecordType) *int {
function getPort (line 260) | func getPort() *int {
function getPriority (line 265) | func getPriority() *int {
function endpointsByZone (line 502) | func endpointsByZone(zoneNameIDMapper provider.ZoneIDName, endpoints []*...
function convertRecordT
Condensed preview — 537 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,724K chars).
[
{
"path": ".editorconfig",
"chars": 567,
"preview": "# EditorConfig helps developers define and maintain consistent\n# coding styles between different editors and IDEs\n# edit"
},
{
"path": ".github/ISSUE_TEMPLATE/---bug-report.md",
"chars": 1935,
"preview": "---\nname: \"\\U0001F41E Bug report\"\nabout: Report a bug encountered while operating external-dns\ntitle: ''\nlabels: kind/bu"
},
{
"path": ".github/ISSUE_TEMPLATE/--enhancement-request.md",
"chars": 331,
"preview": "---\nname: \"✨ Enhancement Request\"\nabout: Suggest an enhancement to external-dns\ntitle: ''\nlabels: kind/feature\nassignees"
},
{
"path": ".github/ISSUE_TEMPLATE/-support-request.md",
"chars": 473,
"preview": "---\nname: \"❓Support Request\"\nabout: Support request or question relating to external-dns\ntitle: ''\nlabels: kind/support\n"
},
{
"path": ".github/ISSUE_TEMPLATE/create-release.md",
"chars": 893,
"preview": "---\nname: Create Release\nabout: Release template to track the next release\ntitle: Release x.y\nlabels: area/release\nassig"
},
{
"path": ".github/dependabot.yml",
"chars": 1332,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/labeler.yml",
"chars": 2139,
"preview": "# Add 'docs' to any changes within 'docs' folder or any subfolders\ndocs:\n - docs/**/*\n\n# Add 'provider/alibaba' in file"
},
{
"path": ".github/pull_request_template.md",
"chars": 590,
"preview": "## What does it do ?\n\n<!-- A brief description of the change being made with this pull request. -->\n\n## Motivation\n\n<!--"
},
{
"path": ".github/renovate-config.js",
"chars": 2179,
"preview": "\"use strict\";\n// https://github.com/renovatebot/github-action/blob/main/.github/renovate.json\n// https://docs.renovatebo"
},
{
"path": ".github/renovate.json",
"chars": 69,
"preview": "{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\"\n}\n"
},
{
"path": ".github/workflows/OWNERS",
"chars": 76,
"preview": "# See the OWNERS docs at https://go.k8s.io/owners\n\nlabels:\n- github_actions\n"
},
{
"path": ".github/workflows/ci.yaml",
"chars": 1493,
"preview": "name: Go\n\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n\npermissions:\n contents: read "
},
{
"path": ".github/workflows/codeql-analysis.yaml",
"chars": 1319,
"preview": "name: \"CodeQL analysis\"\n\non:\n push:\n branches: [ master]\n pull_request:\n # The branches below must be a subset o"
},
{
"path": ".github/workflows/dependency-update.yaml",
"chars": 766,
"preview": "name: update-versions-with-renovate\n\non:\n push:\n branches: [main, master]\n schedule:\n # https://crontab.guru/\n "
},
{
"path": ".github/workflows/docs.yaml",
"chars": 1284,
"preview": "name: Release Docs\n\non:\n push:\n tags:\n - \"v*\"\n # See https://docs.github.com/fr/webhooks/webhook-events-and-pa"
},
{
"path": ".github/workflows/end-to-end-tests.yml",
"chars": 287,
"preview": "name: end to end test\n\non:\n push:\n branches:\n pull_request:\n branches: [ master ]\n workflow_dispatch:\n\njobs:\n "
},
{
"path": ".github/workflows/gh-workflow-approve.yaml",
"chars": 1227,
"preview": "name: Approve GH Workflows\n\non:\n pull_request_target:\n types:\n - labeled\n - synchronize\n branches:\n "
},
{
"path": ".github/workflows/json-yaml-validate.yml",
"chars": 731,
"preview": "name: json-yaml-validate\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n workflow_dispat"
},
{
"path": ".github/workflows/lint-test-chart.yaml",
"chars": 6857,
"preview": "name: Lint and Test Chart\n\non:\n pull_request:\n branches:\n - master\n paths:\n - \"charts/external-dns/**\"\n"
},
{
"path": ".github/workflows/lint.yaml",
"chars": 1392,
"preview": "name: Lint\n\non:\n pull_request:\n branches: [ master ]\n\njobs:\n lint:\n name: Markdown and Go\n runs-on: ubuntu-la"
},
{
"path": ".github/workflows/release-chart.yaml",
"chars": 5404,
"preview": "name: Release Chart\n\non:\n push:\n branches:\n - master\n paths:\n - \"charts/external-dns/Chart.yaml\"\n\nconcu"
},
{
"path": ".github/workflows/staging-image-tester.yaml",
"chars": 794,
"preview": "name: Build all images\n\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n\npermissions:\n co"
},
{
"path": ".github/workflows/validate-crd.yml",
"chars": 2139,
"preview": "name: Validate CRD Generation\n\n# This workflow validates that generated CRD files are up-to-date when tool\n# dependencie"
},
{
"path": ".gitignore",
"chars": 756,
"preview": "# OSX leaves these everywhere on SMB shares\n._*\n\n# OSX trash\n.DS_Store\n\n# Eclipse files\n.classpath\n.project\n.settings/**"
},
{
"path": ".golangci.yml",
"chars": 6020,
"preview": "# https://golangci-lint.run/docs/configuration/\nversion: \"2\"\nlinters:\n default: none\n enable: # golangci-lint help lin"
},
{
"path": ".ko.yaml",
"chars": 211,
"preview": "defaultBaseImage: gcr.io/distroless/static-debian12:latest\nbuilds:\n- env:\n - CGO_ENABLED=0\n flags:\n - -v\n ldflags:\n "
},
{
"path": ".markdownlint.json",
"chars": 262,
"preview": "{\n \"default\": true,\n \"MD010\": { \"code_blocks\": false },\n \"MD013\": { \"line_length\": \"300\" },\n \"MD033\": false,"
},
{
"path": ".pre-commit-config.yaml",
"chars": 722,
"preview": "---\ndefault_language_version:\n node: system\n\nrepos:\n - repo: https://github.com/pre-commit/pre-commit-hooks\n rev: v"
},
{
"path": ".spectral.yaml",
"chars": 26,
"preview": "extends: [\"spectral:oas\"]\n"
},
{
"path": ".zappr.yaml",
"chars": 23,
"preview": "X-Zalando-Team: teapot\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 2763,
"preview": "# Contributing Guidelines\n\nWelcome to Kubernetes. We are excited about the prospect of you joining our [community](https"
},
{
"path": "LICENSE.md",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "Makefile",
"chars": 6643,
"preview": "# Copyright 2017 The Kubernetes Authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
},
{
"path": "OWNERS",
"chars": 731,
"preview": "# See the OWNERS file documentation:\n# https://github.com/kubernetes/community/blob/HEAD/contributors/guide/owners.md\n\n"
},
{
"path": "README.md",
"chars": 20697,
"preview": "---\nhide:\n - toc\n - navigation\n---\n\n<p align=\"center\">\n <img src=\"docs/img/external-dns.png\" width=\"40%\" align=\"center"
},
{
"path": "SECURITY_CONTACTS",
"chars": 599,
"preview": "# Defined below are the security contacts for this repo.\n#\n# They are the contact point for the Product Security Team to"
},
{
"path": "api/webhook.yaml",
"chars": 7739,
"preview": "---\nopenapi: \"3.0.0\"\ninfo:\n version: v0.15.0\n title: External DNS Webhook Server\n description: >-\n Implements the "
},
{
"path": "apis/OWNERS",
"chars": 66,
"preview": "# See the OWNERS docs at https://go.k8s.io/owners\n\nlabels:\n- apis\n"
},
{
"path": "apis/api.go",
"chars": 584,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "apis/v1alpha1/api.go",
"chars": 756,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "apis/v1alpha1/dnsendpoint.go",
"chars": 2121,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "apis/v1alpha1/groupversion_info.go",
"chars": 1450,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "apis/v1alpha1/zz_generated.deepcopy.go",
"chars": 3100,
"preview": "//go:build !ignore_autogenerated\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\truntime"
},
{
"path": "charts/OWNERS",
"chars": 74,
"preview": "labels:\n - chart\napprovers:\n - stevehipwell\nreviewers:\n - stevehipwell\n"
},
{
"path": "cloudbuild.yaml",
"chars": 528,
"preview": "# See https://cloud.google.com/cloud-build/docs/build-config\ntimeout: 5000s\noptions:\n substitution_option: ALLOW_LOOSE\n"
},
{
"path": "code-of-conduct.md",
"chars": 148,
"preview": "# Kubernetes Community Code of Conduct\n\nPlease refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/co"
},
{
"path": "config/crd/standard/dnsendpoints.externaldns.k8s.io.yaml",
"chars": 4284,
"preview": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n annotations:\n api-approved.kubernetes."
},
{
"path": "controller/OWNERS",
"chars": 72,
"preview": "# See the OWNERS docs at https://go.k8s.io/owners\n\nlabels:\n- controller\n"
},
{
"path": "controller/controller.go",
"chars": 6055,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controller/controller_test.go",
"chars": 17749,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controller/events.go",
"chars": 1366,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controller/events_test.go",
"chars": 5622,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controller/execute.go",
"chars": 7561,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controller/execute_test.go",
"chars": 9587,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controller/metrics.go",
"chars": 5417,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "controller/metrics_test.go",
"chars": 10816,
"preview": "/*\nCopyright 2025 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "docs/20190708-external-dns-incubator.md",
"chars": 9632,
"preview": "# Move ExternalDNS out of Kubernetes incubator\n\n<!-- TOC depthFrom:1 depthTo:6 withLinks:1 updateOnSave:1 orderedList:0 "
},
{
"path": "docs/OWNERS",
"chars": 66,
"preview": "# See the OWNERS docs at https://go.k8s.io/owners\n\nlabels:\n- docs\n"
},
{
"path": "docs/advanced/configuration-precedence.md",
"chars": 1963,
"preview": "## Annotations vs. CLI Flags Precedence\n\nExternalDNS configuration can come from these sources: resource annotations, CL"
},
{
"path": "docs/advanced/domain-filter.md",
"chars": 7517,
"preview": "---\ntags:\n - advanced\n - area/domain-filter\n - domain-filter\n---\n\n# Domain Filter\n\n> **Important:** Domain filter fla"
},
{
"path": "docs/advanced/events.md",
"chars": 6025,
"preview": "---\ntags: [\"advanced\", \"area/events\", \"events\"]\n---\n# Kubernetes Events in External-DNS\n\nExternal-DNS manages DNS record"
},
{
"path": "docs/advanced/fqdn-templating.md",
"chars": 22448,
"preview": "---\ntags: [\"advanced\", \"area/fqdn\", \"fqdn\", \"templating\"]\n---\n\n# FQDN Templating Guide\n\n## What is FQDN Templating?\n\n**F"
},
{
"path": "docs/advanced/import-records.md",
"chars": 4016,
"preview": "# Import Existing DNS Records\n\nSometimes DNS records are created manually (e.g., through Route53, CloudDNS, or AzureDNS)"
},
{
"path": "docs/advanced/nat64.md",
"chars": 1889,
"preview": "# Configure NAT64 DNS Records\n\nSome NAT64 configurations are entirely handled outside the Kubernetes cluster, therefore "
},
{
"path": "docs/advanced/operational-best-practices.md",
"chars": 22378,
"preview": "---\ntags:\n - advanced\n - operations\n - performance\n - configuration\n---\n\n# Operational Best Practices\n\nThis guide co"
},
{
"path": "docs/advanced/rate-limits.md",
"chars": 5320,
"preview": "# DNS provider API rate limits considerations\n\n## Introduction\n\nBy design, external-dns refreshes all the records of a z"
},
{
"path": "docs/advanced/split-horizon.md",
"chars": 7404,
"preview": "# Split Horizon DNS\n\nSplit horizon DNS allows you to serve different DNS responses based on the client's location - inte"
},
{
"path": "docs/advanced/ttl.md",
"chars": 7207,
"preview": "# Configure DNS record TTL (Time-To-Live)\n\n> To customize DNS record TTL (Time-To-Live) in a DNS record`, you can use th"
},
{
"path": "docs/annotations/annotations.md",
"chars": 16108,
"preview": "# Annotations\n\nExternalDNS sources support a number of annotations on the Kubernetes resources that they examine.\n\nThe f"
},
{
"path": "docs/contributing/bug-report.md",
"chars": 6936,
"preview": "# Bug Report Guide\n\n> **Before filing a bug:** validate the behavior against the [latest release](https://github.com/kub"
},
{
"path": "docs/contributing/chart.md",
"chars": 615,
"preview": "# Helm Chart\n\n## Chart Changes\n\nWhen contributing chart changes please follow the same process as when contributing othe"
},
{
"path": "docs/contributing/design.md",
"chars": 3064,
"preview": "# Design\n\nExternalDNS's sources of DNS records live in package [source](https://github.com/kubernetes-sigs/external-dns/"
},
{
"path": "docs/contributing/dev-guide.md",
"chars": 15410,
"preview": "---\ntags:\n - contributing\n - build\n - testing\n - integration-tests\n - helm\n---\n\n# Developer Reference\n\nThe `externa"
},
{
"path": "docs/contributing/index.md",
"chars": 948,
"preview": "# Developer Documentations (Advanced Topics)\n\nThis folder contains developer documentation.\n\nWhen you are ready to contr"
},
{
"path": "docs/contributing/source-wrappers.md",
"chars": 6214,
"preview": "# 🧩 Source Wrappers/Middleware\n\n## Overview\n\nIn ExternalDNS, a **Source** is a component responsible for discovering DNS"
},
{
"path": "docs/contributing/sources-and-providers.md",
"chars": 15190,
"preview": "---\ntags:\n - sources\n - providers\n - contributing\n---\n\n# Sources and Providers\n\nExternalDNS supports swapping out end"
},
{
"path": "docs/deprecation.md",
"chars": 4821,
"preview": "# External DNS Deprecation Policy\n\nThis document defines the Deprecation Policy for External DNS.\n\nKubernetes is a dynam"
},
{
"path": "docs/faq.md",
"chars": 21292,
"preview": "# Frequently asked questions\n\n## How is ExternalDNS useful to me?\n\nYou've probably created many deployments. Typically, "
},
{
"path": "docs/flags.md",
"chars": 99761,
"preview": "---\ntags:\n - flags\n - autogenerated\n---\n\n# Flags\n\n<!-- THIS FILE MUST NOT BE EDITED BY HAND -->\n<!-- ON NEW FLAG ADDED"
},
{
"path": "docs/initial-design.md",
"chars": 6918,
"preview": "# Proposal: Design of External DNS\n\n## Background\n\n[Project proposal](https://groups.google.com/forum/#!searching/kubern"
},
{
"path": "docs/monitoring/index.md",
"chars": 5588,
"preview": "# Monitoring & Observability\n\nMonitoring is a crucial aspect of maintaining the health and performance of your applicati"
},
{
"path": "docs/monitoring/metrics.md",
"chars": 7764,
"preview": "---\ntags:\n - metrics\n - autogenerated\n---\n\n# Available Metrics\n\n<!-- THIS FILE MUST NOT BE EDITED BY HAND -->\n<!-- ON "
},
{
"path": "docs/overrides/partials/copyright.html",
"chars": 1683,
"preview": "<!--\n Copyright (c) 2016-2024 Martin Donath <martin.donath@squidfunk.com>\n\n Permission is hereby granted, free of char"
},
{
"path": "docs/proposal/001-leader-election.md",
"chars": 6965,
"preview": "```yaml\n---\ntitle: leader election proposal\nversion: 0.15.1\nauthors: @ivankatliarchuk\ncreation-date: 2025-01-30\nstatus: "
},
{
"path": "docs/proposal/002-internal-ipv6-handling-rollback.md",
"chars": 4086,
"preview": "<!-- clone me -->\n\n```yaml\n---\ntitle: \"Proposal: Rollback IPv6 internal Node IP exposure\"\nversion: if applicable\nauthors"
},
{
"path": "docs/proposal/003-dnsendpoint-graduation-to-beta.md",
"chars": 7555,
"preview": "```yaml\n---\ntitle: \"Proposal: Defining a path to Beta for DNSEndpoint API\"\nversion: v1alpha1\nauthors: @ivankatliarchuk, "
},
{
"path": "docs/proposal/004-gateway-api-annotation-placement.md",
"chars": 19545,
"preview": "```yaml\n---\ntitle: \"Gateway API Annotation Placement Clarity\"\nversion: v1alpha1\nauthors: \"@lexfrei\"\ncreation-date: 2025-"
},
{
"path": "docs/proposal/design-template.md",
"chars": 1123,
"preview": "<!-- clone me -->\n```yaml\n---\ntitle: New Feature or Deprecation/Removal Proposal\nversion: if applicable\nauthors: you, me"
},
{
"path": "docs/proposal/multi-target.md",
"chars": 7514,
"preview": "# Multiple Targets per hostname\n\n*(November 2017)*\n\n## Purpose\n\nOne should be able to define multiple targets (IPs/Hostn"
},
{
"path": "docs/providers.md",
"chars": 1868,
"preview": "# Providers\n\nProvider supported configurations\n\n| Provider Name | Zone Cache | Dry Run | Default TTL (seconds) |\n|:-----"
},
{
"path": "docs/registry/dynamodb.md",
"chars": 5886,
"preview": "# The DynamoDB registry\n\nAs opposed to the default TXT registry, the DynamoDB registry stores DNS record metadata in an "
},
{
"path": "docs/registry/registry.md",
"chars": 854,
"preview": "# Registries\n\nA registry persists metadata pertaining to DNS records.\n\nThe most important metadata is the owning externa"
},
{
"path": "docs/registry/txt.md",
"chars": 12954,
"preview": "# The TXT registry\n\nThe TXT registry is the default registry.\nIt stores DNS record metadata in TXT records, using the sa"
},
{
"path": "docs/release.md",
"chars": 3788,
"preview": "# Release\n\n## Release cycle\n\nCurrently we don't release regularly. Whenever we think it makes sense to release a new ver"
},
{
"path": "docs/scripts/index.html.gotmpl",
"chars": 87,
"preview": "<head>\n <meta http-equiv=\"Refresh\" content=\"0; url='/external-dns/{{.}}/'\" />\n</head>\n"
},
{
"path": "docs/scripts/requirements.txt",
"chars": 190,
"preview": "mkdocs-git-revision-date-localized-plugin == 1.5.1\nmkdocs == 1.6.1\nmkdocs-macros-plugin == 1.5.0\nmkdocs-material == 9.7."
},
{
"path": "docs/snippets/contributing/collect-extdns-info.sh",
"chars": 1359,
"preview": "#!/usr/bin/env bash\n# Collect external-dns version, startup args, and logs.\n#\n# Usage:\n# [NAMESPACE=external-dns] [SIN"
},
{
"path": "docs/snippets/contributing/collect-resources.sh",
"chars": 729,
"preview": "#!/usr/bin/env bash\n# Collect Kubernetes resources relevant to your external-dns source type.\n#\n# Usage:\n# RESOURCE=<k"
},
{
"path": "docs/snippets/exoscale/extdns.yaml",
"chars": 876,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: external-dns\nspec:\n strategy:\n type: Recreate\n selector:\n "
},
{
"path": "docs/snippets/exoscale/how-to-test.yaml",
"chars": 823,
"preview": "---\napiVersion: networking.k8s.io/v1\nkind: Ingress\nmetadata:\n name: nginx\n annotations:\n external-dns.alpha.kuberne"
},
{
"path": "docs/snippets/exoscale/rbac.yaml",
"chars": 811,
"preview": "---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n name: external-dns\n namespace: default\n---\napiVersion: rbac.authori"
},
{
"path": "docs/snippets/security-context/extdns-limited-privilege.yaml",
"chars": 564,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: external-dns\nspec:\n strategy:\n type: Recreate\n selector:\n "
},
{
"path": "docs/snippets/traefik-proxy/ingress-route-default.yaml",
"chars": 417,
"preview": "apiVersion: traefik.io/v1alpha1\nkind: IngressRoute\nmetadata:\n name: traefik-ingress\n annotations:\n external-dns.alp"
},
{
"path": "docs/snippets/traefik-proxy/ingress-route-public-private.yaml",
"chars": 824,
"preview": "---\napiVersion: traefik.io/v1\nkind: IngressRoute\nmetadata:\n name: traefik-public-abc\n annotations:\n kubernetes.io/i"
},
{
"path": "docs/snippets/traefik-proxy/traefik-public-private-config.yaml",
"chars": 269,
"preview": "---\ntype: public\nproviders:\n kubernetesCRD:\n ingressClass: traefik-public\n\n kubernetesIngress:\n ingressClass: tr"
},
{
"path": "docs/snippets/traefik-proxy/with-cluster-rbac.yaml",
"chars": 1425,
"preview": "---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n name: external-dns\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind"
},
{
"path": "docs/snippets/traefik-proxy/without-rbac.yaml",
"chars": 538,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: external-dns\nspec:\n strategy:\n type: Recreate\n selector:\n "
},
{
"path": "docs/snippets/tutorials/coredns/coredns-groups.yaml",
"chars": 1106,
"preview": "---\napiVersion: v1\nkind: Service\nmetadata:\n name: a\n annotations:\n external-dns.alpha.kubernetes.io/hostname: a.dom"
},
{
"path": "docs/snippets/tutorials/coredns/etcd.yaml",
"chars": 2107,
"preview": "# kubectl apply -f docs/snippets/tutorials/coredns/etcd.yaml\n# kubectl delete -f docs/snippets/tutorials/coredns/etcd.ya"
},
{
"path": "docs/snippets/tutorials/coredns/fixtures.yaml",
"chars": 941,
"preview": "# kubectl apply -f docs/snippets/tutorials/coredns/fixtures.yaml\n# kubectl delete -f docs/snippets/tutorials/coredns/fix"
},
{
"path": "docs/snippets/tutorials/coredns/kind.yaml",
"chars": 1077,
"preview": "# ref: https://kind.sigs.k8s.io/docs/user/quick-start/\n# https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-ma"
},
{
"path": "docs/snippets/tutorials/coredns/values-coredns.yaml",
"chars": 1492,
"preview": "# kubectl logs deploy/coredns -n default -c coredns\n# ref: https://github.com/coredns/helm/blob/master/charts/coredns/va"
},
{
"path": "docs/snippets/tutorials/coredns/values-extdns-coredns.yaml",
"chars": 713,
"preview": "\n# ref: https://github.com/kubernetes-sigs/external-dns/blob/master/charts/external-dns/values.yaml\nprovider:\n name: co"
},
{
"path": "docs/sources/about.md",
"chars": 4084,
"preview": "# About\n\nA source in ExternalDNS defines where DNS records are discovered from within your infrastructure. Each source c"
},
{
"path": "docs/sources/crd/dnsendpoint-aws-example.yaml",
"chars": 481,
"preview": "apiVersion: externaldns.k8s.io/v1alpha1\nkind: DNSEndpoint\nmetadata:\n name: examplednsrecord\nspec:\n endpoints:\n - dnsN"
},
{
"path": "docs/sources/crd/dnsendpoint-example.yaml",
"chars": 407,
"preview": "apiVersion: externaldns.k8s.io/v1alpha1\nkind: DNSEndpoint\nmetadata:\n name: examplednsrecord\nspec:\n endpoints:\n - dnsN"
},
{
"path": "docs/sources/crd.md",
"chars": 5999,
"preview": "# CRD Source\n\nCRD source provides a generic mechanism to manage DNS records in your favorite DNS provider supported by e"
},
{
"path": "docs/sources/f5-transportserver.md",
"chars": 2833,
"preview": "# F5 Networks TransportServer Source\n\nThis tutorial describes how to configure ExternalDNS to use the F5 Networks Transp"
},
{
"path": "docs/sources/f5-virtualserver.md",
"chars": 2743,
"preview": "# F5 Networks VirtualServer Source\n\nThis tutorial describes how to configure ExternalDNS to use the F5 Networks VirtualS"
},
{
"path": "docs/sources/gateway-api.md",
"chars": 8746,
"preview": "# Gateway API Route Sources\n\nThis describes how to configure ExternalDNS to use Gateway API Route sources.\nIt is meant t"
},
{
"path": "docs/sources/gateway.md",
"chars": 4764,
"preview": "# Gateway sources\n\nThe gateway-grpcroute, gateway-httproute, gateway-tcproute, gateway-tlsroute, and gateway-udproute\nso"
},
{
"path": "docs/sources/gloo-proxy.md",
"chars": 3715,
"preview": "# Gloo Proxy Source\n\nThis tutorial describes how to configure ExternalDNS to use the Gloo Proxy source.\nIt is meant to s"
},
{
"path": "docs/sources/index.md",
"chars": 7484,
"preview": "---\ntags:\n - sources\n - autogenerated\n---\n\n# Supported Sources\n\n<!-- THIS FILE MUST NOT BE EDITED BY HAND -->\n<!-- ON "
},
{
"path": "docs/sources/ingress.md",
"chars": 1882,
"preview": "# Ingress source\n\nThe ingress source creates DNS entries based on `Ingress.networking.k8s.io` resources.\n\n## Filtering t"
},
{
"path": "docs/sources/istio.md",
"chars": 10276,
"preview": "# Istio Gateway / Virtual Service Source\n\nThis tutorial describes how to configure ExternalDNS to use the Istio Gateway "
},
{
"path": "docs/sources/kong.md",
"chars": 2267,
"preview": "# Kong TCPIngress Source\n\nThis tutorial describes how to configure ExternalDNS to use the Kong TCPIngress source.\nIt is "
},
{
"path": "docs/sources/mx-record.md",
"chars": 972,
"preview": "# MX record with CRD source\n\nYou can create and manage MX records with the help of [CRD source](../sources/crd.md)\nand `"
},
{
"path": "docs/sources/nodes.md",
"chars": 4824,
"preview": "# Cluster Nodes as Source\n\nThis tutorial describes how to configure ExternalDNS to use the cluster nodes as source.\nUsin"
},
{
"path": "docs/sources/ns-record.md",
"chars": 746,
"preview": "# NS record with CRD source\n\nYou can create NS records with the help of [CRD source](../sources/crd.md)\nand `DNSEndpoint"
},
{
"path": "docs/sources/openshift.md",
"chars": 6358,
"preview": "# OpenShift Route Source\n\nThis tutorial describes how to configure ExternalDNS to use the OpenShift Route source.\nIt is "
},
{
"path": "docs/sources/pod.md",
"chars": 1475,
"preview": "# Pod Source\n\nThe pod source creates DNS entries based on `Pod` resources.\n\n## Pods not running with host networking\n\nBy"
},
{
"path": "docs/sources/service.md",
"chars": 6090,
"preview": "# Service source\n\nThe service source creates DNS entries based on `Service` resources.\n\n## Filtering the Services consid"
},
{
"path": "docs/sources/traefik-proxy.md",
"chars": 3170,
"preview": "# Traefik Proxy Source\n\n- [Traefik Documentation](https://doc.traefik.io/traefik/)\n- [Traefik Helm Chart](https://github"
},
{
"path": "docs/sources/txt-record.md",
"chars": 940,
"preview": "# Creating TXT record with CRD source\n\nYou can create and manage TXT records with the help of [CRD source](../sources/cr"
},
{
"path": "docs/sources/unstructured.md",
"chars": 12453,
"preview": "---\ntags: [\"source\", \"area/fqdn\", \"area/source\", \"templating\", \"unstructured\"]\n---\n\n# Unstructured Source\n\nThe `unstruct"
},
{
"path": "docs/tutorials/akamai-edgedns.md",
"chars": 10269,
"preview": "# Akamai Edge DNS\n\n## Prerequisites\n\nExternal-DNS v0.8.0 or greater.\n\n### Zones\n\nExternal-DNS manages service endpoints "
},
{
"path": "docs/tutorials/alibabacloud.md",
"chars": 10247,
"preview": "# Alibaba Cloud\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on Alibaba Cloud"
},
{
"path": "docs/tutorials/anexia-engine.md",
"chars": 528,
"preview": "# Anexia\n\nOfficial documentation of how to use `external-dns` in combination with Anexia Engine can be viewed on the off"
},
{
"path": "docs/tutorials/aws-filters.md",
"chars": 5706,
"preview": "# AWS Filters\n\nThis document provides guidance on filtering AWS zones using various strategies and flags.\n\n## Strategies"
},
{
"path": "docs/tutorials/aws-load-balancer-controller.md",
"chars": 10101,
"preview": "# AWS Load Balancer Controller\n\nThis tutorial describes how to use ExternalDNS with the [aws-load-balancer-controller][1"
},
{
"path": "docs/tutorials/aws-public-private-route53.md",
"chars": 9953,
"preview": "# AWS Route53 with same domain for public and private zones\n\nThis tutorial describes how to setup ExternalDNS using the "
},
{
"path": "docs/tutorials/aws-sd.md",
"chars": 11440,
"preview": "# AWS Cloud Map API\n\nThis tutorial describes how to set up ExternalDNS for usage within a Kubernetes cluster with [AWS C"
},
{
"path": "docs/tutorials/aws.md",
"chars": 50454,
"preview": "# AWS\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on AWS. Make sure to use *"
},
{
"path": "docs/tutorials/azure-private-dns.md",
"chars": 15370,
"preview": "# Azure Private DNS\n\nThis tutorial describes how to set up ExternalDNS for managing records in Azure Private DNS.\n\nIt co"
},
{
"path": "docs/tutorials/azure.md",
"chars": 32567,
"preview": "# Azure DNS\n\nThis tutorial describes how to setup ExternalDNS for [Azure DNS](https://azure.microsoft.com/services/dns/)"
},
{
"path": "docs/tutorials/civo.md",
"chars": 4755,
"preview": "# Civo DNS\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Civo DNS Manage"
},
{
"path": "docs/tutorials/cloudflare.md",
"chars": 15643,
"preview": "# Cloudflare DNS\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Cloudflar"
},
{
"path": "docs/tutorials/contour.md",
"chars": 4476,
"preview": "# Contour HTTPProxy\n\nThis tutorial describes how to configure External DNS to use the Contour `HTTPProxy` source.\nUsing "
},
{
"path": "docs/tutorials/coredns-etcd.md",
"chars": 7254,
"preview": "# CoreDNS with etcd backend\n\n## Overview\n\nThis tutorial describes how to deploy CoreDNS backed by etcd as a dynamic DNS "
},
{
"path": "docs/tutorials/coredns.md",
"chars": 2086,
"preview": "# CoreDNS\n\n- [Documentation](https://coredns.io/)\n\n## Multi cluster support options\n\nThe CoreDNS provider allows records"
},
{
"path": "docs/tutorials/crd.md",
"chars": 3914,
"preview": "# Using CRD Source for DNS Records\n\nThis tutorial describes how to use the CRD source with ExternalDNS to manage DNS rec"
},
{
"path": "docs/tutorials/dnsimple.md",
"chars": 7273,
"preview": "# DNSimple\n\nThis tutorial describes how to setup ExternalDNS for usage with DNSimple.\n\nMake sure to use **>=0.4.6** vers"
},
{
"path": "docs/tutorials/exoscale.md",
"chars": 2003,
"preview": "# Exoscale\n\n## Prerequisites\n\nExoscale provider support was added via [this PR](https://github.com/kubernetes-sigs/exter"
},
{
"path": "docs/tutorials/externalname.md",
"chars": 1861,
"preview": "# ExternalName Services\n\nThis tutorial describes how to setup ExternalDNS for usage in conjunction with an ExternalName "
},
{
"path": "docs/tutorials/gandi.md",
"chars": 5210,
"preview": "# Gandi\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Gandi.\n\nMake sure "
},
{
"path": "docs/tutorials/gke-nginx.md",
"chars": 20803,
"preview": "# GKE with nginx-ingress-controller\n\nThis tutorial describes how to setup ExternalDNS for usage within a GKE cluster tha"
},
{
"path": "docs/tutorials/gke.md",
"chars": 20843,
"preview": "# GKE with default controller\n\nThis tutorial describes how to setup ExternalDNS for usage within a [GKE](https://cloud.g"
},
{
"path": "docs/tutorials/godaddy.md",
"chars": 6092,
"preview": "# GoDaddy\n\nThis tutorial describes how to set up ExternalDNS for use within a\nKubernetes cluster using GoDaddy DNS.\n\nMak"
},
{
"path": "docs/tutorials/hostport.md",
"chars": 6283,
"preview": "# Headless Services\n\nThis tutorial describes how to setup ExternalDNS for usage in conjunction with a Headless service.\n"
},
{
"path": "docs/tutorials/ionoscloud.md",
"chars": 8859,
"preview": "# IONOS Cloud\n\nThis tutorial describes how to set up ExternalDNS for use within a Kubernetes cluster using IONOS Cloud D"
},
{
"path": "docs/tutorials/kops-dns-controller.md",
"chars": 1638,
"preview": "# kOps dns-controller\n\nkOps includes a dns-controller that is primarily used to bootstrap the cluster, but can also be u"
},
{
"path": "docs/tutorials/kube-ingress-aws.md",
"chars": 9435,
"preview": "# kube-ingress-aws-controller\n\nThis tutorial describes how to use ExternalDNS with the [kube-ingress-aws-controller][1]."
},
{
"path": "docs/tutorials/linode.md",
"chars": 5000,
"preview": "# Linode\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Linode DNS Manage"
},
{
"path": "docs/tutorials/myra.md",
"chars": 7387,
"preview": "# Myra ExternalDNS Webhook\n\nThis guide provides quick instructions for setting up and testing the [Myra ExternalDNS Webh"
},
{
"path": "docs/tutorials/ns1.md",
"chars": 7029,
"preview": "# NS1\n\nThis tutorial describes how to setup ExternalDNS for use within a\nKubernetes cluster using NS1 DNS.\n\nMake sure to"
},
{
"path": "docs/tutorials/oracle.md",
"chars": 7305,
"preview": "# Oracle Cloud Infrastructure\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster us"
},
{
"path": "docs/tutorials/ovh.md",
"chars": 7115,
"preview": "# OVHcloud\n\nThis tutorial describes how to setup ExternalDNS for use within a\nKubernetes cluster using OVHcloud DNS.\n\nMa"
},
{
"path": "docs/tutorials/pdns.md",
"chars": 5784,
"preview": "# PowerDNS\n\n## Prerequisites\n\nThe provider has been written for and tested against [PowerDNS](https://github.com/PowerDN"
},
{
"path": "docs/tutorials/pihole.md",
"chars": 5885,
"preview": "# Pi-hole\n\nThis tutorial describes how to setup ExternalDNS to sync records with Pi-hole's Custom DNS.\nPi-hole has an in"
},
{
"path": "docs/tutorials/plural.md",
"chars": 6461,
"preview": "# Plural\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Plural DNS.\n\nMake"
},
{
"path": "docs/tutorials/rfc2136.md",
"chars": 17400,
"preview": "# RFC2136 provider\n\nThis tutorial describes how to use the RFC2136 with either BIND or Windows DNS.\n\n## Using with BIND\n"
},
{
"path": "docs/tutorials/scaleway.md",
"chars": 7443,
"preview": "# Scaleway\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Scaleway DNS.\n\n"
},
{
"path": "docs/tutorials/security-context.md",
"chars": 227,
"preview": "# Running ExternalDNS with limited privileges\n\nYou can run ExternalDNS with reduced privileges since `v0.5.6` using the "
},
{
"path": "docs/tutorials/transip.md",
"chars": 5335,
"preview": "# TransIP\n\nThis tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using TransIP.\n\nMake s"
},
{
"path": "docs/tutorials/webhook-provider.md",
"chars": 7401,
"preview": "# Webhook provider\n\nThe \"Webhook\" provider allows integrating ExternalDNS with DNS providers through an HTTP interface.\n"
},
{
"path": "docs/version-update-playbook.md",
"chars": 5006,
"preview": "# 🧭 External-DNS Version Upgrade Playbook\n\n## Overview\n\nThis playbook describes the best practices and steps to safely u"
},
{
"path": "e2e/deployment.yaml",
"chars": 330,
"preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n labels:\n app: demo-app\n name: demo-app\nspec:\n replicas: 1\n select"
},
{
"path": "e2e/provider/coredns.yaml",
"chars": 1733,
"preview": "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: coredns\n namespace: default\ndata:\n Corefile: |\n external.dns:5"
},
{
"path": "e2e/provider/etcd.yaml",
"chars": 3130,
"preview": "---\napiVersion: v1\nkind: Service\nmetadata:\n name: etcd\n namespace: default\nspec:\n type: ClusterIP\n clusterIP: None\n "
},
{
"path": "e2e/service.yaml",
"chars": 290,
"preview": "apiVersion: v1\nkind: Service\nmetadata:\n labels:\n app: demo-app\n name: demo-app\n annotations:\n external-dns.alph"
},
{
"path": "endpoint/OWNERS",
"chars": 70,
"preview": "# See the OWNERS docs at https://go.k8s.io/owners\n\nlabels:\n- endpoint\n"
},
{
"path": "endpoint/crypto.go",
"chars": 3652,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/crypto_test.go",
"chars": 4075,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/domain_filter.go",
"chars": 9651,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/domain_filter_test.go",
"chars": 34887,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/endpoint.go",
"chars": 22685,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/endpoint_benchmark_test.go",
"chars": 4485,
"preview": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/endpoint_test.go",
"chars": 45731,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/labels.go",
"chars": 5301,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/labels_test.go",
"chars": 7661,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/target_filter.go",
"chars": 2729,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/target_filter_test.go",
"chars": 2729,
"preview": "/*\nCopyright 2017 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/utils.go",
"chars": 2799,
"preview": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/utils_test.go",
"chars": 6025,
"preview": "/*\nCopyright 2026 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not u"
},
{
"path": "endpoint/zz_generated.deepcopy.go",
"chars": 1045,
"preview": "//go:build !ignore_autogenerated\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage endpoint\n\n// DeepCopyInto i"
},
{
"path": "external-dns.code-workspace",
"chars": 44,
"preview": "{\n\t\"folders\": [\n\t\t{\n\t\t\t\"path\": \".\"\n\t\t}\n\t]\n}\n"
},
{
"path": "go.mod",
"chars": 10189,
"preview": "module sigs.k8s.io/external-dns\n\ngo 1.25.7\n\nrequire (\n\tcloud.google.com/go/compute/metadata v0.9.0\n\tgithub.com/Azure/azu"
},
{
"path": "go.sum",
"chars": 148420,
"preview": "bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=\ncloud.google.co"
},
{
"path": "go.tool.mod",
"chars": 6858,
"preview": "module sigs.k8s.io/external-dns/tools\n\ngo 1.25.7\n\ntool (\n\tgithub.com/google/yamlfmt/cmd/yamlfmt\n\tgithub.com/mikefarah/yq"
},
{
"path": "go.tool.sum",
"chars": 36029,
"preview": "github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=\ngithub.com/a8m/envsubst v1.4.3/go.mod h1:"
}
]
// ... and 337 more files (download for full content)
About this extraction
This page contains the full source code of the kubernetes-sigs/external-dns GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 537 files (4.9 MB), approximately 1.3M tokens, and a symbol index with 3730 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.