Skip to content

Commit

Permalink
[TEP-0048] Add support for default results
Browse files Browse the repository at this point in the history
Today, a Task can declare a Result but not produce it and it's execution
will still be successful whereas if a subsequent Task in a Pipeline
attempts to use that Result it will fail. This behaviour can be
confusing to the users as the Task which didn't produced the Result
passed and the follow-up Task fails. Moreover, the user of the Task will
loose faith in it as it failed to produce what it's claiming.

This patch introduces the default results, ie, Task author can now add a
default value to the results so that if their Task doesn't produce any
result then the default value can be emitted. Now, with this change, if
the Task author doesn't specifies any default value for the result and
one of the step within that Task also doesn't produces it then
the Task execution in whole will be marked as failed as it failed to
prove it's contract.
  • Loading branch information
vinamra28 committed Nov 5, 2022
1 parent d310355 commit 993e4ed
Show file tree
Hide file tree
Showing 27 changed files with 674 additions and 326 deletions.
42 changes: 40 additions & 2 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1669,7 +1669,7 @@ Used to distinguish between a single string and an array of strings.</p>
<h3 id="tekton.dev/v1.ParamValue">ParamValue
</h3>
<p>
(<em>Appears on:</em><a href="#tekton.dev/v1.Param">Param</a>, <a href="#tekton.dev/v1.ParamSpec">ParamSpec</a>, <a href="#tekton.dev/v1.PipelineResult">PipelineResult</a>, <a href="#tekton.dev/v1.PipelineRunResult">PipelineRunResult</a>, <a href="#tekton.dev/v1.TaskRunResult">TaskRunResult</a>)
(<em>Appears on:</em><a href="#tekton.dev/v1.Param">Param</a>, <a href="#tekton.dev/v1.ParamSpec">ParamSpec</a>, <a href="#tekton.dev/v1.PipelineResult">PipelineResult</a>, <a href="#tekton.dev/v1.PipelineRunResult">PipelineRunResult</a>, <a href="#tekton.dev/v1.TaskResult">TaskResult</a>, <a href="#tekton.dev/v1.TaskRunResult">TaskRunResult</a>)
</p>
<div>
<p>ResultValue is a type alias of ParamValue</p>
Expand Down Expand Up @@ -4476,6 +4476,22 @@ string
<p>Description is a human-readable description of the result</p>
</td>
</tr>
<tr>
<td>
<code>default</code><br/>
<em>
<a href="#tekton.dev/v1.ParamValue">
ParamValue
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>This is an alpha field. You must set the &ldquo;enable-api-fields&rdquo; feature flag to &ldquo;alpha&rdquo;
for this field to be supported.</p>
<p>Default is the value a result produces if no result is produced by Task</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1.TaskRunDebug">TaskRunDebug
Expand Down Expand Up @@ -8761,7 +8777,7 @@ Used to distinguish between a single string and an array of strings.</p>
<h3 id="tekton.dev/v1beta1.ParamValue">ParamValue
</h3>
<p>
(<em>Appears on:</em><a href="#tekton.dev/v1beta1.Param">Param</a>, <a href="#tekton.dev/v1beta1.ParamSpec">ParamSpec</a>, <a href="#tekton.dev/v1beta1.PipelineResult">PipelineResult</a>, <a href="#tekton.dev/v1beta1.PipelineRunResult">PipelineRunResult</a>, <a href="#tekton.dev/v1beta1.TaskRunResult">TaskRunResult</a>)
(<em>Appears on:</em><a href="#tekton.dev/v1beta1.Param">Param</a>, <a href="#tekton.dev/v1beta1.ParamSpec">ParamSpec</a>, <a href="#tekton.dev/v1beta1.PipelineResult">PipelineResult</a>, <a href="#tekton.dev/v1beta1.PipelineRunResult">PipelineRunResult</a>, <a href="#tekton.dev/v1beta1.TaskResult">TaskResult</a>, <a href="#tekton.dev/v1beta1.TaskRunResult">TaskRunResult</a>)
</p>
<div>
<p>ResultValue is a type alias of ParamValue</p>
Expand Down Expand Up @@ -12417,8 +12433,30 @@ string
<p>Description is a human-readable description of the result</p>
</td>
</tr>
<tr>
<td>
<code>default</code><br/>
<em>
<a href="#tekton.dev/v1beta1.ParamValue">
ParamValue
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>This is an alpha field. You must set the &ldquo;enable-api-fields&rdquo; feature flag to &ldquo;alpha&rdquo;
for this field to be supported.</p>
<p>Default is the value a result produces if no result is produced by Task</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1beta1.TaskRunConditionType">TaskRunConditionType
(<code>string</code> alias)</h3>
<div>
<p>TaskRunConditionType is an enum used to store TaskRun custom conditions
conditions such as one used in spire results verification</p>
</div>
<h3 id="tekton.dev/v1beta1.TaskRunDebug">TaskRunDebug
</h3>
<p>
Expand Down
42 changes: 42 additions & 0 deletions docs/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,48 @@ available size will less than 4096 bytes.
As a general rule-of-thumb, if a result needs to be larger than a kilobyte, you should likely use a
[`Workspace`](#specifying-workspaces) to store and pass it between `Tasks` within a `Pipeline`.

#### Default value

> **Note**: This is an alpha gated feature.

Results declarations (within Tasks and Pipelines) can include default values which will be used if the Result is
not produced, for example to specify defaults for both string params and array params
([full example](../examples/v1beta1/taskruns/alpha/default-result.yaml)) :

```yaml
apiVersion: tekton.dev/v1beta1
kind: Task
metadata:
name: task-with-default-result
spec:
results:
- name: branch
default: main
```

**Note**: If a Task doesn't specify any default value for a particular `Result`
and also fails to emit that `Result`, in that case, the following `Task` is going
to be marked as `Failed` as specified in [TEP-0048](https://github.com/tektoncd/community/blob/main/teps/0048-task-results-without-results.md). Below is the example in which the TaskRun
is going to fail as one of the Result wasn't produced.

```yaml
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
generateName: test-tr-
spec:
taskSpec:
results:
- name: result1
description: will be produced
- name: result2
description: will not be produced
steps:
- name: failing-step
image: busybox
script: "echo -n 123 | tee $(results.result1.path)"
```

### Specifying `Volumes`

Specifies one or more [`Volumes`](https://kubernetes.io/docs/concepts/storage/volumes/) that the `Steps` in your
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: pipelinerun-emit-default-results
spec:
pipelineSpec:
tasks:
- name: task1
taskSpec:
results:
- name: array-results
type: array
description: The array results
default:
- "1"
- "2"
- "3"
steps:
- name: write-array
image: bash:latest
script: |
#!/usr/bin/env bash
echo -n "this script won't emit array result"
- name: task2
taskSpec:
results:
- name: object-results
type: object
description: The object results
properties:
foo: {type: string}
hello: {type: string}
default:
foo: bar
hello: world
steps:
- name: write-array
image: bash:latest
script: |
#!/usr/bin/env bash
echo -n "this script won't emit object result"
results:
- name: array-results
type: array
description: whole array
value: $(tasks.task1.results.array-results[*])
- name: array-results-from-array-indexing-and-object-elements
type: array
description: whole array
value:
[
"$(tasks.task1.results.array-results[0])",
"$(tasks.task2.results.object-results.foo)",
]
- name: array-indexing-results
type: string
description: array element
value: $(tasks.task1.results.array-results[1])
- name: object-results
type: object
description: whole object
value: $(tasks.task2.results.object-results[*])
- name: object-results-from-array-indexing-and-object-elements
type: object
description: whole object
value:
key1: $(tasks.task1.results.array-results[1])
key2: $(tasks.task2.results.object-results.hello)
- name: object-element
type: string
description: object element
value: $(tasks.task2.results.object-results.foo)
33 changes: 33 additions & 0 deletions examples/v1beta1/taskruns/alpha/default-result.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
apiVersion: tekton.dev/v1beta1
kind: TaskRun
metadata:
generateName: default-result-run-
spec:
taskSpec:
results:
- name: result1
description: will be produced
- name: string-result
description: will not be produced by step but has default value
default: "string value"
- name: array-result
description: default array result will be produced
default:
- "foo"
- "bar"
- name: eight-results
type: object
properties:
IMAGE_URL: {type: string}
IMAGE_DIGEST: {type: string}
default:
IMAGE_URL: "default.com"
IMAGE_DIGEST: "defaultsha"
steps:
- name: failing-step
onError: continue
image: busybox
script: |
echo -n 123 | tee $(results.result1.path)
exit 1
echo -n 456 | tee $(results.string-result.path)
8 changes: 7 additions & 1 deletion pkg/apis/pipeline/v1/openapi_generated.go

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

7 changes: 7 additions & 0 deletions pkg/apis/pipeline/v1/result_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ type TaskResult struct {
// Description is a human-readable description of the result
// +optional
Description string `json:"description,omitempty"`

// This is an alpha field. You must set the "enable-api-fields" feature flag to "alpha"
// for this field to be supported.
//
// Default is the value a result produces if no result is produced by Task
// +optional
Default *ResultValue `json:"default,omitempty"`
}

// TaskRunResult used to describe the results of a task
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 @@ -1615,6 +1615,10 @@
"name"
],
"properties": {
"default": {
"description": "This is an alpha field. You must set the \"enable-api-fields\" feature flag to \"alpha\" for this field to be supported.\n\nDefault is the value a result produces if no result is produced by Task",
"$ref": "#/definitions/v1.ParamValue"
},
"description": {
"description": "Description is a human-readable description of the result",
"type": "string"
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/pipeline/v1/zz_generated.deepcopy.go

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

8 changes: 7 additions & 1 deletion pkg/apis/pipeline/v1beta1/openapi_generated.go

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

31 changes: 31 additions & 0 deletions pkg/apis/pipeline/v1beta1/result_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package v1beta1

import (
"context"
"fmt"

v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
)
Expand All @@ -15,6 +16,22 @@ func (r TaskResult) convertTo(ctx context.Context, sink *v1.TaskResult) {
properties[k] = v1.PropertySpec{Type: v1.ParamType(v.Type)}
}
sink.Properties = properties

fmt.Println(r.Default)

if r.Default != nil {
sink.Default = &v1.ParamValue{
Type: v1.ParamType(r.Default.Type),
}
switch r.Default.Type {
case ParamTypeString:
sink.Default.StringVal = r.Default.StringVal
case ParamTypeArray:
sink.Default.ArrayVal = r.Default.ArrayVal
case ParamTypeObject:
sink.Default.ObjectVal = r.Default.ObjectVal
}
}
}

func (r *TaskResult) convertFrom(ctx context.Context, source v1.TaskResult) {
Expand All @@ -26,4 +43,18 @@ func (r *TaskResult) convertFrom(ctx context.Context, source v1.TaskResult) {
properties[k] = PropertySpec{Type: ParamType(v.Type)}
}
r.Properties = properties
if source.Default != nil {
r.Default = &ParamValue{
Type: ParamType(source.Default.Type),
}

switch source.Default.Type {
case v1.ParamTypeString:
r.Default.StringVal = source.Default.StringVal
case v1.ParamTypeArray:
r.Default.ArrayVal = source.Default.ArrayVal
case v1.ParamTypeObject:
r.Default.ObjectVal = source.Default.ObjectVal
}
}
}
7 changes: 7 additions & 0 deletions pkg/apis/pipeline/v1beta1/result_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ type TaskResult struct {
// Description is a human-readable description of the result
// +optional
Description string `json:"description,omitempty"`

// This is an alpha field. You must set the "enable-api-fields" feature flag to "alpha"
// for this field to be supported.
//
// Default is the value a result produces if no result is produced by Task
// +optional
Default *ResultValue `json:"default,omitempty"`
}

// TaskRunResult used to describe the results of a task
Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/pipeline/v1beta1/result_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func (tr TaskResult) Validate(ctx context.Context) (errs *apis.FieldError) {
if !resultNameFormatRegex.MatchString(tr.Name) {
return apis.ErrInvalidKeyName(tr.Name, "name", fmt.Sprintf("Name must consist of alphanumeric characters, '-', '_', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my-name', or 'my_name', regex used for validation is '%s')", ResultNameFormat))
}
// Default results in alpha feature
if tr.Default != nil {
errs = errs.Also(version.ValidateEnabledAPIFields(ctx, "default results", config.AlphaAPIFields))
return errs
}
// Array and Object is alpha feature
if tr.Type == ResultsTypeArray || tr.Type == ResultsTypeObject {
errs := validateObjectResult(tr)
Expand Down
Loading

0 comments on commit 993e4ed

Please sign in to comment.