Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TEP-0076]Pipeline results support array #4965

Merged
merged 1 commit into from
Jul 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 24 additions & 22 deletions docs/pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ A `Pipeline` definition supports the following fields:
a `Task` requires.
- [`from`](#using-the-from-field) - Indicates the data for a [`PipelineResource`](resources.md)
originates from the output of a previous `Task`.
- [`runAfter`](#using-the-runafter-field) - Indicates that a `Task` should execute after one or more other
- [`runAfter`](#using-the-runafter-field) - Indicates that a `Task` should execute after one or more other
`Tasks` without output linking.
- [`retries`](#using-the-retries-field) - Specifies the number of times to retry the execution of a `Task` after
a failure. Does not apply to execution cancellations.
Expand All @@ -109,7 +109,7 @@ A `Pipeline` definition supports the following fields:
- [`results`](#emitting-results-from-a-pipeline) - Specifies the location to which the `Pipeline` emits its execution
results.
- [`description`](#adding-a-description) - Holds an informative description of the `Pipeline` object.
- [`finally`](#adding-finally-to-the-pipeline) - Specifies one or more `Tasks` to be executed in parallel after
- [`finally`](#adding-finally-to-the-pipeline) - Specifies one or more `Tasks` to be executed in parallel after
all other tasks have completed.
- [`name`](#adding-finally-to-the-pipeline) - the name of this `Task` within the context of this `Pipeline`.
- [`taskRef`](#adding-finally-to-the-pipeline) - a reference to a `Task` definition.
Expand Down Expand Up @@ -174,7 +174,7 @@ spec:
workspace: pipeline-ws1
```

For simplicity you can also map the name of the `Workspace` in `PipelineTask` to match with
For simplicity you can also map the name of the `Workspace` in `PipelineTask` to match with
the `Workspace` from the `Pipeline`.
For example:

Expand All @@ -191,12 +191,12 @@ spec:
taskRef:
name: gen-code # gen-code expects a Workspace named "source"
workspaces:
- name: source # <- mapping workspace name
- name: source # <- mapping workspace name
- name: commit
taskRef:
name: commit # commit expects a Workspace named "source"
workspaces:
- name: source # <- mapping workspace name
- name: source # <- mapping workspace name
runAfter:
- gen-code
```
Expand Down Expand Up @@ -402,7 +402,7 @@ spec:
`"true"` in the `feature-flags` configmap, see [`install.md`](./install.md#customizing-the-pipelines-controller-behavior)**

You may also specify your `Task` reference using a `Tekton Bundle`. A `Tekton Bundle` is an OCI artifact that
contains Tekton resources like `Tasks` which can be referenced within a `taskRef`.
contains Tekton resources like `Tasks` which can be referenced within a `taskRef`.

There is currently a hard limit of 20 objects in a bundle.

Expand Down Expand Up @@ -628,7 +628,7 @@ To guard a `Task` and its dependent Tasks:

##### Cascade `when` expressions to the specific dependent `Tasks`

Pick and choose which specific dependent `Tasks` to guard as well, and cascade the `when` expressions to those `Tasks`.
Pick and choose which specific dependent `Tasks` to guard as well, and cascade the `when` expressions to those `Tasks`.

Taking the use case below, a user who wants to guard `manual-approval` and its dependent `Tasks`:

Expand Down Expand Up @@ -689,12 +689,12 @@ tasks:
value: $(tasks.manual-approval.results.approver)
taskRef:
name: slack-msg
```
```

##### Compose using Pipelines in Pipelines

Compose a set of `Tasks` as a unit of execution using `Pipelines` in `Pipelines`, which allows for guarding a `Task` and
its dependent `Tasks` (as a sub-`Pipeline`) using `when` expressions.
Compose a set of `Tasks` as a unit of execution using `Pipelines` in `Pipelines`, which allows for guarding a `Task` and
its dependent `Tasks` (as a sub-`Pipeline`) using `when` expressions.

**Note:** `Pipelines` in `Pipelines` is an [experimental feature](https://github.com/tektoncd/experimental/tree/main/pipelines-in-pipelines)

Expand Down Expand Up @@ -742,7 +742,7 @@ tasks:
value: $(tasks.manual-approval.results.approver)
taskRef:
name: slack-msg

---
## main pipeline
tasks:
Expand All @@ -765,12 +765,12 @@ tasks:

When `when` expressions evaluate to `False`, the `Task` will be skipped and:
- The ordering-dependent `Tasks` will be executed
- The resource-dependent `Tasks` (and their dependencies) will be skipped because of missing `Results` from the skipped
parent `Task`. When we add support for [default `Results`](https://github.com/tektoncd/community/pull/240), then the
resource-dependent `Tasks` may be executed if the default `Results` from the skipped parent `Task` are specified. In
- The resource-dependent `Tasks` (and their dependencies) will be skipped because of missing `Results` from the skipped
parent `Task`. When we add support for [default `Results`](https://github.com/tektoncd/community/pull/240), then the
resource-dependent `Tasks` may be executed if the default `Results` from the skipped parent `Task` are specified. In
addition, if a resource-dependent `Task` needs a file from a guarded parent `Task` in a shared `Workspace`, make sure
to handle the execution of the child `Task` in case the expected file is missing from the `Workspace` because the
guarded parent `Task` is skipped.
to handle the execution of the child `Task` in case the expected file is missing from the `Workspace` because the
guarded parent `Task` is skipped.

On the other hand, the rest of the `Pipeline` will continue executing.

Expand Down Expand Up @@ -823,12 +823,12 @@ tasks:
name: slack-msg
```

If `manual-approval` is skipped, execution of its dependent `Tasks` (`slack-msg`, `build-image` and `deploy-image`)
If `manual-approval` is skipped, execution of its dependent `Tasks` (`slack-msg`, `build-image` and `deploy-image`)
would be unblocked regardless:
- `build-image` and `deploy-image` should be executed successfully
- `slack-msg` will be skipped because it is missing the `approver` `Result` from `manual-approval`
- dependents of `slack-msg` would have been skipped too if it had any of them
- if `manual-approval` specifies a default `approver` `Result`, such as "None", then `slack-msg` would be executed
- if `manual-approval` specifies a default `approver` `Result`, such as "None", then `slack-msg` would be executed
([supporting default `Results` is in progress](https://github.com/tektoncd/community/pull/240))

### Configuring the failure timeout
Expand Down Expand Up @@ -944,7 +944,7 @@ when:

For an end-to-end example, see [`Task` `Results` in a `PipelineRun`](../examples/v1beta1/pipelineruns/task_results_example.yaml).

Note that `when` expressions are whitespace-sensitive. In particular, when producing results intended for inputs to `when`
Note that `when` expressions are whitespace-sensitive. In particular, when producing results intended for inputs to `when`
expressions that may include newlines at their close (e.g. `cat`, `jq`), you may wish to truncate them.

```yaml
Expand Down Expand Up @@ -985,6 +985,8 @@ results:

For an end-to-end example, see [`Results` in a `PipelineRun`](../examples/v1beta1/pipelineruns/pipelinerun-results.yaml).

Array results is supported as alpha feature, see [`Array Results` in a `PipelineRun`](../examples/v1beta1/pipelineruns/alpha/pipelinerun-array-results.yaml).

A `Pipeline Result` is not emitted if any of the following are true:
- A `PipelineTask` referenced by the `Pipeline Result` failed. The `PipelineRun` will also
have failed.
Expand All @@ -1009,9 +1011,9 @@ without getting stuck in an infinite loop.
This is done using:
- _resource dependencies_:
- [`from`](#using-the-from-field) clauses on the [`PipelineResources`](resources.md) used by each `Task`
- [`results`](#emitting-results-from-a-pipeline) of one `Task` being passed into `params` or `when` expressions of
- [`results`](#emitting-results-from-a-pipeline) of one `Task` being passed into `params` or `when` expressions of
another

- _ordering dependencies_:
- [`runAfter`](#using-the-runafter-field) clauses on the corresponding `Tasks`

Expand Down Expand Up @@ -1197,7 +1199,7 @@ spec:
value: "someURL"
matrix:
- name: slack-channel
value:
value:
- "foo"
- "bar"
```
Expand Down
40 changes: 40 additions & 0 deletions examples/v1beta1/pipelineruns/alpha/pipeline-array-results.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: pipelinerun-array-indexing-results
spec:
pipelineSpec:
tasks:
- name: task1
taskSpec:
results:
- name: array-results
type: array
description: The array results
steps:
- name: write-array
image: bash:latest
script: |
#!/usr/bin/env bash
echo -n "[\"1\",\"2\",\"3\"]" | tee $(results.array-results.path)
- name: task2
taskSpec:
results:
- name: array-results
type: array
description: The array results
steps:
- name: write-array
image: bash:latest
script: |
#!/usr/bin/env bash
echo -n "[\"4\",\"5\",\"6\"]" | tee $(results.array-results.path)
results:
- name: echo-indexing-array-results
type: string
description: array element
value: $(tasks.task1.results.array-results[1])
- name: echo-array-results
type: array
description: whole array
value: $(tasks.task2.results.array-results[*])
21 changes: 15 additions & 6 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.

7 changes: 6 additions & 1 deletion pkg/apis/pipeline/v1beta1/param_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func (arrayOrString *ArrayOrString) applyOrCorrect(stringReplacements map[string

// trim the head "$(" and the tail ")" or "[*])"
// i.e. get "params.name" from "$(params.name)" or "$(params.name[*])"
trimedStringVal := strings.TrimSuffix(strings.TrimSuffix(strings.TrimPrefix(stringVal, "$("), ")"), "[*]")
trimedStringVal := StripStarVarSubExpression(stringVal)

// if the stringVal is a reference to a string param
if _, ok := stringReplacements[trimedStringVal]; ok {
Expand All @@ -250,6 +250,11 @@ func (arrayOrString *ArrayOrString) applyOrCorrect(stringReplacements map[string
}
}

// StripStarVarSubExpression strips "$(target[*])"" to get "target"
func StripStarVarSubExpression(s string) string {
return strings.TrimSuffix(strings.TrimSuffix(strings.TrimPrefix(s, "$("), ")"), "[*]")
}

// NewArrayOrString creates an ArrayOrString of type ParamTypeString or ParamTypeArray, based on
// how many inputs are given (>1 input will create an array, not string).
func NewArrayOrString(value string, values ...string) *ArrayOrString {
Expand Down
7 changes: 6 additions & 1 deletion pkg/apis/pipeline/v1beta1/pipeline_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,17 @@ type PipelineResult struct {
// Name the given name
Name string `json:"name"`

// Type is the user-specified type of the result.
// The possible types are 'string', 'array', and 'object', with 'string' as the default.
// 'array' and 'object' types are alpha features.
Type ResultsType `json:"type,omitempty"`
lbernick marked this conversation as resolved.
Show resolved Hide resolved

// Description is a human-readable description of the result
// +optional
Description string `json:"description"`

// Value the expression used to retrieve the value
Value string `json:"value"`
Value ArrayOrString `json:"value"`
lbernick marked this conversation as resolved.
Show resolved Hide resolved
}

// PipelineTaskMetadata contains the labels or annotations for an EmbeddedTask
Expand Down
12 changes: 6 additions & 6 deletions pkg/apis/pipeline/v1beta1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func TestPipeline_Validate_Success(t *testing.T) {
Results: []PipelineResult{{
Name: "pipeline-result",
Description: "this is my pipeline result",
Value: "$(tasks.my-task.results.my-result)",
Value: *NewArrayOrString("$(tasks.my-task.results.my-result)"),
}},
},
},
Expand Down Expand Up @@ -1143,11 +1143,11 @@ func TestValidatePipelineResults_Success(t *testing.T) {
results := []PipelineResult{{
Name: "my-pipeline-result",
Description: "this is my pipeline result",
Value: "$(tasks.a-task.results.output)",
Value: *NewArrayOrString("$(tasks.a-task.results.output)"),
}, {
Name: "my-pipeline-object-result",
Description: "this is my pipeline result",
Value: "$(tasks.a-task.results.gitrepo.commit)",
Value: *NewArrayOrString("$(tasks.a-task.results.gitrepo.commit)"),
}}
if err := validatePipelineResults(results); err != nil {
t.Errorf("Pipeline.validatePipelineResults() returned error for valid pipeline: %s: %v", desc, err)
Expand All @@ -1164,7 +1164,7 @@ func TestValidatePipelineResults_Failure(t *testing.T) {
results: []PipelineResult{{
Name: "my-pipeline-result",
Description: "this is my pipeline result",
Value: "$(tasks.a-task.results.output.key1.extra)",
Value: *NewArrayOrString("$(tasks.a-task.results.output.key1.extra)"),
}},
expectedError: apis.FieldError{
Message: `invalid value: expected all of the expressions [tasks.a-task.results.output.key1.extra] to be result expressions but only [] were`,
Expand All @@ -1175,7 +1175,7 @@ func TestValidatePipelineResults_Failure(t *testing.T) {
results: []PipelineResult{{
Name: "my-pipeline-result",
Description: "this is my pipeline result",
Value: "foo.bar",
Value: *NewArrayOrString("foo.bar"),
}},
expectedError: apis.FieldError{
Message: `invalid value: expected pipeline results to be task result expressions but no expressions were found`,
Expand All @@ -1186,7 +1186,7 @@ func TestValidatePipelineResults_Failure(t *testing.T) {
results: []PipelineResult{{
Name: "my-pipeline-result",
Description: "this is my pipeline result",
Value: "$(foo.bar)",
Value: *NewArrayOrString("$(foo.bar)"),
}},
expectedError: apis.FieldError{
Message: `invalid value: expected pipeline results to be task result expressions but an invalid expressions was found`,
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/pipeline/v1beta1/pipelinerun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ type PipelineRunResult struct {
Name string `json:"name"`

// Value is the result returned from the execution of this PipelineRun
Value string `json:"value"`
Value ArrayOrString `json:"value"`
}

// PipelineRunTaskRunStatus contains the name of the PipelineTask for this TaskRun and the TaskRun's Status
Expand Down
Loading