Skip to content

Commit c506a20

Browse files
authored
Merge pull request #4346 from khuedoan/az-private-dns-zone-name-filter
feat(azure): add zone name filter for Azure Private DNS
2 parents c006a49 + b16d1b3 commit c506a20

File tree

5 files changed

+160
-9
lines changed

5 files changed

+160
-9
lines changed

docs/tutorials/azure-private-dns.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,13 @@ spec:
430430
pathType: Prefix
431431
```
432432

433-
When using ExternalDNS with ingress objects it will automatically create DNS records based on host names specified in ingress objects that match the domain-filter argument in the externaldns deployment manifest. When those host names are removed or renamed the corresponding DNS records are also altered.
433+
When you use ExternalDNS with Ingress resources, it automatically creates DNS records based on the hostnames listed in those Ingress objects.
434+
Those hostnames must match the filters that you defined (if any):
435+
436+
- By default, `--domain-filter` filters Azure Private DNS zone.
437+
- If you use `--domain-filter` together with `--zone-name-filter`, the behavior changes: `--domain-filter` then filters Ingress domains, not the Azure Private DNS zone name.
438+
439+
When those hostnames are removed or renamed the corresponding DNS records are also altered.
434440

435441
Create the deployment, service and ingress object:
436442

docs/tutorials/azure.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,13 @@ spec:
737737
number: 80
738738
```
739739

740-
When using ExternalDNS with `ingress` objects it will automatically create DNS records based on host names specified in ingress objects that match the domain-filter argument in the external-dns deployment manifest. When those host names are removed or renamed the corresponding DNS records are also altered.
740+
When you use ExternalDNS with Ingress resources, it automatically creates DNS records based on the hostnames listed in those Ingress objects.
741+
Those hostnames must match the filters that you defined (if any):
742+
743+
- By default, `--domain-filter` filters Azure DNS zone.
744+
- If you use `--domain-filter` together with `--zone-name-filter`, the behavior changes: `--domain-filter` then filters Ingress domains, not the Azure DNS zone name.
745+
746+
When those hostnames are removed or renamed the corresponding DNS records are also altered.
741747

742748
Create the deployment, service and ingress object:
743749

main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ func main() {
255255
case "azure-dns", "azure":
256256
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.DryRun)
257257
case "azure-private-dns":
258-
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.DryRun)
258+
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.DryRun)
259259
case "bluecat":
260260
p, err = bluecat.NewBluecatProvider(cfg.BluecatConfigFile, cfg.BluecatDNSConfiguration, cfg.BluecatDNSServerName, cfg.BluecatDNSDeployType, cfg.BluecatDNSView, cfg.BluecatGatewayHost, cfg.BluecatRootZone, cfg.TXTPrefix, cfg.TXTSuffix, domainFilter, zoneIDFilter, cfg.DryRun, cfg.BluecatSkipTLSVerify)
261261
case "vinyldns":

provider/azure/azure_private_dns.go

