From 86e42fb223f39bcff0d946bff3812160de83d8e6 Mon Sep 17 00:00:00 2001 From: Dhruvang Makadia Date: Fri, 13 Feb 2026 11:23:55 -0800 Subject: [PATCH] fix: AppProject finalizer should consider apps in all allowed namespaces (#24347) (#26416) Signed-off-by: Dhruvang Makadia --- controller/appcontroller.go | 4 +- controller/appcontroller_test.go | 87 ++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/controller/appcontroller.go b/controller/appcontroller.go index 31d895c2fb..4865113db5 100644 --- a/controller/appcontroller.go +++ b/controller/appcontroller.go @@ -1138,13 +1138,13 @@ func (ctrl *ApplicationController) processProjectQueueItem() (processNext bool) } func (ctrl *ApplicationController) finalizeProjectDeletion(proj *appv1.AppProject) error { - apps, err := ctrl.appLister.Applications(ctrl.namespace).List(labels.Everything()) + apps, err := ctrl.appLister.List(labels.Everything()) if err != nil { return fmt.Errorf("error listing applications: %w", err) } appsCount := 0 for i := range apps { - if apps[i].Spec.GetProject() == proj.Name { + if apps[i].Spec.GetProject() == proj.Name && ctrl.isAppNamespaceAllowed(apps[i]) && proj.IsAppNamespacePermitted(apps[i], ctrl.namespace) { appsCount++ } } diff --git a/controller/appcontroller_test.go b/controller/appcontroller_test.go index c741339f70..244a2e5345 100644 --- a/controller/appcontroller_test.go +++ b/controller/appcontroller_test.go @@ -2313,6 +2313,93 @@ func TestFinalizeProjectDeletion_DoesNotHaveApplications(t *testing.T) { }, receivedPatch) } +func TestFinalizeProjectDeletion_HasApplicationInOtherNamespace(t *testing.T) { + app := newFakeApp() + app.Namespace = "team-a" + proj := &v1alpha1.AppProject{ + ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: test.FakeArgoCDNamespace}, + Spec: v1alpha1.AppProjectSpec{ + SourceNamespaces: []string{"team-a"}, + }, + } + ctrl := newFakeController(t.Context(), &fakeData{ + apps: []runtime.Object{app, proj}, + applicationNamespaces: []string{"team-a"}, + }, nil) + + fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset) + patched := false + fakeAppCs.PrependReactor("patch", "*", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) { + patched = true + return true, &v1alpha1.AppProject{}, nil + }) + + err := ctrl.finalizeProjectDeletion(proj) + require.NoError(t, err) + assert.False(t, patched) +} + +func TestFinalizeProjectDeletion_IgnoresAppsInUnmonitoredNamespace(t *testing.T) { + app := newFakeApp() + app.Namespace = "team-b" + proj := &v1alpha1.AppProject{ + ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: test.FakeArgoCDNamespace}, + } + ctrl := newFakeController(t.Context(), &fakeData{ + apps: []runtime.Object{app, proj}, + applicationNamespaces: []string{"team-a"}, + }, nil) + + fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset) + receivedPatch := map[string]any{} + fakeAppCs.PrependReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { + if patchAction, ok := action.(kubetesting.PatchAction); ok { + require.NoError(t, json.Unmarshal(patchAction.GetPatch(), &receivedPatch)) + } + return true, &v1alpha1.AppProject{}, nil + }) + + err := ctrl.finalizeProjectDeletion(proj) + require.NoError(t, err) + assert.Equal(t, map[string]any{ + "metadata": map[string]any{ + "finalizers": nil, + }, + }, receivedPatch) +} + +func TestFinalizeProjectDeletion_IgnoresAppsNotPermittedByProject(t *testing.T) { + app := newFakeApp() + app.Namespace = "team-b" + proj := &v1alpha1.AppProject{ + ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: test.FakeArgoCDNamespace}, + Spec: v1alpha1.AppProjectSpec{ + SourceNamespaces: []string{"team-a"}, + }, + } + ctrl := newFakeController(t.Context(), &fakeData{ + apps: []runtime.Object{app, proj}, + applicationNamespaces: []string{"team-a", "team-b"}, + }, nil) + + fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset) + receivedPatch := map[string]any{} + fakeAppCs.PrependReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { + if patchAction, ok := action.(kubetesting.PatchAction); ok { + require.NoError(t, json.Unmarshal(patchAction.GetPatch(), &receivedPatch)) + } + return true, &v1alpha1.AppProject{}, nil + }) + + err := ctrl.finalizeProjectDeletion(proj) + require.NoError(t, err) + assert.Equal(t, map[string]any{ + "metadata": map[string]any{ + "finalizers": nil, + }, + }, receivedPatch) +} + func TestProcessRequestedAppOperation_FailedNoRetries(t *testing.T) { app := newFakeApp() app.Spec.Project = "default"