Merge pull request #864 from stevvooe/use-correct-manifest-link
registry/storage: use correct manifest link
This commit is contained in:
commit
9174fc62d2
5 changed files with 127 additions and 43 deletions
|
@ -11,6 +11,10 @@ import (
|
||||||
"github.com/docker/distribution/uuid"
|
"github.com/docker/distribution/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// linkPathFunc describes a function that can resolve a link based on the
|
||||||
|
// repository name and digest.
|
||||||
|
type linkPathFunc func(pm *pathMapper, name string, dgst digest.Digest) (string, error)
|
||||||
|
|
||||||
// linkedBlobStore provides a full BlobService that namespaces the blobs to a
|
// linkedBlobStore provides a full BlobService that namespaces the blobs to a
|
||||||
// given repository. Effectively, it manages the links in a given repository
|
// given repository. Effectively, it manages the links in a given repository
|
||||||
// that grant access to the global blob store.
|
// that grant access to the global blob store.
|
||||||
|
@ -23,11 +27,13 @@ type linkedBlobStore struct {
|
||||||
deleteEnabled bool
|
deleteEnabled bool
|
||||||
resumableDigestEnabled bool
|
resumableDigestEnabled bool
|
||||||
|
|
||||||
// linkPath allows one to control the repository blob link set to which
|
// linkPathFns specifies one or more path functions allowing one to
|
||||||
// the blob store dispatches. This is required because manifest and layer
|
// control the repository blob link set to which the blob store
|
||||||
// blobs have not yet been fully merged. At some point, this functionality
|
// dispatches. This is required because manifest and layer blobs have not
|
||||||
// should be removed an the blob links folder should be merged.
|
// yet been fully merged. At some point, this functionality should be
|
||||||
linkPath func(pm *pathMapper, name string, dgst digest.Digest) (string, error)
|
// removed an the blob links folder should be merged. The first entry is
|
||||||
|
// treated as the "canonical" link location and will be used for writes.
|
||||||
|
linkPathFns []linkPathFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ distribution.BlobStore = &linkedBlobStore{}
|
var _ distribution.BlobStore = &linkedBlobStore{}
|
||||||
|
@ -213,13 +219,16 @@ func (lbs *linkedBlobStore) linkBlob(ctx context.Context, canonical distribution
|
||||||
// Don't make duplicate links.
|
// Don't make duplicate links.
|
||||||
seenDigests := make(map[digest.Digest]struct{}, len(dgsts))
|
seenDigests := make(map[digest.Digest]struct{}, len(dgsts))
|
||||||
|
|
||||||
|
// only use the first link
|
||||||
|
linkPathFn := lbs.linkPathFns[0]
|
||||||
|
|
||||||
for _, dgst := range dgsts {
|
for _, dgst := range dgsts {
|
||||||
if _, seen := seenDigests[dgst]; seen {
|
if _, seen := seenDigests[dgst]; seen {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenDigests[dgst] = struct{}{}
|
seenDigests[dgst] = struct{}{}
|
||||||
|
|
||||||
blobLinkPath, err := lbs.linkPath(lbs.pm, lbs.repository.Name(), dgst)
|
blobLinkPath, err := linkPathFn(lbs.pm, lbs.repository.Name(), dgst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -236,33 +245,43 @@ type linkedBlobStatter struct {
|
||||||
*blobStore
|
*blobStore
|
||||||
repository distribution.Repository
|
repository distribution.Repository
|
||||||
|
|
||||||
// linkPath allows one to control the repository blob link set to which
|
// linkPathFns specifies one or more path functions allowing one to
|
||||||
// the blob store dispatches. This is required because manifest and layer
|
// control the repository blob link set to which the blob store
|
||||||
// blobs have not yet been fully merged. At some point, this functionality
|
// dispatches. This is required because manifest and layer blobs have not
|
||||||
// should be removed an the blob links folder should be merged.
|
// yet been fully merged. At some point, this functionality should be
|
||||||
linkPath func(pm *pathMapper, name string, dgst digest.Digest) (string, error)
|
// removed an the blob links folder should be merged. The first entry is
|
||||||
|
// treated as the "canonical" link location and will be used for writes.
|
||||||
|
linkPathFns []linkPathFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ distribution.BlobDescriptorService = &linkedBlobStatter{}
|
var _ distribution.BlobDescriptorService = &linkedBlobStatter{}
|
||||||
|
|
||||||
func (lbs *linkedBlobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
func (lbs *linkedBlobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||||
blobLinkPath, err := lbs.linkPath(lbs.pm, lbs.repository.Name(), dgst)
|
var (
|
||||||
if err != nil {
|
resolveErr error
|
||||||
return distribution.Descriptor{}, err
|
target digest.Digest
|
||||||
|
)
|
||||||
|
|
||||||
|
// try the many link path functions until we get success or an error that
|
||||||
|
// is not PathNotFoundError.
|
||||||
|
for _, linkPathFn := range lbs.linkPathFns {
|
||||||
|
var err error
|
||||||
|
target, err = lbs.resolveWithLinkFunc(ctx, dgst, linkPathFn)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
break // success!
|
||||||
}
|
}
|
||||||
|
|
||||||
target, err := lbs.blobStore.readlink(ctx, blobLinkPath)
|
|
||||||
if err != nil {
|
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case driver.PathNotFoundError:
|
case driver.PathNotFoundError:
|
||||||
return distribution.Descriptor{}, distribution.ErrBlobUnknown
|
resolveErr = distribution.ErrBlobUnknown // move to the next linkPathFn, saving the error
|
||||||
default:
|
default:
|
||||||
return distribution.Descriptor{}, err
|
return distribution.Descriptor{}, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(stevvooe): For backwards compatibility with data in "_layers", we
|
if resolveErr != nil {
|
||||||
// need to hit layerLinkPath, as well. Or, somehow migrate to the new path
|
return distribution.Descriptor{}, resolveErr
|
||||||
// layout.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if target != dgst {
|
if target != dgst {
|
||||||
|
@ -276,13 +295,38 @@ func (lbs *linkedBlobStatter) Stat(ctx context.Context, dgst digest.Digest) (dis
|
||||||
return lbs.blobStore.statter.Stat(ctx, target)
|
return lbs.blobStore.statter.Stat(ctx, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lbs *linkedBlobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
|
func (lbs *linkedBlobStatter) Clear(ctx context.Context, dgst digest.Digest) (err error) {
|
||||||
blobLinkPath, err := lbs.linkPath(lbs.pm, lbs.repository.Name(), dgst)
|
// clear any possible existence of a link described in linkPathFns
|
||||||
|
for _, linkPathFn := range lbs.linkPathFns {
|
||||||
|
blobLinkPath, err := linkPathFn(lbs.pm, lbs.repository.Name(), dgst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return lbs.blobStore.driver.Delete(ctx, blobLinkPath)
|
err = lbs.blobStore.driver.Delete(ctx, blobLinkPath)
|
||||||
|
if err != nil {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case driver.PathNotFoundError:
|
||||||
|
continue // just ignore this error and continue
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveTargetWithFunc allows us to read a link to a resource with different
|
||||||
|
// linkPathFuncs to let us try a few different paths before returning not
|
||||||
|
// found.
|
||||||
|
func (lbs *linkedBlobStatter) resolveWithLinkFunc(ctx context.Context, dgst digest.Digest, linkPathFn linkPathFunc) (digest.Digest, error) {
|
||||||
|
blobLinkPath, err := linkPathFn(lbs.pm, lbs.repository.Name(), dgst)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lbs.blobStore.readlink(ctx, blobLinkPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lbs *linkedBlobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
func (lbs *linkedBlobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
||||||
|
@ -297,5 +341,5 @@ func blobLinkPath(pm *pathMapper, name string, dgst digest.Digest) (string, erro
|
||||||
|
|
||||||
// manifestRevisionLinkPath provides the path to the manifest revision link.
|
// manifestRevisionLinkPath provides the path to the manifest revision link.
|
||||||
func manifestRevisionLinkPath(pm *pathMapper, name string, dgst digest.Digest) (string, error) {
|
func manifestRevisionLinkPath(pm *pathMapper, name string, dgst digest.Digest) (string, error) {
|
||||||
return pm.path(layerLinkPathSpec{name: name, digest: dgst})
|
return pm.path(manifestRevisionLinkPathSpec{name: name, revision: dgst})
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,3 +362,37 @@ func TestManifestStorage(t *testing.T) {
|
||||||
t.Errorf("Unexpected success deleting while disabled")
|
t.Errorf("Unexpected success deleting while disabled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLinkPathFuncs ensures that the link path functions behavior are locked
|
||||||
|
// down and implemented as expected.
|
||||||
|
func TestLinkPathFuncs(t *testing.T) {
|
||||||
|
for _, testcase := range []struct {
|
||||||
|
repo string
|
||||||
|
digest digest.Digest
|
||||||
|
linkPathFn linkPathFunc
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
repo: "foo/bar",
|
||||||
|
digest: "sha256:deadbeaf",
|
||||||
|
linkPathFn: blobLinkPath,
|
||||||
|
expected: "/docker/registry/v2/repositories/foo/bar/_layers/sha256/deadbeaf/link",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
repo: "foo/bar",
|
||||||
|
digest: "sha256:deadbeaf",
|
||||||
|
linkPathFn: manifestRevisionLinkPath,
|
||||||
|
expected: "/docker/registry/v2/repositories/foo/bar/_manifests/revisions/sha256/deadbeaf/link",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
p, err := testcase.linkPathFn(defaultPathMapper, testcase.repo, testcase.digest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error calling linkPathFn(pm, %q, %q): %v", testcase.repo, testcase.digest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p != testcase.expected {
|
||||||
|
t.Fatalf("incorrect path returned: %q != %q", p, testcase.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -108,6 +108,13 @@ func (repo *repository) Name() string {
|
||||||
// may be context sensitive in the future. The instance should be used similar
|
// may be context sensitive in the future. The instance should be used similar
|
||||||
// to a request local.
|
// to a request local.
|
||||||
func (repo *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
|
func (repo *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
|
||||||
|
manifestLinkPathFns := []linkPathFunc{
|
||||||
|
// NOTE(stevvooe): Need to search through multiple locations since
|
||||||
|
// 2.1.0 unintentionally linked into _layers.
|
||||||
|
manifestRevisionLinkPath,
|
||||||
|
blobLinkPath,
|
||||||
|
}
|
||||||
|
|
||||||
ms := &manifestStore{
|
ms := &manifestStore{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
repository: repo,
|
repository: repo,
|
||||||
|
@ -122,12 +129,12 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M
|
||||||
blobAccessController: &linkedBlobStatter{
|
blobAccessController: &linkedBlobStatter{
|
||||||
blobStore: repo.blobStore,
|
blobStore: repo.blobStore,
|
||||||
repository: repo,
|
repository: repo,
|
||||||
linkPath: manifestRevisionLinkPath,
|
linkPathFns: manifestLinkPathFns,
|
||||||
},
|
},
|
||||||
|
|
||||||
// TODO(stevvooe): linkPath limits this blob store to only
|
// TODO(stevvooe): linkPath limits this blob store to only
|
||||||
// manifests. This instance cannot be used for blob checks.
|
// manifests. This instance cannot be used for blob checks.
|
||||||
linkPath: manifestRevisionLinkPath,
|
linkPathFns: manifestLinkPathFns,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tagStore: &tagStore{
|
tagStore: &tagStore{
|
||||||
|
@ -155,7 +162,7 @@ func (repo *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
||||||
var statter distribution.BlobDescriptorService = &linkedBlobStatter{
|
var statter distribution.BlobDescriptorService = &linkedBlobStatter{
|
||||||
blobStore: repo.blobStore,
|
blobStore: repo.blobStore,
|
||||||
repository: repo,
|
repository: repo,
|
||||||
linkPath: blobLinkPath,
|
linkPathFns: []linkPathFunc{blobLinkPath},
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.descriptorCache != nil {
|
if repo.descriptorCache != nil {
|
||||||
|
@ -171,7 +178,7 @@ func (repo *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
||||||
|
|
||||||
// TODO(stevvooe): linkPath limits this blob store to only layers.
|
// TODO(stevvooe): linkPath limits this blob store to only layers.
|
||||||
// This instance cannot be used for manifest checks.
|
// This instance cannot be used for manifest checks.
|
||||||
linkPath: blobLinkPath,
|
linkPathFns: []linkPathFunc{blobLinkPath},
|
||||||
deleteEnabled: repo.registry.deleteEnabled,
|
deleteEnabled: repo.registry.deleteEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,8 +134,8 @@ func (s *signatureStore) linkedBlobStore(ctx context.Context, revision digest.Di
|
||||||
blobAccessController: &linkedBlobStatter{
|
blobAccessController: &linkedBlobStatter{
|
||||||
blobStore: s.blobStore,
|
blobStore: s.blobStore,
|
||||||
repository: s.repository,
|
repository: s.repository,
|
||||||
linkPath: linkpath,
|
linkPathFns: []linkPathFunc{linkpath},
|
||||||
},
|
},
|
||||||
linkPath: linkpath,
|
linkPathFns: []linkPathFunc{linkpath},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ func (ts *tagStore) delete(tag string) error {
|
||||||
return ts.blobStore.driver.Delete(ts.ctx, tagPath)
|
return ts.blobStore.driver.Delete(ts.ctx, tagPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// namedBlobStore returns the namedBlobStore for the named tag, allowing one
|
// linkedBlobStore returns the linkedBlobStore for the named tag, allowing one
|
||||||
// to index manifest blobs by tag name. While the tag store doesn't map
|
// to index manifest blobs by tag name. While the tag store doesn't map
|
||||||
// precisely to the linked blob store, using this ensures the links are
|
// precisely to the linked blob store, using this ensures the links are
|
||||||
// managed via the same code path.
|
// managed via the same code path.
|
||||||
|
@ -131,13 +131,12 @@ func (ts *tagStore) linkedBlobStore(ctx context.Context, tag string) *linkedBlob
|
||||||
blobStore: ts.blobStore,
|
blobStore: ts.blobStore,
|
||||||
repository: ts.repository,
|
repository: ts.repository,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
linkPath: func(pm *pathMapper, name string, dgst digest.Digest) (string, error) {
|
linkPathFns: []linkPathFunc{func(pm *pathMapper, name string, dgst digest.Digest) (string, error) {
|
||||||
return pm.path(manifestTagIndexEntryLinkPathSpec{
|
return pm.path(manifestTagIndexEntryLinkPathSpec{
|
||||||
name: name,
|
name: name,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
revision: dgst,
|
revision: dgst,
|
||||||
})
|
})
|
||||||
},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue