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 }