diff --git a/pkg/lint/ignore/doc.go b/pkg/lint/ignore/doc.go new file mode 100644 index 00000000000..76606b9522f --- /dev/null +++ b/pkg/lint/ignore/doc.go @@ -0,0 +1,24 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +//Package ignore +/* +Package ignore contains tools for linting charts. + +Linting is the process of testing charts for errors or warnings regarding +formatting, compilation, or standards compliance. +*/ +package ignore // import "helm.sh/helm/v3/pkg/lint/ignore" diff --git a/pkg/lint/ignore/ignorer.go b/pkg/lint/ignore/ignorer.go new file mode 100644 index 00000000000..dcd5d752985 --- /dev/null +++ b/pkg/lint/ignore/ignorer.go @@ -0,0 +1,89 @@ +package ignore + +import ( + "helm.sh/helm/v3/pkg/lint/support" + "log/slog" + "path/filepath" + "strings" +) + +type Ignorer struct { + ChartPath string + Rules []Rule + logger *slog.Logger + RuleLoader *RuleLoader +} + +type PathlessRule struct { + RuleText string + MessageText string +} + +// Ignorer is used to create the ignorer object that contains the ignore rules +func NewActionIgnorer(chartPath string, lintIgnorePath string, debugLogFn func(string, ...interface{})) (*Ignorer, error) { + cmdIgnorer, err := NewRuleLoader(chartPath, lintIgnorePath, debugLogFn) + if err != nil { + return nil, err + } + + return &Ignorer{ChartPath: chartPath, RuleLoader: cmdIgnorer}, nil +} + +// FilterMessages Verify what messages can be kept in the output, using also the error as a verification (calling ShouldKeepError) +func (i *Ignorer) FilterMessages(messages []support.Message) []support.Message { + out := make([]support.Message, 0, len(messages)) + for _, msg := range messages { + if i.ShouldKeepError(msg.Err) { + out = append(out, msg) + } + } + return out +} + +// ShouldKeepError is used to verify if the error associated with the message need to be kept, or it can be ignored, called by FilterMessages and in the pkg/action/lint.go Run main function +func (i *Ignorer) ShouldKeepError(err error) bool { + errText := err.Error() + + // if any of our Matchers match the rule, we can discard it + for _, rule := range i.RuleLoader.Matchers { + match := rule.Match(errText) + if match != nil { + i.RuleLoader.Debug("lint ignore rule matched", match.LogAttrs()) + return false + } + } + + // if we can't find a reason to discard it, we keep it + return true +} + +type MatchesErrors interface { + Match(string) *RuleMatch +} + +type RuleMatch struct { + ErrText string + RuleText string +} + +func (rm RuleMatch) LogAttrs() slog.Attr { + return slog.Group("rule_match", slog.String("err_text", rm.ErrText), slog.String("rule_text", rm.RuleText)) +} + +// Match errors that have no file path in their body with ignorer rules. +// An examples of errors with no file path in their body is chart metadata errors `chart metadata is missing these dependencies` +func (pr PathlessRule) Match(errText string) *RuleMatch { + ignorableError := pr.MessageText + parts := strings.SplitN(ignorableError, ":", 2) + prefix := strings.TrimSpace(parts[0]) + + if match, _ := filepath.Match(ignorableError, errText); match { + return &RuleMatch{ErrText: errText, RuleText: pr.RuleText} + } + + if matched, _ := filepath.Match(prefix, errText); matched { + return &RuleMatch{ErrText: errText, RuleText: pr.RuleText} + } + + return nil +} diff --git a/pkg/lint/ignore/rule.go b/pkg/lint/ignore/rule.go new file mode 100644 index 00000000000..7e175d7baca --- /dev/null +++ b/pkg/lint/ignore/rule.go @@ -0,0 +1,64 @@ +package ignore + +import ( + "fmt" + "log/slog" + "path/filepath" + "strings" +) + +type Rule struct { + RuleText string + MessagePath string + MessageText string +} + +type LintedMessage struct { + ChartPath string + MessagePath string + MessageText string +} + +func NewRule(ruleText string) *Rule { + return &Rule{RuleText: ruleText} +} + +// ShouldKeepLintedMessage Function used to test the test data in rule_test.go and verify that the ignore capability work as needed +func (r Rule) ShouldKeepLintedMessage(msg LintedMessage) bool { + cmdIgnorer := RuleLoader{} + rdr := strings.NewReader(r.RuleText) + cmdIgnorer.LoadFromReader(rdr) + + actionIgnorer := Ignorer{RuleLoader: &cmdIgnorer} + return actionIgnorer.ShouldKeepError(fmt.Errorf(msg.MessageText)) +} + +// LogAttrs Used for troubleshooting and gathering data +func (r Rule) LogAttrs() slog.Attr { + return slog.Group("Rule", + slog.String("rule_text", r.RuleText), + slog.String("key", r.MessagePath), + slog.String("value", r.MessageText), + ) +} + +// Match errors that have a file path in their body with ignorer rules. +// Ignorer rules are built from the lint ignore file +func (r Rule) Match(errText string) *RuleMatch { + errorFullPath, err := extractFullPathFromError(errText) + if err != nil { + return nil + } + + ignorablePath := r.MessagePath + ignorableText := r.MessageText + cleanIgnorablePath := filepath.Clean(ignorablePath) + + if strings.Contains(errorFullPath, cleanIgnorablePath) { + if strings.Contains(errText, ignorableText) { + return &RuleMatch{ErrText: errText, RuleText: r.RuleText} + } + } + + return nil +} diff --git a/pkg/lint/ignore/rule_loader.go b/pkg/lint/ignore/rule_loader.go new file mode 100644 index 00000000000..36f17ee2923 --- /dev/null +++ b/pkg/lint/ignore/rule_loader.go @@ -0,0 +1,146 @@ +package ignore + +import ( + "bufio" + "fmt" + "io" + "log" + "log/slog" + "os" + "path/filepath" + "strings" +) + +// RuleLoader provides a means of suppressing unwanted helm lint errors and messages +// by comparing them to an ignore list provided in a plaintext helm lint ignore file. +type RuleLoader struct { + Matchers []MatchesErrors + debugFnOverride func(string, ...interface{}) +} + +func (i *RuleLoader) LogAttrs() slog.Attr { + return slog.Group("RuleLoader", + slog.String("Matchers", fmt.Sprintf("%v", i.Matchers)), + ) +} + +// DefaultIgnoreFileName is the name of the lint ignore file +// an RuleLoader will seek out at load/parse time. +const DefaultIgnoreFileName = ".helmlintignore" + +const NoMessageText = "" + +// NewRuleLoader builds an RuleLoader object that enables helm to discard specific lint result Messages +// and Errors should they match the ignore rules in the specified .helmlintignore file. +func NewRuleLoader(chartPath, ignoreFilePath string, debugLogFn func(string, ...interface{})) (*RuleLoader, error) { + out := &RuleLoader{ + debugFnOverride: debugLogFn, + } + + if ignoreFilePath == "" { + ignoreFilePath = filepath.Join(chartPath, DefaultIgnoreFileName) + out.Debug("\nNo HelmLintIgnore file specified, will try and use the following: %s\n", ignoreFilePath) + } + + // attempt to load ignore patterns from ignoreFilePath. + // if none are found, return an empty ignorer so the program can keep running. + out.Debug("\nUsing ignore file: %s\n", ignoreFilePath) + file, err := os.Open(ignoreFilePath) + if err != nil { + out.Debug("failed to open lint ignore file: %s", ignoreFilePath) + return out, nil + } + defer file.Close() + + out.LoadFromReader(file) + out.Debug("RuleLoader loaded.", out.LogAttrs()) + return out, nil +} + +// Debug provides an RuleLoader with a caller-overridable logging function +// intended to match the behavior of the top level debug() method from package main. +// +// When no i.debugFnOverride is present Debug will fall back to a naive +// implementation that assumes all debug output should be logged and not swallowed. +func (i *RuleLoader) Debug(format string, args ...interface{}) { + if i.debugFnOverride == nil { + i.debugFnOverride = func(format string, v ...interface{}) { + format = fmt.Sprintf("[debug] %s\n", format) + log.Output(2, fmt.Sprintf(format, v...)) + } + } + + i.debugFnOverride(format, args...) +} + +// TODO: figure out & fix or remove +func extractFullPathFromError(errText string) (string, error) { + delimiter := ":" + // splits into N parts delimited by colons + parts := strings.Split(errText, delimiter) + // if 3 or more parts, return the second part, after trimming its spaces + if len(parts) > 2 { + return strings.TrimSpace(parts[1]), nil + } + // if fewer than 3 parts, return empty string + return "", fmt.Errorf("fewer than three [%s]-delimited parts found, no path here: %s", delimiter, errText) +} + +func (i *RuleLoader) LoadFromReader(rdr io.Reader) { + const pathlessPatternPrefix = "error_lint_ignore=" + scanner := bufio.NewScanner(rdr) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + isPathlessPattern := strings.HasPrefix(line, pathlessPatternPrefix) + + if isPathlessPattern { + i.storePathlessPattern(line, pathlessPatternPrefix) + } else { + i.storePathfulPattern(line) + } + } +} + +func (i *RuleLoader) storePathlessPattern(line string, pathlessPatternPrefix string) { + // handle chart-level errors + // Drop 'error_lint_ignore=' prefix from rule before saving it + const numSplits = 2 + tokens := strings.SplitN(line[len(pathlessPatternPrefix):], pathlessPatternPrefix, numSplits) + if len(tokens) == numSplits { + // TODO: find an example for this one - not sure we still use it + messageText, _ := tokens[0], tokens[1] + i.Matchers = append(i.Matchers, PathlessRule{RuleText: line, MessageText: messageText}) + } else { + messageText := tokens[0] + i.Matchers = append(i.Matchers, PathlessRule{RuleText: line, MessageText: messageText}) + } +} + +func (i *RuleLoader) storePathfulPattern(line string) { + const separator = " " + const numSplits = 2 + + // handle chart yaml file errors in specific template files + parts := strings.SplitN(line, separator, numSplits) + if len(parts) == numSplits { + messagePath, messageText := parts[0], parts[1] + i.Matchers = append(i.Matchers, Rule{RuleText: line, MessagePath: messagePath, MessageText: messageText}) + } else { + messagePath := parts[0] + i.Matchers = append(i.Matchers, Rule{RuleText: line, MessagePath: messagePath, MessageText: NoMessageText}) + } +} + +func (i *RuleLoader) loadFromFilePath(filePath string) { + file, err := os.Open(filePath) + if err != nil { + i.Debug("failed to open lint ignore file: %s", filePath) + return + } + defer file.Close() + i.LoadFromReader(file) +} diff --git a/pkg/lint/ignore/rule_loader_test.go b/pkg/lint/ignore/rule_loader_test.go new file mode 100644 index 00000000000..9469d08f822 --- /dev/null +++ b/pkg/lint/ignore/rule_loader_test.go @@ -0,0 +1,30 @@ +package ignore + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "path/filepath" + "testing" +) + +func TestNewIgnorer(t *testing.T) { + chartPath := "../rules/testdata/withsubchartlintignore" + ignoreFilePath := filepath.Join(chartPath, ".helmlintignore") + ignorer, err := NewRuleLoader(chartPath, ignoreFilePath, func(format string, args ...interface{}) { + t.Logf(format, args...) + }) + assert.NoError(t, err) + assert.NotNil(t, ignorer, "RuleLoader should not be nil") +} + +func TestDebug(t *testing.T) { + var captured string + debugFn := func(format string, args ...interface{}) { + captured = fmt.Sprintf(format, args...) + } + ignorer := &RuleLoader{ + debugFnOverride: debugFn, + } + ignorer.Debug("test %s", "debug") + assert.Equal(t, "test debug", captured) +} diff --git a/pkg/lint/ignore/rule_test.go b/pkg/lint/ignore/rule_test.go new file mode 100644 index 00000000000..dc0ad8544f5 --- /dev/null +++ b/pkg/lint/ignore/rule_test.go @@ -0,0 +1,223 @@ +package ignore + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRule_ShouldKeepMessage(t *testing.T) { + type testCase struct { + Scenario string + RuleText string + Ignorables []LintedMessage + } + + testCases := []testCase{ + { + Scenario: "subchart template not defined", + RuleText: "gitlab/charts/webservice/templates/tests/tests.yaml <{{template \"fullname\" .}}>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/chart/charts/gitlab", + MessagePath: "templates/", + MessageText: "template: gitlab/charts/webservice/templates/tests/tests.yaml:5:20: executing \"gitlab/charts/webservice/templates/tests/tests.yaml\" at <{{template \"fullname\" .}}>: template \"fullname\" not defined", + }}, + }, { + Scenario: "subchart template include template not found", + RuleText: "gitaly/templates/statefulset.yml \n", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/chart/charts/gitlab/charts/gitaly", + MessagePath: "templates/", + MessageText: "template: gitaly/templates/statefulset.yml:1:11: executing \"gitaly/templates/statefulset.yml\" at : error calling include: template: no template \"gitlab.gitaly.includeInternalResources\" associated with template \"gotpl\"", + }}, + }, + { + Scenario: "subchart template evaluation has a nil pointer", + RuleText: "gitlab-exporter/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>\n", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/chart/charts/gitlab/charts/gitlab-exporter", + MessagePath: "templates/", + MessageText: "template: gitlab-exporter/templates/serviceaccount.yaml:1:57: executing \"gitlab-exporter/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, { + Scenario: "webservice path only", + RuleText: "webservice/templates/tests/tests.yaml", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/webservice/templates/tests/tests.yaml", + MessagePath: "templates/", + MessageText: "template: webservice/templates/tests/tests.yaml:8:8: executing \"webservice/templates/tests/tests.yaml\" at : error calling include: template: no template \"gitlab.standardLabels\" associated with template \"gotpl\"", + }}, + }, { + Scenario: "geo-logcursor path only", + RuleText: "geo-logcursor/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/geo-logcursor/templates/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: geo-logcursor/templates/serviceaccount.yaml:1:57: executing \"geo-logcursor/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, { + Scenario: "webservice path only", + RuleText: "webservice/templates/service.yaml ", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/webservice/templates/service.yaml", + MessagePath: "templates/", + MessageText: "template: gitlab/charts/gitlab/charts/webservice/templates/service.yaml:14:11: executing \"gitlab/charts/gitlab/charts/webservice/templates/service.yaml\" at : error calling include: template: gitlab/templates/_helpers.tpl:14:27: executing \"fullname\" at <.Chart.Name>: nil pointer evaluating interface {}.Name", + }}, + }, { + Scenario: "certmanager-issuer path only", + RuleText: "certmanager-issuer/templates/rbac-config.yaml <.Values.global.ingress>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/certmanager-issuer/templates/rbac-config.yaml", + MessagePath: "templates/", + MessageText: "template: certmanager-issuer/templates/rbac-config.yaml:1:67: executing \"certmanager-issuer/templates/rbac-config.yaml\" at <.Values.global.ingress>: nil pointer evaluating interface {}.ingress", + }}, + }, { + Scenario: "gitlab-pages path only", + RuleText: "gitlab-pages/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/gitlab-pages/templates/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: gitlab-pages/templates/serviceaccount.yaml:1:57: executing \"gitlab-pages/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, { + Scenario: "gitlab-shell path only", + RuleText: "gitlab-shell/templates/traefik-tcp-ingressroute.yaml <.Values.global.ingress.provider>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/gitlab-shell/templates/traefik-tcp-ingressroute.yaml", + MessagePath: "templates/", + MessageText: "template: gitlab-shell/templates/traefik-tcp-ingressroute.yaml:2:17: executing \"gitlab-shell/templates/traefik-tcp-ingressroute.yaml\" at <.Values.global.ingress.provider>: nil pointer evaluating interface {}.provider", + }}, + }, { + Scenario: "kas path only", + RuleText: "kas/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/kas/templates/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: kas/templates/serviceaccount.yaml:1:57: executing \"kas/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, { + Scenario: "kas path only", + RuleText: "kas/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/kas/templates/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: kas/templates/serviceaccount.yaml:1:57: executing \"kas/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, { + Scenario: "mailroom path only", + RuleText: "mailroom/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/mailroom/templates/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: mailroom/templates/serviceaccount.yaml:1:57: executing \"mailroom/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, { + Scenario: "migrations path only", + RuleText: "migrations/templates/job.yaml ", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/migrations/templates/job.yaml", + MessagePath: "templates/", + MessageText: "template: migrations/templates/job.yaml:2:3: executing \"migrations/templates/job.yaml\" at : error calling include: template: migrations/templates/_serviceaccountspec.yaml:1:57: executing \"migrations/templates/_serviceaccountspec.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, { + Scenario: "praefect path only", + RuleText: "praefect/templates/statefulset.yaml <.Values.global.image>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/praefect/templates/statefulset.yaml", + MessagePath: "templates/", + MessageText: "template: praefect/templates/statefulset.yaml:1:38: executing \"praefect/templates/statefulset.yaml\" at <.Values.global.image>: nil pointer evaluating interface {}.image", + }}, + }, { + Scenario: "sidekiq path only", + RuleText: "sidekiq/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/sidekiq/templates/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: sidekiq/templates/serviceaccount.yaml:1:57: executing \"sidekiq/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, { + Scenario: "spamcheck path only", + RuleText: "spamcheck/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/spamcheck/templates/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: spamcheck/templates/serviceaccount.yaml:1:57: executing \"spamcheck/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.serviceAccount", + }}, + }, { + Scenario: "toolbox path only", + RuleText: "toolbox/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/toolbox/templates/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: toolbox/templates/serviceaccount.yaml:1:57: executing \"toolbox/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, { + Scenario: "minio path only", + RuleText: "minio/templates/pdb.yaml <{{template \"gitlab.pdb.apiVersion\" $pdbCfg}}>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/minio/templates/pdb.yaml", + MessagePath: "templates/", + MessageText: "template: minio/templates/pdb.yaml:3:24: executing \"minio/templates/pdb.yaml\" at <{{template \"gitlab.pdb.apiVersion\" $pdbCfg}}>: template \"gitlab.pdb.apiVersion\" not defined", + }}, + }, { + Scenario: "nginx-ingress path only", + RuleText: "nginx-ingress/templates/admission-webhooks/job-patch/serviceaccount.yaml <.Values.admissionWebhooks.serviceAccount.automountServiceAccountToken>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/nginx-ingress/templates/admission-webhooks/job-patch/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: nginx-ingress/templates/admission-webhooks/job-patch/serviceaccount.yaml:13:40: executing \"nginx-ingress/templates/admission-webhooks/job-patch/serviceaccount.yaml\" at <.Values.admissionWebhooks.serviceAccount.automountServiceAccountToken>: nil pointer evaluating interface {}.serviceAccount", + }}, + }, { + Scenario: "registry path only", + RuleText: "registry/templates/serviceaccount.yaml <.Values.global.serviceAccount.enabled>", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/charts/registry/templates/serviceaccount.yaml", + MessagePath: "templates/", + MessageText: "template: registry/templates/serviceaccount.yaml:1:57: executing \"registry/templates/serviceaccount.yaml\" at <.Values.global.serviceAccount.enabled>: nil pointer evaluating interface {}.enabled", + }}, + }, + + { + Scenario: "subchart metadata missing dependencies", + RuleText: "error_lint_ignore=chart metadata is missing these dependencies**", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/chart/charts/gitlab", + MessagePath: "gitlab/chart/charts/gitlab", + MessageText: "chart metadata is missing these dependencies: sidekiq,spamcheck,gitaly,gitlab-shell,kas,mailroom,migrations,toolbox,geo-logcursor,gitlab-exporter,webservice", + }}, + }, + { + Scenario: "subchart icon is recommended", + RuleText: "error_lint_ignore=icon is recommended", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/chart/charts/gitlab-zoekt-1.4.0.tgz", + MessagePath: "Chart.yaml", + MessageText: "icon is recommended", + }}, + }, + { + Scenario: "subchart values file does not exist", + RuleText: "error_lint_ignore=file does not exist", + Ignorables: []LintedMessage{{ + ChartPath: "../gitlab/chart/charts/gluon-0.5.0.tgz", + MessagePath: "values.yaml", + MessageText: "file does not exist", + }}, + }, + } + for _, testCase := range testCases { + t.Run(testCase.Scenario, func(t *testing.T) { + rule := NewRule(testCase.RuleText) + + for _, ignorableMessage := range testCase.Ignorables { + assert.False(t, rule.ShouldKeepLintedMessage(ignorableMessage), testCase.Scenario) + } + + keepableMessage := LintedMessage{ + ChartPath: "a/memorable/path", + MessagePath: "wow/", + MessageText: "incredible: something just happened", + } + assert.True(t, rule.ShouldKeepLintedMessage(keepableMessage)) + }) + } +}