@@ -53,19 +53,27 @@ const (
5353 // providerSpecificEvaluateTargetHealth specifies whether an AWS ALIAS record
5454 // has the EvaluateTargetHealth field set to true. Present iff the endpoint
5555 // has a `providerSpecificAlias` value of `true`.
56- providerSpecificEvaluateTargetHealth = "aws/evaluate-target-health"
57- providerSpecificWeight = "aws/weight"
58- providerSpecificRegion = "aws/region"
59- providerSpecificFailover = "aws/failover"
60- providerSpecificGeolocationContinentCode = "aws/geolocation-continent-code"
61- providerSpecificGeolocationCountryCode = "aws/geolocation-country-code"
62- providerSpecificGeolocationSubdivisionCode = "aws/geolocation-subdivision-code"
63- providerSpecificMultiValueAnswer = "aws/multi-value-answer"
64- providerSpecificHealthCheckID = "aws/health-check-id"
65- sameZoneAlias = "same-zone"
56+ providerSpecificEvaluateTargetHealth = "aws/evaluate-target-health"
57+ providerSpecificWeight = "aws/weight"
58+ providerSpecificRegion = "aws/region"
59+ providerSpecificFailover = "aws/failover"
60+ providerSpecificGeolocationContinentCode = "aws/geolocation-continent-code"
61+ providerSpecificGeolocationCountryCode = "aws/geolocation-country-code"
62+ providerSpecificGeolocationSubdivisionCode = "aws/geolocation-subdivision-code"
63+ providerSpecificGeoProximityLocationAWSRegion = "aws/geoproximity-region"
64+ providerSpecificGeoProximityLocationBias = "aws/geoproximity-bias"
65+ providerSpecificGeoProximityLocationCoordinates = "aws/geoproximity-coordinates"
66+ providerSpecificGeoProximityLocationLocalZoneGroup = "aws/geoproximity-local-zone-group"
67+ providerSpecificMultiValueAnswer = "aws/multi-value-answer"
68+ providerSpecificHealthCheckID = "aws/health-check-id"
69+ sameZoneAlias = "same-zone"
6670 // Currently supported up to 10 health checks or hosted zones.
6771 // https://docs.aws.amazon.com/Route53/latest/APIReference/API_ListTagsForResources.html#API_ListTagsForResources_RequestSyntax
68- batchSize = 10
72+ batchSize = 10
73+ minLatitude = - 90.0
74+ maxLatitude = 90.0
75+ minLongitude = - 180.0
76+ maxLongitude = 180.0
6977)
7078
7179// see elb: https://docs.aws.amazon.com/general/latest/gr/elb.html
@@ -231,6 +239,12 @@ type profiledZone struct {
231239 zone * route53types.HostedZone
232240}
233241
242+ type geoProximity struct {
243+ location * route53types.GeoProximityLocation
244+ endpoint * endpoint.Endpoint
245+ isSet bool
246+ }
247+
234248func (cs Route53Changes ) Route53Changes () []route53types.Change {
235249 var ret []route53types.Change
236250 for _ , c := range cs {
@@ -542,6 +556,8 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*profiledZon
542556 ep .WithProviderSpecific (providerSpecificGeolocationSubdivisionCode , * r .GeoLocation .SubdivisionCode )
543557 }
544558 }
559+ case r .GeoProximityLocation != nil :
560+ handleGeoProximityLocationRecord (& r , ep )
545561 default :
546562 // one of the above needs to be set, otherwise SetIdentifier doesn't make sense
547563 }
@@ -560,6 +576,25 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*profiledZon
560576 return endpoints , nil
561577}
562578
579+ func handleGeoProximityLocationRecord (r * route53types.ResourceRecordSet , ep * endpoint.Endpoint ) {
580+ if region := aws .ToString (r .GeoProximityLocation .AWSRegion ); region != "" {
581+ ep .WithProviderSpecific (providerSpecificGeoProximityLocationAWSRegion , region )
582+ }
583+
584+ if bias := r .GeoProximityLocation .Bias ; bias != nil {
585+ ep .WithProviderSpecific (providerSpecificGeoProximityLocationBias , fmt .Sprintf ("%d" , aws .ToInt32 (bias )))
586+ }
587+
588+ if coords := r .GeoProximityLocation .Coordinates ; coords != nil {
589+ coordinates := fmt .Sprintf ("%s,%s" , aws .ToString (coords .Latitude ), aws .ToString (coords .Longitude ))
590+ ep .WithProviderSpecific (providerSpecificGeoProximityLocationCoordinates , coordinates )
591+ }
592+
593+ if localZoneGroup := aws .ToString (r .GeoProximityLocation .LocalZoneGroup ); localZoneGroup != "" {
594+ ep .WithProviderSpecific (providerSpecificGeoProximityLocationLocalZoneGroup , localZoneGroup )
595+ }
596+ }
597+
563598// Identify if old and new endpoints require DELETE/CREATE instead of UPDATE.
564599func (p * AWSProvider ) requiresDeleteCreate (old * endpoint.Endpoint , newE * endpoint.Endpoint ) bool {
565600 // a change of a record type
@@ -832,12 +867,32 @@ func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpoi
832867 } else {
833868 ep .DeleteProviderSpecificProperty (providerSpecificEvaluateTargetHealth )
834869 }
870+
871+ adjustGeoProximityLocationEndpoint (ep )
835872 }
836873
837874 endpoints = append (endpoints , aliasCnameAaaaEndpoints ... )
838875 return endpoints , nil
839876}
840877
878+ // if the endpoint is using geoproximity, set the bias to 0 if not set
879+ // this is needed to avoid unnecessary Upserts if the desired endpoint doesn't specify a bias
880+ func adjustGeoProximityLocationEndpoint (ep * endpoint.Endpoint ) {
881+ if ep .SetIdentifier == "" {
882+ return
883+ }
884+ _ , ok1 := ep .GetProviderSpecificProperty (providerSpecificGeoProximityLocationAWSRegion )
885+ _ , ok2 := ep .GetProviderSpecificProperty (providerSpecificGeoProximityLocationLocalZoneGroup )
886+ _ , ok3 := ep .GetProviderSpecificProperty (providerSpecificGeoProximityLocationCoordinates )
887+
888+ if ok1 || ok2 || ok3 {
889+ // check if ep has bias property and if not, set it to 0
890+ if _ , ok := ep .GetProviderSpecificProperty (providerSpecificGeoProximityLocationBias ); ! ok {
891+ ep .SetProviderSpecificProperty (providerSpecificGeoProximityLocationBias , "0" )
892+ }
893+ }
894+ }
895+
841896// newChange returns a route53 Change
842897// returned Change is based on the given record by the given action, e.g.
843898// action=ChangeActionCreate returns a change for creation of the record and
@@ -926,6 +981,8 @@ func (p *AWSProvider) newChange(action route53types.ChangeAction, ep *endpoint.E
926981 if useGeolocation {
927982 change .ResourceRecordSet .GeoLocation = geolocation
928983 }
984+
985+ withChangeForGeoProximityEndpoint (change , ep )
929986 }
930987
931988 if prop , ok := ep .GetProviderSpecificProperty (providerSpecificHealthCheckID ); ok {
@@ -939,6 +996,99 @@ func (p *AWSProvider) newChange(action route53types.ChangeAction, ep *endpoint.E
939996 return change
940997}
941998
999+ func newGeoProximity (ep * endpoint.Endpoint ) * geoProximity {
1000+ return & geoProximity {
1001+ location : & route53types.GeoProximityLocation {},
1002+ endpoint : ep ,
1003+ isSet : false ,
1004+ }
1005+ }
1006+
1007+ func (gp * geoProximity ) withAWSRegion () * geoProximity {
1008+ if prop , ok := gp .endpoint .GetProviderSpecificProperty (providerSpecificGeoProximityLocationAWSRegion ); ok {
1009+ gp .location .AWSRegion = aws .String (prop )
1010+ gp .isSet = true
1011+ }
1012+ return gp
1013+ }
1014+
1015+ // add a method to set the local zone group for the geoproximity location
1016+ func (gp * geoProximity ) withLocalZoneGroup () * geoProximity {
1017+ if prop , ok := gp .endpoint .GetProviderSpecificProperty (providerSpecificGeoProximityLocationLocalZoneGroup ); ok {
1018+ gp .location .LocalZoneGroup = aws .String (prop )
1019+ gp .isSet = true
1020+ }
1021+ return gp
1022+ }
1023+
1024+ // add a method to set the bias for the geoproximity location
1025+ func (gp * geoProximity ) withBias () * geoProximity {
1026+ if prop , ok := gp .endpoint .GetProviderSpecificProperty (providerSpecificGeoProximityLocationBias ); ok {
1027+ bias , err := strconv .ParseInt (prop , 10 , 32 )
1028+ if err != nil {
1029+ log .Warnf ("Failed parsing value of %s: %s: %v; using bias of 0" , providerSpecificGeoProximityLocationBias , prop , err )
1030+ bias = 0
1031+ }
1032+ gp .location .Bias = aws .Int32 (int32 (bias ))
1033+ gp .isSet = true
1034+ }
1035+ return gp
1036+ }
1037+
1038+ // validateCoordinates checks if the given latitude and longitude are valid.
1039+ func validateCoordinates (lat , long string ) error {
1040+ latitude , err := strconv .ParseFloat (lat , 64 )
1041+ if err != nil || latitude < minLatitude || latitude > maxLatitude {
1042+ return fmt .Errorf ("invalid latitude: must be a number between %f and %f" , minLatitude , maxLatitude )
1043+ }
1044+
1045+ longitude , err := strconv .ParseFloat (long , 64 )
1046+ if err != nil || longitude < minLongitude || longitude > maxLongitude {
1047+ return fmt .Errorf ("invalid longitude: must be a number between %f and %f" , minLongitude , maxLongitude )
1048+ }
1049+
1050+ return nil
1051+ }
1052+
1053+ func (gp * geoProximity ) withCoordinates () * geoProximity {
1054+ if prop , ok := gp .endpoint .GetProviderSpecificProperty (providerSpecificGeoProximityLocationCoordinates ); ok {
1055+ coordinates := strings .Split (prop , "," )
1056+ if len (coordinates ) == 2 {
1057+ latitude := coordinates [0 ]
1058+ longitude := coordinates [1 ]
1059+ if err := validateCoordinates (latitude , longitude ); err != nil {
1060+ log .Warnf ("Invalid coordinates %s for name=%s setIdentifier=%s; %v" , prop , gp .endpoint .DNSName , gp .endpoint .SetIdentifier , err )
1061+ } else {
1062+ gp .location .Coordinates = & route53types.Coordinates {
1063+ Latitude : aws .String (latitude ),
1064+ Longitude : aws .String (longitude ),
1065+ }
1066+ gp .isSet = true
1067+ }
1068+ } else {
1069+ log .Warnf ("Invalid coordinates format for %s: %s; expected format 'latitude,longitude'" , providerSpecificGeoProximityLocationCoordinates , prop )
1070+ }
1071+ }
1072+ return gp
1073+ }
1074+
1075+ func (gp * geoProximity ) build () * route53types.GeoProximityLocation {
1076+ if gp .isSet {
1077+ return gp .location
1078+ }
1079+ return nil
1080+ }
1081+
1082+ func withChangeForGeoProximityEndpoint (change * Route53Change , ep * endpoint.Endpoint ) {
1083+ geoProx := newGeoProximity (ep ).
1084+ withAWSRegion ().
1085+ withCoordinates ().
1086+ withLocalZoneGroup ().
1087+ withBias ()
1088+
1089+ change .ResourceRecordSet .GeoProximityLocation = geoProx .build ()
1090+ }
1091+
9421092// 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`)
9431093func findChangesInQueue (changes Route53Changes , queue Route53Changes ) (Route53Changes , Route53Changes ) {
9441094 if queue == nil {
0 commit comments