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 }