Split OCI Image Index from Docker Manifest List

Move implementation of the index from the manifestlist package to the ocischema package so that other modules making empty imports support the manifest types their authors would expect. This is a breaking change to distribution as a library but not the registry.

As OCI 1.0 released the manifest and index together, that is a good package from which to initialise both manifests. The docker manifest and manifest list remain in separate packages because one was released later.

The image index and manifest list still share common code in many functions not intended for import by other modules.

Signed-off-by: Bracken Dawson <abdawson@gmail.com>
This commit is contained in:
Bracken Dawson
2023-03-31 11:35:30 +01:00
parent 0c958010ac
commit e72294d075
11 changed files with 569 additions and 322 deletions

View File

@@ -7,6 +7,7 @@ import (
"github.com/distribution/distribution/v3"
dcontext "github.com/distribution/distribution/v3/context"
"github.com/distribution/distribution/v3/manifest/manifestlist"
"github.com/distribution/distribution/v3/manifest/ocischema"
"github.com/opencontainers/go-digest"
)
@@ -33,16 +34,24 @@ func (ms *manifestListHandler) Unmarshal(ctx context.Context, dgst digest.Digest
func (ms *manifestListHandler) Put(ctx context.Context, manifestList distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) {
dcontext.GetLogger(ms.ctx).Debug("(*manifestListHandler).Put")
m, ok := manifestList.(*manifestlist.DeserializedManifestList)
if !ok {
var schemaVersion int
switch m := manifestList.(type) {
case *manifestlist.DeserializedManifestList:
schemaVersion = m.SchemaVersion
case *ocischema.DeserializedImageIndex:
schemaVersion = m.SchemaVersion
default:
return "", fmt.Errorf("wrong type put to manifestListHandler: %T", manifestList)
}
if schemaVersion != 2 {
return "", fmt.Errorf("unrecognized manifest list schema version %d", schemaVersion)
}
if err := ms.verifyManifest(ms.ctx, *m, skipDependencyVerification); err != nil {
if err := ms.verifyManifest(ms.ctx, manifestList, skipDependencyVerification); err != nil {
return "", err
}
mt, payload, err := m.Payload()
mt, payload, err := manifestList.Payload()
if err != nil {
return "", err
}
@@ -60,13 +69,9 @@ func (ms *manifestListHandler) Put(ctx context.Context, manifestList distributio
// perspective of the registry. As a policy, the registry only tries to
// store valid content, leaving trust policies of that content up to
// consumers.
func (ms *manifestListHandler) verifyManifest(ctx context.Context, mnfst manifestlist.DeserializedManifestList, skipDependencyVerification bool) error {
func (ms *manifestListHandler) verifyManifest(ctx context.Context, mnfst distribution.Manifest, skipDependencyVerification bool) error {
var errs distribution.ErrManifestVerification
if mnfst.SchemaVersion != 2 {
return fmt.Errorf("unrecognized manifest list schema version %d", mnfst.SchemaVersion)
}
if !skipDependencyVerification {
// This manifest service is different from the blob service
// returned by Blob. It uses a linked blob store to ensure that

View File

@@ -48,10 +48,11 @@ type manifestStore struct {
skipDependencyVerification bool
schema1Handler ManifestHandler
schema2Handler ManifestHandler
ocischemaHandler ManifestHandler
manifestListHandler ManifestHandler
schema1Handler ManifestHandler
schema2Handler ManifestHandler
manifestListHandler ManifestHandler
ocischemaHandler ManifestHandler
ocischemaIndexHandler ManifestHandler
}
var _ distribution.ManifestService = &manifestStore{}
@@ -104,14 +105,16 @@ func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options ..
return ms.schema2Handler.Unmarshal(ctx, dgst, content)
case v1.MediaTypeImageManifest:
return ms.ocischemaHandler.Unmarshal(ctx, dgst, content)
case manifestlist.MediaTypeManifestList, v1.MediaTypeImageIndex:
case manifestlist.MediaTypeManifestList:
return ms.manifestListHandler.Unmarshal(ctx, dgst, content)
case v1.MediaTypeImageIndex:
return ms.ocischemaIndexHandler.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)
res, err := ms.ocischemaIndexHandler.Unmarshal(ctx, dgst, content)
resIndex := res.(*ocischema.DeserializedImageIndex)
if err == nil && resIndex.Manifests != nil {
return resIndex, nil
}
@@ -138,6 +141,8 @@ func (ms *manifestStore) Put(ctx context.Context, manifest distribution.Manifest
return ms.ocischemaHandler.Put(ctx, manifest, ms.skipDependencyVerification)
case *manifestlist.DeserializedManifestList:
return ms.manifestListHandler.Put(ctx, manifest, ms.skipDependencyVerification)
case *ocischema.DeserializedImageIndex:
return ms.ocischemaIndexHandler.Put(ctx, manifest, ms.skipDependencyVerification)
}
return "", fmt.Errorf("unrecognized manifest type %T", manifest)

View File

@@ -3,13 +3,13 @@ package storage
import (
"bytes"
"context"
"encoding/json"
"io"
"reflect"
"testing"
"github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/manifestlist"
"github.com/distribution/distribution/v3/manifest/ocischema"
"github.com/distribution/distribution/v3/manifest/schema1"
"github.com/distribution/distribution/v3/reference"
@@ -465,19 +465,19 @@ func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes boo
}
descriptor.MediaType = v1.MediaTypeImageManifest
platformSpec := manifestlist.PlatformSpec{
platformSpec := v1.Platform{
Architecture: "atari2600",
OS: "CP/M",
}
manifestDescriptors := []manifestlist.ManifestDescriptor{
manifestDescriptors := []ocischema.ManifestDescriptor{
{
Descriptor: descriptor,
Platform: platformSpec,
Platform: &platformSpec,
},
}
imageIndex, err := manifestlist.FromDescriptorsWithMediaType(manifestDescriptors, indexMediaType)
imageIndex, err := ociIndexFromDesriptorsWithMediaType(manifestDescriptors, indexMediaType)
if err != nil {
t.Fatalf("%s: unexpected error creating image index: %v", testname, err)
}
@@ -523,7 +523,7 @@ func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes boo
t.Fatalf("%s: unexpected error fetching image index: %v", testname, err)
}
fetchedIndex, ok := fromStore.(*manifestlist.DeserializedManifestList)
fetchedIndex, ok := fromStore.(*ocischema.DeserializedImageIndex)
if !ok {
t.Fatalf("%s: unexpected type for fetched manifest", testname)
}
@@ -574,3 +574,23 @@ func TestLinkPathFuncs(t *testing.T) {
}
}
}
func ociIndexFromDesriptorsWithMediaType(descriptors []ocischema.ManifestDescriptor, mediaType string) (*ocischema.DeserializedImageIndex, error) {
manifest, err := ocischema.FromDescriptors(descriptors)
if err != nil {
return nil, err
}
manifest.ImageIndex.MediaType = mediaType
rawManifest, err := json.Marshal(manifest.ImageIndex)
if err != nil {
return nil, err
}
var d ocischema.DeserializedImageIndex
if err := d.UnmarshalJSON(rawManifest); err != nil {
return nil, err
}
return &d, nil
}

View File

@@ -0,0 +1,28 @@
package storage
import (
"context"
"github.com/distribution/distribution/v3"
dcontext "github.com/distribution/distribution/v3/context"
"github.com/distribution/distribution/v3/manifest/ocischema"
"github.com/opencontainers/go-digest"
)
// ocischemaIndexHandler is a ManifestHandler that covers the OCI Image Index.
type ocischemaIndexHandler struct {
*manifestListHandler
}
var _ ManifestHandler = &manifestListHandler{}
func (ms *ocischemaIndexHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) {
dcontext.GetLogger(ms.ctx).Debug("(*ociIndexHandler).Unmarshal")
m := &ocischema.DeserializedImageIndex{}
if err := m.UnmarshalJSON(content); err != nil {
return nil, err
}
return m, nil
}

View File

@@ -259,6 +259,12 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M
}
}
manifestListHandler := &manifestListHandler{
ctx: ctx,
repository: repo,
blobStore: blobStore,
}
ms := &manifestStore{
ctx: ctx,
repository: repo,
@@ -270,17 +276,16 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M
blobStore: blobStore,
manifestURLs: repo.registry.manifestURLs,
},
manifestListHandler: &manifestListHandler{
ctx: ctx,
repository: repo,
blobStore: blobStore,
},
manifestListHandler: manifestListHandler,
ocischemaHandler: &ocischemaManifestHandler{
ctx: ctx,
repository: repo,
blobStore: blobStore,
manifestURLs: repo.registry.manifestURLs,
},
ocischemaIndexHandler: &ocischemaIndexHandler{
manifestListHandler: manifestListHandler,
},
}
// Apply options