From f68d6ec6eaba18814465d5706a61b01ed0bd09bf Mon Sep 17 00:00:00 2001 From: Yann Hamon Date: Sun, 16 Oct 2022 12:28:11 +0200 Subject: [PATCH] Add debug information to help understand failures finding schemas (#133) * Add debug information to help understand failures finding schemas * Add debug information to help understand failures finding schemas --- Readme.md | 2 ++ cmd/kubeconform/main.go | 5 +++-- pkg/config/config.go | 2 ++ pkg/config/config_test.go | 3 ++- pkg/registry/http.go | 34 +++++++++++++++++++++++++++++----- pkg/registry/http_test.go | 2 +- pkg/registry/local.go | 26 +++++++++++++++++++++++--- pkg/registry/registry.go | 6 +++--- pkg/validator/validator.go | 11 ++--------- 9 files changed, 67 insertions(+), 24 deletions(-) diff --git a/Readme.md b/Readme.md index c442454..cb2706b 100644 --- a/Readme.md +++ b/Readme.md @@ -76,6 +76,8 @@ Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]... cache schemas downloaded via HTTP to this folder -cpu-prof string debug - log CPU profiling to file + -debug + print debug information -exit-on-error immediately stop execution when the first error is encountered -h show help information diff --git a/cmd/kubeconform/main.go b/cmd/kubeconform/main.go index 2e7001f..b3e0b91 100644 --- a/cmd/kubeconform/main.go +++ b/cmd/kubeconform/main.go @@ -97,9 +97,10 @@ func realMain() int { fmt.Fprintln(os.Stderr, err) return 1 } - - v, err := validator.New(cfg.SchemaLocations, validator.Opts{ + var v validator.Validator + v, err = validator.New(cfg.SchemaLocations, validator.Opts{ Cache: cfg.Cache, + Debug: cfg.Debug, SkipTLS: cfg.SkipTLS, SkipKinds: cfg.SkipKinds, RejectKinds: cfg.RejectKinds, diff --git a/pkg/config/config.go b/pkg/config/config.go index 37df885..d95de35 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -10,6 +10,7 @@ import ( type Config struct { Cache string CPUProfileFile string + Debug bool ExitOnError bool Files []string SchemaLocations []string @@ -67,6 +68,7 @@ func FromFlags(progName string, args []string) (Config, string, error) { flags.Var(&schemaLocationsParam, "schema-location", "override schemas location search path (can be specified multiple times)") flags.StringVar(&skipKindsCSV, "skip", "", "comma-separated list of kinds to ignore") flags.StringVar(&rejectKindsCSV, "reject", "", "comma-separated list of kinds to reject") + flags.BoolVar(&c.Debug, "debug", false, "print debug information") flags.BoolVar(&c.ExitOnError, "exit-on-error", false, "immediately stop execution when the first error is encountered") flags.BoolVar(&c.IgnoreMissingSchemas, "ignore-missing-schemas", false, "skip files with missing schemas instead of failing") flags.Var(&ignoreFilenamePatterns, "ignore-filename-pattern", "regular expression specifying paths to ignore (can be specified multiple times)") diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 038aa3d..9a184d5 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -112,9 +112,10 @@ func TestFromFlags(t *testing.T) { { []string{"-cache", "cache", "-ignore-missing-schemas", "-kubernetes-version", "1.16.0", "-n", "2", "-output", "json", "-schema-location", "folder", "-schema-location", "anotherfolder", "-skip", "kinda,kindb", "-strict", - "-reject", "kindc,kindd", "-summary", "-verbose", "file1", "file2"}, + "-reject", "kindc,kindd", "-summary", "-debug", "-verbose", "file1", "file2"}, Config{ Cache: "cache", + Debug: true, Files: []string{"file1", "file2"}, IgnoreMissingSchemas: true, KubernetesVersion: "1.16.0", diff --git a/pkg/registry/http.go b/pkg/registry/http.go index ecf5ede..db49f96 100644 --- a/pkg/registry/http.go +++ b/pkg/registry/http.go @@ -2,8 +2,10 @@ package registry import ( "crypto/tls" + "errors" "fmt" "io/ioutil" + "log" "net/http" "os" "time" @@ -21,9 +23,10 @@ type SchemaRegistry struct { schemaPathTemplate string cache cache.Cache strict bool + debug bool } -func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool, skipTLS bool) (*SchemaRegistry, error) { +func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool, skipTLS bool, debug bool) (*SchemaRegistry, error) { reghttp := &http.Transport{ MaxIdleConns: 100, IdleConnTimeout: 3 * time.Second, @@ -53,6 +56,7 @@ func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool, schemaPathTemplate: schemaPathTemplate, cache: filecache, strict: strict, + debug: debug, }, nil } @@ -71,21 +75,41 @@ func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVers resp, err := r.c.Get(url) if err != nil { - return nil, fmt.Errorf("failed downloading schema at %s: %s", url, err) + msg := fmt.Sprintf("failed downloading schema at %s: %s", url, err) + if r.debug { + log.Println(msg) + } + return nil, errors.New(msg) } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { - return nil, newNotFoundError(fmt.Errorf("no schema found")) + msg := fmt.Sprintf("could not find schema at %s", url) + if r.debug { + log.Print(msg) + } + return nil, newNotFoundError(errors.New(msg)) } if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("error while downloading schema at %s - received HTTP status %d", url, resp.StatusCode) + msg := fmt.Sprintf("error while downloading schema at %s - received HTTP status %d", url, resp.StatusCode) + if r.debug { + log.Print(msg) + } + return nil, fmt.Errorf(msg) } body, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed downloading schema at %s: %s", url, err) + msg := fmt.Sprintf("failed parsing schema from %s: %s", url, err) + if r.debug { + log.Print(msg) + } + return nil, errors.New(msg) + } + + if r.debug { + log.Printf("using schema found at %s", url) } if r.cache != nil { diff --git a/pkg/registry/http_test.go b/pkg/registry/http_test.go index 6abffaa..f759a1e 100644 --- a/pkg/registry/http_test.go +++ b/pkg/registry/http_test.go @@ -59,7 +59,7 @@ func TestDownloadSchema(t *testing.T) { "v1", "1.18.0", nil, - fmt.Errorf("no schema found"), + fmt.Errorf("could not find schema at http://kubernetesjson.dev"), }, { "getting 503", diff --git a/pkg/registry/local.go b/pkg/registry/local.go index abae6e8..81d69e2 100644 --- a/pkg/registry/local.go +++ b/pkg/registry/local.go @@ -1,21 +1,25 @@ package registry import ( + "errors" "fmt" "io/ioutil" + "log" "os" ) type LocalRegistry struct { pathTemplate string strict bool + debug bool } // NewLocalSchemas creates a new "registry", that will serve schemas from files, given a list of schema filenames -func newLocalRegistry(pathTemplate string, strict bool) (*LocalRegistry, error) { +func newLocalRegistry(pathTemplate string, strict bool, debug bool) (*LocalRegistry, error) { return &LocalRegistry{ pathTemplate, strict, + debug, }, nil } @@ -28,16 +32,32 @@ func (r LocalRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersi f, err := os.Open(schemaFile) if err != nil { if os.IsNotExist(err) { - return nil, newNotFoundError(fmt.Errorf("no schema found")) + msg := fmt.Sprintf("could not open file %s", schemaFile) + if r.debug { + log.Print(msg) + } + return nil, newNotFoundError(errors.New(msg)) } - return nil, fmt.Errorf("failed to open schema %s", schemaFile) + + msg := fmt.Sprintf("failed to open schema at %s: %s", schemaFile, err) + if r.debug { + log.Print(msg) + } + return nil, errors.New(msg) } defer f.Close() content, err := ioutil.ReadAll(f) if err != nil { + msg := fmt.Sprintf("failed to read schema at %s: %s", schemaFile, err) + if r.debug { + log.Print(msg) + } return nil, err } + if r.debug { + log.Printf("using schema found at %s", schemaFile) + } return content, nil } diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index b5451ff..afde801 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -81,7 +81,7 @@ func schemaPath(tpl, resourceKind, resourceAPIVersion, k8sVersion string, strict return buf.String(), nil } -func New(schemaLocation string, cache string, strict bool, skipTLS bool) (Registry, error) { +func New(schemaLocation string, cache string, strict bool, skipTLS bool, debug bool) (Registry, error) { if schemaLocation == "default" { schemaLocation = "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json" } else if !strings.HasSuffix(schemaLocation, "json") { // If we dont specify a full templated path, we assume the paths of our fork of kubernetes-json-schema @@ -94,8 +94,8 @@ func New(schemaLocation string, cache string, strict bool, skipTLS bool) (Regist } if strings.HasPrefix(schemaLocation, "http") { - return newHTTPRegistry(schemaLocation, cache, strict, skipTLS) + return newHTTPRegistry(schemaLocation, cache, strict, skipTLS, debug) } - return newLocalRegistry(schemaLocation, strict) + return newLocalRegistry(schemaLocation, strict, debug) } diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go index 08d2103..f1ae723 100644 --- a/pkg/validator/validator.go +++ b/pkg/validator/validator.go @@ -43,6 +43,7 @@ type Validator interface { // Opts contains a set of options for the validator. type Opts struct { Cache string // Cache schemas downloaded via HTTP to this folder + Debug bool // Debug infos will be print here SkipTLS bool // skip TLS validation when downloading from an HTTP Schema Registry SkipKinds map[string]struct{} // List of resource Kinds to ignore RejectKinds map[string]struct{} // List of resource Kinds to reject @@ -61,7 +62,7 @@ func New(schemaLocations []string, opts Opts) (Validator, error) { registries := []registry.Registry{} for _, schemaLocation := range schemaLocations { - reg, err := registry.New(schemaLocation, opts.Cache, opts.Strict, opts.SkipTLS) + reg, err := registry.New(schemaLocation, opts.Cache, opts.Strict, opts.SkipTLS, opts.Debug) if err != nil { return nil, err } @@ -249,11 +250,3 @@ func downloadSchema(registries []registry.Registry, kind, version, k8sVersion st 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 -// func init () { -// gojsonschema.FormatCheckers.Add("int64", ValidFormat{}) -// gojsonschema.FormatCheckers.Add("byte", ValidFormat{}) -// gojsonschema.FormatCheckers.Add("int32", ValidFormat{}) -// gojsonschema.FormatCheckers.Add("int-or-string", ValidFormat{}) -// }