Skip to content
12 changes: 10 additions & 2 deletions applicationset/controllers/applicationset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,16 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov
var firstError error
var applicationSetReason argov1alpha1.ApplicationSetReasonType

if (applicationSetInfo.Spec.Template == nil && applicationSetInfo.Spec.StringTemplate == nil) ||
(applicationSetInfo.Spec.Template != nil && applicationSetInfo.Spec.StringTemplate != nil) {
firstError = fmt.Errorf("application set spec should have either template or stringTemplate defined")
applicationSetReason = argov1alpha1.ApplicationSetReasonErrorOccurred

return res, applicationSetReason, firstError
}

for _, requestedGenerator := range applicationSetInfo.Spec.Generators {
t, err := generators.Transform(requestedGenerator, r.Generators, applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]interface{}{})
t, err := generators.Transform(requestedGenerator, r.Generators, *applicationSetInfo.Spec.Template, &applicationSetInfo, map[string]interface{}{})
if err != nil {
log.WithError(err).WithField("generator", requestedGenerator).
Error("error generating application from params")
Expand All @@ -446,7 +454,7 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov
tmplApplication := getTempApplication(a.Template)

for _, p := range a.Params {
app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate)
app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.StringTemplate, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate)
if err != nil {
log.WithError(err).WithField("params", a.Params).WithField("generator", requestedGenerator).
Error("error generating application from params")
Expand Down
39 changes: 20 additions & 19 deletions applicationset/controllers/applicationset_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ func (g *generatorMock) GetRequeueAfter(appSetGenerator *argov1alpha1.Applicatio
return args.Get(0).(time.Duration)
}

func (r *rendererMock) RenderTemplateParams(tmpl *argov1alpha1.Application, syncPolicy *argov1alpha1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*argov1alpha1.Application, error) {
func (r *rendererMock) RenderTemplateParams(tmpl *argov1alpha1.Application, stringTemplate *argov1alpha1.ApplicationSetStringTemplate, syncPolicy *argov1alpha1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*argov1alpha1.Application, error) {

args := r.Called(tmpl, params, useGoTemplate)

if args.Error(1) != nil {
Expand Down Expand Up @@ -188,7 +189,7 @@ func TestExtractApplications(t *testing.T) {
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
Template: &cc.template,
},
})

Expand Down Expand Up @@ -301,7 +302,7 @@ func TestMergeTemplateApplications(t *testing.T) {
},
Spec: argov1alpha1.ApplicationSetSpec{
Generators: []argov1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
Template: &cc.template,
},
},
)
Expand Down Expand Up @@ -371,7 +372,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -429,7 +430,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -487,7 +488,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -549,7 +550,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -609,7 +610,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -681,7 +682,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
Source: argov1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
Expand Down Expand Up @@ -761,7 +762,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -903,7 +904,7 @@ func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -1065,7 +1066,7 @@ func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -1193,7 +1194,7 @@ func TestCreateApplications(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -1250,7 +1251,7 @@ func TestCreateApplications(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -1362,7 +1363,7 @@ func TestDeleteInCluster(t *testing.T) {
Namespace: "namespace",
},
Spec: argov1alpha1.ApplicationSetSpec{
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
Expand Down Expand Up @@ -1805,7 +1806,7 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
},
},
},
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: argov1alpha1.ApplicationSetTemplateMeta{
Name: "{{.cluster}}",
Namespace: "argocd",
Expand Down Expand Up @@ -1890,7 +1891,7 @@ func TestSetApplicationSetStatusCondition(t *testing.T) {
}},
}},
},
Template: argov1alpha1.ApplicationSetTemplate{},
Template: &argov1alpha1.ApplicationSetTemplate{},
},
}

