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 May 24, 2022
1 parent 4a93b62 commit 1fb40aa
Show file tree
Hide file tree
Showing 4 changed files with 349 additions and 19 deletions.
62 changes: 53 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 @@ -210,23 +211,28 @@ 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) {
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(validateStringVariableP(param, prefix, paramNames, arrayParamNames, objectParamNameKeys))
}
}
return errs
}

func validatePipelineParametersVariablesInMatrixParameters(matrix []Param, prefix string, paramNames sets.String, arrayParamNames sets.String) (errs *apis.FieldError) {
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 @@ -258,12 +264,50 @@ func validateParameterInOneOfMatrixOrParams(matrix []Param, params []Param) (err
return errs
}

func validateStringVariable(value, prefix string, stringVars sets.String, arrayVars sets.String) *apis.FieldError {
// validateStringVariableP validates param variable usage in string value
// see https://github.com/tektoncd/pipeline/issues/4879#issuecomment-1133120522 for background
// This will allow using the whole object value to provide value for object param. example:
// params:
// - name: arg
// value: $(params.myObject[*])
func validateStringVariableP(param Param, prefix string, paramNames sets.String, arrayVars sets.String, objectParamNameKeys map[string][]string) (errs *apis.FieldError) {
stringValue := param.Value.StringVal
singleVariableRegex := regexp.MustCompile(fmt.Sprintf("^\\$\\(%s\\.[_a-zA-Z][_a-zA-Z0-9.-]*\\[\\*\\]\\)$", prefix))

// if the provided param value is just $(params.myObject[*]) or $(params.myArray[*]), example:
// params:
// - name: arg
// value: $(params.myObject[*])
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))
}

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...)))
}

// TODO (chuangw6): add this once #4861 is merged, and add tests
// return errs.Also(substitution.ValidateEntireVariableProhibitedP(value, prefix, objectNames))
return errs
}
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(tasks []PipelineTask, params []ParamSpec) (errs *apis.FieldError) {
parameterNames := sets.NewString()
arrayParameterNames := sets.NewString()
objectParameterNameKeys := map[string][]string{}

for _, p := range params {
// Verify that p is a valid type.
Expand Down Expand Up @@ -155,16 +156,21 @@ func validatePipelineParameterVariables(tasks []PipelineTask, params []ParamSpec
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 1fb40aa

Please sign in to comment.