diff --git a/internal/testutils/endpoint.go b/internal/testutils/endpoint.go index 277d34e374..76701ee91c 100644 --- a/internal/testutils/endpoint.go +++ b/internal/testutils/endpoint.go @@ -63,6 +63,9 @@ func (b byAllFields) Less(i, j int) bool { // SameEndpoint returns true if two endpoints are same // considers example.org. and example.org DNSName/Target as different endpoints func SameEndpoint(a, b *endpoint.Endpoint) bool { + if a == nil || b == nil { + return a == b + } return a.DNSName == b.DNSName && a.Targets.Same(b.Targets) && a.RecordType == b.RecordType && a.SetIdentifier == b.SetIdentifier && a.Labels[endpoint.OwnerLabelKey] == b.Labels[endpoint.OwnerLabelKey] && a.RecordTTL == b.RecordTTL && a.Labels[endpoint.ResourceLabelKey] == b.Labels[endpoint.ResourceLabelKey] && diff --git a/provider/aws/aws.go b/provider/aws/aws.go index 398985ea8f..9e34bde8b2 100644 --- a/provider/aws/aws.go +++ b/provider/aws/aws.go @@ -827,65 +827,94 @@ func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpoi var aliasCnameAaaaEndpoints []*endpoint.Endpoint for _, ep := range endpoints { - alias := false + if aaaa := p.adjustEndpointAndNewAaaaIfNeeded(ep); aaaa != nil { + aliasCnameAaaaEndpoints = append(aliasCnameAaaaEndpoints, aaaa) + } + } + return append(endpoints, aliasCnameAaaaEndpoints...), nil +} - if aliasString, ok := ep.GetProviderSpecificProperty(providerSpecificAlias); ok { - alias = aliasString == "true" - if alias { - if !slices.Contains([]string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}, ep.RecordType) { - ep.DeleteProviderSpecificProperty(providerSpecificAlias) - } - } else { - if ep.RecordType == endpoint.RecordTypeCNAME { - if aliasString != "false" { - ep.SetProviderSpecificProperty(providerSpecificAlias, "false") - } - } else { - ep.DeleteProviderSpecificProperty(providerSpecificAlias) - } - } - } else if ep.RecordType == endpoint.RecordTypeCNAME { - alias = useAlias(ep, p.preferCNAME) - log.Debugf("Modifying endpoint: %v, setting %s=%v", ep, providerSpecificAlias, alias) - ep.SetProviderSpecificProperty(providerSpecificAlias, strconv.FormatBool(alias)) +func (p *AWSProvider) adjustEndpointAndNewAaaaIfNeeded(ep *endpoint.Endpoint) *endpoint.Endpoint { + var aaaa *endpoint.Endpoint + switch ep.RecordType { + case endpoint.RecordTypeA, endpoint.RecordTypeAAAA: + p.adjustAandAAAARecord(ep) + case endpoint.RecordTypeCNAME: + p.adjustCNAMERecord(ep) + adjustGeoProximityLocationEndpoint(ep) + if isAlias, _ := ep.GetBoolProviderSpecificProperty(providerSpecificAlias); isAlias { + aaaa = ep.DeepCopy() + aaaa.RecordType = endpoint.RecordTypeAAAA } + return aaaa + default: + p.adjustOtherRecord(ep) + } + adjustGeoProximityLocationEndpoint(ep) + return aaaa +} - if alias { - if ep.RecordTTL.IsConfigured() { - log.Debugf("Modifying endpoint: %v, setting ttl=%v", ep, defaultTTL) - ep.RecordTTL = defaultTTL - } - if prop, ok := ep.GetProviderSpecificProperty(providerSpecificEvaluateTargetHealth); ok { - if prop != "true" && prop != "false" { - ep.SetProviderSpecificProperty(providerSpecificEvaluateTargetHealth, "false") - } - } else { - ep.SetProviderSpecificProperty(providerSpecificEvaluateTargetHealth, strconv.FormatBool(p.evaluateTargetHealth)) - } +func (p *AWSProvider) adjustAliasRecord(ep *endpoint.Endpoint) { + if ep.RecordTTL.IsConfigured() { + log.Debugf("Modifying endpoint: %v, setting ttl=%v", ep, defaultTTL) + ep.RecordTTL = defaultTTL + } - if ep.RecordType == endpoint.RecordTypeCNAME { - // This needs to match two records from Route53, one alias for 'A' (IPv4) - // and one alias for 'AAAA' (IPv6). - aliasCnameAaaaEndpoints = append(aliasCnameAaaaEndpoints, &endpoint.Endpoint{ - DNSName: ep.DNSName, - Targets: ep.Targets, - RecordType: endpoint.RecordTypeAAAA, - RecordTTL: ep.RecordTTL, - Labels: ep.Labels, - ProviderSpecific: ep.ProviderSpecific, - SetIdentifier: ep.SetIdentifier, - }) - ep.RecordType = endpoint.RecordTypeA - } - } else { - ep.DeleteProviderSpecificProperty(providerSpecificEvaluateTargetHealth) + if enable, exists := ep.GetBoolProviderSpecificProperty(providerSpecificEvaluateTargetHealth); exists { + // normalize to string "true"/"false" + ep.SetProviderSpecificProperty(providerSpecificEvaluateTargetHealth, strconv.FormatBool(enable)) + } else { + // if not set, use provider default + ep.SetProviderSpecificProperty(providerSpecificEvaluateTargetHealth, strconv.FormatBool(p.evaluateTargetHealth)) + } +} + +func (p *AWSProvider) adjustAandAAAARecord(ep *endpoint.Endpoint) { + isAlias, _ := ep.GetBoolProviderSpecificProperty(providerSpecificAlias) + if isAlias { + p.adjustAliasRecord(ep) + } else { + ep.DeleteProviderSpecificProperty(providerSpecificAlias) + ep.DeleteProviderSpecificProperty(providerSpecificEvaluateTargetHealth) + } +} + +func (p *AWSProvider) adjustCNAMERecord(ep *endpoint.Endpoint) { + isAlias, exists := ep.GetBoolProviderSpecificProperty(providerSpecificAlias) + + // fallback to determining alias based on preferCNAME if not explicitly set + if !exists { + isAlias = useAlias(ep, p.preferCNAME) + log.Debugf("Modifying endpoint: %v, setting %s=%v", ep, providerSpecificAlias, isAlias) + ep.SetProviderSpecificProperty(providerSpecificAlias, strconv.FormatBool(isAlias)) + } + + // if not an alias, ensure alias properties are adjusted accordingly + if !isAlias { + if exists { + // normalize to string "false" when provider specific alias is set to false or other non-true value + ep.SetProviderSpecificProperty(providerSpecificAlias, "false") } + ep.DeleteProviderSpecificProperty(providerSpecificEvaluateTargetHealth) + } - adjustGeoProximityLocationEndpoint(ep) + // if an alias, convert to A record and adjust alias properties + if isAlias { + ep.RecordType = endpoint.RecordTypeA + p.adjustAliasRecord(ep) } +} - endpoints = append(endpoints, aliasCnameAaaaEndpoints...) - return endpoints, nil +func (p *AWSProvider) adjustOtherRecord(ep *endpoint.Endpoint) { + // TODO: fix For records other than A, AAAA, and CNAME, if an alias record is set, the alias record processing is not performed. + // This will be fixed in another PR. + if isAlias, _ := ep.GetBoolProviderSpecificProperty(providerSpecificAlias); isAlias { + p.adjustAliasRecord(ep) + ep.DeleteProviderSpecificProperty(providerSpecificAlias) + } else { + ep.DeleteProviderSpecificProperty(providerSpecificAlias) + ep.DeleteProviderSpecificProperty(providerSpecificEvaluateTargetHealth) + } } // if the endpoint is using geoproximity, set the bias to 0 if not set diff --git a/provider/aws/aws_test.go b/provider/aws/aws_test.go index 1738aa0d54..4b39db730f 100644 --- a/provider/aws/aws_test.go +++ b/provider/aws/aws_test.go @@ -338,7 +338,7 @@ func TestAWSZones(t *testing.T) { {"tag filter single zone match", provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), provider.NewZoneTagFilter([]string{"zone=3"}), privateZones}, } { t.Run(ti.msg, func(t *testing.T) { - provider, _ := newAWSProviderWithTagFilter(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), ti.zoneIDFilter, ti.zoneTypeFilter, ti.zoneTagFilter, defaultEvaluateTargetHealth, false, nil) + provider, _ := newAWSProviderWithTagFilter(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), ti.zoneIDFilter, ti.zoneTypeFilter, ti.zoneTagFilter, defaultEvaluateTargetHealth, false, false, nil) zones, err := provider.Zones(context.Background()) require.NoError(t, err) validateAWSZones(t, zones, ti.expectedZones) @@ -370,7 +370,7 @@ func TestAWSZonesWithTagFilterError(t *testing.T) { } func TestAWSRecordsFilter(t *testing.T) { - provider, _ := newAWSProvider(t, &endpoint.DomainFilter{}, provider.ZoneIDFilter{}, provider.ZoneTypeFilter{}, false, false, nil) + provider, _ := newAWSProvider(t, &endpoint.DomainFilter{}, provider.ZoneIDFilter{}, provider.ZoneTypeFilter{}, false, false, false, nil) domainFilter := provider.GetDomainFilter() require.NotNil(t, domainFilter) require.IsType(t, &endpoint.DomainFilter{}, domainFilter) @@ -393,7 +393,7 @@ func TestAWSRecordsFilter(t *testing.T) { } func TestAWSRecords(t *testing.T) { - provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), false, false, []route53types.ResourceRecordSet{ + provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), false, false, false, []route53types.ResourceRecordSet{ { Name: aws.String("list-test.zone-1.ext-dns-test-2.teapot.zalan.do."), Type: route53types.RRTypeA, @@ -686,7 +686,7 @@ func TestAWSRecords(t *testing.T) { } func TestAWSRecordsSoftError(t *testing.T) { - pvd, subClient := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), false, false, []route53types.ResourceRecordSet{ + pvd, subClient := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), false, false, false, []route53types.ResourceRecordSet{ { Name: aws.String("list-test.zone-1.ext-dns-test-2.teapot.zalan.do."), Type: route53types.RRTypeA, @@ -702,7 +702,7 @@ func TestAWSRecordsSoftError(t *testing.T) { } func TestAWSAdjustEndpoints(t *testing.T) { - provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, nil) + provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, false, nil) records := []*endpoint.Endpoint{ endpoint.NewEndpoint("a-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"), @@ -751,7 +751,7 @@ func TestAWSApplyChanges(t *testing.T) { } for _, tt := range tests { - provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []route53types.ResourceRecordSet{ + provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, false, []route53types.ResourceRecordSet{ { Name: aws.String("update-test.zone-1.ext-dns-test-2.teapot.zalan.do."), Type: route53types.RRTypeA, @@ -1412,7 +1412,7 @@ func TestAWSApplyChangesDryRun(t *testing.T) { }, } - provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, true, originalRecords) + provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, true, originalRecords) createRecords := []*endpoint.Endpoint{ endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"), @@ -1599,7 +1599,7 @@ func TestAWSChangesByZones(t *testing.T) { } func TestAWSsubmitChanges(t *testing.T) { - provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, nil) + provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, false, nil) const subnets = 16 const hosts = defaultBatchChangeSize / subnets @@ -1628,7 +1628,7 @@ func TestAWSsubmitChanges(t *testing.T) { } func TestAWSsubmitChangesError(t *testing.T) { - provider, clientStub := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, nil) + provider, clientStub := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, false, nil) clientStub.MockMethod("ChangeResourceRecordSets", mock.Anything).Return(nil, fmt.Errorf("Mock route53 failure")) ctx := context.Background() @@ -1642,7 +1642,7 @@ func TestAWSsubmitChangesError(t *testing.T) { } func TestAWSsubmitChangesRetryOnError(t *testing.T) { - provider, clientStub := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, nil) + provider, clientStub := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, false, nil) ctx := context.Background() zones, err := provider.zones(ctx) @@ -2077,7 +2077,7 @@ func validateAWSChangeRecord(t *testing.T, record *Route53Change, expected *Rout } func TestAWSCreateRecordsWithCNAME(t *testing.T) { - provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, nil) + provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, false, nil) records := []*endpoint.Endpoint{ {DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Targets: endpoint.Targets{"foo.example.org"}, RecordType: endpoint.RecordTypeCNAME}, @@ -2111,7 +2111,7 @@ func TestAWSCreateRecordsWithALIAS(t *testing.T) { "false": false, "": false, } { - provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, nil) + provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, false, nil) records := []*endpoint.Endpoint{ { DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", @@ -2352,11 +2352,11 @@ func listAWSRecords(t *testing.T, client Route53API, zone string) []route53types return resp.ResourceRecordSets } -func newAWSProvider(t *testing.T, domainFilter *endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, evaluateTargetHealth, dryRun bool, records []route53types.ResourceRecordSet) (*AWSProvider, *Route53APIStub) { - return newAWSProviderWithTagFilter(t, domainFilter, zoneIDFilter, zoneTypeFilter, provider.NewZoneTagFilter([]string{}), evaluateTargetHealth, dryRun, records) +func newAWSProvider(t *testing.T, domainFilter *endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, evaluateTargetHealth, preferCNAME, dryRun bool, records []route53types.ResourceRecordSet) (*AWSProvider, *Route53APIStub) { + return newAWSProviderWithTagFilter(t, domainFilter, zoneIDFilter, zoneTypeFilter, provider.NewZoneTagFilter([]string{}), evaluateTargetHealth, preferCNAME, dryRun, records) } -func newAWSProviderWithTagFilter(t *testing.T, domainFilter *endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, zoneTagFilter provider.ZoneTagFilter, evaluateTargetHealth, dryRun bool, records []route53types.ResourceRecordSet) (*AWSProvider, *Route53APIStub) { +func newAWSProviderWithTagFilter(t *testing.T, domainFilter *endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, zoneTagFilter provider.ZoneTagFilter, evaluateTargetHealth, preferCNAME, dryRun bool, records []route53types.ResourceRecordSet) (*AWSProvider, *Route53APIStub) { client := NewRoute53APIStub(t) provider := &AWSProvider{ @@ -2370,6 +2370,7 @@ func newAWSProviderWithTagFilter(t *testing.T, domainFilter *endpoint.DomainFilt zoneIDFilter: zoneIDFilter, zoneTypeFilter: zoneTypeFilter, zoneTagFilter: zoneTagFilter, + preferCNAME: preferCNAME, dryRun: false, zonesCache: &zonesListCache{duration: 1 * time.Minute}, failedChangesQueue: make(map[string]Route53Changes), @@ -2457,7 +2458,7 @@ func containsRecordWithDNSName(records []*endpoint.Endpoint, dnsName string) boo } func TestRequiresDeleteCreate(t *testing.T) { - provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"foo.bar."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, nil) + provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"foo.bar."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, false, nil) oldRecordType := endpoint.NewEndpointWithTTL("recordType", endpoint.RecordTypeA, endpoint.TTL(defaultTTL), "8.8.8.8") newRecordType := endpoint.NewEndpointWithTTL("recordType", endpoint.RecordTypeCNAME, endpoint.TTL(defaultTTL), "bar").WithProviderSpecific(providerSpecificAlias, "false") @@ -2853,7 +2854,7 @@ func TestGeoProximityWithBias(t *testing.T) { } func TestAWSProvider_createUpdateChanges_NewMoreThanOld(t *testing.T) { - provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"foo.bar."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), true, false, nil) + provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"foo.bar."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), true, false, false, nil) oldEndpoints := []*endpoint.Endpoint{ endpoint.NewEndpointWithTTL("record1.foo.bar.", endpoint.RecordTypeA, endpoint.TTL(300), "1.1.1.1"), @@ -2884,3 +2885,496 @@ func TestAWSProvider_createUpdateChanges_NewMoreThanOld(t *testing.T) { require.Equal(t, 1, upserts, "should upsert the matching endpoint") require.Equal(t, 0, deletes, "should not delete anything") } + +func TestAWSProvider_adjustEndpointAndNewAaaaIfNeeded(t *testing.T) { + tests := []struct { + name string + preferCNAME bool + ep *endpoint.Endpoint + expected *endpoint.Endpoint + expectedAaaa *endpoint.Endpoint + }{ + // --- A / AAAA --- + { + name: "A record without provider specific should not change and not create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.1.1.1"}, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.1.1.1"}, + }, + expectedAaaa: nil, + }, + { + name: "A record with alias=true should set default ttl, add evaluateTargetHealth and not create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.1.1.1"}, + RecordTTL: 600, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.1.1.1"}, + RecordTTL: defaultTTL, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", // p.evaluateTargetHealth=false in this test + }, + }, + }, + expectedAaaa: nil, + }, + { + name: "A record with alias!=true value should remove alias and evaluateTargetHealth and not create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.1.1.1"}, + RecordTTL: 600, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "false", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "true", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.1.1.1"}, + RecordTTL: 600, + ProviderSpecific: endpoint.ProviderSpecific{}, + }, + expectedAaaa: nil, + }, + { + name: "A record with alias=true and invalid evaluateTargetHealth should normalize it to false and set default ttl", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.1.1.1"}, + RecordTTL: 600, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "invalid", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"1.1.1.1"}, + RecordTTL: defaultTTL, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", + }, + }, + }, + expectedAaaa: nil, + }, + { + name: "AAAA record with alias=true should behave like A record", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeAAAA, + Targets: endpoint.Targets{"2001:db8::1"}, + RecordTTL: 600, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeAAAA, + Targets: endpoint.Targets{"2001:db8::1"}, + RecordTTL: defaultTTL, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", + }, + }, + }, + expectedAaaa: nil, + }, + + // --- CNAME --- + { + name: "CNAME record with alias=false should keep alias=false, remove evaluateTargetHealth and not create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + RecordTTL: 600, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "false", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "true", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + RecordTTL: 600, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "false", + }, + }, + }, + expectedAaaa: nil, + }, + { + name: "CNAME record with invalid alias value should normalize to alias=false and not create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + RecordTTL: 600, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "invalid", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + RecordTTL: 600, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "false", + }, + }, + }, + expectedAaaa: nil, + }, + { + name: "CNAME record with alias=true should set default ttl, add evaluateTargetHealth and create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + RecordTTL: 600, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + RecordTTL: defaultTTL, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", // p.evaluateTargetHealth=false in this test + }, + }, + }, + expectedAaaa: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeAAAA, + RecordTTL: defaultTTL, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", // p.evaluateTargetHealth=false in this test + }, + }, + }, + }, + { + name: "CNAME record with alias=true and evaluateTargetHealth=true should keep evaluateTargetHealth and create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + RecordTTL: 600, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "true", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + RecordTTL: defaultTTL, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "true", + }, + }, + }, + expectedAaaa: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeAAAA, + RecordTTL: defaultTTL, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "true", + }, + }, + }, + }, + { + name: "CNAME record with alias=true and invalid evaluateTargetHealth should normalize it to false and create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + RecordTTL: 600, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "invalid", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + RecordTTL: defaultTTL, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", + }, + }, + }, + expectedAaaa: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeAAAA, + RecordTTL: defaultTTL, + Targets: endpoint.Targets{"target.foo.bar."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", + }, + }, + }, + }, + { + name: "CNAME without alias to ELB target should enable alias and create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + Targets: endpoint.Targets{"test-123.us-east-1.elb.amazonaws.com"}, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeA, + Targets: endpoint.Targets{"test-123.us-east-1.elb.amazonaws.com"}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", + }, + }, + }, + expectedAaaa: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeAAAA, + Targets: endpoint.Targets{"test-123.us-east-1.elb.amazonaws.com"}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", + }, + }, + }, + }, + { + name: "CNAME with preferCNAME=true should set alias=false and not create AAAA even for ELB target", + preferCNAME: true, + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + Targets: endpoint.Targets{"test-123.us-east-1.elb.amazonaws.com."}, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeCNAME, + Targets: endpoint.Targets{"test-123.us-east-1.elb.amazonaws.com."}, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "false", + }, + }, + }, + expectedAaaa: nil, + }, + + // --- MX / other records --- + { + name: "MX record without provider specific should not change and not create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeMX, + Targets: endpoint.Targets{"10 mail.example.com."}, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeMX, + Targets: endpoint.Targets{"10 mail.example.com."}, + }, + expectedAaaa: nil, + }, + // TODO: fix For records other than A, AAAA, and CNAME, if an alias record is set, the alias record processing is not performed. This will be fixed in another PR. + { + name: "MX record with alias=true should remove alias and set default ttl, add evaluateTargetHealth and not create AAAA", + ep: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeMX, + Targets: endpoint.Targets{"10 mail.example.com."}, + RecordTTL: 600, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificAlias, + Value: "true", + }, + }, + }, + expected: &endpoint.Endpoint{ + DNSName: "test.foo.bar.", + RecordType: endpoint.RecordTypeMX, + Targets: endpoint.Targets{"10 mail.example.com."}, + RecordTTL: defaultTTL, + ProviderSpecific: endpoint.ProviderSpecific{ + { + Name: providerSpecificEvaluateTargetHealth, + Value: "false", + }, + }, + }, + expectedAaaa: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + p, _ := newAWSProvider( + t, + endpoint.NewDomainFilter([]string{"foo.bar."}), + provider.NewZoneIDFilter([]string{}), + provider.NewZoneTypeFilter(""), + false, + tt.preferCNAME, + false, + nil, + ) + + aaaa := p.adjustEndpointAndNewAaaaIfNeeded(tt.ep) + + assert.True(t, testutils.SameEndpoint(tt.ep, tt.expected), + "actual and expected endpoints don't match. %+v:%+v", tt.ep, tt.expected) + + assert.True(t, testutils.SameEndpoint(aaaa, tt.expectedAaaa), + "actual and expected AAAA endpoints don't match. %+v:%+v", aaaa, tt.expectedAaaa) + }) + } +}