diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index e612fd418e..33c9e6f435 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -894,6 +894,11 @@ For any given DNS name, only **one** of the following routing policies can be us - `external-dns.alpha.kubernetes.io/aws-geolocation-continent-code` - `external-dns.alpha.kubernetes.io/aws-geolocation-country-code` - `external-dns.alpha.kubernetes.io/aws-geolocation-subdivision-code` +- Geoproximity routing: + - `external-dns.alpha.kubernetes.io/aws-geoproximitylocation-aws-region` + - `external-dns.alpha.kubernetes.io/aws-geoproximitylocation-local-zone-group` + - `external-dns.alpha.kubernetes.io/aws-geoproximitylocation-coordinates` + - `external-dns.alpha.kubernetes.io/aws-geoproximitylocation-bias` - Multi-value answer:`external-dns.alpha.kubernetes.io/aws-multi-value-answer` ### Associating DNS records with healthchecks diff --git a/provider/aws/aws.go b/provider/aws/aws.go index 2ec309256b..06d402b317 100644 --- a/provider/aws/aws.go +++ b/provider/aws/aws.go @@ -53,16 +53,20 @@ const ( // 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" - sameZoneAlias = "same-zone" + 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" + providerSpecificGeoProximityLocationAWSRegion = "aws/geoproximitylocation-aws-region" + providerSpecificGeoProximityLocationBias = "aws/geoproximitylocation-bias" + providerSpecificGeoProximityLocationCoordinates = "aws/geoproximitylocation-coordinates" + providerSpecificGeoProximityLocationLocalZoneGroup = "aws/geoproximitylocation-local-zone-group" + providerSpecificMultiValueAnswer = "aws/multi-value-answer" + providerSpecificHealthCheckID = "aws/health-check-id" + sameZoneAlias = "same-zone" // Currently supported up to 10 health checks or hosted zones. // https://docs.aws.amazon.com/Route53/latest/APIReference/API_ListTagsForResources.html#API_ListTagsForResources_RequestSyntax batchSize = 10 @@ -542,6 +546,8 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*profiledZon ep.WithProviderSpecific(providerSpecificGeolocationSubdivisionCode, *r.GeoLocation.SubdivisionCode) } } + case r.GeoProximityLocation != nil: + handleGeoProximityLocationRecord(&r, ep) default: // one of the above needs to be set, otherwise SetIdentifier doesn't make sense } @@ -560,6 +566,25 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*profiledZon return endpoints, nil } +func handleGeoProximityLocationRecord(r *route53types.ResourceRecordSet, ep *endpoint.Endpoint) { + if region := aws.ToString(r.GeoProximityLocation.AWSRegion); region != "" { + ep.WithProviderSpecific(providerSpecificGeoProximityLocationAWSRegion, region) + } + + if bias := r.GeoProximityLocation.Bias; bias != nil { + ep.WithProviderSpecific(providerSpecificGeoProximityLocationBias, fmt.Sprintf("%d", aws.ToInt32(bias))) + } + + if coords := r.GeoProximityLocation.Coordinates; coords != nil { + coordinates := fmt.Sprintf("%s,%s", aws.ToString(coords.Latitude), aws.ToString(coords.Longitude)) + ep.WithProviderSpecific(providerSpecificGeoProximityLocationCoordinates, coordinates) + } + + if localZoneGroup := aws.ToString(r.GeoProximityLocation.LocalZoneGroup); localZoneGroup != "" { + ep.WithProviderSpecific(providerSpecificGeoProximityLocationLocalZoneGroup, localZoneGroup) + } +} + // Identify if old and new endpoints require DELETE/CREATE instead of UPDATE. func (p *AWSProvider) requiresDeleteCreate(old *endpoint.Endpoint, newE *endpoint.Endpoint) bool { // a change of a record type @@ -832,12 +857,31 @@ func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpoi } else { ep.DeleteProviderSpecificProperty(providerSpecificEvaluateTargetHealth) } + + adjustGeoProximityLocationEndpoint(ep) } endpoints = append(endpoints, aliasCnameAaaaEndpoints...) return endpoints, nil } +// if the endpoint is using geoproximity, set the bias to 0 if not set +// this is needed to avoid unnecessary Upserts if the desired endpoint doesn't specify a bias +func adjustGeoProximityLocationEndpoint(ep *endpoint.Endpoint) { + if ep.SetIdentifier != "" { + _, ok1 := ep.GetProviderSpecificProperty(providerSpecificGeoProximityLocationAWSRegion) + _, ok2 := ep.GetProviderSpecificProperty(providerSpecificGeoProximityLocationLocalZoneGroup) + _, ok3 := ep.GetProviderSpecificProperty(providerSpecificGeoProximityLocationCoordinates) + + if ok1 || ok2 || ok3 { + // check if ep has bias property and if not, set it to 0 + if _, ok := ep.GetProviderSpecificProperty(providerSpecificGeoProximityLocationBias); !ok { + ep.SetProviderSpecificProperty(providerSpecificGeoProximityLocationBias, "0") + } + } + } +} + // newChange returns a route53 Change // 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 @@ -926,6 +970,8 @@ func (p *AWSProvider) newChange(action route53types.ChangeAction, ep *endpoint.E if useGeolocation { change.ResourceRecordSet.GeoLocation = geolocation } + + withChangeForGeoProximityEndpoint(change, ep) } if prop, ok := ep.GetProviderSpecificProperty(providerSpecificHealthCheckID); ok { @@ -939,6 +985,80 @@ func (p *AWSProvider) newChange(action route53types.ChangeAction, ep *endpoint.E return change } +func withChangeForGeoProximityEndpoint(change *Route53Change, ep *endpoint.Endpoint) { + geoproximity := &route53types.GeoProximityLocation{} + isGeoproximity := false + + isGeoproximity = withGeoProximityAWSRegion(ep, geoproximity) + isGeoproximity = isGeoproximity || withGeoProximityCoordinates(ep, geoproximity) + isGeoproximity = isGeoproximity || withGeoProximityLocalZoneGroup(ep, geoproximity) + withGeoProximityBias(ep, geoproximity) + + if isGeoproximity { + change.ResourceRecordSet.GeoProximityLocation = geoproximity + } +} + +func withGeoProximityAWSRegion(ep *endpoint.Endpoint, geoproximity *route53types.GeoProximityLocation) bool { + if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGeoProximityLocationAWSRegion); ok { + geoproximity.AWSRegion = aws.String(prop) + return true + } + return false +} + +// validateCoordinates checks if the given latitude and longitude are valid. +func validateCoordinates(lat, long string) error { + latitude, err := strconv.ParseFloat(lat, 64) + if err != nil || latitude < -90 || latitude > 90 { + return errors.New("invalid latitude: must be a number between -90 and 90") + } + + longitude, err := strconv.ParseFloat(long, 64) + if err != nil || longitude < -180 || longitude > 180 { + return errors.New("invalid longitude: must be a number between -180 and 180") + } + + return nil +} + +func withGeoProximityCoordinates(ep *endpoint.Endpoint, geoproximity *route53types.GeoProximityLocation) bool { + if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGeoProximityLocationCoordinates); ok { + coordinates := strings.Split(prop, ",") + if len(coordinates) == 2 { + latitude := coordinates[0] + longitude := coordinates[1] + if err := validateCoordinates(latitude, longitude); err != nil { + log.Errorf("Invalid coordinates %s for name=%s setIdentifier=%s; %v", prop, ep.DNSName, ep.SetIdentifier, err) + return false + } + geoproximity.Coordinates = &route53types.Coordinates{ + Latitude: aws.String(latitude), + Longitude: aws.String(longitude), + } + return true + } else { + log.Errorf("Invalid coordinates format for %s: %s; expected format 'latitude,longitude'", providerSpecificGeoProximityLocationCoordinates, prop) + } + } + return false +} + +func withGeoProximityLocalZoneGroup(ep *endpoint.Endpoint, geoproximity *route53types.GeoProximityLocation) bool { + if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGeoProximityLocationLocalZoneGroup); ok { + geoproximity.LocalZoneGroup = aws.String(prop) + return true + } + return false +} + +func withGeoProximityBias(ep *endpoint.Endpoint, geoproximity *route53types.GeoProximityLocation) { + if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGeoProximityLocationBias); ok { + bias, _ := strconv.ParseInt(prop, 10, 32) + geoproximity.Bias = aws.Int32(int32(bias)) + } +} + // searches for `changes` that are contained in `queue` and returns the `changes` separated by whether they were found in the queue (`foundChanges`) or not (`notFoundChanges`) func findChangesInQueue(changes Route53Changes, queue Route53Changes) (foundChanges, notFoundChanges Route53Changes) { if queue == nil { diff --git a/provider/aws/aws_test.go b/provider/aws/aws_test.go index db13e46173..c29fb772f7 100644 --- a/provider/aws/aws_test.go +++ b/provider/aws/aws_test.go @@ -583,6 +583,42 @@ func TestAWSRecords(t *testing.T) { SubdivisionCode: aws.String("NY"), }, }, + { + Name: aws.String("geoproximitylocation-region.zone-1.ext-dns-test-2.teapot.zalan.do."), + Type: route53types.RRTypeA, + TTL: aws.Int64(defaultTTL), + ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("1.2.3.4")}}, + SetIdentifier: aws.String("test-set-1"), + GeoProximityLocation: &route53types.GeoProximityLocation{ + AWSRegion: aws.String("us-west-2"), + Bias: aws.Int32(10), + }, + }, + { + Name: aws.String("geoproximitylocation-localzone.zone-1.ext-dns-test-2.teapot.zalan.do."), + Type: route53types.RRTypeA, + TTL: aws.Int64(defaultTTL), + ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("1.2.3.4")}}, + SetIdentifier: aws.String("test-set-1"), + GeoProximityLocation: &route53types.GeoProximityLocation{ + LocalZoneGroup: aws.String("usw2-pdx1-az1"), + Bias: aws.Int32(10), + }, + }, + { + Name: aws.String("geoproximitylocation-coordinates.zone-1.ext-dns-test-2.teapot.zalan.do."), + Type: route53types.RRTypeA, + TTL: aws.Int64(defaultTTL), + ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("1.2.3.4")}}, + SetIdentifier: aws.String("test-set-1"), + GeoProximityLocation: &route53types.GeoProximityLocation{ + Coordinates: &route53types.Coordinates{ + Latitude: aws.String("90"), + Longitude: aws.String("90"), + }, + Bias: aws.Int32(0), + }, + }, { Name: aws.String("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do."), Type: route53types.RRTypeCname, @@ -636,6 +672,9 @@ func TestAWSRecords(t *testing.T) { endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(defaultTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationContinentCode, "EU"), endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(defaultTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificGeolocationCountryCode, "DE"), endpoint.NewEndpointWithTTL("geolocation-subdivision-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(defaultTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationSubdivisionCode, "NY"), + endpoint.NewEndpointWithTTL("geoproximitylocation-region.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(defaultTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeoProximityLocationAWSRegion, "us-west-2").WithProviderSpecific(providerSpecificGeoProximityLocationBias, "10"), + endpoint.NewEndpointWithTTL("geoproximitylocation-localzone.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(defaultTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeoProximityLocationLocalZoneGroup, "usw2-pdx1-az1").WithProviderSpecific(providerSpecificGeoProximityLocationBias, "10"), + endpoint.NewEndpointWithTTL("geoproximitylocation-coordinates.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(defaultTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeoProximityLocationCoordinates, "90,90").WithProviderSpecific(providerSpecificGeoProximityLocationBias, "0"), endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(defaultTTL), "foo.example.com").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificWeight, "10").WithProviderSpecific(providerSpecificHealthCheckID, "foo-bar-healthcheck-id").WithProviderSpecific(providerSpecificAlias, "false"), endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(defaultTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificWeight, "20").WithProviderSpecific(providerSpecificHealthCheckID, "abc-def-healthcheck-id"), endpoint.NewEndpointWithTTL("mail.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeMX, endpoint.TTL(defaultTTL), "10 mailhost1.example.com", "20 mailhost2.example.com"), @@ -670,6 +709,7 @@ func TestAWSAdjustEndpoints(t *testing.T) { endpoint.NewEndpoint("cname-test-elb-no-alias.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "false"), endpoint.NewEndpoint("cname-test-elb-no-eth.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "false"), // eth = evaluate target health endpoint.NewEndpoint("cname-test-elb-alias.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "true").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "true"), + endpoint.NewEndpoint("a-test-geoproximity-no-bias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeoProximityLocationAWSRegion, "us-west-2"), } records, err := provider.AdjustEndpoints(records) @@ -687,6 +727,7 @@ func TestAWSAdjustEndpoints(t *testing.T) { endpoint.NewEndpoint("cname-test-elb-no-eth.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeAAAA, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "true").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "false"), // eth = evaluate target health endpoint.NewEndpoint("cname-test-elb-alias.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "true").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "true"), endpoint.NewEndpoint("cname-test-elb-alias.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeAAAA, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "true").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "true"), + endpoint.NewEndpoint("a-test-geoproximity-no-bias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeoProximityLocationAWSRegion, "us-west-2").WithProviderSpecific(providerSpecificGeoProximityLocationBias, "0"), }) } @@ -845,6 +886,27 @@ func TestAWSApplyChanges(t *testing.T) { TTL: aws.Int64(defaultTTL), ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("2606:4700:4700::1111")}, {Value: aws.String("2606:4700:4700::1001")}}, }, + { + Name: aws.String("delete-test-geoproximity.zone-2.ext-dns-test-2.teapot.zalan.do."), + Type: route53types.RRTypeA, + TTL: aws.Int64(defaultTTL), + ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("1.2.3.4")}}, + SetIdentifier: aws.String("geoproximity-delete"), + GeoProximityLocation: &route53types.GeoProximityLocation{ + AWSRegion: aws.String("us-west-2"), + Bias: aws.Int32(10), + }, + }, + { + Name: aws.String("update-test-geoproximity.zone-1.ext-dns-test-2.teapot.zalan.do."), + Type: route53types.RRTypeA, + TTL: aws.Int64(defaultTTL), + ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("1.2.3.4")}}, + SetIdentifier: aws.String("geoproximity-update"), + GeoProximityLocation: &route53types.GeoProximityLocation{ + LocalZoneGroup: aws.String("usw2-lax1-az2"), + }, + }, { Name: aws.String("weighted-to-simple.zone-1.ext-dns-test-2.teapot.zalan.do."), Type: route53types.RRTypeA, @@ -915,6 +977,13 @@ func TestAWSApplyChanges(t *testing.T) { endpoint.NewEndpoint("create-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"), endpoint.NewEndpoint("create-test-multiple-aaaa.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeAAAA, "2606:4700:4700::1111", "2606:4700:4700::1001"), endpoint.NewEndpoint("create-test-mx.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeMX, "10 mailhost1.foo.elb.amazonaws.com"), + endpoint.NewEndpoint("create-test-geoproximity-region.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"). + WithSetIdentifier("geoproximity-region"). + WithProviderSpecific(providerSpecificGeoProximityLocationAWSRegion, "us-west-2"). + WithProviderSpecific(providerSpecificGeoProximityLocationBias, "10"), + endpoint.NewEndpoint("create-test-geoproximity-coordinates.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"). + WithSetIdentifier("geoproximity-coordinates"). + WithProviderSpecific(providerSpecificGeoProximityLocationCoordinates, "60,60"), } currentRecords := []*endpoint.Endpoint{ @@ -930,6 +999,9 @@ func TestAWSApplyChanges(t *testing.T) { endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeAAAA, "bar.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "true"), endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"), endpoint.NewEndpoint("update-test-multiple-aaaa.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeAAAA, "2606:4700:4700::1111", "2606:4700:4700::1001"), + endpoint.NewEndpoint("update-test-geoproximity.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"). + WithSetIdentifier("geoproximity-update"). + WithProviderSpecific(providerSpecificGeoProximityLocationLocalZoneGroup, "usw2-lax1-az2"), endpoint.NewEndpoint("weighted-to-simple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4").WithSetIdentifier("weighted-to-simple").WithProviderSpecific(providerSpecificWeight, "10"), endpoint.NewEndpoint("simple-to-weighted.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"), endpoint.NewEndpoint("policy-change.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4").WithSetIdentifier("policy-change").WithProviderSpecific(providerSpecificWeight, "10"), @@ -951,6 +1023,9 @@ func TestAWSApplyChanges(t *testing.T) { endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeAAAA, "baz.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "true"), endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"), endpoint.NewEndpoint("update-test-multiple-aaaa.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeAAAA, "2606:4700:4700::1001", "2606:4700:4700::1111"), + endpoint.NewEndpoint("update-test-geoproximity.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"). + WithSetIdentifier("geoproximity-update"). + WithProviderSpecific(providerSpecificGeoProximityLocationLocalZoneGroup, "usw2-phx2-az1"), endpoint.NewEndpoint("weighted-to-simple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"), endpoint.NewEndpoint("simple-to-weighted.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4").WithSetIdentifier("simple-to-weighted").WithProviderSpecific(providerSpecificWeight, "10"), endpoint.NewEndpoint("policy-change.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4").WithSetIdentifier("policy-change").WithProviderSpecific(providerSpecificRegion, "us-east-1"), @@ -969,6 +1044,7 @@ func TestAWSApplyChanges(t *testing.T) { endpoint.NewEndpoint("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeAAAA, "qux.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "true"), endpoint.NewEndpoint("delete-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"), endpoint.NewEndpoint("delete-test-multiple-aaaa.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeAAAA, "2606:4700:4700::1111", "2606:4700:4700::1001"), + endpoint.NewEndpoint("delete-test-geoproximity.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4").WithSetIdentifier("geoproximity-delete").WithProviderSpecific(providerSpecificGeoProximityLocationAWSRegion, "us-west-2").WithProviderSpecific(providerSpecificGeoProximityLocationBias, "10"), endpoint.NewEndpoint("delete-test-mx.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeMX, "30 mailhost1.foo.elb.amazonaws.com"), } @@ -1118,6 +1194,40 @@ func TestAWSApplyChanges(t *testing.T) { TTL: aws.Int64(defaultTTL), ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("10 mailhost1.foo.elb.amazonaws.com")}}, }, + { + Name: aws.String("create-test-geoproximity-region.zone-1.ext-dns-test-2.teapot.zalan.do."), + Type: route53types.RRTypeA, + TTL: aws.Int64(defaultTTL), + ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("8.8.8.8")}}, + SetIdentifier: aws.String("geoproximity-region"), + GeoProximityLocation: &route53types.GeoProximityLocation{ + AWSRegion: aws.String("us-west-2"), + Bias: aws.Int32(10), + }, + }, + { + Name: aws.String("update-test-geoproximity.zone-1.ext-dns-test-2.teapot.zalan.do."), + Type: route53types.RRTypeA, + TTL: aws.Int64(defaultTTL), + ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("1.2.3.4")}}, + SetIdentifier: aws.String("geoproximity-update"), + GeoProximityLocation: &route53types.GeoProximityLocation{ + LocalZoneGroup: aws.String("usw2-phx2-az1"), + }, + }, + { + Name: aws.String("create-test-geoproximity-coordinates.zone-1.ext-dns-test-2.teapot.zalan.do."), + Type: route53types.RRTypeA, + TTL: aws.Int64(defaultTTL), + ResourceRecords: []route53types.ResourceRecord{{Value: aws.String("8.8.8.8")}}, + SetIdentifier: aws.String("geoproximity-coordinates"), + GeoProximityLocation: &route53types.GeoProximityLocation{ + Coordinates: &route53types.Coordinates{ + Latitude: aws.String("60"), + Longitude: aws.String("60"), + }, + }, + }, }) validateRecords(t, listAWSRecords(t, provider.clients[defaultAWSProfile], "/hostedzone/zone-2.ext-dns-test-2.teapot.zalan.do."), []route53types.ResourceRecordSet{ { @@ -1902,7 +2012,7 @@ func validateEndpoints(t *testing.T, provider *AWSProvider, endpoints []*endpoin normalized, err := provider.AdjustEndpoints(endpoints) assert.NoError(t, err) - assert.True(t, testutils.SameEndpoints(normalized, expected), "actual and normalized endpoints don't match. %+v:%+v", endpoints, normalized) + assert.True(t, testutils.SameEndpoints(normalized, expected), "normalized and expected endpoints don't match. %+v:%+v", normalized, expected) } func validateAWSZones(t *testing.T, zones map[string]*route53types.HostedZone, expected map[string]*route53types.HostedZone) {