diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index a7aac172a..dfa1d2085 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -100,6 +100,12 @@ func newLintCmd(out io.Writer) *cobra.Command { failed++ } + if settings.Debug { + for _, contents := range result.RenderedContents { + fmt.Fprintf(&message, "---\n# Source: %s\n%s\n", path, contents) + } + } + // Adding extra new line here to break up the // results, stops this from being a big wall of // text and makes it easier to follow. diff --git a/cmd/helm/lint_test.go b/cmd/helm/lint_test.go index 3501ccf87..1afc02d46 100644 --- a/cmd/helm/lint_test.go +++ b/cmd/helm/lint_test.go @@ -33,6 +33,11 @@ func TestLintCmdWithSubchartsFlag(t *testing.T) { cmd: fmt.Sprintf("lint --with-subcharts %s", testChart), golden: "output/lint-chart-with-bad-subcharts-with-subcharts.txt", wantError: true, + }, { + name: "lint bad chart using --debug flag", + cmd: fmt.Sprintf("lint %s --debug", "testdata/testcharts/chart-with-template-with-invalid-yaml"), + golden: "output/lint-chart-with-template-with-invalid-yaml-with-debug.txt", + wantError: true, }} runTestCmd(t, tests) } diff --git a/cmd/helm/testdata/output/lint-chart-with-template-with-invalid-yaml-with-debug.txt b/cmd/helm/testdata/output/lint-chart-with-template-with-invalid-yaml-with-debug.txt new file mode 100644 index 000000000..d04c1efab --- /dev/null +++ b/cmd/helm/testdata/output/lint-chart-with-template-with-invalid-yaml-with-debug.txt @@ -0,0 +1,19 @@ +==> Linting testdata/testcharts/chart-with-template-with-invalid-yaml +[INFO] Chart.yaml: icon is recommended +[ERROR] Chart.yaml: chart type is not valid in apiVersion 'v1'. It is valid in apiVersion 'v2' +[ERROR] templates/alpine-pod.yaml: unable to parse YAML: error converting YAML to JSON: yaml: line 11: could not find expected ':' + +--- +# Source: testdata/testcharts/chart-with-template-with-invalid-yaml +apiVersion: v1 +kind: Pod +metadata: + name: "test-release-my-alpine" +spec: + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] +invalid + +Error: 1 chart(s) linted, 1 chart(s) failed diff --git a/pkg/action/lint.go b/pkg/action/lint.go index 2292c14bf..12b53bbdd 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -41,6 +41,7 @@ type Lint struct { // LintResult is the result of Lint type LintResult struct { TotalChartsLinted int + RenderedContents []string Messages []support.Message Errors []error } @@ -59,6 +60,9 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult { result := &LintResult{} for _, path := range paths { linter, err := lintChart(path, vals, l.Namespace, l.Strict) + + result.RenderedContents = append(result.RenderedContents, linter.RenderedContent...) + if err != nil { result.Errors = append(result.Errors, err) continue diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index 29ed67026..bf5b04d56 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -35,6 +35,7 @@ const badChartDir = "rules/testdata/badchartfile" const badValuesFileDir = "rules/testdata/badvaluesfile" const badYamlFileDir = "rules/testdata/albatross" const goodChartDir = "rules/testdata/goodone" +const multiTemplateFail = "rules/testdata/multi-template-fail" func TestBadChart(t *testing.T) { m := All(badChartDir, values, namespace, strict).Messages @@ -105,6 +106,16 @@ func TestBadValues(t *testing.T) { } } +func TestRenderedContent(t *testing.T) { + m := All(multiTemplateFail, values, namespace, strict).RenderedContent + if len(m) < 1 { + t.Fatalf("All didn't return any content, got %#v", m) + } + if !strings.Contains(m[0], "apiVersion: v1") { + t.Errorf("All didn't have expected content") + } +} + func TestGoodChart(t *testing.T) { m := All(goodChartDir, values, namespace, strict).Messages if len(m) != 0 { diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 0bb9f8671..5cebc446c 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -82,10 +82,6 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace renderOk := linter.RunLinterRule(support.ErrorSev, fpath, err) - if !renderOk { - return - } - /* Iterate over all the templates to check: - It is a .yaml file - All the values in the template file is defined @@ -117,6 +113,14 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)] if strings.TrimSpace(renderedContent) != "" { + + linter.NewRenderedContent(renderedContent) + + // We check if the render was successful here to allow any invalid templates to be returned in debug + if !renderOk { + break + } + linter.RunLinterRule(support.WarningSev, fpath, validateTopIndentLevel(renderedContent)) decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(renderedContent), 4096) @@ -132,7 +136,7 @@ func Templates(linter *support.Linter, values map[string]interface{}, namespace break } - // If YAML linting fails, we sill progress. So we don't capture the returned state + // If YAML linting fails, we still progress. So we don't capture the returned state // on this linter run. linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err)) diff --git a/pkg/lint/support/message.go b/pkg/lint/support/message.go index 5efbc7a61..a11210395 100644 --- a/pkg/lint/support/message.go +++ b/pkg/lint/support/message.go @@ -36,6 +36,10 @@ var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"} // Linter encapsulates a linting run of a particular chart. type Linter struct { Messages []Message + + // Contains the rendered templates + RenderedContent []string + // The highest severity of all the failing lint rules HighestSeverity int ChartDir string @@ -49,6 +53,11 @@ type Message struct { Err error } +// NewRenderedContent appends content +func (l *Linter) NewRenderedContent(content string) { + l.RenderedContent = append(l.RenderedContent, content) +} + func (m Message) Error() string { return fmt.Sprintf("[%s] %s: %s", sev[m.Severity], m.Path, m.Err.Error()) }