distribution/storage/revisionstore.go
Stephen J Day 83d62628fc Refactor storage to use new backend layout
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>
2015-01-15 10:32:18 -08:00

217 lines
5.2 KiB
Go

package storage
import (
"encoding/json"
"path"
"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
}
// 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,
revision: revision,
})
if err != nil {
return false, err
}
exists, err := exists(rs.driver, revpath)
if err != nil {
return false, err
}
return exists, nil
}
// get retrieves the manifest, keyed by revision digest.
func (rs *revisionStore) get(name string, revision digest.Digest) (*manifest.SignedManifest, error) {
// Ensure that this revision is available in this repository.
if exists, err := rs.exists(name, revision); err != nil {
return nil, err
} else if !exists {
return nil, ErrUnknownManifestRevision{
Name: name,
Revision: revision,
}
}
content, err := rs.blobStore.get(revision)
if err != nil {
return nil, err
}
// Fetch the signatures for the manifest
signatures, err := rs.getSignatures(name, revision)
if err != nil {
return nil, err
}
logrus.Infof("retrieved signatures: %v", string(signatures[0]))
jsig, err := libtrust.NewJSONSignature(content, signatures...)
if err != nil {
return nil, err
}
// Extract the pretty JWS
raw, err := jsig.PrettySignature("signatures")
if err != nil {
return nil, err
}
var sm manifest.SignedManifest
if err := json.Unmarshal(raw, &sm); err != nil {
return nil, err
}
return &sm, nil
}
// 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) {
jsig, err := libtrust.ParsePrettySignature(sm.Raw, "signatures")
if err != nil {
return "", err
}
// Resolve the payload in the manifest.
payload, err := jsig.Payload()
if err != nil {
return "", err
}
// Digest and store the manifest payload in the blob store.
revision, err := rs.blobStore.put(payload)
if err != nil {
logrus.Errorf("error putting payload into blobstore: %v", err)
return "", err
}
// Link the revision into the repository.
if err := rs.link(name, revision); err != nil {
return "", err
}
// Grab each json signature and store them.
signatures, err := jsig.Signatures()
if err != nil {
return "", err
}
for _, signature := range signatures {
if err := rs.putSignature(name, revision, signature); err != nil {
return "", err
}
}
return revision, nil
}
// 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,
revision: revision,
})
if err != nil {
return err
}
if exists, err := exists(rs.driver, revisionPath); err != nil {
return err
} else if exists {
// Revision has already been linked!
return nil
}
return rs.blobStore.link(revisionPath, revision)
}
// 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,
revision: revision,
})
if err != nil {
return err
}
return rs.driver.Delete(revisionPath)
}
// 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,
revision: revision,
})
if err != nil {
return nil, err
}
// Need to append signature digest algorithm to path to get all items.
// Perhaps, this should be in the pathMapper but it feels awkward. This
// can be eliminated by implementing listAll on drivers.
signaturesPath = path.Join(signaturesPath, "sha256")
signaturePaths, err := rs.driver.List(signaturesPath)
if err != nil {
return nil, err
}
var signatures [][]byte
for _, sigPath := range signaturePaths {
// Append the link portion
sigPath = path.Join(sigPath, "link")
// TODO(stevvooe): These fetches should be parallelized for performance.
p, err := rs.blobStore.linked(sigPath)
if err != nil {
return nil, err
}
signatures = append(signatures, p)
}
return signatures, nil
}
// putSignature stores the signature for the provided manifest revision.
func (rs *revisionStore) putSignature(name string, 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,
revision: revision,
signature: signatureDigest,
})
if err != nil {
return err
}
return rs.blobStore.link(signaturePath, signatureDigest)
}