Skip to content

Commit

Permalink
Merge pull request #4236 from rafatio/master
Browse files Browse the repository at this point in the history
feat(aws-provider): create flag to support sub-domains match parent
  • Loading branch information
k8s-ci-robot committed Feb 7, 2024
2 parents 67939d0 + ca4f3ae commit 042d13b
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 3 deletions.
9 changes: 9 additions & 0 deletions docs/tutorials/aws.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,15 @@ Annotations which are specific to AWS.

`external-dns.alpha.kubernetes.io/aws-target-hosted-zone` can optionally be set to the ID of a Route53 hosted zone. This will force external-dns to use the specified hosted zone when creating an ALIAS target.

### aws-zone-match-parent
`aws-zone-match-parent` allows support subdomains within the same zone by using their parent domain, i.e --domain-filter=x.example.com would create a DNS entry for x.example.com (and subdomains thereof).

```yaml
## hosted zone domain: example.com
--domain-filter=x.example.com,example.com
--aws-zone-match-parent
```

## Verify ExternalDNS works (Service example)

Create the following sample application to test that ExternalDNS works.
Expand Down
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+".")
}
})
}
}
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ func main() {
ZoneIDFilter: zoneIDFilter,
ZoneTypeFilter: zoneTypeFilter,
ZoneTagFilter: zoneTagFilter,
ZoneMatchParent: cfg.AWSZoneMatchParent,
BatchChangeSize: cfg.AWSBatchChangeSize,
BatchChangeInterval: cfg.AWSBatchChangeInterval,
EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth,
Expand Down
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
4 changes: 4 additions & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ var (
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
AWSZoneTagFilter: []string{""},
AWSZoneMatchParent: false,
AWSAssumeRole: "",
AWSAssumeRoleExternalID: "",
AWSBatchChangeSize: 1000,
Expand Down Expand Up @@ -166,6 +167,7 @@ var (
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "private",
AWSZoneTagFilter: []string{"tag=foo"},
AWSZoneMatchParent: true,
AWSAssumeRole: "some-other-role",
AWSAssumeRoleExternalID: "pg2000",
AWSBatchChangeSize: 100,
Expand Down Expand Up @@ -351,6 +353,7 @@ func TestParseFlags(t *testing.T) {
"--exclude-target-net=1.1.0.0/9",
"--aws-zone-type=private",
"--aws-zone-tags=tag=foo",
"--aws-zone-match-parent",
"--aws-assume-role=some-other-role",
"--aws-assume-role-external-id=pg2000",
"--aws-batch-change-size=100",
Expand Down Expand Up @@ -473,6 +476,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_ZONE_ID_FILTER": "/hostedzone/ZTST1\n/hostedzone/ZTST2",
"EXTERNAL_DNS_AWS_ZONE_TYPE": "private",
"EXTERNAL_DNS_AWS_ZONE_TAGS": "tag=foo",
"EXTERNAL_DNS_AWS_ZONE_MATCH_PARENT": "true",
"EXTERNAL_DNS_AWS_ASSUME_ROLE": "some-other-role",
"EXTERNAL_DNS_AWS_ASSUME_ROLE_EXTERNAL_ID": "pg2000",
"EXTERNAL_DNS_AWS_BATCH_CHANGE_SIZE": "100",
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 042d13b

Please sign in to comment.