From 05da409a0abc8f49da0820bbf5bdabb10ad0a9dd Mon Sep 17 00:00:00 2001 From: Yann Hamon Date: Sun, 31 May 2020 16:47:30 +0200 Subject: [PATCH] Support multi-resource files, include kind/version in logging --- Makefile | 3 ++ main.go | 98 +++++++++++++++++++++++++++------------------- pkg/output/json.go | 6 ++- pkg/output/main.go | 2 +- pkg/output/text.go | 10 ++--- 5 files changed, 70 insertions(+), 49 deletions(-) diff --git a/Makefile b/Makefile index 771c6e8..e88cb4d 100644 --- a/Makefile +++ b/Makefile @@ -3,5 +3,8 @@ build: go build -o bin/kubeconform +test: + go test ./... + build-static: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o bin/kubeconform diff --git a/main.go b/main.go index 9cc813a..01855c2 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "flag" "fmt" "github.com/xeipuuv/gojsonschema" @@ -20,63 +21,78 @@ import ( ) type validationResult struct { - err error - skipped bool + kind, version string + err error + skipped bool } // filter returns true if the file should be skipped // 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 { - rawResource, err := ioutil.ReadAll(f) + file, err := ioutil.ReadAll(f) if err != nil { return []validationResult{{err: fmt.Errorf("failed reading file: %s", err)}} } - sig, err := resource.SignatureFromBytes(rawResource) - if err != nil { - return []validationResult{{err: fmt.Errorf("error while parsing: %s", err)}} - } + validationResults := []validationResult{} + rawResources := bytes.Split(file, []byte("---\n")) - if skip(sig) { - return []validationResult{{err: nil, skipped: true}} - } +RESOURCES: + 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 - var schemaBytes []byte - var ok bool + if skip(sig) { + validationResults = append(validationResults, validationResult{kind: sig.Kind, version: sig.Version, err: nil, skipped: true}) + continue + } - cacheKey := cache.Key(sig.Kind, sig.Version, k8sVersion) - schema, ok = c.Get(cacheKey) - if !ok { - for _, reg := range regs { - schemaBytes, err = reg.DownloadSchema(sig.Kind, sig.Version, k8sVersion) - if err == nil { - schema, err = gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaBytes)) - if err != nil { - return []validationResult{{err: err, skipped: false}} // skip if no schema found + var schema *gojsonschema.Schema + var schemaBytes []byte + var ok bool + + cacheKey := cache.Key(sig.Kind, sig.Version, k8sVersion) + schema, ok = c.Get(cacheKey) + if !ok { + for _, reg := range regs { + 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 er, retryable := err.(registry.Retryable); !(retryable && !er.IsRetryable()) { - return []validationResult{{err: fmt.Errorf("error while downloading schema for resource: %s", err)}} + // 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()) { + 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 - 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}} + return validationResults } type arrayFiles []string @@ -104,7 +120,7 @@ func realMain() int { flag.BoolVar(&printSummary, "printsummary", false, "print a summary at the end") flag.IntVar(&nWorkers, "workers", 4, "number of routines to run in parallel") 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.BoolVar(&quiet, "quiet", false, "quiet output - only print invalid files, and errors") flag.Parse() @@ -174,7 +190,7 @@ func realMain() int { f.Close() for _, resourceValidation := range res { - o.Write(filename, resourceValidation.err, resourceValidation.skipped) + o.Write(filename, resourceValidation.kind, resourceValidation.version, resourceValidation.err, resourceValidation.skipped) } } } diff --git a/pkg/output/json.go b/pkg/output/json.go index b5a0f1a..2c6f6d0 100644 --- a/pkg/output/json.go +++ b/pkg/output/json.go @@ -8,6 +8,8 @@ import ( type result struct { Filename string `json:"filename"` + Kind string `json:"kind"` + Version string `json:"version"` Status string `json:"status"` 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() defer o.Unlock() msg, st := "", "" @@ -56,7 +58,7 @@ func (o *JSONOutput) Write(filename string, err error, skipped bool) { } 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}) } } diff --git a/pkg/output/main.go b/pkg/output/main.go index e23bbfe..35f3a23 100644 --- a/pkg/output/main.go +++ b/pkg/output/main.go @@ -12,7 +12,7 @@ const ( ) type Output interface { - Write(filename string, err error, skipped bool) + Write(filename, kind, version string, err error, skipped bool) Flush() } diff --git a/pkg/output/text.go b/pkg/output/text.go index d8dc921..2fbe226 100644 --- a/pkg/output/text.go +++ b/pkg/output/text.go @@ -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() defer o.Unlock() @@ -31,18 +31,18 @@ func (o *TextOutput) Write(filename string, err error, skipped bool) { switch { case s == VALID: if !o.quiet { - fmt.Printf("%s - file is valid\n", filename) + fmt.Printf("%s - %s is valid\n", filename, kind) } o.nValid++ 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++ 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++ case s == SKIPPED: if !o.quiet { - fmt.Printf("%s - skipping resource\n", filename) + fmt.Printf("%s - %s skipped\n", filename, kind) } o.nSkipped++ }