fix(templating): SplitManifests must preserve line endings for downstream YAML parsers (#31952)

* fix(templating): SplitManifests must preserve line endings for downstream YAML parsers

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

* Address copilot comment about skipping empty docs

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

* Port fix to release v2

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

* Address copilot comments

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>

---------

Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
pull/32050/head
Matheus Pimenta 5 months ago committed by GitHub
parent c51456c45d
commit 265c5eb530
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -21,6 +21,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"unicode"
) )
// SimpleHead defines what the structure of the head of a manifest file // SimpleHead defines what the structure of the head of a manifest file
@ -35,7 +36,16 @@ type SimpleHead struct {
var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*")
// SplitManifests takes a string of manifest and returns a map contains individual manifests // SplitManifests takes a manifest string and returns a map containing individual manifests.
//
// **Note for Chart API v3**: This function (due to the regex above) has allowed _WRONG_
// Go templates to be defined inside charts across the years. The generated text from Go
// templates may contain `---apiVersion: v1`, and this function magically splits this back
// to `---\napiVersion: v1`. This has caused issues recently after Helm 4 introduced
// kio.ParseAll to inject annotations when post-renderers are used. In Chart API v3,
// we should kill this regex with fire (or change it) and expose charts doing the wrong
// thing Go template-wise. Helm should say a big _NO_ to charts doing the wrong thing,
// with or without post-renderers.
func SplitManifests(bigFile string) map[string]string { func SplitManifests(bigFile string) map[string]string {
// Basically, we're quickly splitting a stream of YAML documents into an // Basically, we're quickly splitting a stream of YAML documents into an
// array of YAML docs. The file name is just a place holder, but should be // array of YAML docs. The file name is just a place holder, but should be
@ -44,15 +54,15 @@ func SplitManifests(bigFile string) map[string]string {
tpl := "manifest-%d" tpl := "manifest-%d"
res := map[string]string{} res := map[string]string{}
// Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly.
bigFileTmp := strings.TrimSpace(bigFile) bigFileTmp := strings.TrimLeftFunc(bigFile, unicode.IsSpace)
docs := sep.Split(bigFileTmp, -1) docs := sep.Split(bigFileTmp, -1)
var count int var count int
for _, d := range docs { for _, d := range docs {
if d == "" { if strings.TrimSpace(d) == "" {
continue continue
} }
d = strings.TrimSpace(d) d = strings.TrimLeftFunc(d, unicode.IsSpace)
res[fmt.Sprintf(tpl, count)] = d res[fmt.Sprintf(tpl, count)] = d
count = count + 1 count = count + 1
} }

@ -21,7 +21,15 @@ import (
"testing" "testing"
) )
const mockManifestFile = ` func TestSplitManifests(t *testing.T) {
tests := []struct {
name string
input string
expected map[string]string
}{
{
name: "single doc with leading separator and whitespace",
input: `
--- ---
apiVersion: v1 apiVersion: v1
@ -35,9 +43,9 @@ spec:
- name: nemo-test - name: nemo-test
image: fake-image image: fake-image
cmd: fake-command cmd: fake-command
` `,
expected: map[string]string{
const expectedManifest = `apiVersion: v1 "manifest-0": `apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: finding-nemo, name: finding-nemo,
@ -47,15 +55,463 @@ spec:
containers: containers:
- name: nemo-test - name: nemo-test
image: fake-image image: fake-image
cmd: fake-command` cmd: fake-command
`,
},
},
{
name: "empty input",
input: "",
expected: map[string]string{},
},
{
name: "whitespace only",
input: " \n\n \n",
expected: map[string]string{},
},
{
name: "whitespace-only doc after separator is skipped",
input: "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n---\n \n",
expected: map[string]string{
"manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1",
},
},
{
name: "single doc no separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
`,
},
},
{
name: "two docs with proper separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
},
},
func TestSplitManifest(t *testing.T) { // Block scalar chomping indicator tests using | (clip), |- (strip), and |+ (keep)
manifests := SplitManifests(mockManifestFile) // inputs with 0, 1, and 2 trailing newlines after the block content.
if len(manifests) != 1 { // Note: the emitter may normalize the output chomping indicator when the
t.Errorf("Expected 1 manifest, got %v", len(manifests)) // trailing newline count makes another indicator equivalent for the result.
// | (clip) input — clips trailing newlines to exactly one, though with
// 0 trailing newlines the emitted output may normalize to |-.
{
name: "block scalar clip (|) with 0 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
},
},
{
name: "block scalar clip (|) with 1 trailing newline",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
},
},
{
name: "block scalar clip (|) with 2 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
},
},
// |- (strip)
{
name: "block scalar strip (|-) with 0 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello`,
},
},
{
name: "block scalar strip (|-) with 1 trailing newline",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
},
},
{
name: "block scalar strip (|-) with 2 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
},
},
// |+ (keep)
{
name: "block scalar keep (|+) with 0 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
},
},
{
name: "block scalar keep (|+) with 1 trailing newline",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
},
},
{
name: "block scalar keep (|+) with 2 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
},
},
// Multi-doc with block scalars: the regex consumes \s*\n before ---,
// so trailing newlines from non-last docs are stripped.
{
name: "multi-doc block scalar clip (|) before separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
},
},
{
name: "multi-doc block scalar keep (|+) with 2 trailing newlines before separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
},
},
// **Note for Chart API v3**: The following tests exercise the lenient
// regex that splits `---apiVersion` back into separate documents.
// In Chart API v3, these inputs should return an _ERROR_ instead.
// See the comment on the SplitManifests function for more details.
{
name: "leading glued separator (---apiVersion)",
input: `
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
`,
},
},
{
name: "mid-content glued separator (---apiVersion)",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
},
},
{
name: "multiple glued separators",
input: `
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2`,
"manifest-2": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
},
},
{
name: "mixed glued and proper separators",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2`,
"manifest-2": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
},
},
} }
expected := map[string]string{"manifest-0": expectedManifest}
if !reflect.DeepEqual(manifests, expected) { for _, tt := range tests {
t.Errorf("Expected %v, got %v", expected, manifests) t.Run(tt.name, func(t *testing.T) {
result := SplitManifests(tt.input)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("SplitManifests() =\n%v\nwant:\n%v", result, tt.expected)
}
})
} }
} }

@ -418,7 +418,8 @@ func TestAnnotateAndMerge(t *testing.T) {
{ {
name: "single file with single manifest", name: "single file with single manifest",
files: map[string]string{ files: map[string]string{
"templates/configmap.yaml": `apiVersion: v1 "templates/configmap.yaml": `
apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: test-cm name: test-cm
@ -438,13 +439,15 @@ data:
{ {
name: "multiple files with multiple manifests", name: "multiple files with multiple manifests",
files: map[string]string{ files: map[string]string{
"templates/configmap.yaml": `apiVersion: v1 "templates/configmap.yaml": `
apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: test-cm name: test-cm
data: data:
key: value`, key: value`,
"templates/secret.yaml": `apiVersion: v1 "templates/secret.yaml": `
apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
name: test-secret name: test-secret
@ -473,7 +476,8 @@ data:
{ {
name: "file with multiple manifests", name: "file with multiple manifests",
files: map[string]string{ files: map[string]string{
"templates/multi.yaml": `apiVersion: v1 "templates/multi.yaml": `
apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: test-cm1 name: test-cm1
@ -509,7 +513,8 @@ data:
{ {
name: "partials and empty files are removed", name: "partials and empty files are removed",
files: map[string]string{ files: map[string]string{
"templates/cm.yaml": `apiVersion: v1 "templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: test-cm1 name: test-cm1
@ -531,14 +536,16 @@ metadata:
{ {
name: "empty file", name: "empty file",
files: map[string]string{ files: map[string]string{
"templates/empty.yaml": "", "templates/empty.yaml": `
`,
}, },
expected: ``, expected: ``,
}, },
{ {
name: "invalid yaml", name: "invalid yaml",
files: map[string]string{ files: map[string]string{
"templates/invalid.yaml": `invalid: yaml: content: "templates/invalid.yaml": `
invalid: yaml: content:
- malformed`, - malformed`,
}, },
expectedError: "parsing templates/invalid.yaml", expectedError: "parsing templates/invalid.yaml",
@ -546,7 +553,12 @@ metadata:
{ {
name: "leading doc separator glued to content by template whitespace trimming", name: "leading doc separator glued to content by template whitespace trimming",
files: map[string]string{ files: map[string]string{
"templates/service.yaml": "---apiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", "templates/service.yaml": `
---apiVersion: v1
kind: Service
metadata:
name: test-svc
`,
}, },
expected: `apiVersion: v1 expected: `apiVersion: v1
kind: Service kind: Service
@ -559,7 +571,13 @@ metadata:
{ {
name: "leading doc separator on its own line", name: "leading doc separator on its own line",
files: map[string]string{ files: map[string]string{
"templates/service.yaml": "---\napiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", "templates/service.yaml": `
---
apiVersion: v1
kind: Service
metadata:
name: test-svc
`,
}, },
expected: `apiVersion: v1 expected: `apiVersion: v1
kind: Service kind: Service
@ -572,7 +590,14 @@ metadata:
{ {
name: "multiple leading doc separators", name: "multiple leading doc separators",
files: map[string]string{ files: map[string]string{
"templates/service.yaml": "---\n---\napiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", "templates/service.yaml": `
---
---
apiVersion: v1
kind: Service
metadata:
name: test-svc
`,
}, },
expected: `apiVersion: v1 expected: `apiVersion: v1
kind: Service kind: Service
@ -585,7 +610,16 @@ metadata:
{ {
name: "mid-content doc separator glued to content by template whitespace trimming", name: "mid-content doc separator glued to content by template whitespace trimming",
files: map[string]string{ files: map[string]string{
"templates/all.yaml": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test-cm\n---apiVersion: v1\nkind: Service\nmetadata:\n name: test-svc\n", "templates/all.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test-cm
---apiVersion: v1
kind: Service
metadata:
name: test-svc
`,
}, },
expected: `apiVersion: v1 expected: `apiVersion: v1
kind: ConfigMap kind: ConfigMap
@ -631,7 +665,7 @@ metadata:
annotations: annotations:
postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml' postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml'
data: data:
ca.crt: |- ca.crt: |
------BEGIN CERTIFICATE------ ------BEGIN CERTIFICATE------
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zAKBggqhkjOPQQDAzASMRAw MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zAKBggqhkjOPQQDAzASMRAw
DgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYwMDAw DgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYwMDAw
@ -667,7 +701,7 @@ metadata:
annotations: annotations:
postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml' postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml'
data: data:
config: |- config: |
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
[section] [section]
key = value key = value
@ -694,7 +728,7 @@ metadata:
annotations: annotations:
postrenderer.helm.sh/postrender-filename: 'templates/dashboard.yaml' postrenderer.helm.sh/postrender-filename: 'templates/dashboard.yaml'
data: data:
dashboard.json: |- dashboard.json: |
{"options":{"---------":{"color":"#292929","text":"N/A"}}} {"options":{"---------":{"color":"#292929","text":"N/A"}}}
`, `,
}, },
@ -933,6 +967,605 @@ metadata:
name: cm-12 name: cm-12
annotations: annotations:
postrenderer.helm.sh/postrender-filename: 'templates/many.yaml' postrenderer.helm.sh/postrender-filename: 'templates/many.yaml'
`,
},
// Block scalar chomping indicator tests using | (clip), |- (strip), and |+ (keep)
// inputs with 0, 1, and 2 trailing newlines after the block content.
// Note: the emitter may normalize the output chomping indicator when the
// trailing newline count makes another indicator equivalent for the result.
// | (clip) input — clips trailing newlines to exactly one, though with
// 0 trailing newlines the emitted output may normalize to |-.
{
name: "block scalar clip (|) with 0 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
`,
},
{
name: "block scalar clip (|) with 1 trailing newline",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |
hello
`,
},
{
name: "block scalar clip (|) with 2 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |
hello
`,
},
// |- (strip) — strips all trailing newlines
{
name: "block scalar strip (|-) with 0 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
`,
},
{
name: "block scalar strip (|-) with 1 trailing newline",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
`,
},
{
name: "block scalar strip (|-) with 2 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
`,
},
// |+ (keep) — preserves all trailing newlines
{
name: "block scalar keep (|+) with 0 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
`,
},
{
name: "block scalar keep (|+) with 1 trailing newline",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |
hello
`,
},
{
name: "block scalar keep (|+) with 2 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |+
hello
`,
},
// Multi-doc tests: block scalar doc is NOT the last document.
// SplitManifests' regex consumes \s*\n before ---, so trailing
// newlines from non-last docs are always stripped.
// | (clip) in multi-doc (first doc)
{
name: "multi-doc block scalar clip (|) with 0 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
data:
val: simple`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
val: simple
`,
},
{
name: "multi-doc block scalar clip (|) with 1 trailing newline",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
data:
val: simple`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
val: simple
`,
},
{
name: "multi-doc block scalar clip (|) with 2 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
data:
val: simple`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
val: simple
`,
},
// |- (strip) in multi-doc (first doc)
{
name: "multi-doc block scalar strip (|-) with 0 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
data:
val: simple`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
val: simple
`,
},
{
name: "multi-doc block scalar strip (|-) with 1 trailing newline",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
data:
val: simple`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
val: simple
`,
},
{
name: "multi-doc block scalar strip (|-) with 2 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
data:
val: simple`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
val: simple
`,
},
// |+ (keep) in multi-doc (first doc)
{
name: "multi-doc block scalar keep (|+) with 0 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
data:
val: simple`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
val: simple
`,
},
{
name: "multi-doc block scalar keep (|+) with 1 trailing newline",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
data:
val: simple`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
val: simple
`,
},
{
name: "multi-doc block scalar keep (|+) with 2 trailing newlines",
files: map[string]string{
"templates/cm.yaml": `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
data:
val: simple`,
},
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: test
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
key: |-
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
annotations:
postrenderer.helm.sh/postrender-filename: 'templates/cm.yaml'
data:
val: simple
`, `,
}, },
} }
@ -1201,12 +1834,15 @@ func TestRenderResources_PostRenderer_Success(t *testing.T) {
expectedBuf := `--- expectedBuf := `---
# Source: yellow/templates/foodpie # Source: yellow/templates/foodpie
foodpie: world foodpie: world
--- ---
# Source: yellow/templates/with-partials # Source: yellow/templates/with-partials
yellow: Earth yellow: Earth
--- ---
# Source: yellow/templates/yellow # Source: yellow/templates/yellow
yellow: world yellow: world
` `
expectedHook := `kind: ConfigMap expectedHook := `kind: ConfigMap
metadata: metadata:
@ -1214,7 +1850,8 @@ metadata:
annotations: annotations:
"helm.sh/hook": post-install,pre-delete,post-upgrade "helm.sh/hook": post-install,pre-delete,post-upgrade
data: data:
name: value` name: value
`
assert.Equal(t, expectedBuf, buf.String()) assert.Equal(t, expectedBuf, buf.String())
assert.Len(t, hooks, 1) assert.Len(t, hooks, 1)
@ -1319,14 +1956,17 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) {
# Source: hello/templates/goodbye # Source: hello/templates/goodbye
goodbye: world goodbye: world
color: blue color: blue
--- ---
# Source: hello/templates/hello # Source: hello/templates/hello
hello: world hello: world
color: blue color: blue
--- ---
# Source: hello/templates/with-partials # Source: hello/templates/with-partials
hello: Earth hello: Earth
color: blue color: blue
` `
assert.Contains(t, output, "color: blue") assert.Contains(t, output, "color: blue")
assert.Equal(t, 3, strings.Count(output, "color: blue")) assert.Equal(t, 3, strings.Count(output, "color: blue"))

@ -23,3 +23,4 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: schedule-agents name: schedule-agents
namespace: spaced namespace: spaced

@ -19,3 +19,4 @@ metadata:
data: data:
foo: bar foo: bar

@ -15,6 +15,7 @@ metadata:
name: test-secret name: test-secret
stringData: stringData:
foo: bar foo: bar
--- ---
# Source: chart-with-secret/templates/configmap.yaml # Source: chart-with-secret/templates/configmap.yaml
apiVersion: v1 apiVersion: v1
@ -24,3 +25,4 @@ metadata:
data: data:
foo: bar foo: bar

@ -15,6 +15,7 @@ hash:
key4: 4 key4: 4
key5: 5 key5: 5
key6: 6 key6: 6
--- ---
# Source: issue-9027/templates/values.yaml # Source: issue-9027/templates/values.yaml
global: global:

@ -155,6 +155,7 @@ spec:
policyTypes: policyTypes:
- Egress - Egress
- Ingress - Ingress
--- ---
# Source: object-order/templates/01-a.yml # Source: object-order/templates/01-a.yml
# 4 (Deployment should come after all NetworkPolicy manifests, since 'helm template' outputs in install order) # 4 (Deployment should come after all NetworkPolicy manifests, since 'helm template' outputs in install order)

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/role.yaml # Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -14,6 +15,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -28,6 +30,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -45,6 +48,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -62,6 +66,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -93,6 +98,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -112,3 +118,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/role.yaml # Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -14,6 +15,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -28,6 +30,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -45,6 +48,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -62,6 +66,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -93,6 +98,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -112,3 +118,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/role.yaml # Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -14,6 +15,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -28,6 +30,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -45,6 +48,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -62,6 +66,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/configmap.yaml # Source: subchart/templates/subdir/configmap.yaml
apiVersion: v1 apiVersion: v1
@ -22,6 +23,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -36,6 +38,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -53,6 +56,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -70,6 +74,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -101,6 +106,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -120,3 +126,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/configmap.yaml # Source: subchart/templates/subdir/configmap.yaml
apiVersion: v1 apiVersion: v1
@ -22,6 +23,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -36,6 +38,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -53,6 +56,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -70,6 +74,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -101,6 +106,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -120,3 +126,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/configmap.yaml # Source: subchart/templates/subdir/configmap.yaml
apiVersion: v1 apiVersion: v1
@ -22,6 +23,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -36,6 +38,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -53,6 +56,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -70,6 +74,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -101,6 +106,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -120,3 +126,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/role.yaml # Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -14,6 +15,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -28,6 +30,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -45,6 +48,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -62,6 +66,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -93,6 +98,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -112,3 +118,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/role.yaml # Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -14,6 +15,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -28,6 +30,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -45,6 +48,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -62,6 +66,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -95,6 +100,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -114,3 +120,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -21,6 +21,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/role.yaml # Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -31,6 +32,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -45,6 +47,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -62,6 +65,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -79,6 +83,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -110,6 +115,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -129,3 +135,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/role.yaml # Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -14,6 +15,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -28,6 +30,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -45,6 +48,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -62,6 +66,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -93,6 +98,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -112,3 +118,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -4,6 +4,7 @@ apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: subchart-sa name: subchart-sa
--- ---
# Source: subchart/templates/subdir/role.yaml # Source: subchart/templates/subdir/role.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -14,6 +15,7 @@ rules:
- apiGroups: [""] - apiGroups: [""]
resources: ["pods"] resources: ["pods"]
verbs: ["get","list","watch"] verbs: ["get","list","watch"]
--- ---
# Source: subchart/templates/subdir/rolebinding.yaml # Source: subchart/templates/subdir/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
@ -28,6 +30,7 @@ subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: subchart-sa name: subchart-sa
namespace: default namespace: default
--- ---
# Source: subchart/charts/subcharta/templates/service.yaml # Source: subchart/charts/subcharta/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -45,6 +48,7 @@ spec:
name: apache name: apache
selector: selector:
app.kubernetes.io/name: subcharta app.kubernetes.io/name: subcharta
--- ---
# Source: subchart/charts/subchartb/templates/service.yaml # Source: subchart/charts/subchartb/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -62,6 +66,7 @@ spec:
name: nginx name: nginx
selector: selector:
app.kubernetes.io/name: subchartb app.kubernetes.io/name: subchartb
--- ---
# Source: subchart/templates/service.yaml # Source: subchart/templates/service.yaml
apiVersion: v1 apiVersion: v1
@ -93,6 +98,7 @@ metadata:
"helm.sh/hook": test "helm.sh/hook": test
data: data:
message: Hello World message: Hello World
--- ---
# Source: subchart/templates/tests/test-nothing.yaml # Source: subchart/templates/tests/test-nothing.yaml
apiVersion: v1 apiVersion: v1
@ -112,3 +118,4 @@ spec:
- echo - echo
- "$message" - "$message"
restartPolicy: Never restartPolicy: Never

@ -21,6 +21,7 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"unicode"
) )
// SimpleHead defines what the structure of the head of a manifest file // SimpleHead defines what the structure of the head of a manifest file
@ -35,7 +36,7 @@ type SimpleHead struct {
var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*")
// SplitManifests takes a string of manifest and returns a map contains individual manifests // SplitManifests takes a manifest string and returns a map containing individual manifests.
// //
// **Note for Chart API v3**: This function (due to the regex above) has allowed _WRONG_ // **Note for Chart API v3**: This function (due to the regex above) has allowed _WRONG_
// Go templates to be defined inside charts across the years. The generated text from Go // Go templates to be defined inside charts across the years. The generated text from Go
@ -53,15 +54,15 @@ func SplitManifests(bigFile string) map[string]string {
tpl := "manifest-%d" tpl := "manifest-%d"
res := map[string]string{} res := map[string]string{}
// Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly.
bigFileTmp := strings.TrimSpace(bigFile) bigFileTmp := strings.TrimLeftFunc(bigFile, unicode.IsSpace)
docs := sep.Split(bigFileTmp, -1) docs := sep.Split(bigFileTmp, -1)
var count int var count int
for _, d := range docs { for _, d := range docs {
if d == "" { if strings.TrimSpace(d) == "" {
continue continue
} }
d = strings.TrimSpace(d) d = strings.TrimLeftFunc(d, unicode.IsSpace)
res[fmt.Sprintf(tpl, count)] = d res[fmt.Sprintf(tpl, count)] = d
count = count + 1 count = count + 1
} }

@ -21,7 +21,15 @@ import (
"testing" "testing"
) )
const mockManifestFile = ` func TestSplitManifests(t *testing.T) {
tests := []struct {
name string
input string
expected map[string]string
}{
{
name: "single doc with leading separator and whitespace",
input: `
--- ---
apiVersion: v1 apiVersion: v1
@ -35,9 +43,9 @@ spec:
- name: nemo-test - name: nemo-test
image: fake-image image: fake-image
cmd: fake-command cmd: fake-command
` `,
expected: map[string]string{
const expectedManifest = `apiVersion: v1 "manifest-0": `apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
name: finding-nemo, name: finding-nemo,
@ -47,15 +55,463 @@ spec:
containers: containers:
- name: nemo-test - name: nemo-test
image: fake-image image: fake-image
cmd: fake-command` cmd: fake-command
`,
},
},
{
name: "empty input",
input: "",
expected: map[string]string{},
},
{
name: "whitespace only",
input: " \n\n \n",
expected: map[string]string{},
},
{
name: "whitespace-only doc after separator is skipped",
input: "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1\n---\n \n",
expected: map[string]string{
"manifest-0": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: cm1",
},
},
{
name: "single doc no separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
`,
},
},
{
name: "two docs with proper separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
},
},
func TestSplitManifest(t *testing.T) { // Block scalar chomping indicator tests using | (clip), |- (strip), and |+ (keep)
manifests := SplitManifests(mockManifestFile) // inputs with 0, 1, and 2 trailing newlines after the block content.
if len(manifests) != 1 { // Note: the emitter may normalize the output chomping indicator when the
t.Errorf("Expected 1 manifest, got %v", len(manifests)) // trailing newline count makes another indicator equivalent for the result.
// | (clip) input — clips trailing newlines to exactly one, though with
// 0 trailing newlines the emitted output may normalize to |-.
{
name: "block scalar clip (|) with 0 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
},
},
{
name: "block scalar clip (|) with 1 trailing newline",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
},
},
{
name: "block scalar clip (|) with 2 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
`,
},
},
// |- (strip)
{
name: "block scalar strip (|-) with 0 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello`,
},
},
{
name: "block scalar strip (|-) with 1 trailing newline",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
},
},
{
name: "block scalar strip (|-) with 2 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |-
hello
`,
},
},
// |+ (keep)
{
name: "block scalar keep (|+) with 0 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
},
},
{
name: "block scalar keep (|+) with 1 trailing newline",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
},
},
{
name: "block scalar keep (|+) with 2 trailing newlines",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
`,
},
},
// Multi-doc with block scalars: the regex consumes \s*\n before ---,
// so trailing newlines from non-last docs are stripped.
{
name: "multi-doc block scalar clip (|) before separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |
hello`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
},
},
{
name: "multi-doc block scalar keep (|+) with 2 trailing newlines before separator",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: test
data:
key: |+
hello`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: test2
`,
},
},
// **Note for Chart API v3**: The following tests exercise the lenient
// regex that splits `---apiVersion` back into separate documents.
// In Chart API v3, these inputs should return an _ERROR_ instead.
// See the comment on the SplitManifests function for more details.
{
name: "leading glued separator (---apiVersion)",
input: `
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
`,
},
},
{
name: "mid-content glued separator (---apiVersion)",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
`,
},
},
{
name: "multiple glued separators",
input: `
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2`,
"manifest-2": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
},
},
{
name: "mixed glued and proper separators",
input: `
apiVersion: v1
kind: ConfigMap
metadata:
name: cm1
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cm2
---apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
expected: map[string]string{
"manifest-0": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm1`,
"manifest-1": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm2`,
"manifest-2": `apiVersion: v1
kind: ConfigMap
metadata:
name: cm3
`,
},
},
} }
expected := map[string]string{"manifest-0": expectedManifest}
if !reflect.DeepEqual(manifests, expected) { for _, tt := range tests {
t.Errorf("Expected %v, got %v", expected, manifests) t.Run(tt.name, func(t *testing.T) {
result := SplitManifests(tt.input)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("SplitManifests() =\n%v\nwant:\n%v", result, tt.expected)
}
})
} }
} }

Loading…
Cancel
Save