Support for multiple domains within --domain-filter (#252)

* Support for multiple domains within --domain-filter

The parameter accepts a comma separated list of domains with or without trailing dot. Example: --domain-filter="example.org, company.test.,staging.com". Closes #247 and #229

* Add boilerplate header

* Add documentation for methods and structs

* use StringsVar for the domain-filter flag

* go fmt

* Remove camel case from tests

* Revert changes in README.md

* Move DomainFilter to provider package

* Make a new slice and copy elements to it

* Update CHANGELOG.md

* docs: change minor spelling mistake
This commit is contained in:
Nils Juenemann 2017-06-29 18:59:05 +02:00 committed by Yerken
parent 1592ef0afa
commit 73d397961e
20 changed files with 247 additions and 64 deletions

View File

@ -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 ## v0.3.0 - 2017-05-08
Features: Features:

View File

@ -84,7 +84,7 @@ spec:
args: args:
- --source=service - --source=service
- --source=ingress - --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 - --provider=aws
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
- --registry=txt - --registry=txt

View File

@ -81,7 +81,7 @@ spec:
args: args:
- --source=service - --source=service
- --source=ingress - --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 - --provider=google
- --google-project=zalando-external-dns-test - --google-project=zalando-external-dns-test
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization

View File

@ -220,7 +220,7 @@ spec:
image: registry.opensource.zalan.do/teapot/external-dns:v0.3.0 image: registry.opensource.zalan.do/teapot/external-dns:v0.3.0
args: args:
- --source=ingress - --source=ingress
- --domain-filter=external-dns-test.gcp.zalan.do. - --domain-filter=external-dns-test.gcp.zalan.do
- --provider=google - --provider=google
- --google-project=zalando-external-dns-test - --google-project=zalando-external-dns-test
- --registry=txt - --registry=txt

14
main.go
View File

@ -108,20 +108,22 @@ func main() {
endpointsSource := source.NewDedupSource(source.NewMultiSource(sources)) endpointsSource := source.NewDedupSource(source.NewMultiSource(sources))
domainFilter := provider.NewDomainFilter(cfg.DomainFilter)
var p provider.Provider var p provider.Provider
switch cfg.Provider { switch cfg.Provider {
case "aws": case "aws":
p, err = provider.NewAWSProvider(cfg.DomainFilter, cfg.DryRun) p, err = provider.NewAWSProvider(domainFilter, cfg.DryRun)
case "azure": 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": case "cloudflare":
p, err = provider.NewCloudFlareProvider(cfg.DomainFilter, cfg.DryRun) p, err = provider.NewCloudFlareProvider(domainFilter, cfg.DryRun)
case "google": case "google":
p, err = provider.NewGoogleProvider(cfg.GoogleProject, cfg.DomainFilter, cfg.DryRun) p, err = provider.NewGoogleProvider(cfg.GoogleProject, domainFilter, cfg.DryRun)
case "digitalocean": case "digitalocean":
p, err = provider.NewDigitalOceanProvider(cfg.DomainFilter, cfg.DryRun) p, err = provider.NewDigitalOceanProvider(domainFilter, cfg.DryRun)
case "inmemory": case "inmemory":
p, err = provider.NewInMemoryProvider(provider.InMemoryWithDomain(cfg.DomainFilter), provider.InMemoryWithLogging()), nil p, err = provider.NewInMemoryProvider(provider.InMemoryWithDomain(domainFilter), provider.InMemoryWithLogging()), nil
default: default:
log.Fatalf("unknown dns provider: %s", cfg.Provider) log.Fatalf("unknown dns provider: %s", cfg.Provider)
} }

View File

@ -36,7 +36,7 @@ type Config struct {
Compatibility string Compatibility string
Provider string Provider string
GoogleProject string GoogleProject string
DomainFilter string DomainFilter []string
AzureConfigFile string AzureConfigFile string
AzureResourceGroup string AzureResourceGroup string
Policy string Policy string
@ -60,7 +60,7 @@ var defaultConfig = &Config{
Compatibility: "", Compatibility: "",
Provider: "", Provider: "",
GoogleProject: "", GoogleProject: "",
DomainFilter: "", DomainFilter: []string{},
AzureConfigFile: "/etc/kubernetes/azure.json", AzureConfigFile: "/etc/kubernetes/azure.json",
AzureResourceGroup: "", AzureResourceGroup: "",
Policy: "sync", Policy: "sync",
@ -99,7 +99,7 @@ func (cfg *Config) ParseFlags(args []string) error {
// Flags related to providers // 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("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("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-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) app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup)

View File

@ -35,7 +35,7 @@ var (
Compatibility: "", Compatibility: "",
Provider: "google", Provider: "google",
GoogleProject: "", GoogleProject: "",
DomainFilter: "", DomainFilter: []string{""},
AzureConfigFile: "/etc/kubernetes/azure.json", AzureConfigFile: "/etc/kubernetes/azure.json",
AzureResourceGroup: "", AzureResourceGroup: "",
Policy: "sync", Policy: "sync",
@ -59,7 +59,7 @@ var (
Compatibility: "mate", Compatibility: "mate",
Provider: "google", Provider: "google",
GoogleProject: "project", GoogleProject: "project",
DomainFilter: "example.org.", DomainFilter: []string{"example.org", "company.com"},
AzureConfigFile: "azure.json", AzureConfigFile: "azure.json",
AzureResourceGroup: "arg", AzureResourceGroup: "arg",
Policy: "upsert-only", Policy: "upsert-only",
@ -105,7 +105,8 @@ func TestParseFlags(t *testing.T) {
"--google-project=project", "--google-project=project",
"--azure-config-file=azure.json", "--azure-config-file=azure.json",
"--azure-resource-group=arg", "--azure-resource-group=arg",
"--domain-filter=example.org.", "--domain-filter=example.org",
"--domain-filter=company.com",
"--policy=upsert-only", "--policy=upsert-only",
"--registry=noop", "--registry=noop",
"--txt-owner-id=owner-1", "--txt-owner-id=owner-1",
@ -134,7 +135,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_GOOGLE_PROJECT": "project", "EXTERNAL_DNS_GOOGLE_PROJECT": "project",
"EXTERNAL_DNS_AZURE_CONFIG_FILE": "azure.json", "EXTERNAL_DNS_AZURE_CONFIG_FILE": "azure.json",
"EXTERNAL_DNS_AZURE_RESOURCE_GROUP": "arg", "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_POLICY": "upsert-only",
"EXTERNAL_DNS_REGISTRY": "noop", "EXTERNAL_DNS_REGISTRY": "noop",
"EXTERNAL_DNS_TXT_OWNER_ID": "owner-1", "EXTERNAL_DNS_TXT_OWNER_ID": "owner-1",

View File

@ -70,11 +70,11 @@ type AWSProvider struct {
client Route53API client Route53API
dryRun bool dryRun bool
// only consider hosted zones managing domains ending in this suffix // only consider hosted zones managing domains ending in this suffix
domainFilter string domainFilter DomainFilter
} }
// NewAWSProvider initializes a new AWS Route53 based Provider. // 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 := aws.NewConfig()
config = config.WithHTTPClient( 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) { f := func(resp *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool) {
for _, zone := range resp.HostedZones { 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 zones[aws.StringValue(zone.Id)] = zone
} }
} }

View File

@ -23,7 +23,6 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/route53" "github.com/aws/aws-sdk-go/service/route53"
"github.com/kubernetes-incubator/external-dns/endpoint" "github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/internal/testutils" "github.com/kubernetes-incubator/external-dns/internal/testutils"
"github.com/kubernetes-incubator/external-dns/plan" "github.com/kubernetes-incubator/external-dns/plan"
@ -145,7 +144,7 @@ func (r *Route53APIStub) CreateHostedZone(input *route53.CreateHostedZoneInput)
} }
func TestAWSZones(t *testing.T) { 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() zones, err := provider.Zones()
require.NoError(t, err) require.NoError(t, err)
@ -167,7 +166,7 @@ func TestAWSZones(t *testing.T) {
} }
func TestAWSRecords(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-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.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"), 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) { 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{ records := []*endpoint.Endpoint{
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", ""), 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) { 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-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.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"), 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"), 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)) require.NoError(t, provider.DeleteRecords(originalEndpoints))
@ -254,7 +253,7 @@ func TestAWSDeleteRecords(t *testing.T) {
} }
func TestAWSApplyChanges(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("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("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"), 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"), 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{ createRecords := []*endpoint.Endpoint{
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", ""), 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) { 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{ records := []*endpoint.Endpoint{
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Target: "foo.example.org"}, {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) { 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{ records := []*endpoint.Endpoint{
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Target: "foo.eu-central-1.elb.amazonaws.com"}, {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() client := NewRoute53APIStub()
provider := &AWSProvider{ provider := &AWSProvider{

View File

@ -65,7 +65,7 @@ type RecordsClient interface {
// AzureProvider implements the DNS provider for Microsoft's Azure cloud platform. // AzureProvider implements the DNS provider for Microsoft's Azure cloud platform.
type AzureProvider struct { type AzureProvider struct {
domainFilter string domainFilter DomainFilter
dryRun bool dryRun bool
resourceGroup string resourceGroup string
zonesClient ZonesClient zonesClient ZonesClient
@ -75,7 +75,7 @@ type AzureProvider struct {
// NewAzureProvider creates a new Azure provider. // NewAzureProvider creates a new Azure provider.
// //
// Returns the provider or an error if a provider could not be created. // 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) contents, err := ioutil.ReadFile(configFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err) 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 list.Value != nil && len(*list.Value) > 0 {
for _, zone := range *list.Value { 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) zones = append(zones, zone)
} }
} }

View File

@ -138,7 +138,7 @@ func (client *mockRecordsClient) CreateOrUpdate(resourceGroupName string, zoneNa
return parameters, nil 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{ return &AzureProvider{
domainFilter: domainFilter, domainFilter: domainFilter,
dryRun: dryRun, 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() actual, err := provider.Records()
if err != nil { if err != nil {
@ -224,7 +224,7 @@ func TestAzureApplyChangesDryRun(t *testing.T) {
func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordsClient) { func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordsClient) {
provider := newAzureProvider( provider := newAzureProvider(
"", NewDomainFilter([]string{""}),
dryRun, dryRun,
"group", "group",
&mockZonesClient{ &mockZonesClient{

View File

@ -83,7 +83,7 @@ func (z zoneService) DeleteDNSRecord(zoneID, recordID string) error {
type CloudFlareProvider struct { type CloudFlareProvider struct {
Client cloudFlareDNS Client cloudFlareDNS
// only consider hosted zones managing domains ending in this suffix // only consider hosted zones managing domains ending in this suffix
domainFilter string domainFilter DomainFilter
DryRun bool DryRun bool
} }
@ -94,7 +94,7 @@ type cloudFlareChange struct {
} }
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider. // 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 // 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")) config, err := cloudflare.New(os.Getenv("CF_API_KEY"), os.Getenv("CF_API_EMAIL"))
if err != nil { if err != nil {
@ -119,7 +119,7 @@ func (p *CloudFlareProvider) Zones() ([]cloudflare.Zone, error) {
} }
for _, zone := range zones { for _, zone := range zones {
if strings.HasSuffix(zone.Name, p.domainFilter) { if p.domainFilter.Match(zone.Name) {
result = append(result, zone) result = append(result, zone)
} }
} }

View File

@ -344,7 +344,7 @@ func TestNewCloudFlareChanges(t *testing.T) {
func TestCloudFlareZones(t *testing.T) { func TestCloudFlareZones(t *testing.T) {
provider := &CloudFlareProvider{ provider := &CloudFlareProvider{
Client: &mockCloudFlareClient{}, Client: &mockCloudFlareClient{},
domainFilter: "zalando.to.", domainFilter: NewDomainFilter([]string{"zalando.to."}),
} }
zones, err := provider.Zones() zones, err := provider.Zones()
@ -382,13 +382,13 @@ func TestRecords(t *testing.T) {
func TestNewCloudFlareProvider(t *testing.T) { func TestNewCloudFlareProvider(t *testing.T) {
_ = os.Setenv("CF_API_KEY", "xxxxxxxxxxxxxxxxx") _ = os.Setenv("CF_API_KEY", "xxxxxxxxxxxxxxxxx")
_ = os.Setenv("CF_API_EMAIL", "test@test.com") _ = 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 { if err != nil {
t.Errorf("should not fail, %s", err) t.Errorf("should not fail, %s", err)
} }
_ = os.Unsetenv("CF_API_KEY") _ = os.Unsetenv("CF_API_KEY")
_ = os.Unsetenv("CF_API_EMAIL") _ = 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 { if err == nil {
t.Errorf("expected to fail") t.Errorf("expected to fail")
} }

View File

@ -44,7 +44,7 @@ const (
type DigitalOceanProvider struct { type DigitalOceanProvider struct {
Client godo.DomainsService Client godo.DomainsService
// only consider hosted zones managing domains ending in this suffix // only consider hosted zones managing domains ending in this suffix
domainFilter string domainFilter DomainFilter
DryRun bool DryRun bool
} }
@ -55,7 +55,7 @@ type DigitalOceanChange struct {
} }
// NewDigitalOceanProvider initializes a new DigitalOcean DNS based Provider. // 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") token, ok := os.LookupEnv("DO_TOKEN")
if !ok { if !ok {
return nil, fmt.Errorf("No token found") return nil, fmt.Errorf("No token found")
@ -83,7 +83,7 @@ func (p *DigitalOceanProvider) Zones() ([]godo.Domain, error) {
} }
for _, zone := range zones { for _, zone := range zones {
if strings.HasSuffix(zone.Name, p.domainFilter) { if p.domainFilter.Match(zone.Name) {
result = append(result, zone) result = append(result, zone)
} }
} }

View File

@ -393,7 +393,7 @@ func TestNewDigitalOceanChanges(t *testing.T) {
func TestDigitalOceanZones(t *testing.T) { func TestDigitalOceanZones(t *testing.T) {
provider := &DigitalOceanProvider{ provider := &DigitalOceanProvider{
Client: &mockDigitalOceanClient{}, Client: &mockDigitalOceanClient{},
domainFilter: "com", domainFilter: NewDomainFilter([]string{"com"}),
} }
zones, err := provider.Zones() zones, err := provider.Zones()
@ -442,12 +442,12 @@ func TestDigitalOceanApplyChanges(t *testing.T) {
func TestNewDigitalOceanProvider(t *testing.T) { func TestNewDigitalOceanProvider(t *testing.T) {
_ = os.Setenv("DO_TOKEN", "xxxxxxxxxxxxxxxxx") _ = 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 { if err != nil {
t.Errorf("should not fail, %s", err) t.Errorf("should not fail, %s", err)
} }
_ = os.Unsetenv("DO_TOKEN") _ = 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 { if err == nil {
t.Errorf("expected to fail") t.Errorf("expected to fail")
} }

56
provider/domainfilter.go Normal file
View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -96,7 +96,7 @@ type GoogleProvider struct {
// Enabled dry-run will print any modifying actions rather than execute them. // Enabled dry-run will print any modifying actions rather than execute them.
dryRun bool dryRun bool
// only consider hosted zones managing domains ending in this suffix // only consider hosted zones managing domains ending in this suffix
domainFilter string domainFilter DomainFilter
// A client for managing resource record sets // A client for managing resource record sets
resourceRecordSetsClient resourceRecordSetsClientInterface resourceRecordSetsClient resourceRecordSetsClientInterface
// A client for managing hosted zones // A client for managing hosted zones
@ -106,7 +106,7 @@ type GoogleProvider struct {
} }
// NewGoogleProvider initializes a new Google CloudDNS based Provider. // 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) gcloud, err := google.DefaultClient(context.TODO(), dns.NdevClouddnsReadwriteScope)
if err != nil { if err != nil {
return nil, err return nil, err
@ -142,7 +142,7 @@ func (p *GoogleProvider) Zones() (map[string]*dns.ManagedZone, error) {
f := func(resp *dns.ManagedZonesListResponse) error { f := func(resp *dns.ManagedZonesListResponse) error {
for _, zone := range resp.ManagedZones { for _, zone := range resp.ManagedZones {
if strings.HasSuffix(zone.DnsName, p.domainFilter) { if p.domainFilter.Match(zone.DnsName) {
zones[zone.Name] = zone zones[zone.Name] = zone
} }
} }

View File

@ -192,7 +192,7 @@ func hasTrailingDot(target string) bool {
} }
func TestGoogleZones(t *testing.T) { 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() zones, err := provider.Zones()
require.NoError(t, err) 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"), 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() records, err := provider.Records()
require.NoError(t, err) require.NoError(t, err)
@ -220,7 +220,7 @@ func TestGoogleRecords(t *testing.T) {
} }
func TestGoogleCreateRecords(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{ records := []*endpoint.Endpoint{
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", ""), 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) { 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-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.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"), 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"), 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)) require.NoError(t, provider.DeleteRecords(originalEndpoints))
@ -288,7 +288,7 @@ func TestGoogleDeleteRecords(t *testing.T) {
} }
func TestGoogleApplyChanges(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("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("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"), 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"), 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{ createRecords := []*endpoint.Endpoint{
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", "A"), 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) { 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{})) 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) 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{ provider := &GoogleProvider{
project: "zalando-external-dns-test", project: "zalando-external-dns-test",
domainFilter: domainFilter, domainFilter: domainFilter,

View File

@ -42,7 +42,7 @@ var (
// InMemoryProvider - dns provider only used for testing purposes // InMemoryProvider - dns provider only used for testing purposes
// initialized as dns provider with no records // initialized as dns provider with no records
type InMemoryProvider struct { type InMemoryProvider struct {
domain string domain DomainFilter
client *inMemoryClient client *inMemoryClient
filter *filter filter *filter
OnApplyChanges func(changes *plan.Changes) OnApplyChanges func(changes *plan.Changes)
@ -73,9 +73,9 @@ func InMemoryWithLogging() InMemoryOption {
} }
// InMemoryWithDomain modifies the domain on which dns zones are filtered // InMemoryWithDomain modifies the domain on which dns zones are filtered
func InMemoryWithDomain(domain string) InMemoryOption { func InMemoryWithDomain(domainFilter DomainFilter) InMemoryOption {
return func(p *InMemoryProvider) { return func(p *InMemoryProvider) {
p.domain = domain p.domain = domainFilter
} }
} }
@ -85,7 +85,7 @@ func NewInMemoryProvider(opts ...InMemoryOption) *InMemoryProvider {
filter: &filter{}, filter: &filter{},
OnApplyChanges: func(changes *plan.Changes) {}, OnApplyChanges: func(changes *plan.Changes) {},
OnRecords: func() {}, OnRecords: func() {},
domain: "", domain: NewDomainFilter([]string{""}),
client: newInMemoryClient(), client: newInMemoryClient(),
} }