From 562f646fcf4e26b9f339e8a608a7f1c9e234f810 Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Thu, 13 Apr 2023 17:00:41 -0500 Subject: [PATCH] adopt previously managed resources --- azure/interfaces.go | 3 ++ azure/mock_azure/azure_mock.go | 14 +++++++ azure/services/aso/aso.go | 2 + azure/services/aso/aso_test.go | 60 +++++++++++++++++++++++++++ azure/services/asogroups/spec.go | 9 ++++ azure/services/asogroups/spec_test.go | 59 ++++++++++++++++++++++++++ 6 files changed, 147 insertions(+) diff --git a/azure/interfaces.go b/azure/interfaces.go index 696740b53eb..da77aaaa3d2 100644 --- a/azure/interfaces.go +++ b/azure/interfaces.go @@ -137,4 +137,7 @@ type ASOResourceSpecGetter interface { // Parameters returns a modified object if it points to a non-nil resource. // Otherwise it returns a new value or nil if no updates are needed. Parameters(ctx context.Context, object genruntime.MetaObject) (genruntime.MetaObject, error) + // WasManaged returns whether or not the given resource was managed by a + // non-ASO-backed CAPZ and should be considered eligible for adoption. + WasManaged(genruntime.MetaObject) bool } diff --git a/azure/mock_azure/azure_mock.go b/azure/mock_azure/azure_mock.go index d23c8e4b2d4..8e9da9269fd 100644 --- a/azure/mock_azure/azure_mock.go +++ b/azure/mock_azure/azure_mock.go @@ -1925,3 +1925,17 @@ func (mr *MockASOResourceSpecGetterMockRecorder) ResourceRef() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResourceRef", reflect.TypeOf((*MockASOResourceSpecGetter)(nil).ResourceRef)) } + +// WasManaged mocks base method. +func (m *MockASOResourceSpecGetter) WasManaged(arg0 genruntime.MetaObject) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WasManaged", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// WasManaged indicates an expected call of WasManaged. +func (mr *MockASOResourceSpecGetterMockRecorder) WasManaged(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WasManaged", reflect.TypeOf((*MockASOResourceSpecGetter)(nil).WasManaged), arg0) +} diff --git a/azure/services/aso/aso.go b/azure/services/aso/aso.go index 659cb8d2d13..f860dfaca78 100644 --- a/azure/services/aso/aso.go +++ b/azure/services/aso/aso.go @@ -163,6 +163,8 @@ func (s *Service) CreateOrUpdateResource(ctx context.Context, spec azure.ASOReso // Azure and the ASO resource will be adopted by changing this // annotation to "manage". annotations[ReconcilePolicyAnnotation] = ReconcilePolicySkip + } else { + adopt = adopt || spec.WasManaged(existing) } if adopt { annotations[ReconcilePolicyAnnotation] = ReconcilePolicyManage diff --git a/azure/services/aso/aso_test.go b/azure/services/aso/aso_test.go index bed4f001bbb..cfd2658f6d0 100644 --- a/azure/services/aso/aso_test.go +++ b/azure/services/aso/aso_test.go @@ -344,6 +344,7 @@ func TestCreateOrUpdateResource(t *testing.T) { group.Spec.Location = pointer.String("location") return group, nil }) + specMock.EXPECT().WasManaged(gomock.Any()).Return(false) ctx := context.Background() g.Expect(c.Create(ctx, &asoresourcesv1.ResourceGroup{ @@ -426,6 +427,63 @@ func TestCreateOrUpdateResource(t *testing.T) { })) }) + t.Run("adopt previously managed resource", func(t *testing.T) { + g := NewGomegaWithT(t) + + sch := runtime.NewScheme() + g.Expect(asoresourcesv1.AddToScheme(sch)).To(Succeed()) + c := fakeclient.NewClientBuilder(). + WithScheme(sch). + Build() + clusterName := "cluster" + s := New(c, clusterName) + + mockCtrl := gomock.NewController(t) + specMock := mock_azure.NewMockASOResourceSpecGetter(mockCtrl) + specMock.EXPECT().ResourceRef().Return(&asoresourcesv1.ResourceGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + }, + }) + specMock.EXPECT().Parameters(gomockinternal.AContext(), gomock.Not(gomock.Nil())).DoAndReturn(func(_ context.Context, object genruntime.MetaObject) (genruntime.MetaObject, error) { + return nil, nil + }) + specMock.EXPECT().WasManaged(gomock.Any()).Return(true) + + ctx := context.Background() + g.Expect(c.Create(ctx, &asoresourcesv1.ResourceGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "name", + Namespace: "namespace", + Labels: map[string]string{ + infrav1.OwnedByClusterLabelKey: clusterName, + }, + Annotations: map[string]string{ + ReconcilePolicyAnnotation: ReconcilePolicySkip, + }, + }, + Status: asoresourcesv1.ResourceGroup_STATUS{ + Conditions: []conditions.Condition{ + { + Type: conditions.ConditionTypeReady, + Status: metav1.ConditionTrue, + }, + }, + }, + })).To(Succeed()) + + result, err := s.CreateOrUpdateResource(ctx, specMock, "service") + g.Expect(result).To(BeNil()) + g.Expect(err).NotTo(BeNil()) + + updated := &asoresourcesv1.ResourceGroup{} + g.Expect(c.Get(ctx, types.NamespacedName{Name: "name", Namespace: "namespace"}, updated)).To(Succeed()) + g.Expect(updated.Annotations).To(Equal(map[string]string{ + ReconcilePolicyAnnotation: ReconcilePolicyManage, + })) + }) + t.Run("Parameters error", func(t *testing.T) { g := NewGomegaWithT(t) @@ -532,6 +590,7 @@ func TestCreateOrUpdateResource(t *testing.T) { specMock.EXPECT().Parameters(gomockinternal.AContext(), gomock.Any()).DoAndReturn(func(_ context.Context, object genruntime.MetaObject) (genruntime.MetaObject, error) { return nil, nil }) + specMock.EXPECT().WasManaged(gomock.Any()).Return(false) ctx := context.Background() g.Expect(c.Create(ctx, &asoresourcesv1.ResourceGroup{ @@ -590,6 +649,7 @@ func TestCreateOrUpdateResource(t *testing.T) { group.Spec.Location = pointer.String("location") return group, nil }) + specMock.EXPECT().WasManaged(gomock.Any()).Return(false) ctx := context.Background() g.Expect(c.Create(ctx, &asoresourcesv1.ResourceGroup{ diff --git a/azure/services/asogroups/spec.go b/azure/services/asogroups/spec.go index 6a4fed33da4..4658407b8b9 100644 --- a/azure/services/asogroups/spec.go +++ b/azure/services/asogroups/spec.go @@ -68,3 +68,12 @@ func (s *GroupSpec) Parameters(ctx context.Context, object genruntime.MetaObject }, }, nil } + +// WasManaged implements azure.ASOResourceSpecGetter. +func (s *GroupSpec) WasManaged(object genruntime.MetaObject) bool { + group, ok := object.(*asoresourcesv1.ResourceGroup) + if !ok { + return false + } + return infrav1.Tags(group.Status.Tags).HasOwned(s.ClusterName) +} diff --git a/azure/services/asogroups/spec_test.go b/azure/services/asogroups/spec_test.go index 60da82a5154..ab3047424d9 100644 --- a/azure/services/asogroups/spec_test.go +++ b/azure/services/asogroups/spec_test.go @@ -86,3 +86,62 @@ func TestParameters(t *testing.T) { }) } } + +func TestWasManaged(t *testing.T) { + clusterName := "cluster" + + tests := []struct { + name string + object genruntime.MetaObject + expected bool + }{ + { + name: "wrong type", + object: struct { + genruntime.MetaObject + }{}, + expected: false, + }, + { + name: "no owned label", + object: &asoresourcesv1.ResourceGroup{}, + expected: false, + }, + { + name: "wrong owned label value", + object: &asoresourcesv1.ResourceGroup{ + Status: asoresourcesv1.ResourceGroup_STATUS{ + Tags: infrav1.Build(infrav1.BuildParams{ + ClusterName: clusterName, + Lifecycle: infrav1.ResourceLifecycle("not owned"), + }), + }, + }, + expected: false, + }, + { + name: "with owned label", + object: &asoresourcesv1.ResourceGroup{ + Status: asoresourcesv1.ResourceGroup_STATUS{ + Tags: infrav1.Build(infrav1.BuildParams{ + ClusterName: clusterName, + Lifecycle: infrav1.ResourceLifecycleOwned, + }), + }, + }, + expected: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + g := NewWithT(t) + + s := &GroupSpec{ + ClusterName: clusterName, + } + + g.Expect(s.WasManaged(test.object)).To(Equal(test.expected)) + }) + } +}