diff --git a/controllers/azureasomanagedcontrolplane_controller_test.go b/controllers/azureasomanagedcontrolplane_controller_test.go index 5b8e35f0033..20e7aacb1f5 100644 --- a/controllers/azureasomanagedcontrolplane_controller_test.go +++ b/controllers/azureasomanagedcontrolplane_controller_test.go @@ -302,23 +302,10 @@ func TestAzureASOManagedControlPlaneReconcile(t *testing.T) { Ready: false, }, } - c := fakeClientBuilder(). - WithObjects(cluster, asoManagedControlPlane, managedCluster, kubeconfig). - Build() - kubeConfigPatched := false r := &AzureASOManagedControlPlaneReconciler{ - Client: &FakeClient{ - Client: c, - applyFunc: func(_ context.Context, obj runtime.ApplyConfiguration, _ ...client.ApplyOption) error { - data, err := json.Marshal(obj) - g.Expect(err).NotTo(HaveOccurred()) - kubeconfigSecret := &corev1.Secret{} - g.Expect(json.Unmarshal(data, kubeconfigSecret)).To(Succeed()) - g.Expect(kubeconfigSecret.Data[secret.KubeconfigDataName]).NotTo(BeEmpty()) - kubeConfigPatched = true - return nil - }, - }, + Client: fakeClientBuilder(). + WithObjects(cluster, asoManagedControlPlane, managedCluster, kubeconfig). + Build(), newResourceReconciler: func(_ *infrav1.AzureASOManagedControlPlane, _ []*unstructured.Unstructured) resourceReconciler { return &fakeResourceReconciler{ reconcileFunc: func(ctx context.Context, o client.Object) error { @@ -331,11 +318,19 @@ func TestAzureASOManagedControlPlaneReconcile(t *testing.T) { g.Expect(err).NotTo(HaveOccurred()) g.Expect(result).To(Equal(ctrl.Result{})) - g.Expect(c.Get(ctx, client.ObjectKeyFromObject(asoManagedControlPlane), asoManagedControlPlane)).To(Succeed()) + g.Expect(r.Get(ctx, client.ObjectKeyFromObject(asoManagedControlPlane), asoManagedControlPlane)).To(Succeed()) g.Expect(asoManagedControlPlane.Status.ControlPlaneEndpoint.Host).To(Equal("endpoint")) g.Expect(asoManagedControlPlane.Status.Version).To(Equal("vCurrent")) - g.Expect(kubeConfigPatched).To(BeTrue()) g.Expect(asoManagedControlPlane.Status.Ready).To(BeTrue()) + + kubeconfigSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secret.Name(cluster.Name, secret.Kubeconfig), + Namespace: cluster.Namespace, + }, + } + g.Expect(r.Get(ctx, client.ObjectKeyFromObject(kubeconfigSecret), kubeconfigSecret)).To(Succeed()) + g.Expect(kubeconfigSecret.Data[secret.KubeconfigDataName]).NotTo(BeEmpty()) }) t.Run("successfully reconciles a kubeconfig with a token", func(t *testing.T) { @@ -431,31 +426,10 @@ func TestAzureASOManagedControlPlaneReconcile(t *testing.T) { Ready: false, }, } - c := fakeClientBuilder(). - WithObjects(cluster, asoManagedControlPlane, managedCluster, kubeconfig). - Build() - kubeConfigPatched := false r := &AzureASOManagedControlPlaneReconciler{ - Client: &FakeClient{ - Client: c, - applyFunc: func(_ context.Context, obj runtime.ApplyConfiguration, _ ...client.ApplyOption) error { - data, err := json.Marshal(obj) - g.Expect(err).NotTo(HaveOccurred()) - kubeconfigSecret := &corev1.Secret{} - g.Expect(json.Unmarshal(data, kubeconfigSecret)).To(Succeed()) - g.Expect(kubeconfigSecret.Data[secret.KubeconfigDataName]).NotTo(BeEmpty()) - kubeConfigPatched = true - - kubeconfig, err := clientcmd.Load(kubeconfigSecret.Data[secret.KubeconfigDataName]) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(kubeconfig.AuthInfos).To(HaveEach(Satisfy(func(user *clientcmdapi.AuthInfo) bool { - return user.Exec == nil && - user.Token == "token" - }))) - - return nil - }, - }, + Client: fakeClientBuilder(). + WithObjects(cluster, asoManagedControlPlane, managedCluster, kubeconfig). + Build(), newResourceReconciler: func(_ *infrav1.AzureASOManagedControlPlane, _ []*unstructured.Unstructured) resourceReconciler { return &fakeResourceReconciler{ reconcileFunc: func(ctx context.Context, o client.Object) error { @@ -472,9 +446,23 @@ func TestAzureASOManagedControlPlaneReconcile(t *testing.T) { g.Expect(result.Requeue).To(BeFalse()) //nolint:staticcheck g.Expect(result.RequeueAfter).NotTo(BeZero()) - g.Expect(c.Get(ctx, client.ObjectKeyFromObject(asoManagedControlPlane), asoManagedControlPlane)).To(Succeed()) - g.Expect(kubeConfigPatched).To(BeTrue()) + g.Expect(r.Get(ctx, client.ObjectKeyFromObject(asoManagedControlPlane), asoManagedControlPlane)).To(Succeed()) g.Expect(asoManagedControlPlane.Status.Ready).To(BeTrue()) + + kubeconfigSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secret.Name(cluster.Name, secret.Kubeconfig), + Namespace: cluster.Namespace, + }, + } + g.Expect(r.Get(ctx, client.ObjectKeyFromObject(kubeconfigSecret), kubeconfigSecret)).To(Succeed()) + g.Expect(kubeconfigSecret.Data[secret.KubeconfigDataName]).NotTo(BeEmpty()) + clientConfig, err := clientcmd.Load(kubeconfigSecret.Data[secret.KubeconfigDataName]) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(clientConfig.AuthInfos).To(HaveEach(Satisfy(func(user *clientcmdapi.AuthInfo) bool { + return user.Exec == nil && + user.Token == "token" + }))) }) t.Run("successfully reconciles a kubeconfig with a token that has expired", func(t *testing.T) { @@ -570,31 +558,10 @@ func TestAzureASOManagedControlPlaneReconcile(t *testing.T) { Ready: true, }, } - c := fakeClientBuilder(). - WithObjects(cluster, asoManagedControlPlane, managedCluster, kubeconfig). - Build() - kubeConfigPatched := false r := &AzureASOManagedControlPlaneReconciler{ - Client: &FakeClient{ - Client: c, - applyFunc: func(_ context.Context, obj runtime.ApplyConfiguration, _ ...client.ApplyOption) error { - data, err := json.Marshal(obj) - g.Expect(err).NotTo(HaveOccurred()) - kubeconfigSecret := &corev1.Secret{} - g.Expect(json.Unmarshal(data, kubeconfigSecret)).To(Succeed()) - g.Expect(kubeconfigSecret.Data[secret.KubeconfigDataName]).NotTo(BeEmpty()) - kubeConfigPatched = true - - kubeconfig, err := clientcmd.Load(kubeconfigSecret.Data[secret.KubeconfigDataName]) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(kubeconfig.AuthInfos).To(HaveEach(Satisfy(func(user *clientcmdapi.AuthInfo) bool { - return user.Exec == nil && - user.Token == "token" - }))) - - return nil - }, - }, + Client: fakeClientBuilder(). + WithObjects(cluster, asoManagedControlPlane, managedCluster, kubeconfig). + Build(), newResourceReconciler: func(_ *infrav1.AzureASOManagedControlPlane, _ []*unstructured.Unstructured) resourceReconciler { return &fakeResourceReconciler{ reconcileFunc: func(ctx context.Context, o client.Object) error { @@ -610,9 +577,23 @@ func TestAzureASOManagedControlPlaneReconcile(t *testing.T) { g.Expect(err).NotTo(HaveOccurred()) g.Expect(result).To(Equal(ctrl.Result{Requeue: true})) - g.Expect(c.Get(ctx, client.ObjectKeyFromObject(asoManagedControlPlane), asoManagedControlPlane)).To(Succeed()) - g.Expect(kubeConfigPatched).To(BeTrue()) + g.Expect(r.Get(ctx, client.ObjectKeyFromObject(asoManagedControlPlane), asoManagedControlPlane)).To(Succeed()) g.Expect(asoManagedControlPlane.Status.Ready).To(BeFalse()) + + kubeconfigSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secret.Name(cluster.Name, secret.Kubeconfig), + Namespace: cluster.Namespace, + }, + } + g.Expect(r.Get(ctx, client.ObjectKeyFromObject(kubeconfigSecret), kubeconfigSecret)).To(Succeed()) + g.Expect(kubeconfigSecret.Data[secret.KubeconfigDataName]).NotTo(BeEmpty()) + clientConfig, err := clientcmd.Load(kubeconfigSecret.Data[secret.KubeconfigDataName]) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(clientConfig.AuthInfos).To(HaveEach(Satisfy(func(user *clientcmdapi.AuthInfo) bool { + return user.Exec == nil && + user.Token == "token" + }))) }) t.Run("successfully reconciles pause", func(t *testing.T) { diff --git a/controllers/resource_reconciler_test.go b/controllers/resource_reconciler_test.go index ffb79e7e21c..298b3da2872 100644 --- a/controllers/resource_reconciler_test.go +++ b/controllers/resource_reconciler_test.go @@ -17,14 +17,15 @@ limitations under the License. package controllers import ( - "context" "testing" asoresourcesv1 "github.com/Azure/azure-service-operator/v2/api/resources/v1api20200601" - "github.com/Azure/azure-service-operator/v2/pkg/common/annotations" + asoannotations "github.com/Azure/azure-service-operator/v2/pkg/common/annotations" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/conditions" "github.com/go-logr/logr" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gstruct" + "github.com/onsi/gomega/types" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -39,29 +40,6 @@ import ( infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" ) -type FakeClient struct { - client.Client - // Override the Patch method because controller-runtime's doesn't really support - // server-side apply, so we make our own dollar store version: - // https://github.com/kubernetes-sigs/controller-runtime/issues/2341 - patchFunc func(context.Context, client.Object, client.Patch, ...client.PatchOption) error - applyFunc func(context.Context, runtime.ApplyConfiguration, ...client.ApplyOption) error -} - -func (c *FakeClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error { - if c.patchFunc == nil { - return c.Client.Patch(ctx, obj, patch, opts...) - } - return c.patchFunc(ctx, obj, patch, opts...) -} - -func (c *FakeClient) Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { - if c.applyFunc == nil { - return c.Client.Apply(ctx, obj, opts...) - } - return c.applyFunc(ctx, obj, opts...) -} - type FakeWatcher struct { watching map[string]struct{} } @@ -105,22 +83,11 @@ func TestResourceReconcilerReconcile(t *testing.T) { g := NewGomegaWithT(t) w := &FakeWatcher{} - c := fakeClientBuilder(). - Build() asoManagedCluster := &infrav1.AzureASOManagedCluster{} - unpatchedRGs := map[string]struct{}{} r := &ResourceReconciler{ - Client: &FakeClient{ - Client: c, - applyFunc: func(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { - o := obj.(client.Object) - g.Expect(unpatchedRGs).To(HaveKey(o.GetName())) - delete(unpatchedRGs, o.GetName()) - return nil - }, - }, + Client: fakeClientBuilder().Build(), resources: []*unstructured.Unstructured{ rgJSON(g, s, &asoresourcesv1.ResourceGroup{ ObjectMeta: metav1.ObjectMeta{ @@ -140,7 +107,6 @@ func TestResourceReconcilerReconcile(t *testing.T) { err := r.Reconcile(ctx) g.Expect(err).NotTo(HaveOccurred()) g.Expect(w.watching).To(BeEmpty()) - g.Expect(unpatchedRGs).To(BeEmpty()) // all expected resources were patched g.Expect(asoManagedCluster.Annotations).To(HaveKeyWithValue(ownedKindsAnnotation, getOwnedKindsValue([]schema.GroupVersionKind{asoresourcesv1.GroupVersion.WithKind("ResourceGroup")}))) resourcesStatuses := asoManagedCluster.Status.Resources @@ -149,6 +115,10 @@ func TestResourceReconcilerReconcile(t *testing.T) { g.Expect(resourcesStatuses[0].Ready).To(BeFalse()) g.Expect(resourcesStatuses[1].Resource.Name).To(Equal("rg2")) g.Expect(resourcesStatuses[1].Ready).To(BeFalse()) + + resourceGroups := new(asoresourcesv1.ResourceGroupList) + g.Expect(r.List(ctx, resourceGroups)).To(Succeed()) + g.Expect(resourceGroups.Items).To(BeEmpty(), "Resources should not have been created") }) t.Run("create resources with acknowledged types", func(t *testing.T) { @@ -163,23 +133,9 @@ func TestResourceReconcilerReconcile(t *testing.T) { } w := &FakeWatcher{} - c := fakeClientBuilder(). - Build() - unpatchedRGs := map[string]struct{}{ - "rg1": {}, - "rg2": {}, - } r := &ResourceReconciler{ - Client: &FakeClient{ - Client: c, - applyFunc: func(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { - o := obj.(client.Object) - g.Expect(unpatchedRGs).To(HaveKey(o.GetName())) - delete(unpatchedRGs, o.GetName()) - return nil - }, - }, + Client: fakeClientBuilder().Build(), resources: []*unstructured.Unstructured{ rgJSON(g, s, &asoresourcesv1.ResourceGroup{ ObjectMeta: metav1.ObjectMeta{ @@ -216,7 +172,6 @@ func TestResourceReconcilerReconcile(t *testing.T) { err := r.Reconcile(ctx) g.Expect(err).NotTo(HaveOccurred()) g.Expect(w.watching).To(HaveKey("ResourceGroup.resources.azure.com")) - g.Expect(unpatchedRGs).To(BeEmpty()) // all expected resources were patched g.Expect(asoManagedCluster.Annotations).To(HaveKeyWithValue(ownedKindsAnnotation, getOwnedKindsValue([]schema.GroupVersionKind{asoresourcesv1.GroupVersion.WithKind("ResourceGroup")}))) resourcesStatuses := asoManagedCluster.Status.Resources @@ -225,6 +180,13 @@ func TestResourceReconcilerReconcile(t *testing.T) { g.Expect(resourcesStatuses[0].Ready).To(BeTrue()) g.Expect(resourcesStatuses[1].Resource.Name).To(Equal("rg2")) g.Expect(resourcesStatuses[1].Ready).To(BeFalse()) + + resourceGroups := new(asoresourcesv1.ResourceGroupList) + g.Expect(r.List(ctx, resourceGroups)).To(Succeed()) + g.Expect(resourceGroups.Items).To(ConsistOf( + HaveField("Name", "rg1"), + HaveField("Name", "rg2"), + ), "Expected ResourceGroups should have been created") }) t.Run("delete stale resources", func(t *testing.T) { @@ -280,17 +242,10 @@ func TestResourceReconcilerReconcile(t *testing.T) { }, } - c := fakeClientBuilder(). - WithObjects(objs...). - Build() - r := &ResourceReconciler{ - Client: &FakeClient{ - Client: c, - applyFunc: func(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { - return nil - }, - }, + Client: fakeClientBuilder(). + WithObjects(objs...). + Build(), resources: []*unstructured.Unstructured{ rgJSON(g, s, &asoresourcesv1.ResourceGroup{ ObjectMeta: metav1.ObjectMeta{ @@ -401,25 +356,10 @@ func TestResourceReconcilerPause(t *testing.T) { }, } - c := fakeClientBuilder(). - WithObjects(objs...). - Build() - - var patchedRGs []string r := &ResourceReconciler{ - Client: &FakeClient{ - Client: c, - applyFunc: func(ctx context.Context, obj runtime.ApplyConfiguration, opts ...client.ApplyOption) error { - o := obj.(client.Object) - g.Expect(o.GetAnnotations()).To(HaveKeyWithValue(annotations.ReconcilePolicy, string(annotations.ReconcilePolicySkip))) - if err := c.Get(ctx, client.ObjectKeyFromObject(o), &asoresourcesv1.ResourceGroup{}); err != nil { - // propagate errors like "NotFound" - return err - } - patchedRGs = append(patchedRGs, o.GetName()) - return nil - }, - }, + Client: fakeClientBuilder(). + WithObjects(objs...). + Build(), resources: []*unstructured.Unstructured{ rgJSON(g, s, &asoresourcesv1.ResourceGroup{ ObjectMeta: metav1.ObjectMeta{ @@ -441,7 +381,24 @@ func TestResourceReconcilerPause(t *testing.T) { } g.Expect(r.Pause(ctx)).To(Succeed()) - g.Expect(patchedRGs).To(ConsistOf("rg1", "rg2")) + + haveNameAndAnnotations := func(name string, haveAnnotations types.GomegaMatcher) types.GomegaMatcher { + return gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{ + "ObjectMeta": gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{ + "Name": Equal(name), + "Annotations": haveAnnotations, + }), + }) + } + + resourceGroups := new(asoresourcesv1.ResourceGroupList) + g.Expect(r.List(ctx, resourceGroups)).To(Succeed()) + g.Expect(resourceGroups.Items).To(ConsistOf( + haveNameAndAnnotations("deleted from spec", BeEmpty()), + haveNameAndAnnotations("not-yet-created", HaveKeyWithValue(asoannotations.ReconcilePolicy, string(asoannotations.ReconcilePolicySkip))), + haveNameAndAnnotations("rg1", HaveKeyWithValue(asoannotations.ReconcilePolicy, string(asoannotations.ReconcilePolicySkip))), + haveNameAndAnnotations("rg2", HaveKeyWithValue(asoannotations.ReconcilePolicy, string(asoannotations.ReconcilePolicySkip))), + ), "Expected ResourceGroups should have been updated") }) } @@ -506,14 +463,10 @@ func TestResourceReconcilerDelete(t *testing.T) { }, } - c := fakeClientBuilder(). - WithObjects(objs...). - Build() - r := &ResourceReconciler{ - Client: &FakeClient{ - Client: c, - }, + Client: fakeClientBuilder(). + WithObjects(objs...). + Build(), owner: owner, } @@ -541,14 +494,9 @@ func TestResourceReconcilerDelete(t *testing.T) { }, } - c := fakeClientBuilder(). - Build() - r := &ResourceReconciler{ - Client: &FakeClient{ - Client: c, - }, - owner: owner, + Client: fakeClientBuilder().Build(), + owner: owner, } g.Expect(r.Delete(ctx)).To(Succeed())