diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go index 3d159babd..8d0f644d6 100644 --- a/cmd/helm/flags.go +++ b/cmd/helm/flags.go @@ -47,7 +47,7 @@ func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") - f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)") + f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2 or using json format: {\"key1\": jsonval1, \"key2\": \"jsonval2\"})") f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line") } diff --git a/cmd/helm/install.go b/cmd/helm/install.go index ec651140c..fe09dfc53 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -52,7 +52,7 @@ or use the '--set' flag and pass configuration from the command line, to force a string value use '--set-string'. You can use '--set-file' to set individual values from a file when the value itself is too long for the command line or is dynamically generated. You can also use '--set-json' to set json values -(scalars/objects/arrays) from the command line. +(scalars/objects/arrays) from the command line. Additionally, you can use '--set-json' and passing json object as a string. $ helm install -f myvalues.yaml myredis ./redis @@ -72,6 +72,9 @@ or $ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis +or + + $ helm install --set-json '{"master":{"sidecars":[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]}}' myredis ./redis You can specify the '--values'/'-f' flag multiple times. The priority will be given to the last (right-most) file specified. For example, if both myvalues.yaml and override.yaml diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 7b4267894..6684f9ebf 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -53,7 +53,7 @@ or use the '--set' flag and pass configuration from the command line, to force s values, use '--set-string'. You can use '--set-file' to set individual values from a file when the value itself is too long for the command line or is dynamically generated. You can also use '--set-json' to set json values -(scalars/objects/arrays) from the command line. +(scalars/objects/arrays) from the command line. Additionally, you can use '--set-json' and passing json object as a string. You can specify the '--values'/'-f' flag multiple times. The priority will be given to the last (right-most) file specified. For example, if both myvalues.yaml and override.yaml diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go index 24c47ecba..d7086e8c3 100644 --- a/pkg/cli/values/options.go +++ b/pkg/cli/values/options.go @@ -17,6 +17,7 @@ limitations under the License. package values import ( + "encoding/json" "io" "net/url" "os" @@ -62,8 +63,19 @@ func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, er // User specified a value via --set-json for _, value := range opts.JSONValues { - if err := strvals.ParseJSON(value, base); err != nil { - return nil, errors.Errorf("failed parsing --set-json data %s", value) + trimmedValue := strings.TrimSpace(value) + if len(trimmedValue) > 0 && trimmedValue[0] == '{' { + // If value is JSON object format, parse it as map + var jsonMap map[string]interface{} + if err := json.Unmarshal([]byte(trimmedValue), &jsonMap); err != nil { + return nil, errors.Errorf("failed parsing --set-json data using JSON format: %s", value) + } + base = mergeMaps(base, jsonMap) + } else { + // Otherwise, parse it as key=value format + if err := strvals.ParseJSON(value, base); err != nil { + return nil, errors.Errorf("failed parsing --set-json data %s", value) + } } } diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index 9182e3cc8..5197a1b5e 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -23,7 +23,7 @@ import ( "helm.sh/helm/v4/pkg/getter" ) -func TestMergeValues(t *testing.T) { +func TestMergeMaps(t *testing.T) { nestedMap := map[string]interface{}{ "foo": "bar", "baz": map[string]string{ @@ -86,3 +86,97 @@ func TestReadFile(t *testing.T) { t.Errorf("Expected error when has special strings") } } + +func TestMergeValues(t *testing.T) { + tests := []struct { + name string + opts Options + expected map[string]interface{} + wantErr bool + }{ + { + name: "set-json object", + opts: Options{ + JSONValues: []string{`{"foo": {"bar": "baz"}}`}, + }, + expected: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": "baz", + }, + }, + }, + { + name: "set-json key=value", + opts: Options{ + JSONValues: []string{"foo.bar=[1,2,3]"}, + }, + expected: map[string]interface{}{ + "foo": map[string]interface{}{ + "bar": []interface{}{1.0, 2.0, 3.0}, + }, + }, + }, + { + name: "set regular value", + opts: Options{ + Values: []string{"foo=bar"}, + }, + expected: map[string]interface{}{ + "foo": "bar", + }, + }, + { + name: "set string value", + opts: Options{ + StringValues: []string{"foo=123"}, + }, + expected: map[string]interface{}{ + "foo": "123", + }, + }, + { + name: "set literal value", + opts: Options{ + LiteralValues: []string{"foo=true"}, + }, + expected: map[string]interface{}{ + "foo": "true", + }, + }, + { + name: "multiple options", + opts: Options{ + Values: []string{"a=foo"}, + StringValues: []string{"b=bar"}, + JSONValues: []string{`{"c": "foo1"}`}, + LiteralValues: []string{"d=bar1"}, + }, + expected: map[string]interface{}{ + "a": "foo", + "b": "bar", + "c": "foo1", + "d": "bar1", + }, + }, + { + name: "invalid json", + opts: Options{ + JSONValues: []string{`{invalid`}, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.opts.MergeValues(getter.Providers{}) + if (err != nil) != tt.wantErr { + t.Errorf("MergeValues() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && !reflect.DeepEqual(got, tt.expected) { + t.Errorf("MergeValues() = %v, want %v", got, tt.expected) + } + }) + } +}