Skip to content

Commit

Permalink
Migrate to santhosh-tekuri/jsonschema (#168)
Browse files Browse the repository at this point in the history
* Migrate to santhosh-tekuri/jsonschema
  • Loading branch information
yannh committed Jan 23, 2023
1 parent 84afe70 commit ee7c498
Show file tree
Hide file tree
Showing 65 changed files with 4,651 additions and 9,106 deletions.
13 changes: 9 additions & 4 deletions acceptance.bats
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,12 @@ resetCacheFolder() {
}

@test "Pass when using a valid HTTP -schema-location" {
run bin/kubeconform -schema-location 'https://kubernetesjsonschema.dev/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json' fixtures/valid.yaml
run bin/kubeconform -schema-location 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json' fixtures/valid.yaml
[ "$status" -eq 0 ]
}

@test "Pass when using schemas with HTTP references" {
run bin/kubeconform -summary -schema-location 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{ .NormalizedKubernetesVersion }}{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json' fixtures/valid.yaml
[ "$status" -eq 0 ]
}

Expand Down Expand Up @@ -252,7 +257,7 @@ resetCacheFolder() {

@test "Fail when no schema found, ensure 404 is not cached on disk" {
resetCacheFolder
run bin/kubeconform -cache cache -schema-location 'https://raw.githubusercontent.com/garethr/openshift-json-schema/master/doesnotexist.json' fixtures/valid.yaml
run bin/kubeconform -cache cache -schema-location 'https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/doesnotexist.json' fixtures/valid.yaml
[ "$status" -eq 1 ]
[ "$output" == 'fixtures/valid.yaml - ReplicationController bob failed validation: could not find schema for ReplicationController' ]
[ "`ls cache/ | wc -l`" -eq 0 ]
Expand Down Expand Up @@ -287,14 +292,14 @@ resetCacheFolder() {
@test "Fail when parsing a List that contains an invalid resource" {
run bin/kubeconform -summary fixtures/list_invalid.yaml
[ "$status" -eq 1 ]
[ "${lines[0]}" == 'fixtures/list_invalid.yaml - ReplicationController bob is invalid: For field spec.replicas: Invalid type. Expected: [integer,null], given: string' ]
[ "${lines[0]}" == 'fixtures/list_invalid.yaml - ReplicationController bob is invalid: problem validating schema. Check JSON formatting: jsonschema: '\''/spec/replicas'\'' does not validate with https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master-standalone/replicationcontroller-v1.json#/properties/spec/properties/replicas/type: expected integer or null, but got string' ]
[ "${lines[1]}" == 'Summary: 2 resources found in 1 file - Valid: 1, Invalid: 1, Errors: 0, Skipped: 0' ]
}

@test "Fail when parsing a List that contains an invalid resource from stdin" {
run bash -c "cat fixtures/list_invalid.yaml | bin/kubeconform -summary -"
[ "$status" -eq 1 ]
[ "${lines[0]}" == 'stdin - ReplicationController bob is invalid: For field spec.replicas: Invalid type. Expected: [integer,null], given: string' ]
[ "${lines[0]}" == 'stdin - ReplicationController bob is invalid: problem validating schema. Check JSON formatting: jsonschema: '\''/spec/replicas'\'' does not validate with https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master-standalone/replicationcontroller-v1.json#/properties/spec/properties/replicas/type: expected integer or null, but got string' ]
[ "${lines[1]}" == 'Summary: 2 resources found parsing stdin - Valid: 1, Invalid: 1, Errors: 0, Skipped: 0' ]
}

Expand Down
8 changes: 3 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ module github.com/yannh/kubeconform
go 1.17

require (
github.com/beevik/etree v1.1.0
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0
gopkg.in/yaml.v2 v2.4.0 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1
sigs.k8s.io/yaml v1.2.0
)

require gopkg.in/yaml.v2 v2.4.0 // indirect
17 changes: 2 additions & 15 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/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=
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1 h1:lEOLY2vyGIqKWUI9nzsOJRV3mb3WC9dXYORsLEUcoeY=
github.com/santhosh-tekuri/jsonschema/v5 v5.1.1/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
Expand Down
18 changes: 9 additions & 9 deletions pkg/registry/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ func newHTTPRegistry(schemaPathTemplate string, cacheFolder string, strict bool,
}

// DownloadSchema downloads the schema for a particular resource from an HTTP server
func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error) {
func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) (string, []byte, error) {
url, err := schemaPath(r.schemaPathTemplate, resourceKind, resourceAPIVersion, k8sVersion, r.strict)
if err != nil {
return nil, err
return "", nil, err
}

if r.cache != nil {
if b, err := r.cache.Get(resourceKind, resourceAPIVersion, k8sVersion); err == nil {
return b.([]byte), nil
return url, b.([]byte), nil
}
}

Expand All @@ -79,7 +79,7 @@ func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVers
if r.debug {
log.Println(msg)
}
return nil, errors.New(msg)
return url, nil, errors.New(msg)
}
defer resp.Body.Close()

Expand All @@ -88,15 +88,15 @@ func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVers
if r.debug {
log.Print(msg)
}
return nil, newNotFoundError(errors.New(msg))
return url, nil, newNotFoundError(errors.New(msg))
}

if resp.StatusCode != http.StatusOK {
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)
return url, nil, fmt.Errorf(msg)
}

body, err := ioutil.ReadAll(resp.Body)
Expand All @@ -105,7 +105,7 @@ func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVers
if r.debug {
log.Print(msg)
}
return nil, errors.New(msg)
return url, nil, errors.New(msg)
}

if r.debug {
Expand All @@ -114,9 +114,9 @@ func (r SchemaRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVers

if r.cache != nil {
if err := r.cache.Set(resourceKind, resourceAPIVersion, k8sVersion, body); err != nil {
return nil, fmt.Errorf("failed writing schema to cache: %s", err)
return url, nil, fmt.Errorf("failed writing schema to cache: %s", err)
}
}

return body, nil
return url, body, nil
}
2 changes: 1 addition & 1 deletion pkg/registry/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func TestDownloadSchema(t *testing.T) {
strict: testCase.strict,
}

res, err := reg.DownloadSchema(testCase.resourceKind, testCase.resourceAPIVersion, testCase.k8sversion)
_, res, err := reg.DownloadSchema(testCase.resourceKind, testCase.resourceAPIVersion, testCase.k8sversion)
if err == nil || testCase.expectErr == nil {
if err != testCase.expectErr {
t.Errorf("during test '%s': expected error, got:\n%s\n%s\n", testCase.name, testCase.expectErr, err)
Expand Down
12 changes: 6 additions & 6 deletions pkg/registry/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ func newLocalRegistry(pathTemplate string, strict bool, debug bool) (*LocalRegis
}

// DownloadSchema retrieves the schema from a file for the resource
func (r LocalRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error) {
func (r LocalRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) (string, []byte, error) {
schemaFile, err := schemaPath(r.pathTemplate, resourceKind, resourceAPIVersion, k8sVersion, r.strict)
if err != nil {
return []byte{}, nil
return schemaFile, []byte{}, nil
}
f, err := os.Open(schemaFile)
if err != nil {
Expand All @@ -36,14 +36,14 @@ func (r LocalRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersi
if r.debug {
log.Print(msg)
}
return nil, newNotFoundError(errors.New(msg))
return schemaFile, nil, newNotFoundError(errors.New(msg))
}

msg := fmt.Sprintf("failed to open schema at %s: %s", schemaFile, err)
if r.debug {
log.Print(msg)
}
return nil, errors.New(msg)
return schemaFile, nil, errors.New(msg)
}

defer f.Close()
Expand All @@ -53,11 +53,11 @@ func (r LocalRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersi
if r.debug {
log.Print(msg)
}
return nil, err
return schemaFile, nil, err
}

if r.debug {
log.Printf("using schema found at %s", schemaFile)
}
return content, nil
return schemaFile, content, nil
}
2 changes: 1 addition & 1 deletion pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type Manifest struct {

// Registry is an interface that should be implemented by any source of Kubernetes schemas
type Registry interface {
DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error)
DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) (string, []byte, error)
}

// Retryable indicates whether an error is a temporary or a permanent failure
Expand Down
40 changes: 12 additions & 28 deletions pkg/validator/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (
"fmt"
"io"

jsonschema "github.com/santhosh-tekuri/jsonschema/v5"
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
"github.com/yannh/kubeconform/pkg/cache"
"github.com/yannh/kubeconform/pkg/registry"
"github.com/yannh/kubeconform/pkg/resource"

"github.com/xeipuuv/gojsonschema"
"sigs.k8s.io/yaml"
)

Expand Down Expand Up @@ -91,7 +91,7 @@ func New(schemaLocations []string, opts Opts) (Validator, error) {
type v struct {
opts Opts
schemaCache cache.Cache
schemaDownload func(registries []registry.Registry, kind, version, k8sVersion string) (*gojsonschema.Schema, error)
schemaDownload func(registries []registry.Registry, kind, version, k8sVersion string) (*jsonschema.Schema, error)
regs []registry.Registry
}

Expand Down Expand Up @@ -151,13 +151,13 @@ func (val *v) ValidateResource(res resource.Resource) Result {
}

cached := false
var schema *gojsonschema.Schema
var schema *jsonschema.Schema

if val.schemaCache != nil {
s, err := val.schemaCache.Get(sig.Kind, sig.Version, val.opts.KubernetesVersion)
if err == nil {
cached = true
schema = s.(*gojsonschema.Schema)
schema = s.(*jsonschema.Schema)
}
}

Expand All @@ -179,28 +179,12 @@ func (val *v) ValidateResource(res resource.Resource) Result {
return Result{Resource: res, Err: fmt.Errorf("could not find schema for %s", sig.Kind), Status: Error}
}

resourceLoader := gojsonschema.NewGoLoader(r)

results, err := schema.Validate(resourceLoader)
err = schema.Validate(r)
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 Result{Resource: res, Status: Error, Err: fmt.Errorf("problem validating schema. Check JSON formatting: %s", err)}
}

if results.Valid() {
return Result{Resource: res, Status: Valid}
return Result{Resource: res, Status: Invalid, Err: fmt.Errorf("problem validating schema. Check JSON formatting: %s", err)}
}

msg := ""
for _, errMsg := range results.Errors() {
if msg != "" {
msg += " - "
}
details := errMsg.Details()
msg += fmt.Sprintf("For field %s: %s", details["field"].(string), errMsg.Description())
}

return Result{Resource: res, Status: Invalid, Err: fmt.Errorf("%s", msg)}
return Result{Resource: res, Status: Valid}
}

// ValidateWithContext validates resources found in r
Expand Down Expand Up @@ -235,15 +219,15 @@ func (val *v) Validate(filename string, r io.ReadCloser) []Result {
return val.ValidateWithContext(context.Background(), filename, r)
}

func downloadSchema(registries []registry.Registry, kind, version, k8sVersion string) (*gojsonschema.Schema, error) {
func downloadSchema(registries []registry.Registry, kind, version, k8sVersion string) (*jsonschema.Schema, error) {
var err error
var schemaBytes []byte
var path string

for _, reg := range registries {
schemaBytes, err = reg.DownloadSchema(kind, version, k8sVersion)
path, schemaBytes, err = reg.DownloadSchema(kind, version, k8sVersion)
if err == nil {
schema, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaBytes))

schema, err := jsonschema.CompileString(path, string(schemaBytes))
// If we got a non-parseable response, we try the next registry
if err != nil {
continue
Expand Down
16 changes: 8 additions & 8 deletions pkg/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import (
)

type mockRegistry struct {
SchemaDownloader func() ([]byte, error)
SchemaDownloader func() (string, []byte, error)
}

func newMockRegistry(f func() ([]byte, error)) *mockRegistry {
func newMockRegistry(f func() (string, []byte, error)) *mockRegistry {
return &mockRegistry{
SchemaDownloader: f,
}
}

func (m mockRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error) {
func (m mockRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) (string, []byte, error) {
return m.SchemaDownloader()
}

Expand Down Expand Up @@ -362,17 +362,17 @@ lastName: bar
schemaCache: nil,
schemaDownload: downloadSchema,
regs: []registry.Registry{
newMockRegistry(func() ([]byte, error) {
return testCase.schemaRegistry1, nil
newMockRegistry(func() (string, []byte, error) {
return "", testCase.schemaRegistry1, nil
}),
newMockRegistry(func() ([]byte, error) {
return testCase.schemaRegistry2, nil
newMockRegistry(func() (string, []byte, error) {
return "", testCase.schemaRegistry2, nil
}),
},
}
if got := val.ValidateResource(resource.Resource{Bytes: testCase.rawResource}); got.Status != testCase.expect {
if got.Err != nil {
t.Errorf("%d - expected %d, got %d: %s", i, testCase.expect, got.Status, got.Err.Error())
t.Errorf("Test '%s' - expected %d, got %d: %s", testCase.name, testCase.expect, got.Status, got.Err.Error())
} else {
t.Errorf("%d - expected %d, got %d", i, testCase.expect, got.Status)
}
Expand Down
14 changes: 0 additions & 14 deletions vendor/github.com/beevik/etree/.travis.yml

This file was deleted.

10 changes: 0 additions & 10 deletions vendor/github.com/beevik/etree/CONTRIBUTORS

This file was deleted.

24 changes: 0 additions & 24 deletions vendor/github.com/beevik/etree/LICENSE

This file was deleted.

Loading

0 comments on commit ee7c498

Please sign in to comment.