From 21ef4e86646f400f32186b1f3c5527d3d7cab89e Mon Sep 17 00:00:00 2001 From: xiekeyang Date: Thu, 24 Nov 2016 15:46:12 +0800 Subject: [PATCH] support different levels validation of JSON reader stream Sometimes consumer want to strictly validate specific fields in OCI JSON file. So it support different levels validation to reader stream. Validator organizes the level functions to array, and provide the optional argument. Signed-off-by: xiekeyang --- schema/config_test.go | 2 +- .../manifest_backwards_compatibility_test.go | 6 +- schema/manifest_test.go | 2 +- schema/spec_test.go | 2 +- schema/validator.go | 90 +++++++++++++++---- 5 files changed, 79 insertions(+), 23 deletions(-) diff --git a/schema/config_test.go b/schema/config_test.go index 27c14875c..9f21f67cc 100644 --- a/schema/config_test.go +++ b/schema/config_test.go @@ -156,7 +156,7 @@ func TestConfig(t *testing.T) { }, } { r := strings.NewReader(tt.config) - err := schema.MediaTypeImageConfig.Validate(r) + err := schema.MediaTypeImageConfig.Validate(r, []schema.ValidateFunc{schema.ValidateSchema}) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) diff --git a/schema/manifest_backwards_compatibility_test.go b/schema/manifest_backwards_compatibility_test.go index 3cf43ce63..437d894fc 100644 --- a/schema/manifest_backwards_compatibility_test.go +++ b/schema/manifest_backwards_compatibility_test.go @@ -118,7 +118,7 @@ func TestBackwardsCompatibilityManifestList(t *testing.T) { manifest := convertFormats(tt.manifest) r := strings.NewReader(manifest) - err := schema.MediaTypeManifestList.Validate(r) + err := schema.MediaTypeManifestList.Validate(r, []schema.ValidateFunc{schema.ValidateSchema}) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) @@ -181,7 +181,7 @@ func TestBackwardsCompatibilityManifest(t *testing.T) { manifest := convertFormats(tt.manifest) r := strings.NewReader(manifest) - err := schema.MediaTypeManifest.Validate(r) + err := schema.MediaTypeManifest.Validate(r, []schema.ValidateFunc{schema.ValidateSchema}) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) @@ -221,7 +221,7 @@ func TestBackwardsCompatibilityConfig(t *testing.T) { config := convertFormats(tt.config) r := strings.NewReader(config) - err := schema.MediaTypeImageConfig.Validate(r) + err := schema.MediaTypeImageConfig.Validate(r, []schema.ValidateFunc{schema.ValidateSchema}) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) diff --git a/schema/manifest_test.go b/schema/manifest_test.go index c4a6a8015..48e561af7 100644 --- a/schema/manifest_test.go +++ b/schema/manifest_test.go @@ -144,7 +144,7 @@ func TestManifest(t *testing.T) { }, } { r := strings.NewReader(tt.manifest) - err := schema.MediaTypeManifest.Validate(r) + err := schema.MediaTypeManifest.Validate(r, []schema.ValidateFunc{schema.ValidateSchema}) if got := err != nil; tt.fail != got { t.Errorf("test %d: expected validation failure %t but got %t, err %v", i, tt.fail, got, err) diff --git a/schema/spec_test.go b/schema/spec_test.go index 2a6f4a493..dfcf1e2a5 100644 --- a/schema/spec_test.go +++ b/schema/spec_test.go @@ -73,7 +73,7 @@ func validate(t *testing.T, name string) { continue } - err = schema.Validator(example.Mediatype).Validate(strings.NewReader(example.Body)) + err = schema.Validator(example.Mediatype).Validate(strings.NewReader(example.Body), []schema.ValidateFunc{schema.ValidateSchema, schema.ValidateRefMedia}) if err == nil { printFields(t, "ok", example.Mediatype, example.Title) t.Log(example.Body, "---") diff --git a/schema/validator.go b/schema/validator.go index 432e7b992..a77dc022e 100644 --- a/schema/validator.go +++ b/schema/validator.go @@ -30,12 +30,6 @@ import ( // and implements validation against a JSON schema. type Validator string -type validateDescendantsFunc func(r io.Reader) error - -var mapValidateDescendants = map[Validator]validateDescendantsFunc{ - MediaTypeManifest: validateManifestDescendants, -} - // ValidationError contains all the errors that happened during validation. type ValidationError struct { Errs []error @@ -45,22 +39,35 @@ func (e ValidationError) Error() string { return fmt.Sprintf("%v", e.Errs) } -// Validate validates the given reader against the schema of the wrapped media type. -func (v Validator) Validate(src io.Reader) error { - buf, err := ioutil.ReadAll(src) +// ValidateFunc provides the type of function to validate specific object. +type ValidateFunc func(r io.Reader, v Validator) error + +// Validate validates the given reader against the schema and OCI format defined in spec. +// r: the given reader. +// funcList: the optional functions to validate the wrapped media type. +func (v Validator) Validate(r io.Reader, funcList []ValidateFunc) error { + buf, err := ioutil.ReadAll(r) if err != nil { return errors.Wrap(err, "unable to read the document file") } - if f, ok := mapValidateDescendants[v]; ok { - if f == nil { - return fmt.Errorf("internal error: mapValidateDescendents[%q] is nil", v) - } - err = f(bytes.NewReader(buf)) + for _, f := range funcList { + err := f(bytes.NewReader(buf), v) if err != nil { return err } } + return nil +} + +// ValidateSchema validates the given reader against the schema of the wrapped media type. +// r: the given reader. +// v: the expected media type. +func ValidateSchema(r io.Reader, v Validator) error { + buf, err := ioutil.ReadAll(r) + if err != nil { + return errors.Wrap(err, "unable to read the document file") + } sl := gojsonschema.NewReferenceLoaderFileSystem("file:///"+specs[v], fs) ml := gojsonschema.NewStringLoader(string(buf)) @@ -92,7 +99,56 @@ func (v unimplemented) Validate(src io.Reader) error { return fmt.Errorf("%s: unimplemented", v) } -func validateManifestDescendants(r io.Reader) error { +type validateRefMediaFunc func(r io.Reader) error + +var vlidateRefMediaMap = map[Validator]validateRefMediaFunc{ + MediaTypeManifestList: validateManifestListRefObject, + MediaTypeManifest: validateManifestRefObject, +} + +// ValidateRefMedia validates the referenced objects against OCI media type defined in spec. +// r: the given reader. +// v: the expected media type. +func ValidateRefMedia(r io.Reader, v Validator) error { + f, ok := vlidateRefMediaMap[v] + if ok { + if f == nil { + return fmt.Errorf("referenced media type validation %q unimplemented", v) + } + + buf, err := ioutil.ReadAll(r) + if err != nil { + return errors.Wrap(err, "unable to read the document file") + } + + return f(bytes.NewReader(buf)) + } + + return nil +} + +func validateManifestListRefObject(r io.Reader) error { + header := v1.ManifestList{} + + buf, err := ioutil.ReadAll(r) + if err != nil { + return errors.Wrapf(err, "error reading the io stream") + } + + err = json.Unmarshal(buf, &header) + if err != nil { + return errors.Wrap(err, "manifest list format mismatch") + } + + for _, manifest := range header.Manifests { + if manifest.MediaType != string(v1.MediaTypeImageManifest) { + return fmt.Errorf("manifest %s has an unknown media type: %s", manifest.Digest, manifest.MediaType) + } + } + return nil +} + +func validateManifestRefObject(r io.Reader) error { header := v1.Manifest{} buf, err := ioutil.ReadAll(r) @@ -106,13 +162,13 @@ func validateManifestDescendants(r io.Reader) error { } if header.Config.MediaType != string(v1.MediaTypeImageConfig) { - fmt.Printf("warning: config %s has an unknown media type: %s\n", header.Config.Digest, header.Config.MediaType) + return fmt.Errorf("config %s has an unknown media type: %s", header.Config.Digest, header.Config.MediaType) } for _, layer := range header.Layers { if layer.MediaType != string(v1.MediaTypeImageLayer) && layer.MediaType != string(v1.MediaTypeImageLayerNonDistributable) { - fmt.Printf("warning: layer %s has an unknown media type: %s\n", layer.Digest, layer.MediaType) + return fmt.Errorf("layer %s has an unknown media type: %s", layer.Digest, layer.MediaType) } } return nil