forked from TrueCloudLab/distribution
60d9c5dfad
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 <otaylor@fishsoup.net>
161 lines
5.1 KiB
Go
161 lines
5.1 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/docker/distribution"
|
|
dcontext "github.com/docker/distribution/context"
|
|
"github.com/docker/distribution/manifest"
|
|
"github.com/docker/distribution/manifest/manifestlist"
|
|
"github.com/docker/distribution/manifest/ocischema"
|
|
"github.com/docker/distribution/manifest/schema1"
|
|
"github.com/docker/distribution/manifest/schema2"
|
|
"github.com/opencontainers/go-digest"
|
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
|
)
|
|
|
|
// A ManifestHandler gets and puts manifests of a particular type.
|
|
type ManifestHandler interface {
|
|
// Unmarshal unmarshals the manifest from a byte slice.
|
|
Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error)
|
|
|
|
// Put creates or updates the given manifest returning the manifest digest.
|
|
Put(ctx context.Context, manifest distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error)
|
|
}
|
|
|
|
// SkipLayerVerification allows a manifest to be Put before its
|
|
// layers are on the filesystem
|
|
func SkipLayerVerification() distribution.ManifestServiceOption {
|
|
return skipLayerOption{}
|
|
}
|
|
|
|
type skipLayerOption struct{}
|
|
|
|
func (o skipLayerOption) Apply(m distribution.ManifestService) error {
|
|
if ms, ok := m.(*manifestStore); ok {
|
|
ms.skipDependencyVerification = true
|
|
return nil
|
|
}
|
|
return fmt.Errorf("skip layer verification only valid for manifestStore")
|
|
}
|
|
|
|
type manifestStore struct {
|
|
repository *repository
|
|
blobStore *linkedBlobStore
|
|
ctx context.Context
|
|
|
|
skipDependencyVerification bool
|
|
|
|
schema1Handler ManifestHandler
|
|
schema2Handler ManifestHandler
|
|
ocischemaHandler ManifestHandler
|
|
manifestListHandler ManifestHandler
|
|
}
|
|
|
|
var _ distribution.ManifestService = &manifestStore{}
|
|
|
|
func (ms *manifestStore) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
|
|
dcontext.GetLogger(ms.ctx).Debug("(*manifestStore).Exists")
|
|
|
|
_, err := ms.blobStore.Stat(ms.ctx, dgst)
|
|
if err != nil {
|
|
if err == distribution.ErrBlobUnknown {
|
|
return false, nil
|
|
}
|
|
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
|
|
dcontext.GetLogger(ms.ctx).Debug("(*manifestStore).Get")
|
|
|
|
// TODO(stevvooe): Need to check descriptor from above to ensure that the
|
|
// mediatype is as we expect for the manifest store.
|
|
|
|
content, err := ms.blobStore.Get(ctx, dgst)
|
|
if err != nil {
|
|
if err == distribution.ErrBlobUnknown {
|
|
return nil, distribution.ErrManifestUnknownRevision{
|
|
Name: ms.repository.Named().Name(),
|
|
Revision: dgst,
|
|
}
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
var versioned manifest.Versioned
|
|
if err = json.Unmarshal(content, &versioned); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch versioned.SchemaVersion {
|
|
case 1:
|
|
return ms.schema1Handler.Unmarshal(ctx, dgst, content)
|
|
case 2:
|
|
// This can be an image manifest or a manifest list
|
|
switch versioned.MediaType {
|
|
case schema2.MediaTypeManifest:
|
|
return ms.schema2Handler.Unmarshal(ctx, dgst, content)
|
|
case v1.MediaTypeImageManifest:
|
|
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)}
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("unrecognized manifest schema version %d", versioned.SchemaVersion)
|
|
}
|
|
|
|
func (ms *manifestStore) Put(ctx context.Context, manifest distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
|
|
dcontext.GetLogger(ms.ctx).Debug("(*manifestStore).Put")
|
|
|
|
switch manifest.(type) {
|
|
case *schema1.SignedManifest:
|
|
return ms.schema1Handler.Put(ctx, manifest, ms.skipDependencyVerification)
|
|
case *schema2.DeserializedManifest:
|
|
return ms.schema2Handler.Put(ctx, manifest, ms.skipDependencyVerification)
|
|
case *ocischema.DeserializedManifest:
|
|
return ms.ocischemaHandler.Put(ctx, manifest, ms.skipDependencyVerification)
|
|
case *manifestlist.DeserializedManifestList:
|
|
return ms.manifestListHandler.Put(ctx, manifest, ms.skipDependencyVerification)
|
|
}
|
|
|
|
return "", fmt.Errorf("unrecognized manifest type %T", manifest)
|
|
}
|
|
|
|
// Delete removes the revision of the specified manifest.
|
|
func (ms *manifestStore) Delete(ctx context.Context, dgst digest.Digest) error {
|
|
dcontext.GetLogger(ms.ctx).Debug("(*manifestStore).Delete")
|
|
return ms.blobStore.Delete(ctx, dgst)
|
|
}
|
|
|
|
func (ms *manifestStore) Enumerate(ctx context.Context, ingester func(digest.Digest) error) error {
|
|
err := ms.blobStore.Enumerate(ctx, func(dgst digest.Digest) error {
|
|
err := ingester(dgst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
return err
|
|
}
|