Skip to content

Commit d6a2882

Browse files
porridgeclaude
andcommitted
Support array index notation in --set flag
Replace custom path parsing (strings.Split + unstructured.SetNestedField) with Helm's strvals.Parse, which handles the full --set syntax including bracket notation like `central.spec.central.defaultTLSSecret[0].name=val`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6887804 commit d6a2882

4 files changed

Lines changed: 61 additions & 45 deletions

File tree

cmd/deploy.go

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,10 @@ import (
2020
"github.com/stackrox/roxie/internal/logger"
2121
"github.com/stackrox/roxie/internal/manifest"
2222
"github.com/stackrox/roxie/internal/roxieenv"
23-
"github.com/stackrox/roxie/internal/types"
24-
2523
"github.com/stackrox/roxie/internal/stackroxversions"
24+
"github.com/stackrox/roxie/internal/types"
2625
"gopkg.in/yaml.v3"
27-
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26+
"helm.sh/helm/v3/pkg/strvals"
2827
)
2928

3029
var (
@@ -143,28 +142,16 @@ this flag can be used to tell roxie how to pre-load images for the current clust
143142

144143
registerFlag(cmd, settings, "set", "Set expressions, e.g. securedCluster.spec.clusterName=sensor",
145144
withApplyFn("set-expression", func(config *deployer.Config, expr string) error {
146-
key, yamlValue, found := strings.Cut(expr, "=")
145+
key, _, found := strings.Cut(expr, "=")
147146
if !found {
148147
return fmt.Errorf("invalid set expression '%s': expected format 'key.path=value'", expr)
149148
}
150-
var val interface{}
151-
if err := yaml.Unmarshal([]byte(yamlValue), &val); err != nil {
152-
return fmt.Errorf("failed to unmarshal value '%s' for key '%s': %w", yamlValue, key, err)
153-
}
154-
// SetNestedField requires JSON-compatible types: float64 for numbers, not int.
155-
switch v := val.(type) {
156-
case int:
157-
val = float64(v)
158-
case int64:
159-
val = float64(v)
160-
}
161-
pathElements := strings.Split(key, ".")
162-
if len(pathElements) > 0 && pathElements[0] == "spec" {
149+
if strings.HasPrefix(key, "spec.") {
163150
return errors.New("set expression begins with 'spec.' -- it must be prefixed with 'central.' or 'securedCluster.'")
164151
}
165-
unstructuredPatch := make(map[string]interface{})
166-
if err := unstructured.SetNestedField(unstructuredPatch, val, pathElements...); err != nil {
167-
return err
152+
unstructuredPatch, err := strvals.Parse(expr)
153+
if err != nil {
154+
return fmt.Errorf("parsing set expression %q: %w", expr, err)
168155
}
169156
var patch deployer.Config
170157
if err := helpers.MapToStruct(unstructuredPatch, &patch); err != nil {

cmd/deploy_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,27 @@ central:
208208
)
209209
},
210210
},
211+
{
212+
name: "set expressions support array index notation",
213+
args: []string{
214+
"--set", "central.spec.central.defaultTLSSecret[0].name=frontend-cert",
215+
},
216+
assert: func(t *testing.T, cfg deployer.Config) {
217+
assert.Equal(t,
218+
map[string]interface{}{
219+
"central": map[string]interface{}{
220+
"defaultTLSSecret": []interface{}{
221+
map[string]interface{}{
222+
"name": "frontend-cert",
223+
},
224+
},
225+
},
226+
},
227+
cfg.Central.Spec,
228+
"Central.Spec mismatch",
229+
)
230+
},
231+
},
211232
}
212233

213234
for _, tt := range tests {

go.mod

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,29 @@ go 1.26.0
44

55
require (
66
dario.cat/mergo v1.0.2
7+
github.com/Masterminds/semver/v3 v3.5.0
78
github.com/fatih/color v1.19.0
89
github.com/google/go-containerregistry v0.21.6
910
github.com/moby/moby/client v0.4.1
1011
github.com/spf13/cobra v1.10.2
1112
github.com/spf13/pflag v1.0.10
1213
github.com/stretchr/testify v1.11.1
13-
golang.org/x/term v0.43.0
14+
golang.org/x/term v0.44.0
1415
gopkg.in/yaml.v3 v3.0.1
15-
k8s.io/apimachinery v0.36.1
16+
helm.sh/helm/v3 v3.21.2
17+
k8s.io/apimachinery v0.36.2
1618
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
1719
)
1820

1921
require (
20-
github.com/Masterminds/semver/v3 v3.5.0
2122
github.com/Microsoft/go-winio v0.6.2 // indirect
2223
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2324
github.com/containerd/errdefs v1.0.0 // indirect
2425
github.com/containerd/errdefs/pkg v0.3.0 // indirect
2526
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
2627
github.com/distribution/reference v0.6.0 // indirect
2728
github.com/docker/cli v29.4.3+incompatible // indirect
28-
github.com/docker/docker-credential-helpers v0.9.3 // indirect
29+
github.com/docker/docker-credential-helpers v0.9.5 // indirect
2930
github.com/docker/go-connections v0.7.0 // indirect
3031
github.com/docker/go-units v0.5.0 // indirect
3132
github.com/felixge/httpsnoop v1.0.4 // indirect
@@ -43,25 +44,27 @@ require (
4344
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
4445
github.com/opencontainers/go-digest v1.0.0 // indirect
4546
github.com/opencontainers/image-spec v1.1.1 // indirect
47+
github.com/pkg/errors v0.9.1 // indirect
4648
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
4749
github.com/sirupsen/logrus v1.9.4 // indirect
4850
github.com/x448/float16 v0.8.4 // indirect
4951
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
50-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
52+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
5153
go.opentelemetry.io/otel v1.44.0 // indirect
5254
go.opentelemetry.io/otel/metric v1.44.0 // indirect
5355
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
5456
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
5557
go.opentelemetry.io/otel/trace v1.44.0 // indirect
56-
go.yaml.in/yaml/v2 v2.4.3 // indirect
58+
go.yaml.in/yaml/v2 v2.4.4 // indirect
5759
golang.org/x/net v0.55.0 // indirect
58-
golang.org/x/sync v0.20.0 // indirect
59-
golang.org/x/sys v0.45.0 // indirect
60-
golang.org/x/text v0.37.0 // indirect
60+
golang.org/x/sync v0.21.0 // indirect
61+
golang.org/x/sys v0.46.0 // indirect
62+
golang.org/x/text v0.38.0 // indirect
6163
gopkg.in/inf.v0 v0.9.1 // indirect
6264
k8s.io/klog/v2 v2.140.0 // indirect
6365
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect
6466
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
6567
sigs.k8s.io/randfill v1.0.0 // indirect
6668
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
69+
sigs.k8s.io/yaml v1.6.0 // indirect
6770
)

go.sum

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
1919
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
2020
github.com/docker/cli v29.4.3+incompatible h1:u+UliYm2J/rYrIh2FqHQg32neRG8GjbvNuwQRTzGspU=
2121
github.com/docker/cli v29.4.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
22-
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
23-
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
22+
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
23+
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
2424
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
2525
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
2626
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -73,6 +73,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
7373
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
7474
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
7575
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
76+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
77+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
7678
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
7779
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
7880
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -94,8 +96,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
9496
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
9597
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
9698
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
97-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
98-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
99+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
100+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
99101
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
100102
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
101103
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
@@ -106,20 +108,21 @@ go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRk
106108
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
107109
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
108110
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
109-
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
110-
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
111+
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
112+
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
113+
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
111114
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
112115
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
113116
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
114-
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
115-
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
117+
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
118+
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
116119
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
117-
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
118-
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
119-
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
120-
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
121-
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
122-
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
120+
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
121+
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
122+
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
123+
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
124+
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
125+
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
123126
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
124127
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
125128
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -129,8 +132,10 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
129132
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
130133
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
131134
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
132-
k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
133-
k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
135+
helm.sh/helm/v3 v3.21.2 h1:O8ktw30zQZm4tNRavKRN44rotcHICdbSU3e6gS7CCbQ=
136+
helm.sh/helm/v3 v3.21.2/go.mod h1:Da+9m67Mzg42rQNrWAj0RvUd4tDT4dL8mrxUwvRgYWo=
137+
k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ=
138+
k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4=
134139
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
135140
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
136141
k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg=

0 commit comments

Comments
 (0)