diff --git a/internal/release/v2/util/manifest.go b/internal/release/v2/util/manifest.go index 20d097d9b..5dbcdaea5 100644 --- a/internal/release/v2/util/manifest.go +++ b/internal/release/v2/util/manifest.go @@ -21,6 +21,7 @@ import ( "regexp" "strconv" "strings" + "unicode" ) // 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*") -// 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 { // 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 @@ -44,15 +54,15 @@ func SplitManifests(bigFile string) map[string]string { tpl := "manifest-%d" res := map[string]string{} // 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) var count int for _, d := range docs { - if d == "" { + if strings.TrimSpace(d) == "" { continue } - d = strings.TrimSpace(d) + d = strings.TrimLeftFunc(d, unicode.IsSpace) res[fmt.Sprintf(tpl, count)] = d count = count + 1 } diff --git a/internal/release/v2/util/manifest_test.go b/internal/release/v2/util/manifest_test.go index 7fd332fbc..72b095390 100644 --- a/internal/release/v2/util/manifest_test.go +++ b/internal/release/v2/util/manifest_test.go @@ -21,7 +21,15 @@ import ( "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 @@ -35,9 +43,9 @@ spec: - name: nemo-test image: fake-image cmd: fake-command -` - -const expectedManifest = `apiVersion: v1 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 kind: Pod metadata: name: finding-nemo, @@ -47,15 +55,463 @@ spec: containers: - name: nemo-test 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) { - manifests := SplitManifests(mockManifestFile) - if len(manifests) != 1 { - t.Errorf("Expected 1 manifest, got %v", len(manifests)) + // 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", + 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) { - t.Errorf("Expected %v, got %v", expected, manifests) + + for _, tt := range tests { + 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) + } + }) } } diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index a2b170206..d6575a791 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -418,7 +418,8 @@ func TestAnnotateAndMerge(t *testing.T) { { name: "single file with single manifest", files: map[string]string{ - "templates/configmap.yaml": `apiVersion: v1 + "templates/configmap.yaml": ` +apiVersion: v1 kind: ConfigMap metadata: name: test-cm @@ -438,13 +439,15 @@ data: { name: "multiple files with multiple manifests", files: map[string]string{ - "templates/configmap.yaml": `apiVersion: v1 + "templates/configmap.yaml": ` +apiVersion: v1 kind: ConfigMap metadata: name: test-cm data: key: value`, - "templates/secret.yaml": `apiVersion: v1 + "templates/secret.yaml": ` +apiVersion: v1 kind: Secret metadata: name: test-secret @@ -473,7 +476,8 @@ data: { name: "file with multiple manifests", files: map[string]string{ - "templates/multi.yaml": `apiVersion: v1 + "templates/multi.yaml": ` +apiVersion: v1 kind: ConfigMap metadata: name: test-cm1 @@ -509,7 +513,8 @@ data: { name: "partials and empty files are removed", files: map[string]string{ - "templates/cm.yaml": `apiVersion: v1 + "templates/cm.yaml": ` +apiVersion: v1 kind: ConfigMap metadata: name: test-cm1 @@ -531,14 +536,16 @@ metadata: { name: "empty file", files: map[string]string{ - "templates/empty.yaml": "", + "templates/empty.yaml": ` +`, }, expected: ``, }, { name: "invalid yaml", files: map[string]string{ - "templates/invalid.yaml": `invalid: yaml: content: + "templates/invalid.yaml": ` +invalid: yaml: content: - malformed`, }, expectedError: "parsing templates/invalid.yaml", @@ -546,7 +553,12 @@ metadata: { name: "leading doc separator glued to content by template whitespace trimming", 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 kind: Service @@ -559,7 +571,13 @@ metadata: { name: "leading doc separator on its own line", 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 kind: Service @@ -572,7 +590,14 @@ metadata: { name: "multiple leading doc separators", 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 kind: Service @@ -585,7 +610,16 @@ metadata: { name: "mid-content doc separator glued to content by template whitespace trimming", 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 kind: ConfigMap @@ -631,7 +665,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml' data: - ca.crt: |- + ca.crt: | ------BEGIN CERTIFICATE------ MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zAKBggqhkjOPQQDAzASMRAw DgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYwMDAw @@ -667,7 +701,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/configmap.yaml' data: - config: |- + config: | # --------------------------------------------------------------------------- [section] key = value @@ -694,7 +728,7 @@ metadata: annotations: postrenderer.helm.sh/postrender-filename: 'templates/dashboard.yaml' data: - dashboard.json: |- + dashboard.json: | {"options":{"---------":{"color":"#292929","text":"N/A"}}} `, }, @@ -933,6 +967,605 @@ metadata: name: cm-12 annotations: 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 := `--- # Source: yellow/templates/foodpie foodpie: world + --- # Source: yellow/templates/with-partials yellow: Earth + --- # Source: yellow/templates/yellow yellow: world + ` expectedHook := `kind: ConfigMap metadata: @@ -1214,7 +1850,8 @@ metadata: annotations: "helm.sh/hook": post-install,pre-delete,post-upgrade data: - name: value` + name: value +` assert.Equal(t, expectedBuf, buf.String()) assert.Len(t, hooks, 1) @@ -1319,14 +1956,17 @@ func TestRenderResources_PostRenderer_Integration(t *testing.T) { # Source: hello/templates/goodbye goodbye: world color: blue + --- # Source: hello/templates/hello hello: world color: blue + --- # Source: hello/templates/with-partials hello: Earth color: blue + ` assert.Contains(t, output, "color: blue") assert.Equal(t, 3, strings.Count(output, "color: blue")) diff --git a/pkg/action/testdata/rbac.txt b/pkg/action/testdata/rbac.txt index 0cb15b868..91938d5cc 100644 --- a/pkg/action/testdata/rbac.txt +++ b/pkg/action/testdata/rbac.txt @@ -23,3 +23,4 @@ subjects: - kind: ServiceAccount name: schedule-agents namespace: spaced + diff --git a/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt b/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt index eb770967f..c2219d8c4 100644 --- a/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt +++ b/pkg/cmd/testdata/output/install-dry-run-with-secret-hidden.txt @@ -19,3 +19,4 @@ metadata: data: foo: bar + diff --git a/pkg/cmd/testdata/output/install-dry-run-with-secret.txt b/pkg/cmd/testdata/output/install-dry-run-with-secret.txt index d22c1437f..62bd78018 100644 --- a/pkg/cmd/testdata/output/install-dry-run-with-secret.txt +++ b/pkg/cmd/testdata/output/install-dry-run-with-secret.txt @@ -15,6 +15,7 @@ metadata: name: test-secret stringData: foo: bar + --- # Source: chart-with-secret/templates/configmap.yaml apiVersion: v1 @@ -24,3 +25,4 @@ metadata: data: foo: bar + diff --git a/pkg/cmd/testdata/output/issue-9027.txt b/pkg/cmd/testdata/output/issue-9027.txt index eb19fc383..18fa5f06b 100644 --- a/pkg/cmd/testdata/output/issue-9027.txt +++ b/pkg/cmd/testdata/output/issue-9027.txt @@ -11,6 +11,7 @@ hash: key4: 4 key5: 5 key6: 6 + --- # Source: issue-9027/templates/values.yaml global: diff --git a/pkg/cmd/testdata/output/object-order.txt b/pkg/cmd/testdata/output/object-order.txt index 307f928f2..1ff39f33c 100644 --- a/pkg/cmd/testdata/output/object-order.txt +++ b/pkg/cmd/testdata/output/object-order.txt @@ -155,6 +155,7 @@ spec: policyTypes: - Egress - Ingress + --- # Source: object-order/templates/01-a.yml # 4 (Deployment should come after all NetworkPolicy manifests, since 'helm template' outputs in install order) diff --git a/pkg/cmd/testdata/output/template-name-template.txt b/pkg/cmd/testdata/output/template-name-template.txt index 9406048dd..b1077012e 100644 --- a/pkg/cmd/testdata/output/template-name-template.txt +++ b/pkg/cmd/testdata/output/template-name-template.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-set.txt b/pkg/cmd/testdata/output/template-set.txt index 4040991cf..1ecb8707b 100644 --- a/pkg/cmd/testdata/output/template-set.txt +++ b/pkg/cmd/testdata/output/template-set.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-skip-tests.txt b/pkg/cmd/testdata/output/template-skip-tests.txt index 5c907b563..4c5af8df3 100644 --- a/pkg/cmd/testdata/output/template-skip-tests.txt +++ b/pkg/cmd/testdata/output/template-skip-tests.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 diff --git a/pkg/cmd/testdata/output/template-subchart-cm-set-file.txt b/pkg/cmd/testdata/output/template-subchart-cm-set-file.txt index 56844e292..227d05903 100644 --- a/pkg/cmd/testdata/output/template-subchart-cm-set-file.txt +++ b/pkg/cmd/testdata/output/template-subchart-cm-set-file.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/configmap.yaml apiVersion: v1 @@ -22,6 +23,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -36,6 +38,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -53,6 +56,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -70,6 +74,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -101,6 +106,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -120,3 +126,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-subchart-cm-set.txt b/pkg/cmd/testdata/output/template-subchart-cm-set.txt index e52f7c234..dd8be4db9 100644 --- a/pkg/cmd/testdata/output/template-subchart-cm-set.txt +++ b/pkg/cmd/testdata/output/template-subchart-cm-set.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/configmap.yaml apiVersion: v1 @@ -22,6 +23,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -36,6 +38,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -53,6 +56,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -70,6 +74,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -101,6 +106,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -120,3 +126,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-subchart-cm.txt b/pkg/cmd/testdata/output/template-subchart-cm.txt index 9cc9e2296..c4600a798 100644 --- a/pkg/cmd/testdata/output/template-subchart-cm.txt +++ b/pkg/cmd/testdata/output/template-subchart-cm.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/configmap.yaml apiVersion: v1 @@ -22,6 +23,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -36,6 +38,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -53,6 +56,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -70,6 +74,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -101,6 +106,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -120,3 +126,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-values-files.txt b/pkg/cmd/testdata/output/template-values-files.txt index 4040991cf..1ecb8707b 100644 --- a/pkg/cmd/testdata/output/template-values-files.txt +++ b/pkg/cmd/testdata/output/template-values-files.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-with-api-version.txt b/pkg/cmd/testdata/output/template-with-api-version.txt index 8b6074cdb..ae726e624 100644 --- a/pkg/cmd/testdata/output/template-with-api-version.txt +++ b/pkg/cmd/testdata/output/template-with-api-version.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -95,6 +100,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -114,3 +120,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-with-crds.txt b/pkg/cmd/testdata/output/template-with-crds.txt index 256fc7c3b..1d63265ec 100644 --- a/pkg/cmd/testdata/output/template-with-crds.txt +++ b/pkg/cmd/testdata/output/template-with-crds.txt @@ -21,6 +21,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -31,6 +32,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -45,6 +47,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -62,6 +65,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -79,6 +83,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -110,6 +115,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -129,3 +135,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template-with-kube-version.txt b/pkg/cmd/testdata/output/template-with-kube-version.txt index 9d326f328..2c42e2e84 100644 --- a/pkg/cmd/testdata/output/template-with-kube-version.txt +++ b/pkg/cmd/testdata/output/template-with-kube-version.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/cmd/testdata/output/template.txt b/pkg/cmd/testdata/output/template.txt index 58c480b47..ddbfebe9d 100644 --- a/pkg/cmd/testdata/output/template.txt +++ b/pkg/cmd/testdata/output/template.txt @@ -4,6 +4,7 @@ apiVersion: v1 kind: ServiceAccount metadata: name: subchart-sa + --- # Source: subchart/templates/subdir/role.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -14,6 +15,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get","list","watch"] + --- # Source: subchart/templates/subdir/rolebinding.yaml apiVersion: rbac.authorization.k8s.io/v1 @@ -28,6 +30,7 @@ subjects: - kind: ServiceAccount name: subchart-sa namespace: default + --- # Source: subchart/charts/subcharta/templates/service.yaml apiVersion: v1 @@ -45,6 +48,7 @@ spec: name: apache selector: app.kubernetes.io/name: subcharta + --- # Source: subchart/charts/subchartb/templates/service.yaml apiVersion: v1 @@ -62,6 +66,7 @@ spec: name: nginx selector: app.kubernetes.io/name: subchartb + --- # Source: subchart/templates/service.yaml apiVersion: v1 @@ -93,6 +98,7 @@ metadata: "helm.sh/hook": test data: message: Hello World + --- # Source: subchart/templates/tests/test-nothing.yaml apiVersion: v1 @@ -112,3 +118,4 @@ spec: - echo - "$message" restartPolicy: Never + diff --git a/pkg/release/v1/util/manifest.go b/pkg/release/v1/util/manifest.go index 3160599bc..fa26f6256 100644 --- a/pkg/release/v1/util/manifest.go +++ b/pkg/release/v1/util/manifest.go @@ -21,6 +21,7 @@ import ( "regexp" "strconv" "strings" + "unicode" ) // 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*") -// 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 @@ -53,15 +54,15 @@ func SplitManifests(bigFile string) map[string]string { tpl := "manifest-%d" res := map[string]string{} // 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) var count int for _, d := range docs { - if d == "" { + if strings.TrimSpace(d) == "" { continue } - d = strings.TrimSpace(d) + d = strings.TrimLeftFunc(d, unicode.IsSpace) res[fmt.Sprintf(tpl, count)] = d count = count + 1 } diff --git a/pkg/release/v1/util/manifest_test.go b/pkg/release/v1/util/manifest_test.go index 754ac1367..516ac42d7 100644 --- a/pkg/release/v1/util/manifest_test.go +++ b/pkg/release/v1/util/manifest_test.go @@ -21,7 +21,15 @@ import ( "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 @@ -35,9 +43,9 @@ spec: - name: nemo-test image: fake-image cmd: fake-command -` - -const expectedManifest = `apiVersion: v1 +`, + expected: map[string]string{ + "manifest-0": `apiVersion: v1 kind: Pod metadata: name: finding-nemo, @@ -47,15 +55,463 @@ spec: containers: - name: nemo-test 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) { - manifests := SplitManifests(mockManifestFile) - if len(manifests) != 1 { - t.Errorf("Expected 1 manifest, got %v", len(manifests)) + // 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", + 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) { - t.Errorf("Expected %v, got %v", expected, manifests) + + for _, tt := range tests { + 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) + } + }) } }