Skip to content

Commit

Permalink
TEP-0075: Validate Pipeline object variables in value, matrix and when
Browse files Browse the repository at this point in the history
Besides task steps, Parameter variables can be used as the value of
a param, matrix or when expression.

Those validations regarding object type are added:
- The whole object value can only be used when providing values
for other object param. example:
```
params:
- name: arg
  value: $(params.myObject[*])
```
- Individual object attributes should be used in all other cases
(string/array val, matrix and when expression).
  • Loading branch information
chuangw6 committed Jul 11, 2022
1 parent da43d0e commit 83bccd0
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 26 deletions.
70 changes: 61 additions & 9 deletions pkg/apis/pipeline/v1beta1/param_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
"regexp"
"strings"

resource "github.com/tektoncd/pipeline/pkg/apis/resource/v1alpha1"
Expand Down Expand Up @@ -284,23 +285,32 @@ func ArrayReference(a string) string {
return strings.TrimSuffix(strings.TrimPrefix(a, "$("+ParamsPrefix+"."), "[*])")
}

func validatePipelineParametersVariablesInTaskParameters(params []Param, prefix string, paramNames sets.String, arrayParamNames sets.String) (errs *apis.FieldError) {
// validatePipelineParametersVariablesInTaskParameters validates PipelineTask.Params[*].value
// that may contain the reference(s) to other params to make sure those references are used appropriately.
func validatePipelineParametersVariablesInTaskParameters(params []Param, prefix string, paramNames sets.String, arrayParamNames sets.String, objectParamNameKeys map[string][]string) (errs *apis.FieldError) {
for _, param := range params {
if param.Value.Type == ParamTypeString {
errs = errs.Also(validateStringVariable(param.Value.StringVal, prefix, paramNames, arrayParamNames).ViaFieldKey("params", param.Name))
} else {
switch param.Value.Type {
case ParamTypeArray:
for idx, arrayElement := range param.Value.ArrayVal {
errs = errs.Also(validateArrayVariable(arrayElement, prefix, paramNames, arrayParamNames).ViaFieldIndex("value", idx).ViaFieldKey("params", param.Name))
errs = errs.Also(validateArrayVariable(arrayElement, prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaFieldIndex("value", idx).ViaFieldKey("params", param.Name))
}
case ParamTypeObject:
for key, val := range param.Value.ObjectVal {
errs = errs.Also(validateStringVariable(val, prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaFieldKey("properties", key).ViaFieldKey("params", param.Name))
}
default:
errs = errs.Also(validateParamStringValue(param, prefix, paramNames, arrayParamNames, objectParamNameKeys))
}
}
return errs
}

func validatePipelineParametersVariablesInMatrixParameters(matrix []Param, prefix string, paramNames sets.String, arrayParamNames sets.String) (errs *apis.FieldError) {
// validatePipelineParametersVariablesInMatrixParameters validates PipelineTask.Matrix[*].value
// that may contain the reference(s) to other params to make sure those references are used appropriately.
func validatePipelineParametersVariablesInMatrixParameters(matrix []Param, prefix string, paramNames sets.String, arrayParamNames sets.String, objectParamNameKeys map[string][]string) (errs *apis.FieldError) {
for _, param := range matrix {
for idx, arrayElement := range param.Value.ArrayVal {
errs = errs.Also(validateArrayVariable(arrayElement, prefix, paramNames, arrayParamNames).ViaFieldIndex("value", idx).ViaFieldKey("matrix", param.Name))
errs = errs.Also(validateArrayVariable(arrayElement, prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaFieldIndex("value", idx).ViaFieldKey("matrix", param.Name))
}
}
return errs
Expand Down Expand Up @@ -328,12 +338,54 @@ func validateParameterInOneOfMatrixOrParams(matrix []Param, params []Param) (err
return errs
}

func validateStringVariable(value, prefix string, stringVars sets.String, arrayVars sets.String) *apis.FieldError {
// validateParamStringValue validates the `PipelineTask.Params[*].value` (string type) field
// that may contain references to other isolated array/object params other than string param.
func validateParamStringValue(param Param, prefix string, paramNames sets.String, arrayVars sets.String, objectParamNameKeys map[string][]string) (errs *apis.FieldError) {
stringValue := param.Value.StringVal

nameSubstitution := `[_a-zA-Z0-9.-]+\[\*\]`
// a regex to check if the stringValue is an isolated reference to the whole array/object param without extra string literal.
singleVariablePattern := fmt.Sprintf(fmt.Sprintf("^%s$", substitution.BraceMatchingRegex), prefix, nameSubstitution, nameSubstitution, nameSubstitution)
singleVariableRegex, err := regexp.Compile(singleVariablePattern)
if err != nil {
return &apis.FieldError{
Message: fmt.Sprint("Fail to parse the regex: ", err),
Paths: []string{fmt.Sprintf("params.%s", param.Name)},
}
}

// if the provided param value is only a reference to the whole array/object
// valid example: "$(params.myArray[*])"
// invalid example: "$(params.name-not-exist[*])"
if singleVariableRegex.MatchString(stringValue) {
return errs.Also(substitution.ValidateVariableP(stringValue, prefix, paramNames).ViaFieldKey("params", param.Name))
}

// if the provided param value is string literal and/or contains multiple variables
// valid example: "$(params.myString) and another $(params.myObject.key1)"
// invalid example: "$(params.myString) and another $(params.myObject[*])"
return errs.Also(validateStringVariable(stringValue, prefix, paramNames, arrayVars, objectParamNameKeys).ViaFieldKey("params", param.Name))
}

// validateStringVariable validates the normal string fields that can only accept references to string param or individual keys of object param
func validateStringVariable(value, prefix string, stringVars sets.String, arrayVars sets.String, objectParamNameKeys map[string][]string) *apis.FieldError {
errs := substitution.ValidateVariableP(value, prefix, stringVars)
errs = errs.Also(validateObjectVariable(value, prefix, objectParamNameKeys))
return errs.Also(substitution.ValidateVariableProhibitedP(value, prefix, arrayVars))
}

func validateArrayVariable(value, prefix string, stringVars sets.String, arrayVars sets.String) *apis.FieldError {
func validateArrayVariable(value, prefix string, stringVars sets.String, arrayVars sets.String, objectParamNameKeys map[string][]string) *apis.FieldError {
errs := substitution.ValidateVariableP(value, prefix, stringVars)
errs = errs.Also(validateObjectVariable(value, prefix, objectParamNameKeys))
return errs.Also(substitution.ValidateVariableIsolatedP(value, prefix, arrayVars))
}

func validateObjectVariable(value, prefix string, objectParamNameKeys map[string][]string) (errs *apis.FieldError) {
objectNames := sets.NewString()
for objectParamName, keys := range objectParamNameKeys {
objectNames.Insert(objectParamName)
errs = errs.Also(substitution.ValidateVariableP(value, fmt.Sprintf("%s\\.%s", prefix, objectParamName), sets.NewString(keys...)))
}

return errs.Also(substitution.ValidateEntireVariableProhibitedP(value, prefix, objectNames))
}
18 changes: 12 additions & 6 deletions pkg/apis/pipeline/v1beta1/pipeline_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ func validatePipelineWorkspacesUsage(wss []PipelineWorkspaceDeclaration, pts []P
func validatePipelineParameterVariables(ctx context.Context, tasks []PipelineTask, params []ParamSpec) (errs *apis.FieldError) {
parameterNames := sets.NewString()
arrayParameterNames := sets.NewString()
objectParameterNameKeys := map[string][]string{}

// validates all the types within a slice of ParamSpecs
errs = errs.Also(ValidateParameterTypes(ctx, params).ViaField("params"))
Expand All @@ -141,16 +142,21 @@ func validatePipelineParameterVariables(ctx context.Context, tasks []PipelineTas
if p.Type == ParamTypeArray {
arrayParameterNames.Insert(p.Name)
}
}

return errs.Also(validatePipelineParametersVariables(tasks, "params", parameterNames, arrayParameterNames))
if p.Type == ParamTypeObject {
for k := range p.Properties {
objectParameterNameKeys[p.Name] = append(objectParameterNameKeys[p.Name], k)
}
}
}
return errs.Also(validatePipelineParametersVariables(tasks, "params", parameterNames, arrayParameterNames, objectParameterNameKeys))
}

func validatePipelineParametersVariables(tasks []PipelineTask, prefix string, paramNames sets.String, arrayParamNames sets.String) (errs *apis.FieldError) {
func validatePipelineParametersVariables(tasks []PipelineTask, prefix string, paramNames sets.String, arrayParamNames sets.String, objectParamNameKeys map[string][]string) (errs *apis.FieldError) {
for idx, task := range tasks {
errs = errs.Also(validatePipelineParametersVariablesInTaskParameters(task.Params, prefix, paramNames, arrayParamNames).ViaIndex(idx))
errs = errs.Also(validatePipelineParametersVariablesInMatrixParameters(task.Matrix, prefix, paramNames, arrayParamNames).ViaIndex(idx))
errs = errs.Also(task.WhenExpressions.validatePipelineParametersVariables(prefix, paramNames, arrayParamNames).ViaIndex(idx))
errs = errs.Also(validatePipelineParametersVariablesInTaskParameters(task.Params, prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaIndex(idx))
errs = errs.Also(validatePipelineParametersVariablesInMatrixParameters(task.Matrix, prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaIndex(idx))
errs = errs.Also(task.WhenExpressions.validatePipelineParametersVariables(prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaIndex(idx))
}
return errs
}
Expand Down
Loading

0 comments on commit 83bccd0

Please sign in to comment.