Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[TEP-0075] Support Object Results substitution #5083

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions docs/pipelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -909,9 +909,14 @@ Sharing `Results` between `Tasks` in a `Pipeline` happens via
[variable substitution](variables.md#variables-available-in-a-pipeline) - one `Task` emits
a `Result` and another receives it as a `Parameter` with a variable such as
`$(tasks.<task-name>.results.<result-name>)`. Array `Results` is supported as alpha feature and
can be referer as `$(tasks.<task-name>.results.<result-name>[*])`.
can be referred as `$(tasks.<task-name>.results.<result-name>[*])`. Array indexing can be rererred
as `$(tasks.<task-name>.results.<result-name>[i])` where `i` is the index.
Object `Results` is supported as alpha feature and can be referred as
`$(tasks.<task-name>.results.<result-name>[*])`, object elements can be referred as
`$(tasks.<task-name>.results.<result-name>.key)`.

**Note:** Array `Result` cannot be used in `script`.
**Note:** Whole Array and Object `Results` cannot be referred in `script` and `args`.
**Note:** `Matrix` does not support `object` and `array` results.

When one `Task` receives the `Results` of another, there is a dependency created between those
two `Tasks`. In order for the receiving `Task` to get data from another `Task's` `Result`,
Expand All @@ -928,6 +933,12 @@ params:
value: "$(tasks.checkout-source.results.commit)"
- name: array-params
value: "$(tasks.checkout-source.results.array-results[*])"
- name: array-indexing-params
value: "$(tasks.checkout-source.results.array-results[1])"
- name: object-params
value: "$(tasks.checkout-source.results.object-results[*])"
- name: object-element-params
value: "$(tasks.checkout-source.results.object-results.objectkey)"
```

**Note:** If `checkout-source` exits successfully without initializing `commit` `Result`,
Expand Down
3 changes: 3 additions & 0 deletions docs/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ For instructions on using variable substitutions see the relevant section of [th
| `tasks.<taskName>.results.<resultName>[*]` | The array value of the `Task's` result. Can alter `Task` execution order within a `Pipeline`. Cannot be used in `script`.) |
| `tasks.<taskName>.results['<resultName>'][*]` | (see above)) |
| `tasks.<taskName>.results["<resultName>"][*]` | (see above)) |
| `tasks.<taskName>.results.<resultName>.key` | The `key` value of the `Task's` object result. Can alter `Task` execution order within a `Pipeline`.) |
| `tasks.<taskName>.results['<resultName>'][key]` | (see above)) |
| `tasks.<taskName>.results["<resultName>"][key]` | (see above)) |
| `workspaces.<workspaceName>.bound` | Whether a `Workspace` has been bound or not. "false" if the `Workspace` declaration has `optional: true` and the Workspace binding was omitted by the PipelineRun. |
| `context.pipelineRun.name` | The name of the `PipelineRun` that this `Pipeline` is running in. |
| `context.pipelineRun.namespace` | The namespace of the `PipelineRun` that this `Pipeline` is running in. |
Expand Down
67 changes: 67 additions & 0 deletions examples/v1beta1/pipelineruns/alpha/pipeline-object-results.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: pipelinerun-object-results
spec:
pipelineSpec:
tasks:
- name: task1
taskSpec:
results:
- name: object-results
type: object
description: The object results
properties:
foo:
type: string
hello:
type: string
steps:
- name: write-object
image: bash:latest
script: |
#!/usr/bin/env bash
echo -n "{\"foo\":\"bar\",\"hello\":\"world\"}" | tee $(results.object-results.path)
- name: task2
params:
- name: whole-object
value: "$(tasks.task1.results.object-results[*])"
- name: object-element
value: "$(tasks.task1.results.object-results.hello)"
taskSpec:
params:
- name: whole-object
type: object
properties:
foo:
type: string
hello:
type: string
default: {
hello: "",
foo: "",
}
- name: object-element
type: string
default: "defaultparam1"
steps:
- name: print-object-foo
image: bash:latest
args: [
"echo",
"$(params.whole-object.foo)"
]
- name: print-object-hello
image: ubuntu
script: |
#!/bin/bash
VALUE=$(params.object-element)
EXPECTED="world"
diff=$(diff <(printf "%s\n" "${VALUE[@]}") <(printf "%s\n" "${EXPECTED[@]}"))
if [[ -z "$diff" ]]; then
echo "Get expected: ${VALUE}"
exit 0
else
echo "Want: ${EXPECTED} Got: ${VALUE}"
exit 1
fi
5 changes: 3 additions & 2 deletions pkg/reconciler/pipelinerun/resources/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,12 @@ func ApplyPipelineTaskContexts(pt *v1beta1.PipelineTask) *v1beta1.PipelineTask {
func ApplyTaskResults(targets PipelineRunState, resolvedResultRefs ResolvedResultRefs) {
stringReplacements := resolvedResultRefs.getStringReplacements()
arrayReplacements := resolvedResultRefs.getArrayReplacements()
objectReplacements := resolvedResultRefs.getObjectReplacements()
for _, resolvedPipelineRunTask := range targets {
if resolvedPipelineRunTask.PipelineTask != nil {
pipelineTask := resolvedPipelineRunTask.PipelineTask.DeepCopy()
pipelineTask.Params = replaceParamValues(pipelineTask.Params, stringReplacements, arrayReplacements, nil)
pipelineTask.Matrix = replaceParamValues(pipelineTask.Matrix, stringReplacements, arrayReplacements, nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we removing this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discussed with @ywluogg in the thread above (unresolved it now), she suggests we don't support array and object for Matrix now. The array is added in previous PR by mistake. It is out of scope of TEP 75

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it, thanks!

pipelineTask.Params = replaceParamValues(pipelineTask.Params, stringReplacements, arrayReplacements, objectReplacements)
pipelineTask.Matrix = replaceParamValues(pipelineTask.Matrix, stringReplacements, nil, nil)
pipelineTask.WhenExpressions = pipelineTask.WhenExpressions.ReplaceWhenExpressionsVariables(stringReplacements, arrayReplacements)
resolvedPipelineRunTask.PipelineTask = pipelineTask
}
Expand Down
71 changes: 71 additions & 0 deletions pkg/reconciler/pipelinerun/resources/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,77 @@ func TestApplyTaskResults_MinimalExpression(t *testing.T) {
}},
},
}},
}, {
name: "Test object result as a whole substitution - params",
resolvedResultRefs: ResolvedResultRefs{{
Value: *v1beta1.NewObject(map[string]string{
"key1": "val1",
"key2": "val2",
}),
ResultReference: v1beta1.ResultRef{
PipelineTask: "aTask",
Result: "resultName",
},
FromTaskRun: "aTaskRun",
}},
targets: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
Params: []v1beta1.Param{{
Name: "bParam",
Value: *v1beta1.NewArrayOrString(`$(tasks.aTask.results.resultName[*])`),
}},
},
}},
want: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
Params: []v1beta1.Param{{
Name: "bParam",
// index validation is done in ResolveResultRefs() before ApplyTaskResults()
Value: *v1beta1.NewObject(map[string]string{
"key1": "val1",
"key2": "val2",
}),
}},
},
}},
}, {
name: "Test object result element substitution - params",
resolvedResultRefs: ResolvedResultRefs{{
Value: *v1beta1.NewObject(map[string]string{
"key1": "val1",
"key2": "val2",
}),
ResultReference: v1beta1.ResultRef{
PipelineTask: "aTask",
Result: "resultName",
},
FromTaskRun: "aTaskRun",
}},
targets: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
Params: []v1beta1.Param{{
Name: "bParam",
Value: *v1beta1.NewArrayOrString(`$(tasks.aTask.results.resultName.key1)`),
}},
},
}},
want: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
Params: []v1beta1.Param{{
Name: "bParam",
// index validation is done in ResolveResultRefs() before ApplyTaskResults()
Value: *v1beta1.NewArrayOrString("val1"),
}},
},
}},
}, {
name: "Test result substitution on minimal variable substitution expression - matrix",
resolvedResultRefs: ResolvedResultRefs{{
Expand Down
27 changes: 27 additions & 0 deletions pkg/reconciler/pipelinerun/resources/resultrefresolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ func (rs ResolvedResultRefs) getStringReplacements() map[string]string {
replacements[target] = r.Value.ArrayVal[i]
}
}
case v1beta1.ParamTypeObject:
for key, element := range r.Value.ObjectVal {
for _, target := range r.getReplaceTargetfromObjectKey(key) {
replacements[target] = element
}
}

default:
for _, target := range r.getReplaceTarget() {
replacements[target] = r.Value.StringVal
Expand All @@ -227,6 +234,18 @@ func (rs ResolvedResultRefs) getArrayReplacements() map[string][]string {
return replacements
}

func (rs ResolvedResultRefs) getObjectReplacements() map[string]map[string]string {
replacements := map[string]map[string]string{}
for _, r := range rs {
if r.Value.Type == v1beta1.ParamType(v1beta1.ResultsTypeObject) {
for _, target := range r.getReplaceTarget() {
replacements[target] = r.Value.ObjectVal
}
}
}
return replacements
}

func (r *ResolvedResultRef) getReplaceTarget() []string {
return []string{
fmt.Sprintf("%s.%s.%s.%s", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result),
Expand All @@ -242,3 +261,11 @@ func (r *ResolvedResultRef) getReplaceTargetfromArrayIndex(idx int) []string {
fmt.Sprintf("%s.%s.%s['%s'][%d]", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, idx),
}
}

func (r *ResolvedResultRef) getReplaceTargetfromObjectKey(key string) []string {
return []string{
fmt.Sprintf("%s.%s.%s.%s.%s", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, key),
fmt.Sprintf("%s.%s.%s[%q][%s]", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, key),
fmt.Sprintf("%s.%s.%s['%s'][%s]", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, key),
}
}
2 changes: 1 addition & 1 deletion pkg/reconciler/taskrun/validate_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func wrongTypeParamsNames(params []v1beta1.Param, matrix []v1beta1.Param, needed
// to pass array result to array param, yet in yaml format this will be
// unmarshalled to string for ArrayOrString. So we need to check and skip this validation.
// Please refer issue #4879 for more details and examples.
if param.Value.Type == v1beta1.ParamTypeString && (neededParamsTypes[param.Name] == v1beta1.ParamTypeArray || (neededParamsTypes[param.Name] == v1beta1.ParamTypeObject)) && v1beta1.VariableSubstitutionRegex.MatchString(param.Value.StringVal) {
if param.Value.Type == v1beta1.ParamTypeString && (neededParamsTypes[param.Name] == v1beta1.ParamTypeArray || neededParamsTypes[param.Name] == v1beta1.ParamTypeObject) && v1beta1.VariableSubstitutionRegex.MatchString(param.Value.StringVal) {
continue
}
if param.Value.Type != neededParamsTypes[param.Name] {
Expand Down