-
-
Notifications
You must be signed in to change notification settings - Fork 178
/
scorecard.go
156 lines (130 loc) · 3.47 KB
/
scorecard.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package scorecard
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"strings"
ks "github.com/zegl/kube-score/domain"
)
const (
ignoredChecksAnnotation = "kube-score/ignore"
)
type Scorecard map[string]*ScoredObject
// New creates and initializes a new Scorecard
func New() Scorecard {
return make(Scorecard)
}
func (s Scorecard) NewObject(typeMeta metav1.TypeMeta, objectMeta metav1.ObjectMeta, useIgnoreChecksAnnotation bool) *ScoredObject {
o := &ScoredObject{
TypeMeta: typeMeta,
ObjectMeta: objectMeta,
Checks: make([]TestScore, 0),
}
// If this object already exists, return the previous version
if object, ok := s[o.resourceRefKey()]; ok {
return object
}
if useIgnoreChecksAnnotation {
o.setIgnoredTests()
}
s[o.resourceRefKey()] = o
return o
}
func (s Scorecard) AnyBelowOrEqualToGrade(threshold Grade) bool {
for _, o := range s {
if o.AnyBelowOrEqualToGrade(threshold) {
return true
}
}
return false
}
type ScoredObject struct {
TypeMeta metav1.TypeMeta
ObjectMeta metav1.ObjectMeta
FileLocation ks.FileLocation
Checks []TestScore
ignoredChecks map[string]struct{}
}
func (s ScoredObject) AnyBelowOrEqualToGrade(threshold Grade) bool {
for _, o := range s.Checks {
if o.Skipped == false && o.Grade <= threshold {
return true
}
}
return false
}
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 {
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, locationer ks.FileLocationer) {
ts.Check = check
so.FileLocation = locationer.FileLocation()
// This test is ignored (via annotations), don't save the score
if _, ok := so.ignoredChecks[check.ID]; ok {
ts.Skipped = true
ts.Comments = []TestScoreComment{{Summary: fmt.Sprintf("Skipped because %s is ignored", check.ID)}}
}
so.Checks = append(so.Checks, ts)
}
type TestScore struct {
Check ks.Check
Grade Grade
Skipped bool
Comments []TestScoreComment
}
type Grade int
const (
GradeCritical Grade = 1
GradeWarning Grade = 5
GradeAlmostOK Grade = 7
GradeAllOK Grade = 10
)
func (g Grade) String() string {
switch g {
case GradeCritical:
return "CRITICAL"
case GradeWarning:
return "WARNING"
case GradeAlmostOK, GradeAllOK:
return "OK"
default:
panic("Unknown grade")
}
}
type TestScoreComment struct {
Path string
Summary string
Description string
DocumentationURL string
}
func (ts *TestScore) AddComment(path, summary, description string) {
ts.Comments = append(ts.Comments, TestScoreComment{
Path: path,
Summary: summary,
Description: description,
})
}
func (ts *TestScore) AddCommentWithURL(path, summary, description, documentationURL string) {
ts.Comments = append(ts.Comments, TestScoreComment{
Path: path,
Summary: summary,
Description: description,
DocumentationURL: documentationURL,
})
}