diff --git a/endpoint/labels.go b/endpoint/labels.go index 67a6f2c12..26743ea3f 100644 --- a/endpoint/labels.go +++ b/endpoint/labels.go @@ -38,6 +38,9 @@ const ( // AWSSDDescriptionLabel label responsible for storing raw owner/resource combination information in the Labels // supposed to be inserted by AWS SD Provider, and parsed into OwnerLabelKey and ResourceLabelKey key by AWS SD Registry AWSSDDescriptionLabel = "aws-sd-description" + + // DualstackLabelKey is the name of the label that identifies dualstack endpoints + DualstackLabelKey = "dualstack" ) // Labels store metadata related to the endpoint diff --git a/provider/aws.go b/provider/aws.go index 549bc81f0..7cd1e1935 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -407,60 +407,74 @@ func (p *AWSProvider) newChanges(action string, endpoints []*endpoint.Endpoint, changes := make([]*route53.Change, 0, len(endpoints)) for _, endpoint := range endpoints { - changes = append(changes, p.newChange(action, endpoint, recordsCache, zones)) + change, dualstack := p.newChange(action, endpoint, recordsCache, zones) + changes = append(changes, change) + if dualstack { + // make a copy of change, modify RRS type to AAAA, then add new change + rrs := *change.ResourceRecordSet + change2 := &route53.Change{Action: change.Action, ResourceRecordSet: &rrs} + change2.ResourceRecordSet.Type = aws.String(route53.RRTypeAaaa) + changes = append(changes, change2) + } } return changes } -// newChange returns a Change of the given record by the given action, e.g. +// newChange returns a route53 Change and a boolean indicating if there should also be a change to a AAAA record +// returned Change is based on the given record by the given action, e.g. // action=ChangeActionCreate returns a change for creation of the record and // action=ChangeActionDelete returns a change for deletion of the record. -func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint, recordsCache []*endpoint.Endpoint, zones map[string]*route53.HostedZone) *route53.Change { +func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint, recordsCache []*endpoint.Endpoint, zones map[string]*route53.HostedZone) (*route53.Change, bool) { change := &route53.Change{ Action: aws.String(action), ResourceRecordSet: &route53.ResourceRecordSet{ - Name: aws.String(endpoint.DNSName), + Name: aws.String(ep.DNSName), }, } + dualstack := false - if isAWSLoadBalancer(endpoint) { + if isAWSLoadBalancer(ep) { evalTargetHealth := p.evaluateTargetHealth - if prop, ok := endpoint.GetProviderSpecificProperty(providerSpecificEvaluateTargetHealth); ok { + if prop, ok := ep.GetProviderSpecificProperty(providerSpecificEvaluateTargetHealth); ok { evalTargetHealth = prop.Value == "true" } + // If the endpoint has a Dualstack label, append a change for AAAA record as well. + if val, ok := ep.Labels[endpoint.DualstackLabelKey]; ok { + dualstack = val == "true" + } change.ResourceRecordSet.Type = aws.String(route53.RRTypeA) change.ResourceRecordSet.AliasTarget = &route53.AliasTarget{ - DNSName: aws.String(endpoint.Targets[0]), - HostedZoneId: aws.String(canonicalHostedZone(endpoint.Targets[0])), + DNSName: aws.String(ep.Targets[0]), + HostedZoneId: aws.String(canonicalHostedZone(ep.Targets[0])), EvaluateTargetHealth: aws.Bool(evalTargetHealth), } - } else if hostedZone := isAWSAlias(endpoint, recordsCache); hostedZone != "" { + } else if hostedZone := isAWSAlias(ep, recordsCache); hostedZone != "" { for _, zone := range zones { change.ResourceRecordSet.Type = aws.String(route53.RRTypeA) change.ResourceRecordSet.AliasTarget = &route53.AliasTarget{ - DNSName: aws.String(endpoint.Targets[0]), + DNSName: aws.String(ep.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() { + change.ResourceRecordSet.Type = aws.String(ep.RecordType) + if !ep.RecordTTL.IsConfigured() { change.ResourceRecordSet.TTL = aws.Int64(recordTTL) } else { - change.ResourceRecordSet.TTL = aws.Int64(int64(endpoint.RecordTTL)) + change.ResourceRecordSet.TTL = aws.Int64(int64(ep.RecordTTL)) } - change.ResourceRecordSet.ResourceRecords = make([]*route53.ResourceRecord, len(endpoint.Targets)) - for idx, val := range endpoint.Targets { + change.ResourceRecordSet.ResourceRecords = make([]*route53.ResourceRecord, len(ep.Targets)) + for idx, val := range ep.Targets { change.ResourceRecordSet.ResourceRecords[idx] = &route53.ResourceRecord{ Value: aws.String(val), } } } - return change + return change, dualstack } func (p *AWSProvider) tagsForZone(zoneID string) (map[string]string, error) { diff --git a/source/ingress.go b/source/ingress.go index ff3f00bef..e12a013d4 100644 --- a/source/ingress.go +++ b/source/ingress.go @@ -37,6 +37,13 @@ import ( "k8s.io/client-go/tools/cache" ) +const ( + // The annotation used for determining if an ALB ingress is dualstack + ALBDualstackAnnotationKey = "alb.ingress.kubernetes.io/ip-address-type" + // The value of the ALB dualstack annotation that indicates it is dualstack + ALBDualstackAnnotationValue = "dualstack" +) + // ingressSource is an implementation of Source for Kubernetes ingress objects. // Ingress implementation will use the spec.rules.host value for the hostname // Use targetAnnotationKey to explicitly set Endpoint. (useful if the ingress @@ -148,6 +155,7 @@ func (sc *ingressSource) Endpoints() ([]*endpoint.Endpoint, error) { log.Debugf("Endpoints generated from ingress: %s/%s: %v", ing.Namespace, ing.Name, ingEndpoints) sc.setResourceLabel(ing, ingEndpoints) + sc.setDualstackLabel(ing, ingEndpoints) endpoints = append(endpoints, ingEndpoints...) } @@ -228,6 +236,14 @@ func (sc *ingressSource) setResourceLabel(ingress *v1beta1.Ingress, endpoints [] } } +func (sc *ingressSource) setDualstackLabel(ingress *v1beta1.Ingress, endpoints []*endpoint.Endpoint) { + val, ok := ingress.Annotations[ALBDualstackAnnotationKey] + if ok && val == ALBDualstackAnnotationValue { + log.Debugf("Adding dualstack label to ingress %s/%s.", ingress.Namespace, ingress.Name) + for _, ep := range endpoints { ep.Labels[endpoint.DualstackLabelKey] = "true" } + } +} + // endpointsFromIngress extracts the endpoints from ingress object func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool) []*endpoint.Endpoint { var endpoints []*endpoint.Endpoint