From b395663c9d8bf51e34c8b1ce5907b6e3e8baef68 Mon Sep 17 00:00:00 2001
From: Chitrang Patel
Date: Fri, 10 Nov 2023 16:18:13 -0500
Subject: [PATCH] TEP-0142: Surface step results via termination message
This PR surfaces step results (i.e results written to $(step.results..path)) via termination messages. A followup PR will handle surfacing the results via sidecar logs.
---
cmd/entrypoint/main.go | 2 +
docs/pipeline-api.md | 32 ++-
docs/stepactions.md | 161 +++++++++--
.../v1/taskruns/no-ci/stepaction-results.yaml | 24 ++
hack/ignored-openapi-violations.list | 2 +
pkg/apis/pipeline/v1/openapi_generated.go | 17 +-
pkg/apis/pipeline/v1/result_types.go | 3 +
pkg/apis/pipeline/v1/swagger.json | 9 +-
pkg/apis/pipeline/v1/taskrun_types.go | 7 +-
pkg/apis/pipeline/v1/zz_generated.deepcopy.go | 7 +
.../pipeline/v1beta1/openapi_generated.go | 17 +-
pkg/apis/pipeline/v1beta1/result_types.go | 3 +
pkg/apis/pipeline/v1beta1/swagger.json | 9 +-
.../pipeline/v1beta1/taskrun_conversion.go | 12 +
.../v1beta1/taskrun_conversion_test.go | 23 ++
pkg/apis/pipeline/v1beta1/taskrun_types.go | 7 +-
.../pipeline/v1beta1/zz_generated.deepcopy.go | 7 +
pkg/entrypoint/entrypointer.go | 27 +-
pkg/entrypoint/entrypointer_test.go | 41 ++-
pkg/pod/entrypoint.go | 28 +-
pkg/pod/entrypoint_test.go | 100 ++++++-
pkg/pod/pod.go | 4 +-
pkg/pod/status.go | 89 +++++-
pkg/pod/status_test.go | 257 +++++++++++++++++
pkg/reconciler/taskrun/resources/apply.go | 35 ++-
.../taskrun/resources/apply_test.go | 46 +++-
pkg/reconciler/taskrun/resources/taskspec.go | 3 +
.../taskrun/resources/taskspec_test.go | 35 +++
pkg/reconciler/taskrun/taskrun.go | 2 +-
pkg/reconciler/taskrun/taskrun_test.go | 33 ++-
pkg/result/result.go | 4 +
pkg/result/result_test.go | 4 +
test/clients.go | 2 +
test/stepaction_results_test.go | 258 ++++++++++++++++++
34 files changed, 1231 insertions(+), 79 deletions(-)
create mode 100644 examples/v1/taskruns/no-ci/stepaction-results.yaml
create mode 100644 test/stepaction_results_test.go
diff --git a/cmd/entrypoint/main.go b/cmd/entrypoint/main.go
index 54422d789e5..dead6fa6581 100644
--- a/cmd/entrypoint/main.go
+++ b/cmd/entrypoint/main.go
@@ -49,6 +49,7 @@ var (
postFile = flag.String("post_file", "", "If specified, file to write upon completion")
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")
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")
@@ -159,6 +160,7 @@ func main() {
},
PostWriter: &realPostWriter{},
Results: strings.Split(*results, ","),
+ StepResults: strings.Split(*stepResults, ","),
Timeout: timeout,
BreakpointOnFailure: *breakpointOnFailure,
OnError: *onError,
diff --git a/docs/pipeline-api.md b/docs/pipeline-api.md
index ffd32222dcd..88c0ab2f622 100644
--- a/docs/pipeline-api.md
+++ b/docs/pipeline-api.md
@@ -4625,6 +4625,18 @@ string
|
+
+
+results
+
+
+[]TaskRunResult
+
+
+ |
+
+ |
+
StepTemplate
@@ -5143,10 +5155,10 @@ reasons that emerge from underlying resources are not included here
TaskRunResult
-(Appears on:TaskRunStatusFields)
+(Appears on:StepState, TaskRunStatusFields)
-
TaskRunResult used to describe the results of a task
+
TaskRunStepResult is a type alias of TaskRunResult
StepTemplate
@@ -14367,10 +14391,10 @@ reasons that emerge from underlying resources are not included here
TaskRunResult
-(Appears on:TaskRunStatusFields)
+(Appears on:StepState, TaskRunStatusFields)
-
TaskRunResult used to describe the results of a task
+
TaskRunStepResult is a type alias of TaskRunResult
diff --git a/docs/stepactions.md b/docs/stepactions.md
index 88af514d4c5..65d954ce8c9 100644
--- a/docs/stepactions.md
+++ b/docs/stepactions.md
@@ -8,6 +8,18 @@ weight: 201
# StepActions
- [Overview](#overview)
+- [Configuring a StepAction](#configuring-a-stepaction)
+ - [Declaring Parameters](#declaring-parameters)
+ - [Passing Params to StepAction](#passing-params-to-stepaction)
+ - [Emitting Results](#emitting-results)
+ - [Fetching Emitted Results from StepActions](#fetching-emitted-results-from-stepactions)
+ - [Declaring SecurityContext](#declaring-securitycontext)
+ - [Declaring VolumeMounts](#declaring-volumemounts)
+ - [Referencing a StepAction](#referencing-a-stepaction)
+ - [Specifying Remote StepActions](#specifying-remote-stepactions)
+- [Known Limitations](#known-limitations)
+ - [Cannot pass Step Results between Steps](#cannot-pass-step-results-between-steps)
+ - [Cannot extract Step Results via Sidecar logs](#cannot-extract-step-results-via-sidecar-logs)
## Overview
:warning: This feature is in a preview mode.
@@ -39,7 +51,7 @@ A `StepAction` definition supports the following fields:
- cannot be used at the same time as using `command`.
- `env`
- [`params`](#declaring-params)
- - [`results`](#declaring-results)
+ - [`results`](#emitting-results)
- [`securityContext`](#declaring-securitycontext)
- [`volumeMounts`](#declaring-volumemounts)
@@ -93,7 +105,33 @@ spec:
]
```
-### Declaring Results
+#### Passing Params to StepAction
+
+A `StepAction` may require [params](#(declaring-parameters)). In this case, a `Task` needs to ensure that the `StepAction` has access to all the required `params`.
+When referencing a `StepAction`, a `Step` can also provide it with `params`, just like how a `TaskRun` provides params to the underlying `Task`.
+
+```yaml
+apiVersion: tekton.dev/v1
+kind: Task
+metadata:
+ name: step-action
+spec:
+ TaskSpec:
+ params:
+ - name: param-for-step-action
+ description: "this is a param that the step action needs."
+ steps:
+ - name: action-runner
+ ref:
+ name: step-action
+ params:
+ - name: step-action-param
+ value: $(params.param-for-step-action)
+```
+
+**Note:** If a `Step` declares `params` for an `inlined Step`, it will also lead to a validation error. This is because an `inlined Step` gets it's `params` from the `TaskRun`.
+
+### Emitting Results
A `StepAction` also declares the results that it will emit.
@@ -115,6 +153,85 @@ spec:
date | tee $(results.current-date-human-readable.path)
```
+It is possible that a `StepAction` with `Results` is used multiple times in the same `Task` or multiple `StepActions` in the same `Task` produce `Results` with the same name. Resolving the `Result` names becomes critical otherwise there could be unexpected outcomes. The `Task` needs to be able to resolve these `Result` names clashes by mapping it to a different `Result` name. For this reason, we introduce the capability to store results on a `Step` level.
+
+`StepActions` can also emit `Results` to `$(step.results..path)`.
+
+```yaml
+apiVersion: tekton.dev/v1alpha1
+kind: StepAction
+metadata:
+ name: stepaction-declaring-results
+spec:
+ results:
+ - name: current-date-unix-timestamp
+ description: The current date in unix timestamp format
+ - name: current-date-human-readable
+ description: The current date in human readable format
+ image: bash:latest
+ script: |
+ #!/usr/bin/env bash
+ date +%s | tee $(step.results.current-date-unix-timestamp.path)
+ date | tee $(step.results.current-date-human-readable.path)
+```
+
+`Results` from the above `StepAction` can be [fetched by the `Task`](#fetching-emitted-results-from-step-actions) in another `StepAction` via `$(steps..results.)`.
+
+#### Fetching Emitted Results from StepActions
+
+A `Task` can fetch `Results` produced by the `StepActions` (i.e. only `Results` emitted to `$(step.results..path)`, `NOT` $(results..path)) using variable replacement syntax. We introduce a field to [`Task Results`](./tasks.md#emitting-results) called `Value` whose value can be set to the variable `$(steps..results.)`.
+
+```yaml
+apiVersion: tekton.dev/v1
+kind: Task
+metadata:
+ name: task-fetching-results
+spec:
+ results:
+ - name: git-url
+ description: "url of git repo"
+ value: $(steps.git-clone.results.url)
+ - name: registry-url
+ description: "url of docker registry"
+ value: $(steps.kaniko.results.url)
+ steps:
+ - name: git-clone
+ ref:
+ name: clone-step-action
+ - name: kaniko
+ ref:
+ name: kaniko-step-action
+```
+
+`Results` emitted to `$(step.results..path)` are not automatically available as `TaskRun Results`. The `Task` must explicitly fetch it from the underlying `Step` referencing `StepActions`.
+
+For example, lets assume that in the previous example, the "kaniko" `StepAction` also produced a `Result` named "digest". In that case, the `Task` should also fetch the "digest" from "kaniko" `Step`.
+
+```yaml
+apiVersion: tekton.dev/v1
+kind: Task
+metadata:
+ name: task-fetching-results
+spec:
+ results:
+ - name: git-url
+ description: "url of git repo"
+ value: $(steps.git-clone.results.url)
+ - name: registry-url
+ description: "url of docker registry"
+ value: $(steps.kaniko.results.url)
+ - name: digest
+ description: "digest of the image"
+ value: $(steps.kaniko.results.digest)
+ steps:
+ - name: git-clone
+ ref:
+ name: clone-step-action
+ - name: kaniko
+ ref:
+ name: kaniko-step-action
+```
+
### Declaring SecurityContext
You can declare `securityContext` in a `StepAction`:
@@ -159,7 +276,7 @@ spec:
```
-## Referencing a StepAction
+### Referencing a StepAction
`StepActions` can be referenced from the `Step` using the `ref` field, as follows:
@@ -280,33 +397,7 @@ spec:
onError: continue
```
-### Passing Params to StepAction
-
-A `StepAction` may require [params](#(declaring-parameters)). In this case, a `Task` needs to ensure that the `StepAction` has access to all the required `params`.
-When referencing a `StepAction`, a `Step` can also provide it with `params`, just like how a `TaskRun` provides params to the underlying `Task`.
-
-```yaml
-apiVersion: tekton.dev/v1
-kind: Task
-metadata:
- name: step-action
-spec:
- TaskSpec:
- params:
- - name: param-for-step-action
- description: "this is a param that the step action needs."
- steps:
- - name: action-runner
- ref:
- name: step-action
- params:
- - name: step-action-param
- value: $(params.param-for-step-action)
-```
-
-**Note:** If a `Step` declares `params` for an `inlined Step`, it will also lead to a validation error. This is because an `inlined Step` gets it's `params` from the `TaskRun`.
-
-### Specifying Remote StepActions
+#### Specifying Remote StepActions
A `ref` field may specify a `StepAction` in a remote location such as git.
Support for specific types of remote will depend on the `Resolvers` your
@@ -333,3 +424,13 @@ 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.
+
+## Known Limitations
+
+### Cannot pass Step Results between Steps
+
+It's not currently possible to pass results produced by a `Step` into following `Steps`. We are working on this feature and will be made available soon.
+
+### Cannot extract Step Results via Sidecar logs
+
+Currently, we only support Step Results via `Termination Message` (the default method of result extraction). The ability to extract `Step` results from `sidecar-logs` is not yet available. We are working on enabling this soon.
diff --git a/examples/v1/taskruns/no-ci/stepaction-results.yaml b/examples/v1/taskruns/no-ci/stepaction-results.yaml
new file mode 100644
index 00000000000..f4a285d83db
--- /dev/null
+++ b/examples/v1/taskruns/no-ci/stepaction-results.yaml
@@ -0,0 +1,24 @@
+apiVersion: tekton.dev/v1alpha1
+kind: StepAction
+metadata:
+ name: step-action
+spec:
+ image: alpine
+ results:
+ - name: result1
+ script: |
+ echo "I am a Step Action!!!" >> $(step.results.result1.path)
+---
+apiVersion: tekton.dev/v1
+kind: TaskRun
+metadata:
+ name: step-action-run
+spec:
+ TaskSpec:
+ results:
+ - name: step-result
+ value: $(steps.action-runner.results.result1)
+ steps:
+ - name: action-runner
+ ref:
+ name: step-action
diff --git a/hack/ignored-openapi-violations.list b/hack/ignored-openapi-violations.list
index 43afc5a82d9..ef619305725 100644
--- a/hack/ignored-openapi-violations.list
+++ b/hack/ignored-openapi-violations.list
@@ -49,3 +49,5 @@ API rule violation: list_type_missing,github.com/tektoncd/pipeline/pkg/apis/pipe
API rule violation: list_type_missing,github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1,VerificationPolicySpec,Resources
API rule violation: list_type_missing,github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1,ParamSpec,Enum
API rule violation: list_type_missing,github.com/tektoncd/pipeline/pkg/apis/pipeline/v1,ParamSpec,Enum
+API rule violation: list_type_missing,github.com/tektoncd/pipeline/pkg/apis/pipeline/v1,StepState,Results
+API rule violation: list_type_missing,github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1,StepState,Results
diff --git a/pkg/apis/pipeline/v1/openapi_generated.go b/pkg/apis/pipeline/v1/openapi_generated.go
index e66aea8abf3..0fb74d29b15 100644
--- a/pkg/apis/pipeline/v1/openapi_generated.go
+++ b/pkg/apis/pipeline/v1/openapi_generated.go
@@ -3130,11 +3130,24 @@ func schema_pkg_apis_pipeline_v1_StepState(ref common.ReferenceCallback) common.
Format: "",
},
},
+ "results": {
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunResult"),
+ },
+ },
+ },
+ },
+ },
},
},
},
Dependencies: []string{
- "k8s.io/api/core/v1.ContainerStateRunning", "k8s.io/api/core/v1.ContainerStateTerminated", "k8s.io/api/core/v1.ContainerStateWaiting"},
+ "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunResult", "k8s.io/api/core/v1.ContainerStateRunning", "k8s.io/api/core/v1.ContainerStateTerminated", "k8s.io/api/core/v1.ContainerStateWaiting"},
}
}
@@ -3666,7 +3679,7 @@ func schema_pkg_apis_pipeline_v1_TaskRunResult(ref common.ReferenceCallback) com
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
- Description: "TaskRunResult used to describe the results of a task",
+ Description: "TaskRunStepResult is a type alias of TaskRunResult",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
diff --git a/pkg/apis/pipeline/v1/result_types.go b/pkg/apis/pipeline/v1/result_types.go
index 9727ae49a5b..6361d7a362d 100644
--- a/pkg/apis/pipeline/v1/result_types.go
+++ b/pkg/apis/pipeline/v1/result_types.go
@@ -72,6 +72,9 @@ type TaskRunResult struct {
Value ResultValue `json:"value"`
}
+// TaskRunStepResult is a type alias of TaskRunResult
+type TaskRunStepResult = TaskRunResult
+
// ResultValue is a type alias of ParamValue
type ResultValue = ParamValue
diff --git a/pkg/apis/pipeline/v1/swagger.json b/pkg/apis/pipeline/v1/swagger.json
index ea46bf648ea..d99469bffd7 100644
--- a/pkg/apis/pipeline/v1/swagger.json
+++ b/pkg/apis/pipeline/v1/swagger.json
@@ -1595,6 +1595,13 @@
"name": {
"type": "string"
},
+ "results": {
+ "type": "array",
+ "items": {
+ "default": {},
+ "$ref": "#/definitions/v1.TaskRunResult"
+ }
+ },
"running": {
"description": "Details about a running container",
"$ref": "#/definitions/v1.ContainerStateRunning"
@@ -1887,7 +1894,7 @@
}
},
"v1.TaskRunResult": {
- "description": "TaskRunResult used to describe the results of a task",
+ "description": "TaskRunStepResult is a type alias of TaskRunResult",
"type": "object",
"required": [
"name",
diff --git a/pkg/apis/pipeline/v1/taskrun_types.go b/pkg/apis/pipeline/v1/taskrun_types.go
index 0b8e9451975..ff78c122dfa 100644
--- a/pkg/apis/pipeline/v1/taskrun_types.go
+++ b/pkg/apis/pipeline/v1/taskrun_types.go
@@ -338,9 +338,10 @@ func (trs *TaskRunStatus) SetCondition(newCond *apis.Condition) {
// StepState reports the results of running a step in a Task.
type StepState struct {
corev1.ContainerState `json:",inline"`
- Name string `json:"name,omitempty"`
- Container string `json:"container,omitempty"`
- ImageID string `json:"imageID,omitempty"`
+ Name string `json:"name,omitempty"`
+ Container string `json:"container,omitempty"`
+ ImageID string `json:"imageID,omitempty"`
+ Results []TaskRunStepResult `json:"results,omitempty"`
}
// SidecarState reports the results of running a sidecar in a Task.
diff --git a/pkg/apis/pipeline/v1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1/zz_generated.deepcopy.go
index dd332d9188a..40fe4ba804d 100644
--- a/pkg/apis/pipeline/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/pipeline/v1/zz_generated.deepcopy.go
@@ -1360,6 +1360,13 @@ func (in *StepResult) DeepCopy() *StepResult {
func (in *StepState) DeepCopyInto(out *StepState) {
*out = *in
in.ContainerState.DeepCopyInto(&out.ContainerState)
+ if in.Results != nil {
+ in, out := &in.Results, &out.Results
+ *out = make([]TaskRunResult, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
return
}
diff --git a/pkg/apis/pipeline/v1beta1/openapi_generated.go b/pkg/apis/pipeline/v1beta1/openapi_generated.go
index 182364822bc..d9eaff0ecc4 100644
--- a/pkg/apis/pipeline/v1beta1/openapi_generated.go
+++ b/pkg/apis/pipeline/v1beta1/openapi_generated.go
@@ -4030,11 +4030,24 @@ func schema_pkg_apis_pipeline_v1beta1_StepState(ref common.ReferenceCallback) co
Format: "",
},
},
+ "results": {
+ SchemaProps: spec.SchemaProps{
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunResult"),
+ },
+ },
+ },
+ },
+ },
},
},
},
Dependencies: []string{
- "k8s.io/api/core/v1.ContainerStateRunning", "k8s.io/api/core/v1.ContainerStateTerminated", "k8s.io/api/core/v1.ContainerStateWaiting"},
+ "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1.TaskRunResult", "k8s.io/api/core/v1.ContainerStateRunning", "k8s.io/api/core/v1.ContainerStateTerminated", "k8s.io/api/core/v1.ContainerStateWaiting"},
}
}
@@ -4928,7 +4941,7 @@ func schema_pkg_apis_pipeline_v1beta1_TaskRunResult(ref common.ReferenceCallback
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
- Description: "TaskRunResult used to describe the results of a task",
+ Description: "TaskRunStepResult is a type alias of TaskRunResult",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
diff --git a/pkg/apis/pipeline/v1beta1/result_types.go b/pkg/apis/pipeline/v1beta1/result_types.go
index 2f484f47ddd..ec3192d3925 100644
--- a/pkg/apis/pipeline/v1beta1/result_types.go
+++ b/pkg/apis/pipeline/v1beta1/result_types.go
@@ -52,6 +52,9 @@ type TaskRunResult struct {
Value ResultValue `json:"value"`
}
+// TaskRunStepResult is a type alias of TaskRunResult
+type TaskRunStepResult = TaskRunResult
+
// ResultValue is a type alias of ParamValue
type ResultValue = ParamValue
diff --git a/pkg/apis/pipeline/v1beta1/swagger.json b/pkg/apis/pipeline/v1beta1/swagger.json
index e04ad18a4b3..63c4af3d15b 100644
--- a/pkg/apis/pipeline/v1beta1/swagger.json
+++ b/pkg/apis/pipeline/v1beta1/swagger.json
@@ -2220,6 +2220,13 @@
"name": {
"type": "string"
},
+ "results": {
+ "type": "array",
+ "items": {
+ "default": {},
+ "$ref": "#/definitions/v1beta1.TaskRunResult"
+ }
+ },
"running": {
"description": "Details about a running container",
"$ref": "#/definitions/v1.ContainerStateRunning"
@@ -2704,7 +2711,7 @@
}
},
"v1beta1.TaskRunResult": {
- "description": "TaskRunResult used to describe the results of a task",
+ "description": "TaskRunStepResult is a type alias of TaskRunResult",
"type": "object",
"required": [
"name",
diff --git a/pkg/apis/pipeline/v1beta1/taskrun_conversion.go b/pkg/apis/pipeline/v1beta1/taskrun_conversion.go
index ed3fcc5856e..f2e847c6124 100644
--- a/pkg/apis/pipeline/v1beta1/taskrun_conversion.go
+++ b/pkg/apis/pipeline/v1beta1/taskrun_conversion.go
@@ -330,6 +330,12 @@ func (ss StepState) convertTo(ctx context.Context, sink *v1.StepState) {
sink.Name = ss.Name
sink.Container = ss.ContainerName
sink.ImageID = ss.ImageID
+ sink.Results = nil
+ for _, r := range ss.Results {
+ new := v1.TaskRunStepResult{}
+ r.convertTo(ctx, &new)
+ sink.Results = append(sink.Results, new)
+ }
}
func (ss *StepState) convertFrom(ctx context.Context, source v1.StepState) {
@@ -337,6 +343,12 @@ func (ss *StepState) convertFrom(ctx context.Context, source v1.StepState) {
ss.Name = source.Name
ss.ContainerName = source.Container
ss.ImageID = source.ImageID
+ ss.Results = nil
+ for _, r := range source.Results {
+ new := TaskRunStepResult{}
+ new.convertFrom(ctx, r)
+ ss.Results = append(ss.Results, new)
+ }
}
func (trr TaskRunResult) convertTo(ctx context.Context, sink *v1.TaskRunResult) {
diff --git a/pkg/apis/pipeline/v1beta1/taskrun_conversion_test.go b/pkg/apis/pipeline/v1beta1/taskrun_conversion_test.go
index 13a870e2728..27da78412eb 100644
--- a/pkg/apis/pipeline/v1beta1/taskrun_conversion_test.go
+++ b/pkg/apis/pipeline/v1beta1/taskrun_conversion_test.go
@@ -102,6 +102,29 @@ func TestTaskRunConversion(t *testing.T) {
},
},
},
+ }, {
+ name: "taskrun with step Results in step state",
+ in: &v1beta1.TaskRun{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "foo",
+ Namespace: "bar",
+ },
+ Spec: v1beta1.TaskRunSpec{},
+ Status: v1beta1.TaskRunStatus{
+ TaskRunStatusFields: v1beta1.TaskRunStatusFields{
+ Steps: []v1beta1.StepState{{
+ Results: []v1beta1.TaskRunStepResult{{
+ Name: "foo",
+ Type: v1beta1.ResultsTypeString,
+ Value: v1beta1.ResultValue{
+ Type: v1beta1.ParamTypeString,
+ StringVal: "bar",
+ },
+ }},
+ }},
+ },
+ },
+ },
}, {
name: "taskrun conversion all non deprecated fields",
in: &v1beta1.TaskRun{
diff --git a/pkg/apis/pipeline/v1beta1/taskrun_types.go b/pkg/apis/pipeline/v1beta1/taskrun_types.go
index 2b869121d2c..a12676acb0a 100644
--- a/pkg/apis/pipeline/v1beta1/taskrun_types.go
+++ b/pkg/apis/pipeline/v1beta1/taskrun_types.go
@@ -367,9 +367,10 @@ func (trs *TaskRunStatus) SetCondition(newCond *apis.Condition) {
// StepState reports the results of running a step in a Task.
type StepState struct {
corev1.ContainerState `json:",inline"`
- Name string `json:"name,omitempty"`
- ContainerName string `json:"container,omitempty"`
- ImageID string `json:"imageID,omitempty"`
+ Name string `json:"name,omitempty"`
+ ContainerName string `json:"container,omitempty"`
+ ImageID string `json:"imageID,omitempty"`
+ Results []TaskRunStepResult `json:"results,omitempty"`
}
// SidecarState reports the results of running a sidecar in a Task.
diff --git a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go
index 1eb1695bbcd..2dd4fd8edb2 100644
--- a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go
+++ b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go
@@ -1810,6 +1810,13 @@ func (in *StepOutputConfig) DeepCopy() *StepOutputConfig {
func (in *StepState) DeepCopyInto(out *StepState) {
*out = *in
in.ContainerState.DeepCopyInto(&out.ContainerState)
+ if in.Results != nil {
+ in, out := &in.Results, &out.Results
+ *out = make([]TaskRunResult, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
return
}
diff --git a/pkg/entrypoint/entrypointer.go b/pkg/entrypoint/entrypointer.go
index ac7baf16dfb..c90b6d0960c 100644
--- a/pkg/entrypoint/entrypointer.go
+++ b/pkg/entrypoint/entrypointer.go
@@ -96,6 +96,8 @@ type Entrypointer struct {
// PostWriter encapsulates writing files when complete.
PostWriter PostWriter
+ // StepResults is the set of files that might contain step results
+ StepResults []string
// Results is the set of files that might contain task results
Results []string
// Timeout is an optional user-specified duration within which the Step must complete
@@ -147,6 +149,9 @@ func (e Entrypointer) Go() error {
_ = logger.Sync()
}()
+ if err := os.MkdirAll(filepath.Join(e.StepMetadataDir, "results"), os.ModePerm); err != nil {
+ return err
+ }
for _, f := range e.WaitFiles {
if err := e.Waiter.Wait(context.Background(), f, e.WaitFileContent, e.BreakpointOnFailure); err != nil {
// An error happened while waiting, so we bail
@@ -237,17 +242,30 @@ func (e Entrypointer) Go() error {
if e.ResultsDirectory != "" {
resultPath = e.ResultsDirectory
}
- if err := e.readResultsFromDisk(ctx, resultPath); err != nil {
+ if err := e.readResultsFromDisk(ctx, resultPath, result.TaskRunResultType); err != nil {
logger.Fatalf("Error while handling results: %s", err)
}
}
+ if len(e.StepResults) >= 1 && e.StepResults[0] != "" {
+ stepResultPath := filepath.Join(e.StepMetadataDir, "results")
+ if e.ResultsDirectory != "" {
+ stepResultPath = e.ResultsDirectory
+ }
+ if err := e.readResultsFromDisk(ctx, stepResultPath, result.StepResultType); err != nil {
+ logger.Fatalf("Error while handling step results: %s", err)
+ }
+ }
return err
}
-func (e Entrypointer) readResultsFromDisk(ctx context.Context, resultDir string) error {
+func (e Entrypointer) readResultsFromDisk(ctx context.Context, resultDir string, resultType result.ResultType) error {
output := []result.RunResult{}
- for _, resultFile := range e.Results {
+ results := e.Results
+ if resultType == result.StepResultType {
+ results = e.StepResults
+ }
+ for _, resultFile := range results {
if resultFile == "" {
continue
}
@@ -261,9 +279,10 @@ func (e Entrypointer) readResultsFromDisk(ctx context.Context, resultDir string)
output = append(output, result.RunResult{
Key: resultFile,
Value: string(fileContents),
- ResultType: result.TaskRunResultType,
+ ResultType: resultType,
})
}
+
if e.SpireWorkloadAPI != nil {
signed, err := e.SpireWorkloadAPI.Sign(ctx, output)
if err != nil {
diff --git a/pkg/entrypoint/entrypointer_test.go b/pkg/entrypoint/entrypointer_test.go
index 84731daeb24..127d70b992d 100644
--- a/pkg/entrypoint/entrypointer_test.go
+++ b/pkg/entrypoint/entrypointer_test.go
@@ -262,11 +262,13 @@ func TestReadResultsFromDisk(t *testing.T) {
desc string
results []string
resultContent []v1beta1.ResultValue
+ resultType result.ResultType
want []result.RunResult
}{{
desc: "read string result file",
results: []string{"results"},
resultContent: []v1beta1.ResultValue{*v1beta1.NewStructuredValues("hello world")},
+ resultType: result.TaskRunResultType,
want: []result.RunResult{
{Value: `"hello world"`,
ResultType: 1}},
@@ -274,6 +276,7 @@ func TestReadResultsFromDisk(t *testing.T) {
desc: "read array result file",
results: []string{"results"},
resultContent: []v1beta1.ResultValue{*v1beta1.NewStructuredValues("hello", "world")},
+ resultType: result.TaskRunResultType,
want: []result.RunResult{
{Value: `["hello","world"]`,
ResultType: 1}},
@@ -281,12 +284,40 @@ func TestReadResultsFromDisk(t *testing.T) {
desc: "read string and array result files",
results: []string{"resultsArray", "resultsString"},
resultContent: []v1beta1.ResultValue{*v1beta1.NewStructuredValues("hello", "world"), *v1beta1.NewStructuredValues("hello world")},
+ resultType: result.TaskRunResultType,
want: []result.RunResult{
{Value: `["hello","world"]`,
ResultType: 1},
{Value: `"hello world"`,
ResultType: 1},
},
+ }, {
+ desc: "read string step result file",
+ results: []string{"results"},
+ resultContent: []v1beta1.ResultValue{*v1beta1.NewStructuredValues("hello world")},
+ resultType: result.StepResultType,
+ want: []result.RunResult{
+ {Value: `"hello world"`,
+ ResultType: 4}},
+ }, {
+ desc: "read array step result file",
+ results: []string{"results"},
+ resultContent: []v1beta1.ResultValue{*v1beta1.NewStructuredValues("hello", "world")},
+ resultType: result.StepResultType,
+ want: []result.RunResult{
+ {Value: `["hello","world"]`,
+ ResultType: 4}},
+ }, {
+ desc: "read string and array step result files",
+ results: []string{"resultsArray", "resultsString"},
+ resultContent: []v1beta1.ResultValue{*v1beta1.NewStructuredValues("hello", "world"), *v1beta1.NewStructuredValues("hello world")},
+ resultType: result.StepResultType,
+ want: []result.RunResult{
+ {Value: `["hello","world"]`,
+ ResultType: 4},
+ {Value: `"hello world"`,
+ ResultType: 4},
+ },
},
} {
t.Run(c.desc, func(t *testing.T) {
@@ -319,10 +350,11 @@ func TestReadResultsFromDisk(t *testing.T) {
e := Entrypointer{
Results: resultsFilePath,
+ StepResults: resultsFilePath,
TerminationPath: terminationPath,
ResultExtractionMethod: config.ResultExtractionMethodTerminationMessage,
}
- if err := e.readResultsFromDisk(ctx, ""); err != nil {
+ if err := e.readResultsFromDisk(ctx, "", c.resultType); err != nil {
t.Fatal(err)
}
msg, err := os.ReadFile(terminationPath)
@@ -462,6 +494,12 @@ func TestEntrypointerResults(t *testing.T) {
resultsToWrite: map[string]string{
"foo": "abc",
},
+ }, {
+ desc: "write single step result",
+ entrypoint: "echo",
+ resultsToWrite: map[string]string{
+ "foo": "abc",
+ },
}, {
desc: "write multiple result",
entrypoint: "echo",
@@ -555,6 +593,7 @@ func TestEntrypointerResults(t *testing.T) {
Runner: fr,
PostWriter: fpw,
Results: results,
+ StepResults: results,
ResultsDirectory: resultsDir,
ResultExtractionMethod: config.ResultExtractionMethodTerminationMessage,
TerminationPath: terminationPath,
diff --git a/pkg/pod/entrypoint.go b/pkg/pod/entrypoint.go
index d7cd3506405..72096adb72b 100644
--- a/pkg/pod/entrypoint.go
+++ b/pkg/pod/entrypoint.go
@@ -124,7 +124,7 @@ var (
// command, we must have fetched the image's ENTRYPOINT before calling this
// method, using entrypoint_lookup.go.
// Additionally, Step timeouts are added as entrypoint flag.
-func orderContainers(commonExtraEntrypointArgs []string, steps []corev1.Container, taskSpec *v1.TaskSpec, breakpointConfig *v1.TaskRunDebug, waitForReadyAnnotation, enableKeepPodOnCancel bool) ([]corev1.Container, error) {
+func orderContainers(ctx context.Context, commonExtraEntrypointArgs []string, steps []corev1.Container, taskSpec *v1.TaskSpec, breakpointConfig *v1.TaskRunDebug, waitForReadyAnnotation, enableKeepPodOnCancel bool) ([]corev1.Container, error) {
if len(steps) == 0 {
return nil, errors.New("No steps specified")
}
@@ -149,6 +149,7 @@ func orderContainers(commonExtraEntrypointArgs []string, steps []corev1.Containe
"-termination_path", terminationPath,
"-step_metadata_dir", filepath.Join(RunDir, idx, "status"),
)
+
argsForEntrypoint = append(argsForEntrypoint, commonExtraEntrypointArgs...)
if taskSpec != nil {
if taskSpec.Steps != nil && len(taskSpec.Steps) >= i+1 {
@@ -168,6 +169,9 @@ func orderContainers(commonExtraEntrypointArgs []string, steps []corev1.Containe
if taskSpec.Steps[i].StderrConfig != nil {
argsForEntrypoint = append(argsForEntrypoint, "-stderr_path", taskSpec.Steps[i].StderrConfig.Path)
}
+ // add step results
+ stepResultArgs := stepResultArgument(taskSpec.Steps[i].Results)
+ argsForEntrypoint = append(argsForEntrypoint, stepResultArgs...)
}
argsForEntrypoint = append(argsForEntrypoint, resultArgument(steps, taskSpec.Results)...)
}
@@ -199,6 +203,18 @@ func orderContainers(commonExtraEntrypointArgs []string, steps []corev1.Containe
return steps, nil
}
+// stepResultArgument creates the cli arguments for step results to the entrypointer.
+func stepResultArgument(stepResults []v1.StepResult) []string {
+ if len(stepResults) == 0 {
+ return nil
+ }
+ stepResultNames := []string{}
+ for _, r := range stepResults {
+ stepResultNames = append(stepResultNames, r.Name)
+ }
+ return []string{"-step_results", strings.Join(stepResultNames, ",")}
+}
+
func resultArgument(steps []corev1.Container, results []v1.TaskResult) []string {
if len(results) == 0 {
return nil
@@ -209,7 +225,9 @@ func resultArgument(steps []corev1.Container, results []v1.TaskResult) []string
func collectResultsName(results []v1.TaskResult) string {
var resultNames []string
for _, r := range results {
- resultNames = append(resultNames, r.Name)
+ if r.Value == nil {
+ resultNames = append(resultNames, r.Name)
+ }
}
return strings.Join(resultNames, ",")
}
@@ -335,7 +353,11 @@ func TrimSidecarPrefix(name string) string { return strings.TrimPrefix(name, sid
// returns "step-unnamed-" if not specified
func StepName(name string, i int) string {
if name != "" {
- return fmt.Sprintf("%s%s", stepPrefix, name)
+ return getContainerName(name)
}
return fmt.Sprintf("%sunnamed-%d", stepPrefix, i)
}
+
+func getContainerName(name string) string {
+ return fmt.Sprintf("%s%s", stepPrefix, name)
+}
diff --git a/pkg/pod/entrypoint_test.go b/pkg/pod/entrypoint_test.go
index 56d57c8a146..296bedc3cd8 100644
--- a/pkg/pod/entrypoint_test.go
+++ b/pkg/pod/entrypoint_test.go
@@ -95,7 +95,7 @@ func TestOrderContainers(t *testing.T) {
},
TerminationMessagePath: "/tekton/termination",
}}
- got, err := orderContainers([]string{}, steps, nil, nil, true, false)
+ got, err := orderContainers(context.Background(), []string{}, steps, nil, nil, true, false)
if err != nil {
t.Fatalf("orderContainers: %v", err)
}
@@ -163,7 +163,7 @@ func TestOrderContainersWithResultsSidecarLogs(t *testing.T) {
},
TerminationMessagePath: "/tekton/termination",
}}
- got, err := orderContainers([]string{"-dont_send_results_to_termination_path"}, steps, nil, nil, true, false)
+ got, err := orderContainers(context.Background(), []string{"-dont_send_results_to_termination_path"}, steps, nil, nil, true, false)
if err != nil {
t.Fatalf("orderContainers: %v", err)
}
@@ -209,7 +209,7 @@ func TestOrderContainersWithNoWait(t *testing.T) {
VolumeMounts: []corev1.VolumeMount{volumeMount},
TerminationMessagePath: "/tekton/termination",
}}
- got, err := orderContainers([]string{}, steps, nil, nil, false, false)
+ got, err := orderContainers(context.Background(), []string{}, steps, nil, nil, false, false)
if err != nil {
t.Fatalf("orderContainers: %v", err)
}
@@ -245,7 +245,7 @@ func TestOrderContainersWithDebugOnFailure(t *testing.T) {
OnFailure: "enabled",
},
}
- got, err := orderContainers([]string{}, steps, nil, taskRunDebugConfig, true, false)
+ got, err := orderContainers(context.Background(), []string{}, steps, nil, taskRunDebugConfig, true, false)
if err != nil {
t.Fatalf("orderContainers: %v", err)
}
@@ -273,7 +273,87 @@ func TestOrderContainersWithEnabelKeepPodOnCancel(t *testing.T) {
VolumeMounts: []corev1.VolumeMount{downwardMount},
TerminationMessagePath: "/tekton/termination",
}}
- got, err := orderContainers([]string{}, steps, nil, nil, false, true)
+ got, err := orderContainers(context.Background(), []string{}, steps, nil, nil, false, true)
+ if err != nil {
+ t.Fatalf("orderContainers: %v", err)
+ }
+ if d := cmp.Diff(want, got); d != "" {
+ t.Errorf("Diff %s", diff.PrintWantGot(d))
+ }
+}
+
+func TestStepResultArgument(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ results []v1.StepResult
+ want []string
+ }{{
+ name: "no step results",
+ want: nil,
+ }, {
+ name: "step results",
+ results: []v1.StepResult{{
+ Name: "sum",
+ Description: "This is the sum result of the task",
+ }, {
+ Name: "sub",
+ Description: "This is the sub result of the task",
+ }},
+ want: []string{"-step_results", "sum,sub"},
+ }} {
+ t.Run(tc.name, func(t *testing.T) {
+ got := stepResultArgument(tc.results)
+ if d := cmp.Diff(tc.want, got); d != "" {
+ t.Errorf("Diff %s", diff.PrintWantGot(d))
+ }
+ })
+ }
+}
+
+func TestEntryPointStepActionResults(t *testing.T) {
+ taskSpec := v1.TaskSpec{
+ Steps: []v1.Step{{
+ Results: []v1.StepResult{{
+ Name: "sub",
+ Description: "This is a step result",
+ }},
+ }},
+ Results: []v1.TaskResult{{
+ Name: "sum",
+ Description: "This is the sum result of the task",
+ }},
+ }
+
+ steps := []corev1.Container{{
+ Name: "named-step",
+ Image: "step-1",
+ Command: []string{"cmd"},
+ Args: []string{"arg1", "arg2"},
+ }}
+ want := []corev1.Container{{
+ Name: "named-step",
+ Image: "step-1",
+ Command: []string{entrypointBinary},
+ Args: []string{
+ "-wait_file", "/tekton/downward/ready",
+ "-wait_file_content",
+ "-post_file", "/tekton/run/0/out",
+ "-termination_path", "/tekton/termination",
+ "-step_metadata_dir", "/tekton/run/0/status",
+ "-step_results", "sub",
+ "-results", "sum",
+ "-entrypoint", "cmd", "--",
+ "arg1", "arg2",
+ },
+ VolumeMounts: []corev1.VolumeMount{downwardMount},
+ TerminationMessagePath: "/tekton/termination",
+ }}
+ ctx := config.ToContext(context.Background(), &config.Config{
+ FeatureFlags: &config.FeatureFlags{
+ EnableStepActions: true,
+ },
+ })
+ got, err := orderContainers(ctx, []string{}, steps, &taskSpec, nil, true, false)
if err != nil {
t.Fatalf("orderContainers: %v", err)
}
@@ -351,7 +431,7 @@ func TestEntryPointResults(t *testing.T) {
},
TerminationMessagePath: "/tekton/termination",
}}
- got, err := orderContainers([]string{}, steps, &taskSpec, nil, true, false)
+ got, err := orderContainers(context.Background(), []string{}, steps, &taskSpec, nil, true, false)
if err != nil {
t.Fatalf("orderContainers: %v", err)
}
@@ -392,7 +472,7 @@ func TestEntryPointResultsSingleStep(t *testing.T) {
VolumeMounts: []corev1.VolumeMount{downwardMount},
TerminationMessagePath: "/tekton/termination",
}}
- got, err := orderContainers([]string{}, steps, &taskSpec, nil, true, false)
+ got, err := orderContainers(context.Background(), []string{}, steps, &taskSpec, nil, true, false)
if err != nil {
t.Fatalf("orderContainers: %v", err)
}
@@ -429,7 +509,7 @@ func TestEntryPointSingleResultsSingleStep(t *testing.T) {
VolumeMounts: []corev1.VolumeMount{downwardMount},
TerminationMessagePath: "/tekton/termination",
}}
- got, err := orderContainers([]string{}, steps, &taskSpec, nil, true, false)
+ got, err := orderContainers(context.Background(), []string{}, steps, &taskSpec, nil, true, false)
if err != nil {
t.Fatalf("orderContainers: %v", err)
}
@@ -500,7 +580,7 @@ func TestEntryPointOnError(t *testing.T) {
err: errors.New("task step onError must be either \"continue\" or \"stopAndFail\" but it is set to an invalid value \"invalid-on-error\""),
}} {
t.Run(tc.desc, func(t *testing.T) {
- got, err := orderContainers([]string{}, steps, &tc.taskSpec, nil, true, false)
+ got, err := orderContainers(context.Background(), []string{}, steps, &tc.taskSpec, nil, true, false)
if len(tc.wantContainers) == 0 {
if err == nil {
t.Fatalf("expected an error for an invalid value for onError but received none")
@@ -599,7 +679,7 @@ func TestEntryPointStepOutputConfigs(t *testing.T) {
},
TerminationMessagePath: "/tekton/termination",
}}
- got, err := orderContainers([]string{}, steps, &taskSpec, nil, true, false)
+ got, err := orderContainers(context.Background(), []string{}, steps, &taskSpec, nil, true, false)
if err != nil {
t.Fatalf("orderContainers: %v", err)
}
diff --git a/pkg/pod/pod.go b/pkg/pod/pod.go
index cfc7d69d683..04a0a2dec0e 100644
--- a/pkg/pod/pod.go
+++ b/pkg/pod/pod.go
@@ -239,9 +239,9 @@ func (b *Builder) Build(ctx context.Context, taskRun *v1.TaskRun, taskSpec v1.Ta
readyImmediately := isPodReadyImmediately(*featureFlags, taskSpec.Sidecars)
if alphaAPIEnabled {
- stepContainers, err = orderContainers(commonExtraEntrypointArgs, stepContainers, &taskSpec, taskRun.Spec.Debug, !readyImmediately, enableKeepPodOnCancel)
+ stepContainers, err = orderContainers(ctx, commonExtraEntrypointArgs, stepContainers, &taskSpec, taskRun.Spec.Debug, !readyImmediately, enableKeepPodOnCancel)
} else {
- stepContainers, err = orderContainers(commonExtraEntrypointArgs, stepContainers, &taskSpec, nil, !readyImmediately, enableKeepPodOnCancel)
+ stepContainers, err = orderContainers(ctx, commonExtraEntrypointArgs, stepContainers, &taskSpec, nil, !readyImmediately, enableKeepPodOnCancel)
}
if err != nil {
return nil, err
diff --git a/pkg/pod/status.go b/pkg/pod/status.go
index 7c52ca4ce32..848c72ba05e 100644
--- a/pkg/pod/status.go
+++ b/pkg/pod/status.go
@@ -184,9 +184,9 @@ func setTaskRunStatusBasedOnStepStatus(ctx context.Context, logger *zap.SugaredL
if err != nil {
merr = multierror.Append(merr, err)
}
-
// populate task run CRD with results from sidecar logs
- taskResults, _ := filterResults(sidecarLogResults, specResults)
+ // since sidecar logs does not support step results yet, it is empty for now.
+ taskResults, _, _ := filterResults(sidecarLogResults, specResults, []v1.StepResult{})
if tr.IsDone() {
trs.Results = append(trs.Results, taskResults...)
}
@@ -195,6 +195,7 @@ func setTaskRunStatusBasedOnStepStatus(ctx context.Context, logger *zap.SugaredL
for _, s := range stepStatuses {
// Avoid changing the original value by modifying the pointer value.
state := s.State.DeepCopy()
+ taskRunStepResults := []v1.TaskRunStepResult{}
if state.Terminated != nil && len(state.Terminated.Message) != 0 {
msg := state.Terminated.Message
@@ -214,8 +215,35 @@ func setTaskRunStatusBasedOnStepStatus(ctx context.Context, logger *zap.SugaredL
merr = multierror.Append(merr, err)
}
- taskResults, filteredResults := filterResults(results, specResults)
+ // Identify Step Results
+ stepResults := []v1.StepResult{}
+ if ts != nil {
+ for _, step := range ts.Steps {
+ if getContainerName(step.Name) == s.Name {
+ stepResults = append(stepResults, step.Results...)
+ }
+ }
+ }
+ taskResults, stepRunRes, filteredResults := filterResults(results, specResults, stepResults)
if tr.IsDone() {
+ taskRunStepResults = append(taskRunStepResults, stepRunRes...)
+ // Identify StepResults needed by the Task Results
+ neededStepResults, err := findStepResultsFetchedByTask(s.Name, specResults)
+ if err != nil {
+ merr = multierror.Append(merr, err)
+ }
+ // Set TaskResults from StepResults
+ for _, r := range stepRunRes {
+ // this result was requested by the Task
+ if _, ok := neededStepResults[r.Name]; ok {
+ taskRunResult := v1.TaskRunResult{
+ Name: neededStepResults[r.Name],
+ Type: r.Type,
+ Value: r.Value,
+ }
+ taskResults = append(taskResults, taskRunResult)
+ }
+ }
trs.Results = append(trs.Results, taskResults...)
}
msg, err = createMessageFromResults(filteredResults)
@@ -238,6 +266,7 @@ func setTaskRunStatusBasedOnStepStatus(ctx context.Context, logger *zap.SugaredL
Name: trimStepPrefix(s.Name),
Container: s.Name,
ImageID: s.ImageID,
+ Results: taskRunStepResults,
})
}
@@ -266,16 +295,43 @@ func createMessageFromResults(results []result.RunResult) (string, error) {
return string(bytes), nil
}
+// findStepResultsFetchedByTask fetches step results that the Task needs.
+// It accepts a container name and the TaskResults as input and outputs
+// a map with the name of the step result as the key and the name of the task result that is fetching it as value.
+func findStepResultsFetchedByTask(containerName string, specResults []v1.TaskResult) (map[string]string, error) {
+ neededStepResults := map[string]string{}
+ for _, r := range specResults {
+ if r.Value != nil {
+ if r.Value.StringVal != "" {
+ sName, resultName, err := v1.ExtractStepResultName(r.Value.StringVal)
+ if err != nil {
+ return nil, err
+ }
+ // Only look at named results - referencing unnamed steps is unsupported.
+ if getContainerName(sName) == containerName {
+ neededStepResults[resultName] = r.Name
+ }
+ }
+ }
+ }
+ return neededStepResults, nil
+}
+
// filterResults filters the RunResults and TaskResults based on the results declared in the task spec.
// It returns a slice of any of the input results that are defined in the task spec, converted to TaskRunResults,
// and a slice of any of the RunResults that don't represent internal values (i.e. those that should not be displayed in the TaskRun status.
-func filterResults(results []result.RunResult, specResults []v1.TaskResult) ([]v1.TaskRunResult, []result.RunResult) {
+func filterResults(results []result.RunResult, specResults []v1.TaskResult, stepResults []v1.StepResult) ([]v1.TaskRunResult, []v1.TaskRunStepResult, []result.RunResult) {
var taskResults []v1.TaskRunResult
+ var taskRunStepResults []v1.TaskRunStepResult
var filteredResults []result.RunResult
neededTypes := make(map[string]v1.ResultsType)
+ neededStepTypes := make(map[string]v1.ResultsType)
for _, r := range specResults {
neededTypes[r.Name] = r.Type
}
+ for _, r := range stepResults {
+ neededStepTypes[r.Name] = r.Type
+ }
for _, r := range results {
switch r.ResultType {
case result.TaskRunResultType:
@@ -300,6 +356,28 @@ func filterResults(results []result.RunResult, specResults []v1.TaskResult) ([]v
}
taskResults = append(taskResults, taskRunResult)
filteredResults = append(filteredResults, r)
+ case result.StepResultType:
+ var taskRunStepResult v1.TaskRunStepResult
+ if neededStepTypes[r.Key] == v1.ResultsTypeString {
+ taskRunStepResult = v1.TaskRunStepResult{
+ Name: r.Key,
+ Type: v1.ResultsTypeString,
+ Value: *v1.NewStructuredValues(r.Value),
+ }
+ } else {
+ v := v1.ResultValue{}
+ err := v.UnmarshalJSON([]byte(r.Value))
+ if err != nil {
+ continue
+ }
+ taskRunStepResult = v1.TaskRunStepResult{
+ Name: r.Key,
+ Type: v1.ResultsType(v.Type),
+ Value: v,
+ }
+ }
+ taskRunStepResults = append(taskRunStepResults, taskRunStepResult)
+ filteredResults = append(filteredResults, r)
case result.InternalTektonResultType:
// Internal messages are ignored because they're not used as external result
continue
@@ -307,8 +385,7 @@ func filterResults(results []result.RunResult, specResults []v1.TaskResult) ([]v
filteredResults = append(filteredResults, r)
}
}
-
- return taskResults, filteredResults
+ return taskResults, taskRunStepResults, filteredResults
}
func removeDuplicateResults(taskRunResult []v1.TaskRunResult) []v1.TaskRunResult {
diff --git a/pkg/pod/status_test.go b/pkg/pod/status_test.go
index 77c0176399a..4496651b661 100644
--- a/pkg/pod/status_test.go
+++ b/pkg/pod/status_test.go
@@ -189,6 +189,249 @@ func TestSetTaskRunStatusBasedOnStepStatus_sidecar_logs(t *testing.T) {
}
}
+func TestMakeTaskRunStatus_StepResults(t *testing.T) {
+ for _, c := range []struct {
+ desc string
+ podStatus corev1.PodStatus
+ pod corev1.Pod
+ tr v1.TaskRun
+ want v1.TaskRunStatus
+ }{{
+ desc: "step results string type",
+ podStatus: corev1.PodStatus{
+ Phase: corev1.PodSucceeded,
+ ContainerStatuses: []corev1.ContainerStatus{{
+ Name: "step-one",
+ State: corev1.ContainerState{
+ Terminated: &corev1.ContainerStateTerminated{
+ Message: `[{"key":"uri","value":"https://foo.bar\n","type":4}]`,
+ },
+ },
+ }},
+ },
+ tr: v1.TaskRun{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "task-run",
+ Namespace: "foo",
+ },
+ Spec: v1.TaskRunSpec{
+ TaskSpec: &v1.TaskSpec{
+ Results: []v1.TaskResult{{
+ Name: "task-result",
+ Type: v1.ResultsTypeString,
+ Value: &v1.ParamValue{
+ Type: v1.ParamTypeString,
+ StringVal: "$(steps.one.results.uri)",
+ },
+ }},
+ Steps: []v1.Step{{
+ Name: "one",
+ Results: []v1.StepResult{{
+ Name: "uri",
+ Type: v1.ResultsTypeString,
+ }},
+ }},
+ },
+ },
+ },
+ want: v1.TaskRunStatus{
+ Status: statusSuccess(),
+ TaskRunStatusFields: v1.TaskRunStatusFields{
+ Steps: []v1.StepState{{
+ ContainerState: corev1.ContainerState{
+ Terminated: &corev1.ContainerStateTerminated{
+ Message: `[{"key":"uri","value":"https://foo.bar\n","type":4}]`,
+ }},
+ Name: "one",
+ Container: "step-one",
+ Results: []v1.TaskRunStepResult{{
+ Name: "uri",
+ Type: v1.ResultsTypeString,
+ Value: *v1.NewStructuredValues("https://foo.bar\n"),
+ }},
+ }},
+ Sidecars: []v1.SidecarState{},
+ Results: []v1.TaskRunResult{{
+ Name: "task-result",
+ Type: v1.ResultsTypeString,
+ Value: *v1.NewStructuredValues("https://foo.bar\n"),
+ }},
+ // We don't actually care about the time, just that it's not nil
+ CompletionTime: &metav1.Time{Time: time.Now()},
+ },
+ },
+ }, {
+ desc: "step results array type",
+ podStatus: corev1.PodStatus{
+ Phase: corev1.PodSucceeded,
+ ContainerStatuses: []corev1.ContainerStatus{{
+ Name: "step-one",
+ State: corev1.ContainerState{
+ Terminated: &corev1.ContainerStateTerminated{
+ Message: `[{"key":"array","value":"[\"hello\",\"world\"]","type":4}]`,
+ },
+ },
+ }},
+ },
+ tr: v1.TaskRun{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "task-run",
+ Namespace: "foo",
+ },
+ Spec: v1.TaskRunSpec{
+ TaskSpec: &v1.TaskSpec{
+ Results: []v1.TaskResult{{
+ Name: "resultName",
+ Type: v1.ResultsTypeArray,
+ Value: &v1.ParamValue{
+ Type: v1.ParamTypeString,
+ StringVal: "$(steps.one.results.array)",
+ },
+ }},
+ Steps: []v1.Step{{
+ Name: "one",
+ Results: []v1.StepResult{{
+ Name: "array",
+ Type: v1.ResultsTypeArray,
+ }},
+ }},
+ },
+ },
+ },
+ want: v1.TaskRunStatus{
+ Status: statusSuccess(),
+ TaskRunStatusFields: v1.TaskRunStatusFields{
+ Steps: []v1.StepState{{
+ ContainerState: corev1.ContainerState{
+ Terminated: &corev1.ContainerStateTerminated{
+ Message: `[{"key":"array","value":"[\"hello\",\"world\"]","type":4}]`,
+ }},
+ Name: "one",
+ Container: "step-one",
+ Results: []v1.TaskRunStepResult{{
+ Name: "array",
+ Type: v1.ResultsTypeArray,
+ Value: *v1.NewStructuredValues("hello", "world"),
+ }},
+ }},
+ Sidecars: []v1.SidecarState{},
+ Results: []v1.TaskRunResult{{
+ Name: "resultName",
+ Type: v1.ResultsTypeArray,
+ Value: *v1.NewStructuredValues("hello", "world"),
+ }},
+ // We don't actually care about the time, just that it's not nil
+ CompletionTime: &metav1.Time{Time: time.Now()},
+ },
+ },
+ }, {
+ desc: "filter task results from step results",
+ podStatus: corev1.PodStatus{
+ Phase: corev1.PodSucceeded,
+ ContainerStatuses: []corev1.ContainerStatus{{
+ Name: "step-one",
+ State: corev1.ContainerState{
+ Terminated: &corev1.ContainerStateTerminated{
+ Message: `[{"key":"digest","value":"sha256:1234","type":4},{"key":"resultName","value":"resultValue","type":4}]`,
+ },
+ },
+ }},
+ },
+ tr: v1.TaskRun{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "task-run",
+ Namespace: "foo",
+ },
+ Spec: v1.TaskRunSpec{
+ TaskSpec: &v1.TaskSpec{
+ Results: []v1.TaskResult{{
+ Name: "resultDigest",
+ Type: v1.ResultsTypeString,
+ Value: &v1.ParamValue{
+ Type: v1.ParamTypeString,
+ StringVal: "$(steps.one.results.digest)",
+ },
+ }},
+ Steps: []v1.Step{{
+ Name: "one",
+ Results: []v1.StepResult{{
+ Name: "digest",
+ Type: v1.ResultsTypeString,
+ }, {
+ Name: "resultName",
+ Type: v1.ResultsTypeString,
+ }},
+ }},
+ },
+ },
+ },
+ want: v1.TaskRunStatus{
+ Status: statusSuccess(),
+ TaskRunStatusFields: v1.TaskRunStatusFields{
+ Steps: []v1.StepState{{
+ ContainerState: corev1.ContainerState{
+ Terminated: &corev1.ContainerStateTerminated{
+ Message: `[{"key":"digest","value":"sha256:1234","type":4},{"key":"resultName","value":"resultValue","type":4}]`,
+ }},
+ Name: "one",
+ Container: "step-one",
+ Results: []v1.TaskRunStepResult{{
+ Name: "digest",
+ Type: v1.ResultsTypeString,
+ Value: *v1.NewStructuredValues("sha256:1234"),
+ }, {
+ Name: "resultName",
+ Type: v1.ResultsTypeString,
+ Value: *v1.NewStructuredValues("resultValue"),
+ }},
+ }},
+ Sidecars: []v1.SidecarState{},
+ Results: []v1.TaskRunResult{{
+ Name: "resultDigest",
+ Type: v1.ResultsTypeString,
+ Value: *v1.NewStructuredValues("sha256:1234"),
+ }},
+ // We don't actually care about the time, just that it's not nil
+ CompletionTime: &metav1.Time{Time: time.Now()},
+ },
+ },
+ }} {
+ t.Run(c.desc, func(t *testing.T) {
+ now := metav1.Now()
+ if cmp.Diff(c.pod, corev1.Pod{}) == "" {
+ c.pod = corev1.Pod{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "pod",
+ Namespace: "foo",
+ CreationTimestamp: now,
+ },
+ Status: c.podStatus,
+ }
+ }
+
+ logger, _ := logging.NewLogger("", "status")
+ kubeclient := fakek8s.NewSimpleClientset()
+ got, err := MakeTaskRunStatus(context.Background(), logger, c.tr, &c.pod, kubeclient, c.tr.Spec.TaskSpec)
+ if err != nil {
+ t.Errorf("MakeTaskRunResult: %s", err)
+ }
+
+ // Common traits, set for test case brevity.
+ c.want.PodName = "pod"
+
+ ensureTimeNotNil := cmp.Comparer(func(x, y *metav1.Time) bool {
+ if x == nil {
+ return y == nil
+ }
+ return y != nil
+ })
+ if d := cmp.Diff(c.want, got, ignoreVolatileTime, ensureTimeNotNil); d != "" {
+ t.Errorf("Diff %s", diff.PrintWantGot(d))
+ }
+ })
+ }
+}
+
func TestMakeTaskRunStatus(t *testing.T) {
for _, c := range []struct {
desc string
@@ -1276,6 +1519,11 @@ func TestMakeTaskRunStatus(t *testing.T) {
// Common traits, set for test case brevity.
c.want.PodName = "pod"
c.want.StartTime = &metav1.Time{Time: startTime}
+ for i := range c.want.TaskRunStatusFields.Steps {
+ if c.want.TaskRunStatusFields.Steps[i].Results == nil {
+ c.want.TaskRunStatusFields.Steps[i].Results = []v1.TaskRunResult{}
+ }
+ }
ensureTimeNotNil := cmp.Comparer(func(x, y *metav1.Time) bool {
if x == nil {
@@ -1554,6 +1802,11 @@ func TestMakeTaskRunStatusAlpha(t *testing.T) {
// Common traits, set for test case brevity.
c.want.PodName = "pod"
c.want.StartTime = &metav1.Time{Time: startTime}
+ for i := range c.want.TaskRunStatusFields.Steps {
+ if c.want.TaskRunStatusFields.Steps[i].Results == nil {
+ c.want.TaskRunStatusFields.Steps[i].Results = []v1.TaskRunResult{}
+ }
+ }
ensureTimeNotNil := cmp.Comparer(func(x, y *metav1.Time) bool {
if x == nil {
@@ -1632,24 +1885,28 @@ func TestMakeRunStatusJSONError(t *testing.T) {
}},
Name: "non-json",
Container: "step-non-json",
+ Results: []v1.TaskRunResult{},
ImageID: "image",
}, {
ContainerState: corev1.ContainerState{
Terminated: &corev1.ContainerStateTerminated{}},
Name: "after-non-json",
Container: "step-after-non-json",
+ Results: []v1.TaskRunResult{},
ImageID: "image",
}, {
ContainerState: corev1.ContainerState{
Terminated: &corev1.ContainerStateTerminated{}},
Name: "this-step-might-panic",
Container: "step-this-step-might-panic",
+ Results: []v1.TaskRunResult{},
ImageID: "image",
}, {
ContainerState: corev1.ContainerState{
Terminated: &corev1.ContainerStateTerminated{}},
Name: "foo",
Container: "step-foo",
+ Results: []v1.TaskRunResult{},
ImageID: "image",
}},
Sidecars: []v1.SidecarState{},
diff --git a/pkg/reconciler/taskrun/resources/apply.go b/pkg/reconciler/taskrun/resources/apply.go
index 0af13115fda..40fac760cc2 100644
--- a/pkg/reconciler/taskrun/resources/apply.go
+++ b/pkg/reconciler/taskrun/resources/apply.go
@@ -257,9 +257,38 @@ func applyWorkspaceMountPath(variable string, spec *v1.TaskSpec, declaration v1.
return ApplyReplacements(spec, stringReplacements, emptyArrayReplacements, map[string]map[string]string{})
}
-// ApplyTaskResults applies the substitution from values in results which are referenced in spec as subitems
+// ApplyResults applies the substitution from values in results and step results which are referenced in spec as subitems
// of the replacementStr.
-func ApplyTaskResults(spec *v1.TaskSpec) *v1.TaskSpec {
+func ApplyResults(spec *v1.TaskSpec) *v1.TaskSpec {
+ // Apply all the Step Result replacements
+ for i := range spec.Steps {
+ stringReplacements := getStepResultReplacements(spec.Steps[i], i)
+ container.ApplyStepReplacements(&spec.Steps[i], stringReplacements, map[string][]string{})
+ }
+ stringReplacements := getTaskResultReplacements(spec)
+ return ApplyReplacements(spec, stringReplacements, map[string][]string{}, map[string]map[string]string{})
+}
+
+// getStepResultReplacements creates all combinations of string replacements from Step Results.
+func getStepResultReplacements(step v1.Step, idx int) map[string]string {
+ stringReplacements := map[string]string{}
+
+ patterns := []string{
+ "step.results.%s.path",
+ "step.results[%q].path",
+ "step.results['%s'].path",
+ }
+ stepName := pod.StepName(step.Name, idx)
+ for _, result := range step.Results {
+ for _, pattern := range patterns {
+ stringReplacements[fmt.Sprintf(pattern, result.Name)] = filepath.Join(pipeline.StepsDir, stepName, "results", result.Name)
+ }
+ }
+ return stringReplacements
+}
+
+// getTaskResultReplacements creates all combinations of string replacements from TaskResults.
+func getTaskResultReplacements(spec *v1.TaskSpec) map[string]string {
stringReplacements := map[string]string{}
patterns := []string{
@@ -273,7 +302,7 @@ func ApplyTaskResults(spec *v1.TaskSpec) *v1.TaskSpec {
stringReplacements[fmt.Sprintf(pattern, result.Name)] = filepath.Join(pipeline.DefaultResultPath, result.Name)
}
}
- return ApplyReplacements(spec, stringReplacements, map[string][]string{}, map[string]map[string]string{})
+ return stringReplacements
}
// ApplyStepExitCodePath replaces the occurrences of exitCode path with the absolute tekton internal path
diff --git a/pkg/reconciler/taskrun/resources/apply_test.go b/pkg/reconciler/taskrun/resources/apply_test.go
index 4de5ced1daa..32b825be1ad 100644
--- a/pkg/reconciler/taskrun/resources/apply_test.go
+++ b/pkg/reconciler/taskrun/resources/apply_test.go
@@ -1516,7 +1516,51 @@ func TestTaskResults(t *testing.T) {
spec.Steps[1].Script = "#!/usr/bin/env bash\ndate | tee /tekton/results/current-date-human-readable"
spec.Steps[2].Script = "#!/usr/bin/env bash\ndate | tee /tekton/results/current-date-human-readable"
})
- got := resources.ApplyTaskResults(ts)
+ got := resources.ApplyResults(ts)
+ if d := cmp.Diff(want, got); d != "" {
+ t.Errorf("ApplyTaskResults() got diff %s", diff.PrintWantGot(d))
+ }
+}
+
+func TestStepResults(t *testing.T) {
+ names.TestingSeed()
+ ts := &v1.TaskSpec{
+ Steps: []v1.Step{{
+ Name: "print-date-unix-timestamp",
+ Results: []v1.StepResult{{
+ Name: "current.date.unix.timestamp",
+ Description: "The current date in unix timestamp format",
+ }},
+ Image: "bash:latest",
+ Args: []string{
+ "$(step.results[\"current.date.unix.timestamp\"].path)",
+ },
+ Script: "#!/usr/bin/env bash\ndate +%s | tee $(step.results[\"current.date.unix.timestamp\"].path)",
+ }, {
+ Name: "print-date-human-readable",
+ Results: []v1.StepResult{{
+ Name: "current-date-human-readable",
+ Description: "The current date in humand readable format",
+ }},
+ Image: "bash:latest",
+ Script: "#!/usr/bin/env bash\ndate | tee $(step.results.current-date-human-readable.path)",
+ }, {
+ Name: "print-date-human-readable-again",
+ Image: "bash:latest",
+ Results: []v1.StepResult{{
+ Name: "current-date-human-readable",
+ Description: "The current date in humand readable format",
+ }},
+ Script: "#!/usr/bin/env bash\ndate | tee $(step.results['current-date-human-readable'].path)",
+ }},
+ }
+ want := applyMutation(ts, func(spec *v1.TaskSpec) {
+ spec.Steps[0].Script = "#!/usr/bin/env bash\ndate +%s | tee /tekton/steps/step-print-date-unix-timestamp/results/current.date.unix.timestamp"
+ spec.Steps[0].Args[0] = "/tekton/steps/step-print-date-unix-timestamp/results/current.date.unix.timestamp"
+ spec.Steps[1].Script = "#!/usr/bin/env bash\ndate | tee /tekton/steps/step-print-date-human-readable/results/current-date-human-readable"
+ spec.Steps[2].Script = "#!/usr/bin/env bash\ndate | tee /tekton/steps/step-print-date-human-readable-again/results/current-date-human-readable"
+ })
+ got := resources.ApplyResults(ts)
if d := cmp.Diff(want, got); d != "" {
t.Errorf("ApplyTaskResults() got diff %s", diff.PrintWantGot(d))
}
diff --git a/pkg/reconciler/taskrun/resources/taskspec.go b/pkg/reconciler/taskrun/resources/taskspec.go
index cf24b19b977..255dc399c58 100644
--- a/pkg/reconciler/taskrun/resources/taskspec.go
+++ b/pkg/reconciler/taskrun/resources/taskspec.go
@@ -131,6 +131,9 @@ func GetStepActionsData(ctx context.Context, taskSpec v1.TaskSpec, taskRun *v1.T
if len(stepActionSpec.VolumeMounts) > 0 {
s.VolumeMounts = stepActionSpec.VolumeMounts
}
+ if len(stepActionSpec.Results) > 0 {
+ s.Results = stepActionSpec.Results
+ }
if err := validateStepHasStepActionParameters(s.Params, stepActionSpec.Params); err != nil {
return nil, err
}
diff --git a/pkg/reconciler/taskrun/resources/taskspec_test.go b/pkg/reconciler/taskrun/resources/taskspec_test.go
index 33e11ae9e9b..1a3479a47ad 100644
--- a/pkg/reconciler/taskrun/resources/taskspec_test.go
+++ b/pkg/reconciler/taskrun/resources/taskspec_test.go
@@ -421,6 +421,41 @@ func TestGetStepActionsData(t *testing.T) {
Value: "value1",
}},
}},
+ }, {
+ name: "step-action-with-step-result",
+ tr: &v1.TaskRun{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "mytaskrun",
+ Namespace: "default",
+ },
+ Spec: v1.TaskRunSpec{
+ TaskSpec: &v1.TaskSpec{
+ Steps: []v1.Step{{
+ Ref: &v1.Ref{
+ Name: "stepActionWithScript",
+ },
+ }},
+ },
+ },
+ },
+ stepAction: &v1alpha1.StepAction{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "stepActionWithScript",
+ Namespace: "default",
+ },
+ Spec: v1alpha1.StepActionSpec{
+ Image: "myimage",
+ Script: "ls",
+ Results: []v1.StepResult{{
+ Name: "foo",
+ }},
+ },
+ },
+ want: []v1.Step{{
+ Image: "myimage",
+ Script: "ls",
+ Results: []v1.StepResult{{Name: "foo", Type: "string"}},
+ }},
}, {
name: "inline and ref StepAction",
tr: &v1.TaskRun{
diff --git a/pkg/reconciler/taskrun/taskrun.go b/pkg/reconciler/taskrun/taskrun.go
index a8463fe2b25..c43a0770268 100644
--- a/pkg/reconciler/taskrun/taskrun.go
+++ b/pkg/reconciler/taskrun/taskrun.go
@@ -847,7 +847,7 @@ func applyParamsContextsResultsAndWorkspaces(ctx context.Context, tr *v1.TaskRun
ts = resources.ApplyContexts(ts, rtr.TaskName, tr)
// Apply task result substitution
- ts = resources.ApplyTaskResults(ts)
+ ts = resources.ApplyResults(ts)
// Apply step exitCode path substitution
ts = resources.ApplyStepExitCodePath(ts)
diff --git a/pkg/reconciler/taskrun/taskrun_test.go b/pkg/reconciler/taskrun/taskrun_test.go
index 5c870577793..3796d3757cf 100644
--- a/pkg/reconciler/taskrun/taskrun_test.go
+++ b/pkg/reconciler/taskrun/taskrun_test.go
@@ -3114,6 +3114,37 @@ spec:
Args: []string{"taskrun string param", "taskrun", "array", "taskrun", "array", "param", "taskrun object param"},
Name: "step1",
}},
+ }, {
+ name: "step results",
+ taskRun: parse.MustParseV1TaskRun(t, `
+metadata:
+ name: taskrun-with-step-results
+ namespace: foo
+spec:
+ taskSpec:
+ steps:
+ - ref:
+ name: stepAction
+ name: step1
+`),
+ stepAction: parse.MustParseV1alpha1StepAction(t, `
+metadata:
+ name: stepAction
+ namespace: foo
+spec:
+ results:
+ - name: result
+ image: myImage
+ command: ["echo"]
+ args: ["hi", ">>", "$(step.results.result.path)"]
+`),
+ want: []v1.Step{{
+ Image: "myImage",
+ Command: []string{"echo"},
+ Args: []string{"hi", ">>", "/tekton/steps/step-step1/results/result"},
+ Name: "step1",
+ Results: []v1.StepResult{{Name: "result", Type: "string"}},
+ }},
}, {
name: "params from taskspec",
taskRun: parse.MustParseV1TaskRun(t, `
@@ -3204,8 +3235,6 @@ spec:
type: string
default:
key: "stepaction object param"
- results:
- - name: result
image: myImage
command: ["echo"]
args: ["$(params.string-param)", "$(params.array-param[0])", "$(params.array-param[1])", "$(params.array-param[*])", "$(params.object-param.key)"]
diff --git a/pkg/result/result.go b/pkg/result/result.go
index cfcbc3e90a2..515fe9a6025 100644
--- a/pkg/result/result.go
+++ b/pkg/result/result.go
@@ -33,6 +33,8 @@ const (
InternalTektonResultType = 3
// UnknownResultType default unknown result type value
UnknownResultType = 10
+ // StepResultType default step result value
+ StepResultType ResultType = 4
)
// RunResult is used to write key/value pairs to TaskRun pod termination messages.
@@ -80,6 +82,8 @@ func (r *ResultType) UnmarshalJSON(data []byte) error {
}
switch asString {
+ case "StepResult":
+ *r = StepResultType
case "TaskRunResult":
*r = TaskRunResultType
case "InternalTektonResult":
diff --git a/pkg/result/result_test.go b/pkg/result/result_test.go
index ee2e5c366c4..41006aed079 100644
--- a/pkg/result/result_test.go
+++ b/pkg/result/result_test.go
@@ -33,6 +33,10 @@ func TestRunResult_UnmarshalJSON(t *testing.T) {
name: "type defined as string - TaskRunResult",
data: "{\"key\":\"resultName\",\"value\":\"resultValue\", \"type\": \"TaskRunResult\"}",
pr: RunResult{Key: "resultName", Value: "resultValue", ResultType: TaskRunResultType},
+ }, {
+ name: "type defined as string - StepResult",
+ data: "{\"key\":\"resultName\",\"value\":\"resultValue\", \"type\": \"StepResult\"}",
+ pr: RunResult{Key: "resultName", Value: "resultValue", ResultType: StepResultType},
},
{
name: "type defined as string - InternalTektonResult",
diff --git a/test/clients.go b/test/clients.go
index b3bcec3bd19..0a0a4f21400 100644
--- a/test/clients.go
+++ b/test/clients.go
@@ -68,6 +68,7 @@ type clients struct {
V1TaskClient v1.TaskInterface
V1TaskRunClient v1.TaskRunInterface
V1PipelineRunClient v1.PipelineRunInterface
+ V1alpha1StepActionClient v1alpha1.StepActionInterface
}
// newClients instantiates and returns several clientsets required for making requests to the
@@ -109,5 +110,6 @@ func newClients(t *testing.T, configPath, clusterName, namespace string) *client
c.V1TaskClient = cs.TektonV1().Tasks(namespace)
c.V1TaskRunClient = cs.TektonV1().TaskRuns(namespace)
c.V1PipelineRunClient = cs.TektonV1().PipelineRuns(namespace)
+ c.V1alpha1StepActionClient = cs.TektonV1alpha1().StepActions(namespace)
return c
}
diff --git a/test/stepaction_results_test.go b/test/stepaction_results_test.go
new file mode 100644
index 00000000000..5c3fafa50a2
--- /dev/null
+++ b/test/stepaction_results_test.go
@@ -0,0 +1,258 @@
+//go:build e2e
+// +build e2e
+
+/*
+Copyright 2022 The Tekton Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package test
+
+import (
+ "context"
+ "fmt"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+ "github.com/tektoncd/pipeline/pkg/apis/config"
+ v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
+ v1alpha1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1"
+ "github.com/tektoncd/pipeline/test/parse"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "knative.dev/pkg/system"
+ knativetest "knative.dev/pkg/test"
+)
+
+var (
+ ignoreProvenance = cmpopts.IgnoreFields(v1.TaskRunStatusFields{}, "Provenance")
+ requireEnableStepActionsGate = map[string]string{
+ "enable-step-actions": "true",
+ }
+)
+
+func TestStepResultsStepActions(t *testing.T) {
+ featureFlags := getFeatureFlagsBaseOnAPIFlag(t)
+ previousResultExtractionMethod := featureFlags.ResultExtractionMethod
+
+ type tests struct {
+ name string
+ taskRunFunc func(*testing.T, string) (*v1.TaskRun, *v1.TaskRun)
+ stepActionFunc func(*testing.T, string) *v1alpha1.StepAction
+ }
+
+ tds := []tests{{
+ name: "step results",
+ taskRunFunc: getStepActionsTaskRun,
+ stepActionFunc: getStepAction,
+ }}
+
+ for _, td := range tds {
+ td := td
+ t.Run(td.name, func(t *testing.T) {
+ ctx := context.Background()
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ c, namespace := setUpStepActionsResults(ctx, t)
+
+ // reset configmap
+ knativetest.CleanupOnInterrupt(func() { resetStepActionsResults(ctx, t, c, previousResultExtractionMethod) }, t.Logf)
+ defer resetSidecarLogs(ctx, t, c, previousResultExtractionMethod)
+
+ knativetest.CleanupOnInterrupt(func() { tearDown(ctx, t, c, namespace) }, t.Logf)
+ defer tearDown(ctx, t, c, namespace)
+
+ t.Logf("Setting up test resources for %q test in namespace %s", td.name, namespace)
+ taskRun, expectedResolvedTaskRun := td.taskRunFunc(t, namespace)
+ stepAction := td.stepActionFunc(t, namespace)
+
+ trName := taskRun.Name
+
+ _, err := c.V1alpha1StepActionClient.Create(ctx, stepAction, metav1.CreateOptions{})
+ if err != nil {
+ t.Fatalf("Failed to create StepAction : %s", err)
+ }
+
+ _, err = c.V1TaskRunClient.Create(ctx, taskRun, metav1.CreateOptions{})
+ if err != nil {
+ t.Fatalf("Failed to create TaskRun `%s`: %s", trName, err)
+ }
+
+ t.Logf("Waiting for TaskRun %s in namespace %s to complete", trName, namespace)
+ if err := WaitForTaskRunState(ctx, c, trName, TaskRunSucceed(trName), "TaskRunSuccess", v1Version); err != nil {
+ t.Fatalf("Error waiting for TaskRun %s to finish: %s", trName, err)
+ }
+ taskrun, _ := c.V1TaskRunClient.Get(ctx, trName, metav1.GetOptions{})
+ d := cmp.Diff(expectedResolvedTaskRun, taskrun,
+ ignoreTypeMeta,
+ ignoreObjectMeta,
+ ignoreCondition,
+ ignoreTaskRunStatus,
+ ignoreContainerStates,
+ ignoreProvenance,
+ ignoreSidecarState,
+ ignoreStepState,
+ )
+ if d != "" {
+ t.Fatalf(`The expected taskrun does not match created taskrun. Here is the diff: %v`, d)
+ }
+
+ t.Logf("Successfully finished test %q", td.name)
+ })
+ }
+}
+
+func getStepAction(t *testing.T, namespace string) *v1alpha1.StepAction {
+ t.Helper()
+ return parse.MustParseV1alpha1StepAction(t, fmt.Sprintf(`
+metadata:
+ name: step-action
+ namespace: %s
+spec:
+ results:
+ - name: result1
+ type: string
+ image: alpine
+ script: |
+ echo -n step-action >> $(step.results.result1.path)
+`, namespace))
+}
+
+func getStepActionsTaskRun(t *testing.T, namespace string) (*v1.TaskRun, *v1.TaskRun) {
+ t.Helper()
+ taskRun := parse.MustParseV1TaskRun(t, fmt.Sprintf(`
+metadata:
+ name: step-results-task-run
+ namespace: %s
+spec:
+ taskSpec:
+ steps:
+ - name: step1
+ image: alpine
+ script: |
+ echo -n inlined-step >> $(step.results.result1.path)
+ results:
+ - name: result1
+ type: string
+ - name: step2
+ ref:
+ name: step-action
+ results:
+ - name: inlined-step-result
+ type: string
+ value: $(steps.step1.results.result1)
+ - name: step-action-result
+ type: string
+ value: $(steps.step2.results.result1)
+`, namespace))
+
+ expectedTaskRun := parse.MustParseV1TaskRun(t, fmt.Sprintf(`
+metadata:
+ name: step-results-task-run
+ namespace: %s
+spec:
+ serviceAccountName: default
+ timeout: 1h
+ taskSpec:
+ steps:
+ - name: step1
+ image: alpine
+ script: |
+ echo -n inlined-step >> $(step.results.result1.path)
+ results:
+ - name: result1
+ type: string
+ - name: step2
+ ref:
+ name: step-action
+ results:
+ - name: inlined-step-result
+ type: string
+ value: $(steps.step1.results.result1)
+ - name: step-action-result
+ type: string
+ value: $(steps.step2.results.result1)
+status:
+ conditions:
+ - type: "Succeeded"
+ status: "True"
+ reason: "Succeeded"
+ podName: step-results-task-run-pod
+ taskSpec:
+ steps:
+ - name: step1
+ image: alpine
+ results:
+ - name: result1
+ type: string
+ script: |
+ echo -n inlined-step >> /tekton/steps/step-step1/results/result1
+ - name: step2
+ image: alpine
+ results:
+ - name: result1
+ type: string
+ script: |
+ echo -n step-action >> /tekton/steps/step-step2/results/result1
+ results:
+ - name: inlined-step-result
+ type: string
+ value: $(steps.step1.results.result1)
+ - name: step-action-result
+ type: string
+ value: $(steps.step2.results.result1)
+ results:
+ - name: inlined-step-result
+ type: string
+ value: inlined-step
+ - name: step-action-result
+ type: string
+ value: step-action
+ steps:
+ - name: step1
+ container: step-step1
+ results:
+ - name: result1
+ type: string
+ value: inlined-step
+ - name: step2
+ container: step-step2
+ results:
+ - name: result1
+ type: string
+ value: step-action
+`, namespace))
+ return taskRun, expectedTaskRun
+}
+
+func setUpStepActionsResults(ctx context.Context, t *testing.T) (*clients, string) {
+ t.Helper()
+ c, ns := setup(ctx, t, requireAllGates(requireEnableStepActionsGate))
+ configMapData := map[string]string{
+ "results-from": "termination-message",
+ }
+
+ if err := updateConfigMap(ctx, c.KubeClient, system.Namespace(), config.GetFeatureFlagsConfigName(), configMapData); err != nil {
+ t.Fatal(err)
+ }
+ return c, ns
+}
+
+func resetStepActionsResults(ctx context.Context, t *testing.T, c *clients, previousResultExtractionMethod string) {
+ t.Helper()
+ if err := updateConfigMap(ctx, c.KubeClient, system.Namespace(), config.GetFeatureFlagsConfigName(), map[string]string{"results-from": previousResultExtractionMethod}); err != nil {
+ t.Fatal(err)
+ }
+}