github.com/argoproj/argo-cd/v2@v2.10.9/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  	"fmt"
    20  	"reflect"
    21  	"time"
    22  
    23  	log "github.com/sirupsen/logrus"
    24  	corev1 "k8s.io/api/core/v1"
    25  	apierr "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	"k8s.io/apimachinery/pkg/util/intstr"
    30  	"k8s.io/client-go/kubernetes"
    31  	k8scache "k8s.io/client-go/tools/cache"
    32  	"k8s.io/client-go/tools/record"
    33  	ctrl "sigs.k8s.io/controller-runtime"
    34  	"sigs.k8s.io/controller-runtime/pkg/builder"
    35  	"sigs.k8s.io/controller-runtime/pkg/cache"
    36  	"sigs.k8s.io/controller-runtime/pkg/client"
    37  	"sigs.k8s.io/controller-runtime/pkg/controller"
    38  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    39  	"sigs.k8s.io/controller-runtime/pkg/event"
    40  	"sigs.k8s.io/controller-runtime/pkg/handler"
    41  	"sigs.k8s.io/controller-runtime/pkg/predicate"
    42  	"sigs.k8s.io/controller-runtime/pkg/source"
    43  
    44  	"github.com/argoproj/argo-cd/v2/applicationset/generators"
    45  	"github.com/argoproj/argo-cd/v2/applicationset/utils"
    46  	"github.com/argoproj/argo-cd/v2/common"
    47  	"github.com/argoproj/argo-cd/v2/util/db"
    48  	"github.com/argoproj/argo-cd/v2/util/glob"
    49  
    50  	argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    51  	appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
    52  	argoutil "github.com/argoproj/argo-cd/v2/util/argo"
    53  	"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
    54  
    55  	"github.com/argoproj/argo-cd/v2/pkg/apis/application"
    56  )
    57  
    58  const (
    59  	// Rather than importing the whole argocd-notifications controller, just copying the const here
    60  	//   https://github.com/argoproj-labs/argocd-notifications/blob/33d345fa838829bb50fca5c08523aba380d2c12b/pkg/controller/subscriptions.go#L12
    61  	//   https://github.com/argoproj-labs/argocd-notifications/blob/33d345fa838829bb50fca5c08523aba380d2c12b/pkg/controller/state.go#L17
    62  	NotifiedAnnotationKey             = "notified.notifications.argoproj.io"
    63  	ReconcileRequeueOnValidationError = time.Minute * 3
    64  )
    65  
    66  var (
    67  	defaultPreservedAnnotations = []string{
    68  		NotifiedAnnotationKey,
    69  		argov1alpha1.AnnotationKeyRefresh,
    70  	}
    71  )
    72  
    73  // ApplicationSetReconciler reconciles a ApplicationSet object
    74  type ApplicationSetReconciler struct {
    75  	client.Client
    76  	Scheme               *runtime.Scheme
    77  	Recorder             record.EventRecorder
    78  	Generators           map[string]generators.Generator
    79  	ArgoDB               db.ArgoDB
    80  	ArgoAppClientset     appclientset.Interface
    81  	KubeClientset        kubernetes.Interface
    82  	Policy               argov1alpha1.ApplicationsSyncPolicy
    83  	EnablePolicyOverride bool
    84  	utils.Renderer
    85  	ArgoCDNamespace            string
    86  	ApplicationSetNamespaces   []string
    87  	EnableProgressiveSyncs     bool
    88  	SCMRootCAPath              string
    89  	GlobalPreservedAnnotations []string
    90  	GlobalPreservedLabels      []string
    91  	Cache                      cache.Cache
    92  }
    93  
    94  // +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
    95  // +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets/status,verbs=get;update;patch
    96  
    97  func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    98  	logCtx := log.WithField("applicationset", req.NamespacedName)
    99  
   100  	var applicationSetInfo argov1alpha1.ApplicationSet
   101  	parametersGenerated := false
   102  
   103  	if err := r.Get(ctx, req.NamespacedName, &applicationSetInfo); err != nil {
   104  		if client.IgnoreNotFound(err) != nil {
   105  			logCtx.WithError(err).Infof("unable to get ApplicationSet: '%v' ", err)
   106  		}
   107  		return ctrl.Result{}, client.IgnoreNotFound(err)
   108  	}
   109  
   110  	// Do not attempt to further reconcile the ApplicationSet if it is being deleted.
   111  	if applicationSetInfo.ObjectMeta.DeletionTimestamp != nil {
   112  		deleteAllowed := utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete()
   113  		if !deleteAllowed {
   114  			if err := r.removeOwnerReferencesOnDeleteAppSet(ctx, applicationSetInfo); err != nil {
   115  				return ctrl.Result{}, err
   116  			}
   117  			controllerutil.RemoveFinalizer(&applicationSetInfo, argov1alpha1.ResourcesFinalizerName)
   118  			if err := r.Update(ctx, &applicationSetInfo); err != nil {
   119  				return ctrl.Result{}, err
   120  			}
   121  		}
   122  		return ctrl.Result{}, nil
   123  	}
   124  
   125  	// Log a warning if there are unrecognized generators
   126  	_ = utils.CheckInvalidGenerators(&applicationSetInfo)
   127  	// desiredApplications is the main list of all expected Applications from all generators in this appset.
   128  	desiredApplications, applicationSetReason, err := r.generateApplications(logCtx, applicationSetInfo)
   129  	if err != nil {
   130  		_ = r.setApplicationSetStatusCondition(ctx,
   131  			&applicationSetInfo,
   132  			argov1alpha1.ApplicationSetCondition{
   133  				Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   134  				Message: err.Error(),
   135  				Reason:  string(applicationSetReason),
   136  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   137  			}, parametersGenerated,
   138  		)
   139  		return ctrl.Result{}, err
   140  	}
   141  
   142  	parametersGenerated = true
   143  
   144  	validateErrors, err := r.validateGeneratedApplications(ctx, desiredApplications, applicationSetInfo)
   145  	if err != nil {
   146  		// While some generators may return an error that requires user intervention,
   147  		// other generators reference external resources that may change to cause
   148  		// the error to no longer occur. We thus log the error and requeue
   149  		// with a timeout to give this another shot at a later time.
   150  		//
   151  		// Changes to watched resources will cause this to be reconciled sooner than
   152  		// the RequeueAfter time.
   153  		logCtx.Errorf("error occurred during application validation: %s", err.Error())
   154  
   155  		_ = r.setApplicationSetStatusCondition(ctx,
   156  			&applicationSetInfo,
   157  			argov1alpha1.ApplicationSetCondition{
   158  				Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   159  				Message: err.Error(),
   160  				Reason:  argov1alpha1.ApplicationSetReasonApplicationValidationError,
   161  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   162  			}, parametersGenerated,
   163  		)
   164  		return ctrl.Result{RequeueAfter: ReconcileRequeueOnValidationError}, nil
   165  	}
   166  
   167  	// appMap is a name->app collection of Applications in this ApplicationSet.
   168  	appMap := map[string]argov1alpha1.Application{}
   169  	// appSyncMap tracks which apps will be synced during this reconciliation.
   170  	appSyncMap := map[string]bool{}
   171  
   172  	if r.EnableProgressiveSyncs {
   173  		if applicationSetInfo.Spec.Strategy == nil && len(applicationSetInfo.Status.ApplicationStatus) > 0 {
   174  			// If appset used progressive sync but stopped, clean up the progressive sync application statuses
   175  			logCtx.Infof("Removing %v unnecessary AppStatus entries from ApplicationSet %v", len(applicationSetInfo.Status.ApplicationStatus), applicationSetInfo.Name)
   176  
   177  			err := r.setAppSetApplicationStatus(ctx, logCtx, &applicationSetInfo, []argov1alpha1.ApplicationSetApplicationStatus{})
   178  			if err != nil {
   179  				return ctrl.Result{}, fmt.Errorf("failed to clear previous AppSet application statuses for %v: %w", applicationSetInfo.Name, err)
   180  			}
   181  		} else if applicationSetInfo.Spec.Strategy != nil {
   182  			// appset uses progressive sync
   183  			applications, err := r.getCurrentApplications(ctx, applicationSetInfo)
   184  			if err != nil {
   185  				return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
   186  			}
   187  
   188  			for _, app := range applications {
   189  				appMap[app.Name] = app
   190  			}
   191  
   192  			appSyncMap, err = r.performProgressiveSyncs(ctx, logCtx, applicationSetInfo, applications, desiredApplications, appMap)
   193  			if err != nil {
   194  				return ctrl.Result{}, fmt.Errorf("failed to perform progressive sync reconciliation for application set: %w", err)
   195  			}
   196  		}
   197  	}
   198  
   199  	var validApps []argov1alpha1.Application
   200  	for i := range desiredApplications {
   201  		if validateErrors[i] == nil {
   202  			validApps = append(validApps, desiredApplications[i])
   203  		}
   204  	}
   205  
   206  	if len(validateErrors) > 0 {
   207  		var message string
   208  		for _, v := range validateErrors {
   209  			message = v.Error()
   210  			logCtx.Errorf("validation error found during application validation: %s", message)
   211  		}
   212  		if len(validateErrors) > 1 {
   213  			// Only the last message gets added to the appset status, to keep the size reasonable.
   214  			message = fmt.Sprintf("%s (and %d more)", message, len(validateErrors)-1)
   215  		}
   216  		_ = r.setApplicationSetStatusCondition(ctx,
   217  			&applicationSetInfo,
   218  			argov1alpha1.ApplicationSetCondition{
   219  				Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   220  				Message: message,
   221  				Reason:  argov1alpha1.ApplicationSetReasonApplicationValidationError,
   222  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   223  			}, parametersGenerated,
   224  		)
   225  	}
   226  
   227  	if r.EnableProgressiveSyncs {
   228  		// trigger appropriate application syncs if RollingSync strategy is enabled
   229  		if progressiveSyncsStrategyEnabled(&applicationSetInfo, "RollingSync") {
   230  			validApps, err = r.syncValidApplications(logCtx, &applicationSetInfo, appSyncMap, appMap, validApps)
   231  
   232  			if err != nil {
   233  				_ = r.setApplicationSetStatusCondition(ctx,
   234  					&applicationSetInfo,
   235  					argov1alpha1.ApplicationSetCondition{
   236  						Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   237  						Message: err.Error(),
   238  						Reason:  argov1alpha1.ApplicationSetReasonSyncApplicationError,
   239  						Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   240  					}, parametersGenerated,
   241  				)
   242  				return ctrl.Result{}, err
   243  			}
   244  		}
   245  	}
   246  
   247  	if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowUpdate() {
   248  		err = r.createOrUpdateInCluster(ctx, logCtx, applicationSetInfo, validApps)
   249  		if err != nil {
   250  			_ = r.setApplicationSetStatusCondition(ctx,
   251  				&applicationSetInfo,
   252  				argov1alpha1.ApplicationSetCondition{
   253  					Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   254  					Message: err.Error(),
   255  					Reason:  argov1alpha1.ApplicationSetReasonUpdateApplicationError,
   256  					Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   257  				}, parametersGenerated,
   258  			)
   259  			return ctrl.Result{}, err
   260  		}
   261  	} else {
   262  		err = r.createInCluster(ctx, logCtx, applicationSetInfo, validApps)
   263  		if err != nil {
   264  			_ = r.setApplicationSetStatusCondition(ctx,
   265  				&applicationSetInfo,
   266  				argov1alpha1.ApplicationSetCondition{
   267  					Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   268  					Message: err.Error(),
   269  					Reason:  argov1alpha1.ApplicationSetReasonCreateApplicationError,
   270  					Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   271  				}, parametersGenerated,
   272  			)
   273  			return ctrl.Result{}, err
   274  		}
   275  	}
   276  
   277  	if utils.DefaultPolicy(applicationSetInfo.Spec.SyncPolicy, r.Policy, r.EnablePolicyOverride).AllowDelete() {
   278  		err = r.deleteInCluster(ctx, logCtx, applicationSetInfo, desiredApplications)
   279  		if err != nil {
   280  			_ = r.setApplicationSetStatusCondition(ctx,
   281  				&applicationSetInfo,
   282  				argov1alpha1.ApplicationSetCondition{
   283  					Type:    argov1alpha1.ApplicationSetConditionResourcesUpToDate,
   284  					Message: err.Error(),
   285  					Reason:  argov1alpha1.ApplicationSetReasonDeleteApplicationError,
   286  					Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   287  				}, parametersGenerated,
   288  			)
   289  			return ctrl.Result{}, err
   290  		}
   291  	}
   292  
   293  	if applicationSetInfo.RefreshRequired() {
   294  		delete(applicationSetInfo.Annotations, common.AnnotationApplicationSetRefresh)
   295  		err := r.Client.Update(ctx, &applicationSetInfo)
   296  		if err != nil {
   297  			logCtx.Warnf("error occurred while updating ApplicationSet: %v", err)
   298  			_ = r.setApplicationSetStatusCondition(ctx,
   299  				&applicationSetInfo,
   300  				argov1alpha1.ApplicationSetCondition{
   301  					Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   302  					Message: err.Error(),
   303  					Reason:  argov1alpha1.ApplicationSetReasonRefreshApplicationError,
   304  					Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   305  				}, parametersGenerated,
   306  			)
   307  			return ctrl.Result{}, err
   308  		}
   309  	}
   310  
   311  	requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
   312  
   313  	if len(validateErrors) == 0 {
   314  		if err := r.setApplicationSetStatusCondition(ctx,
   315  			&applicationSetInfo,
   316  			argov1alpha1.ApplicationSetCondition{
   317  				Type:    argov1alpha1.ApplicationSetConditionResourcesUpToDate,
   318  				Message: "All applications have been generated successfully",
   319  				Reason:  argov1alpha1.ApplicationSetReasonApplicationSetUpToDate,
   320  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   321  			}, parametersGenerated,
   322  		); err != nil {
   323  			return ctrl.Result{}, err
   324  		}
   325  	} else if requeueAfter == time.Duration(0) {
   326  		// Ensure that the request is requeued if there are validation errors.
   327  		requeueAfter = ReconcileRequeueOnValidationError
   328  	}
   329  
   330  	logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
   331  
   332  	return ctrl.Result{
   333  		RequeueAfter: requeueAfter,
   334  	}, nil
   335  }
   336  
   337  func getParametersGeneratedCondition(parametersGenerated bool, message string) argov1alpha1.ApplicationSetCondition {
   338  	var paramtersGeneratedCondition argov1alpha1.ApplicationSetCondition
   339  	if parametersGenerated {
   340  		paramtersGeneratedCondition = argov1alpha1.ApplicationSetCondition{
   341  			Type:    argov1alpha1.ApplicationSetConditionParametersGenerated,
   342  			Message: "Successfully generated parameters for all Applications",
   343  			Reason:  argov1alpha1.ApplicationSetReasonParametersGenerated,
   344  			Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   345  		}
   346  	} else {
   347  		paramtersGeneratedCondition = argov1alpha1.ApplicationSetCondition{
   348  			Type:    argov1alpha1.ApplicationSetConditionParametersGenerated,
   349  			Message: message,
   350  			Reason:  argov1alpha1.ApplicationSetReasonErrorOccurred,
   351  			Status:  argov1alpha1.ApplicationSetConditionStatusFalse,
   352  		}
   353  	}
   354  	return paramtersGeneratedCondition
   355  }
   356  
   357  func getResourceUpToDateCondition(errorOccurred bool, message string, reason string) argov1alpha1.ApplicationSetCondition {
   358  	var resourceUpToDateCondition argov1alpha1.ApplicationSetCondition
   359  	if errorOccurred {
   360  		resourceUpToDateCondition = argov1alpha1.ApplicationSetCondition{
   361  			Type:    argov1alpha1.ApplicationSetConditionResourcesUpToDate,
   362  			Message: message,
   363  			Reason:  reason,
   364  			Status:  argov1alpha1.ApplicationSetConditionStatusFalse,
   365  		}
   366  	} else {
   367  		resourceUpToDateCondition = argov1alpha1.ApplicationSetCondition{
   368  			Type:    argov1alpha1.ApplicationSetConditionResourcesUpToDate,
   369  			Message: "ApplicationSet up to date",
   370  			Reason:  argov1alpha1.ApplicationSetReasonApplicationSetUpToDate,
   371  			Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
   372  		}
   373  	}
   374  	return resourceUpToDateCondition
   375  }
   376  
   377  func (r *ApplicationSetReconciler) setApplicationSetStatusCondition(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, condition argov1alpha1.ApplicationSetCondition, paramtersGenerated bool) error {
   378  	// check if error occurred during reconcile process
   379  	errOccurred := condition.Type == argov1alpha1.ApplicationSetConditionErrorOccurred
   380  
   381  	var errOccurredCondition argov1alpha1.ApplicationSetCondition
   382  
   383  	if errOccurred {
   384  		errOccurredCondition = condition
   385  	} else {
   386  		errOccurredCondition = argov1alpha1.ApplicationSetCondition{
   387  			Type:    argov1alpha1.ApplicationSetConditionErrorOccurred,
   388  			Message: "Successfully generated parameters for all Applications",
   389  			Reason:  argov1alpha1.ApplicationSetReasonApplicationSetUpToDate,
   390  			Status:  argov1alpha1.ApplicationSetConditionStatusFalse,
   391  		}
   392  	}
   393  
   394  	paramtersGeneratedCondition := getParametersGeneratedCondition(paramtersGenerated, condition.Message)
   395  	resourceUpToDateCondition := getResourceUpToDateCondition(errOccurred, condition.Message, condition.Reason)
   396  
   397  	newConditions := []argov1alpha1.ApplicationSetCondition{errOccurredCondition, paramtersGeneratedCondition, resourceUpToDateCondition}
   398  
   399  	needToUpdateConditions := false
   400  	for _, condition := range newConditions {
   401  		// do nothing if appset already has same condition
   402  		for _, c := range applicationSet.Status.Conditions {
   403  			if c.Type == condition.Type && (c.Reason != condition.Reason || c.Status != condition.Status || c.Message != condition.Message) {
   404  				needToUpdateConditions = true
   405  				break
   406  			}
   407  		}
   408  	}
   409  	evaluatedTypes := map[argov1alpha1.ApplicationSetConditionType]bool{
   410  		argov1alpha1.ApplicationSetConditionErrorOccurred:       true,
   411  		argov1alpha1.ApplicationSetConditionParametersGenerated: true,
   412  		argov1alpha1.ApplicationSetConditionResourcesUpToDate:   true,
   413  	}
   414  
   415  	if needToUpdateConditions || len(applicationSet.Status.Conditions) < 3 {
   416  		// fetch updated Application Set object before updating it
   417  		namespacedName := types.NamespacedName{Namespace: applicationSet.Namespace, Name: applicationSet.Name}
   418  		if err := r.Get(ctx, namespacedName, applicationSet); err != nil {
   419  			if client.IgnoreNotFound(err) != nil {
   420  				return nil
   421  			}
   422  			return fmt.Errorf("error fetching updated application set: %v", err)
   423  		}
   424  
   425  		applicationSet.Status.SetConditions(
   426  			newConditions, evaluatedTypes,
   427  		)
   428  
   429  		// Update the newly fetched object with new set of conditions
   430  		err := r.Client.Status().Update(ctx, applicationSet)
   431  		if err != nil && !apierr.IsNotFound(err) {
   432  			return fmt.Errorf("unable to set application set condition: %v", err)
   433  		}
   434  	}
   435  
   436  	return nil
   437  }
   438  
   439  // validateGeneratedApplications uses the Argo CD validation functions to verify the correctness of the
   440  // generated applications.
   441  func (r *ApplicationSetReconciler) validateGeneratedApplications(ctx context.Context, desiredApplications []argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet) (map[int]error, error) {
   442  	errorsByIndex := map[int]error{}
   443  	namesSet := map[string]bool{}
   444  	for i, app := range desiredApplications {
   445  
   446  		if !namesSet[app.Name] {
   447  			namesSet[app.Name] = true
   448  		} else {
   449  			errorsByIndex[i] = fmt.Errorf("ApplicationSet %s contains applications with duplicate name: %s", applicationSetInfo.Name, app.Name)
   450  			continue
   451  		}
   452  		_, err := r.ArgoAppClientset.ArgoprojV1alpha1().AppProjects(r.ArgoCDNamespace).Get(ctx, app.Spec.GetProject(), metav1.GetOptions{})
   453  		if err != nil {
   454  			if apierr.IsNotFound(err) {
   455  				errorsByIndex[i] = fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
   456  				continue
   457  			}
   458  			return nil, err
   459  		}
   460  
   461  		if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil {
   462  			errorsByIndex[i] = fmt.Errorf("application destination spec is invalid: %s", err.Error())
   463  			continue
   464  		}
   465  
   466  	}
   467  
   468  	return errorsByIndex, nil
   469  }
   470  
   471  func (r *ApplicationSetReconciler) getMinRequeueAfter(applicationSetInfo *argov1alpha1.ApplicationSet) time.Duration {
   472  	var res time.Duration
   473  	for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
   474  
   475  		relevantGenerators := generators.GetRelevantGenerators(&requestedGenerator, r.Generators)
   476  
   477  		for _, g := range relevantGenerators {
   478  			t := g.GetRequeueAfter(&requestedGenerator)
   479  
   480  			if res == 0 {
   481  				res = t
   482  			} else if t != 0 && t < res {
   483  				res = t
   484  			}
   485  		}
   486  	}
   487  
   488  	return res
   489  }
   490  
   491  func getTempApplication(applicationSetTemplate argov1alpha1.ApplicationSetTemplate) *argov1alpha1.Application {
   492  	var tmplApplication argov1alpha1.Application
   493  	tmplApplication.Annotations = applicationSetTemplate.Annotations
   494  	tmplApplication.Labels = applicationSetTemplate.Labels
   495  	tmplApplication.Namespace = applicationSetTemplate.Namespace
   496  	tmplApplication.Name = applicationSetTemplate.Name
   497  	tmplApplication.Spec = applicationSetTemplate.Spec
   498  	tmplApplication.Finalizers = applicationSetTemplate.Finalizers
   499  
   500  	return &tmplApplication
   501  }
   502  
   503  func (r *ApplicationSetReconciler) generateApplications(logCtx *log.Entry, applicationSetInfo argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, argov1alpha1.ApplicationSetReasonType, error) {
   504  	var res []argov1alpha1.Application
   505  
   506  	var firstError error
   507  	var applicationSetReason argov1alpha1.ApplicationSetReasonType
   508  
   509  	for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
   510  		t, err := generators.Transform(requestedGenerator, r.Generators, applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]interface{}{})
   511  		if err != nil {
   512  			logCtx.WithError(err).WithField("generator", requestedGenerator).
   513  				Error("error generating application from params")
   514  			if firstError == nil {
   515  				firstError = err
   516  				applicationSetReason = argov1alpha1.ApplicationSetReasonApplicationParamsGenerationError
   517  			}
   518  			continue
   519  		}
   520  
   521  		for _, a := range t {
   522  			tmplApplication := getTempApplication(a.Template)
   523  
   524  			for _, p := range a.Params {
   525  				app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
   526  
   527  				if err != nil {
   528  					logCtx.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
   529  						Error("error generating application from params")
   530  
   531  					if firstError == nil {
   532  						firstError = err
   533  						applicationSetReason = argov1alpha1.ApplicationSetReasonRenderTemplateParamsError
   534  					}
   535  					continue
   536  				}
   537  
   538  				if applicationSetInfo.Spec.TemplatePatch != nil {
   539  					patchedApplication, err := r.applyTemplatePatch(app, applicationSetInfo, p)
   540  
   541  					if err != nil {
   542  						log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
   543  							Error("error generating application from params")
   544  
   545  						if firstError == nil {
   546  							firstError = err
   547  							applicationSetReason = argov1alpha1.ApplicationSetReasonRenderTemplateParamsError
   548  						}
   549  						continue
   550  					}
   551  
   552  					app = patchedApplication
   553  				}
   554  
   555  				res = append(res, *app)
   556  			}
   557  		}
   558  
   559  		logCtx.WithField("generator", requestedGenerator).Infof("generated %d applications", len(res))
   560  		logCtx.WithField("generator", requestedGenerator).Debugf("apps from generator: %+v", res)
   561  	}
   562  
   563  	return res, applicationSetReason, firstError
   564  }
   565  
   566  func (r *ApplicationSetReconciler) applyTemplatePatch(app *argov1alpha1.Application, applicationSetInfo argov1alpha1.ApplicationSet, params map[string]interface{}) (*argov1alpha1.Application, error) {
   567  	replacedTemplate, err := r.Renderer.Replace(*applicationSetInfo.Spec.TemplatePatch, params, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)
   568  
   569  	if err != nil {
   570  		return nil, fmt.Errorf("error replacing values in templatePatch: %w", err)
   571  	}
   572  
   573  	return applyTemplatePatch(app, replacedTemplate)
   574  }
   575  
   576  func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
   577  	return predicate.Funcs{
   578  		CreateFunc: func(e event.CreateEvent) bool {
   579  			return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), false)
   580  		},
   581  	}
   582  }
   583  
   584  func appControllerIndexer(rawObj client.Object) []string {
   585  	// grab the job object, extract the owner...
   586  	app := rawObj.(*argov1alpha1.Application)
   587  	owner := metav1.GetControllerOf(app)
   588  	if owner == nil {
   589  		return nil
   590  	}
   591  	// ...make sure it's a application set...
   592  	if owner.APIVersion != argov1alpha1.SchemeGroupVersion.String() || owner.Kind != "ApplicationSet" {
   593  		return nil
   594  	}
   595  
   596  	// ...and if so, return it
   597  	return []string{owner.Name}
   598  }
   599  
   600  func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProgressiveSyncs bool, maxConcurrentReconciliations int) error {
   601  	if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &argov1alpha1.Application{}, ".metadata.controller", appControllerIndexer); err != nil {
   602  		return fmt.Errorf("error setting up with manager: %w", err)
   603  	}
   604  
   605  	ownsHandler := getOwnsHandlerPredicates(enableProgressiveSyncs)
   606  
   607  	return ctrl.NewControllerManagedBy(mgr).WithOptions(controller.Options{
   608  		MaxConcurrentReconciles: maxConcurrentReconciliations,
   609  	}).For(&argov1alpha1.ApplicationSet{}).
   610  		Owns(&argov1alpha1.Application{}, builder.WithPredicates(ownsHandler)).
   611  		WithEventFilter(ignoreNotAllowedNamespaces(r.ApplicationSetNamespaces)).
   612  		Watches(
   613  			&source.Kind{Type: &corev1.Secret{}},
   614  			&clusterSecretEventHandler{
   615  				Client: mgr.GetClient(),
   616  				Log:    log.WithField("type", "createSecretEventHandler"),
   617  			}).
   618  		// TODO: also watch Applications and respond on changes if we own them.
   619  		Complete(r)
   620  }
   621  
   622  func (r *ApplicationSetReconciler) updateCache(ctx context.Context, obj client.Object, logger *log.Entry) {
   623  	informer, err := r.Cache.GetInformer(ctx, obj)
   624  	if err != nil {
   625  		logger.Errorf("failed to get informer: %v", err)
   626  		return
   627  	}
   628  	// The controller runtime abstract away informers creation
   629  	// so unfortunately could not find any other way to access informer store.
   630  	k8sInformer, ok := informer.(k8scache.SharedInformer)
   631  	if !ok {
   632  		logger.Error("informer is not a kubernetes informer")
   633  		return
   634  	}
   635  	if err := k8sInformer.GetStore().Update(obj); err != nil {
   636  		logger.Errorf("failed to update cache: %v", err)
   637  		return
   638  	}
   639  }
   640  
   641  // createOrUpdateInCluster will create / update application resources in the cluster.
   642  // - For new applications, it will call create
   643  // - For existing application, it will call update
   644  // The function also adds owner reference to all applications, and uses it to delete them.
   645  func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
   646  
   647  	var firstError error
   648  	// Creates or updates the application in appList
   649  	for _, generatedApp := range desiredApplications {
   650  		// The app's namespace must be the same as the AppSet's namespace to preserve the appsets-in-any-namespace
   651  		// security boundary.
   652  		generatedApp.Namespace = applicationSet.Namespace
   653  
   654  		appLog := logCtx.WithFields(log.Fields{"app": generatedApp.QualifiedName()})
   655  
   656  		// Normalize to avoid fighting with the application controller.
   657  		generatedApp.Spec = *argoutil.NormalizeApplicationSpec(&generatedApp.Spec)
   658  
   659  		found := &argov1alpha1.Application{
   660  			ObjectMeta: metav1.ObjectMeta{
   661  				Name:      generatedApp.Name,
   662  				Namespace: generatedApp.Namespace,
   663  			},
   664  			TypeMeta: metav1.TypeMeta{
   665  				Kind:       application.ApplicationKind,
   666  				APIVersion: "argoproj.io/v1alpha1",
   667  			},
   668  		}
   669  
   670  		action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, normalizers.IgnoreNormalizerOpts{}, found, func() error {
   671  			// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
   672  			found.Spec = generatedApp.Spec
   673  
   674  			// allow setting the Operation field to trigger a sync operation on an Application
   675  			if generatedApp.Operation != nil {
   676  				found.Operation = generatedApp.Operation
   677  			}
   678  
   679  			preservedAnnotations := make([]string, 0)
   680  			preservedLabels := make([]string, 0)
   681  
   682  			if applicationSet.Spec.PreservedFields != nil {
   683  				preservedAnnotations = append(preservedAnnotations, applicationSet.Spec.PreservedFields.Annotations...)
   684  				preservedLabels = append(preservedLabels, applicationSet.Spec.PreservedFields.Labels...)
   685  			}
   686  
   687  			if len(r.GlobalPreservedAnnotations) > 0 {
   688  				preservedAnnotations = append(preservedAnnotations, r.GlobalPreservedAnnotations...)
   689  			}
   690  
   691  			if len(r.GlobalPreservedLabels) > 0 {
   692  				preservedLabels = append(preservedLabels, r.GlobalPreservedLabels...)
   693  			}
   694  
   695  			// Preserve specially treated argo cd annotations:
   696  			// * https://github.com/argoproj/applicationset/issues/180
   697  			// * https://github.com/argoproj/argo-cd/issues/10500
   698  			preservedAnnotations = append(preservedAnnotations, defaultPreservedAnnotations...)
   699  
   700  			for _, key := range preservedAnnotations {
   701  				if state, exists := found.ObjectMeta.Annotations[key]; exists {
   702  					if generatedApp.Annotations == nil {
   703  						generatedApp.Annotations = map[string]string{}
   704  					}
   705  					generatedApp.Annotations[key] = state
   706  				}
   707  			}
   708  
   709  			for _, key := range preservedLabels {
   710  				if state, exists := found.ObjectMeta.Labels[key]; exists {
   711  					if generatedApp.Labels == nil {
   712  						generatedApp.Labels = map[string]string{}
   713  					}
   714  					generatedApp.Labels[key] = state
   715  				}
   716  			}
   717  
   718  			found.ObjectMeta.Annotations = generatedApp.Annotations
   719  
   720  			found.ObjectMeta.Finalizers = generatedApp.Finalizers
   721  			found.ObjectMeta.Labels = generatedApp.Labels
   722  
   723  			return controllerutil.SetControllerReference(&applicationSet, found, r.Scheme)
   724  		})
   725  
   726  		if err != nil {
   727  			appLog.WithError(err).WithField("action", action).Errorf("failed to %s Application", action)
   728  			if firstError == nil {
   729  				firstError = err
   730  			}
   731  			continue
   732  		}
   733  		r.updateCache(ctx, found, appLog)
   734  
   735  		if action != controllerutil.OperationResultNone {
   736  			// Don't pollute etcd with "unchanged Application" events
   737  			r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, fmt.Sprint(action), "%s Application %q", action, generatedApp.Name)
   738  			appLog.Logf(log.InfoLevel, "%s Application", action)
   739  		} else {
   740  			// "unchanged Application" can be inferred by Reconcile Complete with no action being listed
   741  			// Or enable debug logging
   742  			appLog.Logf(log.DebugLevel, "%s Application", action)
   743  		}
   744  	}
   745  	return firstError
   746  }
   747  
   748  // createInCluster will filter from the desiredApplications only the application that needs to be created
   749  // Then it will call createOrUpdateInCluster to do the actual create
   750  func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
   751  
   752  	var createApps []argov1alpha1.Application
   753  	current, err := r.getCurrentApplications(ctx, applicationSet)
   754  	if err != nil {
   755  		return fmt.Errorf("error getting current applications: %w", err)
   756  	}
   757  
   758  	m := make(map[string]bool) // Will holds the app names that are current in the cluster
   759  
   760  	for _, app := range current {
   761  		m[app.Name] = true
   762  	}
   763  
   764  	// filter applications that are not in m[string]bool (new to the cluster)
   765  	for _, app := range desiredApplications {
   766  		_, exists := m[app.Name]
   767  
   768  		if !exists {
   769  			createApps = append(createApps, app)
   770  		}
   771  	}
   772  
   773  	return r.createOrUpdateInCluster(ctx, logCtx, applicationSet, createApps)
   774  }
   775  
   776  func (r *ApplicationSetReconciler) getCurrentApplications(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) {
   777  	var current argov1alpha1.ApplicationList
   778  	err := r.Client.List(ctx, &current, client.MatchingFields{".metadata.controller": applicationSet.Name}, client.InNamespace(applicationSet.Namespace))
   779  
   780  	if err != nil {
   781  		return nil, fmt.Errorf("error retrieving applications: %w", err)
   782  	}
   783  
   784  	return current.Items, nil
   785  }
   786  
   787  // deleteInCluster will delete Applications that are currently on the cluster, but not in appList.
   788  // The function must be called after all generators had been called and generated applications
   789  func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
   790  	// settingsMgr := settings.NewSettingsManager(context.TODO(), r.KubeClientset, applicationSet.Namespace)
   791  	// argoDB := db.NewDB(applicationSet.Namespace, settingsMgr, r.KubeClientset)
   792  	// clusterList, err := argoDB.ListClusters(ctx)
   793  	clusterList, err := utils.ListClusters(ctx, r.KubeClientset, r.ArgoCDNamespace)
   794  	if err != nil {
   795  		return fmt.Errorf("error listing clusters: %w", err)
   796  	}
   797  
   798  	// Save current applications to be able to delete the ones that are not in appList
   799  	current, err := r.getCurrentApplications(ctx, applicationSet)
   800  	if err != nil {
   801  		return fmt.Errorf("error getting current applications: %w", err)
   802  	}
   803  
   804  	m := make(map[string]bool) // Will holds the app names in appList for the deletion process
   805  
   806  	for _, app := range desiredApplications {
   807  		m[app.Name] = true
   808  	}
   809  
   810  	// Delete apps that are not in m[string]bool
   811  	var firstError error
   812  	for _, app := range current {
   813  		logCtx = logCtx.WithField("app", app.QualifiedName())
   814  		_, exists := m[app.Name]
   815  
   816  		if !exists {
   817  
   818  			// Removes the Argo CD resources finalizer if the application contains an invalid target (eg missing cluster)
   819  			err := r.removeFinalizerOnInvalidDestination(ctx, applicationSet, &app, clusterList, logCtx)
   820  			if err != nil {
   821  				logCtx.WithError(err).Error("failed to update Application")
   822  				if firstError != nil {
   823  					firstError = err
   824  				}
   825  				continue
   826  			}
   827  
   828  			err = r.Client.Delete(ctx, &app)
   829  			if err != nil {
   830  				logCtx.WithError(err).Error("failed to delete Application")
   831  				if firstError != nil {
   832  					firstError = err
   833  				}
   834  				continue
   835  			}
   836  			r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, "Deleted", "Deleted Application %q", app.Name)
   837  			logCtx.Log(log.InfoLevel, "Deleted application")
   838  		}
   839  	}
   840  	return firstError
   841  }
   842  
   843  // removeFinalizerOnInvalidDestination removes the Argo CD resources finalizer if the application contains an invalid target (eg missing cluster)
   844  func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, app *argov1alpha1.Application, clusterList *argov1alpha1.ClusterList, appLog *log.Entry) error {
   845  
   846  	// Only check if the finalizers need to be removed IF there are finalizers to remove
   847  	if len(app.Finalizers) == 0 {
   848  		return nil
   849  	}
   850  
   851  	var validDestination bool
   852  
   853  	// Detect if the destination is invalid (name doesn't correspond to a matching cluster)
   854  	if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, r.ArgoCDNamespace); err != nil {
   855  		appLog.Warnf("The destination cluster for %s couldn't be found: %v", app.Name, err)
   856  		validDestination = false
   857  	} else {
   858  
   859  		// Detect if the destination's server field does not match an existing cluster
   860  
   861  		matchingCluster := false
   862  		for _, cluster := range clusterList.Items {
   863  
   864  			// Server fields must match. Note that ValidateDestination ensures that the server field is set, if applicable.
   865  			if app.Spec.Destination.Server != cluster.Server {
   866  				continue
   867  			}
   868  
   869  			// The name must match, if it is not empty
   870  			if app.Spec.Destination.Name != "" && cluster.Name != app.Spec.Destination.Name {
   871  				continue
   872  			}
   873  
   874  			matchingCluster = true
   875  			break
   876  		}
   877  
   878  		if !matchingCluster {
   879  			appLog.Warnf("A match for the destination cluster for %s, by server url, couldn't be found.", app.Name)
   880  		}
   881  
   882  		validDestination = matchingCluster
   883  	}
   884  	// If the destination is invalid (for example the cluster is no longer defined), then remove
   885  	// the application finalizers to avoid triggering Argo CD bug #5817
   886  	if !validDestination {
   887  
   888  		// Filter out the Argo CD finalizer from the finalizer list
   889  		var newFinalizers []string
   890  		for _, existingFinalizer := range app.Finalizers {
   891  			if existingFinalizer != argov1alpha1.ResourcesFinalizerName { // only remove this one
   892  				newFinalizers = append(newFinalizers, existingFinalizer)
   893  			}
   894  		}
   895  
   896  		// If the finalizer length changed (due to filtering out an Argo finalizer), update the finalizer list on the app
   897  		if len(newFinalizers) != len(app.Finalizers) {
   898  			updated := app.DeepCopy()
   899  			updated.Finalizers = newFinalizers
   900  			patch := client.MergeFrom(app)
   901  			if log.IsLevelEnabled(log.DebugLevel) {
   902  				utils.LogPatch(appLog, patch, updated)
   903  			}
   904  			if err := r.Client.Patch(ctx, updated, patch); err != nil {
   905  				return fmt.Errorf("error updating finalizers: %w", err)
   906  			}
   907  			r.updateCache(ctx, updated, appLog)
   908  			// Application must have updated list of finalizers
   909  			updated.DeepCopyInto(app)
   910  
   911  			r.Recorder.Eventf(&applicationSet, corev1.EventTypeNormal, "Updated", "Updated Application %q finalizer before deletion, because application has an invalid destination", app.Name)
   912  			appLog.Log(log.InfoLevel, "Updating application finalizer before deletion, because application has an invalid destination")
   913  		}
   914  	}
   915  
   916  	return nil
   917  }
   918  
   919  func (r *ApplicationSetReconciler) removeOwnerReferencesOnDeleteAppSet(ctx context.Context, applicationSet argov1alpha1.ApplicationSet) error {
   920  	applications, err := r.getCurrentApplications(ctx, applicationSet)
   921  	if err != nil {
   922  		return err
   923  	}
   924  
   925  	for _, app := range applications {
   926  		app.SetOwnerReferences([]metav1.OwnerReference{})
   927  		err := r.Client.Update(ctx, &app)
   928  		if err != nil {
   929  			return err
   930  		}
   931  	}
   932  
   933  	return nil
   934  }
   935  
   936  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) {
   937  
   938  	appDependencyList, appStepMap, err := r.buildAppDependencyList(logCtx, appset, desiredApplications)
   939  	if err != nil {
   940  		return nil, fmt.Errorf("failed to build app dependency list: %w", err)
   941  	}
   942  
   943  	_, err = r.updateApplicationSetApplicationStatus(ctx, logCtx, &appset, applications, appStepMap)
   944  	if err != nil {
   945  		return nil, fmt.Errorf("failed to update applicationset app status: %w", err)
   946  	}
   947  
   948  	logCtx.Infof("ApplicationSet %v step list:", appset.Name)
   949  	for i, step := range appDependencyList {
   950  		logCtx.Infof("step %v: %+v", i+1, step)
   951  	}
   952  
   953  	appSyncMap, err := r.buildAppSyncMap(ctx, appset, appDependencyList, appMap)
   954  	if err != nil {
   955  		return nil, fmt.Errorf("failed to build app sync map: %w", err)
   956  	}
   957  
   958  	logCtx.Infof("Application allowed to sync before maxUpdate?: %+v", appSyncMap)
   959  
   960  	_, err = r.updateApplicationSetApplicationStatusProgress(ctx, logCtx, &appset, appSyncMap, appStepMap, appMap)
   961  	if err != nil {
   962  		return nil, fmt.Errorf("failed to update applicationset application status progress: %w", err)
   963  	}
   964  
   965  	_, err = r.updateApplicationSetApplicationStatusConditions(ctx, &appset)
   966  	if err != nil {
   967  		return nil, fmt.Errorf("failed to update applicationset application status conditions: %w", err)
   968  	}
   969  
   970  	return appSyncMap, nil
   971  }
   972  
   973  // this list tracks which Applications belong to each RollingUpdate step
   974  func (r *ApplicationSetReconciler) buildAppDependencyList(logCtx *log.Entry, applicationSet argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([][]string, map[string]int, error) {
   975  
   976  	if applicationSet.Spec.Strategy == nil || applicationSet.Spec.Strategy.Type == "" || applicationSet.Spec.Strategy.Type == "AllAtOnce" {
   977  		return [][]string{}, map[string]int{}, nil
   978  	}
   979  
   980  	steps := []argov1alpha1.ApplicationSetRolloutStep{}
   981  	if progressiveSyncsStrategyEnabled(&applicationSet, "RollingSync") {
   982  		steps = applicationSet.Spec.Strategy.RollingSync.Steps
   983  	}
   984  
   985  	appDependencyList := make([][]string, 0)
   986  	for range steps {
   987  		appDependencyList = append(appDependencyList, make([]string, 0))
   988  	}
   989  
   990  	appStepMap := map[string]int{}
   991  
   992  	// use applicationLabelSelectors to filter generated Applications into steps and status by name
   993  	for _, app := range applications {
   994  		for i, step := range steps {
   995  
   996  			selected := true // default to true, assuming the current Application is a match for the given step matchExpression
   997  
   998  			for _, matchExpression := range step.MatchExpressions {
   999  
  1000  				if val, ok := app.Labels[matchExpression.Key]; ok {
  1001  					valueMatched := labelMatchedExpression(logCtx, val, matchExpression)
  1002  
  1003  					if !valueMatched { // none of the matchExpression values was a match with the Application's labels
  1004  						selected = false
  1005  						break
  1006  					}
  1007  				} else if matchExpression.Operator == "In" {
  1008  					selected = false // no matching label key with "In" operator means this Application will not be included in the current step
  1009  					break
  1010  				}
  1011  			}
  1012  
  1013  			if selected {
  1014  				appDependencyList[i] = append(appDependencyList[i], app.Name)
  1015  				if val, ok := appStepMap[app.Name]; ok {
  1016  					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)
  1017  				} else {
  1018  					appStepMap[app.Name] = i
  1019  				}
  1020  			}
  1021  		}
  1022  	}
  1023  
  1024  	return appDependencyList, appStepMap, nil
  1025  }
  1026  
  1027  func labelMatchedExpression(logCtx *log.Entry, val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool {
  1028  	if matchExpression.Operator != "In" && matchExpression.Operator != "NotIn" {
  1029  		logCtx.Errorf("skipping AppSet rollingUpdate step Application selection, invalid matchExpression operator provided: %q ", matchExpression.Operator)
  1030  		return false
  1031  	}
  1032  
  1033  	// if operator == In, default to false
  1034  	// if operator == NotIn, default to true
  1035  	valueMatched := matchExpression.Operator == "NotIn"
  1036  
  1037  	for _, value := range matchExpression.Values {
  1038  		if val == value {
  1039  			// first "In" match returns true
  1040  			// first "NotIn" match returns false
  1041  			return matchExpression.Operator == "In"
  1042  		}
  1043  	}
  1044  	return valueMatched
  1045  }
  1046  
  1047  // this map is used to determine which stage of Applications are ready to be updated in the reconciler loop
  1048  func (r *ApplicationSetReconciler) buildAppSyncMap(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, appDependencyList [][]string, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
  1049  	appSyncMap := map[string]bool{}
  1050  	syncEnabled := true
  1051  
  1052  	// healthy stages and the first non-healthy stage should have sync enabled
  1053  	// every stage after should have sync disabled
  1054  
  1055  	for i := range appDependencyList {
  1056  		// set the syncEnabled boolean for every Application in the current step
  1057  		for _, appName := range appDependencyList[i] {
  1058  			appSyncMap[appName] = syncEnabled
  1059  		}
  1060  
  1061  		// detect if we need to halt before progressing to the next step
  1062  		for _, appName := range appDependencyList[i] {
  1063  
  1064  			idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appName)
  1065  			if idx == -1 {
  1066  				// no Application status found, likely because the Application is being newly created
  1067  				syncEnabled = false
  1068  				break
  1069  			}
  1070  
  1071  			appStatus := applicationSet.Status.ApplicationStatus[idx]
  1072  
  1073  			if app, ok := appMap[appName]; ok {
  1074  
  1075  				syncEnabled = appSyncEnabledForNextStep(&applicationSet, app, appStatus)
  1076  				if !syncEnabled {
  1077  					break
  1078  				}
  1079  			} else {
  1080  				// application name not found in the list of applications managed by this ApplicationSet, maybe because it's being deleted
  1081  				syncEnabled = false
  1082  				break
  1083  			}
  1084  		}
  1085  	}
  1086  
  1087  	return appSyncMap, nil
  1088  }
  1089  
  1090  func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1alpha1.Application, appStatus argov1alpha1.ApplicationSetApplicationStatus) bool {
  1091  
  1092  	if progressiveSyncsStrategyEnabled(appset, "RollingSync") {
  1093  		// we still need to complete the current step if the Application is not yet Healthy or there are still pending Application changes
  1094  		return isApplicationHealthy(app) && appStatus.Status == "Healthy"
  1095  	}
  1096  
  1097  	return true
  1098  }
  1099  
  1100  func progressiveSyncsStrategyEnabled(appset *argov1alpha1.ApplicationSet, strategyType string) bool {
  1101  	if appset.Spec.Strategy == nil || appset.Spec.Strategy.Type != strategyType {
  1102  		return false
  1103  	}
  1104  
  1105  	if strategyType == "RollingSync" && appset.Spec.Strategy.RollingSync == nil {
  1106  		return false
  1107  	}
  1108  
  1109  	return true
  1110  }
  1111  
  1112  func isApplicationHealthy(app argov1alpha1.Application) bool {
  1113  	healthStatusString, syncStatusString, operationPhaseString := statusStrings(app)
  1114  
  1115  	if healthStatusString == "Healthy" && syncStatusString != "OutOfSync" && (operationPhaseString == "Succeeded" || operationPhaseString == "") {
  1116  		return true
  1117  	}
  1118  	return false
  1119  }
  1120  
  1121  func statusStrings(app argov1alpha1.Application) (string, string, string) {
  1122  	healthStatusString := string(app.Status.Health.Status)
  1123  	syncStatusString := string(app.Status.Sync.Status)
  1124  	operationPhaseString := ""
  1125  	if app.Status.OperationState != nil {
  1126  		operationPhaseString = string(app.Status.OperationState.Phase)
  1127  	}
  1128  
  1129  	return healthStatusString, syncStatusString, operationPhaseString
  1130  }
  1131  
  1132  // check the status of each Application's status and promote Applications to the next status if needed
  1133  func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
  1134  
  1135  	now := metav1.Now()
  1136  	appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applications))
  1137  
  1138  	for _, app := range applications {
  1139  
  1140  		healthStatusString, syncStatusString, operationPhaseString := statusStrings(app)
  1141  
  1142  		idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, app.Name)
  1143  
  1144  		currentAppStatus := argov1alpha1.ApplicationSetApplicationStatus{}
  1145  
  1146  		if idx == -1 {
  1147  			// AppStatus not found, set default status of "Waiting"
  1148  			currentAppStatus = argov1alpha1.ApplicationSetApplicationStatus{
  1149  				Application:        app.Name,
  1150  				LastTransitionTime: &now,
  1151  				Message:            "No Application status found, defaulting status to Waiting.",
  1152  				Status:             "Waiting",
  1153  				Step:               fmt.Sprint(appStepMap[app.Name] + 1),
  1154  			}
  1155  		} else {
  1156  			// we have an existing AppStatus
  1157  			currentAppStatus = applicationSet.Status.ApplicationStatus[idx]
  1158  		}
  1159  
  1160  		appOutdated := false
  1161  		if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
  1162  			appOutdated = syncStatusString == "OutOfSync"
  1163  		}
  1164  
  1165  		if appOutdated && currentAppStatus.Status != "Waiting" && currentAppStatus.Status != "Pending" {
  1166  			logCtx.Infof("Application %v is outdated, updating its ApplicationSet status to Waiting", app.Name)
  1167  			currentAppStatus.LastTransitionTime = &now
  1168  			currentAppStatus.Status = "Waiting"
  1169  			currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
  1170  			currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
  1171  		}
  1172  
  1173  		if currentAppStatus.Status == "Pending" {
  1174  			// check for successful syncs started less than 10s before the Application transitioned to Pending
  1175  			// this covers race conditions where syncs initiated by RollingSync miraculously have a sync time before the transition to Pending state occurred (could be a few seconds)
  1176  			if operationPhaseString == "Succeeded" && app.Status.OperationState.StartedAt.Add(time.Duration(10)*time.Second).After(currentAppStatus.LastTransitionTime.Time) {
  1177  				if !app.Status.OperationState.StartedAt.After(currentAppStatus.LastTransitionTime.Time) {
  1178  					logCtx.Warnf("Application %v was synced less than 10s prior to entering Pending status, we'll assume the AppSet controller triggered this sync and update its status to Progressing", app.Name)
  1179  				}
  1180  				logCtx.Infof("Application %v has completed a sync successfully, updating its ApplicationSet status to Progressing", app.Name)
  1181  				currentAppStatus.LastTransitionTime = &now
  1182  				currentAppStatus.Status = "Progressing"
  1183  				currentAppStatus.Message = "Application resource completed a sync successfully, updating status from Pending to Progressing."
  1184  				currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
  1185  			} else if operationPhaseString == "Running" || healthStatusString == "Progressing" {
  1186  				logCtx.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name)
  1187  				currentAppStatus.LastTransitionTime = &now
  1188  				currentAppStatus.Status = "Progressing"
  1189  				currentAppStatus.Message = "Application resource became Progressing, updating status from Pending to Progressing."
  1190  				currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
  1191  			}
  1192  		}
  1193  
  1194  		if currentAppStatus.Status == "Waiting" && isApplicationHealthy(app) {
  1195  			logCtx.Infof("Application %v is already synced and healthy, updating its ApplicationSet status to Healthy", app.Name)
  1196  			currentAppStatus.LastTransitionTime = &now
  1197  			currentAppStatus.Status = healthStatusString
  1198  			currentAppStatus.Message = "Application resource is already Healthy, updating status from Waiting to Healthy."
  1199  			currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
  1200  		}
  1201  
  1202  		if currentAppStatus.Status == "Progressing" && isApplicationHealthy(app) {
  1203  			logCtx.Infof("Application %v has completed Progressing status, updating its ApplicationSet status to Healthy", app.Name)
  1204  			currentAppStatus.LastTransitionTime = &now
  1205  			currentAppStatus.Status = healthStatusString
  1206  			currentAppStatus.Message = "Application resource became Healthy, updating status from Progressing to Healthy."
  1207  			currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
  1208  		}
  1209  
  1210  		appStatuses = append(appStatuses, currentAppStatus)
  1211  	}
  1212  
  1213  	err := r.setAppSetApplicationStatus(ctx, logCtx, applicationSet, appStatuses)
  1214  	if err != nil {
  1215  		return nil, fmt.Errorf("failed to set AppSet application statuses: %w", err)
  1216  	}
  1217  
  1218  	return appStatuses, nil
  1219  }
  1220  
  1221  // check Applications that are in Waiting status and promote them to Pending if needed
  1222  func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int, appMap map[string]argov1alpha1.Application) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
  1223  	now := metav1.Now()
  1224  
  1225  	appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applicationSet.Status.ApplicationStatus))
  1226  
  1227  	// if we have no RollingUpdate steps, clear out the existing ApplicationStatus entries
  1228  	if applicationSet.Spec.Strategy != nil && applicationSet.Spec.Strategy.Type != "" && applicationSet.Spec.Strategy.Type != "AllAtOnce" {
  1229  		updateCountMap := []int{}
  1230  		totalCountMap := []int{}
  1231  
  1232  		length := 0
  1233  		if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
  1234  			length = len(applicationSet.Spec.Strategy.RollingSync.Steps)
  1235  		}
  1236  		for s := 0; s < length; s++ {
  1237  			updateCountMap = append(updateCountMap, 0)
  1238  			totalCountMap = append(totalCountMap, 0)
  1239  		}
  1240  
  1241  		// populate updateCountMap with counts of existing Pending and Progressing Applications
  1242  		for _, appStatus := range applicationSet.Status.ApplicationStatus {
  1243  			totalCountMap[appStepMap[appStatus.Application]] += 1
  1244  
  1245  			if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
  1246  				if appStatus.Status == "Pending" || appStatus.Status == "Progressing" {
  1247  					updateCountMap[appStepMap[appStatus.Application]] += 1
  1248  				}
  1249  			}
  1250  		}
  1251  
  1252  		for _, appStatus := range applicationSet.Status.ApplicationStatus {
  1253  
  1254  			maxUpdateAllowed := true
  1255  			maxUpdate := &intstr.IntOrString{}
  1256  			if progressiveSyncsStrategyEnabled(applicationSet, "RollingSync") {
  1257  				maxUpdate = applicationSet.Spec.Strategy.RollingSync.Steps[appStepMap[appStatus.Application]].MaxUpdate
  1258  			}
  1259  
  1260  			// by default allow all applications to update if maxUpdate is unset
  1261  			if maxUpdate != nil {
  1262  				maxUpdateVal, err := intstr.GetScaledValueFromIntOrPercent(maxUpdate, totalCountMap[appStepMap[appStatus.Application]], false)
  1263  				if err != nil {
  1264  					logCtx.Warnf("AppSet '%v' has a invalid maxUpdate value '%+v', ignoring maxUpdate logic for this step: %v", applicationSet.Name, maxUpdate, err)
  1265  				}
  1266  
  1267  				// ensure that percentage values greater than 0% always result in at least 1 Application being selected
  1268  				if maxUpdate.Type == intstr.String && maxUpdate.StrVal != "0%" && maxUpdateVal < 1 {
  1269  					maxUpdateVal = 1
  1270  				}
  1271  
  1272  				if updateCountMap[appStepMap[appStatus.Application]] >= maxUpdateVal {
  1273  					maxUpdateAllowed = false
  1274  					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, appStepMap[appStatus.Application]+1, applicationSet.Name)
  1275  				}
  1276  
  1277  			}
  1278  
  1279  			if appStatus.Status == "Waiting" && appSyncMap[appStatus.Application] && maxUpdateAllowed {
  1280  				logCtx.Infof("Application %v moved to Pending status, watching for the Application to start Progressing", appStatus.Application)
  1281  				appStatus.LastTransitionTime = &now
  1282  				appStatus.Status = "Pending"
  1283  				appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing."
  1284  				appStatus.Step = fmt.Sprint(appStepMap[appStatus.Application] + 1)
  1285  
  1286  				updateCountMap[appStepMap[appStatus.Application]] += 1
  1287  			}
  1288  
  1289  			appStatuses = append(appStatuses, appStatus)
  1290  		}
  1291  	}
  1292  
  1293  	err := r.setAppSetApplicationStatus(ctx, logCtx, applicationSet, appStatuses)
  1294  	if err != nil {
  1295  		return nil, fmt.Errorf("failed to set AppSet app status: %w", err)
  1296  	}
  1297  
  1298  	return appStatuses, nil
  1299  }
  1300  
  1301  func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditions(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet) ([]argov1alpha1.ApplicationSetCondition, error) {
  1302  
  1303  	appSetProgressing := false
  1304  	for _, appStatus := range applicationSet.Status.ApplicationStatus {
  1305  		if appStatus.Status != "Healthy" {
  1306  			appSetProgressing = true
  1307  			break
  1308  		}
  1309  	}
  1310  
  1311  	appSetConditionProgressing := false
  1312  	for _, appSetCondition := range applicationSet.Status.Conditions {
  1313  		if appSetCondition.Type == argov1alpha1.ApplicationSetConditionRolloutProgressing && appSetCondition.Status == argov1alpha1.ApplicationSetConditionStatusTrue {
  1314  			appSetConditionProgressing = true
  1315  			break
  1316  		}
  1317  	}
  1318  
  1319  	if appSetProgressing && !appSetConditionProgressing {
  1320  		_ = r.setApplicationSetStatusCondition(ctx,
  1321  			applicationSet,
  1322  			argov1alpha1.ApplicationSetCondition{
  1323  				Type:    argov1alpha1.ApplicationSetConditionRolloutProgressing,
  1324  				Message: "ApplicationSet Rollout Rollout started",
  1325  				Reason:  argov1alpha1.ApplicationSetReasonApplicationSetModified,
  1326  				Status:  argov1alpha1.ApplicationSetConditionStatusTrue,
  1327  			}, false,
  1328  		)
  1329  	} else if !appSetProgressing && appSetConditionProgressing {
  1330  		_ = r.setApplicationSetStatusCondition(ctx,
  1331  			applicationSet,
  1332  			argov1alpha1.ApplicationSetCondition{
  1333  				Type:    argov1alpha1.ApplicationSetConditionRolloutProgressing,
  1334  				Message: "ApplicationSet Rollout Rollout complete",
  1335  				Reason:  argov1alpha1.ApplicationSetReasonApplicationSetRolloutComplete,
  1336  				Status:  argov1alpha1.ApplicationSetConditionStatusFalse,
  1337  			}, false,
  1338  		)
  1339  	}
  1340  
  1341  	return applicationSet.Status.Conditions, nil
  1342  }
  1343  
  1344  func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplicationStatus, application string) int {
  1345  	for i := range appStatuses {
  1346  		if appStatuses[i].Application == application {
  1347  			return i
  1348  		}
  1349  	}
  1350  	return -1
  1351  }
  1352  
  1353  // setApplicationSetApplicationStatus updates the ApplicatonSet's status field
  1354  // with any new/changed Application statuses.
  1355  func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applicationStatuses []argov1alpha1.ApplicationSetApplicationStatus) error {
  1356  	needToUpdateStatus := false
  1357  
  1358  	if len(applicationStatuses) != len(applicationSet.Status.ApplicationStatus) {
  1359  		needToUpdateStatus = true
  1360  	} else {
  1361  		for i := range applicationStatuses {
  1362  			appStatus := applicationStatuses[i]
  1363  			idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appStatus.Application)
  1364  			if idx == -1 {
  1365  				needToUpdateStatus = true
  1366  				break
  1367  			}
  1368  			currentStatus := applicationSet.Status.ApplicationStatus[idx]
  1369  			if currentStatus.Message != appStatus.Message || currentStatus.Status != appStatus.Status || currentStatus.Step != appStatus.Step {
  1370  				needToUpdateStatus = true
  1371  				break
  1372  			}
  1373  		}
  1374  	}
  1375  
  1376  	if needToUpdateStatus {
  1377  		namespacedName := types.NamespacedName{Namespace: applicationSet.Namespace, Name: applicationSet.Name}
  1378  
  1379  		// rebuild ApplicationStatus from scratch, we don't need any previous status history
  1380  		applicationSet.Status.ApplicationStatus = []argov1alpha1.ApplicationSetApplicationStatus{}
  1381  		for i := range applicationStatuses {
  1382  			applicationSet.Status.SetApplicationStatus(applicationStatuses[i])
  1383  		}
  1384  
  1385  		// Update the newly fetched object with new set of ApplicationStatus
  1386  		err := r.Client.Status().Update(ctx, applicationSet)
  1387  		if err != nil {
  1388  
  1389  			logCtx.Errorf("unable to set application set status: %v", err)
  1390  			return fmt.Errorf("unable to set application set status: %v", err)
  1391  		}
  1392  
  1393  		if err := r.Get(ctx, namespacedName, applicationSet); err != nil {
  1394  			if client.IgnoreNotFound(err) != nil {
  1395  				return nil
  1396  			}
  1397  			return fmt.Errorf("error fetching updated application set: %v", err)
  1398  		}
  1399  	}
  1400  
  1401  	return nil
  1402  }
  1403  
  1404  func (r *ApplicationSetReconciler) syncValidApplications(logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) ([]argov1alpha1.Application, error) {
  1405  	rolloutApps := []argov1alpha1.Application{}
  1406  	for i := range validApps {
  1407  		pruneEnabled := false
  1408  
  1409  		// ensure that Applications generated with RollingSync do not have an automated sync policy, since the AppSet controller will handle triggering the sync operation instead
  1410  		if validApps[i].Spec.SyncPolicy != nil && validApps[i].Spec.SyncPolicy.Automated != nil {
  1411  			pruneEnabled = validApps[i].Spec.SyncPolicy.Automated.Prune
  1412  			validApps[i].Spec.SyncPolicy.Automated = nil
  1413  		}
  1414  
  1415  		appSetStatusPending := false
  1416  		idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, validApps[i].Name)
  1417  		if idx > -1 && applicationSet.Status.ApplicationStatus[idx].Status == "Pending" {
  1418  			// only trigger a sync for Applications that are in Pending status, since this is governed by maxUpdate
  1419  			appSetStatusPending = true
  1420  		}
  1421  
  1422  		// check appSyncMap to determine which Applications are ready to be updated and which should be skipped
  1423  		if appSyncMap[validApps[i].Name] && appMap[validApps[i].Name].Status.Sync.Status == "OutOfSync" && appSetStatusPending {
  1424  			logCtx.Infof("triggering sync for application: %v, prune enabled: %v", validApps[i].Name, pruneEnabled)
  1425  			validApps[i], _ = syncApplication(validApps[i], pruneEnabled)
  1426  		}
  1427  		rolloutApps = append(rolloutApps, validApps[i])
  1428  	}
  1429  	return rolloutApps, nil
  1430  }
  1431  
  1432  // used by the RollingSync Progressive Sync strategy to trigger a sync of a particular Application resource
  1433  func syncApplication(application argov1alpha1.Application, prune bool) (argov1alpha1.Application, error) {
  1434  
  1435  	operation := argov1alpha1.Operation{
  1436  		InitiatedBy: argov1alpha1.OperationInitiator{
  1437  			Username:  "applicationset-controller",
  1438  			Automated: true,
  1439  		},
  1440  		Info: []*argov1alpha1.Info{
  1441  			{
  1442  				Name:  "Reason",
  1443  				Value: "ApplicationSet RollingSync triggered a sync of this Application resource.",
  1444  			},
  1445  		},
  1446  		Sync: &argov1alpha1.SyncOperation{},
  1447  	}
  1448  
  1449  	if application.Spec.SyncPolicy != nil {
  1450  		if application.Spec.SyncPolicy.Retry != nil {
  1451  			operation.Retry = *application.Spec.SyncPolicy.Retry
  1452  		}
  1453  		if application.Spec.SyncPolicy.SyncOptions != nil {
  1454  			operation.Sync.SyncOptions = application.Spec.SyncPolicy.SyncOptions
  1455  		}
  1456  		operation.Sync.Prune = prune
  1457  	}
  1458  	application.Operation = &operation
  1459  
  1460  	return application, nil
  1461  }
  1462  
  1463  func getOwnsHandlerPredicates(enableProgressiveSyncs bool) predicate.Funcs {
  1464  	return predicate.Funcs{
  1465  		CreateFunc: func(e event.CreateEvent) bool {
  1466  			// if we are the owner and there is a create event, we most likely created it and do not need to
  1467  			// re-reconcile
  1468  			if log.IsLevelEnabled(log.DebugLevel) {
  1469  				var appName string
  1470  				app, isApp := e.Object.(*argov1alpha1.Application)
  1471  				if isApp {
  1472  					appName = app.QualifiedName()
  1473  				}
  1474  				log.WithField("app", appName).Debugln("received create event from owning an application")
  1475  			}
  1476  			return false
  1477  		},
  1478  		DeleteFunc: func(e event.DeleteEvent) bool {
  1479  			if log.IsLevelEnabled(log.DebugLevel) {
  1480  				var appName string
  1481  				app, isApp := e.Object.(*argov1alpha1.Application)
  1482  				if isApp {
  1483  					appName = app.QualifiedName()
  1484  				}
  1485  				log.WithField("app", appName).Debugln("received delete event from owning an application")
  1486  			}
  1487  			return true
  1488  		},
  1489  		UpdateFunc: func(e event.UpdateEvent) bool {
  1490  			appOld, isApp := e.ObjectOld.(*argov1alpha1.Application)
  1491  			if !isApp {
  1492  				return false
  1493  			}
  1494  			logCtx := log.WithField("app", appOld.QualifiedName())
  1495  			logCtx.Debugln("received update event from owning an application")
  1496  			appNew, isApp := e.ObjectNew.(*argov1alpha1.Application)
  1497  			if !isApp {
  1498  				return false
  1499  			}
  1500  			requeue := shouldRequeueApplicationSet(appOld, appNew, enableProgressiveSyncs)
  1501  			logCtx.WithField("requeue", requeue).Debugf("requeue: %t caused by application %s\n", requeue, appNew.Name)
  1502  			return requeue
  1503  		},
  1504  		GenericFunc: func(e event.GenericEvent) bool {
  1505  			if log.IsLevelEnabled(log.DebugLevel) {
  1506  				var appName string
  1507  				app, isApp := e.Object.(*argov1alpha1.Application)
  1508  				if isApp {
  1509  					appName = app.QualifiedName()
  1510  				}
  1511  				log.WithField("app", appName).Debugln("received generic event from owning an application")
  1512  			}
  1513  			return true
  1514  		},
  1515  	}
  1516  }
  1517  
  1518  // shouldRequeueApplicationSet determines when we want to requeue an ApplicationSet for reconciling based on an owned
  1519  // application change
  1520  // The applicationset controller owns a subset of the Application CR.
  1521  // We do not need to re-reconcile if parts of the application change outside the applicationset's control.
  1522  // An example being, Application.ApplicationStatus.ReconciledAt which gets updated by the application controller.
  1523  // Additionally, Application.ObjectMeta.ResourceVersion and Application.ObjectMeta.Generation which are set by K8s.
  1524  func shouldRequeueApplicationSet(appOld *argov1alpha1.Application, appNew *argov1alpha1.Application, enableProgressiveSyncs bool) bool {
  1525  	if appOld == nil || appNew == nil {
  1526  		return false
  1527  	}
  1528  
  1529  	// the applicationset controller owns the application spec, labels, annotations, and finalizers on the applications
  1530  	if !reflect.DeepEqual(appOld.Spec, appNew.Spec) ||
  1531  		!reflect.DeepEqual(appOld.ObjectMeta.GetAnnotations(), appNew.ObjectMeta.GetAnnotations()) ||
  1532  		!reflect.DeepEqual(appOld.ObjectMeta.GetLabels(), appNew.ObjectMeta.GetLabels()) ||
  1533  		!reflect.DeepEqual(appOld.ObjectMeta.GetFinalizers(), appNew.ObjectMeta.GetFinalizers()) {
  1534  		return true
  1535  	}
  1536  
  1537  	// progressive syncs use the application status for updates. if they differ, requeue to trigger the next progression
  1538  	if enableProgressiveSyncs {
  1539  		if appOld.Status.Health.Status != appNew.Status.Health.Status || appOld.Status.Sync.Status != appNew.Status.Sync.Status {
  1540  			return true
  1541  		}
  1542  
  1543  		if appOld.Status.OperationState != nil && appNew.Status.OperationState != nil {
  1544  			if appOld.Status.OperationState.Phase != appNew.Status.OperationState.Phase ||
  1545  				appOld.Status.OperationState.StartedAt != appNew.Status.OperationState.StartedAt {
  1546  				return true
  1547  			}
  1548  		}
  1549  	}
  1550  
  1551  	return false
  1552  }
  1553  
  1554  var _ handler.EventHandler = &clusterSecretEventHandler{}