diff --git a/pkg/action/lint.go b/pkg/action/lint.go index 7b3c00ad2..f08047983 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -32,6 +32,7 @@ import ( // It provides the implementation of 'helm lint'. type Lint struct { Strict bool + ReleaseName string Namespace string WithSubcharts bool Quiet bool @@ -59,7 +60,7 @@ 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.KubeVersion, l.SkipSchemaValidation) + linter, err := lintChart(path, vals, l.ReleaseName, l.Namespace, l.KubeVersion, l.SkipSchemaValidation) if err != nil { result.Errors = append(result.Errors, err) continue @@ -86,7 +87,7 @@ func HasWarningsOrErrors(result *LintResult) bool { return len(result.Errors) > 0 } -func lintChart(path string, vals map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion, skipSchemaValidation bool) (support.Linter, error) { +func lintChart(path string, vals map[string]interface{}, releaseName, namespace string, kubeVersion *chartutil.KubeVersion, skipSchemaValidation bool) (support.Linter, error) { var chartPath string linter := support.Linter{} @@ -125,10 +126,11 @@ func lintChart(path string, vals map[string]interface{}, namespace string, kubeV return linter, fmt.Errorf("unable to check Chart.yaml file in chart: %w", err) } - return lint.RunAll( + return lint.AllWithOptions( chartPath, vals, namespace, + lint.WithReleaseName(releaseName), lint.WithKubeVersion(kubeVersion), lint.WithSkipSchemaValidation(skipSchemaValidation), ), nil diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go index a01580b0a..d10d98a08 100644 --- a/pkg/action/lint_test.go +++ b/pkg/action/lint_test.go @@ -22,6 +22,7 @@ import ( var ( values = make(map[string]interface{}) + defaultName = "test-release" namespace = "testNamespace" chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1" chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2" @@ -83,7 +84,7 @@ func TestLintChart(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, nil, tt.skipSchemaValidation) + _, err := lintChart(tt.chartPath, map[string]interface{}{}, defaultName, namespace, nil, tt.skipSchemaValidation) switch { case err != nil && !tt.err: t.Errorf("%s", err) diff --git a/pkg/cmd/lint.go b/pkg/cmd/lint.go index 78083a7ea..2d6437cce 100644 --- a/pkg/cmd/lint.go +++ b/pkg/cmd/lint.go @@ -145,6 +145,7 @@ func newLintCmd(out io.Writer) *cobra.Command { } f := cmd.Flags() + f.StringVar(&client.ReleaseName, "release-name", "test-release", "release name") f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts") f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors") diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go index a61d5e43f..98213efd5 100644 --- a/pkg/lint/lint.go +++ b/pkg/lint/lint.go @@ -24,42 +24,35 @@ import ( "helm.sh/helm/v4/pkg/lint/support" ) -type linterOptions struct { - KubeVersion *chartutil.KubeVersion - SkipSchemaValidation bool -} - -type LinterOption func(lo *linterOptions) - -func WithKubeVersion(kubeVersion *chartutil.KubeVersion) LinterOption { - return func(lo *linterOptions) { - lo.KubeVersion = kubeVersion - } -} - -func WithSkipSchemaValidation(skipSchemaValidation bool) LinterOption { - return func(lo *linterOptions) { - lo.SkipSchemaValidation = skipSchemaValidation - } -} - -func RunAll(baseDir string, values map[string]interface{}, namespace string, options ...LinterOption) support.Linter { - +func AllWithOptions(baseDir string, values map[string]interface{}, namespace string, options ...LinterOption) support.Linter { + // Using abs path to get directory context chartDir, _ := filepath.Abs(baseDir) - lo := linterOptions{} + linter := support.Linter{ChartDir: chartDir} + for _, option := range options { - option(&lo) + option(&linter) } - result := support.Linter{ - ChartDir: chartDir, - } + rules.Chartfile(&linter) + rules.ValuesWithOverrides(&linter, values) + rules.TemplatesV2(&linter, values, namespace) + rules.Dependencies(&linter) - rules.Chartfile(&result) - rules.ValuesWithOverrides(&result, values) - rules.TemplatesWithSkipSchemaValidation(&result, values, namespace, lo.KubeVersion, lo.SkipSchemaValidation) - rules.Dependencies(&result) + return linter +} + +// All runs all the available linters on the given base directory. +// Deprecated, use AllWithOptions instead. +func All(basedir string, values map[string]interface{}, namespace string, _ bool) support.Linter { + return AllWithOptions(basedir, values, namespace) +} - return result +// AllWithKubeVersion runs all the available linters on the given base directory, allowing to specify the kubernetes version. +// Deprecated, use AllWithOptions instead. +func AllWithKubeVersion(basedir string, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) support.Linter { + return AllWithOptions(basedir, values, namespace, + WithKubeVersion(kubeVersion), + WithSkipSchemaValidation(false), + ) } diff --git a/pkg/lint/lint_options.go b/pkg/lint/lint_options.go new file mode 100644 index 000000000..d0a168f06 --- /dev/null +++ b/pkg/lint/lint_options.go @@ -0,0 +1,28 @@ +package lint + +import ( + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" + "helm.sh/helm/v4/pkg/lint/support" +) + +type LinterOption func(linter *support.Linter) + +// WithReleaseName specifies chart release name +func WithReleaseName(name string) LinterOption { + return func(linter *support.Linter) { + linter.ReleaseName = name + } +} + +// WithKubeVersion specifies kube version +func WithKubeVersion(version *chartutil.KubeVersion) LinterOption { + return func(linter *support.Linter) { + linter.KubeVersion = version + } +} + +func WithSkipSchemaValidation(enabled bool) LinterOption { + return func(linter *support.Linter) { + linter.SkipSchemaValidation = enabled + } +} diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go index 888d3dfe6..ffe0cbd57 100644 --- a/pkg/lint/lint_test.go +++ b/pkg/lint/lint_test.go @@ -27,6 +27,7 @@ import ( var values map[string]interface{} +const defaultName = "test-release" const namespace = "testNamespace" const badChartDir = "rules/testdata/badchartfile" @@ -38,7 +39,7 @@ const malformedTemplate = "rules/testdata/malformed-template" const invalidChartFileDir = "rules/testdata/invalidchartfile" func TestBadChart(t *testing.T) { - m := RunAll(badChartDir, values, namespace).Messages + m := AllWithOptions(badChartDir, values, namespace, WithReleaseName(defaultName)).Messages if len(m) != 8 { t.Errorf("Number of errors %v", len(m)) t.Errorf("All didn't fail with expected errors, got %#v", m) @@ -82,7 +83,7 @@ func TestBadChart(t *testing.T) { } func TestInvalidYaml(t *testing.T) { - m := RunAll(badYamlFileDir, values, namespace).Messages + m := AllWithOptions(badYamlFileDir, values, namespace, WithReleaseName(defaultName)).Messages if len(m) != 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } @@ -92,7 +93,7 @@ func TestInvalidYaml(t *testing.T) { } func TestInvalidChartYaml(t *testing.T) { - m := RunAll(invalidChartFileDir, values, namespace).Messages + m := AllWithOptions(invalidChartFileDir, values, namespace).Messages if len(m) != 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } @@ -102,7 +103,7 @@ func TestInvalidChartYaml(t *testing.T) { } func TestBadValues(t *testing.T) { - m := RunAll(badValuesFileDir, values, namespace).Messages + m := AllWithOptions(badValuesFileDir, values, namespace, WithReleaseName(defaultName)).Messages if len(m) < 1 { t.Fatalf("All didn't fail with expected errors, got %#v", m) } @@ -112,7 +113,7 @@ func TestBadValues(t *testing.T) { } func TestGoodChart(t *testing.T) { - m := RunAll(goodChartDir, values, namespace).Messages + m := AllWithOptions(goodChartDir, values, namespace, WithReleaseName(defaultName)).Messages if len(m) != 0 { t.Error("All returned linter messages when it shouldn't have") for i, msg := range m { @@ -136,7 +137,7 @@ func TestHelmCreateChart(t *testing.T) { // Note: we test with strict=true here, even though others have // strict = false. - m := RunAll(createdChart, values, namespace, WithSkipSchemaValidation(true)).Messages + m := AllWithOptions(createdChart, values, namespace, WithReleaseName(defaultName)).Messages if ll := len(m); ll != 1 { t.Errorf("All should have had exactly 1 error. Got %d", ll) for i, msg := range m { @@ -183,7 +184,7 @@ func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { }, } - linterRunDetails := RunAll(createdChart, updatedValues, namespace, WithSkipSchemaValidation(true)) + linterRunDetails := AllWithOptions(createdChart, updatedValues, namespace, WithSkipSchemaValidation(true)) for _, msg := range linterRunDetails.Messages { if strings.HasPrefix(msg.Error(), "[WARNING]") && strings.Contains(msg.Error(), "deprecated") { @@ -197,7 +198,7 @@ func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) { // lint ignores import-values // See https://github.com/helm/helm/issues/9658 func TestSubChartValuesChart(t *testing.T) { - m := RunAll(subChartValuesDir, values, namespace).Messages + m := AllWithOptions(subChartValuesDir, values, namespace, WithReleaseName(defaultName)).Messages if len(m) != 0 { t.Error("All returned linter messages when it shouldn't have") for i, msg := range m { @@ -213,7 +214,7 @@ func TestMalformedTemplate(t *testing.T) { ch := make(chan int, 1) var m []support.Message go func() { - m = RunAll(malformedTemplate, values, namespace).Messages + m = AllWithOptions(malformedTemplate, values, namespace, WithReleaseName(defaultName)).Messages ch <- 1 }() select { diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index 463bd5341..1da0dcd0e 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -40,17 +40,20 @@ import ( ) // Templates lints the templates in the Linter. +// Deprecated, use TemplatesV2 instead. func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) { - TemplatesWithKubeVersion(linter, values, namespace, nil) + TemplatesV2(linter, values, namespace) } // TemplatesWithKubeVersion lints the templates in the Linter, allowing to specify the kubernetes version. +// Deprecated, use TemplatesV2 instead. func TemplatesWithKubeVersion(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion) { - TemplatesWithSkipSchemaValidation(linter, values, namespace, kubeVersion, false) + linter.KubeVersion = kubeVersion + TemplatesV2(linter, values, namespace) } -// TemplatesWithSkipSchemaValidation lints the templates in the Linter, allowing to specify the kubernetes version and if schema validation is enabled or not. -func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion, skipSchemaValidation bool) { +// TemplatesV2 lints the templates in the Linter. +func TemplatesV2(linter *support.Linter, values map[string]interface{}, namespace string) { fpath := "templates/" templatesPath := filepath.Join(linter.ChartDir, fpath) @@ -71,18 +74,18 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string } options := chartutil.ReleaseOptions{ - Name: "test-release", + Name: linter.ReleaseName, Namespace: namespace, } caps := chartutil.DefaultCapabilities.Copy() - if kubeVersion != nil { - caps.KubeVersion = *kubeVersion + if linter.KubeVersion != nil { + caps.KubeVersion = *linter.KubeVersion } // lint ignores import-values // See https://github.com/helm/helm/issues/9658 - if err := chartutil.ProcessDependencies(chart, values); err != nil { + if err = chartutil.ProcessDependencies(chart, values); err != nil { return } @@ -91,7 +94,7 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string return } - valuesToRender, err := chartutil.ToRenderValuesWithSchemaValidation(chart, cvals, options, caps, skipSchemaValidation) + valuesToRender, err := chartutil.ToRenderValuesWithSchemaValidation(chart, cvals, options, caps, linter.SkipSchemaValidation) if err != nil { linter.RunLinterRule(support.ErrorSev, fpath, err) return @@ -157,7 +160,7 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string // NOTE: set to warnings to allow users to support out-of-date kubernetes // Refs https://github.com/helm/helm/issues/8596 linter.RunLinterRule(support.WarningSev, fpath, validateMetadataName(yamlStruct)) - linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct, kubeVersion)) + linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct, linter.KubeVersion)) linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent)) linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent)) diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go index 787bd6e4b..d694f4d82 100644 --- a/pkg/lint/rules/template_test.go +++ b/pkg/lint/rules/template_test.go @@ -49,12 +49,12 @@ func TestValidateAllowedExtension(t *testing.T) { var values = map[string]interface{}{"nameOverride": "", "httpPort": 80} +const defaultName = "test-release" const namespace = "testNamespace" -const strict = false func TestTemplateParsing(t *testing.T) { - linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, values, namespace, strict) + linter := support.Linter{ChartDir: templateTestBasedir, ReleaseName: defaultName} + TemplatesV2(&linter, values, namespace) res := linter.Messages if len(res) != 1 { @@ -76,8 +76,8 @@ func TestTemplateIntegrationHappyPath(t *testing.T) { os.Rename(wrongTemplatePath, ignoredTemplatePath) defer os.Rename(ignoredTemplatePath, wrongTemplatePath) - linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, values, namespace, strict) + linter := support.Linter{ChartDir: templateTestBasedir, ReleaseName: defaultName} + TemplatesV2(&linter, values, namespace) res := linter.Messages if len(res) != 0 { @@ -86,8 +86,8 @@ func TestTemplateIntegrationHappyPath(t *testing.T) { } func TestMultiTemplateFail(t *testing.T) { - linter := support.Linter{ChartDir: "./testdata/multi-template-fail"} - Templates(&linter, values, namespace, strict) + linter := support.Linter{ChartDir: "./testdata/multi-template-fail", ReleaseName: defaultName} + TemplatesV2(&linter, values, namespace) res := linter.Messages if len(res) != 1 { @@ -206,8 +206,8 @@ func TestDeprecatedAPIFails(t *testing.T) { t.Fatal(err) } - linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} - Templates(&linter, values, namespace, strict) + linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name()), ReleaseName: defaultName} + TemplatesV2(&linter, values, namespace) if l := len(linter.Messages); l != 1 { for i, msg := range linter.Messages { t.Logf("Message %d: %s", i, msg) @@ -261,9 +261,10 @@ func TestStrictTemplateParsingMapError(t *testing.T) { t.Fatal(err) } linter := &support.Linter{ - ChartDir: filepath.Join(dir, ch.Metadata.Name), + ChartDir: filepath.Join(dir, ch.Metadata.Name), + ReleaseName: defaultName, } - Templates(linter, ch.Values, namespace, strict) + TemplatesV2(linter, ch.Values, namespace) if len(linter.Messages) != 0 { t.Errorf("expected zero messages, got %d", len(linter.Messages)) for i, msg := range linter.Messages { @@ -391,8 +392,8 @@ func TestEmptyWithCommentsManifests(t *testing.T) { t.Fatal(err) } - linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} - Templates(&linter, values, namespace, strict) + linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name()), ReleaseName: defaultName} + TemplatesV2(&linter, values, namespace) if l := len(linter.Messages); l > 0 { for i, msg := range linter.Messages { t.Logf("Message %d: %s", i, msg) diff --git a/pkg/lint/support/message.go b/pkg/lint/support/message.go index 5efbc7a61..0c3c08ea8 100644 --- a/pkg/lint/support/message.go +++ b/pkg/lint/support/message.go @@ -16,7 +16,11 @@ limitations under the License. package support -import "fmt" +import ( + "fmt" + + chartutil "helm.sh/helm/v4/pkg/chart/v2/util" +) // Severity indicates the severity of a Message. const ( @@ -37,8 +41,11 @@ var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"} type Linter struct { Messages []Message // The highest severity of all the failing lint rules - HighestSeverity int - ChartDir string + HighestSeverity int + ChartDir string + ReleaseName string + KubeVersion *chartutil.KubeVersion + SkipSchemaValidation bool } // Message describes an error encountered while linting.