registry/storage/manifeststore.go (120 lines of code) (raw):

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" v1 "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(context.Context, digest.Digest, []byte) (distribution.Manifest, error) // Put creates or updates the given manifest returning the manifest digest. Put(context.Context, distribution.Manifest) (digest.Digest, error) } type manifestStore struct { repository *repository blobStore *linkedBlobStore ctx context.Context schema1Handler ManifestHandler schema2Handler ManifestHandler ocischemaHandler ManifestHandler manifestListHandler ManifestHandler } var _ distribution.ManifestService = &manifestStore{} func (ms *manifestStore) Exists(_ 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, _ ...distribution.ManifestServiceOption) (distribution.Manifest, error) { log := dcontext.GetLogger(ms.ctx) log.Debug("(*manifestStore).Get") 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 } // Guard against retrieving empty content from the storage backend. if len(content) == 0 { return nil, distribution.ErrManifestEmpty{ Name: ms.repository.Named().Name(), Digest: dgst, } } var versioned manifest.Versioned if err = json.Unmarshal(content, &versioned); err != nil { return nil, fmt.Errorf("failed to unmarshal manifest payload: %w", 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) // nolint: revive // unchecked-type-assertion 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)} } default: // This is not a valid manifest, and most likely an example of // https://gitlab.com/gitlab-org/container-registry/-/issues/411. We can't block uploads of manifest // lists/indexes with invalid references until we reach a conclusion on // https://gitlab.com/gitlab-org/container-registry/-/issues/409. Meanwhile, we should not raise a 500 error if // a user attempts to read a config/layer as a manifest. We should act as if it does not exist and return a // 404 Not Found. log.WithField("schema_version", versioned.SchemaVersion).Warn("unrecognized manifest schema version, ignoring manifest") return nil, distribution.ErrManifestUnknownRevision{ Name: ms.repository.Named().Name(), Revision: dgst, } } } func (ms *manifestStore) Put(ctx context.Context, m distribution.Manifest, _ ...distribution.ManifestServiceOption) (digest.Digest, error) { dcontext.GetLogger(ms.ctx).Debug("(*manifestStore).Put") switch m.(type) { case *schema1.SignedManifest: return ms.schema1Handler.Put(ctx, m) case *schema2.DeserializedManifest: return ms.schema2Handler.Put(ctx, m) case *ocischema.DeserializedManifest: return ms.ocischemaHandler.Put(ctx, m) case *manifestlist.DeserializedManifestList: return ms.manifestListHandler.Put(ctx, m) } return "", fmt.Errorf("unrecognized manifest type %T", m) } // 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(desc distribution.Descriptor) error { err := ingester(desc.Digest) if err != nil { return err } return nil }) return err }