diff --git a/applicationset/controllers/applicationset_controller.go b/applicationset/controllers/applicationset_controller.go index 82e1ed52037c8..cb0ad2af410d9 100644 --- a/applicationset/controllers/applicationset_controller.go +++ b/applicationset/controllers/applicationset_controller.go @@ -176,6 +176,16 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque return ctrl.Result{}, err } + // ensure finalizer exists if deletionOrder is set as Reverse + if r.EnableProgressiveSyncs && isProgressiveSyncDeletionOrderReversed(&applicationSetInfo) { + if !controllerutil.ContainsFinalizer(&applicationSetInfo, argov1alpha1.ResourcesFinalizerName) { + controllerutil.AddFinalizer(&applicationSetInfo, argov1alpha1.ResourcesFinalizerName) + if err := r.Update(ctx, &applicationSetInfo); err != nil { + return ctrl.Result{}, err + } + } + } + // Log a warning if there are unrecognized generators _ = utils.CheckInvalidGenerators(&applicationSetInfo) // desiredApplications is the main list of all expected Applications from all generators in this appset. diff --git a/applicationset/controllers/applicationset_controller_test.go b/applicationset/controllers/applicationset_controller_test.go index b46985a753bd9..1087a8542d7c5 100644 --- a/applicationset/controllers/applicationset_controller_test.go +++ b/applicationset/controllers/applicationset_controller_test.go @@ -7277,6 +7277,223 @@ func TestIsRollingSyncDeletionReversed(t *testing.T) { } } +func TestReconcileAddsFinalizer_WhenDeletionOrderReverse(t *testing.T) { + scheme := runtime.NewScheme() + err := v1alpha1.AddToScheme(scheme) + require.NoError(t, err) + + kubeclientset := kubefake.NewClientset([]runtime.Object{}...) + + for _, cc := range []struct { + name string + appSet v1alpha1.ApplicationSet + progressiveSyncEnabled bool + expectedFinalizers []string + }{ + { + name: "adds finalizer when DeletionOrder is Reverse", + appSet: v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-appset", + Namespace: "argocd", + // No finalizers initially + }, + Spec: v1alpha1.ApplicationSetSpec{ + Strategy: &v1alpha1.ApplicationSetStrategy{ + Type: "RollingSync", + RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ + Steps: []v1alpha1.ApplicationSetRolloutStep{ + { + MatchExpressions: []v1alpha1.ApplicationMatchExpression{ + { + Key: "env", + Operator: "In", + Values: []string{"dev"}, + }, + }, + }, + }, + }, + DeletionOrder: ReverseDeletionOrder, + }, + Template: v1alpha1.ApplicationSetTemplate{}, + }, + }, + progressiveSyncEnabled: true, + expectedFinalizers: []string{v1alpha1.ResourcesFinalizerName}, + }, + { + name: "does not add finalizer when already exists and DeletionOrder is Reverse", + appSet: v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-appset", + Namespace: "argocd", + Finalizers: []string{ + v1alpha1.ResourcesFinalizerName, + }, + }, + Spec: v1alpha1.ApplicationSetSpec{ + Strategy: &v1alpha1.ApplicationSetStrategy{ + Type: "RollingSync", + RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ + Steps: []v1alpha1.ApplicationSetRolloutStep{ + { + MatchExpressions: []v1alpha1.ApplicationMatchExpression{ + { + Key: "env", + Operator: "In", + Values: []string{"dev"}, + }, + }, + }, + }, + }, + DeletionOrder: ReverseDeletionOrder, + }, + Template: v1alpha1.ApplicationSetTemplate{}, + }, + }, + progressiveSyncEnabled: true, + expectedFinalizers: []string{v1alpha1.ResourcesFinalizerName}, + }, + { + name: "does not add finalizer when DeletionOrder is AllAtOnce", + appSet: v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-appset", + Namespace: "argocd", + }, + Spec: v1alpha1.ApplicationSetSpec{ + Strategy: &v1alpha1.ApplicationSetStrategy{ + Type: "RollingSync", + RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ + Steps: []v1alpha1.ApplicationSetRolloutStep{ + { + MatchExpressions: []v1alpha1.ApplicationMatchExpression{ + { + Key: "env", + Operator: "In", + Values: []string{"dev"}, + }, + }, + }, + }, + }, + DeletionOrder: AllAtOnceDeletionOrder, + }, + Template: v1alpha1.ApplicationSetTemplate{}, + }, + }, + progressiveSyncEnabled: true, + expectedFinalizers: nil, + }, + { + name: "does not add finalizer when DeletionOrder is not set", + appSet: v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-appset", + Namespace: "argocd", + }, + Spec: v1alpha1.ApplicationSetSpec{ + Strategy: &v1alpha1.ApplicationSetStrategy{ + Type: "RollingSync", + RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ + Steps: []v1alpha1.ApplicationSetRolloutStep{ + { + MatchExpressions: []v1alpha1.ApplicationMatchExpression{ + { + Key: "env", + Operator: "In", + Values: []string{"dev"}, + }, + }, + }, + }, + }, + }, + Template: v1alpha1.ApplicationSetTemplate{}, + }, + }, + progressiveSyncEnabled: true, + expectedFinalizers: nil, + }, + { + name: "does not add finalizer when progressive sync not enabled", + appSet: v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-appset", + Namespace: "argocd", + }, + Spec: v1alpha1.ApplicationSetSpec{ + Strategy: &v1alpha1.ApplicationSetStrategy{ + Type: "RollingSync", + RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{ + Steps: []v1alpha1.ApplicationSetRolloutStep{ + { + MatchExpressions: []v1alpha1.ApplicationMatchExpression{ + { + Key: "env", + Operator: "In", + Values: []string{"dev"}, + }, + }, + }, + }, + }, + DeletionOrder: ReverseDeletionOrder, + }, + Template: v1alpha1.ApplicationSetTemplate{}, + }, + }, + progressiveSyncEnabled: false, + expectedFinalizers: nil, + }, + } { + t.Run(cc.name, func(t *testing.T) { + client := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&cc.appSet). + WithStatusSubresource(&cc.appSet). + WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer). + Build() + metrics := appsetmetrics.NewFakeAppsetMetrics() + argodb := db.NewDB("argocd", settings.NewSettingsManager(t.Context(), kubeclientset, "argocd"), kubeclientset) + + r := ApplicationSetReconciler{ + Client: client, + Scheme: scheme, + Renderer: &utils.Render{}, + Recorder: record.NewFakeRecorder(1), + Generators: map[string]generators.Generator{}, + ArgoDB: argodb, + KubeClientset: kubeclientset, + Metrics: metrics, + EnableProgressiveSyncs: cc.progressiveSyncEnabled, + } + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Namespace: cc.appSet.Namespace, + Name: cc.appSet.Name, + }, + } + + // Run reconciliation + _, err = r.Reconcile(t.Context(), req) + require.NoError(t, err) + + // Fetch the updated ApplicationSet + var updatedAppSet v1alpha1.ApplicationSet + err = r.Get(t.Context(), req.NamespacedName, &updatedAppSet) + require.NoError(t, err) + + // Verify the finalizers + assert.Equal(t, cc.expectedFinalizers, updatedAppSet.Finalizers, + "finalizers should match expected value") + }) + } +} + func TestReconcileProgressiveSyncDisabled(t *testing.T) { scheme := runtime.NewScheme() err := v1alpha1.AddToScheme(scheme) diff --git a/docs/operator-manual/applicationset/Progressive-Syncs.md b/docs/operator-manual/applicationset/Progressive-Syncs.md index f432a370218c6..115004f294395 100644 --- a/docs/operator-manual/applicationset/Progressive-Syncs.md +++ b/docs/operator-manual/applicationset/Progressive-Syncs.md @@ -115,7 +115,9 @@ This strategy is particularly useful when you need to tear down dependent servic - Requires `rollingSync.steps` to be defined - Applications are deleted in reverse order of step sequence -**Important:** The ApplicationSet finalizer is not removed until all applications are successfully deleted. This ensures proper cleanup and prevents the ApplicationSet from being removed before its managed applications. +**Important:** The ApplicationSet finalizer is not removed until all applications are successfully deleted. This ensures proper cleanup and prevents the ApplicationSet from being removed before its managed applications. + +**Note:** ApplicationSet controller ensures there is a finalizer when `deletionOrder` is set as `Reverse` with progressive sync enabled. This means that if the applicationset is missing the required finalizer, the applicationset controller adds the finalizer to ApplicationSet before generating applications. ```yaml spec: