Skip to content

Commit

Permalink
[TEP-0075]Pipeline results support object
Browse files Browse the repository at this point in the history
This is part of work in TEP-0075.
Previous to this commit, we have added support for pipeline array
results. This commit supports object results, so pipeline can emit
object results as whole or emit elements of the object from tasks.
Before this commit the pipeline level result only support string and
array.
  • Loading branch information
Yongxuanzhang committed Jul 7, 2022
1 parent 380dbd0 commit 529c979
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 47 deletions.
23 changes: 22 additions & 1 deletion docs/pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,28 @@ results:

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

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

```yaml
results:
- name: array-results
type: array
description: whole array
value: $(tasks.task1.results.array-results[*])
- name: array-indexing-results
type: string
description: array element
value: $(tasks.task1.results.array-results[1])
- name: object-results
type: object
description: whole object
value: $(tasks.task2.results.object-results[*])
- name: object-element
type: string
description: object element
value: $(tasks.task2.results.object-results.foo)
```


A `Pipeline Result` is not emitted if any of the following are true:
- A `PipelineTask` referenced by the `Pipeline Result` failed. The `PipelineRun` will also
Expand Down
40 changes: 0 additions & 40 deletions examples/v1beta1/pipelineruns/alpha/pipeline-array-results.yaml

This file was deleted.

