Repository: zegl/kube-score
Pull request #86 (2019-01-12 19:42:33)

scorecard: simplify the relationship with the object meta and the checks


Patch:
diff --git a/cmd/kube-score/main.go b/cmd/kube-score/main.go
index 8da46e2..29ad244 100644
--- a/cmd/kube-score/main.go
+++ b/cmd/kube-score/main.go
@@ -162,19 +162,18 @@ Use "-" as filename to read from STDIN.`)
 	// Detect which output format we should use
 	humanOutput := *outputFormat == "human"
 
-	for _, resourceScores := range scoreCard.Scores {
+	for _, scoredObject := range scoreCard.Objects {
 		// Headers for each object
 		if humanOutput {
-			firstCard := resourceScores[0]
-			color.New(color.FgMagenta).Printf("%s/%s %s", firstCard.ResourceRef.Version, firstCard.ResourceRef.Kind, firstCard.ResourceRef.Name)
-			if firstCard.ResourceRef.Namespace != "" {
-				color.New(color.FgMagenta).Printf(" in %s\n", firstCard.ResourceRef.Namespace)
+			color.New(color.FgMagenta).Printf("%s/%s %s", scoredObject.TypeMeta.APIVersion, scoredObject.TypeMeta.Kind, scoredObject.ObjectMeta.Name)
+			if scoredObject.ObjectMeta.Namespace != "" {
+				color.New(color.FgMagenta).Printf(" in %s\n", scoredObject.ObjectMeta.Namespace)
 			} else {
 				fmt.Println()
 			}
 		}
 
-		for _, card := range resourceScores {
+		for _, card := range scoredObject.Checks {
 			if _, ok := ignoredTests[card.ID]; ok {
 				continue
 			}
@@ -226,7 +225,7 @@ Use "-" as filename to read from STDIN.`)
 
 					fmt.Printf("[%s] %s: %s\n",
 						status,
-						card.HumanFriendlyRef(),
+						scoredObject.HumanFriendlyRef(),
 						message,
 					)
 				}
