mirror of
https://github.com/yannh/kubeconform.git
synced 2026-02-17 17:07:02 +00:00
Support multi-resource files, include kind/version in logging
This commit is contained in:
parent
a1c2e9dc68
commit
05da409a0a
5 changed files with 70 additions and 49 deletions
3
Makefile
3
Makefile
|
|
@ -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
98
main.go
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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++
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue