From 5a45e268e626d6c1d41007d32f6571e2dd6c166f Mon Sep 17 00:00:00 2001 From: Isaac Fletcher Date: Fri, 6 Feb 2026 12:30:21 -0800 Subject: [PATCH] Fix regex pattern matching for validate command (#595) Summary: The existing regex patterns for `templateVarPattern` and `stepVarsPattern` only matched simple template variable references like `{{.Args.x}}`. This caused the validate command to miss template variables used in more complex Go template expressions such as: - Conditionals: `{{if .Args.x}}` - Function calls: `{{printf "%s" .Args.x}}` - Whitespace variations: `{{ .Args.x }}` Updated the regex patterns to match `.Args.varname` and `.StepVars.varname` anywhere within their respective template delimiters (`{{ }}` and `{[{ }]}`), ensuring all template variable references are properly validated. Additionally, fixed false positive errors for StepVars that are defined in subttps. When a TTP references a subttp via the `ttp:` field, the subttp can export outputvars to the parent TTP. Since we cannot validate which variables the subttp produces without loading it, we now emit a warning instead of an error when a StepVar is not found locally but subttp steps exist. This allows valid TTPs like `subttps-and-variables.yaml` to pass validation while still alerting users to potentially undefined variables. Reviewed By: ivnik Differential Revision: D92402443 --- pkg/validation/templates.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/pkg/validation/templates.go b/pkg/validation/templates.go index 750cb703..f832473e 100644 --- a/pkg/validation/templates.go +++ b/pkg/validation/templates.go @@ -27,8 +27,12 @@ import ( ) var ( - templateVarPattern = regexp.MustCompile(`\{\{\.Args\.([a-zA-Z_][a-zA-Z0-9_]*)\}\}`) - stepVarsPattern = regexp.MustCompile(`\{\[\{\.StepVars\.([a-zA-Z_][a-zA-Z0-9_]*)\}\]\}`) + // Match .Args.varname anywhere within {{ }} template delimiters + // This handles: {{.Args.x}}, {{ .Args.x }}, {{if .Args.x}}, {{printf "%s" .Args.x}}, etc. + templateVarPattern = regexp.MustCompile(`\{\{[^}]*\.Args\.([a-zA-Z_][a-zA-Z0-9_]*)[^}]*\}\}`) + // Match .StepVars.varname anywhere within {[{ }]} template delimiters + // This handles: {[{.StepVars.x}]}, {[{ .StepVars.x }]}, etc. + stepVarsPattern = regexp.MustCompile(`\{\[\{[^}]*\.StepVars\.([a-zA-Z_][a-zA-Z0-9_]*)[^}]*\}\]\}`) ) // ValidateTemplateReferences validates that template variables reference defined args/outputvars @@ -48,7 +52,9 @@ func ValidateTemplateReferences(ttpMap map[string]any, result *Result) { } // Collect defined outputvars from steps (in order) + // Also track if any subttp steps exist, as they can produce outputvars definedOutputVars := make(map[string]bool) + hasSubttpSteps := false if stepsVal, ok := ttpMap["steps"]; ok { if stepsList, isList := stepsVal.([]any); isList { for _, step := range stepsList { @@ -56,6 +62,9 @@ func ValidateTemplateReferences(ttpMap map[string]any, result *Result) { if outputvarVal, ok := stepMap["outputvar"]; ok { definedOutputVars[fmt.Sprintf("%v", outputvarVal)] = true } + if _, ok := stepMap["ttp"]; ok { + hasSubttpSteps = true + } } } } @@ -89,7 +98,12 @@ func ValidateTemplateReferences(ttpMap map[string]any, result *Result) { if len(match) > 1 { varName := match[1] if !seenStepVars[varName] && !definedOutputVars[varName] { - result.AddError(fmt.Sprintf("Template variable '{[{.StepVars.%s}]}' references undefined outputvar '%s'", varName, varName)) + if hasSubttpSteps { + // Subttp steps can produce outputvars, so only warn + result.AddWarning(fmt.Sprintf("Template variable '{[{.StepVars.%s}]}' references outputvar '%s' not defined in this TTP (may be defined in a subttp)", varName, varName)) + } else { + result.AddError(fmt.Sprintf("Template variable '{[{.StepVars.%s}]}' references undefined outputvar '%s'", varName, varName)) + } seenStepVars[varName] = true } }