Repository: zegl/kube-score
Pull request #87 (2019-01-12 20:06:34)

score: per object test ignores with the kube-score/ignore annotation


Patch:
diff --git a/README.md b/README.md
index 24e0fac..08e8b19 100644
--- a/README.md
+++ b/README.md
@@ -78,3 +78,32 @@ Flags for score:
       --threshold-warning int        The score threshold for treating a score as WARNING. Grades below this threshold are CRITICAL. Must be between 1 and 10 (inclusive). (default 5)
       --v                            Verbose output
 ```
+
+### Ignoring a test
+
+Tests can be ignored in the whole run of the program, with the `--ignore-test` flag.
+
+A test can also be ignored on a per-object basis, by adding the annotation `kube-score/ignore` to the object.
+The value should be a comma separated string of the [test IDs](README_CHECKS.md).
+
+Example:
+
+Testing this object will temporarily disable the `service-type` test, which warns against using services of type NodePort.
+
+```yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: node-port-service-with-ignore
+  namespace: foospace
+  annotations:
+    kube-score/ignore: service-type
+spec:
+  selector:
+    app: my-app
+  ports:
+  - protocol: TCP
+    port: 80
+    targetPort: 8080
+  type: NodePort
+```
diff --git a/scorecard/scorecard.go b/scorecard/scorecard.go
index 6eec07f..61ea8cd 100644
--- a/scorecard/scorecard.go
+++ b/scorecard/scorecard.go
@@ -3,6 +3,11 @@ package scorecard
 import (
 	ks "github.com/zegl/kube-score"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"strings"
+)
+
+const (
+	ignoredChecksAnnotation = "kube-score/ignore"
 )
 
 type Scorecard struct {
@@ -28,6 +33,8 @@ func (s *Scorecard) NewObject(typeMeta metav1.TypeMeta, objectMeta metav1.Object
 		return object
 	}
 
+	o.setIgnoredTests()
+
 	s.Objects[o.resourceRefKey()] = o
 	return o
 }
@@ -36,6 +43,18 @@ type ScoredObject struct {
 	TypeMeta   metav1.TypeMeta
 	ObjectMeta metav1.ObjectMeta
 	Checks     []TestScore
+
+	ignoredChecks map[string]struct{}
+}
+
+func (so *ScoredObject) setIgnoredTests() {
+	ignoredMap := make(map[string]struct{})
+	if ignoredCSV, ok := so.ObjectMeta.Annotations[ignoredChecksAnnotation]; ok {
+		for _, ignored := range strings.Split(ignoredCSV, ",") {
+			ignoredMap[strings.TrimSpace(ignored)] = struct{}{}
+		}
+	}
+	so.ignoredChecks = ignoredMap
 }
 
 func (so ScoredObject) resourceRefKey() string {
@@ -52,6 +71,11 @@ func (so ScoredObject) HumanFriendlyRef() string {
 }
 
 func (so *ScoredObject) Add(ts TestScore, check ks.Check) {
+	// This test is ignored, don't save it
+	if _, ok := so.ignoredChecks[check.ID]; ok {
+		return
+	}
+
 	ts.Check = check
 	so.Checks = append(so.Checks, ts)
 }


Test patch:
diff --git a/score/score_test.go b/score/score_test.go
index 0a1db62..d667597 100644
--- a/score/score_test.go
+++ b/score/score_test.go
@@ -133,3 +133,18 @@ func TestConfigMapMultiDash(t *testing.T) {
 	_, err := testScore(config.Configuration{}, "configmap-multi-dash.yaml")
 	assert.Nil(t, err)
 }
+
+func TestAnnotationIgnore(t *testing.T) {
+	s, err := testScore(config.Configuration{}, "ignore-annotation-service.yaml")
+	assert.Nil(t, err)
+	assert.Len(t, s.Objects, 1)
+
+	for _, o := range s.Objects {
+		for _, c := range o.Checks {
+			if c.ID == "service-type" {
+				t.Error("service-type was tested")
+			}
+		}
+		assert.Equal(t, "node-port-service-with-ignore", o.ObjectMeta.Name)
+	}
+}
diff --git a/score/testdata/ignore-annotation-service.yaml b/score/testdata/ignore-annotation-service.yaml
new file mode 100644
index 0000000..866bb10
--- /dev/null
+++ b/score/testdata/ignore-annotation-service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: node-port-service-with-ignore
+  namespace: foospace
+  annotations:
+    kube-score/ignore: service-type
+spec:
+  selector:
+    app: my-app
+  ports:
+  - protocol: TCP
+    port: 80
+    targetPort: 8080
+  type: NodePort