From 688e2d3a3ff8b9acf2a70aaf178f79f00b7eb60c Mon Sep 17 00:00:00 2001 From: Gustav Westling Date: Wed, 8 May 2024 23:00:33 +0200 Subject: [PATCH] all: extendable externally usable API Refactor/rewrite the Go API (not previously considered to be stable) to be extendable with custom checks written in Go, and to make the API easier to consume. The API is not yet considered to be stable, but this is a good step in that direction. --- cmd/kube-score/main.go | 20 ++--- config/config.go | 7 +- domain/kube-score.go | 1 + examples/custom_check.go | 68 +++++++++++++++++ examples/custom_check_test.go | 64 ++++++++++++++++ parser/parse.go | 29 ++++--- parser/parse_test.go | 9 +-- renderer/sarif/sarif.go | 4 +- score/apps_test.go | 6 +- score/checks/checks.go | 12 ++- score/container/container.go | 5 +- score/deployment_test.go | 29 ++++--- score/filelocation_test.go | 6 +- score/optional_test.go | 26 +++---- score/score.go | 21 +++-- score/score_test.go | 139 ++++++++++++++++------------------ score/security_test.go | 43 ++++------- score/stable_version_test.go | 9 +-- scorecard/scorecard.go | 6 +- 19 files changed, 315 insertions(+), 189 deletions(-) create mode 100644 examples/custom_check.go create mode 100644 examples/custom_check_test.go diff --git a/cmd/kube-score/main.go b/cmd/kube-score/main.go index c7fc30ff..29765950 100644 --- a/cmd/kube-score/main.go +++ b/cmd/kube-score/main.go @@ -20,6 +20,7 @@ import ( "github.com/zegl/kube-score/renderer/json_v2" "github.com/zegl/kube-score/renderer/sarif" "github.com/zegl/kube-score/score" + "github.com/zegl/kube-score/score/checks" "github.com/zegl/kube-score/scorecard" "golang.org/x/term" ) @@ -174,7 +175,7 @@ Use "-" as filename to read from STDIN.`, execName(binName)) if *allDefaultOptional { var addOptionalChecks []string - for _, c := range score.RegisterAllChecks(parser.Empty(), config.Configuration{}).All() { + for _, c := range score.RegisterAllChecks(parser.Empty(), nil, nil).All() { if c.Optional { addOptionalChecks = append(addOptionalChecks, c.ID) } @@ -190,29 +191,30 @@ Use "-" as filename to read from STDIN.`, execName(binName)) return errors.New("Invalid --kubernetes-version. Use on format \"vN.NN\"") } - cnf := config.Configuration{ - AllFiles: allFilePointers, - VerboseOutput: *verboseOutput, + runConfig := &config.RunConfiguration{ IgnoreContainerCpuLimitRequirement: *ignoreContainerCpuLimit, IgnoreContainerMemoryLimitRequirement: *ignoreContainerMemoryLimit, - IgnoredTests: ignoredTests, EnabledOptionalTests: enabledOptionalTests, UseIgnoreChecksAnnotation: !*disableIgnoreChecksAnnotation, UseOptionalChecksAnnotation: !*disableOptionalChecksAnnotation, KubernetesVersion: kubeVer, } - p, err := parser.New() + p, err := parser.New(&parser.Config{ + VerboseOutput: *verboseOutput, + }) if err != nil { return fmt.Errorf("failed to initializer parser: %w", err) } - parsedFiles, err := p.ParseFiles(cnf) + parsedFiles, err := p.ParseFiles(allFilePointers) if err != nil { return fmt.Errorf("failed to parse files: %w", err) } - scoreCard, err := score.Score(parsedFiles, cnf) + checks := score.RegisterAllChecks(parsedFiles, &checks.Config{IgnoredTests: ignoredTests}, runConfig) + + scoreCard, err := score.Score(parsedFiles, checks, runConfig) if err != nil { return err } @@ -290,7 +292,7 @@ func listChecks(binName string, args []string) error { return nil } - allChecks := score.RegisterAllChecks(parser.Empty(), config.Configuration{}) + allChecks := score.RegisterAllChecks(parser.Empty(), nil, nil) output := csv.NewWriter(os.Stdout) for _, c := range allChecks.All() { diff --git a/config/config.go b/config/config.go index 6d0a4ee0..e4c89530 100644 --- a/config/config.go +++ b/config/config.go @@ -5,16 +5,11 @@ import ( "fmt" "strconv" "strings" - - ks "github.com/zegl/kube-score/domain" ) -type Configuration struct { - AllFiles []ks.NamedReader - VerboseOutput int +type RunConfiguration struct { IgnoreContainerCpuLimitRequirement bool IgnoreContainerMemoryLimitRequirement bool - IgnoredTests map[string]struct{} EnabledOptionalTests map[string]struct{} UseIgnoreChecksAnnotation bool UseOptionalChecksAnnotation bool diff --git a/domain/kube-score.go b/domain/kube-score.go index ffe6b0cf..a962a7f1 100644 --- a/domain/kube-score.go +++ b/domain/kube-score.go @@ -2,6 +2,7 @@ package domain import ( "io" + autoscalingv1 "k8s.io/api/autoscaling/v1" appsv1 "k8s.io/api/apps/v1" diff --git a/examples/custom_check.go b/examples/custom_check.go new file mode 100644 index 00000000..e7905baf --- /dev/null +++ b/examples/custom_check.go @@ -0,0 +1,68 @@ +package examples + +import ( + "bytes" + "io" + "strings" + + "github.com/zegl/kube-score/config" + "github.com/zegl/kube-score/domain" + "github.com/zegl/kube-score/parser" + "github.com/zegl/kube-score/score" + "github.com/zegl/kube-score/score/checks" + "github.com/zegl/kube-score/scorecard" + + v1 "k8s.io/api/apps/v1" +) + +type namedReader struct { + io.Reader + name string +} + +func (n namedReader) Name() string { + return n.name +} + +// ExampleCheckObject shows how kube-score can be extended with a custom check function +// +// In this example, raw is a YAML encoded Kubernetes object +func ExampleCheckObject(raw []byte) (*scorecard.Scorecard, error) { + parser, err := parser.New(nil) + if err != nil { + return nil, err + } + + reader := bytes.NewReader(raw) + + // Parse all objects to read + allObjects, err := parser.ParseFiles( + []domain.NamedReader{ + namedReader{ + Reader: reader, + name: "input", + }, + }, + ) + if err != nil { + return nil, err + } + + // Register check functions to run + checks := checks.New(nil) + checks.RegisterDeploymentCheck("custom-deployment-check", "A custom kube-score check function", customDeploymentCheck) + + return score.Score(allObjects, checks, &config.RunConfiguration{}) +} + +func customDeploymentCheck(d v1.Deployment) (scorecard.TestScore, error) { + if strings.Contains(d.Name, "foo") { + return scorecard.TestScore{ + Grade: scorecard.GradeCritical, + Comments: []scorecard.TestScoreComment{{ + Summary: "Deployments names can not contian 'foo'", + }}}, nil + } + + return scorecard.TestScore{Grade: scorecard.GradeAllOK}, nil +} diff --git a/examples/custom_check_test.go b/examples/custom_check_test.go new file mode 100644 index 00000000..7bfe2032 --- /dev/null +++ b/examples/custom_check_test.go @@ -0,0 +1,64 @@ +package examples + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/zegl/kube-score/scorecard" +) + +func TestExampleCheckObjectAllOK(t *testing.T) { + card, err := ExampleCheckObject([]byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: example +spec: + replicas: 10 + template: + metadata: + labels: + app: foo + spec: + containers: + - name: foobar + image: foo:bar`)) + + assert.NoError(t, err) + + assert.Len(t, *card, 1) + + for _, v := range *card { + assert.Len(t, v.Checks, 1) + assert.Equal(t, "custom-deployment-check", v.Checks[0].Check.ID) + assert.Equal(t, scorecard.GradeAllOK, v.Checks[0].Grade) + } +} + +func TestExampleCheckObjectErrorNameContainsFoo(t *testing.T) { + card, err := ExampleCheckObject([]byte(` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: example-foo +spec: + replicas: 10 + template: + metadata: + labels: + app: foo + spec: + containers: + - name: foobar + image: foo:bar`)) + + assert.NoError(t, err) + + assert.Len(t, *card, 1) + + for _, v := range *card { + assert.Len(t, v.Checks, 1) + assert.Equal(t, "custom-deployment-check", v.Checks[0].Check.ID) + assert.Equal(t, scorecard.GradeCritical, v.Checks[0].Grade) + } +} diff --git a/parser/parse.go b/parser/parse.go index 601ae82f..ffba172a 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -27,7 +27,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - "github.com/zegl/kube-score/config" ks "github.com/zegl/kube-score/domain" "github.com/zegl/kube-score/parser/internal" internalcronjob "github.com/zegl/kube-score/parser/internal/cronjob" @@ -40,15 +39,25 @@ import ( type Parser struct { scheme *runtime.Scheme codecs serializer.CodecFactory + config *Config +} + +type Config struct { + VerboseOutput int } type schemaAdderFunc func(scheme *runtime.Scheme) error -func New() (*Parser, error) { +func New(config *Config) (*Parser, error) { + if config == nil { + config = &Config{} + } + scheme := runtime.NewScheme() p := &Parser{ scheme: scheme, codecs: serializer.NewCodecFactory(scheme), + config: config, } if err := p.addToScheme(); err != nil { return nil, fmt.Errorf("failed to init: %w", err) @@ -146,10 +155,10 @@ func Empty() ks.AllTypes { return &parsedObjects{} } -func (p *Parser) ParseFiles(cnf config.Configuration) (ks.AllTypes, error) { +func (p *Parser) ParseFiles(files []ks.NamedReader) (ks.AllTypes, error) { s := &parsedObjects{} - for _, namedReader := range cnf.AllFiles { + for _, namedReader := range files { fullFile, err := io.ReadAll(namedReader) if err != nil { return nil, err @@ -169,7 +178,7 @@ func (p *Parser) ParseFiles(cnf config.Configuration) (ks.AllTypes, error) { for _, fileContents := range bytes.Split(fullFile, []byte("\n---\n")) { if len(bytes.TrimSpace(fileContents)) > 0 { - if err := p.detectAndDecode(cnf, s, namedReader.Name(), offset, fileContents); err != nil { + if err := p.detectAndDecode(s, namedReader.Name(), offset, fileContents); err != nil { return nil, err } } @@ -181,7 +190,7 @@ func (p *Parser) ParseFiles(cnf config.Configuration) (ks.AllTypes, error) { return s, nil } -func (p *Parser) detectAndDecode(cnf config.Configuration, s *parsedObjects, fileName string, fileOffset int, raw []byte) error { +func (p *Parser) detectAndDecode(s *parsedObjects, fileName string, fileOffset int, raw []byte) error { var detect detectKind err := yaml.Unmarshal(raw, &detect) if err != nil { @@ -198,7 +207,7 @@ func (p *Parser) detectAndDecode(cnf config.Configuration, s *parsedObjects, fil return err } for _, listItem := range list.Items { - err := p.detectAndDecode(cnf, s, fileName, fileOffset, listItem.Raw) + err := p.detectAndDecode(s, fileName, fileOffset, listItem.Raw) if err != nil { return err } @@ -206,7 +215,7 @@ func (p *Parser) detectAndDecode(cnf config.Configuration, s *parsedObjects, fil return nil } - err = p.decodeItem(cnf, s, detectedVersion, fileName, fileOffset, raw) + err = p.decodeItem(s, detectedVersion, fileName, fileOffset, raw) if err != nil { return err } @@ -241,7 +250,7 @@ func detectFileLocation(fileName string, fileOffset int, fileContents []byte) ks } } -func (p *Parser) decodeItem(cnf config.Configuration, s *parsedObjects, detectedVersion schema.GroupVersionKind, fileName string, fileOffset int, fileContents []byte) error { +func (p *Parser) decodeItem(s *parsedObjects, detectedVersion schema.GroupVersionKind, fileName string, fileOffset int, fileContents []byte) error { addPodSpeccer := func(ps ks.PodSpecer) { s.podspecers = append(s.podspecers, ps) s.bothMetas = append(s.bothMetas, ks.BothMeta{ @@ -418,7 +427,7 @@ func (p *Parser) decodeItem(cnf config.Configuration, s *parsedObjects, detected s.bothMetas = append(s.bothMetas, ks.BothMeta{TypeMeta: hpa.TypeMeta, ObjectMeta: hpa.ObjectMeta, FileLocationer: h}) default: - if cnf.VerboseOutput > 1 { + if p.config.VerboseOutput > 1 { log.Printf("Unknown datatype: %s", detectedVersion.String()) } } diff --git a/parser/parse_test.go b/parser/parse_test.go index 196dc43b..d7c7b8eb 100644 --- a/parser/parse_test.go +++ b/parser/parse_test.go @@ -5,7 +5,6 @@ import ( "os" "testing" - "github.com/zegl/kube-score/config" ks "github.com/zegl/kube-score/domain" "github.com/stretchr/testify/assert" @@ -25,15 +24,15 @@ func TestParse(t *testing.T) { }, } - parser, err := New() + parser, err := New(nil) assert.NoError(t, err) for _, tc := range cases { fp, err := os.Open(tc.fname) assert.Nil(t, err) - _, err = parser.ParseFiles(config.Configuration{ - AllFiles: []ks.NamedReader{fp}, - }) + _, err = parser.ParseFiles( + []ks.NamedReader{fp}, + ) if tc.expected == nil { assert.Nil(t, err) } else { diff --git a/renderer/sarif/sarif.go b/renderer/sarif/sarif.go index 6fa68555..99bbdf8b 100644 --- a/renderer/sarif/sarif.go +++ b/renderer/sarif/sarif.go @@ -83,10 +83,10 @@ func Output(input *scorecard.Scorecard) io.Reader { Conversion: sarif.Conversion{ Tool: sarif.Tool{ Driver: sarif.Driver{ - Name: "kube-score", }, + Name: "kube-score"}, }, }, - Results: results, + Results: results, } res := sarif.Sarif{ Runs: []sarif.Run{run}, diff --git a/score/apps_test.go b/score/apps_test.go index bf086069..f62a3c8e 100644 --- a/score/apps_test.go +++ b/score/apps_test.go @@ -108,20 +108,18 @@ func TestStatefulsetSelectorLabels(t *testing.T) { func TestStatefulsetTemplateIgnores(t *testing.T) { t.Parallel() - skipped := wasSkipped(t, config.Configuration{ + skipped := wasSkipped(t, []ks.NamedReader{testFile("statefulset-nested-ignores.yaml")}, nil, &config.RunConfiguration{ UseIgnoreChecksAnnotation: true, UseOptionalChecksAnnotation: true, - AllFiles: []ks.NamedReader{testFile("statefulset-nested-ignores.yaml")}, }, "Container Image Tag") assert.True(t, skipped) } func TestStatefulsetTemplateIgnoresNotIgnoredWhenFlagDisabled(t *testing.T) { t.Parallel() - skipped := wasSkipped(t, config.Configuration{ + skipped := wasSkipped(t, []ks.NamedReader{testFile("statefulset-nested-ignores.yaml")}, nil, &config.RunConfiguration{ UseIgnoreChecksAnnotation: false, UseOptionalChecksAnnotation: true, - AllFiles: []ks.NamedReader{testFile("statefulset-nested-ignores.yaml")}, }, "Container Image Tag") assert.False(t, skipped) } diff --git a/score/checks/checks.go b/score/checks/checks.go index 77101052..5ed7c4f2 100644 --- a/score/checks/checks.go +++ b/score/checks/checks.go @@ -3,7 +3,6 @@ package checks import ( "strings" - "github.com/zegl/kube-score/config" ks "github.com/zegl/kube-score/domain" "github.com/zegl/kube-score/scorecard" appsv1 "k8s.io/api/apps/v1" @@ -11,7 +10,14 @@ import ( networkingv1 "k8s.io/api/networking/v1" ) -func New(cnf config.Configuration) *Checks { +type Config struct { + IgnoredTests map[string]struct{} +} + +func New(cnf *Config) *Checks { + if cnf == nil { + cnf = &Config{} + } return &Checks{ cnf: cnf, @@ -71,7 +77,7 @@ type Checks struct { horizontalPodAutoscalers map[string]GenCheck[ks.HpaTargeter] poddisruptionbudgets map[string]GenCheck[ks.PodDisruptionBudget] - cnf config.Configuration + cnf *Config } func (c Checks) isEnabled(check ks.Check) bool { diff --git a/score/container/container.go b/score/container/container.go index 39527a24..51fdb304 100644 --- a/score/container/container.go +++ b/score/container/container.go @@ -4,15 +4,14 @@ import ( "fmt" "strings" - "github.com/zegl/kube-score/config" ks "github.com/zegl/kube-score/domain" "github.com/zegl/kube-score/score/checks" "github.com/zegl/kube-score/scorecard" corev1 "k8s.io/api/core/v1" ) -func Register(allChecks *checks.Checks, cnf config.Configuration) { - allChecks.RegisterPodCheck("Container Resources", `Makes sure that all pods have resource limits and requests set. The --ignore-container-cpu-limit flag can be used to disable the requirement of having a CPU limit`, containerResources(!cnf.IgnoreContainerCpuLimitRequirement, !cnf.IgnoreContainerMemoryLimitRequirement)) +func Register(allChecks *checks.Checks, ignoreContainerCpuLimitRequirement, ignoreContainerMemoryLimitRequirement bool) { + allChecks.RegisterPodCheck("Container Resources", `Makes sure that all pods have resource limits and requests set. The --ignore-container-cpu-limit flag can be used to disable the requirement of having a CPU limit`, containerResources(!ignoreContainerCpuLimitRequirement, !ignoreContainerMemoryLimitRequirement)) allChecks.RegisterOptionalPodCheck("Container Resource Requests Equal Limits", `Makes sure that all pods have the same requests as limits on resources set.`, containerResourceRequestsEqualLimits) allChecks.RegisterOptionalPodCheck("Container CPU Requests Equal Limits", `Makes sure that all pods have the same CPU requests as limits set.`, containerCPURequestsEqualLimits) allChecks.RegisterOptionalPodCheck("Container Memory Requests Equal Limits", `Makes sure that all pods have the same memory requests as limits set.`, containerMemoryRequestsEqualLimits) diff --git a/score/deployment_test.go b/score/deployment_test.go index fa5ccf9a..13b3dab5 100644 --- a/score/deployment_test.go +++ b/score/deployment_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/zegl/kube-score/config" ks "github.com/zegl/kube-score/domain" "github.com/zegl/kube-score/scorecard" ) @@ -16,9 +15,9 @@ func TestServiceTargetsDeploymentStrategyRolling(t *testing.T) { func TestServiceNotTargetsDeploymentStrategyNotRelevant(t *testing.T) { t.Parallel() - skipped := wasSkipped(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("service-not-target-deployment.yaml")}, - }, "Deployment Strategy") + skipped := wasSkipped(t, + []ks.NamedReader{testFile("service-not-target-deployment.yaml")}, nil, nil, + "Deployment Strategy") assert.True(t, skipped) } @@ -39,13 +38,12 @@ func TestServiceTargetsDeploymentReplicasOk(t *testing.T) { func TestServiceNotTargetsDeploymentReplicasNotRelevant(t *testing.T) { t.Parallel() - assert.True(t, wasSkipped(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("service-not-target-deployment.yaml")}, - }, "Deployment Replicas")) + assert.True(t, wasSkipped(t, + []ks.NamedReader{testFile("service-not-target-deployment.yaml")}, nil, nil, + "Deployment Replicas")) - summaries := getSummaries(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("service-not-target-deployment.yaml")}, - }, "Deployment Replicas") + summaries := getSummaries(t, []ks.NamedReader{testFile("service-not-target-deployment.yaml")}, nil, nil, + "Deployment Replicas") assert.Contains(t, summaries, "Skipped as the Deployment is not targeted by service") } @@ -56,12 +54,11 @@ func TestServiceTargetsDeploymentReplicasNok(t *testing.T) { func TestHPATargetsDeployment(t *testing.T) { t.Parallel() - assert.True(t, wasSkipped(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("hpa-target-deployment.yaml")}, - }, "Deployment Replicas")) + assert.True(t, wasSkipped(t, + []ks.NamedReader{testFile("hpa-target-deployment.yaml")}, nil, nil, + "Deployment Replicas")) - summaries := getSummaries(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("hpa-target-deployment.yaml")}, - }, "Deployment Replicas") + summaries := getSummaries(t, []ks.NamedReader{testFile("hpa-target-deployment.yaml")}, nil, nil, + "Deployment Replicas") assert.Contains(t, summaries, "Skipped as the Deployment is controlled by a HorizontalPodAutoscaler") } diff --git a/score/filelocation_test.go b/score/filelocation_test.go index b5bee06a..fcdb1e85 100644 --- a/score/filelocation_test.go +++ b/score/filelocation_test.go @@ -10,8 +10,7 @@ import ( ) func TestFileLocationHelm(t *testing.T) { - sc, err := testScore(config.Configuration{ - AllFiles: []ks.NamedReader{testFile("linenumbers-helm.yaml")}, + sc, err := testScore([]ks.NamedReader{testFile("linenumbers-helm.yaml")}, nil, &config.RunConfiguration{ KubernetesVersion: config.Semver{Major: 1, Minor: 18}, }) assert.Nil(t, err) @@ -23,8 +22,7 @@ func TestFileLocationHelm(t *testing.T) { } func TestFileLocation(t *testing.T) { - sc, err := testScore(config.Configuration{ - AllFiles: []ks.NamedReader{testFile("linenumbers.yaml")}, + sc, err := testScore([]ks.NamedReader{testFile("linenumbers.yaml")}, nil, &config.RunConfiguration{ KubernetesVersion: config.Semver{Major: 1, Minor: 18}, }) assert.Nil(t, err) diff --git a/score/optional_test.go b/score/optional_test.go index 52f329e9..ebdf8a36 100644 --- a/score/optional_test.go +++ b/score/optional_test.go @@ -5,14 +5,14 @@ import ( "github.com/zegl/kube-score/config" ks "github.com/zegl/kube-score/domain" + "github.com/zegl/kube-score/score/checks" "github.com/zegl/kube-score/scorecard" ) func TestOptionalSkippedByDefault(t *testing.T) { t.Parallel() enabledOptionalTests := make(map[string]struct{}) - wasSkipped(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-container-memory-requests.yaml")}, + wasSkipped(t, []ks.NamedReader{testFile("pod-container-memory-requests.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: enabledOptionalTests, }, "Container Memory Requests Equal Limits") } @@ -26,10 +26,10 @@ func TestOptionalIgnoredAndEnabled(t *testing.T) { ignoredTests := make(map[string]struct{}) ignoredTests["container-resource-requests-equal-limits"] = struct{}{} - wasSkipped(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-container-memory-requests.yaml")}, + wasSkipped(t, []ks.NamedReader{testFile("pod-container-memory-requests.yaml")}, &checks.Config{ + IgnoredTests: ignoredTests, + }, &config.RunConfiguration{ EnabledOptionalTests: enabledOptionalTests, - IgnoredTests: ignoredTests, }, "Container Memory Requests Equal Limits") } @@ -39,10 +39,10 @@ func TestOptionalRunCliFlagEnabledDefault(t *testing.T) { enabledOptionalTests := make(map[string]struct{}) enabledOptionalTests["container-resource-requests-equal-limits"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-container-memory-requests.yaml")}, - EnabledOptionalTests: enabledOptionalTests, - }, "Container Memory Requests Equal Limits", scorecard.GradeCritical) + testExpectedScoreWithConfig(t, + []ks.NamedReader{testFile("pod-container-memory-requests.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: enabledOptionalTests, + }, "Container Memory Requests Equal Limits", scorecard.GradeCritical) } func TestOptionalRunAnnotationEnabled(t *testing.T) { @@ -50,8 +50,8 @@ func TestOptionalRunAnnotationEnabled(t *testing.T) { enabledOptionalTests := make(map[string]struct{}) - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-container-memory-requests-annotation-optional.yaml")}, - EnabledOptionalTests: enabledOptionalTests, - }, "Container Memory Requests Equal Limits", scorecard.GradeCritical) + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-memory-requests-annotation-optional.yaml")}, nil, + &config.RunConfiguration{ + EnabledOptionalTests: enabledOptionalTests, + }, "Container Memory Requests Equal Limits", scorecard.GradeCritical) } diff --git a/score/score.go b/score/score.go index 7ddc8b8a..390d7afd 100644 --- a/score/score.go +++ b/score/score.go @@ -1,6 +1,8 @@ package score import ( + "errors" + "github.com/zegl/kube-score/config" ks "github.com/zegl/kube-score/domain" "github.com/zegl/kube-score/score/apps" @@ -23,19 +25,19 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func RegisterAllChecks(allObjects ks.AllTypes, cnf config.Configuration) *checks.Checks { - allChecks := checks.New(cnf) +func RegisterAllChecks(allObjects ks.AllTypes, checksConfig *checks.Config, runConfig *config.RunConfiguration) *checks.Checks { + allChecks := checks.New(checksConfig) deployment.Register(allChecks, allObjects) ingress.Register(allChecks, allObjects) cronjob.Register(allChecks) - container.Register(allChecks, cnf) + container.Register(allChecks, runConfig.IgnoreContainerCpuLimitRequirement, runConfig.IgnoreContainerMemoryLimitRequirement) disruptionbudget.Register(allChecks, allObjects) networkpolicy.Register(allChecks, allObjects, allObjects, allObjects) probes.Register(allChecks, allObjects) security.Register(allChecks) service.Register(allChecks, allObjects, allObjects) - stable.Register(cnf.KubernetesVersion, allChecks) + stable.Register(runConfig.KubernetesVersion, allChecks) apps.Register(allChecks, allObjects.HorizontalPodAutoscalers(), allObjects.Services()) meta.Register(allChecks) hpa.Register(allChecks, allObjects.Metas()) @@ -68,8 +70,15 @@ func (p *podSpeccer) FileLocation() ks.FileLocation { // Score runs a pre-configured list of tests against the files defined in the configuration, and returns a scorecard. // Additional configuration and tuning parameters can be provided via the config. -func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scorecard, error) { - allChecks := RegisterAllChecks(allObjects, cnf) +func Score(allObjects ks.AllTypes, allChecks *checks.Checks, cnf *config.RunConfiguration) (*scorecard.Scorecard, error) { + if cnf == nil { + cnf = &config.RunConfiguration{} + } + + if allChecks == nil { + return nil, errors.New("no checks registered") + } + scoreCard := scorecard.New() newObject := func(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta) *scorecard.ScoredObject { diff --git a/score/score_test.go b/score/score_test.go index 10860954..d32d6445 100644 --- a/score/score_test.go +++ b/score/score_test.go @@ -9,6 +9,7 @@ import ( "github.com/zegl/kube-score/config" ks "github.com/zegl/kube-score/domain" "github.com/zegl/kube-score/parser" + "github.com/zegl/kube-score/score/checks" "github.com/zegl/kube-score/scorecard" ) @@ -22,8 +23,8 @@ func testFile(name string) *os.File { // testExpectedScoreWithConfig runs all tests, but makes sure that the test for "testcase" was executed, and that // the grade is set to expectedScore. The function returns the comments of "testcase". -func testExpectedScoreWithConfig(t *testing.T, config config.Configuration, testcase string, expectedScore scorecard.Grade) []scorecard.TestScoreComment { - sc, err := testScore(config) +func testExpectedScoreWithConfig(t *testing.T, files []ks.NamedReader, checksConfig *checks.Config, runConfig *config.RunConfiguration, testcase string, expectedScore scorecard.Grade) []scorecard.TestScoreComment { + sc, err := testScore(files, checksConfig, runConfig) assert.NoError(t, err) for _, objectScore := range sc { @@ -39,8 +40,8 @@ func testExpectedScoreWithConfig(t *testing.T, config config.Configuration, test return nil } -func wasSkipped(t *testing.T, config config.Configuration, testcase string) bool { - sc, err := testScore(config) +func wasSkipped(t *testing.T, files []ks.NamedReader, checksConfig *checks.Config, runConfig *config.RunConfiguration, testcase string) bool { + sc, err := testScore(files, checksConfig, runConfig) assert.NoError(t, err) for _, objectScore := range sc { for _, s := range objectScore.Checks { @@ -54,8 +55,8 @@ func wasSkipped(t *testing.T, config config.Configuration, testcase string) bool return false } -func getSummaries(t *testing.T, config config.Configuration, testcase string) []string { - sc, err := testScore(config) +func getSummaries(t *testing.T, files []ks.NamedReader, checksConfig *checks.Config, runConfig *config.RunConfiguration, testcase string) []string { + sc, err := testScore(files, checksConfig, runConfig) assert.NoError(t, err) var summaries []string for _, objectScore := range sc { @@ -73,18 +74,27 @@ func getSummaries(t *testing.T, config config.Configuration, testcase string) [] return summaries } -func testScore(config config.Configuration) (scorecard.Scorecard, error) { - p, err := parser.New() +func testScore(files []ks.NamedReader, checksConfig *checks.Config, runConfig *config.RunConfiguration) (scorecard.Scorecard, error) { + p, err := parser.New(nil) if err != nil { return nil, err } - parsed, err := p.ParseFiles(config) + parsed, err := p.ParseFiles(files) if err != nil { return nil, err } - card, err := Score(parsed, config) + if checksConfig == nil { + checksConfig = &checks.Config{} + } + if runConfig == nil { + runConfig = &config.RunConfiguration{} + } + + allChecks := RegisterAllChecks(parsed, checksConfig, runConfig) + + card, err := Score(parsed, allChecks, runConfig) if err != nil { return nil, err } @@ -93,8 +103,7 @@ func testScore(config config.Configuration) (scorecard.Scorecard, error) { } func testExpectedScore(t *testing.T, filename string, testcase string, expectedScore scorecard.Grade) []scorecard.TestScoreComment { - return testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile(filename)}, + return testExpectedScoreWithConfig(t, []ks.NamedReader{testFile(filename)}, nil, &config.RunConfiguration{ KubernetesVersion: config.Semver{Major: 1, Minor: 18}, }, testcase, expectedScore) } @@ -116,26 +125,23 @@ func TestPodContainerResourceLimitsAndRequests(t *testing.T) { func TestPodContainerResourceLimitCpuNotRequired(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests-no-cpu-limit.yaml")}, nil, &config.RunConfiguration{ IgnoreContainerCpuLimitRequirement: true, - AllFiles: []ks.NamedReader{testFile("pod-test-resources-limits-and-requests-no-cpu-limit.yaml")}, }, "Container Resources", scorecard.GradeAllOK) } func TestPodContainerResourceLimitCpuRequired(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests-no-cpu-limit.yaml")}, nil, &config.RunConfiguration{ IgnoreContainerCpuLimitRequirement: false, - AllFiles: []ks.NamedReader{testFile("pod-test-resources-limits-and-requests-no-cpu-limit.yaml")}, }, "Container Resources", scorecard.GradeCritical) } func TestPodContainerResourceNoLimitRequired(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, nil, &config.RunConfiguration{ IgnoreContainerCpuLimitRequirement: true, IgnoreContainerMemoryLimitRequirement: true, - AllFiles: []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, }, "Container Resources", scorecard.GradeAllOK) } @@ -145,8 +151,8 @@ func TestPodContainerResourceRequestsEqualLimits(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-resource-requests-equal-limits"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-test-resources-limits-and-requests.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container Resource Requests Equal Limits", scorecard.GradeAllOK) } @@ -157,8 +163,7 @@ func TestPodContainerMemoryRequestsEqualLimits(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-memory-requests-equal-limits"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-test-resources-limits-and-requests.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: structMap, }, "Container Memory Requests Equal Limits", scorecard.GradeAllOK) } @@ -169,8 +174,7 @@ func TestPodContainerCPURequestsEqualLimits(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-cpu-requests-equal-limits"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-test-resources-limits-and-requests.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-limits-and-requests.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: structMap, }, "Container CPU Requests Equal Limits", scorecard.GradeAllOK) } @@ -181,8 +185,8 @@ func TestPodContainerResourceRequestsEqualLimitsNoLimits(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-resource-requests-equal-limits"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container Resource Requests Equal Limits", scorecard.GradeCritical) } @@ -190,8 +194,7 @@ func TestPodContainerResourceRequestsEqualLimitsNoLimits(t *testing.T) { func TestPodContainerResourceRequestsEqualLimitsNoLimitsAnnotation(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-test-resources-no-limits-annotation-optional.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits-annotation-optional.yaml")}, nil, &config.RunConfiguration{ UseOptionalChecksAnnotation: true, }, "Container Resource Requests Equal Limits", scorecard.GradeCritical) } @@ -202,8 +205,8 @@ func TestPodContainerMemoryRequestsEqualLimitsNoLimits(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-memory-requests-equal-limits"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container Memory Requests Equal Limits", scorecard.GradeCritical) } @@ -211,8 +214,8 @@ func TestPodContainerMemoryRequestsEqualLimitsNoLimits(t *testing.T) { func TestPodContainerMemoryRequestsEqualLimitsNoLimitsAnnotation(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-test-resources-no-limits-annotation-optional.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits-annotation-optional.yaml")}, nil, &config.RunConfiguration{ + UseOptionalChecksAnnotation: true, }, "Container Memory Requests Equal Limits", scorecard.GradeCritical) } @@ -223,8 +226,8 @@ func TestPodContainerCPURequestsEqualLimitsNoLimits(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-cpu-requests-equal-limits"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container CPU Requests Equal Limits", scorecard.GradeCritical) } @@ -232,8 +235,8 @@ func TestPodContainerCPURequestsEqualLimitsNoLimits(t *testing.T) { func TestPodContainerCPURequestsEqualLimitsNoLimitsAnnotation(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-test-resources-no-limits-annotation-optional.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-test-resources-no-limits-annotation-optional.yaml")}, nil, &config.RunConfiguration{ + UseOptionalChecksAnnotation: true, }, "Container CPU Requests Equal Limits", scorecard.GradeCritical) } @@ -285,17 +288,13 @@ func TestPodContainerPullPolicyAlways(t *testing.T) { func TestConfigMapMultiDash(t *testing.T) { t.Parallel() - _, err := testScore(config.Configuration{ - AllFiles: []ks.NamedReader{testFile("configmap-multi-dash.yaml")}, - }) + _, err := testScore([]ks.NamedReader{testFile("configmap-multi-dash.yaml")}, nil, nil) assert.Nil(t, err) } func TestAnnotationIgnore(t *testing.T) { t.Parallel() - s, err := testScore(config.Configuration{ - VerboseOutput: 0, - AllFiles: []ks.NamedReader{testFile("ignore-annotation-service.yaml")}, + s, err := testScore([]ks.NamedReader{testFile("ignore-annotation-service.yaml")}, nil, &config.RunConfiguration{ UseIgnoreChecksAnnotation: true, }) assert.Nil(t, err) @@ -317,9 +316,7 @@ func TestAnnotationIgnore(t *testing.T) { func TestAnnotationIgnoreDisabled(t *testing.T) { t.Parallel() - s, err := testScore(config.Configuration{ - VerboseOutput: 0, - AllFiles: []ks.NamedReader{testFile("ignore-annotation-service.yaml")}, + s, err := testScore([]ks.NamedReader{testFile("ignore-annotation-service.yaml")}, nil, &config.RunConfiguration{ UseIgnoreChecksAnnotation: false, }) assert.Nil(t, err) @@ -342,9 +339,7 @@ func TestAnnotationIgnoreDisabled(t *testing.T) { func TestList(t *testing.T) { t.Parallel() - s, err := testScore(config.Configuration{ - AllFiles: []ks.NamedReader{testFile("list.yaml")}, - }) + s, err := testScore([]ks.NamedReader{testFile("list.yaml")}, nil, nil) assert.Nil(t, err) assert.Len(t, s, 2) @@ -388,8 +383,8 @@ func TestPodContainerStorageEphemeralRequestEqualsLimit(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-ephemeral-storage-request-equals-limit"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-ephemeral-storage-request-matches-limit.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-ephemeral-storage-request-matches-limit.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container Ephemeral Storage Request Equals Limit", scorecard.GradeAllOK) } @@ -400,8 +395,7 @@ func TestPodContainerStorageEphemeralRequestNoMatchLimit(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-ephemeral-storage-request-equals-limit"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-ephemeral-storage-request-nomatch-limit.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-ephemeral-storage-request-nomatch-limit.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: structMap, }, "Container Ephemeral Storage Request Equals Limit", scorecard.GradeCritical) } @@ -409,8 +403,8 @@ func TestPodContainerStorageEphemeralRequestNoMatchLimit(t *testing.T) { func TestPodContainerStorageEphemeralRequestNoMatchLimitAnnotation(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-ephemeral-storage-request-nomatch-limit-annotation-optional.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-ephemeral-storage-request-nomatch-limit-annotation-optional.yaml")}, nil, &config.RunConfiguration{ + UseOptionalChecksAnnotation: true, }, "Container Ephemeral Storage Request Equals Limit", scorecard.GradeCritical) } @@ -418,9 +412,8 @@ func TestPodContainerStorageEphemeralRequestNoMatchLimitAnnotation(t *testing.T) func TestPodContainerStorageEphemeralIgnoreAnnotation(t *testing.T) { t.Parallel() - s, err := testScore(config.Configuration{ - VerboseOutput: 0, - AllFiles: []ks.NamedReader{testFile("pod-ephemeral-storage-annotation-ignore.yaml")}, + s, err := testScore([]ks.NamedReader{testFile("pod-ephemeral-storage-annotation-ignore.yaml")}, nil, &config.RunConfiguration{ + UseIgnoreChecksAnnotation: true, }) assert.Nil(t, err) @@ -445,8 +438,8 @@ func TestPodContainerPortsContainerPortMissing(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-ports-check"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-container-ports-missing-containerport.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-missing-containerport.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container Ports Check", scorecard.GradeCritical) } @@ -454,8 +447,8 @@ func TestPodContainerPortsContainerPortMissing(t *testing.T) { func TestPodContainerPortsContainerPortMissingAnnotation(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-container-ports-missing-containerport-annotation-optional.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-missing-containerport-annotation-optional.yaml")}, nil, &config.RunConfiguration{ + UseOptionalChecksAnnotation: true, }, "Container Ports Check", scorecard.GradeCritical) } @@ -466,8 +459,8 @@ func TestPodContainerPortsDuplicateNames(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-ports-check"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-container-ports-duplicate-names.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-duplicate-names.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container Ports Check", scorecard.GradeCritical) } @@ -478,8 +471,8 @@ func TestPodContainerPortsNameLength(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-ports-check"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-container-ports-name-too-long.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-name-too-long.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container Ports Check", scorecard.GradeCritical) } @@ -490,8 +483,8 @@ func TestPodContainerPortsOK(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-ports-check"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-container-ports-ok.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-container-ports-ok.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container Ports Check", scorecard.GradeAllOK) } @@ -502,8 +495,8 @@ func TestPodEnvOK(t *testing.T) { structMap := make(map[string]struct{}) structMap["environment-variable-key-duplication"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-env-ok.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-env-ok.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Environment Variable Key Duplication", scorecard.GradeAllOK) } @@ -514,8 +507,8 @@ func TestPodEnvDuplicated(t *testing.T) { structMap := make(map[string]struct{}) structMap["environment-variable-key-duplication"] = struct{}{} - actual := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-env-duplicated.yaml")}, + actual := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-env-duplicated.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Environment Variable Key Duplication", scorecard.GradeCritical) @@ -537,9 +530,7 @@ func TestPodEnvDuplicated(t *testing.T) { func TestMultipleIgnoreAnnotations(t *testing.T) { t.Parallel() - s, err := testScore(config.Configuration{ - VerboseOutput: 0, - AllFiles: []ks.NamedReader{testFile("kube-score-ignore-annotations.yaml")}, + s, err := testScore([]ks.NamedReader{testFile("kube-score-ignore-annotations.yaml")}, nil, &config.RunConfiguration{ UseIgnoreChecksAnnotation: true, }) assert.Nil(t, err) diff --git a/score/security_test.go b/score/security_test.go index 1c45663a..2f077eaa 100644 --- a/score/security_test.go +++ b/score/security_test.go @@ -15,8 +15,7 @@ func TestContainerSeccompMissing(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-seccomp-profile"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-seccomp-no-annotation.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-no-annotation.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: structMap, }, "Container Seccomp Profile", scorecard.GradeWarning) @@ -24,9 +23,7 @@ func TestContainerSeccompMissing(t *testing.T) { func TestContainerSeccompMissingNotRunByDefault(t *testing.T) { t.Parallel() - skipped := wasSkipped(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-seccomp-no-annotation.yaml")}, - }, "Container Seccomp Profile") + skipped := wasSkipped(t, []ks.NamedReader{testFile("pod-seccomp-no-annotation.yaml")}, nil, nil, "Container Seccomp Profile") assert.True(t, skipped) } @@ -36,8 +33,8 @@ func TestContainerSeccompAllGood(t *testing.T) { structMap := make(map[string]struct{}) structMap["container-seccomp-profile"] = struct{}{} - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-seccomp-annotated.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-annotated.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: structMap, }, "Container Seccomp Profile", scorecard.GradeAllOK) } @@ -45,8 +42,7 @@ func TestContainerSeccompAllGood(t *testing.T) { func TestContainerSeccompAllGoodAnnotation(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-seccomp-annotated-annotation-optional.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-seccomp-annotated-annotation-optional.yaml")}, nil, &config.RunConfiguration{ UseOptionalChecksAnnotation: true, }, "Container Seccomp Profile", scorecard.GradeAllOK) } @@ -55,8 +51,7 @@ func TestContainerSecurityContextUserGroupIDAllGood(t *testing.T) { t.Parallel() structMap := make(map[string]struct{}) structMap["container-security-context-user-group-id"] = struct{}{} - c := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-security-context-all-good.yaml")}, + c := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-all-good.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: structMap, }, "Container Security Context User Group ID", scorecard.GradeAllOK) assert.Empty(t, c) @@ -66,8 +61,8 @@ func TestContainerSecurityContextUserGroupIDLowGroup(t *testing.T) { t.Parallel() optionalChecks := make(map[string]struct{}) optionalChecks["container-security-context-user-group-id"] = struct{}{} - comments := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-security-context-low-group-id.yaml")}, + comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-low-group-id.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: optionalChecks, }, "Container Security Context User Group ID", scorecard.GradeCritical) assert.Contains(t, comments, scorecard.TestScoreComment{ @@ -81,8 +76,7 @@ func TestContainerSecurityContextUserGroupIDLowUser(t *testing.T) { t.Parallel() optionalChecks := make(map[string]struct{}) optionalChecks["container-security-context-user-group-id"] = struct{}{} - comments := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-security-context-low-user-id.yaml")}, + comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-low-user-id.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: optionalChecks, }, "Container Security Context User Group ID", scorecard.GradeCritical) assert.Contains(t, comments, scorecard.TestScoreComment{ @@ -96,8 +90,8 @@ func TestContainerSecurityContextUserGroupIDNoSecurityContext(t *testing.T) { t.Parallel() optionalChecks := make(map[string]struct{}) optionalChecks["container-security-context-user-group-id"] = struct{}{} - comments := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-security-context-nosecuritycontext.yaml")}, + comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-nosecuritycontext.yaml")}, nil, &config.RunConfiguration{ + EnabledOptionalTests: optionalChecks, }, "Container Security Context User Group ID", scorecard.GradeCritical) assert.Contains(t, comments, scorecard.TestScoreComment{ @@ -111,8 +105,7 @@ func TestContainerSecurityContextPrivilegedAllGood(t *testing.T) { t.Parallel() structMap := make(map[string]struct{}) structMap["container-security-context-privileged"] = struct{}{} - c := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-security-context-all-good.yaml")}, + c := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-all-good.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: structMap, }, "Container Security Context Privileged", scorecard.GradeAllOK) assert.Empty(t, c) @@ -122,8 +115,7 @@ func TestContainerSecurityContextPrivilegedPrivileged(t *testing.T) { t.Parallel() optionalChecks := make(map[string]struct{}) optionalChecks["container-security-context-privileged"] = struct{}{} - comments := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-security-context-privileged.yaml")}, + comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-privileged.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: optionalChecks, }, "Container Security Context Privileged", scorecard.GradeCritical) assert.Contains(t, comments, scorecard.TestScoreComment{ @@ -137,8 +129,7 @@ func TestContainerSecurityContextReadOnlyRootFilesystemAllGood(t *testing.T) { t.Parallel() structMap := make(map[string]struct{}) structMap["container-security-context-readonlyrootfilesystem"] = struct{}{} - c := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-security-context-all-good.yaml")}, + c := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-all-good.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: structMap, }, "Container Security Context ReadOnlyRootFilesystem", scorecard.GradeAllOK) assert.Empty(t, c) @@ -148,8 +139,7 @@ func TestContainerSecurityContextReadOnlyRootFilesystemWriteable(t *testing.T) { t.Parallel() optionalChecks := make(map[string]struct{}) optionalChecks["container-security-context-readonlyrootfilesystem"] = struct{}{} - comments := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-security-context-writeablerootfilesystem.yaml")}, + comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-writeablerootfilesystem.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: optionalChecks, }, "Container Security Context ReadOnlyRootFilesystem", scorecard.GradeCritical) assert.Contains(t, comments, scorecard.TestScoreComment{ @@ -163,8 +153,7 @@ func TestContainerSecurityContextReadOnlyRootFilesystemNoSecurityContext(t *test t.Parallel() optionalChecks := make(map[string]struct{}) optionalChecks["container-security-context-readonlyrootfilesystem"] = struct{}{} - comments := testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("pod-security-context-nosecuritycontext.yaml")}, + comments := testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("pod-security-context-nosecuritycontext.yaml")}, nil, &config.RunConfiguration{ EnabledOptionalTests: optionalChecks, }, "Container Security Context ReadOnlyRootFilesystem", scorecard.GradeCritical) assert.Contains(t, comments, scorecard.TestScoreComment{ diff --git a/score/stable_version_test.go b/score/stable_version_test.go index 6a472334..ba8ae29e 100644 --- a/score/stable_version_test.go +++ b/score/stable_version_test.go @@ -15,16 +15,14 @@ func TestStatefulSetAppsv1beta1(t *testing.T) { func TestStatefulSetAppsv1beta1Kubernetes1dot4(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("statefulset-appsv1beta1.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("statefulset-appsv1beta1.yaml")}, nil, &config.RunConfiguration{ KubernetesVersion: config.Semver{Major: 1, Minor: 4}, }, "Stable version", scorecard.GradeAllOK) } func TestStatefulSetAppsv1beta1Kubernetes1dot18(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("statefulset-appsv1beta1.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("statefulset-appsv1beta1.yaml")}, nil, &config.RunConfiguration{ KubernetesVersion: config.Semver{Major: 1, Minor: 18}, }, "Stable version", scorecard.GradeWarning) } @@ -71,8 +69,7 @@ func TestCronJobBatchv1beta1(t *testing.T) { func TestCronJobBatchv1beta1Kubernetes1dot21(t *testing.T) { t.Parallel() - testExpectedScoreWithConfig(t, config.Configuration{ - AllFiles: []ks.NamedReader{testFile("cronjob-batchv1beta1.yaml")}, + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("cronjob-batchv1beta1.yaml")}, nil, &config.RunConfiguration{ KubernetesVersion: config.Semver{Major: 1, Minor: 21}, }, "Stable version", scorecard.GradeWarning) } diff --git a/scorecard/scorecard.go b/scorecard/scorecard.go index a1766a19..6f59e094 100644 --- a/scorecard/scorecard.go +++ b/scorecard/scorecard.go @@ -25,7 +25,11 @@ func New() Scorecard { return make(Scorecard) } -func (s Scorecard) NewObject(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta, cnf config.Configuration) *ScoredObject { +func (s Scorecard) NewObject(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta, cnf *config.RunConfiguration) *ScoredObject { + if cnf == nil { + cnf = &config.RunConfiguration{} + } + o := &ScoredObject{ TypeMeta: typeMeta, ObjectMeta: objectMeta,