diff --git a/provider/aws.go b/provider/aws.go index 6aa0f0412..c43de0091 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -364,6 +364,11 @@ func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint) *rou }, } + rec, err := p.Records() + if err != nil { + log.Errorf("getting records failed: %v", err) + } + if isAWSLoadBalancer(endpoint) { evalTargetHealth := p.evaluateTargetHealth if _, ok := endpoint.ProviderSpecific[providerSpecificEvaluateTargetHealth]; ok { @@ -376,6 +381,20 @@ func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint) *rou HostedZoneId: aws.String(canonicalHostedZone(endpoint.Targets[0])), EvaluateTargetHealth: aws.Bool(evalTargetHealth), } + } else if hostedZone := isAWSAlias(endpoint, rec); hostedZone != "" { + //FIXME should break if err != nil + zones, err := p.Zones() + if err != nil { + log.Errorf("getting zones failed: %v", err) + } + for _, zone := range zones { + change.ResourceRecordSet.Type = aws.String(route53.RRTypeA) + change.ResourceRecordSet.AliasTarget = &route53.AliasTarget{ + DNSName: aws.String(endpoint.Targets[0]), + HostedZoneId: aws.String(cleanZoneID(*zone.Id)), + EvaluateTargetHealth: aws.Bool(p.evaluateTargetHealth), + } + } } else { change.ResourceRecordSet.Type = aws.String(endpoint.RecordType) if !endpoint.RecordTTL.IsConfigured() { @@ -529,6 +548,21 @@ func isAWSLoadBalancer(ep *endpoint.Endpoint) bool { return false } +// isAWSAlias determines if a given hostname belongs to an AWS Alias record by doing an reverse lookup. +func isAWSAlias(ep *endpoint.Endpoint, addrs []*endpoint.Endpoint) string { + if val, exists := ep.ProviderSpecific["alias"]; ep.RecordType == endpoint.RecordTypeCNAME && exists && val == "true" { + for _, addr := range addrs { + if addr.DNSName == ep.Targets[0] { + if hostedZone := canonicalHostedZone(addr.Targets[0]); hostedZone != "" { + return hostedZone + } + + } + } + } + return "" +} + // canonicalHostedZone returns the matching canonical zone for a given hostname. func canonicalHostedZone(hostname string) string { for suffix, zone := range canonicalHostedZones { @@ -539,3 +573,11 @@ func canonicalHostedZone(hostname string) string { return "" } + +// cleanZoneID removes the "/hostedzone/" prefix +func cleanZoneID(ID string) string { + if strings.HasPrefix(ID, "/hostedzone/") { + ID = strings.TrimPrefix(ID, "/hostedzone/") + } + return ID +} diff --git a/provider/aws_test.go b/provider/aws_test.go index 60c451e99..8ece0f454 100644 --- a/provider/aws_test.go +++ b/provider/aws_test.go @@ -819,6 +819,35 @@ func TestAWSisLoadBalancer(t *testing.T) { } } +func TestAWSisAWSAlias(t *testing.T) { + for _, tc := range []struct { + target string + recordType string + alias string + expected string + }{ + {"bar.example.org", endpoint.RecordTypeCNAME, "true", "Z215JYRZR1TBD5"}, + {"foo.example.org", endpoint.RecordTypeCNAME, "true", ""}, + } { + ep := &endpoint.Endpoint{ + Targets: endpoint.Targets{tc.target}, + RecordType: tc.recordType, + ProviderSpecific: map[string]string{"alias": tc.alias}, + } + addrs := []*endpoint.Endpoint{ + &endpoint.Endpoint{ + DNSName: "foo.example.org", + Targets: endpoint.Targets{"foobar.example.org"}, + }, + &endpoint.Endpoint{ + DNSName: "bar.example.org", + Targets: endpoint.Targets{"bar.eu-central-1.elb.amazonaws.com"}, + }, + } + assert.Equal(t, tc.expected, isAWSAlias(ep, addrs)) + } +} + func TestAWSCanonicalHostedZone(t *testing.T) { for _, tc := range []struct { hostname string diff --git a/source/gateway.go b/source/gateway.go index 0494f0a4a..30aa53b10 100644 --- a/source/gateway.go +++ b/source/gateway.go @@ -172,12 +172,14 @@ func (sc *gatewaySource) endpointsFromTemplate(config *istiomodel.Config) ([]*en } } + providerSpecific := getProviderSpecificAnnotations(config.Annotations) + var endpoints []*endpoint.Endpoint // splits the FQDN template and removes the trailing periods hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",") for _, hostname := range hostnameList { hostname = strings.TrimSuffix(hostname, ".") - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) } return endpoints, nil } @@ -256,18 +258,20 @@ func (sc *gatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([ gateway := config.Spec.(*istionetworking.Gateway) + providerSpecific := getProviderSpecificAnnotations(config.Annotations) + for _, server := range gateway.Servers { for _, host := range server.Hosts { if host == "" { continue } - endpoints = append(endpoints, endpointsForHostname(host, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...) } } hostnameList := getHostnamesFromAnnotations(config.Annotations) for _, hostname := range hostnameList { - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) } return endpoints, nil diff --git a/source/ingress.go b/source/ingress.go index a9ab76cfb..cbda3cd92 100644 --- a/source/ingress.go +++ b/source/ingress.go @@ -146,12 +146,14 @@ func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoin targets = targetsFromIngressStatus(ing.Status) } + providerSpecific := getProviderSpecificAnnotations(ing.Annotations) + var endpoints []*endpoint.Endpoint // splits the FQDN template and removes the trailing periods hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",") for _, hostname := range hostnameList { hostname = strings.TrimSuffix(hostname, ".") - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) } return endpoints, nil } @@ -208,11 +210,13 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint { targets = targetsFromIngressStatus(ing.Status) } + providerSpecific := getProviderSpecificAnnotations(ing.Annotations) + for _, rule := range ing.Spec.Rules { if rule.Host == "" { continue } - endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl, providerSpecific)...) } for _, tls := range ing.Spec.TLS { @@ -220,13 +224,13 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint { if host == "" { continue } - endpoints = append(endpoints, endpointsForHostname(host, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...) } } hostnameList := getHostnamesFromAnnotations(ing.Annotations) for _, hostname := range hostnameList { - endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) + endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) } return endpoints diff --git a/source/ingress_test.go b/source/ingress_test.go index 2b13287d6..7d0182653 100644 --- a/source/ingress_test.go +++ b/source/ingress_test.go @@ -817,6 +817,52 @@ func testIngressEndpoints(t *testing.T) { }, }, }, + { + title: "ingress rules with alias and target annotation", + targetNamespace: "", + ingressItems: []fakeIngress{ + { + name: "fake1", + namespace: namespace, + annotations: map[string]string{ + targetAnnotationKey: "ingress-target.com", + aliasAnnotationKey: "true", + }, + dnsnames: []string{"example.org"}, + ips: []string{}, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "example.org", + Targets: endpoint.Targets{"ingress-target.com"}, + RecordType: endpoint.RecordTypeCNAME, + }, + }, + }, + { + title: "ingress rules with alias set false and target annotation", + targetNamespace: "", + ingressItems: []fakeIngress{ + { + name: "fake1", + namespace: namespace, + annotations: map[string]string{ + targetAnnotationKey: "ingress-target.com", + aliasAnnotationKey: "false", + }, + dnsnames: []string{"example.org"}, + ips: []string{}, + }, + }, + expected: []*endpoint.Endpoint{ + { + DNSName: "example.org", + Targets: endpoint.Targets{"ingress-target.com"}, + RecordType: endpoint.RecordTypeCNAME, + }, + }, + }, { title: "template for ingress with annotation", targetNamespace: "", diff --git a/source/source.go b/source/source.go index 14f99fe5c..fac716c81 100644 --- a/source/source.go +++ b/source/source.go @@ -35,6 +35,8 @@ const ( targetAnnotationKey = "external-dns.alpha.kubernetes.io/target" // The annotation used for defining the desired DNS record TTL ttlAnnotationKey = "external-dns.alpha.kubernetes.io/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" // The value of the controller annotation so that we feel responsible controllerAnnotationValue = "dns-controller" ) @@ -74,6 +76,18 @@ func getHostnamesFromAnnotations(annotations map[string]string) []string { return strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",") } +func getAliasFromAnnotations(annotations map[string]string) bool { + aliasAnnotation, exists := annotations[aliasAnnotationKey] + return exists && aliasAnnotation == "true" +} + +func getProviderSpecificAnnotations(annotations map[string]string) endpoint.ProviderSpecific { + if getAliasFromAnnotations(annotations) { + return map[string]string{"alias": "true"} + } + return map[string]string{} +} + // getTargetsFromTargetAnnotation gets endpoints from optional "target" annotation. // Returns empty endpoints array if none are found. func getTargetsFromTargetAnnotation(annotations map[string]string) endpoint.Targets { @@ -102,7 +116,7 @@ func suitableType(target string) string { } // endpointsForHostname returns the endpoint objects for each host-target combination. -func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoint.TTL) []*endpoint.Endpoint { +func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoint.TTL, providerSpecific endpoint.ProviderSpecific) []*endpoint.Endpoint { var endpoints []*endpoint.Endpoint var aTargets endpoint.Targets @@ -119,22 +133,24 @@ func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoin if len(aTargets) > 0 { epA := &endpoint.Endpoint{ - DNSName: strings.TrimSuffix(hostname, "."), - Targets: aTargets, - RecordTTL: ttl, - RecordType: endpoint.RecordTypeA, - Labels: endpoint.NewLabels(), + DNSName: strings.TrimSuffix(hostname, "."), + Targets: aTargets, + RecordTTL: ttl, + RecordType: endpoint.RecordTypeA, + Labels: endpoint.NewLabels(), + ProviderSpecific: providerSpecific, } endpoints = append(endpoints, epA) } if len(cnameTargets) > 0 { epCNAME := &endpoint.Endpoint{ - DNSName: strings.TrimSuffix(hostname, "."), - Targets: cnameTargets, - RecordTTL: ttl, - RecordType: endpoint.RecordTypeCNAME, - Labels: endpoint.NewLabels(), + DNSName: strings.TrimSuffix(hostname, "."), + Targets: cnameTargets, + RecordTTL: ttl, + RecordType: endpoint.RecordTypeCNAME, + Labels: endpoint.NewLabels(), + ProviderSpecific: providerSpecific, } endpoints = append(endpoints, epCNAME) }