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

feat: introduce when expressions to steps #7746

Merged
merged 1 commit into from
Jul 3, 2024
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
9 changes: 9 additions & 0 deletions cmd/entrypoint/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/tektoncd/pipeline/cmd/entrypoint/subcommands"
featureFlags "github.com/tektoncd/pipeline/pkg/apis/config"
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
"github.com/tektoncd/pipeline/pkg/credentials"
"github.com/tektoncd/pipeline/pkg/credentials/dockercreds"
"github.com/tektoncd/pipeline/pkg/credentials/gitcreds"
Expand All @@ -50,6 +51,7 @@ var (
terminationPath = flag.String("termination_path", "/tekton/termination", "If specified, file to write upon termination")
results = flag.String("results", "", "If specified, list of file names that might contain task results")
stepResults = flag.String("step_results", "", "step results if specified")
whenExpressions = flag.String("when_expressions", "", "when expressions if specified")
timeout = flag.Duration("timeout", time.Duration(0), "If specified, sets timeout for step")
stdoutPath = flag.String("stdout_path", "", "If specified, file to copy stdout to")
stderrPath = flag.String("stderr_path", "", "If specified, file to copy stderr to")
Expand Down Expand Up @@ -138,6 +140,12 @@ func main() {
log.Fatal(err)
}
}
var when v1.StepWhenExpressions
if len(*whenExpressions) > 0 {
if err := json.Unmarshal([]byte(*whenExpressions), &when); err != nil {
log.Fatal(err)
}
}

