diff --git a/controller/execute_test.go b/controller/execute_test.go index 8d761bccb..442e3d7b7 100644 --- a/controller/execute_test.go +++ b/controller/execute_test.go @@ -18,6 +18,7 @@ package controller import ( "bytes" + "context" "fmt" "net" "net/http" @@ -29,10 +30,9 @@ import ( "testing" "time" - "context" - 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" "sigs.k8s.io/external-dns/plan" @@ -219,18 +219,45 @@ func TestHandleSigterm(t *testing.T) { } } -func TestServeMetrics(t *testing.T) { - l, _ := net.Listen("tcp", ":0") - _ = l.Close() - _, port, _ := net.SplitHostPort(l.Addr().String()) +func getRandomPort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } - go serveMetrics(fmt.Sprintf(":%s", port)) - resp, err := http.Get(fmt.Sprintf("http://localhost:%s", port) + "/healthz") - assert.NoError(t, err) + l, err := net.ListenTCP("tcp", addr) + if err != nil { + 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) - resp, err = http.Get(fmt.Sprintf("http://localhost:%s", port) + "/metrics") - assert.NoError(t, err) + resp, err = http.Get(fmt.Sprintf("http://%s/metrics", addresse)) + require.NoError(t, err) 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 { - return nil } diff --git a/docs/flags.md b/docs/flags.md index 3cf19d4d8..06af52774 100644 --- a/docs/flags.md +++ b/docs/flags.md @@ -17,40 +17,40 @@ | `--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) | | `--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) | +| `--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 | | `--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 | -| `--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) | -| `--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) | -| `--[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-]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-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) | diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 5e9aac21a..b8e8d5c9b 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -217,167 +217,168 @@ type Config struct { } var defaultConfig = &Config{ - APIServerURL: "", - KubeConfig: "", - RequestTimeout: time.Second * 30, - DefaultTargets: []string{}, - GlooNamespaces: []string{"gloo-system"}, - SkipperRouteGroupVersion: "zalando.org/v1", - Sources: nil, - Namespace: "", - AnnotationFilter: "", - LabelFilter: labels.Everything().String(), - IngressClassNames: nil, - FQDNTemplate: "", - CombineFQDNAndAnnotation: false, - IgnoreHostnameAnnotation: false, - IgnoreIngressTLSSpec: false, - IgnoreIngressRulesSpec: false, - GatewayName: "", - GatewayNamespace: "", - GatewayLabelFilter: "", - Compatibility: "", - PublishInternal: false, - PublishHostIP: false, - ExposeInternalIPV6: true, - ConnectorSourceServer: "localhost:8080", - Provider: "", - ProviderCacheTime: 0, - GoogleProject: "", - GoogleBatchChangeSize: 1000, - GoogleBatchChangeInterval: time.Second, - GoogleZoneVisibility: "", - DomainFilter: []string{}, - ZoneIDFilter: []string{}, - ExcludeDomains: []string{}, - 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", + AkamaiAccessToken: "", + AkamaiClientSecret: "", + AkamaiClientToken: "", + AkamaiEdgercPath: "", + AkamaiEdgercSection: "", + AkamaiServiceConsumerDomain: "", + AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", + AnnotationFilter: "", + APIServerURL: "", + AWSAPIRetries: 3, + AWSAssumeRole: "", + AWSAssumeRoleExternalID: "", + AWSBatchChangeInterval: time.Second, + AWSBatchChangeSize: 1000, + AWSBatchChangeSizeBytes: 32000, + AWSBatchChangeSizeValues: 1000, + AWSDynamoDBRegion: "", + AWSDynamoDBTable: "external-dns", + AWSEvaluateTargetHealth: true, + AWSPreferCNAME: false, + AWSSDCreateTag: map[string]string{}, + AWSSDServiceCleanup: false, + AWSZoneCacheDuration: 0 * time.Second, + AWSZoneMatchParent: false, + AWSZoneTagFilter: []string{}, + AWSZoneType: "", + AzureConfigFile: "/etc/kubernetes/azure.json", + AzureResourceGroup: "", + AzureSubscriptionID: "", + AzureZonesCacheDuration: 0 * time.Second, + CFAPIEndpoint: "", + CFPassword: "", + CFUsername: "", CloudflareCustomHostnamesCertificateAuthority: "google", + CloudflareCustomHostnames: false, + CloudflareCustomHostnamesMinTLSVersion: "1.0", CloudflareDNSRecordsPerPage: 100, + CloudflareProxied: false, CloudflareRegionKey: "earth", - CoreDNSPrefix: "/skydns/", - AkamaiServiceConsumerDomain: "", - AkamaiClientToken: "", - AkamaiClientSecret: "", - AkamaiAccessToken: "", - AkamaiEdgercSection: "", - AkamaiEdgercPath: "", - OCIConfigFile: "/etc/kubernetes/oci.yaml", - OCIZoneScope: "GLOBAL", - OCIZoneCacheDuration: 0 * time.Second, - InMemoryZones: []string{}, - OVHEndpoint: "ovh-eu", - OVHApiRateLimit: 20, - OVHEnableCNAMERelative: false, - PDNSServer: "http://localhost:8081", - PDNSServerID: "localhost", - PDNSAPIKey: "", - PDNSSkipTLSVerify: false, - PodSourceDomain: "", - TLSCA: "", - TLSClientCert: "", - TLSClientCertKey: "", - Policy: "sync", - Registry: "txt", - TXTOwnerID: "default", - TXTPrefix: "", - TXTSuffix: "", - TXTCacheInterval: 0, - TXTWildcardReplacement: "", - MinEventSyncInterval: 5 * time.Second, - TXTEncryptEnabled: false, - TXTEncryptAESKey: "", - TXTNewFormatOnly: false, - Interval: time.Minute, - Once: false, - DryRun: false, - UpdateEvents: false, - LogFormat: "text", - MetricsAddress: ":7979", - LogLevel: logrus.InfoLevel.String(), - ExoscaleAPIEnvironment: "api", - ExoscaleAPIZone: "ch-gva-2", - ExoscaleAPIKey: "", - ExoscaleAPISecret: "", - CRDSourceAPIVersion: "externaldns.k8s.io/v1alpha1", - CRDSourceKind: "DNSEndpoint", - ServiceTypeFilter: []string{}, - CFAPIEndpoint: "", - CFUsername: "", - CFPassword: "", - RFC2136Host: []string{""}, - RFC2136Port: 0, - RFC2136Zone: []string{}, - RFC2136Insecure: false, - RFC2136GSSTSIG: false, - RFC2136KerberosRealm: "", - RFC2136KerberosUsername: "", - RFC2136KerberosPassword: "", - RFC2136TSIGKeyName: "", - RFC2136TSIGSecret: "", - RFC2136TSIGSecretAlg: "", - RFC2136TAXFR: true, - RFC2136MinTTL: 0, - RFC2136BatchChangeSize: 50, - RFC2136UseTLS: false, - RFC2136LoadBalancingStrategy: "disabled", - RFC2136SkipTLSVerify: false, - NS1Endpoint: "", - NS1IgnoreSSL: false, - TransIPAccountName: "", - TransIPPrivateKeyFile: "", - DigitalOceanAPIPageSize: 50, - ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}, - ExcludeDNSRecordTypes: []string{}, - GoDaddyAPIKey: "", - GoDaddySecretKey: "", - GoDaddyTTL: 600, - GoDaddyOTE: false, - IBMCloudProxied: false, - IBMCloudConfigFile: "/etc/kubernetes/ibmcloud.json", - TencentCloudConfigFile: "/etc/kubernetes/tencent-cloud.json", - TencentCloudZoneType: "", - PiholeServer: "", - PiholePassword: "", - PiholeTLSInsecureSkipVerify: false, - PiholeApiVersion: "5", - PluralCluster: "", - PluralProvider: "", - WebhookProviderURL: "http://localhost:8888", - WebhookProviderReadTimeout: 5 * time.Second, - WebhookProviderWriteTimeout: 10 * time.Second, - WebhookServer: false, - TraefikDisableLegacy: false, - TraefikDisableNew: false, - NAT64Networks: []string{}, - ExcludeUnschedulable: true, + + CombineFQDNAndAnnotation: false, + Compatibility: "", + ConnectorSourceServer: "localhost:8080", + CoreDNSPrefix: "/skydns/", + CRDSourceAPIVersion: "externaldns.k8s.io/v1alpha1", + CRDSourceKind: "DNSEndpoint", + DefaultTargets: []string{}, + DigitalOceanAPIPageSize: 50, + DomainFilter: []string{}, + DryRun: false, + ExcludeDNSRecordTypes: []string{}, + ExcludeDomains: []string{}, + ExcludeTargetNets: []string{}, + ExcludeUnschedulable: true, + ExoscaleAPIEnvironment: "api", + ExoscaleAPIKey: "", + ExoscaleAPISecret: "", + ExoscaleAPIZone: "ch-gva-2", + ExposeInternalIPV6: true, + FQDNTemplate: "", + GatewayLabelFilter: "", + GatewayName: "", + GatewayNamespace: "", + GlooNamespaces: []string{"gloo-system"}, + GoDaddyAPIKey: "", + GoDaddyOTE: false, + GoDaddySecretKey: "", + GoDaddyTTL: 600, + GoogleBatchChangeInterval: time.Second, + GoogleBatchChangeSize: 1000, + GoogleProject: "", + GoogleZoneVisibility: "", + IBMCloudConfigFile: "/etc/kubernetes/ibmcloud.json", + IBMCloudProxied: false, + IgnoreHostnameAnnotation: false, + IgnoreIngressRulesSpec: false, + IgnoreIngressTLSSpec: false, + IngressClassNames: nil, + InMemoryZones: []string{}, + Interval: time.Minute, + KubeConfig: "", + LabelFilter: labels.Everything().String(), + LogFormat: "text", + LogLevel: logrus.InfoLevel.String(), + ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}, + MetricsAddress: ":7979", + MinEventSyncInterval: 5 * time.Second, + Namespace: "", + NAT64Networks: []string{}, + NS1Endpoint: "", + NS1IgnoreSSL: false, + OCIConfigFile: "/etc/kubernetes/oci.yaml", + OCIZoneCacheDuration: 0 * time.Second, + OCIZoneScope: "GLOBAL", + Once: false, + OVHApiRateLimit: 20, + OVHEnableCNAMERelative: false, + OVHEndpoint: "ovh-eu", + PDNSAPIKey: "", + PDNSServer: "http://localhost:8081", + PDNSServerID: "localhost", + PDNSSkipTLSVerify: false, + PiholeApiVersion: "5", + PiholePassword: "", + PiholeServer: "", + PiholeTLSInsecureSkipVerify: false, + PluralCluster: "", + PluralProvider: "", + PodSourceDomain: "", + Policy: "sync", + Provider: "", + ProviderCacheTime: 0, + PublishHostIP: false, + PublishInternal: false, + RegexDomainExclusion: regexp.MustCompile(""), + RegexDomainFilter: regexp.MustCompile(""), + Registry: "txt", + RequestTimeout: time.Second * 30, + RFC2136BatchChangeSize: 50, + RFC2136GSSTSIG: false, + RFC2136Host: []string{""}, + RFC2136Insecure: false, + RFC2136KerberosPassword: "", + RFC2136KerberosRealm: "", + RFC2136KerberosUsername: "", + RFC2136LoadBalancingStrategy: "disabled", + RFC2136MinTTL: 0, + RFC2136Port: 0, + RFC2136SkipTLSVerify: false, + RFC2136TAXFR: true, + RFC2136TSIGKeyName: "", + RFC2136TSIGSecret: "", + RFC2136TSIGSecretAlg: "", + RFC2136UseTLS: false, + 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 @@ -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) // 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("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("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("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("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("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("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 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"} diff --git a/plan/plan.go b/plan/plan.go index 6124b7640..c77f2f2a1 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -262,9 +262,11 @@ func (p *Plan) Calculate() *Plan { } plan := &Plan{ - Current: p.Current, - Desired: p.Desired, - Changes: changes, + Current: p.Current, + Desired: p.Desired, + 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}, } diff --git a/plan/plan_test.go b/plan/plan_test.go index 5aecc8632..af1f5c2c0 100644 --- a/plan/plan_test.go +++ b/plan/plan_test.go @@ -367,9 +367,22 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificNoChange() { } changes := p.Calculate().Changes - if changes.HasChanges() { - suite.T().Fatal("test should not have changes") + suite.Assert().False(changes.HasChanges()) +} + +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() {