From deebc5b7fb95dfcde3c26b7d5eb5a0f76792f5a0 Mon Sep 17 00:00:00 2001 From: Chuang Wang Date: Mon, 23 May 2022 17:39:31 -0700 Subject: [PATCH] TEP-0075: Validate Pipeline object variables in value, matrix and when 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). --- pkg/apis/pipeline/v1beta1/param_types.go | 56 +++- .../pipeline/v1beta1/pipeline_validation.go | 18 +- .../v1beta1/pipeline_validation_test.go | 284 +++++++++++++++++- pkg/apis/pipeline/v1beta1/when_validation.go | 8 +- pkg/substitution/substitution.go | 31 +- 5 files changed, 374 insertions(+), 23 deletions(-) diff --git a/pkg/apis/pipeline/v1beta1/param_types.go b/pkg/apis/pipeline/v1beta1/param_types.go index b58768b19ae..5121d552384 100644 --- a/pkg/apis/pipeline/v1beta1/param_types.go +++ b/pkg/apis/pipeline/v1beta1/param_types.go @@ -284,23 +284,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 param 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 matrix param 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 @@ -328,12 +337,41 @@ 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 param value field of string type +// 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 + + isIsolated, errs := substitution.ValidateWholeArrayOrObjectRefInStringVariable(param.Name, stringValue, prefix, paramNames) + if isIsolated { + return errs + } + + // 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 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)) +} diff --git a/pkg/apis/pipeline/v1beta1/pipeline_validation.go b/pkg/apis/pipeline/v1beta1/pipeline_validation.go index c96a86c5cd5..1455a19bf6f 100644 --- a/pkg/apis/pipeline/v1beta1/pipeline_validation.go +++ b/pkg/apis/pipeline/v1beta1/pipeline_validation.go @@ -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")) @@ -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 } diff --git a/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go b/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go index 248e7b16630..9933bda9159 100644 --- a/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go +++ b/pkg/apis/pipeline/v1beta1/pipeline_validation_test.go @@ -1327,10 +1327,152 @@ func TestValidatePipelineParameterVariables_Success(t *testing.T) { Name: "a-param", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"$(params.baz[*])", "and", "$(params.foo-is-baz[*])"}}, }}, }}, + }, { + name: "array param - using the whole variable as a param's value that is intended to be array type", + params: []ParamSpec{{ + Name: "myArray", + Type: ParamTypeArray, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "a-param-intended-to-be-array", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(params.myArray[*])"}, + }}, + }}, + }, { + name: "object param - using single individual variable in string param", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "a-string-param", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(params.myObject.key1)"}, + }}, + }}, + }, { + name: "object param - using multiple individual variables in string param", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "a-string-param", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(params.myObject.key1) and $(params.myObject.key2)"}, + }}, + }}, + }, { + name: "object param - using individual variables in array param", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "an-array-param", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"$(params.myObject.key1)", "another one $(params.myObject.key2)"}}, + }}, + }}, + }, { + name: "object param - using individual variables and string param as the value of other object individual keys", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }, { + Name: "myString", + Type: ParamTypeString, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "an-object-param", Value: ArrayOrString{Type: ParamTypeObject, ObjectVal: map[string]string{ + "url": "$(params.myObject.key1)", + "commit": "$(params.myString)", + }}, + }}, + }}, + }, { + name: "object param - using individual variables in matrix", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Matrix: []Param{{ + Name: "a-param", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"$(params.myObject.key1)", "and", "$(params.myObject.key2)"}}, + }}, + }}, + }, { + name: "object param - using the whole variable as a param's value that is intended to be object type", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "a-param-intended-to-be-object", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(params.myObject[*])"}, + }}, + }}, + }, { + name: "object param - using individual variable in input of when expression, and using both object individual variable and array reference in values of when expression", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }, { + Name: "foo", Type: ParamTypeArray, Default: &ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"anarray", "elements"}}, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + WhenExpressions: []WhenExpression{{ + Input: "$(params.myObject.key1)", + Operator: selection.In, + Values: []string{"$(params.foo[*])", "$(params.myObject.key2)"}, + }}, + }}, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() + ctx := config.EnableAlphaAPIFields(context.Background()) err := validatePipelineParameterVariables(ctx, tt.tasks, tt.params) if err != nil { t.Errorf("Pipeline.validatePipelineParameterVariables() returned error for valid pipeline parameters: %v", err) @@ -1653,10 +1795,148 @@ func TestValidatePipelineParameterVariables_Failure(t *testing.T) { Message: `non-existent variable in "$(params.does-not-exist)"`, Paths: []string{"[0].matrix[b-param].value[0]"}, }, + }, { + name: "invalid object key in the input of the when expression", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + WhenExpressions: []WhenExpression{{ + Input: "$(params.myObject.non-exist-key)", + Operator: selection.In, + Values: []string{"foo"}, + }}, + }}, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.myObject.non-exist-key)"`, + Paths: []string{"[0].when[0].input"}, + }, + }, { + name: "invalid object key in the Values of the when expression", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + WhenExpressions: []WhenExpression{{ + Input: "bax", + Operator: selection.In, + Values: []string{"$(params.myObject.non-exist-key)"}, + }}, + }}, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.myObject.non-exist-key)"`, + Paths: []string{"[0].when[0].values"}, + }, + }, { + name: "invalid object key is used to provide values for array params", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "a-param", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"$(params.myObject.non-exist-key)", "last"}}, + }}, + }}, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.myObject.non-exist-key)"`, + Paths: []string{"[0].params[a-param].value[0]"}, + }, + }, { + name: "invalid object key is used to provide values for string params", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "a-param", Value: ArrayOrString{Type: ParamTypeString, StringVal: "$(params.myObject.non-exist-key)"}, + }}, + }}, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.myObject.non-exist-key)"`, + Paths: []string{"[0].params[a-param]"}, + }, + }, { + name: "invalid object key is used to provide values for object params", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }, { + Name: "myString", + Type: ParamTypeString, + }}, + tasks: []PipelineTask{{ + Name: "bar", + TaskRef: &TaskRef{Name: "bar-task"}, + Params: []Param{{ + Name: "an-object-param", Value: ArrayOrString{Type: ParamTypeObject, ObjectVal: map[string]string{ + "url": "$(params.myObject.non-exist-key)", + "commit": "$(params.myString)", + }}, + }}, + }}, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.myObject.non-exist-key)"`, + Paths: []string{"[0].params[an-object-param].properties[url]"}, + }, + }, { + name: "invalid object key is used to provide values for matrix params", + params: []ParamSpec{{ + Name: "myObject", + Type: ParamTypeObject, + Properties: map[string]PropertySpec{ + "key1": {Type: "string"}, + "key2": {Type: "string"}, + }, + }}, + tasks: []PipelineTask{{ + Name: "foo-task", + TaskRef: &TaskRef{Name: "foo-task"}, + Matrix: []Param{{ + Name: "a-param", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"$(params.myObject.key1)"}}, + }, { + Name: "b-param", Value: ArrayOrString{Type: ParamTypeArray, ArrayVal: []string{"$(params.myObject.non-exist-key)"}}, + }}, + }}, + expectedError: apis.FieldError{ + Message: `non-existent variable in "$(params.myObject.non-exist-key)"`, + Paths: []string{"[0].matrix[b-param].value[0]"}, + }, }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx := context.Background() + ctx := config.EnableAlphaAPIFields(context.Background()) err := validatePipelineParameterVariables(ctx, tt.tasks, tt.params) if err == nil { t.Errorf("Pipeline.validatePipelineParameterVariables() did not return error for invalid pipeline parameters") diff --git a/pkg/apis/pipeline/v1beta1/when_validation.go b/pkg/apis/pipeline/v1beta1/when_validation.go index 316dbf10f24..b5a0b1c8e99 100644 --- a/pkg/apis/pipeline/v1beta1/when_validation.go +++ b/pkg/apis/pipeline/v1beta1/when_validation.go @@ -74,17 +74,17 @@ func (wes WhenExpressions) validateTaskResultsVariables() *apis.FieldError { return nil } -func (wes WhenExpressions) validatePipelineParametersVariables(prefix string, paramNames sets.String, arrayParamNames sets.String) (errs *apis.FieldError) { +func (wes WhenExpressions) validatePipelineParametersVariables(prefix string, paramNames sets.String, arrayParamNames sets.String, objectParamNameKeys map[string][]string) (errs *apis.FieldError) { for idx, we := range wes { - errs = errs.Also(validateStringVariable(we.Input, prefix, paramNames, arrayParamNames).ViaField("input").ViaFieldIndex("when", idx)) + errs = errs.Also(validateStringVariable(we.Input, prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaField("input").ViaFieldIndex("when", idx)) for _, val := range we.Values { // one of the values could be a reference to an array param, such as, $(params.foo[*]) // extract the variable name from the pattern $(params.foo[*]), if the variable name matches with one of the array params // validate the param as an array variable otherwise, validate it as a string variable if arrayParamNames.Has(ArrayReference(val)) { - errs = errs.Also(validateArrayVariable(val, prefix, paramNames, arrayParamNames).ViaField("values").ViaFieldIndex("when", idx)) + errs = errs.Also(validateArrayVariable(val, prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaField("values").ViaFieldIndex("when", idx)) } else { - errs = errs.Also(validateStringVariable(val, prefix, paramNames, arrayParamNames).ViaField("values").ViaFieldIndex("when", idx)) + errs = errs.Also(validateStringVariable(val, prefix, paramNames, arrayParamNames, objectParamNameKeys).ViaField("values").ViaFieldIndex("when", idx)) } } } diff --git a/pkg/substitution/substitution.go b/pkg/substitution/substitution.go index 08f1c4e9452..45b65a0f96a 100644 --- a/pkg/substitution/substitution.go +++ b/pkg/substitution/substitution.go @@ -25,8 +25,12 @@ import ( "knative.dev/pkg/apis" ) -const parameterSubstitution = `.*?(\[\*\])?` -const braceMatchingRegex = "(\\$(\\(%s(\\.(?P%s)|\\[\"(?P%s)\"\\]|\\['(?P%s)'\\])\\)))" +const ( + parameterSubstitution = `.*?(\[\*\])?` + + // braceMatchingRegex is a regex for parameter references including dot notation, bracket notation with single and double quotes. + braceMatchingRegex = "(\\$(\\(%s(\\.(?P%s)|\\[\"(?P%s)\"\\]|\\['(?P%s)'\\])\\)))" +) // ValidateVariable makes sure all variables in the provided string are known func ValidateVariable(name, value, prefix, locationName, path string, vars sets.String) *apis.FieldError { @@ -179,6 +183,29 @@ func ValidateVariableIsolatedP(value, prefix string, vars sets.String) *apis.Fie return nil } +// ValidateWholeArrayOrObjectRefInStringVariable validates if a single string field uses references to the whole array/object appropriately +// valid example: "$(params.myObject[*])" +// invalid example: "$(params.name-not-exist[*])" +func ValidateWholeArrayOrObjectRefInStringVariable(name, value, prefix string, vars sets.String) (isIsolated bool, errs *apis.FieldError) { + 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. + isolatedVariablePattern := fmt.Sprintf(fmt.Sprintf("^%s$", braceMatchingRegex), prefix, nameSubstitution, nameSubstitution, nameSubstitution) + isolatedVariableRegex, err := regexp.Compile(isolatedVariablePattern) + if err != nil { + return false, &apis.FieldError{ + Message: fmt.Sprint("Fail to parse the regex: ", err), + Paths: []string{fmt.Sprintf("%s.%s", prefix, name)}, + } + } + + if isolatedVariableRegex.MatchString(value) { + return true, ValidateVariableP(value, prefix, vars).ViaFieldKey(prefix, name) + } + + return false, nil +} + // Extract a the first full string expressions found (e.g "$(input.params.foo)"). Return // "" and false if nothing is found. func extractExpressionFromString(s, prefix string) (string, bool) {