diff --git a/cmd/main.go b/cmd/main.go index 331d0611a..f2616ce12 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -173,7 +173,7 @@ func main() { Client: mgr.GetClient(), UnstructuredCachingClient: unstructuredCachingClient, SourceClusterClassNamespace: namespacesyncOptions.SourceNamespace, - TargetNamespaceFilter: namespacesync.NamespaceHasLabelKey(namespacesyncOptions.TargetNamespaceLabelKey), + IsTargetNamespace: namespacesync.NamespaceHasLabelKey(namespacesyncOptions.TargetNamespaceLabelKey), }).SetupWithManager( signalCtx, mgr, diff --git a/pkg/controllers/namespacesync/controller.go b/pkg/controllers/namespacesync/controller.go index 448a5fef2..a7b8c5987 100644 --- a/pkg/controllers/namespacesync/controller.go +++ b/pkg/controllers/namespacesync/controller.go @@ -28,8 +28,8 @@ type Reconciler struct { // SourceClusterClassNamespace is the namespace from which ClusterClasses are copied. SourceClusterClassNamespace string - // TargetNamespaceFilter determines whether ClusterClasses should be copied to a given namespace. - TargetNamespaceFilter func(ns *corev1.Namespace) bool + // IsTargetNamespace determines whether ClusterClasses should be copied to a given namespace. + IsTargetNamespace func(ns *corev1.Namespace) bool } func (r *Reconciler) SetupWithManager( @@ -37,8 +37,8 @@ func (r *Reconciler) SetupWithManager( mgr ctrl.Manager, options controller.Options, ) error { - if r.TargetNamespaceFilter == nil { - return fmt.Errorf("target Namespace filter is nil") + if r.IsTargetNamespace == nil { + return fmt.Errorf("define IsTargetNamespace function to use controller") } err := ctrl.NewControllerManagedBy(mgr). @@ -52,12 +52,22 @@ func (r *Reconciler) SetupWithManager( if !ok { return false } - return r.TargetNamespaceFilter(ns) + return r.IsTargetNamespace(ns) }, UpdateFunc: func(e event.UpdateEvent) bool { // Called when an object is already in the cache, and it is either updated, // or fetched as part of a re-list (aka re-sync). - return false + nsOld, ok := e.ObjectOld.(*corev1.Namespace) + if !ok { + return false + } + nsNew, ok := e.ObjectNew.(*corev1.Namespace) + if !ok { + return false + } + // Only reconcile the namespace if the answer to the question "Is this a + // target namespace?" changed from no to yes. + return !r.IsTargetNamespace(nsOld) && r.IsTargetNamespace(nsNew) }, DeleteFunc: func(e event.DeleteEvent) bool { // Ignore deletes. @@ -93,7 +103,7 @@ func (r *Reconciler) clusterClassToNamespaces(ctx context.Context, o client.Obje rs := []ctrl.Request{} for i := range namespaceList.Items { ns := &namespaceList.Items[i] - if r.TargetNamespaceFilter(ns) { + if r.IsTargetNamespace(ns) { rs = append(rs, ctrl.Request{ NamespacedName: client.ObjectKeyFromObject(ns), diff --git a/pkg/controllers/namespacesync/controller_test.go b/pkg/controllers/namespacesync/controller_test.go index 93ee0dcc8..c97c7bab5 100644 --- a/pkg/controllers/namespacesync/controller_test.go +++ b/pkg/controllers/namespacesync/controller_test.go @@ -17,6 +17,38 @@ import ( "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/internal/test/builder" ) +func TestReconcileExistingNamespaceWithUpdatedLabels(t *testing.T) { + g := NewWithT(t) + timeout := 5 * time.Second + + sourceClusterClassName, cleanup, err := createUniqueClusterClassAndTemplates( + sourceClusterClassNamespace, + ) + g.Expect(err).ToNot(HaveOccurred()) + defer func() { + g.Expect(cleanup()).To(Succeed()) + }() + + // Create namespace without label + targetNamespace, err := env.CreateNamespace(ctx, "target", map[string]string{}) + g.Expect(err).ToNot(HaveOccurred()) + + // Label the namespace + targetNamespace.Labels[targetNamespaceLabelKey] = "" + err = env.Update(ctx, targetNamespace) + g.Expect(err).ToNot(HaveOccurred()) + + g.Eventually(func() error { + return verifyClusterClassAndTemplates( + env.Client, + sourceClusterClassName, + targetNamespace.Name, + ) + }, + timeout, + ).Should(Succeed()) +} + func TestReconcileNewNamespaces(t *testing.T) { g := NewWithT(t) timeout := 5 * time.Second diff --git a/pkg/controllers/namespacesync/suite_test.go b/pkg/controllers/namespacesync/suite_test.go index 76e79b54a..18b5c1e4a 100644 --- a/pkg/controllers/namespacesync/suite_test.go +++ b/pkg/controllers/namespacesync/suite_test.go @@ -58,7 +58,7 @@ func TestMain(m *testing.M) { Client: mgr.GetClient(), UnstructuredCachingClient: unstructuredCachingClient, SourceClusterClassNamespace: sourceClusterClassNamespace, - TargetNamespaceFilter: NamespaceHasLabelKey(targetNamespaceLabelKey), + IsTargetNamespace: NamespaceHasLabelKey(targetNamespaceLabelKey), }).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: 1}); err != nil { panic(fmt.Sprintf("unable to create reconciler: %v", err)) }