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

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