From 96e35cb92629dfd9a6fd5906719c760bfdeb583a Mon Sep 17 00:00:00 2001 From: Yann Hamon Date: Sat, 30 May 2020 03:37:40 +0200 Subject: [PATCH] try to validate --- go.mod | 5 ++++- go.sum | 10 ++++++++++ main.go | 25 ++++++++++++++++++++---- pkg/registry/registry.go | 8 ++++---- pkg/resource/main.go | 18 ++++++----------- pkg/validator/main.go | 42 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 pkg/validator/main.go diff --git a/go.mod b/go.mod index 1e64db4..1f01f27 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/yannh/kubeconform go 1.14 -require gopkg.in/yaml.v2 v2.3.0 +require ( + github.com/xeipuuv/gojsonschema v1.2.0 + gopkg.in/yaml.v2 v2.3.0 +) diff --git a/go.sum b/go.sum index 8fabe8d..529ab99 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,13 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index b5231b7..e3000aa 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,19 @@ package main import ( + "io/ioutil" "log" "os" "github.com/yannh/kubeconform/pkg/registry" "github.com/yannh/kubeconform/pkg/resource" + "github.com/yannh/kubeconform/pkg/validator" ) func realMain() int { + const k8sVersion = "1.18.0" filename := "fixtures/valid_1.yaml" + f, err := os.Open(filename) if err != nil { log.Fatalf("failed opening %s", filename) @@ -17,21 +21,34 @@ func realMain() int { } defer f.Close() - res, err := resource.Read(f) + rawResource, err := ioutil.ReadAll(f) + if err != nil { + log.Printf("failed reading file %s", filename) + return 1 + } + + sig, err := resource.SignatureFromBytes(rawResource) if err != nil { log.Printf("failed parsing %s", filename) return 1 } r := registry.NewKubernetesRegistry() - schema, err := r.DownloadSchema(res.Kind, res.Version, "1.18.0") + schema, err := r.DownloadSchema(sig.Kind, sig.Version, k8sVersion) if err != nil { - log.Printf("error downloading Schema: %s") + log.Printf("error downloading Schema: %s", err) return 1 } - log.Printf("downloaded schema successfully: %s", schema) + err = validator.Validate(rawResource, schema) + if err != nil { + log.Printf("failed validating: %s", err) + return 1 + } + + + log.Printf("resource is valid!: %s", schema) return 0 } diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 8739d7f..27ca339 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -50,22 +50,22 @@ func (r KubernetesRegistry) schemaURL(resourceKind, resourceAPIVersion, k8sVersi return fmt.Sprintf("%s/%s-standalone%s/%s%s.json", r.baseURL, normalisedVersion, strictSuffix, strings.ToLower(resourceKind), kindSuffix) } -func (r KubernetesRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) (string, error) { +func (r KubernetesRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error) { url := r.schemaURL(resourceKind, resourceAPIVersion, k8sVersion) resp, err := http.Get(url) if err != nil { - return "", fmt.Errorf("failed downloading schema at %s: %s", url, err) + return []byte{}, fmt.Errorf("failed downloading schema at %s: %s", url, err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { - return "", fmt.Errorf("failed downloading schema at %s: %s", url, err) + return []byte{}, fmt.Errorf("failed downloading schema at %s: %s", url, err) } fmt.Printf("downloaded %s\n", url) - return string(body), nil + return body, nil } diff --git a/pkg/resource/main.go b/pkg/resource/main.go index cdc57f4..bf55c30 100644 --- a/pkg/resource/main.go +++ b/pkg/resource/main.go @@ -1,22 +1,15 @@ package resource import ( - "io" - "io/ioutil" yaml "gopkg.in/yaml.v2" ) -type Resource struct { +type Signature struct { Kind, Version, Namespace string } // TODO: Support multi-resources yaml files -func Read(r io.Reader) (Resource, error) { - s, err := ioutil.ReadAll(r) - if err != nil { - return Resource{}, err - } - +func SignatureFromBytes(s []byte) (Signature, error) { resource := struct { APIVersion string `yaml:"apiVersion"` Kind string `yaml:"kind"` @@ -24,7 +17,8 @@ func Read(r io.Reader) (Resource, error) { Namespace string `yaml:"Namespace"` } `yaml:"Metadata"` }{} - err = yaml.Unmarshal(s, &resource) + err := yaml.Unmarshal(s, &resource) + + return Signature{Kind: resource.Kind, Version: resource.APIVersion, Namespace: resource.Metadata.Namespace}, err +} - return Resource{Kind: resource.Kind, Version: resource.APIVersion, Namespace: resource.Metadata.Namespace}, err -} \ No newline at end of file diff --git a/pkg/validator/main.go b/pkg/validator/main.go new file mode 100644 index 0000000..1f54bd1 --- /dev/null +++ b/pkg/validator/main.go @@ -0,0 +1,42 @@ +package validator + +import ( + "fmt" + "github.com/xeipuuv/gojsonschema" +) + +// ValidFormat is a type for quickly forcing +// new formats on the gojsonschema loader +type ValidFormat struct{} +func (f ValidFormat) IsFormat(input interface{}) bool { + return true +} + +func init () { + gojsonschema.FormatCheckers.Add("int64", ValidFormat{}) + gojsonschema.FormatCheckers.Add("byte", ValidFormat{}) + gojsonschema.FormatCheckers.Add("int32", ValidFormat{}) + gojsonschema.FormatCheckers.Add("int-or-string", ValidFormat{}) +} + +func Validate(resource interface{}, rawSchema []byte) error { + schemaLoader := gojsonschema.NewBytesLoader(rawSchema) + schema, err := gojsonschema.NewSchema(schemaLoader) + if err != nil { + return err + } + + documentLoader := gojsonschema.NewGoLoader(resource) + results, err := schema.Validate(documentLoader) + if err != nil { + // This error can only happen if the Object to validate is poorly formed. There's no hope of saving this one + return fmt.Errorf("problem validating schema. Check JSON formatting: %s", err) + } + + if !results.Valid() { + return fmt.Errorf("resource does not conform to schema") + } + + return nil +} +