diff --git a/blobs.go b/blobs.go index d1253301..400bc346 100644 --- a/blobs.go +++ b/blobs.go @@ -192,6 +192,15 @@ type BlobCreateOption interface { Apply(interface{}) error } +// CreateOptions is a collection of blob creation modifiers relevant to general +// blob storage intended to be configured by the BlobCreateOption.Apply method. +type CreateOptions struct { + Mount struct { + ShouldMount bool + From reference.Canonical + } +} + // BlobWriter provides a handle for inserting data into a blob store. // Instances should be obtained from BlobWriteService.Writer and // BlobWriteService.Resume. If supported by the store, a writer can be diff --git a/registry/client/repository.go b/registry/client/repository.go index 97312556..0d405077 100644 --- a/registry/client/repository.go +++ b/registry/client/repository.go @@ -680,15 +680,6 @@ func (bs *blobs) Put(ctx context.Context, mediaType string, p []byte) (distribut return writer.Commit(ctx, desc) } -// createOptions is a collection of blob creation modifiers relevant to general -// blob storage intended to be configured by the BlobCreateOption.Apply method. -type createOptions struct { - Mount struct { - ShouldMount bool - From reference.Canonical - } -} - type optionFunc func(interface{}) error func (f optionFunc) Apply(v interface{}) error { @@ -699,7 +690,7 @@ func (f optionFunc) Apply(v interface{}) error { // mounted from the given canonical reference. func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption { return optionFunc(func(v interface{}) error { - opts, ok := v.(*createOptions) + opts, ok := v.(*distribution.CreateOptions) if !ok { return fmt.Errorf("unexpected options type: %T", v) } @@ -712,7 +703,7 @@ func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption { } func (bs *blobs) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { - var opts createOptions + var opts distribution.CreateOptions for _, option := range options { err := option.Apply(&opts) diff --git a/registry/storage/linkedblobstore.go b/registry/storage/linkedblobstore.go index d254bbb8..ebd305b3 100644 --- a/registry/storage/linkedblobstore.go +++ b/registry/storage/linkedblobstore.go @@ -101,15 +101,6 @@ func (lbs *linkedBlobStore) Put(ctx context.Context, mediaType string, p []byte) return desc, lbs.linkBlob(ctx, desc) } -// createOptions is a collection of blob creation modifiers relevant to general -// blob storage intended to be configured by the BlobCreateOption.Apply method. -type createOptions struct { - Mount struct { - ShouldMount bool - From reference.Canonical - } -} - type optionFunc func(interface{}) error func (f optionFunc) Apply(v interface{}) error { @@ -120,7 +111,7 @@ func (f optionFunc) Apply(v interface{}) error { // mounted from the given canonical reference. func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption { return optionFunc(func(v interface{}) error { - opts, ok := v.(*createOptions) + opts, ok := v.(*distribution.CreateOptions) if !ok { return fmt.Errorf("unexpected options type: %T", v) } @@ -136,7 +127,7 @@ func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption { func (lbs *linkedBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { context.GetLogger(ctx).Debug("(*linkedBlobStore).Writer") - var opts createOptions + var opts distribution.CreateOptions for _, option := range options { err := option.Apply(&opts) diff --git a/registry/storage/linkedblobstore_test.go b/registry/storage/linkedblobstore_test.go new file mode 100644 index 00000000..6d066c33 --- /dev/null +++ b/registry/storage/linkedblobstore_test.go @@ -0,0 +1,78 @@ +package storage + +import ( + "io" + "testing" + + "github.com/docker/distribution" + "github.com/docker/distribution/context" + "github.com/docker/distribution/digest" + + "github.com/docker/distribution/reference" + "github.com/docker/distribution/testutil" +) + +func TestLinkedBlobStoreCreateWithMountFrom(t *testing.T) { + fooRepoName, _ := reference.ParseNamed("nm/foo") + fooEnv := newManifestStoreTestEnv(t, fooRepoName, "thetag") + ctx := context.Background() + + // Build up some test layers and add them to the manifest, saving the + // readseekers for upload later. + testLayers := map[digest.Digest]io.ReadSeeker{} + for i := 0; i < 2; i++ { + rs, ds, err := testutil.CreateRandomTarFile() + if err != nil { + t.Fatalf("unexpected error generating test layer file") + } + dgst := digest.Digest(ds) + + testLayers[digest.Digest(dgst)] = rs + } + + // upload the layers to foo/bar + for dgst, rs := range testLayers { + wr, err := fooEnv.repository.Blobs(fooEnv.ctx).Create(fooEnv.ctx) + if err != nil { + t.Fatalf("unexpected error creating test upload: %v", err) + } + + if _, err := io.Copy(wr, rs); err != nil { + t.Fatalf("unexpected error copying to upload: %v", err) + } + + if _, err := wr.Commit(fooEnv.ctx, distribution.Descriptor{Digest: dgst}); err != nil { + t.Fatalf("unexpected error finishing upload: %v", err) + } + } + + // create another repository nm/bar + barRepoName, _ := reference.ParseNamed("nm/bar") + + barRepo, err := fooEnv.registry.Repository(ctx, barRepoName) + if err != nil { + t.Fatalf("unexpected error getting repo: %v", err) + } + + // cross-repo mount the test layers into a nm/bar + for dgst := range testLayers { + fooCanonical, _ := reference.WithDigest(fooRepoName, dgst) + option := WithMountFrom(fooCanonical) + // ensure we can instrospect it + createOpts := distribution.CreateOptions{} + if err := option.Apply(&createOpts); err != nil { + t.Fatalf("failed to apply MountFrom option: %v", err) + } + if !createOpts.Mount.ShouldMount || createOpts.Mount.From.String() != fooCanonical.String() { + t.Fatalf("unexpected create options: %#+v", createOpts.Mount) + } + + _, err := barRepo.Blobs(ctx).Create(ctx, WithMountFrom(fooCanonical)) + if err == nil { + t.Fatalf("unexpected non-error while mounting from %q: %v", fooRepoName.String(), err) + } + if _, ok := err.(distribution.ErrBlobMounted); !ok { + t.Fatalf("expected ErrMountFrom error, not %T: %v", err, err) + } + } +}