github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/controllers/applicationset_controller.go (about)

     1  /*
     2  Licensed under the Apache License, Version 2.0 (the "License");
     3  you may not use this file except in compliance with the License.
     4  You may obtain a copy of the License at
     5  
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  
     8  Unless required by applicable law or agreed to in writing, software
     9  distributed under the License is distributed on an "AS IS" BASIS,
    10  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  See the License for the specific language governing permissions and
    12  limitations under the License.
    13  */
    14  
    15  package controllers
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"reflect"
    22  	"runtime/debug"
    23  	"sort"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/google/go-cmp/cmp"
    29  	"github.com/google/go-cmp/cmp/cmpopts"
    30  	log "github.com/sirupsen/logrus"
    31  	corev1 "k8s.io/api/core/v1"
    32  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/apimachinery/pkg/types"
    36  	"k8s.io/apimachinery/pkg/util/intstr"
    37  	"k8s.io/client-go/kubernetes"
    38  	"k8s.io/client-go/tools/record"
    39  	"k8s.io/client-go/util/retry"
    40  	ctrl "sigs.k8s.io/controller-runtime"
    41  	"sigs.k8s.io/controller-runtime/pkg/builder"
    42  	"sigs.k8s.io/controller-runtime/pkg/client"
    43  	"sigs.k8s.io/controller-runtime/pkg/controller"
    44  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    45  	"sigs.k8s.io/controller-runtime/pkg/event"
    46  	"sigs.k8s.io/controller-runtime/pkg/handler"
    47  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    48  
    49  	"github.com/argoproj/argo-cd/v3/applicationset/controllers/template"
    50  	"github.com/argoproj/argo-cd/v3/applicationset/generators"
    51  	"github.com/argoproj/argo-cd/v3/applicationset/metrics"
    52  	"github.com/argoproj/argo-cd/v3/applicationset/status"
    53  	"github.com/argoproj/argo-cd/v3/applicationset/utils"
    54  	"github.com/argoproj/argo-cd/v3/common"
    55  	applog "github.com/argoproj/argo-cd/v3/util/app/log"
    56  	"github.com/argoproj/argo-cd/v3/util/db"
    57  
    58  	argov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    59  	argoutil "github.com/argoproj/argo-cd/v3/util/argo"
    60  	"github.com/argoproj/argo-cd/v3/util/argo/normalizers"
    61  
    62  	"github.com/argoproj/argo-cd/v3/pkg/apis/application"
    63  )
    64  
    65  const (
    66  	// Rather than importing the whole argocd-notifications controller, just copying the const here
    67  	//   https://github.com/argoproj-labs/argocd-notifications/blob/33d345fa838829bb50fca5c08523aba380d2c12b/pkg/controller/subscriptions.go#L12
    68  	//   https://github.com/argoproj-labs/argocd-notifications/blob/33d345fa838829bb50fca5c08523aba380d2c12b/pkg/controller/state.go#L17
    69  	NotifiedAnnotationKey             = "notified.notifications.argoproj.io"
    70  	ReconcileRequeueOnValidationError = time.Minute * 3
    71  	ReverseDeletionOrder              = "Reverse"
    72  	AllAtOnceDeletionOrder            = "AllAtOnce"
    73  )
    74  
    75  var defaultPreservedAnnotations = []string{
    76  	NotifiedAnnotationKey,
    77  	argov1alpha1.AnnotationKeyRefresh,
    78  }
    79  
    80  type deleteInOrder struct {
    81  	AppName string
    82  	Step    int
    83  }
    84  
    85  // ApplicationSetReconciler reconciles a ApplicationSet object
    86  type ApplicationSetReconciler struct {
    87  	client.Client
    88  	Scheme               *runtime.Scheme
    89  	Recorder             record.EventRecorder
    90  	Generators           map[string]generators.Generator
    91  	ArgoDB               db.ArgoDB
    92  	KubeClientset        kubernetes.Interface
    93  	Policy               argov1alpha1.ApplicationsSyncPolicy
    94  	EnablePolicyOverride bool
    95  	utils.Renderer
    96  	ArgoCDNamespace            string
    97  	ApplicationSetNamespaces   []string
    98  	EnableProgressiveSyncs     bool
    99  	SCMRootCAPath              string
   100  	GlobalPreservedAnnotations []string
   101  	GlobalPreservedLabels      []string
   102  	Metrics                    *metrics.ApplicationsetMetrics
   103  	MaxResourcesStatusCount    int
   104  }
   105  
   106  // +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
   107  // +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets/status,verbs=get;update;patch
   108  
   109  func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
   110  	startReconcile := time.Now()
   111  	logCtx := log.WithField("applicationset", req.NamespacedName)
   112  
   113  	defer func() {
   114  		if rec := recover(); rec != nil {
   115  			logCtx.Errorf("Recovered from panic: %+v\n%s", rec, debug.Stack())
   116  			result = ctrl.Result{}
   117  			var ok bool
   118  			err, ok = rec.(error)
   119  			if !ok {
   120  				err = fmt.Errorf("%v", r)
   121  			}
   122  		}
   123  	}()
   124  
   125  	var applicationSetInfo argov1alpha1.ApplicationSet
   126  	parametersGenerated := false
   127  	startTime := time.Now()
   128  	if err := r.Get(ctx, req.NamespacedName, &applicationSetInfo); err != nil {
   129  		if client.IgnoreNotFound(err) != nil {
   130  			logCtx.WithError(err).Infof("unable to get ApplicationSet: '%v' ", err)
   131  		}
   132  		return ctrl.Result{}, client.IgnoreNotFound(err)
   133  	}
   134  
   135  	defer func() {
   136  		r.Metrics.ObserveReconcile(&applicationSetInfo, time.Since(startTime))
   137  	}()
   138  
   139  	// Do not attempt to further reconcile the ApplicationSet if it is being deleted.
   140  	if applicationSetInfo.DeletionTimestamp != nil {
   141  		appsetName := applicationSetInfo.Name
   142  		logCtx.Debugf("DeletionTimestamp is set on %s", appsetName)
   143  		deleteAllowed := utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete()
   144  		if !deleteAllowed {
   145  			logCtx.Debugf("ApplicationSet policy does not allow to delete")
   146  			if err := r.removeOwnerReferencesOnDeleteAppSet(ctx, applicationSetInfo); err != nil {
   147  				return ctrl.Result{}, err
   148  			}
   149  			logCtx.Debugf("ownerReferences referring %s is deleted from generated applications", appsetName)
   150  		}
   151  		if isProgressiveSyncDeletionOrderReversed(&applicationSetInfo) {
   152  			logCtx.Debugf("DeletionOrder is set as Reverse on %s", appsetName)
   153  			currentApplications, err := r.getCurrentApplications(ctx, applicationSetInfo)
   154  			if err != nil {
   155  				return ctrl.Result{}, err
   156  			}
   157  			requeueTime, err := r.performReverseDeletion(ctx, logCtx, applicationSetInfo, currentApplications)
   158  			if err != nil {
   159  				return ctrl.Result{}, err
   160  			} else if requeueTime > 0 {
   161  				return ctrl.Result{RequeueAfter: requeueTime}, err
   162  			}
   163  		}
   164  		controllerutil.RemoveFinalizer(&applicationSetInfo, argov1alpha1.ResourcesFinalizerName)
   165  		if err := r.Update(ctx, &applicationSetInfo); err != nil {
   166  			return ctrl.Result{}, err
   167  		}
   168  		return ctrl.Result{}, nil
   169  	}
   170  
   171  	if err := r.migrateStatus(ctx, &applicationSetInfo); err != nil {
   172  		logCtx.Errorf("failed to migrate status subresource %v", err)
   173  		return ctrl.Result{}, err
   174  	}
   175  
   176  	// Log a warning if there are unrecognized generators
   177  	_ = utils.CheckInvalidGenerators(&applicationSetInfo)
   178  	// desiredApplications is the main list of all expected Applications from all generators in this appset.
   179  	generatedApplications, applicationSetReason, err := template.GenerateApplications(logCtx, applicationSetInfo, r.Generators, r.Renderer, r.Client)
   180  	if err != nil {
   181  		logCtx.Errorf("unable to generate applications: %v", err)
   182  		_ = r.setApplicationSetStatusCondition(ctx,
   183  			&applicationSetInfo,
   184  			argov1alpha1.ApplicationSetCondition{
   185  				Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   186  				Message: err.Error(),
   187  				Reason:  string(applicationSetReason),
   188  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   189  			}, parametersGenerated,
   190  		)
   191  		// In order for the controller SDK to respect RequeueAfter, the error must be nil
   192  		return ctrl.Result{RequeueAfter: ReconcileRequeueOnValidationError}, nil
   193  	}
   194  
   195  	parametersGenerated = true
   196  
   197  	validateErrors, err := r.validateGeneratedApplications(ctx, generatedApplications, applicationSetInfo)
   198  	if err != nil {
   199  		// While some generators may return an error that requires user intervention,
   200  		// other generators reference external resources that may change to cause
   201  		// the error to no longer occur. We thus log the error and requeue
   202  		// with a timeout to give this another shot at a later time.
   203  		//
   204  		// Changes to watched resources will cause this to be reconciled sooner than
   205  		// the RequeueAfter time.
   206  		logCtx.Errorf("error occurred during application validation: %s", err.Error())
   207  
   208  		_ = r.setApplicationSetStatusCondition(ctx,
   209  			&applicationSetInfo,
   210  			argov1alpha1.ApplicationSetCondition{
   211  				Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   212  				Message: err.Error(),
   213  				Reason:  argov1alpha1.ApplicationSetReasonApplicationValidationError,
   214  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   215  			}, parametersGenerated,
   216  		)
   217  		return ctrl.Result{RequeueAfter: ReconcileRequeueOnValidationError}, nil
   218  	}
   219  
   220  	currentApplications, err := r.getCurrentApplications(ctx, applicationSetInfo)
   221  	if err != nil {
   222  		return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
   223  	}
   224  
   225  	err = r.updateResourcesStatus(ctx, logCtx, &applicationSetInfo, currentApplications)
   226  	if err != nil {
   227  		return ctrl.Result{}, fmt.Errorf("failed to get update resources status for application set: %w", err)
   228  	}
   229  
   230  	// appMap is a name->app collection of Applications in this ApplicationSet.
   231  	appMap := map[string]argov1alpha1.Application{}
   232  	// appSyncMap tracks which apps will be synced during this reconciliation.
   233  	appSyncMap := map[string]bool{}
   234  
   235  	if r.EnableProgressiveSyncs {
   236  		if !isRollingSyncStrategy(&applicationSetInfo) && len(applicationSetInfo.Status.ApplicationStatus) > 0 {
   237  			// If an appset was previously syncing with a `RollingSync` strategy but it has switched to the default strategy, clean up the progressive sync application statuses
   238  			logCtx.Infof("Removing %v unnecessary AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
   239  
   240  			err := r.setAppSetApplicationStatus(ctx, logCtx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
   241  			if err != nil {
   242  				return ctrl.Result{}, fmt.Errorf("failed to clear previous AppSet application statuses for %v: %w", applicationSetInfo.Name, err)
   243  			}
   244  		} else if isRollingSyncStrategy(&applicationSetInfo) {
   245  			// The appset uses progressive sync with `RollingSync` strategy
   246  			for _, app := range currentApplications {
   247  				appMap[app.Name] = app
   248  			}
   249  
   250  			appSyncMap, err = r.performProgressiveSyncs(ctx, logCtx, applicationSetInfo, currentApplications, generatedApplications, appMap)
   251  			if err != nil {
   252  				return ctrl.Result{}, fmt.Errorf("failed to perform progressive sync reconciliation for application set: %w", err)
   253  			}
   254  		}
   255  	} else {
   256  		// Progressive Sync is disabled, clear any existing applicationStatus to prevent stale data
   257  		if len(applicationSetInfo.Status.ApplicationStatus) > 0 {
   258  			logCtx.Infof("Progressive Sync disabled, removing %v AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
   259  
   260  			err := r.setAppSetApplicationStatus(ctx, logCtx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
   261  			if err != nil {
   262  				return ctrl.Result{}, fmt.Errorf("failed to clear AppSet application statuses when Progressive Sync is disabled for %v: %w", applicationSetInfo.Name, err)
   263  			}
   264  		}
   265  	}
   266  
   267  	var validApps []argov1alpha1.Application
   268  	for i := range generatedApplications {
   269  		if validateErrors[generatedApplications[i].QualifiedName()] == nil {
   270  			validApps = append(validApps, generatedApplications[i])
   271  		}
   272  	}
   273  
   274  	if len(validateErrors) > 0 {
   275  		errorApps := make([]string, 0, len(validateErrors))
   276  		for key := range validateErrors {
   277  			errorApps = append(errorApps, key)
   278  		}
   279  		sort.Strings(errorApps)
   280  
   281  		var message string
   282  		for _, appName := range errorApps {
   283  			message = validateErrors[appName].Error()
   284  			logCtx.WithField("application", appName).Errorf("validation error found during application validation: %s", message)
   285  		}
   286  		if len(validateErrors) > 1 {
   287  			// Only the last message gets added to the appset status, to keep the size reasonable.
   288  			message = fmt.Sprintf("%s (and %d more)", message, len(validateErrors)-1)
   289  		}
   290  		_ = r.setApplicationSetStatusCondition(ctx,
   291  			&applicationSetInfo,
   292  			argov1alpha1.ApplicationSetCondition{
   293  				Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   294  				Message: message,
   295  				Reason:  argov1alpha1.ApplicationSetReasonApplicationValidationError,
   296  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   297  			}, parametersGenerated,
   298  		)
   299  	}
   300  
   301  	if r.EnableProgressiveSyncs {
   302  		// trigger appropriate application syncs if RollingSync strategy is enabled
   303  		if progressiveSyncsRollingSyncStrategyEnabled(&applicationSetInfo) {
   304  			validApps = r.syncValidApplications(logCtx, &applicationSetInfo, appSyncMap, appMap, validApps)
   305  		}
   306  	}
   307  
   308  	if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowUpdate() {
   309  		err = r.createOrUpdateInCluster(ctx, logCtx, applicationSetInfo, validApps)
   310  		if err != nil {
   311  			_ = r.setApplicationSetStatusCondition(ctx,
   312  				&applicationSetInfo,
   313  				argov1alpha1.ApplicationSetCondition{
   314  					Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   315  					Message: err.Error(),
   316  					Reason:  argov1alpha1.ApplicationSetReasonUpdateApplicationError,
   317  					Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   318  				}, parametersGenerated,
   319  			)
   320  			return ctrl.Result{}, err
   321  		}
   322  	} else {
   323  		err = r.createInCluster(ctx, logCtx, applicationSetInfo, validApps)
   324  		if err != nil {
   325  			_ = r.setApplicationSetStatusCondition(ctx,
   326  				&applicationSetInfo,
   327  				argov1alpha1.ApplicationSetCondition{
   328  					Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   329  					Message: err.Error(),
   330  					Reason:  argov1alpha1.ApplicationSetReasonCreateApplicationError,
   331  					Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   332  				}, parametersGenerated,
   333  			)
   334  			return ctrl.Result{}, err
   335  		}
   336  	}
   337  
   338  	if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete() {
   339  		err = r.deleteInCluster(ctx, logCtx, applicationSetInfo, generatedApplications)
   340  		if err != nil {
   341  			_ = r.setApplicationSetStatusCondition(ctx,
   342  				&applicationSetInfo,
   343  				argov1alpha1.ApplicationSetCondition{
   344  					Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   345  					Message: err.Error(),
   346  					Reason:  argov1alpha1.ApplicationSetReasonDeleteApplicationError,
   347  					Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   348  				}, parametersGenerated,
   349  			)
   350  			return ctrl.Result{}, err
   351  		}
   352  	}
   353  
   354  	if applicationSetInfo.RefreshRequired() {
   355  		delete(applicationSetInfo.Annotations, common.AnnotationApplicationSetRefresh)
   356  		err := r.Update(ctx, &applicationSetInfo)
   357  		if err != nil {
   358  			logCtx.Warnf("error occurred while updating ApplicationSet: %v", err)
   359  			_ = r.setApplicationSetStatusCondition(ctx,
   360  				&applicationSetInfo,
   361  				argov1alpha1.ApplicationSetCondition{
   362  					Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   363  					Message: err.Error(),
   364  					Reason:  argov1alpha1.ApplicationSetReasonRefreshApplicationError,
   365  					Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   366  				}, parametersGenerated,
   367  			)
   368  			return ctrl.Result{}, err
   369  		}
   370  	}
   371  
   372  	requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
   373  
   374  	if len(validateErrors) == 0 {
   375  		if err := r.setApplicationSetStatusCondition(ctx,
   376  			&applicationSetInfo,
   377  			argov1alpha1.ApplicationSetCondition{
   378  				Type:    argov1alpha1.ApplicationSetConditionResourcesUpToDate,
   379  				Message: "All applications have been generated successfully",
   380  				Reason:  argov1alpha1.ApplicationSetReasonApplicationSetUpToDate,
   381  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   382  			}, parametersGenerated,
   383  		); err != nil {
   384  			return ctrl.Result{}, err
   385  		}
   386  	} else if requeueAfter == time.Duration(0) {
   387  		// Ensure that the request is requeued if there are validation errors.
   388  		requeueAfter = ReconcileRequeueOnValidationError
   389  	}
   390  
   391  	logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile in ", time.Since(startReconcile))
   392  
   393  	return ctrl.Result{
   394  		RequeueAfter: requeueAfter,
   395  	}, nil
   396  }
   397  
   398  func (r *ApplicationSetReconciler) performReverseDeletion(ctx context.Context, logCtx *log.Entry, appset argov1alpha1.ApplicationSet, currentApps []argov1alpha1.Application) (time.Duration, error) {
   399  	requeueTime := 10 * time.Second
   400  	stepLength := len(appset.Spec.Strategy.RollingSync.Steps)
   401  
   402  	// map applications by name using current applications
   403  	appMap := make(map[string]*argov1alpha1.Application)
   404  	for _, app := range currentApps {
   405  		appMap[app.Name] = &app
   406  	}
   407  
   408  	// Get Rolling Sync Step Maps
   409  	_, appStepMap := r.buildAppDependencyList(logCtx, appset, currentApps)
   410  	// reverse the AppStepMap to perform deletion
   411  	var reverseDeleteAppSteps []deleteInOrder
   412  	for appName, appStep := range appStepMap {
   413  		reverseDeleteAppSteps = append(reverseDeleteAppSteps, deleteInOrder{appName, stepLength - appStep - 1})
   414  	}
   415  
   416  	sort.Slice(reverseDeleteAppSteps, func(i, j int) bool {
   417  		return reverseDeleteAppSteps[i].Step < reverseDeleteAppSteps[j].Step
   418  	})
   419  
   420  	for _, step := range reverseDeleteAppSteps {
   421  		logCtx.Infof("step %v : app %v", step.Step, step.AppName)
   422  		app := appMap[step.AppName]
   423  		retrievedApp := argov1alpha1.Application{}
   424  		if err := r.Get(ctx, types.NamespacedName{Name: app.Name, Namespace: app.Namespace}, &retrievedApp); err != nil {
   425  			if apierrors.IsNotFound(err) {
   426  				logCtx.Infof("application %s successfully deleted", step.AppName)
   427  				continue
   428  			}
   429  		}
   430  		// Check if the application is already being deleted
   431  		if retrievedApp.DeletionTimestamp != nil {
   432  			logCtx.Infof("application %s has been marked for deletion, but object not removed yet", step.AppName)
   433  			if time.Since(retrievedApp.DeletionTimestamp.Time) > 2*time.Minute {
   434  				return 0, errors.New("application has not been deleted in over 2 minutes")
   435  			}
   436  		}
   437  		// The application has not been deleted yet, trigger its deletion
   438  		if err := r.Delete(ctx, &retrievedApp); err != nil {
   439  			return 0, err
   440  		}
   441  		return requeueTime, nil
   442  	}
   443  	logCtx.Infof("completed reverse deletion for ApplicationSet %v", appset.Name)
   444  	return 0, nil
   445  }
   446  
   447  func getParametersGeneratedCondition(parametersGenerated bool, message string) argov1alpha1.ApplicationSetCondition {
   448  	var parametersGeneratedCondition argov1alpha1.ApplicationSetCondition
   449  	if parametersGenerated {
   450  		parametersGeneratedCondition = argov1alpha1.ApplicationSetCondition{
   451  			Type:    argov1alpha1.ApplicationSetConditionParametersGenerated,
   452  			Message: "Successfully generated parameters for all Applications",
   453  			Reason:  argov1alpha1.ApplicationSetReasonParametersGenerated,
   454  			Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   455  		}
   456  	} else {
   457  		parametersGeneratedCondition = argov1alpha1.ApplicationSetCondition{
   458  			Type:    argov1alpha1.ApplicationSetConditionParametersGenerated,
   459  			Message: message,
   460  			Reason:  argov1alpha1.ApplicationSetReasonErrorOccurred,
   461  			Status:  argov1alpha1.ApplicationSetConditionStatusFalse,
   462  		}
   463  	}
   464  	return parametersGeneratedCondition
   465  }
   466  
   467  func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, condition argov1alpha1.ApplicationSetCondition, parametersGenerated bool) error {
   468  	// Initialize the default condition types that this method evaluates
   469  	evaluatedTypes := map[argov1alpha1.ApplicationSetConditionType]bool{
   470  		argov1alpha1.ApplicationSetConditionParametersGenerated: true,
   471  		argov1alpha1.ApplicationSetConditionErrorOccurred:       false,
   472  		argov1alpha1.ApplicationSetConditionResourcesUpToDate:   false,
   473  		argov1alpha1.ApplicationSetConditionRolloutProgressing:  false,
   474  	}
   475  	// Evaluate current condition
   476  	evaluatedTypes[condition.Type] = true
   477  	newConditions := []argov1alpha1.ApplicationSetCondition{condition}
   478  
   479  	if !isRollingSyncStrategy(applicationSet) {
   480  		// Progressing sync is always evaluated so conditions are removed when it is not enabled
   481  		evaluatedTypes[argov1alpha1.ApplicationSetConditionRolloutProgressing] = true
   482  	}
   483  
   484  	// Evaluate ParametersGenerated since it is always provided
   485  	if condition.Type != argov1alpha1.ApplicationSetConditionParametersGenerated {
   486  		newConditions = append(newConditions, getParametersGeneratedCondition(parametersGenerated, condition.Message))
   487  	}
   488  
   489  	// Evaluate dependencies between conditions.
   490  	switch condition.Type {
   491  	case argov1alpha1.ApplicationSetConditionResourcesUpToDate:
   492  		if condition.Status == argov1alpha1.ApplicationSetConditionStatusTrue {
   493  			// If the resources are up to date, we know there was no errors
   494  			evaluatedTypes[argov1alpha1.ApplicationSetConditionErrorOccurred] = true
   495  			newConditions = append(newConditions, argov1alpha1.ApplicationSetCondition{
   496  				Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   497  				Status:  argov1alpha1.ApplicationSetConditionStatusFalse,
   498  				Reason:  condition.Reason,
   499  				Message: condition.Message,
   500  			})
   501  		}
   502  	case argov1alpha1.ApplicationSetConditionErrorOccurred:
   503  		if condition.Status == argov1alpha1.ApplicationSetConditionStatusTrue {
   504  			// If there is an error anywhere in the reconciliation, we cannot consider the resources up to date
   505  			evaluatedTypes[argov1alpha1.ApplicationSetConditionResourcesUpToDate] = true
   506  			newConditions = append(newConditions, argov1alpha1.ApplicationSetCondition{
   507  				Type:    argov1alpha1.ApplicationSetConditionResourcesUpToDate,
   508  				Status:  argov1alpha1.ApplicationSetConditionStatusFalse,
   509  				Reason:  argov1alpha1.ApplicationSetReasonErrorOccurred,
   510  				Message: condition.Message,
   511  			})
   512  		}
   513  	case argov1alpha1.ApplicationSetConditionRolloutProgressing:
   514  		if !isRollingSyncStrategy(applicationSet) {
   515  			// if the condition is a rolling sync and it is disabled, ignore it
   516  			evaluatedTypes[condition.Type] = false
   517  		}
   518  	}
   519  
   520  	// Update the applicationSet conditions
   521  	previousConditions := applicationSet.Status.Conditions
   522  	applicationSet.Status.SetConditions(newConditions, evaluatedTypes)
   523  
   524  	// Try to not call get/update if nothing has changed
   525  	needToUpdateConditions := len(applicationSet.Status.Conditions) != len(previousConditions)
   526  	if !needToUpdateConditions {
   527  		for i, c := range applicationSet.Status.Conditions {
   528  			previous := previousConditions[i]
   529  			if c.Type != previous.Type || c.Reason != previous.Reason || c.Status != previous.Status || c.Message != previous.Message {
   530  				needToUpdateConditions = true
   531  				break
   532  			}
   533  		}
   534  	}
   535  
   536  	if !needToUpdateConditions {
   537  		return nil
   538  	}
   539  	// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms
   540  	err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
   541  		updatedAppset := &argov1alpha1.ApplicationSet{}
   542  		if err := r.Get(ctx, types.NamespacedName{Namespace: applicationSet.Namespace, Name: applicationSet.Name}, updatedAppset); err != nil {
   543  			if client.IgnoreNotFound(err) != nil {
   544  				return nil
   545  			}
   546  			return fmt.Errorf("error fetching updated application set: %w", err)
   547  		}
   548  
   549  		updatedAppset.Status.SetConditions(newConditions, evaluatedTypes)
   550  
   551  		// Update the newly fetched object with new set of conditions
   552  		err := r.Client.Status().Update(ctx, updatedAppset)
   553  		if err != nil {
   554  			return err
   555  		}
   556  		updatedAppset.DeepCopyInto(applicationSet)
   557  		return nil
   558  	})
   559  	if err != nil && !apierrors.IsNotFound(err) {
   560  		return fmt.Errorf("unable to set application set condition: %w", err)
   561  	}
   562  
   563  	return nil
   564  }
   565  
   566  // validateGeneratedApplications uses the Argo CD validation functions to verify the correctness of the
   567  // generated applications.
   568  func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Context, desiredApplications []argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet) (map[string]error, error) {
   569  	errorsByApp := map[string]error{}
   570  	namesSet := map[string]bool{}
   571  	for i := range desiredApplications {
   572  		app := &desiredApplications[i]
   573  		if namesSet[app.Name] {
   574  			errorsByApp[app.QualifiedName()] = fmt.Errorf("ApplicationSet %s contains applications with duplicate name: %s", applicationSetInfo.Name, app.Name)
   575  			continue
   576  		}
   577  		namesSet[app.Name] = true
   578  		appProject := &argov1alpha1.AppProject{}
   579  		err := r.Get(ctx, types.NamespacedName{Name: app.Spec.Project, Namespace: r.ArgoCDNamespace}, appProject)
   580  		if err != nil {
   581  			if apierrors.IsNotFound(err) {
   582  				errorsByApp[app.QualifiedName()] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
   583  				continue
   584  			}
   585  			return nil, err
   586  		}
   587  
   588  		if _, err = argoutil.GetDestinationCluster(ctx, app.Spec.Destination, r.ArgoDB); err != nil {
   589  			errorsByApp[app.QualifiedName()] = fmt.Errorf("application destination spec is invalid: %s", err.Error())
   590  			continue
   591  		}
   592  	}
   593  
   594  	return errorsByApp, nil
   595  }
   596  
   597  func (r *ApplicationSetReconciler) getMinRequeueAfter(applicationSetInfo *argov1alpha1.ApplicationSet) time.Duration {
   598  	var res time.Duration
   599  	for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
   600  		relevantGenerators := generators.GetRelevantGenerators(&requestedGenerator, r.Generators)
   601  
   602  		for _, g := range relevantGenerators {
   603  			t := g.GetRequeueAfter(&requestedGenerator)
   604  
   605  			if res == 0 {
   606  				res = t
   607  			} else if t != 0 && t < res {
   608  				res = t
   609  			}
   610  		}
   611  	}
   612  
   613  	return res
   614  }
   615  
   616  func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
   617  	return predicate.NewPredicateFuncs(func(object client.Object) bool {
   618  		return utils.IsNamespaceAllowed(namespaces, object.GetNamespace())
   619  	})
   620  }
   621  
   622  func appControllerIndexer(rawObj client.Object) []string {
   623  	// grab the job object, extract the owner...
   624  	app := rawObj.(*argov1alpha1.Application)
   625  	owner := metav1.GetControllerOf(app)
   626  	if owner == nil {
   627  		return nil
   628  	}
   629  	// ...make sure it's a application set...
   630  	if owner.APIVersion != argov1alpha1.SchemeGroupVersion.String() || owner.Kind != "ApplicationSet" {
   631  		return nil
   632  	}
   633  
   634  	// ...and if so, return it
   635  	return []string{owner.Name}
   636  }
   637  
   638  func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProgressiveSyncs bool, maxConcurrentReconciliations int) error {
   639  	if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &argov1alpha1.Application{}, ".metadata.controller", appControllerIndexer); err != nil {
   640  		return fmt.Errorf("error setting up with manager: %w", err)
   641  	}
   642  
   643  	appOwnsHandler := getApplicationOwnsHandler(enableProgressiveSyncs)
   644  	appSetOwnsHandler := getApplicationSetOwnsHandler(enableProgressiveSyncs)
   645  
   646  	return ctrl.NewControllerManagedBy(mgr).WithOptions(controller.Options{
   647  		MaxConcurrentReconciles: maxConcurrentReconciliations,
   648  	}).For(&argov1alpha1.ApplicationSet{}, builder.WithPredicates(appSetOwnsHandler)).
   649  		Owns(&argov1alpha1.Application{}, builder.WithPredicates(appOwnsHandler)).
   650  		WithEventFilter(ignoreNotAllowedNamespaces(r.ApplicationSetNamespaces)).
   651  		Watches(
   652  			&corev1.Secret{},
   653  			&clusterSecretEventHandler{
   654  				Client: mgr.GetClient(),
   655  				Log:    log.WithField("type", "createSecretEventHandler"),
   656  			}).
   657  		Complete(r)
   658  }
   659  
   660  // createOrUpdateInCluster will create / update application resources in the cluster.
   661  // - For new applications, it will call create
   662  // - For existing application, it will call update
   663  // The function also adds owner reference to all applications, and uses it to delete them.
   664  func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
   665  	var firstError error
   666  	// Creates or updates the application in appList
   667  	for _, generatedApp := range desiredApplications {
   668  		appLog := logCtx.WithFields(applog.GetAppLogFields(&generatedApp))
   669  
   670  		// Normalize to avoid fighting with the application controller.
   671  		generatedApp.Spec = *argoutil.NormalizeApplicationSpec(&generatedApp.Spec)
   672  
   673  		found := &argov1alpha1.Application{
   674  			ObjectMeta: metav1.ObjectMeta{
   675  				Name:      generatedApp.Name,
   676  				Namespace: generatedApp.Namespace,
   677  			},
   678  			TypeMeta: metav1.TypeMeta{
   679  				Kind:       application.ApplicationKind,
   680  				APIVersion: "argoproj.io/v1alpha1",
   681  			},
   682  		}
   683  
   684  		action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, normalizers.IgnoreNormalizerOpts{}, found, func() error {
   685  			// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
   686  			found.Spec = generatedApp.Spec
   687  
   688  			// allow setting the Operation field to trigger a sync operation on an Application
   689  			if generatedApp.Operation != nil {
   690  				found.Operation = generatedApp.Operation
   691  			}
   692  
   693  			preservedAnnotations := make([]string, 0)
   694  			preservedLabels := make([]string, 0)
   695  
   696  			if applicationSet.Spec.PreservedFields != nil {
   697  				preservedAnnotations = append(preservedAnnotations, applicationSet.Spec.PreservedFields.Annotations...)
   698  				preservedLabels = append(preservedLabels, applicationSet.Spec.PreservedFields.Labels...)
   699  			}
   700  
   701  			if len(r.GlobalPreservedAnnotations) > 0 {
   702  				preservedAnnotations = append(preservedAnnotations, r.GlobalPreservedAnnotations...)
   703  			}
   704  
   705  			if len(r.GlobalPreservedLabels) > 0 {
   706  				preservedLabels = append(preservedLabels, r.GlobalPreservedLabels...)
   707  			}
   708  
   709  			// Preserve specially treated argo cd annotations:
   710  			// * https://github.com/argoproj/applicationset/issues/180
   711  			// * https://github.com/argoproj/argo-cd/issues/10500
   712  			preservedAnnotations = append(preservedAnnotations, defaultPreservedAnnotations...)
   713  
   714  			for _, key := range preservedAnnotations {
   715  				if state, exists := found.Annotations[key]; exists {
   716  					if generatedApp.Annotations == nil {
   717  						generatedApp.Annotations = map[string]string{}
   718  					}
   719  					generatedApp.Annotations[key] = state
   720  				}
   721  			}
   722  
   723  			for _, key := range preservedLabels {
   724  				if state, exists := found.Labels[key]; exists {
   725  					if generatedApp.Labels == nil {
   726  						generatedApp.Labels = map[string]string{}
   727  					}
   728  					generatedApp.Labels[key] = state
   729  				}
   730  			}
   731  
   732  			// Preserve post-delete finalizers:
   733  			//   https://github.com/argoproj/argo-cd/issues/17181
   734  			for _, finalizer := range found.Finalizers {
   735  				if strings.HasPrefix(finalizer, argov1alpha1.PostDeleteFinalizerName) {
   736  					if generatedApp.Finalizers == nil {
   737  						generatedApp.Finalizers = []string{}
   738  					}
   739  					generatedApp.Finalizers = append(generatedApp.Finalizers, finalizer)
   740  				}
   741  			}
   742  
   743  			found.Annotations = generatedApp.Annotations
   744  
   745  			found.Finalizers = generatedApp.Finalizers
   746  			found.Labels = generatedApp.Labels
   747  
   748  			return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme)
   749  		})
   750  		if err != nil {
   751  			appLog.WithError(err).WithField("action", action).Errorf("failed to %s Application", action)
   752  			if firstError == nil {
   753  				firstError = err
   754  			}
   755  			continue
   756  		}
   757  
   758  		if action != controllerutil.OperationResultNone {
   759  			// Don't pollute etcd with "unchanged Application" events
   760  			r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, fmt.Sprint(action), "%s Application %q", action, generatedApp.Name)
   761  			appLog.Logf(log.InfoLevel, "%s Application", action)
   762  		} else {
   763  			// "unchanged Application" can be inferred by Reconcile Complete with no action being listed
   764  			// Or enable debug logging
   765  			appLog.Logf(log.DebugLevel, "%s Application", action)
   766  		}
   767  	}
   768  	return firstError
   769  }
   770  
   771  // createInCluster will filter from the desiredApplications only the application that needs to be created
   772  // Then it will call createOrUpdateInCluster to do the actual create
   773  func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
   774  	var createApps []argov1alpha1.Application
   775  	current, err := r.getCurrentApplications(ctx, applicationSet)
   776  	if err != nil {
   777  		return fmt.Errorf("error getting current applications: %w", err)
   778  	}
   779  
   780  	m := make(map[string]bool) // Will holds the app names that are current in the cluster
   781  
   782  	for _, app := range current {
   783  		m[app.Name] = true
   784  	}
   785  
   786  	// filter applications that are not in m[string]bool (new to the cluster)
   787  	for _, app := range desiredApplications {
   788  		_, exists := m[app.Name]
   789  
   790  		if !exists {
   791  			createApps = append(createApps, app)
   792  		}
   793  	}
   794  
   795  	return r.createOrUpdateInCluster(ctx, logCtx, applicationSet, createApps)
   796  }
   797  
   798  func (r *ApplicationSetReconciler) getCurrentApplications(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) {
   799  	var current argov1alpha1.ApplicationList
   800  	err := r.List(ctx, &current, client.MatchingFields{".metadata.controller": applicationSet.Name}, client.InNamespace(applicationSet.Namespace))
   801  	if err != nil {
   802  		return nil, fmt.Errorf("error retrieving applications: %w", err)
   803  	}
   804  
   805  	return current.Items, nil
   806  }
   807  
   808  // deleteInCluster will delete Applications that are currently on the cluster, but not in appList.
   809  // The function must be called after all generators had been called and generated applications
   810  func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
   811  	clusterList, err := utils.ListClusters(ctx, r.KubeClientset, r.ArgoCDNamespace)
   812  	if err != nil {
   813  		return fmt.Errorf("error listing clusters: %w", err)
   814  	}
   815  
   816  	// Save current applications to be able to delete the ones that are not in appList
   817  	current, err := r.getCurrentApplications(ctx, applicationSet)
   818  	if err != nil {
   819  		return fmt.Errorf("error getting current applications: %w", err)
   820  	}
   821  
   822  	m := make(map[string]bool) // will hold the app names in appList for the deletion process
   823  
   824  	for _, app := range desiredApplications {
   825  		m[app.Name] = true
   826  	}
   827  
   828  	// Delete apps that are not in m[string]bool
   829  	var firstError error
   830  	for _, app := range current {
   831  		logCtx = logCtx.WithFields(applog.GetAppLogFields(&app))
   832  		_, exists := m[app.Name]
   833  
   834  		if !exists {
   835  			// Removes the Argo CD resources finalizer if the application contains an invalid target (eg missing cluster)
   836  			err := r.removeFinalizerOnInvalidDestination(ctx, applicationSet, &app, clusterList, logCtx)
   837  			if err != nil {
   838  				logCtx.WithError(err).Error("failed to update Application")
   839  				if firstError != nil {
   840  					firstError = err
   841  				}
   842  				continue
   843  			}
   844  
   845  			err = r.Delete(ctx, &app)
   846  			if err != nil {
   847  				logCtx.WithError(err).Error("failed to delete Application")
   848  				if firstError != nil {
   849  					firstError = err
   850  				}
   851  				continue
   852  			}
   853  			r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, "Deleted", "Deleted Application %q", app.Name)
   854  			logCtx.Log(log.InfoLevel, "Deleted application")
   855  		}
   856  	}
   857  	return firstError
   858  }
   859  
   860  // removeFinalizerOnInvalidDestination removes the Argo CD resources finalizer if the application contains an invalid target (eg missing cluster)
   861  func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, app *argov1alpha1.Application, clusterList []utils.ClusterSpecifier, appLog *log.Entry) error {
   862  	// Only check if the finalizers need to be removed IF there are finalizers to remove
   863  	if len(app.Finalizers) == 0 {
   864  		return nil
   865  	}
   866  
   867  	var validDestination bool
   868  
   869  	// Detect if the destination is invalid (name doesn't correspond to a matching cluster)
   870  	if destCluster, err := argoutil.GetDestinationCluster(ctx, app.Spec.Destination, r.ArgoDB); err != nil {
   871  		appLog.Warnf("The destination cluster for %s could not be found: %v", app.Name, err)
   872  		validDestination = false
   873  	} else {
   874  		// Detect if the destination's server field does not match an existing cluster
   875  		matchingCluster := false
   876  		for _, cluster := range clusterList {
   877  			if destCluster.Server != cluster.Server {
   878  				continue
   879  			}
   880  
   881  			if destCluster.Name != cluster.Name {
   882  				continue
   883  			}
   884  
   885  			matchingCluster = true
   886  			break
   887  		}
   888  
   889  		if !matchingCluster {
   890  			appLog.Warnf("A match for the destination cluster for %s, by server url, could not be found", app.Name)
   891  		}
   892  
   893  		validDestination = matchingCluster
   894  	}
   895  	// If the destination is invalid (for example the cluster is no longer defined), then remove
   896  	// the application finalizers to avoid triggering Argo CD bug #5817
   897  	if !validDestination {
   898  		// Filter out the Argo CD finalizer from the finalizer list
   899  		var newFinalizers []string
   900  		for _, existingFinalizer := range app.Finalizers {
   901  			if existingFinalizer != argov1alpha1.ResourcesFinalizerName { // only remove this one
   902  				newFinalizers = append(newFinalizers, existingFinalizer)
   903  			}
   904  		}
   905  
   906  		// If the finalizer length changed (due to filtering out an Argo finalizer), update the finalizer list on the app
   907  		if len(newFinalizers) != len(app.Finalizers) {
   908  			updated := app.DeepCopy()
   909  			updated.Finalizers = newFinalizers
   910  			patch := client.MergeFrom(app)
   911  			if log.IsLevelEnabled(log.DebugLevel) {
   912  				utils.LogPatch(appLog, patch, updated)
   913  			}
   914  			if err := r.Patch(ctx, updated, patch); err != nil {
   915  				return fmt.Errorf("error updating finalizers: %w", err)
   916  			}
   917  			// Application must have updated list of finalizers
   918  			updated.DeepCopyInto(app)
   919  
   920  			r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, "Updated", "Updated Application %q finalizer before deletion, because application has an invalid destination", app.Name)
   921  			appLog.Log(log.InfoLevel, "Updating application finalizer before deletion, because application has an invalid destination")
   922  		}
   923  	}
   924  
   925  	return nil
   926  }
   927  
   928  func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) error {
   929  	applications, err := r.getCurrentApplications(ctx, applicationSet)
   930  	if err != nil {
   931  		return fmt.Errorf("error getting current applications for ApplicationSet: %w", err)
   932  	}
   933  
   934  	for _, app := range applications {
   935  		app.SetOwnerReferences([]metav1.OwnerReference{})
   936  		err := r.Update(ctx, &app)
   937  		if err != nil {
   938  			return fmt.Errorf("error updating application: %w", err)
   939  		}
   940  	}
   941  
   942  	return nil
   943  }
   944  
   945  func (r *ApplicationSetReconciler) performProgressiveSyncs(ctx context.Context, logCtx *log.Entry, appset argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, desiredApplications []argov1alpha1.Application, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
   946  	appDependencyList, appStepMap := r.buildAppDependencyList(logCtx, appset, desiredApplications)
   947  
   948  	_, err := r.updateApplicationSetApplicationStatus(ctx, logCtx, &appset, applications, appStepMap)
   949  	if err != nil {
   950  		return nil, fmt.Errorf("failed to update applicationset app status: %w", err)
   951  	}
   952  
   953  	logCtx.Infof("ApplicationSet %v step list:", appset.Name)
   954  	for i, step := range appDependencyList {
   955  		logCtx.Infof("step %v: %+v", i+1, step)
   956  	}
   957  
   958  	appSyncMap := r.buildAppSyncMap(appset, appDependencyList, appMap)
   959  	logCtx.Infof("Application allowed to sync before maxUpdate?: %+v", appSyncMap)
   960  
   961  	_, err = r.updateApplicationSetApplicationStatusProgress(ctx, logCtx, &appset, appSyncMap, appStepMap)
   962  	if err != nil {
   963  		return nil, fmt.Errorf("failed to update applicationset application status progress: %w", err)
   964  	}
   965  
   966  	_ = r.updateApplicationSetApplicationStatusConditions(ctx, &appset)
   967  
   968  	return appSyncMap, nil
   969  }
   970  
   971  // this list tracks which Applications belong to each RollingUpdate step
   972  func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([][]string, map[string]int) {
   973  	if applicationSet.Spec.Strategy == nil || applicationSet.Spec.Strategy.Type == "" || applicationSet.Spec.Strategy.Type == "AllAtOnce" {
   974  		return [][]string{}, map[string]int{}
   975  	}
   976  
   977  	steps := []argov1alpha1.ApplicationSetRolloutStep{}
   978  	if progressiveSyncsRollingSyncStrategyEnabled(&applicationSet) {
   979  		steps = applicationSet.Spec.Strategy.RollingSync.Steps
   980  	}
   981  
   982  	appDependencyList := make([][]string, 0)
   983  	for range steps {
   984  		appDependencyList = append(appDependencyList, make([]string, 0))
   985  	}
   986  
   987  	appStepMap := map[string]int{}
   988  
   989  	// use applicationLabelSelectors to filter generated Applications into steps and status by name
   990  	for _, app := range applications {
   991  		for i, step := range steps {
   992  			selected := true // default to true, assuming the current Application is a match for the given step matchExpression
   993  
   994  			for _, matchExpression := range step.MatchExpressions {
   995  				if val, ok := app.Labels[matchExpression.Key]; ok {
   996  					valueMatched := labelMatchedExpression(logCtx, val, matchExpression)
   997  
   998  					if !valueMatched { // none of the matchExpression values was a match with the Application's labels
   999  						selected = false
  1000  						break
  1001  					}
  1002  				} else if matchExpression.Operator == "In" {
  1003  					selected = false // no matching label key with "In" operator means this Application will not be included in the current step
  1004  					break
  1005  				}
  1006  			}
  1007  
  1008  			if selected {
  1009  				appDependencyList[i] = append(appDependencyList[i], app.Name)
  1010  				if val, ok := appStepMap[app.Name]; ok {
  1011  					logCtx.Warnf("AppSet '%v' has a invalid matchExpression that selects Application '%v' label twice, in steps %v and %v", applicationSet.Name, app.Name, val+1, i+1)
  1012  				} else {
  1013  					appStepMap[app.Name] = i
  1014  				}
  1015  			}
  1016  		}
  1017  	}
  1018  
  1019  	return appDependencyList, appStepMap
  1020  }
  1021  
  1022  func labelMatchedExpression(logCtx *log.Entry, val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool {
  1023  	if matchExpression.Operator != "In" && matchExpression.Operator != "NotIn" {
  1024  		logCtx.Errorf("skipping AppSet rollingUpdate step Application selection, invalid matchExpression operator provided: %q ", matchExpression.Operator)
  1025  		return false
  1026  	}
  1027  
  1028  	// if operator == In, default to false
  1029  	// if operator == NotIn, default to true
  1030  	valueMatched := matchExpression.Operator == "NotIn"
  1031  
  1032  	for _, value := range matchExpression.Values {
  1033  		if val == value {
  1034  			// first "In" match returns true
  1035  			// first "NotIn" match returns false
  1036  			return matchExpression.Operator == "In"
  1037  		}
  1038  	}
  1039  	return valueMatched
  1040  }
  1041  
  1042  // this map is used to determine which stage of Applications are ready to be updated in the reconciler loop
  1043  func (r *ApplicationSetReconciler) buildAppSyncMap(applicationSet argov1alpha1.ApplicationSet, appDependencyList [][]string, appMap map[string]argov1alpha1.Application) map[string]bool {
  1044  	appSyncMap := map[string]bool{}
  1045  	syncEnabled := true
  1046  
  1047  	// healthy stages and the first non-healthy stage should have sync enabled
  1048  	// every stage after should have sync disabled
  1049  
  1050  	for i := range appDependencyList {
  1051  		// set the syncEnabled boolean for every Application in the current step
  1052  		for _, appName := range appDependencyList[i] {
  1053  			appSyncMap[appName] = syncEnabled
  1054  		}
  1055  
  1056  		// detect if we need to halt before progressing to the next step
  1057  		for _, appName := range appDependencyList[i] {
  1058  			idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appName)
  1059  			if idx == -1 {
  1060  				// no Application status found, likely because the Application is being newly created
  1061  				syncEnabled = false
  1062  				break
  1063  			}
  1064  
  1065  			appStatus := applicationSet.Status.ApplicationStatus[idx]
  1066  			app, ok := appMap[appName]
  1067  			if !ok {
  1068  				// application name not found in the list of applications managed by this ApplicationSet, maybe because it's being deleted
  1069  				syncEnabled = false
  1070  				break
  1071  			}
  1072  			syncEnabled = appSyncEnabledForNextStep(&applicationSet, app, appStatus)
  1073  			if !syncEnabled {
  1074  				break
  1075  			}
  1076  		}
  1077  	}
  1078  
  1079  	return appSyncMap
  1080  }
  1081  
  1082  func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1alpha1.Application, appStatus argov1alpha1.ApplicationSetApplicationStatus) bool {
  1083  	if progressiveSyncsRollingSyncStrategyEnabled(appset) {
  1084  		// we still need to complete the current step if the Application is not yet Healthy or there are still pending Application changes
  1085  		return isApplicationHealthy(app) && appStatus.Status == "Healthy"
  1086  	}
  1087  
  1088  	return true
  1089  }
  1090  
  1091  func isRollingSyncStrategy(appset *argov1alpha1.ApplicationSet) bool {
  1092  	// It's only RollingSync if the type specifically sets it
  1093  	return appset.Spec.Strategy != nil && appset.Spec.Strategy.Type == "RollingSync" && appset.Spec.Strategy.RollingSync != nil
  1094  }
  1095  
  1096  func progressiveSyncsRollingSyncStrategyEnabled(appset *argov1alpha1.ApplicationSet) bool {
  1097  	// ProgressiveSync is enabled if the strategy is set to `RollingSync` + steps slice is not empty
  1098  	return isRollingSyncStrategy(appset) && len(appset.Spec.Strategy.RollingSync.Steps) > 0
  1099  }
  1100  
  1101  func isProgressiveSyncDeletionOrderReversed(appset *argov1alpha1.ApplicationSet) bool {
  1102  	// When progressive sync is enabled + deletionOrder is set to Reverse (case-insensitive)
  1103  	return progressiveSyncsRollingSyncStrategyEnabled(appset) && strings.EqualFold(appset.Spec.Strategy.DeletionOrder, ReverseDeletionOrder)
  1104  }
  1105  
  1106  func isApplicationHealthy(app argov1alpha1.Application) bool {
  1107  	healthStatusString, syncStatusString, operationPhaseString := statusStrings(app)
  1108  
  1109  	if healthStatusString == "Healthy" && syncStatusString != "OutOfSync" && (operationPhaseString == "Succeeded" || operationPhaseString == "") {
  1110  		return true
  1111  	}
  1112  	return false
  1113  }
  1114  
  1115  func statusStrings(app argov1alpha1.Application) (string, string, string) {
  1116  	healthStatusString := string(app.Status.Health.Status)
  1117  	syncStatusString := string(app.Status.Sync.Status)
  1118  	operationPhaseString := ""
  1119  	if app.Status.OperationState != nil {
  1120  		operationPhaseString = string(app.Status.OperationState.Phase)
  1121  	}
  1122  
  1123  	return healthStatusString, syncStatusString, operationPhaseString
  1124  }
  1125  
  1126  func getAppStep(appName string, appStepMap map[string]int) int {
  1127  	// if an application is not selected by any match expression, it defaults to step -1
  1128  	step := -1
  1129  	if appStep, ok := appStepMap[appName]; ok {
  1130  		// 1-based indexing
  1131  		step = appStep + 1
  1132  	}
  1133  	return step
  1134  }
  1135  
  1136  // check the status of each Application's status and promote Applications to the next status if needed
  1137  func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
  1138  	now := metav1.Now()
  1139  	appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applications))
  1140  
  1141  	for _, app := range applications {
  1142  		healthStatusString, syncStatusString, operationPhaseString := statusStrings(app)
  1143  
  1144  		idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, app.Name)
  1145  
  1146  		currentAppStatus := argov1alpha1.ApplicationSetApplicationStatus{}
  1147  
  1148  		if idx == -1 {
  1149  			// AppStatus not found, set default status of "Waiting"
  1150  			currentAppStatus = argov1alpha1.ApplicationSetApplicationStatus{
  1151  				Application:        app.Name,
  1152  				LastTransitionTime: &now,
  1153  				Message:            "No Application status found, defaulting status to Waiting.",
  1154  				Status:             "Waiting",
  1155  				Step:               strconv.Itoa(getAppStep(app.Name, appStepMap)),
  1156  			}
  1157  		} else {
  1158  			// we have an existing AppStatus
  1159  			currentAppStatus = applicationSet.Status.ApplicationStatus[idx]
  1160  			if !reflect.DeepEqual(currentAppStatus.TargetRevisions, app.Status.GetRevisions()) {
  1161  				currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
  1162  			}
  1163  		}
  1164  		if !reflect.DeepEqual(currentAppStatus.TargetRevisions, app.Status.GetRevisions()) {
  1165  			currentAppStatus.TargetRevisions = app.Status.GetRevisions()
  1166  			currentAppStatus.Status = "Waiting"
  1167  			currentAppStatus.LastTransitionTime = &now
  1168  			currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
  1169  		}
  1170  
  1171  		appOutdated := false
  1172  		if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
  1173  			appOutdated = syncStatusString == "OutOfSync"
  1174  		}
  1175  
  1176  		if appOutdated && currentAppStatus.Status != "Waiting" && currentAppStatus.Status != "Pending" {
  1177  			logCtx.Infof("Application %v is outdated, updating its ApplicationSet status to Waiting", app.Name)
  1178  			currentAppStatus.LastTransitionTime = &now
  1179  			currentAppStatus.Status = "Waiting"
  1180  			currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
  1181  			currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
  1182  		}
  1183  
  1184  		if currentAppStatus.Status == "Pending" {
  1185  			if !appOutdated && operationPhaseString == "Succeeded" {
  1186  				logCtx.Infof("Application %v has completed a sync successfully, updating its ApplicationSet status to Progressing", app.Name)
  1187  				currentAppStatus.LastTransitionTime = &now
  1188  				currentAppStatus.Status = "Progressing"
  1189  				currentAppStatus.Message = "Application resource completed a sync successfully, updating status from Pending to Progressing."
  1190  				currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
  1191  			} else if operationPhaseString == "Running" || healthStatusString == "Progressing" {
  1192  				logCtx.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name)
  1193  				currentAppStatus.LastTransitionTime = &now
  1194  				currentAppStatus.Status = "Progressing"
  1195  				currentAppStatus.Message = "Application resource became Progressing, updating status from Pending to Progressing."
  1196  				currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
  1197  			}
  1198  		}
  1199  
  1200  		if currentAppStatus.Status == "Waiting" && isApplicationHealthy(app) {
  1201  			logCtx.Infof("Application %v is already synced and healthy, updating its ApplicationSet status to Healthy", app.Name)
  1202  			currentAppStatus.LastTransitionTime = &now
  1203  			currentAppStatus.Status = healthStatusString
  1204  			currentAppStatus.Message = "Application resource is already Healthy, updating status from Waiting to Healthy."
  1205  			currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
  1206  		}
  1207  
  1208  		if currentAppStatus.Status == "Progressing" && isApplicationHealthy(app) {
  1209  			logCtx.Infof("Application %v has completed Progressing status, updating its ApplicationSet status to Healthy", app.Name)
  1210  			currentAppStatus.LastTransitionTime = &now
  1211  			currentAppStatus.Status = healthStatusString
  1212  			currentAppStatus.Message = "Application resource became Healthy, updating status from Progressing to Healthy."
  1213  			currentAppStatus.Step = strconv.Itoa(getAppStep(currentAppStatus.Application, appStepMap))
  1214  		}
  1215  
  1216  		appStatuses = append(appStatuses, currentAppStatus)
  1217  	}
  1218  
  1219  	err := r.setAppSetApplicationStatus(ctx, logCtx, applicationSet, appStatuses)
  1220  	if err != nil {
  1221  		return nil, fmt.Errorf("failed to set AppSet application statuses: %w", err)
  1222  	}
  1223  
  1224  	return appStatuses, nil
  1225  }
  1226  
  1227  // check Applications that are in Waiting status and promote them to Pending if needed
  1228  func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
  1229  	now := metav1.Now()
  1230  
  1231  	appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applicationSet.Status.ApplicationStatus))
  1232  
  1233  	// if we have no RollingUpdate steps, clear out the existing ApplicationStatus entries
  1234  	if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
  1235  		length := len(applicationSet.Spec.Strategy.RollingSync.Steps)
  1236  
  1237  		updateCountMap := make([]int, length)
  1238  		totalCountMap := make([]int, length)
  1239  
  1240  		// populate updateCountMap with counts of existing Pending and Progressing Applications
  1241  		for _, appStatus := range applicationSet.Status.ApplicationStatus {
  1242  			totalCountMap[appStepMap[appStatus.Application]]++
  1243  
  1244  			if appStatus.Status == "Pending" || appStatus.Status == "Progressing" {
  1245  				updateCountMap[appStepMap[appStatus.Application]]++
  1246  			}
  1247  		}
  1248  
  1249  		for _, appStatus := range applicationSet.Status.ApplicationStatus {
  1250  			maxUpdateAllowed := true
  1251  			maxUpdate := &intstr.IntOrString{}
  1252  			if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
  1253  				maxUpdate = applicationSet.Spec.Strategy.RollingSync.Steps[appStepMap[appStatus.Application]].MaxUpdate
  1254  			}
  1255  
  1256  			// by default allow all applications to update if maxUpdate is unset
  1257  			if maxUpdate != nil {
  1258  				maxUpdateVal, err := intstr.GetScaledValueFromIntOrPercent(maxUpdate, totalCountMap[appStepMap[appStatus.Application]], false)
  1259  				if err != nil {
  1260  					logCtx.Warnf("AppSet '%v' has a invalid maxUpdate value '%+v', ignoring maxUpdate logic for this step: %v", applicationSet.Name, maxUpdate, err)
  1261  				}
  1262  
  1263  				// ensure that percentage values greater than 0% always result in at least 1 Application being selected
  1264  				if maxUpdate.Type == intstr.String && maxUpdate.StrVal != "0%" && maxUpdateVal < 1 {
  1265  					maxUpdateVal = 1
  1266  				}
  1267  
  1268  				if updateCountMap[appStepMap[appStatus.Application]] >= maxUpdateVal {
  1269  					maxUpdateAllowed = false
  1270  					logCtx.Infof("Application %v is not allowed to update yet, %v/%v Applications already updating in step %v in AppSet %v", appStatus.Application, updateCountMap[appStepMap[appStatus.Application]], maxUpdateVal, getAppStep(appStatus.Application, appStepMap), applicationSet.Name)
  1271  				}
  1272  			}
  1273  
  1274  			if appStatus.Status == "Waiting" && appSyncMap[appStatus.Application] && maxUpdateAllowed {
  1275  				logCtx.Infof("Application %v moved to Pending status, watching for the Application to start Progressing", appStatus.Application)
  1276  				appStatus.LastTransitionTime = &now
  1277  				appStatus.Status = "Pending"
  1278  				appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing."
  1279  				appStatus.Step = strconv.Itoa(getAppStep(appStatus.Application, appStepMap))
  1280  
  1281  				updateCountMap[appStepMap[appStatus.Application]]++
  1282  			}
  1283  
  1284  			appStatuses = append(appStatuses, appStatus)
  1285  		}
  1286  	}
  1287  
  1288  	err := r.setAppSetApplicationStatus(ctx, logCtx, applicationSet, appStatuses)
  1289  	if err != nil {
  1290  		return nil, fmt.Errorf("failed to set AppSet app status: %w", err)
  1291  	}
  1292  
  1293  	return appStatuses, nil
  1294  }
  1295  
  1296  func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditions(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet) []argov1alpha1.ApplicationSetCondition {
  1297  	if !isRollingSyncStrategy(applicationSet) {
  1298  		return applicationSet.Status.Conditions
  1299  	}
  1300  
  1301  	completedWaves := map[string]bool{}
  1302  	for _, appStatus := range applicationSet.Status.ApplicationStatus {
  1303  		if v, ok := completedWaves[appStatus.Step]; !ok {
  1304  			completedWaves[appStatus.Step] = appStatus.Status == "Healthy"
  1305  		} else {
  1306  			completedWaves[appStatus.Step] = v && appStatus.Status == "Healthy"
  1307  		}
  1308  	}
  1309  
  1310  	isProgressing := false
  1311  	progressingStep := ""
  1312  	for i := range applicationSet.Spec.Strategy.RollingSync.Steps {
  1313  		step := strconv.Itoa(i + 1)
  1314  		isCompleted, ok := completedWaves[step]
  1315  		if !ok {
  1316  			// Step has no applications, so it is completed
  1317  			continue
  1318  		}
  1319  		if !isCompleted {
  1320  			isProgressing = true
  1321  			progressingStep = step
  1322  			break
  1323  		}
  1324  	}
  1325  
  1326  	if isProgressing {
  1327  		_ = r.setApplicationSetStatusCondition(ctx,
  1328  			applicationSet,
  1329  			argov1alpha1.ApplicationSetCondition{
  1330  				Type:    argov1alpha1.ApplicationSetConditionRolloutProgressing,
  1331  				Message: "ApplicationSet is performing rollout of step " + progressingStep,
  1332  				Reason:  argov1alpha1.ApplicationSetReasonApplicationSetModified,
  1333  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
  1334  			}, true,
  1335  		)
  1336  	} else {
  1337  		_ = r.setApplicationSetStatusCondition(ctx,
  1338  			applicationSet,
  1339  			argov1alpha1.ApplicationSetCondition{
  1340  				Type:    argov1alpha1.ApplicationSetConditionRolloutProgressing,
  1341  				Message: "ApplicationSet Rollout has completed",
  1342  				Reason:  argov1alpha1.ApplicationSetReasonApplicationSetRolloutComplete,
  1343  				Status:  argov1alpha1.ApplicationSetConditionStatusFalse,
  1344  			}, true,
  1345  		)
  1346  	}
  1347  	return applicationSet.Status.Conditions
  1348  }
  1349  
  1350  func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplicationStatus, application string) int {
  1351  	for i := range appStatuses {
  1352  		if appStatuses[i].Application == application {
  1353  			return i
  1354  		}
  1355  	}
  1356  	return -1
  1357  }
  1358  
  1359  // migrateStatus run migrations on the status subresource of ApplicationSet early during the run of ApplicationSetReconciler.Reconcile
  1360  // this handles any defaulting of values - which would otherwise cause the references to r.Client.Status().Update to fail given missing required fields.
  1361  func (r *ApplicationSetReconciler) migrateStatus(ctx context.Context, appset *argov1alpha1.ApplicationSet) error {
  1362  	update := false
  1363  	if statusList := appset.Status.ApplicationStatus; statusList != nil {
  1364  		for idx := range statusList {
  1365  			if statusList[idx].TargetRevisions == nil {
  1366  				statusList[idx].TargetRevisions = []string{}
  1367  				update = true
  1368  			}
  1369  		}
  1370  	}
  1371  
  1372  	if update {
  1373  		// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms
  1374  		err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
  1375  			namespacedName := types.NamespacedName{Namespace: appset.Namespace, Name: appset.Name}
  1376  			updatedAppset := &argov1alpha1.ApplicationSet{}
  1377  			if err := r.Get(ctx, namespacedName, updatedAppset); err != nil {
  1378  				if client.IgnoreNotFound(err) != nil {
  1379  					return nil
  1380  				}
  1381  				return fmt.Errorf("error fetching updated application set: %w", err)
  1382  			}
  1383  
  1384  			updatedAppset.Status.ApplicationStatus = appset.Status.ApplicationStatus
  1385  
  1386  			// Update the newly fetched object with new set of ApplicationStatus
  1387  			err := r.Client.Status().Update(ctx, updatedAppset)
  1388  			if err != nil {
  1389  				return err
  1390  			}
  1391  			updatedAppset.DeepCopyInto(appset)
  1392  			return nil
  1393  		})
  1394  		if err != nil && !apierrors.IsNotFound(err) {
  1395  			return fmt.Errorf("unable to set application set condition: %w", err)
  1396  		}
  1397  	}
  1398  	return nil
  1399  }
  1400  
  1401  func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, logCtx *log.Entry, appset *argov1alpha1.ApplicationSet, apps []argov1alpha1.Application) error {
  1402  	statusMap := status.GetResourceStatusMap(appset)
  1403  	statusMap = status.BuildResourceStatus(statusMap, apps)
  1404  
  1405  	statuses := []argov1alpha1.ResourceStatus{}
  1406  	for _, status := range statusMap {
  1407  		statuses = append(statuses, status)
  1408  	}
  1409  	sort.Slice(statuses, func(i, j int) bool {
  1410  		return statuses[i].Name < statuses[j].Name
  1411  	})
  1412  	resourcesCount := int64(len(statuses))
  1413  	if r.MaxResourcesStatusCount > 0 && len(statuses) > r.MaxResourcesStatusCount {
  1414  		logCtx.Warnf("Truncating ApplicationSet %s resource status from %d to max allowed %d entries", appset.Name, len(statuses), r.MaxResourcesStatusCount)
  1415  		statuses = statuses[:r.MaxResourcesStatusCount]
  1416  	}
  1417  	appset.Status.Resources = statuses
  1418  	appset.Status.ResourcesCount = resourcesCount
  1419  	// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms
  1420  	err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
  1421  		namespacedName := types.NamespacedName{Namespace: appset.Namespace, Name: appset.Name}
  1422  		updatedAppset := &argov1alpha1.ApplicationSet{}
  1423  		if err := r.Get(ctx, namespacedName, updatedAppset); err != nil {
  1424  			if client.IgnoreNotFound(err) != nil {
  1425  				return nil
  1426  			}
  1427  			return fmt.Errorf("error fetching updated application set: %w", err)
  1428  		}
  1429  
  1430  		updatedAppset.Status.Resources = appset.Status.Resources
  1431  		updatedAppset.Status.ResourcesCount = resourcesCount
  1432  
  1433  		// Update the newly fetched object with new status resources
  1434  		err := r.Client.Status().Update(ctx, updatedAppset)
  1435  		if err != nil {
  1436  			return err
  1437  		}
  1438  		updatedAppset.DeepCopyInto(appset)
  1439  		return nil
  1440  	})
  1441  	if err != nil {
  1442  		logCtx.Errorf("unable to set application set status: %v", err)
  1443  		return fmt.Errorf("unable to set application set status: %w", err)
  1444  	}
  1445  	return nil
  1446  }
  1447  
  1448  // setAppSetApplicationStatus updates the ApplicationSet's status field
  1449  // with any new/changed Application statuses.
  1450  func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applicationStatuses []argov1alpha1.ApplicationSetApplicationStatus) error {
  1451  	needToUpdateStatus := false
  1452  
  1453  	if len(applicationStatuses) != len(applicationSet.Status.ApplicationStatus) {
  1454  		logCtx.WithFields(log.Fields{
  1455  			"current_count":  len(applicationSet.Status.ApplicationStatus),
  1456  			"expected_count": len(applicationStatuses),
  1457  		}).Debug("application status count changed")
  1458  		needToUpdateStatus = true
  1459  	} else {
  1460  		for i := range applicationStatuses {
  1461  			appStatus := applicationStatuses[i]
  1462  			idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appStatus.Application)
  1463  			if idx == -1 {
  1464  				logCtx.WithFields(log.Fields{"application": appStatus.Application}).Debug("application not found in current status")
  1465  				needToUpdateStatus = true
  1466  				break
  1467  			}
  1468  			currentStatus := applicationSet.Status.ApplicationStatus[idx]
  1469  			statusChanged := currentStatus.Status != appStatus.Status
  1470  			stepChanged := currentStatus.Step != appStatus.Step
  1471  			messageChanged := currentStatus.Message != appStatus.Message
  1472  
  1473  			if statusChanged || stepChanged || messageChanged {
  1474  				if statusChanged {
  1475  					logCtx.WithFields(log.Fields{"application": appStatus.Application, "previous_status": currentStatus.Status, "new_status": appStatus.Status}).
  1476  						Debug("application status changed")
  1477  				}
  1478  				if stepChanged {
  1479  					logCtx.WithFields(log.Fields{"application": appStatus.Application, "previous_step": currentStatus.Step, "new_step": appStatus.Step}).
  1480  						Debug("application step changed")
  1481  				}
  1482  				if messageChanged {
  1483  					logCtx.WithFields(log.Fields{"application": appStatus.Application}).Debug("application message changed")
  1484  				}
  1485  				needToUpdateStatus = true
  1486  				break
  1487  			}
  1488  		}
  1489  	}
  1490  
  1491  	if needToUpdateStatus {
  1492  		// sort to make sure the array is always in the same order
  1493  		applicationSet.Status.ApplicationStatus = make([]argov1alpha1.ApplicationSetApplicationStatus, len(applicationStatuses))
  1494  		copy(applicationSet.Status.ApplicationStatus, applicationStatuses)
  1495  		sort.Slice(applicationSet.Status.ApplicationStatus, func(i, j int) bool {
  1496  			return applicationSet.Status.ApplicationStatus[i].Application < applicationSet.Status.ApplicationStatus[j].Application
  1497  		})
  1498  
  1499  		// DefaultRetry will retry 5 times with a backoff factor of 1, jitter of 0.1 and a duration of 10ms
  1500  		err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
  1501  			updatedAppset := &argov1alpha1.ApplicationSet{}
  1502  			if err := r.Get(ctx, types.NamespacedName{Namespace: applicationSet.Namespace, Name: applicationSet.Name}, updatedAppset); err != nil {
  1503  				if client.IgnoreNotFound(err) != nil {
  1504  					return nil
  1505  				}
  1506  				return fmt.Errorf("error fetching updated application set: %w", err)
  1507  			}
  1508  
  1509  			updatedAppset.Status.ApplicationStatus = applicationSet.Status.ApplicationStatus
  1510  
  1511  			// Update the newly fetched object with new set of ApplicationStatus
  1512  			err := r.Client.Status().Update(ctx, updatedAppset)
  1513  			if err != nil {
  1514  				return err
  1515  			}
  1516  			updatedAppset.DeepCopyInto(applicationSet)
  1517  			return nil
  1518  		})
  1519  		if err != nil {
  1520  			logCtx.Errorf("unable to set application set status: %v", err)
  1521  			return fmt.Errorf("unable to set application set status: %w", err)
  1522  		}
  1523  	}
  1524  
  1525  	return nil
  1526  }
  1527  
  1528  func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) []argov1alpha1.Application {
  1529  	rolloutApps := []argov1alpha1.Application{}
  1530  	for i := range validApps {
  1531  		pruneEnabled := false
  1532  
  1533  		// ensure that Applications generated with RollingSync do not have an automated sync policy, since the AppSet controller will handle triggering the sync operation instead
  1534  		if validApps[i].Spec.SyncPolicy != nil && validApps[i].Spec.SyncPolicy.IsAutomatedSyncEnabled() {
  1535  			pruneEnabled = validApps[i].Spec.SyncPolicy.Automated.Prune
  1536  			validApps[i].Spec.SyncPolicy.Automated = nil
  1537  		}
  1538  
  1539  		appSetStatusPending := false
  1540  		idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, validApps[i].Name)
  1541  		if idx > -1 && applicationSet.Status.ApplicationStatus[idx].Status == "Pending" {
  1542  			// only trigger a sync for Applications that are in Pending status, since this is governed by maxUpdate
  1543  			appSetStatusPending = true
  1544  		}
  1545  
  1546  		// check appSyncMap to determine which Applications are ready to be updated and which should be skipped
  1547  		if appSyncMap[validApps[i].Name] && appMap[validApps[i].Name].Status.Sync.Status == "OutOfSync" && appSetStatusPending {
  1548  			logCtx.Infof("triggering sync for application: %v, prune enabled: %v", validApps[i].Name, pruneEnabled)
  1549  			validApps[i] = syncApplication(validApps[i], pruneEnabled)
  1550  		}
  1551  		rolloutApps = append(rolloutApps, validApps[i])
  1552  	}
  1553  	return rolloutApps
  1554  }
  1555  
  1556  // used by the RollingSync Progressive Sync strategy to trigger a sync of a particular Application resource
  1557  func syncApplication(application argov1alpha1.Application, prune bool) argov1alpha1.Application {
  1558  	operation := argov1alpha1.Operation{
  1559  		InitiatedBy: argov1alpha1.OperationInitiator{
  1560  			Username:  "applicationset-controller",
  1561  			Automated: true,
  1562  		},
  1563  		Info: []*argov1alpha1.Info{
  1564  			{
  1565  				Name:  "Reason",
  1566  				Value: "ApplicationSet RollingSync triggered a sync of this Application resource",
  1567  			},
  1568  		},
  1569  		Sync: &argov1alpha1.SyncOperation{},
  1570  		// Set a retry limit of 5, aligning with the default in Argo CD's appcontroller auto-sync behavior.
  1571  		// This provides consistency for retry behavior across controllers.
  1572  		// See: https://github.com/argoproj/argo-cd/blob/af9ebac0bb35dc16eb034c1cefaf7c92d1029927/controller/appcontroller.go#L2126
  1573  		Retry: argov1alpha1.RetryStrategy{Limit: 5},
  1574  	}
  1575  
  1576  	if application.Spec.SyncPolicy != nil {
  1577  		if application.Spec.SyncPolicy.Retry != nil {
  1578  			operation.Retry = *application.Spec.SyncPolicy.Retry
  1579  		}
  1580  		if application.Spec.SyncPolicy.SyncOptions != nil {
  1581  			operation.Sync.SyncOptions = application.Spec.SyncPolicy.SyncOptions
  1582  		}
  1583  		operation.Sync.Prune = prune
  1584  	}
  1585  	application.Operation = &operation
  1586  
  1587  	return application
  1588  }
  1589  
  1590  func getApplicationOwnsHandler(enableProgressiveSyncs bool) predicate.Funcs {
  1591  	return predicate.Funcs{
  1592  		CreateFunc: func(e event.CreateEvent) bool {
  1593  			// if we are the owner and there is a create event, we most likely created it and do not need to
  1594  			// re-reconcile
  1595  			if log.IsLevelEnabled(log.DebugLevel) {
  1596  				logFields := log.Fields{"app": ""}
  1597  				app, isApp := e.Object.(*argov1alpha1.Application)
  1598  				if isApp {
  1599  					logFields = applog.GetAppLogFields(app)
  1600  				}
  1601  				log.WithFields(logFields).Debugln("received create event from owning an application")
  1602  			}
  1603  			return false
  1604  		},
  1605  		DeleteFunc: func(e event.DeleteEvent) bool {
  1606  			if log.IsLevelEnabled(log.DebugLevel) {
  1607  				logFields := log.Fields{"app": ""}
  1608  				app, isApp := e.Object.(*argov1alpha1.Application)
  1609  				if isApp {
  1610  					logFields = applog.GetAppLogFields(app)
  1611  				}
  1612  				log.WithFields(logFields).Debugln("received delete event from owning an application")
  1613  			}
  1614  			return true
  1615  		},
  1616  		UpdateFunc: func(e event.UpdateEvent) bool {
  1617  			appOld, isApp := e.ObjectOld.(*argov1alpha1.Application)
  1618  			if !isApp {
  1619  				return false
  1620  			}
  1621  			logCtx := log.WithFields(applog.GetAppLogFields(appOld))
  1622  			logCtx.Debugln("received update event from owning an application")
  1623  			appNew, isApp := e.ObjectNew.(*argov1alpha1.Application)
  1624  			if !isApp {
  1625  				return false
  1626  			}
  1627  			requeue := shouldRequeueForApplication(appOld, appNew, enableProgressiveSyncs)
  1628  			logCtx.WithField("requeue", requeue).Debugf("requeue caused by application %s", appNew.Name)
  1629  			return requeue
  1630  		},
  1631  		GenericFunc: func(e event.GenericEvent) bool {
  1632  			if log.IsLevelEnabled(log.DebugLevel) {
  1633  				logFields := log.Fields{}
  1634  				app, isApp := e.Object.(*argov1alpha1.Application)
  1635  				if isApp {
  1636  					logFields = applog.GetAppLogFields(app)
  1637  				}
  1638  				log.WithFields(logFields).Debugln("received generic event from owning an application")
  1639  			}
  1640  			return true
  1641  		},
  1642  	}
  1643  }
  1644  
  1645  // shouldRequeueForApplication determines when we want to requeue an ApplicationSet for reconciling based on an owned
  1646  // application change
  1647  // The applicationset controller owns a subset of the Application CR.
  1648  // We do not need to re-reconcile if parts of the application change outside the applicationset's control.
  1649  // An example being, Application.ApplicationStatus.ReconciledAt which gets updated by the application controller.
  1650  // Additionally, Application.ObjectMeta.ResourceVersion and Application.ObjectMeta.Generation which are set by K8s.
  1651  func shouldRequeueForApplication(appOld *argov1alpha1.Application, appNew *argov1alpha1.Application, enableProgressiveSyncs bool) bool {
  1652  	if appOld == nil || appNew == nil {
  1653  		return false
  1654  	}
  1655  
  1656  	// the applicationset controller owns the application spec, labels, annotations, and finalizers on the applications
  1657  	// reflect.DeepEqual considers nil slices/maps not equal to empty slices/maps
  1658  	// https://pkg.go.dev/reflect#DeepEqual
  1659  	// ApplicationDestination has an unexported field so we can just use the == for comparison
  1660  	if !cmp.Equal(appOld.Spec, appNew.Spec, cmpopts.EquateEmpty(), cmpopts.EquateComparable(argov1alpha1.ApplicationDestination{})) ||
  1661  		!cmp.Equal(appOld.GetAnnotations(), appNew.GetAnnotations(), cmpopts.EquateEmpty()) ||
  1662  		!cmp.Equal(appOld.GetLabels(), appNew.GetLabels(), cmpopts.EquateEmpty()) ||
  1663  		!cmp.Equal(appOld.GetFinalizers(), appNew.GetFinalizers(), cmpopts.EquateEmpty()) {
  1664  		return true
  1665  	}
  1666  
  1667  	// progressive syncs use the application status for updates. if they differ, requeue to trigger the next progression
  1668  	if enableProgressiveSyncs {
  1669  		if appOld.Status.Health.Status != appNew.Status.Health.Status || appOld.Status.Sync.Status != appNew.Status.Sync.Status {
  1670  			return true
  1671  		}
  1672  
  1673  		if appOld.Status.OperationState != nil && appNew.Status.OperationState != nil {
  1674  			if appOld.Status.OperationState.Phase != appNew.Status.OperationState.Phase ||
  1675  				appOld.Status.OperationState.StartedAt != appNew.Status.OperationState.StartedAt {
  1676  				return true
  1677  			}
  1678  		}
  1679  	}
  1680  
  1681  	return false
  1682  }
  1683  
  1684  func getApplicationSetOwnsHandler(enableProgressiveSyncs bool) predicate.Funcs {
  1685  	return predicate.Funcs{
  1686  		CreateFunc: func(e event.CreateEvent) bool {
  1687  			appSet, isApp := e.Object.(*argov1alpha1.ApplicationSet)
  1688  			if !isApp {
  1689  				return false
  1690  			}
  1691  			log.WithField("applicationset", appSet.QualifiedName()).Debugln("received create event")
  1692  			// Always queue a new applicationset
  1693  			return true
  1694  		},
  1695  		DeleteFunc: func(e event.DeleteEvent) bool {
  1696  			appSet, isApp := e.Object.(*argov1alpha1.ApplicationSet)
  1697  			if !isApp {
  1698  				return false
  1699  			}
  1700  			log.WithField("applicationset", appSet.QualifiedName()).Debugln("received delete event")
  1701  			// Always queue for the deletion of an applicationset
  1702  			return true
  1703  		},
  1704  		UpdateFunc: func(e event.UpdateEvent) bool {
  1705  			appSetOld, isAppSet := e.ObjectOld.(*argov1alpha1.ApplicationSet)
  1706  			if !isAppSet {
  1707  				return false
  1708  			}
  1709  			appSetNew, isAppSet := e.ObjectNew.(*argov1alpha1.ApplicationSet)
  1710  			if !isAppSet {
  1711  				return false
  1712  			}
  1713  			requeue := shouldRequeueForApplicationSet(appSetOld, appSetNew, enableProgressiveSyncs)
  1714  			log.WithField("applicationset", appSetNew.QualifiedName()).
  1715  				WithField("requeue", requeue).Debugln("received update event")
  1716  			return requeue
  1717  		},
  1718  		GenericFunc: func(e event.GenericEvent) bool {
  1719  			appSet, isApp := e.Object.(*argov1alpha1.ApplicationSet)
  1720  			if !isApp {
  1721  				return false
  1722  			}
  1723  			log.WithField("applicationset", appSet.QualifiedName()).Debugln("received generic event")
  1724  			// Always queue for the generic of an applicationset
  1725  			return true
  1726  		},
  1727  	}
  1728  }
  1729  
  1730  // shouldRequeueForApplicationSet determines when we need to requeue an applicationset
  1731  func shouldRequeueForApplicationSet(appSetOld, appSetNew *argov1alpha1.ApplicationSet, enableProgressiveSyncs bool) bool {
  1732  	if appSetOld == nil || appSetNew == nil {
  1733  		return false
  1734  	}
  1735  
  1736  	// Requeue if any ApplicationStatus.Status changed for Progressive sync strategy
  1737  	if enableProgressiveSyncs {
  1738  		if !cmp.Equal(appSetOld.Status.ApplicationStatus, appSetNew.Status.ApplicationStatus, cmpopts.EquateEmpty()) {
  1739  			return true
  1740  		}
  1741  	}
  1742  
  1743  	// only compare the applicationset spec, annotations, labels and finalizers, deletionTimestamp, specifically avoiding
  1744  	// the status field. status is owned by the applicationset controller,
  1745  	// and we do not need to requeue when it does bookkeeping
  1746  	// NB: the ApplicationDestination comes from the ApplicationSpec being embedded
  1747  	// in the ApplicationSetTemplate from the generators
  1748  	if !cmp.Equal(appSetOld.Spec, appSetNew.Spec, cmpopts.EquateEmpty(), cmpopts.EquateComparable(argov1alpha1.ApplicationDestination{})) ||
  1749  		!cmp.Equal(appSetOld.GetLabels(), appSetNew.GetLabels(), cmpopts.EquateEmpty()) ||
  1750  		!cmp.Equal(appSetOld.GetFinalizers(), appSetNew.GetFinalizers(), cmpopts.EquateEmpty()) ||
  1751  		!cmp.Equal(appSetOld.DeletionTimestamp, appSetNew.DeletionTimestamp, cmpopts.EquateEmpty()) {
  1752  		return true
  1753  	}
  1754  
  1755  	// Requeue only when the refresh annotation is newly added to the ApplicationSet.
  1756  	// Changes to other annotations made simultaneously might be missed, but such cases are rare.
  1757  	if !cmp.Equal(appSetOld.GetAnnotations(), appSetNew.GetAnnotations(), cmpopts.EquateEmpty()) {
  1758  		_, oldHasRefreshAnnotation := appSetOld.Annotations[common.AnnotationApplicationSetRefresh]
  1759  		_, newHasRefreshAnnotation := appSetNew.Annotations[common.AnnotationApplicationSetRefresh]
  1760  
  1761  		if oldHasRefreshAnnotation && !newHasRefreshAnnotation {
  1762  			return false
  1763  		}
  1764  		return true
  1765  	}
  1766  
  1767  	return false
  1768  }
  1769  
  1770  var _ handler.EventHandler = &clusterSecretEventHandler{}