Support multi-resource files, include kind/version in logging

This commit is contained in:
Yann Hamon 2020-05-31 16:47:30 +02:00
parent a1c2e9dc68
commit 05da409a0a
5 changed files with 70 additions and 49 deletions

View file

@ -3,5 +3,8 @@
build: build:
go build -o bin/kubeconform go build -o bin/kubeconform
test:
go test ./...
build-static: build-static:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o bin/kubeconform CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o bin/kubeconform

98
main.go
View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"flag" "flag"
"fmt" "fmt"
"github.com/xeipuuv/gojsonschema" "github.com/xeipuuv/gojsonschema"
@ -20,63 +21,78 @@ import (
) )
type validationResult struct { type validationResult struct {
err error kind, version string
skipped bool err error
skipped bool
} }
// filter returns true if the file should be skipped // filter returns true if the file should be skipped
// Returning an array, this Reader might container multiple resources // Returning an array, this Reader might container multiple resources
func validateFile(f io.Reader, regs []registry.Registry, k8sVersion string, c *cache.SchemaCache, skip func(signature resource.Signature) bool) []validationResult { func validateFile(f io.Reader, regs []registry.Registry, k8sVersion string, c *cache.SchemaCache, skip func(signature resource.Signature) bool) []validationResult {
rawResource, err := ioutil.ReadAll(f) file, err := ioutil.ReadAll(f)
if err != nil { if err != nil {
return []validationResult{{err: fmt.Errorf("failed reading file: %s", err)}} return []validationResult{{err: fmt.Errorf("failed reading file: %s", err)}}
} }
sig, err := resource.SignatureFromBytes(rawResource) validationResults := []validationResult{}
if err != nil { rawResources := bytes.Split(file, []byte("---\n"))
return []validationResult{{err: fmt.Errorf("error while parsing: %s", err)}}
}
if skip(sig) { RESOURCES:
return []validationResult{{err: nil, skipped: true}} for _, rawResource := range rawResources {
} sig, err := resource.SignatureFromBytes(rawResource)
if err != nil {
validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: fmt.Errorf("error while parsing: %s", err)})
continue
}
var schema *gojsonschema.Schema if skip(sig) {
var schemaBytes []byte validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: nil, skipped: true})
var ok bool continue
}
cacheKey := cache.Key(sig.Kind, sig.Version, k8sVersion) var schema *gojsonschema.Schema
schema, ok = c.Get(cacheKey) var schemaBytes []byte
if !ok { var ok bool
for _, reg := range regs {
schemaBytes, err = reg.DownloadSchema(sig.Kind, sig.Version, k8sVersion) cacheKey := cache.Key(sig.Kind, sig.Version, k8sVersion)
if err == nil { schema, ok = c.Get(cacheKey)
schema, err = gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaBytes)) if !ok {
if err != nil { for _, reg := range regs {
return []validationResult{{err: err, skipped: false}} // skip if no schema found schemaBytes, err = reg.DownloadSchema(sig.Kind, sig.Version, k8sVersion)
if err == nil {
schema, err = gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaBytes))
if err != nil {
// Downloaded a schema but failed to parse it
validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: err, skipped: false})
continue RESOURCES
}
// success
break
} }
break
}
// If we get a 404, we keep trying, but we exit if we get a real failure // If we get a 404, we keep trying, but we exit if we get a real failure
if er, retryable := err.(registry.Retryable); !(retryable && !er.IsRetryable()) { if er, retryable := err.(registry.Retryable); !(retryable && !er.IsRetryable()) {
return []validationResult{{err: fmt.Errorf("error while downloading schema for resource: %s", err)}} validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: fmt.Errorf("error while downloading schema for resource: %s", err)})
}
} }
} }
// Cache both found & not found
c.Set(cacheKey, schema)
if err != nil { // Not found
validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: nil, skipped: true}) // skip if no schema found
}
if err = validator.Validate(rawResource, schema); err != nil {
validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: err})
}
validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: nil})
} }
// Cache both found & not found return validationResults
c.Set(cacheKey, schema)
if err != nil { // Not found
return []validationResult{{err: nil, skipped: true}} // skip if no schema found
}
if err = validator.Validate(rawResource, schema); err != nil {
return []validationResult{{err: err}}
}
return []validationResult{{err: nil}}
} }
type arrayFiles []string type arrayFiles []string
@ -104,7 +120,7 @@ func realMain() int {
flag.BoolVar(&printSummary, "printsummary", false, "print a summary at the end") flag.BoolVar(&printSummary, "printsummary", false, "print a summary at the end")
flag.IntVar(&nWorkers, "workers", 4, "number of routines to run in parallel") flag.IntVar(&nWorkers, "workers", 4, "number of routines to run in parallel")
flag.StringVar(&skipKinds, "skipKinds", "", "comma-separated list of kinds to ignore") flag.StringVar(&skipKinds, "skipKinds", "", "comma-separated list of kinds to ignore")
flag.BoolVar(&strict, "strict", false, "activate strict mode") flag.BoolVar(&strict, "strict", false, "disallow additional properties not in schema")
flag.StringVar(&outputFormat, "output", "text", "output format - text, json") flag.StringVar(&outputFormat, "output", "text", "output format - text, json")
flag.BoolVar(&quiet, "quiet", false, "quiet output - only print invalid files, and errors") flag.BoolVar(&quiet, "quiet", false, "quiet output - only print invalid files, and errors")
flag.Parse() flag.Parse()
@ -174,7 +190,7 @@ func realMain() int {
f.Close() f.Close()
for _, resourceValidation := range res { for _, resourceValidation := range res {
o.Write(filename, resourceValidation.err, resourceValidation.skipped) o.Write(filename, resourceValidation.kind, resourceValidation.version, resourceValidation.err, resourceValidation.skipped)
} }
} }
} }

