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,