Added to vendor

This commit is contained in:
Priya Wadhwa
2018-02-28 12:22:10 -08:00
parent 3ce3dca56d
commit 66bb59ff12
602 changed files with 230607 additions and 5 deletions
+808
View File
@@ -0,0 +1,808 @@
// +build !containers_image_storage_stub
package storage
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sync/atomic"
"github.com/containers/image/image"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/ioutils"
digest "github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const temporaryDirectoryForBigFiles = "/var/tmp" // Do not use the system default of os.TempDir(), usually /tmp, because with systemd it could be a tmpfs.
var (
// ErrBlobDigestMismatch is returned when PutBlob() is given a blob
// with a digest-based name that doesn't match its contents.
ErrBlobDigestMismatch = errors.New("blob digest mismatch")
// ErrBlobSizeMismatch is returned when PutBlob() is given a blob
// with an expected size that doesn't match the reader.
ErrBlobSizeMismatch = errors.New("blob size mismatch")
// ErrNoManifestLists is returned when GetManifest() is called.
// with a non-nil instanceDigest.
ErrNoManifestLists = errors.New("manifest lists are not supported by this transport")
// ErrNoSuchImage is returned when we attempt to access an image which
// doesn't exist in the storage area.
ErrNoSuchImage = storage.ErrNotAnImage
)
type storageImageSource struct {
imageRef storageReference
ID string
layerPosition map[digest.Digest]int // Where we are in reading a blob's layers
cachedManifest []byte // A cached copy of the manifest, if already known, or nil
SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice
}
type storageImageDestination struct {
image types.ImageCloser
systemContext *types.SystemContext
imageRef storageReference // The reference we'll use to name the image
publicRef storageReference // The reference we return when asked about the name we'll give to the image
directory string // Temporary directory where we store blobs until Commit() time
nextTempFileID int32 // A counter that we use for computing filenames to assign to blobs
manifest []byte // Manifest contents, temporary
signatures []byte // Signature contents, temporary
blobDiffIDs map[digest.Digest]digest.Digest // Mapping from layer blobsums to their corresponding DiffIDs
fileSizes map[digest.Digest]int64 // Mapping from layer blobsums to their sizes
filenames map[digest.Digest]string // Mapping from layer blobsums to names of files we used to hold them
SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice
}
type storageImageCloser struct {
types.ImageCloser
size int64
}
// newImageSource sets up an image for reading.
func newImageSource(imageRef storageReference) (*storageImageSource, error) {
// First, locate the image.
img, err := imageRef.resolveImage()
if err != nil {
return nil, err
}
// Build the reader object.
image := &storageImageSource{
imageRef: imageRef,
ID: img.ID,
layerPosition: make(map[digest.Digest]int),
SignatureSizes: []int{},
}
if img.Metadata != "" {
if err := json.Unmarshal([]byte(img.Metadata), image); err != nil {
return nil, errors.Wrap(err, "error decoding metadata for source image")
}
}
return image, nil
}
// Reference returns the image reference that we used to find this image.
func (s storageImageSource) Reference() types.ImageReference {
return s.imageRef
}
// Close cleans up any resources we tied up while reading the image.
func (s storageImageSource) Close() error {
return nil
}
// GetBlob reads the data blob or filesystem layer which matches the digest and size, if given.
func (s *storageImageSource) GetBlob(info types.BlobInfo) (rc io.ReadCloser, n int64, err error) {
rc, n, _, err = s.getBlobAndLayerID(info)
return rc, n, err
}
// getBlobAndLayer reads the data blob or filesystem layer which matches the digest and size, if given.
func (s *storageImageSource) getBlobAndLayerID(info types.BlobInfo) (rc io.ReadCloser, n int64, layerID string, err error) {
var layer storage.Layer
var diffOptions *storage.DiffOptions
// We need a valid digest value.
err = info.Digest.Validate()
if err != nil {
return nil, -1, "", err
}
// Check if the blob corresponds to a diff that was used to initialize any layers. Our
// callers should try to retrieve layers using their uncompressed digests, so no need to
// check if they're using one of the compressed digests, which we can't reproduce anyway.
layers, err := s.imageRef.transport.store.LayersByUncompressedDigest(info.Digest)
// If it's not a layer, then it must be a data item.
if len(layers) == 0 {
b, err := s.imageRef.transport.store.ImageBigData(s.ID, info.Digest.String())
if err != nil {
return nil, -1, "", err
}
r := bytes.NewReader(b)
logrus.Debugf("exporting opaque data as blob %q", info.Digest.String())
return ioutil.NopCloser(r), int64(r.Len()), "", nil
}
// Step through the list of matching layers. Tests may want to verify that if we have multiple layers
// which claim to have the same contents, that we actually do have multiple layers, otherwise we could
// just go ahead and use the first one every time.
i := s.layerPosition[info.Digest]
s.layerPosition[info.Digest] = i + 1
if len(layers) > 0 {
layer = layers[i%len(layers)]
}
// Force the storage layer to not try to match any compression that was used when the layer was first
// handed to it.
noCompression := archive.Uncompressed
diffOptions = &storage.DiffOptions{
Compression: &noCompression,
}
if layer.UncompressedSize < 0 {
n = -1
} else {
n = layer.UncompressedSize
}
logrus.Debugf("exporting filesystem layer %q without compression for blob %q", layer.ID, info.Digest)
rc, err = s.imageRef.transport.store.Diff("", layer.ID, diffOptions)
if err != nil {
return nil, -1, "", err
}
return rc, n, layer.ID, err
}
// GetManifest() reads the image's manifest.
func (s *storageImageSource) GetManifest(instanceDigest *digest.Digest) (manifestBlob []byte, MIMEType string, err error) {
if instanceDigest != nil {
return nil, "", ErrNoManifestLists
}
if len(s.cachedManifest) == 0 {
// We stored the manifest as an item named after storage.ImageDigestBigDataKey.
cachedBlob, err := s.imageRef.transport.store.ImageBigData(s.ID, storage.ImageDigestBigDataKey)
if err != nil {
return nil, "", err
}
s.cachedManifest = cachedBlob
}
return s.cachedManifest, manifest.GuessMIMEType(s.cachedManifest), err
}
// LayerInfosForCopy() returns the list of layer blobs that make up the root filesystem of
// the image, after they've been decompressed.
func (s *storageImageSource) LayerInfosForCopy() []types.BlobInfo {
simg, err := s.imageRef.transport.store.Image(s.ID)
if err != nil {
logrus.Errorf("error reading image %q: %v", s.ID, err)
return nil
}
updatedBlobInfos := []types.BlobInfo{}
layerID := simg.TopLayer
_, manifestType, err := s.GetManifest(nil)
if err != nil {
logrus.Errorf("error reading image manifest for %q: %v", s.ID, err)
return nil
}
uncompressedLayerType := ""
switch manifestType {
case imgspecv1.MediaTypeImageManifest:
uncompressedLayerType = imgspecv1.MediaTypeImageLayer
case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType, manifest.DockerV2Schema2MediaType:
// This is actually a compressed type, but there's no uncompressed type defined
uncompressedLayerType = manifest.DockerV2Schema2LayerMediaType
}
for layerID != "" {
layer, err := s.imageRef.transport.store.Layer(layerID)
if err != nil {
logrus.Errorf("error reading layer %q in image %q: %v", layerID, s.ID, err)
return nil
}
if layer.UncompressedDigest == "" {
logrus.Errorf("uncompressed digest for layer %q is unknown", layerID)
return nil
}
if layer.UncompressedSize < 0 {
logrus.Errorf("uncompressed size for layer %q is unknown", layerID)
return nil
}
blobInfo := types.BlobInfo{
Digest: layer.UncompressedDigest,
Size: layer.UncompressedSize,
MediaType: uncompressedLayerType,
}
updatedBlobInfos = append([]types.BlobInfo{blobInfo}, updatedBlobInfos...)
layerID = layer.Parent
}
return updatedBlobInfos
}
// GetSignatures() parses the image's signatures blob into a slice of byte slices.
func (s *storageImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) (signatures [][]byte, err error) {
if instanceDigest != nil {
return nil, ErrNoManifestLists
}
var offset int
sigslice := [][]byte{}
signature := []byte{}
if len(s.SignatureSizes) > 0 {
signatureBlob, err := s.imageRef.transport.store.ImageBigData(s.ID, "signatures")
if err != nil {
return nil, errors.Wrapf(err, "error looking up signatures data for image %q", s.ID)
}
signature = signatureBlob
}
for _, length := range s.SignatureSizes {
sigslice = append(sigslice, signature[offset:offset+length])
offset += length
}
if offset != len(signature) {
return nil, errors.Errorf("signatures data contained %d extra bytes", len(signatures)-offset)
}
return sigslice, nil
}
// newImageDestination sets us up to write a new image, caching blobs in a temporary directory until
// it's time to Commit() the image
func newImageDestination(ctx *types.SystemContext, imageRef storageReference) (*storageImageDestination, error) {
directory, err := ioutil.TempDir(temporaryDirectoryForBigFiles, "storage")
if err != nil {
return nil, errors.Wrapf(err, "error creating a temporary directory")
}
// Break reading of the reference we're writing, so that copy.Image() won't try to rewrite
// schema1 image manifests to remove embedded references, since that changes the manifest's
// digest, and that makes the image unusable if we subsequently try to access it using a
// reference that mentions the no-longer-correct digest.
publicRef := imageRef
publicRef.name = nil
image := &storageImageDestination{
systemContext: ctx,
imageRef: imageRef,
publicRef: publicRef,
directory: directory,
blobDiffIDs: make(map[digest.Digest]digest.Digest),
fileSizes: make(map[digest.Digest]int64),
filenames: make(map[digest.Digest]string),
SignatureSizes: []int{},
}
return image, nil
}
// Reference returns a mostly-usable image reference that can't return a DockerReference, to
// avoid triggering logic in copy.Image() that rewrites schema 1 image manifests in order to
// remove image names that they contain which don't match the value we're using.
func (s storageImageDestination) Reference() types.ImageReference {
return s.publicRef
}
// Close cleans up the temporary directory.
func (s *storageImageDestination) Close() error {
return os.RemoveAll(s.directory)
}
// ShouldCompressLayers indicates whether or not a caller should compress not-already-compressed
// data when handing it to us.
func (s storageImageDestination) ShouldCompressLayers() bool {
// We ultimately have to decompress layers to populate trees on disk, so callers shouldn't
// bother compressing them before handing them to us, if they're not already compressed.
return false
}
// PutBlob stores a layer or data blob in our temporary directory, checking that any information
// in the blobinfo matches the incoming data.
func (s *storageImageDestination) PutBlob(stream io.Reader, blobinfo types.BlobInfo) (types.BlobInfo, error) {
errorBlobInfo := types.BlobInfo{
Digest: "",
Size: -1,
}
// Set up to digest the blob and count its size while saving it to a file.
hasher := digest.Canonical.Digester()
if blobinfo.Digest.Validate() == nil {
if a := blobinfo.Digest.Algorithm(); a.Available() {
hasher = a.Digester()
}
}
diffID := digest.Canonical.Digester()
filename := filepath.Join(s.directory, fmt.Sprintf("%d", atomic.AddInt32(&s.nextTempFileID, 1)))
file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_EXCL, 0600)
if err != nil {
return errorBlobInfo, errors.Wrapf(err, "error creating temporary file %q", filename)
}
defer file.Close()
counter := ioutils.NewWriteCounter(hasher.Hash())
reader := io.TeeReader(io.TeeReader(stream, counter), file)
decompressed, err := archive.DecompressStream(reader)
if err != nil {
return errorBlobInfo, errors.Wrap(err, "error setting up to decompress blob")
}
// Copy the data to the file.
_, err = io.Copy(diffID.Hash(), decompressed)
decompressed.Close()
if err != nil {
return errorBlobInfo, errors.Wrapf(err, "error storing blob to file %q", filename)
}
// Ensure that any information that we were given about the blob is correct.
if blobinfo.Digest.Validate() == nil && blobinfo.Digest != hasher.Digest() {
return errorBlobInfo, ErrBlobDigestMismatch
}
if blobinfo.Size >= 0 && blobinfo.Size != counter.Count {
return errorBlobInfo, ErrBlobSizeMismatch
}
// Record information about the blob.
s.blobDiffIDs[hasher.Digest()] = diffID.Digest()
s.fileSizes[hasher.Digest()] = counter.Count
s.filenames[hasher.Digest()] = filename
blobDigest := blobinfo.Digest
if blobDigest.Validate() != nil {
blobDigest = hasher.Digest()
}
blobSize := blobinfo.Size
if blobSize < 0 {
blobSize = counter.Count
}
return types.BlobInfo{
Digest: blobDigest,
Size: blobSize,
MediaType: blobinfo.MediaType,
}, nil
}
// HasBlob returns true iff the image destination already contains a blob with the matching digest which can be
// reapplied using ReapplyBlob.
//
// Unlike PutBlob, the digest can not be empty. If HasBlob returns true, the size of the blob must also be returned.
// If the destination does not contain the blob, or it is unknown, HasBlob ordinarily returns (false, -1, nil);
// it returns a non-nil error only on an unexpected failure.
func (s *storageImageDestination) HasBlob(blobinfo types.BlobInfo) (bool, int64, error) {
if blobinfo.Digest == "" {
return false, -1, errors.Errorf(`Can not check for a blob with unknown digest`)
}
if err := blobinfo.Digest.Validate(); err != nil {
return false, -1, errors.Wrapf(err, `Can not check for a blob with invalid digest`)
}
// Check if we've already cached it in a file.
if size, ok := s.fileSizes[blobinfo.Digest]; ok {
return true, size, nil
}
// Check if we have a wasn't-compressed layer in storage that's based on that blob.
layers, err := s.imageRef.transport.store.LayersByUncompressedDigest(blobinfo.Digest)
if err != nil && errors.Cause(err) != storage.ErrLayerUnknown {
return false, -1, errors.Wrapf(err, `Error looking for layers with digest %q`, blobinfo.Digest)
}
if len(layers) > 0 {
// Save this for completeness.
s.blobDiffIDs[blobinfo.Digest] = layers[0].UncompressedDigest
return true, layers[0].UncompressedSize, nil
}
// Check if we have a was-compressed layer in storage that's based on that blob.
layers, err = s.imageRef.transport.store.LayersByCompressedDigest(blobinfo.Digest)
if err != nil && errors.Cause(err) != storage.ErrLayerUnknown {
return false, -1, errors.Wrapf(err, `Error looking for compressed layers with digest %q`, blobinfo.Digest)
}
if len(layers) > 0 {
// Record the uncompressed value so that we can use it to calculate layer IDs.
s.blobDiffIDs[blobinfo.Digest] = layers[0].UncompressedDigest
return true, layers[0].CompressedSize, nil
}
// Nope, we don't have it.
return false, -1, nil
}
// ReapplyBlob is now a no-op, assuming HasBlob() says we already have it, since Commit() can just apply the
// same one when it walks the list in the manifest.
func (s *storageImageDestination) ReapplyBlob(blobinfo types.BlobInfo) (types.BlobInfo, error) {
present, size, err := s.HasBlob(blobinfo)
if !present {
return types.BlobInfo{}, errors.Errorf("error reapplying blob %+v: blob was not previously applied", blobinfo)
}
if err != nil {
return types.BlobInfo{}, errors.Wrapf(err, "error reapplying blob %+v", blobinfo)
}
blobinfo.Size = size
return blobinfo, nil
}
// computeID computes a recommended image ID based on information we have so far. If
// the manifest is not of a type that we recognize, we return an empty value, indicating
// that since we don't have a recommendation, a random ID should be used if one needs
// to be allocated.
func (s *storageImageDestination) computeID(m manifest.Manifest) string {
// Build the diffID list. We need the decompressed sums that we've been calculating to
// fill in the DiffIDs. It's expected (but not enforced by us) that the number of
// diffIDs corresponds to the number of non-EmptyLayer entries in the history.
var diffIDs []digest.Digest
switch m.(type) {
case *manifest.Schema1:
// Build a list of the diffIDs we've generated for the non-throwaway FS layers,
// in reverse of the order in which they were originally listed.
s1, ok := m.(*manifest.Schema1)
if !ok {
// Shouldn't happen
logrus.Debugf("internal error reading schema 1 manifest")
return ""
}
for i, history := range s1.History {
compat := manifest.Schema1V1Compatibility{}
if err := json.Unmarshal([]byte(history.V1Compatibility), &compat); err != nil {
logrus.Debugf("internal error reading schema 1 history: %v", err)
return ""
}
if compat.ThrowAway {
continue
}
blobSum := s1.FSLayers[i].BlobSum
diffID, ok := s.blobDiffIDs[blobSum]
if !ok {
logrus.Infof("error looking up diffID for layer %q", blobSum.String())
return ""
}
diffIDs = append([]digest.Digest{diffID}, diffIDs...)
}
case *manifest.Schema2, *manifest.OCI1:
// We know the ID calculation for these formats doesn't actually use the diffIDs,
// so we don't need to populate the diffID list.
}
id, err := m.ImageID(diffIDs)
if err != nil {
return ""
}
return id
}
// getConfigBlob exists only to let us retrieve the configuration blob so that the manifest package can dig
// information out of it for Inspect().
func (s *storageImageDestination) getConfigBlob(info types.BlobInfo) ([]byte, error) {
if info.Digest == "" {
return nil, errors.Errorf(`no digest supplied when reading blob`)
}
if err := info.Digest.Validate(); err != nil {
return nil, errors.Wrapf(err, `invalid digest supplied when reading blob`)
}
// Assume it's a file, since we're only calling this from a place that expects to read files.
if filename, ok := s.filenames[info.Digest]; ok {
contents, err2 := ioutil.ReadFile(filename)
if err2 != nil {
return nil, errors.Wrapf(err2, `error reading blob from file %q`, filename)
}
return contents, nil
}
// If it's not a file, it's a bug, because we're not expecting to be asked for a layer.
return nil, errors.New("blob not found")
}
func (s *storageImageDestination) Commit() error {
// Find the list of layer blobs.
if len(s.manifest) == 0 {
return errors.New("Internal error: storageImageDestination.Commit() called without PutManifest()")
}
man, err := manifest.FromBlob(s.manifest, manifest.GuessMIMEType(s.manifest))
if err != nil {
return errors.Wrapf(err, "error parsing manifest")
}
layerBlobs := man.LayerInfos()
// Extract or find the layers.
lastLayer := ""
addedLayers := []string{}
for _, blob := range layerBlobs {
var diff io.ReadCloser
// Check if there's already a layer with the ID that we'd give to the result of applying
// this layer blob to its parent, if it has one, or the blob's hex value otherwise.
diffID, haveDiffID := s.blobDiffIDs[blob.Digest]
if !haveDiffID {
// Check if it's elsewhere and the caller just forgot to pass it to us in a PutBlob(),
// or to even check if we had it.
logrus.Debugf("looking for diffID for blob %+v", blob.Digest)
has, _, err := s.HasBlob(blob)
if err != nil {
return errors.Wrapf(err, "error checking for a layer based on blob %q", blob.Digest.String())
}
if !has {
return errors.Errorf("error determining uncompressed digest for blob %q", blob.Digest.String())
}
diffID, haveDiffID = s.blobDiffIDs[blob.Digest]
if !haveDiffID {
return errors.Errorf("we have blob %q, but don't know its uncompressed digest", blob.Digest.String())
}
}
id := diffID.Hex()
if lastLayer != "" {
id = digest.Canonical.FromBytes([]byte(lastLayer + "+" + diffID.Hex())).Hex()
}
if layer, err2 := s.imageRef.transport.store.Layer(id); layer != nil && err2 == nil {
// There's already a layer that should have the right contents, just reuse it.
lastLayer = layer.ID
continue
}
// Check if we cached a file with that blobsum. If we didn't already have a layer with
// the blob's contents, we should have gotten a copy.
if filename, ok := s.filenames[blob.Digest]; ok {
// Use the file's contents to initialize the layer.
file, err2 := os.Open(filename)
if err2 != nil {
return errors.Wrapf(err2, "error opening file %q", filename)
}
defer file.Close()
diff = file
}
if diff == nil {
// Try to find a layer with contents matching that blobsum.
layer := ""
layers, err2 := s.imageRef.transport.store.LayersByUncompressedDigest(blob.Digest)
if err2 == nil && len(layers) > 0 {
layer = layers[0].ID
} else {
layers, err2 = s.imageRef.transport.store.LayersByCompressedDigest(blob.Digest)
if err2 == nil && len(layers) > 0 {
layer = layers[0].ID
}
}
if layer == "" {
return errors.Wrapf(err2, "error locating layer for blob %q", blob.Digest)
}
// Use the layer's contents to initialize the new layer.
noCompression := archive.Uncompressed
diffOptions := &storage.DiffOptions{
Compression: &noCompression,
}
diff, err2 = s.imageRef.transport.store.Diff("", layer, diffOptions)
if err2 != nil {
return errors.Wrapf(err2, "error reading layer %q for blob %q", layer, blob.Digest)
}
defer diff.Close()
}
if diff == nil {
// This shouldn't have happened.
return errors.Errorf("error applying blob %q: content not found", blob.Digest)
}
// Build the new layer using the diff, regardless of where it came from.
layer, _, err := s.imageRef.transport.store.PutLayer(id, lastLayer, nil, "", false, diff)
if err != nil {
return errors.Wrapf(err, "error adding layer with blob %q", blob.Digest)
}
lastLayer = layer.ID
addedLayers = append([]string{lastLayer}, addedLayers...)
}
// If one of those blobs was a configuration blob, then we can try to dig out the date when the image
// was originally created, in case we're just copying it. If not, no harm done.
options := &storage.ImageOptions{}
if inspect, err := man.Inspect(s.getConfigBlob); err == nil {
logrus.Debugf("setting image creation date to %s", inspect.Created)
options.CreationDate = inspect.Created
}
if manifestDigest, err := manifest.Digest(s.manifest); err == nil {
options.Digest = manifestDigest
}
// Create the image record, pointing to the most-recently added layer.
intendedID := s.imageRef.id
if intendedID == "" {
intendedID = s.computeID(man)
}
oldNames := []string{}
img, err := s.imageRef.transport.store.CreateImage(intendedID, nil, lastLayer, "", options)
if err != nil {
if errors.Cause(err) != storage.ErrDuplicateID {
logrus.Debugf("error creating image: %q", err)
return errors.Wrapf(err, "error creating image %q", intendedID)
}
img, err = s.imageRef.transport.store.Image(intendedID)
if err != nil {
return errors.Wrapf(err, "error reading image %q", intendedID)
}
if img.TopLayer != lastLayer {
logrus.Debugf("error creating image: image with ID %q exists, but uses different layers", intendedID)
return errors.Wrapf(storage.ErrDuplicateID, "image with ID %q already exists, but uses a different top layer", intendedID)
}
logrus.Debugf("reusing image ID %q", img.ID)
oldNames = append(oldNames, img.Names...)
} else {
logrus.Debugf("created new image ID %q", img.ID)
}
// Add the non-layer blobs as data items. Since we only share layers, they should all be in files, so
// we just need to screen out the ones that are actually layers to get the list of non-layers.
dataBlobs := make(map[digest.Digest]struct{})
for blob := range s.filenames {
dataBlobs[blob] = struct{}{}
}
for _, layerBlob := range layerBlobs {
delete(dataBlobs, layerBlob.Digest)
}
for blob := range dataBlobs {
v, err := ioutil.ReadFile(s.filenames[blob])
if err != nil {
return errors.Wrapf(err, "error copying non-layer blob %q to image", blob)
}
if err := s.imageRef.transport.store.SetImageBigData(img.ID, blob.String(), v); err != nil {
if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
}
logrus.Debugf("error saving big data %q for image %q: %v", blob.String(), img.ID, err)
return errors.Wrapf(err, "error saving big data %q for image %q", blob.String(), img.ID)
}
}
// Set the reference's name on the image.
if name := s.imageRef.DockerReference(); len(oldNames) > 0 || name != nil {
names := []string{}
if name != nil {
names = append(names, verboseName(name))
}
if len(oldNames) > 0 {
names = append(names, oldNames...)
}
if err := s.imageRef.transport.store.SetNames(img.ID, names); err != nil {
if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
}
logrus.Debugf("error setting names %v on image %q: %v", names, img.ID, err)
return errors.Wrapf(err, "error setting names %v on image %q", names, img.ID)
}
logrus.Debugf("set names of image %q to %v", img.ID, names)
}
// Save the manifest. Use storage.ImageDigestBigDataKey as the item's
// name, so that its digest can be used to locate the image in the Store.
if err := s.imageRef.transport.store.SetImageBigData(img.ID, storage.ImageDigestBigDataKey, s.manifest); err != nil {
if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
}
logrus.Debugf("error saving manifest for image %q: %v", img.ID, err)
return err
}
// Save the signatures, if we have any.
if len(s.signatures) > 0 {
if err := s.imageRef.transport.store.SetImageBigData(img.ID, "signatures", s.signatures); err != nil {
if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
}
logrus.Debugf("error saving signatures for image %q: %v", img.ID, err)
return err
}
}
// Save our metadata.
metadata, err := json.Marshal(s)
if err != nil {
if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
}
logrus.Debugf("error encoding metadata for image %q: %v", img.ID, err)
return err
}
if len(metadata) != 0 {
if err = s.imageRef.transport.store.SetMetadata(img.ID, string(metadata)); err != nil {
if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
}
logrus.Debugf("error saving metadata for image %q: %v", img.ID, err)
return err
}
logrus.Debugf("saved image metadata %q", string(metadata))
}
return nil
}
var manifestMIMETypes = []string{
imgspecv1.MediaTypeImageManifest,
manifest.DockerV2Schema2MediaType,
manifest.DockerV2Schema1SignedMediaType,
manifest.DockerV2Schema1MediaType,
}
func (s *storageImageDestination) SupportedManifestMIMETypes() []string {
return manifestMIMETypes
}
// PutManifest writes the manifest to the destination.
func (s *storageImageDestination) PutManifest(manifest []byte) error {
s.manifest = make([]byte, len(manifest))
copy(s.manifest, manifest)
return nil
}
// SupportsSignatures returns an error if we can't expect GetSignatures() to return data that was
// previously supplied to PutSignatures().
func (s *storageImageDestination) SupportsSignatures() error {
return nil
}
// AcceptsForeignLayerURLs returns false iff foreign layers in the manifest should actually be
// uploaded to the image destination, true otherwise.
func (s *storageImageDestination) AcceptsForeignLayerURLs() bool {
return false
}
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
func (s *storageImageDestination) MustMatchRuntimeOS() bool {
return true
}
// PutSignatures records the image's signatures for committing as a single data blob.
func (s *storageImageDestination) PutSignatures(signatures [][]byte) error {
sizes := []int{}
sigblob := []byte{}
for _, sig := range signatures {
sizes = append(sizes, len(sig))
newblob := make([]byte, len(sigblob)+len(sig))
copy(newblob, sigblob)
copy(newblob[len(sigblob):], sig)
sigblob = newblob
}
s.signatures = sigblob
s.SignatureSizes = sizes
return nil
}
// getSize() adds up the sizes of the image's data blobs (which includes the configuration blob), the
// signatures, and the uncompressed sizes of all of the image's layers.
func (s *storageImageSource) getSize() (int64, error) {
var sum int64
// Size up the data blobs.
dataNames, err := s.imageRef.transport.store.ListImageBigData(s.ID)
if err != nil {
return -1, errors.Wrapf(err, "error reading image %q", s.ID)
}
for _, dataName := range dataNames {
bigSize, err := s.imageRef.transport.store.ImageBigDataSize(s.ID, dataName)
if err != nil {
return -1, errors.Wrapf(err, "error reading data blob size %q for %q", dataName, s.ID)
}
sum += bigSize
}
// Add the signature sizes.
for _, sigSize := range s.SignatureSizes {
sum += int64(sigSize)
}
// Prepare to walk the layer list.
img, err := s.imageRef.transport.store.Image(s.ID)
if err != nil {
return -1, errors.Wrapf(err, "error reading image info %q", s.ID)
}
// Walk the layer list.
layerID := img.TopLayer
for layerID != "" {
layer, err := s.imageRef.transport.store.Layer(layerID)
if err != nil {
return -1, err
}
if layer.UncompressedDigest == "" || layer.UncompressedSize < 0 {
return -1, errors.Errorf("size for layer %q is unknown, failing getSize()", layerID)
}
sum += layer.UncompressedSize
if layer.Parent == "" {
break
}
layerID = layer.Parent
}
return sum, nil
}
// Size() adds up the sizes of the image's data blobs (which includes the configuration blob), the
// signatures, and the uncompressed sizes of all of the image's layers.
func (s *storageImageSource) Size() (int64, error) {
return s.getSize()
}
// Size() returns the previously-computed size of the image, with no error.
func (s *storageImageCloser) Size() (int64, error) {
return s.size, nil
}
// newImage creates an image that also knows its size
func newImage(ctx *types.SystemContext, s storageReference) (types.ImageCloser, error) {
src, err := newImageSource(s)
if err != nil {
return nil, err
}
img, err := image.FromSource(ctx, src)
if err != nil {
return nil, err
}
size, err := src.getSize()
if err != nil {
return nil, err
}
return &storageImageCloser{ImageCloser: img, size: size}, nil
}
+209
View File
@@ -0,0 +1,209 @@
// +build !containers_image_storage_stub
package storage
import (
"strings"
"github.com/containers/image/docker/reference"
"github.com/containers/image/types"
"github.com/containers/storage"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// A storageReference holds an arbitrary name and/or an ID, which is a 32-byte
// value hex-encoded into a 64-character string, and a reference to a Store
// where an image is, or would be, kept.
type storageReference struct {
transport storageTransport
reference string
id string
name reference.Named
tag string
digest digest.Digest
}
func newReference(transport storageTransport, reference, id string, name reference.Named, tag string, digest digest.Digest) *storageReference {
// We take a copy of the transport, which contains a pointer to the
// store that it used for resolving this reference, so that the
// transport that we'll return from Transport() won't be affected by
// further calls to the original transport's SetStore() method.
return &storageReference{
transport: transport,
reference: reference,
id: id,
name: name,
tag: tag,
digest: digest,
}
}
// Resolve the reference's name to an image ID in the store, if there's already
// one present with the same name or ID, and return the image.
func (s *storageReference) resolveImage() (*storage.Image, error) {
if s.id == "" {
// Look for an image that has the expanded reference name as an explicit Name value.
image, err := s.transport.store.Image(s.reference)
if image != nil && err == nil {
s.id = image.ID
}
}
if s.id == "" && s.name != nil && s.digest != "" {
// Look for an image with the specified digest that has the same name,
// though possibly with a different tag or digest, as a Name value, so
// that the canonical reference can be implicitly resolved to the image.
images, err := s.transport.store.ImagesByDigest(s.digest)
if images != nil && err == nil {
repo := reference.FamiliarName(reference.TrimNamed(s.name))
search:
for _, image := range images {
for _, name := range image.Names {
if named, err := reference.ParseNormalizedNamed(name); err == nil {
if reference.FamiliarName(reference.TrimNamed(named)) == repo {
s.id = image.ID
break search
}
}
}
}
}
}
if s.id == "" {
logrus.Debugf("reference %q does not resolve to an image ID", s.StringWithinTransport())
return nil, errors.Wrapf(ErrNoSuchImage, "reference %q does not resolve to an image ID", s.StringWithinTransport())
}
img, err := s.transport.store.Image(s.id)
if err != nil {
return nil, errors.Wrapf(err, "error reading image %q", s.id)
}
if s.name != nil {
repo := reference.FamiliarName(reference.TrimNamed(s.name))
nameMatch := false
for _, name := range img.Names {
if named, err := reference.ParseNormalizedNamed(name); err == nil {
if reference.FamiliarName(reference.TrimNamed(named)) == repo {
nameMatch = true
break
}
}
}
if !nameMatch {
logrus.Errorf("no image matching reference %q found", s.StringWithinTransport())
return nil, ErrNoSuchImage
}
}
return img, nil
}
// Return a Transport object that defaults to using the same store that we used
// to build this reference object.
func (s storageReference) Transport() types.ImageTransport {
return &storageTransport{
store: s.transport.store,
defaultUIDMap: s.transport.defaultUIDMap,
defaultGIDMap: s.transport.defaultGIDMap,
}
}
// Return a name with a tag or digest, if we have either, else return it bare.
func (s storageReference) DockerReference() reference.Named {
if s.name == nil {
return nil
}
if s.tag != "" {
if namedTagged, err := reference.WithTag(s.name, s.tag); err == nil {
return namedTagged
}
}
if s.digest != "" {
if canonical, err := reference.WithDigest(s.name, s.digest); err == nil {
return canonical
}
}
return s.name
}
// Return a name with a tag, prefixed with the graph root and driver name, to
// disambiguate between images which may be present in multiple stores and
// share only their names.
func (s storageReference) StringWithinTransport() string {
optionsList := ""
options := s.transport.store.GraphOptions()
if len(options) > 0 {
optionsList = ":" + strings.Join(options, ",")
}
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "+" + s.transport.store.RunRoot() + optionsList + "]"
if s.reference == "" {
return storeSpec + "@" + s.id
}
if s.id == "" {
return storeSpec + s.reference
}
return storeSpec + s.reference + "@" + s.id
}
func (s storageReference) PolicyConfigurationIdentity() string {
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]"
if s.name == nil {
return storeSpec + "@" + s.id
}
if s.id == "" {
return storeSpec + s.reference
}
return storeSpec + s.reference + "@" + s.id
}
// Also accept policy that's tied to the combination of the graph root and
// driver name, to apply to all images stored in the Store, and to just the
// graph root, in case we're using multiple drivers in the same directory for
// some reason.
func (s storageReference) PolicyConfigurationNamespaces() []string {
storeSpec := "[" + s.transport.store.GraphDriverName() + "@" + s.transport.store.GraphRoot() + "]"
driverlessStoreSpec := "[" + s.transport.store.GraphRoot() + "]"
namespaces := []string{}
if s.name != nil {
name := reference.TrimNamed(s.name)
components := strings.Split(name.String(), "/")
for len(components) > 0 {
namespaces = append(namespaces, storeSpec+strings.Join(components, "/"))
components = components[:len(components)-1]
}
}
namespaces = append(namespaces, storeSpec)
namespaces = append(namespaces, driverlessStoreSpec)
return namespaces
}
// NewImage returns a types.ImageCloser for this reference, possibly specialized for this ImageTransport.
// The caller must call .Close() on the returned ImageCloser.
// NOTE: If any kind of signature verification should happen, build an UnparsedImage from the value returned by NewImageSource,
// verify that UnparsedImage, and convert it into a real Image via image.FromUnparsedImage.
// WARNING: This may not do the right thing for a manifest list, see image.FromSource for details.
func (s storageReference) NewImage(ctx *types.SystemContext) (types.ImageCloser, error) {
return newImage(ctx, s)
}
func (s storageReference) DeleteImage(ctx *types.SystemContext) error {
img, err := s.resolveImage()
if err != nil {
return err
}
layers, err := s.transport.store.DeleteImage(img.ID, true)
if err == nil {
logrus.Debugf("deleted image %q", img.ID)
for _, layer := range layers {
logrus.Debugf("deleted layer %q", layer)
}
}
return err
}
func (s storageReference) NewImageSource(ctx *types.SystemContext) (types.ImageSource, error) {
return newImageSource(s)
}
func (s storageReference) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) {
return newImageDestination(ctx, s)
}
+450
View File
@@ -0,0 +1,450 @@
// +build !containers_image_storage_stub
package storage
import (
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/containers/image/docker/reference"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
digest "github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
)
const (
minimumTruncatedIDLength = 3
)
func init() {
transports.Register(Transport)
}
var (
// Transport is an ImageTransport that uses either a default
// storage.Store or one that's it's explicitly told to use.
Transport StoreTransport = &storageTransport{}
// ErrInvalidReference is returned when ParseReference() is passed an
// empty reference.
ErrInvalidReference = errors.New("invalid reference")
// ErrPathNotAbsolute is returned when a graph root is not an absolute
// path name.
ErrPathNotAbsolute = errors.New("path name is not absolute")
)
// StoreTransport is an ImageTransport that uses a storage.Store to parse
// references, either its own default or one that it's told to use.
type StoreTransport interface {
types.ImageTransport
// SetStore sets the default store for this transport.
SetStore(storage.Store)
// GetImage retrieves the image from the transport's store that's named
// by the reference.
GetImage(types.ImageReference) (*storage.Image, error)
// GetStoreImage retrieves the image from a specified store that's named
// by the reference.
GetStoreImage(storage.Store, types.ImageReference) (*storage.Image, error)
// ParseStoreReference parses a reference, overriding any store
// specification that it may contain.
ParseStoreReference(store storage.Store, reference string) (*storageReference, error)
// SetDefaultUIDMap sets the default UID map to use when opening stores.
SetDefaultUIDMap(idmap []idtools.IDMap)
// SetDefaultGIDMap sets the default GID map to use when opening stores.
SetDefaultGIDMap(idmap []idtools.IDMap)
// DefaultUIDMap returns the default UID map used when opening stores.
DefaultUIDMap() []idtools.IDMap
// DefaultGIDMap returns the default GID map used when opening stores.
DefaultGIDMap() []idtools.IDMap
}
type storageTransport struct {
store storage.Store
defaultUIDMap []idtools.IDMap
defaultGIDMap []idtools.IDMap
}
func (s *storageTransport) Name() string {
// Still haven't really settled on a name.
return "containers-storage"
}
// SetStore sets the Store object which the Transport will use for parsing
// references when information about a Store is not directly specified as part
// of the reference. If one is not set, the library will attempt to initialize
// one with default settings when a reference needs to be parsed. Calling
// SetStore does not affect previously parsed references.
func (s *storageTransport) SetStore(store storage.Store) {
s.store = store
}
// SetDefaultUIDMap sets the default UID map to use when opening stores.
func (s *storageTransport) SetDefaultUIDMap(idmap []idtools.IDMap) {
s.defaultUIDMap = idmap
}
// SetDefaultGIDMap sets the default GID map to use when opening stores.
func (s *storageTransport) SetDefaultGIDMap(idmap []idtools.IDMap) {
s.defaultGIDMap = idmap
}
// DefaultUIDMap returns the default UID map used when opening stores.
func (s *storageTransport) DefaultUIDMap() []idtools.IDMap {
return s.defaultUIDMap
}
// DefaultGIDMap returns the default GID map used when opening stores.
func (s *storageTransport) DefaultGIDMap() []idtools.IDMap {
return s.defaultGIDMap
}
// ParseStoreReference takes a name or an ID, tries to figure out which it is
// relative to the given store, and returns it in a reference object.
func (s storageTransport) ParseStoreReference(store storage.Store, ref string) (*storageReference, error) {
var name reference.Named
if ref == "" {
return nil, errors.Wrapf(ErrInvalidReference, "%q is an empty reference")
}
if ref[0] == '[' {
// Ignore the store specifier.
closeIndex := strings.IndexRune(ref, ']')
if closeIndex < 1 {
return nil, errors.Wrapf(ErrInvalidReference, "store specifier in %q did not end", ref)
}
ref = ref[closeIndex+1:]
}
// The last segment, if there's more than one, is either a digest from a reference, or an image ID.
split := strings.LastIndex(ref, "@")
idOrDigest := ""
if split != -1 {
// Peel off that last bit so that we can work on the rest.
idOrDigest = ref[split+1:]
if idOrDigest == "" {
return nil, errors.Wrapf(ErrInvalidReference, "%q does not look like a digest or image ID", idOrDigest)
}
ref = ref[:split]
}
// The middle segment (now the last segment), if there is one, is a digest.
split = strings.LastIndex(ref, "@")
sum := digest.Digest("")
if split != -1 {
sum = digest.Digest(ref[split+1:])
if sum == "" {
return nil, errors.Wrapf(ErrInvalidReference, "%q does not look like an image digest", sum)
}
ref = ref[:split]
}
// If we have something that unambiguously should be a digest, validate it, and then the third part,
// if we have one, as an ID.
id := ""
if sum != "" {
if idSum, err := digest.Parse("sha256:" + idOrDigest); err != nil || idSum.Validate() != nil {
return nil, errors.Wrapf(ErrInvalidReference, "%q does not look like an image ID", idOrDigest)
}
if err := sum.Validate(); err != nil {
return nil, errors.Wrapf(ErrInvalidReference, "%q does not look like an image digest", sum)
}
id = idOrDigest
if img, err := store.Image(idOrDigest); err == nil && img != nil && len(idOrDigest) >= minimumTruncatedIDLength && strings.HasPrefix(img.ID, idOrDigest) {
// The ID is a truncated version of the ID of an image that's present in local storage,
// so we might as well use the expanded value.
id = img.ID
}
} else if idOrDigest != "" {
// There was no middle portion, so the final portion could be either a digest or an ID.
if idSum, err := digest.Parse("sha256:" + idOrDigest); err == nil && idSum.Validate() == nil {
// It's an ID.
id = idOrDigest
} else if idSum, err := digest.Parse(idOrDigest); err == nil && idSum.Validate() == nil {
// It's a digest.
sum = idSum
} else if img, err := store.Image(idOrDigest); err == nil && img != nil && len(idOrDigest) >= minimumTruncatedIDLength && strings.HasPrefix(img.ID, idOrDigest) {
// It's a truncated version of the ID of an image that's present in local storage,
// and we may need the expanded value.
id = img.ID
} else {
return nil, errors.Wrapf(ErrInvalidReference, "%q does not look like a digest or image ID", idOrDigest)
}
}
// If we only had one portion, then _maybe_ it's a truncated image ID. Only check on that if it's
// at least of what we guess is a reasonable minimum length, because we don't want a really short value
// like "a" matching an image by ID prefix when the input was actually meant to specify an image name.
if len(ref) >= minimumTruncatedIDLength && sum == "" && id == "" {
if img, err := store.Image(ref); err == nil && img != nil && strings.HasPrefix(img.ID, ref) {
// It's a truncated version of the ID of an image that's present in local storage;
// we need to expand it.
id = img.ID
ref = ""
}
}
// The initial portion is probably a name, possibly with a tag.
if ref != "" {
var err error
if name, err = reference.ParseNormalizedNamed(ref); err != nil {
return nil, errors.Wrapf(err, "error parsing named reference %q", ref)
}
}
if name == nil && sum == "" && id == "" {
return nil, errors.Errorf("error parsing reference")
}
// Construct a copy of the store spec.
optionsList := ""
options := store.GraphOptions()
if len(options) > 0 {
optionsList = ":" + strings.Join(options, ",")
}
storeSpec := "[" + store.GraphDriverName() + "@" + store.GraphRoot() + "+" + store.RunRoot() + optionsList + "]"
// Convert the name back into a reference string, if we got a name.
refname := ""
tag := ""
if name != nil {
if sum.Validate() == nil {
canonical, err := reference.WithDigest(name, sum)
if err != nil {
return nil, errors.Wrapf(err, "error mixing name %q with digest %q", name, sum)
}
refname = verboseName(canonical)
} else {
name = reference.TagNameOnly(name)
tagged, ok := name.(reference.Tagged)
if !ok {
return nil, errors.Errorf("error parsing possibly-tagless name %q", ref)
}
refname = verboseName(name)
tag = tagged.Tag()
}
}
if refname == "" {
logrus.Debugf("parsed reference to id into %q", storeSpec+"@"+id)
} else if id == "" {
logrus.Debugf("parsed reference to refname into %q", storeSpec+refname)
} else {
logrus.Debugf("parsed reference to refname@id into %q", storeSpec+refname+"@"+id)
}
return newReference(storageTransport{store: store, defaultUIDMap: s.defaultUIDMap, defaultGIDMap: s.defaultGIDMap}, refname, id, name, tag, sum), nil
}
func (s *storageTransport) GetStore() (storage.Store, error) {
// Return the transport's previously-set store. If we don't have one
// of those, initialize one now.
if s.store == nil {
options := storage.DefaultStoreOptions
options.UIDMap = s.defaultUIDMap
options.GIDMap = s.defaultGIDMap
store, err := storage.GetStore(options)
if err != nil {
return nil, err
}
s.store = store
}
return s.store, nil
}
// ParseReference takes a name and a tag or digest and/or ID
// ("_name_"/"@_id_"/"_name_:_tag_"/"_name_:_tag_@_id_"/"_name_@_digest_"/"_name_@_digest_@_id_"),
// possibly prefixed with a store specifier in the form "[_graphroot_]" or
// "[_driver_@_graphroot_]" or "[_driver_@_graphroot_+_runroot_]" or
// "[_driver_@_graphroot_:_options_]" or "[_driver_@_graphroot_+_runroot_:_options_]",
// tries to figure out which it is, and returns it in a reference object.
// If _id_ is the ID of an image that's present in local storage, it can be truncated, and
// even be specified as if it were a _name_, value.
func (s *storageTransport) ParseReference(reference string) (types.ImageReference, error) {
var store storage.Store
// Check if there's a store location prefix. If there is, then it
// needs to match a store that was previously initialized using
// storage.GetStore(), or be enough to let the storage library fill out
// the rest using knowledge that it has from elsewhere.
if reference[0] == '[' {
closeIndex := strings.IndexRune(reference, ']')
if closeIndex < 1 {
return nil, ErrInvalidReference
}
storeSpec := reference[1:closeIndex]
reference = reference[closeIndex+1:]
// Peel off a "driver@" from the start.
driverInfo := ""
driverSplit := strings.SplitN(storeSpec, "@", 2)
if len(driverSplit) != 2 {
if storeSpec == "" {
return nil, ErrInvalidReference
}
} else {
driverInfo = driverSplit[0]
if driverInfo == "" {
return nil, ErrInvalidReference
}
storeSpec = driverSplit[1]
if storeSpec == "" {
return nil, ErrInvalidReference
}
}
// Peel off a ":options" from the end.
var options []string
optionsSplit := strings.SplitN(storeSpec, ":", 2)
if len(optionsSplit) == 2 {
options = strings.Split(optionsSplit[1], ",")
storeSpec = optionsSplit[0]
}
// Peel off a "+runroot" from the new end.
runRootInfo := ""
runRootSplit := strings.SplitN(storeSpec, "+", 2)
if len(runRootSplit) == 2 {
runRootInfo = runRootSplit[1]
storeSpec = runRootSplit[0]
}
// The rest is our graph root.
rootInfo := storeSpec
// Check that any paths are absolute paths.
if rootInfo != "" && !filepath.IsAbs(rootInfo) {
return nil, ErrPathNotAbsolute
}
if runRootInfo != "" && !filepath.IsAbs(runRootInfo) {
return nil, ErrPathNotAbsolute
}
store2, err := storage.GetStore(storage.StoreOptions{
GraphDriverName: driverInfo,
GraphRoot: rootInfo,
RunRoot: runRootInfo,
GraphDriverOptions: options,
UIDMap: s.defaultUIDMap,
GIDMap: s.defaultGIDMap,
})
if err != nil {
return nil, err
}
store = store2
} else {
// We didn't have a store spec, so use the default.
store2, err := s.GetStore()
if err != nil {
return nil, err
}
store = store2
}
return s.ParseStoreReference(store, reference)
}
func (s storageTransport) GetStoreImage(store storage.Store, ref types.ImageReference) (*storage.Image, error) {
dref := ref.DockerReference()
if dref != nil {
if img, err := store.Image(verboseName(dref)); err == nil {
return img, nil
}
}
if sref, ok := ref.(*storageReference); ok {
if sref.id != "" {
if img, err := store.Image(sref.id); err == nil {
return img, nil
}
}
tmpRef := *sref
if img, err := tmpRef.resolveImage(); err == nil {
return img, nil
}
}
return nil, storage.ErrImageUnknown
}
func (s *storageTransport) GetImage(ref types.ImageReference) (*storage.Image, error) {
store, err := s.GetStore()
if err != nil {
return nil, err
}
return s.GetStoreImage(store, ref)
}
func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error {
// Check that there's a store location prefix. Values we're passed are
// expected to come from PolicyConfigurationIdentity or
// PolicyConfigurationNamespaces, so if there's no store location,
// something's wrong.
if scope[0] != '[' {
return ErrInvalidReference
}
// Parse the store location prefix.
closeIndex := strings.IndexRune(scope, ']')
if closeIndex < 1 {
return ErrInvalidReference
}
storeSpec := scope[1:closeIndex]
scope = scope[closeIndex+1:]
storeInfo := strings.SplitN(storeSpec, "@", 2)
if len(storeInfo) == 1 && storeInfo[0] != "" {
// One component: the graph root.
if !filepath.IsAbs(storeInfo[0]) {
return ErrPathNotAbsolute
}
} else if len(storeInfo) == 2 && storeInfo[0] != "" && storeInfo[1] != "" {
// Two components: the driver type and the graph root.
if !filepath.IsAbs(storeInfo[1]) {
return ErrPathNotAbsolute
}
} else {
// Anything else: scope specified in a form we don't
// recognize.
return ErrInvalidReference
}
// That might be all of it, and that's okay.
if scope == "" {
return nil
}
// But if there is anything left, it has to be a name, with or without
// a tag, with or without an ID, since we don't return namespace values
// that are just bare IDs.
scopeInfo := strings.SplitN(scope, "@", 2)
if len(scopeInfo) == 1 && scopeInfo[0] != "" {
_, err := reference.ParseNormalizedNamed(scopeInfo[0])
if err != nil {
return err
}
} else if len(scopeInfo) == 2 && scopeInfo[0] != "" && scopeInfo[1] != "" {
_, err := reference.ParseNormalizedNamed(scopeInfo[0])
if err != nil {
return err
}
_, err = digest.Parse("sha256:" + scopeInfo[1])
if err != nil {
return err
}
} else {
return ErrInvalidReference
}
return nil
}
func verboseName(r reference.Reference) string {
if r == nil {
return ""
}
named, isNamed := r.(reference.Named)
digested, isDigested := r.(reference.Digested)
tagged, isTagged := r.(reference.Tagged)
name := ""
tag := ""
sum := ""
if isNamed {
name = (reference.TrimNamed(named)).String()
}
if isTagged {
if tagged.Tag() != "" {
tag = ":" + tagged.Tag()
}
}
if isDigested {
if digested.Digest().Validate() == nil {
sum = "@" + digested.Digest().String()
}
}
return name + tag + sum
}