Skip to content

Commit 2d4ab69

Browse files
Update tests
1 parent b8c2245 commit 2d4ab69

File tree

8 files changed

+139
-33
lines changed

8 files changed

+139
-33
lines changed

cmd/kubeapply/subcmd/validate.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,16 @@ func execValidation(ctx context.Context, clusterConfig *config.ClusterConfig) er
112112
return err
113113
}
114114

115-
policies, err := validation.DefaultPoliciesFromGlobs(ctx, validateFlagValues.policies)
115+
policies, err := validation.DefaultPoliciesFromGlobs(
116+
ctx,
117+
validateFlagValues.policies,
118+
map[string]interface{}{
119+
// TODO: Add more parameters here (or entire config)?
120+
"cluster": clusterConfig.Cluster,
121+
"region": clusterConfig.Region,
122+
"env": clusterConfig.Env,
123+
},
124+
)
116125
if err != nil {
117126
return err
118127
}
@@ -129,33 +138,37 @@ func execValidation(ctx context.Context, clusterConfig *config.ClusterConfig) er
129138
},
130139
)
131140

132-
log.Infof("Running kubeconform on configs in %+v", clusterConfig.AbsSubpaths())
141+
log.Infof("Running validator on configs in %+v", clusterConfig.AbsSubpaths())
133142
results, err := validator.RunChecks(ctx, clusterConfig.AbsSubpaths()[0])
134143
if err != nil {
135144
return err
136145
}
137146

138-
numInvalidResources := 0
147+
numInvalidResourceChecks := 0
148+
numValidResourceChecks := 0
149+
numSkippedResourceChecks := 0
139150

