-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathlinter.go
405 lines (371 loc) · 17.2 KB
/
linter.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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
package kubelint
import (
"fmt"
"io/ioutil"
log "github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
batchV1 "k8s.io/api/batch/v1"
batchV1beta1 "k8s.io/api/batch/v1beta1"
v1 "k8s.io/api/core/v1"
v1beta1Extensions "k8s.io/api/extensions/v1beta1"
networkingV1 "k8s.io/api/networking/v1"
rbacV1 "k8s.io/api/rbac/v1"
rbacV1beta1 "k8s.io/api/rbac/v1beta1"
"os"
)
// Linter: This Linter represents something you can pass in resources to
// and get results out of that you can eventually log.
// Also some utility methods for input handling.
type Linter struct {
logger *log.Logger
appsV1DeploymentRules []*AppsV1DeploymentRule // a register for all user-defined appsV1Deployment rules
v1NamespaceRules []*V1NamespaceRule // a register for all user-defined v1Namespace rules
v1PodSpecRules []*V1PodSpecRule // a register for all user-defined v1PodSpec rules
v1ContainerRules []*V1ContainerRule // a register for all user-defined v1Container rules
v1PersistentVolumeClaimRules []*V1PersistentVolumeClaimRule // a register for all user-defined v1PersistentVolumeClaim rules
v1Beta1ExtensionsDeploymentRules []*V1Beta1ExtensionsDeploymentRule // a register for all user-defined v1Beta1ExtensionsDeployment rules
batchV1JobRules []*BatchV1JobRule // a register for all user-defined batchV1Job rules
batchV1Beta1CronJobRules []*BatchV1Beta1CronJobRule // a register for all user-defined batchV1Beta1CronJob rules
v1Beta1ExtensionsIngressRules []*V1Beta1ExtensionsIngressRule // a register for all user-defined v1Beta1ExtensionsIngress rules
networkingV1NetworkPolicyRules []*NetworkingV1NetworkPolicyRule // a register for all user-defined networkingV1NetworkPolicy rules
v1Beta1ExtensionsNetworkPolicyRules []*V1Beta1ExtensionsNetworkPolicyRule // a register for all user-defined v1Beta1ExtensionsNetworkPolicy rules
rbacV1RoleRules []*RbacV1RoleRule // a register for all user-defined rbacV1Role rules
rbacV1Beta1RoleBindingRules []*RbacV1Beta1RoleBindingRule // a register for all user-defined rbacV1Beta1RoleBinding rules
v1ServiceAccountRules []*V1ServiceAccountRule // a register for all user-defined v1ServiceAccount rules
v1ServiceRules []*V1ServiceRule // a register for all user-defined v1Service rules
genericRules []*GenericRule // a register for all user-defined Generic rules (applied to every object)
interdependentRules []*InterdependentRule // a register for all user-defined Interdependent rules (applied to the system as a whole)
fixes []*ruleSorter // fixes that should be applied to the resources in order to mitigate some errors on a future pass
interdependentFixes []*interdependentRule
resources []*Resource // All the resources that have been read in by this linter
}
// NewDefaultLinter returns a linter with absolutely no rules.
func NewDefaultLinter() *Linter {
return &Linter{
logger: &log.Logger{
Out: ioutil.Discard,
},
}
}
// NewLinter sets the logger passed in and makes it possible to turn on debug statements to trace the execution of the linter, in case there are any problems on the client side. I recommend setting a logrus logger to DebugLevel to see
// the execution trace.
func NewLinter(l *log.Logger) *Linter {
return &Linter{logger: l}
}
// Lint opens and lints the files and produces results that
// can be logged later on
func (l *Linter) Lint(filepaths ...string) ([]*Result, []error) {
l.logger.Debugf("Linting files: %#v\n", filepaths)
var errors []error
resources, errs := Read(filepaths...)
for _, resource := range resources {
l.resources = append(l.resources, &resource.Resource)
}
errors = append(errors, errs...)
var results []*Result
// add interdependent checks
results = append(results, l.lintResources(resources)...)
for _, resource := range resources {
r, err := l.LintResource(resource)
l.logger.Debugln("results from linting", resource.Filepath, r)
results = append(results, r...)
if err != nil {
l.logger.Debugln("Error from LintResource: ", err)
errors = append(errors, err)
}
}
return results, errors
}
// LintBytes takes a slice of bytes to lint and a filepath and
// returns a list of Results and errors to report or log later on
func (l *Linter) LintBytes(data []byte, filepath string) ([]*Result, []error) {
resources, errors := ReadBytes(data, filepath)
for _, resource := range resources {
l.resources = append(l.resources, &resource.Resource)
}
var results []*Result
// add interdependent checks
results = append(results, l.lintResources(resources)...)
for _, resource := range resources {
r, err := l.LintResource(resource)
results = append(results, r...)
if err != nil {
errors = append(errors, err)
}
}
return results, errors
}
// LintFile takes a file pointer and returns a list of Reults and Errors
// to be logged or reported later on
func (l *Linter) LintFile(file *os.File) ([]*Result, []error) {
resources, errors := ReadFile(file)
for _, resource := range resources {
l.resources = append(l.resources, &resource.Resource)
}
var results []*Result
// add interdependent checks
results = append(results, l.lintResources(resources)...)
for _, resource := range resources {
r, err := l.LintResource(resource)
results = append(results, r...)
if err != nil {
errors = append(errors, err)
}
}
return results, errors
}
// lintResources takes a list of Yaml Derived Resources, applying interdependent rules ONLY
// and returns a list of Results
// to be logged or reported
func (l *Linter) lintResources(resources []*YamlDerivedResource) []*Result {
var results []*Result
rules := l.createInterdependentRules(resources)
for _, rule := range rules {
if !rule.Condition() {
results = append(results, &Result{
Resources: rule.Resources,
Message: rule.Message,
Level: rule.Level,
})
l.interdependentFixes = append(l.interdependentFixes, rule)
}
}
return results
}
// LintResource takes a yaml derived resource and returns a list of results and errors
// to be logged or reported
func (l *Linter) LintResource(resource *YamlDerivedResource) ([]*Result, error) {
var results []*Result
rules, err := l.createRules(resource)
l.logger.Debugln(len(rules), "rules created for", resource.Filepath)
// log rules and their dependent rules
for _, rule := range rules {
l.logger.Debugf("Rule ID: %s\n\tPrereqs: %#v\n", rule.ID, rule.Prereqs)
}
ruleSorter := newRuleSorter(rules)
fixSorter := ruleSorter.clone()
l.fixes = append(l.fixes, fixSorter)
for !ruleSorter.isEmpty() {
rule := ruleSorter.popNextAvailable()
l.logger.Debugln("Testing rule", rule.ID)
if !rule.Condition() {
l.logger.Debugln("Rule failed")
results = append(results, &Result{
Resources: []*YamlDerivedResource{resource},
Message: rule.Message,
Level: rule.Level,
})
l.logger.Debugf("Adding result: %#v\n", results[len(results)-1])
dependentRules := ruleSorter.popDependentRules(rule.ID)
l.logger.Debugf("Dependent rules:\n")
for _, rule := range dependentRules {
l.logger.Debugln(rule.ID)
}
for _, dependentRule := range dependentRules {
results = append(results, &Result{
Resources: []*YamlDerivedResource{resource},
Message: dependentRule.Message,
Level: dependentRule.Level,
})
}
} else {
// this doesn't need to be fixed, so remove it from the fixSorter
fixSorter.remove(rule.ID)
}
}
return results, err
}
// ApplyFixes applies all fixes that were registered as necessary during the lint phase.
// The references to all the objects are kept in the Resources array so it will be reflected there.
func (l *Linter) ApplyFixes() ([]*Resource, []string) {
var appliedFixDescriptions []string
for _, sorter := range l.fixes {
for !sorter.isEmpty() {
rule := sorter.popNextAvailable()
fixed := rule.Fix()
if !fixed {
_ = sorter.popDependentRules(rule.ID)
} else {
appliedFixDescriptions = append(appliedFixDescriptions, rule.FixDescription())
}
}
}
for _, rule := range l.interdependentFixes {
fixed := rule.Fix()
if fixed {
appliedFixDescriptions = append(appliedFixDescriptions, rule.FixDescription())
}
}
return l.resources, appliedFixDescriptions
}
// createInterdependentRules finds the registered interdependent rules and transforms them
// to generic rules by applying the ydrs parameter.
func (l *Linter) createInterdependentRules(ydrs []*YamlDerivedResource) []*interdependentRule {
var rules []*interdependentRule
for _, interdependentRule := range l.interdependentRules {
rules = append(rules, interdependentRule.createRule(ydrs))
}
return rules
}
// createRules finds the type-appropriate rules that are registered in the linter
// and transforms them to generic rules by applying the resource parameter.
// Then the list of rules are returned. I think I put it into a ruleSorter later on.
func (l *Linter) createRules(ydr *YamlDerivedResource) ([]*rule, error) {
var rules []*rule
resource := &ydr.Resource
// generic rules always need to be added
for _, genericRule := range l.genericRules {
rules = append(rules, genericRule.createRule(resource, ydr))
}
// append type-specific rules
switch concrete := resource.Object.(type) {
case *appsv1.Deployment:
for _, deploymentRule := range l.appsV1DeploymentRules {
rules = append(rules, deploymentRule.createRule(concrete, ydr))
}
for _, podSpecRule := range l.v1PodSpecRules {
rules = append(rules, podSpecRule.createRule(&concrete.Spec.Template.Spec, ydr))
}
for _, v1ContainerRule := range l.v1ContainerRules {
for i, _ := range concrete.Spec.Template.Spec.Containers {
rules = append(rules, v1ContainerRule.createRule(&concrete.Spec.Template.Spec.Containers[i], ydr))
}
}
case *v1.Namespace:
for _, v1NamespaceRule := range l.v1NamespaceRules {
rules = append(rules, v1NamespaceRule.createRule(concrete, ydr))
}
case *v1.PersistentVolumeClaim:
for _, v1PersistentVolumeClaimRule := range l.v1PersistentVolumeClaimRules {
rules = append(rules, v1PersistentVolumeClaimRule.createRule(concrete, ydr))
}
case *v1beta1Extensions.Deployment:
for _, v1Beta1ExtensionsDeploymentRule := range l.v1Beta1ExtensionsDeploymentRules {
rules = append(rules, v1Beta1ExtensionsDeploymentRule.createRule(concrete, ydr))
}
case *batchV1.Job:
for _, batchV1JobRule := range l.batchV1JobRules {
rules = append(rules, batchV1JobRule.createRule(concrete, ydr))
}
case *batchV1beta1.CronJob:
for _, batchV1Beta1CronJobRule := range l.batchV1Beta1CronJobRules {
rules = append(rules, batchV1Beta1CronJobRule.createRule(concrete, ydr))
}
case *v1beta1Extensions.Ingress:
for _, v1Beta1ExtensionsIngressRule := range l.v1Beta1ExtensionsIngressRules {
rules = append(rules, v1Beta1ExtensionsIngressRule.createRule(concrete, ydr))
}
case *networkingV1.NetworkPolicy:
for _, networkingV1NetworkPolicyRule := range l.networkingV1NetworkPolicyRules {
rules = append(rules, networkingV1NetworkPolicyRule.createRule(concrete, ydr))
}
case *v1beta1Extensions.NetworkPolicy:
for _, v1Beta1ExtensionsNetworkPolicyRule := range l.v1Beta1ExtensionsNetworkPolicyRules {
rules = append(rules, v1Beta1ExtensionsNetworkPolicyRule.createRule(concrete, ydr))
}
case *rbacV1.Role:
for _, rbacV1RoleRule := range l.rbacV1RoleRules {
rules = append(rules, rbacV1RoleRule.createRule(concrete, ydr))
}
case *rbacV1beta1.RoleBinding:
for _, rbacV1Beta1RoleBindingRule := range l.rbacV1Beta1RoleBindingRules {
rules = append(rules, rbacV1Beta1RoleBindingRule.createRule(concrete, ydr))
}
case *v1.ServiceAccount:
for _, v1ServiceAccountRule := range l.v1ServiceAccountRules {
rules = append(rules, v1ServiceAccountRule.createRule(concrete, ydr))
}
case *v1.Service:
for _, v1ServiceRule := range l.v1ServiceRules {
rules = append(rules, v1ServiceRule.createRule(concrete, ydr))
}
default:
return nil, fmt.Errorf("Resources of type %T have not been considered by the linter", concrete)
}
return rules, nil
}
// AddAppsV1DeploymentRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddAppsV1DeploymentRule(rules ...*AppsV1DeploymentRule) {
l.appsV1DeploymentRules = append(l.appsV1DeploymentRules, rules...)
}
// AddV1NamespaceRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddV1NamespaceRule(rules ...*V1NamespaceRule) {
l.v1NamespaceRules = append(l.v1NamespaceRules, rules...)
}
// AddV1PodSpecRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddV1PodSpecRule(rules ...*V1PodSpecRule) {
l.v1PodSpecRules = append(l.v1PodSpecRules, rules...)
}
// AddV1ContainerRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddV1ContainerRule(rules ...*V1ContainerRule) {
l.v1ContainerRules = append(l.v1ContainerRules, rules...)
}
// AddV1PersistentVolumeClaimRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddV1PersistentVolumeClaimRule(rules ...*V1PersistentVolumeClaimRule) {
l.v1PersistentVolumeClaimRules = append(l.v1PersistentVolumeClaimRules, rules...)
}
// AddV1Beta1ExtensionsDeploymentRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddV1Beta1ExtensionsDeploymentRule(rules ...*V1Beta1ExtensionsDeploymentRule) {
l.v1Beta1ExtensionsDeploymentRules = append(l.v1Beta1ExtensionsDeploymentRules, rules...)
}
// AddBatchV1JobRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddBatchV1JobRule(rules ...*BatchV1JobRule) {
l.batchV1JobRules = append(l.batchV1JobRules, rules...)
}
// AddBatchV1Beta1CronJobRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddBatchV1Beta1CronJobRule(rules ...*BatchV1Beta1CronJobRule) {
l.batchV1Beta1CronJobRules = append(l.batchV1Beta1CronJobRules, rules...)
}
// AddV1Beta1ExtensionsIngressRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddV1Beta1ExtensionsIngressRule(rules ...*V1Beta1ExtensionsIngressRule) {
l.v1Beta1ExtensionsIngressRules = append(l.v1Beta1ExtensionsIngressRules, rules...)
}
// AddNetworkingV1NetworkPolicyRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddNetworkingV1NetworkPolicyRule(rules ...*NetworkingV1NetworkPolicyRule) {
l.networkingV1NetworkPolicyRules = append(l.networkingV1NetworkPolicyRules, rules...)
}
// AddV1Beta1ExtensionsNetworkPolicyRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddV1Beta1ExtensionsNetworkPolicyRule(rules ...*V1Beta1ExtensionsNetworkPolicyRule) {
l.v1Beta1ExtensionsNetworkPolicyRules = append(l.v1Beta1ExtensionsNetworkPolicyRules, rules...)
}
// AddRbacV1RoleRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddRbacV1RoleRule(rules ...*RbacV1RoleRule) {
l.rbacV1RoleRules = append(l.rbacV1RoleRules, rules...)
}
// AddRbacV1Beta1RoleBindingRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddRbacV1Beta1RoleBindingRule(rules ...*RbacV1Beta1RoleBindingRule) {
l.rbacV1Beta1RoleBindingRules = append(l.rbacV1Beta1RoleBindingRules, rules...)
}
// AddV1ServiceAccountRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddV1ServiceAccountRule(rules ...*V1ServiceAccountRule) {
l.v1ServiceAccountRules = append(l.v1ServiceAccountRules, rules...)
}
// AddV1ServiceRule adds a custom rule (or many) so that anything sent through the linter of the correct type
// has this rule applied to it.
func (l *Linter) AddV1ServiceRule(rules ...*V1ServiceRule) {
l.v1ServiceRules = append(l.v1ServiceRules, rules...)
}
// AddGenericRule adds a custom rule (or many) so that anything sent through the linter
// has this rule applied to it.
func (l *Linter) AddGenericRule(rules ...*GenericRule) {
l.genericRules = append(l.genericRules, rules...)
}
// AddInterdependentRule adds a custom rule (or many) so that anything sent through the linter
// has this rule applied to it.
func (l *Linter) AddInterdependentRule(rules ...*InterdependentRule) {
l.interdependentRules = append(l.interdependentRules, rules...)
}