var spireWorkloadAPI spire.EntrypointerAPIClient
if enableSpire != nil && *enableSpire && socketPath != nil && *socketPath != "" {
Expand All @@ -162,6 +170,7 @@ func main() {
Results: strings.Split(*results, ","),
StepResults: strings.Split(*stepResults, ","),
Timeout: timeout,
StepWhenExpressions: when,
BreakpointOnFailure: *breakpointOnFailure,
OnError: *onError,
StepMetadataDir: *stepMetadataDir,
Expand Down
30 changes: 28 additions & 2 deletions docs/pipeline-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4647,6 +4647,20 @@ It cannot be used when referencing StepActions using [v1.Step.Ref].
The Results declared by the StepActions will be stored here instead.</p>
</td>
</tr>
<tr>
<td>
<code>when</code><br/>
<em>
<a href="#tekton.dev/v1.WhenExpressions">
WhenExpressions
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>When is a list of when expressions that need to be true for the task to run</p>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1.StepOutputConfig">StepOutputConfig
Expand Down Expand Up @@ -6251,7 +6265,7 @@ More info about CEL syntax: <a href="https://github.com/google/cel-spec/blob/mas
<h3 id="tekton.dev/v1.WhenExpressions">WhenExpressions
(<code>[]github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.WhenExpression</code> alias)</h3>
<p>
(<em>Appears on:</em><a href="#tekton.dev/v1.PipelineTask">PipelineTask</a>)
(<em>Appears on:</em><a href="#tekton.dev/v1.PipelineTask">PipelineTask</a>, <a href="#tekton.dev/v1.Step">Step</a>)
</p>
<div>
<p>WhenExpressions are used to specify whether a Task should be executed or skipped
Expand Down Expand Up @@ -14069,6 +14083,18 @@ It cannot be used when referencing StepActions using [v1beta1.Step.Ref].
The Results declared by the StepActions will be stored here instead.</p>
</td>
</tr>
<tr>
<td>
<code>when</code><br/>
<em>
<a href="#tekton.dev/v1beta1.WhenExpressions">
WhenExpressions
</a>
</em>
</td>
<td>
</td>
</tr>
</tbody>
</table>
<h3 id="tekton.dev/v1beta1.StepActionObject">StepActionObject
Expand Down Expand Up @@ -16190,7 +16216,7 @@ More info about CEL syntax: <a href="https://github.com/google/cel-spec/blob/mas
<h3 id="tekton.dev/v1beta1.WhenExpressions">WhenExpressions
(<code>[]github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.WhenExpression</code> alias)</h3>
<p>
(<em>Appears on:</em><a href="#tekton.dev/v1beta1.PipelineTask">PipelineTask</a>)
(<em>Appears on:</em><a href="#tekton.dev/v1beta1.PipelineTask">PipelineTask</a>, <a href="#tekton.dev/v1beta1.Step">Step</a>)
</p>
<div>
<p>WhenExpressions are used to specify whether a Task should be executed or skipped
Expand Down
102 changes: 102 additions & 0 deletions docs/stepactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ weight: 201
- [Declaring VolumeMounts](#declaring-volumemounts)
- [Referencing a StepAction](#referencing-a-stepaction)
- [Specifying Remote StepActions](#specifying-remote-stepactions)
- [Controlling Step Execution with when Expressions](#controlling-step-execution-with-when-expressions)
- [Known Limitations](#known-limitations)
- [Cannot pass Step Results between Steps](#cannot-pass-step-results-between-steps)

Expand Down Expand Up @@ -521,3 +522,104 @@ spec:
```

The default resolver type can be configured by the `default-resolver-type` field in the `config-defaults` ConfigMap (`alpha` feature). See [additional-configs.md](./additional-configs.md) for details.

### Controlling Step Execution with when Expressions

You can define `when` in a `step` to control its execution.

The components of `when` expressions are `input`, `operator`, `values`, `cel`:

| Component | Description | Syntax |
|------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `input` | Input for the `when` expression, defaults to an empty string if not provided. | * Static values e.g. `"ubuntu"`<br/> * Variables (parameters or results) e.g. `"$(params.image)"` or `"$(tasks.task1.results.image)"` or `"$(tasks.task1.results.array-results[1])"` |
| `operator` | `operator` represents an `input`'s relationship to a set of `values`, a valid `operator` must be provided. | `in` or `notin` |
| `values` | An array of string values, the `values` array must be provided and has to be non-empty. | * An array param e.g. `["$(params.images[*])"]`<br/> * An array result of a task `["$(tasks.task1.results.array-results[*])"]`<br/> * An array result of a step`["(steps.step1.results.array-results[*])"]`<br/>* `values` can contain static values e.g. `"ubuntu"`<br/> * `values` can contain variables (parameters or results) or a Workspaces's `bound` state e.g. `["$(params.image)"]` or `["$(steps.step1.results.image)"]` or `["$(tasks.task1.results.array-results[1])"]` or `["$(steps.step1.results.array-results[1])"]` |
| `cel` | The Common Expression Language (CEL) implements common semantics for expression evaluation, enabling different applications to more easily interoperate. This is an `alpha` feature, `enable-cel-in-whenexpression` needs to be set to true to use this feature. | [cel-syntax](https://github.com/google/cel-spec/blob/master/doc/langdef.md#syntax)

The below example shows how to use when expressions to control step executions:

```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc-2
spec:
resources:
requests:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
---
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
generateName: step-when-example
spec:
workspaces:
- name: custom
persistentVolumeClaim:
claimName: my-pvc-2
taskSpec:
description: |
A simple task that shows how to use when determine if a step should be executed
steps:
- name: should-execute
image: bash:latest
script: |
#!/usr/bin/env bash
echo "executed..."
when:
- input: "$(workspaces.custom.bound)"
operator: in
values: [ "true" ]
- name: should-skip
image: bash:latest
script: |
#!/usr/bin/env bash
echo skipskipskip
when:
- input: "$(workspaces.custom2.bound)"
operator: in
values: [ "true" ]
- name: should-continue
image: bash:latest
script: |
#!/usr/bin/env bash
echo blabalbaba
- name: produce-step
image: alpine
results:
- name: result2
type: string
script: |
echo -n "foo" | tee $(step.results.result2.path)
- name: run-based-on-step-results
image: alpine
script: |
echo "wooooooo"
when:
- input: "$(steps.produce-step.results.result2)"
operator: in
values: [ "bar" ]
workspaces:
- name: custom
```

The StepState for a skipped step looks like something similar to the below:
```yaml
{
"container": "step-run-based-on-step-results",
"imageID": "docker.io/library/alpine@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b",
"name": "run-based-on-step-results",
"terminated": {
"containerID": "containerd://bf81162e79cf66a2bbc03e3654942d3464db06ff368c0be263a8a70f363a899b",
"exitCode": 0,
"finishedAt": "2024-03-26T03:57:47Z",
"reason": "Completed",
"startedAt": "2024-03-26T03:57:47Z"
},
"terminationReason": "Skipped"
}
```
Where `terminated.exitCode` is `0` and `terminationReason` is `Skipped` to indicate the Step exited successfully and was skipped.
72 changes: 72 additions & 0 deletions examples/v1/taskruns/alpha/stepaction-when.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc-2
spec:
resources:
requests:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
---
apiVersion: tekton.dev/v1alpha1
kind: StepAction
metadata:
name: step-action-when
spec:
image: alpine
script: |
echo "I am a Step Action!!!"
---
apiVersion: tekton.dev/v1
kind: TaskRun
metadata:
generateName: when-in-steps-
spec:
workspaces:
- name: custom
persistentVolumeClaim:
claimName: my-pvc-2
taskSpec:
description: |
A simple task to demotrate how when expressions work in steps.
steps:
- name: should-execute
ref:
name: "step-action-when"
when:
- input: "$(workspaces.custom.bound)"
operator: in
values: ["true"]
- name: should-skip
image: bash:latest
script: |
#!/usr/bin/env bash
echo skipskipskip
when:
- input: "$(workspaces.custom2.bound)"
operator: in
values: ["true"]
- name: should-continue
image: bash:latest
script: |
#!/usr/bin/env bash
echo blabalbaba
- name: produce-step
image: alpine
results:
- name: result2
type: string
script: |
echo -n "foo" | tee $(step.results.result2.path)
- name: run-based-on-step-results
image: alpine
script: |
echo "wooooooo"
when:
- input: "$(steps.produce-step.results.result2)"
operator: in
values: ["bar"]
workspaces:
- name: custom
4 changes: 4 additions & 0 deletions pkg/apis/pipeline/v1/container_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ type Step struct {
// +optional
// +listType=atomic
Results []StepResult `json:"results,omitempty"`

// When is a list of when expressions that need to be true for the task to run
// +optional
When StepWhenExpressions `json:"when,omitempty"`
}

// Ref can be used to refer to a specific instance of a StepAction.
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/pipeline/v1/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func MergeStepsWithStepTemplate(template *StepTemplate, steps []Step) ([]Step, e
amendConflictingContainerFields(&merged, s)

// Pass through original step Script, for later conversion.
newStep := Step{Script: s.Script, OnError: s.OnError, Timeout: s.Timeout, StdoutConfig: s.StdoutConfig, StderrConfig: s.StderrConfig, Results: s.Results, Params: s.Params, Ref: s.Ref}
newStep := Step{Script: s.Script, OnError: s.OnError, Timeout: s.Timeout, StdoutConfig: s.StdoutConfig, StderrConfig: s.StderrConfig, Results: s.Results, Params: s.Params, Ref: s.Ref, When: s.When}
newStep.SetContainerFields(merged)
steps[i] = newStep
}
Expand Down
12 changes: 12 additions & 0 deletions pkg/apis/pipeline/v1/merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/tektoncd/pipeline/test/diff"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/selection"
)

func TestMergeStepsWithStepTemplate(t *testing.T) {
Expand Down Expand Up @@ -257,6 +258,17 @@ func TestMergeStepsWithStepTemplate(t *testing.T) {
},
}},
}},
}, {
name: "when",
template: nil,
steps: []v1.Step{{
Image: "some-image",
When: v1.StepWhenExpressions{{Input: "foo", Operator: selection.In, Values: []string{"foo", "bar"}}},
}},
expected: []v1.Step{{
Image: "some-image",
When: v1.StepWhenExpressions{{Input: "foo", Operator: selection.In, Values: []string{"foo", "bar"}}},
}},
}} {
t.Run(tc.name, func(t *testing.T) {
result, err := v1.MergeStepsWithStepTemplate(tc.template, tc.steps)
Expand Down
16 changes: 15 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.

8 changes: 8 additions & 0 deletions pkg/apis/pipeline/v1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,14 @@
"x-kubernetes-patch-merge-key": "mountPath",
"x-kubernetes-patch-strategy": "merge"
},
"when": {
"description": "When is a list of when expressions that need to be true for the task to run",
"type": "array",
"items": {
"default": {},
"$ref": "#/definitions/v1.WhenExpression"
}
},
"workingDir": {
"description": "Step's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.",
"type": "string"
Expand Down
Loading