Skip to content

Commit

Permalink
[TEP-0050] Add OnError field
Browse files Browse the repository at this point in the history
In [TEP-0050][tep-0050], we proposed to add an `OnError` API field under `PipelineTask` to configure error handling strategy.

This commits add the new `OnError` API field and the related validation, conversion and validation. The business logic will be added in the follow-up PRs.

Note: OnError is in preview mode and not yet supported.

/kind feature

[tep-0050]: https://github.com/tektoncd/community/blob/main/teps/0050-ignore-task-failures.md
  • Loading branch information
QuanZhang-William committed Sep 29, 2023
1 parent c2858d8 commit 8b55c08
Show file tree
Hide file tree
Showing 14 changed files with 429 additions and 1 deletion.
65 changes: 65 additions & 0 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2851,6 +2851,23 @@ PipelineSpec
Note: PipelineSpec is in preview mode and not yet supported</p>
</td>
</tr>
<tr>
<td>
<code>onError</code><br/>
<em>
<a href="#tekton.dev/v1.PipelineTaskOnErrorType">
PipelineTaskOnErrorType
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>OnError defines the exiting behavior of a PipelineRun on error
can be set to [ continue | stopAndFail ]
Note: OnError is in preview mode and not yet supported
TODO(@QuanZhang-William)</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1.PipelineTaskMetadata">PipelineTaskMetadata
Expand Down Expand Up @@ -2893,6 +2910,29 @@ map[string]string
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1.PipelineTaskOnErrorType">PipelineTaskOnErrorType
(<code>string</code> alias)</h3>
<p>
(<em>Appears on:</em><a href="#tekton.dev/v1.PipelineTask">PipelineTask</a>)
</p>
<div>
<p>PipelineTaskOnErrorType defines a list of supported failure handling behaviors of a PipelineTask on error</p>
</div>
<table>
<thead>
<tr>
<th>Value</th>
<th>Description</th>
</tr>
</thead>
<tbody><tr><td><p>&#34;continue&#34;</p></td>
<td><p>PipelineTaskContinue indicates to continue executing the rest of the DAG when the PipelineTask fails</p>
</td>
</tr><tr><td><p>&#34;stopAndFail&#34;</p></td>
<td><p>PipelineTaskStopAndFail indicates to stop and fail the PipelineRun if the PipelineTask fails</p>
</td>
</tr></tbody>
</table>
<h3 id="tekton.dev/v1.PipelineTaskParam">PipelineTaskParam
</h3>
<div>
Expand Down Expand Up @@ -10900,6 +10940,23 @@ PipelineSpec
Note: PipelineSpec is in preview mode and not yet supported</p>
</td>
</tr>
<tr>
<td>
<code>onError</code><br/>
<em>
<a href="#tekton.dev/v1beta1.PipelineTaskOnErrorType">
PipelineTaskOnErrorType
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>OnError defines the exiting behavior of a PipelineRun on error
can be set to [ continue | stopAndFail ]
Note: OnError is in preview mode and not yet supported
TODO(@QuanZhang-William)</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1beta1.PipelineTaskInputResource">PipelineTaskInputResource
Expand Down Expand Up @@ -10998,6 +11055,14 @@ map[string]string
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1beta1.PipelineTaskOnErrorType">PipelineTaskOnErrorType
(<code>string</code> alias)</h3>
<p>
(<em>Appears on:</em><a href="#tekton.dev/v1beta1.PipelineTask">PipelineTask</a>)
</p>
<div>
<p>PipelineTaskOnErrorType defines a list of supported failure handling behaviors of a PipelineTask on error</p>
</div>
<h3 id="tekton.dev/v1beta1.PipelineTaskOutputResource">PipelineTaskOutputResource
</h3>
<p>
Expand Down
102 changes: 102 additions & 0 deletions docs/pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ weight: 203
- [Tekton Bundles](#tekton-bundles)
- [Using the `runAfter` field](#using-the-runafter-field)
- [Using the `retries` field](#using-the-retries-field)
- [Using the `onError` field](#using-the-retries-field)
- [Produce results with `OnError`](#produce-results-with-onerror)
- [Guard `Task` execution using `when` expressions](#guard-task-execution-using-when-expressions)
- [Guarding a `Task` and its dependent `Tasks`](#guarding-a-task-and-its-dependent-tasks)
- [Cascade `when` expressions to the specific dependent `Tasks`](#cascade-when-expressions-to-the-specific-dependent-tasks)
Expand Down Expand Up @@ -606,6 +608,106 @@ tasks:
name: build-push
```

### Using the `onError` field

> :seedling: **Specifying `onError` in `PipelineTasks` is an [alpha](additional-configs.md#alpha-features) feature.** The `enable-api-fields` feature flag must be set to `"alpha"` to specify `onError` in a `PipelineTask`.

> :seedling: This feature is in **Preview Only** mode and not yet supported/implemented.

When a `PipelineTask` fails, the rest of the `PipelineTasks` are skipped and the `PipelineRun` is declared a failure. If you would like to
ignore such `PipelineTask` failure and continue executing the rest of the `PipelineTasks`, you can specify `onError` for such a `PipelineTask`.

`OnError` can be set to `stopAndFail` (default) and `continue`. The failure of a `PipelineTask` with `stopAndFail` would stop and fail the whole `PipelineRun`. A `PipelineTask` fails with `continue` does not fail the whole `PipelineRun`, and the rest of the `PipelineTask` will continue to execute.

To ignore a `PipelineTask` failure, set `onError` to `continue`:

``` yaml
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: demo
spec:
tasks:
- name: task1
onError: continue
taskSpec:
steps:
- name: step1
image: alpine
script: |
exit 1
```

At runtime, the failure is ignored to determine the `PipelineRun` status. The `PipelineRun` `message` contains the ignored failure info:

``` yaml
status:
conditions:
- lastTransitionTime: "2023-09-28T19:08:30Z"
message: 'Tasks Completed: 1 (Failed: 1 (Ignored: 1), Cancelled 0), Skipped: 0'
reason: Succeeded
status: "True"
type: Succeeded
...
```

Note that the `TaskRun` status remains as it is irrelevant to `OnError`. Failed but ignored `TaskRuns` result in a `failed` status with reason
`FailureIgnored`.

For example, the `TaskRun` created by the above `PipelineRun` has the following status:

``` bash
$ kubectl get tr demo-run-task1
NAME SUCCEEDED REASON STARTTIME COMPLETIONTIME
demo-run-task1 False FailureIgnored 12m 12m
```

To specify `onError` for a `step`, please see [specifying onError for a step](./tasks.md#specifying-onerror-for-a-step).

**Note:** Setting [`Retry`](#specifying-retries) and `OnError:continue` at the same time is **NOT** allowed.

### Produce results with `OnError`

When a `PipelineTask` is set to ignore error and the `PipelineTask` is able to initialize a result before failing, the result is made available to the consumer `PipelineTasks`.

``` yaml
tasks:
- name: task1
onError: continue
taskSpec:
results:
- name: result1
steps:
- name: step1
image: alpine
script: |
echo -n 123 | tee $(results.result1.path)
exit 1
```

The consumer `PipelineTasks` can access the result by referencing `$(tasks.task1.results.result1)`.

If the result is **NOT** initialized before failing, and there is a `PipelineTask` consuming it:

``` yaml
tasks:
- name: task1
onError: continue
taskSpec:
results:
- name: result1
steps:
- name: step1
image: alpine
script: |
exit 1
echo -n 123 | tee $(results.result1.path)
```

- If the consuming `PipelineTask` has `OnError:stopAndFail`, the `PipelineRun` will fail with `InvalidTaskResultReference`.
- If the consuming `PipelineTask` has `OnError:continue`, the consuming `PipelineTask` will be skipped with reason `Results were missing`,
and the `PipelineRun` will continue to execute.

### Guard `Task` execution using `when` expressions

To run a `Task` only when certain conditions are met, it is possible to _guard_ task execution using the `when` field. The `when` field allows you to list a series of references to `when` expressions.
Expand Down
7 changes: 7 additions & 0 deletions pkg/apis/pipeline/v1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions pkg/apis/pipeline/v1/pipeline_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,20 @@ import (
"knative.dev/pkg/kmeta"
)

// PipelineTaskOnErrorType defines a list of supported failure handling behaviors of a PipelineTask on error
type PipelineTaskOnErrorType string

const (
// PipelineTasksAggregateStatus is a param representing aggregate status of all dag pipelineTasks
PipelineTasksAggregateStatus = "tasks.status"
// PipelineTasks is a value representing a task is a member of "tasks" section of the pipeline
PipelineTasks = "tasks"
// PipelineFinallyTasks is a value representing a task is a member of "finally" section of the pipeline
PipelineFinallyTasks = "finally"
// PipelineTaskStopAndFail indicates to stop and fail the PipelineRun if the PipelineTask fails
PipelineTaskStopAndFail PipelineTaskOnErrorType = "stopAndFail"
// PipelineTaskContinue indicates to continue executing the rest of the DAG when the PipelineTask fails
PipelineTaskContinue PipelineTaskOnErrorType = "continue"
)

// +genclient
Expand Down Expand Up @@ -238,6 +245,13 @@ type PipelineTask struct {
// Note: PipelineSpec is in preview mode and not yet supported
// +optional
PipelineSpec *PipelineSpec `json:"pipelineSpec,omitempty"`

// OnError defines the exiting behavior of a PipelineRun on error
// can be set to [ continue | stopAndFail ]
// Note: OnError is in preview mode and not yet supported
// TODO(#7165)
// +optional
OnError PipelineTaskOnErrorType `json:"onError,omitempty"`
}

// IsCustomTask checks whether an embedded TaskSpec is a Custom Task
Expand Down
92 changes: 92 additions & 0 deletions pkg/apis/pipeline/v1/pipeline_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,98 @@ func TestPipelineTask_ValidateName(t *testing.T) {
}
}

func TestPipelineTask_OnError(t *testing.T) {
tests := []struct {
name string
p PipelineTask
expectedError *apis.FieldError
wc func(context.Context) context.Context
}{{
name: "valid PipelineTask with onError:continue",
p: PipelineTask{
Name: "foo",
OnError: PipelineTaskContinue,
TaskRef: &TaskRef{Name: "foo"},
},
wc: cfgtesting.EnableAlphaAPIFields,
}, {
name: "valid PipelineTask with onError:stopAndFail",
p: PipelineTask{
Name: "foo",
OnError: PipelineTaskStopAndFail,
TaskRef: &TaskRef{Name: "foo"},
},
wc: cfgtesting.EnableAlphaAPIFields,
}, {
name: "invalid OnError value",
p: PipelineTask{
Name: "foo",
OnError: "invalid-val",
TaskRef: &TaskRef{Name: "foo"},
},
expectedError: apis.ErrInvalidValue("invalid-val", "OnError", "PipelineTask onError must be either \"continue\" or \"stopAndFail\""),
wc: cfgtesting.EnableAlphaAPIFields,
}, {
name: "OnError:stopAndFail and retries coexists - success",
p: PipelineTask{
Name: "foo",
OnError: PipelineTaskStopAndFail,
Retries: 1,
TaskRef: &TaskRef{Name: "foo"},
},
wc: cfgtesting.EnableAlphaAPIFields,
}, {
name: "OnError:continue and retries coexists - failure",
p: PipelineTask{
Name: "foo",
OnError: PipelineTaskContinue,
Retries: 1,
TaskRef: &TaskRef{Name: "foo"},
},
expectedError: apis.ErrMultipleOneOf("OnError", "Retries"),
wc: cfgtesting.EnableAlphaAPIFields,
}, {
name: "setting OnError in beta API version - failure",
p: PipelineTask{
Name: "foo",
OnError: PipelineTaskContinue,
TaskRef: &TaskRef{Name: "foo"},
},
expectedError: apis.ErrGeneric("OnError requires \"enable-api-fields\" feature gate to be \"alpha\" but it is \"beta\""),
wc: cfgtesting.EnableBetaAPIFields,
}, {
name: "setting OnError in stable API version - failure",
p: PipelineTask{
Name: "foo",
OnError: PipelineTaskContinue,
TaskRef: &TaskRef{Name: "foo"},
},
expectedError: apis.ErrGeneric("OnError requires \"enable-api-fields\" feature gate to be \"alpha\" but it is \"stable\""),
wc: cfgtesting.EnableStableAPIFields,
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
if tt.wc != nil {
ctx = tt.wc(ctx)
}
err := tt.p.Validate(ctx)
if tt.expectedError == nil {
if err != nil {
t.Error("PipelineTask.Validate() returned error for valid pipeline task")
}
} else {
if err == nil {
t.Error("PipelineTask.Validate() did not return error for invalid pipeline task with OnError")
}
if d := cmp.Diff(tt.expectedError.Error(), err.Error(), cmpopts.IgnoreUnexported(apis.FieldError{})); d != "" {
t.Errorf("PipelineTask.Validate() errors diff %s", diff.PrintWantGot(d))
}
}
})
}
}

func TestPipelineTask_ValidateRefOrSpec(t *testing.T) {
tests := []struct {
name string
Expand Down
11 changes: 11 additions & 0 deletions pkg/apis/pipeline/v1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,17 @@ func (pt PipelineTask) Validate(ctx context.Context) (errs *apis.FieldError) {
NamespacedTaskKind: true,
ClusterTaskRefKind: true,
}

if pt.OnError != "" {
errs = errs.Also(version.ValidateEnabledAPIFields(ctx, "OnError", config.AlphaAPIFields))
if pt.OnError != PipelineTaskContinue && pt.OnError != PipelineTaskStopAndFail {
errs = errs.Also(apis.ErrInvalidValue(pt.OnError, "OnError", "PipelineTask onError must be either \"continue\" or \"stopAndFail\""))
}
if pt.OnError == PipelineTaskContinue && pt.Retries > 0 {
errs = errs.Also(apis.ErrMultipleOneOf("OnError", "Retries"))
}
}

// Pipeline task having taskRef/taskSpec with APIVersion is classified as custom task
switch {
case pt.TaskRef != nil && !taskKinds[pt.TaskRef.Kind]:
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,10 @@
"description": "Name is the name of this task within the context of a Pipeline. Name is used as a coordinate with the `from` and `runAfter` fields to establish the execution order of tasks relative to one another.",
"type": "string"
},
"onError": {
"description": "OnError defines the exiting behavior of a PipelineRun on error can be set to [ continue | stopAndFail ] Note: OnError is in preview mode and not yet supported",
"type": "string"
},
"params": {
"description": "Parameters declares parameters passed to this task.",
"type": "array",
Expand Down
7 changes: 7 additions & 0 deletions pkg/apis/pipeline/v1beta1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8b55c08

Please sign in to comment.