forked from TrueCloudLab/distribution
83d62628fc
This change refactors the storage backend to use the new path layout. To facilitate this, manifest storage has been separated into a revision store and tag store, supported by a more general blob store. The blob store is a hybrid object, effectively providing both small object access, keyed by content address, as well as methods that can be used to manage and traverse links to underlying blobs. This covers common operations used in the revision store and tag store, such as linking and traversal. The blob store can also be updated to better support layer reading but this refactoring has been left for another day. The revision store and tag store support the manifest store's compound view of data. These underlying stores provide facilities for richer access models, such as content-addressable access and a richer tagging model. The highlight of this change is the ability to sign a manifest from different hosts and have the registry merge and serve those signatures as part of the manifest package. Various other items, such as the delegate layer handler, were updated to more directly use the blob store or other mechanism to fit with the changes. Signed-off-by: Stephen J Day <stephen.day@docker.com>
165 lines
3.7 KiB
Go
165 lines
3.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"time"
|
|
|
|
"code.google.com/p/go-uuid/uuid"
|
|
"github.com/docker/distribution/digest"
|
|
"github.com/docker/distribution/manifest"
|
|
"github.com/docker/distribution/storagedriver"
|
|
)
|
|
|
|
type layerStore struct {
|
|
driver storagedriver.StorageDriver
|
|
pathMapper *pathMapper
|
|
blobStore *blobStore
|
|
}
|
|
|
|
func (ls *layerStore) Exists(name string, 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)
|
|
|
|
if err != nil {
|
|
switch err.(type) {
|
|
case ErrUnknownLayer:
|
|
return false, nil
|
|
}
|
|
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (ls *layerStore) Fetch(name string, dgst digest.Digest) (Layer, error) {
|
|
bp, err := ls.path(name, dgst)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fr, err := newFileReader(ls.driver, bp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &layerReader{
|
|
fileReader: *fr,
|
|
name: name,
|
|
digest: dgst,
|
|
}, nil
|
|
}
|
|
|
|
// 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) {
|
|
|
|
// NOTE(stevvooe): Consider the issues with allowing concurrent upload of
|
|
// the same two layers. Should it be disallowed? For now, we allow both
|
|
// parties to proceed and the the first one uploads the layer.
|
|
|
|
uuid := uuid.New()
|
|
startedAt := time.Now().UTC()
|
|
|
|
path, err := ls.pathMapper.path(uploadDataPathSpec{
|
|
name: name,
|
|
uuid: uuid,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
startedAtPath, err := ls.pathMapper.path(uploadStartedAtPathSpec{
|
|
name: name,
|
|
uuid: uuid,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Write a startedat file for this upload
|
|
if err := ls.driver.PutContent(startedAtPath, []byte(startedAt.Format(time.RFC3339))); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ls.newLayerUpload(name, 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,
|
|
uuid: uuid,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
startedAtBytes, err := ls.driver.GetContent(startedAtPath)
|
|
if err != nil {
|
|
switch err := err.(type) {
|
|
case storagedriver.PathNotFoundError:
|
|
return nil, ErrLayerUploadUnknown
|
|
default:
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
startedAt, err := time.Parse(time.RFC3339, string(startedAtBytes))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
path, err := ls.pathMapper.path(uploadDataPathSpec{
|
|
name: name,
|
|
uuid: uuid,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ls.newLayerUpload(name, 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)
|
|
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) {
|
|
// We must traverse this path through the link to enforce ownership.
|
|
layerLinkPath, err := ls.pathMapper.path(layerLinkPathSpec{name: name, digest: dgst})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
blobPath, err := ls.blobStore.resolve(layerLinkPath)
|
|
|
|
if err != nil {
|
|
switch err := err.(type) {
|
|
case storagedriver.PathNotFoundError:
|
|
return "", ErrUnknownLayer{manifest.FSLayer{BlobSum: dgst}}
|
|
default:
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
return blobPath, nil
|
|
}
|