Expand Down Expand Up @@ -2017,7 +2018,7 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
Generators: []argov1alpha1.ApplicationSetGenerator{{
PullRequest: &argov1alpha1.PullRequestGenerator{},
}},
Template: cases.template,
Template: &cases.template,
},
},
)
Expand Down Expand Up @@ -2104,7 +2105,7 @@ func TestPolicies(t *testing.T) {
},
},
},
Template: argov1alpha1.ApplicationSetTemplate{
Template: &argov1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: argov1alpha1.ApplicationSetTemplateMeta{
Name: "{{.name}}",
Namespace: "argocd",
Expand Down
16 changes: 11 additions & 5 deletions applicationset/generators/generator_spec_processor.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package generators

import (
"fmt"
"encoding/json"
"fmt"
"reflect"

"github.com/argoproj/argo-cd/v2/applicationset/utils"
Expand All @@ -25,7 +25,7 @@ type TransformResult struct {
Template argoprojiov1alpha1.ApplicationSetTemplate
}

//Transform a spec generator to list of paramSets and a template
// 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, genParams map[string]interface{}) ([]TransformResult, error) {
selector, err := metav1.LabelSelectorAsSelector(requestedGenerator.Selector)
if err != nil {
Expand All @@ -39,7 +39,7 @@ func Transform(requestedGenerator argoprojiov1alpha1.ApplicationSetGenerator, al
generators := GetRelevantGenerators(&requestedGenerator, allGenerators)
for _, g := range generators {
// we call mergeGeneratorTemplate first because GenerateParams might be more costly so we want to fail fast if there is an error
mergedTemplate, err := mergeGeneratorTemplate(g, &requestedGenerator, baseTemplate)
mergedTemplate, err := mergeGeneratorTemplate(g, &requestedGenerator, &baseTemplate)
if err != nil {
log.WithError(err).WithField("generator", g).
Error("error generating params")
Expand Down Expand Up @@ -122,12 +122,18 @@ func GetRelevantGenerators(requestedGenerator *argoprojiov1alpha1.ApplicationSet
return res
}

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
}
Expand Down
50 changes: 40 additions & 10 deletions applicationset/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/Masterminds/sprig/v3"
"github.com/valyala/fasttemplate"
"sigs.k8s.io/yaml"

log "github.com/sirupsen/logrus"

Expand All @@ -27,11 +28,19 @@ func init() {
delete(sprigFuncMap, "env")
delete(sprigFuncMap, "expandenv")
delete(sprigFuncMap, "getHostByName")
sprigFuncMap["normalize"] = SanitizeName
extraFuncMap := template.FuncMap{
"toYaml": ToYaml,
"normalize": SanitizeName,
}

for name, f := range extraFuncMap {
sprigFuncMap[name] = f
}

}

type Renderer interface {
RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *argoappsv1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*argoappsv1.Application, error)
RenderTemplateParams(tmpl *argoappsv1.Application, stringTemplate *argoappsv1.ApplicationSetStringTemplate, syncPolicy *argoappsv1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*argoappsv1.Application, error)
}

type Render struct {
Expand Down Expand Up @@ -172,24 +181,37 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
return nil
}

func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, syncPolicy *argoappsv1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*argoappsv1.Application, error) {
func (r *Render) RenderTemplateParams(tmpl *argoappsv1.Application, stringTemplate *argoappsv1.ApplicationSetStringTemplate, syncPolicy *argoappsv1.ApplicationSetSyncPolicy, params map[string]interface{}, useGoTemplate bool) (*argoappsv1.Application, error) {

if tmpl == nil {
return nil, fmt.Errorf("application template is empty ")
}

if len(params) == 0 {
return tmpl, nil
}
var replacedTmpl *argoappsv1.Application
if stringTemplate == nil {
original := reflect.ValueOf(tmpl)
copy := reflect.New(original.Type()).Elem()

original := reflect.ValueOf(tmpl)
copy := reflect.New(original.Type()).Elem()
if err := r.deeplyReplace(copy, original, params, useGoTemplate); err != nil {
return nil, err
}

if err := r.deeplyReplace(copy, original, params, useGoTemplate); err != nil {
return nil, err
replacedTmpl = copy.Interface().(*argoappsv1.Application)
} else {
replacedTmplStr, err := r.Replace(string(*stringTemplate), params, true)
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
}
}

replacedTmpl := copy.Interface().(*argoappsv1.Application)

// Add the 'resources-finalizer' finalizer if:
// The template application doesn't have any finalizers, and:
// a) there is no syncPolicy, or
Expand Down Expand Up @@ -353,3 +375,11 @@ func SanitizeName(name string) string {

return strings.Trim(name, "-.")
}

func ToYaml(v interface{}) (string, error) {
data, err := yaml.Marshal(v)
if err != nil {
return "", err
}
return string(data), nil
}
Loading