Repository: zegl/kube-score (go)

Issue:
Feature: disable annotation kube-score/ignore with option
**Which version of kube-score are you using?**

> kube-score version: 1.4.0

**What did you do?**

I want to cancel deployment on Kubernetes if my clients don't respect checks requirements with kubescore.

**What did you expect to see?**

But with annotation kube-score/ignore, my clients are able to deactivate some checks that are required for deployment.

**What did you see instead?**

It would be great if an option was able to disable annotation kube-score/ignore when we do checks analytics with kubescore before deployment.


Pull request:
cmd: allow disabling kube-score/ignore annotations
Via new --disable-ignore-checks-annotations flag

Fixes #202 

```
RELNOTE: Allow disabling `kube-score/ignore` annotation with a new `--disable-ignore-checks-annotations` flag
```


Patch:
diff --git a/README.md b/README.md
index 9e317b5..c514acf 100644
--- a/README.md
+++ b/README.md
@@ -72,14 +72,15 @@ Actions:
 	help	Print this message
 
 Flags for score:
-      --enable-optional-test strings    Enable an optional test, can be set multiple times
-      --exit-one-on-warning             Exit with code 1 in case of warnings
-      --help                            Print help
-      --ignore-container-cpu-limit      Disables the requirement of setting a container CPU limit
-      --ignore-container-memory-limit   Disables the requirement of setting a container memory limit
-      --ignore-test strings             Disable a test, can be set multiple times
-      --output-format string            Set to 'human', 'json' or 'ci'. If set to ci, kube-score will output the program in a format that is easier to parse by other programs. (default "human")
-  -v, --verbose count                   Enable verbose output, can be set multiple times for increased verbosity.
+      --disable-ignore-checks-annotations   Set to true to disable the effect of the 'kube-score/ignore' annotations
+      --enable-optional-test strings        Enable an optional test, can be set multiple times
+      --exit-one-on-warning                 Exit with code 1 in case of warnings
+      --help                                Print help
+      --ignore-container-cpu-limit          Disables the requirement of setting a container CPU limit
+      --ignore-container-memory-limit       Disables the requirement of setting a container memory limit
+      --ignore-test strings                 Disable a test, can be set multiple times
+  -o, --output-format string                Set to 'human', 'json' or 'ci'. If set to ci, kube-score will output the program in a format that is easier to parse by other programs. (default "human")
+  -v, --verbose count                       Enable verbose output, can be set multiple times for increased verbosity.
 ```
 
 ### Ignoring a test
diff --git a/cmd/kube-score/main.go b/cmd/kube-score/main.go
index 939dac6..cdda552 100644
--- a/cmd/kube-score/main.go
+++ b/cmd/kube-score/main.go
@@ -85,6 +85,7 @@ func scoreFiles() error {
 	outputFormat := fs.StringP("output-format", "o", "human", "Set to 'human', 'json' or 'ci'. If set to ci, kube-score will output the program in a format that is easier to parse by other programs.")
 	optionalTests := fs.StringSlice("enable-optional-test", []string{}, "Enable an optional test, can be set multiple times")
 	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")
 	setDefault(fs, "score", false)
 
 	err := fs.Parse(os.Args[2:])
@@ -139,6 +140,7 @@ Use "-" as filename to read from STDIN.`)
 		IgnoreContainerMemoryLimitRequirement: *ignoreContainerMemoryLimit,
 		IgnoredTests:                          ignoredTests,
 		EnabledOptionalTests:                  enabledOptionalTests,
+		UseIgnoreChecksAnnotation:             !*disableIgnoreChecksAnnotation,
 	}
 
 	parsedFiles, err := parser.ParseFiles(cnf)
diff --git a/config/config.go b/config/config.go
index 8b682c6..54f86d6 100644
--- a/config/config.go
+++ b/config/config.go
@@ -9,4 +9,5 @@ type Configuration struct {
 	IgnoreContainerMemoryLimitRequirement bool
 	IgnoredTests                          map[string]struct{}
 	EnabledOptionalTests                  map[string]struct{}
+	UseIgnoreChecksAnnotation             bool
 }
