Skip to content

Commit

Permalink
access reason along with status in finally task
Browse files Browse the repository at this point in the history
A finally task can access both the reason and status of a pipelineTask from
the tasks section.

When onError is set to either continue or stopAndFail and the task fails, the
status is set to the same value Failed, making it indistinguishable whether the
failure was allowed or not. However, when onError is set to continue,
the reason is assigned FailureIgnored. This additional information can be used
to identify that the checks failed, but the failure can be ignored.

The syntax is $(tasks.<pipelineTask>.reason)

Signed-off-by: Priti Desai <[email protected]>
  • Loading branch information
pritidesai authored and tekton-robot committed Jul 18, 2024
1 parent fcfe0d3 commit b787c94
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 13 deletions.
3 changes: 2 additions & 1 deletion docs/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ For instructions on using variable substitutions see the relevant section of [th
| `context.pipelineRun.namespace` | The namespace of the `PipelineRun` that this `Pipeline` is running in. |
| `context.pipelineRun.uid` | The uid of the `PipelineRun` that this `Pipeline` is running in. |
| `context.pipeline.name` | The name of this `Pipeline` . |
| `tasks.<pipelineTaskName>.status` | The execution status of the specified `pipelineTask`, only available in `finally` tasks. The execution status can be set to any one of the values (`Succeeded`, `Failed`, or `None`) described [here](pipelines.md#using-execution-status-of-pipelinetask) |
| `tasks.<pipelineTaskName>.status` | The execution status of the specified `pipelineTask`, only available in `finally` tasks. The execution status can be set to any one of the values (`Succeeded`, `Failed`, or `None`) described [here](pipelines.md#using-execution-status-of-pipelinetask). |
| `tasks.<pipelineTaskName>.reason` | The execution reason of the specified `pipelineTask`, only available in `finally` tasks. The reason can be set to any one of the values (`Failed`, `TaskRunCancelled`, `TaskRunTimeout`, `FailureIgnored`, etc ) described [here](taskruns.md#monitoring-execution-status). |
| `tasks.status` | An aggregate status of all the `pipelineTasks` under the `tasks` section (excluding the `finally` section). This variable is only available in the `finally` tasks and can have any one of the values (`Succeeded`, `Failed`, `Completed`, or `None`) described [here](pipelines.md#using-aggregate-execution-status-of-all-tasks). |
| `context.pipelineTask.retries` | The retries of this `PipelineTask`. |
| `tasks.<taskName>.outputs.<artifactName>` | The value of a specific output artifact of the `Task` |
Expand Down
44 changes: 43 additions & 1 deletion examples/v1/pipelineruns/beta/ignore-task-error.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,30 @@ spec:
- name: write
image: docker.io/library/alpine:3.20.1
script: |
#!/usr/bin/env sh
echo "this is a failing task"
exit 1
exit 1;
- name: echo-continue-with-matrix
onError: continue
matrix:
params:
- name: param1
value:
- "foo"
- "bar"
taskSpec:
params:
- name: param1
steps:
- name: write
image: docker.io/library/alpine
script: |
#!/usr/bin/env sh
echo "this is a failing task if param1 is set bar"
if [[ $(params.param1) == "bar" ]]; then
exit 1;
fi
exit 0
- name: echo
runAfter:
- echo-continue
Expand All @@ -22,4 +44,24 @@ spec:
- name: write
image: docker.io/library/alpine:3.20.1
script: |
#!/usr/bin/env sh
echo "this is a success task"
finally:
- name: verify-task-reason # this task verifies the reason of failure ignored task, it fails if verification fails
params:
- name: echo-continue-task-reason
value: $(tasks.echo-continue.reason)
- name: echo-continue-with-matrix-task-reason
value: $(tasks.echo-continue-with-matrix.reason)
taskSpec:
steps:
- name: verify-task-reason
image: docker.io/library/alpine
script: |
#!/usr/bin/env sh
if [[ $(params.echo-continue-task-reason) != "FailureIgnored" ]]; then
exit 1;
fi
if [[ $(params.echo-continue-with-matrix-task-reason) != "FailureIgnored" ]]; then
exit 1;
fi
24 changes: 18 additions & 6 deletions pkg/apis/pipeline/v1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,13 @@ func (pt *PipelineTask) GetVarSubstitutionExpressions() []string {
return allExpressions
}

// containsExecutionStatusRef checks if a specified param has a reference to execution status or reason
// $(tasks.<task-name>.status), $(tasks.status), or $(tasks.<task-name>.reason)
func containsExecutionStatusRef(p string) bool {
if strings.HasPrefix(p, "tasks.") && strings.HasSuffix(p, ".status") {
return true
if strings.HasPrefix(p, "tasks.") {
if strings.HasSuffix(p, ".status") || strings.HasSuffix(p, ".reason") {
return true
}
}
return false
}
Expand Down Expand Up @@ -589,7 +593,7 @@ func containsExecutionStatusReferences(expressions []string) bool {
if !LooksLikeContainsResultRefs(expressions) {
for _, e := range expressions {
// check if it contains context variable accessing execution status - $(tasks.taskname.status)
// or an aggregate status - $(tasks.status)
// or an aggregate status - $(tasks.status) or reason - $(tasks.taskname.reason)
if containsExecutionStatusRef(e) {
return true
}
Expand All @@ -606,17 +610,25 @@ func validateExecutionStatusVariablesExpressions(expressions []string, ptNames s
if expression == PipelineTasksAggregateStatus {
continue
}
// check if it contains context variable accessing execution status - $(tasks.taskname.status)
// check if it contains context variable accessing execution status - $(tasks.taskname.status) | $(tasks.taskname.reason)
if containsExecutionStatusRef(expression) {
// strip tasks. and .status from tasks.taskname.status to further verify task name
pt := strings.TrimSuffix(strings.TrimPrefix(expression, "tasks."), ".status")
var pt string
if strings.HasSuffix(expression, ".status") {
// strip tasks. and .status from tasks.taskname.status to further verify task name
pt = strings.TrimSuffix(strings.TrimPrefix(expression, "tasks."), ".status")
}
if strings.HasSuffix(expression, ".reason") {
// strip tasks. and .reason from tasks.taskname.reason to further verify task name
pt = strings.TrimSuffix(strings.TrimPrefix(expression, "tasks."), ".reason")
}
// 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))
}
}
}
}

return errs
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/pipeline/v1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3866,13 +3866,19 @@ func TestPipelineTasksExecutionStatus(t *testing.T) {
TaskRef: &TaskRef{Name: "bar-task"},
Params: Params{{
Name: "foo-status", Value: ParamValue{Type: ParamTypeString, StringVal: "$(tasks.foo.status)"},
}, {
Name: "foo-reason", Value: ParamValue{Type: ParamTypeString, StringVal: "$(tasks.foo.reason)"},
}, {
Name: "tasks-status", Value: ParamValue{Type: ParamTypeString, StringVal: "$(tasks.status)"},
}},
When: WhenExpressions{{
Input: "$(tasks.foo.status)",
Operator: selection.In,
Values: []string{"Failure"},
}, {
Input: "$(tasks.foo.reason)",
Operator: selection.In,
Values: []string{"Failed"},
}, {
Input: "$(tasks.status)",
Operator: selection.In,
Expand Down
21 changes: 16 additions & 5 deletions pkg/apis/pipeline/v1beta1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,13 @@ func (pt *PipelineTask) extractAllParams() Params {
return allParams
}

// containsExecutionStatusRef checks if a specified param has a reference to execution status or reason
// $(tasks.<task-name>.status), $(tasks.status), or $(tasks.<task-name>.reason)
func containsExecutionStatusRef(p string) bool {
if strings.HasPrefix(p, "tasks.") && strings.HasSuffix(p, ".status") {
return true
if strings.HasPrefix(p, "tasks.") {
if strings.HasSuffix(p, ".status") || strings.HasSuffix(p, ".reason") {
return true
}
}
return false
}
Expand Down Expand Up @@ -588,10 +592,17 @@ func validateExecutionStatusVariablesExpressions(expressions []string, ptNames s
if expression == PipelineTasksAggregateStatus {
continue
}
// check if it contains context variable accessing execution status - $(tasks.taskname.status)
// check if it contains context variable accessing execution status - $(tasks.taskname.status) | $(tasks.taskname.reason)
if containsExecutionStatusRef(expression) {
// strip tasks. and .status from tasks.taskname.status to further verify task name
pt := strings.TrimSuffix(strings.TrimPrefix(expression, "tasks."), ".status")
var pt string
if strings.HasSuffix(expression, ".status") {
// strip tasks. and .status from tasks.taskname.status to further verify task name
pt = strings.TrimSuffix(strings.TrimPrefix(expression, "tasks."), ".status")
}
if strings.HasSuffix(expression, ".reason") {
// strip tasks. and .reason from tasks.taskname.reason to further verify task name
pt = strings.TrimSuffix(strings.TrimPrefix(expression, "tasks."), ".reason")
}
// 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))
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/pipeline/v1beta1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3785,13 +3785,19 @@ func TestPipelineTasksExecutionStatus(t *testing.T) {
TaskRef: &TaskRef{Name: "bar-task"},
Params: Params{{
Name: "foo-status", Value: ParamValue{Type: ParamTypeString, StringVal: "$(tasks.foo.status)"},
}, {
Name: "foo-reason", Value: ParamValue{Type: ParamTypeString, StringVal: "$(tasks.foo.reason)"},
}, {
Name: "tasks-status", Value: ParamValue{Type: ParamTypeString, StringVal: "$(tasks.status)"},
}},
WhenExpressions: WhenExpressions{{
Input: "$(tasks.foo.status)",
Operator: selection.In,
Values: []string{"Failure"},
}, {
Input: "$(tasks.foo.reason)",
Operator: selection.In,
Values: []string{"Failed"},
}, {
Input: "$(tasks.status)",
Operator: selection.In,
Expand Down
31 changes: 31 additions & 0 deletions pkg/reconciler/pipelinerun/resources/pipelinerunresolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,37 @@ func (t ResolvedPipelineTask) IsCustomTask() bool {
return t.CustomTask
}

// getReason returns the latest reason if the run has completed successfully
// If the PipelineTask has a Matrix, getReason returns the failure reason for any failure
// otherwise, it returns an empty string
func (t ResolvedPipelineTask) getReason() string {
if t.IsCustomTask() {
if len(t.CustomRuns) == 0 {
return ""
}
for _, run := range t.CustomRuns {
if !run.IsSuccessful() && len(run.Status.Conditions) >= 1 {
return run.Status.Conditions[0].Reason
}
}
if len(t.CustomRuns) >= 1 && len(t.CustomRuns[0].Status.Conditions) >= 1 {
return t.CustomRuns[0].Status.Conditions[0].Reason
}
}
if len(t.TaskRuns) == 0 {
return ""
}
for _, taskRun := range t.TaskRuns {
if !taskRun.IsSuccessful() && len(taskRun.Status.Conditions) >= 1 {
return taskRun.Status.Conditions[0].Reason
}
}
if len(t.TaskRuns) >= 1 && len(t.TaskRuns[0].Status.Conditions) >= 1 {
return t.TaskRuns[0].Status.Conditions[0].Reason
}
return ""
}

// isSuccessful returns true only if the run has completed successfully
// If the PipelineTask has a Matrix, isSuccessful returns true if all runs have completed successfully
func (t ResolvedPipelineTask) isSuccessful() bool {
Expand Down
Loading

0 comments on commit b787c94

Please sign in to comment.