forked from helm/helm
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
HIP-0019 adds .helmlintignore capability
See HIP-0019 proposal at helm/community: helm/community#353 Co-authored-by: Danilo Patrucco <[email protected]> Signed-off-by: Daniel J. Pritchett <[email protected]>
- Loading branch information
1 parent
c86e0d3
commit c64d08e
Showing
6 changed files
with
576 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
Oops, something went wrong.