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 }