diff --git a/.gitignore b/.gitignore index 62cb519..7a52c72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ dist/ bin/ +.idea/ diff --git a/pkg/validator/validator.go b/pkg/validator/validator.go index 256a613..ffa224d 100644 --- a/pkg/validator/validator.go +++ b/pkg/validator/validator.go @@ -225,7 +225,13 @@ func downloadSchema(registries []registry.Registry, kind, version, k8sVersion st for _, reg := range registries { schemaBytes, err = reg.DownloadSchema(kind, version, k8sVersion) if err == nil { - return gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaBytes)) + schema, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(schemaBytes)) + + // If we got a non-parseable response, we try the next registry + if err != nil { + continue + } + return schema, err } // If we get a 404, we try the next registry, but we exit if we get a real failure diff --git a/pkg/validator/validator_test.go b/pkg/validator/validator_test.go index f97e284..cd8b2e4 100644 --- a/pkg/validator/validator_test.go +++ b/pkg/validator/validator_test.go @@ -1,19 +1,35 @@ package validator import ( - "testing" + "github.com/yannh/kubeconform/pkg/registry" + "testing" - "github.com/yannh/kubeconform/pkg/registry" - "github.com/yannh/kubeconform/pkg/resource" - - "github.com/xeipuuv/gojsonschema" + "github.com/yannh/kubeconform/pkg/resource" ) + +type mockRegistry struct { + SchemaDownloader func() ([]byte, error) +} + +func newMockRegistry(f func() ([]byte, error)) *mockRegistry { + return &mockRegistry{ + SchemaDownloader: f, + } +} + +func (m mockRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) ([]byte, error) { + return m.SchemaDownloader() +} + + func TestValidate(t *testing.T) { for i, testCase := range []struct { - name string - rawResource, schema []byte - expect Status + name string + rawResource, schemaRegistry1 []byte + schemaRegistry2 []byte + ignoreMissingSchema bool + expect Status }{ { "valid resource", @@ -44,6 +60,8 @@ lastName: bar }, "required": ["firstName", "lastName"] }`), + nil, + false, Valid, }, { @@ -75,6 +93,8 @@ lastName: bar }, "required": ["firstName", "lastName"] }`), + nil, + false, Invalid, }, { @@ -105,6 +125,8 @@ firstName: foo }, "required": ["firstName", "lastName"] }`), + nil, + false, Invalid, }, { @@ -139,6 +161,132 @@ lastName: bar }, "required": ["firstName", "lastName"] }`), + nil, + false, + Error, + }, + { + "missing schema in 1st registry", + []byte(` +kind: name +apiVersion: v1 +firstName: foo +lastName: bar +`), + nil, + []byte(`{ + "title": "Example Schema", + "type": "object", + "properties": { + "kind": { + "type": "string" + }, + "apiVersion": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "age": { + "description": "Age in years", + "type": "integer", + "minimum": 0 + } + }, + "required": ["firstName", "lastName"] +}`), + false, + Valid, + }, + { + "non-json response in 1st registry", + []byte(` +kind: name +apiVersion: v1 +firstName: foo +lastName: bar +`), + []byte(`error page`), + []byte(`{ + "title": "Example Schema", + "type": "object", + "properties": { + "kind": { + "type": "string" + }, + "apiVersion": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "age": { + "description": "Age in years", + "type": "integer", + "minimum": 0 + } + }, + "required": ["firstName", "lastName"] +}`), + false, + Valid, + }, + { + "missing schema in both registries, ignore missing", + []byte(` +kind: name +apiVersion: v1 +firstName: foo +lastName: bar +`), + nil, + nil, + true, + Skipped, + }, + { + "missing schema in both registries, do not ignore missing", + []byte(` +kind: name +apiVersion: v1 +firstName: foo +lastName: bar +`), + nil, + nil, + false, + Error, + }, + { + "non-json response in both registries, ignore missing", + []byte(` +kind: name +apiVersion: v1 +firstName: foo +lastName: bar +`), + []byte(`error page`), + []byte(`error page`), + true, + Skipped, + }, + { + "non-json response in both registries, do not ignore missing", + []byte(` +kind: name +apiVersion: v1 +firstName: foo +lastName: bar +`), + []byte(`error page`), + []byte(`error page`), + false, Error, }, } { @@ -146,16 +294,18 @@ lastName: bar opts: Opts{ SkipKinds: map[string]struct{}{}, RejectKinds: map[string]struct{}{}, + IgnoreMissingSchemas: testCase.ignoreMissingSchema, }, schemaCache: nil, - schemaDownload: func(_ []registry.Registry, _, _, _ string) (*gojsonschema.Schema, error) { - schema, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(testCase.schema)) - if err != nil { - t.Errorf("failed parsing test schema") - } - return schema, nil + schemaDownload: downloadSchema, + regs: []registry.Registry{ + newMockRegistry(func() ([]byte, error) { + return testCase.schemaRegistry1, nil + }), + newMockRegistry(func() ([]byte, error) { + return testCase.schemaRegistry2, nil + }), }, - regs: nil, } if got := val.ValidateResource(resource.Resource{Bytes: testCase.rawResource}); got.Status != testCase.expect { t.Errorf("%d - expected %d, got %d: %s", i, testCase.expect, got.Status, got.Err.Error())