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

     1  package generators
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/imdario/mergo"
     9  
    10  	"github.com/argoproj/argo-cd/v2/applicationset/utils"
    11  	argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    12  
    13  	log "github.com/sirupsen/logrus"
    14  )
    15  
    16  var _ Generator = (*MergeGenerator)(nil)
    17  
    18  var (
    19  	ErrLessThanTwoGeneratorsInMerge = fmt.Errorf("found less than two generators, Merge requires two or more")
    20  	ErrNoMergeKeys                  = fmt.Errorf("no merge keys were specified, Merge requires at least one")
    21  	ErrNonUniqueParamSets           = fmt.Errorf("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) ([][]map[string]interface{}, error) {
    40  	var paramSets [][]map[string]interface{}
    41  	for i, generator := range generators {
    42  		generatorParamSets, err := m.getParams(generator, appSet)
    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) ([]map[string]interface{}, error) {
    54  	if appSetGenerator.Merge == nil {
    55  		return nil, EmptyAppSetGeneratorError
    56  	}
    57  
    58  	if len(appSetGenerator.Merge.Generators) < 2 {
    59  		return nil, ErrLessThanTwoGeneratorsInMerge
    60  	}
    61  
    62  	paramSetsFromGenerators, err := m.getParamSetsForAllGenerators(appSetGenerator.Merge.Generators, appSet)
    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  
    81  				if appSet.Spec.GoTemplate {
    82  					if err := mergo.Merge(&baseParamSet, overrideParamSet, mergo.WithOverride); err != nil {
    83  						return nil, fmt.Errorf("error merging base param set with override param set: %w", err)
    84  					}
    85  					baseParamSetsByMergeKey[mergeKeyValue] = baseParamSet
    86  				} else {
    87  					overriddenParamSet, err := utils.CombineStringMapsAllowDuplicates(baseParamSet, overrideParamSet)
    88  					if err != nil {
    89  						return nil, fmt.Errorf("error combining string maps: %w", err)
    90  					}
    91  					baseParamSetsByMergeKey[mergeKeyValue] = utils.ConvertToMapStringInterface(overriddenParamSet)
    92  				}
    93  			}
    94  		}
    95  	}
    96  
    97  	mergedParamSets := make([]map[string]interface{}, len(baseParamSetsByMergeKey))
    98  	var i = 0
    99  	for _, mergedParamSet := range baseParamSetsByMergeKey {
   100  		mergedParamSets[i] = mergedParamSet
   101  		i += 1
   102  	}
   103  
   104  	return mergedParamSets, nil
   105  }
   106  
   107  // getParamSetsByMergeKey converts the given list of parameter sets to a map of parameter sets where the key is the
   108  // unique key of the parameter set as determined by the given mergeKeys. If any two parameter sets share the same merge
   109  // key, getParamSetsByMergeKey will throw NonUniqueParamSets.
   110  func getParamSetsByMergeKey(mergeKeys []string, paramSets []map[string]interface{}) (map[string]map[string]interface{}, error) {
   111  	if len(mergeKeys) < 1 {
   112  		return nil, ErrNoMergeKeys
   113  	}
   114  
   115  	deDuplicatedMergeKeys := make(map[string]bool, len(mergeKeys))
   116  	for _, mergeKey := range mergeKeys {
   117  		deDuplicatedMergeKeys[mergeKey] = false
   118  	}
   119  
   120  	paramSetsByMergeKey := make(map[string]map[string]interface{}, len(paramSets))
   121  	for _, paramSet := range paramSets {
   122  		paramSetKey := make(map[string]interface{})
   123  		for mergeKey := range deDuplicatedMergeKeys {
   124  			paramSetKey[mergeKey] = paramSet[mergeKey]
   125  		}
   126  		paramSetKeyJson, err := json.Marshal(paramSetKey)
   127  		if err != nil {
   128  			return nil, fmt.Errorf("error marshalling param set key json: %w", err)
   129  		}
   130  		paramSetKeyString := string(paramSetKeyJson)
   131  		if _, exists := paramSetsByMergeKey[paramSetKeyString]; exists {
   132  			return nil, fmt.Errorf("%w. Duplicate key was %s", ErrNonUniqueParamSets, paramSetKeyString)
   133  		}
   134  		paramSetsByMergeKey[paramSetKeyString] = paramSet
   135  	}
   136  
   137  	return paramSetsByMergeKey, nil
   138  }
   139  
   140  // getParams get the parameters generated by this generator.
   141  func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.ApplicationSetNestedGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]map[string]interface{}, error) {
   142  	matrixGen, err := getMatrixGenerator(appSetBaseGenerator)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	if matrixGen != nil && !appSet.Spec.ApplyNestedSelectors {
   147  		foundSelector := dropDisabledNestedSelectors(matrixGen.Generators)
   148  		if foundSelector {
   149  			log.Warnf("AppSet '%v' defines selector on nested matrix generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
   150  		}
   151  	}
   152  	mergeGen, err := getMergeGenerator(appSetBaseGenerator)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	if mergeGen != nil && !appSet.Spec.ApplyNestedSelectors {
   157  		foundSelector := dropDisabledNestedSelectors(mergeGen.Generators)
   158  		if foundSelector {
   159  			log.Warnf("AppSet '%v' defines selector on nested merge generator's generator without enabling them via 'spec.applyNestedSelectors', ignoring nested selector", appSet.Name)
   160  		}
   161  	}
   162  
   163  	t, err := Transform(
   164  		argoprojiov1alpha1.ApplicationSetGenerator{
   165  			List:                    appSetBaseGenerator.List,
   166  			Clusters:                appSetBaseGenerator.Clusters,
   167  			Git:                     appSetBaseGenerator.Git,
   168  			SCMProvider:             appSetBaseGenerator.SCMProvider,
   169  			ClusterDecisionResource: appSetBaseGenerator.ClusterDecisionResource,
   170  			PullRequest:             appSetBaseGenerator.PullRequest,
   171  			Plugin:                  appSetBaseGenerator.Plugin,
   172  			Matrix:                  matrixGen,
   173  			Merge:                   mergeGen,
   174  			Selector:                appSetBaseGenerator.Selector,
   175  		},
   176  		m.supportedGenerators,
   177  		argoprojiov1alpha1.ApplicationSetTemplate{},
   178  		appSet,
   179  		map[string]interface{}{})
   180  
   181  	if err != nil {
   182  		return nil, fmt.Errorf("child generator returned an error on parameter generation: %v", err)
   183  	}
   184  
   185  	if len(t) == 0 {
   186  		return nil, fmt.Errorf("child generator generated no parameters")
   187  	}
   188  
   189  	if len(t) > 1 {
   190  		return nil, ErrMoreThenOneInnerGenerators
   191  	}
   192  
   193  	return t[0].Params, nil
   194  }
   195  
   196  func (m *MergeGenerator) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) time.Duration {
   197  	res := maxDuration
   198  	var found bool
   199  
   200  	for _, r := range appSetGenerator.Merge.Generators {
   201  		matrixGen, _ := getMatrixGenerator(r)
   202  		mergeGen, _ := getMergeGenerator(r)
   203  		base := &argoprojiov1alpha1.ApplicationSetGenerator{
   204  			List:                    r.List,
   205  			Clusters:                r.Clusters,
   206  			Git:                     r.Git,
   207  			PullRequest:             r.PullRequest,
   208  			Plugin:                  r.Plugin,
   209  			SCMProvider:             r.SCMProvider,
   210  			ClusterDecisionResource: r.ClusterDecisionResource,
   211  			Matrix:                  matrixGen,
   212  			Merge:                   mergeGen,
   213  		}
   214  		generators := GetRelevantGenerators(base, m.supportedGenerators)
   215  
   216  		for _, g := range generators {
   217  			temp := g.GetRequeueAfter(base)
   218  			if temp < res && temp != NoRequeueAfter {
   219  				found = true
   220  				res = temp
   221  			}
   222  		}
   223  	}
   224  
   225  	if found {
   226  		return res
   227  	} else {
   228  		return NoRequeueAfter
   229  	}
   230  
   231  }
   232  
   233  func getMergeGenerator(r argoprojiov1alpha1.ApplicationSetNestedGenerator) (*argoprojiov1alpha1.MergeGenerator, error) {
   234  	if r.Merge == nil {
   235  		return nil, nil
   236  	}
   237  	merge, err := argoprojiov1alpha1.ToNestedMergeGenerator(r.Merge)
   238  	if err != nil {
   239  		return nil, fmt.Errorf("error converting to nested merge generator: %w", err)
   240  	}
   241  	return merge.ToMergeGenerator(), nil
   242  }
   243  
   244  // GetTemplate gets the Template field for the MergeGenerator.
   245  func (m *MergeGenerator) GetTemplate(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator) *argoprojiov1alpha1.ApplicationSetTemplate {
   246  	return &appSetGenerator.Merge.Template
   247  }