Skip to content

Commit

Permalink
feat: introduce when expressions to steps
Browse files Browse the repository at this point in the history
  • Loading branch information
ericzzzzzzz authored and tekton-robot committed Jul 3, 2024
1 parent ba7cb10 commit 5625a6a
Show file tree
Hide file tree
Showing 33 changed files with 1,910 additions and 344 deletions.
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

0 comments on commit 5625a6a

Please sign in to comment.