Skip to content

Commit

Permalink
feat(aws-provider): create flag to support sub-domains match parent
Browse files Browse the repository at this point in the history
The current implementation of external-dns from sig-external-dns does
not support domain filtering (--domain-filter) for sub-domains on Route53,
such as test.sub-domain.domain.com. The function MatchParent was recently
removed from the base code, but it is still necessary for this purpose.
An example of a use case for this support is having a cluster per hosted
zone with a hundred ingress related to that zone with different variants of
sub-domains. With the matchParent function and zone-match-parent flag,
external-dns will now support an extended automatic match for sub-domains.
  • Loading branch information
thiagoluiznunes committed Feb 6, 2024
1 parent 67939d0 commit 70835ab
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 3 deletions.
21 changes: 21 additions & 0 deletions endpoint/domain_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,24 @@ func (df *DomainFilter) UnmarshalJSON(b []byte) error {
*df = NewRegexDomainFilter(include, exclude)
return nil
}

func (df DomainFilter) MatchParent(domain string) bool {
if matchFilter(df.exclude, domain, false) {
return false
}
if len(df.Filters) == 0 {
return true
}

strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))
for _, filter := range df.Filters {
if filter == "" || strings.HasPrefix(filter, ".") {
// We don't check parents if the filter is prefixed with "."
continue
}
if strings.HasSuffix(filter, "."+strippedDomain) {
return true
}
}
return false
}
101 changes: 101 additions & 0 deletions endpoint/domain_filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,3 +668,104 @@ func deserialize[T any](t *testing.T, serialized map[string]T) DomainFilter {

return deserialized
}

func TestDomainFilterMatchParent(t *testing.T) {
parentMatchTests := []domainFilterTest{
{
[]string{"a.example.com."},
[]string{},
[]string{"example.com"},
true,
map[string][]string{
"include": {"a.example.com"},
},
},
{
[]string{" a.example.com "},
[]string{},
[]string{"example.com"},
true,
map[string][]string{
"include": {"a.example.com"},
},
},
{
[]string{""},
[]string{},
[]string{"example.com"},
true,
map[string][]string{},
},
{
[]string{".a.example.com."},
[]string{},
[]string{"example.com"},
false,
map[string][]string{
"include": {".a.example.com"},
},
},
{
[]string{"a.example.com.", "b.example.com"},
[]string{},
[]string{"example.com"},
true,
map[string][]string{
"include": {"a.example.com", "b.example.com"},
},
},
{
[]string{"a.example.com"},
[]string{},
[]string{"b.example.com"},
false,
map[string][]string{
"include": {"a.example.com"},
},
},
{
[]string{"example.com"},
[]string{},
[]string{"example.com"},
false,
map[string][]string{
"include": {"example.com"},
},
},
{
[]string{"example.com"},
[]string{},
[]string{"anexample.com"},
false,
map[string][]string{
"include": {"example.com"},
},
},
{
[]string{""},
[]string{},
[]string{""},
true,
map[string][]string{},
},
}
for i, tt := range parentMatchTests {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
domainFilter := NewDomainFilterWithExclusions(tt.domainFilter, tt.exclusions)

assertSerializes(t, domainFilter, tt.expectedSerialization)
deserialized := deserialize(t, map[string][]string{
"include": tt.domainFilter,
"exclude": tt.exclusions,
})

for _, domain := range tt.domains {
assert.Equal(t, tt.expected, domainFilter.MatchParent(domain), "%v", domain)
assert.Equal(t, tt.expected, domainFilter.MatchParent(domain+"."), "%v", domain+".")

assert.Equal(t, tt.expected, deserialized.MatchParent(domain), "deserialized %v", domain)
assert.Equal(t, tt.expected, deserialized.MatchParent(domain+"."), "deserialized %v", domain+".")
}
})
}
}
3 changes: 3 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type Config struct {
AWSPreferCNAME bool
AWSZoneCacheDuration time.Duration
AWSSDServiceCleanup bool
AWSZoneMatchParent bool
AWSDynamoDBRegion string
AWSDynamoDBTable string
AzureConfigFile string
Expand Down Expand Up @@ -257,6 +258,7 @@ var defaultConfig = &Config{
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
AWSZoneTagFilter: []string{},
AWSZoneMatchParent: false,
AWSAssumeRole: "",
AWSAssumeRoleExternalID: "",
AWSBatchChangeSize: 1000,
Expand Down Expand Up @@ -488,6 +490,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("aws-api-retries", "When using the AWS API, set the maximum number of retries before giving up.").Default(strconv.Itoa(defaultConfig.AWSAPIRetries)).IntVar(&cfg.AWSAPIRetries)
app.Flag("aws-prefer-cname", "When using the AWS provider, prefer using CNAME instead of ALIAS (default: disabled)").BoolVar(&cfg.AWSPreferCNAME)
app.Flag("aws-zones-cache-duration", "When using the AWS provider, set the zones list cache TTL (0s to disable).").Default(defaultConfig.AWSZoneCacheDuration.String()).DurationVar(&cfg.AWSZoneCacheDuration)
app.Flag("aws-zone-match-parent", "Expand limit possible target by sub-domains (default: disabled)").BoolVar(&cfg.AWSZoneMatchParent)
app.Flag("aws-sd-service-cleanup", "When using the AWS CloudMap provider, delete empty Services without endpoints (default: disabled)").BoolVar(&cfg.AWSSDServiceCleanup)
app.Flag("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure)").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile)
app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (required when --provider=azure-private-dns)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup)
Expand Down
15 changes: 12 additions & 3 deletions provider/aws/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,10 @@ type AWSProvider struct {
zoneTypeFilter provider.ZoneTypeFilter
// filter hosted zones by tags
zoneTagFilter provider.ZoneTagFilter
preferCNAME bool
zonesCache *zonesListCache
// extend filter for sub-domains in the zone (e.g. first.us-east-1.example.com)
zoneMatchParent bool
preferCNAME bool
zonesCache *zonesListCache
// queue for collecting changes to submit them in the next iteration, but after all other changes
failedChangesQueue map[string]Route53Changes
}
Expand All @@ -251,6 +253,7 @@ type AWSConfig struct {
ZoneIDFilter provider.ZoneIDFilter
ZoneTypeFilter provider.ZoneTypeFilter
ZoneTagFilter provider.ZoneTagFilter
ZoneMatchParent bool
BatchChangeSize int
BatchChangeInterval time.Duration
EvaluateTargetHealth bool
Expand All @@ -267,6 +270,7 @@ func NewAWSProvider(awsConfig AWSConfig, client Route53API) (*AWSProvider, error
zoneIDFilter: awsConfig.ZoneIDFilter,
zoneTypeFilter: awsConfig.ZoneTypeFilter,
zoneTagFilter: awsConfig.ZoneTagFilter,
zoneMatchParent: awsConfig.ZoneMatchParent,
batchChangeSize: awsConfig.BatchChangeSize,
batchChangeInterval: awsConfig.BatchChangeInterval,
evaluateTargetHealth: awsConfig.EvaluateTargetHealth,
Expand Down Expand Up @@ -301,7 +305,12 @@ func (p *AWSProvider) Zones(ctx context.Context) (map[string]*route53.HostedZone
}

if !p.domainFilter.Match(aws.StringValue(zone.Name)) {
continue
if !p.zoneMatchParent {
continue
}
if !p.domainFilter.MatchParent(aws.StringValue(zone.Name)) {
continue
}
}

// Only fetch tags if a tag filter was specified
Expand Down

0 comments on commit 70835ab

Please sign in to comment.