0270bec916
Several API tests were added to ensure correct acceptance of zero-size and empty tar files. This led to several changes in the storage backend around the guarantees of remote file reading, which backs the layer and layer upload type. In support of these changes, zero-length and empty checks have been added to the digest package. These provide a sanity check against upstream tarsum changes. The fileReader has been modified to be more robust when reading and seeking on zero-length or non-existent files. The file no longer needs to exist for the reader to be created. Seeks can now move beyond the end of the file, causing reads to issue an io.EOF. This eliminates errors during certain race conditions for reading files which should be detected by stat calls. As a part of this, a few error types were factored out and the read buffer size was increased to something more reasonable. Signed-off-by: Stephen J Day <stephen.day@docker.com>
212 lines
4.9 KiB
Go
212 lines
4.9 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/libtrust"
|
|
)
|
|
|
|
// revisionStore supports storing and managing manifest revisions.
|
|
type revisionStore struct {
|
|
*repository
|
|
}
|
|
|
|
// exists returns true if the revision is available in the named repository.
|
|
func (rs *revisionStore) exists(revision digest.Digest) (bool, error) {
|
|
revpath, err := rs.pm.path(manifestRevisionPathSpec{
|
|
name: rs.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(revision digest.Digest) (*manifest.SignedManifest, error) {
|
|
// Ensure that this revision is available in this repository.
|
|
if exists, err := rs.exists(revision); err != nil {
|
|
return nil, err
|
|
} else if !exists {
|
|
return nil, ErrUnknownManifestRevision{
|
|
Name: rs.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(revision)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
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(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(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(revision, signature); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
return revision, nil
|
|
}
|
|
|
|
// link links the revision into the repository.
|
|
func (rs *revisionStore) link(revision digest.Digest) error {
|
|
revisionPath, err := rs.pm.path(manifestRevisionLinkPathSpec{
|
|
name: rs.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(revision digest.Digest) error {
|
|
revisionPath, err := rs.pm.path(manifestRevisionPathSpec{
|
|
name: rs.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(revision digest.Digest) ([][]byte, error) {
|
|
signaturesPath, err := rs.pm.path(manifestSignaturesPathSpec{
|
|
name: rs.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(revision digest.Digest, signature []byte) error {
|
|
signatureDigest, err := rs.blobStore.put(signature)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
signaturePath, err := rs.pm.path(manifestSignatureLinkPathSpec{
|
|
name: rs.Name(),
|
|
revision: revision,
|
|
signature: signatureDigest,
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return rs.blobStore.link(signaturePath, signatureDigest)
|
|
}
|