Skip to content

Commit ec54ea7

Browse files
authored
Merge pull request kubernetes-sigs#2153 from RentTheRunway/falconertc/clouddns_visibility_filter
CloudDNS: Allow filtering for private and public zones
2 parents f9a3fe3 + afe9381 commit ec54ea7

File tree

10 files changed

+113
-57
lines changed

10 files changed

+113
-57
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ The following tutorials are provided:
139139
* [Dyn](docs/tutorials/dyn.md)
140140
* [Exoscale](docs/tutorials/exoscale.md)
141141
* [ExternalName Services](docs/tutorials/externalname.md)
142-
* Google Container Engine
142+
* Google Kubernetes Engine
143143
* [Using Google's Default Ingress Controller](docs/tutorials/gke.md)
144144
* [Using the Nginx Ingress Controller](docs/tutorials/nginx-ingress.md)
145145
* [Headless Services](docs/tutorials/hostport.md)

docs/faq.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
### How is ExternalDNS useful to me?
44

5-
You've probably created many deployments. Typically, you expose your deployment to the Internet by creating a Service with `type=LoadBalancer`. Depending on your environment, this usually assigns a random publicly available endpoint to your service that you can access from anywhere in the world. On Google Container Engine, this is a public IP address:
5+
You've probably created many deployments. Typically, you expose your deployment to the Internet by creating a Service with `type=LoadBalancer`. Depending on your environment, this usually assigns a random publicly available endpoint to your service that you can access from anywhere in the world. On Google Kubernetes Engine, this is a public IP address:
66

77
```console
88
$ kubectl get svc
@@ -54,7 +54,7 @@ Yes, you can. Pass in a comma separated list to `--fqdn-template`. Beaware this
5454

5555
### Which Service and Ingress controllers are supported?
5656

57-
Regarding Services, we'll support the OSI Layer 4 load balancers that Kubernetes creates on AWS and Google Container Engine, and possibly other clusters running on Google Compute Engine.
57+
Regarding Services, we'll support the OSI Layer 4 load balancers that Kubernetes creates on AWS and Google Kubernetes Engine, and possibly other clusters running on Google Compute Engine.
5858

5959
Regarding Ingress, we'll support:
6060
* Google's Ingress Controller on GKE that integrates with their Layer 7 load balancers (GLBC)

docs/tutorials/gke.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Setting up ExternalDNS on Google Container Engine
1+
# Setting up ExternalDNS on Google Kubernetes Engine
22

33
This tutorial describes how to setup ExternalDNS for usage within a GKE cluster. Make sure to use **>=0.4** version of ExternalDNS for this tutorial
44

@@ -123,6 +123,7 @@ spec:
123123
- --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
124124
- --provider=google
125125
# - --google-project=zalando-external-dns-test # Use this to specify a project different from the one external-dns is running inside
126+
- --google-zone-visibility=private # Use this to filter to only zones with this visibility. Set to either 'public' or 'private'. Omitting will match public and private zones
126127
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
127128
- --registry=txt
128129
- --txt-owner-id=my-identifier

main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func main() {
218218
case "rcodezero":
219219
p, err = rcode0.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt)
220220
case "google":
221-
p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.DryRun)
221+
p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun)
222222
case "digitalocean":
223223
p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
224224
case "hetzner":

pkg/apis/externaldns/types.go

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ type Config struct {
6767
GoogleProject string
6868
GoogleBatchChangeSize int
6969
GoogleBatchChangeInterval time.Duration
70+
GoogleZoneVisibility string
7071
DomainFilter []string
7172
ExcludeDomains []string
7273
RegexDomainFilter *regexp.Regexp
@@ -198,6 +199,7 @@ var defaultConfig = &Config{
198199
GoogleProject: "",
199200
GoogleBatchChangeSize: 1000,
200201
GoogleBatchChangeInterval: time.Second,
202+
GoogleZoneVisibility: "",
201203
DomainFilter: []string{},
202204
ExcludeDomains: []string{},
203205
RegexDomainFilter: regexp.MustCompile(""),
@@ -387,6 +389,7 @@ func (cfg *Config) ParseFlags(args []string) error {
387389
app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
388390
app.Flag("google-batch-change-size", "When using the Google provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.GoogleBatchChangeSize)).IntVar(&cfg.GoogleBatchChangeSize)
389391
app.Flag("google-batch-change-interval", "When using the Google provider, set the interval between batch changes.").Default(defaultConfig.GoogleBatchChangeInterval.String()).DurationVar(&cfg.GoogleBatchChangeInterval)
392+
app.Flag("google-zone-visibility", "When using the Google provider, filter for zones with this visibility (optional, options: public, private)").Default(defaultConfig.GoogleZoneVisibility).EnumVar(&cfg.GoogleZoneVisibility, "", "public", "private")
390393
app.Flag("alibaba-cloud-config-file", "When using the Alibaba Cloud provider, specify the Alibaba Cloud configuration file (required when --provider=alibabacloud").Default(defaultConfig.AlibabaCloudConfigFile).StringVar(&cfg.AlibabaCloudConfigFile)
391394
app.Flag("alibaba-cloud-zone-type", "When using the Alibaba Cloud provider, filter for zones of this type (optional, options: public, private)").Default(defaultConfig.AlibabaCloudZoneType).EnumVar(&cfg.AlibabaCloudZoneType, "", "public", "private")
392395
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")

pkg/apis/externaldns/types_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ var (
4646
GoogleProject: "",
4747
GoogleBatchChangeSize: 1000,
4848
GoogleBatchChangeInterval: time.Second,
49+
GoogleZoneVisibility: "",
4950
DomainFilter: []string{""},
5051
ExcludeDomains: []string{""},
5152
RegexDomainFilter: regexp.MustCompile(""),
@@ -134,6 +135,7 @@ var (
134135
GoogleProject: "project",
135136
GoogleBatchChangeSize: 100,
136137
GoogleBatchChangeInterval: time.Second * 2,
138+
GoogleZoneVisibility: "private",
137139
DomainFilter: []string{"example.org", "company.com"},
138140
ExcludeDomains: []string{"xapi.example.org", "xapi.company.com"},
139141
RegexDomainFilter: regexp.MustCompile("(example\\.org|company\\.com)$"),
@@ -249,6 +251,7 @@ func TestParseFlags(t *testing.T) {
249251
"--google-project=project",
250252
"--google-batch-change-size=100",
251253
"--google-batch-change-interval=2s",
254+
"--google-zone-visibility=private",
252255
"--azure-config-file=azure.json",
253256
"--azure-resource-group=arg",
254257
"--azure-subscription-id=arg",
@@ -351,6 +354,7 @@ func TestParseFlags(t *testing.T) {
351354
"EXTERNAL_DNS_GOOGLE_PROJECT": "project",
352355
"EXTERNAL_DNS_GOOGLE_BATCH_CHANGE_SIZE": "100",
353356
"EXTERNAL_DNS_GOOGLE_BATCH_CHANGE_INTERVAL": "2s",
357+
"EXTERNAL_DNS_GOOGLE_ZONE_VISIBILITY": "private",
354358
"EXTERNAL_DNS_AZURE_CONFIG_FILE": "azure.json",
355359
"EXTERNAL_DNS_AZURE_RESOURCE_GROUP": "arg",
356360
"EXTERNAL_DNS_AZURE_SUBSCRIPTION_ID": "arg",

provider/google/google.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ type GoogleProvider struct {
110110
batchChangeInterval time.Duration
111111
// only consider hosted zones managing domains ending in this suffix
112112
domainFilter endpoint.DomainFilter
113+
// filter for zones based on visibility
114+
zoneTypeFilter provider.ZoneTypeFilter
113115
// only consider hosted zones ending with this zone id
114116
zoneIDFilter provider.ZoneIDFilter
115117
// A client for managing resource record sets
@@ -123,7 +125,7 @@ type GoogleProvider struct {
123125
}
124126

125127
// NewGoogleProvider initializes a new Google CloudDNS based Provider.
126-
func NewGoogleProvider(ctx context.Context, project string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, batchChangeSize int, batchChangeInterval time.Duration, dryRun bool) (*GoogleProvider, error) {
128+
func NewGoogleProvider(ctx context.Context, project string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, batchChangeSize int, batchChangeInterval time.Duration, zoneVisibility string, dryRun bool) (*GoogleProvider, error) {
127129
gcloud, err := google.DefaultClient(ctx, dns.NdevClouddnsReadwriteScope)
128130
if err != nil {
129131
return nil, err
@@ -149,12 +151,15 @@ func NewGoogleProvider(ctx context.Context, project string, domainFilter endpoin
149151
}
150152
}
151153

154+
zoneTypeFilter := provider.NewZoneTypeFilter(zoneVisibility)
155+
152156
provider := &GoogleProvider{
153157
project: project,
154158
dryRun: dryRun,
155159
batchChangeSize: batchChangeSize,
156160
batchChangeInterval: batchChangeInterval,
157161
domainFilter: domainFilter,
162+
zoneTypeFilter: zoneTypeFilter,
158163
zoneIDFilter: zoneIDFilter,
159164
resourceRecordSetsClient: resourceRecordSetsService{dnsClient.ResourceRecordSets},
160165
managedZonesClient: managedZonesService{dnsClient.ManagedZones},
@@ -171,11 +176,11 @@ func (p *GoogleProvider) Zones(ctx context.Context) (map[string]*dns.ManagedZone
171176

172177
f := func(resp *dns.ManagedZonesListResponse) error {
173178
for _, zone := range resp.ManagedZones {
174-
if p.domainFilter.Match(zone.DnsName) && (p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Id)) || p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Name))) {
179+
if p.domainFilter.Match(zone.DnsName) && p.zoneTypeFilter.Match(zone.Visibility) && (p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Id)) || p.zoneIDFilter.Match(fmt.Sprintf("%v", zone.Name))) {
175180
zones[zone.Name] = zone
176-
log.Debugf("Matched %s (zone: %s)", zone.DnsName, zone.Name)
181+
log.Debugf("Matched %s (zone: %s) (visibility: %s)", zone.DnsName, zone.Name, zone.Visibility)
177182
} else {
178-
log.Debugf("Filtered %s (zone: %s)", zone.DnsName, zone.Name)
183+
log.Debugf("Filtered %s (zone: %s) (visibility: %s)", zone.DnsName, zone.Name, zone.Visibility)
179184
}
180185
}
181186

provider/google/google_test.go

+55-14
Original file line numberDiff line numberDiff line change
@@ -194,24 +194,46 @@ func hasTrailingDot(target string) bool {
194194
}
195195

196196
func TestGoogleZonesIDFilter(t *testing.T) {
197-
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"10002"}), false, []*endpoint.Endpoint{})
197+
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"10002"}), provider.NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
198198

199199
zones, err := provider.Zones(context.Background())
200200
require.NoError(t, err)
201201

202202
validateZones(t, zones, map[string]*dns.ManagedZone{
203-
"internal-2": {Name: "internal-2", DnsName: "cluster.local.", Id: 10002},
203+
"internal-2": {Name: "internal-2", DnsName: "cluster.local.", Id: 10002, Visibility: "private"},
204204
})
205205
}
206206

207207
func TestGoogleZonesNameFilter(t *testing.T) {
208-
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"internal-2"}), false, []*endpoint.Endpoint{})
208+
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"internal-2"}), provider.NewZoneTypeFilter(""), false, []*endpoint.Endpoint{})
209209

210210
zones, err := provider.Zones(context.Background())
211211
require.NoError(t, err)
212212

213213
validateZones(t, zones, map[string]*dns.ManagedZone{
214-
"internal-2": {Name: "internal-2", DnsName: "cluster.local.", Id: 10002},
214+
"internal-2": {Name: "internal-2", DnsName: "cluster.local.", Id: 10002, Visibility: "private"},
215+
})
216+
}
217+
218+
func TestGoogleZonesVisibilityFilterPublic(t *testing.T) {
219+
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"split-horizon-1"}), provider.NewZoneTypeFilter("public"), false, []*endpoint.Endpoint{})
220+
221+
zones, err := provider.Zones(context.Background())
222+
require.NoError(t, err)
223+
224+
validateZones(t, zones, map[string]*dns.ManagedZone{
225+
"split-horizon-1": {Name: "split-horizon-1", DnsName: "cluster.local.", Id: 10001, Visibility: "public"},
226+
})
227+
}
228+
229+
func TestGoogleZonesVisibilityFilterPrivate(t *testing.T) {
230+
provider := newGoogleProviderZoneOverlap(t, endpoint.NewDomainFilter([]string{"cluster.local."}), provider.NewZoneIDFilter([]string{"split-horizon-1"}), provider.NewZoneTypeFilter("public"), false, []*endpoint.Endpoint{})
231+
232+
zones, err := provider.Zones(context.Background())
233+
require.NoError(t, err)
234+
235+
validateZones(t, zones, map[string]*dns.ManagedZone{
236+
"split-horizon-1": {Name: "split-horizon-1", DnsName: "cluster.local.", Id: 10001, Visibility: "public"},
215237
})
216238
}
217239

@@ -650,6 +672,7 @@ func validateZones(t *testing.T, zones map[string]*dns.ManagedZone, expected map
650672
func validateZone(t *testing.T, zone *dns.ManagedZone, expected *dns.ManagedZone) {
651673
assert.Equal(t, expected.Name, zone.Name)
652674
assert.Equal(t, expected.DnsName, zone.DnsName)
675+
assert.Equal(t, expected.Visibility, zone.Visibility)
653676
}
654677

655678
func validateChange(t *testing.T, change *dns.Change, expected *dns.Change) {
@@ -672,33 +695,51 @@ func validateChangeRecord(t *testing.T, record *dns.ResourceRecordSet, expected
672695
assert.Equal(t, expected.Type, record.Type)
673696
}
674697

675-
func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
698+
func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
676699
provider := &GoogleProvider{
677700
project: "zalando-external-dns-test",
678701
dryRun: false,
679702
domainFilter: domainFilter,
680703
zoneIDFilter: zoneIDFilter,
704+
zoneTypeFilter: zoneTypeFilter,
681705
resourceRecordSetsClient: &mockResourceRecordSetsClient{},
682706
managedZonesClient: &mockManagedZonesClient{},
683707
changesClient: &mockChangesClient{},
684708
}
685709

686710
createZone(t, provider, &dns.ManagedZone{
687-
Name: "internal-1",
688-
DnsName: "cluster.local.",
689-
Id: 10001,
711+
Name: "internal-1",
712+
DnsName: "cluster.local.",
713+
Id: 10001,
714+
Visibility: "private",
715+
})
716+
717+
createZone(t, provider, &dns.ManagedZone{
718+
Name: "internal-2",
719+
DnsName: "cluster.local.",
720+
Id: 10002,
721+
Visibility: "private",
722+
})
723+
724+
createZone(t, provider, &dns.ManagedZone{
725+
Name: "internal-3",
726+
DnsName: "cluster.local.",
727+
Id: 10003,
728+
Visibility: "private",
690729
})
691730

692731
createZone(t, provider, &dns.ManagedZone{
693-
Name: "internal-2",
694-
DnsName: "cluster.local.",
695-
Id: 10002,
732+
Name: "split-horizon-1",
733+
DnsName: "cluster.local.",
734+
Id: 10004,
735+
Visibility: "public",
696736
})
697737

698738
createZone(t, provider, &dns.ManagedZone{
699-
Name: "internal-3",
700-
DnsName: "cluster.local.",
701-
Id: 10003,
739+
Name: "split-horizon-1",
740+
DnsName: "cluster.local.",
741+
Id: 10004,
742+
Visibility: "private",
702743
})
703744

704745
provider.dryRun = dryRun

provider/zone_type_filter.go

+22-12
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,34 @@ func NewZoneTypeFilter(zoneType string) ZoneTypeFilter {
3737
}
3838

3939
// Match checks whether a zone matches the zone type that's filtered for.
40-
func (f ZoneTypeFilter) Match(zone *route53.HostedZone) bool {
40+
func (f ZoneTypeFilter) Match(rawZoneType interface{}) bool {
4141
// An empty zone filter includes all hosted zones.
4242
if f.zoneType == "" {
4343
return true
4444
}
4545

46-
// If the zone has no config we assume it's a public zone since the config's field
47-
// `PrivateZone` is false by default in go.
48-
if zone.Config == nil {
49-
return f.zoneType == zoneTypePublic
50-
}
51-
46+
switch zoneType := rawZoneType.(type) {
5247
// Given a zone type we return true if the given zone matches this type.
53-
switch f.zoneType {
54-
case zoneTypePublic:
55-
return !aws.BoolValue(zone.Config.PrivateZone)
56-
case zoneTypePrivate:
57-
return aws.BoolValue(zone.Config.PrivateZone)
48+
case string:
49+
switch f.zoneType {
50+
case zoneTypePublic:
51+
return zoneType == zoneTypePublic
52+
case zoneTypePrivate:
53+
return zoneType == zoneTypePrivate
54+
}
55+
case *route53.HostedZone:
56+
// If the zone has no config we assume it's a public zone since the config's field
57+
// `PrivateZone` is false by default in go.
58+
if zoneType.Config == nil {
59+
return f.zoneType == zoneTypePublic
60+
}
61+
62+
switch f.zoneType {
63+
case zoneTypePublic:
64+
return !aws.BoolValue(zoneType.Config.PrivateZone)
65+
case zoneTypePrivate:
66+
return aws.BoolValue(zoneType.Config.PrivateZone)
67+
}
5868
}
5969

6070
// We return false on any other path, e.g. unknown zone type filter value.

provider/zone_type_filter_test.go

+14-22
Original file line numberDiff line numberDiff line change
@@ -26,46 +26,38 @@ import (
2626
)
2727

2828
func TestZoneTypeFilterMatch(t *testing.T) {
29-
publicZone := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(false)}}
30-
privateZone := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(true)}}
29+
publicZoneStr := "public"
30+
privateZoneStr := "private"
31+
publicZoneAWS := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(false)}}
32+
privateZoneAWS := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(true)}}
3133

3234
for _, tc := range []struct {
3335
zoneTypeFilter string
34-
zone *route53.HostedZone
3536
matches bool
37+
zones []interface{}
3638
}{
3739
{
38-
"", publicZone, true,
40+
"", true, []interface{}{ publicZoneStr, privateZoneStr, &route53.HostedZone{} },
3941
},
4042
{
41-
"", privateZone, true,
43+
"public", true, []interface{}{ publicZoneStr, publicZoneAWS, &route53.HostedZone{} },
4244
},
4345
{
44-
"public", publicZone, true,
46+
"public", false, []interface{}{ privateZoneStr, privateZoneAWS },
4547
},
4648
{
47-
"public", privateZone, false,
49+
"private", true, []interface{}{ privateZoneStr, privateZoneAWS },
4850
},
4951
{
50-
"private", publicZone, false,
52+
"private", false, []interface{}{ publicZoneStr, publicZoneAWS, &route53.HostedZone{} },
5153
},
5254
{
53-
"private", privateZone, true,
54-
},
55-
{
56-
"unknown", publicZone, false,
57-
},
58-
{
59-
"", &route53.HostedZone{}, true,
60-
},
61-
{
62-
"public", &route53.HostedZone{}, true,
63-
},
64-
{
65-
"private", &route53.HostedZone{}, false,
55+
"unknown", false, []interface{}{ publicZoneStr },
6656
},
6757
} {
6858
zoneTypeFilter := NewZoneTypeFilter(tc.zoneTypeFilter)
69-
assert.Equal(t, tc.matches, zoneTypeFilter.Match(tc.zone))
59+
for _, zone := range tc.zones {
60+
assert.Equal(t, tc.matches, zoneTypeFilter.Match(zone))
61+
}
7062
}
7163
}

0 commit comments

Comments
 (0)