Skip to content

Commit

Permalink
Implement feature request: a configuration file zegl#384
Browse files Browse the repository at this point in the history
  • Loading branch information
kmarteaux committed Mar 5, 2022
1 parent dc33f3e commit b9ba777
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 6 deletions.
31 changes: 25 additions & 6 deletions cmd/kube-score/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import (
"path/filepath"

flag "github.com/spf13/pflag"
"golang.org/x/crypto/ssh/terminal"

"github.com/zegl/kube-score/config"
ks "github.com/zegl/kube-score/domain"
"github.com/zegl/kube-score/parser"
Expand All @@ -23,6 +21,7 @@ import (
"github.com/zegl/kube-score/renderer/sarif"
"github.com/zegl/kube-score/score"
"github.com/zegl/kube-score/scorecard"
"golang.org/x/crypto/ssh/terminal"
)

func main() {
Expand All @@ -43,6 +42,10 @@ func main() {
listChecks(helpName, args)
},

"mkconfig": func(helpName string, args []string) {
mkConfigFile(helpName, args)
},

"version": func(helpName string, args []string) {
cmdVersion()
},
Expand Down Expand Up @@ -73,10 +76,11 @@ func setDefault(fs *flag.FlagSet, binName, actionName string, displayForMoreInfo
%s [action] --flags
Actions:
score Checks all files in the input, and gives them a score and recommendations
list Prints a CSV list of all available score checks
version Print the version of kube-score
help Print this message`+"\n\n", binName, binName)
score Checks all files in the input, and gives them a score and recommendations
list Prints a CSV list of all available score checks
version Print the version of kube-score
mkconfig Creates a .kube-score.yml configuration file from kube-score's registered checks in the current working directory
help Print this message`+"\n\n", binName, binName)

if displayForMoreInfo {
usage += fmt.Sprintf(`Run "%s [action] --help" for more information about a particular command`, binName)
Expand Down Expand Up @@ -107,6 +111,7 @@ func scoreFiles(binName string, args []string) error {
ignoreTests := fs.StringSlice("ignore-test", []string{}, "Disable a test, can be set multiple times")
disableIgnoreChecksAnnotation := fs.Bool("disable-ignore-checks-annotations", false, "Set to true to disable the effect of the 'kube-score/ignore' annotations")
kubernetesVersion := fs.String("kubernetes-version", "v1.18", "Setting the kubernetes-version will affect the checks ran against the manifests. Set this to the version of Kubernetes that you're using in production for the best results.")
configFile := fs.String("config", ".kube-score.yml", "Optional kube-score configuration file")
setDefault(fs, binName, "score", false)

err := fs.Parse(args)
Expand Down Expand Up @@ -153,9 +158,23 @@ Use "-" as filename to read from STDIN.`, execName(binName))
allFilePointers = append(allFilePointers, namedReader{Reader: fp, name: filename})
}

// load kube-score.yml configuration file (if present)
cfg := loadConfigFile(*configFile)
excludeChks := excludeChecks(&cfg)
includeChks := includeChecks(&cfg)

fmt.Println("excludeChks <- ", excludeChks)
fmt.Println("includeChks <- ", includeChks)

*ignoreTests = append(*ignoreTests, excludeChks...)
*optionalTests = append(*optionalTests, includeChks...)

ignoredTests := listToStructMap(ignoreTests)
enabledOptionalTests := listToStructMap(optionalTests)

fmt.Println("ignoredTest <- ", ignoredTests)
fmt.Println("enabledOptionalTests <- ", optionalTests)

kubeVer, err := config.ParseSemver(*kubernetesVersion)
if err != nil {
return errors.New("Invalid --kubernetes-version. Use on format \"vN.NN\"")
Expand Down
113 changes: 113 additions & 0 deletions cmd/kube-score/object-checks-config-file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package main

import (
"fmt"
"os"

flag "github.com/spf13/pflag"
"github.com/zegl/kube-score/config"
"github.com/zegl/kube-score/parser"
"github.com/zegl/kube-score/score"
"gopkg.in/yaml.v3"
)

type kubescorechecks struct {
AddAllDefaultChecks bool `yaml:"addAllDefaultChecks"`
AddAllOptionalChecks bool `yaml:"addAllOptionalChecks"`
DisableIgnoreChecksAnnotations bool `yaml:"disableIgnoreChecksAnnotations"`
DefaultChecks []string `yaml:"defaultChecks"`
OptionalChecks []string `yaml:"optionalChecks"`
IncludeChecks []string `yaml:"include"`
ExcludeChecks []string `yaml:"exclude"`
}

func mkConfigFile(binName string, args []string) {
fs := flag.NewFlagSet(binName, flag.ExitOnError)
printHelp := fs.Bool("help", false, "Print help")
setDefault(fs, binName, "mkconfig", false)
cfgFile := fs.String("config", ".kube-score.yml", "Optional kube-score configuration file")
cfgForce := fs.Bool("force", false, "Force overwrite of existing .kube-score.yml file")

err := fs.Parse(args)

if err != nil {
panic("Failed to parse mkconfig arguments")
}

if *printHelp {
fs.Usage()
return
}

if _, err := os.Stat(*cfgFile); err == nil {
if !*cfgForce {
errmsg := fmt.Errorf("File %s exists. Use --force flag to overwrite\n", *cfgFile)
fmt.Println(errmsg)
fs.Usage()
return
}
}

allChecks := score.RegisterAllChecks(parser.Empty(), config.Configuration{})

var checks kubescorechecks

checks.AddAllDefaultChecks = true
checks.AddAllOptionalChecks = false
checks.DisableIgnoreChecksAnnotations = false

for _, c := range allChecks.All() {
if c.Optional {
checks.OptionalChecks = append(checks.OptionalChecks, c.ID)
} else {
checks.DefaultChecks = append(checks.DefaultChecks, c.ID)
}
}

if o, err := yaml.Marshal(&checks); err != nil {
err := fmt.Errorf("Failed to marshal checks")
fmt.Println(err.Error())
} else {
if err := os.WriteFile(*cfgFile, []byte(o), 0600); err != nil {
panic(err)
}
fmt.Println("Created kube-score configuration file ", *cfgFile)
}
}

func loadConfigFile(fp string) (config kubescorechecks) {

content, err := os.ReadFile(fp)

// if the file does not exist, create it
if err != nil {
mkConfigFile("mkconfig", []string{fp})
}

err2 := yaml.Unmarshal(content, &config)
if err2 != nil {
panic(err2)
}

return config
}

func includeChecks(k *kubescorechecks) (checks []string) {
if k.AddAllOptionalChecks {
checks = append(checks, k.OptionalChecks...)
}
if len(k.IncludeChecks) > 0 {
checks = append(checks, k.IncludeChecks...)
}
return
}

func excludeChecks(k *kubescorechecks) (checks []string) {
if !k.AddAllDefaultChecks {
checks = append(checks, k.DefaultChecks...)
}
if len(k.ExcludeChecks) > 0 {
checks = append(checks, k.ExcludeChecks...)
}
return
}
55 changes: 55 additions & 0 deletions cmd/kube-score/object-checks-config-file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestKubeScoreConfigExcludeAllDefaultChecks(t *testing.T) {

cfg := loadConfigFile("testdata/kube-score.yml")
cfg.AddAllDefaultChecks = false
excludeThese := excludeChecks(&cfg)

assert.Equal(t, len(excludeThese), len(cfg.DefaultChecks))
}

func TestKubeScoreConfigIncludeAllOptionalChecks(t *testing.T) {

cfg := loadConfigFile("testdata/kube-score.yml")
cfg.AddAllOptionalChecks = true
includeThese := includeChecks(&cfg)

assert.Equal(t, len(includeThese), len(cfg.OptionalChecks))
}

func TestKubeScoreConfigExcludeSelectDefaultChecks(t *testing.T) {

cfg := loadConfigFile("testdata/kube-score.yml")
cfg.AddAllDefaultChecks = true
cfg.ExcludeChecks = append(cfg.ExcludeChecks, "pod-probes")
excludeThese := excludeChecks(&cfg)

assert.Contains(t, cfg.ExcludeChecks, "pod-probes")
assert.Equal(t, len(excludeThese), 1)
}

func TestKubeScoreConfigNoDefaultChecksIncludeSelectChecks(t *testing.T) {

cfg := loadConfigFile("testdata/kube-score.yml")
cfg.AddAllDefaultChecks = false

onlyThese := []string{"container-resources", "image-tag", "image-pull-policy"}

cfg.IncludeChecks = append(cfg.IncludeChecks, onlyThese...)
includeThese := includeChecks(&cfg)

for _, v := range onlyThese {
assert.Contains(t, cfg.IncludeChecks, v)
}

assert.NotContains(t, cfg.IncludeChecks, "pod-networkpolicy")

assert.Equal(t, len(includeThese), len(onlyThese))
}
40 changes: 40 additions & 0 deletions cmd/kube-score/testdata/kube-score.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
addAllDefaultChecks: true
addAllOptionalChecks: false
disableIgnoreChecksAnnotations: false
defaultChecks:
- ingress-targets-service
- cronjob-has-deadline
- container-resources
- container-image-tag
- container-image-pull-policy
- container-ephemeral-storage-request-and-limit
- statefulset-has-poddisruptionbudget
- deployment-has-poddisruptionbudget
- poddisruptionbudget-has-policy
- pod-networkpolicy
- networkpolicy-targets-pod
- pod-probes
- container-security-context-user-group-id
- container-security-context-privileged
- container-security-context-readonlyrootfilesystem
- service-targets-pod
- service-type
- stable-version
- deployment-has-host-podantiaffinity
- statefulset-has-host-podantiaffinity
- deployment-targeted-by-hpa-does-not-have-replicas-configured
- statefulset-has-servicename
- deployment-pod-selector-labels-match-template-metadata-labels
- statefulset-pod-selector-labels-match-template-metadata-labels
- label-values
- horizontalpodautoscaler-has-target
- container-ephemeral-storage-requests-and-limits
optionalChecks:
- container-resource-requests-equal-limits
- container-cpu-requests-equal-limits
- container-memory-requests-equal-limits
- container-ephemeral-storage-request-equals-limit
- container-ports-check
- container-seccomp-profile
include: []
exclude: []

0 comments on commit b9ba777

Please sign in to comment.