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

     1  package generators
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"maps"
     8  	"time"
     9  
    10  	"dario.cat/mergo"
    11  	"sigs.k8s.io/controller-runtime/pkg/client"
    12  
    13  	argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    14  )
    15  
    16  var _ Generator = (*MergeGenerator)(nil)
    17  
    18  var (
    19  	ErrLessThanTwoGeneratorsInMerge = errors.New("found less than two generators, Merge requires two or more")
    20  	ErrNoMergeKeys                  = errors.New("no merge keys were specified, Merge requires at least one")
    21  	ErrNonUniqueParamSets           = errors.New("the parameters from a generator were not unique by the given mergeKeys, Merge requires all param sets to be unique")
    22  )
    23  
    24  type MergeGenerator struct {
    25  	// The inner generators supported by the merge generator (cluster, git, list...)
    26  	supportedGenerators map[string]Generator
    27  }
    28  
    29  // NewMergeGenerator returns a MergeGenerator which allows the given supportedGenerators as child generators.
    30  func NewMergeGenerator(supportedGenerators map[string]Generator) Generator {
    31  	m := &MergeGenerator{
    32  		supportedGenerators: supportedGenerators,
    33  	}
    34  	return m
    35  }
    36  
    37  // getParamSetsForAllGenerators generates params for each child generator in a MergeGenerator. Param sets are returned
    38  // in slices ordered according to the order of the given generators.
    39  func (m *MergeGenerator) getParamSetsForAllGenerators(generators []argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([][]map[string]any, error) {
    40  	var paramSets [][]map[string]any
    41  	for i, generator := range generators {
    42  		generatorParamSets, err := m.getParams(generator, appSet, client)
    43  		if err != nil {
    44  			return nil, fmt.Errorf("error getting params from generator %d of %d: %w", i+1, len(generators), err)
    45  		}
    46  		// concatenate param lists produced by each generator
    47  		paramSets = append(paramSets, generatorParamSets)
    48  	}
    49  	return paramSets, nil
    50  }
    51  
    52  // GenerateParams gets the params produced by the MergeGenerator.
    53  func (m *MergeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
    54  	if appSetGenerator.Merge == nil {
    55  		return nil, ErrEmptyAppSetGenerator
    56  	}
    57  
    58  	if len(appSetGenerator.Merge.Generators) < 2 {
    59  		return nil, ErrLessThanTwoGeneratorsInMerge
    60  	}
    61  
    62  	paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet, client)
    63  	if err != nil {
    64  		return nil, fmt.Errorf("error getting param sets from generators: %w", err)
    65  	}
    66  
    67  	baseParamSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSetsFromGenerators[0])
    68  	if err != nil {
    69  		return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
    70  	}
    71  
    72  	for _, paramSets := range paramSetsFromGenerators[1:] {
    73  		paramSetsByMergeKey, err := getParamSetsByMergeKey(appSetGenerator.Merge.MergeKeys, paramSets)
    74  		if err != nil {
    75  			return nil, fmt.Errorf("error getting param sets by merge key: %w", err)
    76  		}
    77  
    78  		for mergeKeyValue, baseParamSet := range baseParamSetsByMergeKey {
    79  			if overrideParamSet, exists := paramSetsByMergeKey[mergeKeyValue]; exists {
    80  				if appSet.Spec.GoTemplate {
    81  					if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil {
    82  						return nil, fmt.Errorf("error merging base param set with override param set: %w", err)
    83  					}
    84  					baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
    85  				} else {
    86  					maps.Copy(baseParamSet, overrideParamSet)
    87  					baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
    88  				}
    89  			}
    90  		}
    91  	}
    92  
    93  	mergedParamSets := make([]map[string]any, len(baseParamSetsByMergeKey))
    94  	i := 0
    95  	for _, mergedParamSet := range baseParamSetsByMergeKey {
    96  		mergedParamSets[i] = mergedParamSet
    97  		i++
    98  	}
    99  
   100  	return mergedParamSets, nil
   101  }
   102  
   103  // getParamSetsByMergeKey converts the given list of parameter sets to a map of parameter sets where the key is the
   104  // unique key of the parameter set as determined by the given mergeKeys. If any two parameter sets share the same merge
   105  // key, getParamSetsByMergeKey will throw NonUniqueParamSets.
   106  func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]any) (map[string]map[string]any, error) {
   107  	if len(mergeKeys) < 1 {
   108  		return nil, ErrNoMergeKeys
   109  	}
   110  
   111  	deDuplicatedMergeKeys := make(map[string]bool, len(mergeKeys))
   112  	for _, mergeKey := range mergeKeys {
   113  		deDuplicatedMergeKeys[mergeKey] = false
   114  	}
   115  
   116  	paramSetsByMergeKey := make(map[string]map[string]any, len(paramSets))
   117  	for _, paramSet := range paramSets {
   118  		paramSetKey := make(map[string]any)
   119  		for mergeKey := range deDuplicatedMergeKeys {
   120  			paramSetKey[mergeKey] = paramSet[mergeKey]
   121  		}
   122  		paramSetKeyJSON, err := json.Marshal(paramSetKey)
   123  		if err != nil {
   124  			return nil, fmt.Errorf("error marshalling param set key json: %w", err)
   125  		}
   126  		paramSetKeyString := string(paramSetKeyJSON)
   127  		if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
   128  			return nil, fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, paramSetKeyString)
   129  		}
   130  		paramSetsByMergeKey[paramSetKeyString] = paramSet
   131  	}
   132  
   133  	return paramSetsByMergeKey, nil
   134  }
   135  
   136  // getParams get the parameters generated by this generator.
   137  func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet, client client.Client) ([]map[string]any, error) {
   138  	matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	mergeGen, err := getMergeGenerator(appSetBaseGenerator)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	t, err := Transform(
   148  		argoprojiov1alpha1.ApplicationSetGenerator{
   149  			List:                    appSetBaseGenerator.List,
   150  			Clusters:                appSetBaseGenerator.Clusters,
   151  			Git:                     appSetBaseGenerator.Git,
   152  			SCMProvider:             appSetBaseGenerator.SCMProvider,
   153  			ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
   154  			PullRequest:             appSetBaseGenerator.PullRequest,
   155  			Plugin:                  appSetBaseGenerator.Plugin,
   156  			Matrix:                  matrixGen,
   157  			Merge:                   mergeGen,
   158  			Selector:                appSetBaseGenerator.Selector,
   159  		},
   160  		m.supportedGenerators,
   161  		argoprojiov1alpha1.ApplicationSetTemplate{},
   162  		appSet,
   163  		map[string]any{}, client)
   164  	if err != nil {
   165  		return nil, fmt.Errorf("child generator returned an error on parameter generation: %w", err)
   166  	}
   167  
   168  	if len(t) == 0 {
   169  		return nil, errors.New("child generator generated no parameters")
   170  	}
   171  
   172  	if len(t) > 1 {
   173  		return nil, ErrMoreThenOneInnerGenerators
   174  	}
   175  
   176  	return t[0].Params, nil
   177  }
   178  
   179  func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
   180  	res := maxDuration
   181  	var found bool
   182  
   183  	for _, r := range appSetGenerator.Merge.Generators {
   184  		matrixGen, _ := getMatrixGenerator(r)
   185  		mergeGen, _ := getMergeGenerator(r)
   186  		base := &argoprojiov1alpha1.ApplicationSetGenerator{
   187  			List:                    r.List,
   188  			Clusters:                r.Clusters,
   189  			Git:                     r.Git,
   190  			PullRequest:             r.PullRequest,
   191  			Plugin:                  r.Plugin,
   192  			SCMProvider:             r.SCMProvider,
   193  			ClusterDecisionResource: r.ClusterDecisionResource,
   194  			Matrix:                  matrixGen,
   195  			Merge:                   mergeGen,
   196  		}
   197  		generators := GetRelevantGenerators(base, m.supportedGenerators)
   198  
   199  		for _, g := range generators {
   200  			temp := g.GetRequeueAfter(base)
   201  			if temp < res && temp != NoRequeueAfter {
   202  				found = true
   203  				res = temp
   204  			}
   205  		}
   206  	}
   207  
   208  	if found {
   209  		return res
   210  	}
   211  	return NoRequeueAfter
   212  }
   213  
   214  func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) {
   215  	if r.Merge == nil {
   216  		return nil, nil
   217  	}
   218  	merge, err := argoprojiov1alpha1.ToNestedMergeGenerator(r.Merge)
   219  	if err != nil {
   220  		return nil, fmt.Errorf("error converting to nested merge generator: %w", err)
   221  	}
   222  	return merge.ToMergeGenerator(), nil
   223  }
   224  
   225  // GetTemplate gets the Template field for the MergeGenerator.
   226  func (m *MergeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
   227  	return &appSetGenerator.Merge.Template
   228  }