Add alias annotation for ingress

This commit is contained in:
vaegt 2018-10-16 13:30:07 +02:00
parent db13a3d5d7
commit bb80f99e17
No known key found for this signature in database
GPG Key ID: D6EB0FF4A03B395F
6 changed files with 159 additions and 18 deletions

View File

@ -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) { if isAWSLoadBalancer(endpoint) {
evalTargetHealth := p.evaluateTargetHealth evalTargetHealth := p.evaluateTargetHealth
if _, ok := endpoint.ProviderSpecific[providerSpecificEvaluateTargetHealth]; ok { 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])), HostedZoneId: aws.String(canonicalHostedZone(endpoint.Targets[0])),
EvaluateTargetHealth: aws.Bool(evalTargetHealth), 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 { } else {
change.ResourceRecordSet.Type = aws.String(endpoint.RecordType) change.ResourceRecordSet.Type = aws.String(endpoint.RecordType)
if !endpoint.RecordTTL.IsConfigured() { if !endpoint.RecordTTL.IsConfigured() {
@ -529,6 +548,21 @@ func isAWSLoadBalancer(ep *endpoint.Endpoint) bool {
return false 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. // canonicalHostedZone returns the matching canonical zone for a given hostname.
func canonicalHostedZone(hostname string) string { func canonicalHostedZone(hostname string) string {
for suffix, zone := range canonicalHostedZones { for suffix, zone := range canonicalHostedZones {
@ -539,3 +573,11 @@ func canonicalHostedZone(hostname string) string {
return "" return ""
} }
// cleanZoneID removes the "/hostedzone/" prefix
func cleanZoneID(ID string) string {
if strings.HasPrefix(ID, "/hostedzone/") {
ID = strings.TrimPrefix(ID, "/hostedzone/")
}
return ID
}

View File

@ -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) { func TestAWSCanonicalHostedZone(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
hostname string hostname string

View File

@ -172,12 +172,14 @@ func (sc *gatewaySource) endpointsFromTemplate(config *istiomodel.Config) ([]*en
} }
} }
providerSpecific := getProviderSpecificAnnotations(config.Annotations)
var endpoints []*endpoint.Endpoint var endpoints []*endpoint.Endpoint
// splits the FQDN template and removes the trailing periods // splits the FQDN template and removes the trailing periods
hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",") hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",")
for _, hostname := range hostnameList { for _, hostname := range hostnameList {
hostname = strings.TrimSuffix(hostname, ".") hostname = strings.TrimSuffix(hostname, ".")
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
} }
return endpoints, nil return endpoints, nil
} }
@ -256,18 +258,20 @@ func (sc *gatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([
gateway := config.Spec.(*istionetworking.Gateway) gateway := config.Spec.(*istionetworking.Gateway)
providerSpecific := getProviderSpecificAnnotations(config.Annotations)
for _, server := range gateway.Servers { for _, server := range gateway.Servers {
for _, host := range server.Hosts { for _, host := range server.Hosts {
if host == "" { if host == "" {
continue continue
} }
endpoints = append(endpoints, endpointsForHostname(host, targets, ttl)...) endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...)
} }
} }
hostnameList := getHostnamesFromAnnotations(config.Annotations) hostnameList := getHostnamesFromAnnotations(config.Annotations)
for _, hostname := range hostnameList { for _, hostname := range hostnameList {
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
} }
return endpoints, nil return endpoints, nil

View File

@ -146,12 +146,14 @@ func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoin
targets = targetsFromIngressStatus(ing.Status) targets = targetsFromIngressStatus(ing.Status)
} }
providerSpecific := getProviderSpecificAnnotations(ing.Annotations)
var endpoints []*endpoint.Endpoint var endpoints []*endpoint.Endpoint
// splits the FQDN template and removes the trailing periods // splits the FQDN template and removes the trailing periods
hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",") hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",")
for _, hostname := range hostnameList { for _, hostname := range hostnameList {
hostname = strings.TrimSuffix(hostname, ".") hostname = strings.TrimSuffix(hostname, ".")
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
} }
return endpoints, nil return endpoints, nil
} }
@ -208,11 +210,13 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
targets = targetsFromIngressStatus(ing.Status) targets = targetsFromIngressStatus(ing.Status)
} }
providerSpecific := getProviderSpecificAnnotations(ing.Annotations)
for _, rule := range ing.Spec.Rules { for _, rule := range ing.Spec.Rules {
if rule.Host == "" { if rule.Host == "" {
continue continue
} }
endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl)...) endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl, providerSpecific)...)
} }
for _, tls := range ing.Spec.TLS { for _, tls := range ing.Spec.TLS {
@ -220,13 +224,13 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint {
if host == "" { if host == "" {
continue continue
} }
endpoints = append(endpoints, endpointsForHostname(host, targets, ttl)...) endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...)
} }
} }
hostnameList := getHostnamesFromAnnotations(ing.Annotations) hostnameList := getHostnamesFromAnnotations(ing.Annotations)
for _, hostname := range hostnameList { for _, hostname := range hostnameList {
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...) endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
} }
return endpoints return endpoints