+18-1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type PrivateRecordSetsClient interface {
4848
type AzurePrivateDNSProvider struct {
4949
provider.BaseProvider
5050
domainFilter endpoint.DomainFilter
51+
zoneNameFilter endpoint.DomainFilter
5152
zoneIDFilter provider.ZoneIDFilter
5253
dryRun bool
5354
resourceGroup string
@@ -60,7 +61,7 @@ type AzurePrivateDNSProvider struct {
6061
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
6162
//
6263
// Returns the provider or an error if a provider could not be created.
63-
func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, dryRun bool) (*AzurePrivateDNSProvider, error) {
64+
func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, dryRun bool) (*AzurePrivateDNSProvider, error) {
6465
cfg, err := getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID, activeDirectoryAuthorityHost)
6566
if err != nil {
6667
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
@@ -80,6 +81,7 @@ func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainF
8081
}
8182
return &AzurePrivateDNSProvider{
8283
domainFilter: domainFilter,
84+
zoneNameFilter: zoneNameFilter,
8385
zoneIDFilter: zoneIDFilter,
8486
dryRun: dryRun,
8587
resourceGroup: cfg.ResourceGroup,
@@ -124,6 +126,10 @@ func (p *AzurePrivateDNSProvider) Records(ctx context.Context) (endpoints []*end
124126
}
125127
name = formatAzureDNSName(*recordSet.Name, *zone.Name)
126128

129+
if len(p.zoneNameFilter.Filters) > 0 && !p.domainFilter.Match(name) {
130+
log.Debugf("Skipping return of record %s because it was filtered out by the specified --domain-filter", name)
131+
continue
132+
}
127133
targets := extractAzurePrivateDNSTargets(recordSet)
128134
if len(targets) == 0 {
129135
log.Debugf("Failed to extract targets for '%s' with type '%s'.", name, recordType)
@@ -185,6 +191,9 @@ func (p *AzurePrivateDNSProvider) zones(ctx context.Context) ([]privatedns.Priva
185191

186192
if zone.Name != nil && p.domainFilter.Match(*zone.Name) && p.zoneIDFilter.Match(*zone.ID) {
187193
zones = append(zones, *zone)
194+
} else if zone.Name != nil && len(p.zoneNameFilter.Filters) > 0 && p.zoneNameFilter.Match(*zone.Name) {
195+
// Handle zoneNameFilter
196+
zones = append(zones, *zone)
188197
}
189198
}
190199
}
@@ -238,6 +247,10 @@ func (p *AzurePrivateDNSProvider) deleteRecords(ctx context.Context, deleted azu
238247
for zone, endpoints := range deleted {
239248
for _, ep := range endpoints {
240249
name := p.recordSetNameForZone(zone, ep)
250+
if !p.domainFilter.Match(ep.DNSName) {
251+
log.Debugf("Skipping deletion of record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
252+
continue
253+
}
241254
if p.dryRun {
242255
log.Infof("Would delete %s record named '%s' for Azure Private DNS zone '%s'.", ep.RecordType, name, zone)
243256
} else {
@@ -261,6 +274,10 @@ func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azu
261274
for zone, endpoints := range updated {
262275
for _, ep := range endpoints {
263276
name := p.recordSetNameForZone(zone, ep)
277+
if !p.domainFilter.Match(ep.DNSName) {
278+
log.Debugf("Skipping update of record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
279+
continue
280+
}
264281
if p.dryRun {
265282
log.Infof(
266283
"Would update %s record named '%s' to '%s' for Azure Private DNS zone '%s'.",

provider/azure/azure_privatedns_test.go

+127-5
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,16 @@ func createPrivateMockRecordSetMultiWithTTL(name, recordType string, ttl int64,
224224
}
225225

226226
// newMockedAzurePrivateDNSProvider creates an AzureProvider comprising the mocked clients for zones and recordsets
227-
func newMockedAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, zones []*privatedns.PrivateZone, recordSets []*privatedns.RecordSet) (*AzurePrivateDNSProvider, error) {
227+
func newMockedAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, zones []*privatedns.PrivateZone, recordSets []*privatedns.RecordSet) (*AzurePrivateDNSProvider, error) {
228228
zonesClient := newMockPrivateZonesClient(zones)
229229
recordSetsClient := newMockPrivateRecordSectsClient(recordSets)
230-
return newAzurePrivateDNSProvider(domainFilter, zoneIDFilter, dryRun, resourceGroup, &zonesClient, &recordSetsClient), nil
230+
return newAzurePrivateDNSProvider(domainFilter, zoneNameFilter, zoneIDFilter, dryRun, resourceGroup, &zonesClient, &recordSetsClient), nil
231231
}
232232

233-
func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, privateZonesClient PrivateZonesClient, privateRecordsClient PrivateRecordSetsClient) *AzurePrivateDNSProvider {
233+
func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, resourceGroup string, privateZonesClient PrivateZonesClient, privateRecordsClient PrivateRecordSetsClient) *AzurePrivateDNSProvider {
234234
return &AzurePrivateDNSProvider{
235235
domainFilter: domainFilter,
236+
zoneNameFilter: zoneNameFilter,
236237
zoneIDFilter: zoneIDFilter,
237238
dryRun: dryRun,
238239
resourceGroup: resourceGroup,
@@ -242,7 +243,7 @@ func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter
242243
}
243244

244245
func TestAzurePrivateDNSRecord(t *testing.T) {
245-
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
246+
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), endpoint.NewDomainFilter([]string{}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
246247
[]*privatedns.PrivateZone{
247248
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
248249
},
@@ -281,7 +282,7 @@ func TestAzurePrivateDNSRecord(t *testing.T) {
281282
}
282283

283284
func TestAzurePrivateDNSMultiRecord(t *testing.T) {
284-
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
285+
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), endpoint.NewDomainFilter([]string{}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
285286
[]*privatedns.PrivateZone{
286287
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
287288
},
@@ -369,6 +370,7 @@ func testAzurePrivateDNSApplyChangesInternal(t *testing.T, dryRun bool, client P
369370
zonesClient := newMockPrivateZonesClient(zones)
370371

371372
provider := newAzurePrivateDNSProvider(
373+
endpoint.NewDomainFilter([]string{""}),
372374
endpoint.NewDomainFilter([]string{""}),
373375
provider.NewZoneIDFilter([]string{""}),
374376
dryRun,
@@ -430,3 +432,123 @@ func testAzurePrivateDNSApplyChangesInternal(t *testing.T, dryRun bool, client P
430432
t.Fatal(err)
431433
}
432434
}
435+
436+
func TestAzurePrivateDNSNameFilter(t *testing.T) {
437+
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"nginx.example.com"}), endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
438+
[]*privatedns.PrivateZone{
439+
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
440+
},
441+
442+
[]*privatedns.RecordSet{
443+
createPrivateMockRecordSet("@", "NS", "ns1-03.azure-dns.com."),
444+
createPrivateMockRecordSet("@", "SOA", "Email: azuredns-hostmaster.microsoft.com"),
445+
createPrivateMockRecordSet("@", endpoint.RecordTypeA, "123.123.123.122"),
446+
createPrivateMockRecordSet("@", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"),
447+
createPrivateMockRecordSetWithTTL("test.nginx", endpoint.RecordTypeA, "123.123.123.123", 3600),
448+
createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeA, "123.123.123.123", 3600),
449+
createPrivateMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL),
450+
createPrivateMockRecordSetWithTTL("mail.nginx", endpoint.RecordTypeMX, "20 example.com", recordTTL),
451+
createPrivateMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10),
452+
})
453+
if err != nil {
454+
t.Fatal(err)
455+
}
456+
457+
ctx := context.Background()
458+
actual, err := provider.Records(ctx)
459+
if err != nil {
460+
t.Fatal(err)
461+
}
462+
expected := []*endpoint.Endpoint{
463+
endpoint.NewEndpointWithTTL("test.nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123"),
464+
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeA, 3600, "123.123.123.123"),
465+
endpoint.NewEndpointWithTTL("nginx.example.com", endpoint.RecordTypeTXT, recordTTL, "heritage=external-dns,external-dns/owner=default"),
466+
endpoint.NewEndpointWithTTL("mail.nginx.example.com", endpoint.RecordTypeMX, recordTTL, "20 example.com"),
467+
}
468+
469+
validateAzureEndpoints(t, actual, expected)
470+
}
471+
472+
func TestAzurePrivateDNSApplyChangesZoneName(t *testing.T) {
473+
recordsClient := mockRecordSetsClient{}
474+
475+
testAzureApplyChangesInternalZoneName(t, false, &recordsClient)
476+
477+
validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{
478+
endpoint.NewEndpoint("deleted.foo.example.com", endpoint.RecordTypeA, ""),
479+
endpoint.NewEndpoint("deletedaaaa.foo.example.com", endpoint.RecordTypeAAAA, ""),
480+
endpoint.NewEndpoint("deletedcname.foo.example.com", endpoint.RecordTypeCNAME, ""),
481+
})
482+
483+
validateAzureEndpoints(t, recordsClient.updatedEndpoints, []*endpoint.Endpoint{
484+
endpoint.NewEndpointWithTTL("foo.example.com", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4", "1.2.3.5"),
485+
endpoint.NewEndpointWithTTL("foo.example.com", endpoint.RecordTypeAAAA, endpoint.TTL(recordTTL), "2001::1:2:3:4", "2001::1:2:3:5"),
486+
endpoint.NewEndpointWithTTL("foo.example.com", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "tag"),
487+
endpoint.NewEndpointWithTTL("new.foo.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
488+
endpoint.NewEndpointWithTTL("new.foo.example.com", endpoint.RecordTypeAAAA, 3600, "2001::111:222:111:222"),
489+
endpoint.NewEndpointWithTTL("newcname.foo.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
490+
})
491+
}
492+
493+
func testAzurePrivateDNSApplyChangesInternalZoneName(t *testing.T, dryRun bool, client PrivateRecordSetsClient) {
494+
zones := []*privatedns.PrivateZone{
495+
createMockPrivateZone("example.com", "/privateDnsZones/example.com"),
496+
}
497+
zonesClient := newMockPrivateZonesClient(zones)
498+
499+
provider := newAzurePrivateDNSProvider(
500+
endpoint.NewDomainFilter([]string{"foo.example.com"}),
501+
endpoint.NewDomainFilter([]string{"example.com"}),
502+
provider.NewZoneIDFilter([]string{""}),
503+
dryRun,
504+
"group",
505+
&zonesClient,
506+
client,
507+
)
508+
509+
createRecords := []*endpoint.Endpoint{
510+
endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"),
511+
endpoint.NewEndpoint("example.com", endpoint.RecordTypeAAAA, "2001::1:2:3:4"),
512+
endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"),
513+
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.5", "1.2.3.4"),
514+
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeAAAA, "2001::1:2:3:5", "2001::1:2:3:4"),
515+
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"),
516+
endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"),
517+
endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"),
518+
endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"),
519+
endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"),
520+
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"),
521+
endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"),
522+
}
523+
524+
currentRecords := []*endpoint.Endpoint{
525+
endpoint.NewEndpoint("old.foo.example.com", endpoint.RecordTypeA, "121.212.121.212"),
526+
endpoint.NewEndpoint("oldcname.foo.example.com", endpoint.RecordTypeCNAME, "other.com"),
527+
endpoint.NewEndpoint("old.nope.example.com", endpoint.RecordTypeA, "121.212.121.212"),
528+
}
529+
updatedRecords := []*endpoint.Endpoint{
530+
endpoint.NewEndpointWithTTL("new.foo.example.com", endpoint.RecordTypeA, 3600, "111.222.111.222"),
531+
endpoint.NewEndpointWithTTL("new.foo.example.com", endpoint.RecordTypeAAAA, 3600, "2001::111:222:111:222"),
532+
endpoint.NewEndpointWithTTL("newcname.foo.example.com", endpoint.RecordTypeCNAME, 10, "other.com"),
533+
endpoint.NewEndpoint("new.nope.example.com", endpoint.RecordTypeA, "222.111.222.111"),
534+
endpoint.NewEndpoint("new.nope.example.com", endpoint.RecordTypeAAAA, "2001::222:111:222:111"),
535+
}
536+
537+
deleteRecords := []*endpoint.Endpoint{
538+
endpoint.NewEndpoint("deleted.foo.example.com", endpoint.RecordTypeA, "111.222.111.222"),
539+
endpoint.NewEndpoint("deletedaaaa.foo.example.com", endpoint.RecordTypeAAAA, "2001::111:222:111:222"),
540+
endpoint.NewEndpoint("deletedcname.foo.example.com", endpoint.RecordTypeCNAME, "other.com"),
541+
endpoint.NewEndpoint("deleted.nope.example.com", endpoint.RecordTypeA, "222.111.222.111"),
542+
}
543+
544+
changes := &plan.Changes{
545+
Create: createRecords,
546+
UpdateNew: updatedRecords,
547+
UpdateOld: currentRecords,
548+
Delete: deleteRecords,
549+
}
550+
551+
if err := provider.ApplyChanges(context.Background(), changes); err != nil {
552+
t.Fatal(err)
553+
}
554+
}

0 commit comments

Comments
 (0)