-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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-0076]Support Results Array Indexing #4911
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,13 +19,15 @@ package v1beta1 | |
import ( | ||
"fmt" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// ResultRef is a type that represents a reference to a task run result | ||
type ResultRef struct { | ||
PipelineTask string `json:"pipelineTask"` | ||
Result string `json:"result"` | ||
ResultsIndex int `json:"resultsIndex"` | ||
} | ||
|
||
const ( | ||
|
@@ -35,28 +37,33 @@ 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.-]+)*(\[([0-9])*\*?\])?\)` | ||
// arrayIndexing will match all `[int]` and `[*]` for parseExpression | ||
arrayIndexing = `\[([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 arrayIndexingRegex = regexp.MustCompile(arrayIndexing) | ||
|
||
// NewResultRefs extracts all ResultReferences from a param or a pipeline result. | ||
// If the ResultReference can be extracted, they are returned. Expressions which are not | ||
// results are ignored. | ||
func NewResultRefs(expressions []string) []*ResultRef { | ||
var resultRefs []*ResultRef | ||
for _, expression := range expressions { | ||
pipelineTask, result, err := parseExpression(expression) | ||
pipelineTask, result, index, err := parseExpression(expression) | ||
// If the expression isn't a result but is some other expression, | ||
// parseExpression will return an error, in which case we just skip that expression, | ||
// since although it's not a result ref, it might be some other kind of reference | ||
if err == nil { | ||
resultRefs = append(resultRefs, &ResultRef{ | ||
PipelineTask: pipelineTask, | ||
Result: result, | ||
ResultsIndex: index, | ||
}) | ||
} | ||
} | ||
|
@@ -122,12 +129,19 @@ func stripVarSubExpression(expression string) string { | |
return strings.TrimSuffix(strings.TrimPrefix(expression, "$("), ")") | ||
} | ||
|
||
func parseExpression(substitutionExpression string) (string, string, error) { | ||
func parseExpression(substitutionExpression string) (string, string, int, error) { | ||
subExpressions := strings.Split(substitutionExpression, ".") | ||
if len(subExpressions) != 4 || subExpressions[0] != ResultTaskPart || subExpressions[2] != ResultResultPart { | ||
return "", "", fmt.Errorf("Must be of the form %q", resultExpressionFormat) | ||
return "", "", 0, fmt.Errorf("Must be of the form %q", resultExpressionFormat) | ||
} | ||
return subExpressions[1], subExpressions[3], nil | ||
|
||
stringIdx := strings.TrimSuffix(strings.TrimPrefix(arrayIndexingRegex.FindString(subExpressions[3]), "["), "]") | ||
subExpressions[3] = arrayIndexingRegex.ReplaceAllString(subExpressions[3], "") | ||
if stringIdx != "" { | ||
intIdx, _ := strconv.Atoi(stringIdx) | ||
return subExpressions[1], subExpressions[3], intIdx, nil | ||
} | ||
return subExpressions[1], subExpressions[3], 0, nil | ||
Comment on lines
+132
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When the index part in bracket for array or the whole expression is invalid, or it's a reference to a string result, wouldn't it make more sense if we set the third return value to be |
||
} | ||
|
||
// PipelineTaskResultRefs walks all the places a result reference can be used | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -601,6 +601,67 @@ func TestApplyTaskResults_MinimalExpression(t *testing.T) { | |
}}, | ||
}, | ||
}}, | ||
}, { | ||
Yongxuanzhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 array indexing result substitution out of bound - 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"][3])`), | ||
}}, | ||
}, | ||
}}, | ||
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(`$(tasks.aTask.results["a.Result"][3])`), | ||
}}, | ||
}, | ||
}}, | ||
Comment on lines
+634
to
+664
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah adding the tests in resultresolution_test should be good enough. But this test case makes it clear that out of bound cases here are handled in this specific way. I think keeping this case is fine. You can resolve this comment |
||
}, { | ||
name: "Test result substitution on minimal variable substitution expression - when expressions", | ||
resolvedResultRefs: ResolvedResultRefs{{ | ||
|
@@ -633,6 +694,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) | ||
|
@@ -679,6 +772,36 @@ func TestApplyTaskResults_EmbeddedExpression(t *testing.T) { | |
}}, | ||
}, | ||
}}, | ||
}, { | ||
name: "Test array indexing result substitution on embedded variable substitution expression - params", | ||
Yongxuanzhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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{{ | ||
|
@@ -713,6 +836,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) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -281,8 +281,10 @@ func (t *ResolvedPipelineRunTask) skipBecauseResultReferencesAreMissing(facts *P | |
if t.checkParentsDone(facts) && t.hasResultReferences() { | ||
resolvedResultRefs, pt, err := ResolveResultRefs(facts.State, PipelineRunState{t}) | ||
rprt := facts.State.ToMap()[pt] | ||
if err != nil && (t.IsFinalTask(facts) || rprt.Skip(facts).SkippingReason == v1beta1.WhenExpressionsSkip) { | ||
return true | ||
if rprt != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the reason for this change? it doesn't look related to results There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it is not so straightforward, not sure if I'm doing here is correct or there could be better solutions. We need to validate if the referred index is out of bound of results array, and it is done in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And in this case of this function we will still return true even we got err here. But it will return our err in another function |
||
if err != nil && (t.IsFinalTask(facts) || rprt.Skip(facts).SkippingReason == v1beta1.WhenExpressionsSkip) { | ||
return true | ||
} | ||
} | ||
ApplyTaskResults(PipelineRunState{t}, resolvedResultRefs) | ||
facts.ResetSkippedCache() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about start notation -
tasks.<taskName>.results.<resultName>[*]
? is that something supported in this context?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
star notation is not supported in this pr, it is in this one #4908
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
star notation is used to refer the whole array while this pr focus on the reference of array elements via index