Skip to content

Commit 6f21e97

Browse files
graza-ioclaude
andauthored
Fix YAML document splitting to only split on unindented --- separators (#341) (#349)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent aad00e1 commit 6f21e97

3 files changed

Lines changed: 131 additions & 4 deletions

File tree

kubernetes/helm_template_render.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func getHelmRenderedTemplatesUncached(ctx context.Context, d *plugin.QueryData,
111111
continue
112112
}
113113

114-
splitManifest := strings.Split(manifest.Manifest, "---")
114+
splitManifest := yamlDocSeparator.Split(manifest.Manifest, -1)
115115
for _, content := range splitManifest {
116116
if len(content) == 0 {
117117
continue
@@ -185,7 +185,7 @@ func renderedHelmTemplateContentUncached(ctx context.Context, d *plugin.QueryDat
185185
}
186186
lineInfo := temp[t.Path]
187187

188-
for _, resource := range strings.Split(t.Data, "---") {
188+
for _, resource := range yamlDocSeparator.Split(t.Data, -1) {
189189
// Skip empty documents, `Decode` will fail on them
190190
// Also, increment the pos to include the separator position (e.g. ---)
191191
if len(resource) == 0 {
@@ -347,7 +347,7 @@ func getRawTemplateLineInfo(ctx context.Context, d *plugin.QueryData) (map[strin
347347
startLine := 0
348348
count := 0
349349

350-
for _, content := range strings.Split(templateContent, "---") {
350+
for _, content := range yamlDocSeparator.Split(templateContent, -1) {
351351
// Skip empty documents, `Decode` will fail on them
352352
// Also, increment the pos to include the separator position (e.g. ---)
353353
if len(content) == 0 {

kubernetes/utils.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ import (
3636
"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"
3737
)
3838

39+
// yamlDocSeparator matches YAML document separator lines: "---" at the start
40+
// of a line (column 0), optionally followed by trailing whitespace. Per the
41+
// YAML spec, an indented "---" inside a multi-line string value is NOT a
42+
// document separator, so we must anchor to the beginning of the line.
43+
var yamlDocSeparator = regexp.MustCompile(`(?m)^---\s*$`)
44+
3945
type SourceType string
4046

4147
const (
@@ -727,7 +733,7 @@ func parsedManifestFileContentUncached(ctx context.Context, d *plugin.QueryData,
727733

728734
// Check for the start of the document
729735
pos := 0
730-
for _, resource := range strings.Split(string(content), "---") {
736+
for _, resource := range yamlDocSeparator.Split(string(content), -1) {
731737
// Skip empty documents, `Decode` will fail on them
732738
// Also, increment the pos to include the separator position (e.g. ---)
733739
if len(resource) == 0 {

kubernetes/yaml_split_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package kubernetes
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestYamlDocSeparator(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
input string
12+
expected []string
13+
}{
14+
{
15+
name: "simple two documents",
16+
input: "apiVersion: v1\nkind: Pod\n---\napiVersion: v1\nkind: Service",
17+
expected: []string{"apiVersion: v1\nkind: Pod\n", "\napiVersion: v1\nkind: Service"},
18+
},
19+
{
20+
name: "separator at start of file",
21+
input: "---\napiVersion: v1\nkind: Pod",
22+
expected: []string{"", "\napiVersion: v1\nkind: Pod"},
23+
},
24+
{
25+
name: "separator with trailing whitespace",
26+
input: "apiVersion: v1\nkind: Pod\n--- \napiVersion: v1\nkind: Service",
27+
expected: []string{"apiVersion: v1\nkind: Pod\n", "\napiVersion: v1\nkind: Service"},
28+
},
29+
{
30+
name: "separator with trailing tab",
31+
input: "apiVersion: v1\nkind: Pod\n---\t\napiVersion: v1\nkind: Service",
32+
expected: []string{"apiVersion: v1\nkind: Pod\n", "\napiVersion: v1\nkind: Service"},
33+
},
34+
{
35+
name: "indented separator inside multiline string is NOT a split point",
36+
input: "apiVersion: v1\ndata:\n install_info: |\n ---\n install_method:\n tool: helm\nkind: ConfigMap",
37+
expected: []string{
38+
"apiVersion: v1\ndata:\n install_info: |\n ---\n install_method:\n tool: helm\nkind: ConfigMap",
39+
},
40+
},
41+
{
42+
name: "issue 341 exact scenario - ConfigMap with indented separator",
43+
input: `---
44+
apiVersion: v1
45+
data:
46+
install_info: |
47+
---
48+
install_method:
49+
tool: helm
50+
tool_version: Helm
51+
installer_version: datadog-3.135.4
52+
kind: ConfigMap
53+
metadata:
54+
annotations:
55+
checksum/install_info: 22bff6a15fb7a4521a3b6a06f55f1fe8ca1570dab4d8bca9f437f28e5301e89a
56+
labels:
57+
app.kubernetes.io/instance: datadog
58+
---
59+
apiVersion: v1
60+
kind: Service
61+
metadata:
62+
name: my-service`,
63+
expected: []string{
64+
"",
65+
"\napiVersion: v1\ndata:\n install_info: |\n ---\n install_method:\n tool: helm\n tool_version: Helm\n installer_version: datadog-3.135.4\nkind: ConfigMap\nmetadata:\n annotations:\n checksum/install_info: 22bff6a15fb7a4521a3b6a06f55f1fe8ca1570dab4d8bca9f437f28e5301e89a\n labels:\n app.kubernetes.io/instance: datadog\n",
66+
"\napiVersion: v1\nkind: Service\nmetadata:\n name: my-service",
67+
},
68+
},
69+
{
70+
name: "three documents",
71+
input: "---\napiVersion: v1\nkind: Pod\n---\napiVersion: v1\nkind: Service\n---\napiVersion: v1\nkind: Deployment",
72+
expected: []string{"", "\napiVersion: v1\nkind: Pod\n", "\napiVersion: v1\nkind: Service\n", "\napiVersion: v1\nkind: Deployment"},
73+
},
74+
{
75+
name: "no separator - single document",
76+
input: "apiVersion: v1\nkind: Pod\nmetadata:\n name: test",
77+
expected: []string{"apiVersion: v1\nkind: Pod\nmetadata:\n name: test"},
78+
},
79+
{
80+
name: "dashes inside a value are not separators",
81+
input: "apiVersion: v1\nkind: ConfigMap\ndata:\n key: ---some-value---",
82+
expected: []string{"apiVersion: v1\nkind: ConfigMap\ndata:\n key: ---some-value---"},
83+
},
84+
{
85+
name: "four dashes on own line are not a separator",
86+
input: "apiVersion: v1\nkind: Pod\n----\napiVersion: v1\nkind: Service",
87+
expected: []string{"apiVersion: v1\nkind: Pod\n----\napiVersion: v1\nkind: Service"},
88+
},
89+
{
90+
name: "multiple indented separators inside strings",
91+
input: "apiVersion: v1\nkind: ConfigMap\ndata:\n a: |\n ---\n foo: bar\n b: |\n ---\n baz: qux",
92+
expected: []string{
93+
"apiVersion: v1\nkind: ConfigMap\ndata:\n a: |\n ---\n foo: bar\n b: |\n ---\n baz: qux",
94+
},
95+
},
96+
{
97+
name: "empty input",
98+
input: "",
99+
expected: []string{""},
100+
},
101+
{
102+
name: "only separator",
103+
input: "---",
104+
expected: []string{"", ""},
105+
},
106+
{
107+
name: "consecutive separators",
108+
input: "---\n---\n---",
109+
expected: []string{"", "\n", "\n", ""},
110+
},
111+
}
112+
113+
for _, tc := range tests {
114+
t.Run(tc.name, func(t *testing.T) {
115+
result := yamlDocSeparator.Split(tc.input, -1)
116+
if !reflect.DeepEqual(result, tc.expected) {
117+
t.Errorf("yamlDocSeparator.Split() mismatch\n input: %q\n got: %q\n expected: %q", tc.input, result, tc.expected)
118+
}
119+
})
120+
}
121+
}

0 commit comments

Comments
 (0)