chore(code): improve some tests + re-order sources flags CLI (#5288)

* fix(plan): always use managed records

* robust random port in test

* use defaultconfig for managed-record-types

* be explicit about static variable

* fix wait

* re-order flags related to sources + dynamic managedrecordtype help

* fix flag doc
This commit is contained in:
Michel Loiseleur 2025-04-27 23:11:24 +02:00 committed by GitHub
parent 5dcf2f0f54
commit 3c93bcb076
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 270 additions and 227 deletions

View File

@ -18,6 +18,7 @@ package controller
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
@ -29,10 +30,9 @@ import (
"testing" "testing"
"time" "time"
"context"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/apis/externaldns" "sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/plan"
@ -219,18 +219,45 @@ func TestHandleSigterm(t *testing.T) {
} }
} }
func TestServeMetrics(t *testing.T) { func getRandomPort() (int, error) {
l, _ := net.Listen("tcp", ":0") addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
_ = l.Close() if err != nil {
_, port, _ := net.SplitHostPort(l.Addr().String()) return 0, err
}
go serveMetrics(fmt.Sprintf(":%s", port)) l, err := net.ListenTCP("tcp", addr)
resp, err := http.Get(fmt.Sprintf("http://localhost:%s", port) + "/healthz") if err != nil {
assert.NoError(t, err) return 0, err
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
func TestServeMetrics(t *testing.T) {
t.Parallel()
port, err := getRandomPort()
require.NoError(t, err)
addresse := fmt.Sprintf("localhost:%d", port)
go serveMetrics(fmt.Sprintf(":%d", port))
// Wait for the TCP socket to be ready
require.Eventually(t, func() bool {
conn, err := net.Dial("tcp", addresse)
if err != nil {
return false
}
_ = conn.Close()
return true
}, 1*time.Second, 5*time.Millisecond, "server not ready with port open in time")
resp, err := http.Get(fmt.Sprintf("http://%s/healthz", addresse))
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, http.StatusOK, resp.StatusCode)
resp, err = http.Get(fmt.Sprintf("http://localhost:%s", port) + "/metrics") resp, err = http.Get(fmt.Sprintf("http://%s/metrics", addresse))
assert.NoError(t, err) require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, http.StatusOK, resp.StatusCode)
} }
@ -308,7 +335,6 @@ func (m *MockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error
} }
func (p *MockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { func (p *MockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
return nil return nil
} }

View File

@ -17,40 +17,40 @@
| `--cf-password=""` | The password to log into the cloud foundry API | | `--cf-password=""` | The password to log into the cloud foundry API |
| `--gloo-namespace=gloo-system` | The Gloo Proxy namespace; specify multiple times for multiple namespaces. (default: gloo-system) | | `--gloo-namespace=gloo-system` | The Gloo Proxy namespace; specify multiple times for multiple namespaces. (default: gloo-system) |
| `--skipper-routegroup-groupversion="zalando.org/v1"` | The resource version for skipper routegroup | | `--skipper-routegroup-groupversion="zalando.org/v1"` | The resource version for skipper routegroup |
| `--source=source` | The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, pod, fake, connector, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, istio-gateway, istio-virtualservice, cloudfoundry, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress, f5-virtualserver, f5-transportserver, traefik-proxy) |
| `--openshift-router-name=OPENSHIFT-ROUTER-NAME` | if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record. |
| `--namespace=""` | Limit resources queried for endpoints to a specific namespace (default: all namespaces) |
| `--annotation-filter=""` | Filter resources queried for endpoints by annotation, using label selector semantics |
| `--label-filter=""` | Filter resources queried for endpoints by label selector; currently supported by source types crd, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, ingress, node, openshift-route, service and ambassador-host |
| `--ingress-class=INGRESS-CLASS` | Require an Ingress to have this class name (defaults to any class; specify multiple times to allow more than one class) |
| `--fqdn-template=""` | A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN. |
| `--[no-]combine-fqdn-annotation` | Combine FQDN template and Annotations instead of overwriting |
| `--[no-]ignore-hostname-annotation` | Ignore hostname annotation when generating DNS names, valid only when --fqdn-template is set (default: false) |
| `--[no-]ignore-non-host-network-pods` | Ignore pods not running on host network when using pod source (default: true) |
| `--[no-]ignore-ingress-tls-spec` | Ignore the spec.tls section in Ingress resources (default: false) |
| `--gateway-name=GATEWAY-NAME` | Limit Gateways of Route endpoints to a specific name (default: all names) |
| `--gateway-namespace=GATEWAY-NAMESPACE` | Limit Gateways of Route endpoints to a specific namespace (default: all namespaces) |
| `--gateway-label-filter=GATEWAY-LABEL-FILTER` | Filter Gateways of Route endpoints via label selector (default: all gateways) |
| `--compatibility=` | Process annotation semantics from legacy implementations (optional, options: mate, molecule, kops-dns-controller) |
| `--[no-]ignore-ingress-rules-spec` | Ignore the spec.rules section in Ingress resources (default: false) |
| `--pod-source-domain=""` | Domain to use for pods records (optional) |
| `--[no-]publish-internal-services` | Allow external-dns to publish DNS records for ClusterIP services (optional) |
| `--[no-]publish-host-ip` | Allow external-dns to publish host-ip for headless services (optional) |
| `--[no-]always-publish-not-ready-addresses` | Always publish also not ready addresses for headless services (optional) | | `--[no-]always-publish-not-ready-addresses` | Always publish also not ready addresses for headless services (optional) |
| `--annotation-filter=""` | Filter resources queried for endpoints by annotation, using label selector semantics |
| `--[no-]combine-fqdn-annotation` | Combine FQDN template and Annotations instead of overwriting |
| `--compatibility=` | Process annotation semantics from legacy implementations (optional, options: mate, molecule, kops-dns-controller) |
| `--connector-source-server="localhost:8080"` | The server to connect for connector source, valid only when using connector source | | `--connector-source-server="localhost:8080"` | The server to connect for connector source, valid only when using connector source |
| `--crd-source-apiversion="externaldns.k8s.io/v1alpha1"` | API version of the CRD for crd source, e.g. `externaldns.k8s.io/v1alpha1`, valid only when using crd source | | `--crd-source-apiversion="externaldns.k8s.io/v1alpha1"` | API version of the CRD for crd source, e.g. `externaldns.k8s.io/v1alpha1`, valid only when using crd source |
| `--crd-source-kind="DNSEndpoint"` | Kind of the CRD for the crd source in API group and version specified by crd-source-apiversion | | `--crd-source-kind="DNSEndpoint"` | Kind of the CRD for the crd source in API group and version specified by crd-source-apiversion |
| `--service-type-filter=SERVICE-TYPE-FILTER` | The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName) |
| `--managed-record-types=A...` | Record types to manage; specify multiple times to include many; (default: A, AAAA, CNAME) (supported records: A, AAAA, CNAME, NS, SRV, TXT) |
| `--exclude-record-types=EXCLUDE-RECORD-TYPES` | Record types to exclude from management; specify multiple times to exclude many; (optional) |
| `--default-targets=DEFAULT-TARGETS` | Set globally default host/IP that will apply as a target instead of source addresses. Specify multiple times for multiple targets (optional) | | `--default-targets=DEFAULT-TARGETS` | Set globally default host/IP that will apply as a target instead of source addresses. Specify multiple times for multiple targets (optional) |
| `--target-net-filter=TARGET-NET-FILTER` | Limit possible targets by a net filter; specify multiple times for multiple possible nets (optional) | | `--exclude-record-types=EXCLUDE-RECORD-TYPES` | Record types to exclude from management; specify multiple times to exclude many; (optional) |
| `--exclude-target-net=EXCLUDE-TARGET-NET` | Exclude target nets (optional) | | `--exclude-target-net=EXCLUDE-TARGET-NET` | Exclude target nets (optional) |
| `--[no-]traefik-disable-legacy` | Disable listeners on Resources under the traefik.containo.us API Group |
| `--[no-]traefik-disable-new` | Disable listeners on Resources under the traefik.io API Group |
| `--nat64-networks=NAT64-NETWORKS` | Adding an A record for each AAAA record in NAT64-enabled networks; specify multiple times for multiple possible nets (optional) |
| `--[no-]exclude-unschedulable` | Exclude nodes that are considered unschedulable (default: true) | | `--[no-]exclude-unschedulable` | Exclude nodes that are considered unschedulable (default: true) |
| `--[no-]expose-internal-ipv6` | When using the node source, expose internal IPv6 addresses (optional). Default is true. | | `--[no-]expose-internal-ipv6` | When using the node source, expose internal IPv6 addresses (optional). Default is true. |
| `--fqdn-template=""` | A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN. |
| `--gateway-label-filter=GATEWAY-LABEL-FILTER` | Filter Gateways of Route endpoints via label selector (default: all gateways) |
| `--gateway-name=GATEWAY-NAME` | Limit Gateways of Route endpoints to a specific name (default: all names) |
| `--gateway-namespace=GATEWAY-NAMESPACE` | Limit Gateways of Route endpoints to a specific namespace (default: all namespaces) |
| `--[no-]ignore-hostname-annotation` | Ignore hostname annotation when generating DNS names, valid only when --fqdn-template is set (default: false) |
| `--[no-]ignore-ingress-rules-spec` | Ignore the spec.rules section in Ingress resources (default: false) |
| `--[no-]ignore-ingress-tls-spec` | Ignore the spec.tls section in Ingress resources (default: false) |
| `--[no-]ignore-non-host-network-pods` | Ignore pods not running on host network when using pod source (default: true) |
| `--ingress-class=INGRESS-CLASS` | Require an Ingress to have this class name (defaults to any class; specify multiple times to allow more than one class) |
| `--label-filter=""` | Filter resources queried for endpoints by label selector; currently supported by source types crd, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, ingress, node, openshift-route, service and ambassador-host |
| `--managed-record-types=A...` | Record types to manage; specify multiple times to include many; (default: A,AAAA,CNAME) (supported records: A, AAAA, CNAME, NS, SRV, TXT) |
| `--namespace=""` | Limit resources queried for endpoints to a specific namespace (default: all namespaces) |
| `--nat64-networks=NAT64-NETWORKS` | Adding an A record for each AAAA record in NAT64-enabled networks; specify multiple times for multiple possible nets (optional) |
| `--openshift-router-name=OPENSHIFT-ROUTER-NAME` | if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record. |
| `--pod-source-domain=""` | Domain to use for pods records (optional) |
| `--[no-]publish-host-ip` | Allow external-dns to publish host-ip for headless services (optional) |
| `--[no-]publish-internal-services` | Allow external-dns to publish DNS records for ClusterIP services (optional) |
| `--service-type-filter=SERVICE-TYPE-FILTER` | The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName) |
| `--source=source` | The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, pod, fake, connector, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, istio-gateway, istio-virtualservice, cloudfoundry, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress, f5-virtualserver, f5-transportserver, traefik-proxy) |
| `--target-net-filter=TARGET-NET-FILTER` | Limit possible targets by a net filter; specify multiple times for multiple possible nets (optional) |
| `--[no-]traefik-disable-legacy` | Disable listeners on Resources under the traefik.containo.us API Group |
| `--[no-]traefik-disable-new` | Disable listeners on Resources under the traefik.io API Group |
| `--provider=provider` | The DNS provider where the DNS records will be created (required, options: akamai, alibabacloud, aws, aws-sd, azure, azure-dns, azure-private-dns, civo, cloudflare, coredns, digitalocean, dnsimple, exoscale, gandi, godaddy, google, ibmcloud, inmemory, linode, ns1, oci, ovh, pdns, pihole, plural, rfc2136, scaleway, skydns, tencentcloud, transip, ultradns, webhook) | | `--provider=provider` | The DNS provider where the DNS records will be created (required, options: akamai, alibabacloud, aws, aws-sd, azure, azure-dns, azure-private-dns, civo, cloudflare, coredns, digitalocean, dnsimple, exoscale, gandi, godaddy, google, ibmcloud, inmemory, linode, ns1, oci, ovh, pdns, pihole, plural, rfc2136, scaleway, skydns, tencentcloud, transip, ultradns, webhook) |
| `--provider-cache-time=0s` | The time to cache the DNS provider record list requests. | | `--provider-cache-time=0s` | The time to cache the DNS provider record list requests. |
| `--domain-filter=` | Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional) | | `--domain-filter=` | Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional) |

