Skip to content

Commit

Permalink
[TEP-0076]Support Results Array Indexing
Browse files Browse the repository at this point in the history
This is part of work in TEP-0076.
This commit provides the support to refer array indexing results.
Previous this commit we support emitting array results so users can
write array results to task level, but we cannot pass array results via
index between tasks within one pipeline. This commit adds the support for this.
  • Loading branch information
Yongxuanzhang committed May 26, 2022
1 parent e24df83 commit dc30ce8
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 3 deletions.
3 changes: 3 additions & 0 deletions docs/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ For instructions on using variable substitutions see the relevant section of [th
| `tasks.<taskName>.results.<resultName>` | The value of the `Task's` result. Can alter `Task` execution order within a `Pipeline`.) |
| `tasks.<taskName>.results['<resultName>']` | (see above)) |
| `tasks.<taskName>.results["<resultName>"]` | (see above)) |
| `tasks.<taskName>.results.<resultName>[i]` | The ith value of the `Task's` array result. Can alter `Task` execution order within a `Pipeline`.) |
| `tasks.<taskName>.results['<resultName>'][i]` | (see above)) |
| `tasks.<taskName>.results["<resultName>"][i]` | (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
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: pipelinerun-array-indexing-results
spec:
pipelineSpec:
tasks:
- name: task1
taskSpec:
results:
- name: array-results
description: The array resluts
steps:
- name: write-array
image: bash:latest
script: |
#!/usr/bin/env bash
echo -n "[\"1\",\"2\",\"3\"]" | tee $(results.array-results.path)
- name: task2
params:
- name: foo
value: "$(tasks.task1.results.array-results[1])"
taskSpec:
params:
- name: foo
type: string
default: defaultparam
steps:
- name: print-param
image: ubuntu
script: |
#!/bin/bash
VALUE=$(params.foo)
EXPECTED="2"
diff=$(diff <(printf "%s\n" "${VALUE[@]}") <(printf "%s\n" "${EXPECTED[@]}"))
if [[ -z "$diff" ]]; then
echo "TRUE"
else
echo "FALSE"
fi
8 changes: 7 additions & 1 deletion pkg/apis/pipeline/v1beta1/resultref.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,18 @@ const (
// ResultResultPart Constant used to define the "results" part of a pipeline result reference
ResultResultPart = "results"
// TODO(#2462) use one regex across all substitutions
variableSubstitutionFormat = `\$\([_a-zA-Z0-9.-]+(\.[_a-zA-Z0-9.-]+)*\)`
// variableSubstitutionFormat matches format like $result.resultname, $result.resultname[int] and $result.resultname[*]
//variableSubstitutionFormat = `\$\([_a-zA-Z0-9.-]+(\.[_a-zA-Z0-9.-]+)*\[([_a-zA-Z0-9.-]+)*\]?(\[([0-9])*\*?\])?\)`
variableSubstitutionFormat = `\$\([_a-zA-Z0-9.-]+(\.[_a-zA-Z0-9.-]+)*(\[([0-9])*\*?\])?\)`
// excludeArrayIndexing will replace all `[int]` and `[*]` for parseExpression to extract result name
excludeArrayIndexing = `\[([0-9])*\*?\]`
// ResultNameFormat Constant used to define the the regex Result.Name should follow
ResultNameFormat = `^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$`
)

var variableSubstitutionRegex = regexp.MustCompile(variableSubstitutionFormat)
var resultNameFormatRegex = regexp.MustCompile(ResultNameFormat)
var excludeArrayIndexingRegex = regexp.MustCompile(excludeArrayIndexing)

// NewResultRefs extracts all ResultReferences from a param or a pipeline result.
// If the ResultReference can be extracted, they are returned. Expressions which are not
Expand Down Expand Up @@ -127,6 +132,7 @@ func parseExpression(substitutionExpression string) (string, string, error) {
if len(subExpressions) != 4 || subExpressions[0] != ResultTaskPart || subExpressions[2] != ResultResultPart {
return "", "", fmt.Errorf("Must be of the form %q", resultExpressionFormat)
}
subExpressions[3] = excludeArrayIndexingRegex.ReplaceAllString(subExpressions[3], "")
return subExpressions[1], subExpressions[3], nil
}

Expand Down
176 changes: 176 additions & 0 deletions pkg/reconciler/pipelinerun/resources/apply_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,36 @@ func TestApplyTaskResults_MinimalExpression(t *testing.T) {
}},
},
}},
}, {
name: "Test array indexing result substitution on minimal variable substitution expression - params",
resolvedResultRefs: ResolvedResultRefs{{
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
ResultReference: v1beta1.ResultRef{
PipelineTask: "aTask",
Result: "a.Result",
},
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["a.Result"][1])`),
}},
},
}},
want: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
Params: []v1beta1.Param{{
Name: "bParam",
Value: *v1beta1.NewArrayOrString("arrayResultValueTwo"),
}},
},
}},
}, {
name: "Test result substitution on minimal variable substitution expression - when expressions",
resolvedResultRefs: ResolvedResultRefs{{
Expand Down Expand Up @@ -433,6 +463,38 @@ func TestApplyTaskResults_MinimalExpression(t *testing.T) {
}},
},
}},
}, {
name: "Test array indexing result substitution on minimal variable substitution expression - when expressions",
resolvedResultRefs: ResolvedResultRefs{{
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
ResultReference: v1beta1.ResultRef{
PipelineTask: "aTask",
Result: "aResult",
},
FromTaskRun: "aTaskRun",
}},
targets: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
WhenExpressions: []v1beta1.WhenExpression{{
Input: "$(tasks.aTask.results.aResult[1])",
Operator: selection.In,
Values: []string{"$(tasks.aTask.results.aResult[0])"},
}},
},
}},
want: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
WhenExpressions: []v1beta1.WhenExpression{{
Input: "arrayResultValueTwo",
Operator: selection.In,
Values: []string{"arrayResultValueOne"},
}},
},
}},
}} {
t.Run(tt.name, func(t *testing.T) {
ApplyTaskResults(tt.targets, tt.resolvedResultRefs)
Expand Down Expand Up @@ -479,6 +541,36 @@ func TestApplyTaskResults_EmbeddedExpression(t *testing.T) {
}},
},
}},
}, {
name: "Test array indexing result substitution on embedded variable substitution expression - params",
resolvedResultRefs: ResolvedResultRefs{{
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
ResultReference: v1beta1.ResultRef{
PipelineTask: "aTask",
Result: "aResult",
},
FromTaskRun: "aTaskRun",
}},
targets: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
Params: []v1beta1.Param{{
Name: "bParam",
Value: *v1beta1.NewArrayOrString("Result value --> $(tasks.aTask.results.aResult[0])"),
}},
},
}},
want: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
Params: []v1beta1.Param{{
Name: "bParam",
Value: *v1beta1.NewArrayOrString("Result value --> arrayResultValueOne"),
}},
},
}},
}, {
name: "Test result substitution on embedded variable substitution expression - when expressions",
resolvedResultRefs: ResolvedResultRefs{{
Expand Down Expand Up @@ -513,6 +605,40 @@ func TestApplyTaskResults_EmbeddedExpression(t *testing.T) {
}},
},
}},
}, {
name: "Test array indexing result substitution on embedded variable substitution expression - when expressions",
resolvedResultRefs: ResolvedResultRefs{{
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
ResultReference: v1beta1.ResultRef{
PipelineTask: "aTask",
Result: "aResult",
},
FromTaskRun: "aTaskRun",
}},
targets: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
WhenExpressions: []v1beta1.WhenExpression{
{
Input: "Result value --> $(tasks.aTask.results.aResult[1])",
Operator: selection.In,
Values: []string{"Result value --> $(tasks.aTask.results.aResult[0])"},
},
},
},
}},
want: PipelineRunState{{
PipelineTask: &v1beta1.PipelineTask{
Name: "bTask",
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
WhenExpressions: []v1beta1.WhenExpression{{
Input: "Result value --> arrayResultValueTwo",
Operator: selection.In,
Values: []string{"Result value --> arrayResultValueOne"},
}},
},
}},
}} {
t.Run(tt.name, func(t *testing.T) {
ApplyTaskResults(tt.targets, tt.resolvedResultRefs)
Expand Down Expand Up @@ -579,6 +705,56 @@ func TestApplyTaskResults_Conditions(t *testing.T) {
}}},
}},
}},
}, {
name: "Test result substitution in condition parameter",
resolvedResultRefs: ResolvedResultRefs{{
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
ResultReference: v1beta1.ResultRef{
PipelineTask: "aTask",
Result: "aResult",
},
FromTaskRun: "aTaskRun",
}},
targets: PipelineRunState{{
ResolvedConditionChecks: TaskConditionCheckState{{
ConditionRegisterName: "always-true-0",
ConditionCheckName: "test",
Condition: &v1alpha1.Condition{
ObjectMeta: metav1.ObjectMeta{
Name: "always-true",
},
Spec: v1alpha1.ConditionSpec{
Check: v1beta1.Step{},
},
},
ResolvedResources: map[string]*resourcev1alpha1.PipelineResource{},
PipelineTaskCondition: &v1beta1.PipelineTaskCondition{
Params: []v1beta1.Param{{
Name: "cParam",
Value: *v1beta1.NewArrayOrString("Result value --> $(tasks.aTask.results.aResult[0])"),
}},
},
}},
}},
want: PipelineRunState{{
ResolvedConditionChecks: TaskConditionCheckState{{
ConditionRegisterName: "always-true-0",
ConditionCheckName: "test",
Condition: &v1alpha1.Condition{
ObjectMeta: metav1.ObjectMeta{
Name: "always-true",
},
Spec: v1alpha1.ConditionSpec{
Check: v1beta1.Step{},
},
},
ResolvedResources: map[string]*resourcev1alpha1.PipelineResource{},
PipelineTaskCondition: &v1beta1.PipelineTaskCondition{Params: []v1beta1.Param{{
Name: "cParam",
Value: *v1beta1.NewArrayOrString("Result value --> arrayResultValueOne"),
}}},
}},
}},
}} {
t.Run(tt.name, func(t *testing.T) {
ApplyTaskResults(tt.targets, tt.resolvedResultRefs)
Expand Down
21 changes: 19 additions & 2 deletions pkg/reconciler/pipelinerun/resources/resultrefresolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,17 @@ func findTaskResultForParam(taskRun *v1beta1.TaskRun, reference *v1beta1.ResultR
func (rs ResolvedResultRefs) getStringReplacements() map[string]string {
replacements := map[string]string{}
for _, r := range rs {
for _, target := range r.getReplaceTarget() {
replacements[target] = r.Value.StringVal
switch r.Value.Type {
case v1beta1.ParamTypeArray:
for i := 0; i < len(r.Value.ArrayVal); i++ {
for _, target := range r.getReplaceTargetfromArrayIndex(i) {
replacements[target] = r.Value.ArrayVal[i]
}
}
default:
for _, target := range r.getReplaceTarget() {
replacements[target] = r.Value.StringVal
}
}
}
return replacements
Expand All @@ -201,3 +210,11 @@ func (r *ResolvedResultRef) getReplaceTarget() []string {
fmt.Sprintf("%s.%s.%s['%s']", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result),
}
}

func (r *ResolvedResultRef) getReplaceTargetfromArrayIndex(idx int) []string {
return []string{
fmt.Sprintf("%s.%s.%s.%s[%d]", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, idx),
fmt.Sprintf("%s.%s.%s[%q][%d]", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, idx),
fmt.Sprintf("%s.%s.%s['%s'][%d]", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, idx),
}
}

0 comments on commit dc30ce8

Please sign in to comment.