Skip to content

Commit

Permalink
[TEP-0075] Validate object task result variable
Browse files Browse the repository at this point in the history
Prior to this commit, when providing param value with task result
variable, it only allowed using the task result as whole in the format
of `tasks.<taskName>.results.<resultName>` since result can
be only of type string previously.

As we are adding support for object type result, we need to support
using the result variable of object type in the format of
`tasks.<taskName>.results.<objectResultName>.<individualAttribute>`.

In this commit, we consider the object case in the validation webhook.
  • Loading branch information
chuangw6 committed May 18, 2022
1 parent 2cff700 commit da753b0
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 11 deletions.
9 changes: 8 additions & 1 deletion pkg/apis/pipeline/v1beta1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions pkg/apis/pipeline/v1beta1/pipeline_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,10 @@ func TestValidatePipelineResults_Success(t *testing.T) {
Name: "my-pipeline-result",
Description: "this is my pipeline result",
Value: "$(tasks.a-task.results.output)",
}, {
Name: "my-pipeline-object-result",
Description: "this is my pipeline result",
Value: "$(tasks.a-task.results.gitrepo.commit)",
}}
if err := validatePipelineResults(results); err != nil {
t.Errorf("Pipeline.validatePipelineResults() returned error for valid pipeline: %s: %v", desc, err)
Expand All @@ -1208,10 +1212,10 @@ func TestValidatePipelineResults_Failure(t *testing.T) {
results := []PipelineResult{{
Name: "my-pipeline-result",
Description: "this is my pipeline result",
Value: "$(tasks.a-task.results.output.output)",
Value: "$(tasks.a-task.results.output.key1.invalid)",
}}
expectedError := apis.FieldError{
Message: `invalid value: expected all of the expressions [tasks.a-task.results.output.output] to be result expressions but only [] were`,
Message: `invalid value: expected all of the expressions [tasks.a-task.results.output.key1.invalid] to be result expressions but only [] were`,
Paths: []string{"results[0].value"},
}
err := validatePipelineResults(results)
Expand Down
30 changes: 25 additions & 5 deletions pkg/apis/pipeline/v1beta1/resultref.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,15 @@ import (
type ResultRef struct {
PipelineTask string `json:"pipelineTask"`
Result string `json:"result"`
Property string `json:"property"`
}

const (
resultExpressionFormat = "tasks.<taskName>.results.<resultName>"
// To avoid conflicts with standalone names that include dots in it,
// TEP-0080 added support for using bracket notation [] for such names instead of dot notation.
// see more details in TEP-0080 and TEP-0075 regarding this.
objectResultExpressionFormat = "tasks.<taskName>.results.<objectResultName>.<individualAttribute>"
// ResultTaskPart Constant used to define the "tasks" part of a pipeline result reference
ResultTaskPart = "tasks"
// ResultResultPart Constant used to define the "results" part of a pipeline result reference
Expand All @@ -49,14 +54,15 @@ var resultNameFormatRegex = regexp.MustCompile(ResultNameFormat)
func NewResultRefs(expressions []string) []*ResultRef {
var resultRefs []*ResultRef
for _, expression := range expressions {
pipelineTask, result, err := parseExpression(expression)
pipelineTask, result, property, 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,
Property: property,
})
}
}
Expand Down Expand Up @@ -94,6 +100,11 @@ func GetVarSubstitutionExpressionsForParam(param Param) ([]string, bool) {
case ParamTypeString:
// string type
allExpressions = append(allExpressions, validateString(param.Value.StringVal)...)
case ParamTypeObject:
// object type
for _, value := range param.Value.ObjectVal {
allExpressions = append(allExpressions, validateString(value)...)
}
default:
return nil, false
}
Expand Down Expand Up @@ -122,12 +133,21 @@ func stripVarSubExpression(expression string) string {
return strings.TrimSuffix(strings.TrimPrefix(expression, "$("), ")")
}

func parseExpression(substitutionExpression string) (string, string, error) {
func parseExpression(substitutionExpression string) (string, string, string, 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)

// TODO (@yongxuanzhang): consider array type result here tasks.<taskName>.results.<resultName>[*] and indexing tasks.<taskName>.results.<resultName>[0]
// For string type result: tasks.<taskName>.results.<resultName>
if len(subExpressions) == 4 && subExpressions[0] == ResultTaskPart && subExpressions[2] == ResultResultPart {
return subExpressions[1], subExpressions[3], "", nil
}
return subExpressions[1], subExpressions[3], nil

// For object type result: tasks.<taskName>.results.<objectResultName>.<individualAttribute>
if len(subExpressions) == 5 && subExpressions[0] == ResultTaskPart && subExpressions[2] == ResultResultPart {
return subExpressions[1], subExpressions[3], subExpressions[4], nil
}

return "", "", "", fmt.Errorf("Must be one of the form 1). %q; 2). %q", resultExpressionFormat, objectResultExpressionFormat)
}

// PipelineTaskResultRefs walks all the places a result reference can be used
Expand Down
58 changes: 56 additions & 2 deletions pkg/apis/pipeline/v1beta1/resultref_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,32 @@ func TestNewResultReference(t *testing.T) {
PipelineTask: "sumTask",
Result: "sumResult",
}},
}, {
name: "Test valid expression with single object result property",
param: v1beta1.Param{
Name: "param",
Value: *v1beta1.NewArrayOrString("$(tasks.sumTask.results.sumResult.key1)"),
},
want: []*v1beta1.ResultRef{{
PipelineTask: "sumTask",
Result: "sumResult",
Property: "key1",
}},
}, {
name: "Test valid expression with multiple object result properties",
param: v1beta1.Param{
Name: "param",
Value: *v1beta1.NewArrayOrString("$(tasks.sumTask.results.imageresult.digest), and another one $(tasks.sumTask.results.imageresult.tag)"),
},
want: []*v1beta1.ResultRef{{
PipelineTask: "sumTask",
Result: "imageresult",
Property: "digest",
}, {
PipelineTask: "sumTask",
Result: "imageresult",
Property: "tag",
}},
}, {
name: "substitution within string",
param: v1beta1.Param{
Expand All @@ -55,27 +81,35 @@ func TestNewResultReference(t *testing.T) {
name: "multiple substitution",
param: v1beta1.Param{
Name: "param",
Value: *v1beta1.NewArrayOrString("$(tasks.sumTask1.results.sumResult) and another $(tasks.sumTask2.results.sumResult)"),
Value: *v1beta1.NewArrayOrString("$(tasks.sumTask1.results.sumResult) and another $(tasks.sumTask2.results.sumResult), last one $(tasks.sumTask3.results.sumResult.key1)"),
},
want: []*v1beta1.ResultRef{{
PipelineTask: "sumTask1",
Result: "sumResult",
}, {
PipelineTask: "sumTask2",
Result: "sumResult",
}, {
PipelineTask: "sumTask3",
Result: "sumResult",
Property: "key1",
}},
}, {
name: "multiple substitution with param",
param: v1beta1.Param{
Name: "param",
Value: *v1beta1.NewArrayOrString("$(params.param) $(tasks.sumTask1.results.sumResult) and another $(tasks.sumTask2.results.sumResult)"),
Value: *v1beta1.NewArrayOrString("$(params.param) $(tasks.sumTask1.results.sumResult) and another $(tasks.sumTask2.results.sumResult), last one $(tasks.sumTask3.results.sumResult.key1)"),
},
want: []*v1beta1.ResultRef{{
PipelineTask: "sumTask1",
Result: "sumResult",
}, {
PipelineTask: "sumTask2",
Result: "sumResult",
}, {
PipelineTask: "sumTask3",
Result: "sumResult",
Property: "key1",
}},
}, {
name: "first separator typo",
Expand Down Expand Up @@ -188,6 +222,26 @@ func TestHasResultReference(t *testing.T) {
PipelineTask: "sumTask2",
Result: "sumResult2",
}},
}, {
name: "Test valid expression in object",
param: v1beta1.Param{
Name: "param",
Value: *v1beta1.NewObject(map[string]string{
"key1": "$(tasks.sumTask1.results.sumResult1)",
"key2": "$(tasks.sumTask2.results.sumResult2) and another one $(tasks.sumTask3.results.sumResult3)",
"key3": "no ref here",
}),
},
wantRef: []*v1beta1.ResultRef{{
PipelineTask: "sumTask1",
Result: "sumResult1",
}, {
PipelineTask: "sumTask2",
Result: "sumResult2",
}, {
PipelineTask: "sumTask3",
Result: "sumResult3",
}},
}} {
t.Run(tt.name, func(t *testing.T) {
expressions, ok := v1beta1.GetVarSubstitutionExpressionsForParam(tt.param)
Expand Down
7 changes: 6 additions & 1 deletion pkg/apis/pipeline/v1beta1/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1711,13 +1711,18 @@
"type": "object",
"required": [
"pipelineTask",
"result"
"result",
"property"
],
"properties": {
"pipelineTask": {
"type": "string",
"default": ""
},
"property": {
"type": "string",
"default": ""
},
"result": {
"type": "string",
"default": ""
Expand Down

0 comments on commit da753b0

Please sign in to comment.