View file

@ -8,6 +8,8 @@ import (
type result struct { type result struct {
Filename string `json:"filename"` Filename string `json:"filename"`
Kind string `json:"kind"`
Version string `json:"version"`
Status string `json:"status"` Status string `json:"status"`
Msg string `json:"msg"` Msg string `json:"msg"`
} }
@ -32,7 +34,7 @@ func NewJSONOutput(withSummary bool, quiet bool) Output {
} }
} }
func (o *JSONOutput) Write(filename string, err error, skipped bool) { func (o *JSONOutput) Write(filename, kind, version string, err error, skipped bool) {
o.Lock() o.Lock()
defer o.Unlock() defer o.Unlock()
msg, st := "", "" msg, st := "", ""
@ -56,7 +58,7 @@ func (o *JSONOutput) Write(filename string, err error, skipped bool) {
} }
if !o.quiet || (s != VALID && s != SKIPPED) { if !o.quiet || (s != VALID && s != SKIPPED) {
o.results = append(o.results, result{Filename: filename, Status: st, Msg: msg}) o.results = append(o.results, result{Filename: filename, Kind: kind, Version: version, Status: st, Msg: msg})
} }
} }

View file

@ -12,7 +12,7 @@ const (
) )
type Output interface { type Output interface {
Write(filename string, err error, skipped bool) Write(filename, kind, version string, err error, skipped bool)
Flush() Flush()
} }

View file

@ -23,7 +23,7 @@ func NewTextOutput(withSummary, quiet bool) Output {
} }
} }
func (o *TextOutput) Write(filename string, err error, skipped bool) { func (o *TextOutput) Write(filename, kind, version string, err error, skipped bool) {
o.Lock() o.Lock()
defer o.Unlock() defer o.Unlock()
@ -31,18 +31,18 @@ func (o *TextOutput) Write(filename string, err error, skipped bool) {
switch { switch {
case s == VALID: case s == VALID:
if !o.quiet { if !o.quiet {
fmt.Printf("%s - file is valid\n", filename) fmt.Printf("%s - %s is valid\n", filename, kind)
} }
o.nValid++ o.nValid++
case s == INVALID: case s == INVALID:
fmt.Printf("%s - invalid resource: %s\n", filename, err) fmt.Printf("%s - %s is invalid: %s\n", filename, kind, err)
o.nInvalid++ o.nInvalid++
case s == ERROR: case s == ERROR:
fmt.Printf("%s - failed validating resource: %s\n", filename, err) fmt.Printf("%s - %s failed validation: %s\n", filename, kind, err)
o.nErrors++ o.nErrors++
case s == SKIPPED: case s == SKIPPED:
if !o.quiet { if !o.quiet {
fmt.Printf("%s - skipping resource\n", filename) fmt.Printf("%s - %s skipped\n", filename, kind)
} }
o.nSkipped++ o.nSkipped++
} }