diff --git a/docs/pipelineruns.md b/docs/pipelineruns.md index 82b22b9f1d2..0af30996966 100644 --- a/docs/pipelineruns.md +++ b/docs/pipelineruns.md @@ -15,6 +15,10 @@ weight: 500 - [Remote Pipelines](#remote-pipelines) - [Specifying Resources](#specifying-resources) - [Specifying Parameters](#specifying-parameters) + - [Propagated Parameters](#propagated-parameters) + - [Scope and Precedence](#scope-and-precedence) + - [Default Values](#default-values) + - [Referenced Resources](#referenced-resources) - [Specifying custom ServiceAccount credentials](#specifying-custom-serviceaccount-credentials) - [Mapping ServiceAccount credentials to Tasks](#mapping-serviceaccount-credentials-to-tasks) - [Specifying a Pod template](#specifying-a-pod-template) @@ -283,6 +287,348 @@ case is when your CI system autogenerates `PipelineRuns` and it has `Parameters` provide to all `PipelineRuns`. Because you can pass in extra `Parameters`, you don't have to go through the complexity of checking each `Pipeline` and providing only the required params. +#### Propagated Parameters + +**([alpha only](https://github.com/tektoncd/pipeline/blob/main/docs/install.md#alpha-features))** + +When using an inlined spec, parameters from the parent `PipelineRun` will be +propagated to any inlined specs without needing to be explicitly defined. This +allows authors to simplify specs by automatically propagating top-level +parameters down to other inlined resources. + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pr-echo- +spec: + params: + - name: HELLO + value: "Hello World!" + - name: BYE + value: "Bye World!" + pipelineSpec: + tasks: + - name: echo-hello + taskSpec: + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "$(params.HELLO)" + - name: echo-bye + taskSpec: + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "$(params.BYE)" +``` + +On executing the pipeline run, the parameters will be interpolated during resolution. +The specifications are not mutated before storage and so it remains the same. +The status is updated. + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: pr-echo-szzs9 + ... +spec: + params: + - name: HELLO + value: Hello World! + - name: BYE + value: Bye World! + pipelineSpec: + tasks: + - name: echo-hello + taskSpec: + steps: + - image: ubuntu + name: echo + script: | + #!/usr/bin/env bash + echo "$(params.HELLO)" + - name: echo-bye + taskSpec: + steps: + - image: ubuntu + name: echo + script: | + #!/usr/bin/env bash + echo "$(params.BYE)" +status: + conditions: + - lastTransitionTime: "2022-04-07T12:34:58Z" + message: 'Tasks Completed: 2 (Failed: 0, Canceled 0), Skipped: 0' + reason: Succeeded + status: "True" + type: Succeeded + pipelineSpec: + ... + taskRuns: + pr-echo-szzs9-echo-hello: + pipelineTaskName: echo-hello + status: + ... + taskSpec: + steps: + - image: ubuntu + name: echo + resources: {} + script: | + #!/usr/bin/env bash + echo "Hello World!" + pr-echo-szzs9-echo-bye: + pipelineTaskName: echo-bye + status: + ... + taskSpec: + steps: + - image: ubuntu + name: echo + resources: {} + script: | + #!/usr/bin/env bash + echo "Bye World!" +``` + +##### Scope and Precedence + +When Parameters names conflict, the inner scope would take precedence as shown in this example: + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pr-echo- +spec: + params: + - name: HELLO + value: "Hello World!" + - name: BYE + value: "Bye World!" + pipelineSpec: + tasks: + - name: echo-hello + params: + - name: HELLO + value: "Sasa World!" + taskSpec: + params: + - name: HELLO + type: string + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "$(params.HELLO)" + ... +``` + +resolves to + +```yaml +# Successful execution of the above PipelineRun +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: pr-echo-szzs9 + ... +spec: + ... +status: + conditions: + - lastTransitionTime: "2022-04-07T12:34:58Z" + message: 'Tasks Completed: 2 (Failed: 0, Canceled 0), Skipped: 0' + reason: Succeeded + status: "True" + type: Succeeded + ... + taskRuns: + pr-echo-szzs9-echo-hello: + pipelineTaskName: echo-hello + status: + conditions: + - lastTransitionTime: "2022-04-07T12:34:57Z" + message: All Steps have completed executing + reason: Succeeded + status: "True" + type: Succeeded + taskSpec: + steps: + - image: ubuntu + name: echo + resources: {} + script: | + #!/usr/bin/env bash + echo "Sasa World!" + ... +``` + +##### Default Values + +When `Parameter` specifications have default values, the `Parameter` value provided at runtime would take precedence to give users control, as shown in this example: + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pr-echo- +spec: + params: + - name: HELLO + value: "Hello World!" + - name: BYE + value: "Bye World!" + pipelineSpec: + tasks: + - name: echo-hello + taskSpec: + params: + - name: HELLO + type: string + default: "Sasa World!" + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "$(params.HELLO)" + ... +``` + +resolves to + +```yaml +# Successful execution of the above PipelineRun +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: pr-echo-szzs9 + ... +spec: + ... +status: + conditions: + - lastTransitionTime: "2022-04-07T12:34:58Z" + message: 'Tasks Completed: 2 (Failed: 0, Canceled 0), Skipped: 0' + reason: Succeeded + status: "True" + type: Succeeded + ... + taskRuns: + pr-echo-szzs9-echo-hello: + pipelineTaskName: echo-hello + status: + conditions: + - lastTransitionTime: "2022-04-07T12:34:57Z" + message: All Steps have completed executing + reason: Succeeded + status: "True" + type: Succeeded + taskSpec: + steps: + - image: ubuntu + name: echo + resources: {} + script: | + #!/usr/bin/env bash + echo "Hello World!" + ... +``` + +##### Referenced Resources + +When a PipelineRun definition has referenced specifications but does not explicitly pass Parameters, the PipelineRun will be created but the execution will fail because of missing Parameters. + +```yaml +# Invalid PipelineRun attempting to propagate Parameters to referenced Tasks +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pr-echo- +spec: + params: + - name: HELLO + value: "Hello World!" + - name: BYE + value: "Bye World!" + pipelineSpec: + tasks: + - name: echo-hello + taskRef: + name: echo-hello + - name: echo-bye + taskRef: + name: echo-bye +--- +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: echo-hello +spec: + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "$(params.HELLO)" +--- +apiVersion: tekton.dev/v1beta1 +kind: Task +metadata: + name: echo-bye +spec: + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "$(params.BYE)" +``` + +Fails as follows: + +```yaml +# Failed execution of the above PipelineRun +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: pr-echo-24lmf + ... +spec: + params: + - name: HELLO + value: Hello World! + - name: BYE + value: Bye World! + pipelineSpec: + tasks: + - name: echo-hello + taskRef: + kind: Task + name: echo-hello + - name: echo-bye + taskRef: + kind: Task + name: echo-bye +status: + conditions: + - lastTransitionTime: "2022-04-07T20:24:51Z" + message: 'invalid input params for task echo-hello: missing values for + these params which have no default values: [HELLO]' + reason: PipelineValidationFailed + status: "False" + type: Succeeded + ... +``` + ### Specifying custom `ServiceAccount` credentials You can execute the `Pipeline` in your `PipelineRun` with a specific set of credentials by diff --git a/docs/taskruns.md b/docs/taskruns.md index 1b0a60ed4b9..5da4f98a1af 100644 --- a/docs/taskruns.md +++ b/docs/taskruns.md @@ -14,6 +14,7 @@ weight: 300 - [Tekton Bundles](#tekton-bundles) - [Remote Tasks](#remote-tasks) - [Specifying `Parameters`](#specifying-parameters) + - [Propagated Parameters](#propagated-parameters) - [Extra Parameters](#extra-parameters) - [Specifying `Resources`](#specifying-resources) - [Specifying `Resource` limits](#specifying-resource-limits) @@ -197,6 +198,73 @@ spec: **Note:** If a parameter does not have an implicit default value, you must explicitly set its value. +#### Propagated Parameters + +**([alpha only](https://github.com/tektoncd/pipeline/blob/main/docs/install.md#alpha-features))** + +When using an inlined `taskSpec`, parameters from the parent `TaskRun` will be +available to the `Task` without needing to be explicitly defined. + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: TaskRun +metadata: + generateName: hello- +spec: + params: + - name: message + value: "hello world!" + taskSpec: + # There are no explicit params defined here. + # They are derived from the TaskRun params above. + steps: + - name: default + image: ubuntu + script: | + echo $(params.message) +``` + +On executing the task run, the parameters will be interpolated during resolution. +The specifications are not mutated before storage and so it remains the same. +The status is updated. + +```yaml +kind: TaskRun +metadata: + name: hello-dlqm9 + ... +spec: + params: + - name: message + value: hello world! + serviceAccountName: default + taskSpec: + steps: + - image: ubuntu + name: default + resources: {} + script: | + echo $(params.message) +status: + conditions: + - lastTransitionTime: "2022-05-20T15:24:41Z" + message: All Steps have completed executing + reason: Succeeded + status: "True" + type: Succeeded + ... + steps: + - container: step-default + ... + taskSpec: + steps: + - image: ubuntu + name: default + resources: {} + script: | + echo "hello world!" +``` + #### Extra Parameters **([alpha only](https://github.com/tektoncd/pipeline/blob/main/docs/install.md#alpha-features))** diff --git a/examples/v1beta1/pipelineruns/alpha/propagating_params_implicit_parameters.yaml b/examples/v1beta1/pipelineruns/alpha/propagating_params_implicit_parameters.yaml new file mode 100644 index 00000000000..9cf03eea189 --- /dev/null +++ b/examples/v1beta1/pipelineruns/alpha/propagating_params_implicit_parameters.yaml @@ -0,0 +1,18 @@ +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pr-echo- +spec: + params: + - name: HELLO + value: "Pipeline Hello World!" + pipelineSpec: + tasks: + - name: echo-hello + taskSpec: + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "$(params.HELLO)" diff --git a/examples/v1beta1/pipelineruns/alpha/propagating_params_with_scope_precedence.yaml b/examples/v1beta1/pipelineruns/alpha/propagating_params_with_scope_precedence.yaml new file mode 100644 index 00000000000..0791e27ad41 --- /dev/null +++ b/examples/v1beta1/pipelineruns/alpha/propagating_params_with_scope_precedence.yaml @@ -0,0 +1,21 @@ +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pr-echo- +spec: + params: + - name: HELLO + value: "Pipeline Hello World!" + pipelineSpec: + tasks: + - name: echo-hello + params: + - name: HELLO + value: "Task Hello World!" + taskSpec: + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "$(params.HELLO)" diff --git a/examples/v1beta1/pipelineruns/alpha/propagating_params_with_scope_precedence_default_only.yaml b/examples/v1beta1/pipelineruns/alpha/propagating_params_with_scope_precedence_default_only.yaml new file mode 100644 index 00000000000..3d5af4a40c0 --- /dev/null +++ b/examples/v1beta1/pipelineruns/alpha/propagating_params_with_scope_precedence_default_only.yaml @@ -0,0 +1,21 @@ +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pr-echo- +spec: + params: + - name: HELLO + value: "Pipeline Hello World!" + pipelineSpec: + tasks: + - name: echo-hello + taskSpec: + params: + - name: HELLO + default: "Default Hello World!" + steps: + - name: echo + image: ubuntu + script: | + #!/usr/bin/env bash + echo "$(params.HELLO)" diff --git a/examples/v1beta1/taskruns/alpha/propagating_params_implicit.yaml b/examples/v1beta1/taskruns/alpha/propagating_params_implicit.yaml new file mode 100644 index 00000000000..d0a47a8eacc --- /dev/null +++ b/examples/v1beta1/taskruns/alpha/propagating_params_implicit.yaml @@ -0,0 +1,15 @@ +apiVersion: tekton.dev/v1beta1 +kind: TaskRun +metadata: + generateName: hello- +spec: + params: + - name: message + value: "hello world!" + taskSpec: + # There are no explicit params defined here. They are derived from the TaskRun. + steps: + - name: default + image: ubuntu + script: | + echo $(params.message) diff --git a/pkg/apis/pipeline/v1alpha1/task_validation.go b/pkg/apis/pipeline/v1alpha1/task_validation.go index 8941ebff560..a80b5658f6e 100644 --- a/pkg/apis/pipeline/v1alpha1/task_validation.go +++ b/pkg/apis/pipeline/v1alpha1/task_validation.go @@ -130,7 +130,7 @@ func (ts *TaskSpec) Validate(ctx context.Context) *apis.FieldError { } } - if err := v1beta1.ValidateParameterVariables(ts.Steps, ts.Params); err != nil { + if err := v1beta1.ValidateParameterVariables(ctx, ts.Steps, ts.Params); err != nil { return err } // Deprecated @@ -138,7 +138,7 @@ func (ts *TaskSpec) Validate(ctx context.Context) *apis.FieldError { return err } - if err := v1beta1.ValidateResourcesVariables(ts.Steps, ts.Resources); err != nil { + if err := v1beta1.ValidateResourcesVariables(ctx, ts.Steps, ts.Resources); err != nil { return err } // Deprecated diff --git a/pkg/apis/pipeline/v1beta1/task_validation.go b/pkg/apis/pipeline/v1beta1/task_validation.go index acea12ba00d..771aaeed74c 100644 --- a/pkg/apis/pipeline/v1beta1/task_validation.go +++ b/pkg/apis/pipeline/v1beta1/task_validation.go @@ -76,9 +76,9 @@ func (ts *TaskSpec) Validate(ctx context.Context) (errs *apis.FieldError) { errs = errs.Also(validateSteps(ctx, mergedSteps).ViaField("steps")) errs = errs.Also(ts.Resources.Validate(ctx).ViaField("resources")) errs = errs.Also(ValidateParameterTypes(ctx, ts.Params).ViaField("params")) - errs = errs.Also(ValidateParameterVariables(ts.Steps, ts.Params)) - errs = errs.Also(ValidateResourcesVariables(ts.Steps, ts.Resources)) - errs = errs.Also(validateTaskContextVariables(ts.Steps)) + errs = errs.Also(ValidateParameterVariables(ctx, ts.Steps, ts.Params)) + errs = errs.Also(ValidateResourcesVariables(ctx, ts.Steps, ts.Resources)) + errs = errs.Also(validateTaskContextVariables(ctx, ts.Steps)) errs = errs.Also(validateResults(ctx, ts.Results).ViaField("results")) return errs } @@ -317,7 +317,7 @@ func (p ParamSpec) ValidateObjectType() *apis.FieldError { } // ValidateParameterVariables validates all variables within a slice of ParamSpecs against a slice of Steps -func ValidateParameterVariables(steps []Step, params []ParamSpec) *apis.FieldError { +func ValidateParameterVariables(ctx context.Context, steps []Step, params []ParamSpec) *apis.FieldError { parameterNames := sets.NewString() arrayParameterNames := sets.NewString() objectParamSpecs := []ParamSpec{} @@ -336,12 +336,12 @@ func ValidateParameterVariables(steps []Step, params []ParamSpec) *apis.FieldErr } } - errs = errs.Also(validateVariables(steps, "params", parameterNames)) + errs = errs.Also(validateVariables(ctx, steps, "params", parameterNames)) errs = errs.Also(validateArrayUsage(steps, "params", arrayParameterNames)) - return errs.Also(validateObjectUsage(steps, objectParamSpecs)) + return errs.Also(validateObjectUsage(ctx, steps, objectParamSpecs)) } -func validateTaskContextVariables(steps []Step) *apis.FieldError { +func validateTaskContextVariables(ctx context.Context, steps []Step) *apis.FieldError { taskRunContextNames := sets.NewString().Insert( "name", "namespace", @@ -351,12 +351,12 @@ func validateTaskContextVariables(steps []Step) *apis.FieldError { "name", "retry-count", ) - errs := validateVariables(steps, "context\\.taskRun", taskRunContextNames) - return errs.Also(validateVariables(steps, "context\\.task", taskContextNames)) + errs := validateVariables(ctx, steps, "context\\.taskRun", taskRunContextNames) + return errs.Also(validateVariables(ctx, steps, "context\\.task", taskContextNames)) } // ValidateResourcesVariables validates all variables within a TaskResources against a slice of Steps -func ValidateResourcesVariables(steps []Step, resources *TaskResources) *apis.FieldError { +func ValidateResourcesVariables(ctx context.Context, steps []Step, resources *TaskResources) *apis.FieldError { if resources == nil { return nil } @@ -371,7 +371,7 @@ func ValidateResourcesVariables(steps []Step, resources *TaskResources) *apis.Fi resourceNames.Insert(r.Name) } } - return validateVariables(steps, "resources.(?:inputs|outputs)", resourceNames) + return validateVariables(ctx, steps, "resources.(?:inputs|outputs)", resourceNames) } // TODO (@chuangw6): Make sure an object param is not used as a whole when providing values for strings. @@ -379,7 +379,7 @@ func ValidateResourcesVariables(steps []Step, resources *TaskResources) *apis.Fi // "When providing values for strings, Task and Pipeline authors can access // individual attributes of an object param; they cannot access the object // as whole (we could add support for this later)." -func validateObjectUsage(steps []Step, params []ParamSpec) (errs *apis.FieldError) { +func validateObjectUsage(ctx context.Context, steps []Step, params []ParamSpec) (errs *apis.FieldError) { objectParameterNames := sets.NewString() for _, p := range params { // collect all names of object type params @@ -396,7 +396,7 @@ func validateObjectUsage(steps []Step, params []ParamSpec) (errs *apis.FieldErro } // check if the object's key names are referenced correctly i.e. param.objectParam.key1 - errs = errs.Also(validateVariables(steps, fmt.Sprintf("params\\.%s", p.Name), objectKeys)) + errs = errs.Also(validateVariables(ctx, steps, fmt.Sprintf("params\\.%s", p.Name), objectKeys)) } return errs @@ -450,7 +450,7 @@ func validateStepArrayUsage(step Step, prefix string, vars sets.String) *apis.Fi return errs } -func validateVariables(steps []Step, prefix string, vars sets.String) (errs *apis.FieldError) { +func validateVariables(ctx context.Context, steps []Step, prefix string, vars sets.String) (errs *apis.FieldError) { // validate that the variable name format follows the rules // - Must only contain alphanumeric characters, hyphens (-), underscores (_), and dots (.) // - Must begin with a letter or an underscore (_) @@ -474,16 +474,18 @@ func validateVariables(steps []Step, prefix string, vars sets.String) (errs *api // We've checked param name format. Now, we want to check if param names are referenced correctly in each step for idx, step := range steps { - errs = errs.Also(validateStepVariables(step, prefix, vars).ViaFieldIndex("steps", idx)) + errs = errs.Also(validateStepVariables(ctx, step, prefix, vars).ViaFieldIndex("steps", idx)) } return errs } -func validateStepVariables(step Step, prefix string, vars sets.String) *apis.FieldError { +func validateStepVariables(ctx context.Context, step Step, prefix string, vars sets.String) *apis.FieldError { errs := validateTaskVariable(step.Name, prefix, vars).ViaField("name") errs = errs.Also(validateTaskVariable(step.Image, prefix, vars).ViaField("image")) errs = errs.Also(validateTaskVariable(step.WorkingDir, prefix, vars).ViaField("workingDir")) - errs = errs.Also(validateTaskVariable(step.Script, prefix, vars).ViaField("script")) + if !(config.FromContextOrDefaults(ctx).FeatureFlags.EnableAPIFields == "alpha" && prefix == "params") { + errs = errs.Also(validateTaskVariable(step.Script, prefix, vars).ViaField("script")) + } for i, cmd := range step.Command { errs = errs.Also(validateTaskVariable(cmd, prefix, vars).ViaFieldIndex("command", i)) } diff --git a/pkg/reconciler/pipelinerun/pipelinerun.go b/pkg/reconciler/pipelinerun/pipelinerun.go index a6dfd810f18..f6faa371f32 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun.go +++ b/pkg/reconciler/pipelinerun/pipelinerun.go @@ -473,9 +473,9 @@ func (c *Reconciler) reconcile(ctx context.Context, pr *v1beta1.PipelineRun, get } // Apply parameter substitution from the PipelineRun - pipelineSpec = resources.ApplyParameters(pipelineSpec, pr) - pipelineSpec = resources.ApplyContexts(pipelineSpec, pipelineMeta.Name, pr) - pipelineSpec = resources.ApplyWorkspaces(pipelineSpec, pr) + pipelineSpec = resources.ApplyParameters(ctx, pipelineSpec, pr) + pipelineSpec = resources.ApplyContexts(ctx, pipelineSpec, pipelineMeta.Name, pr) + pipelineSpec = resources.ApplyWorkspaces(ctx, pipelineSpec, pr) // pipelineState holds a list of pipeline tasks after resolving conditions and pipeline resources // pipelineState also holds a taskRun for each pipeline task after the taskRun is created diff --git a/pkg/reconciler/pipelinerun/resources/apply.go b/pkg/reconciler/pipelinerun/resources/apply.go index 089b5fc7bf8..9ed5a944830 100644 --- a/pkg/reconciler/pipelinerun/resources/apply.go +++ b/pkg/reconciler/pipelinerun/resources/apply.go @@ -17,18 +17,21 @@ limitations under the License. package resources import ( + "context" "fmt" "strconv" "strings" + "github.com/tektoncd/pipeline/pkg/apis/config" "github.com/tektoncd/pipeline/pkg/apis/run/v1alpha1" + "github.com/tektoncd/pipeline/pkg/reconciler/taskrun/resources" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/pkg/substitution" ) // ApplyParameters applies the params from a PipelineRun.Params to a PipelineSpec. -func ApplyParameters(p *v1beta1.PipelineSpec, pr *v1beta1.PipelineRun) *v1beta1.PipelineSpec { +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 @@ -69,19 +72,19 @@ func ApplyParameters(p *v1beta1.PipelineSpec, pr *v1beta1.PipelineRun) *v1beta1. } } - return ApplyReplacements(p, stringReplacements, arrayReplacements) + return ApplyReplacements(ctx, p, stringReplacements, arrayReplacements) } // ApplyContexts applies the substitution from $(context.(pipelineRun|pipeline).*) with the specified values. // Currently supports only name substitution. Uses "" as a default if name is not specified. -func ApplyContexts(spec *v1beta1.PipelineSpec, pipelineName string, pr *v1beta1.PipelineRun) *v1beta1.PipelineSpec { +func ApplyContexts(ctx context.Context, spec *v1beta1.PipelineSpec, pipelineName string, pr *v1beta1.PipelineRun) *v1beta1.PipelineSpec { replacements := map[string]string{ "context.pipelineRun.name": pr.Name, "context.pipeline.name": pipelineName, "context.pipelineRun.namespace": pr.Namespace, "context.pipelineRun.uid": string(pr.ObjectMeta.UID), } - return ApplyReplacements(spec, replacements, map[string][]string{}) + return ApplyReplacements(ctx, spec, replacements, map[string][]string{}) } // ApplyPipelineTaskContexts applies the substitution from $(context.pipelineTask.*) with the specified values. @@ -129,7 +132,7 @@ func ApplyPipelineTaskStateContext(state PipelineRunState, replacements map[stri // ApplyWorkspaces replaces workspace variables in the given pipeline spec with their // concrete values. -func ApplyWorkspaces(p *v1beta1.PipelineSpec, pr *v1beta1.PipelineRun) *v1beta1.PipelineSpec { +func ApplyWorkspaces(ctx context.Context, p *v1beta1.PipelineSpec, pr *v1beta1.PipelineRun) *v1beta1.PipelineSpec { p = p.DeepCopy() replacements := map[string]string{} for _, declaredWorkspace := range p.Workspaces { @@ -140,11 +143,11 @@ func ApplyWorkspaces(p *v1beta1.PipelineSpec, pr *v1beta1.PipelineRun) *v1beta1. key := fmt.Sprintf("workspaces.%s.bound", boundWorkspace.Name) replacements[key] = "true" } - return ApplyReplacements(p, replacements, map[string][]string{}) + return ApplyReplacements(ctx, p, replacements, map[string][]string{}) } // ApplyReplacements replaces placeholders for declared parameters with the specified replacements. -func ApplyReplacements(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) *v1beta1.PipelineSpec { p = p.DeepCopy() for i := range p.Tasks { @@ -158,6 +161,7 @@ func ApplyReplacements(p *v1beta1.PipelineSpec, replacements map[string]string, c.Params = replaceParamValues(c.Params, replacements, arrayReplacements) } p.Tasks[i].WhenExpressions = p.Tasks[i].WhenExpressions.ReplaceWhenExpressionsVariables(replacements, arrayReplacements) + p.Tasks[i], replacements, arrayReplacements = propagateParams(ctx, p.Tasks[i], replacements, arrayReplacements) } for i := range p.Finally { @@ -169,6 +173,33 @@ func ApplyReplacements(p *v1beta1.PipelineSpec, replacements map[string]string, 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) { + if t.TaskSpec != nil && config.FromContextOrDefaults(ctx).FeatureFlags.EnableAPIFields == "alpha" { + patterns := []string{ + "params.%s", + "params[%q]", + "params['%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 { + for _, pattern := range patterns { + checkName := fmt.Sprintf(pattern, par.Name) + // Scoping. Task Params will replace Pipeline Params + if _, ok := replacements[checkName]; ok { + replacements[checkName] = par.Value.StringVal + } + if _, ok := arrayReplacements[checkName]; ok { + arrayReplacements[checkName] = par.Value.ArrayVal + } + } + } + } + t.TaskSpec.TaskSpec = *resources.ApplyReplacements(&t.TaskSpec.TaskSpec, replacements, arrayReplacements) + } + return t, replacements, arrayReplacements +} + func replaceParamValues(params []v1beta1.Param, stringReplacements map[string]string, arrayReplacements map[string][]string) []v1beta1.Param { for i := range params { params[i].Value.ApplyReplacements(stringReplacements, arrayReplacements) diff --git a/pkg/reconciler/pipelinerun/resources/apply_test.go b/pkg/reconciler/pipelinerun/resources/apply_test.go index 495f39b9483..5e5ab9605bb 100644 --- a/pkg/reconciler/pipelinerun/resources/apply_test.go +++ b/pkg/reconciler/pipelinerun/resources/apply_test.go @@ -17,10 +17,12 @@ limitations under the License. package resources import ( + "context" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/tektoncd/pipeline/pkg/apis/config" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" resourcev1alpha1 "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1" @@ -35,6 +37,7 @@ func TestApplyParameters(t *testing.T) { original v1beta1.PipelineSpec params []v1beta1.Param expected v1beta1.PipelineSpec + alpha bool }{{ name: "single parameter", original: v1beta1.PipelineSpec{ @@ -64,6 +67,230 @@ func TestApplyParameters(t *testing.T) { }, }}, }, + }, { + name: "parameter propagation string 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", + Script: `#!/usr/bin/env bash\necho "$(params.HELLO)"`, + }}, + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "HELLO", Value: *v1beta1.NewArrayOrString("hello param!")}}, + expected: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Script: `#!/usr/bin/env bash\necho "hello param!"`, + }}, + }, + }, + }}, + }, + alpha: true, + }, { + name: "parameter propagation array 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.HELLO[*])"}, + }}, + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "HELLO", Value: *v1beta1.NewArrayOrString("hello", "param", "!!!")}}, + 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", "param", "!!!"}, + }}, + }, + }, + }}, + }, + alpha: true, + }, { + name: "parameter propagation with task default but no task winner pipeline", + original: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "HELLO", + Default: v1beta1.NewArrayOrString("default param!"), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Script: `#!/usr/bin/env bash\necho "$(params.HELLO)"`, + }}, + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "HELLO", Value: *v1beta1.NewArrayOrString("pipeline param!")}}, + expected: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "HELLO", + Default: v1beta1.NewArrayOrString("default param!"), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Script: `#!/usr/bin/env bash\necho "pipeline param!"`, + }}, + }, + }, + }}, + }, + alpha: true, + }, { + name: "parameter propagation array with task default but no task winner pipeline", + original: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "HELLO", + Default: v1beta1.NewArrayOrString("default", "param!"), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "$(params.HELLO)"}, + }}, + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "HELLO", Value: *v1beta1.NewArrayOrString("pipeline", "param!")}}, + expected: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "HELLO", + Default: v1beta1.NewArrayOrString("default", "param!"), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "pipeline", "param!"}, + }}, + }, + }, + }}, + }, + alpha: true, + }, { + name: "parameter propagation array with task default and task winner task", + original: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "HELLO", Value: *v1beta1.NewArrayOrString("task", "param!")}, + }, + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "HELLO", + Default: v1beta1.NewArrayOrString("default", "param!"), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "$(params.HELLO)"}, + }}, + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "HELLO", Value: *v1beta1.NewArrayOrString("pipeline", "param!")}}, + expected: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "HELLO", Value: *v1beta1.NewArrayOrString("task", "param!")}, + }, + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "HELLO", + Default: v1beta1.NewArrayOrString("default", "param!"), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Args: []string{"#!/usr/bin/env bash\n", "echo", "task", "param!"}, + }}, + }, + }, + }}, + }, + alpha: true, + }, { + name: "parameter propagation with task default and task winner task", + original: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "HELLO", Value: *v1beta1.NewArrayOrString("task param!")}, + }, + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "HELLO", + Default: v1beta1.NewArrayOrString("default param!"), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Script: `#!/usr/bin/env bash\necho "$(params.HELLO)"`, + }}, + }, + }, + }}, + }, + params: []v1beta1.Param{{Name: "HELLO", Value: *v1beta1.NewArrayOrString("pipeline param!")}}, + expected: v1beta1.PipelineSpec{ + Tasks: []v1beta1.PipelineTask{{ + Params: []v1beta1.Param{ + {Name: "HELLO", Value: *v1beta1.NewArrayOrString("task param!")}, + }, + TaskSpec: &v1beta1.EmbeddedTask{ + TaskSpec: v1beta1.TaskSpec{ + Params: []v1beta1.ParamSpec{{ + Name: "HELLO", + Default: v1beta1.NewArrayOrString("default param!"), + }}, + Steps: []v1beta1.Step{{ + Name: "step1", + Image: "ubuntu", + Script: `#!/usr/bin/env bash\necho "task param!"`, + }}, + }, + }, + }}, + }, + alpha: true, }, { name: "single parameter with when expression", original: v1beta1.PipelineSpec{ @@ -349,6 +576,12 @@ func TestApplyParameters(t *testing.T) { }, }, } { + ctx := context.Background() + if tt.alpha { + cfg := config.FromContextOrDefaults(ctx) + cfg.FeatureFlags = &config.FeatureFlags{EnableAPIFields: "alpha"} + ctx = config.ToContext(ctx, cfg) + } tt := tt // capture range variable t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -357,7 +590,7 @@ func TestApplyParameters(t *testing.T) { Params: tt.params, }, } - got := ApplyParameters(&tt.original, run) + got := ApplyParameters(ctx, &tt.original, run) if d := cmp.Diff(&tt.expected, got); d != "" { t.Errorf("ApplyParameters() got diff %s", diff.PrintWantGot(d)) } @@ -590,6 +823,7 @@ func TestApplyTaskResults_Conditions(t *testing.T) { } func TestContext(t *testing.T) { + ctx := context.Background() for _, tc := range []struct { description string pr *v1beta1.PipelineRun @@ -655,7 +889,7 @@ func TestContext(t *testing.T) { }}, }, } - got := ApplyContexts(&orig.Spec, orig.Name, tc.pr) + got := ApplyContexts(ctx, &orig.Spec, orig.Name, tc.pr) if d := cmp.Diff(tc.expected, got.Tasks[0].Params[0]); d != "" { t.Errorf(diff.PrintWantGot(d)) } @@ -728,6 +962,7 @@ func TestApplyPipelineTaskContexts(t *testing.T) { } func TestApplyWorkspaces(t *testing.T) { + ctx := context.Background() for _, tc := range []struct { description string declarations []v1beta1.PipelineWorkspaceDeclaration @@ -769,7 +1004,7 @@ func TestApplyWorkspaces(t *testing.T) { Workspaces: tc.bindings, }, } - p2 := ApplyWorkspaces(&p1, pr) + p2 := ApplyWorkspaces(ctx, &p1, pr) str := p2.Tasks[0].Params[0].Value.StringVal if str != tc.expectedReplacement { t.Errorf("expected %q, received %q", tc.expectedReplacement, str)