From b81b2d02a0e0ea3ec64f1f678db64786becafb42 Mon Sep 17 00:00:00 2001 From: Tejas Soham Date: Thu, 26 Mar 2026 15:01:10 +0530 Subject: [PATCH] Added KAM resource cleanup logic along with tests Signed-off-by: Tejas Soham --- controllers/gitopsservice_controller.go | 41 ++++++ controllers/gitopsservice_controller_test.go | 126 +++++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/controllers/gitopsservice_controller.go b/controllers/gitopsservice_controller.go index 4782e40e8f0..9a7bece2f33 100644 --- a/controllers/gitopsservice_controller.go +++ b/controllers/gitopsservice_controller.go @@ -73,6 +73,7 @@ var ( const ( gitopsServicePrefix = "gitops-service-" + kamResourceName = "kam" ) // SetupWithManager sets up the controller with the Manager. @@ -259,6 +260,8 @@ func (r *ReconcileGitopsService) Reconcile(ctx context.Context, request reconcil Namespace: namespace, } + r.cleanKAMResources(ctx, reqLogger) + if !r.DisableDefaultInstall { // Create/reconcile the default Argo CD instance, unless default install is disabled if result, err := r.reconcileDefaultArgoCDInstance(instance, reqLogger); err != nil { @@ -312,6 +315,44 @@ func (r *ReconcileGitopsService) Reconcile(ctx context.Context, request reconcil } } +// Detect the unsupported KAM components across Deployments , Routes , Services and deletes them to perform cleanup as KAM is no longer supported since 1.15 +func (r *ReconcileGitopsService) cleanKAMResources(ctx context.Context, reqLogger logr.Logger) { + + // KAM Deployment + cleanupKAMDeployment := &appsv1.Deployment{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: kamResourceName, Namespace: serviceNamespace}, cleanupKAMDeployment); err == nil { + reqLogger.Info("Detected unsupported KAM Deployment, deleting", "Name", kamResourceName, "Namespace", serviceNamespace) + if err := r.Client.Delete(ctx, cleanupKAMDeployment); err != nil && !errors.IsNotFound(err) { + reqLogger.Error(err, "Failed to delete KAM Deployment", "Name", kamResourceName, "Namespace", serviceNamespace) + } + } else if !errors.IsNotFound(err) { + reqLogger.Error(err, "Failed to retrieve KAM Deployment", "Name", kamResourceName, "Namespace", serviceNamespace) + } + + // KAM Service + cleanupKAMService := &corev1.Service{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: kamResourceName, Namespace: serviceNamespace}, cleanupKAMService); err == nil { + reqLogger.Info("Detected unsupported KAM Service, deleting", "Name", kamResourceName, "Namespace", serviceNamespace) + if err := r.Client.Delete(ctx, cleanupKAMService); err != nil && !errors.IsNotFound(err) { + reqLogger.Error(err, "Failed to delete KAM Service", "Name", kamResourceName, "Namespace", serviceNamespace) + } + } else if !errors.IsNotFound(err) { + reqLogger.Error(err, "Failed to retrieve KAM Service", "Name", kamResourceName, "Namespace", serviceNamespace) + } + + // KAM Route + cleanupKAMRoute := &routev1.Route{} + if err := r.Client.Get(ctx, types.NamespacedName{Name: kamResourceName, Namespace: serviceNamespace}, cleanupKAMRoute); err == nil { + reqLogger.Info("Detected unsupported KAM Route, deleting", "Name", kamResourceName, "Namespace", serviceNamespace) + if err := r.Client.Delete(ctx, cleanupKAMRoute); err != nil && !errors.IsNotFound(err) { + reqLogger.Error(err, "Failed to delete KAM Route", "Name", kamResourceName, "Namespace", serviceNamespace) + } + } else if !errors.IsNotFound(err) { + reqLogger.Error(err, "Failed to retrieve KAM Route", "Name", kamResourceName, "Namespace", serviceNamespace) + } + +} + func (r *ReconcileGitopsService) ensureDefaultArgoCDInstanceDoesntExist() error { defaultArgoCDInstance, err := argocd.NewCR(common.ArgoCDInstanceName, serviceNamespace, r.Client) diff --git a/controllers/gitopsservice_controller_test.go b/controllers/gitopsservice_controller_test.go index ddfad2bf3d0..ae0a6420ab0 100644 --- a/controllers/gitopsservice_controller_test.go +++ b/controllers/gitopsservice_controller_test.go @@ -889,6 +889,132 @@ func TestReconcileBackend_DefaultRequestsAndLimits(t *testing.T) { assert.Equal(t, deployment.Spec.Template.Spec.Containers[0].Resources.Limits["cpu"], resourcev1.MustParse("500m")) } +// Tests for KAM component cleanup +// No resources exist +func TestCleanKAMResources_NoResourcesExist(t *testing.T) { + logf.SetLogger(zap.New(zap.UseDevMode(true))) + s := scheme.Scheme + addKnownTypesToScheme(s) + fakeClient := fake.NewClientBuilder().WithScheme(s).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + reqLogger := logs.WithValues("Request.Namespace", "test", "Request.Name", "test") + + // No KAM resources exist - function should be a silent no-op + reconciler.cleanKAMResources(context.TODO(), reqLogger) +} + +// Deployment exist +func TestCleanKAMResources_DeploymentExist(t *testing.T) { + logf.SetLogger(zap.New(zap.UseDevMode(true))) + s := scheme.Scheme + addKnownTypesToScheme(s) + + kamDeploy := &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{Name: kamResourceName, Namespace: serviceNamespace}, + } + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(kamDeploy).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + reqLogger := logs.WithValues("Request.Namespace", "test", "Request.Name", "test") + + reconciler.cleanKAMResources(context.TODO(), reqLogger) + + err := fakeClient.Get(context.TODO(), types.NamespacedName{Name: kamResourceName, Namespace: serviceNamespace}, &appsv1.Deployment{}) + if !errors.IsNotFound(err) { + t.Fatalf("expected KAM Deployment to be deleted , got err: %v", err) + } +} + +// Service exist +func TestCleanKAMResources_ServiceExist(t *testing.T) { + logf.SetLogger(zap.New(zap.UseDevMode(true))) + s := scheme.Scheme + addKnownTypesToScheme(s) + kamService := &corev1.Service{ + ObjectMeta: v1.ObjectMeta{Name: kamResourceName, Namespace: serviceNamespace}, + } + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(kamService).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + reqLogger := logs.WithValues("Request.Namespace", "test", "Request.Name", "test") + + reconciler.cleanKAMResources(context.TODO(), reqLogger) + + err := fakeClient.Get(context.TODO(), types.NamespacedName{Name: kamResourceName, Namespace: serviceNamespace}, &corev1.Service{}) + if !errors.IsNotFound(err) { + t.Fatalf("expected KAM Service to be deleted , got err: %v", err) + } +} + +// Route exist +func TestCleanKAMResources_RouteExist(t *testing.T) { + logf.SetLogger(zap.New(zap.UseDevMode(true))) + s := scheme.Scheme + addKnownTypesToScheme(s) + kamRoute := &routev1.Route{ + ObjectMeta: v1.ObjectMeta{Name: kamResourceName, Namespace: serviceNamespace}, + } + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(kamRoute).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + reqLogger := logs.WithValues("Request.Namespace", "test", "Request.Name", "test") + + reconciler.cleanKAMResources(context.TODO(), reqLogger) + + err := fakeClient.Get(context.TODO(), types.NamespacedName{Name: kamResourceName, Namespace: serviceNamespace}, &routev1.Route{}) + if !errors.IsNotFound(err) { + t.Fatalf("expected KAM Route to be deleted , got err: %v", err) + } +} + +// All Resources exist +func TestCleanKAMResources_AllResourcesExist(t *testing.T) { + logf.SetLogger(zap.New(zap.UseDevMode(true))) + s := scheme.Scheme + addKnownTypesToScheme(s) + kamDeploy := &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{Name: kamResourceName, Namespace: serviceNamespace}, + } + kamService := &corev1.Service{ + ObjectMeta: v1.ObjectMeta{Name: kamResourceName, Namespace: serviceNamespace}, + } + kamRoute := &routev1.Route{ + ObjectMeta: v1.ObjectMeta{Name: kamResourceName, Namespace: serviceNamespace}, + } + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(kamDeploy, kamService, kamRoute).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + reqLogger := logs.WithValues("Request.Namespace", "test", "Request.Name", "test") + + reconciler.cleanKAMResources(context.TODO(), reqLogger) + + if err := fakeClient.Get(context.TODO(), types.NamespacedName{Name: kamResourceName, Namespace: serviceNamespace}, &appsv1.Deployment{}); !errors.IsNotFound(err) { + t.Fatalf("expected KAM Deployment to be deleted , got err: %v", err) + } + if err := fakeClient.Get(context.TODO(), types.NamespacedName{Name: kamResourceName, Namespace: serviceNamespace}, &corev1.Service{}); !errors.IsNotFound(err) { + t.Fatalf("expected KAM Service to be deleted , got err: %v", err) + } + if err := fakeClient.Get(context.TODO(), types.NamespacedName{Name: kamResourceName, Namespace: serviceNamespace}, &routev1.Route{}); !errors.IsNotFound(err) { + t.Fatalf("expected KAM Route to be deleted , got err: %v", err) + } +} + +// Idempotency +func TestCleanKAMResources_Idempotent(t *testing.T) { + logf.SetLogger(zap.New(zap.UseDevMode(true))) + s := scheme.Scheme + addKnownTypesToScheme(s) + kamDeploy := &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{Name: kamResourceName, Namespace: serviceNamespace}, + } + fakeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(kamDeploy).Build() + reconciler := newReconcileGitOpsService(fakeClient, s) + reqLogger := logs.WithValues("Request.Namespace", "test", "Request.Name", "test") + + // First call - deletes the KAM resource + reconciler.cleanKAMResources(context.TODO(), reqLogger) + + // Second call - must not panic even if the resources are already deleted + reconciler.cleanKAMResources(context.TODO(), reqLogger) + +} + func addKnownTypesToScheme(scheme *runtime.Scheme) { scheme.AddKnownTypes(configv1.GroupVersion, &configv1.ClusterVersion{}) scheme.AddKnownTypes(pipelinesv1alpha1.GroupVersion, &pipelinesv1alpha1.GitopsService{})