From 1b09919ce0cdadbadfb782b62c511051edfb6d07 Mon Sep 17 00:00:00 2001 From: Ashutosh Kumar Date: Mon, 25 Oct 2021 17:37:47 +0530 Subject: [PATCH] chore(private_dns): tag dns zone and delete only if owned This commit tags private DNS zone created by CAPZ and delete only if it has been created by CAPZ Signed-off-by: Ashutosh Kumar --- azure/services/privatedns/client.go | 25 +- .../privatedns/mock_privatedns/client_mock.go | 30 ++ azure/services/privatedns/privatedns.go | 94 +++++- azure/services/privatedns/privatedns_test.go | 304 ++++++++++++++++-- docs/book/src/topics/custom-dns.md | 15 + 5 files changed, 445 insertions(+), 23 deletions(-) diff --git a/azure/services/privatedns/client.go b/azure/services/privatedns/client.go index e3c0fdc80a80..f4f9e2b8a103 100644 --- a/azure/services/privatedns/client.go +++ b/azure/services/privatedns/client.go @@ -28,8 +28,10 @@ import ( // Client wraps go-sdk. type client interface { + GetZone(context.Context, string, string) (privatedns.PrivateZone, error) CreateOrUpdateZone(context.Context, string, string, privatedns.PrivateZone) error DeleteZone(context.Context, string, string) error + GetLink(context.Context, string, string, string) (privatedns.VirtualNetworkLink, error) CreateOrUpdateLink(context.Context, string, string, string, privatedns.VirtualNetworkLink) error DeleteLink(context.Context, string, string, string) error CreateOrUpdateRecordSet(context.Context, string, string, privatedns.RecordType, string, privatedns.RecordSet) error @@ -74,11 +76,21 @@ func newRecordSetsClient(subscriptionID string, baseURI string, authorizer autor return recordsClient } +// GetZone returns a private zone. +func (ac *azureClient) GetZone(ctx context.Context, resourceGroupName, zoneName string) (privatedns.PrivateZone, error) { + ctx, _, done := tele.StartSpanWithLogger(ctx, "privatedns.AzureClient.GetZone") + defer done() + zone, err := ac.privatezones.Get(ctx, resourceGroupName, zoneName) + if err != nil { + return privatedns.PrivateZone{}, err + } + return zone, nil +} + // CreateOrUpdateZone creates or updates a private zone. func (ac *azureClient) CreateOrUpdateZone(ctx context.Context, resourceGroupName string, zoneName string, zone privatedns.PrivateZone) error { ctx, _, done := tele.StartSpanWithLogger(ctx, "privatedns.AzureClient.CreateOrUpdateZone") defer done() - future, err := ac.privatezones.CreateOrUpdate(ctx, resourceGroupName, zoneName, zone, "", "") if err != nil { return err @@ -108,6 +120,17 @@ func (ac *azureClient) DeleteZone(ctx context.Context, resourceGroupName, name s return err } +// GetLink returns a vnet link. +func (ac *azureClient) GetLink(ctx context.Context, resourceGroupName, zoneName, vnetLinkName string) (privatedns.VirtualNetworkLink, error) { + ctx, _, done := tele.StartSpanWithLogger(ctx, "privatedns.AzureClient.GetLink") + defer done() + vnetLink, err := ac.vnetlinks.Get(ctx, resourceGroupName, zoneName, vnetLinkName) + if err != nil { + return privatedns.VirtualNetworkLink{}, err + } + return vnetLink, nil +} + // CreateOrUpdateLink creates or updates a virtual network link to the specified Private DNS zone. func (ac *azureClient) CreateOrUpdateLink(ctx context.Context, resourceGroupName, privateZoneName, name string, link privatedns.VirtualNetworkLink) error { ctx, _, done := tele.StartSpanWithLogger(ctx, "privatedns.AzureClient.CreateOrUpdateLink") diff --git a/azure/services/privatedns/mock_privatedns/client_mock.go b/azure/services/privatedns/mock_privatedns/client_mock.go index 7bf87e084b61..cf9d71ec70cd 100644 --- a/azure/services/privatedns/mock_privatedns/client_mock.go +++ b/azure/services/privatedns/mock_privatedns/client_mock.go @@ -134,3 +134,33 @@ func (mr *MockclientMockRecorder) DeleteZone(arg0, arg1, arg2 interface{}) *gomo mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteZone", reflect.TypeOf((*Mockclient)(nil).DeleteZone), arg0, arg1, arg2) } + +// GetLink mocks base method. +func (m *Mockclient) GetLink(arg0 context.Context, arg1, arg2, arg3 string) (privatedns.VirtualNetworkLink, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLink", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(privatedns.VirtualNetworkLink) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLink indicates an expected call of GetLink. +func (mr *MockclientMockRecorder) GetLink(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLink", reflect.TypeOf((*Mockclient)(nil).GetLink), arg0, arg1, arg2, arg3) +} + +// GetZone mocks base method. +func (m *Mockclient) GetZone(arg0 context.Context, arg1, arg2 string) (privatedns.PrivateZone, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetZone", arg0, arg1, arg2) + ret0, _ := ret[0].(privatedns.PrivateZone) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetZone indicates an expected call of GetZone. +func (mr *MockclientMockRecorder) GetZone(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetZone", reflect.TypeOf((*Mockclient)(nil).GetZone), arg0, arg1, arg2) +} diff --git a/azure/services/privatedns/privatedns.go b/azure/services/privatedns/privatedns.go index f4aced098711..02a940c7dc36 100644 --- a/azure/services/privatedns/privatedns.go +++ b/azure/services/privatedns/privatedns.go @@ -19,6 +19,8 @@ package privatedns import ( "context" + infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" + "github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns" "github.com/Azure/go-autorest/autorest/to" "github.com/go-logr/logr" @@ -57,15 +59,53 @@ func (s *Service) Reconcile(ctx context.Context) error { zoneSpec := s.Scope.PrivateDNSSpec() if zoneSpec != nil { + // Skip the reconciliation of private DNS zone which is not managed by capz. + isManaged, err := s.isPrivateDNSManaged(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName) + if err != nil && !azure.ResourceNotFound(err) { + return errors.Wrapf(err, "could not get private DNS zone state of %s in resource group %s", zoneSpec.ZoneName, s.Scope.ResourceGroup()) + } + // If resource is not found, it means it should be created and hence setting isVnetLinkManaged to true + // will allow the reconciliation to continue + if err != nil && azure.ResourceNotFound(err) { + isManaged = true + } + if !isManaged { + s.Scope.V(1).Info("Skipping reconciliation of unmanaged private DNS zone", "private DNS", zoneSpec.ZoneName) + s.Scope.V(1).Info("Tag the DNS manually from azure to manage it with capz."+ + "Please see https://capz.sigs.k8s.io/topics/custom-dns.html#manage-dns-via-capz-tool", "private DNS", zoneSpec.ZoneName) + return nil + } // Create the private DNS zone. s.Scope.V(2).Info("creating private DNS zone", "private dns zone", zoneSpec.ZoneName) - err := s.client.CreateOrUpdateZone(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName, privatedns.PrivateZone{Location: to.StringPtr(azure.Global)}) + pDNS := privatedns.PrivateZone{ + Location: to.StringPtr(azure.Global), + Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{ + ClusterName: s.Scope.ClusterName(), + Lifecycle: infrav1.ResourceLifecycleOwned, + Additional: s.Scope.AdditionalTags(), + })), + } + err = s.client.CreateOrUpdateZone(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName, pDNS) if err != nil { return errors.Wrapf(err, "failed to create private DNS zone %s", zoneSpec.ZoneName) } s.Scope.V(2).Info("successfully created private DNS zone", "private dns zone", zoneSpec.ZoneName) for _, linkSpec := range zoneSpec.Links { + // If the virtual network link is not managed by capz, skip its reconciliation + isVnetLinkManaged, err := s.isVnetLinkManaged(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName, linkSpec.LinkName) + if err != nil && !azure.ResourceNotFound(err) { + return errors.Wrapf(err, "could not get vnet link state of %s in resource group %s", zoneSpec.ZoneName, s.Scope.ResourceGroup()) + } + // If resource is not found, it means it should be created and hence setting isVnetLinkManaged to true + // will allow the reconciliation to continue + if err != nil && azure.ResourceNotFound(err) { + isVnetLinkManaged = true + } + if !isVnetLinkManaged { + s.Scope.V(2).Info("Skipping vnet link reconciliation for unmanaged vnet link", "vnet link", linkSpec.LinkName, "private dns zone", zoneSpec.ZoneName) + continue + } // Link each virtual network. s.Scope.V(2).Info("creating a virtual network link", "virtual network", linkSpec.VNetName, "private dns zone", zoneSpec.ZoneName) link := privatedns.VirtualNetworkLink{ @@ -76,6 +116,11 @@ func (s *Service) Reconcile(ctx context.Context) error { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: converters.TagsToMap(infrav1.Build(infrav1.BuildParams{ + ClusterName: s.Scope.ClusterName(), + Lifecycle: infrav1.ResourceLifecycleOwned, + Additional: s.Scope.AdditionalTags(), + })), } err = s.client.CreateOrUpdateLink(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName, linkSpec.LinkName, link) if err != nil { @@ -112,7 +157,7 @@ func (s *Service) Reconcile(ctx context.Context) error { return nil } -// Delete deletes the private zone. +// Delete deletes the private zone and vnet links. func (s *Service) Delete(ctx context.Context) error { ctx, _, done := tele.StartSpanWithLogger(ctx, "privatedns.Service.Delete") defer done() @@ -121,16 +166,35 @@ func (s *Service) Delete(ctx context.Context) error { if zoneSpec != nil { for _, linkSpec := range zoneSpec.Links { // Remove each virtual network link. + // If the virtual network link is not managed by capz, skip its removal + isVnetLinkManaged, err := s.isVnetLinkManaged(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName, linkSpec.LinkName) + if err != nil && !azure.ResourceNotFound(err) { + return errors.Wrapf(err, "could not get vnet link state of %s in resource group %s", zoneSpec.ZoneName, s.Scope.ResourceGroup()) + } + if !isVnetLinkManaged { + s.Scope.V(2).Info("Skipping vnet link deletion for unmanaged vnet link", "vnet link", linkSpec.LinkName, "private dns zone", zoneSpec.ZoneName) + continue + } s.Scope.V(2).Info("removing virtual network link", "virtual network", linkSpec.VNetName, "private dns zone", zoneSpec.ZoneName) - err := s.client.DeleteLink(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName, linkSpec.LinkName) + err = s.client.DeleteLink(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName, linkSpec.LinkName) if err != nil && !azure.ResourceNotFound(err) { return errors.Wrapf(err, "failed to delete virtual network link %s with zone %s in resource group %s", linkSpec.VNetName, zoneSpec.ZoneName, s.Scope.ResourceGroup()) } } // Delete the private DNS zone, which also deletes all records. + // Skip the deletion of private DNS zone which is not managed by capz. + isManaged, err := s.isPrivateDNSManaged(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName) + if err != nil && !azure.ResourceNotFound(err) { + return errors.Wrapf(err, "could not get private DNS zone state of %s in resource group %s", zoneSpec.ZoneName, s.Scope.ResourceGroup()) + } + + if !isManaged { + s.Scope.V(1).Info("Skipping private DNS zone deletion for unmanaged private DNS zone", "private DNS", zoneSpec.ZoneName) + return nil + } s.Scope.V(2).Info("deleting private dns zone", "private dns zone", zoneSpec.ZoneName) - err := s.client.DeleteZone(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName) + err = s.client.DeleteZone(ctx, s.Scope.ResourceGroup(), zoneSpec.ZoneName) if err != nil && azure.ResourceNotFound(err) { // already deleted return nil @@ -142,3 +206,25 @@ func (s *Service) Delete(ctx context.Context) error { } return nil } + +// isPrivateDNSManaged returns true if the private DNS has an owned tag with the cluster name as value, +// meaning that the DNS lifecycle is managed. +func (s *Service) isPrivateDNSManaged(ctx context.Context, resourceGroup, zoneName string) (bool, error) { + zone, err := s.client.GetZone(ctx, resourceGroup, zoneName) + if err != nil { + return false, err + } + tags := converters.MapToTags(zone.Tags) + return tags.HasOwned(s.Scope.ClusterName()), nil +} + +// isVnetLinkManaged returns true if the vnet link has an owned tag with the cluster name as value, +// meaning that the vnet link lifecycle is managed. +func (s *Service) isVnetLinkManaged(ctx context.Context, resourceGroupName, zoneName, vnetLinkName string) (bool, error) { + zone, err := s.client.GetLink(ctx, resourceGroupName, zoneName, vnetLinkName) + if err != nil { + return false, err + } + tags := converters.MapToTags(zone.Tags) + return tags.HasOwned(s.Scope.ClusterName()), nil +} diff --git a/azure/services/privatedns/privatedns_test.go b/azure/services/privatedns/privatedns_test.go index c318bb9497d1..35ac56bc7e6d 100644 --- a/azure/services/privatedns/privatedns_test.go +++ b/azure/services/privatedns/privatedns_test.go @@ -70,8 +70,19 @@ func TestReconcilePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + s.AdditionalTags().AnyTimes().Return(infrav1.Tags{}) s.SubscriptionID().Return("123") - m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{Location: to.StringPtr(azure.Global)}) + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). + Return(privatedns.PrivateZone{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{ + Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, + }) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) m.CreateOrUpdateLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link", privatedns.VirtualNetworkLink{ VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ VirtualNetwork: &privatedns.SubResource{ @@ -80,6 +91,9 @@ func TestReconcilePrivateDNS(t *testing.T) { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, }) m.CreateOrUpdateRecordSet(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.A, "hostname-1", privatedns.RecordSet{ RecordSetProperties: &privatedns.RecordSetProperties{ @@ -120,8 +134,19 @@ func TestReconcilePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + s.AdditionalTags().AnyTimes().Return(infrav1.Tags{}) s.SubscriptionID().AnyTimes().Return("123") - m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{Location: to.StringPtr(azure.Global)}) + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). + Return(privatedns.PrivateZone{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{ + Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, + }) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) m.CreateOrUpdateLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1", privatedns.VirtualNetworkLink{ VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ VirtualNetwork: &privatedns.SubResource{ @@ -130,7 +155,12 @@ func TestReconcilePrivateDNS(t *testing.T) { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, }) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) m.CreateOrUpdateLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2", privatedns.VirtualNetworkLink{ VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ VirtualNetwork: &privatedns.SubResource{ @@ -139,6 +169,9 @@ func TestReconcilePrivateDNS(t *testing.T) { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, }) m.CreateOrUpdateRecordSet(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.A, "hostname-1", privatedns.RecordSet{ RecordSetProperties: &privatedns.RecordSetProperties{ @@ -174,8 +207,19 @@ func TestReconcilePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") - s.SubscriptionID().Return("123") - m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{Location: to.StringPtr(azure.Global)}) + s.ClusterName().AnyTimes().Return("my-cluster") + s.AdditionalTags().AnyTimes().Return(infrav1.Tags{}) + s.SubscriptionID().AnyTimes().Return("123") + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). + Return(privatedns.PrivateZone{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{ + Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, + }) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) m.CreateOrUpdateLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link", privatedns.VirtualNetworkLink{ VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ VirtualNetwork: &privatedns.SubResource{ @@ -184,6 +228,9 @@ func TestReconcilePrivateDNS(t *testing.T) { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, }) m.CreateOrUpdateRecordSet(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.AAAA, "hostname-2", privatedns.RecordSet{ RecordSetProperties: &privatedns.RecordSetProperties{ @@ -224,8 +271,19 @@ func TestReconcilePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + s.AdditionalTags().AnyTimes().Return(infrav1.Tags{}) s.SubscriptionID().AnyTimes().Return("123") - m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{Location: to.StringPtr(azure.Global)}) + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). + Return(privatedns.PrivateZone{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{ + Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, + }) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) m.CreateOrUpdateLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1", privatedns.VirtualNetworkLink{ VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ VirtualNetwork: &privatedns.SubResource{ @@ -234,7 +292,12 @@ func TestReconcilePrivateDNS(t *testing.T) { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, }) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) m.CreateOrUpdateLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2", privatedns.VirtualNetworkLink{ VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ VirtualNetwork: &privatedns.SubResource{ @@ -243,6 +306,9 @@ func TestReconcilePrivateDNS(t *testing.T) { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, }) m.CreateOrUpdateRecordSet(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.AAAA, "hostname-2", privatedns.RecordSet{ RecordSetProperties: &privatedns.RecordSetProperties{ @@ -278,8 +344,19 @@ func TestReconcilePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") - s.SubscriptionID().Return("123") - m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{Location: to.StringPtr(azure.Global)}) + s.ClusterName().AnyTimes().Return("my-cluster") + s.AdditionalTags().AnyTimes().Return(infrav1.Tags{}) + s.SubscriptionID().AnyTimes().Return("123") + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). + Return(privatedns.PrivateZone{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{ + Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, + }) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) m.CreateOrUpdateLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link", privatedns.VirtualNetworkLink{ VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ VirtualNetwork: &privatedns.SubResource{ @@ -288,6 +365,9 @@ func TestReconcilePrivateDNS(t *testing.T) { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, }).Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error")) }, }, @@ -323,8 +403,19 @@ func TestReconcilePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + s.AdditionalTags().AnyTimes().Return(infrav1.Tags{}) s.SubscriptionID().AnyTimes().Return("123") - m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{Location: to.StringPtr(azure.Global)}) + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). + Return(privatedns.PrivateZone{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.CreateOrUpdateZone(gomockinternal.AContext(), "my-rg", "my-dns-zone", privatedns.PrivateZone{ + Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, + }) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) m.CreateOrUpdateLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1", privatedns.VirtualNetworkLink{ VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ VirtualNetwork: &privatedns.SubResource{ @@ -333,7 +424,12 @@ func TestReconcilePrivateDNS(t *testing.T) { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, }) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) m.CreateOrUpdateLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2", privatedns.VirtualNetworkLink{ VirtualNetworkLinkProperties: &privatedns.VirtualNetworkLinkProperties{ VirtualNetwork: &privatedns.SubResource{ @@ -342,6 +438,9 @@ func TestReconcilePrivateDNS(t *testing.T) { RegistrationEnabled: to.BoolPtr(false), }, Location: to.StringPtr(azure.Global), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + }, }).Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error")) }, }, @@ -391,7 +490,7 @@ func TestDeletePrivateDNS(t *testing.T) { }, }, { - name: "delete the dns zone", + name: "delete the dns zone and vnet links managed by capz", expectedError: "", expect: func(s *mock_privatedns.MockScopeMockRecorder, m *mock_privatedns.MockclientMockRecorder) { s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New()) @@ -412,10 +511,52 @@ func TestDeletePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-link"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link") + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone").Return(privatedns.PrivateZone{ + Name: to.StringPtr("my-dns-zone"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteZone(gomockinternal.AContext(), "my-rg", "my-dns-zone") }, }, + { + name: "skip unmanaged private dns zone and vnet link deletion", + expectedError: "", + expect: func(s *mock_privatedns.MockScopeMockRecorder, m *mock_privatedns.MockclientMockRecorder) { + s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New()) + s.PrivateDNSSpec().Return(&azure.PrivateDNSSpec{ + ZoneName: "my-dns-zone", + Links: []azure.PrivateDNSLinkSpec{ + { + VNetName: "my-vnet", + VNetResourceGroup: "vnet-rg", + LinkName: "my-link", + }, + }, + Records: []infrav1.AddressRecord{ + { + Hostname: "hostname-1", + IP: "10.0.0.8", + }, + }, + }) + s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link") + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone") + }, + }, { name: "delete the dns zone with multiple links", expectedError: "", @@ -448,9 +589,38 @@ func TestDeletePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-3").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-3") + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone").Return(privatedns.PrivateZone{ + Name: to.StringPtr("my-dns-zone"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteZone(gomockinternal.AContext(), "my-rg", "my-dns-zone") }, }, @@ -476,8 +646,16 @@ func TestDeletePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") - m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link"). - Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone").Return(privatedns.PrivateZone{ + Name: to.StringPtr("my-dns-zone"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteZone(gomockinternal.AContext(), "my-rg", "my-dns-zone") }, }, @@ -513,15 +691,37 @@ func TestDeletePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1") - m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2"). - Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-3").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-3") + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone").Return(privatedns.PrivateZone{ + Name: to.StringPtr("my-dns-zone"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteZone(gomockinternal.AContext(), "my-rg", "my-dns-zone") }, }, { - name: "zone already deleted", + name: "zone and all vnet links already deleted", expectedError: "", expect: func(s *mock_privatedns.MockScopeMockRecorder, m *mock_privatedns.MockclientMockRecorder) { s.V(gomock.AssignableToTypeOf(2)).AnyTimes().Return(klogr.New()) @@ -542,10 +742,11 @@ func TestDeletePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") - m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link"). - Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) - m.DeleteZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). - Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link"). + Return(privatedns.VirtualNetworkLink{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). + Return(privatedns.PrivateZone{}, autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 404}, "Not found")) }, }, { @@ -570,6 +771,14 @@ func TestDeletePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link"). Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error")) }, @@ -606,7 +815,22 @@ func TestDeletePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2"). Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error")) }, @@ -633,7 +857,22 @@ func TestDeletePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link") + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone").Return(privatedns.PrivateZone{ + Name: to.StringPtr("my-dns-zone"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error")) }, @@ -670,9 +909,38 @@ func TestDeletePrivateDNS(t *testing.T) { }, }) s.ResourceGroup().AnyTimes().Return("my-rg") + s.ClusterName().AnyTimes().Return("my-cluster") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-1") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-2") + m.GetLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-3").Return(privatedns.VirtualNetworkLink{ + Name: to.StringPtr("my-vnet"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteLink(gomockinternal.AContext(), "my-rg", "my-dns-zone", "my-link-3") + m.GetZone(gomockinternal.AContext(), "my-rg", "my-dns-zone").Return(privatedns.PrivateZone{ + Name: to.StringPtr("my-dns-zone"), + Tags: map[string]*string{ + "sigs.k8s.io_cluster-api-provider-azure_cluster_my-cluster": to.StringPtr("owned"), + "foo": to.StringPtr("bar"), + }, + }, nil) m.DeleteZone(gomockinternal.AContext(), "my-rg", "my-dns-zone"). Return(autorest.NewErrorWithResponse("", "", &http.Response{StatusCode: 500}, "Internal Server Error")) }, diff --git a/docs/book/src/topics/custom-dns.md b/docs/book/src/topics/custom-dns.md index ee82149e3ddc..2e7bc2291a4e 100644 --- a/docs/book/src/topics/custom-dns.md +++ b/docs/book/src/topics/custom-dns.md @@ -35,3 +35,18 @@ spec: resourceGroup: cluster-example ``` +# Manage DNS Via CAPZ Tool + +Private DNS when created by CAPZ can be managed by CAPZ tool itself automatically. To give the flexibility to have BYO +as well as managed DNS zone, an enhancement is made that causes all the managed zones created in the CAPZ version before +the enhancement changes to be treated as unmanaged. The enhancement is captured in PR +[1791](https://github.com/kubernetes-sigs/cluster-api-provider-azure/pull/1791) + +To manage the private DNS via CAPZ please tag it manually from azure portal. + +Steps to tag: + +- Go to azure portal and search for `Private DNS zones`. +- Select the DNS zone that you want to be managed. +- Go to `Tags` section and add key as `sigs.k8s.io_cluster-api-provider-azure_cluster_` and value as +`owned`. (Note: clustername is the name of the cluster that you created) \ No newline at end of file