From 2ee3dfda63d1d7b899b1a89fad2e79372ba2496e Mon Sep 17 00:00:00 2001 From: Vitor Rodrigo Vezani Date: Wed, 8 Jan 2025 14:13:11 -0300 Subject: [PATCH] INSIGHTS-588 - support for multiple test files (#230) * support for multiple test files * add tests and some fixes --------- Co-authored-by: jdesouza --- pkg/opavalidation/opavalidation.go | 5 +- .../multiple-validations/rego.1.failure.yaml | 22 +++++++++ .../multiple-validations/rego.1.success.yaml | 22 +++++++++ .../multiple-validations/rego.2.failure.yaml | 22 +++++++++ .../multiple-validations/rego.2.success.yaml | 22 +++++++++ .../multiple-validations/rego.failure.yaml | 22 +++++++++ .../testdata/multiple-validations/rego.rego | 14 ++++++ .../multiple-validations/rego.success.yaml | 22 +++++++++ pkg/opavalidation/types.go | 47 +++++++++++++------ pkg/opavalidation/types_test.go | 20 ++++++++ pkg/opavalidation/utils.go | 20 ++++++++ 11 files changed, 222 insertions(+), 16 deletions(-) create mode 100644 pkg/opavalidation/testdata/multiple-validations/rego.1.failure.yaml create mode 100644 pkg/opavalidation/testdata/multiple-validations/rego.1.success.yaml create mode 100644 pkg/opavalidation/testdata/multiple-validations/rego.2.failure.yaml create mode 100644 pkg/opavalidation/testdata/multiple-validations/rego.2.success.yaml create mode 100644 pkg/opavalidation/testdata/multiple-validations/rego.failure.yaml create mode 100644 pkg/opavalidation/testdata/multiple-validations/rego.rego create mode 100644 pkg/opavalidation/testdata/multiple-validations/rego.success.yaml create mode 100644 pkg/opavalidation/types_test.go diff --git a/pkg/opavalidation/opavalidation.go b/pkg/opavalidation/opavalidation.go index 5653694..355c383 100644 --- a/pkg/opavalidation/opavalidation.go +++ b/pkg/opavalidation/opavalidation.go @@ -92,7 +92,10 @@ func RunBatch(batchDir string, expectAIOptions ExpectActionItemOptions, insights return successfulPolicies, failedPolicies, fmt.Errorf("unable to list .rego files: %v", err) } for _, regoFileName := range regoFiles { - objectFileNames, ok := expectAIOptions.getObjectFileNamesForPolicy(regoFileName) + objectFileNames, ok, err := expectAIOptions.getObjectFileNamesForPolicy(regoFileName) + if err != nil { + return nil, nil, fmt.Errorf("error finding object files for policy %s: %w", regoFileName, err) + } if !ok { logrus.Errorf("No Kubernetes manifest files found to use as input for validation of OPA policy %s", regoFileName) failedPolicies = append(failedPolicies, regoFileName) diff --git a/pkg/opavalidation/testdata/multiple-validations/rego.1.failure.yaml b/pkg/opavalidation/testdata/multiple-validations/rego.1.failure.yaml new file mode 100644 index 0000000..baa92d3 --- /dev/null +++ b/pkg/opavalidation/testdata/multiple-validations/rego.1.failure.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myapp +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myapp:1.0 + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 diff --git a/pkg/opavalidation/testdata/multiple-validations/rego.1.success.yaml b/pkg/opavalidation/testdata/multiple-validations/rego.1.success.yaml new file mode 100644 index 0000000..baa92d3 --- /dev/null +++ b/pkg/opavalidation/testdata/multiple-validations/rego.1.success.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myapp +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myapp:1.0 + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 diff --git a/pkg/opavalidation/testdata/multiple-validations/rego.2.failure.yaml b/pkg/opavalidation/testdata/multiple-validations/rego.2.failure.yaml new file mode 100644 index 0000000..baa92d3 --- /dev/null +++ b/pkg/opavalidation/testdata/multiple-validations/rego.2.failure.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myapp +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myapp:1.0 + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 diff --git a/pkg/opavalidation/testdata/multiple-validations/rego.2.success.yaml b/pkg/opavalidation/testdata/multiple-validations/rego.2.success.yaml new file mode 100644 index 0000000..baa92d3 --- /dev/null +++ b/pkg/opavalidation/testdata/multiple-validations/rego.2.success.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myapp +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myapp:1.0 + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 diff --git a/pkg/opavalidation/testdata/multiple-validations/rego.failure.yaml b/pkg/opavalidation/testdata/multiple-validations/rego.failure.yaml new file mode 100644 index 0000000..baa92d3 --- /dev/null +++ b/pkg/opavalidation/testdata/multiple-validations/rego.failure.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myapp +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myapp:1.0 + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 diff --git a/pkg/opavalidation/testdata/multiple-validations/rego.rego b/pkg/opavalidation/testdata/multiple-validations/rego.rego new file mode 100644 index 0000000..2dfa3d0 --- /dev/null +++ b/pkg/opavalidation/testdata/multiple-validations/rego.rego @@ -0,0 +1,14 @@ +package fairwinds + +labelrequired[actionItem] { + requiredLabelValue := "development" + provided := [input.metadata.labels[_]] + description := sprintf("Label value %v is missing", [requiredLabelValue]) + actionItem := { + "title": "Label is missing", + "description": description, + "severity": .2, + "remediation": "Add the label", + "category": "Reliability" + } +} \ No newline at end of file diff --git a/pkg/opavalidation/testdata/multiple-validations/rego.success.yaml b/pkg/opavalidation/testdata/multiple-validations/rego.success.yaml new file mode 100644 index 0000000..baa92d3 --- /dev/null +++ b/pkg/opavalidation/testdata/multiple-validations/rego.success.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myapp +spec: + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myapp:1.0 + resources: + limits: + memory: "128Mi" + cpu: "500m" + ports: + - containerPort: 80 diff --git a/pkg/opavalidation/types.go b/pkg/opavalidation/types.go index 283308f..a0e6e40 100644 --- a/pkg/opavalidation/types.go +++ b/pkg/opavalidation/types.go @@ -3,8 +3,8 @@ package opavalidation import ( "errors" "fmt" - "os" "path/filepath" + "regexp" "strings" "github.com/fatih/color" @@ -174,7 +174,7 @@ type ExpectActionItemOptions struct { } // ForFileName returns true if the given Kubernetes manifest file name should -// expectan OPA policy to output an action item. +// expect an OPA policy to output an action item. func (o ExpectActionItemOptions) ForFileName(fileName string) bool { LCFileName := strings.ToLower(fileName) LCSuccessExtension := strings.ToLower(o.SuccessFileExtension) @@ -192,24 +192,41 @@ func (o ExpectActionItemOptions) ForFileName(fileName string) bool { } // getObjectFileNamesForPolicy returns a list of existing file names matching -// the pattern {base rego file name}.yaml|.success.yaml|.failure.yaml (the +// the pattern {base rego file name}.yaml|{base rego file name}..success.yaml|{base rego file name}..failure.yaml (the // latter two being configurable via the expectActionItemOptions struct). -func (o ExpectActionItemOptions) getObjectFileNamesForPolicy(regoFileName string) (objectFileNames []string, foundAny bool) { +func (o ExpectActionItemOptions) getObjectFileNamesForPolicy(regoFileName string) (objectFileNames []string, foundAny bool, err error) { baseFileName := strings.TrimSuffix(regoFileName, filepath.Ext(regoFileName)) - var lookForFileNames []string = []string{ - baseFileName + ".yaml", - baseFileName + o.SuccessFileExtension, - baseFileName + o.FailureFileExtension} - logrus.Debugf("Looking for these object files for policy %s: %v", regoFileName, lookForFileNames) - for _, potentialFileName := range lookForFileNames { - _, err := os.Stat(potentialFileName) - if err == nil { - objectFileNames = append(objectFileNames, potentialFileName) - foundAny = true + dir := filepath.Dir(regoFileName) + + filenameYaml := fmt.Sprintf(`^%s\.yaml$`, regexp.QuoteMeta(baseFileName)) + anythingFailureYaml := fmt.Sprintf(`^%s(\.[a-zA-Z0-9_-]+)?%s$`, regexp.QuoteMeta(baseFileName), regexp.QuoteMeta(o.FailureFileExtension)) + anythingSuccessYaml := fmt.Sprintf(`^%s(\.[a-zA-Z0-9_-]+)?%s$`, regexp.QuoteMeta(baseFileName), regexp.QuoteMeta(o.SuccessFileExtension)) + + filenameYamlRegex := regexp.MustCompile(filenameYaml) + anythingFailureYamlRegex := regexp.MustCompile(anythingFailureYaml) + anythingSuccessYamlRegex := regexp.MustCompile(anythingSuccessYaml) + + files, err := ListAllFilesInDir(dir, true) + if err != nil { + return nil, false, err + } + + for _, file := range files { + if filenameYamlRegex.MatchString(file) { + objectFileNames = append(objectFileNames, file) + } + if anythingFailureYamlRegex.MatchString(file) { + objectFileNames = append(objectFileNames, file) + } + if anythingSuccessYamlRegex.MatchString(file) { + objectFileNames = append(objectFileNames, file) } } - if foundAny { + + if len(objectFileNames) > 0 { logrus.Debugf("Matched these object files for policy %s: %v", regoFileName, objectFileNames) + foundAny = true } + return } diff --git a/pkg/opavalidation/types_test.go b/pkg/opavalidation/types_test.go new file mode 100644 index 0000000..d10ab7d --- /dev/null +++ b/pkg/opavalidation/types_test.go @@ -0,0 +1,20 @@ +package opavalidation + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetObjectFileNamesForPolicy(t *testing.T) { + opts := ExpectActionItemOptions{ + Default: true, + SuccessFileExtension: ".success.yaml", + FailureFileExtension: ".failure.yaml", + } + + f, ok, err := opts.getObjectFileNamesForPolicy("testdata/multiple-validations/rego.rego") + assert.NoError(t, err) + assert.True(t, ok) + assert.Len(t, f, 6) +} diff --git a/pkg/opavalidation/utils.go b/pkg/opavalidation/utils.go index 0265f44..486c2b0 100644 --- a/pkg/opavalidation/utils.go +++ b/pkg/opavalidation/utils.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "os" "path/filepath" "strings" @@ -230,3 +231,22 @@ func FindFilesWithExtension(dir, ext string) ([]string, error) { }) return files, err } + +func ListAllFilesInDir(dirPath string, includeDirPath bool) ([]string, error) { + files, err := os.ReadDir(dirPath) + if err != nil { + return nil, err + } + var fileNames []string + for _, file := range files { + if !file.IsDir() { + if includeDirPath { + fullPath := filepath.Join(dirPath, file.Name()) + fileNames = append(fileNames, fullPath) + } else { + fileNames = append(fileNames, file.Name()) + } + } + } + return fileNames, nil +}