65 changes: 65 additions & 0 deletions examples/v1beta1/pipelineruns/alpha/pipeline-emitting-results.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: pipelinerun-array-indexing-results
spec:
pipelineSpec:
tasks:
- name: task1
taskSpec:
results:
- name: array-results
type: array
description: The array results
steps:
- name: write-array
image: bash:latest
script: |
#!/usr/bin/env bash
echo -n "[\"1\",\"2\",\"3\"]" | tee $(results.array-results.path)
- name: task2
taskSpec:
results:
- name: object-results
type: object
description: The object results
properties:
foo: {
type: string
}
hello: {
type: string
}
steps:
- name: write-array
image: bash:latest
script: |
#!/usr/bin/env bash
echo -n "{\"foo\":\"bar\",\"hello\":\"world\"}" | tee $(results.object-results.path)
results:
- name: array-results
type: array
description: whole array
value: $(tasks.task1.results.array-results[*])
- name: array-results-from-array-indexing-and-object-elements
type: array
description: whole array
value: ["$(tasks.task1.results.array-results[0])", "$(tasks.task2.results.object-results.foo)"]
- name: array-indexing-results
type: string
description: array element
value: $(tasks.task1.results.array-results[1])
- name: object-results
type: object
description: whole object
value: $(tasks.task2.results.object-results[*])
- name: object-results-from-array-indexing-and-object-elements
type: object
description: whole object
value:
key1: $(tasks.task1.results.array-results[1])
key2: $(tasks.task2.results.object-results.hello)
- name: object-element
type: string
description: object element
value: $(tasks.task2.results.object-results.foo)
6 changes: 6 additions & 0 deletions pkg/apis/pipeline/v1beta1/resultref.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ func GetVarSubstitutionExpressionsForParam(param Param) ([]string, bool) {
// GetVarSubstitutionExpressionsForPipelineResult extracts all the value between "$(" and ")"" for a pipeline result
func GetVarSubstitutionExpressionsForPipelineResult(result PipelineResult) ([]string, bool) {
allExpressions := validateString(result.Value.StringVal)
for _, v := range result.Value.ArrayVal {
allExpressions = append(allExpressions, validateString(v)...)
}
for _, v := range result.Value.ObjectVal {
allExpressions = append(allExpressions, validateString(v)...)
}
return allExpressions, len(allExpressions) != 0
}

Expand Down
37 changes: 31 additions & 6 deletions pkg/reconciler/pipelinerun/resources/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import (
"github.com/tektoncd/pipeline/pkg/substitution"
)

const (
resultsParseNumber = 4
objectElementResultsParseNumber = 5
)

// ApplyParameters applies the params from a PipelineRun.Params to a PipelineSpec.
func ApplyParameters(ctx context.Context, p *v1beta1.PipelineSpec, pr *v1beta1.PipelineRun) *v1beta1.PipelineSpec {
// This assumes that the PipelineRun inputs have been validated against what the Pipeline requests.
Expand Down Expand Up @@ -237,10 +242,12 @@ func ApplyTaskResultsToPipelineResults(
results []v1beta1.PipelineResult,
taskRunResults map[string][]v1beta1.TaskRunResult,
customTaskResults map[string][]v1alpha1.RunResult) []v1beta1.PipelineRunResult {

// TODO(#4723): Validate array and object results.
// TODO(#4723): Test from pipelinerun reconciler
var runResults []v1beta1.PipelineRunResult
stringReplacements := map[string]string{}
arrayReplacements := map[string][]string{}
objectReplacements := map[string]map[string]string{}
for _, pipelineResult := range results {
variablesInPipelineResult, _ := v1beta1.GetVarSubstitutionExpressionsForPipelineResult(pipelineResult)
validPipelineResult := true
Expand All @@ -251,10 +258,19 @@ func ApplyTaskResultsToPipelineResults(
if _, isMemoized := arrayReplacements[variable]; isMemoized {
continue
}
// TODO(#4723): Need to consider object case.
// e.g.: tasks.taskname.results.resultname.objectkey
if _, isMemoized := objectReplacements[variable]; isMemoized {
continue
}
variableParts := strings.Split(variable, ".")
if len(variableParts) == 4 && variableParts[0] == "tasks" && variableParts[2] == "results" {
if variableParts[0] != v1beta1.ResultTaskPart || variableParts[2] != v1beta1.ResultResultPart {
validPipelineResult = false
continue
}
switch len(variableParts) {
// For string result: tasks.<taskName>.results.<objectResultName>
// For array result: tasks.<taskName>.results.<objectResultName>[*], tasks.<taskName>.results.<objectResultName>[i]
// For object result: tasks.<taskName>.results.<objectResultName>[*],
case resultsParseNumber:
taskName, resultName := variableParts[1], variableParts[3]
resultName, stringIdx := v1beta1.ParseResultName(resultName)
if resultValue := taskResultValue(taskName, resultName, taskRunResults); resultValue != nil {
Expand All @@ -268,19 +284,28 @@ func ApplyTaskResultsToPipelineResults(
} else {
arrayReplacements[v1beta1.StripStarVarSubExpression(variable)] = resultValue.ArrayVal
}
case v1beta1.ParamTypeObject:
objectReplacements[v1beta1.StripStarVarSubExpression(variable)] = resultValue.ObjectVal
}
} else if resultValue := runResultValue(taskName, resultName, customTaskResults); resultValue != nil {
stringReplacements[variable] = *resultValue
} else {
validPipelineResult = false
}
} else {
// For object type result: tasks.<taskName>.results.<objectResultName>.<individualAttribute>
case objectElementResultsParseNumber:
taskName, resultName, objectKey := variableParts[1], variableParts[3], variableParts[4]
resultName, _ = v1beta1.ParseResultName(resultName)
if resultValue := taskResultValue(taskName, resultName, taskRunResults); resultValue != nil {
stringReplacements[variable] = resultValue.ObjectVal[objectKey]
}
default:
validPipelineResult = false
}
}
if validPipelineResult {
finalValue := pipelineResult.Value
finalValue.ApplyReplacements(stringReplacements, arrayReplacements, nil)
finalValue.ApplyReplacements(stringReplacements, arrayReplacements, objectReplacements)
runResults = append(runResults, v1beta1.PipelineRunResult{
Name: pipelineResult.Name,
Value: finalValue,
Expand Down
105 changes: 105 additions & 0 deletions pkg/reconciler/pipelinerun/resources/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,111 @@ func TestApplyTaskResultsToPipelineResults(t *testing.T) {
Name: "pipeline-result-1",
Value: *v1beta1.NewArrayOrString("rae"),
}},
}, {
description: "apply-object-results",
results: []v1beta1.PipelineResult{{
Name: "pipeline-result-1",
Value: *v1beta1.NewArrayOrString("$(tasks.pt1.results.foo[*])"),
}},
taskResults: map[string][]v1beta1.TaskRunResult{
"pt1": {
{
Name: "foo",
Value: *v1beta1.NewObject(map[string]string{
"key1": "val1",
"key2": "val2",
}),
},
},
},
expected: []v1beta1.PipelineRunResult{{
Name: "pipeline-result-1",
Value: *v1beta1.NewObject(map[string]string{
"key1": "val1",
"key2": "val2",
}),
}},
}, {
description: "object-results-from-array-indexing-and-object-element",
results: []v1beta1.PipelineResult{{
Name: "pipeline-result-1",
Value: *v1beta1.NewObject(map[string]string{
"pkey1": "$(tasks.pt1.results.foo.key1)",
"pkey2": "$(tasks.pt2.results.bar[1])",
}),
}},
taskResults: map[string][]v1beta1.TaskRunResult{
"pt1": {
{
Name: "foo",
Value: *v1beta1.NewObject(map[string]string{
"key1": "val1",
"key2": "val2",
}),
},
},
"pt2": {
{
Name: "bar",
Value: *v1beta1.NewArrayOrString("do", "rae", "mi"),
},
},
},
expected: []v1beta1.PipelineRunResult{{
Name: "pipeline-result-1",
Value: *v1beta1.NewObject(map[string]string{
"pkey1": "val1",
"pkey2": "rae",
}),
}},
}, {
description: "array-results-from-array-indexing-and-object-element",
results: []v1beta1.PipelineResult{{
Name: "pipeline-result-1",
Value: *v1beta1.NewArrayOrString("$(tasks.pt1.results.foo.key1)", "$(tasks.pt2.results.bar[1])"),
}},
taskResults: map[string][]v1beta1.TaskRunResult{
"pt1": {
{
Name: "foo",
Value: *v1beta1.NewObject(map[string]string{
"key1": "val1",
"key2": "val2",
}),
},
},
"pt2": {
{
Name: "bar",
Value: *v1beta1.NewArrayOrString("do", "rae", "mi"),
},
},
},
expected: []v1beta1.PipelineRunResult{{
Name: "pipeline-result-1",
Value: *v1beta1.NewArrayOrString("val1", "rae"),
}},
}, {
description: "apply-object-element",
results: []v1beta1.PipelineResult{{
Name: "pipeline-result-1",
Value: *v1beta1.NewArrayOrString("$(tasks.pt1.results.foo.key1)"),
}},
taskResults: map[string][]v1beta1.TaskRunResult{
"pt1": {
{
Name: "foo",
Value: *v1beta1.NewObject(map[string]string{
"key1": "val1",
"key2": "val2",
}),
},
},
},
expected: []v1beta1.PipelineRunResult{{
Name: "pipeline-result-1",
Value: *v1beta1.NewArrayOrString("val1"),
}},
}, {
description: "multiple-array-results-multiple-successful-tasks ",
results: []v1beta1.PipelineResult{{
Expand Down

0 comments on commit 529c979

Please sign in to comment.