diff --git a/schema/backwards_compatibility_test.go b/schema/backwards_compatibility_test.go index 46c05c6d3..4db7b8ab5 100644 --- a/schema/backwards_compatibility_test.go +++ b/schema/backwards_compatibility_test.go @@ -116,7 +116,7 @@ func TestBackwardsCompatibilityImageIndex(t *testing.T) { imageIndex := convertFormats(tt.imageIndex) r := strings.NewReader(imageIndex) - err := schema.ValidatorMediaTypeImageIndex.Validate(r) + err := schema.ValidatorMediaTypeImageIndex.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) @@ -178,7 +178,7 @@ func TestBackwardsCompatibilityManifest(t *testing.T) { manifest := convertFormats(tt.manifest) r := strings.NewReader(manifest) - err := schema.ValidatorMediaTypeManifest.Validate(r) + err := schema.ValidatorMediaTypeManifest.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) @@ -217,7 +217,7 @@ func TestBackwardsCompatibilityConfig(t *testing.T) { config := convertFormats(tt.config) r := strings.NewReader(config) - err := schema.ValidatorMediaTypeImageConfig.Validate(r) + err := schema.ValidatorMediaTypeImageConfig.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/config_test.go b/schema/config_test.go index 9961e77b9..637885d58 100644 --- a/schema/config_test.go +++ b/schema/config_test.go @@ -212,7 +212,7 @@ func TestConfig(t *testing.T) { }, } { r := strings.NewReader(tt.config) - err := schema.ValidatorMediaTypeImageConfig.Validate(r) + err := schema.ValidatorMediaTypeImageConfig.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/descriptor_test.go b/schema/descriptor_test.go index 1fe925782..1170f32b0 100644 --- a/schema/descriptor_test.go +++ b/schema/descriptor_test.go @@ -204,7 +204,7 @@ func TestDescriptor(t *testing.T) { }, } { r := strings.NewReader(tt.descriptor) - err := schema.ValidatorMediaTypeDescriptor.Validate(r) + err := schema.ValidatorMediaTypeDescriptor.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/imageindex_test.go b/schema/imageindex_test.go index 8f5c55d47..b46a4a55d 100644 --- a/schema/imageindex_test.go +++ b/schema/imageindex_test.go @@ -239,7 +239,7 @@ func TestImageIndex(t *testing.T) { }, } { r := strings.NewReader(tt.imageIndex) - err := schema.ValidatorMediaTypeImageIndex.Validate(r) + err := schema.ValidatorMediaTypeImageIndex.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 2a6a53529..976257dc5 100644 --- a/schema/manifest_test.go +++ b/schema/manifest_test.go @@ -193,7 +193,7 @@ func TestManifest(t *testing.T) { }, } { r := strings.NewReader(tt.manifest) - err := schema.ValidatorMediaTypeManifest.Validate(r) + err := schema.ValidatorMediaTypeManifest.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 2d56627ab..1d08cdc40 100644 --- a/schema/spec_test.go +++ b/schema/spec_test.go @@ -77,7 +77,8 @@ 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}) 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 1006b510f..ef923b84d 100644 --- a/schema/validator.go +++ b/schema/validator.go @@ -26,14 +26,42 @@ import ( "github.com/xeipuuv/gojsonschema" ) +type validateRefMediaFunc func(r io.Reader) error + +var validateRefMediaMap = map[Validator]validateRefMediaFunc{ + ValidatorMediaTypeImageIndex: validateImageIndexRefObject, + ValidatorMediaTypeManifest: validateManifestRefObject, +} + +// ValidateFunc provides the type of function to validate specific object. +type ValidateFunc func(r io.Reader, v Validator) error + // Validator wraps a media type string identifier // and implements validation against a JSON schema. type Validator string -type validateDescendantsFunc func(r io.Reader) error +// Validate validates the given read r to memory and call funcList on the content. +// 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") + } + + for _, f := range funcList { + err := f(bytes.NewReader(buf), v) + if err != nil { + return err + } + } + return nil +} + +type unimplemented string -var mapValidateDescendants = map[Validator]validateDescendantsFunc{ - ValidatorMediaTypeManifest: validateManifestDescendants, +func (v unimplemented) Validate(src io.Reader) error { + return fmt.Errorf("%s: unimplemented", v) } // ValidationError contains all the errors that happened during validation. @@ -45,23 +73,15 @@ 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 { +// ValidateSchema validates the given reader against the schema of the wrapped media type. +// src: the given reader. +// v: the expected media type. +func ValidateSchema(src io.Reader, v Validator) error { buf, err := ioutil.ReadAll(src) 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)) - if err != nil { - return err - } - } - sl := gojsonschema.NewReferenceLoaderFileSystem("file:///"+specs[v], fs) ml := gojsonschema.NewStringLoader(string(buf)) @@ -86,16 +106,26 @@ func (v Validator) Validate(src io.Reader) error { } } -type unimplemented string +// ValidateRefMedia validates the referenced objects against OCI media type defined in spec. +// src: the given reader. +// v: the expected media type. +func ValidateRefMedia(src io.Reader, v Validator) error { + f, ok := validateRefMediaMap[v] + if ok { + if f == nil { + return fmt.Errorf("referenced media type validation %q unimplemented", v) + } -func (v unimplemented) Validate(src io.Reader) error { - return fmt.Errorf("%s: unimplemented", v) + return f(src) + } + + return nil } -func validateManifestDescendants(r io.Reader) error { +func validateManifestRefObject(src io.Reader) error { header := v1.Manifest{} - buf, err := ioutil.ReadAll(r) + buf, err := ioutil.ReadAll(src) if err != nil { return errors.Wrapf(err, "error reading the io stream") } @@ -119,3 +149,24 @@ func validateManifestDescendants(r io.Reader) error { } return nil } + +func validateImageIndexRefObject(src io.Reader) error { + header := v1.ImageIndex{} + + buf, err := ioutil.ReadAll(src) + 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 +}