pull/12280/merge
Artem Bass 2 months ago committed by GitHub
commit b2d9265853
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -32,6 +32,7 @@ import (
// It provides the implementation of 'helm lint'. // It provides the implementation of 'helm lint'.
type Lint struct { type Lint struct {
Strict bool Strict bool
ReleaseName string
Namespace string Namespace string
WithSubcharts bool WithSubcharts bool
Quiet bool Quiet bool
@ -59,7 +60,7 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult {
} }
result := &LintResult{} result := &LintResult{}
for _, path := range paths { 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 { if err != nil {
result.Errors = append(result.Errors, err) result.Errors = append(result.Errors, err)
continue continue
@ -86,7 +87,7 @@ func HasWarningsOrErrors(result *LintResult) bool {
return len(result.Errors) > 0 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 var chartPath string
linter := support.Linter{} 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 linter, fmt.Errorf("unable to check Chart.yaml file in chart: %w", err)
} }
return lint.RunAll( return lint.AllWithOptions(
chartPath, chartPath,
vals, vals,
namespace, namespace,
lint.WithReleaseName(releaseName),
lint.WithKubeVersion(kubeVersion), lint.WithKubeVersion(kubeVersion),
lint.WithSkipSchemaValidation(skipSchemaValidation), lint.WithSkipSchemaValidation(skipSchemaValidation),
), nil ), nil

@ -22,6 +22,7 @@ import (
var ( var (
values = make(map[string]interface{}) values = make(map[string]interface{})
defaultName = "test-release"
namespace = "testNamespace" namespace = "testNamespace"
chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1" chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1"
chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2" chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2"
@ -83,7 +84,7 @@ func TestLintChart(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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 { switch {
case err != nil && !tt.err: case err != nil && !tt.err:
t.Errorf("%s", err) t.Errorf("%s", err)

@ -145,6 +145,7 @@ func newLintCmd(out io.Writer) *cobra.Command {
} }
f := cmd.Flags() 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.Strict, "strict", false, "fail on lint warnings")
f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts") f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts")
f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors") f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors")

@ -24,42 +24,35 @@ import (
"helm.sh/helm/v4/pkg/lint/support" "helm.sh/helm/v4/pkg/lint/support"
) )
type linterOptions struct { func AllWithOptions(baseDir string, values map[string]interface{}, namespace string, options ...LinterOption) support.Linter {
KubeVersion *chartutil.KubeVersion // Using abs path to get directory context
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 {
chartDir, _ := filepath.Abs(baseDir) chartDir, _ := filepath.Abs(baseDir)
lo := linterOptions{} linter := support.Linter{ChartDir: chartDir}
for _, option := range options { for _, option := range options {
option(&lo) option(&linter)
} }
result := support.Linter{ rules.Chartfile(&linter)
ChartDir: chartDir, rules.ValuesWithOverrides(&linter, values)
} rules.TemplatesV2(&linter, values, namespace)
rules.Dependencies(&linter)
rules.Chartfile(&result) return linter
rules.ValuesWithOverrides(&result, values) }
rules.TemplatesWithSkipSchemaValidation(&result, values, namespace, lo.KubeVersion, lo.SkipSchemaValidation)
rules.Dependencies(&result) // 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),
)
} }

@ -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
}
}

@ -27,6 +27,7 @@ import (
var values map[string]interface{} var values map[string]interface{}
const defaultName = "test-release"
const namespace = "testNamespace" const namespace = "testNamespace"
const badChartDir = "rules/testdata/badchartfile" const badChartDir = "rules/testdata/badchartfile"
@ -38,7 +39,7 @@ const malformedTemplate = "rules/testdata/malformed-template"
const invalidChartFileDir = "rules/testdata/invalidchartfile" const invalidChartFileDir = "rules/testdata/invalidchartfile"
func TestBadChart(t *testing.T) { func TestBadChart(t *testing.T) {
m := RunAll(badChartDir, values, namespace).Messages m := AllWithOptions(badChartDir, values, namespace, WithReleaseName(defaultName)).Messages
if len(m) != 8 { if len(m) != 8 {
t.Errorf("Number of errors %v", len(m)) t.Errorf("Number of errors %v", len(m))
t.Errorf("All didn't fail with expected errors, got %#v", 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) { func TestInvalidYaml(t *testing.T) {
m := RunAll(badYamlFileDir, values, namespace).Messages m := AllWithOptions(badYamlFileDir, values, namespace, WithReleaseName(defaultName)).Messages
if len(m) != 1 { if len(m) != 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m) 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) { func TestInvalidChartYaml(t *testing.T) {
m := RunAll(invalidChartFileDir, values, namespace).Messages m := AllWithOptions(invalidChartFileDir, values, namespace).Messages
if len(m) != 1 { if len(m) != 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m) 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) { func TestBadValues(t *testing.T) {
m := RunAll(badValuesFileDir, values, namespace).Messages m := AllWithOptions(badValuesFileDir, values, namespace, WithReleaseName(defaultName)).Messages
if len(m) < 1 { if len(m) < 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m) 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) { func TestGoodChart(t *testing.T) {
m := RunAll(goodChartDir, values, namespace).Messages m := AllWithOptions(goodChartDir, values, namespace, WithReleaseName(defaultName)).Messages
if len(m) != 0 { if len(m) != 0 {
t.Error("All returned linter messages when it shouldn't have") t.Error("All returned linter messages when it shouldn't have")
for i, msg := range m { 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 // Note: we test with strict=true here, even though others have
// strict = false. // strict = false.
m := RunAll(createdChart, values, namespace, WithSkipSchemaValidation(true)).Messages m := AllWithOptions(createdChart, values, namespace, WithReleaseName(defaultName)).Messages
if ll := len(m); ll != 1 { if ll := len(m); ll != 1 {
t.Errorf("All should have had exactly 1 error. Got %d", ll) t.Errorf("All should have had exactly 1 error. Got %d", ll)
for i, msg := range m { 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 { for _, msg := range linterRunDetails.Messages {
if strings.HasPrefix(msg.Error(), "[WARNING]") && if strings.HasPrefix(msg.Error(), "[WARNING]") &&
strings.Contains(msg.Error(), "deprecated") { strings.Contains(msg.Error(), "deprecated") {
@ -197,7 +198,7 @@ func TestHelmCreateChart_CheckDeprecatedWarnings(t *testing.T) {
// lint ignores import-values // lint ignores import-values
// See https://github.com/helm/helm/issues/9658 // See https://github.com/helm/helm/issues/9658
func TestSubChartValuesChart(t *testing.T) { func TestSubChartValuesChart(t *testing.T) {
m := RunAll(subChartValuesDir, values, namespace).Messages m := AllWithOptions(subChartValuesDir, values, namespace, WithReleaseName(defaultName)).Messages
if len(m) != 0 { if len(m) != 0 {
t.Error("All returned linter messages when it shouldn't have") t.Error("All returned linter messages when it shouldn't have")
for i, msg := range m { for i, msg := range m {
@ -213,7 +214,7 @@ func TestMalformedTemplate(t *testing.T) {
ch := make(chan int, 1) ch := make(chan int, 1)
var m []support.Message var m []support.Message
go func() { go func() {
m = RunAll(malformedTemplate, values, namespace).Messages m = AllWithOptions(malformedTemplate, values, namespace, WithReleaseName(defaultName)).Messages
ch <- 1 ch <- 1
}() }()
select { select {

@ -40,17 +40,20 @@ import (
) )
// Templates lints the templates in the Linter. // Templates lints the templates in the Linter.
// Deprecated, use TemplatesV2 instead.
func Templates(linter *support.Linter, values map[string]interface{}, namespace string, _ bool) { 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. // 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) { 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. // TemplatesV2 lints the templates in the Linter.
func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string]interface{}, namespace string, kubeVersion *chartutil.KubeVersion, skipSchemaValidation bool) { func TemplatesV2(linter *support.Linter, values map[string]interface{}, namespace string) {
fpath := "templates/" fpath := "templates/"
templatesPath := filepath.Join(linter.ChartDir, fpath) templatesPath := filepath.Join(linter.ChartDir, fpath)
@ -71,18 +74,18 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string
} }
options := chartutil.ReleaseOptions{ options := chartutil.ReleaseOptions{
Name: "test-release", Name: linter.ReleaseName,
Namespace: namespace, Namespace: namespace,
} }
caps := chartutil.DefaultCapabilities.Copy() caps := chartutil.DefaultCapabilities.Copy()
if kubeVersion != nil { if linter.KubeVersion != nil {
caps.KubeVersion = *kubeVersion caps.KubeVersion = *linter.KubeVersion
} }
// lint ignores import-values // lint ignores import-values
// See https://github.com/helm/helm/issues/9658 // 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 return
} }
@ -91,7 +94,7 @@ func TemplatesWithSkipSchemaValidation(linter *support.Linter, values map[string
return return
} }
valuesToRender, err := chartutil.ToRenderValuesWithSchemaValidation(chart, cvals, options, caps, skipSchemaValidation) valuesToRender, err := chartutil.ToRenderValuesWithSchemaValidation(chart, cvals, options, caps, linter.SkipSchemaValidation)
if err != nil { if err != nil {
linter.RunLinterRule(support.ErrorSev, fpath, err) linter.RunLinterRule(support.ErrorSev, fpath, err)
return 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 // NOTE: set to warnings to allow users to support out-of-date kubernetes
// Refs https://github.com/helm/helm/issues/8596 // Refs https://github.com/helm/helm/issues/8596
linter.RunLinterRule(support.WarningSev, fpath, validateMetadataName(yamlStruct)) 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, validateMatchSelector(yamlStruct, renderedContent))
linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent)) linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent))

@ -49,12 +49,12 @@ func TestValidateAllowedExtension(t *testing.T) {
var values = map[string]interface{}{"nameOverride": "", "httpPort": 80} var values = map[string]interface{}{"nameOverride": "", "httpPort": 80}
const defaultName = "test-release"
const namespace = "testNamespace" const namespace = "testNamespace"
const strict = false
func TestTemplateParsing(t *testing.T) { func TestTemplateParsing(t *testing.T) {
linter := support.Linter{ChartDir: templateTestBasedir} linter := support.Linter{ChartDir: templateTestBasedir, ReleaseName: defaultName}
Templates(&linter, values, namespace, strict) TemplatesV2(&linter, values, namespace)
res := linter.Messages res := linter.Messages
if len(res) != 1 { if len(res) != 1 {
@ -76,8 +76,8 @@ func TestTemplateIntegrationHappyPath(t *testing.T) {
os.Rename(wrongTemplatePath, ignoredTemplatePath) os.Rename(wrongTemplatePath, ignoredTemplatePath)
defer os.Rename(ignoredTemplatePath, wrongTemplatePath) defer os.Rename(ignoredTemplatePath, wrongTemplatePath)
linter := support.Linter{ChartDir: templateTestBasedir} linter := support.Linter{ChartDir: templateTestBasedir, ReleaseName: defaultName}
Templates(&linter, values, namespace, strict) TemplatesV2(&linter, values, namespace)
res := linter.Messages res := linter.Messages
if len(res) != 0 { if len(res) != 0 {
@ -86,8 +86,8 @@ func TestTemplateIntegrationHappyPath(t *testing.T) {
} }
func TestMultiTemplateFail(t *testing.T) { func TestMultiTemplateFail(t *testing.T) {
linter := support.Linter{ChartDir: "./testdata/multi-template-fail"} linter := support.Linter{ChartDir: "./testdata/multi-template-fail", ReleaseName: defaultName}
Templates(&linter, values, namespace, strict) TemplatesV2(&linter, values, namespace)
res := linter.Messages res := linter.Messages
if len(res) != 1 { if len(res) != 1 {
@ -206,8 +206,8 @@ func TestDeprecatedAPIFails(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name()), ReleaseName: defaultName}
Templates(&linter, values, namespace, strict) TemplatesV2(&linter, values, namespace)
if l := len(linter.Messages); l != 1 { if l := len(linter.Messages); l != 1 {
for i, msg := range linter.Messages { for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg) t.Logf("Message %d: %s", i, msg)
@ -261,9 +261,10 @@ func TestStrictTemplateParsingMapError(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
linter := &support.Linter{ 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 { if len(linter.Messages) != 0 {
t.Errorf("expected zero messages, got %d", len(linter.Messages)) t.Errorf("expected zero messages, got %d", len(linter.Messages))
for i, msg := range linter.Messages { for i, msg := range linter.Messages {
@ -391,8 +392,8 @@ func TestEmptyWithCommentsManifests(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name()), ReleaseName: defaultName}
Templates(&linter, values, namespace, strict) TemplatesV2(&linter, values, namespace)
if l := len(linter.Messages); l > 0 { if l := len(linter.Messages); l > 0 {
for i, msg := range linter.Messages { for i, msg := range linter.Messages {
t.Logf("Message %d: %s", i, msg) t.Logf("Message %d: %s", i, msg)

@ -16,7 +16,11 @@ limitations under the License.
package support package support
import "fmt" import (
"fmt"
chartutil "helm.sh/helm/v4/pkg/chart/v2/util"
)
// Severity indicates the severity of a Message. // Severity indicates the severity of a Message.
const ( const (
@ -37,8 +41,11 @@ var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"}
type Linter struct { type Linter struct {
Messages []Message Messages []Message
// The highest severity of all the failing lint rules // The highest severity of all the failing lint rules
HighestSeverity int HighestSeverity int
ChartDir string ChartDir string
ReleaseName string
KubeVersion *chartutil.KubeVersion
SkipSchemaValidation bool
} }
// Message describes an error encountered while linting. // Message describes an error encountered while linting.

Loading…
Cancel
Save