From ca2b4def34111362f671d87c581e49acb3b411fc Mon Sep 17 00:00:00 2001 From: Meyazhagan Date: Wed, 10 May 2023 17:17:11 +0530 Subject: [PATCH] feat: add flag `--exclude` paths regex (#932) * feat:exclude paths regex * change to 'excludeRegex' * add validation for exclude flag * add test case for FilterFiles --- cmd/kustomize/main_test.go | 2 +- cmd/test/main.go | 15 ++++++-- cmd/test/main_test.go | 2 +- pkg/fileReader/reader.go | 11 +++++- pkg/fileReader/reader_test.go | 69 +++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 7 deletions(-) diff --git a/cmd/kustomize/main_test.go b/cmd/kustomize/main_test.go index 3287df8364..d1c04fc465 100644 --- a/cmd/kustomize/main_test.go +++ b/cmd/kustomize/main_test.go @@ -179,7 +179,7 @@ type ReaderMock struct { mock.Mock } -func (rm *ReaderMock) FilterFiles(paths []string) ([]string, error) { +func (rm *ReaderMock) FilterFiles(paths []string, excludePattern string) ([]string, error) { args := rm.Called(paths) return args.Get(0).([]string), nil } diff --git a/cmd/test/main.go b/cmd/test/main.go index 4fb36d3582..269c9a1b10 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -52,6 +52,7 @@ type K8sValidator interface { type TestCommandFlags struct { Output string K8sVersion string + ExcludePattern string IgnoreMissingSchemas bool OnlyK8sFiles bool Verbose bool @@ -70,6 +71,7 @@ func NewTestCommandFlags() *TestCommandFlags { return &TestCommandFlags{ Output: "", K8sVersion: "", + ExcludePattern: "", IgnoreMissingSchemas: false, OnlyK8sFiles: false, Verbose: false, @@ -102,7 +104,6 @@ func (flags *TestCommandFlags) Validate() error { } err := validateK8sVersionFormatIfProvided(flags.K8sVersion) - if err != nil { return err } @@ -112,6 +113,11 @@ func (flags *TestCommandFlags) Validate() error { return err } + _, err = regexp.Compile(flags.ExcludePattern) + if err != nil { + return fmt.Errorf("invalid --exclude flag: " + err.Error()) + } + return nil } @@ -127,7 +133,7 @@ type EvaluationPrinter interface { } type Reader interface { - FilterFiles(paths []string) ([]string, error) + FilterFiles(paths []string, excludePattern string) ([]string, error) } type LocalConfig interface { @@ -144,6 +150,7 @@ type CliClient interface { type TestCommandData struct { Output string K8sVersion string + ExcludePattern string IgnoreMissingSchemas bool OnlyK8sFiles bool Verbose bool @@ -262,6 +269,7 @@ func (flags *TestCommandFlags) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&flags.K8sVersion, "schema-version", "s", "", "Set kubernetes version to validate against. Defaults to 1.24.0") cmd.Flags().StringVarP(&flags.PolicyName, "policy", "p", "", "Policy name to run against") + cmd.Flags().StringVarP(&flags.ExcludePattern, "exclude", "", "", "Exclude paths pattern (regex)") cmd.Flags().StringVar(&flags.PolicyConfig, "policy-config", "", "Path for local policies configuration file") cmd.Flags().BoolVar(&flags.OnlyK8sFiles, "only-k8s-files", false, "Evaluate only valid yaml files with the properties 'apiVersion' and 'kind'. Ignore everything else") @@ -342,6 +350,7 @@ func GenerateTestCommandData(testCommandFlags *TestCommandFlags, localConfigCont testCommandOptions := &TestCommandData{Output: testCommandFlags.Output, K8sVersion: k8sVersion, + ExcludePattern: testCommandFlags.ExcludePattern, IgnoreMissingSchemas: testCommandFlags.IgnoreMissingSchemas, OnlyK8sFiles: testCommandFlags.OnlyK8sFiles, Verbose: testCommandFlags.Verbose, @@ -414,7 +423,7 @@ func test(ctx *TestCommandContext, paths []string, testCommandData *TestCommandD paths = []string{tempFile.Name()} } - filesPaths, err := ctx.Reader.FilterFiles(paths) + filesPaths, err := ctx.Reader.FilterFiles(paths, testCommandData.ExcludePattern) if err != nil { return err } diff --git a/cmd/test/main_test.go b/cmd/test/main_test.go index 166139d94f..b32cfa3f4e 100644 --- a/cmd/test/main_test.go +++ b/cmd/test/main_test.go @@ -191,7 +191,7 @@ type ReaderMock struct { mock.Mock } -func (rm *ReaderMock) FilterFiles(paths []string) ([]string, error) { +func (rm *ReaderMock) FilterFiles(paths []string, excludePattern string) ([]string, error) { args := rm.Called(paths) return args.Get(0).([]string), nil } diff --git a/pkg/fileReader/reader.go b/pkg/fileReader/reader.go index 3683523e7e..8507dc3ec3 100644 --- a/pkg/fileReader/reader.go +++ b/pkg/fileReader/reader.go @@ -3,6 +3,7 @@ package fileReader import ( "os" "path/filepath" + "regexp" "github.com/bmatcuk/doublestar/v2" ) @@ -55,16 +56,22 @@ func CreateFileReader(opts *FileReaderOptions) *FileReader { return fileReader } -func (fr *FileReader) FilterFiles(paths []string) ([]string, error) { +func (fr *FileReader) FilterFiles(paths []string, excludePattern string) ([]string, error) { var filePaths []string + excludeRegex, err := regexp.Compile(excludePattern) + if err != nil { + return nil, err + } + for _, path := range paths { stat, err := fr.stat(path) if err != nil { return []string{}, err } - if !stat.IsDir() { + isMatched := excludePattern != "" && excludeRegex.MatchString(path) + if !stat.IsDir() && !isMatched { filePaths = append(filePaths, path) } } diff --git a/pkg/fileReader/reader_test.go b/pkg/fileReader/reader_test.go index 3a9cab2794..ea5dfdf0c3 100644 --- a/pkg/fileReader/reader_test.go +++ b/pkg/fileReader/reader_test.go @@ -1,6 +1,7 @@ package fileReader import ( + "io/fs" "os" "reflect" "testing" @@ -118,6 +119,74 @@ func (c *absMock) Abs(path string) (string, error) { return args.Get(0).(string), args.Error(1) } +type filterFilesTestCase struct { + name string + args struct { + paths []string + excludePattern string + } + mock struct { + stat struct { + response fs.FileInfo + err error + } + } + expected struct { + filePaths []string + } +} + +func TestFilterFiles(t *testing.T) { + stat := statMock{} + fileInfo := &MockFileInfo{} + + tests := []filterFilesTestCase{ + { + name: "success", + args: struct { + paths []string + excludePattern string + }{ + paths: []string{"file1.yaml", "file2.yaml", "file3-exclude.yaml", "file4.yaml", "file5-exclude.yaml"}, + excludePattern: "exclude.yaml", + }, + mock: struct { + stat struct { + response fs.FileInfo + err error + } + }{ + stat: struct { + response fs.FileInfo + err error + }{ + response: fileInfo, + err: nil, + }, + }, + expected: struct{ filePaths []string }{ + filePaths: []string{"file1.yaml", "file2.yaml", "file4.yaml"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stat.On("Stat", mock.Anything).Return(tt.mock.stat.response, tt.mock.stat.err) + fileInfo.On("IsDir", mock.Anything).Return(false) + fileReader := &FileReader{ + glob: nil, + stat: stat.Stat, + } + + filteredfiles, _ := fileReader.FilterFiles(tt.args.paths, tt.args.excludePattern) + stat.AssertCalled(t, "Stat", tt.expected.filePaths[0]) + fileInfo.AssertCalled(t, "IsDir") + assert.Equal(t, tt.expected.filePaths, filteredfiles) + }) + } +} + func TestCreateFileReader(t *testing.T) { glob := globMock{} io := ioMock{}