Merge pull request #466 from nalind/update-storage

Bump containers/storage and containers/image
This commit is contained in:
Miloslav Trmač 2017-12-14 12:21:08 +01:00 committed by GitHub
commit 1c27d6918f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 348 additions and 182 deletions

View File

@ -14,6 +14,7 @@ import (
type daemonImageDestination struct { type daemonImageDestination struct {
ref daemonReference ref daemonReference
mustMatchRuntimeOS bool
*tarfile.Destination // Implements most of types.ImageDestination *tarfile.Destination // Implements most of types.ImageDestination
// For talking to imageLoadGoroutine // For talking to imageLoadGoroutine
goroutineCancel context.CancelFunc goroutineCancel context.CancelFunc
@ -33,6 +34,11 @@ func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.I
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport()) return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
} }
var mustMatchRuntimeOS = true
if ctx != nil && ctx.DockerDaemonHost != client.DefaultDockerHost {
mustMatchRuntimeOS = false
}
c, err := newDockerClient(ctx) c, err := newDockerClient(ctx)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "Error initializing docker engine client") return nil, errors.Wrap(err, "Error initializing docker engine client")
@ -47,6 +53,7 @@ func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.I
return &daemonImageDestination{ return &daemonImageDestination{
ref: ref, ref: ref,
mustMatchRuntimeOS: mustMatchRuntimeOS,
Destination: tarfile.NewDestination(writer, namedTaggedRef), Destination: tarfile.NewDestination(writer, namedTaggedRef),
goroutineCancel: goroutineCancel, goroutineCancel: goroutineCancel,
statusChannel: statusChannel, statusChannel: statusChannel,
@ -80,7 +87,7 @@ func imageLoadGoroutine(ctx context.Context, c *client.Client, reader *io.PipeRe
// MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise. // MustMatchRuntimeOS returns true iff the destination can store only images targeted for the current runtime OS. False otherwise.
func (d *daemonImageDestination) MustMatchRuntimeOS() bool { func (d *daemonImageDestination) MustMatchRuntimeOS() bool {
return true return d.mustMatchRuntimeOS
} }
// Close removes resources associated with an initialized ImageDestination, if any. // Close removes resources associated with an initialized ImageDestination, if any.

View File

@ -6,13 +6,12 @@ import (
"os" "os"
"github.com/containers/image/docker/tarfile" "github.com/containers/image/docker/tarfile"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/types" "github.com/containers/image/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
const temporaryDirectoryForBigFiles = "/var/tmp" // Do not use the system default of os.TempDir(), usually /tmp, because with systemd it could be a tmpfs.
type daemonImageSource struct { type daemonImageSource struct {
ref daemonReference ref daemonReference
*tarfile.Source // Implements most of types.ImageSource *tarfile.Source // Implements most of types.ImageSource
@ -47,7 +46,7 @@ func newImageSource(ctx *types.SystemContext, ref daemonReference) (types.ImageS
defer inputStream.Close() defer inputStream.Close()
// FIXME: use SystemContext here. // FIXME: use SystemContext here.
tarCopyFile, err := ioutil.TempFile(temporaryDirectoryForBigFiles, "docker-daemon-tar") tarCopyFile, err := ioutil.TempFile(tmpdir.TemporaryDirectoryForBigFiles(), "docker-daemon-tar")
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/containers/image/docker/reference" "github.com/containers/image/docker/reference"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/manifest" "github.com/containers/image/manifest"
"github.com/containers/image/types" "github.com/containers/image/types"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
@ -18,8 +19,6 @@ import (
"github.com/sirupsen/logrus" "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.
// Destination is a partial implementation of types.ImageDestination for writing to an io.Writer. // Destination is a partial implementation of types.ImageDestination for writing to an io.Writer.
type Destination struct { type Destination struct {
writer io.Writer writer io.Writer
@ -107,7 +106,7 @@ func (d *Destination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types
if inputInfo.Size == -1 { // Ouch, we need to stream the blob into a temporary file just to determine the size. if inputInfo.Size == -1 { // Ouch, we need to stream the blob into a temporary file just to determine the size.
logrus.Debugf("docker tarfile: input with unknown size, streaming to disk first ...") logrus.Debugf("docker tarfile: input with unknown size, streaming to disk first ...")
streamCopy, err := ioutil.TempFile(temporaryDirectoryForBigFiles, "docker-tarfile-blob") streamCopy, err := ioutil.TempFile(tmpdir.TemporaryDirectoryForBigFiles(), "docker-tarfile-blob")
if err != nil { if err != nil {
return types.BlobInfo{}, err return types.BlobInfo{}, err
} }

View File

@ -0,0 +1,19 @@
package tmpdir
import (
"os"
"runtime"
)
// TemporaryDirectoryForBigFiles returns a directory for temporary (big) files.
// On non Windows systems it avoids the use of os.TempDir(), because the default temporary directory usually falls under /tmp
// which on systemd based systems could be the unsuitable tmpfs filesystem.
func TemporaryDirectoryForBigFiles() string {
var temporaryDirectoryForBigFiles string
if runtime.GOOS == "windows" {
temporaryDirectoryForBigFiles = os.TempDir()
} else {
temporaryDirectoryForBigFiles = "/var/tmp"
}
return temporaryDirectoryForBigFiles
}

View File

@ -4,13 +4,13 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"regexp"
"strings" "strings"
"github.com/containers/image/directory/explicitfilepath" "github.com/containers/image/directory/explicitfilepath"
"github.com/containers/image/docker/reference" "github.com/containers/image/docker/reference"
"github.com/containers/image/image" "github.com/containers/image/image"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/oci/internal"
ocilayout "github.com/containers/image/oci/layout" ocilayout "github.com/containers/image/oci/layout"
"github.com/containers/image/transports" "github.com/containers/image/transports"
"github.com/containers/image/types" "github.com/containers/image/types"
@ -48,51 +48,12 @@ func (t ociArchiveTransport) ParseReference(reference string) (types.ImageRefere
// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys // ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys
func (t ociArchiveTransport) ValidatePolicyConfigurationScope(scope string) error { func (t ociArchiveTransport) ValidatePolicyConfigurationScope(scope string) error {
var file string return internal.ValidateScope(scope)
sep := strings.SplitN(scope, ":", 2)
file = sep[0]
if len(sep) == 2 {
image := sep[1]
if !refRegexp.MatchString(image) {
return errors.Errorf("Invalid image %s", image)
}
}
if !strings.HasPrefix(file, "/") {
return errors.Errorf("Invalid scope %s: must be an absolute path", scope)
}
// Refuse also "/", otherwise "/" and "" would have the same semantics,
// and "" could be unexpectedly shadowed by the "/" entry.
// (Note: we do allow "/:someimage", a bit ridiculous but why refuse it?)
if scope == "/" {
return errors.New(`Invalid scope "/": Use the generic default scope ""`)
}
cleaned := filepath.Clean(file)
if cleaned != file {
return errors.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
}
return nil
} }
// annotation spex from https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys
const (
separator = `(?:[-._:@+]|--)`
alphanum = `(?:[A-Za-z0-9]+)`
component = `(?:` + alphanum + `(?:` + separator + alphanum + `)*)`
)
var refRegexp = regexp.MustCompile(`^` + component + `(?:/` + component + `)*$`)
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference. // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference.
func ParseReference(reference string) (types.ImageReference, error) { func ParseReference(reference string) (types.ImageReference, error) {
var file, image string file, image := internal.SplitPathAndImage(reference)
sep := strings.SplitN(reference, ":", 2)
file = sep[0]
if len(sep) == 2 {
image = sep[1]
}
return NewReference(file, image) return NewReference(file, image)
} }
@ -102,14 +63,15 @@ func NewReference(file, image string) (types.ImageReference, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// This is necessary to prevent directory paths returned by PolicyConfigurationNamespaces
// from being ambiguous with values of PolicyConfigurationIdentity. if err := internal.ValidateOCIPath(file); err != nil {
if strings.Contains(resolved, ":") { return nil, err
return nil, errors.Errorf("Invalid OCI reference %s:%s: path %s contains a colon", file, image, resolved)
} }
if len(image) > 0 && !refRegexp.MatchString(image) {
return nil, errors.Errorf("Invalid image %s", image) if err := internal.ValidateImageName(image); err != nil {
return nil, err
} }
return ociArchiveReference{file: file, resolvedFile: resolved, image: image}, nil return ociArchiveReference{file: file, resolvedFile: resolved, image: image}, nil
} }
@ -197,7 +159,7 @@ func (t *tempDirOCIRef) deleteTempDir() error {
// createOCIRef creates the oci reference of the image // createOCIRef creates the oci reference of the image
func createOCIRef(image string) (tempDirOCIRef, error) { func createOCIRef(image string) (tempDirOCIRef, error) {
dir, err := ioutil.TempDir("/var/tmp", "oci") dir, err := ioutil.TempDir(tmpdir.TemporaryDirectoryForBigFiles(), "oci")
if err != nil { if err != nil {
return tempDirOCIRef{}, errors.Wrapf(err, "error creating temp directory") return tempDirOCIRef{}, errors.Wrapf(err, "error creating temp directory")
} }

View File

@ -0,0 +1,126 @@
package internal
import (
"github.com/pkg/errors"
"path/filepath"
"regexp"
"runtime"
"strings"
)
// annotation spex from https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys
const (
separator = `(?:[-._:@+]|--)`
alphanum = `(?:[A-Za-z0-9]+)`
component = `(?:` + alphanum + `(?:` + separator + alphanum + `)*)`
)
var refRegexp = regexp.MustCompile(`^` + component + `(?:/` + component + `)*$`)
var windowsRefRegexp = regexp.MustCompile(`^([a-zA-Z]:\\.+?):(.*)$`)
// ValidateImageName returns nil if the image name is empty or matches the open-containers image name specs.
// In any other case an error is returned.
func ValidateImageName(image string) error {
if len(image) == 0 {
return nil
}
var err error
if !refRegexp.MatchString(image) {
err = errors.Errorf("Invalid image %s", image)
}
return err
}
// SplitPathAndImage tries to split the provided OCI reference into the OCI path and image.
// Neither path nor image parts are validated at this stage.
func SplitPathAndImage(reference string) (string, string) {
if runtime.GOOS == "windows" {
return splitPathAndImageWindows(reference)
}
return splitPathAndImageNonWindows(reference)
}
func splitPathAndImageWindows(reference string) (string, string) {
groups := windowsRefRegexp.FindStringSubmatch(reference)
// nil group means no match
if groups == nil {
return reference, ""
}
// we expect three elements. First one full match, second the capture group for the path and
// the third the capture group for the image
if len(groups) != 3 {
return reference, ""
}
return groups[1], groups[2]
}
func splitPathAndImageNonWindows(reference string) (string, string) {
sep := strings.SplitN(reference, ":", 2)
path := sep[0]
var image string
if len(sep) == 2 {
image = sep[1]
}
return path, image
}
// ValidateOCIPath takes the OCI path and validates it.
func ValidateOCIPath(path string) error {
if runtime.GOOS == "windows" {
// On Windows we must allow for a ':' as part of the path
if strings.Count(path, ":") > 1 {
return errors.Errorf("Invalid OCI reference: path %s contains more than one colon", path)
}
} else {
if strings.Contains(path, ":") {
return errors.Errorf("Invalid OCI reference: path %s contains a colon", path)
}
}
return nil
}
// ValidateScope validates a policy configuration scope for an OCI transport.
func ValidateScope(scope string) error {
var err error
if runtime.GOOS == "windows" {
err = validateScopeWindows(scope)
} else {
err = validateScopeNonWindows(scope)
}
if err != nil {
return err
}
cleaned := filepath.Clean(scope)
if cleaned != scope {
return errors.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
}
return nil
}
func validateScopeWindows(scope string) error {
matched, _ := regexp.Match(`^[a-zA-Z]:\\`, []byte(scope))
if !matched {
return errors.Errorf("Invalid scope '%s'. Must be an absolute path", scope)
}
return nil
}
func validateScopeNonWindows(scope string) error {
if !strings.HasPrefix(scope, "/") {
return errors.Errorf("Invalid scope %s: must be an absolute path", scope)
}
// Refuse also "/", otherwise "/" and "" would have the same semantics,
// and "" could be unexpectedly shadowed by the "/" entry.
if scope == "/" {
return errors.New(`Invalid scope "/": Use the generic default scope ""`)
}
return nil
}

View File

@ -112,8 +112,11 @@ func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
return types.BlobInfo{}, err return types.BlobInfo{}, err
} }
succeeded := false succeeded := false
explicitClosed := false
defer func() { defer func() {
if !explicitClosed {
blobFile.Close() blobFile.Close()
}
if !succeeded { if !succeeded {
os.Remove(blobFile.Name()) os.Remove(blobFile.Name())
} }
@ -133,9 +136,16 @@ func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
if err := blobFile.Sync(); err != nil { if err := blobFile.Sync(); err != nil {
return types.BlobInfo{}, err return types.BlobInfo{}, err
} }
// On POSIX systems, blobFile was created with mode 0600, so we need to make it readable.
// On Windows, the “permissions of newly created files” argument to syscall.Open is
// ignored and the file is already readable; besides, blobFile.Chmod, i.e. syscall.Fchmod,
// always fails on Windows.
if runtime.GOOS != "windows" {
if err := blobFile.Chmod(0644); err != nil { if err := blobFile.Chmod(0644); err != nil {
return types.BlobInfo{}, err return types.BlobInfo{}, err
} }
}
blobPath, err := d.ref.blobPath(computedDigest, d.sharedBlobDir) blobPath, err := d.ref.blobPath(computedDigest, d.sharedBlobDir)
if err != nil { if err != nil {
@ -144,6 +154,10 @@ func (d *ociImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo
if err := ensureParentDirectoryExists(blobPath); err != nil { if err := ensureParentDirectoryExists(blobPath); err != nil {
return types.BlobInfo{}, err return types.BlobInfo{}, err
} }
// need to explicitly close the file, since a rename won't otherwise not work on Windows
blobFile.Close()
explicitClosed = true
if err := os.Rename(blobFile.Name(), blobPath); err != nil { if err := os.Rename(blobFile.Name(), blobPath); err != nil {
return types.BlobInfo{}, err return types.BlobInfo{}, err
} }

View File

@ -5,12 +5,12 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"github.com/containers/image/directory/explicitfilepath" "github.com/containers/image/directory/explicitfilepath"
"github.com/containers/image/docker/reference" "github.com/containers/image/docker/reference"
"github.com/containers/image/image" "github.com/containers/image/image"
"github.com/containers/image/oci/internal"
"github.com/containers/image/transports" "github.com/containers/image/transports"
"github.com/containers/image/types" "github.com/containers/image/types"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
@ -36,45 +36,12 @@ func (t ociTransport) ParseReference(reference string) (types.ImageReference, er
return ParseReference(reference) return ParseReference(reference)
} }
// annotation spex from https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys
const (
separator = `(?:[-._:@+]|--)`
alphanum = `(?:[A-Za-z0-9]+)`
component = `(?:` + alphanum + `(?:` + separator + alphanum + `)*)`
)
var refRegexp = regexp.MustCompile(`^` + component + `(?:/` + component + `)*$`)
// ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys // ValidatePolicyConfigurationScope checks that scope is a valid name for a signature.PolicyTransportScopes keys
// (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value). // (i.e. a valid PolicyConfigurationIdentity() or PolicyConfigurationNamespaces() return value).
// It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion. // It is acceptable to allow an invalid value which will never be matched, it can "only" cause user confusion.
// scope passed to this function will not be "", that value is always allowed. // scope passed to this function will not be "", that value is always allowed.
func (t ociTransport) ValidatePolicyConfigurationScope(scope string) error { func (t ociTransport) ValidatePolicyConfigurationScope(scope string) error {
var dir string return internal.ValidateScope(scope)
sep := strings.SplitN(scope, ":", 2)
dir = sep[0]
if len(sep) == 2 {
image := sep[1]
if !refRegexp.MatchString(image) {
return errors.Errorf("Invalid image %s", image)
}
}
if !strings.HasPrefix(dir, "/") {
return errors.Errorf("Invalid scope %s: must be an absolute path", scope)
}
// Refuse also "/", otherwise "/" and "" would have the same semantics,
// and "" could be unexpectedly shadowed by the "/" entry.
// (Note: we do allow "/:someimage", a bit ridiculous but why refuse it?)
if scope == "/" {
return errors.New(`Invalid scope "/": Use the generic default scope ""`)
}
cleaned := filepath.Clean(dir)
if cleaned != dir {
return errors.Errorf(`Invalid scope %s: Uses non-canonical path format, perhaps try with path %s`, scope, cleaned)
}
return nil
} }
// ociReference is an ImageReference for OCI directory paths. // ociReference is an ImageReference for OCI directory paths.
@ -92,13 +59,7 @@ type ociReference struct {
// ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference. // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OCI ImageReference.
func ParseReference(reference string) (types.ImageReference, error) { func ParseReference(reference string) (types.ImageReference, error) {
var dir, image string dir, image := internal.SplitPathAndImage(reference)
sep := strings.SplitN(reference, ":", 2)
dir = sep[0]
if len(sep) == 2 {
image = sep[1]
}
return NewReference(dir, image) return NewReference(dir, image)
} }
@ -111,14 +72,15 @@ func NewReference(dir, image string) (types.ImageReference, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// This is necessary to prevent directory paths returned by PolicyConfigurationNamespaces
// from being ambiguous with values of PolicyConfigurationIdentity. if err := internal.ValidateOCIPath(dir); err != nil {
if strings.Contains(resolved, ":") { return nil, err
return nil, errors.Errorf("Invalid OCI reference %s:%s: path %s contains a colon", dir, image, resolved)
} }
if len(image) > 0 && !refRegexp.MatchString(image) {
return nil, errors.Errorf("Invalid image %s", image) if err = internal.ValidateImageName(image); err != nil {
return nil, err
} }
return ociReference{dir: dir, resolvedDir: resolved, image: image}, nil return ociReference{dir: dir, resolvedDir: resolved, image: image}, nil
} }

View File

@ -159,11 +159,11 @@ func (s storageTransport) ParseStoreReference(store storage.Store, ref string) (
refname = verboseName(name) refname = verboseName(name)
} }
if refname == "" { if refname == "" {
logrus.Debugf("parsed reference into %q", storeSpec+"@"+id) logrus.Debugf("parsed reference to id into %q", storeSpec+"@"+id)
} else if id == "" { } else if id == "" {
logrus.Debugf("parsed reference into %q", storeSpec+refname) logrus.Debugf("parsed reference to refname into %q", storeSpec+refname)
} else { } else {
logrus.Debugf("parsed reference into %q", storeSpec+refname+"@"+id) 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), nil return newReference(storageTransport{store: store, defaultUIDMap: s.defaultUIDMap, defaultGIDMap: s.defaultGIDMap}, refname, id, name), nil
} }

View File

@ -3,7 +3,6 @@
package overlay package overlay
import ( import (
"bufio"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -26,7 +25,6 @@ import (
"github.com/containers/storage/pkg/locker" "github.com/containers/storage/pkg/locker"
"github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/mount"
"github.com/containers/storage/pkg/parsers" "github.com/containers/storage/pkg/parsers"
"github.com/containers/storage/pkg/parsers/kernel"
"github.com/containers/storage/pkg/system" "github.com/containers/storage/pkg/system"
units "github.com/docker/go-units" units "github.com/docker/go-units"
"github.com/opencontainers/selinux/go-selinux/label" "github.com/opencontainers/selinux/go-selinux/label"
@ -124,22 +122,6 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
return nil, err return nil, err
} }
if err := supportsOverlay(); err != nil {
return nil, errors.Wrap(graphdriver.ErrNotSupported, "kernel does not support overlay fs")
}
// require kernel 4.0.0 to ensure multiple lower dirs are supported
v, err := kernel.GetKernelVersion()
if err != nil {
return nil, err
}
if kernel.CompareKernelVersion(*v, kernel.VersionInfo{Kernel: 4, Major: 0, Minor: 0}) < 0 {
if !opts.overrideKernelCheck {
return nil, errors.Wrap(graphdriver.ErrNotSupported, "kernel too old to provide multiple lowers feature for overlay")
}
logrus.Warn("Using pre-4.0.0 kernel for overlay, mount failures may require kernel update")
}
fsMagic, err := graphdriver.GetFSMagic(home) fsMagic, err := graphdriver.GetFSMagic(home)
if err != nil { if err != nil {
return nil, err return nil, err
@ -153,22 +135,18 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
case graphdriver.FsMagicAufs, graphdriver.FsMagicZfs, graphdriver.FsMagicOverlay, graphdriver.FsMagicEcryptfs: case graphdriver.FsMagicAufs, graphdriver.FsMagicZfs, graphdriver.FsMagicOverlay, graphdriver.FsMagicEcryptfs:
logrus.Errorf("'overlay' is not supported over %s", backingFs) logrus.Errorf("'overlay' is not supported over %s", backingFs)
return nil, errors.Wrapf(graphdriver.ErrIncompatibleFS, "'overlay' is not supported over %s", backingFs) return nil, errors.Wrapf(graphdriver.ErrIncompatibleFS, "'overlay' is not supported over %s", backingFs)
case graphdriver.FsMagicBtrfs:
// Support for OverlayFS on BTRFS was added in kernel 4.7
// See https://btrfs.wiki.kernel.org/index.php/Changelog
if kernel.CompareKernelVersion(*v, kernel.VersionInfo{Kernel: 4, Major: 7, Minor: 0}) < 0 {
if !opts.overrideKernelCheck {
logrus.Errorf("'overlay' requires kernel 4.7 to use on %s", backingFs)
return nil, errors.Wrapf(graphdriver.ErrIncompatibleFS, "'overlay' requires kernel 4.7 to use on %s", backingFs)
}
logrus.Warn("Using pre-4.7.0 kernel for overlay on btrfs, may require kernel update")
}
} }
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil { if err != nil {
return nil, err return nil, err
} }
supportsDType, err := supportsOverlay(home, fsMagic, rootUID, rootGID)
if err != nil {
return nil, errors.Wrap(graphdriver.ErrNotSupported, "kernel does not support overlay fs")
}
// Create the driver home dir // Create the driver home dir
if err := idtools.MkdirAllAs(path.Join(home, linkDir), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) { if err := idtools.MkdirAllAs(path.Join(home, linkDir), 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
return nil, err return nil, err
@ -178,16 +156,6 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
return nil, err return nil, err
} }
supportsDType, err := fsutils.SupportsDType(home)
if err != nil {
return nil, err
}
if !supportsDType {
logrus.Warn(overlayutils.ErrDTypeNotSupported("overlay", backingFs))
// TODO: Will make fatal when CRI-O Has AMI built on RHEL7.4
// return nil, overlayutils.ErrDTypeNotSupported("overlay", backingFs)
}
d := &Driver{ d := &Driver{
name: "overlay", name: "overlay",
home: home, home: home,
@ -210,7 +178,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
} }
} else if opts.quota.Size > 0 { } else if opts.quota.Size > 0 {
// if xfs is not the backing fs then error out if the storage-opt overlay.size is used. // if xfs is not the backing fs then error out if the storage-opt overlay.size is used.
return nil, fmt.Errorf("Storage Option overlay.size only supported for backingFS XFS. Found %v", backingFs) return nil, fmt.Errorf("Storage option overlay.size only supported for backingFS XFS. Found %v", backingFs)
} }
logrus.Debugf("backingFs=%s, projectQuotaSupported=%v", backingFs, projectQuotaSupported) logrus.Debugf("backingFs=%s, projectQuotaSupported=%v", backingFs, projectQuotaSupported)
@ -264,25 +232,59 @@ func parseOptions(options []string) (*overlayOptions, error) {
return o, nil return o, nil
} }
func supportsOverlay() error { func supportsOverlay(home string, homeMagic graphdriver.FsMagic, rootUID, rootGID int) (supportsDType bool, err error) {
// We can try to modprobe overlay first before looking at // We can try to modprobe overlay first
// proc/filesystems for when overlay is supported
exec.Command("modprobe", "overlay").Run() exec.Command("modprobe", "overlay").Run()
f, err := os.Open("/proc/filesystems") layerDir, err := ioutil.TempDir(home, "compat")
if err == nil {
// Check if reading the directory's contents populates the d_type field, which is required
// for proper operation of the overlay filesystem.
supportsDType, err = fsutils.SupportsDType(layerDir)
if err != nil { if err != nil {
return err return false, err
}
if !supportsDType {
logrus.Warn(overlayutils.ErrDTypeNotSupported("overlay", backingFs))
// TODO: Will make fatal when CRI-O Has AMI built on RHEL7.4
// return nil, overlayutils.ErrDTypeNotSupported("overlay", backingFs)
} }
defer f.Close()
s := bufio.NewScanner(f) // Try a test mount in the specific location we're looking at using.
for s.Scan() { mergedDir := filepath.Join(layerDir, "merged")
if s.Text() == "nodev\toverlay" { lower1Dir := filepath.Join(layerDir, "lower1")
return nil lower2Dir := filepath.Join(layerDir, "lower2")
defer func() {
// Permitted to fail, since the various subdirectories
// can be empty or not even there, and the home might
// legitimately be not empty
_ = unix.Unmount(mergedDir, unix.MNT_DETACH)
_ = os.RemoveAll(layerDir)
_ = os.Remove(home)
}()
_ = idtools.MkdirAs(mergedDir, 0700, rootUID, rootGID)
_ = idtools.MkdirAs(lower1Dir, 0700, rootUID, rootGID)
_ = idtools.MkdirAs(lower2Dir, 0700, rootUID, rootGID)
flags := fmt.Sprintf("lowerdir=%s:%s", lower1Dir, lower2Dir)
if len(flags) < unix.Getpagesize() {
if mountFrom(filepath.Dir(home), "overlay", mergedDir, "overlay", 0, flags) == nil {
logrus.Debugf("overlay test mount with multiple lowers succeeded")
return supportsDType, nil
} }
} }
flags = fmt.Sprintf("lowerdir=%s", lower1Dir)
if len(flags) < unix.Getpagesize() {
if mountFrom(filepath.Dir(home), "overlay", mergedDir, "overlay", 0, flags) == nil {
logrus.Errorf("overlay test mount with multiple lowers failed, but succeeded with a single lower")
return supportsDType, errors.Wrap(graphdriver.ErrNotSupported, "kernel too old to provide multiple lowers feature for overlay")
}
}
logrus.Errorf("'overlay' is not supported over %s at %q", backingFs, home)
return supportsDType, errors.Wrapf(graphdriver.ErrIncompatibleFS, "'overlay' is not supported over %s at %q", backingFs, home)
}
logrus.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.") logrus.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
return errors.Wrap(graphdriver.ErrNotSupported, "'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.") return supportsDType, errors.Wrap(graphdriver.ErrNotSupported, "'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
} }
func useNaiveDiff(home string) bool { func useNaiveDiff(home string) bool {

View File

@ -27,6 +27,9 @@ type Image struct {
// value which was generated by the library. // value which was generated by the library.
ID string `json:"id"` ID string `json:"id"`
// Digest is a digest value that we can use to locate the image.
Digest digest.Digest `json:"digest,omitempty"`
// Names is an optional set of user-defined convenience values. The // Names is an optional set of user-defined convenience values. The
// image can be referred to by its ID or any of its names. Names are // image can be referred to by its ID or any of its names. Names are
// unique among images. // unique among images.
@ -98,7 +101,7 @@ type ImageStore interface {
// Create creates an image that has a specified ID (or a random one) and // Create creates an image that has a specified ID (or a random one) and
// optional names, using the specified layer as its topmost (hopefully // optional names, using the specified layer as its topmost (hopefully
// read-only) layer. That layer can be referenced by multiple images. // read-only) layer. That layer can be referenced by multiple images.
Create(id string, names []string, layer, metadata string, created time.Time) (*Image, error) Create(id string, names []string, layer, metadata string, created time.Time, searchableDigest digest.Digest) (*Image, error)
// SetNames replaces the list of names associated with an image with the // SetNames replaces the list of names associated with an image with the
// supplied values. // supplied values.
@ -165,9 +168,16 @@ func (r *imageStore) Load() error {
} }
names[name] = images[n] names[name] = images[n]
} }
// Implicit digest
if digest, ok := image.BigDataDigests[ImageDigestBigDataKey]; ok { if digest, ok := image.BigDataDigests[ImageDigestBigDataKey]; ok {
digests[digest] = append(digests[digest], images[n]) digests[digest] = append(digests[digest], images[n])
} }
// Explicit digest
if image.Digest == "" {
image.Digest = image.BigDataDigests[ImageDigestBigDataKey]
} else if image.Digest != image.BigDataDigests[ImageDigestBigDataKey] {
digests[image.Digest] = append(digests[image.Digest], images[n])
}
} }
} }
if shouldSave && !r.IsReadWrite() { if shouldSave && !r.IsReadWrite() {
@ -284,7 +294,7 @@ func (r *imageStore) SetFlag(id string, flag string, value interface{}) error {
return r.Save() return r.Save()
} }
func (r *imageStore) Create(id string, names []string, layer, metadata string, created time.Time) (image *Image, err error) { func (r *imageStore) Create(id string, names []string, layer, metadata string, created time.Time, searchableDigest digest.Digest) (image *Image, err error) {
if !r.IsReadWrite() { if !r.IsReadWrite() {
return nil, errors.Wrapf(ErrStoreIsReadOnly, "not allowed to create new images at %q", r.imagespath()) return nil, errors.Wrapf(ErrStoreIsReadOnly, "not allowed to create new images at %q", r.imagespath())
} }
@ -311,6 +321,7 @@ func (r *imageStore) Create(id string, names []string, layer, metadata string, c
if err == nil { if err == nil {
image = &Image{ image = &Image{
ID: id, ID: id,
Digest: searchableDigest,
Names: names, Names: names,
TopLayer: layer, TopLayer: layer,
Metadata: metadata, Metadata: metadata,
@ -323,6 +334,10 @@ func (r *imageStore) Create(id string, names []string, layer, metadata string, c
r.images = append(r.images, image) r.images = append(r.images, image)
r.idindex.Add(id) r.idindex.Add(id)
r.byid[id] = image r.byid[id] = image
if searchableDigest != "" {
list := r.bydigest[searchableDigest]
r.bydigest[searchableDigest] = append(list, image)
}
for _, name := range names { for _, name := range names {
r.byname[name] = image r.byname[name] = image
} }
@ -413,6 +428,17 @@ func (r *imageStore) Delete(id string) error {
} }
} }
} }
if image.Digest != "" {
// remove the image's hard-coded digest from the digest-based index
if list, ok := r.bydigest[image.Digest]; ok {
prunedList := imageSliceWithoutValue(list, image)
if len(prunedList) == 0 {
delete(r.bydigest, image.Digest)
} else {
r.bydigest[image.Digest] = prunedList
}
}
}
if err := r.Save(); err != nil { if err := r.Save(); err != nil {
return err return err
} }
@ -577,9 +603,10 @@ func (r *imageStore) SetBigData(id, key string, data []byte) error {
save = true save = true
} }
if key == ImageDigestBigDataKey { if key == ImageDigestBigDataKey {
if oldDigest != "" && oldDigest != newDigest { if oldDigest != "" && oldDigest != newDigest && oldDigest != image.Digest {
// remove the image from the list of images in the digest-based // remove the image from the list of images in the digest-based
// index which corresponds to the old digest for this item // index which corresponds to the old digest for this item, unless
// it's also the hard-coded digest
if list, ok := r.bydigest[oldDigest]; ok { if list, ok := r.bydigest[oldDigest]; ok {
prunedList := imageSliceWithoutValue(list, image) prunedList := imageSliceWithoutValue(list, image)
if len(prunedList) == 0 { if len(prunedList) == 0 {

View File

@ -38,6 +38,11 @@ func (j *Image) MarshalJSONBuf(buf fflib.EncodingBuffer) error {
buf.WriteString(`{ "id":`) buf.WriteString(`{ "id":`)
fflib.WriteJsonString(buf, string(j.ID)) fflib.WriteJsonString(buf, string(j.ID))
buf.WriteByte(',') buf.WriteByte(',')
if len(j.Digest) != 0 {
buf.WriteString(`"digest":`)
fflib.WriteJsonString(buf, string(j.Digest))
buf.WriteByte(',')
}
if len(j.Names) != 0 { if len(j.Names) != 0 {
buf.WriteString(`"names":`) buf.WriteString(`"names":`)
if j.Names != nil { if j.Names != nil {
@ -146,6 +151,8 @@ const (
ffjtImageID ffjtImageID
ffjtImageDigest
ffjtImageNames ffjtImageNames
ffjtImageTopLayer ffjtImageTopLayer
@ -165,6 +172,8 @@ const (
var ffjKeyImageID = []byte("id") var ffjKeyImageID = []byte("id")
var ffjKeyImageDigest = []byte("digest")
var ffjKeyImageNames = []byte("names") var ffjKeyImageNames = []byte("names")
var ffjKeyImageTopLayer = []byte("layer") var ffjKeyImageTopLayer = []byte("layer")
@ -268,6 +277,14 @@ mainparse:
goto mainparse goto mainparse
} }
case 'd':
if bytes.Equal(ffjKeyImageDigest, kn) {
currentKey = ffjtImageDigest
state = fflib.FFParse_want_colon
goto mainparse
}
case 'f': case 'f':
if bytes.Equal(ffjKeyImageFlags, kn) { if bytes.Equal(ffjKeyImageFlags, kn) {
@ -358,6 +375,12 @@ mainparse:
goto mainparse goto mainparse
} }
if fflib.EqualFoldRight(ffjKeyImageDigest, kn) {
currentKey = ffjtImageDigest
state = fflib.FFParse_want_colon
goto mainparse
}
if fflib.SimpleLetterEqualFold(ffjKeyImageID, kn) { if fflib.SimpleLetterEqualFold(ffjKeyImageID, kn) {
currentKey = ffjtImageID currentKey = ffjtImageID
state = fflib.FFParse_want_colon state = fflib.FFParse_want_colon
@ -384,6 +407,9 @@ mainparse:
case ffjtImageID: case ffjtImageID:
goto handle_ID goto handle_ID
case ffjtImageDigest:
goto handle_Digest
case ffjtImageNames: case ffjtImageNames:
goto handle_Names goto handle_Names
@ -448,6 +474,32 @@ handle_ID:
state = fflib.FFParse_after_value state = fflib.FFParse_after_value
goto mainparse goto mainparse
handle_Digest:
/* handler: j.Digest type=digest.Digest kind=string quoted=false*/
{
{
if tok != fflib.FFTok_string && tok != fflib.FFTok_null {
return fs.WrapErr(fmt.Errorf("cannot unmarshal %s into Go value for Digest", tok))
}
}
if tok == fflib.FFTok_null {
} else {
outBuf := fs.Output.Bytes()
j.Digest = digest.Digest(string(outBuf))
}
}
state = fflib.FFParse_after_value
goto mainparse
handle_Names: handle_Names:
/* handler: j.Names type=[]string kind=slice quoted=false*/ /* handler: j.Names type=[]string kind=slice quoted=false*/

View File

@ -434,6 +434,8 @@ type ImageOptions struct {
// CreationDate, if not zero, will override the default behavior of marking the image as having been // CreationDate, if not zero, will override the default behavior of marking the image as having been
// created when CreateImage() was called, recording CreationDate instead. // created when CreateImage() was called, recording CreationDate instead.
CreationDate time.Time CreationDate time.Time
// Digest is a hard-coded digest value that we can use to look up the image. It is optional.
Digest digest.Digest
} }
// ContainerOptions is used for passing options to a Store's CreateContainer() method. // ContainerOptions is used for passing options to a Store's CreateContainer() method.
@ -491,11 +493,6 @@ func GetStore(options StoreOptions) (Store, error) {
if err := os.MkdirAll(options.RunRoot, 0700); err != nil && !os.IsExist(err) { if err := os.MkdirAll(options.RunRoot, 0700); err != nil && !os.IsExist(err) {
return nil, err return nil, err
} }
for _, subdir := range []string{} {
if err := os.MkdirAll(filepath.Join(options.RunRoot, subdir), 0700); err != nil && !os.IsExist(err) {
return nil, err
}
}
if err := os.MkdirAll(options.GraphRoot, 0700); err != nil && !os.IsExist(err) { if err := os.MkdirAll(options.GraphRoot, 0700); err != nil && !os.IsExist(err) {
return nil, err return nil, err
} }
@ -838,11 +835,11 @@ func (s *store) CreateImage(id string, names []string, layer, metadata string, o
} }
creationDate := time.Now().UTC() creationDate := time.Now().UTC()
if options != nil { if options != nil && !options.CreationDate.IsZero() {
creationDate = options.CreationDate creationDate = options.CreationDate
} }
return ristore.Create(id, names, layer, metadata, creationDate) return ristore.Create(id, names, layer, metadata, creationDate, options.Digest)
} }
func (s *store) CreateContainer(id string, names []string, image, layer, metadata string, options *ContainerOptions) (*Container, error) { func (s *store) CreateContainer(id string, names []string, image, layer, metadata string, options *ContainerOptions) (*Container, error) {