@@ -15,14 +15,21 @@ import (
1515
1616var validateCmd = & cobra.Command {
1717 Use : "validate [cluster configs]" ,
18- Short : "validate checks the cluster configs using kubeval " ,
18+ Short : "validate checks the cluster configs using kubeconform and (optionally) opa policies " ,
1919 Args : cobra .MinimumNArgs (1 ),
2020 RunE : validateRun ,
2121}
2222
2323type validateFlags struct {
2424 // Expand before validating.
2525 expand bool
26+
27+ // Number of worker goroutines to use for validation.
28+ numWorkers int
29+
30+ // Paths to OPA policy rego files that will be run against kube resources.
31+ // See https://www.openpolicyagent.org/ for more details.
32+ policies []string
2633}
2734
2835var validateFlagValues validateFlags
@@ -34,6 +41,18 @@ func init() {
3441 false ,
3542 "Expand before validating" ,
3643 )
44+ validateCmd .Flags ().IntVar (
45+ & validateFlagValues .numWorkers ,
46+ "num-workers" ,
47+ 4 ,
48+ "Number of workers to use for validation" ,
49+ )
50+ validateCmd .Flags ().StringArrayVar (
51+ & validateFlagValues .policies ,
52+ "policy" ,
53+ []string {},
54+ "Paths to OPA policies" ,
55+ )
3756
3857 RootCmd .AddCommand (validateCmd )
3958}
@@ -88,43 +107,106 @@ func validateClusterPath(ctx context.Context, path string) error {
88107func execValidation (ctx context.Context , clusterConfig * config.ClusterConfig ) error {
89108 log .Infof ("Validating cluster %s" , clusterConfig .DescriptiveName ())
90109
91- kubeValidator := validation .NewKubeValidator ()
110+ kubeconformChecker , err := validation .NewKubeconformChecker ()
111+ if err != nil {
112+ return err
113+ }
92114
93- log .Infof (
94- "Checking that expanded configs for %s are valid YAML" ,
95- clusterConfig .DescriptiveName (),
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+ },
96124 )
97- err := kubeValidator .CheckYAML (clusterConfig .AbsSubpaths ())
98125 if err != nil {
99126 return err
100127 }
101128
102- log .Infof ("Running kubeval on configs in %+v" , clusterConfig .AbsSubpaths ())
103- results , err := kubeValidator .RunKubeval (ctx , clusterConfig .AbsSubpaths ()[0 ])
129+ checkers := []validation.Checker {kubeconformChecker }
130+ for _ , policy := range policies {
131+ checkers = append (checkers , policy )
132+ }
133+
134+ validator := validation .NewKubeValidator (
135+ validation.KubeValidatorConfig {
136+ NumWorkers : validateFlagValues .numWorkers ,
137+ Checkers : checkers ,
138+ },
139+ )
140+
141+ log .Infof ("Running validator on configs in %+v" , clusterConfig .AbsSubpaths ())
142+ results , err := validator .RunChecks (ctx , clusterConfig .AbsSubpaths ()[0 ])
104143 if err != nil {
105144 return err
106145 }
107146
108- numInvalidFiles := 0
147+ numInvalidResourceChecks := 0
148+ numValidResourceChecks := 0
149+ numSkippedResourceChecks := 0
109150
110151 for _ , result := range results {
111- switch result .Status {
112- case "valid" :
113- log .Infof ("File %s OK" , result .Filename )
114- case "skipped" :
115- log .Debugf ("File %s skipped" , result .Filename )
116- case "invalid" :
117- numInvalidFiles ++
118- log .Errorf ("File %s is invalid; errors: %+v" , result .Filename , result .Errors )
119- default :
120- log .Infof ("Unrecognized result type: %+v" , result )
152+ for _ , checkResult := range result .CheckResults {
153+ switch checkResult .Status {
154+ case validation .StatusValid :
155+ numValidResourceChecks ++
156+ log .Debugf (
157+ "Resource %s in file %s OK according to check %s" ,
158+ result .Resource .PrettyName (),
159+ result .Resource .Path ,
160+ checkResult .CheckName ,
161+ )
162+ case validation .StatusSkipped :
163+ numSkippedResourceChecks ++
164+ log .Debugf (
165+ "Resource %s in file %s was skipped by check %s" ,
166+ result .Resource .PrettyName (),
167+ result .Resource .Path ,
168+ checkResult .CheckName ,
169+ )
170+ case validation .StatusError :
171+ numInvalidResourceChecks ++
172+ log .Errorf (
173+ "Resource %s in file %s could not be processed by check %s: %s" ,
174+ result .Resource .PrettyName (),
175+ result .Resource .Path ,
176+ checkResult .CheckName ,
177+ checkResult .Message ,
178+ )
179+ case validation .StatusInvalid :
180+ numInvalidResourceChecks ++
181+ log .Errorf (
182+ "Resource %s in file %s is invalid according to check %s: %s" ,
183+ result .Resource .PrettyName (),
184+ result .Resource .Path ,
185+ checkResult .CheckName ,
186+ checkResult .Message ,
187+ )
188+ case validation .StatusEmpty :
189+ default :
190+ log .Infof ("Unrecognized result type: %+v" , result )
191+ }
121192 }
122193 }
123194
124- if numInvalidFiles > 0 {
125- return fmt .Errorf ("Validation failed for %d files" , numInvalidFiles )
195+ if numInvalidResourceChecks > 0 {
196+ return fmt .Errorf (
197+ "Validation failed for %d resources in cluster %s (%d checks valid, %d skipped)" ,
198+ numInvalidResourceChecks ,
199+ clusterConfig .DescriptiveName (),
200+ numValidResourceChecks ,
201+ numSkippedResourceChecks ,
202+ )
126203 }
127204
128- 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+ )
129211 return nil
130212}
0 commit comments