From ea5b999fc0d196d31c5093a104cb62e4c347a645 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Fri, 16 Jan 2015 18:24:07 -0800 Subject: [PATCH] Refactor storage API to be registry oriented In support of making the storage API ready for supporting notifications and mirroring, we've begun the process of paring down the storage model. The process started by creating a central Registry interface. From there, the common name argument on the LayerService and ManifestService was factored into a Repository interface. The rest of the changes directly follow from this. An interface wishlist was added, suggesting a direction to take the registry package that should support the distribution project's future goals. As these objects move out of the storage package and we implement a Registry backed by the http client, these design choices will start getting validation. Signed-off-by: Stephen J Day --- storage/blobstore.go | 3 +- storage/layer_test.go | 48 ++++-------- storage/layerstore.go | 55 +++++++------- storage/layerupload.go | 17 ++--- storage/manifeststore.go | 49 ++++++------ storage/manifeststore_test.go | 136 ++++++++++++---------------------- storage/registry.go | 75 +++++++++++++++++++ storage/revisionstore.go | 49 ++++++------ storage/services.go | 108 +++++++++++---------------- storage/tagstore.go | 48 ++++++------ 10 files changed, 287 insertions(+), 301 deletions(-) create mode 100644 storage/registry.go diff --git a/storage/blobstore.go b/storage/blobstore.go index bd7b3fc83..04d247ffc 100644 --- a/storage/blobstore.go +++ b/storage/blobstore.go @@ -18,8 +18,7 @@ import ( // abstraction, providing utility methods that support creating and traversing // backend links. type blobStore struct { - driver storagedriver.StorageDriver - pm *pathMapper + *registry } // exists reports whether or not the path exists. If the driver returns error diff --git a/storage/layer_test.go b/storage/layer_test.go index c6b7b0d8a..7da64190a 100644 --- a/storage/layer_test.go +++ b/storage/layer_test.go @@ -32,23 +32,13 @@ func TestSimpleLayerUpload(t *testing.T) { imageName := "foo/bar" driver := inmemory.New() - pm := &pathMapper{ - root: "/storage/testing", - version: storagePathVersion, - } - ls := &layerStore{ - driver: driver, - blobStore: &blobStore{ - driver: driver, - pm: pm, - }, - pathMapper: pm, - } + registry := NewRegistryWithDriver(driver) + ls := registry.Repository(imageName).Layers() h := sha256.New() rd := io.TeeReader(randomDataReader, h) - layerUpload, err := ls.Upload(imageName) + layerUpload, err := ls.Upload() if err != nil { t.Fatalf("unexpected error starting layer upload: %s", err) @@ -60,13 +50,13 @@ func TestSimpleLayerUpload(t *testing.T) { } // Do a resume, get unknown upload - layerUpload, err = ls.Resume(layerUpload.Name(), layerUpload.UUID()) + layerUpload, err = ls.Resume(layerUpload.UUID()) if err != ErrLayerUploadUnknown { t.Fatalf("unexpected error resuming upload, should be unkown: %v", err) } // Restart! - layerUpload, err = ls.Upload(imageName) + layerUpload, err = ls.Upload() if err != nil { t.Fatalf("unexpected error starting layer upload: %s", err) } @@ -97,7 +87,7 @@ func TestSimpleLayerUpload(t *testing.T) { layerUpload.Close() // Do a resume, for good fun - layerUpload, err = ls.Resume(layerUpload.Name(), layerUpload.UUID()) + layerUpload, err = ls.Resume(layerUpload.UUID()) if err != nil { t.Fatalf("unexpected error resuming upload: %v", err) } @@ -110,12 +100,12 @@ func TestSimpleLayerUpload(t *testing.T) { } // After finishing an upload, it should no longer exist. - if _, err := ls.Resume(layerUpload.Name(), layerUpload.UUID()); err != ErrLayerUploadUnknown { + if _, err := ls.Resume(layerUpload.UUID()); err != ErrLayerUploadUnknown { t.Fatalf("expected layer upload to be unknown, got %v", err) } // Test for existence. - exists, err := ls.Exists(layer.Name(), layer.Digest()) + exists, err := ls.Exists(layer.Digest()) if err != nil { t.Fatalf("unexpected error checking for existence: %v", err) } @@ -145,18 +135,8 @@ func TestSimpleLayerUpload(t *testing.T) { func TestSimpleLayerRead(t *testing.T) { imageName := "foo/bar" driver := inmemory.New() - pm := &pathMapper{ - root: "/storage/testing", - version: storagePathVersion, - } - ls := &layerStore{ - driver: driver, - blobStore: &blobStore{ - driver: driver, - pm: pm, - }, - pathMapper: pm, - } + registry := NewRegistryWithDriver(driver) + ls := registry.Repository(imageName).Layers() randomLayerReader, tarSumStr, err := testutil.CreateRandomTarFile() if err != nil { @@ -166,7 +146,7 @@ func TestSimpleLayerRead(t *testing.T) { dgst := digest.Digest(tarSumStr) // Test for existence. - exists, err := ls.Exists(imageName, dgst) + exists, err := ls.Exists(dgst) if err != nil { t.Fatalf("unexpected error checking for existence: %v", err) } @@ -176,7 +156,7 @@ func TestSimpleLayerRead(t *testing.T) { } // Try to get the layer and make sure we get a not found error - layer, err := ls.Fetch(imageName, dgst) + layer, err := ls.Fetch(dgst) if err == nil { t.Fatalf("error expected fetching unknown layer") } @@ -188,7 +168,7 @@ func TestSimpleLayerRead(t *testing.T) { t.Fatalf("unexpected error fetching non-existent layer: %v", err) } - randomLayerDigest, err := writeTestLayer(driver, ls.pathMapper, imageName, dgst, randomLayerReader) + randomLayerDigest, err := writeTestLayer(driver, ls.(*layerStore).repository.pm, imageName, dgst, randomLayerReader) if err != nil { t.Fatalf("unexpected error writing test layer: %v", err) } @@ -198,7 +178,7 @@ func TestSimpleLayerRead(t *testing.T) { t.Fatalf("error getting seeker size for random layer: %v", err) } - layer, err = ls.Fetch(imageName, dgst) + layer, err = ls.Fetch(dgst) if err != nil { t.Fatal(err) } diff --git a/storage/layerstore.go b/storage/layerstore.go index 6d399af0e..7dd7e2ac6 100644 --- a/storage/layerstore.go +++ b/storage/layerstore.go @@ -10,15 +10,13 @@ import ( ) type layerStore struct { - driver storagedriver.StorageDriver - pathMapper *pathMapper - blobStore *blobStore + repository *repository } -func (ls *layerStore) Exists(name string, digest digest.Digest) (bool, error) { +func (ls *layerStore) Exists(digest digest.Digest) (bool, error) { // Because this implementation just follows blob links, an existence check // is pretty cheap by starting and closing a fetch. - _, err := ls.Fetch(name, digest) + _, err := ls.Fetch(digest) if err != nil { switch err.(type) { @@ -32,20 +30,20 @@ func (ls *layerStore) Exists(name string, digest digest.Digest) (bool, error) { return true, nil } -func (ls *layerStore) Fetch(name string, dgst digest.Digest) (Layer, error) { - bp, err := ls.path(name, dgst) +func (ls *layerStore) Fetch(dgst digest.Digest) (Layer, error) { + bp, err := ls.path(dgst) if err != nil { return nil, err } - fr, err := newFileReader(ls.driver, bp) + fr, err := newFileReader(ls.repository.driver, bp) if err != nil { return nil, err } return &layerReader{ fileReader: *fr, - name: name, + name: ls.repository.Name(), digest: dgst, }, nil } @@ -53,7 +51,7 @@ func (ls *layerStore) Fetch(name string, dgst digest.Digest) (Layer, error) { // Upload begins a layer upload, returning a handle. If the layer upload // is already in progress or the layer has already been uploaded, this // will return an error. -func (ls *layerStore) Upload(name string) (LayerUpload, error) { +func (ls *layerStore) Upload() (LayerUpload, error) { // NOTE(stevvooe): Consider the issues with allowing concurrent upload of // the same two layers. Should it be disallowed? For now, we allow both @@ -62,8 +60,8 @@ func (ls *layerStore) Upload(name string) (LayerUpload, error) { uuid := uuid.New() startedAt := time.Now().UTC() - path, err := ls.pathMapper.path(uploadDataPathSpec{ - name: name, + path, err := ls.repository.registry.pm.path(uploadDataPathSpec{ + name: ls.repository.Name(), uuid: uuid, }) @@ -71,8 +69,8 @@ func (ls *layerStore) Upload(name string) (LayerUpload, error) { return nil, err } - startedAtPath, err := ls.pathMapper.path(uploadStartedAtPathSpec{ - name: name, + startedAtPath, err := ls.repository.registry.pm.path(uploadStartedAtPathSpec{ + name: ls.repository.Name(), uuid: uuid, }) @@ -81,18 +79,18 @@ func (ls *layerStore) Upload(name string) (LayerUpload, error) { } // Write a startedat file for this upload - if err := ls.driver.PutContent(startedAtPath, []byte(startedAt.Format(time.RFC3339))); err != nil { + if err := ls.repository.driver.PutContent(startedAtPath, []byte(startedAt.Format(time.RFC3339))); err != nil { return nil, err } - return ls.newLayerUpload(name, uuid, path, startedAt) + return ls.newLayerUpload(uuid, path, startedAt) } // Resume continues an in progress layer upload, returning the current // state of the upload. -func (ls *layerStore) Resume(name, uuid string) (LayerUpload, error) { - startedAtPath, err := ls.pathMapper.path(uploadStartedAtPathSpec{ - name: name, +func (ls *layerStore) Resume(uuid string) (LayerUpload, error) { + startedAtPath, err := ls.repository.registry.pm.path(uploadStartedAtPathSpec{ + name: ls.repository.Name(), uuid: uuid, }) @@ -100,7 +98,7 @@ func (ls *layerStore) Resume(name, uuid string) (LayerUpload, error) { return nil, err } - startedAtBytes, err := ls.driver.GetContent(startedAtPath) + startedAtBytes, err := ls.repository.driver.GetContent(startedAtPath) if err != nil { switch err := err.(type) { case storagedriver.PathNotFoundError: @@ -115,8 +113,8 @@ func (ls *layerStore) Resume(name, uuid string) (LayerUpload, error) { return nil, err } - path, err := ls.pathMapper.path(uploadDataPathSpec{ - name: name, + path, err := ls.repository.pm.path(uploadDataPathSpec{ + name: ls.repository.Name(), uuid: uuid, }) @@ -124,33 +122,32 @@ func (ls *layerStore) Resume(name, uuid string) (LayerUpload, error) { return nil, err } - return ls.newLayerUpload(name, uuid, path, startedAt) + return ls.newLayerUpload(uuid, path, startedAt) } // newLayerUpload allocates a new upload controller with the given state. -func (ls *layerStore) newLayerUpload(name, uuid, path string, startedAt time.Time) (LayerUpload, error) { - fw, err := newFileWriter(ls.driver, path) +func (ls *layerStore) newLayerUpload(uuid, path string, startedAt time.Time) (LayerUpload, error) { + fw, err := newFileWriter(ls.repository.driver, path) if err != nil { return nil, err } return &layerUploadController{ layerStore: ls, - name: name, uuid: uuid, startedAt: startedAt, fileWriter: *fw, }, nil } -func (ls *layerStore) path(name string, dgst digest.Digest) (string, error) { +func (ls *layerStore) path(dgst digest.Digest) (string, error) { // We must traverse this path through the link to enforce ownership. - layerLinkPath, err := ls.pathMapper.path(layerLinkPathSpec{name: name, digest: dgst}) + layerLinkPath, err := ls.repository.registry.pm.path(layerLinkPathSpec{name: ls.repository.Name(), digest: dgst}) if err != nil { return "", err } - blobPath, err := ls.blobStore.resolve(layerLinkPath) + blobPath, err := ls.repository.blobStore.resolve(layerLinkPath) if err != nil { switch err := err.(type) { diff --git a/storage/layerupload.go b/storage/layerupload.go index c71176350..690e99ec9 100644 --- a/storage/layerupload.go +++ b/storage/layerupload.go @@ -16,7 +16,6 @@ import ( type layerUploadController struct { layerStore *layerStore - name string uuid string startedAt time.Time @@ -27,7 +26,7 @@ var _ LayerUpload = &layerUploadController{} // Name of the repository under which the layer will be linked. func (luc *layerUploadController) Name() string { - return luc.name + return luc.layerStore.repository.Name() } // UUID returns the identifier for this upload. @@ -63,7 +62,7 @@ func (luc *layerUploadController) Finish(digest digest.Digest) (Layer, error) { return nil, err } - return luc.layerStore.Fetch(luc.Name(), canonical) + return luc.layerStore.Fetch(canonical) } // Cancel the layer upload process. @@ -128,7 +127,7 @@ func (luc *layerUploadController) validateLayer(dgst digest.Digest) (digest.Dige // identified by dgst. The layer should be validated before commencing the // move. func (luc *layerUploadController) moveLayer(dgst digest.Digest) error { - blobPath, err := luc.layerStore.pathMapper.path(blobDataPathSpec{ + blobPath, err := luc.layerStore.repository.registry.pm.path(blobDataPathSpec{ digest: dgst, }) @@ -137,7 +136,7 @@ func (luc *layerUploadController) moveLayer(dgst digest.Digest) error { } // Check for existence - if _, err := luc.layerStore.driver.Stat(blobPath); err != nil { + if _, err := luc.layerStore.repository.registry.driver.Stat(blobPath); err != nil { switch err := err.(type) { case storagedriver.PathNotFoundError: break // ensure that it doesn't exist. @@ -158,7 +157,7 @@ func (luc *layerUploadController) moveLayer(dgst digest.Digest) error { // linkLayer links a valid, written layer blob into the registry under the // named repository for the upload controller. func (luc *layerUploadController) linkLayer(digest digest.Digest) error { - layerLinkPath, err := luc.layerStore.pathMapper.path(layerLinkPathSpec{ + layerLinkPath, err := luc.layerStore.repository.registry.pm.path(layerLinkPathSpec{ name: luc.Name(), digest: digest, }) @@ -167,15 +166,15 @@ func (luc *layerUploadController) linkLayer(digest digest.Digest) error { return err } - return luc.layerStore.driver.PutContent(layerLinkPath, []byte(digest)) + return luc.layerStore.repository.registry.driver.PutContent(layerLinkPath, []byte(digest)) } // removeResources should clean up all resources associated with the upload // instance. An error will be returned if the clean up cannot proceed. If the // resources are already not present, no error will be returned. func (luc *layerUploadController) removeResources() error { - dataPath, err := luc.layerStore.pathMapper.path(uploadDataPathSpec{ - name: luc.name, + dataPath, err := luc.layerStore.repository.registry.pm.path(uploadDataPathSpec{ + name: luc.Name(), uuid: luc.uuid, }) diff --git a/storage/manifeststore.go b/storage/manifeststore.go index 2a8c5f18a..bc28f3b8b 100644 --- a/storage/manifeststore.go +++ b/storage/manifeststore.go @@ -6,7 +6,6 @@ import ( "github.com/docker/distribution/digest" "github.com/docker/distribution/manifest" - "github.com/docker/distribution/storagedriver" "github.com/docker/libtrust" ) @@ -65,65 +64,67 @@ func (errs ErrManifestVerification) Error() string { } type manifestStore struct { - driver storagedriver.StorageDriver - pathMapper *pathMapper + repository *repository + revisionStore *revisionStore tagStore *tagStore - blobStore *blobStore - layerService LayerService } var _ ManifestService = &manifestStore{} -func (ms *manifestStore) Tags(name string) ([]string, error) { - return ms.tagStore.tags(name) +// func (ms *manifestStore) Repository() Repository { +// return ms.repository +// } + +func (ms *manifestStore) Tags() ([]string, error) { + return ms.tagStore.tags() } -func (ms *manifestStore) Exists(name, tag string) (bool, error) { - return ms.tagStore.exists(name, tag) +func (ms *manifestStore) Exists(tag string) (bool, error) { + return ms.tagStore.exists(tag) } -func (ms *manifestStore) Get(name, tag string) (*manifest.SignedManifest, error) { - dgst, err := ms.tagStore.resolve(name, tag) +func (ms *manifestStore) Get(tag string) (*manifest.SignedManifest, error) { + dgst, err := ms.tagStore.resolve(tag) if err != nil { return nil, err } - return ms.revisionStore.get(name, dgst) + return ms.revisionStore.get(dgst) } -func (ms *manifestStore) Put(name, tag string, manifest *manifest.SignedManifest) error { +func (ms *manifestStore) Put(tag string, manifest *manifest.SignedManifest) error { // Verify the manifest. - if err := ms.verifyManifest(name, tag, manifest); err != nil { + if err := ms.verifyManifest(tag, manifest); err != nil { return err } // Store the revision of the manifest - revision, err := ms.revisionStore.put(name, manifest) + revision, err := ms.revisionStore.put(manifest) if err != nil { return err } // Now, tag the manifest - return ms.tagStore.tag(name, tag, revision) + return ms.tagStore.tag(tag, revision) } // Delete removes all revisions of the given tag. We may want to change these // semantics in the future, but this will maintain consistency. The underlying // blobs are left alone. -func (ms *manifestStore) Delete(name, tag string) error { - revisions, err := ms.tagStore.revisions(name, tag) +func (ms *manifestStore) Delete(tag string) error { + revisions, err := ms.tagStore.revisions(tag) if err != nil { return err } for _, revision := range revisions { - if err := ms.revisionStore.delete(name, revision); err != nil { + if err := ms.revisionStore.delete(revision); err != nil { return err } } - return ms.tagStore.delete(name, tag) + return ms.tagStore.delete(tag) } // verifyManifest ensures that the manifest content is valid from the @@ -131,11 +132,11 @@ func (ms *manifestStore) Delete(name, tag string) error { // that the signature is valid for the enclosed payload. As a policy, the // registry only tries to store valid content, leaving trust policies of that // content up to consumers. -func (ms *manifestStore) verifyManifest(name, tag string, mnfst *manifest.SignedManifest) error { +func (ms *manifestStore) verifyManifest(tag string, mnfst *manifest.SignedManifest) error { var errs ErrManifestVerification - if mnfst.Name != name { + if mnfst.Name != ms.repository.Name() { // TODO(stevvooe): This needs to be an exported error - errs = append(errs, fmt.Errorf("name does not match manifest name")) + errs = append(errs, fmt.Errorf("repository name does not match manifest name")) } if mnfst.Tag != tag { @@ -157,7 +158,7 @@ func (ms *manifestStore) verifyManifest(name, tag string, mnfst *manifest.Signed } for _, fsLayer := range mnfst.FSLayers { - exists, err := ms.layerService.Exists(name, fsLayer.BlobSum) + exists, err := ms.repository.Layers().Exists(fsLayer.BlobSum) if err != nil { errs = append(errs, err) } diff --git a/storage/manifeststore_test.go b/storage/manifeststore_test.go index 5f9b3f379..15bf27beb 100644 --- a/storage/manifeststore_test.go +++ b/storage/manifeststore_test.go @@ -2,9 +2,12 @@ package storage import ( "bytes" + "io" "reflect" "testing" + "github.com/docker/distribution/testutil" + "github.com/docker/distribution/digest" "github.com/docker/distribution/manifest" "github.com/docker/distribution/storagedriver/inmemory" @@ -12,36 +15,14 @@ import ( ) func TestManifestStorage(t *testing.T) { - driver := inmemory.New() - pm := pathMapper{ - root: "/storage/testing", - version: storagePathVersion, - } - bs := blobStore{ - driver: driver, - pm: &pm, - } - ms := &manifestStore{ - driver: driver, - pathMapper: &pm, - revisionStore: &revisionStore{ - driver: driver, - pathMapper: &pm, - blobStore: &bs, - }, - tagStore: &tagStore{ - driver: driver, - pathMapper: &pm, - blobStore: &bs, - }, - blobStore: &bs, - layerService: newMockedLayerService(), - } - name := "foo/bar" tag := "thetag" + driver := inmemory.New() + registry := NewRegistryWithDriver(driver) + repo := registry.Repository(name) + ms := repo.Manifests() - exists, err := ms.Exists(name, tag) + exists, err := ms.Exists(tag) if err != nil { t.Fatalf("unexpected error checking manifest existence: %v", err) } @@ -50,7 +31,7 @@ func TestManifestStorage(t *testing.T) { t.Fatalf("manifest should not exist") } - if _, err := ms.Get(name, tag); true { + if _, err := ms.Get(tag); true { switch err.(type) { case ErrUnknownManifest: break @@ -65,14 +46,22 @@ func TestManifestStorage(t *testing.T) { }, Name: name, Tag: tag, - FSLayers: []manifest.FSLayer{ - { - BlobSum: "asdf", - }, - { - BlobSum: "qwer", - }, - }, + } + + // 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 + m.FSLayers = append(m.FSLayers, manifest.FSLayer{ + BlobSum: dgst, + }) } pk, err := libtrust.GenerateECP256PrivateKey() @@ -85,21 +74,34 @@ func TestManifestStorage(t *testing.T) { t.Fatalf("error signing manifest: %v", err) } - err = ms.Put(name, tag, sm) + err = ms.Put(tag, sm) if err == nil { t.Fatalf("expected errors putting manifest") } // TODO(stevvooe): We expect errors describing all of the missing layers. - ms.layerService.(*mockedExistenceLayerService).add(name, "asdf") - ms.layerService.(*mockedExistenceLayerService).add(name, "qwer") + // Now, upload the layers that were missing! + for dgst, rs := range testLayers { + upload, err := repo.Layers().Upload() + if err != nil { + t.Fatalf("unexpected error creating test upload: %v", err) + } - if err = ms.Put(name, tag, sm); err != nil { + if _, err := io.Copy(upload, rs); err != nil { + t.Fatalf("unexpected error copying to upload: %v", err) + } + + if _, err := upload.Finish(dgst); err != nil { + t.Fatalf("unexpected error finishing upload: %v", err) + } + } + + if err = ms.Put(tag, sm); err != nil { t.Fatalf("unexpected error putting manifest: %v", err) } - exists, err = ms.Exists(name, tag) + exists, err = ms.Exists(tag) if err != nil { t.Fatalf("unexpected error checking manifest existence: %v", err) } @@ -108,7 +110,7 @@ func TestManifestStorage(t *testing.T) { t.Fatalf("manifest should exist") } - fetchedManifest, err := ms.Get(name, tag) + fetchedManifest, err := ms.Get(tag) if err != nil { t.Fatalf("unexpected error fetching manifest: %v", err) } @@ -137,7 +139,7 @@ func TestManifestStorage(t *testing.T) { } // Grabs the tags and check that this tagged manifest is present - tags, err := ms.Tags(name) + tags, err := ms.Tags() if err != nil { t.Fatalf("unexpected error fetching tags: %v", err) } @@ -175,11 +177,11 @@ func TestManifestStorage(t *testing.T) { t.Fatalf("unexpected number of signatures: %d != %d", len(sigs2), 1) } - if err = ms.Put(name, tag, sm2); err != nil { + if err = ms.Put(tag, sm2); err != nil { t.Fatalf("unexpected error putting manifest: %v", err) } - fetched, err := ms.Get(name, tag) + fetched, err := ms.Get(tag) if err != nil { t.Fatalf("unexpected error fetching manifest: %v", err) } @@ -224,49 +226,7 @@ func TestManifestStorage(t *testing.T) { } } - if err := ms.Delete(name, tag); err != nil { + if err := ms.Delete(tag); err != nil { t.Fatalf("unexpected error deleting manifest: %v", err) } } - -type layerKey struct { - name string - digest digest.Digest -} - -type mockedExistenceLayerService struct { - exists map[layerKey]struct{} -} - -func newMockedLayerService() *mockedExistenceLayerService { - return &mockedExistenceLayerService{ - exists: make(map[layerKey]struct{}), - } -} - -var _ LayerService = &mockedExistenceLayerService{} - -func (mels *mockedExistenceLayerService) add(name string, digest digest.Digest) { - mels.exists[layerKey{name: name, digest: digest}] = struct{}{} -} - -func (mels *mockedExistenceLayerService) remove(name string, digest digest.Digest) { - delete(mels.exists, layerKey{name: name, digest: digest}) -} - -func (mels *mockedExistenceLayerService) Exists(name string, digest digest.Digest) (bool, error) { - _, ok := mels.exists[layerKey{name: name, digest: digest}] - return ok, nil -} - -func (mockedExistenceLayerService) Fetch(name string, digest digest.Digest) (Layer, error) { - panic("not implemented") -} - -func (mockedExistenceLayerService) Upload(name string) (LayerUpload, error) { - panic("not implemented") -} - -func (mockedExistenceLayerService) Resume(name, uuid string) (LayerUpload, error) { - panic("not implemented") -} diff --git a/storage/registry.go b/storage/registry.go new file mode 100644 index 000000000..b1e20eecb --- /dev/null +++ b/storage/registry.go @@ -0,0 +1,75 @@ +package storage + +import "github.com/docker/distribution/storagedriver" + +// registry is the top-level implementation of Registry for use in the storage +// package. All instances should descend from this object. +type registry struct { + driver storagedriver.StorageDriver + pm *pathMapper + blobStore *blobStore +} + +// NewRegistryWithDriver creates a new registry instance from the provided +// driver. The resulting registry may be shared by multiple goroutines but is +// cheap to allocate. +func NewRegistryWithDriver(driver storagedriver.StorageDriver) Registry { + bs := &blobStore{} + + reg := ®istry{ + driver: driver, + blobStore: bs, + + // TODO(sday): This should be configurable. + pm: defaultPathMapper, + } + + reg.blobStore.registry = reg + + return reg +} + +// Repository returns an instance of the repository tied to the registry. +// Instances should not be shared between goroutines but are cheap to +// allocate. In general, they should be request scoped. +func (reg *registry) Repository(name string) Repository { + return &repository{ + registry: reg, + name: name, + } +} + +// repository provides name-scoped access to various services. +type repository struct { + *registry + name string +} + +// Name returns the name of the repository. +func (repo *repository) Name() string { + return repo.name +} + +// Manifests returns an instance of ManifestService. Instantiation is cheap and +// may be context sensitive in the future. The instance should be used similar +// to a request local. +func (repo *repository) Manifests() ManifestService { + return &manifestStore{ + repository: repo, + revisionStore: &revisionStore{ + repository: repo, + }, + tagStore: &tagStore{ + repository: repo, + }, + } +} + +// Layers returns an instance of the LayerService. Instantiation is cheap and +// may be context sensitive in the future. The instance should be used similar +// to a request local. +func (repo *repository) Layers() LayerService { + return &layerStore{ + repository: repo, + } +} diff --git a/storage/revisionstore.go b/storage/revisionstore.go index ff286cab8..97518df19 100644 --- a/storage/revisionstore.go +++ b/storage/revisionstore.go @@ -7,21 +7,18 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/distribution/manifest" - "github.com/docker/distribution/storagedriver" "github.com/docker/libtrust" ) // revisionStore supports storing and managing manifest revisions. type revisionStore struct { - driver storagedriver.StorageDriver - pathMapper *pathMapper - blobStore *blobStore + *repository } // exists returns true if the revision is available in the named repository. -func (rs *revisionStore) exists(name string, revision digest.Digest) (bool, error) { - revpath, err := rs.pathMapper.path(manifestRevisionPathSpec{ - name: name, +func (rs *revisionStore) exists(revision digest.Digest) (bool, error) { + revpath, err := rs.pm.path(manifestRevisionPathSpec{ + name: rs.Name(), revision: revision, }) @@ -38,13 +35,13 @@ func (rs *revisionStore) exists(name string, revision digest.Digest) (bool, erro } // get retrieves the manifest, keyed by revision digest. -func (rs *revisionStore) get(name string, revision digest.Digest) (*manifest.SignedManifest, error) { +func (rs *revisionStore) get(revision digest.Digest) (*manifest.SignedManifest, error) { // Ensure that this revision is available in this repository. - if exists, err := rs.exists(name, revision); err != nil { + if exists, err := rs.exists(revision); err != nil { return nil, err } else if !exists { return nil, ErrUnknownManifestRevision{ - Name: name, + Name: rs.Name(), Revision: revision, } } @@ -55,7 +52,7 @@ func (rs *revisionStore) get(name string, revision digest.Digest) (*manifest.Sig } // Fetch the signatures for the manifest - signatures, err := rs.getSignatures(name, revision) + signatures, err := rs.getSignatures(revision) if err != nil { return nil, err } @@ -83,7 +80,7 @@ func (rs *revisionStore) get(name string, revision digest.Digest) (*manifest.Sig // put stores the manifest in the repository, if not already present. Any // updated signatures will be stored, as well. -func (rs *revisionStore) put(name string, sm *manifest.SignedManifest) (digest.Digest, error) { +func (rs *revisionStore) put(sm *manifest.SignedManifest) (digest.Digest, error) { jsig, err := libtrust.ParsePrettySignature(sm.Raw, "signatures") if err != nil { return "", err @@ -103,7 +100,7 @@ func (rs *revisionStore) put(name string, sm *manifest.SignedManifest) (digest.D } // Link the revision into the repository. - if err := rs.link(name, revision); err != nil { + if err := rs.link(revision); err != nil { return "", err } @@ -114,7 +111,7 @@ func (rs *revisionStore) put(name string, sm *manifest.SignedManifest) (digest.D } for _, signature := range signatures { - if err := rs.putSignature(name, revision, signature); err != nil { + if err := rs.putSignature(revision, signature); err != nil { return "", err } } @@ -123,9 +120,9 @@ func (rs *revisionStore) put(name string, sm *manifest.SignedManifest) (digest.D } // link links the revision into the repository. -func (rs *revisionStore) link(name string, revision digest.Digest) error { - revisionPath, err := rs.pathMapper.path(manifestRevisionLinkPathSpec{ - name: name, +func (rs *revisionStore) link(revision digest.Digest) error { + revisionPath, err := rs.pm.path(manifestRevisionLinkPathSpec{ + name: rs.Name(), revision: revision, }) @@ -144,9 +141,9 @@ func (rs *revisionStore) link(name string, revision digest.Digest) error { } // delete removes the specified manifest revision from storage. -func (rs *revisionStore) delete(name string, revision digest.Digest) error { - revisionPath, err := rs.pathMapper.path(manifestRevisionPathSpec{ - name: name, +func (rs *revisionStore) delete(revision digest.Digest) error { + revisionPath, err := rs.pm.path(manifestRevisionPathSpec{ + name: rs.Name(), revision: revision, }) @@ -159,9 +156,9 @@ func (rs *revisionStore) delete(name string, revision digest.Digest) error { // getSignatures retrieves all of the signature blobs for the specified // manifest revision. -func (rs *revisionStore) getSignatures(name string, revision digest.Digest) ([][]byte, error) { - signaturesPath, err := rs.pathMapper.path(manifestSignaturesPathSpec{ - name: name, +func (rs *revisionStore) getSignatures(revision digest.Digest) ([][]byte, error) { + signaturesPath, err := rs.pm.path(manifestSignaturesPathSpec{ + name: rs.Name(), revision: revision, }) @@ -197,14 +194,14 @@ func (rs *revisionStore) getSignatures(name string, revision digest.Digest) ([][ } // putSignature stores the signature for the provided manifest revision. -func (rs *revisionStore) putSignature(name string, revision digest.Digest, signature []byte) error { +func (rs *revisionStore) putSignature(revision digest.Digest, signature []byte) error { signatureDigest, err := rs.blobStore.put(signature) if err != nil { return err } - signaturePath, err := rs.pathMapper.path(manifestSignatureLinkPathSpec{ - name: name, + signaturePath, err := rs.pm.path(manifestSignatureLinkPathSpec{ + name: rs.Name(), revision: revision, signature: signatureDigest, }) diff --git a/storage/services.go b/storage/services.go index 81b25025e..cfb8c7876 100644 --- a/storage/services.go +++ b/storage/services.go @@ -3,101 +3,81 @@ package storage import ( "github.com/docker/distribution/digest" "github.com/docker/distribution/manifest" - "github.com/docker/distribution/storagedriver" ) -// Services provides various services with application-level operations for -// use across backend storage drivers. -type Services struct { - driver storagedriver.StorageDriver - pathMapper *pathMapper +// TODO(stevvooe): These types need to be moved out of the storage package. + +// Registry represents a collection of repositories, addressable by name. +type Registry interface { + // Repository should return a reference to the named repository. The + // registry may or may not have the repository but should always return a + // reference. + Repository(name string) Repository } -// NewServices creates a new Services object to access docker objects stored -// in the underlying driver. -func NewServices(driver storagedriver.StorageDriver) *Services { +// Repository is a named collection of manifests and layers. +type Repository interface { + // Name returns the name of the repository. + Name() string - return &Services{ - driver: driver, - // TODO(sday): This should be configurable. - pathMapper: defaultPathMapper, - } -} + // Manifests returns a reference to this repository's manifest service. + Manifests() ManifestService -// Layers returns an instance of the LayerService. Instantiation is cheap and -// may be context sensitive in the future. The instance should be used similar -// to a request local. -func (ss *Services) Layers() LayerService { - return &layerStore{ - driver: ss.driver, - blobStore: &blobStore{ - driver: ss.driver, - pm: ss.pathMapper, - }, - pathMapper: ss.pathMapper, - } -} - -// Manifests returns an instance of ManifestService. Instantiation is cheap and -// may be context sensitive in the future. The instance should be used similar -// to a request local. -func (ss *Services) Manifests() ManifestService { - // TODO(stevvooe): Lose this kludge. An intermediary object is clearly - // missing here. This initialization is a mess. - bs := &blobStore{ - driver: ss.driver, - pm: ss.pathMapper, - } - - return &manifestStore{ - driver: ss.driver, - pathMapper: ss.pathMapper, - revisionStore: &revisionStore{ - driver: ss.driver, - pathMapper: ss.pathMapper, - blobStore: bs, - }, - tagStore: &tagStore{ - driver: ss.driver, - blobStore: bs, - pathMapper: ss.pathMapper, - }, - blobStore: bs, - layerService: ss.Layers()} + // Layers returns a reference to this repository's layers service. + Layers() LayerService } // ManifestService provides operations on image manifests. type ManifestService interface { // Tags lists the tags under the named repository. - Tags(name string) ([]string, error) + Tags() ([]string, error) // Exists returns true if the manifest exists. - Exists(name, tag string) (bool, error) + Exists(tag string) (bool, error) // Get retrieves the named manifest, if it exists. - Get(name, tag string) (*manifest.SignedManifest, error) + Get(tag string) (*manifest.SignedManifest, error) // Put creates or updates the named manifest. - Put(name, tag string, manifest *manifest.SignedManifest) error + // Put(tag string, manifest *manifest.SignedManifest) (digest.Digest, error) + Put(tag string, manifest *manifest.SignedManifest) error // Delete removes the named manifest, if it exists. - Delete(name, tag string) error + Delete(tag string) error + + // TODO(stevvooe): There are several changes that need to be done to this + // interface: + // + // 1. Get(tag string) should be GetByTag(tag string) + // 2. Put(tag string, manifest *manifest.SignedManifest) should be + // Put(manifest *manifest.SignedManifest). The method can read the + // tag on manifest to automatically tag it in the repository. + // 3. Need a GetByDigest(dgst digest.Digest) method. + // 4. Allow explicit tagging with Tag(digest digest.Digest, tag string) + // 5. Support reading tags with a re-entrant reader to avoid large + // allocations in the registry. + // 6. Long-term: Provide All() method that lets one scroll through all of + // the manifest entries. + // 7. Long-term: break out concept of signing from manifests. This is + // really a part of the distribution sprint. + // 8. Long-term: Manifest should be an interface. This code shouldn't + // really be concerned with the storage format. } // LayerService provides operations on layer files in a backend storage. type LayerService interface { // Exists returns true if the layer exists. - Exists(name string, digest digest.Digest) (bool, error) + Exists(digest digest.Digest) (bool, error) // Fetch the layer identifed by TarSum. - Fetch(name string, digest digest.Digest) (Layer, error) + Fetch(digest digest.Digest) (Layer, error) // Upload begins a layer upload to repository identified by name, // returning a handle. - Upload(name string) (LayerUpload, error) + Upload() (LayerUpload, error) // Resume continues an in progress layer upload, returning a handle to the // upload. The caller should seek to the latest desired upload location // before proceeding. - Resume(name, uuid string) (LayerUpload, error) + Resume(uuid string) (LayerUpload, error) } diff --git a/storage/tagstore.go b/storage/tagstore.go index a3fd6da2c..f7b87a25a 100644 --- a/storage/tagstore.go +++ b/storage/tagstore.go @@ -9,15 +9,13 @@ import ( // tagStore provides methods to manage manifest tags in a backend storage driver. type tagStore struct { - driver storagedriver.StorageDriver - blobStore *blobStore - pathMapper *pathMapper + *repository } // tags lists the manifest tags for the specified repository. -func (ts *tagStore) tags(name string) ([]string, error) { - p, err := ts.pathMapper.path(manifestTagPathSpec{ - name: name, +func (ts *tagStore) tags() ([]string, error) { + p, err := ts.pm.path(manifestTagPathSpec{ + name: ts.name, }) if err != nil { return nil, err @@ -28,7 +26,7 @@ func (ts *tagStore) tags(name string) ([]string, error) { if err != nil { switch err := err.(type) { case storagedriver.PathNotFoundError: - return nil, ErrUnknownRepository{Name: name} + return nil, ErrUnknownRepository{Name: ts.name} default: return nil, err } @@ -44,9 +42,9 @@ func (ts *tagStore) tags(name string) ([]string, error) { } // exists returns true if the specified manifest tag exists in the repository. -func (ts *tagStore) exists(name, tag string) (bool, error) { - tagPath, err := ts.pathMapper.path(manifestTagCurrentPathSpec{ - name: name, +func (ts *tagStore) exists(tag string) (bool, error) { + tagPath, err := ts.pm.path(manifestTagCurrentPathSpec{ + name: ts.Name(), tag: tag, }) if err != nil { @@ -63,9 +61,9 @@ func (ts *tagStore) exists(name, tag string) (bool, error) { // tag tags the digest with the given tag, updating the the store to point at // the current tag. The digest must point to a manifest. -func (ts *tagStore) tag(name, tag string, revision digest.Digest) error { - indexEntryPath, err := ts.pathMapper.path(manifestTagIndexEntryPathSpec{ - name: name, +func (ts *tagStore) tag(tag string, revision digest.Digest) error { + indexEntryPath, err := ts.pm.path(manifestTagIndexEntryPathSpec{ + name: ts.Name(), tag: tag, revision: revision, }) @@ -74,8 +72,8 @@ func (ts *tagStore) tag(name, tag string, revision digest.Digest) error { return err } - currentPath, err := ts.pathMapper.path(manifestTagCurrentPathSpec{ - name: name, + currentPath, err := ts.pm.path(manifestTagCurrentPathSpec{ + name: ts.Name(), tag: tag, }) @@ -93,9 +91,9 @@ func (ts *tagStore) tag(name, tag string, revision digest.Digest) error { } // resolve the current revision for name and tag. -func (ts *tagStore) resolve(name, tag string) (digest.Digest, error) { - currentPath, err := ts.pathMapper.path(manifestTagCurrentPathSpec{ - name: name, +func (ts *tagStore) resolve(tag string) (digest.Digest, error) { + currentPath, err := ts.pm.path(manifestTagCurrentPathSpec{ + name: ts.Name(), tag: tag, }) @@ -106,7 +104,7 @@ func (ts *tagStore) resolve(name, tag string) (digest.Digest, error) { if exists, err := exists(ts.driver, currentPath); err != nil { return "", err } else if !exists { - return "", ErrUnknownManifest{Name: name, Tag: tag} + return "", ErrUnknownManifest{Name: ts.Name(), Tag: tag} } revision, err := ts.blobStore.readlink(currentPath) @@ -118,9 +116,9 @@ func (ts *tagStore) resolve(name, tag string) (digest.Digest, error) { } // revisions returns all revisions with the specified name and tag. -func (ts *tagStore) revisions(name, tag string) ([]digest.Digest, error) { - manifestTagIndexPath, err := ts.pathMapper.path(manifestTagIndexPathSpec{ - name: name, +func (ts *tagStore) revisions(tag string) ([]digest.Digest, error) { + manifestTagIndexPath, err := ts.pm.path(manifestTagIndexPathSpec{ + name: ts.Name(), tag: tag, }) @@ -146,9 +144,9 @@ func (ts *tagStore) revisions(name, tag string) ([]digest.Digest, error) { // delete removes the tag from repository, including the history of all // revisions that have the specified tag. -func (ts *tagStore) delete(name, tag string) error { - tagPath, err := ts.pathMapper.path(manifestTagPathSpec{ - name: name, +func (ts *tagStore) delete(tag string) error { + tagPath, err := ts.pm.path(manifestTagPathSpec{ + name: ts.Name(), tag: tag, }) if err != nil {