diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index fd3790563..be2d1d44e 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -46,7 +46,7 @@ func newLintCmd(out io.Writer) *cobra.Command { client := action.NewLint() valueOpts := &values.Options{} var kubeVersion string - var lintIgnoreFile string + var lintConfigFile string cmd := &cobra.Command{ Use: "lint PATH", @@ -80,7 +80,6 @@ func newLintCmd(out io.Writer) *cobra.Command { }) } } - client.Namespace = settings.Namespace() vals, err := valueOpts.MergeValues(getter.All(settings)) if err != nil { @@ -92,7 +91,7 @@ func newLintCmd(out io.Writer) *cobra.Command { errorsOrWarnings := 0 for _, path := range paths { - result := client.Run([]string{path}, vals, lintIgnoreFile, debug) + result := client.Run([]string{path}, vals, lintConfigFile, debug) // If there is no errors/warnings and quiet flag is set // go to the next chart @@ -151,7 +150,7 @@ func newLintCmd(out io.Writer) *cobra.Command { f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors") f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for capabilities and deprecation checks") - f.StringVar(&lintIgnoreFile, "lint-ignore-file", "", "path to .helmlintignore file to specify ignore patterns") + f.StringVar(&lintConfigFile, "lint-config-file", os.Getenv("HELM_LINT_CONFIG_FILE"), "path to .helmlintconfig.yaml file to specify ignore patterns") addValueOptionsFlags(f, valueOpts) return cmd diff --git a/cmd/helm/root.go b/cmd/helm/root.go index c5f8cc708..36272c460 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -71,6 +71,7 @@ Environment variables: | $HELM_KUBETLS_SERVER_NAME | set the server name used to validate the Kubernetes API server certificate | | $HELM_BURST_LIMIT | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable) | | $HELM_QPS | set the Queries Per Second in cases where a high number of calls exceed the option for higher burst values | +| $HELM_LINT_CONFIG_FILE | set the location for the config file for linting | Helm stores cache, configuration, and data based on the following configuration order: diff --git a/pkg/action/lint.go b/pkg/action/lint.go index 9d7129a8b..1fe18d87a 100644 --- a/pkg/action/lint.go +++ b/pkg/action/lint.go @@ -55,7 +55,7 @@ func NewLint() *Lint { } // Run executes 'helm Lint' against the given chart. -func (l *Lint) Run(paths []string, vals map[string]interface{}, lintIgnoreFilePath string, debugLogFn func(string, ...interface{})) *LintResult { +func (l *Lint) Run(paths []string, vals map[string]interface{}, lintConfigFilePath string, debugLogFn func(string, ...interface{})) *LintResult { lowestTolerance := support.ErrorSev if l.Strict { lowestTolerance = support.WarningSev @@ -63,7 +63,7 @@ func (l *Lint) Run(paths []string, vals map[string]interface{}, lintIgnoreFilePa result := &LintResult{} for chartIndex, path := range paths { // attempt to build an action-level lint result ignorer - ignorer, err := ignore.NewIgnorer(path, lintIgnoreFilePath, debugLogFn) + ignorer, err := ignore.NewIgnorer(path, lintConfigFilePath, debugLogFn) linter, err := lintChart(path, vals, l.Namespace, l.KubeVersion, l.SkipSchemaValidation) if err != nil { // ❗ Discard ignorable errors as early as possible diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go index 78649d35d..690fbc70e 100644 --- a/pkg/action/lint_test.go +++ b/pkg/action/lint_test.go @@ -30,10 +30,10 @@ var ( chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2" corruptedTgzChart = "testdata/charts/corrupted-compressed-chart.tgz" chartWithNoTemplatesDir = "testdata/charts/chart-with-no-templates-dir" - messyChartWithLintIgnore = "testdata/charts/messy-chart-with-lintignore" + messyChartWithLintIgnore = "testdata/charts/messy-chart-with-lintconfig" ) -const emptyLintIgnoreFilePath = "" +const emptyLintConfigFilePath = "" var settings = cli.New() @@ -115,7 +115,7 @@ func TestNonExistentChart(t *testing.T) { expectedError := "unable to open tarball: open non-existent-chart.tgz: no such file or directory" testLint := NewLint() - result := testLint.Run(testCharts, values, emptyLintIgnoreFilePath, debugLogFn) + result := testLint.Run(testCharts, values, emptyLintConfigFilePath, debugLogFn) if len(result.Errors) != 1 { t.Error("expected one error, but got", len(result.Errors)) } @@ -131,7 +131,7 @@ func TestNonExistentChart(t *testing.T) { expectedEOFError := "unable to extract tarball: EOF" testLint := NewLint() - result := testLint.Run(testCharts, values, emptyLintIgnoreFilePath, debugLogFn) + result := testLint.Run(testCharts, values, emptyLintConfigFilePath, debugLogFn) if len(result.Errors) != 1 { t.Error("expected one error, but got", len(result.Errors)) } @@ -146,7 +146,7 @@ func TestNonExistentChart(t *testing.T) { func TestLint_MultipleCharts(t *testing.T) { testCharts := []string{chart2MultipleChartLint, chart1MultipleChartLint} testLint := NewLint() - if result := testLint.Run(testCharts, values, emptyLintIgnoreFilePath, debugLogFn); len(result.Errors) > 0 { + if result := testLint.Run(testCharts, values, emptyLintConfigFilePath, debugLogFn); len(result.Errors) > 0 { t.Error(result.Errors) } } @@ -154,7 +154,7 @@ func TestLint_MultipleCharts(t *testing.T) { func TestLint_EmptyResultErrors(t *testing.T) { testCharts := []string{chart2MultipleChartLint} testLint := NewLint() - if result := testLint.Run(testCharts, values, emptyLintIgnoreFilePath, debugLogFn); len(result.Errors) > 0 { + if result := testLint.Run(testCharts, values, emptyLintConfigFilePath, debugLogFn); len(result.Errors) > 0 { t.Error("Expected no error, got more") } } @@ -164,7 +164,7 @@ func TestLint_ChartWithWarnings(t *testing.T) { testCharts := []string{chartWithNoTemplatesDir} testLint := NewLint() testLint.Strict = false - if result := testLint.Run(testCharts, values, emptyLintIgnoreFilePath, debugLogFn); len(result.Errors) > 0 { + if result := testLint.Run(testCharts, values, emptyLintConfigFilePath, debugLogFn); len(result.Errors) > 0 { t.Error("Expected no error, got more") } }) @@ -173,18 +173,18 @@ func TestLint_ChartWithWarnings(t *testing.T) { testCharts := []string{chartWithNoTemplatesDir} testLint := NewLint() testLint.Strict = true - if result := testLint.Run(testCharts, values, emptyLintIgnoreFilePath, debugLogFn); len(result.Errors) != 0 { + if result := testLint.Run(testCharts, values, emptyLintConfigFilePath, debugLogFn); len(result.Errors) != 0 { t.Error("expected no errors, but got", len(result.Errors)) } }) } -func TestLint_MessyChartWithLintIgnoreFile(t *testing.T) { - t.Run("should find no errors or messages using its own .helmlintignore file", func(t *testing.T) { +func TestLint_MessyChartWithLintConfigFile(t *testing.T) { + t.Run("should find no errors or messages using its own .helmlintconfig file", func(t *testing.T) { testCharts := []string{messyChartWithLintIgnore} testLint := NewLint() testLint.Strict = false - result := testLint.Run(testCharts, values, emptyLintIgnoreFilePath, debugLogFn) + result := testLint.Run(testCharts, values, emptyLintConfigFilePath, debugLogFn) if len(result.Errors) != 0 { t.Error("expected no errors, but got", len(result.Errors)) } @@ -194,11 +194,11 @@ func TestLint_MessyChartWithLintIgnoreFile(t *testing.T) { }) t.Run("should find all four errors when we feed it a fake lint ignore file path", func(t *testing.T) { - fakeLintIgnoreFilePath := "some/file/might/exist/here" + fakeLintConfigFilePath := "some/file/might/exist/here" testCharts := []string{messyChartWithLintIgnore} testLint := NewLint() testLint.Strict = true - result := testLint.Run(testCharts, values, fakeLintIgnoreFilePath, debugLogFn) + result := testLint.Run(testCharts, values, fakeLintConfigFilePath, debugLogFn) if len(result.Errors) != 2 { t.Error("expected two errors, but got", len(result.Errors)) } diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 0f28c61fd..16b0dd58d 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -47,6 +47,8 @@ const defaultBurstLimit = 100 // defaultQPS sets the default QPS value to 0 to use library defaults unless specified const defaultQPS = float32(0) +const defaultLintConfigFile = string(".helmlintconfig.yaml") + // EnvSettings describes all of the environment settings. type EnvSettings struct { namespace string @@ -88,6 +90,8 @@ type EnvSettings struct { BurstLimit int // QPS is queries per second which may be used to avoid throttling. QPS float32 + // Lint config file location + LintConfigFile string } func New() *EnvSettings { @@ -108,6 +112,7 @@ func New() *EnvSettings { RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), QPS: envFloat32Or("HELM_QPS", defaultQPS), + LintConfigFile: envOr("HELM_LINT_CONFIG_FILE", defaultLintConfigFile), } env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) @@ -159,6 +164,7 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the directory containing cached repository indexes") fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit") fs.Float32Var(&s.QPS, "qps", s.QPS, "queries per second used when communicating with the Kubernetes API, not including bursting") + fs.StringVar(&s.LintConfigFile, "lint-config-file", s.LintConfigFile, "path to .helmlintconfig.yaml file to specify ignore patterns") } func envOr(name, def string) string { @@ -227,6 +233,7 @@ func (s *EnvSettings) EnvVars() map[string]string { "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), "HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit), "HELM_QPS": strconv.FormatFloat(float64(s.QPS), 'f', 2, 32), + "HELM_LINT_CONFIG_FILE": s.LintConfigFile, // broken, these are populated from helm flags and not kubeconfig. "HELM_KUBECONTEXT": s.KubeContext, diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index f7709045c..affc012de 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -50,16 +50,17 @@ func TestEnvSettings(t *testing.T) { envvars map[string]string // expected values - ns, kcontext string - debug bool - maxhistory int - kubeAsUser string - kubeAsGroups []string - kubeCaFile string - kubeInsecure bool - kubeTLSServer string - burstLimit int - qps float32 + ns, kcontext string + debug bool + maxhistory int + kubeAsUser string + kubeAsGroups []string + kubeCaFile string + kubeInsecure bool + kubeTLSServer string + burstLimit int + qps float32 + lintConfigFile string }{ { name: "defaults", @@ -69,47 +70,50 @@ func TestEnvSettings(t *testing.T) { qps: defaultQPS, }, { - name: "with flags set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --qps 50.12 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", - ns: "myns", - debug: true, - maxhistory: defaultMaxHistory, - burstLimit: 100, - qps: 50.12, - kubeAsUser: "poro", - kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, - kubeCaFile: "/tmp/ca.crt", - kubeTLSServer: "example.org", - kubeInsecure: true, + name: "with flags set", + args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --qps 50.12 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org --lint-config-file /tmp/.helmlintconfig.yaml", + ns: "myns", + debug: true, + maxhistory: defaultMaxHistory, + burstLimit: 100, + qps: 50.12, + kubeAsUser: "poro", + kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, + kubeCaFile: "/tmp/ca.crt", + kubeTLSServer: "example.org", + kubeInsecure: true, + lintConfigFile: "/tmp/.helmlintconfig.yaml", }, { - name: "with envvars set", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "60.34"}, - ns: "yourns", - maxhistory: 5, - burstLimit: 150, - qps: 60.34, - debug: true, - kubeAsUser: "pikachu", - kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"}, - kubeCaFile: "/tmp/ca.crt", - kubeTLSServer: "example.org", - kubeInsecure: true, + name: "with envvars set", + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "60.34", "HELM_LINT_CONFIG_FILE": "/tmp/.helmlintconfig.yaml"}, + ns: "yourns", + maxhistory: 5, + burstLimit: 150, + qps: 60.34, + debug: true, + kubeAsUser: "pikachu", + kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"}, + kubeCaFile: "/tmp/ca.crt", + kubeTLSServer: "example.org", + kubeInsecure: true, + lintConfigFile: "/tmp/.helmlintconfig.yaml", }, { - name: "with flags and envvars set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --qps 70 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "40"}, - ns: "myns", - debug: true, - maxhistory: 5, - burstLimit: 175, - qps: 70, - kubeAsUser: "poro", - kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, - kubeCaFile: "/my/ca.crt", - kubeTLSServer: "example.org", - kubeInsecure: true, + name: "with flags and envvars set", + args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --qps 70 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org --lint-config-file /tmp/.helmlintconfig.yaml", + envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "40", "HELM_LINT_CONFIG_FILE": "/tmp/.helmlintconfig.yaml"}, + ns: "myns", + debug: true, + maxhistory: 5, + burstLimit: 175, + qps: 70, + kubeAsUser: "poro", + kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, + kubeCaFile: "/my/ca.crt", + kubeTLSServer: "example.org", + kubeInsecure: true, + lintConfigFile: "/tmp/.helmlintconfig.yaml", }, { name: "invalid kubeconfig", diff --git a/pkg/lint/ignore/ignorer.go b/pkg/lint/ignore/ignorer.go index 98baf25e9..d07b0eb9d 100644 --- a/pkg/lint/ignore/ignorer.go +++ b/pkg/lint/ignore/ignorer.go @@ -7,7 +7,7 @@ import ( ) // DefaultIgnoreFileName is the name of the lint ignore file -const DefaultIgnoreFileName = ".helmlintignore" +const DefaultIgnoreFileName = ".helmlintconfig.yaml" var debugFn func(format string, v ...interface{}) @@ -16,9 +16,9 @@ type Ignorer struct { Matchers []MatchesErrors } -func NewIgnorer(chartPath string, lintIgnorePath string, debugLogFn func(string, ...interface{})) (*Ignorer, error) { +func NewIgnorer(chartPath string, lintConfigPath string, debugLogFn func(string, ...interface{})) (*Ignorer, error) { debugFn = debugLogFn - matchers, err := LoadFromFilePath(chartPath, lintIgnorePath) + matchers, err := LoadFromFilePath(chartPath, lintConfigPath) if err != nil { return nil, err }