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 := &paramHolder{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  }