44 "bytes"
55 "encoding/json"
66 "fmt"
7- "regexp"
87 "strings"
98 "testing"
109 "text/template"
@@ -53,7 +52,12 @@ posit.team/dynamic-label-cap-reached: "true"
5352{{- if hasKey $jobMap $rule.field }}
5453{{- $val := index $jobMap $rule.field }}
5554{{- if $rule.labelKey }}
56- {{- $labelVal := $val | toString | regexReplaceAll "[^a-zA-Z0-9._-]" "_" | regexReplaceAll "_{2,}" "_" | trunc 63 | regexReplaceAll "[^a-zA-Z0-9]+$" "" | regexReplaceAll "^[^a-zA-Z0-9]+" "" }}
55+ {{- /* regexReplaceAll uses Sprig arg order: (regex, source, replacement). Do NOT pipe into it. */ -}}
56+ {{- $labelVal := regexReplaceAll "[^a-zA-Z0-9._-]" ($val | toString) "_" }}
57+ {{- $labelVal = regexReplaceAll "_{2,}" $labelVal "_" }}
58+ {{- $labelVal = $labelVal | trunc 63 }}
59+ {{- $labelVal = regexReplaceAll "[^a-zA-Z0-9]+$" $labelVal "" }}
60+ {{- $labelVal = regexReplaceAll "^[^a-zA-Z0-9]+" $labelVal "" }}
5761{{- if ne $labelVal "" }}
5862{{ $rule.labelKey }}: {{ $labelVal | quote }}
5963{{- end }}
@@ -63,7 +67,12 @@ posit.team/dynamic-label-cap-reached: "true"
6367{{- /* Go validation (ValidateDynamicLabelRules) enforces namePrefix < 53 chars, so $maxSuffix is always > 0. */ -}}
6468{{- $maxSuffix := int (sub 63 (len $namePrefix)) }}
6569{{- range $match := $matches }}
66- {{- $suffix := trimPrefix ($rule.trimPrefix | default "") $match | lower | regexReplaceAll "[^a-zA-Z0-9._-]" "_" | regexReplaceAll "_{2,}" "_" | trunc $maxSuffix | regexReplaceAll "[^a-zA-Z0-9]+$" "" | regexReplaceAll "^[^a-zA-Z0-9]+" "" }}
70+ {{- $suffix := trimPrefix ($rule.trimPrefix | default "") $match | lower }}
71+ {{- $suffix = regexReplaceAll "[^a-zA-Z0-9._-]" $suffix "_" }}
72+ {{- $suffix = regexReplaceAll "_{2,}" $suffix "_" }}
73+ {{- $suffix = $suffix | trunc $maxSuffix }}
74+ {{- $suffix = regexReplaceAll "[^a-zA-Z0-9]+$" $suffix "" }}
75+ {{- $suffix = regexReplaceAll "^[^a-zA-Z0-9]+" $suffix "" }}
6776{{- $computedKey := printf "%s%s" $rule.labelPrefix $suffix }}
6877{{- /* must match reservedOperatorAnnotationKey in session_config.go */ -}}
6978{{- if and (ne $suffix "") (ne $computedKey "posit.team/dynamic-label-cap-reached") }}
@@ -76,8 +85,8 @@ posit.team/dynamic-label-cap-reached: "true"
7685{{- end }}`
7786
7887// renderDynamicLabels renders the dynamic labels template block with the given
79- // session config (templateData) and Job data. Uses Helm-compatible regexReplaceAll
80- // argument order (regex, repl, s) where the piped value is the source string .
88+ // session config (templateData) and Job data. Uses Sprig's native regexReplaceAll
89+ // argument order (regex, s, repl) — the template uses explicit args, not piping .
8190func renderDynamicLabels (t * testing.T , templateData map [string ]any , jobData map [string ]any ) string {
8291 t .Helper ()
8392
@@ -89,18 +98,8 @@ func renderDynamicLabels(t *testing.T, templateData map[string]any, jobData map[
8998 tmpl := template .New ("gotpl" )
9099 f := TemplateFuncMap (tmpl )
91100 f = AddOnFuncMap (tmpl , f )
92- // Override regexReplaceAll to match Helm's pipeline-friendly argument order.
93- // Sprig: regexReplaceAll(regex, s, repl) — piped value becomes repl.
94- // Helm: regexReplaceAll(regex, repl, s) — piped value becomes s (source).
95- // NOTE: If upgrading Helm/Sprig, verify that the production argument order still
96- // matches this mock — otherwise tests will pass against a stale signature.
97- // TODO: These Go tests exercise a mocked argument order, not the real Helm rendering
98- // pipeline. Consider adding an integration test that renders through `helm template`
99- // to validate the actual end-to-end rendering path.
100- f ["regexReplaceAll" ] = func (regex string , repl string , s string ) string {
101- r := regexp .MustCompile (regex )
102- return r .ReplaceAllString (s , repl )
103- }
101+ // No regexReplaceAll override needed — the template now uses Sprig's native
102+ // argument order (regex, source, replacement) with explicit args instead of piping.
104103 tmpl .Funcs (f )
105104
106105 _ , err = tmpl .Parse (mockDataDefine + "\n " + dynamicLabelsTemplate )
@@ -118,9 +117,9 @@ func renderDynamicLabels(t *testing.T, templateData map[string]any, jobData map[
118117}
119118
120119// TestCanary_SprigRegexReplaceAllOrder verifies that Sprig's regexReplaceAll
121- // still uses (regex, s, repl) order, which differs from Helm's (regex, repl, s).
122- // Our mock in renderDynamicLabels overrides to Helm's order. If Sprig ever changes
123- // to match Helm, this canary will fire and the mock override can be removed .
120+ // still uses (regex, s, repl) order. Our template uses explicit args matching
121+ // this order. If Sprig ever changes to Helm's order (regex, repl, s), the
122+ // template's explicit calls would break and need updating .
124123func TestCanary_SprigRegexReplaceAllOrder (t * testing.T ) {
125124 tmpl := template .New ("canary" )
126125 f := TemplateFuncMap (tmpl )
@@ -130,12 +129,10 @@ func TestCanary_SprigRegexReplaceAllOrder(t *testing.T) {
130129 fn , ok := prodFn .(func (string , string , string ) string )
131130 require .True (t , ok , "regexReplaceAll should be func(string, string, string) string" )
132131
133- // With Sprig order (regex, s, repl): fn("h", "world", "hello") → replace "h" in "world" → "world" (no match)
134- // With Helm order (regex, repl, s): fn("h", "world", "hello") → replace "h" in "hello" → "worldello"
135- result := fn ("h" , "world" , "hello" )
136- if result == "worldello" {
137- t .Fatalf ("Sprig regexReplaceAll now uses Helm's argument order — remove the mock override in renderDynamicLabels" )
138- }
132+ // With Sprig order (regex, s, repl): fn("h", "hello", "world") → replace "h" in "hello" → "worldello"
133+ // If Sprig changes order, this assertion will fail.
134+ result := fn ("h" , "hello" , "world" )
135+ assert .Equal (t , "worldello" , result , "Sprig regexReplaceAll order changed — template explicit args need updating" )
139136}
140137
141138// TestDynamicLabelsTemplate_DriftDetection verifies that the dynamicLabelsTemplate
@@ -156,8 +153,8 @@ func TestDynamicLabelsTemplate_DriftDetection(t *testing.T) {
156153 {"per-rule cap" , "slice $matches 0 50" },
157154 {"global cap" , "sub 200" },
158155 {"raw match cap" , "regexFindAll $rule.match $str 500" },
159- {"sanitize non-alnum" , `regexReplaceAll "[^a-zA-Z0-9._-]" "_" ` },
160- {"collapse underscores" , `regexReplaceAll "_{2,}" "_" ` },
156+ {"sanitize non-alnum" , `regexReplaceAll "[^a-zA-Z0-9._-]"` },
157+ {"collapse underscores" , `regexReplaceAll "_{2,}"` },
161158 {"reserved key guard" , `posit.team/dynamic-label-cap-reached` },
162159 {"dedup seen dict" , "$seen := dict" },
163160 {"global total dict" , `$globalTotal := dict "n" 0` },
0 commit comments