Skip to content

Commit f41d7ea

Browse files
committed
[TEP-0076]Support Results Array Indexing
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.
1 parent e24df83 commit f41d7ea

File tree

5 files changed

+244
-3
lines changed

5 files changed

+244
-3
lines changed

docs/variables.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ For instructions on using variable substitutions see the relevant section of [th
2222
| `tasks.<taskName>.results.<resultName>` | The value of the `Task's` result. Can alter `Task` execution order within a `Pipeline`.) |
2323
| `tasks.<taskName>.results['<resultName>']` | (see above)) |
2424
| `tasks.<taskName>.results["<resultName>"]` | (see above)) |
25+
| `tasks.<taskName>.results.<resultName>[i]` | The ith value of the `Task's` array result. Can alter `Task` execution order within a `Pipeline`.) |
26+
| `tasks.<taskName>.results['<resultName>'][i]` | (see above)) |
27+
| `tasks.<taskName>.results["<resultName>"][i]` | (see above)) |
2528
| `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. |
2629
| `context.pipelineRun.name` | The name of the `PipelineRun` that this `Pipeline` is running in. |
2730
| `context.pipelineRun.namespace` | The namespace of the `PipelineRun` that this `Pipeline` is running in. |
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
apiVersion: tekton.dev/v1beta1
2+
kind: PipelineRun
3+
metadata:
4+
name: pipelinerun-array-indexing-results
5+
spec:
6+
pipelineSpec:
7+
tasks:
8+
- name: task1
9+
taskSpec:
10+
results:
11+
- name: array-results
12+
description: The array resluts
13+
steps:
14+
- name: write-array
15+
image: bash:latest
16+
script: |
17+
#!/usr/bin/env bash
18+
echo -n "[\"1\",\"2\",\"3\"]" | tee $(results.array-results.path)
19+
- name: task2
20+
params:
21+
- name: foo
22+
value: "$(tasks.task1.results.array-results[1])"
23+
taskSpec:
24+
params:
25+
- name: foo
26+
type: string
27+
default: defaultparam
28+
steps:
29+
- name: print-param
30+
image: ubuntu
31+
script: |
32+
#!/bin/bash
33+
VALUE=$(params.foo)
34+
EXPECTED="2"
35+
diff=$(diff <(printf "%s\n" "${VALUE[@]}") <(printf "%s\n" "${EXPECTED[@]}"))
36+
if [[ -z "$diff" ]]; then
37+
echo "TRUE"
38+
else
39+
echo "FALSE"
40+
fi

pkg/apis/pipeline/v1beta1/resultref.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,17 @@ const (
3535
// ResultResultPart Constant used to define the "results" part of a pipeline result reference
3636
ResultResultPart = "results"
3737
// TODO(#2462) use one regex across all substitutions
38-
variableSubstitutionFormat = `\$\([_a-zA-Z0-9.-]+(\.[_a-zA-Z0-9.-]+)*\)`
38+
// variableSubstitutionFormat matches format like $result.resultname, $result.resultname[int] and $result.resultname[*]
39+
variableSubstitutionFormat = `\$\([_a-zA-Z0-9.-]+(\.[_a-zA-Z0-9.-]+)*(\[([0-9])*\*?\])?\)`
40+
// excludeArrayIndexing will replace all `[int]` and `[*]` for parseExpression to extract result name
41+
excludeArrayIndexing = `\[([0-9])*\*?\]`
3942
// ResultNameFormat Constant used to define the the regex Result.Name should follow
4043
ResultNameFormat = `^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$`
4144
)
4245

4346
var variableSubstitutionRegex = regexp.MustCompile(variableSubstitutionFormat)
4447
var resultNameFormatRegex = regexp.MustCompile(ResultNameFormat)
48+
var excludeArrayIndexingRegex = regexp.MustCompile(excludeArrayIndexing)
4549

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

pkg/reconciler/pipelinerun/resources/apply_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,36 @@ func TestApplyTaskResults_MinimalExpression(t *testing.T) {
401401
}},
402402
},
403403
}},
404+
}, {
405+
name: "Test array indexing result substitution on minimal variable substitution expression - params",
406+
resolvedResultRefs: ResolvedResultRefs{{
407+
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
408+
ResultReference: v1beta1.ResultRef{
409+
PipelineTask: "aTask",
410+
Result: "a.Result",
411+
},
412+
FromTaskRun: "aTaskRun",
413+
}},
414+
targets: PipelineRunState{{
415+
PipelineTask: &v1beta1.PipelineTask{
416+
Name: "bTask",
417+
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
418+
Params: []v1beta1.Param{{
419+
Name: "bParam",
420+
Value: *v1beta1.NewArrayOrString(`$(tasks.aTask.results["a.Result"][1])`),
421+
}},
422+
},
423+
}},
424+
want: PipelineRunState{{
425+
PipelineTask: &v1beta1.PipelineTask{
426+
Name: "bTask",
427+
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
428+
Params: []v1beta1.Param{{
429+
Name: "bParam",
430+
Value: *v1beta1.NewArrayOrString("arrayResultValueTwo"),
431+
}},
432+
},
433+
}},
404434
}, {
405435
name: "Test result substitution on minimal variable substitution expression - when expressions",
406436
resolvedResultRefs: ResolvedResultRefs{{
@@ -433,6 +463,38 @@ func TestApplyTaskResults_MinimalExpression(t *testing.T) {
433463
}},
434464
},
435465
}},
466+
}, {
467+
name: "Test array indexing result substitution on minimal variable substitution expression - when expressions",
468+
resolvedResultRefs: ResolvedResultRefs{{
469+
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
470+
ResultReference: v1beta1.ResultRef{
471+
PipelineTask: "aTask",
472+
Result: "aResult",
473+
},
474+
FromTaskRun: "aTaskRun",
475+
}},
476+
targets: PipelineRunState{{
477+
PipelineTask: &v1beta1.PipelineTask{
478+
Name: "bTask",
479+
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
480+
WhenExpressions: []v1beta1.WhenExpression{{
481+
Input: "$(tasks.aTask.results.aResult[1])",
482+
Operator: selection.In,
483+
Values: []string{"$(tasks.aTask.results.aResult[0])"},
484+
}},
485+
},
486+
}},
487+
want: PipelineRunState{{
488+
PipelineTask: &v1beta1.PipelineTask{
489+
Name: "bTask",
490+
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
491+
WhenExpressions: []v1beta1.WhenExpression{{
492+
Input: "arrayResultValueTwo",
493+
Operator: selection.In,
494+
Values: []string{"arrayResultValueOne"},
495+
}},
496+
},
497+
}},
436498
}} {
437499
t.Run(tt.name, func(t *testing.T) {
438500
ApplyTaskResults(tt.targets, tt.resolvedResultRefs)
@@ -479,6 +541,36 @@ func TestApplyTaskResults_EmbeddedExpression(t *testing.T) {
479541
}},
480542
},
481543
}},
544+
}, {
545+
name: "Test array indexing result substitution on embedded variable substitution expression - params",
546+
resolvedResultRefs: ResolvedResultRefs{{
547+
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
548+
ResultReference: v1beta1.ResultRef{
549+
PipelineTask: "aTask",
550+
Result: "aResult",
551+
},
552+
FromTaskRun: "aTaskRun",
553+
}},
554+
targets: PipelineRunState{{
555+
PipelineTask: &v1beta1.PipelineTask{
556+
Name: "bTask",
557+
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
558+
Params: []v1beta1.Param{{
559+
Name: "bParam",
560+
Value: *v1beta1.NewArrayOrString("Result value --> $(tasks.aTask.results.aResult[0])"),
561+
}},
562+
},
563+
}},
564+
want: PipelineRunState{{
565+
PipelineTask: &v1beta1.PipelineTask{
566+
Name: "bTask",
567+
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
568+
Params: []v1beta1.Param{{
569+
Name: "bParam",
570+
Value: *v1beta1.NewArrayOrString("Result value --> arrayResultValueOne"),
571+
}},
572+
},
573+
}},
482574
}, {
483575
name: "Test result substitution on embedded variable substitution expression - when expressions",
484576
resolvedResultRefs: ResolvedResultRefs{{
@@ -513,6 +605,40 @@ func TestApplyTaskResults_EmbeddedExpression(t *testing.T) {
513605
}},
514606
},
515607
}},
608+
}, {
609+
name: "Test array indexing result substitution on embedded variable substitution expression - when expressions",
610+
resolvedResultRefs: ResolvedResultRefs{{
611+
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
612+
ResultReference: v1beta1.ResultRef{
613+
PipelineTask: "aTask",
614+
Result: "aResult",
615+
},
616+
FromTaskRun: "aTaskRun",
617+
}},
618+
targets: PipelineRunState{{
619+
PipelineTask: &v1beta1.PipelineTask{
620+
Name: "bTask",
621+
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
622+
WhenExpressions: []v1beta1.WhenExpression{
623+
{
624+
Input: "Result value --> $(tasks.aTask.results.aResult[1])",
625+
Operator: selection.In,
626+
Values: []string{"Result value --> $(tasks.aTask.results.aResult[0])"},
627+
},
628+
},
629+
},
630+
}},
631+
want: PipelineRunState{{
632+
PipelineTask: &v1beta1.PipelineTask{
633+
Name: "bTask",
634+
TaskRef: &v1beta1.TaskRef{Name: "bTask"},
635+
WhenExpressions: []v1beta1.WhenExpression{{
636+
Input: "Result value --> arrayResultValueTwo",
637+
Operator: selection.In,
638+
Values: []string{"Result value --> arrayResultValueOne"},
639+
}},
640+
},
641+
}},
516642
}} {
517643
t.Run(tt.name, func(t *testing.T) {
518644
ApplyTaskResults(tt.targets, tt.resolvedResultRefs)
@@ -579,6 +705,56 @@ func TestApplyTaskResults_Conditions(t *testing.T) {
579705
}}},
580706
}},
581707
}},
708+
}, {
709+
name: "Test result substitution in condition parameter",
710+
resolvedResultRefs: ResolvedResultRefs{{
711+
Value: *v1beta1.NewArrayOrString("arrayResultValueOne", "arrayResultValueTwo"),
712+
ResultReference: v1beta1.ResultRef{
713+
PipelineTask: "aTask",
714+
Result: "aResult",
715+
},
716+
FromTaskRun: "aTaskRun",
717+
}},
718+
targets: PipelineRunState{{
719+
ResolvedConditionChecks: TaskConditionCheckState{{
720+
ConditionRegisterName: "always-true-0",
721+
ConditionCheckName: "test",
722+
Condition: &v1alpha1.Condition{
723+
ObjectMeta: metav1.ObjectMeta{
724+
Name: "always-true",
725+
},
726+
Spec: v1alpha1.ConditionSpec{
727+
Check: v1beta1.Step{},
728+
},
729+
},
730+
ResolvedResources: map[string]*resourcev1alpha1.PipelineResource{},
731+
PipelineTaskCondition: &v1beta1.PipelineTaskCondition{
732+
Params: []v1beta1.Param{{
733+
Name: "cParam",
734+
Value: *v1beta1.NewArrayOrString("Result value --> $(tasks.aTask.results.aResult[0])"),
735+
}},
736+
},
737+
}},
738+
}},
739+
want: PipelineRunState{{
740+
ResolvedConditionChecks: TaskConditionCheckState{{
741+
ConditionRegisterName: "always-true-0",
742+
ConditionCheckName: "test",
743+
Condition: &v1alpha1.Condition{
744+
ObjectMeta: metav1.ObjectMeta{
745+
Name: "always-true",
746+
},
747+
Spec: v1alpha1.ConditionSpec{
748+
Check: v1beta1.Step{},
749+
},
750+
},
751+
ResolvedResources: map[string]*resourcev1alpha1.PipelineResource{},
752+
PipelineTaskCondition: &v1beta1.PipelineTaskCondition{Params: []v1beta1.Param{{
753+
Name: "cParam",
754+
Value: *v1beta1.NewArrayOrString("Result value --> arrayResultValueOne"),
755+
}}},
756+
}},
757+
}},
582758
}} {
583759
t.Run(tt.name, func(t *testing.T) {
584760
ApplyTaskResults(tt.targets, tt.resolvedResultRefs)

pkg/reconciler/pipelinerun/resources/resultrefresolution.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,17 @@ func findTaskResultForParam(taskRun *v1beta1.TaskRun, reference *v1beta1.ResultR
187187
func (rs ResolvedResultRefs) getStringReplacements() map[string]string {
188188
replacements := map[string]string{}
189189
for _, r := range rs {
190-
for _, target := range r.getReplaceTarget() {
191-
replacements[target] = r.Value.StringVal
190+
switch r.Value.Type {
191+
case v1beta1.ParamTypeArray:
192+
for i := 0; i < len(r.Value.ArrayVal); i++ {
193+
for _, target := range r.getReplaceTargetfromArrayIndex(i) {
194+
replacements[target] = r.Value.ArrayVal[i]
195+
}
196+
}
197+
default:
198+
for _, target := range r.getReplaceTarget() {
199+
replacements[target] = r.Value.StringVal
200+
}
192201
}
193202
}
194203
return replacements
@@ -201,3 +210,11 @@ func (r *ResolvedResultRef) getReplaceTarget() []string {
201210
fmt.Sprintf("%s.%s.%s['%s']", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result),
202211
}
203212
}
213+
214+
func (r *ResolvedResultRef) getReplaceTargetfromArrayIndex(idx int) []string {
215+
return []string{
216+
fmt.Sprintf("%s.%s.%s.%s[%d]", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, idx),
217+
fmt.Sprintf("%s.%s.%s[%q][%d]", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, idx),
218+
fmt.Sprintf("%s.%s.%s['%s'][%d]", v1beta1.ResultTaskPart, r.ResultReference.PipelineTask, v1beta1.ResultResultPart, r.ResultReference.Result, idx),
219+
}
220+
}

0 commit comments

Comments
 (0)