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

     1  package controllers
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
     8  
     9  	log "github.com/sirupsen/logrus"
    10  
    11  	"k8s.io/apimachinery/pkg/types"
    12  	"k8s.io/client-go/util/workqueue"
    13  	ctrl "sigs.k8s.io/controller-runtime"
    14  	"sigs.k8s.io/controller-runtime/pkg/client"
    15  	"sigs.k8s.io/controller-runtime/pkg/event"
    16  
    17  	"github.com/argoproj/argo-cd/v3/common"
    18  	argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    19  )
    20  
    21  // clusterSecretEventHandler is used when watching Secrets to check if they are ArgoCD Cluster Secrets, and if so
    22  // requeue any related ApplicationSets.
    23  type clusterSecretEventHandler struct {
    24  	// handler.EnqueueRequestForOwner
    25  	Log    log.FieldLogger
    26  	Client client.Client
    27  }
    28  
    29  func (h *clusterSecretEventHandler) Create(ctx context.Context, e event.CreateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
    30  	h.queueRelatedAppGenerators(ctx, q, e.Object)
    31  }
    32  
    33  func (h *clusterSecretEventHandler) Update(ctx context.Context, e event.UpdateEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
    34  	h.queueRelatedAppGenerators(ctx, q, e.ObjectNew)
    35  }
    36  
    37  func (h *clusterSecretEventHandler) Delete(ctx context.Context, e event.DeleteEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
    38  	h.queueRelatedAppGenerators(ctx, q, e.Object)
    39  }
    40  
    41  func (h *clusterSecretEventHandler) Generic(ctx context.Context, e event.GenericEvent, q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
    42  	h.queueRelatedAppGenerators(ctx, q, e.Object)
    43  }
    44  
    45  // addRateLimitingInterface defines the Add method of workqueue.RateLimitingInterface, allow us to easily mock
    46  // it for testing purposes.
    47  type addRateLimitingInterface[T comparable] interface {
    48  	Add(item T)
    49  }
    50  
    51  func (h *clusterSecretEventHandler) queueRelatedAppGenerators(ctx context.Context, q addRateLimitingInterface[reconcile.Request], object client.Object) {
    52  	// Check for label, lookup all ApplicationSets that might match the cluster, queue them all
    53  	if object.GetLabels()[common.LabelKeySecretType] != common.LabelValueSecretTypeCluster {
    54  		return
    55  	}
    56  
    57  	h.Log.WithFields(log.Fields{
    58  		"namespace": object.GetNamespace(),
    59  		"name":      object.GetName(),
    60  	}).Info("processing event for cluster secret")
    61  
    62  	appSetList := &argoprojiov1alpha1.ApplicationSetList{}
    63  	err := h.Client.List(ctx, appSetList)
    64  	if err != nil {
    65  		h.Log.WithError(err).Error("unable to list ApplicationSets")
    66  		return
    67  	}
    68  
    69  	h.Log.WithField("count", len(appSetList.Items)).Info("listed ApplicationSets")
    70  	for _, appSet := range appSetList.Items {
    71  		foundClusterGenerator := false
    72  		for _, generator := range appSet.Spec.Generators {
    73  			if generator.Clusters != nil {
    74  				foundClusterGenerator = true
    75  				break
    76  			}
    77  
    78  			if generator.Matrix != nil {
    79  				ok, err := nestedGeneratorsHaveClusterGenerator(generator.Matrix.Generators)
    80  				if err != nil {
    81  					h.Log.
    82  						WithFields(log.Fields{
    83  							"namespace": appSet.GetNamespace(),
    84  							"name":      appSet.GetName(),
    85  						}).
    86  						WithError(err).
    87  						Error("Unable to check if ApplicationSet matrix generators have cluster generator")
    88  				}
    89  				if ok {
    90  					foundClusterGenerator = true
    91  					break
    92  				}
    93  			}
    94  
    95  			if generator.Merge != nil {
    96  				ok, err := nestedGeneratorsHaveClusterGenerator(generator.Merge.Generators)
    97  				if err != nil {
    98  					h.Log.
    99  						WithFields(log.Fields{
   100  							"namespace": appSet.GetNamespace(),
   101  							"name":      appSet.GetName(),
   102  						}).
   103  						WithError(err).
   104  						Error("Unable to check if ApplicationSet merge generators have cluster generator")
   105  				}
   106  				if ok {
   107  					foundClusterGenerator = true
   108  					break
   109  				}
   110  			}
   111  		}
   112  		if foundClusterGenerator {
   113  			// TODO: only queue the AppGenerator if the labels match this cluster
   114  			req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: appSet.Namespace, Name: appSet.Name}}
   115  			q.Add(req)
   116  		}
   117  	}
   118  }
   119  
   120  // nestedGeneratorsHaveClusterGenerator iterate over provided nested generators to check if they have a cluster generator.
   121  func nestedGeneratorsHaveClusterGenerator(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator) (bool, error) {
   122  	for _, generator := range generators {
   123  		if ok, err := nestedGeneratorHasClusterGenerator(generator); ok || err != nil {
   124  			return ok, err
   125  		}
   126  	}
   127  	return false, nil
   128  }
   129  
   130  // nestedGeneratorHasClusterGenerator checks if the provided generator has a cluster generator.
   131  func nestedGeneratorHasClusterGenerator(nested argoprojiov1alpha1.ApplicationSetNestedGenerator) (bool, error) {
   132  	if nested.Clusters != nil {
   133  		return true, nil
   134  	}
   135  
   136  	if nested.Matrix != nil {
   137  		nestedMatrix, err := argoprojiov1alpha1.ToNestedMatrixGenerator(nested.Matrix)
   138  		if err != nil {
   139  			return false, fmt.Errorf("unable to get nested matrix generator: %w", err)
   140  		}
   141  		if nestedMatrix != nil {
   142  			hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMatrix.ToMatrixGenerator().Generators)
   143  			if err != nil {
   144  				return false, fmt.Errorf("error evaluating nested matrix generator: %w", err)
   145  			}
   146  			return hasClusterGenerator, nil
   147  		}
   148  	}
   149  
   150  	if nested.Merge != nil {
   151  		nestedMerge, err := argoprojiov1alpha1.ToNestedMergeGenerator(nested.Merge)
   152  		if err != nil {
   153  			return false, fmt.Errorf("unable to get nested merge generator: %w", err)
   154  		}
   155  		if nestedMerge != nil {
   156  			hasClusterGenerator, err := nestedGeneratorsHaveClusterGenerator(nestedMerge.ToMergeGenerator().Generators)
   157  			if err != nil {
   158  				return false, fmt.Errorf("error evaluating nested merge generator: %w", err)
   159  			}
   160  			return hasClusterGenerator, nil
   161  		}
   162  	}
   163  
   164  	return false, nil
   165  }