only expose interfac

This commit is contained in:
Yann Hamon 2020-11-14 16:23:33 +01:00
parent 9d91ec4aa9
commit 4672ded043
3 changed files with 57 additions and 72 deletions

View file

@ -90,7 +90,7 @@ func realMain() int {
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
for i := 0; i < cfg.NumberOfWorkers; i++ { for i := 0; i < cfg.NumberOfWorkers; i++ {
wg.Add(1) wg.Add(1)
go func(resources <-chan resource.Resource, validationResults chan<- validator.Result, v *validator.Validator) { go func(resources <-chan resource.Resource, validationResults chan<- validator.Result, v validator.Validator) {
for res := range resources { for res := range resources {
validationResults <- v.Validate(res) validationResults <- v.Validate(res)
} }
@ -106,7 +106,11 @@ func realMain() int {
} }
if err, ok := err.(resource.DiscoveryError); ok { if err, ok := err.(resource.DiscoveryError); ok {
validationResults <- validator.NewError(err.Path, err.Err) validationResults <- validator.Result{
Resource: resource.Resource{Path: err.Path},
Err: err.Err,
Status: validator.Error,
}
ctx.Done() ctx.Done()
} }
} }

View file

@ -21,11 +21,15 @@ const (
Empty Empty
) )
type Validator struct { // Result contains the details of the result of a resource validation
opts Opts type Result struct {
schemaCache *cache.SchemaCache Resource resource.Resource
schemaDownload func(registries []registry.Registry, kind, version, k8sVersion string) (*gojsonschema.Schema, error) Err error
regs []registry.Registry Status Status
}
type Validator interface {
Validate(res resource.Resource) Result
} }
type Opts struct { type Opts struct {
@ -37,28 +41,7 @@ type Opts struct {
IgnoreMissingSchemas bool IgnoreMissingSchemas bool
} }
func downloadSchema(registries []registry.Registry, kind, version, k8sVersion string) (*gojsonschema.Schema, error) { func New(schemaLocations []string, opts Opts) Validator {
var err error
var schemaBytes []byte
for _, reg := range registries {
schemaBytes, err = reg.DownloadSchema(kind, version, k8sVersion)
if err == nil {
return gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaBytes))
}
// If we get a 404, we try the next registry, but we exit if we get a real failure
if _, notfound := err.(*registry.NotFoundError); notfound {
continue
}
return nil, err
}
return nil, nil // No schema found - we don't consider it an error, resource will be skipped
}
func New(schemaLocations []string, opts Opts) *Validator {
registries := []registry.Registry{} registries := []registry.Registry{}
for _, schemaLocation := range schemaLocations { for _, schemaLocation := range schemaLocations {
registries = append(registries, registry.New(schemaLocation, opts.Strict, opts.SkipTLS)) registries = append(registries, registry.New(schemaLocation, opts.Strict, opts.SkipTLS))
@ -71,7 +54,7 @@ func New(schemaLocations []string, opts Opts) *Validator {
opts.RejectKinds = map[string]bool{} opts.RejectKinds = map[string]bool{}
} }
return &Validator{ return &v{
opts: opts, opts: opts,
schemaDownload: downloadSchema, schemaDownload: downloadSchema,
schemaCache: cache.New(), schemaCache: cache.New(),
@ -79,14 +62,21 @@ func New(schemaLocations []string, opts Opts) *Validator {
} }
} }
func (v *Validator) Validate(res resource.Resource) Result { type v struct {
opts Opts
schemaCache *cache.SchemaCache
schemaDownload func(registries []registry.Registry, kind, version, k8sVersion string) (*gojsonschema.Schema, error)
regs []registry.Registry
}
func (val *v) Validate(res resource.Resource) Result {
skip := func(signature resource.Signature) bool { skip := func(signature resource.Signature) bool {
isSkipKind, ok := v.opts.SkipKinds[signature.Kind] isSkipKind, ok := val.opts.SkipKinds[signature.Kind]
return ok && isSkipKind return ok && isSkipKind
} }
reject := func(signature resource.Signature) bool { reject := func(signature resource.Signature) bool {
_, ok := v.opts.RejectKinds[signature.Kind] _, ok := val.opts.RejectKinds[signature.Kind]
return ok return ok
} }
@ -111,38 +101,34 @@ func (v *Validator) Validate(res resource.Resource) Result {
var schema *gojsonschema.Schema var schema *gojsonschema.Schema
cacheKey := "" cacheKey := ""
if v.schemaCache != nil { if val.schemaCache != nil {
cacheKey = cache.Key(sig.Kind, sig.Version, v.opts.KubernetesVersion) cacheKey = cache.Key(sig.Kind, sig.Version, val.opts.KubernetesVersion)
schema, cached = v.schemaCache.Get(cacheKey) schema, cached = val.schemaCache.Get(cacheKey)
} }
if !cached { if !cached {
if schema, err = v.schemaDownload(v.regs, sig.Kind, sig.Version, v.opts.KubernetesVersion); err != nil { if schema, err = val.schemaDownload(val.regs, sig.Kind, sig.Version, val.opts.KubernetesVersion); err != nil {
return Result{Resource: res, Err: err, Status: Error} return Result{Resource: res, Err: err, Status: Error}
} }
if v.schemaCache != nil { if val.schemaCache != nil {
v.schemaCache.Set(cacheKey, schema) val.schemaCache.Set(cacheKey, schema)
} }
} }
if schema == nil { if schema == nil {
if v.opts.IgnoreMissingSchemas { if val.opts.IgnoreMissingSchemas {
return Result{Resource: res, Err: nil, Status: Skipped} return Result{Resource: res, Err: nil, Status: Skipped}
} else { } else {
return Result{Resource: res, Err: fmt.Errorf("could not find schema for %s", sig.Kind), Status: Error} return Result{Resource: res, Err: fmt.Errorf("could not find schema for %s", sig.Kind), Status: Error}
} }
} }
if schema == nil { var r map[string]interface{}
return Result{Resource: res, Status: Skipped, Err: nil} if err := yaml.Unmarshal(res.Bytes, &r); err != nil {
}
var resource map[string]interface{}
if err := yaml.Unmarshal(res.Bytes, &resource); err != nil {
return Result{Resource: res, Status: Error, Err: fmt.Errorf("error unmarshalling resource: %s", err)} return Result{Resource: res, Status: Error, Err: fmt.Errorf("error unmarshalling resource: %s", err)}
} }
resourceLoader := gojsonschema.NewGoLoader(resource) resourceLoader := gojsonschema.NewGoLoader(r)
results, err := schema.Validate(resourceLoader) results, err := schema.Validate(resourceLoader)
if err != nil { if err != nil {
@ -165,14 +151,25 @@ func (v *Validator) Validate(res resource.Resource) Result {
return Result{Resource: res, Status: Invalid, Err: fmt.Errorf("%s", msg)} return Result{Resource: res, Status: Invalid, Err: fmt.Errorf("%s", msg)}
} }
// ValidFormat is a type for quickly forcing func downloadSchema(registries []registry.Registry, kind, version, k8sVersion string) (*gojsonschema.Schema, error) {
// new formats on the gojsonschema loader var err error
type ValidFormat struct{} var schemaBytes []byte
// ValidFormat is a type for quickly forcing for _, reg := range registries {
// new formats on the gojsonschema loader schemaBytes, err = reg.DownloadSchema(kind, version, k8sVersion)
func (f ValidFormat) IsFormat(input interface{}) bool { if err == nil {
return true return gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaBytes))
}
// If we get a 404, we try the next registry, but we exit if we get a real failure
if _, notfound := err.(*registry.NotFoundError); notfound {
continue
}
return nil, err
}
return nil, nil // No schema found - we don't consider it an error, resource will be skipped
} }
// From kubeval - let's see if absolutely necessary // From kubeval - let's see if absolutely necessary
@ -182,19 +179,3 @@ func (f ValidFormat) IsFormat(input interface{}) bool {
// gojsonschema.FormatCheckers.Add("int32", ValidFormat{}) // gojsonschema.FormatCheckers.Add("int32", ValidFormat{})
// gojsonschema.FormatCheckers.Add("int-or-string", ValidFormat{}) // gojsonschema.FormatCheckers.Add("int-or-string", ValidFormat{})
// } // }
// Result contains the details of the result of a resource validation
type Result struct {
Resource resource.Resource
Err error
Status Status
}
// NewError is a utility function to generate a validation error
func NewError(filename string, err error) Result {
return Result{
Resource: resource.Resource{Path: filename},
Err: err,
Status: Error,
}
}

View file

@ -134,7 +134,7 @@ lastName: bar
Error, Error,
}, },
} { } {
v := Validator{ val := v{
opts: Opts{ opts: Opts{
SkipKinds: map[string]bool{}, SkipKinds: map[string]bool{},
RejectKinds: map[string]bool{}, RejectKinds: map[string]bool{},
@ -149,7 +149,7 @@ lastName: bar
}, },
regs: nil, regs: nil,
} }
if got := v.Validate(resource.Resource{Bytes: testCase.rawResource}); got.Status != testCase.expect { if got := val.Validate(resource.Resource{Bytes: testCase.rawResource}); got.Status != testCase.expect {
t.Errorf("%d - expected %d, got %d", i, testCase.expect, got.Status) t.Errorf("%d - expected %d, got %d", i, testCase.expect, got.Status)
} }
} }