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 2, 2022
1 parent 7189162 commit af5afd7
Show file tree
Hide file tree
Showing 26 changed files with 616 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 @@ -8785,7 +8801,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 @@ -12441,8 +12457,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
40 changes: 40 additions & 0 deletions docs/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,46 @@ 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

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.

18 changes: 18 additions & 0 deletions pkg/apis/pipeline/v1beta1/result_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,22 @@ 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),
}

if source.Default.StringVal != "" {
r.Default.StringVal = source.Default.StringVal
}

if len(source.Default.ArrayVal) != 0 {
r.Default.ArrayVal = source.Default.ArrayVal
}

if len(source.Default.ObjectVal) != 0 {
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
21 changes: 21 additions & 0 deletions pkg/apis/pipeline/v1beta1/result_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ func TestResultsValidate(t *testing.T) {
Properties: map[string]v1beta1.PropertySpec{"hello": {Type: v1beta1.ParamTypeString}},
},
apiFields: "alpha",
}, {
name: "valid result with default value",
Result: v1beta1.TaskResult{
Name: "MY-RESULT",
Type: v1beta1.ResultsTypeString,
Description: "my great result",
Default: &v1beta1.ParamValue{StringVal: "string value"},
},
apiFields: "alpha",
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -161,6 +170,18 @@ func TestResultsValidateError(t *testing.T) {
Message: "missing field(s)",
Paths: []string{"MY-RESULT.properties"},
},
}, {
name: "valid result with default value",
Result: v1beta1.TaskResult{
Name: "MY-RESULT",
Type: v1beta1.ResultsTypeString,
Description: "my great result",
Default: &v1beta1.ParamValue{StringVal: "string value"},
},
apiFields: "stable",
expectedError: apis.FieldError{
Message: "default results requires \"enable-api-fields\" feature gate to be \"alpha\" but it is \"stable\"",
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1beta1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2572,6 +2572,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/v1beta1.ParamValue"
},
"description": {
"description": "Description is a human-readable description of the result",
"type": "string"
Expand Down
Loading

0 comments on commit af5afd7

Please sign in to comment.