diff --git a/docs/annotations/annotations.md b/docs/annotations/annotations.md index 7c2d1f9023..d6b055da4a 100644 --- a/docs/annotations/annotations.md +++ b/docs/annotations/annotations.md @@ -10,7 +10,7 @@ The following table documents which sources support which annotations: | Connector | | | | | | | | Contour | Yes | Yes[^1] | | Yes | Yes | Yes | | CloudFoundry | | | | | | | -| CRD | | | | | | | +| CRD | | Yes | | Yes | Yes | Yes | | F5 | | | | Yes | Yes | | | Gateway | Yes | Yes[^1] | | Yes[^4] | Yes | Yes | | Gloo | | | | Yes | Yes[^5] | Yes[^5] | @@ -99,7 +99,13 @@ It must be between 1 and 2,147,483,647 seconds. ## Provider-specific annotations -Some providers define their own annotations. Cloud-specific annotations have keys prefixed as follows: +Provider-specific annotations are prefixed by `external-dns.alpha.kubernetes.io/` +and the provider's name, for example `external-dns.alpha.kubernetes.io/aws-`. + +Individual provider implementations should be examined to identify the additional +configuration offered via supported annotations. + +Currently, supported Cloud-specific annotations include the following prefixes: | Cloud | Annotation prefix | |------------|------------------------------------------------| diff --git a/docs/sources/crd/dnsendpoint-aws-example.yaml b/docs/sources/crd/dnsendpoint-aws-example.yaml index e437f0f047..92d0e50f61 100644 --- a/docs/sources/crd/dnsendpoint-aws-example.yaml +++ b/docs/sources/crd/dnsendpoint-aws-example.yaml @@ -2,17 +2,10 @@ apiVersion: externaldns.k8s.io/v1alpha1 kind: DNSEndpoint metadata: name: examplednsrecord -spec: - endpoints: - - dnsName: subdomain.foo.bar.com - providerSpecific: - - name: "aws/failover" - value: "PRIMARY" - - name: "aws/health-check-id" - value: "asdf1234-as12-as12-as12-asdf12345678" - - name: "aws/evaluate-target-health" - value: "true" - recordType: CNAME - setIdentifier: some-unique-id - targets: - - other-subdomain.foo.bar.com + annotations: + external-dns.alpha.kubernetes.io/hostname: "subdomain.foo.bar.com" + external-dns.alpha.kubernetes.io/target: "other-subdomain.foo.bar.com" + external-dns.alpha.kubernetes.io/set-identifier: "some-unique-id" + external-dns.alpha.kubernetes.io/aws-failover: "PRIMARY" + external-dns.alpha.kubernetes.io/aws-health-check-id: "asdf1234-as12-as12-as12-asdf12345678" + external-dns.alpha.kubernetes.io/aws-evaluate-target-health: "true" \ No newline at end of file diff --git a/docs/sources/crd/dnsendpoint-example.yaml b/docs/sources/crd/dnsendpoint-example.yaml index 2ed7d7fa01..c7e41ab4f3 100644 --- a/docs/sources/crd/dnsendpoint-example.yaml +++ b/docs/sources/crd/dnsendpoint-example.yaml @@ -2,14 +2,8 @@ apiVersion: externaldns.k8s.io/v1alpha1 kind: DNSEndpoint metadata: name: examplednsrecord -spec: - endpoints: - - dnsName: foo.bar.com - recordTTL: 180 - recordType: A - targets: - - 192.168.99.216 - # Provider specific configurations are set like an annotation would on other sources - providerSpecific: - - name: external-dns.alpha.kubernetes.io/cloudflare-proxied - value: "true" + annotations: + external-dns.alpha.kubernetes.io/hostname: foo.bar.com + external-dns.alpha.kubernetes.io/target: 192.168.99.216 + external-dns.alpha.kubernetes.io/ttl: "180" + external-dns.alpha.kubernetes.io/cloudflare-proxied: "true" \ No newline at end of file diff --git a/docs/tutorials/webhook-provider.md b/docs/tutorials/webhook-provider.md index f3115506bf..b305ddc648 100644 --- a/docs/tutorials/webhook-provider.md +++ b/docs/tutorials/webhook-provider.md @@ -45,12 +45,6 @@ The default recommended port for the provider endpoints is `8888`, and should li The default recommended port for the exposed endpoints is `8080`, and it should be bound to all interfaces (`0.0.0.0`) -## Custom Annotations - -The Webhook provider supports custom annotations for DNS records. This feature allows users to define additional configuration options for DNS records managed by the Webhook provider. Custom annotations are defined using the annotation format `external-dns.alpha.kubernetes.io/webhook-`. - -Custom annotations can be used to influence DNS record creation and updates. Providers implementing the Webhook API should document the custom annotations they support and how they affect DNS record management. - ## Provider registry To simplify the discovery of providers, we will accept pull requests that will add links to providers in this documentation. This list will only serve the purpose of simplifying finding providers and will not constitute an official endorsement of any of the externally implemented providers unless otherwise stated. diff --git a/provider/aws/aws.go b/provider/aws/aws.go index db1ce06c16..c9403837a0 100644 --- a/provider/aws/aws.go +++ b/provider/aws/aws.go @@ -48,19 +48,19 @@ const ( route53PageSize int32 = 300 // providerSpecificAlias specifies whether a CNAME endpoint maps to an AWS ALIAS record. providerSpecificAlias = "alias" - providerSpecificTargetHostedZone = "aws/target-hosted-zone" + providerSpecificTargetHostedZone = "aws-target-hosted-zone" // providerSpecificEvaluateTargetHealth specifies whether an AWS ALIAS record // has the EvaluateTargetHealth field set to true. Present iff the endpoint // has a `providerSpecificAlias` value of `true`. - providerSpecificEvaluateTargetHealth = "aws/evaluate-target-health" - providerSpecificWeight = "aws/weight" - providerSpecificRegion = "aws/region" - providerSpecificFailover = "aws/failover" - providerSpecificGeolocationContinentCode = "aws/geolocation-continent-code" - providerSpecificGeolocationCountryCode = "aws/geolocation-country-code" - providerSpecificGeolocationSubdivisionCode = "aws/geolocation-subdivision-code" - providerSpecificMultiValueAnswer = "aws/multi-value-answer" - providerSpecificHealthCheckID = "aws/health-check-id" + providerSpecificEvaluateTargetHealth = "aws-evaluate-target-health" + providerSpecificWeight = "aws-weight" + providerSpecificRegion = "aws-region" + providerSpecificFailover = "aws-failover" + providerSpecificGeolocationContinentCode = "aws-geolocation-continent-code" + providerSpecificGeolocationCountryCode = "aws-geolocation-country-code" + providerSpecificGeolocationSubdivisionCode = "aws-geolocation-subdivision-code" + providerSpecificMultiValueAnswer = "aws-multi-value-answer" + providerSpecificHealthCheckID = "aws-health-check-id" sameZoneAlias = "same-zone" ) diff --git a/provider/cloudflare/cloudflare.go b/provider/cloudflare/cloudflare.go index 34a40197cb..5e0ce40e71 100644 --- a/provider/cloudflare/cloudflare.go +++ b/provider/cloudflare/cloudflare.go @@ -32,7 +32,6 @@ import ( "sigs.k8s.io/external-dns/endpoint" "sigs.k8s.io/external-dns/plan" "sigs.k8s.io/external-dns/provider" - "sigs.k8s.io/external-dns/source" ) const ( @@ -44,6 +43,9 @@ const ( cloudFlareUpdate = "UPDATE" // defaultCloudFlareRecordTTL 1 = automatic defaultCloudFlareRecordTTL = 1 + + // providerSpecificProxied specifies whether traffic will go through Cloudflare + providerSpecificProxied = "cloudflare-proxied" ) // We have to use pointers to bools now, as the upstream cloudflare-go library requires them @@ -423,7 +425,7 @@ func (p *CloudFlareProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([] if proxied { e.RecordTTL = 0 } - e.SetProviderSpecificProperty(source.CloudflareProxiedKey, strconv.FormatBool(proxied)) + e.SetProviderSpecificProperty(providerSpecificProxied, strconv.FormatBool(proxied)) adjustedEndpoints = append(adjustedEndpoints, e) } @@ -520,10 +522,10 @@ func shouldBeProxied(endpoint *endpoint.Endpoint, proxiedByDefault bool) bool { proxied := proxiedByDefault for _, v := range endpoint.ProviderSpecific { - if v.Name == source.CloudflareProxiedKey { + if v.Name == providerSpecificProxied { b, err := strconv.ParseBool(v.Value) if err != nil { - log.Errorf("Failed to parse annotation [%s]: %v", source.CloudflareProxiedKey, err) + log.Errorf("Failed parsing value of %s: %v", providerSpecificProxied, err) } else { proxied = b } @@ -568,7 +570,7 @@ func groupByNameAndType(records []cloudflare.DNSRecord) []*endpoint.Endpoint { records[0].Type, endpoint.TTL(records[0].TTL), targets...). - WithProviderSpecific(source.CloudflareProxiedKey, strconv.FormatBool(*records[0].Proxied)), + WithProviderSpecific(providerSpecificProxied, strconv.FormatBool(*records[0].Proxied)), ) } diff --git a/provider/cloudflare/cloudflare_test.go b/provider/cloudflare/cloudflare_test.go index 93477d7488..e946887409 100644 --- a/provider/cloudflare/cloudflare_test.go +++ b/provider/cloudflare/cloudflare_test.go @@ -479,7 +479,7 @@ func TestCloudflareProxiedOverrideTrue(t *testing.T) { Targets: endpoint.Targets{"127.0.0.1"}, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "true", }, }, @@ -511,7 +511,7 @@ func TestCloudflareProxiedOverrideFalse(t *testing.T) { Targets: endpoint.Targets{"127.0.0.1"}, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "false", }, }, @@ -543,7 +543,7 @@ func TestCloudflareProxiedOverrideIllegal(t *testing.T) { Targets: endpoint.Targets{"127.0.0.1"}, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "asfasdfa", }, }, @@ -594,7 +594,7 @@ func TestCloudflareSetProxied(t *testing.T) { Targets: endpoint.Targets{"127.0.0.1"}, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "true", }, }, @@ -964,7 +964,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "false", }, }, @@ -998,7 +998,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "false", }, }, @@ -1046,7 +1046,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "false", }, }, @@ -1059,7 +1059,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "false", }, }, @@ -1100,7 +1100,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "false", }, }, @@ -1113,7 +1113,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "false", }, }, @@ -1154,7 +1154,7 @@ func TestCloudflareGroupByNameAndType(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "false", }, }, @@ -1293,7 +1293,7 @@ func TestCloudflareComplexUpdate(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "true", }, }, @@ -1388,7 +1388,7 @@ func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: providerSpecificProxied, Value: "true", }, }, diff --git a/provider/ibmcloud/ibmcloud.go b/provider/ibmcloud/ibmcloud.go index f442c76a36..57749bc094 100644 --- a/provider/ibmcloud/ibmcloud.go +++ b/provider/ibmcloud/ibmcloud.go @@ -965,7 +965,7 @@ func shouldBeProxied(endpoint *endpoint.Endpoint, proxiedByDefault bool) bool { if v.Name == proxyFilter { b, err := strconv.ParseBool(v.Value) if err != nil { - log.Errorf("Failed to parse annotation [%s]: %v", proxyFilter, err) + log.Errorf("Failed parsing value of %s: %v", proxyFilter, err) } else { proxied = b } diff --git a/provider/ibmcloud/ibmcloud_test.go b/provider/ibmcloud/ibmcloud_test.go index 6c94db504c..99292b5ec2 100644 --- a/provider/ibmcloud/ibmcloud_test.go +++ b/provider/ibmcloud/ibmcloud_test.go @@ -161,7 +161,7 @@ func newTestIBMCloudProvider(private bool) *IBMCloudProvider { Targets: endpoint.Targets{"4.3.2.1"}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "ibmcloud-vpc", + Name: vpcFilter, Value: "crn:v1:staging:public:is:us-south:a/0821fa9f9ebcc7b7c9a0d6e9bf9442a4::vpc:be33cdad-9a03-4bfa-82ca-eadb9f1de688", }, }, @@ -223,7 +223,7 @@ func TestPublic_ApplyChanges(t *testing.T) { Targets: endpoint.NewTargets("4.3.2.1"), ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "ibmcloud-proxied", + Name: proxyFilter, Value: "false", }, }, @@ -237,7 +237,7 @@ func TestPublic_ApplyChanges(t *testing.T) { Targets: endpoint.NewTargets("1.2.3.4"), ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "ibmcloud-proxied", + Name: proxyFilter, Value: "false", }, }, @@ -251,7 +251,7 @@ func TestPublic_ApplyChanges(t *testing.T) { Targets: endpoint.NewTargets("1.2.3.4", "5.6.7.8"), ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "ibmcloud-proxied", + Name: proxyFilter, Value: "true", }, }, @@ -284,7 +284,7 @@ func TestPrivate_ApplyChanges(t *testing.T) { Targets: endpoint.NewTargets("4.3.2.1"), ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "ibmcloud-vpc", + Name: vpcFilter, Value: "crn:v1:staging:public:is:us-south:a/0821fa9f9ebcc7b7c9a0d6e9bf9442a4::vpc:be33cdad-9a03-4bfa-82ca-eadb9f1de688", }, }, @@ -352,7 +352,7 @@ func TestAdjustEndpoints(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ { - Name: "ibmcloud-proxied", + Name: proxyFilter, Value: "1", }, }, @@ -364,7 +364,7 @@ func TestAdjustEndpoints(t *testing.T) { assert.Equal(t, endpoint.TTL(0), ep[0].RecordTTL) assert.Equal(t, "test.example.com", ep[0].DNSName) - proxied, _ := ep[0].GetProviderSpecificProperty("ibmcloud-proxied") + proxied, _ := ep[0].GetProviderSpecificProperty(proxyFilter) assert.Equal(t, "true", proxied) } diff --git a/provider/scaleway/scaleway.go b/provider/scaleway/scaleway.go index f383b92aca..ce5e141745 100644 --- a/provider/scaleway/scaleway.go +++ b/provider/scaleway/scaleway.go @@ -36,7 +36,7 @@ import ( const ( scalewyRecordTTL uint32 = 300 scalewayDefaultPriority uint32 = 0 - scalewayPriorityKey string = "scw/priority" + scalewayPriorityKey string = "scw-priority" ) // ScalewayProvider implements the DNS provider for Scaleway DNS diff --git a/source/ambassador_host_test.go b/source/ambassador_host_test.go index 970bb2a19c..940e863c76 100644 --- a/source/ambassador_host_test.go +++ b/source/ambassador_host_test.go @@ -247,7 +247,7 @@ func TestAmbassadorHostSource(t *testing.T) { Name: "basic-host", Annotations: map[string]string{ ambHostAnnotation: hostAnnotation, - CloudflareProxiedKey: "true", + "external-dns.alpha.kubernetes.io/cloudflare-proxied": "true", }, }, Spec: &ambassador.HostSpec{ @@ -272,7 +272,7 @@ func TestAmbassadorHostSource(t *testing.T) { RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.1.1.1"}, ProviderSpecific: endpoint.ProviderSpecific{{ - Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Name: "cloudflare-proxied", Value: "true", }}, }, diff --git a/source/crd.go b/source/crd.go index d15b176edc..08a2ca1afe 100644 --- a/source/crd.go +++ b/source/crd.go @@ -180,8 +180,14 @@ func (cs *crdSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error } for _, dnsEndpoint := range result.Items { + resource := fmt.Sprintf("crd/%s/%s", dnsEndpoint.Namespace, dnsEndpoint.Name) + providerSpecific, setIdentifier := getProviderSpecificAnnotations(dnsEndpoint.Annotations) + ttl := getTTLFromAnnotations(dnsEndpoint.Annotations, resource) + targets := getTargetsFromTargetAnnotation(dnsEndpoint.Annotations) + for _, host := range getHostnamesFromAnnotations(dnsEndpoint.Annotations) { + endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific, setIdentifier, resource)...) + } // Make sure that all endpoints have targets for A or CNAME type - crdEndpoints := []*endpoint.Endpoint{} for _, ep := range dnsEndpoint.Spec.Endpoints { if (ep.RecordType == "CNAME" || ep.RecordType == "A" || ep.RecordType == "AAAA") && len(ep.Targets) < 1 { log.Warnf("Endpoint %s with DNSName %s has an empty list of targets", dnsEndpoint.ObjectMeta.Name, ep.DNSName) @@ -207,12 +213,36 @@ func (cs *crdSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error if ep.Labels == nil { ep.Labels = endpoint.NewLabels() } + ep.Labels[endpoint.ResourceLabelKey] = resource + if ttl.IsConfigured() { + ep.RecordTTL = ttl + } - crdEndpoints = append(crdEndpoints, ep) - } + // Fixup legacy naming of some provider specific properties + for index := range ep.ProviderSpecific { + property := &ep.ProviderSpecific[index] + if strings.HasPrefix(property.Name, "aws/") || strings.HasPrefix(property.Name, "scw/") { + property.Name = strings.Replace(property.Name, "/", "-", 1) + } + } + for _, property := range providerSpecific { + present := false + for _, search := range ep.ProviderSpecific { + if property.Name == search.Name { + present = true + break + } + } + if !present { + ep.ProviderSpecific = append(ep.ProviderSpecific, property) + } + } + if setIdentifier != "" { + ep.SetIdentifier = setIdentifier + } - cs.setResourceLabel(&dnsEndpoint, crdEndpoints) - endpoints = append(endpoints, crdEndpoints...) + endpoints = append(endpoints, ep) + } if dnsEndpoint.Status.ObservedGeneration == dnsEndpoint.Generation { continue @@ -229,12 +259,6 @@ func (cs *crdSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error return endpoints, nil } -func (cs *crdSource) setResourceLabel(crd *endpoint.DNSEndpoint, endpoints []*endpoint.Endpoint) { - for _, ep := range endpoints { - ep.Labels[endpoint.ResourceLabelKey] = fmt.Sprintf("crd/%s/%s", crd.ObjectMeta.Namespace, crd.ObjectMeta.Name) - } -} - func (cs *crdSource) watch(ctx context.Context, opts *metav1.ListOptions) (watch.Interface, error) { opts.Watch = true return cs.crdClient.Get(). diff --git a/source/crd_test.go b/source/crd_test.go index f0adcb183f..9d67493d18 100644 --- a/source/crd_test.go +++ b/source/crd_test.go @@ -177,6 +177,37 @@ func testCRDSourceEndpoints(t *testing.T) { }, expectError: true, }, + { + title: "endpoint with annotations", + registeredAPIVersion: "test.k8s.io/v1alpha1", + apiVersion: "test.k8s.io/v1alpha1", + registeredKind: "DNSEndpoint", + kind: "DNSEndpoint", + namespace: "namespace", + registeredNamespace: "namespace", + annotations: map[string]string{ + hostnameAnnotationKey: "example.com", + targetAnnotationKey: "1.2.3.4", + ttlAnnotationKey: "180", + annotationKeyPrefix + "property": "value", + }, + spec: &endpoint.DNSEndpointSpec{}, + endpoints: []*endpoint.Endpoint{ + { + DNSName: "example.com", + Targets: endpoint.Targets{"1.2.3.4"}, + RecordType: endpoint.RecordTypeA, + RecordTTL: 180, + ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "property", + Value: "value", + }, + }, + }, + }, + expectError: false, + }, { title: "endpoint with provider property", registeredAPIVersion: "test.k8s.io/v1alpha1", @@ -185,6 +216,10 @@ func testCRDSourceEndpoints(t *testing.T) { kind: "DNSEndpoint", namespace: "namespace", registeredNamespace: "namespace", + annotations: map[string]string{ + annotationKeyPrefix + "override": "ignored", + annotationKeyPrefix + "property": "value", + }, spec: &endpoint.DNSEndpointSpec{ Endpoints: []*endpoint.Endpoint{ { @@ -194,7 +229,7 @@ func testCRDSourceEndpoints(t *testing.T) { RecordTTL: 180, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "property", + Name: "override", Value: "value", }, endpoint.ProviderSpecificProperty{ @@ -221,21 +256,25 @@ func testCRDSourceEndpoints(t *testing.T) { RecordTTL: 180, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "property", + Name: "override", Value: "value", }, endpoint.ProviderSpecificProperty{ - Name: "aws/property", + Name: "aws-property", Value: "value", }, endpoint.ProviderSpecificProperty{ - Name: "scw/property", + Name: "scw-property", Value: "value", }, endpoint.ProviderSpecificProperty{ Name: "webhook/property", Value: "value", }, + endpoint.ProviderSpecificProperty{ + Name: "property", + Value: "value", + }, }, }, }, diff --git a/source/gateway.go b/source/gateway.go index 5d8474999d..c7f2fb6b5c 100644 --- a/source/gateway.go +++ b/source/gateway.go @@ -46,7 +46,7 @@ const ( gatewayGroup = "gateway.networking.k8s.io" gatewayKind = "Gateway" // gatewayAPIDualstackAnnotationKey is the annotation used for determining if a Gateway Route is dualstack - gatewayAPIDualstackAnnotationKey = "external-dns.alpha.kubernetes.io/dualstack" + gatewayAPIDualstackAnnotationKey = annotationKeyPrefix + "dualstack" // gatewayAPIDualstackAnnotationValue is the value of the Gateway Route dualstack annotation that indicates it is dualstack gatewayAPIDualstackAnnotationValue = "true" ) diff --git a/source/gloo_proxy_test.go b/source/gloo_proxy_test.go index 03eb12b364..426714d9ca 100644 --- a/source/gloo_proxy_test.go +++ b/source/gloo_proxy_test.go @@ -508,7 +508,7 @@ func TestGlooSource(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "aws/geolocation-country-code", + Name: "aws-geolocation-country-code", Value: "LU", }, }, @@ -530,7 +530,7 @@ func TestGlooSource(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "aws/geolocation-country-code", + Name: "aws-geolocation-country-code", Value: "JP", }, }, @@ -560,7 +560,7 @@ func TestGlooSource(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "aws/geolocation-country-code", + Name: "aws-geolocation-country-code", Value: "ES", }, }, @@ -581,7 +581,7 @@ func TestGlooSource(t *testing.T) { Labels: endpoint.Labels{}, ProviderSpecific: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "aws/geolocation-country-code", + Name: "aws-geolocation-country-code", Value: "IT", }, }, diff --git a/source/istio_gateway.go b/source/istio_gateway.go index d158b30a7f..fd591f79c3 100644 --- a/source/istio_gateway.go +++ b/source/istio_gateway.go @@ -40,7 +40,7 @@ import ( // IstioGatewayIngressSource is the annotation used to determine if the gateway is implemented by an Ingress object // instead of a standard LoadBalancer service type -const IstioGatewayIngressSource = "external-dns.alpha.kubernetes.io/ingress" +const IstioGatewayIngressSource = annotationKeyPrefix + "ingress" // gatewaySource is an implementation of Source for Istio Gateway objects. // The gateway implementation uses the spec.servers.hosts values for the hostnames. diff --git a/source/source.go b/source/source.go index 012cf82739..99c6583e4e 100644 --- a/source/source.go +++ b/source/source.go @@ -39,27 +39,29 @@ import ( ) const ( + // The prefix used for all supported annotations + annotationKeyPrefix = "external-dns.alpha.kubernetes.io/" // The annotation used for figuring out which controller is responsible - controllerAnnotationKey = "external-dns.alpha.kubernetes.io/controller" + controllerAnnotationKey = annotationKeyPrefix + "controller" // The annotation used for defining the desired hostname - hostnameAnnotationKey = "external-dns.alpha.kubernetes.io/hostname" + hostnameAnnotationKey = annotationKeyPrefix + "hostname" // The annotation used for specifying whether the public or private interface address is used - accessAnnotationKey = "external-dns.alpha.kubernetes.io/access" + accessAnnotationKey = annotationKeyPrefix + "access" // The annotation used for specifying the type of endpoints to use for headless services - endpointsTypeAnnotationKey = "external-dns.alpha.kubernetes.io/endpoints-type" + endpointsTypeAnnotationKey = annotationKeyPrefix + "endpoints-type" // The annotation used for defining the desired ingress/service target - targetAnnotationKey = "external-dns.alpha.kubernetes.io/target" + targetAnnotationKey = annotationKeyPrefix + "target" // The annotation used for defining the desired DNS record TTL - ttlAnnotationKey = "external-dns.alpha.kubernetes.io/ttl" + ttlAnnotationKey = annotationKeyPrefix + "ttl" // The annotation used for switching to the alias record types e. g. AWS Alias records instead of a normal CNAME - aliasAnnotationKey = "external-dns.alpha.kubernetes.io/alias" + aliasAnnotationKey = annotationKeyPrefix + "alias" // The annotation used to determine the source of hostnames for ingresses. This is an optional field - all // available hostname sources are used if not specified. - ingressHostnameSourceKey = "external-dns.alpha.kubernetes.io/ingress-hostname-source" + ingressHostnameSourceKey = annotationKeyPrefix + "ingress-hostname-source" // The value of the controller annotation so that we feel responsible controllerAnnotationValue = "dns-controller" // The annotation used for defining the desired hostname - internalHostnameAnnotationKey = "external-dns.alpha.kubernetes.io/internal-hostname" + internalHostnameAnnotationKey = annotationKeyPrefix + "internal-hostname" ) const ( @@ -69,10 +71,7 @@ const ( // Provider-specific annotations const ( - // The annotation used for determining if traffic will go through Cloudflare - CloudflareProxiedKey = "external-dns.alpha.kubernetes.io/cloudflare-proxied" - - SetIdentifierKey = "external-dns.alpha.kubernetes.io/set-identifier" + SetIdentifierKey = annotationKeyPrefix + "set-identifier" ) const ( @@ -181,57 +180,50 @@ func splitHostnameAnnotation(annotation string) []string { return strings.Split(strings.Replace(annotation, " ", "", -1), ",") } -func getAliasFromAnnotations(annotations map[string]string) bool { - aliasAnnotation, exists := annotations[aliasAnnotationKey] - return exists && aliasAnnotation == "true" -} - func getProviderSpecificAnnotations(annotations map[string]string) (endpoint.ProviderSpecific, string) { providerSpecificAnnotations := endpoint.ProviderSpecific{} - - v, exists := annotations[CloudflareProxiedKey] - if exists { - providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ - Name: CloudflareProxiedKey, - Value: v, - }) - } - if getAliasFromAnnotations(annotations) { - providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ - Name: "alias", - Value: "true", - }) - } setIdentifier := "" - for k, v := range annotations { - if k == SetIdentifierKey { - setIdentifier = v - } else if strings.HasPrefix(k, "external-dns.alpha.kubernetes.io/aws-") { - attr := strings.TrimPrefix(k, "external-dns.alpha.kubernetes.io/aws-") - providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ - Name: fmt.Sprintf("aws/%s", attr), - Value: v, - }) - } else if strings.HasPrefix(k, "external-dns.alpha.kubernetes.io/scw-") { - attr := strings.TrimPrefix(k, "external-dns.alpha.kubernetes.io/scw-") - providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ - Name: fmt.Sprintf("scw/%s", attr), - Value: v, - }) - } else if strings.HasPrefix(k, "external-dns.alpha.kubernetes.io/ibmcloud-") { - attr := strings.TrimPrefix(k, "external-dns.alpha.kubernetes.io/ibmcloud-") - providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ - Name: fmt.Sprintf("ibmcloud-%s", attr), - Value: v, - }) - } else if strings.HasPrefix(k, "external-dns.alpha.kubernetes.io/webhook-") { - // Support for wildcard annotations for webhook providers - attr := strings.TrimPrefix(k, "external-dns.alpha.kubernetes.io/webhook-") - providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ - Name: fmt.Sprintf("webhook/%s", attr), - Value: v, - }) + for key, value := range annotations { + if !strings.HasPrefix(key, annotationKeyPrefix) { + continue + } + name := strings.TrimPrefix(key, annotationKeyPrefix) + switch name { + case "access": + continue + case "alias": + if value != "true" { + continue + } + case "endpoints-type": + continue + case "controller": + continue + case "dualstack": + continue + case "hostname": + continue + case "ingress": + continue + case "ingress-hostname-source": + continue + case "internal-hostname": + continue + case "set-identifier": + setIdentifier = value + continue + case "target": + continue + case "ttl": + continue } + if strings.HasPrefix(name, "webhook-") { + name = strings.Replace(name, "-", "/", 1) + } + providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ + Name: name, + Value: value, + }) } return providerSpecificAnnotations, setIdentifier } diff --git a/source/source_test.go b/source/source_test.go index b1ff12bef1..17eec7220f 100644 --- a/source/source_test.go +++ b/source/source_test.go @@ -26,7 +26,7 @@ import ( ) func TestGetProviderSpecificAnnotations(t *testing.T) { - for _, test := range []struct { + tests := []struct { title string annotations map[string]string properties endpoint.ProviderSpecific @@ -38,63 +38,21 @@ func TestGetProviderSpecificAnnotations(t *testing.T) { properties: endpoint.ProviderSpecific{}, }, { - title: "Native", + title: "SetIdentifier", annotations: map[string]string{ - aliasAnnotationKey: "true", SetIdentifierKey: "identifier", }, identifier: &[]string{"identifier"}[0], - properties: endpoint.ProviderSpecific{ - endpoint.ProviderSpecificProperty{ - Name: "alias", - Value: "true", - }, - }, - }, - { - title: "ProviderAWS", - annotations: map[string]string{ - "external-dns.alpha.kubernetes.io/aws-property": "value", - }, - properties: endpoint.ProviderSpecific{ - endpoint.ProviderSpecificProperty{ - Name: "aws/property", - Value: "value", - }, - }, - }, - { - title: "ProviderCloudflare", - annotations: map[string]string{ - CloudflareProxiedKey: "true", - }, - properties: endpoint.ProviderSpecific{ - endpoint.ProviderSpecificProperty{ - Name: CloudflareProxiedKey, - Value: "true", - }, - }, - }, - { - title: "ProviderIBMCloud", - annotations: map[string]string{ - "external-dns.alpha.kubernetes.io/ibmcloud-property": "value", - }, - properties: endpoint.ProviderSpecific{ - endpoint.ProviderSpecificProperty{ - Name: "ibmcloud-property", - Value: "value", - }, - }, + properties: endpoint.ProviderSpecific{}, }, { - title: "ProviderSCW", + title: "ProviderProperty", annotations: map[string]string{ - "external-dns.alpha.kubernetes.io/scw-property": "value", + annotationKeyPrefix + "property": "value", }, properties: endpoint.ProviderSpecific{ endpoint.ProviderSpecificProperty{ - Name: "scw/property", + Name: "property", Value: "value", }, }, @@ -111,7 +69,35 @@ func TestGetProviderSpecificAnnotations(t *testing.T) { }, }, }, + } + for _, name := range []string{ + "access", + "alias", + "endpoints-type", + "controller", + "dualstack", + "hostname", + "ingress", + "ingress-hostname-source", + "internal-hostname", + "set-identifier", + "target", + "ttl", } { + tests = append(tests, struct { + title string + annotations map[string]string + properties endpoint.ProviderSpecific + identifier *string + }{ + title: "Core" + name, + annotations: map[string]string{ + annotationKeyPrefix + name: "", + }, + properties: endpoint.ProviderSpecific{}, + }) + } + for _, test := range tests { t.Run(test.title, func(t *testing.T) { properties, identifier := getProviderSpecificAnnotations(test.annotations) assert.Equal(t, test.properties, properties)