diff --git a/score/score.go b/score/score.go
index 6c038d2..f928024 100644
--- a/score/score.go
+++ b/score/score.go
@@ -43,77 +43,70 @@ func Score(allObjects ks.AllTypes, cnf config.Configuration) (*scorecard.Scoreca
 	scoreCard := scorecard.New()
 
 	for _, ingress := range allObjects.Ingresses() {
+		o := scoreCard.NewObject(ingress.TypeMeta, ingress.ObjectMeta)
 		for _, test := range allChecks.Ingresses() {
-			score := test.Fn(ingress)
-			score.AddMeta(ingress.TypeMeta, ingress.ObjectMeta)
-			scoreCard.Add(score, test.Check)
+			o.Add(test.Fn(ingress), test.Check)
 		}
 	}
 
 	for _, meta := range allObjects.Metas() {
+		o := scoreCard.NewObject(meta.TypeMeta, meta.ObjectMeta)
 		for _, test := range allChecks.Metas() {
-			score := test.Fn(meta.TypeMeta)
-			score.AddMeta(meta.TypeMeta, meta.ObjectMeta)
-			scoreCard.Add(score, test.Check)
+			o.Add(test.Fn(meta.TypeMeta), test.Check)
 		}
 	}
 
 	for _, pod := range allObjects.Pods() {
+		o := scoreCard.NewObject(pod.TypeMeta, pod.ObjectMeta)
 		for _, test := range allChecks.Pods() {
 			score := test.Fn(corev1.PodTemplateSpec{
 				ObjectMeta: pod.ObjectMeta,
 				Spec:       pod.Spec,
 			})
-			score.AddMeta(pod.TypeMeta, pod.ObjectMeta)
-			scoreCard.Add(score, test.Check)
+			o.Add(score, test.Check)
 		}
 	}
 
 	for _, podspecer := range allObjects.PodSpeccers() {
+		o := scoreCard.NewObject(podspecer.GetTypeMeta(), podspecer.GetObjectMeta())
 		for _, test := range allChecks.Pods() {
 			score := test.Fn(podspecer.GetPodTemplateSpec())
-			score.AddMeta(podspecer.GetTypeMeta(), podspecer.GetObjectMeta())
-			scoreCard.Add(score, test.Check)
+			o.Add(score, test.Check)
 		}
 	}
 
 	for _, service := range allObjects.Services() {
+		o := scoreCard.NewObject(service.TypeMeta, service.ObjectMeta)
 		for _, test := range allChecks.Services() {
-			score := test.Fn(service)
-			score.AddMeta(service.TypeMeta, service.ObjectMeta)
-			scoreCard.Add(score, test.Check)
+			o.Add(test.Fn(service), test.Check)
 		}
 	}
 
 	for _, statefulset := range allObjects.StatefulSets() {
+		o := scoreCard.NewObject(statefulset.TypeMeta, statefulset.ObjectMeta)
 		for _, test := range allChecks.StatefulSets() {
-			score := test.Fn(statefulset)
-			score.AddMeta(statefulset.TypeMeta, statefulset.ObjectMeta)
-			scoreCard.Add(score, test.Check)
+			o.Add(test.Fn(statefulset), test.Check)
 		}
 	}
 
 	for _, deployment := range allObjects.Deployments() {
+		o := scoreCard.NewObject(deployment.TypeMeta, deployment.ObjectMeta)
 		for _, test := range allChecks.Deployments() {
-			score := test.Fn(deployment)
-			score.AddMeta(deployment.TypeMeta, deployment.ObjectMeta)
-			scoreCard.Add(score, test.Check)
+			o.Add(test.Fn(deployment), test.Check)
 		}
 	}
 
 	for _, netpol := range allObjects.NetworkPolicies() {
+		o := scoreCard.NewObject(netpol.TypeMeta, netpol.ObjectMeta)
 		for _, test := range allChecks.NetworkPolicies() {
-			score := test.Fn(netpol)
-			score.AddMeta(netpol.TypeMeta, netpol.ObjectMeta)
-			scoreCard.Add(score, test.Check)
+			o.Add(test.Fn(netpol), test.Check)
 		}
 	}
 
 	for _, cjob := range allObjects.CronJobs() {
+		o := scoreCard.NewObject(cjob.TypeMeta, cjob.ObjectMeta)
 		for _, test := range allChecks.CronJobs() {
-			score := test.Fn(cjob)
-			score.AddMeta(cjob.TypeMeta, cjob.ObjectMeta)
-			scoreCard.Add(score, test.Check)
+			o.Add(test.Fn(cjob), test.Check)
 		}
 	}
 
diff --git a/scorecard/scorecard.go b/scorecard/scorecard.go
index 4d629b2..6eec07f 100644
--- a/scorecard/scorecard.go
+++ b/scorecard/scorecard.go
@@ -6,40 +6,61 @@ import (
 )
 
 type Scorecard struct {
-	Scores map[string][]TestScore
+	Objects map[string]*ScoredObject
 }
 
 // New creates and initializes a new Scorecard
 func New() *Scorecard {
 	return &Scorecard{
-		Scores: make(map[string][]TestScore),
+		Objects: make(map[string]*ScoredObject),
 	}
 }
 
-// Add adds a TestScore to the Scorecard
-func (s *Scorecard) Add(ts TestScore, check ks.Check) {
-	ts.Check = check
+func (s *Scorecard) NewObject(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta) *ScoredObject {
+	o := &ScoredObject{
+		TypeMeta:   typeMeta,
+		ObjectMeta: objectMeta,
+		Checks:     make([]TestScore, 0),
+	}
 
-	if existingScores, ok := s.Scores[ts.resourceRefKey()]; ok {
-		existingScores = append(existingScores, ts)
-		s.Scores[ts.resourceRefKey()] = existingScores
-	} else {
-		s.Scores[ts.resourceRefKey()] = []TestScore{ts}
+	// If this object already exists, return the previous version
+	if object, ok := s.Objects[o.resourceRefKey()]; ok {
+		return object
 	}
+
+	s.Objects[o.resourceRefKey()] = o
+	return o
 }
 
-type TestScore struct {
-	ks.Check
+type ScoredObject struct {
+	TypeMeta   metav1.TypeMeta
+	ObjectMeta metav1.ObjectMeta
+	Checks     []TestScore
+}
 
-	ResourceRef struct {
-		Kind      string
-		Name      string
-		Namespace string
-		Version   string
+func (so ScoredObject) resourceRefKey() string {
+	return so.TypeMeta.Kind + "/" + so.TypeMeta.APIVersion + "/" + so.ObjectMeta.Namespace + "/" + so.ObjectMeta.Name
+}
+
+func (so ScoredObject) HumanFriendlyRef() string {
+	s := so.ObjectMeta.Name
+	if so.ObjectMeta.Namespace != "" {
+		s += "/" + so.ObjectMeta.Namespace
 	}
+	s += " " + so.TypeMeta.APIVersion + "/" + so.TypeMeta.Kind
+	return s
+}
+
+func (so *ScoredObject) Add(ts TestScore, check ks.Check) {
+	ts.Check = check
+	so.Checks = append(so.Checks, ts)
+}
 
-	Grade    Grade
-	Comments []TestScoreComment
+type TestScore struct {
+	ks.Check
+	Grade        Grade
+	Comments     []TestScoreComment
+	IgnoredTests []string
 }
 
 type Grade int
@@ -64,26 +85,3 @@ func (ts *TestScore) AddComment(path, summary, description string) {
 		Description: description,
 	})
 }
-
-func (ts *TestScore) AddMeta(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta) {
-	ts.ResourceRef.Name = objectMeta.Name
-	ts.ResourceRef.Namespace = objectMeta.Namespace
-	ts.ResourceRef.Kind = typeMeta.Kind
-	ts.ResourceRef.Version = typeMeta.APIVersion
-}
-
-func (ts TestScore) resourceRefKey() string {
-	return ts.ResourceRef.Kind + "/" + ts.ResourceRef.Namespace + "/" + ts.ResourceRef.Name
-}
-
-func (ts TestScore) HumanFriendlyRef() string {
-	s := ts.ResourceRef.Name
-
-	if ts.ResourceRef.Namespace != "" {
-		s += "/" + ts.ResourceRef.Namespace
-	}
-
-	s += " " + ts.ResourceRef.Version + "/" + ts.ResourceRef.Kind
-
-	return s
-}


Test patch:
diff --git a/score/score_test.go b/score/score_test.go
index 98cdf9c..0a1db62 100644
--- a/score/score_test.go
+++ b/score/score_test.go
@@ -25,8 +25,8 @@ func testExpectedScoreWithConfig(t *testing.T, config config.Configuration, file
 	sc, err := testScore(config, filename)
 	assert.NoError(t, err)
 
-	for _, objectScore := range sc.Scores {
-		for _, s := range objectScore {
+	for _, objectScore := range sc.Objects {
+		for _, s := range objectScore.Checks {
 			if s.Name == testcase {
 				assert.Equal(t, expectedScore, s.Grade)
 				return s.Comments