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  }