github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/generators/cluster.go (about) 1 package generators 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 log "github.com/sirupsen/logrus" 9 10 corev1 "k8s.io/api/core/v1" 11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 12 "k8s.io/client-go/kubernetes" 13 "sigs.k8s.io/controller-runtime/pkg/client" 14 15 "github.com/argoproj/argo-cd/v3/applicationset/utils" 16 "github.com/argoproj/argo-cd/v3/common" 17 argoappsetv1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 18 ) 19 20 var _ Generator = (*ClusterGenerator)(nil) 21 22 // ClusterGenerator generates Applications for some or all clusters registered with ArgoCD. 23 type ClusterGenerator struct { 24 client.Client 25 ctx context.Context 26 clientset kubernetes.Interface 27 // namespace is the Argo CD namespace 28 namespace string 29 } 30 31 var render = &utils.Render{} 32 33 func NewClusterGenerator(ctx context.Context, c client.Client, clientset kubernetes.Interface, namespace string) Generator { 34 g := &ClusterGenerator{ 35 Client: c, 36 ctx: ctx, 37 clientset: clientset, 38 namespace: namespace, 39 } 40 return g 41 } 42 43 // GetRequeueAfter never requeue the cluster generator because the `clusterSecretEventHandler` will requeue the appsets 44 // when the cluster secrets change 45 func (g *ClusterGenerator) GetRequeueAfter(_ *argoappsetv1alpha1.ApplicationSetGenerator) time.Duration { 46 return NoRequeueAfter 47 } 48 49 func (g *ClusterGenerator) GetTemplate(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) *argoappsetv1alpha1.ApplicationSetTemplate { 50 return &appSetGenerator.Clusters.Template 51 } 52 53 func (g *ClusterGenerator) GenerateParams(appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator, appSet *argoappsetv1alpha1.ApplicationSet, _ client.Client) ([]map[string]any, error) { 54 logCtx := log.WithField("applicationset", appSet.GetName()).WithField("namespace", appSet.GetNamespace()) 55 if appSetGenerator == nil { 56 return nil, ErrEmptyAppSetGenerator 57 } 58 59 if appSetGenerator.Clusters == nil { 60 return nil, ErrEmptyAppSetGenerator 61 } 62 63 // Do not include the local cluster in the cluster parameters IF there is a non-empty selector 64 // - Since local clusters do not have secrets, they do not have labels to match against 65 ignoreLocalClusters := len(appSetGenerator.Clusters.Selector.MatchExpressions) > 0 || len(appSetGenerator.Clusters.Selector.MatchLabels) > 0 66 67 // ListCluster will include the local cluster in the list of clusters 68 clustersFromArgoCD, err := utils.ListClusters(g.ctx, g.clientset, g.namespace) 69 if err != nil { 70 return nil, fmt.Errorf("error listing clusters: %w", err) 71 } 72 73 if clustersFromArgoCD == nil { 74 return nil, nil 75 } 76 77 clusterSecrets, err := g.getSecretsByClusterName(logCtx, appSetGenerator) 78 if err != nil { 79 return nil, fmt.Errorf("error getting cluster secrets: %w", err) 80 } 81 82 paramHolder := ¶mHolder{isFlatMode: appSetGenerator.Clusters.FlatList} 83 logCtx.Debugf("Using flat mode = %t for cluster generator", paramHolder.isFlatMode) 84 85 secretsFound := []corev1.Secret{} 86 for _, cluster := range clustersFromArgoCD { 87 // If there is a secret for this cluster, then it's a non-local cluster, so it will be 88 // handled by the next step. 89 if secretForCluster, exists := clusterSecrets[cluster.Name]; exists { 90 secretsFound = append(secretsFound, secretForCluster) 91 } else if !ignoreLocalClusters { 92 // If there is no secret for the cluster, it's the local cluster, so handle it here. 93 params := map[string]any{} 94 params["name"] = cluster.Name 95 params["nameNormalized"] = cluster.Name 96 params["server"] = cluster.Server 97 params["project"] = "" 98 99 err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) 100 if err != nil { 101 return nil, fmt.Errorf("error appending templated values for local cluster: %w", err) 102 } 103 104 paramHolder.append(params) 105 logCtx.WithField("cluster", "local cluster").Info("matched local cluster") 106 } 107 } 108 109 // For each matching cluster secret (non-local clusters only) 110 for _, cluster := range secretsFound { 111 params := g.getClusterParameters(cluster, appSet) 112 113 err = appendTemplatedValues(appSetGenerator.Clusters.Values, params, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) 114 if err != nil { 115 return nil, fmt.Errorf("error appending templated values for cluster: %w", err) 116 } 117 118 paramHolder.append(params) 119 logCtx.WithField("cluster", cluster.Name).Debug("matched cluster secret") 120 } 121 122 return paramHolder.consolidate(), nil 123 } 124 125 type paramHolder struct { 126 isFlatMode bool 127 params []map[string]any 128 } 129 130 func (p *paramHolder) append(params map[string]any) { 131 p.params = append(p.params, params) 132 } 133 134 func (p *paramHolder) consolidate() []map[string]any { 135 if p.isFlatMode { 136 p.params = []map[string]any{ 137 {"clusters": p.params}, 138 } 139 } 140 return p.params 141 } 142 143 func (g *ClusterGenerator) getClusterParameters(cluster corev1.Secret, appSet *argoappsetv1alpha1.ApplicationSet) map[string]any { 144 params := map[string]any{} 145 146 params["name"] = string(cluster.Data["name"]) 147 params["nameNormalized"] = utils.SanitizeName(string(cluster.Data["name"])) 148 params["server"] = string(cluster.Data["server"]) 149 150 project, ok := cluster.Data["project"] 151 if ok { 152 params["project"] = string(project) 153 } else { 154 params["project"] = "" 155 } 156 157 if appSet.Spec.GoTemplate { 158 meta := map[string]any{} 159 160 if len(cluster.Annotations) > 0 { 161 meta["annotations"] = cluster.Annotations 162 } 163 if len(cluster.Labels) > 0 { 164 meta["labels"] = cluster.Labels 165 } 166 167 params["metadata"] = meta 168 } else { 169 for key, value := range cluster.Annotations { 170 params["metadata.annotations."+key] = value 171 } 172 173 for key, value := range cluster.Labels { 174 params["metadata.labels."+key] = value 175 } 176 } 177 return params 178 } 179 180 func (g *ClusterGenerator) getSecretsByClusterName(log *log.Entry, appSetGenerator *argoappsetv1alpha1.ApplicationSetGenerator) (map[string]corev1.Secret, error) { 181 clusterSecretList := &corev1.SecretList{} 182 183 selector := metav1.AddLabelToSelector(&appSetGenerator.Clusters.Selector, common.LabelKeySecretType, common.LabelValueSecretTypeCluster) 184 secretSelector, err := metav1.LabelSelectorAsSelector(selector) 185 if err != nil { 186 return nil, fmt.Errorf("error converting label selector: %w", err) 187 } 188 189 if err := g.List(context.Background(), clusterSecretList, client.MatchingLabelsSelector{Selector: secretSelector}); err != nil { 190 return nil, err 191 } 192 log.Debugf("clusters matching labels: %d", len(clusterSecretList.Items)) 193 194 res := map[string]corev1.Secret{} 195 196 for _, cluster := range clusterSecretList.Items { 197 clusterName := string(cluster.Data["name"]) 198 199 res[clusterName] = cluster 200 } 201 202 return res, nil 203 }