From 70b45a8a90fa8977c29241c0a89c9b46af5d5c77 Mon Sep 17 00:00:00 2001 From: Artem Mikhalitsin Date: Sat, 29 Jan 2022 13:18:07 -0500 Subject: [PATCH] Add line numbers for debugging invalid yaml Signed-off-by: Artem Mikhalitsin --- .../template-with-invalid-yaml-debug.txt | 20 ++++----- pkg/action/action.go | 42 +++++++++++++++++++ pkg/action/action_test.go | 10 +++++ pkg/action/install_test.go | 28 +++++++++++++ 4 files changed, 90 insertions(+), 10 deletions(-) diff --git a/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt b/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt index 909c543d3..0ac96d298 100644 --- a/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt +++ b/cmd/helm/testdata/output/template-with-invalid-yaml-debug.txt @@ -1,13 +1,13 @@ --- # Source: chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml -apiVersion: v1 -kind: Pod -metadata: - name: "release-name-my-alpine" -spec: - containers: - - name: waiter - image: "alpine:3.9" - command: ["/bin/sleep","9000"] -invalid + 1 apiVersion: v1 + 2 kind: Pod + 3 metadata: + 4 name: "release-name-my-alpine" + 5 spec: + 6 containers: + 7 - name: waiter + 8 image: "alpine:3.9" + 9 command: ["/bin/sleep","9000"] +10 invalid Error: YAML parse error on chart-with-template-with-invalid-yaml/templates/alpine-pod.yaml: error converting YAML to JSON: yaml: line 11: could not find expected ':' diff --git a/pkg/action/action.go b/pkg/action/action.go index deb3f65df..2d7247dea 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -17,12 +17,14 @@ limitations under the License. package action import ( + "bufio" "bytes" "fmt" "os" "path" "path/filepath" "regexp" + "strconv" "strings" "github.com/pkg/errors" @@ -173,6 +175,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu if strings.TrimSpace(content) == "" { continue } + content = addLineNumbers(content) fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content) } return hs, b, "", err @@ -225,6 +228,45 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu return hs, b, notes, nil } +// getResourceLines separates a resource into individual lines +func getResourceLines(resource string) []string { + result := []string{} + in := strings.NewReader(resource) + scanner := bufio.NewScanner(in) + for scanner.Scan() { + result = append(result, scanner.Text()) + } + + return result +} + +// addLineNumbers numbers the lines of a resource +func addLineNumbers(resource string) string { + resourceLines := getResourceLines(resource) + lineFormat := getNumberedLineFormat(len(resourceLines)) + var result bytes.Buffer + + for index, line := range resourceLines { + lineNumber := index + 1 + lineWithNum := fmt.Sprintf(lineFormat, lineNumber, line) + fmt.Fprintln(&result, lineWithNum) + } + return result.String() +} + +// getNumberedLineFormat determines which format to use +// when printing lines of a resource based on the +// max possible number of digits +func getNumberedLineFormat(maxLineNumber int) string { + strBuilder := strings.Builder{} + numDigits := len(strconv.Itoa(maxLineNumber)) + // Format is like "%3d %s" + strBuilder.WriteString("%") + strBuilder.WriteString(strconv.Itoa(numDigits)) + strBuilder.WriteString("d %s") + return strBuilder.String() +} + // RESTClientGetter gets the rest client type RESTClientGetter interface { ToRESTConfig() (*rest.Config, error) diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index 190d00cae..061a416df 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -217,6 +217,16 @@ func withSampleIncludingIncorrectTemplates() chartOption { } } +func withBadYaml() chartOption { + return func(opts *chartOptions) { + badYamlTemplate := []*chart.File{ + {Name: "templates/goodyaml", Data: []byte("goodbye:world")}, + {Name: "templates/badyaml", Data: []byte("this:isn't:\nhow:you_write_yaml")}, + } + opts.Templates = append(opts.Templates, badYamlTemplate...) + } +} + func withMultipleManifestTemplate() chartOption { return func(opts *chartOptions) { sampleTemplates := []*chart.File{ diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 39520d620..474dbd807 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -291,6 +291,34 @@ func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { } } +func TestInstallRelease_LineNumbers(t *testing.T) { + is := assert.New(t) + t.Run("Should print line numbers on incorrect yaml when flag is set", func(t *testing.T) { + instAction := installAction(t) + instAction.DryRun = true + dummyVals := map[string]interface{}{} + badChart := buildChart(withBadYaml()) + res, _ := instAction.Run(badChart, dummyVals) + + // Should have line numbers for bad yaml + is.Contains(res.Manifest, "1 this:isn't:") + is.Contains(res.Manifest, "2 how:you_write_yaml") + // Should have line numbers for every other resource + is.Contains(res.Manifest, "1 kind: ConfigMap") + }) + + t.Run("Should not print line numbers on well-formatted yaml", func(t *testing.T) { + instAction := installAction(t) + instAction.DryRun = true + dummyVals := map[string]interface{}{} + goodChart := buildChart(withSampleTemplates()) + res, _ := instAction.Run(goodChart, dummyVals) + + is.Contains(res.Manifest, "hello: world") + is.NotContains(res.Manifest, "1 hello: Earth") + }) +} + func TestInstallRelease_NoHooks(t *testing.T) { is := assert.New(t) instAction := installAction(t)