diff --git a/pkg/apis/pipeline/v1alpha1/param_types_test.go b/pkg/apis/pipeline/v1alpha1/param_types_test.go index 9d1aec7c4dd..1da488a6cb8 100644 --- a/pkg/apis/pipeline/v1alpha1/param_types_test.go +++ b/pkg/apis/pipeline/v1alpha1/param_types_test.go @@ -173,7 +173,7 @@ func TestArrayOrString_ApplyReplacements(t *testing.T) { }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.args.input.ApplyReplacements(tt.args.stringReplacements, tt.args.arrayReplacements) + tt.args.input.ApplyReplacements(tt.args.stringReplacements, tt.args.arrayReplacements, nil) if d := cmp.Diff(tt.expectedOutput, tt.args.input); d != "" { t.Errorf("ApplyReplacements() output did not match expected value %s", diff.PrintWantGot(d)) } diff --git a/pkg/apis/pipeline/v1beta1/param_types.go b/pkg/apis/pipeline/v1beta1/param_types.go index 068278264fd..1c3389c8051 100644 --- a/pkg/apis/pipeline/v1beta1/param_types.go +++ b/pkg/apis/pipeline/v1beta1/param_types.go @@ -195,15 +195,58 @@ func (arrayOrString ArrayOrString) MarshalJSON() ([]byte, error) { } // ApplyReplacements applyes replacements for ArrayOrString type -func (arrayOrString *ArrayOrString) ApplyReplacements(stringReplacements map[string]string, arrayReplacements map[string][]string) { - if arrayOrString.Type == ParamTypeString { - arrayOrString.StringVal = substitution.ApplyReplacements(arrayOrString.StringVal, stringReplacements) - } else { +func (arrayOrString *ArrayOrString) ApplyReplacements(stringReplacements map[string]string, arrayReplacements map[string][]string, objectReplacements map[string]map[string]string) { + switch arrayOrString.Type { + case ParamTypeArray: var newArrayVal []string for _, v := range arrayOrString.ArrayVal { newArrayVal = append(newArrayVal, substitution.ApplyArrayReplacements(v, stringReplacements, arrayReplacements)...) } arrayOrString.ArrayVal = newArrayVal + case ParamTypeObject: + newObjectVal := map[string]string{} + for k, v := range arrayOrString.ObjectVal { + newObjectVal[k] = substitution.ApplyReplacements(v, stringReplacements) + } + arrayOrString.ObjectVal = newObjectVal + default: + arrayOrString.applyOrCorrect(stringReplacements, arrayReplacements, objectReplacements) + } +} + +// applyOrCorrect deals with string param whose value can be string literal or a reference to a string/array/object param/result. +// If the value of arrayOrString is a reference to array or object, the type will be corrected from string to array/object. +func (arrayOrString *ArrayOrString) applyOrCorrect(stringReplacements map[string]string, arrayReplacements map[string][]string, objectReplacements map[string]map[string]string) { + stringVal := arrayOrString.StringVal + + // if the stringVal is a string literal or a string that mixed with var references + // just do the normal string replacement + if !exactVariableSubstitutionRegex.MatchString(stringVal) { + arrayOrString.StringVal = substitution.ApplyReplacements(arrayOrString.StringVal, stringReplacements) + return + } + + // trim the head "$(" and the tail ")" or "[*])" + // i.e. get "params.name" from "$(params.name)" or "$(params.name[*])" + trimedStringVal := strings.TrimSuffix(strings.TrimSuffix(strings.TrimPrefix(stringVal, "$("), ")"), "[*]") + + // if the stringVal is a reference to a string param + if _, ok := stringReplacements[trimedStringVal]; ok { + arrayOrString.StringVal = substitution.ApplyReplacements(arrayOrString.StringVal, stringReplacements) + } + + // if the stringVal is a reference to an array param, we need to change the type other than apply replacement + if _, ok := arrayReplacements[trimedStringVal]; ok { + arrayOrString.StringVal = "" + arrayOrString.ArrayVal = substitution.ApplyArrayReplacements(stringVal, stringReplacements, arrayReplacements) + arrayOrString.Type = ParamTypeArray + } + + // if the stringVal is a reference an object param, we need to change the type other than apply replacement + if _, ok := objectReplacements[trimedStringVal]; ok { + arrayOrString.StringVal = "" + arrayOrString.ObjectVal = objectReplacements[trimedStringVal] + arrayOrString.Type = ParamTypeObject } } diff --git a/pkg/apis/pipeline/v1beta1/param_types_test.go b/pkg/apis/pipeline/v1beta1/param_types_test.go index b9dedac0100..40676152371 100644 --- a/pkg/apis/pipeline/v1beta1/param_types_test.go +++ b/pkg/apis/pipeline/v1beta1/param_types_test.go @@ -162,6 +162,7 @@ func TestArrayOrString_ApplyReplacements(t *testing.T) { input *v1beta1.ArrayOrString stringReplacements map[string]string arrayReplacements map[string][]string + objectReplacements map[string]map[string]string } tests := []struct { name string @@ -176,7 +177,15 @@ func TestArrayOrString_ApplyReplacements(t *testing.T) { }, expectedOutput: v1beta1.NewArrayOrString("an", "array"), }, { - name: "string replacements on string", + name: "single string replacement on string", + args: args{ + input: v1beta1.NewArrayOrString("$(params.myString1)"), + stringReplacements: map[string]string{"params.myString1": "value1", "params.myString2": "value2"}, + arrayReplacements: map[string][]string{"arraykey": {"array", "value"}, "sdfdf": {"asdf", "sdfsd"}}, + }, + expectedOutput: v1beta1.NewArrayOrString("value1"), + }, { + name: "multiple string replacements on string", args: args{ input: v1beta1.NewArrayOrString("astring$(some) asdf $(anotherkey)"), stringReplacements: map[string]string{"some": "value", "anotherkey": "value"}, @@ -207,10 +216,70 @@ func TestArrayOrString_ApplyReplacements(t *testing.T) { arrayReplacements: map[string][]string{"arraykey": {}}, }, expectedOutput: v1beta1.NewArrayOrString("firstvalue", "lastvalue"), + }, { + name: "array replacement on string val", + args: args{ + input: v1beta1.NewArrayOrString("$(params.myarray)"), + arrayReplacements: map[string][]string{"params.myarray": {"a", "b", "c"}}, + }, + expectedOutput: v1beta1.NewArrayOrString("a", "b", "c"), + }, { + name: "array star replacement on string val", + args: args{ + input: v1beta1.NewArrayOrString("$(params.myarray[*])"), + arrayReplacements: map[string][]string{"params.myarray": {"a", "b", "c"}}, + }, + expectedOutput: v1beta1.NewArrayOrString("a", "b", "c"), + }, { + name: "object replacement on string val", + args: args{ + input: v1beta1.NewArrayOrString("$(params.object)"), + objectReplacements: map[string]map[string]string{ + "params.object": { + "url": "abc.com", + "commit": "af234", + }, + }, + }, + expectedOutput: v1beta1.NewObject(map[string]string{ + "url": "abc.com", + "commit": "af234", + }), + }, { + name: "object star replacement on string val", + args: args{ + input: v1beta1.NewArrayOrString("$(params.object[*])"), + objectReplacements: map[string]map[string]string{ + "params.object": { + "url": "abc.com", + "commit": "af234", + }, + }, + }, + expectedOutput: v1beta1.NewObject(map[string]string{ + "url": "abc.com", + "commit": "af234", + }), + }, { + name: "string replacement on object individual variables", + args: args{ + input: v1beta1.NewObject(map[string]string{ + "key1": "$(mystring)", + "key2": "$(anotherObject.key)", + }), + stringReplacements: map[string]string{ + "mystring": "foo", + "anotherObject.key": "bar", + }, + }, + expectedOutput: v1beta1.NewObject(map[string]string{ + "key1": "foo", + "key2": "bar", + }), }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.args.input.ApplyReplacements(tt.args.stringReplacements, tt.args.arrayReplacements) + tt.args.input.ApplyReplacements(tt.args.stringReplacements, tt.args.arrayReplacements, tt.args.objectReplacements) if d := cmp.Diff(tt.expectedOutput, tt.args.input); d != "" { t.Errorf("ApplyReplacements() output did not match expected value %s", diff.PrintWantGot(d)) } diff --git a/pkg/apis/pipeline/v1beta1/resultref.go b/pkg/apis/pipeline/v1beta1/resultref.go index 466923dc8df..2bf23ddf2fb 100644 --- a/pkg/apis/pipeline/v1beta1/resultref.go +++ b/pkg/apis/pipeline/v1beta1/resultref.go @@ -43,7 +43,8 @@ const ( ResultResultPart = "results" // TODO(#2462) use one regex across all substitutions // variableSubstitutionFormat matches format like $result.resultname, $result.resultname[int] and $result.resultname[*] - variableSubstitutionFormat = `\$\([_a-zA-Z0-9.-]+(\.[_a-zA-Z0-9.-]+)*(\[([0-9])*\*?\])?\)` + variableSubstitutionFormat = `\$\([_a-zA-Z0-9.-]+(\.[_a-zA-Z0-9.-]+)*(\[([0-9]+|\*)\])?\)` + exactVariableSubstitutionFormat = `^\$\([_a-zA-Z0-9.-]+(\.[_a-zA-Z0-9.-]+)*(\[([0-9]+|\*)\])?\)$` // arrayIndexing will match all `[int]` and `[*]` for parseExpression arrayIndexing = `\[([0-9])*\*?\]` // ResultNameFormat Constant used to define the the regex Result.Name should follow @@ -51,6 +52,7 @@ const ( ) var variableSubstitutionRegex = regexp.MustCompile(variableSubstitutionFormat) +var exactVariableSubstitutionRegex = regexp.MustCompile(exactVariableSubstitutionFormat) var resultNameFormatRegex = regexp.MustCompile(ResultNameFormat) var arrayIndexingRegex = regexp.MustCompile(arrayIndexing) diff --git a/pkg/reconciler/pipelinerun/resources/apply.go b/pkg/reconciler/pipelinerun/resources/apply.go index fb17e486174..3097c56c243 100644 --- a/pkg/reconciler/pipelinerun/resources/apply.go +++ b/pkg/reconciler/pipelinerun/resources/apply.go @@ -19,6 +19,7 @@ package resources import ( "context" "fmt" + "log" "strconv" "strings" @@ -34,10 +35,11 @@ import ( func ApplyParameters(ctx context.Context, p *v1beta1.PipelineSpec, pr *v1beta1.PipelineRun) *v1beta1.PipelineSpec { // This assumes that the PipelineRun inputs have been validated against what the Pipeline requests. - // stringReplacements is used for standard single-string stringReplacements, while arrayReplacements contains arrays - // that need to be further processed. + // stringReplacements is used for standard single-string stringReplacements, + // while arrayReplacements/objectReplacements contains arrays/objects that need to be further processed. stringReplacements := map[string]string{} arrayReplacements := map[string][]string{} + objectReplacements := map[string]map[string]string{} patterns := []string{ "params.%s", @@ -45,34 +47,53 @@ func ApplyParameters(ctx context.Context, p *v1beta1.PipelineSpec, pr *v1beta1.P "params['%s']", } + // reference pattern for object individual keys params.. + objectIndividualVariablePattern := "params.%s.%s" + // Set all the default stringReplacements for _, p := range p.Params { if p.Default != nil { - if p.Default.Type == v1beta1.ParamTypeString { + switch p.Default.Type { + case v1beta1.ParamTypeArray: for _, pattern := range patterns { - stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.StringVal + arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.ArrayVal } - } else { + case v1beta1.ParamTypeObject: for _, pattern := range patterns { - arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.ArrayVal + objectReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.ObjectVal + } + for k, v := range p.Default.ObjectVal { + stringReplacements[fmt.Sprintf(objectIndividualVariablePattern, p.Name, k)] = v + } + default: + for _, pattern := range patterns { + stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Default.StringVal } } } } // Set and overwrite params with the ones from the PipelineRun for _, p := range pr.Spec.Params { - if p.Value.Type == v1beta1.ParamTypeString { + switch p.Value.Type { + case v1beta1.ParamTypeArray: for _, pattern := range patterns { - stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.StringVal + arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.ArrayVal } - } else { + case v1beta1.ParamTypeObject: for _, pattern := range patterns { - arrayReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.ArrayVal + objectReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.ObjectVal + } + for k, v := range p.Value.ObjectVal { + stringReplacements[fmt.Sprintf(objectIndividualVariablePattern, p.Name, k)] = v + } + default: + for _, pattern := range patterns { + stringReplacements[fmt.Sprintf(pattern, p.Name)] = p.Value.StringVal } } } - return ApplyReplacements(ctx, p, stringReplacements, arrayReplacements) + return ApplyReplacements(ctx, p, stringReplacements, arrayReplacements, objectReplacements) } // ApplyContexts applies the substitution from $(context.(pipelineRun|pipeline).*) with the specified values. @@ -84,7 +105,7 @@ func ApplyContexts(ctx context.Context, spec *v1beta1.PipelineSpec, pipelineName "context.pipelineRun.namespace": pr.Namespace, "context.pipelineRun.uid": string(pr.ObjectMeta.UID), } - return ApplyReplacements(ctx, spec, replacements, map[string][]string{}) + return ApplyReplacements(ctx, spec, replacements, map[string][]string{}, map[string]map[string]string{}) } // ApplyPipelineTaskContexts applies the substitution from $(context.pipelineTask.*) with the specified values. @@ -94,8 +115,8 @@ func ApplyPipelineTaskContexts(pt *v1beta1.PipelineTask) *v1beta1.PipelineTask { replacements := map[string]string{ "context.pipelineTask.retries": strconv.Itoa(pt.Retries), } - pt.Params = replaceParamValues(pt.Params, replacements, map[string][]string{}) - pt.Matrix = replaceParamValues(pt.Matrix, replacements, map[string][]string{}) + pt.Params = replaceParamValues(pt.Params, replacements, map[string][]string{}, map[string]map[string]string{}) + pt.Matrix = replaceParamValues(pt.Matrix, replacements, map[string][]string{}, map[string]map[string]string{}) return pt } @@ -105,7 +126,7 @@ func ApplyTaskResults(targets PipelineRunState, resolvedResultRefs ResolvedResul for _, resolvedPipelineRunTask := range targets { if resolvedPipelineRunTask.PipelineTask != nil { pipelineTask := resolvedPipelineRunTask.PipelineTask.DeepCopy() - pipelineTask.Params = replaceParamValues(pipelineTask.Params, stringReplacements, nil) + pipelineTask.Params = replaceParamValues(pipelineTask.Params, stringReplacements, nil, nil) pipelineTask.WhenExpressions = pipelineTask.WhenExpressions.ReplaceWhenExpressionsVariables(stringReplacements, nil) resolvedPipelineRunTask.PipelineTask = pipelineTask } @@ -117,7 +138,7 @@ func ApplyPipelineTaskStateContext(state PipelineRunState, replacements map[stri for _, resolvedPipelineRunTask := range state { if resolvedPipelineRunTask.PipelineTask != nil { pipelineTask := resolvedPipelineRunTask.PipelineTask.DeepCopy() - pipelineTask.Params = replaceParamValues(pipelineTask.Params, replacements, nil) + pipelineTask.Params = replaceParamValues(pipelineTask.Params, replacements, nil, nil) pipelineTask.WhenExpressions = pipelineTask.WhenExpressions.ReplaceWhenExpressionsVariables(replacements, nil) resolvedPipelineRunTask.PipelineTask = pipelineTask } @@ -137,39 +158,44 @@ func ApplyWorkspaces(ctx context.Context, p *v1beta1.PipelineSpec, pr *v1beta1.P key := fmt.Sprintf("workspaces.%s.bound", boundWorkspace.Name) replacements[key] = "true" } - return ApplyReplacements(ctx, p, replacements, map[string][]string{}) + return ApplyReplacements(ctx, p, replacements, map[string][]string{}, map[string]map[string]string{}) } // ApplyReplacements replaces placeholders for declared parameters with the specified replacements. -func ApplyReplacements(ctx context.Context, p *v1beta1.PipelineSpec, replacements map[string]string, arrayReplacements map[string][]string) *v1beta1.PipelineSpec { +func ApplyReplacements(ctx context.Context, p *v1beta1.PipelineSpec, replacements map[string]string, arrayReplacements map[string][]string, objectReplacements map[string]map[string]string) *v1beta1.PipelineSpec { p = p.DeepCopy() for i := range p.Tasks { - p.Tasks[i].Params = replaceParamValues(p.Tasks[i].Params, replacements, arrayReplacements) - p.Tasks[i].Matrix = replaceParamValues(p.Tasks[i].Matrix, replacements, arrayReplacements) + p.Tasks[i].Params = replaceParamValues(p.Tasks[i].Params, replacements, arrayReplacements, objectReplacements) + p.Tasks[i].Matrix = replaceParamValues(p.Tasks[i].Matrix, replacements, arrayReplacements, objectReplacements) for j := range p.Tasks[i].Workspaces { p.Tasks[i].Workspaces[j].SubPath = substitution.ApplyReplacements(p.Tasks[i].Workspaces[j].SubPath, replacements) } + log.Println("chuangw", arrayReplacements) p.Tasks[i].WhenExpressions = p.Tasks[i].WhenExpressions.ReplaceWhenExpressionsVariables(replacements, arrayReplacements) - p.Tasks[i], replacements, arrayReplacements = propagateParams(ctx, p.Tasks[i], replacements, arrayReplacements) + p.Tasks[i], replacements, arrayReplacements, objectReplacements = propagateParams(ctx, p.Tasks[i], replacements, arrayReplacements, objectReplacements) } for i := range p.Finally { - p.Finally[i].Params = replaceParamValues(p.Finally[i].Params, replacements, arrayReplacements) - p.Finally[i].Matrix = replaceParamValues(p.Finally[i].Matrix, replacements, arrayReplacements) + p.Finally[i].Params = replaceParamValues(p.Finally[i].Params, replacements, arrayReplacements, objectReplacements) + p.Finally[i].Matrix = replaceParamValues(p.Finally[i].Matrix, replacements, arrayReplacements, objectReplacements) p.Finally[i].WhenExpressions = p.Finally[i].WhenExpressions.ReplaceWhenExpressionsVariables(replacements, arrayReplacements) } return p } -func propagateParams(ctx context.Context, t v1beta1.PipelineTask, replacements map[string]string, arrayReplacements map[string][]string) (v1beta1.PipelineTask, map[string]string, map[string][]string) { +func propagateParams(ctx context.Context, t v1beta1.PipelineTask, replacements map[string]string, arrayReplacements map[string][]string, objectReplacements map[string]map[string]string) (v1beta1.PipelineTask, map[string]string, map[string][]string, map[string]map[string]string) { if t.TaskSpec != nil && config.FromContextOrDefaults(ctx).FeatureFlags.EnableAPIFields == "alpha" { patterns := []string{ "params.%s", "params[%q]", "params['%s']", } + + // reference pattern for object individual keys params.. + objectIndividualVariablePattern := "params.%s.%s" + // check if there are task parameters defined that match the params at pipeline level if len(t.Params) > 0 { for _, par := range t.Params { @@ -182,17 +208,23 @@ func propagateParams(ctx context.Context, t v1beta1.PipelineTask, replacements m if _, ok := arrayReplacements[checkName]; ok { arrayReplacements[checkName] = par.Value.ArrayVal } + if _, ok := objectReplacements[checkName]; ok { + objectReplacements[checkName] = par.Value.ObjectVal + for k, v := range par.Value.ObjectVal { + replacements[fmt.Sprintf(objectIndividualVariablePattern, par.Name, k)] = v + } + } } } } t.TaskSpec.TaskSpec = *resources.ApplyReplacements(&t.TaskSpec.TaskSpec, replacements, arrayReplacements) } - return t, replacements, arrayReplacements + return t, replacements, arrayReplacements, objectReplacements } -func replaceParamValues(params []v1beta1.Param, stringReplacements map[string]string, arrayReplacements map[string][]string) []v1beta1.Param { +func replaceParamValues(params []v1beta1.Param, stringReplacements map[string]string, arrayReplacements map[string][]string, objectReplacements map[string]map[string]string) []v1beta1.Param { for i := range params { - params[i].Value.ApplyReplacements(stringReplacements, arrayReplacements) + params[i].Value.ApplyReplacements(stringReplacements, arrayReplacements, objectReplacements) } return params } diff --git a/pkg/reconciler/pipelinerun/resources/apply_test.go b/pkg/reconciler/pipelinerun/resources/apply_test.go index 22b845c8ddb..ff614346f2c 100644 --- a/pkg/reconciler/pipelinerun/resources/apply_test.go +++ b/pkg/reconciler/pipelinerun/resources/apply_test.go @@ -125,6 +125,36 @@ func TestApplyParameters(t *testing.T) { }}, }, alpha: true, + }, { + name: "parameter propagation object no task or task default winner pipeline", + original: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "$(params.myObject.key1) $(params.myObject.key2)"}, + }}, + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "myObject", Value: *v1beta1.NewObject(map[string]string{"key1": "hello", "key2": "world!"})}}, + expected: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "hello world!"}, + }}, + }, + }, + }}, + }, + alpha: true, }, { name: "parameter propagation with task default but no task winner pipeline", original: v1beta1.PipelineSpec{ @@ -289,6 +319,125 @@ func TestApplyParameters(t *testing.T) { }}, }, alpha: true, + }, { + name: "parameter propagation object with task default but no task winner pipeline", + original: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "myobject", + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "default", + "key2": "param!", + }), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "$(params.myobject.key1) $(params.myobject.key2)"}, + }}, + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "myobject", Value: *v1beta1.NewObject(map[string]string{ + "key1": "pipeline", + "key2": "param!!", + })}}, + expected: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "myobject", + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "default", + "key2": "param!", + }), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "pipeline param!!"}, + }}, + }, + }, + }}, + }, + alpha: true, + }, { + name: "parameter propagation object with task default and task winner task", + original: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "myobject", Value: *v1beta1.NewObject(map[string]string{ + "key1": "task", + "key2": "param!", + })}, + }, + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "myobject", + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "default", + "key2": "param!!", + }), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "$(params.myobject.key1) $(params.myobject.key2)"}, + }}, + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "myobject", Value: *v1beta1.NewObject(map[string]string{"key1": "pipeline", "key2": "param!!!"})}}, + expected: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "myobject", Value: *v1beta1.NewObject(map[string]string{ + "key1": "task", + "key2": "param!", + })}, + }, + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "myobject", + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "default", + "key2": "param!!", + }), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "task param!"}, + }}, + }, + }, + }}, + }, + alpha: true, }, { name: "single parameter with when expression", original: v1beta1.PipelineSpec{ @@ -319,7 +468,64 @@ func TestApplyParameters(t *testing.T) { }}, }, }, { - name: "pipeline parameter nested inside task parameter", + name: "object parameter with when expression", + original: v1beta1.PipelineSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "myobject", + Type: v1beta1.ParamTypeObject, + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + "key3": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "val1", + "key2": "val2", + "key3": "val3", + }), + }, + }, + Tasks: []v1beta1.PipelineTask{{ + WhenExpressions: []v1beta1.WhenExpression{{ + Input: "$(params.myobject.key1)", + Operator: selection.In, + Values: []string{"$(params.myobject.key2)", "$(params.myobject.key3)"}, + }}, + }}, + }, + params: []v1beta1.Param{{Name: "myobject", Value: *v1beta1.NewObject(map[string]string{ + "key1": "val1", + "key2": "val2", + "key3": "val1", + })}}, + expected: v1beta1.PipelineSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "myobject", + Type: v1beta1.ParamTypeObject, + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + "key3": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "val1", + "key2": "val2", + "key3": "val3", + }), + }, + }, + Tasks: []v1beta1.PipelineTask{{ + WhenExpressions: []v1beta1.WhenExpression{{ + Input: "val1", + Operator: selection.In, + Values: []string{"val2", "val1"}, + }}, + }}, + }, + }, { + name: "string pipeline parameter nested inside task parameter", original: v1beta1.PipelineSpec{ Params: []v1beta1.ParamSpec{ {Name: "first-param", Type: v1beta1.ParamTypeString, Default: v1beta1.NewArrayOrString("default-value")}, @@ -346,7 +552,7 @@ func TestApplyParameters(t *testing.T) { }}, }, }, { - name: "array parameter", + name: "array pipeline parameter nested inside task parameter", original: v1beta1.PipelineSpec{ Params: []v1beta1.ParamSpec{ {Name: "first-param", Type: v1beta1.ParamTypeArray, Default: v1beta1.NewArrayOrString("default", "array", "value")}, @@ -374,6 +580,53 @@ func TestApplyParameters(t *testing.T) { }, }}, }, + }, { + name: "object pipeline parameter nested inside task parameter", + original: v1beta1.PipelineSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "myobject", + Type: v1beta1.ParamTypeObject, + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "val1", + "key2": "val2", + }), + }, + }, + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "first-task-first-param", Value: *v1beta1.NewArrayOrString("$(input.workspace.$(params.myobject.key1))")}, + {Name: "first-task-second-param", Value: *v1beta1.NewArrayOrString("$(input.workspace.$(params.myobject.key2))")}, + }, + }}, + }, + params: nil, // no parameter values. + expected: v1beta1.PipelineSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "myobject", + Type: v1beta1.ParamTypeObject, + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "val1", + "key2": "val2", + }), + }, + }, + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "first-task-first-param", Value: *v1beta1.NewArrayOrString("$(input.workspace.val1)")}, + {Name: "first-task-second-param", Value: *v1beta1.NewArrayOrString("$(input.workspace.val2)")}, + }, + }}, + }, }, { name: "parameter evaluation with final tasks", original: v1beta1.PipelineSpec{ @@ -460,6 +713,78 @@ func TestApplyParameters(t *testing.T) { }}, }}, }, + }, { + name: "object parameter evaluation with both tasks and final tasks", + original: v1beta1.PipelineSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "myobject", + Type: v1beta1.ParamTypeObject, + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "val1", + "key2": "val2", + }), + }, + }, + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "final-task-first-param", Value: *v1beta1.NewArrayOrString("$(params.myobject.key1)")}, + {Name: "final-task-second-param", Value: *v1beta1.NewArrayOrString("$(params.myobject.key2)")}, + }, + }}, + Finally: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "final-task-first-param", Value: *v1beta1.NewArrayOrString("$(params.myobject.key1)")}, + {Name: "final-task-second-param", Value: *v1beta1.NewArrayOrString("$(params.myobject.key2)")}, + }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(params.myobject.key1)", + Operator: selection.In, + Values: []string{"$(params.myobject.key2)"}, + }}, + }}, + }, + params: []v1beta1.Param{{Name: "myobject", Value: *v1beta1.NewObject(map[string]string{ + "key1": "foo", + "key2": "bar", + })}}, + expected: v1beta1.PipelineSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "myobject", + Type: v1beta1.ParamTypeObject, + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "val1", + "key2": "val2", + }), + }, + }, + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "final-task-first-param", Value: *v1beta1.NewArrayOrString("foo")}, + {Name: "final-task-second-param", Value: *v1beta1.NewArrayOrString("bar")}, + }, + }}, + Finally: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "final-task-first-param", Value: *v1beta1.NewArrayOrString("foo")}, + {Name: "final-task-second-param", Value: *v1beta1.NewArrayOrString("bar")}, + }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "foo", + Operator: selection.In, + Values: []string{"bar"}, + }}, + }}, + }, }, { name: "parameter references with bracket notation and special characters", original: v1beta1.PipelineSpec{ @@ -541,6 +866,70 @@ func TestApplyParameters(t *testing.T) { }, }}, }, + }, { + name: "object parameter in workspace subpath", + original: v1beta1.PipelineSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "myobject", + Type: v1beta1.ParamTypeObject, + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "val1", + "key2": "val2", + }), + }, + }, + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "first-task-first-param", Value: *v1beta1.NewArrayOrString("$(params.myobject.key1)")}, + {Name: "first-task-second-param", Value: *v1beta1.NewArrayOrString("static value")}, + }, + Workspaces: []v1beta1.WorkspacePipelineTaskBinding{ + { + Name: "first-workspace", + Workspace: "first-workspace", + SubPath: "$(params.myobject.key2)", + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "myobject", Value: *v1beta1.NewObject(map[string]string{ + "key1": "foo", + "key2": "bar", + })}}, + expected: v1beta1.PipelineSpec{ + Params: []v1beta1.ParamSpec{ + { + Name: "myobject", + Type: v1beta1.ParamTypeObject, + Properties: map[string]v1beta1.PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + Default: v1beta1.NewObject(map[string]string{ + "key1": "val1", + "key2": "val2", + }), + }, + }, + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "first-task-first-param", Value: *v1beta1.NewArrayOrString("foo")}, + {Name: "first-task-second-param", Value: *v1beta1.NewArrayOrString("static value")}, + }, + Workspaces: []v1beta1.WorkspacePipelineTaskBinding{ + { + Name: "first-workspace", + Workspace: "first-workspace", + SubPath: "bar", + }, + }, + }}, + }, }, } { ctx := context.Background()