From c473d1b3dff3f3891d5ee31692e1cae7b3842640 Mon Sep 17 00:00:00 2001 From: Jerop Date: Fri, 29 Jan 2021 13:40:42 -0500 Subject: [PATCH] WhenExpressions in Finally Tasks Users can guard execution of `Tasks` using `WhenExpressions`, but that is currently not supported in `Finally Tasks`. This change adds support for `WhenExpressions` in `Finally Tasks` not only to provide efficient guarded execution but also to improve the reusability of `Tasks` in `Finally`. The proposal is described further in the [WhenExpressions in Finally Tasks TEP](https://github.com/tektoncd/community/blob/master/teps/0045-whenexpressions-in-finally-tasks.md). Given we've recently added support for `Results` and `Status` in `Finally Tasks`, this is an opportune time to enable `WhenExpressions` in `Finally Tasks`. --- docs/pipelines.md | 116 ++++++ .../pipelinerun-with-when-expressions.yaml | 42 ++- .../pipeline/v1beta1/pipeline_validation.go | 52 +-- .../v1beta1/pipeline_validation_test.go | 339 +++++++++++++++++- pkg/reconciler/pipelinerun/pipelinerun.go | 2 +- .../pipelinerun/pipelinerun_test.go | 68 +++- pkg/reconciler/pipelinerun/resources/apply.go | 2 + .../pipelinerun/resources/apply_test.go | 30 ++ .../resources/pipelinerunresolution.go | 6 + .../resources/pipelinerunresolution_test.go | 44 +++ test/pipelinefinally_test.go | 112 +++++- 11 files changed, 757 insertions(+), 56 deletions(-) diff --git a/docs/pipelines.md b/docs/pipelines.md index 649634c60b0..a6fb22eddd7 100644 --- a/docs/pipelines.md +++ b/docs/pipelines.md @@ -31,6 +31,10 @@ weight: 3 - [Consuming `Task` execution results in `finally`](#consuming-task-execution-results-in-finally) - [`PipelineRun` Status with `finally`](#pipelinerun-status-with-finally) - [Using Execution `Status` of `pipelineTask`](#using-execution-status-of-pipelinetask) + - [Guard `Finally Task` execution using `WhenExpressions`](#guard-finally-task-execution-using-whenexpressions) + - [`WhenExpressions` using `Parameters` in `Finally Tasks`](#whenexpressions-using-parameters-in-finally-tasks) + - [`WhenExpressions` using `Results` in `Finally Tasks`](#whenexpressions-using-results-in-finally-tasks) + - [`WhenExpressions` using `Execution Status` of `PipelineTask` in `Finally Tasks`](#whenexpressions-using-execution-status-of-pipelinetask-in-finally-tasks) - [Known Limitations](#known-limitations) - [Specifying `Resources` in Final Tasks](#specifying-resources-in-final-tasks) - [Cannot configure the Final Task execution order](#cannot-configure-the-final-task-execution-order) @@ -895,6 +899,118 @@ This kind of variable can have any one of the values from the following table: For an end-to-end example, see [`status` in a `PipelineRun`](../examples/v1beta1/pipelineruns/pipelinerun-task-execution-status.yaml). +### Guard `Finally Task` execution using `WhenExpressions` + +Similar to `Tasks`, `Finally Tasks` can be guarded using [`WhenExpressions`](#guard-task-execution-using-whenexpressions) +that operate on static inputs or variables. Like in `Tasks`, `WhenExpressions` in `Finally Tasks` can operate on +`Parameters` and `Results`. Unlike in `Tasks`, `WhenExpressions` in `Finally Tasks` can also operate on the [`Execution +Status`](#using-execution-status-of-pipelinetask) of `Tasks`. + +#### `WhenExpressions` using `Parameters` in `Finally Tasks` + +`WhenExpressions` in `Finally Tasks` can utilize `Parameters` as demonstrated using [`golang-build`](https://github.com/tektoncd/catalog/tree/master/task/golang-build/0.1) +and [`send-to-channel-slack`](https://github.com/tektoncd/catalog/tree/master/task/send-to-channel-slack/0.1) Catalog +`Tasks`: + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pipelinerun- +spec: + pipelineSpec: + params: + - name: enable-notifications + type: string + description: a boolean indicating whether the notifications should be sent + tasks: + - name: golang-build + taskRef: + name: golang-build + # […] + finally: + - name: notify-build-failure # executed only when build task fails and notifications are enabled + when: + - input: $(tasks.golang-build.status) + operator: in + values: ["Failed"] + - input: $(params.enable-notifications) + operator: in + values: ["true"] + taskRef: + name: send-to-slack-channel + # […] + params: + - name: enable-notifications + value: true +``` + +#### `WhenExpressions` using `Results` in `Finally Tasks` + +`WhenExpressions` in `Finally Tasks` can utilize `Results`, as demonstrated using [`git-clone`](https://github.com/tektoncd/catalog/tree/master/task/git-clone/0.2) +and [`github-add-comment`](https://github.com/tektoncd/catalog/tree/master/task/github-add-comment/0.2) Catalog `Tasks`: + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pipelinerun- +spec: + pipelineSpec: + tasks: + - name: git-clone + taskRef: + name: git-clone + - name: go-build + # […] + finally: + - name: notify-commit-sha # executed only when commit sha is not the expected sha + when: + - input: $(tasks.git-clone.results.commit) + operator: notin + values: [$(params.expected-sha)] + taskRef: + name: github-add-comment + # […] + params: + - name: expected-sha + value: 54dd3984affab47f3018852e61a1a6f9946ecfa +``` + +If the `WhenExpressions` in a `Finally Task` use `Results` from a skipped or failed non-finally `Tasks`, then the +`Finally Task` would also be skipped and be included in the list of `Skipped Tasks` in the `Status`, [similarly to when using +`Results` in other parts of the `Finally Task`](#consuming-task-execution-results-in-finally). + +#### `WhenExpressions` using `Execution Status` of `PipelineTask` in `Finally Tasks` + +`WhenExpressions` in `Finally Tasks` can utilize [`Execution Status` of `PipelineTasks`](#using-execution-status-of-pipelinetask), +as as demonstrated using [`golang-build`](https://github.com/tektoncd/catalog/tree/master/task/golang-build/0.1) and +[`send-to-channel-slack`](https://github.com/tektoncd/catalog/tree/master/task/send-to-channel-slack/0.1) Catalog `Tasks`: + +```yaml +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + generateName: pipelinerun- +spec: + pipelineSpec: + tasks: + - name: golang-build + taskRef: + name: golang-build + # […] + finally: + - name: notify-build-failure # executed only when build task fails + when: + - input: $(tasks.golang-build.status) + operator: in + values: ["Failed"] + taskRef: + name: send-to-slack-channel + # […] +``` + +For an end-to-end example, see [PipelineRun with WhenExpressions](../examples/v1beta1/pipelineruns/pipelinerun-with-when-expressions.yaml). ### Known Limitations diff --git a/examples/v1beta1/pipelineruns/pipelinerun-with-when-expressions.yaml b/examples/v1beta1/pipelineruns/pipelinerun-with-when-expressions.yaml index 6a552d508c9..cb3161a557c 100644 --- a/examples/v1beta1/pipelineruns/pipelinerun-with-when-expressions.yaml +++ b/examples/v1beta1/pipelineruns/pipelinerun-with-when-expressions.yaml @@ -100,7 +100,47 @@ spec: image: ubuntu script: exit 1 finally: - - name: do-something-finally + - name: finally-task-should-be-skipped-1 # when expression using execution status, evaluates to false + when: + - input: "$(tasks.echo-file-exists.status)" + operator: in + values: ["Failure"] + taskSpec: + steps: + - name: echo + image: ubuntu + script: exit 1 + - name: finally-task-should-be-skipped-2 # when expression using task result, evaluates to false + when: + - input: "$(tasks.check-file.results.exists)" + operator: in + values: ["missing"] + taskSpec: + steps: + - name: echo + image: ubuntu + script: exit 1 + - name: finally-task-should-be-skipped-3 # when expression using parameter, evaluates to false + when: + - input: "$(params.path)" + operator: notin + values: ["README.md"] + taskSpec: + steps: + - name: echo + image: ubuntu + script: exit 1 + - name: finally-task-should-be-executed # when expression using execution status, param and results + when: + - input: "$(tasks.echo-file-exists.status)" + operator: in + values: ["Succeeded"] + - input: "$(tasks.check-file.results.exists)" + operator: in + values: ["yes"] + - input: "$(params.path)" + operator: in + values: ["README.md"] taskSpec: steps: - name: echo diff --git a/pkg/apis/pipeline/v1beta1/pipeline_validation.go b/pkg/apis/pipeline/v1beta1/pipeline_validation.go index 0e2b18d96f6..0db801cff12 100644 --- a/pkg/apis/pipeline/v1beta1/pipeline_validation.go +++ b/pkg/apis/pipeline/v1beta1/pipeline_validation.go @@ -70,7 +70,7 @@ func (ps *PipelineSpec) Validate(ctx context.Context) (errs *apis.FieldError) { errs = errs.Also(validatePipelineResults(ps.Results)) errs = errs.Also(validateTasksAndFinallySection(ps)) errs = errs.Also(validateFinalTasks(ps.Tasks, ps.Finally)) - errs = errs.Also(validateWhenExpressions(ps.Tasks)) + errs = errs.Also(validateWhenExpressions(ps.Tasks, ps.Finally)) return errs } @@ -329,22 +329,32 @@ func validateExecutionStatusVariablesInFinally(tasks []PipelineTask, finally []P ptNames := PipelineTaskList(tasks).Names() for idx, t := range finally { for _, param := range t.Params { - // retrieve a list of substitution expression from a param - if ps, ok := GetVarSubstitutionExpressionsForParam(param); ok { - // validate tasks.pipelineTask.status if this expression is not a result reference - if !LooksLikeContainsResultRefs(ps) { - for _, p := range ps { - // check if it contains context variable accessing execution status - $(tasks.taskname.status) - if containsExecutionStatusRef(p) { - // strip tasks. and .status from tasks.taskname.status to further verify task name - pt := strings.TrimSuffix(strings.TrimPrefix(p, "tasks."), ".status") - // report an error if the task name does not exist in the list of dag tasks - if !ptNames.Has(pt) { - errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("pipeline task %s is not defined in the pipeline", pt), - "value").ViaFieldKey("params", param.Name).ViaFieldIndex("finally", idx)) - } - } - } + if expressions, ok := GetVarSubstitutionExpressionsForParam(param); ok { + errs = errs.Also(validateExecutionStatusVariablesExpressions(expressions, ptNames, "value").ViaFieldKey( + "params", param.Name).ViaFieldIndex("finally", idx)) + } + } + for i, we := range t.WhenExpressions { + if expressions, ok := we.GetVarSubstitutionExpressions(); ok { + errs = errs.Also(validateExecutionStatusVariablesExpressions(expressions, ptNames, "").ViaFieldIndex( + "when", i).ViaFieldIndex("finally", idx)) + } + } + } + return errs +} + +func validateExecutionStatusVariablesExpressions(expressions []string, ptNames sets.String, fieldPath string) (errs *apis.FieldError) { + // validate tasks.pipelineTask.status if this expression is not a result reference + if !LooksLikeContainsResultRefs(expressions) { + for _, expression := range expressions { + // check if it contains context variable accessing execution status - $(tasks.taskname.status) + if containsExecutionStatusRef(expression) { + // strip tasks. and .status from tasks.taskname.status to further verify task name + pt := strings.TrimSuffix(strings.TrimPrefix(expression, "tasks."), ".status") + // report an error if the task name does not exist in the list of dag tasks + if !ptNames.Has(pt) { + errs = errs.Also(apis.ErrInvalidValue(fmt.Sprintf("pipeline task %s is not defined in the pipeline", pt), fieldPath)) } } } @@ -429,9 +439,6 @@ func validateFinalTasks(tasks []PipelineTask, finalTasks []PipelineTask) *apis.F if len(f.Conditions) != 0 { return apis.ErrInvalidValue(fmt.Sprintf("no conditions allowed under spec.finally, final task %s has conditions specified", f.Name), "").ViaFieldIndex("finally", idx) } - if len(f.WhenExpressions) != 0 { - return apis.ErrInvalidValue(fmt.Sprintf("no when expressions allowed under spec.finally, final task %s has when expressions specified", f.Name), "").ViaFieldIndex("finally", idx) - } } ts := PipelineTaskList(tasks).Names() @@ -487,11 +494,14 @@ func validateTasksInputFrom(tasks []PipelineTask) (errs *apis.FieldError) { return errs } -func validateWhenExpressions(tasks []PipelineTask) (errs *apis.FieldError) { +func validateWhenExpressions(tasks []PipelineTask, finalTasks []PipelineTask) (errs *apis.FieldError) { for i, t := range tasks { errs = errs.Also(validateOneOfWhenExpressionsOrConditions(t).ViaFieldIndex("tasks", i)) errs = errs.Also(t.WhenExpressions.validate().ViaFieldIndex("tasks", i)) } + for i, t := range finalTasks { + errs = errs.Also(t.WhenExpressions.validate().ViaFieldIndex("finally", i)) + } return errs } diff --git a/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go b/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go index 46deed5f57d..3d2a529b314 100644 --- a/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go +++ b/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go @@ -276,6 +276,55 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) { Message: `invalid value: operator "exists" is not recognized. valid operators: in,notin`, Paths: []string{"tasks[0].when[0]"}, }, + }, { + name: "invalid pipeline with final task having when expression with invalid operator (not In/NotIn)", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "bar-task"}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "bar-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Operator: selection.Exists, + Values: []string{"foo"}, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: operator "exists" is not recognized. valid operators: in,notin`, + Paths: []string{"finally[0].when[0]"}, + }, + }, { + name: "invalid pipeline with dag task and final task having when expression with invalid operator (not In/NotIn)", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "bar-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Operator: selection.Exists, + Values: []string{"foo"}, + }}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "bar-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Operator: selection.Exists, + Values: []string{"foo"}, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: operator "exists" is not recognized. valid operators: in,notin`, + Paths: []string{"tasks[0].when[0]", "finally[0].when[0]"}, + }, }, { name: "invalid pipeline with one pipeline task having when expression with invalid values (empty)", ps: &PipelineSpec{ @@ -294,6 +343,55 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) { Message: `invalid value: expecting non-empty values field`, Paths: []string{"tasks[0].when[0]"}, }, + }, { + name: "invalid pipeline with final task having when expression with invalid values (empty)", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Operator: selection.In, + Values: []string{}, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: expecting non-empty values field`, + Paths: []string{"finally[0].when[0]"}, + }, + }, { + name: "invalid pipeline with dag task and final task having when expression with invalid values (empty)", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Operator: selection.In, + Values: []string{}, + }}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Operator: selection.In, + Values: []string{}, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: expecting non-empty values field`, + Paths: []string{"tasks[0].when[0]", "finally[0].when[0]"}, + }, }, { name: "invalid pipeline with one pipeline task having when expression with invalid operator (missing)", ps: &PipelineSpec{ @@ -311,6 +409,52 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) { Message: `invalid value: operator "" is not recognized. valid operators: in,notin`, Paths: []string{"tasks[0].when[0]"}, }, + }, { + name: "invalid pipeline with final task having when expression with invalid operator (missing)", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Values: []string{"foo"}, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: operator "" is not recognized. valid operators: in,notin`, + Paths: []string{"finally[0].when[0]"}, + }, + }, { + name: "invalid pipeline with dag task and final task having when expression with invalid operator (missing)", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Values: []string{"foo"}, + }}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Values: []string{"foo"}, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: operator "" is not recognized. valid operators: in,notin`, + Paths: []string{"tasks[0].when[0]", "finally[0].when[0]"}, + }, }, { name: "invalid pipeline with one pipeline task having when expression with invalid values (missing)", ps: &PipelineSpec{ @@ -328,6 +472,52 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) { Message: `invalid value: expecting non-empty values field`, Paths: []string{"tasks[0].when[0]"}, }, + }, { + name: "invalid pipeline with final task having when expression with invalid values (missing)", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Operator: selection.In, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: expecting non-empty values field`, + Paths: []string{"finally[0].when[0]"}, + }, + }, { + name: "invalid pipeline with dag task and final task having when expression with invalid values (missing)", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Operator: selection.In, + }}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "foo", + Operator: selection.In, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: expecting non-empty values field`, + Paths: []string{"tasks[0].when[0]", "finally[0].when[0]"}, + }, }, { name: "invalid pipeline with one pipeline task having when expression with misconfigured result reference", ps: &PipelineSpec{ @@ -349,6 +539,61 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) { Message: `invalid value: expected all of the expressions [tasks.a-task.resultTypo.bResult] to be result expressions but only [] were`, Paths: []string{"tasks[1].when[0]"}, }, + }, { + name: "invalid pipeline with final task having when expression with misconfigured result reference", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "valid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + }, { + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "$(tasks.a-task.resultTypo.bResult)", + Operator: selection.In, + Values: []string{"bar"}, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: expected all of the expressions [tasks.a-task.resultTypo.bResult] to be result expressions but only [] were`, + Paths: []string{"finally[0].when[0]"}, + }, + }, { + name: "invalid pipeline with dag task and final task having when expression with misconfigured result reference", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "valid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + }, { + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "$(tasks.a-task.resultTypo.bResult)", + Operator: selection.In, + Values: []string{"bar"}, + }}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{ + Input: "$(tasks.a-task.resultTypo.bResult)", + Operator: selection.In, + Values: []string{"bar"}, + }}, + }}, + }, + expectedError: apis.FieldError{ + Message: `invalid value: expected all of the expressions [tasks.a-task.resultTypo.bResult] to be result expressions but only [] were`, + Paths: []string{"tasks[1].when[0]", "finally[0].when[0]"}, + }, }, { name: "invalid pipeline with one pipeline task having blank when expression", ps: &PipelineSpec{ @@ -366,6 +611,49 @@ func TestPipelineSpec_Validate_Failure(t *testing.T) { Message: `missing field(s)`, Paths: []string{"tasks[1].when[0]"}, }, + }, { + name: "invalid pipeline with final task having blank when expression", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "valid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + }, { + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{}}, + }}, + }, + expectedError: apis.FieldError{ + Message: `missing field(s)`, + Paths: []string{"finally[0].when[0]"}, + }, + }, { + name: "invalid pipeline with dag task and final task having blank when expression", + ps: &PipelineSpec{ + Description: "this is an invalid pipeline with invalid pipeline task", + Tasks: []PipelineTask{{ + Name: "valid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + }, { + Name: "invalid-pipeline-task", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{}}, + }}, + Finally: []PipelineTask{{ + Name: "invalid-pipeline-task-finally", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: []WhenExpression{{}}, + }}, + }, + expectedError: apis.FieldError{ + Message: `missing field(s)`, + Paths: []string{"tasks[1].when[0]", "finally[0].when[0]"}, + }, }, { name: "invalid pipeline with pipeline task having reference to resources which does not exist", ps: &PipelineSpec{ @@ -2047,21 +2335,6 @@ func TestValidateFinalTasks_Failure(t *testing.T) { Message: `invalid value: invalid task result reference, final task param param1 has task result reference from a task which is not defined in the pipeline`, Paths: []string{"finally[0].params"}, }, - }, { - name: "invalid pipeline with final task specifying when expressions", - finalTasks: []PipelineTask{{ - Name: "final-task", - TaskRef: &TaskRef{Name: "final-task"}, - WhenExpressions: []WhenExpression{{ - Input: "foo", - Operator: selection.In, - Values: []string{"foo", "bar"}, - }}, - }}, - expectedError: apis.FieldError{ - Message: `invalid value: no when expressions allowed under spec.finally, final task final-task has when expressions specified`, - Paths: []string{"finally[0]"}, - }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -2294,6 +2567,42 @@ func TestPipelineTasksExecutionStatus(t *testing.T) { Message: `invalid value: pipeline task notask is not defined in the pipeline`, Paths: []string{"finally[0].params[notask-status].value"}, }, + }, { + name: "invalid string variable in finally accessing missing pipelineTask status in when expression", + finalTasks: []PipelineTask{{ + Name: "foo", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: WhenExpressions{{ + Input: "$(tasks.notask.status)", + Operator: selection.In, + Values: []string{"Success"}, + }}, + }}, + expectedError: apis.FieldError{ + Message: `invalid value: pipeline task notask is not defined in the pipeline`, + Paths: []string{"finally[0].when[0]"}, + }, + }, { + name: "invalid string variable in finally accessing missing pipelineTask status in params and when expression", + finalTasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "notask-status", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(tasks.notask.status)"}, + }}, + }, { + Name: "foo", + TaskRef: &TaskRef{Name: "foo-task"}, + WhenExpressions: WhenExpressions{{ + Input: "$(tasks.notask.status)", + Operator: selection.In, + Values: []string{"Success"}, + }}, + }}, + expectedError: apis.FieldError{ + Message: `invalid value: pipeline task notask is not defined in the pipeline`, + Paths: []string{"finally[0].params[notask-status].value", "finally[1].when[0]"}, + }, }, { name: "invalid variable concatenated with extra string in finally accessing missing pipelineTask status", finalTasks: []PipelineTask{{ diff --git a/pkg/reconciler/pipelinerun/pipelinerun.go b/pkg/reconciler/pipelinerun/pipelinerun.go index 86ce5ef7087..47a7293e171 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun.go +++ b/pkg/reconciler/pipelinerun/pipelinerun.go @@ -630,7 +630,7 @@ func (c *Reconciler) runNextSchedulableTask(ctx context.Context, pr *v1beta1.Pip } for _, rprt := range nextRprts { - if rprt == nil || rprt.Skip(pipelineRunFacts) { + if rprt == nil || rprt.Skip(pipelineRunFacts) || rprt.IsFinallySkipped(pipelineRunFacts) { continue } if rprt.ResolvedConditionChecks == nil || rprt.ResolvedConditionChecks.IsSuccess() { diff --git a/pkg/reconciler/pipelinerun/pipelinerun_test.go b/pkg/reconciler/pipelinerun/pipelinerun_test.go index 5d8115cbbaf..92d5eceb53d 100644 --- a/pkg/reconciler/pipelinerun/pipelinerun_test.go +++ b/pkg/reconciler/pipelinerun/pipelinerun_test.go @@ -4714,6 +4714,66 @@ func TestReconcileWithTaskResultsInFinalTasks(t *testing.T) { StringVal: "$(tasks.dag-task-2.results.aResult)", }}, }, + }, { + Name: "final-task-3", + TaskRef: &v1beta1.TaskRef{Name: "final-task"}, + Params: []v1beta1.Param{{ + Name: "finalParam", + Value: v1beta1.ArrayOrString{ + Type: "string", + StringVal: "param", + }}, + }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(tasks.dag-task-1.results.aResult)", + Operator: selection.NotIn, + Values: []string{"aResultValue"}, + }}, + }, { + Name: "final-task-4", + TaskRef: &v1beta1.TaskRef{Name: "final-task"}, + Params: []v1beta1.Param{{ + Name: "finalParam", + Value: v1beta1.ArrayOrString{ + Type: "string", + StringVal: "param", + }}, + }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(tasks.dag-task-1.results.aResult)", + Operator: selection.In, + Values: []string{"aResultValue"}, + }}, + }, { + Name: "final-task-5", + TaskRef: &v1beta1.TaskRef{Name: "final-task"}, + Params: []v1beta1.Param{{ + Name: "finalParam", + Value: v1beta1.ArrayOrString{ + Type: "string", + StringVal: "$(tasks.dag-task-2.results.aResult)", + }}, + }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(tasks.dag-task-1.results.aResult)", + Operator: selection.NotIn, + Values: []string{"aResultValue"}, + }}, + }, { + Name: "final-task-6", + TaskRef: &v1beta1.TaskRef{Name: "final-task"}, + Params: []v1beta1.Param{{ + Name: "finalParam", + Value: v1beta1.ArrayOrString{ + Type: "string", + StringVal: "$(tasks.dag-task-2.results.aResult)", + }}, + }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(tasks.dag-task-1.results.aResult)", + Operator: selection.In, + Values: []string{"aResultValue"}, + }}, }}, }, }} @@ -4855,10 +4915,16 @@ func TestReconcileWithTaskResultsInFinalTasks(t *testing.T) { } expectedSkippedTasks := []v1beta1.SkippedTask{{ Name: "final-task-2", + }, { + Name: "final-task-3", + }, { + Name: "final-task-5", + }, { + Name: "final-task-6", }} if d := cmp.Diff(expectedSkippedTasks, reconciledRun.Status.SkippedTasks); d != "" { - t.Fatalf("Expected to see only one final task (final-task-2) in the list of skipped tasks. Diff: %s", diff.PrintWantGot(d)) + t.Fatalf("Didn't get the expected list of skipped tasks. Diff: %s", diff.PrintWantGot(d)) } } diff --git a/pkg/reconciler/pipelinerun/resources/apply.go b/pkg/reconciler/pipelinerun/resources/apply.go index e327924e532..409b6b1a525 100644 --- a/pkg/reconciler/pipelinerun/resources/apply.go +++ b/pkg/reconciler/pipelinerun/resources/apply.go @@ -93,6 +93,7 @@ func ApplyPipelineTaskContext(state PipelineRunState, replacements map[string]st if resolvedPipelineRunTask.PipelineTask != nil { pipelineTask := resolvedPipelineRunTask.PipelineTask.DeepCopy() pipelineTask.Params = replaceParamValues(pipelineTask.Params, replacements, nil) + pipelineTask.WhenExpressions = pipelineTask.WhenExpressions.ReplaceWhenExpressionsVariables(replacements) resolvedPipelineRunTask.PipelineTask = pipelineTask } } @@ -129,6 +130,7 @@ func ApplyReplacements(p *v1beta1.PipelineSpec, replacements map[string]string, for i := range p.Finally { p.Finally[i].Params = replaceParamValues(p.Finally[i].Params, replacements, arrayReplacements) + p.Finally[i].WhenExpressions = p.Finally[i].WhenExpressions.ReplaceWhenExpressionsVariables(replacements) } return p diff --git a/pkg/reconciler/pipelinerun/resources/apply_test.go b/pkg/reconciler/pipelinerun/resources/apply_test.go index 8db62221be4..0c44b050d8e 100644 --- a/pkg/reconciler/pipelinerun/resources/apply_test.go +++ b/pkg/reconciler/pipelinerun/resources/apply_test.go @@ -197,6 +197,11 @@ func TestApplyParameters(t *testing.T) { {Name: "final-task-first-param", Value: *v1beta1.NewArrayOrString("$(params.first-param)")}, {Name: "final-task-second-param", Value: *v1beta1.NewArrayOrString("$(params.second-param)")}, }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(params.first-param)", + Operator: selection.In, + Values: []string{"$(params.second-param)"}, + }}, }}, }, params: []v1beta1.Param{{Name: "second-param", Value: *v1beta1.NewArrayOrString("second-value")}}, @@ -210,6 +215,11 @@ func TestApplyParameters(t *testing.T) { {Name: "final-task-first-param", Value: *v1beta1.NewArrayOrString("default-value")}, {Name: "final-task-second-param", Value: *v1beta1.NewArrayOrString("second-value")}, }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "default-value", + Operator: selection.In, + Values: []string{"second-value"}, + }}, }}, }, }, { @@ -230,6 +240,11 @@ func TestApplyParameters(t *testing.T) { {Name: "final-task-first-param", Value: *v1beta1.NewArrayOrString("$(params.first-param)")}, {Name: "final-task-second-param", Value: *v1beta1.NewArrayOrString("$(params.second-param)")}, }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(params.first-param)", + Operator: selection.In, + Values: []string{"$(params.second-param)"}, + }}, }}, }, params: []v1beta1.Param{{Name: "second-param", Value: *v1beta1.NewArrayOrString("second-value")}}, @@ -249,6 +264,11 @@ func TestApplyParameters(t *testing.T) { {Name: "final-task-first-param", Value: *v1beta1.NewArrayOrString("default-value")}, {Name: "final-task-second-param", Value: *v1beta1.NewArrayOrString("second-value")}, }, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "default-value", + Operator: selection.In, + Values: []string{"second-value"}, + }}, }}, }, }} { @@ -1062,6 +1082,11 @@ func TestApplyTaskRunContext(t *testing.T) { Name: "task3", Value: *v1beta1.NewArrayOrString("$(tasks.task3.status)"), }}, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(tasks.task1.status)", + Operator: selection.In, + Values: []string{"$(tasks.task3.status)"}, + }}, }, }} expectedState := PipelineRunState{{ @@ -1075,6 +1100,11 @@ func TestApplyTaskRunContext(t *testing.T) { Name: "task3", Value: *v1beta1.NewArrayOrString("none"), }}, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "succeeded", + Operator: selection.In, + Values: []string{"none"}, + }}, }, }} ApplyPipelineTaskContext(state, r) diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go index 5164b3405ea..3cb080fb588 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go @@ -145,6 +145,9 @@ func (t ResolvedPipelineRunTask) IsConditionStatusFalse() bool { } func (t *ResolvedPipelineRunTask) checkParentsDone(facts *PipelineRunFacts) bool { + if facts.isFinalTask(t.PipelineTask.Name) { + return true + } stateMap := facts.State.ToMap() node := facts.TasksGraph.Nodes[t.PipelineTask.Name] for _, p := range node.Prev { @@ -225,6 +228,9 @@ func (t *ResolvedPipelineRunTask) IsFinallySkipped(facts *PipelineRunFacts) bool if _, err := ResolveResultRef(facts.State, t); err != nil { return true } + if t.whenExpressionsSkip(facts) { + return true + } } return false } diff --git a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go index 5baf3d35788..88bb102a6cb 100644 --- a/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go +++ b/pkg/reconciler/pipelinerun/resources/pipelinerunresolution_test.go @@ -2244,11 +2244,55 @@ func TestResolvedPipelineRunTask_IsFinallySkipped(t *testing.T) { Value: *v1beta1.NewArrayOrString("$(tasks.dag-task.results.missingResult)"), }}, }, + }, { + PipelineTask: &v1beta1.PipelineTask{ + Name: "final-task-3", + TaskRef: &v1beta1.TaskRef{Name: "task"}, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "foo", + Operator: selection.NotIn, + Values: []string{"bar"}, + }}, + }, + }, { + PipelineTask: &v1beta1.PipelineTask{ + Name: "final-task-4", + TaskRef: &v1beta1.TaskRef{Name: "task"}, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "foo", + Operator: selection.In, + Values: []string{"bar"}, + }}, + }, + }, { + PipelineTask: &v1beta1.PipelineTask{ + Name: "final-task-5", + TaskRef: &v1beta1.TaskRef{Name: "task"}, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(tasks.dag-task.results.commit)", + Operator: selection.In, + Values: []string{"SHA2"}, + }}, + }, + }, { + PipelineTask: &v1beta1.PipelineTask{ + Name: "final-task-6", + TaskRef: &v1beta1.TaskRef{Name: "task"}, + WhenExpressions: v1beta1.WhenExpressions{{ + Input: "$(tasks.dag-task.results.missing)", + Operator: selection.In, + Values: []string{"none"}, + }}, + }, }} expected := map[string]bool{ "final-task-1": false, "final-task-2": true, + "final-task-3": false, + "final-task-4": true, + "final-task-5": false, + "final-task-6": true, } tasks := v1beta1.PipelineTaskList([]v1beta1.PipelineTask{*state[0].PipelineTask}) diff --git a/test/pipelinefinally_test.go b/test/pipelinefinally_test.go index 498091c7316..29c69f2df87 100644 --- a/test/pipelinefinally_test.go +++ b/test/pipelinefinally_test.go @@ -84,9 +84,14 @@ func TestPipelineLevelFinally_OneDAGTaskFailed_InvalidTaskResult_Failure(t *test t.Fatalf("Failed to create Task producing task results: %s", err) } - taskConsumingResult := getSuccessTaskConsumingResults(t, namespace) - if _, err := c.TaskClient.Create(ctx, taskConsumingResult, metav1.CreateOptions{}); err != nil { - t.Fatalf("Failed to create Task consuming task results: %s", err) + taskConsumingResultInParam := getSuccessTaskConsumingResults(t, namespace, []v1beta1.ParamSpec{{Name: "dagtask-result"}}, []v1beta1.TaskResult{}) + if _, err := c.TaskClient.Create(ctx, taskConsumingResultInParam, metav1.CreateOptions{}); err != nil { + t.Fatalf("Failed to create Task consuming task results in param: %s", err) + } + + taskConsumingResultInWhenExpression := getSuccessTaskConsumingResults(t, namespace, []v1beta1.ParamSpec{}, []v1beta1.TaskResult{}) + if _, err := c.TaskClient.Create(ctx, taskConsumingResultInWhenExpression, metav1.CreateOptions{}); err != nil { + t.Fatalf("Failed to create Task consuming task results in when expressions: %s", err) } we := v1beta1.WhenExpressions{{ @@ -143,7 +148,7 @@ func TestPipelineLevelFinally_OneDAGTaskFailed_InvalidTaskResult_Failure(t *test }, // final task consuming result from a failed dag task "finaltaskconsumingdagtask1": { - TaskName: taskConsumingResult.Name, + TaskName: taskConsumingResultInParam.Name, Param: []v1beta1.Param{{ Name: "dagtask-result", Value: v1beta1.ArrayOrString{ @@ -154,7 +159,7 @@ func TestPipelineLevelFinally_OneDAGTaskFailed_InvalidTaskResult_Failure(t *test }, // final task consuming result from a skipped dag task due to when expression "finaltaskconsumingdagtask4": { - TaskName: taskConsumingResult.Name, + TaskName: taskConsumingResultInParam.Name, Param: []v1beta1.Param{{ Name: "dagtask-result", Value: v1beta1.ArrayOrString{ @@ -164,7 +169,7 @@ func TestPipelineLevelFinally_OneDAGTaskFailed_InvalidTaskResult_Failure(t *test }}, }, "finaltaskconsumingdagtask5": { - TaskName: taskConsumingResult.Name, + TaskName: taskConsumingResultInParam.Name, Param: []v1beta1.Param{{ Name: "dagtask-result", Value: v1beta1.ArrayOrString{ @@ -173,6 +178,55 @@ func TestPipelineLevelFinally_OneDAGTaskFailed_InvalidTaskResult_Failure(t *test }, }}, }, + // final task with when expressions using results from skipped dag task + "guardedfinaltaskconsumingdagtask4": { + TaskName: taskConsumingResultInWhenExpression.Name, + When: v1beta1.WhenExpressions{{ + Input: "$(tasks.dagtask4.results.result)", + Operator: "in", + Values: []string{"aResult"}, + }}, + }, + // final task with when expressions using results from successful dag task + // when expressions evaluate to true + "guardedfinaltaskusingdagtask5result1": { + TaskName: taskConsumingResultInWhenExpression.Name, + When: v1beta1.WhenExpressions{{ + Input: "$(tasks.dagtask5.results.result)", + Operator: "in", + Values: []string{"Hello"}, + }}, + }, + // final task with when expressions using results from successful dag task + // when expressions evaluate to false + "guardedfinaltaskusingdagtask5result2": { + TaskName: taskConsumingResultInWhenExpression.Name, + When: v1beta1.WhenExpressions{{ + Input: "$(tasks.dagtask5.results.result)", + Operator: "notin", + Values: []string{"Hello"}, + }}, + }, + // final task with when expressions using execution status of a dag task + // when expressions evaluate to true + "guardedfinaltaskusingdagtask5status1": { + TaskName: taskConsumingResultInWhenExpression.Name, + When: v1beta1.WhenExpressions{{ + Input: "$(tasks.dagtask5.status)", + Operator: "in", + Values: []string{"Succeeded"}, + }}, + }, + // final task with when expressions using execution status of a dag task + // when expressions evaluate to false + "guardedfinaltaskusingdagtask5status2": { + TaskName: taskConsumingResultInWhenExpression.Name, + When: v1beta1.WhenExpressions{{ + Input: "$(tasks.dagtask5.status)", + Operator: "in", + Values: []string{"Failed"}, + }}, + }, } pipeline := getPipeline(t, namespace, dag, f) @@ -201,13 +255,16 @@ func TestPipelineLevelFinally_OneDAGTaskFailed_InvalidTaskResult_Failure(t *test t.Fatalf("Error listing TaskRuns for PipelineRun %s: %s", pipelineRun.Name, err) } - // expecting taskRuns for dagtask1, dagtask2, dagtask3 (with condition failure), dagtask5, finaltask1, finaltask2, and finaltaskconsumingdagtask5 - if len(taskrunList.Items) != 7 { + // expecting taskRuns for dagtask1, dagtask2, dagtask3 (with condition failure), dagtask5, finaltask1, finaltask2, + // finaltaskconsumingdagtask5, guardedfinaltaskusingdagtask5result1, guardedfinaltaskusingdagtask5status1 + expectedTaskRunsCount := 9 + if len(taskrunList.Items) != expectedTaskRunsCount { var s []string for _, n := range taskrunList.Items { s = append(s, n.Labels["tekton.dev/pipelineTask"]) } - t.Fatalf("Error retrieving TaskRuns for PipelineRun %s. Expected 5 taskRuns and found taskRuns for: %s", pipelineRun.Name, strings.Join(s, ", ")) + t.Fatalf("Error retrieving TaskRuns for PipelineRun %s. Expected %d taskRuns and found %d taskRuns for: %s", + pipelineRun.Name, expectedTaskRunsCount, len(taskrunList.Items), strings.Join(s, ", ")) } var dagTask1EndTime, dagTask2EndTime, finalTaskStartTime *metav1.Time @@ -269,7 +326,17 @@ func TestPipelineLevelFinally_OneDAGTaskFailed_InvalidTaskResult_Failure(t *test t.Errorf("Error resolving task result reference in a finally task %s", n) } } - case n == "finaltaskconsumingdagtask1" || n == "finaltaskconsumingdagtask4": + case n == "guardedfinaltaskusingdagtask5result1": + if !isSuccessful(t, n, taskrunItem.Status.Conditions) { + t.Fatalf("final task %s should have succeeded", n) + } + case n == "guardedfinaltaskusingdagtask5status1": + if !isSuccessful(t, n, taskrunItem.Status.Conditions) { + t.Fatalf("final task %s should have succeeded", n) + } + case n == "guardedfinaltaskusingdagtask5result2": + t.Fatalf("final task %s should have skipped due to when expression evaluating to false", n) + case n == "finaltaskconsumingdagtask1" || n == "finaltaskconsumingdagtask4" || n == "guardedfinaltaskconsumingdagtask4": t.Fatalf("final task %s should have skipped due to missing task result reference", n) default: t.Fatalf("Found unexpected taskRun %s", n) @@ -292,6 +359,12 @@ func TestPipelineLevelFinally_OneDAGTaskFailed_InvalidTaskResult_Failure(t *test Name: "finaltaskconsumingdagtask1", }, { Name: "finaltaskconsumingdagtask4", + }, { + Name: "guardedfinaltaskconsumingdagtask4", + }, { + Name: "guardedfinaltaskusingdagtask5result2", + }, { + Name: "guardedfinaltaskusingdagtask5status2", }} actualSkippedTasks := pr.Status.SkippedTasks @@ -435,8 +508,8 @@ func getSuccessTaskProducingResults(t *testing.T, namespace string) *v1beta1.Tas return getTaskDef(helpers.ObjectNameForTest(t), namespace, "echo -n \"Hello\" > $(results.result.path)", []v1beta1.ParamSpec{}, []v1beta1.TaskResult{{Name: "result"}}) } -func getSuccessTaskConsumingResults(t *testing.T, namespace string) *v1beta1.Task { - return getTaskDef(helpers.ObjectNameForTest(t), namespace, "exit 0", []v1beta1.ParamSpec{{Name: "dagtask-result"}}, []v1beta1.TaskResult{}) +func getSuccessTaskConsumingResults(t *testing.T, namespace string, params []v1beta1.ParamSpec, results []v1beta1.TaskResult) *v1beta1.Task { + return getTaskDef(helpers.ObjectNameForTest(t), namespace, "exit 0", params, results) } func getCondition(t *testing.T, namespace string) *v1alpha1.Condition { @@ -477,12 +550,17 @@ func getPipeline(t *testing.T, namespace string, dag map[string]pipelineTask, f pt = append(pt, task) } for k, v := range f { - fpt = append(fpt, v1beta1.PipelineTask{ - Name: k, - + finalTask := v1beta1.PipelineTask{ + Name: k, TaskRef: &v1beta1.TaskRef{Name: v.TaskName}, - Params: v.Param, - }) + } + if len(v.Param) != 0 { + finalTask.Params = v.Param + } + if len(v.When) != 0 { + finalTask.WhenExpressions = v.When + } + fpt = append(fpt, finalTask) } pipeline := &v1beta1.Pipeline{ ObjectMeta: metav1.ObjectMeta{Name: helpers.ObjectNameForTest(t), Namespace: namespace},