From 7d21f0b02254d73a1716476bd1533b9232117808 Mon Sep 17 00:00:00 2001 From: Sergio Morales Date: Mon, 14 Oct 2019 13:17:31 -0300 Subject: [PATCH 1/2] chore(aws): Adds BestZoneMatch as an alternative way to target zones. Signed-off-by: Sergio Morales --- main.go | 1 + pkg/apis/externaldns/types.go | 4 +++ provider/aws/aws.go | 48 +++++++++++++++++++++++++++++------ provider/aws/aws_test.go | 36 +++++++++++++++++++++++++- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/main.go b/main.go index f02a9f293e..61d614b4c1 100644 --- a/main.go +++ b/main.go @@ -175,6 +175,7 @@ func main() { APIRetries: cfg.AWSAPIRetries, PreferCNAME: cfg.AWSPreferCNAME, DryRun: cfg.DryRun, + AwsUseBestZoneMatch: cfg.AwsUseBestZoneMatch, }, ) case "aws-sd": diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 80b734c2e7..6678adf3f3 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -142,6 +142,7 @@ type Config struct { TransIPAccountName string TransIPPrivateKeyFile string DigitalOceanAPIPageSize int + AwsUseBestZoneMatch bool } var defaultConfig = &Config{ @@ -241,6 +242,7 @@ var defaultConfig = &Config{ TransIPAccountName: "", TransIPPrivateKeyFile: "", DigitalOceanAPIPageSize: 50, + AwsUseBestZoneMatch: false, } // NewConfig returns new Config object @@ -415,6 +417,8 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("metrics-address", "Specify where to serve the metrics and health check endpoint (default: :7979)").Default(defaultConfig.MetricsAddress).StringVar(&cfg.MetricsAddress) app.Flag("log-level", "Set the level of logging. (default: info, options: panic, debug, info, warning, error, fatal").Default(defaultConfig.LogLevel).EnumVar(&cfg.LogLevel, allLogLevelsAsStrings()...) + // Best Zone Match flag + app.Flag("aws-best-zone-match", "Search for the longest zone suffix possible when filtering zones (default: disabled)").Default(strconv.FormatBool(defaultConfig.AwsUseBestZoneMatch)).BoolVar(&cfg.AwsUseBestZoneMatch) _, err := app.Parse(args) if err != nil { return err diff --git a/provider/aws/aws.go b/provider/aws/aws.go index f94609b29d..1804430b39 100644 --- a/provider/aws/aws.go +++ b/provider/aws/aws.go @@ -131,6 +131,8 @@ type AWSProvider struct { // filter hosted zones by tags zoneTagFilter provider.ZoneTagFilter preferCNAME bool + // find best zone match + awsUseBestZoneMatch bool } // AWSConfig contains configuration to create a new AWS provider. @@ -146,6 +148,7 @@ type AWSConfig struct { APIRetries int PreferCNAME bool DryRun bool + AwsUseBestZoneMatch bool } // NewAWSProvider initializes a new AWS Route53 based Provider. @@ -185,6 +188,7 @@ func NewAWSProvider(awsConfig AWSConfig) (*AWSProvider, error) { evaluateTargetHealth: awsConfig.EvaluateTargetHealth, preferCNAME: awsConfig.PreferCNAME, dryRun: awsConfig.DryRun, + awsUseBestZoneMatch: awsConfig.AwsUseBestZoneMatch, } return provider, nil @@ -408,7 +412,7 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes []*route53.Chan } // separate into per-zone change sets to be passed to the API. - changesByZone := changesByZone(zones, changes) + changesByZone := changesByZone(zones, changes, p.awsUseBestZoneMatch) if len(changesByZone) == 0 { log.Info("All records are already up to date, there are no changes for the matching hosted zones") } @@ -659,22 +663,25 @@ func sortChangesByActionNameType(cs []*route53.Change) []*route53.Change { } // changesByZone separates a multi-zone change into a single change per zone. -func changesByZone(zones map[string]*route53.HostedZone, changeSet []*route53.Change) map[string][]*route53.Change { +func changesByZone(zones map[string]*route53.HostedZone, changeSet []*route53.Change, awsUseBestZoneMatch bool) map[string][]*route53.Change { changes := make(map[string][]*route53.Change) for _, z := range zones { changes[aws.StringValue(z.Id)] = []*route53.Change{} } - for _, c := range changeSet { - hostname := provider.EnsureTrailingDot(aws.StringValue(c.ResourceRecordSet.Name)) - - zones := suitableZones(hostname, zones) - if len(zones) == 0 { + hostname := ensureTrailingDot(aws.StringValue(c.ResourceRecordSet.Name)) + var thisZones []*route53.HostedZone + if awsUseBestZoneMatch { + thisZones = findBestZone(hostname, zones) + } else { + thisZones = suitableZones(hostname, zones) + } + if len(thisZones) == 0 { log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", c.String()) continue } - for _, z := range zones { + for _, z := range thisZones { changes[aws.StringValue(z.Id)] = append(changes[aws.StringValue(z.Id)], c) log.Debugf("Adding %s to zone %s [Id: %s]", hostname, aws.StringValue(z.Name), aws.StringValue(z.Id)) } @@ -717,6 +724,31 @@ func suitableZones(hostname string, zones map[string]*route53.HostedZone) []*rou return matchingZones } +// Return the list of zones that cover the longest suffix. +func findBestZone(hostname string, zones map[string]*route53.HostedZone) []*route53.HostedZone { + // return only one zone with the longest match. + // This allows to only create the record in the best matching domain name. + var matchingZones []*route53.HostedZone + var maxNameLength int + for _, zone := range zones { + if aws.StringValue(zone.Name) == hostname || strings.HasSuffix(hostname, "."+aws.StringValue(zone.Name)) { + // The hosname is zone.name or ends with zone.name as suffix. + if len(aws.StringValue(zone.Name)) >= maxNameLength { + if len(aws.StringValue(zone.Name)) == maxNameLength { + // if len (zone.name) is equal to the longest zone found, append to the current response. + // This should not be a possible scenario + matchingZones = append(matchingZones, zone) + } else { + // if len (zone.name) is greater than the last zone.name found, replace the array with a new one + maxNameLength = len(aws.StringValue(zone.Name)) + matchingZones = []*route53.HostedZone{zone} + } + } + } + } + return matchingZones +} + // useAlias determines if AWS ALIAS should be used. func useAlias(ep *endpoint.Endpoint, preferCNAME bool) bool { if preferCNAME { diff --git a/provider/aws/aws_test.go b/provider/aws/aws_test.go index aaa32741f7..5048d6e3ce 100644 --- a/provider/aws/aws_test.go +++ b/provider/aws/aws_test.go @@ -637,7 +637,7 @@ func TestAWSChangesByZones(t *testing.T) { }, } - changesByZone := changesByZone(zones, changes) + changesByZone := changesByZone(zones, changes, false) require.Len(t, changesByZone, 3) validateAWSChangeRecords(t, changesByZone["foo-example-org"], []*route53.Change{ @@ -686,6 +686,40 @@ func TestAWSChangesByZones(t *testing.T) { }) } +func TestAWSChangesByZonesWithBestzone(t *testing.T) { + changes := []*route53.Change{ + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("bar.foo.example.org"), TTL: aws.Int64(1), + }, + }, + } + + zones := map[string]*route53.HostedZone{ + "foo-example-org": { + Id: aws.String("foo-example-org"), + Name: aws.String("foo.example.org."), + }, + "example-org": { + Id: aws.String("example-org"), + Name: aws.String("example.org."), + }, + } + + changesByZone := changesByZone(zones, changes, true) + require.Len(t, changesByZone, 1) + + validateAWSChangeRecords(t, changesByZone["foo-example-org"], []*route53.Change{ + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("bar.foo.example.org"), TTL: aws.Int64(1), + }, + }, + }) +} + 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, []*endpoint.Endpoint{}) const subnets = 16 From 540b97a0d78932e957b77046c5e9b707c9eb9edb Mon Sep 17 00:00:00 2001 From: Sergio Morales Date: Wed, 24 Jun 2020 14:45:34 -0400 Subject: [PATCH 2/2] chore(aws): Adopted provider.EnsureTrailingDot method Signed-off-by: Sergio Morales --- provider/aws/aws.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/aws/aws.go b/provider/aws/aws.go index 1804430b39..129ba3624f 100644 --- a/provider/aws/aws.go +++ b/provider/aws/aws.go @@ -670,7 +670,7 @@ func changesByZone(zones map[string]*route53.HostedZone, changeSet []*route53.Ch changes[aws.StringValue(z.Id)] = []*route53.Change{} } for _, c := range changeSet { - hostname := ensureTrailingDot(aws.StringValue(c.ResourceRecordSet.Name)) + hostname := provider.EnsureTrailingDot(aws.StringValue(c.ResourceRecordSet.Name)) var thisZones []*route53.HostedZone if awsUseBestZoneMatch { thisZones = findBestZone(hostname, zones)