diff --git a/score/score.go b/score/score.go
index 5a8f595..64a62b4 100644
--- a/score/score.go
+++ b/score/score.go
@@ -19,6 +19,7 @@ import (
 	"github.com/zegl/kube-score/scorecard"
 
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func RegisterAllChecks(allObjects ks.AllTypes, cnf config.Configuration) *checks.Checks {
@@ -46,22 +47,26 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	allChecks := RegisterAllChecks(allObjects, cnf)
 	scoreCard := scorecard.New()
 
+	newObject := func(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta) *scorecard.ScoredObject {
+		return scoreCard.NewObject(typeMeta, objectMeta, cnf.UseIgnoreChecksAnnotation)
+	}
+
 	for _, ingress := range allObjects.Ingresses() {
-		o := scoreCard.NewObject(ingress.TypeMeta, ingress.ObjectMeta)
+		o := newObject(ingress.TypeMeta, ingress.ObjectMeta)
 		for _, test := range allChecks.Ingresses() {
 			o.Add(test.Fn(ingress), test.Check)
 		}
 	}
 
 	for _, meta := range allObjects.Metas() {
-		o := scoreCard.NewObject(meta.TypeMeta, meta.ObjectMeta)
+		o := newObject(meta.TypeMeta, meta.ObjectMeta)
 		for _, test := range allChecks.Metas() {
 			o.Add(test.Fn(meta), test.Check)
 		}
 	}
 
 	for _, pod := range allObjects.Pods() {
-		o := scoreCard.NewObject(pod.TypeMeta, pod.ObjectMeta)
+		o := newObject(pod.TypeMeta, pod.ObjectMeta)
 		for _, test := range allChecks.Pods() {
 			score := test.Fn(corev1.PodTemplateSpec{
 				ObjectMeta: pod.ObjectMeta,
@@ -72,7 +77,7 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	}
 
 	for _, podspecer := range allObjects.PodSpeccers() {
-		o := scoreCard.NewObject(podspecer.GetTypeMeta(), podspecer.GetObjectMeta())
+		o := newObject(podspecer.GetTypeMeta(), podspecer.GetObjectMeta())
 		for _, test := range allChecks.Pods() {
 			score := test.Fn(podspecer.GetPodTemplateSpec(), podspecer.GetTypeMeta())
 			o.Add(score, test.Check)
@@ -80,14 +85,14 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	}
 
 	for _, service := range allObjects.Services() {
-		o := scoreCard.NewObject(service.TypeMeta, service.ObjectMeta)
+		o := newObject(service.TypeMeta, service.ObjectMeta)
 		for _, test := range allChecks.Services() {
 			o.Add(test.Fn(service), test.Check)
 		}
 	}
 
 	for _, statefulset := range allObjects.StatefulSets() {
-		o := scoreCard.NewObject(statefulset.TypeMeta, statefulset.ObjectMeta)
+		o := newObject(statefulset.TypeMeta, statefulset.ObjectMeta)
 		for _, test := range allChecks.StatefulSets() {
 			res, err := test.Fn(statefulset)
 			if err != nil {
@@ -98,7 +103,7 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	}
 
 	for _, deployment := range allObjects.Deployments() {
-		o := scoreCard.NewObject(deployment.TypeMeta, deployment.ObjectMeta)
+		o := newObject(deployment.TypeMeta, deployment.ObjectMeta)
 		for _, test := range allChecks.Deployments() {
 			res, err := test.Fn(deployment)
 			if err != nil {
@@ -109,21 +114,21 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	}
 
 	for _, netpol := range allObjects.NetworkPolicies() {
-		o := scoreCard.NewObject(netpol.TypeMeta, netpol.ObjectMeta)
+		o := newObject(netpol.TypeMeta, netpol.ObjectMeta)
 		for _, test := range allChecks.NetworkPolicies() {
 			o.Add(test.Fn(netpol), test.Check)
 		}
 	}
 
 	for _, cjob := range allObjects.CronJobs() {
-		o := scoreCard.NewObject(cjob.TypeMeta, cjob.ObjectMeta)
+		o := newObject(cjob.TypeMeta, cjob.ObjectMeta)
 		for _, test := range allChecks.CronJobs() {
 			o.Add(test.Fn(cjob), test.Check)
 		}
 	}
 
 	for _, hpa := range allObjects.HorizontalPodAutoscalers() {
-		o := scoreCard.NewObject(hpa.TypeMeta, hpa.ObjectMeta)
+		o := newObject(hpa.TypeMeta, hpa.ObjectMeta)
 		for _, test := range allChecks.HorizontalPodAutoscalers() {
 			o.Add(test.Fn(hpa), test.Check)
 		}
diff --git a/scorecard/scorecard.go b/scorecard/scorecard.go
index c0bc04c..5b58a0b 100644
--- a/scorecard/scorecard.go
+++ b/scorecard/scorecard.go
@@ -19,7 +19,7 @@ func New() Scorecard {
 	return make(Scorecard)
 }
 
-func (s Scorecard) NewObject(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta) *ScoredObject {
+func (s Scorecard) NewObject(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta, useIgnoreChecksAnnotation bool) *ScoredObject {
 	o := &ScoredObject{
 		TypeMeta:   typeMeta,
 		ObjectMeta: objectMeta,
@@ -31,7 +31,9 @@ func (s Scorecard) NewObject(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectM
 		return object
 	}
 
-	o.setIgnoredTests()
+	if useIgnoreChecksAnnotation {
+		o.setIgnoredTests()
+	}
 
 	s[o.resourceRefKey()] = o
 	return o


Test patch:
diff --git a/score/score_test.go b/score/score_test.go
index 1c0b243..4d8fc4e 100644
--- a/score/score_test.go
+++ b/score/score_test.go
@@ -165,8 +165,9 @@ func TestConfigMapMultiDash(t *testing.T) {
 func TestAnnotationIgnore(t *testing.T) {
 	t.Parallel()
 	s, err := testScore(config.Configuration{
-		VerboseOutput: 0,
-		AllFiles:      []io.Reader{testFile("ignore-annotation-service.yaml")},
+		VerboseOutput:             0,
+		AllFiles:                  []io.Reader{testFile("ignore-annotation-service.yaml")},
+		UseIgnoreChecksAnnotation: true,
 	})
 	assert.Nil(t, err)
 	assert.Len(t, s, 1)
@@ -185,6 +186,31 @@ func TestAnnotationIgnore(t *testing.T) {
 	assert.True(t, tested)
 }
 
+func TestAnnotationIgnoreDisabled(t *testing.T) {
+	t.Parallel()
+	s, err := testScore(config.Configuration{
+		VerboseOutput:             0,
+		AllFiles:                  []io.Reader{testFile("ignore-annotation-service.yaml")},
+		UseIgnoreChecksAnnotation: false,
+	})
+	assert.Nil(t, err)
+	assert.Len(t, s, 1)
+
+	tested := false
+
+	for _, o := range s {
+		for _, c := range o.Checks {
+			if c.Check.ID == "service-type" {
+				assert.False(t, c.Skipped)
+				assert.Equal(t, scorecard.GradeWarning, c.Grade)
+				tested = true
+			}
+		}
+		assert.Equal(t, "node-port-service-with-ignore", o.ObjectMeta.Name)
+	}
+	assert.True(t, tested)
+}
+
 func TestList(t *testing.T) {
 	t.Parallel()
 	s, err := testScore(config.Configuration{