Repository: helm/helm (go)

Issue:
helm 3.0.0-rc.3 --set-string does not work with booleans 
It seems that --set-string does not work with boolean as expected under helm 3.0.0-rc.3

According to https://helm.sh/docs/using_helm/#the-format-and-limitations-of-set, 
> --set-string foo=true results in a string value of "true".


Running the following, I would expect it to quote the "true", and deploy the nginx-ingress on the nodes matching the selector.
```
helm upgrade --install nginx-ingress stable/nginx-ingress --namespace nginx-ingress --set-string controller.nodeSelector."boolean\.dotted/slashed-dashed"=true
```
However, I get the following error:
```
Release "nginx-ingress" does not exist. Installing it now.
Error: Deployment in version "v1" cannot be handled as a Deployment: v1.Deployment.Spec: v1.DeploymentSpec.Template: v1.PodTemplateSpec.Spec: v1.PodSpec.NodeSelector: ReadString: expects " or n, but found t, error found in #10 byte of ...|-dashed":true},"serv|..., bigger context ...|,"nodeSelector":{"boolean.dotted/slashed-dashed":true},"serviceAccountName":"nginx-ingress-nginx-|...
```

It seems to be a regression of #2848, the code merged in #4142 has disappeared 


Output of `helm version`:
```helm version
version.BuildInfo{Version:"v3.0.0-rc.3", GitCommit:"2ed206799b451830c68bff30af2a52879b8b937a", GitTreeState:"clean", GoVersion:"go1.13.4"}
```
Output of `kubectl version`:
```
Client Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.2", GitCommit:"c97fe5036ef3df2967d086711e6c0c405941e14b", GitTreeState:"clean", BuildDate:"2019-10-15T19:18:23Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.2", GitCommit:"c97fe5036ef3df2967d086711e6c0c405941e14b", GitTreeState:"clean", BuildDate:"2019-10-15T19:09:08Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}
```
Cloud Provider/Platform (AKS, GKE, Minikube etc.): 
Bare metal nodes deployed with RKE



Pull request:
fix(strvals): port #3912, #4142, #4682, and #5151 to Helm 3
see #3912, #4142. #4682, and #5151
closes #6940

Signed-off-by: Matthew Fisher <matt.fisher@microsoft.com>

Patch:
diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go
index eaadbfb07..03adbd3cb 100644
--- a/pkg/strvals/parser.go
+++ b/pkg/strvals/parser.go
@@ -84,7 +84,7 @@ func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error
 	return vals, err
 }
 
