github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/generators/generator_spec_processor.go (about) 1 package generators 2 3 import ( 4 "fmt" 5 "reflect" 6 7 "github.com/jeremywohl/flatten" 8 "sigs.k8s.io/controller-runtime/pkg/client" 9 10 "github.com/argoproj/argo-cd/v3/applicationset/utils" 11 12 "k8s.io/apimachinery/pkg/labels" 13 14 argoprojiov1alpha1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 15 16 "dario.cat/mergo" 17 log "github.com/sirupsen/logrus" 18 ) 19 20 const ( 21 selectorKey = "Selector" 22 ) 23 24 type TransformResult struct { 25 Params []map[string]any 26 Template argoprojiov1alpha1.ApplicationSetTemplate 27 } 28 29 // Transform a spec generator to list of paramSets and a template 30 func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet, genParams map[string]any, client client.Client) ([]TransformResult, error) { 31 // This is a custom version of the `LabelSelectorAsSelector` that is in k8s.io/apimachinery. This has been copied 32 // verbatim from that package, with the difference that we do not have any restrictions on label values. This is done 33 // so that, among other things, we can match on cluster urls. 34 selector, err := utils.LabelSelectorAsSelector(requestedGenerator.Selector) 35 if err != nil { 36 return nil, fmt.Errorf("error parsing label selector: %w", err) 37 } 38 39 res := []TransformResult{} 40 var firstError error 41 interpolatedGenerator := requestedGenerator.DeepCopy() 42 43 generators := GetRelevantGenerators(&requestedGenerator, allGenerators) 44 for _, g := range generators { 45 // we call mergeGeneratorTemplate first because GenerateParams might be more costly so we want to fail fast if there is an error 46 mergedTemplate, err := mergeGeneratorTemplate(g, &requestedGenerator, baseTemplate) 47 if err != nil { 48 log.WithError(err).WithField("generator", g). 49 Error("error generating params") 50 if firstError == nil { 51 firstError = err 52 } 53 continue 54 } 55 var params []map[string]any 56 if len(genParams) != 0 { 57 tempInterpolatedGenerator, err := InterpolateGenerator(&requestedGenerator, genParams, appSet.Spec.GoTemplate, appSet.Spec.GoTemplateOptions) 58 interpolatedGenerator = &tempInterpolatedGenerator 59 if err != nil { 60 log.WithError(err).WithField("genParams", genParams). 61 Error("error interpolating params for generator") 62 if firstError == nil { 63 firstError = err 64 } 65 continue 66 } 67 } 68 params, err = g.GenerateParams(interpolatedGenerator, appSet, client) 69 if err != nil { 70 log.WithError(err).WithField("generator", g). 71 Error("error generating params") 72 if firstError == nil { 73 firstError = err 74 } 75 continue 76 } 77 var filterParams []map[string]any 78 for _, param := range params { 79 flatParam, err := flattenParameters(param) 80 if err != nil { 81 log.WithError(err).WithField("generator", g). 82 Error("error flattening params") 83 if firstError == nil { 84 firstError = err 85 } 86 continue 87 } 88 89 if requestedGenerator.Selector != nil && !selector.Matches(labels.Set(flatParam)) { 90 continue 91 } 92 filterParams = append(filterParams, param) 93 } 94 95 res = append(res, TransformResult{ 96 Params: filterParams, 97 Template: mergedTemplate, 98 }) 99 } 100 101 return res, firstError 102 } 103 104 func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, generators map[string]Generator) []Generator { 105 var res []Generator 106 107 v := reflect.Indirect(reflect.ValueOf(requestedGenerator)) 108 for i := 0; i < v.NumField(); i++ { 109 field := v.Field(i) 110 if !field.CanInterface() { 111 continue 112 } 113 name := v.Type().Field(i).Name 114 if name == selectorKey { 115 continue 116 } 117 118 if !reflect.ValueOf(field.Interface()).IsNil() { 119 res = append(res, generators[name]) 120 } 121 } 122 123 return res 124 } 125 126 func flattenParameters(in map[string]any) (map[string]string, error) { 127 flat, err := flatten.Flatten(in, "", flatten.DotStyle) 128 if err != nil { 129 return nil, fmt.Errorf("error flatenning parameters: %w", err) 130 } 131 132 out := make(map[string]string, len(flat)) 133 for k, v := range flat { 134 out[k] = fmt.Sprintf("%v", v) 135 } 136 137 return out, nil 138 } 139 140 func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetTemplate argoprojiov1alpha1.ApplicationSetTemplate) (argoprojiov1alpha1.ApplicationSetTemplate, error) { 141 // Make a copy of the value from `GetTemplate()` before merge, rather than copying directly into 142 // the provided parameter (which will touch the original resource object returned by client-go) 143 dest := g.GetTemplate(requestedGenerator).DeepCopy() 144 145 err := mergo.Merge(dest, applicationSetTemplate) 146 147 return *dest, err 148 } 149 150 // InterpolateGenerator allows interpolating the matrix's 2nd child generator with values from the 1st child generator 151 // "params" parameter is an array, where each index corresponds to a generator. Each index contains a map w/ that generator's parameters. 152 func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, params map[string]any, useGoTemplate bool, goTemplateOptions []string) (argoprojiov1alpha1.ApplicationSetGenerator, error) { 153 render := utils.Render{} 154 interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate, goTemplateOptions) 155 if err != nil { 156 log.WithError(err).WithField("interpolatedGenerator", interpolatedGenerator).Error("error interpolating generator with other generator's parameter") 157 return argoprojiov1alpha1.ApplicationSetGenerator{}, err 158 } 159 160 return *interpolatedGenerator, nil 161 }