From 60d9c5dfad8c0d44fd5ee0482f6de7b09a15fb02 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Wed, 14 Mar 2018 17:22:36 -0400 Subject: [PATCH] Handle OCI manifests and image indexes without a media type In the OCI image specification, the MediaType field is reserved and otherwise undefined; assume that manifests without a media in storage are OCI images or image indexes, and determine which by looking at what fields are in the JSON. We do keep a check that when unmarshalling an OCI image or image index, if it has a MediaType field, it must match that media type of the upload. Signed-off-by: Owen W. Taylor --- manifest/manifestlist/manifestlist.go | 13 ++++++++++--- manifest/manifestlist/manifestlist_test.go | 2 +- manifest/ocischema/manifest.go | 6 +++--- manifest/ocischema/manifest_test.go | 2 +- registry/storage/manifeststore.go | 12 ++++++++++++ 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/manifest/manifestlist/manifestlist.go b/manifest/manifestlist/manifestlist.go index 11df4c250..a08f1e5a0 100644 --- a/manifest/manifestlist/manifestlist.go +++ b/manifest/manifestlist/manifestlist.go @@ -60,8 +60,8 @@ func init() { return nil, distribution.Descriptor{}, err } - if m.MediaType != v1.MediaTypeImageIndex { - err = fmt.Errorf("mediaType in image index should be '%s' not '%s'", + if m.MediaType != "" && m.MediaType != v1.MediaTypeImageIndex { + err = fmt.Errorf("if present, mediaType in image index should be '%s' not '%s'", v1.MediaTypeImageIndex, m.MediaType) return nil, distribution.Descriptor{}, err @@ -205,5 +205,12 @@ func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) { // Payload returns the raw content of the manifest list. The contents can be // used to calculate the content identifier. func (m DeserializedManifestList) Payload() (string, []byte, error) { - return m.MediaType, m.canonical, nil + var mediaType string + if m.MediaType == "" { + mediaType = v1.MediaTypeImageIndex + } else { + mediaType = m.MediaType + } + + return mediaType, m.canonical, nil } diff --git a/manifest/manifestlist/manifestlist_test.go b/manifest/manifestlist/manifestlist_test.go index 00b439006..e72292f07 100644 --- a/manifest/manifestlist/manifestlist_test.go +++ b/manifest/manifestlist/manifestlist_test.go @@ -299,7 +299,7 @@ func TestMediaTypes(t *testing.T) { mediaTypeTest(t, MediaTypeManifestList, "", true) mediaTypeTest(t, MediaTypeManifestList, MediaTypeManifestList, false) mediaTypeTest(t, MediaTypeManifestList, MediaTypeManifestList+"XXX", true) - mediaTypeTest(t, v1.MediaTypeImageIndex, "", true) + mediaTypeTest(t, v1.MediaTypeImageIndex, "", false) mediaTypeTest(t, v1.MediaTypeImageIndex, v1.MediaTypeImageIndex, false) mediaTypeTest(t, v1.MediaTypeImageIndex, v1.MediaTypeImageIndex+"XXX", true) } diff --git a/manifest/ocischema/manifest.go b/manifest/ocischema/manifest.go index a6850fdea..6de17f9ac 100644 --- a/manifest/ocischema/manifest.go +++ b/manifest/ocischema/manifest.go @@ -97,8 +97,8 @@ func (m *DeserializedManifest) UnmarshalJSON(b []byte) error { return err } - if manifest.MediaType != v1.MediaTypeImageManifest { - return fmt.Errorf("mediaType in manifest should be '%s' not '%s'", + if manifest.MediaType != "" && manifest.MediaType != v1.MediaTypeImageManifest { + return fmt.Errorf("if present, mediaType in manifest should be '%s' not '%s'", v1.MediaTypeImageManifest, manifest.MediaType) } @@ -120,5 +120,5 @@ func (m *DeserializedManifest) MarshalJSON() ([]byte, error) { // Payload returns the raw content of the manifest. The contents can be used to // calculate the content identifier. func (m DeserializedManifest) Payload() (string, []byte, error) { - return m.MediaType, m.canonical, nil + return v1.MediaTypeImageManifest, m.canonical, nil } diff --git a/manifest/ocischema/manifest_test.go b/manifest/ocischema/manifest_test.go index de53806d0..c39d1c9fc 100644 --- a/manifest/ocischema/manifest_test.go +++ b/manifest/ocischema/manifest_test.go @@ -178,7 +178,7 @@ func mediaTypeTest(t *testing.T, mediaType string, shouldError bool) { } func TestMediaTypes(t *testing.T) { - mediaTypeTest(t, "", true) + mediaTypeTest(t, "", false) mediaTypeTest(t, v1.MediaTypeImageManifest, false) mediaTypeTest(t, v1.MediaTypeImageManifest+"XXX", true) } diff --git a/registry/storage/manifeststore.go b/registry/storage/manifeststore.go index 6543f6fd6..73bff5733 100644 --- a/registry/storage/manifeststore.go +++ b/registry/storage/manifeststore.go @@ -106,6 +106,18 @@ func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options .. return ms.ocischemaHandler.Unmarshal(ctx, dgst, content) case manifestlist.MediaTypeManifestList, v1.MediaTypeImageIndex: return ms.manifestListHandler.Unmarshal(ctx, dgst, content) + case "": + // OCI image or image index - no media type in the content + + // First see if it looks like an image index + res, err := ms.manifestListHandler.Unmarshal(ctx, dgst, content) + resIndex := res.(*manifestlist.DeserializedManifestList) + if err == nil && resIndex.Manifests != nil { + return resIndex, nil + } + + // Otherwise, assume it must be an image manifest + return ms.ocischemaHandler.Unmarshal(ctx, dgst, content) default: return nil, distribution.ErrManifestVerification{fmt.Errorf("unrecognized manifest content type %s", versioned.MediaType)} }