View File

@ -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", title: "template for ingress with annotation",
targetNamespace: "", targetNamespace: "",

View File

@ -35,6 +35,8 @@ const (
targetAnnotationKey = "external-dns.alpha.kubernetes.io/target" targetAnnotationKey = "external-dns.alpha.kubernetes.io/target"
// The annotation used for defining the desired DNS record TTL // The annotation used for defining the desired DNS record TTL
ttlAnnotationKey = "external-dns.alpha.kubernetes.io/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 // The value of the controller annotation so that we feel responsible
controllerAnnotationValue = "dns-controller" controllerAnnotationValue = "dns-controller"
) )
@ -74,6 +76,18 @@ func getHostnamesFromAnnotations(annotations map[string]string) []string {
return strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",") 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. // getTargetsFromTargetAnnotation gets endpoints from optional "target" annotation.
// Returns empty endpoints array if none are found. // Returns empty endpoints array if none are found.
func getTargetsFromTargetAnnotation(annotations map[string]string) endpoint.Targets { 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. // 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 endpoints []*endpoint.Endpoint
var aTargets endpoint.Targets var aTargets endpoint.Targets
@ -119,22 +133,24 @@ func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoin
if len(aTargets) > 0 { if len(aTargets) > 0 {
epA := &endpoint.Endpoint{ epA := &endpoint.Endpoint{
DNSName: strings.TrimSuffix(hostname, "."), DNSName: strings.TrimSuffix(hostname, "."),
Targets: aTargets, Targets: aTargets,
RecordTTL: ttl, RecordTTL: ttl,
RecordType: endpoint.RecordTypeA, RecordType: endpoint.RecordTypeA,
Labels: endpoint.NewLabels(), Labels: endpoint.NewLabels(),
ProviderSpecific: providerSpecific,
} }
endpoints = append(endpoints, epA) endpoints = append(endpoints, epA)
} }
if len(cnameTargets) > 0 { if len(cnameTargets) > 0 {
epCNAME := &endpoint.Endpoint{ epCNAME := &endpoint.Endpoint{
DNSName: strings.TrimSuffix(hostname, "."), DNSName: strings.TrimSuffix(hostname, "."),
Targets: cnameTargets, Targets: cnameTargets,
RecordTTL: ttl, RecordTTL: ttl,
RecordType: endpoint.RecordTypeCNAME, RecordType: endpoint.RecordTypeCNAME,
Labels: endpoint.NewLabels(), Labels: endpoint.NewLabels(),
ProviderSpecific: providerSpecific,
} }
endpoints = append(endpoints, epCNAME) endpoints = append(endpoints, epCNAME)
} }