-// ParseIntoString parses a strvals line nad merges the result into dest.
+// ParseIntoString parses a strvals line and merges the result into dest.
 //
 // This method always returns a string as the value.
 func ParseIntoString(s string, dest map[string]interface{}) error {
@@ -108,6 +108,9 @@ type RunesValueReader func([]rune) (interface{}, error)
 
 // parser is a simple parser that takes a strvals line and parses it into a
 // map representation.
+//
+// where sc is the source of the original data being parsed
+// where data is the final parsed data from the parses with correct types
 type parser struct {
 	sc     *bytes.Buffer
 	data   map[string]interface{}
@@ -285,7 +288,13 @@ func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) {
 		// We have a nested object. Send to t.key
 		inner := map[string]interface{}{}
 		if len(list) > i {
-			inner = list[i].(map[string]interface{})
+			var ok bool
+			inner, ok = list[i].(map[string]interface{})
+			if !ok {
+				// We have indices out of order. Initialize empty value.
+				list[i] = map[string]interface{}{}
+				inner = list[i].(map[string]interface{})
+			}
 		}
 
 		// Recurse
@@ -367,6 +376,11 @@ func inMap(k rune, m map[rune]bool) bool {
 
 func typedVal(v []rune, st bool) interface{} {
 	val := string(v)
+
+	if st {
+		return val
+	}
+
 	if strings.EqualFold(val, "true") {
 		return true
 	}
@@ -375,8 +389,16 @@ func typedVal(v []rune, st bool) interface{} {
 		return false
 	}
 
-	// If this value does not start with zero, and not returnString, try parsing it to an int
-	if !st && len(val) != 0 && val[0] != '0' {
+	if strings.EqualFold(val, "null") {
+		return nil
+	}
+
+	if strings.EqualFold(val, "0") {
+		return int64(0)
+	}
+
+	// If this value does not start with zero, try parsing it to an int
+	if len(val) != 0 && val[0] != '0' {
 		if iv, err := strconv.ParseInt(val, 10, 64); err == nil {
 			return iv
 		}


Test patch:
diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go
index ff8d58587..7f38efa52 100644
--- a/pkg/strvals/parser_test.go
+++ b/pkg/strvals/parser_test.go
@@ -75,12 +75,32 @@ func TestParseSet(t *testing.T) {
 			expect: map[string]interface{}{"long_int_string": "1234567890"},
 			err:    false,
 		},
+		{
+			str:    "boolean=true",
+			expect: map[string]interface{}{"boolean": "true"},
+			err:    false,
+		},
+		{
+			str:    "is_null=null",
+			expect: map[string]interface{}{"is_null": "null"},
+			err:    false,
+		},
+		{
+			str:    "zero=0",
+			expect: map[string]interface{}{"zero": "0"},
+			err:    false,
+		},
 	}
 	tests := []struct {
 		str    string
 		expect map[string]interface{}
 		err    bool
 	}{
+		{
+			"name1=null,f=false,t=true",
+			map[string]interface{}{"name1": nil, "f": false, "t": true},
+			false,
+		},
 		{
 			"name1=value1",
 			map[string]interface{}{"name1": "value1"},
@@ -108,10 +128,23 @@ func TestParseSet(t *testing.T) {
 			str:    "leading_zeros=00009",
 			expect: map[string]interface{}{"leading_zeros": "00009"},
 		},
+		{
+			str:    "zero_int=0",
+			expect: map[string]interface{}{"zero_int": 0},
+		},
 		{
 			str:    "long_int=1234567890",
 			expect: map[string]interface{}{"long_int": 1234567890},
 		},
+		{
+			str:    "boolean=true",
+			expect: map[string]interface{}{"boolean": true},
+		},
+		{
+			str:    "is_null=null",
+			expect: map[string]interface{}{"is_null": nil},
+			err:    false,
+		},
 		{
 			str: "name1,name2=",
 			err: true,
@@ -270,6 +303,30 @@ func TestParseSet(t *testing.T) {
 			str:    "nested[1][1]=1",
 			expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, 1}}},
 		},
+		{
+			str: "name1.name2[0].foo=bar,name1.name2[1].foo=bar",
+			expect: map[string]interface{}{
+				"name1": map[string]interface{}{
+					"name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}},
+				},
+			},
+		},
+		{
+			str: "name1.name2[1].foo=bar,name1.name2[0].foo=bar",
+			expect: map[string]interface{}{
+				"name1": map[string]interface{}{
+					"name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}},
+				},
+			},
+		},
+		{
+			str: "name1.name2[1].foo=bar",
+			expect: map[string]interface{}{
+				"name1": map[string]interface{}{
+					"name2": []map[string]interface{}{nil, {"foo": "bar"}},
+				},
+			},
+		},
 	}
 
 	for _, tt := range tests {
@@ -331,12 +388,13 @@ func TestParseInto(t *testing.T) {
 			"inner2": "value2",
 		},
 	}
-	input := "outer.inner1=value1,outer.inner3=value3"
+	input := "outer.inner1=value1,outer.inner3=value3,outer.inner4=4"
 	expect := map[string]interface{}{
 		"outer": map[string]interface{}{
 			"inner1": "value1",
 			"inner2": "value2",
 			"inner3": "value3",
+			"inner4": 4,
 		},
 	}
 
@@ -357,6 +415,39 @@ func TestParseInto(t *testing.T) {
 		t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2)
 	}
 }
+func TestParseIntoString(t *testing.T) {
+	got := map[string]interface{}{
+		"outer": map[string]interface{}{
+			"inner1": "overwrite",
+			"inner2": "value2",
+		},
+	}
+	input := "outer.inner1=1,outer.inner3=3"
+	expect := map[string]interface{}{
+		"outer": map[string]interface{}{
+			"inner1": "1",
+			"inner2": "value2",
+			"inner3": "3",
+		},
+	}
+
+	if err := ParseIntoString(input, got); err != nil {
+		t.Fatal(err)
+	}
+
+	y1, err := yaml.Marshal(expect)
+	if err != nil {
+		t.Fatal(err)
+	}
+	y2, err := yaml.Marshal(got)
+	if err != nil {
+		t.Fatalf("Error serializing parsed value: %s", err)
+	}
+
+	if string(y1) != string(y2) {
+		t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2)
+	}
+}
 
 func TestParseIntoFile(t *testing.T) {
 	got := map[string]interface{}{}
@@ -367,7 +458,7 @@ func TestParseIntoFile(t *testing.T) {
 	rs2v := func(rs []rune) (interface{}, error) {
 		v := string(rs)
 		if v != "path1" {
-			t.Errorf("%s: RunesValueReader: Expected value path1, got %s", input, v)
+			t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v)
 			return "", nil
 		}
 		return "value1", nil