New filter: --zone-id-filter (#422)

* Add aws-zone-id flag

* Add Zone ID filter

* Update AWS provider and main

* Make ZoneIDFilter generic

* Implement ZoneIDFilter for all providers

* Update CHANGELOG
This commit is contained in:
Valentyn Boginskey 2017-12-20 11:00:13 -05:00 committed by Martin Linkhorst
parent ec07f45c8e
commit 4dacf81238
18 changed files with 255 additions and 57 deletions

View File

@ -3,6 +3,7 @@
- Target of DNS record is changed only if corresponding kubernetes resource target changes
- If kubernetes resource is deleted, then another resource may acquire DNS name
- "Flapping" target issue is resolved by providing a consistent and defined mechanism for choosing a target
- New `--zone-id-filter` parameter allows filtering by zone id
## v0.4.8 - 2017-11-22

12
main.go
View File

@ -88,26 +88,28 @@ func main() {
endpointsSource := source.NewDedupSource(source.NewMultiSource(sources))
domainFilter := provider.NewDomainFilter(cfg.DomainFilter)
zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
var p provider.Provider
switch cfg.Provider {
case "aws":
p, err = provider.NewAWSProvider(domainFilter, zoneTypeFilter, cfg.DryRun)
p, err = provider.NewAWSProvider(domainFilter, zoneIDFilter, zoneTypeFilter, cfg.DryRun)
case "azure":
p, err = provider.NewAzureProvider(cfg.AzureConfigFile, domainFilter, cfg.AzureResourceGroup, cfg.DryRun)
p, err = provider.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.DryRun)
case "cloudflare":
p, err = provider.NewCloudFlareProvider(domainFilter, cfg.CloudflareProxied, cfg.DryRun)
p, err = provider.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareProxied, cfg.DryRun)
case "google":
p, err = provider.NewGoogleProvider(cfg.GoogleProject, domainFilter, cfg.DryRun)
p, err = provider.NewGoogleProvider(cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.DryRun)
case "digitalocean":
p, err = provider.NewDigitalOceanProvider(domainFilter, cfg.DryRun)
case "dnsimple":
p, err = provider.NewDnsimpleProvider(domainFilter, cfg.DryRun)
p, err = provider.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
case "infoblox":
p, err = provider.NewInfobloxProvider(
provider.InfobloxConfig{
DomainFilter: domainFilter,
ZoneIDFilter: zoneIDFilter,
Host: cfg.InfobloxGridHost,
Port: cfg.InfobloxWapiPort,
Username: cfg.InfobloxWapiUsername,

View File

@ -41,6 +41,7 @@ type Config struct {
Provider string
GoogleProject string
DomainFilter []string
ZoneIDFilter []string
AWSZoneType string
AzureConfigFile string
AzureResourceGroup string
@ -134,6 +135,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, dnsimple, infoblox, inmemory)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "dnsimple", "infoblox", "inmemory")
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("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, specify the Google project (required when --provider=google)").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
app.Flag("aws-zone-type", "When using the AWS provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AWSZoneType).EnumVar(&cfg.AWSZoneType, "", "public", "private")
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)

View File

@ -37,6 +37,7 @@ var (
Provider: "google",
GoogleProject: "",
DomainFilter: []string{""},
ZoneIDFilter: []string{""},
AWSZoneType: "",
AzureConfigFile: "/etc/kubernetes/azure.json",
AzureResourceGroup: "",
@ -70,6 +71,7 @@ var (
Provider: "google",
GoogleProject: "project",
DomainFilter: []string{"example.org", "company.com"},
ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"},
AWSZoneType: "private",
AzureConfigFile: "azure.json",
AzureResourceGroup: "arg",
@ -135,6 +137,8 @@ func TestParseFlags(t *testing.T) {
"--no-infoblox-ssl-verify",
"--domain-filter=example.org",
"--domain-filter=company.com",
"--zone-id-filter=/hostedzone/ZTST1",
"--zone-id-filter=/hostedzone/ZTST2",
"--aws-zone-type=private",
"--policy=upsert-only",
"--registry=noop",
@ -173,6 +177,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_INFOBLOX_SSL_VERIFY": "0",
"EXTERNAL_DNS_INMEMORY_ZONE": "example.org\ncompany.com",
"EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com",
"EXTERNAL_DNS_ZONE_ID_FILTER": "/hostedzone/ZTST1\n/hostedzone/ZTST2",
"EXTERNAL_DNS_AWS_ZONE_TYPE": "private",
"EXTERNAL_DNS_POLICY": "upsert-only",
"EXTERNAL_DNS_REGISTRY": "noop",

View File

@ -71,12 +71,14 @@ type AWSProvider struct {
dryRun bool
// only consider hosted zones managing domains ending in this suffix
domainFilter DomainFilter
// filter hosted zones by id
zoneIDFilter ZoneIDFilter
// filter hosted zones by type (e.g. private or public)
zoneTypeFilter ZoneTypeFilter
}
// NewAWSProvider initializes a new AWS Route53 based Provider.
func NewAWSProvider(domainFilter DomainFilter, zoneTypeFilter ZoneTypeFilter, dryRun bool) (*AWSProvider, error) {
func NewAWSProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, dryRun bool) (*AWSProvider, error) {
config := aws.NewConfig()
config = config.WithHTTPClient(
@ -99,6 +101,7 @@ func NewAWSProvider(domainFilter DomainFilter, zoneTypeFilter ZoneTypeFilter, dr
provider := &AWSProvider{
client: route53.New(session),
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
zoneTypeFilter: zoneTypeFilter,
dryRun: dryRun,
}
@ -112,6 +115,10 @@ func (p *AWSProvider) Zones() (map[string]*route53.HostedZone, error) {
f := func(resp *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool) {
for _, zone := range resp.HostedZones {
if !p.zoneIDFilter.Match(aws.StringValue(zone.Id)) {
continue
}
if !p.zoneTypeFilter.Match(zone) {
continue
}

View File

@ -183,15 +183,17 @@ func TestAWSZones(t *testing.T) {
for _, ti := range []struct {
msg string
zoneIDFilter ZoneIDFilter
zoneTypeFilter ZoneTypeFilter
expectedZones map[string]*route53.HostedZone
}{
{"no filter", NewZoneTypeFilter(""), allZones},
{"public filter", NewZoneTypeFilter("public"), publicZones},
{"private filter", NewZoneTypeFilter("private"), privateZones},
{"unknown filter", NewZoneTypeFilter("unknown"), noZones},
{"no filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), allZones},
{"public filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter("public"), publicZones},
{"private filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter("private"), privateZones},
{"unknown filter", NewZoneIDFilter([]string{}), NewZoneTypeFilter("unknown"), noZones},
{"zone id filter", NewZoneIDFilter([]string{"/hostedzone/zone-3.ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), privateZones},
} {
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), ti.zoneTypeFilter, false, []*endpoint.Endpoint{})
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), ti.zoneIDFilter, ti.zoneTypeFilter, false, []*endpoint.Endpoint{})
zones, err := provider.Zones()
require.NoError(t, err)
@ -201,7 +203,7 @@ func TestAWSZones(t *testing.T) {
}
func TestAWSRecords(t *testing.T) {
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("list-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
endpoint.NewEndpointWithTTL("list-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
endpoint.NewEndpointWithTTL("*.wildcard-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
@ -223,7 +225,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."}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
records := []*endpoint.Endpoint{
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
@ -246,7 +248,7 @@ func TestAWSCreateRecords(t *testing.T) {
}
func TestAWSUpdateRecords(t *testing.T) {
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
@ -284,7 +286,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", endpoint.RecordTypeCNAME),
}
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), false, originalEndpoints)
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, originalEndpoints)
require.NoError(t, provider.DeleteRecords(originalEndpoints))
@ -296,7 +298,7 @@ func TestAWSDeleteRecords(t *testing.T) {
}
func TestAWSApplyChanges(t *testing.T) {
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", "8.8.4.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)),
@ -370,7 +372,7 @@ func TestAWSApplyChangesDryRun(t *testing.T) {
endpoint.NewEndpointWithTTL("delete-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)),
}
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), true, originalEndpoints)
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), true, originalEndpoints)
createRecords := []*endpoint.Endpoint{
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.teapot.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
@ -492,7 +494,7 @@ func TestAWSChangesByZones(t *testing.T) {
}
func TestAWSsubmitChanges(t *testing.T) {
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
const subnets = 16
const hosts = maxChangeCount / subnets
@ -604,7 +606,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."}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
records := []*endpoint.Endpoint{
{DNSName: "create-test.zone-1.ext-dns-test-2.teapot.zalan.do", Target: "foo.example.org", RecordType: endpoint.RecordTypeCNAME},
@ -629,7 +631,7 @@ func TestAWSCreateRecordsWithCNAME(t *testing.T) {
}
func TestAWSCreateRecordsWithALIAS(t *testing.T) {
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
provider := newAWSProvider(t, NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), NewZoneIDFilter([]string{}), NewZoneTypeFilter(""), 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", RecordType: endpoint.RecordTypeCNAME},
@ -764,12 +766,13 @@ func clearAWSRecords(t *testing.T, provider *AWSProvider, zone string) {
}
}
func newAWSProvider(t *testing.T, domainFilter DomainFilter, zoneTypeFilter ZoneTypeFilter, dryRun bool, records []*endpoint.Endpoint) *AWSProvider {
func newAWSProvider(t *testing.T, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, dryRun bool, records []*endpoint.Endpoint) *AWSProvider {
client := NewRoute53APIStub()
provider := &AWSProvider{
client: client,
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
zoneTypeFilter: zoneTypeFilter,
dryRun: false,
}

View File

@ -66,6 +66,7 @@ type RecordsClient interface {
// AzureProvider implements the DNS provider for Microsoft's Azure cloud platform.
type AzureProvider struct {
domainFilter DomainFilter
zoneIDFilter ZoneIDFilter
dryRun bool
resourceGroup string
zonesClient ZonesClient
@ -75,7 +76,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 DomainFilter, resourceGroup string, dryRun bool) (*AzureProvider, error) {
func NewAzureProvider(configFile string, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, 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)
@ -118,6 +119,7 @@ func NewAzureProvider(configFile string, domainFilter DomainFilter, resourceGrou
provider := &AzureProvider{
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
dryRun: dryRun,
resourceGroup: cfg.ResourceGroup,
zonesClient: zonesClient,
@ -194,9 +196,19 @@ func (p *AzureProvider) zones() ([]dns.Zone, error) {
for list.Value != nil && len(*list.Value) > 0 {
for _, zone := range *list.Value {
if zone.Name != nil && p.domainFilter.Match(*zone.Name) {
zones = append(zones, zone)
if zone.Name == nil {
continue
}
if !p.domainFilter.Match(*zone.Name) {
continue
}
if !p.zoneIDFilter.Match(*zone.ID) {
continue
}
zones = append(zones, zone)
}
list, err = p.zonesClient.ListByResourceGroupNextResults(list)

View File

@ -37,8 +37,9 @@ type mockRecordsClient struct {
updatedEndpoints []*endpoint.Endpoint
}
func createMockZone(zone string) dns.Zone {
func createMockZone(zone string, id string) dns.Zone {
return dns.Zone{
ID: to.StringPtr(id),
Name: to.StringPtr(zone),
}
}
@ -138,9 +139,10 @@ func (client *mockRecordsClient) CreateOrUpdate(resourceGroupName string, zoneNa
return parameters, nil
}
func newAzureProvider(domainFilter DomainFilter, dryRun bool, resourceGroup string, zonesClient ZonesClient, recordsClient RecordsClient) *AzureProvider {
func newAzureProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, resourceGroup string, zonesClient ZonesClient, recordsClient RecordsClient) *AzureProvider {
return &AzureProvider{
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
dryRun: dryRun,
resourceGroup: resourceGroup,
zonesClient: zonesClient,
@ -152,7 +154,7 @@ func TestAzureRecord(t *testing.T) {
zonesClient := mockZonesClient{
mockZoneListResult: &dns.ZoneListResult{
Value: &[]dns.Zone{
createMockZone("example.com"),
createMockZone("example.com", "/dnszones/example.com"),
},
},
}
@ -169,7 +171,7 @@ func TestAzureRecord(t *testing.T) {
},
}
provider := newAzureProvider(NewDomainFilter([]string{"example.com"}), true, "k8s", &zonesClient, &recordsClient)
provider := newAzureProvider(NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, "k8s", &zonesClient, &recordsClient)
actual, err := provider.Records()
if err != nil {
@ -225,13 +227,14 @@ func TestAzureApplyChangesDryRun(t *testing.T) {
func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordsClient) {
provider := newAzureProvider(
NewDomainFilter([]string{""}),
NewZoneIDFilter([]string{""}),
dryRun,
"group",
&mockZonesClient{
mockZoneListResult: &dns.ZoneListResult{
Value: &[]dns.Zone{
createMockZone("example.com"),
createMockZone("other.com"),
createMockZone("example.com", "/dnszones/example.com"),
createMockZone("other.com", "/dnszones/other.com"),
},
},
},

View File

@ -92,6 +92,7 @@ type CloudFlareProvider struct {
Client cloudFlareDNS
// only consider hosted zones managing domains ending in this suffix
domainFilter DomainFilter
zoneIDFilter ZoneIDFilter
proxied bool
DryRun bool
}
@ -103,7 +104,7 @@ type cloudFlareChange struct {
}
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
func NewCloudFlareProvider(domainFilter DomainFilter, proxied bool, dryRun bool) (*CloudFlareProvider, error) {
func NewCloudFlareProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, proxied bool, 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 {
@ -113,6 +114,7 @@ func NewCloudFlareProvider(domainFilter DomainFilter, proxied bool, dryRun bool)
//Client: config,
Client: zoneService{config},
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
proxied: proxied,
DryRun: dryRun,
}
@ -129,9 +131,15 @@ func (p *CloudFlareProvider) Zones() ([]cloudflare.Zone, error) {
}
for _, zone := range zones {
if p.domainFilter.Match(zone.Name) {
result = append(result, zone)
if !p.domainFilter.Match(zone.Name) {
continue
}
if !p.zoneIDFilter.Match(zone.ID) {
continue
}
result = append(result, zone)
}
return result, nil

View File

@ -378,6 +378,7 @@ func TestCloudFlareZones(t *testing.T) {
provider := &CloudFlareProvider{
Client: &mockCloudFlareClient{},
domainFilter: NewDomainFilter([]string{"zalando.to."}),
zoneIDFilter: NewZoneIDFilter([]string{""}),
}
zones, err := provider.Zones()
@ -415,13 +416,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(NewDomainFilter([]string{"ext-dns-test.zalando.to."}), false, true)
_, err := NewCloudFlareProvider(NewDomainFilter([]string{"ext-dns-test.zalando.to."}), NewZoneIDFilter([]string{""}), false, true)
if err != nil {
t.Errorf("should not fail, %s", err)
}
_ = os.Unsetenv("CF_API_KEY")
_ = os.Unsetenv("CF_API_EMAIL")
_, err = NewCloudFlareProvider(NewDomainFilter([]string{"ext-dns-test.zalando.to."}), false, true)
_, err = NewCloudFlareProvider(NewDomainFilter([]string{"ext-dns-test.zalando.to."}), NewZoneIDFilter([]string{""}), false, true)
if err == nil {
t.Errorf("expected to fail")
}

View File

@ -84,6 +84,7 @@ type dnsimpleProvider struct {
identity identityService
accountID string
domainFilter DomainFilter
zoneIDFilter ZoneIDFilter
dryRun bool
}
@ -99,7 +100,7 @@ const (
)
// NewDnsimpleProvider initializes a new Dnsimple based provider
func NewDnsimpleProvider(domainFilter DomainFilter, dryRun bool) (Provider, error) {
func NewDnsimpleProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) (Provider, error) {
oauthToken := os.Getenv("DNSIMPLE_OAUTH")
if len(oauthToken) == 0 {
return nil, fmt.Errorf("No dnsimple oauth token provided")
@ -109,6 +110,7 @@ func NewDnsimpleProvider(domainFilter DomainFilter, dryRun bool) (Provider, erro
client: dnsimpleZoneService{service: client.Zones},
identity: identityService{service: client.Identity},
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
dryRun: dryRun,
}
whoamiResponse, err := provider.identity.service.Whoami()
@ -119,7 +121,7 @@ func NewDnsimpleProvider(domainFilter DomainFilter, dryRun bool) (Provider, erro
return provider, nil
}
// Returns a list of Zones that end with the provider's domainFilter
// Returns a list of filtered Zones
func (p *dnsimpleProvider) Zones() (map[string]dnsimple.Zone, error) {
zones := make(map[string]dnsimple.Zone)
zonesResponse, err := p.client.ListZones(p.accountID, &dnsimple.ZoneListOptions{})
@ -127,9 +129,15 @@ func (p *dnsimpleProvider) Zones() (map[string]dnsimple.Zone, error) {
return nil, err
}
for _, zone := range zonesResponse.Data {
if p.domainFilter.Match(zone.Name) {
zones[strconv.Itoa(zone.ID)] = zone
if !p.domainFilter.Match(zone.Name) {
continue
}
if !p.zoneIDFilter.Match(strconv.Itoa(zone.ID)) {
continue
}
zones[strconv.Itoa(zone.ID)] = zone
}
return zones, nil
}

View File

@ -153,7 +153,7 @@ func testDnsimpleSuitableZone(t *testing.T) {
func TestNewDnsimpleProvider(t *testing.T) {
os.Setenv("DNSIMPLE_OAUTH", "xxxxxxxxxxxxxxxxxxxxxxxxxx")
_, err := NewDnsimpleProvider(DomainFilter{filters: []string{"example.com"}}, true)
_, err := NewDnsimpleProvider(NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true)
if err == nil {
t.Errorf("Expected to fail new provider on bad token")
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package provider
import (
"fmt"
"strings"
"github.com/linki/instrumented_http"
@ -102,6 +103,8 @@ type GoogleProvider struct {
dryRun bool
// only consider hosted zones managing domains ending in this suffix
domainFilter DomainFilter
// only consider hosted zones ending with this zone id
zoneIDFilter ZoneIDFilter
// A client for managing resource record sets
resourceRecordSetsClient resourceRecordSetsClientInterface
// A client for managing hosted zones
@ -111,7 +114,7 @@ type GoogleProvider struct {
}
// NewGoogleProvider initializes a new Google CloudDNS based Provider.
func NewGoogleProvider(project string, domainFilter DomainFilter, dryRun bool) (*GoogleProvider, error) {
func NewGoogleProvider(project string, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool) (*GoogleProvider, error) {
gcloud, err := google.DefaultClient(context.TODO(), dns.NdevClouddnsReadwriteScope)
if err != nil {
return nil, err
@ -132,6 +135,7 @@ func NewGoogleProvider(project string, domainFilter DomainFilter, dryRun bool) (
provider := &GoogleProvider{
project: project,
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
dryRun: dryRun,
resourceRecordSetsClient: resourceRecordSetsService{dnsClient.ResourceRecordSets},
managedZonesClient: managedZonesService{dnsClient.ManagedZones},
@ -147,9 +151,15 @@ func (p *GoogleProvider) Zones() (map[string]*dns.ManagedZone, error) {
f := func(resp *dns.ManagedZonesListResponse) error {
for _, zone := range resp.ManagedZones {
if p.domainFilter.Match(zone.DnsName) {
zones[zone.Name] = zone
if !p.domainFilter.Match(zone.DnsName) {
continue
}
if !p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Id)) {
continue
}
zones[zone.Name] = zone
}
return nil

View File

@ -193,7 +193,7 @@ func hasTrailingDot(target string) bool {
}
func TestGoogleZones(t *testing.T) {
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{})
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
zones, err := provider.Zones()
require.NoError(t, err)
@ -212,7 +212,7 @@ func TestGoogleRecords(t *testing.T) {
endpoint.NewEndpoint("list-test-alias.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
}
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, originalEndpoints)
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, originalEndpoints)
records, err := provider.Records()
require.NoError(t, err)
@ -221,7 +221,7 @@ func TestGoogleRecords(t *testing.T) {
}
func TestGoogleCreateRecords(t *testing.T) {
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{})
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
records := []*endpoint.Endpoint{
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "1.2.3.4", endpoint.RecordTypeA),
@ -242,7 +242,7 @@ func TestGoogleCreateRecords(t *testing.T) {
}
func TestGoogleUpdateRecords(t *testing.T) {
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "foo.elb.amazonaws.com", endpoint.RecordTypeCNAME),
@ -278,7 +278,7 @@ func TestGoogleDeleteRecords(t *testing.T) {
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "baz.elb.amazonaws.com", endpoint.RecordTypeCNAME),
}
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, originalEndpoints)
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, originalEndpoints)
require.NoError(t, provider.DeleteRecords(originalEndpoints))
@ -289,7 +289,7 @@ func TestGoogleDeleteRecords(t *testing.T) {
}
func TestGoogleApplyChanges(t *testing.T) {
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
endpoint.NewEndpoint("delete-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", "8.8.4.4", endpoint.RecordTypeA),
@ -353,7 +353,7 @@ func TestGoogleApplyChangesDryRun(t *testing.T) {
endpoint.NewEndpoint("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", "qux.elb.amazonaws.com", endpoint.RecordTypeCNAME),
}
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), true, originalEndpoints)
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), true, originalEndpoints)
createRecords := []*endpoint.Endpoint{
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", "8.8.8.8", endpoint.RecordTypeA),
@ -394,7 +394,7 @@ func TestGoogleApplyChangesDryRun(t *testing.T) {
}
func TestGoogleApplyChangesEmpty(t *testing.T) {
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), false, []*endpoint.Endpoint{})
provider := newGoogleProvider(t, NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
assert.NoError(t, provider.ApplyChanges(&plan.Changes{}))
}
@ -501,10 +501,11 @@ func validateChangeRecord(t *testing.T, record *dns.ResourceRecordSet, expected
assert.Equal(t, expected.Type, record.Type)
}
func newGoogleProvider(t *testing.T, domainFilter DomainFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
func newGoogleProvider(t *testing.T, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
provider := &GoogleProvider{
project: "zalando-external-dns-test",
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
dryRun: false,
resourceRecordSetsClient: &mockResourceRecordSetsClient{},
managedZonesClient: &mockManagedZonesClient{},

View File

@ -30,6 +30,7 @@ import (
// InfobloxConfig clarifies the method signature
type InfobloxConfig struct {
DomainFilter DomainFilter
ZoneIDFilter ZoneIDFilter
Host string
Port int
Username string
@ -43,6 +44,7 @@ type InfobloxConfig struct {
type InfobloxProvider struct {
client ibclient.IBConnector
domainFilter DomainFilter
zoneIDFilter ZoneIDFilter
dryRun bool
}
@ -82,6 +84,7 @@ func NewInfobloxProvider(infobloxConfig InfobloxConfig) (*InfobloxProvider, erro
provider := &InfobloxProvider{
client: client,
domainFilter: infobloxConfig.DomainFilter,
zoneIDFilter: infobloxConfig.ZoneIDFilter,
dryRun: infobloxConfig.DryRun,
}
@ -186,9 +189,15 @@ func (p *InfobloxProvider) zones() ([]ibclient.ZoneAuth, error) {
}
for _, zone := range res {
if p.domainFilter.Match(zone.Fqdn) {
result = append(result, zone)
if !p.domainFilter.Match(zone.Fqdn) {
continue
}
if !p.zoneIDFilter.Match(zone.Ref) {
continue
}
result = append(result, zone)
}
return result, nil

View File

@ -327,10 +327,11 @@ func createMockInfobloxObject(name, recordType, value string) ibclient.IBObject
return nil
}
func newInfobloxProvider(domainFilter DomainFilter, dryRun bool, client ibclient.IBConnector) *InfobloxProvider {
func newInfobloxProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, dryRun bool, client ibclient.IBConnector) *InfobloxProvider {
return &InfobloxProvider{
client: client,
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
dryRun: dryRun,
}
}
@ -351,7 +352,7 @@ func TestInfobloxRecords(t *testing.T) {
},
}
provider := newInfobloxProvider(NewDomainFilter([]string{"example.com"}), true, &client)
provider := newInfobloxProvider(NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, &client)
actual, err := provider.Records()
if err != nil {
@ -425,6 +426,7 @@ func testInfobloxApplyChangesInternal(t *testing.T, dryRun bool, client ibclient
provider := newInfobloxProvider(
NewDomainFilter([]string{""}),
NewZoneIDFilter([]string{""}),
dryRun,
client,
)
@ -482,7 +484,7 @@ func TestInfobloxZones(t *testing.T) {
mockInfobloxObjects: &[]ibclient.IBObject{},
}
provider := newInfobloxProvider(NewDomainFilter([]string{"example.com"}), true, &client)
provider := newInfobloxProvider(NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, &client)
zones, _ := provider.zones()
assert.Equal(t, provider.findZone(zones, "example.com").Fqdn, "example.com")

View File

@ -0,0 +1,45 @@
/*
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"
// ZoneIDFilter holds a list of zone ids to filter by
type ZoneIDFilter struct {
zoneIDs []string
}
// NewZoneIDFilter returns a new ZoneIDFilter given a list of zone ids
func NewZoneIDFilter(zoneIDs []string) ZoneIDFilter {
return ZoneIDFilter{zoneIDs}
}
// Match checks whether a zone matches one of the provided zone ids
func (f ZoneIDFilter) Match(zoneID string) bool {
// An empty filter includes all zones.
if len(f.zoneIDs) == 0 {
return true
}
for _, id := range f.zoneIDs {
if strings.HasSuffix(zoneID, id) {
return true
}
}
return false
}

View File

@ -0,0 +1,79 @@
/*
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 zoneIDFilterTest struct {
zoneIDFilter []string
zone string
expected bool
}
func TestZoneIDFilterMatch(t *testing.T) {
zone := "/hostedzone/ZTST1"
for _, tt := range []zoneIDFilterTest{
{
[]string{},
zone,
true,
},
{
[]string{"/hostedzone/ZTST1"},
zone,
true,
},
{
[]string{"/hostedzone/ZTST2"},
zone,
false,
},
{
[]string{"ZTST1"},
zone,
true,
},
{
[]string{"ZTST2"},
zone,
false,
},
{
[]string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"},
zone,
true,
},
{
[]string{"/hostedzone/ZTST2", "/hostedzone/ZTST3"},
zone,
false,
},
{
[]string{"/hostedzone/ZTST2", "/hostedzone/ZTST1"},
zone,
true,
},
} {
zoneIDFilter := NewZoneIDFilter(tt.zoneIDFilter)
assert.Equal(t, tt.expected, zoneIDFilter.Match(tt.zone))
}
}