140151
for _, result := range results {
141152
for _, checkResult := range result.CheckResults {
142153
switch checkResult.Status {
143154
case validation.StatusValid:
155+
numValidResourceChecks++
144156
log.Debugf(
145157
"Resource %s in file %s OK according to check %s",
146158
result.Resource.PrettyName(),
147159
result.Resource.Path,
148160
checkResult.CheckName,
149161
)
150162
case validation.StatusSkipped:
163+
numSkippedResourceChecks++
151164
log.Debugf(
152165
"Resource %s in file %s was skipped by check %s",
153166
result.Resource.PrettyName(),
154167
result.Resource.Path,
155168
checkResult.CheckName,
156169
)
157170
case validation.StatusError:
158-
numInvalidResources++
171+
numInvalidResourceChecks++
159172
log.Errorf(
160173
"Resource %s in file %s could not be processed by check %s: %s",
161174
result.Resource.PrettyName(),
@@ -164,7 +177,7 @@ func execValidation(ctx context.Context, clusterConfig *config.ClusterConfig) er
164177
checkResult.Message,
165178
)
166179
case validation.StatusInvalid:
167-
numInvalidResources++
180+
numInvalidResourceChecks++
168181
log.Errorf(
169182
"Resource %s in file %s is invalid according to check %s: %s",
170183
result.Resource.PrettyName(),
@@ -179,13 +192,21 @@ func execValidation(ctx context.Context, clusterConfig *config.ClusterConfig) er
179192
}
180193
}
181194

182-
if numInvalidResources > 0 {
195+
if numInvalidResourceChecks > 0 {
183196
return fmt.Errorf(
184-
"Validation failed for %d resources",
185-
numInvalidResources,
197+
"Validation failed for %d resources in cluster %s (%d checks valid, %d skipped)",
198+
numInvalidResourceChecks,
199+
clusterConfig.DescriptiveName(),
200+
numValidResourceChecks,
201+
numSkippedResourceChecks,
186202
)
187203
}
188204

189-
log.Infof("Validation of cluster %s passed", clusterConfig.DescriptiveName())
205+
log.Infof(
206+
"Validation of cluster %s passed (%d checks valid, %d skipped)",
207+
clusterConfig.DescriptiveName(),
208+
numValidResourceChecks,
209+
numSkippedResourceChecks,
210+
)
190211
return nil
191212
}

pkg/util/headers.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import (
99
)
1010

1111
var (
12-
headerComment = []byte("# Generated by \"kubeapply expand\". DO NOT EDIT.\n")
12+
HeaderComment = []byte("# Generated by \"kubeapply expand\". DO NOT EDIT.\n")
13+
HeaderCommentStr = string(HeaderComment)
1314
)
1415

1516
// AddHeaders adds a comment header to all yaml files in the argument path.
@@ -30,11 +31,11 @@ func AddHeaders(root string) error {
3031
return err
3132
}
3233

33-
if bytes.HasPrefix(fileBytes, headerComment) {
34+
if bytes.HasPrefix(fileBytes, HeaderComment) {
3435
return nil
3536
}
3637

37-
fileBytes = append(headerComment, fileBytes...)
38+
fileBytes = append(HeaderComment, fileBytes...)
3839

3940
return ioutil.WriteFile(path, fileBytes, info.Mode().Perm())
4041
},

pkg/validation/kubeconform.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ import (
66
"github.com/yannh/kubeconform/pkg/validator"
77
)
88

9+
// KubeconformChecker is a Checker implementation that runs kubeconform over all Kubernetes
10+
// resources.
911
type KubeconformChecker struct {
1012
validatorObj validator.Validator
1113
}
1214

1315
var _ Checker = (*KubeconformChecker)(nil)
1416

17+
// NewKubeconformChecker creates a new KubeconformChecker instance.
1518
func NewKubeconformChecker() (*KubeconformChecker, error) {
1619
validatorObj, err := validator.New(
1720
nil,
@@ -29,6 +32,7 @@ func NewKubeconformChecker() (*KubeconformChecker, error) {
2932
}, nil
3033
}
3134

35+
// Check runs Kubeconform over the argument resource.
3236
func (k *KubeconformChecker) Check(_ context.Context, resource Resource) CheckResult {
3337
kResult := k.validatorObj.ValidateResource(resource.TokResource())
3438

pkg/validation/policy.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,31 @@ const (
1818

1919
// Policy wraps a policy module and a prepared query.
2020
type PolicyChecker struct {
21-
Module PolicyModule
22-
Query rego.PreparedEvalQuery
21+
Module PolicyModule
22+
Query rego.PreparedEvalQuery
23+
ExtraFields map[string]interface{}
2324
}
2425

2526
var _ Checker = (*PolicyChecker)(nil)
2627

2728
// PolicyModule contains information about a policy.
2829
type PolicyModule struct {
29-
Name string
30-
Contents string
31-
Package string
32-
Result string
30+
Name string
31+
32+
// Contents is a string that stores the policy in rego format.
33+
Contents string
34+
35+
// Package is the name of the package in the rego contents.
36+
Package string
37+
38+
// Result is the variable that should be accessed to get the evaluation results.
39+
Result string
40+
41+
// ExtraFields are added into the input and usable for policy evaluation.
3342
ExtraFields map[string]interface{}
3443
}
3544

45+
// NewPolicyChecker creates a new PolicyChecker from the given module.
3646
func NewPolicyChecker(ctx context.Context, module PolicyModule) (*PolicyChecker, error) {
3747
query, err := rego.New(
3848
rego.Query(
@@ -51,9 +61,12 @@ func NewPolicyChecker(ctx context.Context, module PolicyModule) (*PolicyChecker,
5161
}, nil
5262
}
5363

64+
// DefaultPoliciesFromGlobs creates policy checkers from one or more file policy globs, using
65+
// the default package and result values.
5466
func DefaultPoliciesFromGlobs(
5567
ctx context.Context,
5668
globs []string,
69+
extraFields map[string]interface{},
5770
) ([]*PolicyChecker, error) {
5871
checkers := []*PolicyChecker{}
5972

@@ -71,10 +84,11 @@ func DefaultPoliciesFromGlobs(
7184
checker, err := NewPolicyChecker(
7285
ctx,
7386
PolicyModule{
74-
Name: filepath.Base(match),
75-
Contents: string(contents),
76-
Package: defaultPackage,
77-
Result: defaultResult,
87+
Name: filepath.Base(match),
88+
Contents: string(contents),
89+
Package: defaultPackage,
90+
Result: defaultResult,
91+
ExtraFields: extraFields,
7892
},
7993
)
8094
if err != nil {
@@ -87,13 +101,23 @@ func DefaultPoliciesFromGlobs(
87101
return checkers, nil
88102
}
89103

104+
// Check runs a check against the argument resource using the current policy.
90105
func (p *PolicyChecker) Check(ctx context.Context, resource Resource) CheckResult {
91-
data := map[string]interface{}{}
92106
result := CheckResult{
93107
CheckType: CheckTypeOPA,
94108
CheckName: p.Module.Name,
95109
}
96110

111+
if resource.Name == "" {
112+
// Skip over resources that aren't likely to have any Kubernetes-related
113+
// structure.
114+
result.Status = StatusEmpty
115+
result.Message = fmt.Sprintf("No resource content")
116+
return result
117+
}
118+
119+
data := map[string]interface{}{}
120+
97121
if err := yaml.Unmarshal(resource.Contents, &data); err != nil {
98122
result.Status = StatusError
99123
result.Message = fmt.Sprintf("Error unmarshalling yaml: %+v", err)

pkg/validation/policy_test.go

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ package example
1414
1515
deny[msg] {
1616
input.apiVersion == "badVersion"
17+
input.extraKey == "extraBadValue"
1718
msg = "Cannot have bad api version"
1819
}`
1920

@@ -25,6 +26,18 @@ default allow = true
2526
allow = false {
2627
input.apiVersion == "badVersion"
2728
}`
29+
30+
goodVersionResourceStr = `
31+
apiVersion: goodVersion
32+
kind: Deployment
33+
metadata:
34+
name: test`
35+
36+
badVersionResourceStr = `
37+
apiVersion: badVersion
38+
kind: Deployment
39+
metadata:
40+
name: test`
2841
)
2942

3043
func TestPolicyChecker(t *testing.T) {
@@ -43,8 +56,29 @@ func TestPolicyChecker(t *testing.T) {
4356
Contents: denyPolicyStr,
4457
Package: "example",
4558
Result: "deny",
59+
ExtraFields: map[string]interface{}{
60+
"extraKey": "extraBadValue",
61+
},
62+
},
63+
resource: MakeResource("test/path", []byte(goodVersionResourceStr), 0),
64+
expected: CheckResult{
65+
CheckType: CheckTypeOPA,
66+
CheckName: "testDenyPolicy",
67+
Status: StatusValid,
68+
Message: "Policy returned 0 deny reasons",
69+
},
70+
},
71+
{
72+
policyModule: PolicyModule{
73+
Name: "testDenyPolicy",
74+
Contents: denyPolicyStr,
75+
Package: "example",
76+
Result: "deny",
77+
ExtraFields: map[string]interface{}{
78+
"extraKey": "goodValue",
79+
},
4680
},
47-
resource: MakeResource("test/path", []byte("apiVersion: goodVersion"), 0),
81+
resource: MakeResource("test/path", []byte(badVersionResourceStr), 0),
4882
expected: CheckResult{
4983
CheckType: CheckTypeOPA,
5084
CheckName: "testDenyPolicy",
@@ -58,8 +92,11 @@ func TestPolicyChecker(t *testing.T) {
5892
Contents: denyPolicyStr,
5993
Package: "example",
6094
Result: "deny",
95+
ExtraFields: map[string]interface{}{
96+
"extraKey": "extraBadValue",
97+
},
6198
},
62-
resource: MakeResource("test/path", []byte("apiVersion: badVersion"), 0),
99+
resource: MakeResource("test/path", []byte(badVersionResourceStr), 0),
63100
expected: CheckResult{
64101
CheckType: CheckTypeOPA,
65102
CheckName: "testDenyPolicy",
@@ -74,7 +111,7 @@ func TestPolicyChecker(t *testing.T) {
74111
Package: "example",
75112
Result: "allow",
76113
},
77-
resource: MakeResource("test/path", []byte("apiVersion: goodVersion"), 0),
114+
resource: MakeResource("test/path", []byte(goodVersionResourceStr), 0),
78115
expected: CheckResult{
79116
CheckType: CheckTypeOPA,
80117
CheckName: "testAllowPolicy",
@@ -89,14 +126,29 @@ func TestPolicyChecker(t *testing.T) {
89126
Package: "example",
90127
Result: "allow",
91128
},
92-
resource: MakeResource("test/path", []byte("apiVersion: badVersion"), 0),
129+
resource: MakeResource("test/path", []byte(badVersionResourceStr), 0),
93130
expected: CheckResult{
94131
CheckType: CheckTypeOPA,
95132
CheckName: "testAllowPolicy",
96133
Status: StatusInvalid,
97134
Message: "Policy returned allowed = false",
98135
},
99136
},
137+
{
138+
policyModule: PolicyModule{
139+
Name: "testAllowPolicy",
140+
Contents: allowPolicyStr,
141+
Package: "example",
142+
Result: "allow",
143+
},
144+
resource: MakeResource("test/path", []byte(""), 0),
145+
expected: CheckResult{
146+
CheckType: CheckTypeOPA,
147+
CheckName: "testAllowPolicy",
148+
Status: StatusEmpty,
149+
Message: "No resource content",
150+
},
151+
},
100152
}
101153

102154
for _, testCase := range testCases {

pkg/validation/resource.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func (r Resource) PrettyName() string {
5555
}
5656
}
5757

58+
// TokResource converts a Resource to a Kubeconform resource (useful for running the latter).
5859
func (r Resource) TokResource() kresource.Resource {
5960
return kresource.Resource{
6061
Path: r.Path,

pkg/validation/result.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,21 @@ const (
1212
StatusOther Status = "other"
1313
)
1414

15+
// CheckType represents the type of check that has been done.
1516
type CheckType string
1617

1718
const (
1819
CheckTypeKubeconform CheckType = "kubeconform"
1920
CheckTypeOPA CheckType = "opa"
2021
)
2122

22-
// Result stores the results of validating a single resource in a single file.
23+
// Result stores the results of validating a single resource in a single file, for all checks.
2324
type Result struct {
2425
Resource Resource
2526
CheckResults []CheckResult
2627
}
2728

29+
// CheckResult contains the detailed results of a single check.
2830
type CheckResult struct {
2931
CheckType CheckType
3032
CheckName string

0 commit comments

Comments
 (0)