diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index 23cd0c77b9..922bcf425f 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -18,22 +18,13 @@ Hosted Zone IDs. "Statement": [ { "Effect": "Allow", - "Action": [ - "route53:ChangeResourceRecordSets" - ], - "Resource": [ - "arn:aws:route53:::hostedzone/*" - ] + "Action": ["route53:ChangeResourceRecordSets"], + "Resource": ["arn:aws:route53:::hostedzone/*"] }, { "Effect": "Allow", - "Action": [ - "route53:ListHostedZones", - "route53:ListResourceRecordSets" - ], - "Resource": [ - "*" - ] + "Action": ["route53:ListHostedZones", "route53:ListResourceRecordSets"], + "Resource": ["*"] } ] } @@ -85,7 +76,7 @@ instance metadata service (169.254.169.254). This is allowed by default. ## Set up a hosted zone -*If you prefer to try-out ExternalDNS in one of the existing hosted-zones you can skip this step* +_If you prefer to try-out ExternalDNS in one of the existing hosted-zones you can skip this step_ Create a DNS zone which will contain the managed DNS records. @@ -119,6 +110,7 @@ Then apply one of the following manifests file to deploy ExternalDNS. You can ch For clusters with RBAC enabled, be sure to choose the correct `namespace`. ### Manifest (for clusters without RBAC enabled) + ```yaml apiVersion: apps/v1 kind: Deployment @@ -140,17 +132,17 @@ spec: iam.amazonaws.com/role: arn:aws:iam::ACCOUNT-ID:role/IAM-SERVICE-ROLE-NAME spec: containers: - - name: external-dns - image: registry.opensource.zalan.do/teapot/external-dns:latest - args: - - --source=service - - --source=ingress - - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones - - --provider=aws - - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization - - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both) - - --registry=txt - - --txt-owner-id=my-hostedzone-identifier + - name: external-dns + image: registry.opensource.zalan.do/teapot/external-dns:latest + args: + - --source=service + - --source=ingress + - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones + - --provider=aws + - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization + - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both) + - --registry=txt + - --txt-owner-id=my-hostedzone-identifier ``` ### Manifest (for clusters with RBAC enabled) @@ -171,18 +163,18 @@ kind: ClusterRole metadata: name: external-dns rules: -- apiGroups: [""] - resources: ["services"] - verbs: ["get","watch","list"] -- apiGroups: [""] - resources: ["pods"] - verbs: ["get","watch","list"] -- apiGroups: ["extensions"] - resources: ["ingresses"] - verbs: ["get","watch","list"] -- apiGroups: [""] - resources: ["nodes"] - verbs: ["list","watch"] + - apiGroups: [""] + resources: ["services"] + verbs: ["get", "watch", "list"] + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "watch", "list"] + - apiGroups: ["extensions"] + resources: ["ingresses"] + verbs: ["get", "watch", "list"] + - apiGroups: [""] + resources: ["nodes"] + verbs: ["list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding @@ -193,9 +185,9 @@ roleRef: kind: ClusterRole name: external-dns subjects: -- kind: ServiceAccount - name: external-dns - namespace: default + - kind: ServiceAccount + name: external-dns + namespace: default --- apiVersion: apps/v1 kind: Deployment @@ -218,17 +210,17 @@ spec: spec: serviceAccountName: external-dns containers: - - name: external-dns - image: registry.opensource.zalan.do/teapot/external-dns:latest - args: - - --source=service - - --source=ingress - - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones - - --provider=aws - - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization - - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both) - - --registry=txt - - --txt-owner-id=my-hostedzone-identifier + - name: external-dns + image: registry.opensource.zalan.do/teapot/external-dns:latest + args: + - --source=service + - --source=ingress + - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones + - --provider=aws + - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization + - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both) + - --registry=txt + - --txt-owner-id=my-hostedzone-identifier securityContext: fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes and AWS token files ``` @@ -241,6 +233,10 @@ This list is not the full list, but a few arguments that where chosen. `aws-zone-type` allows filtering for private and public zones +### subdomainFilter + +`subdomainFilter` allows only domains matched by the subdomainfilter to be created. + ## Annotations Annotations which are specific to AWS. @@ -264,12 +260,12 @@ metadata: kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller. spec: rules: - - host: foo.bar.com - http: - paths: - - backend: - serviceName: foo - servicePort: 80 + - host: foo.bar.com + http: + paths: + - backend: + serviceName: foo + servicePort: 80 ``` ## Verify ExternalDNS works (Service example) @@ -290,14 +286,13 @@ metadata: spec: type: LoadBalancer ports: - - port: 80 - name: http - targetPort: 80 + - port: 80 + name: http + targetPort: 80 selector: app: nginx --- - apiVersion: apps/v1 kind: Deployment metadata: @@ -312,11 +307,11 @@ spec: app: nginx spec: containers: - - image: nginx - name: nginx - ports: - - containerPort: 80 - name: http + - image: nginx + name: nginx + ports: + - containerPort: 80 + name: http ``` After roughly two minutes check that a corresponding DNS record for your service was created. @@ -387,8 +382,7 @@ metadata: annotations: external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com external-dns.alpha.kubernetes.io/ttl: 60 -spec: - ... +spec: ... ``` This will set the DNS record's TTL to 60 seconds. @@ -397,18 +391,18 @@ This will set the DNS record's TTL to 60 seconds. Route53 offers [different routing policies](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html). The routing policy for a record can be controlled with the following annotations: -* `external-dns.alpha.kubernetes.io/set-identifier`: this **needs** to be set to use any of the following routing policies +- `external-dns.alpha.kubernetes.io/set-identifier`: this **needs** to be set to use any of the following routing policies For any given DNS name, only **one** of the following routing policies can be used: -* Weighted records: `external-dns.alpha.kubernetes.io/aws-weight` -* Latency-based routing: `external-dns.alpha.kubernetes.io/aws-region` -* Failover:`external-dns.alpha.kubernetes.io/aws-failover` -* Geolocation-based routing: - * `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` -* Multi-value answer:`external-dns.alpha.kubernetes.io/aws-multi-value-answer` +- Weighted records: `external-dns.alpha.kubernetes.io/aws-weight` +- Latency-based routing: `external-dns.alpha.kubernetes.io/aws-region` +- Failover:`external-dns.alpha.kubernetes.io/aws-failover` +- Geolocation-based routing: + - `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` +- Multi-value answer:`external-dns.alpha.kubernetes.io/aws-multi-value-answer` ## Clean up diff --git a/main.go b/main.go index eca48b351f..49a214906e 100644 --- a/main.go +++ b/main.go @@ -108,6 +108,7 @@ func main() { zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter) zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType) zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter) + subdomainFilter := provider.NewDomainFilter(cfg.AWSSubdomainFilter) var p provider.Provider switch cfg.Provider { @@ -132,6 +133,7 @@ func main() { ZoneIDFilter: zoneIDFilter, ZoneTypeFilter: zoneTypeFilter, ZoneTagFilter: zoneTagFilter, + SubdomainFilter: subdomainFilter, BatchChangeSize: cfg.AWSBatchChangeSize, BatchChangeInterval: cfg.AWSBatchChangeInterval, EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth, diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index c24761287a..aed870f92a 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -63,6 +63,7 @@ type Config struct { AlibabaCloudZoneType string AWSZoneType string AWSZoneTagFilter []string + AWSSubdomainFilter []string AWSAssumeRole string AWSBatchChangeSize int AWSBatchChangeInterval time.Duration @@ -160,6 +161,7 @@ var defaultConfig = &Config{ AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", AWSZoneType: "", AWSZoneTagFilter: []string{}, + AWSSubdomainFilter: []string{}, AWSAssumeRole: "", AWSBatchChangeSize: 1000, AWSBatchChangeInterval: time.Second, @@ -300,6 +302,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns") app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter) app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains) + app.Flag("subdomain-filter", "Allow only changes to specific subdomain").Default("").StringsVar(&cfg.AWSSubdomainFilter) app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter) app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject) app.Flag("google-batch-change-size", "When using the Google provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.GoogleBatchChangeSize)).IntVar(&cfg.GoogleBatchChangeSize) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 6a2b89fb23..d2f2e406d6 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -43,6 +43,7 @@ var ( GoogleBatchChangeInterval: time.Second, DomainFilter: []string{""}, ExcludeDomains: []string{""}, + AWSSubdomainFilter: []string{""}, ZoneIDFilter: []string{""}, AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", AWSZoneType: "", @@ -112,6 +113,7 @@ var ( GoogleBatchChangeSize: 100, GoogleBatchChangeInterval: time.Second * 2, DomainFilter: []string{"example.org", "company.com"}, + AWSSubdomainFilter: []string{"foo.example.org"}, ExcludeDomains: []string{"xapi.example.org", "xapi.company.com"}, ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"}, AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json", @@ -241,6 +243,7 @@ func TestParseFlags(t *testing.T) { "--exclude-domains=xapi.company.com", "--zone-id-filter=/hostedzone/ZTST1", "--zone-id-filter=/hostedzone/ZTST2", + "--subdomain-filter=foo.example.org", "--aws-zone-type=private", "--aws-zone-tags=tag=foo", "--aws-assume-role=some-other-role", @@ -314,6 +317,7 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_INMEMORY_ZONE": "example.org\ncompany.com", "EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com", "EXTERNAL_DNS_EXCLUDE_DOMAINS": "xapi.example.org\nxapi.company.com", + "EXTERNAL_DNS_SUBDOMAIN_FILTER": "foo.example.org", "EXTERNAL_DNS_PDNS_SERVER": "http://ns.example.com:8081", "EXTERNAL_DNS_PDNS_API_KEY": "some-secret-key", "EXTERNAL_DNS_PDNS_TLS_ENABLED": "1", diff --git a/provider/aws.go b/provider/aws.go index 2cbfee31e3..bc9525e78c 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -124,7 +124,9 @@ type AWSProvider struct { zoneTypeFilter ZoneTypeFilter // filter hosted zones by tags zoneTagFilter ZoneTagFilter - preferCNAME bool + // only allow changes to specified subdomain and its subdomains + subdomainFilter DomainFilter + preferCNAME bool } // AWSConfig contains configuration to create a new AWS provider. @@ -133,6 +135,7 @@ type AWSConfig struct { ZoneIDFilter ZoneIDFilter ZoneTypeFilter ZoneTypeFilter ZoneTagFilter ZoneTagFilter + SubdomainFilter DomainFilter BatchChangeSize int BatchChangeInterval time.Duration EvaluateTargetHealth bool @@ -174,6 +177,7 @@ func NewAWSProvider(awsConfig AWSConfig) (*AWSProvider, error) { zoneIDFilter: awsConfig.ZoneIDFilter, zoneTypeFilter: awsConfig.ZoneTypeFilter, zoneTagFilter: awsConfig.ZoneTagFilter, + subdomainFilter: awsConfig.SubdomainFilter, batchChangeSize: awsConfig.BatchChangeSize, batchChangeInterval: awsConfig.BatchChangeInterval, evaluateTargetHealth: awsConfig.EvaluateTargetHealth, @@ -401,8 +405,10 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes []*route53.Chan return nil } + filteredChangesBySubdomains := filteredChangesBySubdomains(changes, p) + // separate into per-zone change sets to be passed to the API. - changesByZone := changesByZone(zones, changes) + changesByZone := changesByZone(zones, filteredChangesBySubdomains) if len(changesByZone) == 0 { log.Info("All records are already up to date, there are no changes for the matching hosted zones") } @@ -651,6 +657,18 @@ func sortChangesByActionNameType(cs []*route53.Change) []*route53.Change { return cs } +func filteredChangesBySubdomains(changeSet []*route53.Change, p *AWSProvider) []*route53.Change { + changes := []*route53.Change{} + + for _, c := range changeSet { + hostname := aws.StringValue(c.ResourceRecordSet.Name) + if p.subdomainFilter.Match(hostname) { + changes = append(changes, c) + } + } + return changes +} + // 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 { changes := make(map[string][]*route53.Change) diff --git a/provider/aws_test.go b/provider/aws_test.go index 1dc1120100..726e870b82 100644 --- a/provider/aws_test.go +++ b/provider/aws_test.go @@ -301,7 +301,7 @@ func TestAWSZones(t *testing.T) { {"zone id filter", NewZoneIDFilter([]string{"/hostedzone/zone-3.ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), NewZoneTagFilter([]string{}), privateZones}, {"tag filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), NewZoneTagFilter([]string{"zone=3"}), privateZones}, } { - provider, _ := newAWSProviderWithTagFilter(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), ti.zoneIDFilter, ti.zoneTypeFilter, ti.zoneTagFilter, defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) + provider, _ := newAWSProviderWithTagFilter(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), ti.zoneIDFilter, ti.zoneTypeFilter, ti.zoneTagFilter, defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) zones, err := provider.Zones(context.Background()) require.NoError(t, err) @@ -311,7 +311,7 @@ func TestAWSZones(t *testing.T) { } func TestAWSRecords(t *testing.T) { - provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, false, []*endpoint.Endpoint{ + provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, false, []*endpoint.Endpoint{ endpoint.NewEndpointWithTTL("list-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"), endpoint.NewEndpointWithTTL("list-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"), endpoint.NewEndpointWithTTL("*.wildcard-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"), @@ -353,7 +353,7 @@ func TestAWSRecords(t *testing.T) { func TestAWSCreateRecords(t *testing.T) { customTTL := endpoint.TTL(60) - provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) + provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) records := []*endpoint.Endpoint{ endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"), @@ -378,7 +378,7 @@ func TestAWSCreateRecords(t *testing.T) { } func TestAWSUpdateRecords(t *testing.T) { - provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{ + provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{ endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"), endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"), endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"), @@ -421,7 +421,7 @@ func TestAWSDeleteRecords(t *testing.T) { endpoint.NewEndpointWithTTL("delete-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"), } - provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, false, originalEndpoints) + provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, false, originalEndpoints) require.NoError(t, provider.DeleteRecords(context.Background(), originalEndpoints)) @@ -448,7 +448,7 @@ func TestAWSApplyChanges(t *testing.T) { } for _, tt := range tests { - provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{ + provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{ endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"), endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"), endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"), @@ -540,7 +540,7 @@ func TestAWSApplyChangesDryRun(t *testing.T) { endpoint.NewEndpointWithTTL("delete-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4", "4.3.2.1"), } - provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, true, originalEndpoints) + provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, true, originalEndpoints) createRecords := []*endpoint.Endpoint{ endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"), @@ -688,7 +688,7 @@ func TestAWSChangesByZones(t *testing.T) { } func TestAWSsubmitChanges(t *testing.T) { - provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) + provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) const subnets = 16 const hosts = defaultBatchChangeSize / subnets @@ -717,7 +717,7 @@ func TestAWSsubmitChanges(t *testing.T) { } func TestAWSsubmitChangesError(t *testing.T) { - provider, clientStub := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) + provider, clientStub := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) clientStub.MockMethod("ChangeResourceRecordSets", mock.Anything).Return(nil, fmt.Errorf("Mock route53 failure")) ctx := context.Background() @@ -853,7 +853,7 @@ func validateAWSChangeRecord(t *testing.T, record *route53.Change, expected *rou } func TestAWSCreateRecordsWithCNAME(t *testing.T) { - provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) + provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) records := []*endpoint.Endpoint{ {DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Targets: endpoint.Targets{"foo.example.org"}, RecordType: endpoint.RecordTypeCNAME}, @@ -883,7 +883,7 @@ func TestAWSCreateRecordsWithALIAS(t *testing.T) { "false": false, "": false, } { - provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) + provider, _ := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewDomainFilter([]string{}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) // Test dualstack and ipv4 load balancer targets records := []*endpoint.Endpoint{ @@ -1092,6 +1092,70 @@ func TestAWSSuitableZones(t *testing.T) { } } +func TestSubdomainFilter(t *testing.T) { + provider, _ := newAWSProvider(t, NewDomainFilter([]string{}), NewDomainFilter([]string{"foo.example.org"}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{}) + + validChanges := []*route53.Change{ + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("bar.foo.example.org"), + }, + }, + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("dev.bar.foo.example.org"), + }, + }, + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("db-dev.foo.example.org"), + }, + }, + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("foo.example.org"), + }, + }, + } + + invalidChanges := []*route53.Change{ + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("foo.bar.example.org"), + }, + }, + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("foobar.example.org"), + }, + }, + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("bar.foo.bar.example.org"), + }, + }, + { + Action: aws.String(route53.ChangeActionCreate), + ResourceRecordSet: &route53.ResourceRecordSet{ + Name: aws.String("foo.example.com"), + }, + }, + } + + validFilteredChanges := filteredChangesBySubdomains(validChanges, provider) + validateAWSChangeRecords(t, validChanges, validFilteredChanges) + + invalidFilteredChanges := filteredChangesBySubdomains(invalidChanges, provider) + validateAWSChangeRecords(t, invalidFilteredChanges, []*route53.Change{}) +} + func createAWSZone(t *testing.T, provider *AWSProvider, zone *route53.HostedZone) { params := &route53.CreateHostedZoneInput{ CallerReference: aws.String("external-dns.alpha.kubernetes.io/test-zone"), @@ -1184,11 +1248,11 @@ func escapeAWSRecords(t *testing.T, provider *AWSProvider, zone string) { require.NoError(t, err) } } -func newAWSProvider(t *testing.T, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) { - return newAWSProviderWithTagFilter(t, domainFilter, zoneIDFilter, zoneTypeFilter, NewZoneTagFilter([]string{}), evaluateTargetHealth, dryRun, records) +func newAWSProvider(t *testing.T, domainFilter DomainFilter, subdomainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) { + return newAWSProviderWithTagFilter(t, domainFilter, subdomainFilter, zoneIDFilter, zoneTypeFilter, NewZoneTagFilter([]string{}), evaluateTargetHealth, dryRun, records) } -func newAWSProviderWithTagFilter(t *testing.T, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, zoneTagFilter ZoneTagFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) { +func newAWSProviderWithTagFilter(t *testing.T, domainFilter DomainFilter, subdomainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, zoneTagFilter ZoneTagFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) { client := NewRoute53APIStub() provider := &AWSProvider{ @@ -1197,6 +1261,7 @@ func newAWSProviderWithTagFilter(t *testing.T, domainFilter DomainFilter, zoneID batchChangeInterval: defaultBatchChangeInterval, evaluateTargetHealth: evaluateTargetHealth, domainFilter: domainFilter, + subdomainFilter: subdomainFilter, zoneIDFilter: zoneIDFilter, zoneTypeFilter: zoneTypeFilter, zoneTagFilter: zoneTagFilter,