diff --git a/CHANGELOG.md b/CHANGELOG.md index c35588162..e0e07ce1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ + - The flag `--domain-filter` can be repeated multiple times like `--domain-filter=example.com --domain-filter=company.org.`. + - A trailing period is not required anymore for `--domain-filter` when AWS (or any other) provider is used. + ## v0.3.0 - 2017-05-08 Features: diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index a3c27cf01..89676999b 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -84,7 +84,7 @@ spec: 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 + - --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 - --registry=txt diff --git a/docs/tutorials/gke.md b/docs/tutorials/gke.md index c380b55ca..abb2b8a9d 100644 --- a/docs/tutorials/gke.md +++ b/docs/tutorials/gke.md @@ -81,7 +81,7 @@ spec: args: - --source=service - --source=ingress - - --domain-filter=external-dns-test.gcp.zalan.do. #will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones + - --domain-filter=external-dns-test.gcp.zalan.do # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones - --provider=google - --google-project=zalando-external-dns-test - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization diff --git a/docs/tutorials/nginx-ingress.md b/docs/tutorials/nginx-ingress.md index 98ebc3eac..f5e518653 100644 --- a/docs/tutorials/nginx-ingress.md +++ b/docs/tutorials/nginx-ingress.md @@ -220,7 +220,7 @@ spec: image: registry.opensource.zalan.do/teapot/external-dns:v0.3.0 args: - --source=ingress - - --domain-filter=external-dns-test.gcp.zalan.do. + - --domain-filter=external-dns-test.gcp.zalan.do - --provider=google - --google-project=zalando-external-dns-test - --registry=txt diff --git a/main.go b/main.go index d68087dee..9d3ac1ba7 100644 --- a/main.go +++ b/main.go @@ -108,20 +108,22 @@ func main() { endpointsSource := source.NewDedupSource(source.NewMultiSource(sources)) + domainFilter := provider.NewDomainFilter(cfg.DomainFilter) + var p provider.Provider switch cfg.Provider { case "aws": - p, err = provider.NewAWSProvider(cfg.DomainFilter, cfg.DryRun) + p, err = provider.NewAWSProvider(domainFilter, cfg.DryRun) case "azure": - p, err = provider.NewAzureProvider(cfg.AzureConfigFile, cfg.DomainFilter, cfg.AzureResourceGroup, cfg.DryRun) + p, err = provider.NewAzureProvider(cfg.AzureConfigFile, domainFilter, cfg.AzureResourceGroup, cfg.DryRun) case "cloudflare": - p, err = provider.NewCloudFlareProvider(cfg.DomainFilter, cfg.DryRun) + p, err = provider.NewCloudFlareProvider(domainFilter, cfg.DryRun) case "google": - p, err = provider.NewGoogleProvider(cfg.GoogleProject, cfg.DomainFilter, cfg.DryRun) + p, err = provider.NewGoogleProvider(cfg.GoogleProject, domainFilter, cfg.DryRun) case "digitalocean": - p, err = provider.NewDigitalOceanProvider(cfg.DomainFilter, cfg.DryRun) + p, err = provider.NewDigitalOceanProvider(domainFilter, cfg.DryRun) case "inmemory": - p, err = provider.NewInMemoryProvider(provider.InMemoryWithDomain(cfg.DomainFilter), provider.InMemoryWithLogging()), nil + p, err = provider.NewInMemoryProvider(provider.InMemoryWithDomain(domainFilter), provider.InMemoryWithLogging()), nil default: log.Fatalf("unknown dns provider: %s", cfg.Provider) } diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 5ee83e832..d5156a832 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -36,7 +36,7 @@ type Config struct { Compatibility string Provider string GoogleProject string - DomainFilter string + DomainFilter []string AzureConfigFile string AzureResourceGroup string Policy string @@ -60,7 +60,7 @@ var defaultConfig = &Config{ Compatibility: "", Provider: "", GoogleProject: "", - DomainFilter: "", + DomainFilter: []string{}, AzureConfigFile: "/etc/kubernetes/azure.json", AzureResourceGroup: "", Policy: "sync", @@ -99,7 +99,7 @@ func (cfg *Config) ParseFlags(args []string) error { // Flags related to providers app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, inmemory)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "inmemory") app.Flag("google-project", "When using the Google provider, specify the Google project (required when --provider=google)").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject) - app.Flag("domain-filter", "Limit possible target zones by a domain suffix (optional)").Default(defaultConfig.DomainFilter).StringVar(&cfg.DomainFilter) + 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("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 (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 83313c2e7..84e8363ee 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -35,7 +35,7 @@ var ( Compatibility: "", Provider: "google", GoogleProject: "", - DomainFilter: "", + DomainFilter: []string{""}, AzureConfigFile: "/etc/kubernetes/azure.json", AzureResourceGroup: "", Policy: "sync", @@ -59,7 +59,7 @@ var ( Compatibility: "mate", Provider: "google", GoogleProject: "project", - DomainFilter: "example.org.", + DomainFilter: []string{"example.org", "company.com"}, AzureConfigFile: "azure.json", AzureResourceGroup: "arg", Policy: "upsert-only", @@ -105,7 +105,8 @@ func TestParseFlags(t *testing.T) { "--google-project=project", "--azure-config-file=azure.json", "--azure-resource-group=arg", - "--domain-filter=example.org.", + "--domain-filter=example.org", + "--domain-filter=company.com", "--policy=upsert-only", "--registry=noop", "--txt-owner-id=owner-1", @@ -134,7 +135,7 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_GOOGLE_PROJECT": "project", "EXTERNAL_DNS_AZURE_CONFIG_FILE": "azure.json", "EXTERNAL_DNS_AZURE_RESOURCE_GROUP": "arg", - "EXTERNAL_DNS_DOMAIN_FILTER": "example.org.", + "EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com", "EXTERNAL_DNS_POLICY": "upsert-only", "EXTERNAL_DNS_REGISTRY": "noop", "EXTERNAL_DNS_TXT_OWNER_ID": "owner-1", diff --git a/provider/aws.go b/provider/aws.go index dfe90dd94..4a5ee5cf7 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -70,11 +70,11 @@ type AWSProvider struct { client Route53API dryRun bool // only consider hosted zones managing domains ending in this suffix - domainFilter string + domainFilter DomainFilter } // NewAWSProvider initializes a new AWS Route53 based Provider. -func NewAWSProvider(domainFilter string, dryRun bool) (*AWSProvider, error) { +func NewAWSProvider(domainFilter DomainFilter, dryRun bool) (*AWSProvider, error) { config := aws.NewConfig() config = config.WithHTTPClient( @@ -109,7 +109,7 @@ func (p *AWSProvider) Zones() (map[string]*route53.HostedZone, error) { f := func(resp *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool) { for _, zone := range resp.HostedZones { - if strings.HasSuffix(aws.StringValue(zone.Name), p.domainFilter) { + if p.domainFilter.Match(aws.StringValue(zone.Name)) { zones[aws.StringValue(zone.Id)] = zone } } diff --git a/provider/aws_test.go b/provider/aws_test.go index 09f8c3efe..72e1aa291 100644 --- a/provider/aws_test.go +++ b/provider/aws_test.go @@ -23,7 +23,6 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/route53" - "github.com/kubernetes-incubator/external-dns/endpoint" "github.com/kubernetes-incubator/external-dns/internal/testutils" "github.com/kubernetes-incubator/external-dns/plan" @@ -145,7 +144,7 @@ func (r *Route53APIStub) CreateHostedZone(input *route53.CreateHostedZoneInput) } func TestAWSZones(t *testing.T) { - provider := newAWSProvider(t, "ext-dns-test-2.teapot.zalan.do.", false, []*endpoint.Endpoint{}) + provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), false, []*endpoint.Endpoint{}) zones, err := provider.Zones() require.NoError(t, err) @@ -167,7 +166,7 @@ func TestAWSZones(t *testing.T) { } func TestAWSRecords(t *testing.T) { - provider := newAWSProvider(t, "ext-dns-test-2.teapot.zalan.do.", false, []*endpoint.Endpoint{ + provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), false, []*endpoint.Endpoint{ endpoint.NewEndpoint("list-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", "A"), endpoint.NewEndpoint("list-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", "A"), endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.eu-central-1.elb.amazonaws.com", "ALIAS"), @@ -184,7 +183,7 @@ func TestAWSRecords(t *testing.T) { } func TestAWSCreateRecords(t *testing.T) { - provider := newAWSProvider(t, "ext-dns-test-2.teapot.zalan.do.", false, []*endpoint.Endpoint{}) + provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), false, []*endpoint.Endpoint{}) records := []*endpoint.Endpoint{ endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", ""), @@ -205,7 +204,7 @@ func TestAWSCreateRecords(t *testing.T) { } func TestAWSUpdateRecords(t *testing.T) { - provider := newAWSProvider(t, "ext-dns-test-2.teapot.zalan.do.", false, []*endpoint.Endpoint{ + provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), false, []*endpoint.Endpoint{ endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", "A"), endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", "A"), endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", "CNAME"), @@ -243,7 +242,7 @@ func TestAWSDeleteRecords(t *testing.T) { endpoint.NewEndpoint("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.eu-central-1.elb.amazonaws.com", "CNAME"), } - provider := newAWSProvider(t, "ext-dns-test-2.teapot.zalan.do.", false, originalEndpoints) + provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), false, originalEndpoints) require.NoError(t, provider.DeleteRecords(originalEndpoints)) @@ -254,7 +253,7 @@ func TestAWSDeleteRecords(t *testing.T) { } func TestAWSApplyChanges(t *testing.T) { - provider := newAWSProvider(t, "ext-dns-test-2.teapot.zalan.do.", false, []*endpoint.Endpoint{ + provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), false, []*endpoint.Endpoint{ endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", "A"), endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", "A"), endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", "A"), @@ -328,7 +327,7 @@ func TestAWSApplyChangesDryRun(t *testing.T) { endpoint.NewEndpoint("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", "ALIAS"), } - provider := newAWSProvider(t, "ext-dns-test-2.teapot.zalan.do.", true, originalEndpoints) + provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), true, originalEndpoints) createRecords := []*endpoint.Endpoint{ endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", ""), @@ -480,7 +479,7 @@ func validateAWSChangeRecord(t *testing.T, record *route53.Change, expected *rou } func TestAWSCreateRecordsWithCNAME(t *testing.T) { - provider := newAWSProvider(t, "ext-dns-test-2.teapot.zalan.do.", false, []*endpoint.Endpoint{}) + provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), false, []*endpoint.Endpoint{}) records := []*endpoint.Endpoint{ {DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Target: "foo.example.org"}, @@ -505,7 +504,7 @@ func TestAWSCreateRecordsWithCNAME(t *testing.T) { } func TestAWSCreateRecordsWithALIAS(t *testing.T) { - provider := newAWSProvider(t, "ext-dns-test-2.teapot.zalan.do.", false, []*endpoint.Endpoint{}) + provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), false, []*endpoint.Endpoint{}) records := []*endpoint.Endpoint{ {DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Target: "foo.eu-central-1.elb.amazonaws.com"}, @@ -662,7 +661,7 @@ func clearAWSRecords(t *testing.T, provider *AWSProvider, zone string) { } } -func newAWSProvider(t *testing.T, domainFilter string, dryRun bool, records []*endpoint.Endpoint) *AWSProvider { +func newAWSProvider(t *testing.T, domainFilter DomainFilter, dryRun bool, records []*endpoint.Endpoint) *AWSProvider { client := NewRoute53APIStub() provider := &AWSProvider{ diff --git a/provider/azure.go b/provider/azure.go index 98c929548..6ca18698d 100644 --- a/provider/azure.go +++ b/provider/azure.go @@ -65,7 +65,7 @@ type RecordsClient interface { // AzureProvider implements the DNS provider for Microsoft's Azure cloud platform. type AzureProvider struct { - domainFilter string + domainFilter DomainFilter dryRun bool resourceGroup string zonesClient ZonesClient @@ -75,7 +75,7 @@ type AzureProvider struct { // NewAzureProvider creates a new Azure provider. // // Returns the provider or an error if a provider could not be created. -func NewAzureProvider(configFile string, domainFilter string, resourceGroup string, dryRun bool) (*AzureProvider, error) { +func NewAzureProvider(configFile string, domainFilter DomainFilter, resourceGroup string, dryRun bool) (*AzureProvider, error) { contents, err := ioutil.ReadFile(configFile) if err != nil { return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err) @@ -195,7 +195,7 @@ func (p *AzureProvider) zones() ([]dns.Zone, error) { for list.Value != nil && len(*list.Value) > 0 { for _, zone := range *list.Value { - if zone.Name != nil && strings.HasSuffix(*zone.Name, p.domainFilter) { + if zone.Name != nil && p.domainFilter.Match(*zone.Name) { zones = append(zones, zone) } } diff --git a/provider/azure_test.go b/provider/azure_test.go index 393b09371..fce3f2b35 100644 --- a/provider/azure_test.go +++ b/provider/azure_test.go @@ -138,7 +138,7 @@ func (client *mockRecordsClient) CreateOrUpdate(resourceGroupName string, zoneNa return parameters, nil } -func newAzureProvider(domainFilter string, dryRun bool, resourceGroup string, zonesClient ZonesClient, recordsClient RecordsClient) *AzureProvider { +func newAzureProvider(domainFilter DomainFilter, dryRun bool, resourceGroup string, zonesClient ZonesClient, recordsClient RecordsClient) *AzureProvider { return &AzureProvider{ domainFilter: domainFilter, dryRun: dryRun, @@ -169,7 +169,7 @@ func TestAzureRecord(t *testing.T) { }, } - provider := newAzureProvider("example.com", true, "k8s", &zonesClient, &recordsClient) + provider := newAzureProvider(NewDomainFilter([]string{"example.com"}), true, "k8s", &zonesClient, &recordsClient) actual, err := provider.Records() if err != nil { @@ -224,7 +224,7 @@ func TestAzureApplyChangesDryRun(t *testing.T) { func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordsClient) { provider := newAzureProvider( - "", + NewDomainFilter([]string{""}), dryRun, "group", &mockZonesClient{ diff --git a/provider/cloudflare.go b/provider/cloudflare.go index 91b7ad556..8e1577a2a 100644 --- a/provider/cloudflare.go +++ b/provider/cloudflare.go @@ -83,7 +83,7 @@ func (z zoneService) DeleteDNSRecord(zoneID, recordID string) error { type CloudFlareProvider struct { Client cloudFlareDNS // only consider hosted zones managing domains ending in this suffix - domainFilter string + domainFilter DomainFilter DryRun bool } @@ -94,7 +94,7 @@ type cloudFlareChange struct { } // NewCloudFlareProvider initializes a new CloudFlare DNS based Provider. -func NewCloudFlareProvider(domainFilter string, dryRun bool) (*CloudFlareProvider, error) { +func NewCloudFlareProvider(domainFilter DomainFilter, dryRun bool) (*CloudFlareProvider, error) { // initialize via API email and API key and returns new API object config, err := cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL")) if err != nil { @@ -119,7 +119,7 @@ func (p *CloudFlareProvider) Zones() ([]cloudflare.Zone, error) { } for _, zone := range zones { - if strings.HasSuffix(zone.Name, p.domainFilter) { + if p.domainFilter.Match(zone.Name) { result = append(result, zone) } } diff --git a/provider/cloudflare_test.go b/provider/cloudflare_test.go index b9a4fb996..69db45913 100644 --- a/provider/cloudflare_test.go +++ b/provider/cloudflare_test.go @@ -344,7 +344,7 @@ func TestNewCloudFlareChanges(t *testing.T) { func TestCloudFlareZones(t *testing.T) { provider := &CloudFlareProvider{ Client: &mockCloudFlareClient{}, - domainFilter: "zalando.to.", + domainFilter: NewDomainFilter([]string{"zalando.to."}), } zones, err := provider.Zones() @@ -382,13 +382,13 @@ func TestRecords(t *testing.T) { func TestNewCloudFlareProvider(t *testing.T) { _ = os.Setenv("CF_API_KEY", "xxxxxxxxxxxxxxxxx") _ = os.Setenv("CF_API_EMAIL", "test@test.com") - _, err := NewCloudFlareProvider("ext-dns-test.zalando.to.", true) + _, err := NewCloudFlareProvider(NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true) if err != nil { t.Errorf("should not fail, %s", err) } _ = os.Unsetenv("CF_API_KEY") _ = os.Unsetenv("CF_API_EMAIL") - _, err = NewCloudFlareProvider("ext-dns-test.zalando.to.", true) + _, err = NewCloudFlareProvider(NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true) if err == nil { t.Errorf("expected to fail") } diff --git a/provider/digital_ocean.go b/provider/digital_ocean.go index 73c34a1a5..349d309f8 100644 --- a/provider/digital_ocean.go +++ b/provider/digital_ocean.go @@ -44,7 +44,7 @@ const ( type DigitalOceanProvider struct { Client godo.DomainsService // only consider hosted zones managing domains ending in this suffix - domainFilter string + domainFilter DomainFilter DryRun bool } @@ -55,7 +55,7 @@ type DigitalOceanChange struct { } // NewDigitalOceanProvider initializes a new DigitalOcean DNS based Provider. -func NewDigitalOceanProvider(domainFilter string, dryRun bool) (*DigitalOceanProvider, error) { +func NewDigitalOceanProvider(domainFilter DomainFilter, dryRun bool) (*DigitalOceanProvider, error) { token, ok := os.LookupEnv("DO_TOKEN") if !ok { return nil, fmt.Errorf("No token found") @@ -83,7 +83,7 @@ func (p *DigitalOceanProvider) Zones() ([]godo.Domain, error) { } for _, zone := range zones { - if strings.HasSuffix(zone.Name, p.domainFilter) { + if p.domainFilter.Match(zone.Name) { result = append(result, zone) } } diff --git a/provider/digital_ocean_test.go b/provider/digital_ocean_test.go index 2a57c7103..6a7427dd0 100644 --- a/provider/digital_ocean_test.go +++ b/provider/digital_ocean_test.go @@ -393,7 +393,7 @@ func TestNewDigitalOceanChanges(t *testing.T) { func TestDigitalOceanZones(t *testing.T) { provider := &DigitalOceanProvider{ Client: &mockDigitalOceanClient{}, - domainFilter: "com", + domainFilter: NewDomainFilter([]string{"com"}), } zones, err := provider.Zones() @@ -442,12 +442,12 @@ func TestDigitalOceanApplyChanges(t *testing.T) { func TestNewDigitalOceanProvider(t *testing.T) { _ = os.Setenv("DO_TOKEN", "xxxxxxxxxxxxxxxxx") - _, err := NewDigitalOceanProvider("ext-dns-test.zalando.to.", true) + _, err := NewDigitalOceanProvider(NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true) if err != nil { t.Errorf("should not fail, %s", err) } _ = os.Unsetenv("DO_TOKEN") - _, err = NewDigitalOceanProvider("ext-dns-test.zalando.to.", true) + _, err = NewDigitalOceanProvider(NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true) if err == nil { t.Errorf("expected to fail") } diff --git a/provider/domainfilter.go b/provider/domainfilter.go new file mode 100644 index 000000000..80be2c397 --- /dev/null +++ b/provider/domainfilter.go @@ -0,0 +1,56 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provider + +import ( + "strings" +) + +// DomainFilter holds a lists of valid domain names +type DomainFilter struct { + filters []string +} + +// NewDomainFilter returns a new DomainFilter given a comma separated list of domains +func NewDomainFilter(domainFilters []string) DomainFilter { + filters := make([]string, len(domainFilters)) + + // user can define filter domains either with trailing dot or without, we remove all trailing periods from + // the internal representation + for i, domain := range domainFilters { + filters[i] = strings.TrimSuffix(strings.TrimSpace(domain), ".") + } + + return DomainFilter{filters} +} + +// Match checks whether a domain can be found in the DomainFilter. +func (df DomainFilter) Match(domain string) bool { + // return always true, if not filter is specified + if len(df.filters) == 0 { + return true + } + + for _, filter := range df.filters { + + if strings.HasSuffix(strings.TrimSuffix(domain, "."), filter) { + return true + } + } + + return false +} diff --git a/provider/domainfilter_test.go b/provider/domainfilter_test.go new file mode 100644 index 000000000..cb11ebe2f --- /dev/null +++ b/provider/domainfilter_test.go @@ -0,0 +1,122 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provider + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type domainFilterTest struct { + domainFilter []string + domains []string + expected bool +} + +var domainFilterTests = []domainFilterTest{ + { + []string{"google.com.", "exaring.de", "inovex.de"}, + []string{"google.com", "exaring.de", "inovex.de"}, + true, + }, + { + []string{"google.com.", "exaring.de", "inovex.de"}, + []string{"google.com", "exaring.de", "inovex.de"}, + true, + }, + { + []string{"google.com.", "exaring.de.", "inovex.de"}, + []string{"google.com", "exaring.de", "inovex.de"}, + true, + }, + { + []string{"foo.org. "}, + []string{"foo.org"}, + true, + }, + { + []string{" foo.org"}, + []string{"foo.org"}, + true, + }, + { + []string{"foo.org."}, + []string{"foo.org"}, + true, + }, + { + []string{"foo.org."}, + []string{"baz.org"}, + false, + }, + { + []string{"baz.foo.org."}, + []string{"foo.org"}, + false, + }, + { + []string{"", "foo.org."}, + []string{"foo.org"}, + true, + }, + { + []string{"", "foo.org."}, + []string{}, + true, + }, + { + []string{""}, + []string{"foo.org"}, + true, + }, + { + []string{""}, + []string{}, + true, + }, + { + []string{" "}, + []string{}, + true, + }, + { + []string{"bar.sub.example.org"}, + []string{"foo.bar.sub.example.org"}, + true, + }, +} + +func TestDomainFilterMatch(t *testing.T) { + for i, tt := range domainFilterTests { + domainFilter := NewDomainFilter(tt.domainFilter) + for _, domain := range tt.domains { + assert.Equal(t, tt.expected, domainFilter.Match(domain), "should not fail: %v in test-case #%v", domain, i) + assert.Equal(t, tt.expected, domainFilter.Match(domain+"."), "should not fail: %v in test-case #%v", domain+".", i) + } + } +} + +func TestDomainFilterMatchWithEmptyFilter(t *testing.T) { + for _, tt := range domainFilterTests { + domainFilter := DomainFilter{} + for i, domain := range tt.domains { + assert.True(t, domainFilter.Match(domain), "should not fail: %v in test-case #%v", domain, i) + assert.True(t, domainFilter.Match(domain+"."), "should not fail: %v in test-case #%v", domain+".", i) + } + } +} diff --git a/provider/google.go b/provider/google.go index 75313ad52..61f18aa1f 100644 --- a/provider/google.go +++ b/provider/google.go @@ -96,7 +96,7 @@ type GoogleProvider struct { // Enabled dry-run will print any modifying actions rather than execute them. dryRun bool // only consider hosted zones managing domains ending in this suffix - domainFilter string + domainFilter DomainFilter // A client for managing resource record sets resourceRecordSetsClient resourceRecordSetsClientInterface // A client for managing hosted zones @@ -106,7 +106,7 @@ type GoogleProvider struct { } // NewGoogleProvider initializes a new Google CloudDNS based Provider. -func NewGoogleProvider(project string, domainFilter string, dryRun bool) (*GoogleProvider, error) { +func NewGoogleProvider(project string, domainFilter DomainFilter, dryRun bool) (*GoogleProvider, error) { gcloud, err := google.DefaultClient(context.TODO(), dns.NdevClouddnsReadwriteScope) if err != nil { return nil, err @@ -142,7 +142,7 @@ func (p *GoogleProvider) Zones() (map[string]*dns.ManagedZone, error) { f := func(resp *dns.ManagedZonesListResponse) error { for _, zone := range resp.ManagedZones { - if strings.HasSuffix(zone.DnsName, p.domainFilter) { + if p.domainFilter.Match(zone.DnsName) { zones[zone.Name] = zone } } diff --git a/provider/google_test.go b/provider/google_test.go index 52044fc7f..69c9a9f42 100644 --- a/provider/google_test.go +++ b/provider/google_test.go @@ -192,7 +192,7 @@ func hasTrailingDot(target string) bool { } func TestGoogleZones(t *testing.T) { - provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{}) + provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{}) zones, err := provider.Zones() require.NoError(t, err) @@ -211,7 +211,7 @@ func TestGoogleRecords(t *testing.T) { endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", "CNAME"), } - provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, originalEndpoints) + provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, originalEndpoints) records, err := provider.Records() require.NoError(t, err) @@ -220,7 +220,7 @@ func TestGoogleRecords(t *testing.T) { } func TestGoogleCreateRecords(t *testing.T) { - provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{}) + provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{}) records := []*endpoint.Endpoint{ endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", ""), @@ -241,7 +241,7 @@ func TestGoogleCreateRecords(t *testing.T) { } func TestGoogleUpdateRecords(t *testing.T) { - provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{ + provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{ endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"), endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"), endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", "CNAME"), @@ -277,7 +277,7 @@ func TestGoogleDeleteRecords(t *testing.T) { endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", "CNAME"), } - provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, originalEndpoints) + provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, originalEndpoints) require.NoError(t, provider.DeleteRecords(originalEndpoints)) @@ -288,7 +288,7 @@ func TestGoogleDeleteRecords(t *testing.T) { } func TestGoogleApplyChanges(t *testing.T) { - provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{ + provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{ endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"), endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"), endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", "A"), @@ -352,7 +352,7 @@ func TestGoogleApplyChangesDryRun(t *testing.T) { endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", "CNAME"), } - provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", true, originalEndpoints) + provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), true, originalEndpoints) createRecords := []*endpoint.Endpoint{ endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"), @@ -393,7 +393,7 @@ func TestGoogleApplyChangesDryRun(t *testing.T) { } func TestGoogleApplyChangesEmpty(t *testing.T) { - provider := newGoogleProvider(t, "ext-dns-test-2.gcp.zalan.do.", false, []*endpoint.Endpoint{}) + provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{}) assert.NoError(t, provider.ApplyChanges(&plan.Changes{})) } @@ -496,7 +496,7 @@ func validateChangeRecord(t *testing.T, record *dns.ResourceRecordSet, expected assert.Equal(t, expected.Ttl, record.Ttl) } -func newGoogleProvider(t *testing.T, domainFilter string, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider { +func newGoogleProvider(t *testing.T, domainFilter DomainFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider { provider := &GoogleProvider{ project: "zalando-external-dns-test", domainFilter: domainFilter, diff --git a/provider/inmemory.go b/provider/inmemory.go index 8560eb75c..4f7134422 100644 --- a/provider/inmemory.go +++ b/provider/inmemory.go @@ -42,7 +42,7 @@ var ( // InMemoryProvider - dns provider only used for testing purposes // initialized as dns provider with no records type InMemoryProvider struct { - domain string + domain DomainFilter client *inMemoryClient filter *filter OnApplyChanges func(changes *plan.Changes) @@ -73,9 +73,9 @@ func InMemoryWithLogging() InMemoryOption { } // InMemoryWithDomain modifies the domain on which dns zones are filtered -func InMemoryWithDomain(domain string) InMemoryOption { +func InMemoryWithDomain(domainFilter DomainFilter) InMemoryOption { return func(p *InMemoryProvider) { - p.domain = domain + p.domain = domainFilter } } @@ -85,7 +85,7 @@ func NewInMemoryProvider(opts ...InMemoryOption) *InMemoryProvider { filter: &filter{}, OnApplyChanges: func(changes *plan.Changes) {}, OnRecords: func() {}, - domain: "", + domain: NewDomainFilter([]string{""}), client: newInMemoryClient(), }