View File

@ -217,167 +217,168 @@ type Config struct {
} }
var defaultConfig = &Config{ var defaultConfig = &Config{
APIServerURL: "", AkamaiAccessToken: "",
KubeConfig: "", AkamaiClientSecret: "",
RequestTimeout: time.Second * 30, AkamaiClientToken: "",
DefaultTargets: []string{}, AkamaiEdgercPath: "",
GlooNamespaces: []string{"gloo-system"}, AkamaiEdgercSection: "",
SkipperRouteGroupVersion: "zalando.org/v1", AkamaiServiceConsumerDomain: "",
Sources: nil, AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
Namespace: "", AnnotationFilter: "",
AnnotationFilter: "", APIServerURL: "",
LabelFilter: labels.Everything().String(), AWSAPIRetries: 3,
IngressClassNames: nil, AWSAssumeRole: "",
FQDNTemplate: "", AWSAssumeRoleExternalID: "",
CombineFQDNAndAnnotation: false, AWSBatchChangeInterval: time.Second,
IgnoreHostnameAnnotation: false, AWSBatchChangeSize: 1000,
IgnoreIngressTLSSpec: false, AWSBatchChangeSizeBytes: 32000,
IgnoreIngressRulesSpec: false, AWSBatchChangeSizeValues: 1000,
GatewayName: "", AWSDynamoDBRegion: "",
GatewayNamespace: "", AWSDynamoDBTable: "external-dns",
GatewayLabelFilter: "", AWSEvaluateTargetHealth: true,
Compatibility: "", AWSPreferCNAME: false,
PublishInternal: false, AWSSDCreateTag: map[string]string{},
PublishHostIP: false, AWSSDServiceCleanup: false,
ExposeInternalIPV6: true, AWSZoneCacheDuration: 0 * time.Second,
ConnectorSourceServer: "localhost:8080", AWSZoneMatchParent: false,
Provider: "", AWSZoneTagFilter: []string{},
ProviderCacheTime: 0, AWSZoneType: "",
GoogleProject: "", AzureConfigFile: "/etc/kubernetes/azure.json",
GoogleBatchChangeSize: 1000, AzureResourceGroup: "",
GoogleBatchChangeInterval: time.Second, AzureSubscriptionID: "",
GoogleZoneVisibility: "", AzureZonesCacheDuration: 0 * time.Second,
DomainFilter: []string{}, CFAPIEndpoint: "",
ZoneIDFilter: []string{}, CFPassword: "",
ExcludeDomains: []string{}, CFUsername: "",
RegexDomainFilter: regexp.MustCompile(""),
RegexDomainExclusion: regexp.MustCompile(""),
TargetNetFilter: []string{},
ExcludeTargetNets: []string{},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
AWSZoneTagFilter: []string{},
AWSZoneMatchParent: false,
AWSAssumeRole: "",
AWSAssumeRoleExternalID: "",
AWSBatchChangeSize: 1000,
AWSBatchChangeSizeBytes: 32000,
AWSBatchChangeSizeValues: 1000,
AWSBatchChangeInterval: time.Second,
AWSEvaluateTargetHealth: true,
AWSAPIRetries: 3,
AWSPreferCNAME: false,
AWSZoneCacheDuration: 0 * time.Second,
AWSSDServiceCleanup: false,
AWSSDCreateTag: map[string]string{},
AWSDynamoDBRegion: "",
AWSDynamoDBTable: "external-dns",
AzureConfigFile: "/etc/kubernetes/azure.json",
AzureResourceGroup: "",
AzureSubscriptionID: "",
AzureZonesCacheDuration: 0 * time.Second,
CloudflareProxied: false,
CloudflareCustomHostnames: false,
CloudflareCustomHostnamesMinTLSVersion: "1.0",
CloudflareCustomHostnamesCertificateAuthority: "google", CloudflareCustomHostnamesCertificateAuthority: "google",
CloudflareCustomHostnames: false,
CloudflareCustomHostnamesMinTLSVersion: "1.0",
CloudflareDNSRecordsPerPage: 100, CloudflareDNSRecordsPerPage: 100,
CloudflareProxied: false,
CloudflareRegionKey: "earth", CloudflareRegionKey: "earth",
CoreDNSPrefix: "/skydns/",
AkamaiServiceConsumerDomain: "", CombineFQDNAndAnnotation: false,
AkamaiClientToken: "", Compatibility: "",
AkamaiClientSecret: "", ConnectorSourceServer: "localhost:8080",
AkamaiAccessToken: "", CoreDNSPrefix: "/skydns/",
AkamaiEdgercSection: "", CRDSourceAPIVersion: "externaldns.k8s.io/v1alpha1",
AkamaiEdgercPath: "", CRDSourceKind: "DNSEndpoint",
OCIConfigFile: "/etc/kubernetes/oci.yaml", DefaultTargets: []string{},
OCIZoneScope: "GLOBAL", DigitalOceanAPIPageSize: 50,
OCIZoneCacheDuration: 0 * time.Second, DomainFilter: []string{},
InMemoryZones: []string{}, DryRun: false,
OVHEndpoint: "ovh-eu", ExcludeDNSRecordTypes: []string{},
OVHApiRateLimit: 20, ExcludeDomains: []string{},
OVHEnableCNAMERelative: false, ExcludeTargetNets: []string{},
PDNSServer: "http://localhost:8081", ExcludeUnschedulable: true,
PDNSServerID: "localhost", ExoscaleAPIEnvironment: "api",
PDNSAPIKey: "", ExoscaleAPIKey: "",
PDNSSkipTLSVerify: false, ExoscaleAPISecret: "",
PodSourceDomain: "", ExoscaleAPIZone: "ch-gva-2",
TLSCA: "", ExposeInternalIPV6: true,
TLSClientCert: "", FQDNTemplate: "",
TLSClientCertKey: "", GatewayLabelFilter: "",
Policy: "sync", GatewayName: "",
Registry: "txt", GatewayNamespace: "",
TXTOwnerID: "default", GlooNamespaces: []string{"gloo-system"},
TXTPrefix: "", GoDaddyAPIKey: "",
TXTSuffix: "", GoDaddyOTE: false,
TXTCacheInterval: 0, GoDaddySecretKey: "",
TXTWildcardReplacement: "", GoDaddyTTL: 600,
MinEventSyncInterval: 5 * time.Second, GoogleBatchChangeInterval: time.Second,
TXTEncryptEnabled: false, GoogleBatchChangeSize: 1000,
TXTEncryptAESKey: "", GoogleProject: "",
TXTNewFormatOnly: false, GoogleZoneVisibility: "",
Interval: time.Minute, IBMCloudConfigFile: "/etc/kubernetes/ibmcloud.json",
Once: false, IBMCloudProxied: false,
DryRun: false, IgnoreHostnameAnnotation: false,
UpdateEvents: false, IgnoreIngressRulesSpec: false,
LogFormat: "text", IgnoreIngressTLSSpec: false,
MetricsAddress: ":7979", IngressClassNames: nil,
LogLevel: logrus.InfoLevel.String(), InMemoryZones: []string{},
ExoscaleAPIEnvironment: "api", Interval: time.Minute,
ExoscaleAPIZone: "ch-gva-2", KubeConfig: "",
ExoscaleAPIKey: "", LabelFilter: labels.Everything().String(),
ExoscaleAPISecret: "", LogFormat: "text",
CRDSourceAPIVersion: "externaldns.k8s.io/v1alpha1", LogLevel: logrus.InfoLevel.String(),
CRDSourceKind: "DNSEndpoint", ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
ServiceTypeFilter: []string{}, MetricsAddress: ":7979",
CFAPIEndpoint: "", MinEventSyncInterval: 5 * time.Second,
CFUsername: "", Namespace: "",
CFPassword: "", NAT64Networks: []string{},
RFC2136Host: []string{""}, NS1Endpoint: "",
RFC2136Port: 0, NS1IgnoreSSL: false,
RFC2136Zone: []string{}, OCIConfigFile: "/etc/kubernetes/oci.yaml",
RFC2136Insecure: false, OCIZoneCacheDuration: 0 * time.Second,
RFC2136GSSTSIG: false, OCIZoneScope: "GLOBAL",
RFC2136KerberosRealm: "", Once: false,
RFC2136KerberosUsername: "", OVHApiRateLimit: 20,
RFC2136KerberosPassword: "", OVHEnableCNAMERelative: false,
RFC2136TSIGKeyName: "", OVHEndpoint: "ovh-eu",
RFC2136TSIGSecret: "", PDNSAPIKey: "",
RFC2136TSIGSecretAlg: "", PDNSServer: "http://localhost:8081",
RFC2136TAXFR: true, PDNSServerID: "localhost",
RFC2136MinTTL: 0, PDNSSkipTLSVerify: false,
RFC2136BatchChangeSize: 50, PiholeApiVersion: "5",
RFC2136UseTLS: false, PiholePassword: "",
RFC2136LoadBalancingStrategy: "disabled", PiholeServer: "",
RFC2136SkipTLSVerify: false, PiholeTLSInsecureSkipVerify: false,
NS1Endpoint: "", PluralCluster: "",
NS1IgnoreSSL: false, PluralProvider: "",
TransIPAccountName: "", PodSourceDomain: "",
TransIPPrivateKeyFile: "", Policy: "sync",
DigitalOceanAPIPageSize: 50, Provider: "",
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}, ProviderCacheTime: 0,
ExcludeDNSRecordTypes: []string{}, PublishHostIP: false,
GoDaddyAPIKey: "", PublishInternal: false,
GoDaddySecretKey: "", RegexDomainExclusion: regexp.MustCompile(""),
GoDaddyTTL: 600, RegexDomainFilter: regexp.MustCompile(""),
GoDaddyOTE: false, Registry: "txt",
IBMCloudProxied: false, RequestTimeout: time.Second * 30,
IBMCloudConfigFile: "/etc/kubernetes/ibmcloud.json", RFC2136BatchChangeSize: 50,
TencentCloudConfigFile: "/etc/kubernetes/tencent-cloud.json", RFC2136GSSTSIG: false,
TencentCloudZoneType: "", RFC2136Host: []string{""},
PiholeServer: "", RFC2136Insecure: false,
PiholePassword: "", RFC2136KerberosPassword: "",
PiholeTLSInsecureSkipVerify: false, RFC2136KerberosRealm: "",
PiholeApiVersion: "5", RFC2136KerberosUsername: "",
PluralCluster: "", RFC2136LoadBalancingStrategy: "disabled",
PluralProvider: "", RFC2136MinTTL: 0,
WebhookProviderURL: "http://localhost:8888", RFC2136Port: 0,
WebhookProviderReadTimeout: 5 * time.Second, RFC2136SkipTLSVerify: false,
WebhookProviderWriteTimeout: 10 * time.Second, RFC2136TAXFR: true,
WebhookServer: false, RFC2136TSIGKeyName: "",
TraefikDisableLegacy: false, RFC2136TSIGSecret: "",
TraefikDisableNew: false, RFC2136TSIGSecretAlg: "",
NAT64Networks: []string{}, RFC2136UseTLS: false,
ExcludeUnschedulable: true, RFC2136Zone: []string{},
ServiceTypeFilter: []string{},
SkipperRouteGroupVersion: "zalando.org/v1",
Sources: nil,
TargetNetFilter: []string{},
TencentCloudConfigFile: "/etc/kubernetes/tencent-cloud.json",
TencentCloudZoneType: "",
TLSCA: "",
TLSClientCert: "",
TLSClientCertKey: "",
TraefikDisableLegacy: false,
TraefikDisableNew: false,
TransIPAccountName: "",
TransIPPrivateKeyFile: "",
TXTCacheInterval: 0,
TXTEncryptAESKey: "",
TXTEncryptEnabled: false,
TXTNewFormatOnly: false,
TXTOwnerID: "default",
TXTPrefix: "",
TXTSuffix: "",
TXTWildcardReplacement: "",
UpdateEvents: false,
WebhookProviderReadTimeout: 5 * time.Second,
WebhookProviderURL: "http://localhost:8888",
WebhookProviderWriteTimeout: 10 * time.Second,
WebhookServer: false,
ZoneIDFilter: []string{},
} }
// NewConfig returns new Config object // NewConfig returns new Config object
@ -453,40 +454,41 @@ func App(cfg *Config) *kingpin.Application {
app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(defaultConfig.SkipperRouteGroupVersion).StringVar(&cfg.SkipperRouteGroupVersion) app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(defaultConfig.SkipperRouteGroupVersion).StringVar(&cfg.SkipperRouteGroupVersion)
// Flags related to processing source // Flags related to processing source
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, pod, fake, connector, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, istio-gateway, istio-virtualservice, cloudfoundry, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress, f5-virtualserver, f5-transportserver, traefik-proxy)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "gateway-httproute", "gateway-grpcroute", "gateway-tlsroute", "gateway-tcproute", "gateway-udproute", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress", "f5-virtualserver", "f5-transportserver", "traefik-proxy")
app.Flag("openshift-router-name", "if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record.").StringVar(&cfg.OCPRouterName)
app.Flag("namespace", "Limit resources queried for endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
app.Flag("annotation-filter", "Filter resources queried for endpoints by annotation, using label selector semantics").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter)
app.Flag("label-filter", "Filter resources queried for endpoints by label selector; currently supported by source types crd, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, ingress, node, openshift-route, service and ambassador-host").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter)
app.Flag("ingress-class", "Require an Ingress to have this class name (defaults to any class; specify multiple times to allow more than one class)").StringsVar(&cfg.IngressClassNames)
app.Flag("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate)
app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation)
app.Flag("ignore-hostname-annotation", "Ignore hostname annotation when generating DNS names, valid only when --fqdn-template is set (default: false)").BoolVar(&cfg.IgnoreHostnameAnnotation)
app.Flag("ignore-non-host-network-pods", "Ignore pods not running on host network when using pod source (default: true)").BoolVar(&cfg.IgnoreNonHostNetworkPods)
app.Flag("ignore-ingress-tls-spec", "Ignore the spec.tls section in Ingress resources (default: false)").BoolVar(&cfg.IgnoreIngressTLSSpec)
app.Flag("gateway-name", "Limit Gateways of Route endpoints to a specific name (default: all names)").StringVar(&cfg.GatewayName)
app.Flag("gateway-namespace", "Limit Gateways of Route endpoints to a specific namespace (default: all namespaces)").StringVar(&cfg.GatewayNamespace)
app.Flag("gateway-label-filter", "Filter Gateways of Route endpoints via label selector (default: all gateways)").StringVar(&cfg.GatewayLabelFilter)
app.Flag("compatibility", "Process annotation semantics from legacy implementations (optional, options: mate, molecule, kops-dns-controller)").Default(defaultConfig.Compatibility).EnumVar(&cfg.Compatibility, "", "mate", "molecule", "kops-dns-controller")
app.Flag("ignore-ingress-rules-spec", "Ignore the spec.rules section in Ingress resources (default: false)").BoolVar(&cfg.IgnoreIngressRulesSpec)
app.Flag("pod-source-domain", "Domain to use for pods records (optional)").Default(defaultConfig.PodSourceDomain).StringVar(&cfg.PodSourceDomain)
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)
app.Flag("publish-host-ip", "Allow external-dns to publish host-ip for headless services (optional)").BoolVar(&cfg.PublishHostIP)
app.Flag("always-publish-not-ready-addresses", "Always publish also not ready addresses for headless services (optional)").BoolVar(&cfg.AlwaysPublishNotReadyAddresses) app.Flag("always-publish-not-ready-addresses", "Always publish also not ready addresses for headless services (optional)").BoolVar(&cfg.AlwaysPublishNotReadyAddresses)
app.Flag("annotation-filter", "Filter resources queried for endpoints by annotation, using label selector semantics").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter)
app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation)
app.Flag("compatibility", "Process annotation semantics from legacy implementations (optional, options: mate, molecule, kops-dns-controller)").Default(defaultConfig.Compatibility).EnumVar(&cfg.Compatibility, "", "mate", "molecule", "kops-dns-controller")
app.Flag("connector-source-server", "The server to connect for connector source, valid only when using connector source").Default(defaultConfig.ConnectorSourceServer).StringVar(&cfg.ConnectorSourceServer) app.Flag("connector-source-server", "The server to connect for connector source, valid only when using connector source").Default(defaultConfig.ConnectorSourceServer).StringVar(&cfg.ConnectorSourceServer)
app.Flag("crd-source-apiversion", "API version of the CRD for crd source, e.g. `externaldns.k8s.io/v1alpha1`, valid only when using crd source").Default(defaultConfig.CRDSourceAPIVersion).StringVar(&cfg.CRDSourceAPIVersion) app.Flag("crd-source-apiversion", "API version of the CRD for crd source, e.g. `externaldns.k8s.io/v1alpha1`, valid only when using crd source").Default(defaultConfig.CRDSourceAPIVersion).StringVar(&cfg.CRDSourceAPIVersion)
app.Flag("crd-source-kind", "Kind of the CRD for the crd source in API group and version specified by crd-source-apiversion").Default(defaultConfig.CRDSourceKind).StringVar(&cfg.CRDSourceKind) app.Flag("crd-source-kind", "Kind of the CRD for the crd source in API group and version specified by crd-source-apiversion").Default(defaultConfig.CRDSourceKind).StringVar(&cfg.CRDSourceKind)
app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter)
app.Flag("managed-record-types", "Record types to manage; specify multiple times to include many; (default: A, AAAA, CNAME) (supported records: A, AAAA, CNAME, NS, SRV, TXT)").Default("A", "AAAA", "CNAME").StringsVar(&cfg.ManagedDNSRecordTypes)
app.Flag("exclude-record-types", "Record types to exclude from management; specify multiple times to exclude many; (optional)").Default().StringsVar(&cfg.ExcludeDNSRecordTypes)
app.Flag("default-targets", "Set globally default host/IP that will apply as a target instead of source addresses. Specify multiple times for multiple targets (optional)").StringsVar(&cfg.DefaultTargets) app.Flag("default-targets", "Set globally default host/IP that will apply as a target instead of source addresses. Specify multiple times for multiple targets (optional)").StringsVar(&cfg.DefaultTargets)
app.Flag("target-net-filter", "Limit possible targets by a net filter; specify multiple times for multiple possible nets (optional)").StringsVar(&cfg.TargetNetFilter) app.Flag("exclude-record-types", "Record types to exclude from management; specify multiple times to exclude many; (optional)").Default().StringsVar(&cfg.ExcludeDNSRecordTypes)
app.Flag("exclude-target-net", "Exclude target nets (optional)").StringsVar(&cfg.ExcludeTargetNets) app.Flag("exclude-target-net", "Exclude target nets (optional)").StringsVar(&cfg.ExcludeTargetNets)
app.Flag("traefik-disable-legacy", "Disable listeners on Resources under the traefik.containo.us API Group").Default(strconv.FormatBool(defaultConfig.TraefikDisableLegacy)).BoolVar(&cfg.TraefikDisableLegacy)
app.Flag("traefik-disable-new", "Disable listeners on Resources under the traefik.io API Group").Default(strconv.FormatBool(defaultConfig.TraefikDisableNew)).BoolVar(&cfg.TraefikDisableNew)
app.Flag("nat64-networks", "Adding an A record for each AAAA record in NAT64-enabled networks; specify multiple times for multiple possible nets (optional)").StringsVar(&cfg.NAT64Networks)
app.Flag("exclude-unschedulable", "Exclude nodes that are considered unschedulable (default: true)").Default(strconv.FormatBool(defaultConfig.ExcludeUnschedulable)).BoolVar(&cfg.ExcludeUnschedulable) app.Flag("exclude-unschedulable", "Exclude nodes that are considered unschedulable (default: true)").Default(strconv.FormatBool(defaultConfig.ExcludeUnschedulable)).BoolVar(&cfg.ExcludeUnschedulable)
app.Flag("expose-internal-ipv6", "When using the node source, expose internal IPv6 addresses (optional). Default is true.").BoolVar(&cfg.ExposeInternalIPV6) app.Flag("expose-internal-ipv6", "When using the node source, expose internal IPv6 addresses (optional). Default is true.").BoolVar(&cfg.ExposeInternalIPV6)
app.Flag("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate)
app.Flag("gateway-label-filter", "Filter Gateways of Route endpoints via label selector (default: all gateways)").StringVar(&cfg.GatewayLabelFilter)
app.Flag("gateway-name", "Limit Gateways of Route endpoints to a specific name (default: all names)").StringVar(&cfg.GatewayName)
app.Flag("gateway-namespace", "Limit Gateways of Route endpoints to a specific namespace (default: all namespaces)").StringVar(&cfg.GatewayNamespace)
app.Flag("ignore-hostname-annotation", "Ignore hostname annotation when generating DNS names, valid only when --fqdn-template is set (default: false)").BoolVar(&cfg.IgnoreHostnameAnnotation)
app.Flag("ignore-ingress-rules-spec", "Ignore the spec.rules section in Ingress resources (default: false)").BoolVar(&cfg.IgnoreIngressRulesSpec)
app.Flag("ignore-ingress-tls-spec", "Ignore the spec.tls section in Ingress resources (default: false)").BoolVar(&cfg.IgnoreIngressTLSSpec)
app.Flag("ignore-non-host-network-pods", "Ignore pods not running on host network when using pod source (default: true)").BoolVar(&cfg.IgnoreNonHostNetworkPods)
app.Flag("ingress-class", "Require an Ingress to have this class name (defaults to any class; specify multiple times to allow more than one class)").StringsVar(&cfg.IngressClassNames)
app.Flag("label-filter", "Filter resources queried for endpoints by label selector; currently supported by source types crd, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, ingress, node, openshift-route, service and ambassador-host").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter)
managedRecordTypesHelp := fmt.Sprintf("Record types to manage; specify multiple times to include many; (default: %s) (supported records: A, AAAA, CNAME, NS, SRV, TXT)", strings.Join(defaultConfig.ManagedDNSRecordTypes, ","))
app.Flag("managed-record-types", managedRecordTypesHelp).Default(defaultConfig.ManagedDNSRecordTypes...).StringsVar(&cfg.ManagedDNSRecordTypes)
app.Flag("namespace", "Limit resources queried for endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
app.Flag("nat64-networks", "Adding an A record for each AAAA record in NAT64-enabled networks; specify multiple times for multiple possible nets (optional)").StringsVar(&cfg.NAT64Networks)
app.Flag("openshift-router-name", "if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record.").StringVar(&cfg.OCPRouterName)
app.Flag("pod-source-domain", "Domain to use for pods records (optional)").Default(defaultConfig.PodSourceDomain).StringVar(&cfg.PodSourceDomain)
app.Flag("publish-host-ip", "Allow external-dns to publish host-ip for headless services (optional)").BoolVar(&cfg.PublishHostIP)
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)
app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter)
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, pod, fake, connector, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, istio-gateway, istio-virtualservice, cloudfoundry, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress, f5-virtualserver, f5-transportserver, traefik-proxy)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "gateway-httproute", "gateway-grpcroute", "gateway-tlsroute", "gateway-tcproute", "gateway-udproute", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress", "f5-virtualserver", "f5-transportserver", "traefik-proxy")
app.Flag("target-net-filter", "Limit possible targets by a net filter; specify multiple times for multiple possible nets (optional)").StringsVar(&cfg.TargetNetFilter)
app.Flag("traefik-disable-legacy", "Disable listeners on Resources under the traefik.containo.us API Group").Default(strconv.FormatBool(defaultConfig.TraefikDisableLegacy)).BoolVar(&cfg.TraefikDisableLegacy)
app.Flag("traefik-disable-new", "Disable listeners on Resources under the traefik.io API Group").Default(strconv.FormatBool(defaultConfig.TraefikDisableNew)).BoolVar(&cfg.TraefikDisableNew)
// Flags related to providers // Flags related to providers
providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "civo", "cloudflare", "coredns", "digitalocean", "dnsimple", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rfc2136", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "webhook"} providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "civo", "cloudflare", "coredns", "digitalocean", "dnsimple", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rfc2136", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "webhook"}

View File

@ -262,9 +262,11 @@ func (p *Plan) Calculate() *Plan {
} }
plan := &Plan{ plan := &Plan{
Current: p.Current, Current: p.Current,
Desired: p.Desired, Desired: p.Desired,
Changes: changes, Changes: changes,
// The default for ExternalDNS is to always only consider A/AAAA and CNAMEs.
// Everything else is an add on or something to be considered.
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}, ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
} }

View File

@ -367,9 +367,22 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificNoChange() {
} }
changes := p.Calculate().Changes changes := p.Calculate().Changes
if changes.HasChanges() { suite.Assert().False(changes.HasChanges())
suite.T().Fatal("test should not have changes") }
func (suite *PlanTestSuite) TestHasChanges() {
current := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificTrue}
desired := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificFalse}
p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
} }
changes := p.Calculate().Changes
suite.Assert().True(changes.HasChanges())
} }
func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificRemoval() { func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificRemoval() {