diff --git a/api/v1alpha1/applicationset_types.go b/api/v1alpha1/applicationset_types.go index 168c2021..886a7ba7 100644 --- a/api/v1alpha1/applicationset_types.go +++ b/api/v1alpha1/applicationset_types.go @@ -49,9 +49,9 @@ type ApplicationSet struct { // ApplicationSetSpec represents a class of application set state. type ApplicationSetSpec struct { Generators []ApplicationSetGenerator `json:"generators"` - Template ApplicationSetTemplate `json:"template"` + Template *ApplicationSetTemplate `json:"template,omitempty"` + UntypedTemplate *ApplicationSetUntypedTemplate `json:"untypedTemplate,omitempty"` SyncPolicy *ApplicationSetSyncPolicy `json:"syncPolicy,omitempty"` - TemplateOptions *ApplicationSetTemplateOptions `json:"templateOptions,omitempty"` } // ApplicationSetSyncPolicy configures how generated Applications will relate to their @@ -61,18 +61,15 @@ type ApplicationSetSyncPolicy struct { PreserveResourcesOnDeletion bool `json:"preserveResourcesOnDeletion,omitempty"` } -// ApplicationSetTemplateOptions configures how template rendering behaves -type ApplicationSetTemplateOptions struct { - // GotemplateEnabled will switch to Go template rendering engine. - GotemplateEnabled bool `json:"gotemplateEnabled,omitempty"` -} - // ApplicationSetTemplate represents argocd ApplicationSpec type ApplicationSetTemplate struct { ApplicationSetTemplateMeta `json:"metadata"` Spec v1alpha1.ApplicationSpec `json:"spec"` } +// ApplicationSetUntypedTemplate represents argocd ApplicationSpec without type check +type ApplicationSetUntypedTemplate string + // ApplicationSetTemplateMeta represents the Argo CD application fields that may // be used for Applications generated from the ApplicationSet (based on metav1.ObjectMeta) type ApplicationSetTemplateMeta struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8d1a6ab3..fab02ff9 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated /* @@ -242,17 +243,21 @@ func (in *ApplicationSetSpec) DeepCopyInto(out *ApplicationSetSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - in.Template.DeepCopyInto(&out.Template) + if in.Template != nil { + in, out := &in.Template, &out.Template + *out = new(ApplicationSetTemplate) + (*in).DeepCopyInto(*out) + } + if in.UntypedTemplate != nil { + in, out := &in.UntypedTemplate, &out.UntypedTemplate + *out = new(ApplicationSetUntypedTemplate) + **out = **in + } if in.SyncPolicy != nil { in, out := &in.SyncPolicy, &out.SyncPolicy *out = new(ApplicationSetSyncPolicy) **out = **in } - if in.TemplateOptions != nil { - in, out := &in.TemplateOptions, &out.TemplateOptions - *out = new(ApplicationSetTemplateOptions) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSetSpec. @@ -353,21 +358,6 @@ func (in *ApplicationSetTemplateMeta) DeepCopy() *ApplicationSetTemplateMeta { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ApplicationSetTemplateOptions) DeepCopyInto(out *ApplicationSetTemplateOptions) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSetTemplateOptions. -func (in *ApplicationSetTemplateOptions) DeepCopy() *ApplicationSetTemplateOptions { - if in == nil { - return nil - } - out := new(ApplicationSetTemplateOptions) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApplicationSetTerminalGenerator) DeepCopyInto(out *ApplicationSetTerminalGenerator) { *out = *in diff --git a/go.mod b/go.mod index 736375c1..ea63c3ef 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/argoproj/applicationset go 1.17 require ( + github.com/Masterminds/sprig/v3 v3.2.2 github.com/argoproj/argo-cd/v2 v2.3.0-rc5.0.20220225234205-31676e2aea6f github.com/argoproj/gitops-engine v0.6.0 github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 @@ -29,6 +30,7 @@ require ( cloud.google.com/go v0.81.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd // indirect + github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect @@ -76,6 +78,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.0 // indirect + github.com/huandu/xstrings v1.3.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect @@ -87,8 +90,10 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect github.com/moby/spdystream v0.2.0 // indirect github.com/moby/term v0.0.0-20210610120745-9d4ed1856297 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -107,6 +112,8 @@ require ( github.com/robfig/cron v1.2.0 // indirect github.com/russross/blackfriday v1.5.2 // indirect github.com/sergi/go-diff v1.1.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.2.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.2.0 // indirect diff --git a/go.sum b/go.sum index d764c3e6..b488d5e0 100644 --- a/go.sum +++ b/go.sum @@ -65,11 +65,16 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/hcsshim v0.8.22/go.mod h1:91uVCVzvX2QD16sMCenoxxXo6L1wJnLMX2PSufFMtF0= @@ -537,12 +542,15 @@ github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7U github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/improbable-eng/grpc-web v0.0.0-20181111100011-16092bd1d58a/go.mod h1:6hRR09jOEG81ADP5wCQju1z71g6OL4eEvELdran/3cs= @@ -661,6 +669,7 @@ github.com/minio/minio-go/v7 v7.0.2/go.mod h1:dJ80Mv2HeGkYLH1sqS/ksz07ON6csH3S6J github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -673,6 +682,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= @@ -868,6 +878,7 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= diff --git a/manifests/crds/argoproj.io_applicationsets.yaml b/manifests/crds/argoproj.io_applicationsets.yaml index d730a11d..74aec7fb 100644 --- a/manifests/crds/argoproj.io_applicationsets.yaml +++ b/manifests/crds/argoproj.io_applicationsets.yaml @@ -6458,14 +6458,10 @@ spec: - metadata - spec type: object - templateOptions: - properties: - gotemplateEnabled: - type: boolean - type: object + untypedTemplate: + type: string required: - generators - - template type: object status: properties: diff --git a/manifests/install.yaml b/manifests/install.yaml index 635db7f4..1a44c57e 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -6457,14 +6457,10 @@ spec: - metadata - spec type: object - templateOptions: - properties: - gotemplateEnabled: - type: boolean - type: object + untypedTemplate: + type: string required: - generators - - template type: object status: properties: diff --git a/pkg/controllers/applicationset_controller.go b/pkg/controllers/applicationset_controller.go index b86ec54d..bf8f62a1 100644 --- a/pkg/controllers/applicationset_controller.go +++ b/pkg/controllers/applicationset_controller.go @@ -428,6 +428,14 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argop var firstError error var applicationSetReason argoprojiov1alpha1.ApplicationSetReasonType + if (applicationSetInfo.Spec.Template == nil && applicationSetInfo.Spec.UntypedTemplate == nil) || + (applicationSetInfo.Spec.Template != nil && applicationSetInfo.Spec.UntypedTemplate != nil) { + firstError = fmt.Errorf("application set spec should have either template or untypedTemplate defined") + applicationSetReason = argoprojiov1alpha1.ApplicationSetReasonErrorOccurred + + return res, applicationSetReason, firstError + } + for _, requestedGenerator := range applicationSetInfo.Spec.Generators { t, err := generators.Transform(requestedGenerator, r.Generators, applicationSetInfo.Spec.Template, &applicationSetInfo) if err != nil { @@ -444,7 +452,7 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argop tmplApplication := getTempApplication(a.Template) for _, p := range a.Params { - app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, applicationSetInfo.Spec.TemplateOptions, p) + app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.UntypedTemplate, applicationSetInfo.Spec.SyncPolicy, p) if err != nil { log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator). Error("error generating application from params") diff --git a/pkg/controllers/applicationset_controller_test.go b/pkg/controllers/applicationset_controller_test.go index f831423e..9efbddec 100644 --- a/pkg/controllers/applicationset_controller_test.go +++ b/pkg/controllers/applicationset_controller_test.go @@ -59,7 +59,7 @@ func (g *generatorMock) GetRequeueAfter(appSetGenerator *argoprojiov1alpha1.Appl return args.Get(0).(time.Duration) } -func (r *rendererMock) RenderTemplateParams(tmpl *argov1alpha1.Application, syncPolicy *argoprojiov1alpha1.ApplicationSetSyncPolicy, templateOptions *argoprojiov1alpha1.ApplicationSetTemplateOptions, params map[string]string) (*argov1alpha1.Application, error) { +func (r *rendererMock) RenderTemplateParams(tmpl *argov1alpha1.Application, untypedTemplate *argoprojiov1alpha1.ApplicationSetUntypedTemplate, syncPolicy *argoprojiov1alpha1.ApplicationSetSyncPolicy, params map[string]string) (*argov1alpha1.Application, error) { args := r.Called(tmpl, params) if args.Error(1) != nil { @@ -187,7 +187,7 @@ func TestExtractApplications(t *testing.T) { }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ Generators: []argoprojiov1alpha1.ApplicationSetGenerator{generator}, - Template: cc.template, + Template: &cc.template, }, }) @@ -300,7 +300,7 @@ func TestMergeTemplateApplications(t *testing.T) { }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ Generators: []argoprojiov1alpha1.ApplicationSetGenerator{generator}, - Template: cc.template, + Template: &cc.template, }, }, ) @@ -370,7 +370,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -428,7 +428,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -486,7 +486,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -548,7 +548,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -608,7 +608,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -680,7 +680,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", Source: argov1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"}, @@ -760,7 +760,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -900,7 +900,7 @@ func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -1060,7 +1060,7 @@ func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -1186,7 +1186,7 @@ func TestCreateApplications(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -1243,7 +1243,7 @@ func TestCreateApplications(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -1355,7 +1355,7 @@ func TestDeleteInCluster(t *testing.T) { Namespace: "namespace", }, Spec: argoprojiov1alpha1.ApplicationSetSpec{ - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ Spec: argov1alpha1.ApplicationSpec{ Project: "project", }, @@ -1797,7 +1797,7 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) { }, }, }, - Template: argoprojiov1alpha1.ApplicationSetTemplate{ + Template: &argoprojiov1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: argoprojiov1alpha1.ApplicationSetTemplateMeta{ Name: "{{cluster}}", Namespace: "argocd", @@ -1883,7 +1883,7 @@ func TestSetApplicationSetStatusCondition(t *testing.T) { }}, }}, }, - Template: argoprojiov1alpha1.ApplicationSetTemplate{}, + Template: &argoprojiov1alpha1.ApplicationSetTemplate{}, }, } diff --git a/pkg/generators/generator_spec_processor.go b/pkg/generators/generator_spec_processor.go index 84786ba1..17963488 100644 --- a/pkg/generators/generator_spec_processor.go +++ b/pkg/generators/generator_spec_processor.go @@ -32,7 +32,7 @@ type TransformResult struct { } //Transform a spec generator to list of paramSets and a template -func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet) ([]TransformResult, error) { +func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, allGenerators map[string]Generator, baseTemplate *argoprojiov1alpha1.ApplicationSetTemplate, appSet *argoprojiov1alpha1.ApplicationSet) ([]TransformResult, error) { res := []TransformResult{} var firstError error @@ -50,6 +50,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al } params, err := g.GenerateParams(&requestedGenerator, appSet) + if err != nil { log.WithError(err).WithField("generator", g). Error("error generating params") @@ -70,13 +71,19 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al } -func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetTemplate argoprojiov1alpha1.ApplicationSetTemplate) (argoprojiov1alpha1.ApplicationSetTemplate, error) { +func mergeGeneratorTemplate(g Generator, requestedGenerator *argoprojiov1alpha1.ApplicationSetGenerator, applicationSetTemplate *argoprojiov1alpha1.ApplicationSetTemplate) (argoprojiov1alpha1.ApplicationSetTemplate, error) { // Make a copy of the value from `GetTemplate()` before merge, rather than copying directly into // the provided parameter (which will touch the original resource object returned by client-go) dest := g.GetTemplate(requestedGenerator).DeepCopy() - err := mergo.Merge(dest, applicationSetTemplate) + var err error + + if applicationSetTemplate != nil { + err = mergo.Merge(dest, applicationSetTemplate) + } else { + log.Warn("generator template won't be applied when standard application template is not used") + } return *dest, err } diff --git a/pkg/generators/matrix.go b/pkg/generators/matrix.go index b6deedc6..a583a753 100644 --- a/pkg/generators/matrix.go +++ b/pkg/generators/matrix.go @@ -103,7 +103,7 @@ func (m *MatrixGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Appli Merge: mergeGenerator, }, m.supportedGenerators, - argoprojiov1alpha1.ApplicationSetTemplate{}, + &argoprojiov1alpha1.ApplicationSetTemplate{}, appSet) if err != nil { diff --git a/pkg/generators/merge.go b/pkg/generators/merge.go index 73fe1412..b2d2ac60 100644 --- a/pkg/generators/merge.go +++ b/pkg/generators/merge.go @@ -162,7 +162,7 @@ func (m *MergeGenerator) getParams(appSetBaseGenerator argoprojiov1alpha1.Applic Merge: mergeGenerator, }, m.supportedGenerators, - argoprojiov1alpha1.ApplicationSetTemplate{}, + &argoprojiov1alpha1.ApplicationSetTemplate{}, appSet) if err != nil { diff --git a/pkg/utils/templating.go b/pkg/utils/templating.go new file mode 100644 index 00000000..da0331e8 --- /dev/null +++ b/pkg/utils/templating.go @@ -0,0 +1,203 @@ +package utils + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + + argoprojiov1alpha1 "github.com/argoproj/applicationset/api/v1alpha1" + argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/valyala/fasttemplate" + + "strconv" + "strings" + "text/template" + + "github.com/Masterminds/sprig/v3" + "sigs.k8s.io/yaml" +) + +type Renderer interface { + RenderTemplateParams(tmpl *argov1alpha1.Application, untypedTemplate *argoprojiov1alpha1.ApplicationSetUntypedTemplate, syncPolicy *argoprojiov1alpha1.ApplicationSetSyncPolicy, params map[string]string) (*argov1alpha1.Application, error) +} + +type Render struct { +} + +func (r *Render) RenderTemplateParams(tmpl *argov1alpha1.Application, untypedTemplate *argoprojiov1alpha1.ApplicationSetUntypedTemplate, syncPolicy *argoprojiov1alpha1.ApplicationSetSyncPolicy, params map[string]string) (*argov1alpha1.Application, error) { + if tmpl == nil { + return nil, fmt.Errorf("application template is empty ") + } + + if len(params) == 0 { + return tmpl, nil + } + + var replacedTmpl argov1alpha1.Application + var replacedTmplStr string + + if untypedTemplate == nil { + // interpolates the `${expression}` first and simple `{reference}` right after + + tmplBytes, err := json.Marshal(tmpl) + if err != nil { + return nil, err + } + + replacedTmplStr, err = renderWithFastTemplateAndGoTemplate(string(tmplBytes), params) + if err != nil { + return nil, err + } + + replacedTmplStr, err = renderWithFastTemplate(replacedTmplStr, params) + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(replacedTmplStr), &replacedTmpl) + if err != nil { + return nil, err + } + } else { + replacedTmplStr, err := renderWithGoTemplate(string(*untypedTemplate), params) + if err != nil { + return nil, err + } + + // UnmarshalStrict to fail early and raise the fact that template + // result produced not what is expected + err = yaml.UnmarshalStrict([]byte(replacedTmplStr), &replacedTmpl) + if err != nil { + return nil, err + } + } + + // Add the 'resources-finalizer' finalizer if: + // The template application doesn't have any finalizers, and: + // a) there is no syncPolicy, or + // b) there IS a syncPolicy, but preserveResourcesOnDeletion is set to false + // See TestRenderTemplateParamsFinalizers in util_test.go for test-based definition of behaviour + if (syncPolicy == nil || !syncPolicy.PreserveResourcesOnDeletion) && + (replacedTmpl.ObjectMeta.Finalizers == nil || len(replacedTmpl.ObjectMeta.Finalizers) == 0) { + + replacedTmpl.ObjectMeta.Finalizers = []string{"resources-finalizer.argocd.argoproj.io"} + } + + return &replacedTmpl, nil +} + +// renderWithGoTemplate executes gotemplate with sprig functions against the raw (untyped) template +func renderWithGoTemplate(rawTemplate string, data map[string]string) (string, error) { + goTemplate, err := template.New("").Option("missingkey=zero").Funcs(createFuncMap()).Parse(rawTemplate) + + if err != nil { + return "", err + } + var tplString bytes.Buffer + + err = goTemplate.Execute(&tplString, data) + if err != nil { + return "", err + } + + return tplString.String(), nil +} + +// renderWithFastTemplateAndGoTemplate executes string substitution with the result of gotemplate run +// for every token found +func renderWithFastTemplateAndGoTemplate(rawTemplate string, data map[string]string) (string, error) { + + fstTmpl := fasttemplate.New(rawTemplate, "${{", "}}") + + replacedTmplStr, err := fstTmpl.ExecuteFuncStringWithErr(func(w io.Writer, tag string) (int, error) { + + trimmedTag := strings.TrimSpace(tag) + // json control characters here are double escaped, what was a double quote " becomes \" when + // unmarshalled to ApplicationSet and then \\\" when marshaled to json + // this becomes a problem with gotemplate trying to parse the token, so unquote the string + unquotedTag, err := strconv.Unquote(`"` + trimmedTag + `"`) + + if err != nil { + return 0, err + } + + // wrapping back in {{}} for gotemplate to identify the expression + gotemplateTag := fmt.Sprintf("{{%s}}", unquotedTag) + + goTemplate, err := template.New("").Option("missingkey=zero").Funcs(createFuncMap()).Parse(string(gotemplateTag)) + if err != nil { + return 0, err + } + + var tplString bytes.Buffer + + err = goTemplate.Execute(&tplString, data) + + if err != nil { + return 0, err + } + + // The following escapes any special characters (e.g. newlines, tabs, etc...) + // in preparation for substitution + replacement := strconv.Quote(tplString.String()) + replacement = replacement[1 : len(replacement)-1] + return w.Write([]byte(replacement)) + }) + + if err != nil { + return "", err + } + + return replacedTmplStr, nil +} + +// replaceWithFastTemplate executes basic string substitution of a template with replacement values. +func renderWithFastTemplate(rawTemplate string, data map[string]string) (string, error) { + var unresolvedErr error + + fstTmpl := fasttemplate.New(rawTemplate, "{{", "}}") + + replacedTmplStr := fstTmpl.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { + + trimmedTag := strings.TrimSpace(tag) + + replacement, ok := data[trimmedTag] + if len(trimmedTag) == 0 || !ok { + return w.Write([]byte(fmt.Sprintf("{{%s}}", tag))) + } + // The following escapes any special characters (e.g. newlines, tabs, etc...) + // in preparation for substitution + replacement = strconv.Quote(replacement) + replacement = replacement[1 : len(replacement)-1] + return w.Write([]byte(replacement)) + }) + if unresolvedErr != nil { + return "", unresolvedErr + } + + return replacedTmplStr, nil + +} + +func ToYaml(v interface{}) (string, error) { + data, err := yaml.Marshal(v) + if err != nil { + return "", err + } + return string(data), nil +} + +func createFuncMap() template.FuncMap { + funcMap := sprig.TxtFuncMap() + + extraFuncMap := template.FuncMap{ + "toYaml": ToYaml, + } + + for name, f := range extraFuncMap { + funcMap[name] = f + } + + return funcMap +} diff --git a/pkg/utils/templating_test.go b/pkg/utils/templating_test.go new file mode 100644 index 00000000..745155ee --- /dev/null +++ b/pkg/utils/templating_test.go @@ -0,0 +1,402 @@ +package utils + +import ( + "testing" + + argoprojiov1alpha1 "github.com/argoproj/applicationset/api/v1alpha1" + argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/stretchr/testify/assert" + // "sigs.k8s.io/yaml" +) + +func composeApplicationFieldsFuncMap() map[string]func(app *argov1alpha1.Application) *string { + // Believe it or not, this is actually less complex than the equivalent solution using reflection + fieldMap := map[string]func(app *argov1alpha1.Application) *string{} + fieldMap["Path"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Source.Path } + fieldMap["RepoURL"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Source.RepoURL } + fieldMap["TargetRevision"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Source.TargetRevision } + fieldMap["Chart"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Source.Chart } + + fieldMap["Server"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Destination.Server } + fieldMap["Namespace"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Destination.Namespace } + fieldMap["Name"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Destination.Name } + + fieldMap["Project"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Project } + + return fieldMap +} + +func TestRenderTemplateParamsWithFasttemplate(t *testing.T) { + + fieldMap := composeApplicationFieldsFuncMap() + + emptyApplication := &argov1alpha1.Application{ + Spec: argov1alpha1.ApplicationSpec{ + Source: argov1alpha1.ApplicationSource{ + Path: "", + RepoURL: "", + TargetRevision: "", + Chart: "", + }, + Destination: argov1alpha1.ApplicationDestination{ + Server: "", + Namespace: "", + Name: "", + }, + Project: "", + }, + } + + tests := []struct { + name string + fieldVal string + params map[string]string + expectedVal string + }{ + { + name: "simple substitution", + fieldVal: "{{one}}", + expectedVal: "two", + params: map[string]string{ + "one": "two", + }, + }, + { + name: "simple substitution with whitespace", + fieldVal: "{{ one }}", + expectedVal: "two", + params: map[string]string{ + "one": "two", + }, + }, + + { + name: "template characters but not in a template", + fieldVal: "}} {{", + expectedVal: "}} {{", + params: map[string]string{ + "one": "two", + }, + }, + + { + name: "nested template", + fieldVal: "{{ }}", + expectedVal: "{{ }}", + params: map[string]string{ + "one": "{{ }}", + }, + }, + { + name: "field with whitespace", + fieldVal: "{{ }}", + expectedVal: "{{ }}", + params: map[string]string{ + " ": "two", + "": "three", + }, + }, + + { + name: "template contains itself, containing itself", + fieldVal: "{{one}}", + expectedVal: "{{one}}", + params: map[string]string{ + "{{one}}": "{{one}}", + }, + }, + + { + name: "template contains itself, containing something else", + fieldVal: "{{one}}", + expectedVal: "{{one}}", + params: map[string]string{ + "{{one}}": "{{two}}", + }, + }, + + { + name: "templates are case sensitive", + fieldVal: "{{ONE}}", + expectedVal: "{{ONE}}", + params: map[string]string{ + "{{one}}": "two", + }, + }, + { + name: "multiple on a line", + fieldVal: "{{one}}{{one}}", + expectedVal: "twotwo", + params: map[string]string{ + "one": "two", + }, + }, + { + name: "multiple different on a line", + fieldVal: "{{one}}{{three}}", + expectedVal: "twofour", + params: map[string]string{ + "one": "two", + "three": "four", + }, + }, + { + name: "gotemplate expressions are supported", + fieldVal: "${{.one}} ${{.three}}", + expectedVal: "two four", + params: map[string]string{ + "one": "two", + "three": "four", + }, + }, + { + name: "sprig functions are avilable", + fieldVal: `${{upper .one}} ${{default "four" .three}}`, + expectedVal: "TWO four", + params: map[string]string{ + "one": "two", + }, + }, + { + name: "mix of gotemplate expressions and fasttemplate", + fieldVal: `{{one}} ${{default "four" .three}}`, + expectedVal: "two four", + params: map[string]string{ + "one": "two", + }, + }, + } + + for _, test := range tests { + + t.Run(test.name, func(t *testing.T) { + + for fieldName, getPtrFunc := range fieldMap { + + // Clone the template application + application := emptyApplication.DeepCopy() + + // Set the value of the target field, to the test value + *getPtrFunc(application) = test.fieldVal + + // Render the cloned application, into a new application + render := Render{} + newApplication, err := render.RenderTemplateParams(application, nil, nil, test.params) + + // Retrieve the value of the target field from the newApplication, then verify that + // the target field has been templated into the expected value + actualValue := *getPtrFunc(newApplication) + assert.Equal(t, test.expectedVal, actualValue, "Field '%s' had an unexpected value. expected: '%s' value: '%s'", fieldName, test.expectedVal, actualValue) + assert.NoError(t, err) + + } + }) + } + +} + +func TestRenderTemplateParamsWithGotemplate(t *testing.T) { + + tests := []struct { + name string + input argoprojiov1alpha1.ApplicationSetUntypedTemplate + params map[string]string + expectedApp argov1alpha1.Application + }{ + { + name: "advanced gotemplate with sprig", + input: ` +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: 'app.{{ default .cluster .DoesNotExist }}' + labels: + {{ range $key, $val := . }} + {{ if (hasPrefix "appLables." $key ) }} + {{ $key | replace "appLables." "" }}: {{ $val }} + {{ end }} + {{ end }} +spec: + project: default + revisionHistoryLimit: {{ atoi .revisionHistoryLimit }} + source: + repoURL: https://github.com/argoproj-labs/applicationset.git + targetRevision: HEAD + path: examples/list-generator/guestbook/engineering-{{ .cluster }} + {{ toYaml (fromJson .sourceRendererConf) | nindent 4 }} + destination: + server: https://kubernetes.default.svc + namespace: {{ if (eq .cluster "dev") }}dev-namespace{{ else }}other-namespace{{ end }} + syncPolicy: + automated: + selfHeal: {{ eq .autosyncSelfHeal "true" }}`, + + params: map[string]string{ + "cluster": "dev", + "appLables.label1": "label1", + "appLables.label2": "label2", + "revisionHistoryLimit": "0", + "sourceRendererConf": "{\"plugin\": {\"name\": \"kustomized-helm\"} }", + "autosyncSelfHeal": "true", + }, + expectedApp: argov1alpha1.Application{ + TypeMeta: metav1.TypeMeta{ + Kind: "Application", + APIVersion: "argoproj.io/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "app.dev", + Finalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + Labels: map[string]string{ + "label1": "label1", + "label2": "label2", + }, + }, + Spec: argov1alpha1.ApplicationSpec{ + Source: argov1alpha1.ApplicationSource{ + Path: "examples/list-generator/guestbook/engineering-dev", + RepoURL: "https://github.com/argoproj-labs/applicationset.git", + TargetRevision: "HEAD", + Chart: "", + Plugin: &argov1alpha1.ApplicationSourcePlugin{ + Name: "kustomized-helm", + }, + }, + Destination: argov1alpha1.ApplicationDestination{ + Server: "https://kubernetes.default.svc", + Namespace: "dev-namespace", + Name: "", + }, + SyncPolicy: &argov1alpha1.SyncPolicy{ + Automated: &argov1alpha1.SyncPolicyAutomated{ + SelfHeal: true, + }, + }, + Project: "default", + RevisionHistoryLimit: new(int64), + }, + }, + }, + } + + for _, test := range tests { + + t.Run(test.name, func(t *testing.T) { + + render := Render{} + + newApplication, err := render.RenderTemplateParams(&argov1alpha1.Application{}, &test.input, nil, test.params) + + assert.NoError(t, err) + assert.Equal(t, *newApplication, test.expectedApp, "Expected: '%s' value: '%s'", *newApplication, test.expectedApp) + }) + } + +} + +func TestRenderTemplateParamsFinalizers(t *testing.T) { + + emptyApplication := &argov1alpha1.Application{ + Spec: argov1alpha1.ApplicationSpec{ + Source: argov1alpha1.ApplicationSource{ + Path: "", + RepoURL: "", + TargetRevision: "", + Chart: "", + }, + Destination: argov1alpha1.ApplicationDestination{ + Server: "", + Namespace: "", + Name: "", + }, + Project: "", + }, + } + + for _, c := range []struct { + testName string + syncPolicy *argoprojiov1alpha1.ApplicationSetSyncPolicy + existingFinalizers []string + expectedFinalizers []string + }{ + { + testName: "existing finalizer should be preserved", + existingFinalizers: []string{"existing-finalizer"}, + syncPolicy: nil, + expectedFinalizers: []string{"existing-finalizer"}, + }, + { + testName: "background finalizer should be preserved", + existingFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"}, + syncPolicy: nil, + expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"}, + }, + + { + testName: "empty finalizer and empty sync should use standard finalizer", + existingFinalizers: nil, + syncPolicy: nil, + expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + }, + + { + testName: "standard finalizer should be preserved", + existingFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + syncPolicy: nil, + expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + }, + { + testName: "empty array finalizers should use standard finalizer", + existingFinalizers: []string{}, + syncPolicy: nil, + expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + }, + { + testName: "non-nil sync policy should use standard finalizer", + existingFinalizers: nil, + syncPolicy: &argoprojiov1alpha1.ApplicationSetSyncPolicy{}, + expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + }, + { + testName: "preserveResourcesOnDeletion should not have a finalizer", + existingFinalizers: nil, + syncPolicy: &argoprojiov1alpha1.ApplicationSetSyncPolicy{ + PreserveResourcesOnDeletion: true, + }, + expectedFinalizers: nil, + }, + { + testName: "user-specified finalizer should overwrite preserveResourcesOnDeletion", + existingFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"}, + syncPolicy: &argoprojiov1alpha1.ApplicationSetSyncPolicy{ + PreserveResourcesOnDeletion: true, + }, + expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"}, + }, + } { + + t.Run(c.testName, func(t *testing.T) { + + // Clone the template application + application := emptyApplication.DeepCopy() + application.Finalizers = c.existingFinalizers + + params := map[string]string{ + "one": "two", + } + + // Render the cloned application, into a new application + render := Render{} + + res, err := render.RenderTemplateParams(application, nil, c.syncPolicy, params) + assert.Nil(t, err) + + assert.ElementsMatch(t, res.Finalizers, c.expectedFinalizers) + + }) + + } + +} diff --git a/pkg/utils/util.go b/pkg/utils/util.go index 40d021b7..22887877 100644 --- a/pkg/utils/util.go +++ b/pkg/utils/util.go @@ -1,140 +1,15 @@ package utils import ( - "bytes" "encoding/json" - "fmt" - "io" "reflect" "sort" - "strconv" "strings" - "text/template" - "github.com/Masterminds/sprig/v3" argoprojiov1alpha1 "github.com/argoproj/applicationset/api/v1alpha1" - argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" log "github.com/sirupsen/logrus" - "github.com/valyala/fasttemplate" - "sigs.k8s.io/yaml" ) -type Renderer interface { - RenderTemplateParams(tmpl *argov1alpha1.Application, syncPolicy *argoprojiov1alpha1.ApplicationSetSyncPolicy, templateOptions *argoprojiov1alpha1.ApplicationSetTemplateOptions, params map[string]string) (*argov1alpha1.Application, error) -} - -type Render struct { -} - -func (r *Render) RenderTemplateParams(tmpl *argov1alpha1.Application, syncPolicy *argoprojiov1alpha1.ApplicationSetSyncPolicy, templateOptions *argoprojiov1alpha1.ApplicationSetTemplateOptions, params map[string]string) (*argov1alpha1.Application, error) { - if tmpl == nil { - return nil, fmt.Errorf("application template is empty ") - } - - if len(params) == 0 { - return tmpl, nil - } - - var replacedTmpl argov1alpha1.Application - var replacedTmplStr string - - if templateOptions == nil || !templateOptions.GotemplateEnabled { - tmplBytes, err := json.Marshal(tmpl) - if err != nil { - return nil, err - } - - fstTmpl := fasttemplate.New(string(tmplBytes), "{{", "}}") - replacedTmplStr, err = r.replaceWithFastTemplate(fstTmpl, params, true) - if err != nil { - return nil, err - } - - err = json.Unmarshal([]byte(replacedTmplStr), &replacedTmpl) - if err != nil { - return nil, err - } - } else { - - // as opposed to json, yaml escapes double quote sign with only one slash and hence allow to use {{ "string" }} scalar variables for various use-cases - tmplBytes, err := yaml.Marshal(tmpl) - if err != nil { - return nil, err - } - - // missingkey=zero - replace unmatched variables with "" - goTemplate, err := template.New("argocd-app").Option("missingkey=zero").Funcs(sprig.TxtFuncMap()).Parse(string(tmplBytes)) - if err != nil { - return nil, err - } - replacedTmplStr, err = r.replaceWithGoTemplate(goTemplate, params) - if err != nil { - return nil, err - } - - err = yaml.Unmarshal([]byte(replacedTmplStr), &replacedTmpl) - if err != nil { - return nil, err - } - } - - // Add the 'resources-finalizer' finalizer if: - // The template application doesn't have any finalizers, and: - // a) there is no syncPolicy, or - // b) there IS a syncPolicy, but preserveResourcesOnDeletion is set to false - // See TestRenderTemplateParamsFinalizers in util_test.go for test-based definition of behaviour - if (syncPolicy == nil || !syncPolicy.PreserveResourcesOnDeletion) && - (replacedTmpl.ObjectMeta.Finalizers == nil || len(replacedTmpl.ObjectMeta.Finalizers) == 0) { - - replacedTmpl.ObjectMeta.Finalizers = []string{"resources-finalizer.argocd.argoproj.io"} - } - - return &replacedTmpl, nil -} - -// replaceWithFastTemplate executes basic string substitution of a template with replacement values. -// 'allowUnresolved' indicates whether or not it is acceptable to have unresolved variables -// remaining in the substituted template. -func (r *Render) replaceWithFastTemplate(fstTmpl *fasttemplate.Template, replaceMap map[string]string, allowUnresolved bool) (string, error) { - var unresolvedErr error - replacedTmpl := fstTmpl.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { - - trimmedTag := strings.TrimSpace(tag) - - replacement, ok := replaceMap[trimmedTag] - if len(trimmedTag) == 0 || !ok { - if allowUnresolved { - // just write the same string back - return w.Write([]byte(fmt.Sprintf("{{%s}}", tag))) - } - unresolvedErr = fmt.Errorf("failed to resolve {{%s}}", tag) - return 0, nil - } - // The following escapes any special characters (e.g. newlines, tabs, etc...) - // in preparation for substitution - replacement = strconv.Quote(replacement) - replacement = replacement[1 : len(replacement)-1] - return w.Write([]byte(replacement)) - }) - if unresolvedErr != nil { - return "", unresolvedErr - } - - return replacedTmpl, nil -} - -// replaceWithGoTemplate applies a parsed Go template to replacement values -func (r *Render) replaceWithGoTemplate(goTemplate *template.Template, replaceMap map[string]string) (string, error) { - var tplString bytes.Buffer - - err := goTemplate.Execute(&tplString, replaceMap) - if err != nil { - return "", err - } - - return tplString.String(), nil -} - // Log a warning if there are unrecognized generators func CheckInvalidGenerators(applicationSetInfo *argoprojiov1alpha1.ApplicationSet) { hasInvalidGenerators, invalidGenerators := invalidGenerators(applicationSetInfo) diff --git a/pkg/utils/util_test.go b/pkg/utils/util_test.go index 05fd562f..8829a9cc 100644 --- a/pkg/utils/util_test.go +++ b/pkg/utils/util_test.go @@ -12,397 +12,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -func composeApplicationFieldsFuncMap() map[string]func(app *argov1alpha1.Application) *string { - // Believe it or not, this is actually less complex than the equivalent solution using reflection - fieldMap := map[string]func(app *argov1alpha1.Application) *string{} - fieldMap["Path"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Source.Path } - fieldMap["RepoURL"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Source.RepoURL } - fieldMap["TargetRevision"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Source.TargetRevision } - fieldMap["Chart"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Source.Chart } - - fieldMap["Server"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Destination.Server } - fieldMap["Namespace"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Destination.Namespace } - fieldMap["Name"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Destination.Name } - - fieldMap["Project"] = func(app *argov1alpha1.Application) *string { return &app.Spec.Project } - - return fieldMap -} - -func TestRenderTemplateParams(t *testing.T) { - - fieldMap := composeApplicationFieldsFuncMap() - - emptyApplication := &argov1alpha1.Application{ - Spec: argov1alpha1.ApplicationSpec{ - Source: argov1alpha1.ApplicationSource{ - Path: "", - RepoURL: "", - TargetRevision: "", - Chart: "", - }, - Destination: argov1alpha1.ApplicationDestination{ - Server: "", - Namespace: "", - Name: "", - }, - Project: "", - }, - } - - tests := []struct { - name string - fieldVal string - params map[string]string - expectedVal string - }{ - { - name: "simple substitution", - fieldVal: "{{one}}", - expectedVal: "two", - params: map[string]string{ - "one": "two", - }, - }, - { - name: "simple substitution with whitespace", - fieldVal: "{{ one }}", - expectedVal: "two", - params: map[string]string{ - "one": "two", - }, - }, - - { - name: "template characters but not in a template", - fieldVal: "}} {{", - expectedVal: "}} {{", - params: map[string]string{ - "one": "two", - }, - }, - - { - name: "nested template", - fieldVal: "{{ }}", - expectedVal: "{{ }}", - params: map[string]string{ - "one": "{{ }}", - }, - }, - { - name: "field with whitespace", - fieldVal: "{{ }}", - expectedVal: "{{ }}", - params: map[string]string{ - " ": "two", - "": "three", - }, - }, - - { - name: "template contains itself, containing itself", - fieldVal: "{{one}}", - expectedVal: "{{one}}", - params: map[string]string{ - "{{one}}": "{{one}}", - }, - }, - - { - name: "template contains itself, containing something else", - fieldVal: "{{one}}", - expectedVal: "{{one}}", - params: map[string]string{ - "{{one}}": "{{two}}", - }, - }, - - { - name: "templates are case sensitive", - fieldVal: "{{ONE}}", - expectedVal: "{{ONE}}", - params: map[string]string{ - "{{one}}": "two", - }, - }, - { - name: "multiple on a line", - fieldVal: "{{one}}{{one}}", - expectedVal: "twotwo", - params: map[string]string{ - "one": "two", - }, - }, - { - name: "multiple different on a line", - fieldVal: "{{one}}{{three}}", - expectedVal: "twofour", - params: map[string]string{ - "one": "two", - "three": "four", - }, - }, - } - - for _, test := range tests { - - t.Run(test.name, func(t *testing.T) { - - for fieldName, getPtrFunc := range fieldMap { - - // Clone the template application - application := emptyApplication.DeepCopy() - - // Set the value of the target field, to the test value - *getPtrFunc(application) = test.fieldVal - - // Render the cloned application, into a new application - render := Render{} - newApplication, err := render.RenderTemplateParams(application, nil, nil, test.params) - - // Retrieve the value of the target field from the newApplication, then verify that - // the target field has been templated into the expected value - actualValue := *getPtrFunc(newApplication) - assert.Equal(t, test.expectedVal, actualValue, "Field '%s' had an unexpected value. expected: '%s' value: '%s'", fieldName, test.expectedVal, actualValue) - assert.NoError(t, err) - - } - }) - } - -} - -func TestRenderTemplateParamsFinalizers(t *testing.T) { - - emptyApplication := &argov1alpha1.Application{ - Spec: argov1alpha1.ApplicationSpec{ - Source: argov1alpha1.ApplicationSource{ - Path: "", - RepoURL: "", - TargetRevision: "", - Chart: "", - }, - Destination: argov1alpha1.ApplicationDestination{ - Server: "", - Namespace: "", - Name: "", - }, - Project: "", - }, - } - - for _, c := range []struct { - testName string - syncPolicy *argoprojiov1alpha1.ApplicationSetSyncPolicy - existingFinalizers []string - expectedFinalizers []string - }{ - { - testName: "existing finalizer should be preserved", - existingFinalizers: []string{"existing-finalizer"}, - syncPolicy: nil, - expectedFinalizers: []string{"existing-finalizer"}, - }, - { - testName: "background finalizer should be preserved", - existingFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"}, - syncPolicy: nil, - expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"}, - }, - - { - testName: "empty finalizer and empty sync should use standard finalizer", - existingFinalizers: nil, - syncPolicy: nil, - expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, - }, - - { - testName: "standard finalizer should be preserved", - existingFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, - syncPolicy: nil, - expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, - }, - { - testName: "empty array finalizers should use standard finalizer", - existingFinalizers: []string{}, - syncPolicy: nil, - expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, - }, - { - testName: "non-nil sync policy should use standard finalizer", - existingFinalizers: nil, - syncPolicy: &argoprojiov1alpha1.ApplicationSetSyncPolicy{}, - expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io"}, - }, - { - testName: "preserveResourcesOnDeletion should not have a finalizer", - existingFinalizers: nil, - syncPolicy: &argoprojiov1alpha1.ApplicationSetSyncPolicy{ - PreserveResourcesOnDeletion: true, - }, - expectedFinalizers: nil, - }, - { - testName: "user-specified finalizer should overwrite preserveResourcesOnDeletion", - existingFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"}, - syncPolicy: &argoprojiov1alpha1.ApplicationSetSyncPolicy{ - PreserveResourcesOnDeletion: true, - }, - expectedFinalizers: []string{"resources-finalizer.argocd.argoproj.io/background"}, - }, - } { - - t.Run(c.testName, func(t *testing.T) { - - // Clone the template application - application := emptyApplication.DeepCopy() - application.Finalizers = c.existingFinalizers - - params := map[string]string{ - "one": "two", - } - - // Render the cloned application, into a new application - render := Render{} - - res, err := render.RenderTemplateParams(application, c.syncPolicy, nil, params) - assert.Nil(t, err) - - assert.ElementsMatch(t, res.Finalizers, c.expectedFinalizers) - - }) - - } - -} - -func TestRenderGoTemplateParams(t *testing.T) { - - fieldMap := composeApplicationFieldsFuncMap() - - emptyApplication := &argov1alpha1.Application{ - Spec: argov1alpha1.ApplicationSpec{ - Source: argov1alpha1.ApplicationSource{ - Path: "", - RepoURL: "", - TargetRevision: "", - Chart: "", - }, - Destination: argov1alpha1.ApplicationDestination{ - Server: "", - Namespace: "", - Name: "", - }, - Project: "", - }, - } - - tests := []struct { - name string - fieldVal string - templateOptions *argoprojiov1alpha1.ApplicationSetTemplateOptions - params map[string]string - expectedVal string - }{ - { - name: "simple substitution", - fieldVal: "{{.one}}", - expectedVal: "two", - templateOptions: &argoprojiov1alpha1.ApplicationSetTemplateOptions{ - GotemplateEnabled: true, - }, - params: map[string]string{ - "one": "two", - }, - }, - { - name: "simple substitution with whitespace", - fieldVal: "{{ .one }}", - expectedVal: "two", - templateOptions: &argoprojiov1alpha1.ApplicationSetTemplateOptions{ - GotemplateEnabled: true, - }, - params: map[string]string{ - "one": "two", - }, - }, - { - name: "multiple on a line", - fieldVal: "{{.one}}{{.one}}", - expectedVal: "twotwo", - templateOptions: &argoprojiov1alpha1.ApplicationSetTemplateOptions{ - GotemplateEnabled: true, - }, - params: map[string]string{ - "one": "two", - }, - }, - { - name: "multiple different on a line", - fieldVal: "{{.one}}{{.three}}", - expectedVal: "twofour", - templateOptions: &argoprojiov1alpha1.ApplicationSetTemplateOptions{ - GotemplateEnabled: true, - }, - params: map[string]string{ - "one": "two", - "three": "four", - }, - }, - { - name: "sprig functions are avilable", - fieldVal: `{{upper .one}}{{default "four" .three}}`, - expectedVal: "TWOfour", - templateOptions: &argoprojiov1alpha1.ApplicationSetTemplateOptions{ - GotemplateEnabled: true, - }, - params: map[string]string{ - "one": "two", - }, - }, - { - name: "the output application structure is imutable", - fieldVal: "{{ .yamlPiece }}\n\tsomeother: element", - expectedVal: "targetRevision: my-other-branch\n\tsomeother: element", - templateOptions: &argoprojiov1alpha1.ApplicationSetTemplateOptions{ - GotemplateEnabled: true, - }, - params: map[string]string{ - "yamlPiece": "targetRevision: my-other-branch", - }, - }, - } - - for _, test := range tests { - - t.Run(test.name, func(t *testing.T) { - - for fieldName, getPtrFunc := range fieldMap { - - // Clone the template application - application := emptyApplication.DeepCopy() - - // Set the value of the target field, to the test value - *getPtrFunc(application) = test.fieldVal - - // Render the cloned application, into a new application - render := Render{} - newApplication, err := render.RenderTemplateParams(application, nil, test.templateOptions, test.params) - - // Retrieve the value of the target field from the newApplication, then verify that - // the target field has been templated into the expected value - actualValue := *getPtrFunc(newApplication) - assert.Equal(t, test.expectedVal, actualValue, "Field '%s' had an unexpected value. expected: '%s' value: '%s'", fieldName, test.expectedVal, actualValue) - assert.NoError(t, err) - - } - }) - } - -} - func TestCheckInvalidGenerators(t *testing.T) { scheme := runtime.NewScheme() diff --git a/test/e2e/applicationset/applicationset_test.go b/test/e2e/applicationset/applicationset_test.go index 0e50691e..576dd92d 100644 --- a/test/e2e/applicationset/applicationset_test.go +++ b/test/e2e/applicationset/applicationset_test.go @@ -71,7 +71,7 @@ func TestSimpleListGenerator(t *testing.T) { Name: "simple-list-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{cluster}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -172,7 +172,7 @@ func TestSimpleGitDirectoryGenerator(t *testing.T) { Name: "simple-git-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{path.basename}}"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -281,7 +281,7 @@ func TestSimpleGitFilesGenerator(t *testing.T) { Name: "simple-git-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{cluster.name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -357,7 +357,7 @@ func TestSimpleGitFilesPreserveResourcesOnDeletion(t *testing.T) { Name: "simple-git-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{cluster.name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -442,7 +442,7 @@ func TestSimpleSCMProviderGenerator(t *testing.T) { Name: "simple-scm-provider-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{ repository }}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -506,7 +506,7 @@ func TestCustomApplicationFinalizers(t *testing.T) { Name: "simple-list-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ Name: "{{cluster}}-guestbook", Finalizers: []string{"resources-finalizer.argocd.argoproj.io/background"}, @@ -580,7 +580,7 @@ func TestSimplePullRequestGenerator(t *testing.T) { Name: "simple-pull-request-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "guestbook-{{ number }}"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", diff --git a/test/e2e/applicationset/cluster_e2e_test.go b/test/e2e/applicationset/cluster_e2e_test.go index f5128950..cb099139 100644 --- a/test/e2e/applicationset/cluster_e2e_test.go +++ b/test/e2e/applicationset/cluster_e2e_test.go @@ -47,7 +47,7 @@ func TestSimpleClusterGenerator(t *testing.T) { Name: "simple-cluster-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -172,7 +172,7 @@ func TestClusterGeneratorWithLocalCluster(t *testing.T) { Name: "in-cluster-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -263,7 +263,7 @@ func TestSimpleClusterGeneratorAddingCluster(t *testing.T) { Name: "simple-cluster-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -346,7 +346,7 @@ func TestSimpleClusterGeneratorDeletingCluster(t *testing.T) { Name: "simple-cluster-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", diff --git a/test/e2e/applicationset/crl_e2e_test.go b/test/e2e/applicationset/crl_e2e_test.go index 7e6c18fc..33b5d1e4 100644 --- a/test/e2e/applicationset/crl_e2e_test.go +++ b/test/e2e/applicationset/crl_e2e_test.go @@ -60,7 +60,7 @@ func TestSimpleClusterDecisionResourceGenerator(t *testing.T) { Name: "simple-cluster-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -171,7 +171,7 @@ func TestSimpleClusterDecisionResourceGeneratorAddingCluster(t *testing.T) { Name: "simple-cluster-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -267,7 +267,7 @@ func TestSimpleClusterDecisionResourceGeneratorDeletingClusterSecret(t *testing. Name: "simple-cluster-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -371,7 +371,7 @@ func TestSimpleClusterDecisionResourceGeneratorDeletingClusterFromResource(t *te Name: "simple-cluster-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-guestbook"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", diff --git a/test/e2e/applicationset/matrix_e2e_test.go b/test/e2e/applicationset/matrix_e2e_test.go index be5d8bb2..6dd1188a 100644 --- a/test/e2e/applicationset/matrix_e2e_test.go +++ b/test/e2e/applicationset/matrix_e2e_test.go @@ -59,7 +59,7 @@ func TestListMatrixGenerator(t *testing.T) { Name: "matrix-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{values.name}}-{{path.basename}}"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -185,7 +185,7 @@ func TestClusterMatrixGenerator(t *testing.T) { Name: "matrix-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-{{path.basename}}"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", diff --git a/test/e2e/applicationset/merge_e2e_test.go b/test/e2e/applicationset/merge_e2e_test.go index 4ed34544..39f90669 100644 --- a/test/e2e/applicationset/merge_e2e_test.go +++ b/test/e2e/applicationset/merge_e2e_test.go @@ -55,7 +55,7 @@ func TestListMergeGenerator(t *testing.T) { Name: "merge-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{path.basename}}-{{name-suffix}}"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default", @@ -182,7 +182,7 @@ func TestClusterMergeGenerator(t *testing.T) { Name: "merge-generator", }, Spec: v1alpha1.ApplicationSetSpec{ - Template: v1alpha1.ApplicationSetTemplate{ + Template: &v1alpha1.ApplicationSetTemplate{ ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-{{path.basename}}-{{values.name-suffix}}"}, Spec: argov1alpha1.ApplicationSpec{ Project: "default",