Repository: helm/helm (go)

Issue:
`toYaml` does not properly indent list items
Calling `toYaml` always places array items at the same indentation depth as the parent item. Here's an example of this behavior:

Template:
```yaml
---
{{- $valuesInObjForm := mustDeepCopy .Values -}}

# Via pipe:
{{ $valuesInObjForm | toYaml }}

# Via arg:
{{ toYaml $valuesInObjForm }}
```

Values:
```yaml
---
array:
  - val 1
  - val 2

key:
  nestedArray:
    - val 1
    - val 2
```

Output of `helm template`:
```yaml
---
# Source: bugtest/templates/test-template.yaml
# Via pipe:
array:
- val 1
- val 2
key:
  nestedArray:
  - val 1
  - val 2

# Via arg:
array:
- val 1
- val 2
key:
  nestedArray:
  - val 1
  - val 2
```

I would expect the following output instead:
```yaml
---
# Source: bugtest/templates/test-template.yaml
# Via pipe:
array:
  - val 1
  - val 2
key:
  nestedArray:
    - val 1
    - val 2

# Via arg:
array:
  - val 1
  - val 2
key:
  nestedArray:
    - val 1
    - val 2
```

Am I missing something here? If this is an actual bug then I figured somebody else would have found it by now.

Output of `helm version`:
`version.BuildInfo{Version:"v3.13.2", GitCommit:"2a2fb3b98829f1e0be6fb18af2f6599e0f4e8243", GitTreeState:"clean", GoVersion:"go1.21.4"}`
Also tested this on v3.10 with identical results

Output of `kubectl version`: N/A

Cloud Provider/Platform (AKS, GKE, Minikube etc.): N/A




Pull request:
Added `toYamlPretty` template function
<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/main/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:
Currently the `toYaml` implementation does not indent list items, which causes issues with other tools in the YAML ecosystem such as linters and formatters. Rather than introduce a potentially breaking change to `toYaml` to support this, I've added a `toYamlPretty` function as suggested by somebody in the [related issue](https://github.com/helm/helm/issues/12577).

Given a Golang map described as:
```go
m := map[string]interface{} {
    "key": []int {1, 2, 3}
}
```

The `toYaml` function will produce:
```yaml
key:
- 1
- 2
- 3
```

The `toYamlPretty` function will produce:
```yaml
key:
  - 1
  - 2
  - 3
```

These are both syntactically correct, but other tools use an indented version which is where this function is useful.

**Special notes for your reviewer**:
I'm guessing this needs docs somewhere, but I'm not sure where to add them. Could somebody please point me in the right direction?

**If applicable**:
- [ ] this PR contains documentation
- [x] this PR contains unit tests
- [ ] this PR has been tested for backwards compatibility

closes #12577

Patch:
diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go
index e291fae15..d03a818c2 100644
--- a/pkg/engine/funcs.go
+++ b/pkg/engine/funcs.go
@@ -25,6 +25,7 @@ import (
 	"github.com/BurntSushi/toml"
 	"github.com/Masterminds/sprig/v3"
 	"sigs.k8s.io/yaml"
+	goYaml "sigs.k8s.io/yaml/goyaml.v3"
 )
 
 // funcMap returns a mapping of all of the functions that Engine has.
@@ -50,6 +51,7 @@ func funcMap() template.FuncMap {
 		"toToml":        toTOML,
 		"fromToml":      fromTOML,
 		"toYaml":        toYAML,
+		"toYamlPretty":  toYAMLPretty,
 		"fromYaml":      fromYAML,
 		"fromYamlArray": fromYAMLArray,
 		"toJson":        toJSON,
@@ -89,6 +91,19 @@ func toYAML(v interface{}) string {
 	return strings.TrimSuffix(string(data), "\n")
 }
 
+func toYAMLPretty(v interface{}) string {
+	var data bytes.Buffer
+	encoder := goYaml.NewEncoder(&data)
+	encoder.SetIndent(2)
+	err := encoder.Encode(v)
+
+	if err != nil {
+		// Swallow errors inside of a template.
+		return ""
+	}
+	return strings.TrimSuffix(data.String(), "\n")
+}
+
 // fromYAML converts a YAML document into a map[string]interface{}.
 //
 // This is not a general-purpose YAML parser, and will not parse all valid


Test patch:
diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go
index 95984c490..a4f4d604f 100644
--- a/pkg/engine/funcs_test.go
+++ b/pkg/engine/funcs_test.go
@@ -33,6 +33,10 @@ func TestFuncs(t *testing.T) {
 		tpl:    `{{ toYaml . }}`,
 		expect: `foo: bar`,
 		vars:   map[string]interface{}{"foo": "bar"},
+	}, {
+		tpl:    `{{ toYamlPretty . }}`,
+		expect: "baz:\n  - 1\n  - 2\n  - 3",
+		vars:   map[string]interface{}{"baz": []int{1, 2, 3}},
 	}, {
 		tpl:    `{{ toToml . }}`,
 		expect: "foo = \"bar\"\n",