summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xAPI.md6
-rw-r--r--cmd/podman/cleanup.go2
-rw-r--r--cmd/podman/cliconfig/config.go18
-rw-r--r--cmd/podman/cliconfig/create.go4
-rw-r--r--cmd/podman/commands.go2
-rw-r--r--cmd/podman/containers_prune.go13
-rw-r--r--cmd/podman/cp.go257
-rw-r--r--cmd/podman/create.go3
-rw-r--r--cmd/podman/image.go1
-rw-r--r--cmd/podman/login.go23
-rw-r--r--cmd/podman/main.go1
-rw-r--r--cmd/podman/rm.go5
-rw-r--r--cmd/podman/run.go2
-rw-r--r--cmd/podman/start.go2
-rw-r--r--cmd/podman/system_prune.go2
-rw-r--r--cmd/podman/varlink/io.podman.varlink4
-rw-r--r--commands.md3
-rw-r--r--completions/bash/podman1
-rw-r--r--contrib/perftest/main.go2
-rw-r--r--docs/podman-cp.1.md80
-rw-r--r--docs/podman-login.1.md14
-rw-r--r--docs/podman.1.md1
-rw-r--r--libpod/adapter/runtime_remote.go2
-rw-r--r--libpod/container.go3
-rw-r--r--libpod/container_internal.go139
-rw-r--r--libpod/container_internal_linux.go7
-rw-r--r--libpod/options.go3
-rw-r--r--libpod/runtime_ctr.go29
-rw-r--r--libpod/runtime_img.go2
-rw-r--r--libpod/volume_internal.go4
-rw-r--r--pkg/spec/createconfig.go44
-rw-r--r--pkg/util/utils.go10
-rw-r--r--pkg/varlinkapi/containers.go6
-rw-r--r--pkg/varlinkapi/containers_create.go7
-rw-r--r--test/e2e/cp_test.go115
-rw-r--r--transfer.md4
-rw-r--r--vendor.conf4
-rw-r--r--vendor/github.com/containers/image/copy/copy.go87
-rw-r--r--vendor/github.com/containers/image/docker/docker_client.go33
-rw-r--r--vendor/github.com/containers/image/docker/docker_image.go2
-rw-r--r--vendor/github.com/containers/image/docker/docker_image_dest.go62
-rw-r--r--vendor/github.com/containers/image/docker/docker_image_src.go16
-rw-r--r--vendor/github.com/containers/image/docker/tarfile/src.go76
-rw-r--r--vendor/github.com/containers/image/ostree/ostree_src.go13
-rw-r--r--vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go23
-rw-r--r--vendor/github.com/containers/image/storage/storage_image.go73
-rw-r--r--vendor/github.com/containers/image/storage/storage_reference.go22
-rw-r--r--vendor/github.com/containers/image/storage/storage_transport.go5
-rw-r--r--vendor/github.com/containers/image/version/version.go2
-rw-r--r--vendor/github.com/containers/storage/images.go193
-rw-r--r--vendor/github.com/containers/storage/images_ffjson.go2
-rw-r--r--vendor/github.com/containers/storage/pkg/config/config.go96
-rw-r--r--vendor/github.com/containers/storage/store.go441
-rw-r--r--vendor/github.com/containers/storage/vendor.conf9
54 files changed, 1389 insertions, 591 deletions
diff --git a/API.md b/API.md
index dea6b7a4c..ce70b702c 100755
--- a/API.md
+++ b/API.md
@@ -97,7 +97,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[func ReceiveFile(path: string, delete: bool) int](#ReceiveFile)
-[func RemoveContainer(name: string, force: bool) string](#RemoveContainer)
+[func RemoveContainer(name: string, force: bool, removeVolumes: bool) string](#RemoveContainer)
[func RemoveImage(name: string, force: bool) string](#RemoveImage)
@@ -779,9 +779,9 @@ ReceiveFile allows the host to send a remote client a file
### <a name="RemoveContainer"></a>func RemoveContainer
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
-method RemoveContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div>
+method RemoveContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool), removeVolumes: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div>
RemoveContainer takes requires the name or ID of container as well a boolean representing whether a running
-container can be stopped and removed. Upon successful removal of the container, its ID is returned. If the
+container can be stopped and removed. It also takes a flag on whether or not to remove builtin volumes. Upon successful removal of the container, its ID is returned. If the
container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned.
#### Example
~~~
diff --git a/cmd/podman/cleanup.go b/cmd/podman/cleanup.go
index 537679d75..e465a30e6 100644
--- a/cmd/podman/cleanup.go
+++ b/cmd/podman/cleanup.go
@@ -58,7 +58,7 @@ func cleanupCmd(c *cliconfig.CleanupValues) error {
for _, ctr := range cleanupContainers {
hadError := false
if c.Remove {
- if err := runtime.RemoveContainer(ctx, ctr, false); err != nil {
+ if err := runtime.RemoveContainer(ctx, ctr, false, false); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index 85ded6da0..f5d6a8685 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -135,6 +135,11 @@ type PruneImagesValues struct {
All bool
}
+type PruneContainersValues struct {
+ PodmanCommand
+ Force bool
+}
+
type ImportValues struct {
PodmanCommand
Change []string
@@ -172,12 +177,13 @@ type LoadValues struct {
type LoginValues struct {
PodmanCommand
- Password string
- Username string
- Authfile string
- CertDir string
- GetLogin bool
- TlsVerify bool
+ Password string
+ StdinPassword bool
+ Username string
+ Authfile string
+ CertDir string
+ GetLogin bool
+ TlsVerify bool
}
type LogoutValues struct {
diff --git a/cmd/podman/cliconfig/create.go b/cmd/podman/cliconfig/create.go
index 68ba4d857..b5ca1be9c 100644
--- a/cmd/podman/cliconfig/create.go
+++ b/cmd/podman/cliconfig/create.go
@@ -20,3 +20,7 @@ type BuildValues struct {
*buildahcli.NameSpaceResults
*buildahcli.LayerResults
}
+
+type CpValues struct {
+ PodmanCommand
+}
diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go
index 9004a5941..fa3839a53 100644
--- a/cmd/podman/commands.go
+++ b/cmd/podman/commands.go
@@ -10,7 +10,6 @@ import (
func getMainCommands() []*cobra.Command {
rootCommands := []*cobra.Command{
_attachCommand,
- _buildCommand,
_commitCommand,
_createCommand,
_diffCommand,
@@ -54,7 +53,6 @@ func getMainCommands() []*cobra.Command {
// Commands that the local client implements
func getImageSubCommands() []*cobra.Command {
return []*cobra.Command{
- _buildCommand,
_loadCommand,
_saveCommand,
_signCommand,
diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go
index acc138fe0..bae578e1d 100644
--- a/cmd/podman/containers_prune.go
+++ b/cmd/podman/containers_prune.go
@@ -13,13 +13,12 @@ import (
)
var (
- pruneContainersCommand cliconfig.ContainersPrune
+ pruneContainersCommand cliconfig.PruneContainersValues
pruneContainersDescription = `
podman container prune
Removes all exited containers
`
-
_pruneContainersCommand = &cobra.Command{
Use: "prune",
Short: "Remove all stopped containers",
@@ -35,9 +34,11 @@ var (
func init() {
pruneContainersCommand.Command = _pruneContainersCommand
pruneContainersCommand.SetUsageTemplate(UsageTemplate())
+ flags := pruneContainersCommand.Flags()
+ flags.BoolVarP(&pruneContainersCommand.Force, "force", "f", false, "Force removal of a running container. The default is false")
}
-func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force bool) error {
+func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWorkers int, force, volumes bool) error {
var deleteFuncs []shared.ParallelWorkerInput
filter := func(c *libpod.Container) bool {
@@ -57,7 +58,7 @@ func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWork
for _, container := range delContainers {
con := container
f := func() error {
- return runtime.RemoveContainer(ctx, con, force)
+ return runtime.RemoveContainer(ctx, con, force, volumes)
}
deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{
@@ -70,7 +71,7 @@ func pruneContainers(runtime *adapter.LocalRuntime, ctx context.Context, maxWork
return printParallelOutput(deleteErrors, errCount)
}
-func pruneContainersCmd(c *cliconfig.ContainersPrune) error {
+func pruneContainersCmd(c *cliconfig.PruneContainersValues) error {
runtime, err := adapter.GetRuntime(&c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
@@ -83,5 +84,5 @@ func pruneContainersCmd(c *cliconfig.ContainersPrune) error {
}
logrus.Debugf("Setting maximum workers to %d", maxWorkers)
- return pruneContainers(runtime, getContext(), maxWorkers, c.Bool("force"))
+ return pruneContainers(runtime, getContext(), maxWorkers, c.Bool("force"), c.Bool("volumes"))
}
diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go
new file mode 100644
index 000000000..89114fda1
--- /dev/null
+++ b/cmd/podman/cp.go
@@ -0,0 +1,257 @@
+package main
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah/util"
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/chrootuser"
+ "github.com/containers/storage"
+ "github.com/containers/storage/pkg/archive"
+ "github.com/containers/storage/pkg/chrootarchive"
+ "github.com/containers/storage/pkg/idtools"
+ digest "github.com/opencontainers/go-digest"
+ specs "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var (
+ cpCommand cliconfig.CpValues
+
+ cpDescription = "Copy files/folders between a container and the local filesystem"
+ _cpCommand = &cobra.Command{
+ Use: "cp",
+ Short: "Copy files/folders between a container and the local filesystem",
+ Long: cpDescription,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ cpCommand.InputArgs = args
+ cpCommand.GlobalFlags = MainGlobalOpts
+ return cpCmd(&cpCommand)
+ },
+ Example: "[CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH",
+ }
+)
+
+func init() {
+ cpCommand.Command = _cpCommand
+ rootCmd.AddCommand(cpCommand.Command)
+}
+
+func cpCmd(c *cliconfig.CpValues) error {
+ args := c.InputArgs
+ if len(args) != 2 {
+ return errors.Errorf("you must provide a source path and a destination path")
+ }
+
+ runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ return copyBetweenHostAndContainer(runtime, args[0], args[1])
+}
+
+func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string) error {
+
+ srcCtr, srcPath := parsePath(runtime, src)
+ destCtr, destPath := parsePath(runtime, dest)
+
+ if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) {
+ return errors.Errorf("invalid arguments %s, %s you must use just one container", src, dest)
+ }
+
+ if len(srcPath) == 0 || len(destPath) == 0 {
+ return errors.Errorf("invalid arguments %s, %s you must specify paths", src, dest)
+ }
+ ctr := srcCtr
+ isFromHostToCtr := (ctr == nil)
+ if isFromHostToCtr {
+ ctr = destCtr
+ }
+
+ mountPoint, err := ctr.Mount()
+ if err != nil {
+ return err
+ }
+ defer ctr.Unmount(false)
+ user, err := getUser(mountPoint, ctr.User())
+ if err != nil {
+ return err
+ }
+ idMappingOpts, err := ctr.IDMappings()
+ if err != nil {
+ return errors.Wrapf(err, "error getting IDMappingOptions")
+ }
+ containerOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
+ hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID)
+ if err != nil {
+ return err
+ }
+
+ hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
+
+ var glob []string
+ if isFromHostToCtr {
+ if filepath.IsAbs(destPath) {
+ destPath = filepath.Join(mountPoint, destPath)
+
+ } else {
+ if err = idtools.MkdirAllAndChownNew(filepath.Join(mountPoint, ctr.WorkingDir()), 0755, hostOwner); err != nil {
+ return errors.Wrapf(err, "error creating directory %q", destPath)
+ }
+ destPath = filepath.Join(mountPoint, ctr.WorkingDir(), destPath)
+ }
+ } else {
+ if filepath.IsAbs(srcPath) {
+ srcPath = filepath.Join(mountPoint, srcPath)
+ } else {
+ srcPath = filepath.Join(mountPoint, ctr.WorkingDir(), srcPath)
+ }
+ }
+ glob, err = filepath.Glob(srcPath)
+ if err != nil {
+ return errors.Wrapf(err, "invalid glob %q", srcPath)
+ }
+ if len(glob) == 0 {
+ glob = append(glob, srcPath)
+ }
+ if !filepath.IsAbs(destPath) {
+ dir, err := os.Getwd()
+ if err != nil {
+ return errors.Wrapf(err, "err getting current working directory")
+ }
+ destPath = filepath.Join(dir, destPath)
+ }
+
+ var lastError error
+ for _, src := range glob {
+ err := copy(src, destPath, dest, idMappingOpts, &containerOwner)
+ if lastError != nil {
+ logrus.Error(lastError)
+ }
+ lastError = err
+ }
+ return lastError
+}
+
+func getUser(mountPoint string, userspec string) (specs.User, error) {
+ uid, gid, err := chrootuser.GetUser(mountPoint, userspec)
+ u := specs.User{
+ UID: uid,
+ GID: gid,
+ Username: userspec,
+ }
+ if !strings.Contains(userspec, ":") {
+ groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
+ if err2 != nil {
+ if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil {
+ err = err2
+ }
+ } else {
+ u.AdditionalGids = groups
+ }
+
+ }
+ return u, err
+}
+
+func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) {
+ pathArr := strings.SplitN(path, ":", 2)
+ if len(pathArr) == 2 {
+ ctr, err := runtime.LookupContainer(pathArr[0])
+ if err == nil {
+ return ctr, pathArr[1]
+ }
+ }
+ return nil, path
+}
+
+func getPathInfo(path string) (string, os.FileInfo, error) {
+ path, err := filepath.EvalSymlinks(path)
+ if err != nil {
+ return "", nil, errors.Wrapf(err, "error evaluating symlinks %q", path)
+ }
+ srcfi, err := os.Stat(path)
+ if err != nil {
+ return "", nil, errors.Wrapf(err, "error reading path %q", path)
+ }
+ return path, srcfi, nil
+}
+
+func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair) error {
+ srcPath, err := filepath.EvalSymlinks(src)
+ if err != nil {
+ return errors.Wrapf(err, "error evaluating symlinks %q", srcPath)
+ }
+
+ srcPath, srcfi, err := getPathInfo(srcPath)
+ if err != nil {
+ return err
+ }
+ destdir := destPath
+ if !srcfi.IsDir() && !strings.HasSuffix(dest, string(os.PathSeparator)) {
+ destdir = filepath.Dir(destPath)
+ }
+ if err = os.MkdirAll(destdir, 0755); err != nil {
+ return errors.Wrapf(err, "error creating directory %q", destdir)
+ }
+
+ // return functions for copying items
+ copyFileWithTar := chrootarchive.CopyFileWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
+ copyWithTar := chrootarchive.CopyWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
+ untarPath := chrootarchive.UntarPathAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
+
+ if srcfi.IsDir() {
+
+ logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*")
+ if err = copyWithTar(srcPath, destPath); err != nil {
+ return errors.Wrapf(err, "error copying %q to %q", srcPath, dest)
+ }
+ return nil
+ }
+ if !archive.IsArchivePath(srcPath) {
+ // This srcPath is a file, and either it's not an
+ // archive, or we don't care whether or not it's an
+ // archive.
+ destfi, err := os.Stat(destPath)
+ if err != nil {
+ if !os.IsNotExist(err) {
+ return errors.Wrapf(err, "failed to get stat of dest path %s", destPath)
+ }
+ }
+ if destfi != nil && destfi.IsDir() {
+ destPath = filepath.Join(destPath, filepath.Base(srcPath))
+ }
+ // Copy the file, preserving attributes.
+ logrus.Debugf("copying %q to %q", srcPath, destPath)
+ if err = copyFileWithTar(srcPath, destPath); err != nil {
+ return errors.Wrapf(err, "error copying %q to %q", srcPath, destPath)
+ }
+ return nil
+ }
+ // We're extracting an archive into the destination directory.
+ logrus.Debugf("extracting contents of %q into %q", srcPath, destPath)
+ if err = untarPath(srcPath, destPath); err != nil {
+ return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath)
+ }
+ return nil
+}
+
+func convertIDMap(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) {
+ for _, idmap := range idMaps {
+ tempIDMap := specs.LinuxIDMapping{
+ ContainerID: uint32(idmap.ContainerID),
+ HostID: uint32(idmap.HostID),
+ Size: uint32(idmap.Size),
+ }
+ convertedIDMap = append(convertedIDMap, tempIDMap)
+ }
+ return convertedIDMap
+}
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index 1a7f419c0..392163424 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -646,9 +646,10 @@ func parseCreateOpts(ctx context.Context, c *cliconfig.PodmanCommand, runtime *l
}
var ImageVolumes map[string]struct{}
- if data != nil {
+ if data != nil && c.String("image-volume") != "ignore" {
ImageVolumes = data.Config.Volumes
}
+
var imageVolType = map[string]string{
"bind": "",
"tmpfs": "",
diff --git a/cmd/podman/image.go b/cmd/podman/image.go
index edc37b28a..4f9c7cd6a 100644
--- a/cmd/podman/image.go
+++ b/cmd/podman/image.go
@@ -18,6 +18,7 @@ var (
//imageSubCommands are implemented both in local and remote clients
var imageSubCommands = []*cobra.Command{
+ _buildCommand,
_historyCommand,
_imageExistsCommand,
_imagesCommand,
diff --git a/cmd/podman/login.go b/cmd/podman/login.go
index 0bd58ff78..1fc4d5327 100644
--- a/cmd/podman/login.go
+++ b/cmd/podman/login.go
@@ -44,6 +44,7 @@ func init() {
flags.StringVarP(&loginCommand.Password, "password", "p", "", "Password for registry")
flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)")
flags.StringVarP(&loginCommand.Username, "username", "u", "", "Username for registry")
+ flags.BoolVar(&loginCommand.StdinPassword, "password-stdin", false, "Take the password from stdin")
}
@@ -90,8 +91,26 @@ func loginCmd(c *cliconfig.LoginValues) error {
}
ctx := getContext()
+
+ password := c.Password
+
+ if c.Flag("password-stdin").Changed {
+ var stdinPasswordStrBuilder strings.Builder
+ if c.Password != "" {
+ return errors.Errorf("Can't specify both --password-stdin and --password")
+ }
+ if c.Username == "" {
+ return errors.Errorf("Must provide --username with --password-stdin")
+ }
+ scanner := bufio.NewScanner(os.Stdin)
+ for scanner.Scan() {
+ fmt.Fprint(&stdinPasswordStrBuilder, scanner.Text())
+ }
+ password = stdinPasswordStrBuilder.String()
+ }
+
// If no username and no password is specified, try to use existing ones.
- if c.Username == "" && c.Password == "" {
+ if c.Username == "" && password == "" {
fmt.Println("Authenticating with existing credentials...")
if err := docker.CheckAuth(ctx, sc, userFromAuthFile, passFromAuthFile, server); err == nil {
fmt.Println("Existing credentials are valid. Already logged in to", server)
@@ -100,7 +119,7 @@ func loginCmd(c *cliconfig.LoginValues) error {
fmt.Println("Existing credentials are invalid, please enter valid username and password")
}
- username, password, err := getUserAndPass(c.Username, c.Password, userFromAuthFile)
+ username, password, err := getUserAndPass(c.Username, password, userFromAuthFile)
if err != nil {
return errors.Wrapf(err, "error getting username and password")
}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index a6f0c500a..f9820c075 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -30,6 +30,7 @@ var (
// Commands that the remote and local client have
// implemented.
var mainCommands = []*cobra.Command{
+ _buildCommand,
_exportCommand,
_historyCommand,
_imagesCommand,
diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go
index 1e5e9d254..d170e5357 100644
--- a/cmd/podman/rm.go
+++ b/cmd/podman/rm.go
@@ -39,8 +39,7 @@ func init() {
flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers")
flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running container. The default is false")
flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
- flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container (Not implemented yet)")
-
+ flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container")
}
// saveCmd saves the image to either docker-archive or oci
@@ -79,7 +78,7 @@ func rmCmd(c *cliconfig.RmValues) error {
for _, container := range delContainers {
con := container
f := func() error {
- return runtime.RemoveContainer(ctx, con, c.Force)
+ return runtime.RemoveContainer(ctx, con, c.Force, c.Volumes)
}
deleteFuncs = append(deleteFuncs, shared.ParallelWorkerInput{
diff --git a/cmd/podman/run.go b/cmd/podman/run.go
index 16ec7c3c0..64f8b6856 100644
--- a/cmd/podman/run.go
+++ b/cmd/podman/run.go
@@ -132,7 +132,7 @@ func runCmd(c *cliconfig.RunValues) error {
exitCode = 126
}
if c.IsSet("rm") {
- if deleteError := runtime.RemoveContainer(ctx, ctr, true); deleteError != nil {
+ if deleteError := runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil {
logrus.Errorf("unable to remove container %s after failing to start and attach to it", ctr.ID())
}
}
diff --git a/cmd/podman/start.go b/cmd/podman/start.go
index d1434508d..3a606d662 100644
--- a/cmd/podman/start.go
+++ b/cmd/podman/start.go
@@ -144,7 +144,7 @@ func startCmd(c *cliconfig.StartValues) error {
logrus.Errorf("unable to detect if container %s should be deleted", ctr.ID())
}
if createArtifact.Rm {
- if rmErr := runtime.RemoveContainer(ctx, ctr, true); rmErr != nil {
+ if rmErr := runtime.RemoveContainer(ctx, ctr, true, false); rmErr != nil {
logrus.Errorf("unable to remove container %s after it failed to start", ctr.ID())
}
}
diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go
index a88027558..a91d7bf0a 100644
--- a/cmd/podman/system_prune.go
+++ b/cmd/podman/system_prune.go
@@ -76,7 +76,7 @@ Are you sure you want to continue? [y/N] `, volumeString)
ctx := getContext()
fmt.Println("Deleted Containers")
- lasterr := pruneContainers(runtime, ctx, shared.Parallelize("rm"), false)
+ lasterr := pruneContainers(runtime, ctx, shared.Parallelize("rm"), false, false)
if c.Bool("volumes") {
fmt.Println("Deleted Volumes")
err := volumePrune(runtime, getContext())
diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink
index 94947c0e0..d6d9936f2 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -600,7 +600,7 @@ method GetAttachSockets(name: string) -> (sockets: Sockets)
# a [ContainerNotFound](#ContainerNotFound) error is returned.
method WaitContainer(name: string) -> (exitcode: int)
-# RemoveContainer takes requires the name or ID of container as well a boolean representing whether a running
+# RemoveContainer takes requires the name or ID of container as well a boolean representing whether a running and a boolean indicating whether to remove builtin volumes
# container can be stopped and removed. Upon successful removal of the container, its ID is returned. If the
# container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned.
# #### Example
@@ -610,7 +610,7 @@ method WaitContainer(name: string) -> (exitcode: int)
# "container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20"
# }
# ~~~
-method RemoveContainer(name: string, force: bool) -> (container: string)
+method RemoveContainer(name: string, force: bool, removeVolumes: bool) -> (container: string)
# DeleteStoppedContainers will delete all containers that are not running. It will return a list the deleted
# container IDs. See also [RemoveContainer](RemoveContainer).
diff --git a/commands.md b/commands.md
index c7d03d5ad..3fd27ad5d 100644
--- a/commands.md
+++ b/commands.md
@@ -16,7 +16,7 @@
| [podman-container-refresh(1)](/docs/podman-container-refresh.1.md) | Refresh all containers state in database ||
| [podman-container-restore(1)](/docs/podman-container-restore.1.md) | Restores one or more running containers ||
| [podman-container-runlabel(1)](/docs/podman-container-runlabel.1.md) | Execute Image Label Method ||
-| [podman-cp(1)](/docs/podman-cp.1.md) | Instead of providing a `podman cp` command, the man page `podman-cp` describes how to use the `podman mount` command to have even more flexibility and functionality||
+| [podman-cp(1)](/docs/podman-cp.1.md) | Copy files/folders between a container and the local filesystem ||
| [podman-create(1)](/docs/podman-create.1.md) | Create a new container ||
| [podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem |[![...](/docs/play.png)](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)|
| [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container
@@ -73,6 +73,7 @@
| [podman-unpause(1)](/docs/podman-unpause.1.md) | Unpause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)|
| [podman-varlink(1)](/docs/podman-varlink.1.md) | Run the varlink backend ||
| [podman-version(1)](/docs/podman-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)|
+| [podman-volume(1)](/docs/podman-volume.1.md) | Manage Volumes ||
| [podman-volume-create(1)](/docs/podman-volume-create.1.md) | Create a volume ||
| [podman-volume-inspect(1)](/docs/podman-volume-inspect.1.md) | Get detailed information on one or more volumes ||
| [podman-volume-ls(1)](/docs/podman-volume-ls.1.md) | List all the available volumes ||
diff --git a/completions/bash/podman b/completions/bash/podman
index d367b8237..36ac27d52 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -2320,6 +2320,7 @@ _podman_login() {
local boolean_options="
--help
-h
+ --password-stdin
"
_complete_ "$options_with_args" "$boolean_options"
}
diff --git a/contrib/perftest/main.go b/contrib/perftest/main.go
index 6a6725ab9..c0a91209f 100644
--- a/contrib/perftest/main.go
+++ b/contrib/perftest/main.go
@@ -218,7 +218,7 @@ func runSingleThreadedStressTest(ctx context.Context, client *libpod.Runtime, im
//Delete Container
deleteStartTime := time.Now()
- err = client.RemoveContainer(ctx, ctr, true)
+ err = client.RemoveContainer(ctx, ctr, true, false)
if err != nil {
return nil, err
}
diff --git a/docs/podman-cp.1.md b/docs/podman-cp.1.md
index 88e50e86b..37426b236 100644
--- a/docs/podman-cp.1.md
+++ b/docs/podman-cp.1.md
@@ -3,20 +3,70 @@
## NAME
podman\-cp - Copy files/folders between a container and the local filesystem
-## Description
-We chose not to implement the `cp` feature in `podman` even though the upstream Docker
-project has it. We have a much stronger capability. Using standard podman-mount
-and podman-umount, we can take advantage of the entire linux tool chain, rather
+## SYNOPSIS
+**podman cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH**
+
+## DESCRIPTION
+Copies the contents of **SRC_PATH** to the **DEST_PATH**. You can copy from the containers's filesystem to the local machine or the reverse, from the local filesystem to the container.
+
+The CONTAINER can be a running or stopped container. The **SRC_PATH** or **DEST_PATH** can be a file or directory.
+
+The **podman cp** command assumes container paths are relative to the container's / (root) directory.
+
+This means supplying the initial forward slash is optional;
+
+The command sees **compassionate_darwin:/tmp/foo/myfile.txt** and **compassionate_darwin:tmp/foo/myfile.txt** as identical.
+
+Local machine paths can be an absolute or relative value.
+The command interprets a local machine's relative paths as relative to the current working directory where **podman cp** is run.
+
+Assuming a path separator of /, a first argument of **SRC_PATH** and second argument of **DEST_PATH**, the behavior is as follows:
+
+**SRC_PATH** specifies a file
+ - **DEST_PATH** does not exist
+ - the file is saved to a file created at **DEST_PATH**
+ - **DEST_PATH** does not exist and ends with /
+ - **DEST_PATH** is created as a directory and the file is copied into this directory using the basename from **SRC_PATH**
+ - **DEST_PATH** exists and is a file
+ - the destination is overwritten with the source file's contents
+ - **DEST_PATH** exists and is a directory
+ - the file is copied into this directory using the basename from **SRC_PATH**
+
+**SRC_PATH** specifies a directory
+ - **DEST_PATH** does not exist
+ - **DEST_PATH** is created as a directory and the contents of the source directory are copied into this directory
+ - **DEST_PATH** exists and is a file
+ - Error condition: cannot copy a directory to a file
+ - **DEST_PATH** exists and is a directory
+ - **SRC_PATH** ends with /
+ - the source directory is copied into this directory
+ - **SRC_PATH** ends with /. (that is: slash followed by dot)
+ - the content of the source directory is copied into this directory
+
+The command requires **SRC_PATH** and **DEST_PATH** to exist according to the above rules.
+
+If **SRC_PATH** is local and is a symbolic link, the symbolic target, is copied by default.
+
+A colon (:) is used as a delimiter between CONTAINER and its path.
+
+You can also use : when specifying paths to a **SRC_PATH** or **DEST_PATH** on a local machine, for example, `file:name.txt`.
+
+If you use a : in a local machine path, you must be explicit with a relative or absolute path, for example:
+ `/path/to/file:name.txt` or `./file:name.txt`
+
+
+## ALTERNATIVES
+
+Podman has much stronger capabilities than just `podman cp` to achieve copy files between host and container.
+
+Using standard podman-mount and podman-umount takes advantage of the entire linux tool chain, rather
then just cp.
-If a user wants to copy contents out of a container or into a container, they
-can execute a few simple commands.
+If a user wants to copy contents out of a container or into a container, they can execute a few simple commands.
-You can copy from the container's file system to the local machine or the
-reverse, from the local filesystem to the container.
+You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container.
-If you want to copy the /etc/foobar directory out of a container and onto /tmp
-on the host, you could execute the following commands:
+If you want to copy the /etc/foobar directory out of a container and onto /tmp on the host, you could execute the following commands:
mnt=$(podman mount CONTAINERID)
cp -R ${mnt}/etc/foobar /tmp
@@ -40,5 +90,15 @@ This shows that using `podman mount` and `podman umount` you can use all of the
standard linux tools for moving files into and out of containers, not just
the cp command.
+## EXAMPLE
+
+podman cp /myapp/app.conf containerID:/myapp/app.conf
+
+podman cp /home/myuser/myfiles.tar containerID:/tmp
+
+podman cp containerID:/myapp/ /myapp/
+
+podman cp containerID:/home/myuser/. /home/myuser/
+
## SEE ALSO
podman(1), podman-mount(1), podman-umount(1)
diff --git a/docs/podman-login.1.md b/docs/podman-login.1.md
index e72d1deca..3ac0e30ef 100644
--- a/docs/podman-login.1.md
+++ b/docs/podman-login.1.md
@@ -25,6 +25,10 @@ flag. The default path used is **${XDG\_RUNTIME_DIR}/containers/auth.json**.
Password for registry
+**--password-stdin**
+
+Take the password from stdin
+
**--username, -u**
Username for registry
@@ -86,6 +90,16 @@ $ podman login --cert-dir /etc/containers/certs.d/ -u foo -p bar localhost:5000
Login Succeeded!
```
+```
+$ podman login -u testuser --password-stdin < testpassword.txt docker.io
+Login Succeeded!
+```
+
+```
+$ echo $testpassword | podman login -u testuser --password-stdin docker.io
+Login Succeeded!
+```
+
## SEE ALSO
podman(1), podman-logout(1), crio(8)
diff --git a/docs/podman.1.md b/docs/podman.1.md
index 51ef00383..760f27310 100644
--- a/docs/podman.1.md
+++ b/docs/podman.1.md
@@ -168,6 +168,7 @@ the exit codes follow the `chroot` standard, see below:
| [podman-umount(1)](podman-umount.1.md) | Unmount a working container's root filesystem. |
| [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. |
| [podman-version(1)](podman-version.1.md) | Display the Podman version information. |
+| [podman-volume(1)](podman-volume.1.md) | Manage Volumes. |
| [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. |
## FILES
diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go
index 974ee63ca..d0a3c5b1f 100644
--- a/libpod/adapter/runtime_remote.go
+++ b/libpod/adapter/runtime_remote.go
@@ -544,7 +544,7 @@ func (r *LocalRuntime) GetContainers(filters ...libpod.ContainerFilter) ([]*libp
// RemoveContainer removes the given container
// If force is specified, the container will be stopped first
// Otherwise, RemoveContainer will return an error if the container is running
-func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force bool) error {
+func (r *LocalRuntime) RemoveContainer(ctx context.Context, c *libpod.Container, force, volumes bool) error {
return libpod.ErrNotImplemented
}
diff --git a/libpod/container.go b/libpod/container.go
index fec61533d..75f4a4a4f 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -358,8 +358,7 @@ type ContainerConfig struct {
ExitCommand []string `json:"exitCommand,omitempty"`
// LocalVolumes are the built-in volumes we get from the --volumes-from flag
// It picks up the built-in volumes of the container used by --volumes-from
- LocalVolumes []string
-
+ LocalVolumes []spec.Mount
// IsInfra is a bool indicating whether this container is an infra container used for
// sharing kernel namespaces in a pod
IsInfra bool `json:"pause"`
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index f82cbd674..b2ebad777 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -10,21 +10,16 @@ import (
"path/filepath"
"strconv"
"strings"
- "syscall"
"time"
- "github.com/containers/buildah/imagebuildah"
"github.com/containers/libpod/pkg/ctime"
"github.com/containers/libpod/pkg/hooks"
"github.com/containers/libpod/pkg/hooks/exec"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
- "github.com/containers/storage/pkg/chrootarchive"
"github.com/containers/storage/pkg/mount"
- "github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
- "github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -1053,113 +1048,6 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error)
return filepath.Join(c.state.DestinationRunDir, destFile), nil
}
-func (c *Container) addLocalVolumes(ctx context.Context, g *generate.Generator, execUser *user.ExecUser) error {
- var uid, gid int
- mountPoint := c.state.Mountpoint
- if !c.state.Mounted {
- return errors.Wrapf(ErrInternal, "container is not mounted")
- }
- newImage, err := c.runtime.imageRuntime.NewFromLocal(c.config.RootfsImageID)
- if err != nil {
- return err
- }
- imageData, err := newImage.Inspect(ctx)
- if err != nil {
- return err
- }
- // Add the built-in volumes of the container passed in to --volumes-from
- for _, vol := range c.config.LocalVolumes {
- if imageData.Config.Volumes == nil {
- imageData.Config.Volumes = map[string]struct{}{
- vol: {},
- }
- } else {
- imageData.Config.Volumes[vol] = struct{}{}
- }
- }
-
- if c.config.User != "" {
- if execUser == nil {
- return errors.Wrapf(ErrInternal, "nil pointer passed to addLocalVolumes for execUser")
- }
- uid = execUser.Uid
- gid = execUser.Gid
- }
-
- for k := range imageData.Config.Volumes {
- mount := spec.Mount{
- Destination: k,
- Type: "bind",
- Options: []string{"private", "bind", "rw"},
- }
- if MountExists(g.Mounts(), k) {
- continue
- }
- volumePath := filepath.Join(c.config.StaticDir, "volumes", k)
-
- // Ensure the symlinks are resolved
- resolvedSymlink, err := imagebuildah.ResolveSymLink(mountPoint, k)
- if err != nil {
- return errors.Wrapf(ErrCtrStateInvalid, "cannot resolve %s in %s for container %s", k, mountPoint, c.ID())
- }
- var srcPath string
- if resolvedSymlink != "" {
- srcPath = filepath.Join(mountPoint, resolvedSymlink)
- } else {
- srcPath = filepath.Join(mountPoint, k)
- }
-
- if _, err := os.Stat(srcPath); os.IsNotExist(err) {
- logrus.Infof("Volume image mount point %s does not exist in root FS, need to create it", k)
- if err = os.MkdirAll(srcPath, 0755); err != nil {
- return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID())
- }
-
- if err = os.Chown(srcPath, uid, gid); err != nil {
- return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", srcPath, k, c.ID())
- }
- }
-
- if _, err := os.Stat(volumePath); os.IsNotExist(err) {
- if err = os.MkdirAll(volumePath, 0755); err != nil {
- return errors.Wrapf(err, "error creating directory %q for volume %q in container %q", volumePath, k, c.ID())
- }
-
- if err = os.Chown(volumePath, uid, gid); err != nil {
- return errors.Wrapf(err, "error chowning directory %q for volume %q in container %q", volumePath, k, c.ID())
- }
-
- if err = label.Relabel(volumePath, c.config.MountLabel, false); err != nil {
- return errors.Wrapf(err, "error relabeling directory %q for volume %q in container %q", volumePath, k, c.ID())
- }
- if err = chrootarchive.NewArchiver(nil).CopyWithTar(srcPath, volumePath); err != nil && !os.IsNotExist(err) {
- return errors.Wrapf(err, "error populating directory %q for volume %q in container %q using contents of %q", volumePath, k, c.ID(), srcPath)
- }
-
- // Set the volume path with the same owner and permission of source path
- sstat, _ := os.Stat(srcPath)
- st, ok := sstat.Sys().(*syscall.Stat_t)
- if !ok {
- return fmt.Errorf("could not convert to syscall.Stat_t")
- }
- uid := int(st.Uid)
- gid := int(st.Gid)
-
- if err := os.Lchown(volumePath, uid, gid); err != nil {
- return err
- }
- if os.Chmod(volumePath, sstat.Mode()); err != nil {
- return err
- }
-
- }
-
- mount.Source = volumePath
- g.AddMount(mount)
- }
- return nil
-}
-
// Save OCI spec to disk, replacing any existing specs for the container
func (c *Container) saveSpec(spec *spec.Spec) error {
// If the OCI spec already exists, we need to replace it
@@ -1303,3 +1191,30 @@ func getExcludedCGroups() (excludes []string) {
excludes = []string{"rdma"}
return
}
+
+// namedVolumes returns named volumes for the container
+func (c *Container) namedVolumes() ([]string, error) {
+ var volumes []string
+ for _, vol := range c.config.Spec.Mounts {
+ if strings.HasPrefix(vol.Source, c.runtime.config.VolumePath) {
+ volume := strings.TrimPrefix(vol.Source, c.runtime.config.VolumePath+"/")
+ split := strings.Split(volume, "/")
+ volume = split[0]
+ if _, err := c.runtime.state.Volume(volume); err == nil {
+ volumes = append(volumes, volume)
+ }
+ }
+ }
+ return volumes, nil
+}
+
+// this should be from chrootarchive.
+func (c *Container) copyWithTarFromImage(src, dest string) error {
+ mountpoint, err := c.mount()
+ if err != nil {
+ return err
+ }
+ a := archive.NewDefaultArchiver()
+ source := filepath.Join(mountpoint, src)
+ return a.CopyWithTar(source, dest)
+}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index bcdfdaee3..65cb47c8c 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -235,13 +235,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
}
}
- // Bind builtin image volumes
- if c.config.Rootfs == "" && c.config.ImageVolumes {
- if err := c.addLocalVolumes(ctx, &g, execUser); err != nil {
- return nil, errors.Wrapf(err, "error mounting image volumes")
- }
- }
-
if c.config.User != "" {
// User and Group must go together
g.SetProcessUID(uint32(execUser.Uid))
diff --git a/libpod/options.go b/libpod/options.go
index d965c058e..06737776b 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/cri-o/ocicni/pkg/ocicni"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
@@ -1058,7 +1059,7 @@ func WithUserVolumes(volumes []string) CtrCreateOption {
// from a container passed in to the --volumes-from flag.
// This stores the built-in volume information in the Config so we can
// add them when creating the container.
-func WithLocalVolumes(volumes []string) CtrCreateOption {
+func WithLocalVolumes(volumes []spec.Mount) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return ErrCtrFinalized
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 4f8192198..185090cf7 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -177,9 +177,12 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ..
if err != nil {
newVol, err := r.newVolume(ctx, WithVolumeName(vol.Source))
if err != nil {
- logrus.Errorf("error creating named volume %q: %v", vol.Source, err)
+ return nil, errors.Wrapf(err, "error creating named volume %q", vol.Source)
}
ctr.config.Spec.Mounts[i].Source = newVol.MountPoint()
+ if err := ctr.copyWithTarFromImage(ctr.config.Spec.Mounts[i].Destination, ctr.config.Spec.Mounts[i].Source); err != nil && !os.IsNotExist(err) {
+ return nil, errors.Wrapf(err, "Failed to copy content into new volume mount %q", vol.Source)
+ }
continue
}
ctr.config.Spec.Mounts[i].Source = volInfo.MountPoint()
@@ -225,17 +228,19 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options ..
// RemoveContainer removes the given container
// If force is specified, the container will be stopped first
+// If removeVolume is specified, named volumes used by the container will
+// be removed also if and only if the container is the sole user
// Otherwise, RemoveContainer will return an error if the container is running
-func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool) error {
+func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool, removeVolume bool) error {
r.lock.Lock()
defer r.lock.Unlock()
- return r.removeContainer(ctx, c, force)
+ return r.removeContainer(ctx, c, force, removeVolume)
}
// Internal function to remove a container
// Locks the container, but does not lock the runtime
-func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool) error {
+func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, removeVolume bool) error {
if !c.valid {
if ok, _ := r.state.HasContainer(c.ID()); !ok {
// Container probably already removed
@@ -248,6 +253,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool)
// To avoid races around removing a container and the pod it is in
var pod *Pod
var err error
+ runtime := c.runtime
if c.config.Pod != "" {
pod, err = r.state.Pod(c.config.Pod)
if err != nil {
@@ -333,6 +339,13 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool)
return errors.Wrapf(ErrCtrExists, "container %s has dependent containers which must be removed before it: %s", c.ID(), depsStr)
}
+ var volumes []string
+ if removeVolume {
+ volumes, err = c.namedVolumes()
+ if err != nil {
+ logrus.Errorf("unable to retrieve builtin volumes for container %v: %v", c.ID(), err)
+ }
+ }
var cleanupErr error
// Remove the container from the state
if c.config.Pod != "" {
@@ -397,6 +410,14 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool)
}
}
+ for _, v := range volumes {
+ if volume, err := runtime.state.Volume(v); err == nil {
+ if err := runtime.removeVolume(ctx, volume, false, true); err != nil && err != ErrNoSuchVolume && err != ErrVolumeBeingUsed {
+ logrus.Errorf("cleanup volume (%s): %v", v, err)
+ }
+ }
+ }
+
return cleanupErr
}
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index c20aa77a3..1e9689362 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -43,7 +43,7 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
if len(imageCtrs) > 0 && len(img.Names()) <= 1 {
if force {
for _, ctr := range imageCtrs {
- if err := r.removeContainer(ctx, ctr, true); err != nil {
+ if err := r.removeContainer(ctx, ctr, true, false); err != nil {
return "", errors.Wrapf(err, "error removing image %s: container %s using image could not be removed", img.ID(), ctr.ID())
}
}
diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go
index 800e6d106..0de8a2350 100644
--- a/libpod/volume_internal.go
+++ b/libpod/volume_internal.go
@@ -5,10 +5,6 @@ import (
"path/filepath"
)
-// VolumePath is the path under which all volumes that are created using the
-// local driver will be created
-// const VolumePath = "/var/lib/containers/storage/volumes"
-
// Creates a new volume
func newVolume(runtime *Runtime) (*Volume, error) {
volume := new(Volume)
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 8edab831f..8da44a2f0 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -11,7 +11,9 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/namespaces"
+ "github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
+ "github.com/containers/storage/pkg/stringid"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -133,8 +135,8 @@ type CreateConfig struct {
SeccompProfilePath string //SecurityOpts
SecurityOpts []string
Rootfs string
- LocalVolumes []string //Keeps track of the built-in volumes of container used in the --volumes-from flag
- Syslog bool // Whether to enable syslog on exit commands
+ LocalVolumes []spec.Mount //Keeps track of the built-in volumes of container used in the --volumes-from flag
+ Syslog bool // Whether to enable syslog on exit commands
}
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
@@ -215,7 +217,7 @@ func (c *CreateConfig) initFSMounts() []spec.Mount {
//GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, error) {
- var m []spec.Mount
+ m := c.LocalVolumes
for _, i := range c.Volumes {
var options []string
spliti := strings.Split(i, ":")
@@ -233,22 +235,31 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e
logrus.Debugf("User mount %s:%s options %v", spliti[0], spliti[1], options)
}
- // volumes from image config
- if c.ImageVolumeType != "tmpfs" {
+ if c.ImageVolumeType == "ignore" {
return m, nil
}
+
for vol := range c.BuiltinImgVolumes {
if libpod.MountExists(specMounts, vol) {
continue
}
+
mount := spec.Mount{
Destination: vol,
- Type: string(TypeTmpfs),
- Source: string(TypeTmpfs),
- Options: []string{"rprivate", "rw", "noexec", "nosuid", "nodev", "tmpcopyup"},
+ Type: c.ImageVolumeType,
+ Options: []string{"rprivate", "rw", "nodev"},
+ }
+ if c.ImageVolumeType == "tmpfs" {
+ mount.Source = "tmpfs"
+ mount.Options = append(mount.Options, "tmpcopyup")
+ } else {
+ // This will cause a new local Volume to be created on your system
+ mount.Source = stringid.GenerateNonCryptoID()
+ mount.Options = append(mount.Options, "bind")
}
m = append(m, mount)
}
+
return m, nil
}
@@ -256,6 +267,11 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e
// and adds it to c.Volumes of the current container.
func (c *CreateConfig) GetVolumesFrom() error {
var options string
+
+ if rootless.SkipStorageSetup() {
+ return nil
+ }
+
for _, vol := range c.VolumesFrom {
splitVol := strings.SplitN(vol, ":", 2)
if len(splitVol) == 2 {
@@ -265,6 +281,10 @@ func (c *CreateConfig) GetVolumesFrom() error {
if err != nil {
return errors.Wrapf(err, "error looking up container %q", splitVol[0])
}
+ inspect, err := ctr.Inspect(false)
+ if err != nil {
+ return errors.Wrapf(err, "error inspecting %q", splitVol[0])
+ }
var createArtifact CreateConfig
artifact, err := ctr.GetArtifact("create-config")
if err != nil {
@@ -273,9 +293,13 @@ func (c *CreateConfig) GetVolumesFrom() error {
if err := json.Unmarshal(artifact, &createArtifact); err != nil {
return err
}
-
for key := range createArtifact.BuiltinImgVolumes {
- c.LocalVolumes = append(c.LocalVolumes, key)
+ for _, m := range inspect.Mounts {
+ if m.Destination == key {
+ c.LocalVolumes = append(c.LocalVolumes, m)
+ break
+ }
+ }
}
for _, i := range createArtifact.Volumes {
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 52f431881..db8a3d5bb 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -259,8 +259,8 @@ func GetRootlessStorageOpts() (storage.StoreOptions, error) {
return opts, nil
}
-// GetRootlessVolumeInfo returns where all the name volumes will be created in rootless mode
-func GetRootlessVolumeInfo() (string, error) {
+// GetRootlessVolumePath returns where all the name volumes will be created in rootless mode
+func GetRootlessVolumePath() (string, error) {
dataDir, _, err := GetRootlessDirInfo()
if err != nil {
return "", err
@@ -307,15 +307,13 @@ func GetDefaultStoreOptions() (storage.StoreOptions, string, error) {
err error
)
storageOpts := storage.DefaultStoreOptions
- volumePath := "/var/lib/containers/storage"
-
+ volumePath := filepath.Join(storageOpts.GraphRoot, "volumes")
if rootless.IsRootless() {
storageOpts, err = GetRootlessStorageOpts()
if err != nil {
return storageOpts, volumePath, err
}
-
- volumePath, err = GetRootlessVolumeInfo()
+ volumePath, err = GetRootlessVolumePath()
if err != nil {
return storageOpts, volumePath, err
}
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 2b2832838..8a52efa61 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -358,13 +358,13 @@ func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string) error
}
// RemoveContainer ...
-func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, force bool) error {
+func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, force bool, removeVolumes bool) error {
ctx := getContext()
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
- if err := i.Runtime.RemoveContainer(ctx, ctr, force); err != nil {
+ if err := i.Runtime.RemoveContainer(ctx, ctr, force, removeVolumes); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyRemoveContainer(ctr.ID())
@@ -385,7 +385,7 @@ func (i *LibpodAPI) DeleteStoppedContainers(call iopodman.VarlinkCall) error {
return call.ReplyErrorOccurred(err.Error())
}
if state != libpod.ContainerStateRunning {
- if err := i.Runtime.RemoveContainer(ctx, ctr, false); err != nil {
+ if err := i.Runtime.RemoveContainer(ctx, ctr, false, false); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
deletedContainers = append(deletedContainers, ctr.ID())
diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go
index f1835a189..6b53b22c6 100644
--- a/pkg/varlinkapi/containers_create.go
+++ b/pkg/varlinkapi/containers_create.go
@@ -131,9 +131,14 @@ func varlinkCreateToCreateConfig(ctx context.Context, create iopodman.Create, ru
}
imageID := data.ID
+ var ImageVolumes map[string]struct{}
+ if data != nil && create.Image_volume_type != "ignore" {
+ ImageVolumes = data.Config.Volumes
+ }
+
config := &cc.CreateConfig{
Runtime: runtime,
- BuiltinImgVolumes: data.Config.Volumes,
+ BuiltinImgVolumes: ImageVolumes,
ConmonPidFile: create.Conmon_pidfile,
ImageVolumeType: create.Image_volume_type,
CapAdd: create.Cap_add,
diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go
new file mode 100644
index 000000000..e1e760ee0
--- /dev/null
+++ b/test/e2e/cp_test.go
@@ -0,0 +1,115 @@
+// +build !remoteclient
+
+package integration
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman cp", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+ })
+
+ It("podman cp file", func() {
+ path, err := os.Getwd()
+ if err != nil {
+ os.Exit(1)
+ }
+ filePath := filepath.Join(path, "cp_test.txt")
+ fromHostToContainer := []byte("copy from host to container")
+ err = ioutil.WriteFile(filePath, fromHostToContainer, 0644)
+ if err != nil {
+ os.Exit(1)
+ }
+
+ session := podmanTest.Podman([]string{"create", ALPINE, "cat", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ name := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"cp", filepath.Join(path, "cp_test.txt"), name + ":foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"start", "-a", name})
+ session.WaitWithDefaultTimeout()
+
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(Equal("copy from host to container"))
+
+ session = podmanTest.Podman([]string{"cp", name + ":foo", filepath.Join(path, "cp_from_container")})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ c := exec.Command("cat", filepath.Join(path, "cp_from_container"))
+ output, err := c.Output()
+ if err != nil {
+ os.Exit(1)
+ }
+ Expect(string(output)).To(Equal("copy from host to container"))
+ })
+
+ It("podman cp file to dir", func() {
+ path, err := os.Getwd()
+ if err != nil {
+ os.Exit(1)
+ }
+ filePath := filepath.Join(path, "cp_test.txt")
+ fromHostToContainer := []byte("copy from host to container directory")
+ err = ioutil.WriteFile(filePath, fromHostToContainer, 0644)
+ if err != nil {
+ os.Exit(1)
+ }
+ session := podmanTest.Podman([]string{"create", ALPINE, "ls", "foodir/"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"ps", "-a", "-q"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ name := session.OutputToString()
+
+ session = podmanTest.Podman([]string{"cp", filepath.Join(path, "cp_test.txt"), name + ":foodir/"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"start", "-a", name})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(Equal("cp_test.txt"))
+
+ session = podmanTest.Podman([]string{"cp", name + ":foodir/cp_test.txt", path + "/receive/"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ c := exec.Command("cat", filepath.Join(path, "receive", "cp_test.txt"))
+ output, err := c.Output()
+ if err != nil {
+ os.Exit(1)
+ }
+ Expect(string(output)).To(Equal("copy from host to container directory"))
+ })
+})
diff --git a/transfer.md b/transfer.md
index af7904e5f..c2d472f08 100644
--- a/transfer.md
+++ b/transfer.md
@@ -37,11 +37,11 @@ There are other equivalents for these tools
| Existing Step | `Podman` (and friends) |
| :--- | :--- |
-| `docker attach` | [`podman exec`](./docs/podman-attach.1.md) |
+| `docker attach` | [`podman attach`](./docs/podman-attach.1.md) |
+| `docker cp` | [`podman cp`](./docs/podman-cp.1.md) |
| `docker build` | [`podman build`](./docs/podman-build.1.md) |
| `docker commit` | [`podman commit`](./docs/podman-commit.1.md) |
| `docker container`|[`podman container`](./docs/podman-container.1.md) |
-| `docker cp` | [`podman mount`](./docs/podman-cp.1.md) **** |
| `docker create` | [`podman create`](./docs/podman-create.1.md) |
| `docker diff` | [`podman diff`](./docs/podman-diff.1.md) |
| `docker export` | [`podman export`](./docs/podman-export.1.md) |
diff --git a/vendor.conf b/vendor.conf
index 911b83325..36452038f 100644
--- a/vendor.conf
+++ b/vendor.conf
@@ -15,8 +15,8 @@ github.com/containerd/cgroups 39b18af02c4120960f517a3a4c2588fabb61d02c
github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d
github.com/containernetworking/cni v0.7.0-alpha1
github.com/containernetworking/plugins v0.7.4
-github.com/containers/image v1.3
-github.com/containers/storage v1.9
+github.com/containers/image 93bced01015eb94bec4821df1876314be8197680
+github.com/containers/storage 06b6c2e4cf254f5922a79da058c94ac2a65bb92f
github.com/containers/psgo v1.1
github.com/coreos/go-systemd v14
github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c
diff --git a/vendor/github.com/containers/image/copy/copy.go b/vendor/github.com/containers/image/copy/copy.go
index 89c7e580f..2d3a2a1a8 100644
--- a/vendor/github.com/containers/image/copy/copy.go
+++ b/vendor/github.com/containers/image/copy/copy.go
@@ -6,13 +6,16 @@ import (
"fmt"
"io"
"io/ioutil"
+ "os"
"reflect"
"runtime"
"strings"
"sync"
"time"
+ "github.com/containers/image/docker/reference"
"github.com/containers/image/image"
+ "github.com/containers/image/manifest"
"github.com/containers/image/pkg/blobinfocache"
"github.com/containers/image/pkg/compression"
"github.com/containers/image/signature"
@@ -22,6 +25,7 @@ import (
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh/terminal"
"golang.org/x/sync/semaphore"
pb "gopkg.in/cheggaaa/pb.v1"
)
@@ -84,6 +88,7 @@ type copier struct {
dest types.ImageDestination
rawSource types.ImageSource
reportWriter io.Writer
+ progressOutput io.Writer
progressInterval time.Duration
progress chan types.ProgressProperties
blobInfoCache types.BlobInfoCache
@@ -152,11 +157,19 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef,
}
}()
+ // If reportWriter is not a TTY (e.g., when piping to a file), do not
+ // print the progress bars to avoid long and hard to parse output.
+ // createProgressBar() will print a single line instead.
+ progressOutput := reportWriter
+ if !isTTY(reportWriter) {
+ progressOutput = ioutil.Discard
+ }
copyInParallel := dest.HasThreadSafePutBlob() && rawSource.HasThreadSafeGetBlob()
c := &copier{
dest: dest,
rawSource: rawSource,
reportWriter: reportWriter,
+ progressOutput: progressOutput,
progressInterval: options.ProgressInterval,
progress: options.Progress,
copyInParallel: copyInParallel,
@@ -201,7 +214,7 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef,
// Image copies a single (on-manifest-list) image unparsedImage, using policyContext to validate
// source image admissibility.
-func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedImage *image.UnparsedImage) (manifest []byte, retErr error) {
+func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedImage *image.UnparsedImage) (manifestBytes []byte, retErr error) {
// The caller is handling manifest lists; this could happen only if a manifest list contains a manifest list.
// Make sure we fail cleanly in such cases.
multiImage, err := isMultiImage(ctx, unparsedImage)
@@ -224,6 +237,26 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
return nil, errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference()))
}
+ // If the destination is a digested reference, make a note of that, determine what digest value we're
+ // expecting, and check that the source manifest matches it.
+ destIsDigestedReference := false
+ if named := c.dest.Reference().DockerReference(); named != nil {
+ if digested, ok := named.(reference.Digested); ok {
+ destIsDigestedReference = true
+ sourceManifest, _, err := src.Manifest(ctx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Error reading manifest from source image")
+ }
+ matches, err := manifest.MatchesDigest(sourceManifest, digested.Digest())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Error computing digest of source image's manifest")
+ }
+ if !matches {
+ return nil, errors.New("Digest of source image's manifest would not match destination reference")
+ }
+ }
+ }
+
if err := checkImageDestinationForCurrentRuntimeOS(ctx, options.DestinationCtx, src, c.dest); err != nil {
return nil, err
}
@@ -251,15 +284,15 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
manifestUpdates: &types.ManifestUpdateOptions{InformationOnly: types.ManifestUpdateInformation{Destination: c.dest}},
src: src,
// diffIDsAreNeeded is computed later
- canModifyManifest: len(sigs) == 0,
- // Ensure _this_ copy sees exactly the intended data when either processing a signed image or signing it.
- // This may be too conservative, but for now, better safe than sorry, _especially_ on the SignBy path:
- // The signature makes the content non-repudiable, so it very much matters that the signature is made over exactly what the user intended.
- // We do intend the RecordDigestUncompressedPair calls to only work with reliable data, but at least there’s a risk
- // that the compressed version coming from a third party may be designed to attack some other decompressor implementation,
- // and we would reuse and sign it.
- canSubstituteBlobs: len(sigs) == 0 && options.SignBy == "",
+ canModifyManifest: len(sigs) == 0 && !destIsDigestedReference,
}
+ // Ensure _this_ copy sees exactly the intended data when either processing a signed image or signing it.
+ // This may be too conservative, but for now, better safe than sorry, _especially_ on the SignBy path:
+ // The signature makes the content non-repudiable, so it very much matters that the signature is made over exactly what the user intended.
+ // We do intend the RecordDigestUncompressedPair calls to only work with reliable data, but at least there’s a risk
+ // that the compressed version coming from a third party may be designed to attack some other decompressor implementation,
+ // and we would reuse and sign it.
+ ic.canSubstituteBlobs = ic.canModifyManifest && options.SignBy == ""
if err := ic.updateEmbeddedDockerReference(); err != nil {
return nil, err
@@ -283,7 +316,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
// and at least with the OpenShift registry "acceptschema2" option, there is no way to detect the support
// without actually trying to upload something and getting a types.ManifestTypeRejectedError.
// So, try the preferred manifest MIME type. If the process succeeds, fine…
- manifest, err = ic.copyUpdatedConfigAndManifest(ctx)
+ manifestBytes, err = ic.copyUpdatedConfigAndManifest(ctx)
if err != nil {
logrus.Debugf("Writing manifest using preferred type %s failed: %v", preferredManifestMIMEType, err)
// … if it fails, _and_ the failure is because the manifest is rejected, we may have other options.
@@ -314,7 +347,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
}
// We have successfully uploaded a manifest.
- manifest = attemptedManifest
+ manifestBytes = attemptedManifest
errs = nil // Mark this as a success so that we don't abort below.
break
}
@@ -324,7 +357,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
}
if options.SignBy != "" {
- newSig, err := c.createSignature(manifest, options.SignBy)
+ newSig, err := c.createSignature(manifestBytes, options.SignBy)
if err != nil {
return nil, err
}
@@ -336,7 +369,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
return nil, errors.Wrap(err, "Error writing signatures")
}
- return manifest, nil
+ return manifestBytes, nil
}
// Printf writes a formatted string to c.reportWriter.
@@ -394,17 +427,30 @@ func shortDigest(d digest.Digest) string {
return d.Encoded()[:12]
}
-// createProgressBar creates a pb.ProgressBar.
-func createProgressBar(srcInfo types.BlobInfo, kind string, writer io.Writer) *pb.ProgressBar {
+// createProgressBar creates a pb.ProgressBar. Note that if the copier's
+// reportWriter is ioutil.Discard, the progress bar's output will be discarded
+// and a single line will be printed instead.
+func (c *copier) createProgressBar(srcInfo types.BlobInfo, kind string) *pb.ProgressBar {
bar := pb.New(int(srcInfo.Size)).SetUnits(pb.U_BYTES)
bar.SetMaxWidth(80)
bar.ShowTimeLeft = false
bar.ShowPercent = false
bar.Prefix(fmt.Sprintf("Copying %s %s:", kind, shortDigest(srcInfo.Digest)))
- bar.Output = writer
+ bar.Output = c.progressOutput
+ if bar.Output == ioutil.Discard {
+ c.Printf("Copying %s %s\n", kind, srcInfo.Digest)
+ }
return bar
}
+// isTTY returns true if the io.Writer is a file and a tty.
+func isTTY(w io.Writer) bool {
+ if f, ok := w.(*os.File); ok {
+ return terminal.IsTerminal(int(f.Fd()))
+ }
+ return false
+}
+
// copyLayers copies layers from ic.src/ic.c.rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.canModifyManifest.
func (ic *imageCopier) copyLayers(ctx context.Context) error {
srcInfos := ic.src.LayerInfos()
@@ -456,7 +502,7 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error {
bar.Finish()
} else {
cld.destInfo = srcLayer
- logrus.Debugf("Skipping foreign layer %q copy to %s\n", cld.destInfo.Digest, ic.c.dest.Reference().Transport().Name())
+ logrus.Debugf("Skipping foreign layer %q copy to %s", cld.destInfo.Digest, ic.c.dest.Reference().Transport().Name())
bar.Prefix(fmt.Sprintf("Skipping blob %s (foreign layer):", shortDigest(srcLayer.Digest)))
bar.Add64(bar.Total)
bar.Finish()
@@ -469,12 +515,13 @@ func (ic *imageCopier) copyLayers(ctx context.Context) error {
progressBars := make([]*pb.ProgressBar, numLayers)
for i, srcInfo := range srcInfos {
- bar := createProgressBar(srcInfo, "blob", nil)
+ bar := ic.c.createProgressBar(srcInfo, "blob")
progressBars[i] = bar
}
progressPool := pb.NewPool(progressBars...)
- progressPool.Output = ic.c.reportWriter
+ progressPool.Output = ic.c.progressOutput
+
if err := progressPool.Start(); err != nil {
return errors.Wrapf(err, "error creating progress-bar pool")
}
@@ -568,7 +615,7 @@ func (c *copier) copyConfig(ctx context.Context, src types.Image) error {
if err != nil {
return errors.Wrapf(err, "Error reading config blob %s", srcInfo.Digest)
}
- bar := createProgressBar(srcInfo, "config", c.reportWriter)
+ bar := c.createProgressBar(srcInfo, "config")
defer bar.Finish()
bar.Start()
destInfo, err := c.copyBlobFromStream(ctx, bytes.NewReader(configBlob), srcInfo, nil, false, true, bar)
diff --git a/vendor/github.com/containers/image/docker/docker_client.go b/vendor/github.com/containers/image/docker/docker_client.go
index 23d2ac70f..43eb22ba2 100644
--- a/vendor/github.com/containers/image/docker/docker_client.go
+++ b/vendor/github.com/containers/image/docker/docker_client.go
@@ -91,7 +91,6 @@ type dockerClient struct {
password string
signatureBase signatureStorageBase
scope authScope
- extraScope *authScope // If non-nil, a temporary extra token scope (necessary for mounting from another repo)
// The following members are detected registry properties:
// They are set after a successful detectProperties(), and never change afterwards.
scheme string // Empty value also used to indicate detectProperties() has not yet succeeded.
@@ -282,7 +281,7 @@ func CheckAuth(ctx context.Context, sys *types.SystemContext, username, password
client.username = username
client.password = password
- resp, err := client.makeRequest(ctx, "GET", "/v2/", nil, nil, v2Auth)
+ resp, err := client.makeRequest(ctx, "GET", "/v2/", nil, nil, v2Auth, nil)
if err != nil {
return err
}
@@ -362,8 +361,8 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
q.Set("n", strconv.Itoa(limit))
u.RawQuery = q.Encode()
- logrus.Debugf("trying to talk to v1 search endpoint\n")
- resp, err := client.makeRequest(ctx, "GET", u.String(), nil, nil, noAuth)
+ logrus.Debugf("trying to talk to v1 search endpoint")
+ resp, err := client.makeRequest(ctx, "GET", u.String(), nil, nil, noAuth, nil)
if err != nil {
logrus.Debugf("error getting search results from v1 endpoint %q: %v", registry, err)
} else {
@@ -379,8 +378,8 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
}
}
- logrus.Debugf("trying to talk to v2 search endpoint\n")
- resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil, v2Auth)
+ logrus.Debugf("trying to talk to v2 search endpoint")
+ resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil, v2Auth, nil)
if err != nil {
logrus.Debugf("error getting search results from v2 endpoint %q: %v", registry, err)
} else {
@@ -409,20 +408,20 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
// The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/.
-func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader, auth sendAuth) (*http.Response, error) {
+func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader, auth sendAuth, extraScope *authScope) (*http.Response, error) {
if err := c.detectProperties(ctx); err != nil {
return nil, err
}
url := fmt.Sprintf("%s://%s%s", c.scheme, c.registry, path)
- return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth)
+ return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth, extraScope)
}
// makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
// streamLen, if not -1, specifies the length of the data expected on stream.
// makeRequest should generally be preferred.
// TODO(runcom): too many arguments here, use a struct
-func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth) (*http.Response, error) {
+func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth, extraScope *authScope) (*http.Response, error) {
req, err := http.NewRequest(method, url, stream)
if err != nil {
return nil, err
@@ -441,7 +440,7 @@ func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url
req.Header.Add("User-Agent", c.sys.DockerRegistryUserAgent)
}
if auth == v2Auth {
- if err := c.setupRequestAuth(req); err != nil {
+ if err := c.setupRequestAuth(req, extraScope); err != nil {
return nil, err
}
}
@@ -460,7 +459,7 @@ func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url
// 2) gcr.io is sending 401 without a WWW-Authenticate header in the real request
//
// debugging: https://github.com/containers/image/pull/211#issuecomment-273426236 and follows up
-func (c *dockerClient) setupRequestAuth(req *http.Request) error {
+func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope) error {
if len(c.challenges) == 0 {
return nil
}
@@ -474,10 +473,10 @@ func (c *dockerClient) setupRequestAuth(req *http.Request) error {
case "bearer":
cacheKey := ""
scopes := []authScope{c.scope}
- if c.extraScope != nil {
+ if extraScope != nil {
// Using ':' as a separator here is unambiguous because getBearerToken below uses the same separator when formatting a remote request (and because repository names can't contain colons).
- cacheKey = fmt.Sprintf("%s:%s", c.extraScope.remoteName, c.extraScope.actions)
- scopes = append(scopes, *c.extraScope)
+ cacheKey = fmt.Sprintf("%s:%s", extraScope.remoteName, extraScope.actions)
+ scopes = append(scopes, *extraScope)
}
var token bearerToken
t, inCache := c.tokenCache.Load(cacheKey)
@@ -564,7 +563,7 @@ func (c *dockerClient) detectPropertiesHelper(ctx context.Context) error {
ping := func(scheme string) error {
url := fmt.Sprintf(resolvedPingV2URL, scheme, c.registry)
- resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth)
+ resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth, nil)
if err != nil {
logrus.Debugf("Ping %s err %s (%#v)", url, err.Error(), err)
return err
@@ -591,7 +590,7 @@ func (c *dockerClient) detectPropertiesHelper(ctx context.Context) error {
// best effort to understand if we're talking to a V1 registry
pingV1 := func(scheme string) bool {
url := fmt.Sprintf(resolvedPingV1URL, scheme, c.registry)
- resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth)
+ resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth, nil)
if err != nil {
logrus.Debugf("Ping %s err %s (%#v)", url, err.Error(), err)
return false
@@ -625,7 +624,7 @@ func (c *dockerClient) detectProperties(ctx context.Context) error {
// using the original data structures.
func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerReference, manifestDigest digest.Digest) (*extensionSignatureList, error) {
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(ref.ref), manifestDigest)
- res, err := c.makeRequest(ctx, "GET", path, nil, nil, v2Auth)
+ res, err := c.makeRequest(ctx, "GET", path, nil, nil, v2Auth, nil)
if err != nil {
return nil, err
}
diff --git a/vendor/github.com/containers/image/docker/docker_image.go b/vendor/github.com/containers/image/docker/docker_image.go
index 2ab95f329..530c7513e 100644
--- a/vendor/github.com/containers/image/docker/docker_image.go
+++ b/vendor/github.com/containers/image/docker/docker_image.go
@@ -66,7 +66,7 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types.
tags := make([]string, 0)
for {
- res, err := client.makeRequest(ctx, "GET", path, nil, nil, v2Auth)
+ res, err := client.makeRequest(ctx, "GET", path, nil, nil, v2Auth, nil)
if err != nil {
return nil, err
}
diff --git a/vendor/github.com/containers/image/docker/docker_image_dest.go b/vendor/github.com/containers/image/docker/docker_image_dest.go
index 973d160d0..38500dd0e 100644
--- a/vendor/github.com/containers/image/docker/docker_image_dest.go
+++ b/vendor/github.com/containers/image/docker/docker_image_dest.go
@@ -12,6 +12,7 @@ import (
"net/url"
"os"
"path/filepath"
+ "strings"
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
@@ -113,7 +114,7 @@ func (c *sizeCounter) Write(p []byte) (n int, err error) {
// HasThreadSafePutBlob indicates whether PutBlob can be executed concurrently.
func (d *dockerImageDestination) HasThreadSafePutBlob() bool {
- return false
+ return true
}
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
@@ -140,7 +141,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader,
// FIXME? Chunked upload, progress reporting, etc.
uploadPath := fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref))
logrus.Debugf("Uploading %s", uploadPath)
- res, err := d.c.makeRequest(ctx, "POST", uploadPath, nil, nil, v2Auth)
+ res, err := d.c.makeRequest(ctx, "POST", uploadPath, nil, nil, v2Auth, nil)
if err != nil {
return types.BlobInfo{}, err
}
@@ -157,7 +158,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader,
digester := digest.Canonical.Digester()
sizeCounter := &sizeCounter{}
tee := io.TeeReader(stream, io.MultiWriter(digester.Hash(), sizeCounter))
- res, err = d.c.makeRequestToResolvedURL(ctx, "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, v2Auth)
+ res, err = d.c.makeRequestToResolvedURL(ctx, "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, v2Auth, nil)
if err != nil {
logrus.Debugf("Error uploading layer chunked, response %#v", res)
return types.BlobInfo{}, err
@@ -176,7 +177,7 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader,
// TODO: check inputInfo.Digest == computedDigest https://github.com/containers/image/pull/70#discussion_r77646717
locationQuery.Set("digest", computedDigest.String())
uploadLocation.RawQuery = locationQuery.Encode()
- res, err = d.c.makeRequestToResolvedURL(ctx, "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, v2Auth)
+ res, err = d.c.makeRequestToResolvedURL(ctx, "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, v2Auth, nil)
if err != nil {
return types.BlobInfo{}, err
}
@@ -194,10 +195,10 @@ func (d *dockerImageDestination) PutBlob(ctx context.Context, stream io.Reader,
// blobExists returns true iff repo contains a blob with digest, and if so, also its size.
// If the destination does not contain the blob, or it is unknown, blobExists ordinarily returns (false, -1, nil);
// it returns a non-nil error only on an unexpected failure.
-func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.Named, digest digest.Digest) (bool, int64, error) {
+func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.Named, digest digest.Digest, extraScope *authScope) (bool, int64, error) {
checkPath := fmt.Sprintf(blobsPath, reference.Path(repo), digest.String())
logrus.Debugf("Checking %s", checkPath)
- res, err := d.c.makeRequest(ctx, "HEAD", checkPath, nil, nil, v2Auth)
+ res, err := d.c.makeRequest(ctx, "HEAD", checkPath, nil, nil, v2Auth, extraScope)
if err != nil {
return false, -1, err
}
@@ -218,7 +219,7 @@ func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.
}
// mountBlob tries to mount blob srcDigest from srcRepo to the current destination.
-func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo reference.Named, srcDigest digest.Digest) error {
+func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo reference.Named, srcDigest digest.Digest, extraScope *authScope) error {
u := url.URL{
Path: fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref)),
RawQuery: url.Values{
@@ -228,7 +229,7 @@ func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo referenc
}
mountPath := u.String()
logrus.Debugf("Trying to mount %s", mountPath)
- res, err := d.c.makeRequest(ctx, "POST", mountPath, nil, nil, v2Auth)
+ res, err := d.c.makeRequest(ctx, "POST", mountPath, nil, nil, v2Auth, extraScope)
if err != nil {
return err
}
@@ -246,7 +247,7 @@ func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo referenc
return errors.Wrap(err, "Error determining upload URL after a mount attempt")
}
logrus.Debugf("... started an upload instead of mounting, trying to cancel at %s", uploadLocation.String())
- res2, err := d.c.makeRequestToResolvedURL(ctx, "DELETE", uploadLocation.String(), nil, nil, -1, v2Auth)
+ res2, err := d.c.makeRequestToResolvedURL(ctx, "DELETE", uploadLocation.String(), nil, nil, -1, v2Auth, extraScope)
if err != nil {
logrus.Debugf("Error trying to cancel an inadvertent upload: %s", err)
} else {
@@ -276,7 +277,7 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types.
}
// First, check whether the blob happens to already exist at the destination.
- exists, size, err := d.blobExists(ctx, d.ref.ref, info.Digest)
+ exists, size, err := d.blobExists(ctx, d.ref.ref, info.Digest, nil)
if err != nil {
return false, types.BlobInfo{}, err
}
@@ -286,15 +287,6 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types.
}
// Then try reusing blobs from other locations.
-
- // Checking candidateRepo, and mounting from it, requires an expanded token scope.
- // We still want to reuse the ping information and other aspects of the client, so rather than make a fresh copy, there is this a bit ugly extraScope hack.
- if d.c.extraScope != nil {
- return false, types.BlobInfo{}, errors.New("Internal error: dockerClient.extraScope was set before TryReusingBlob")
- }
- defer func() {
- d.c.extraScope = nil
- }()
for _, candidate := range cache.CandidateLocations(d.ref.Transport(), bicTransportScope(d.ref), info.Digest, canSubstitute) {
candidateRepo, err := parseBICLocationReference(candidate.Location)
if err != nil {
@@ -314,7 +306,10 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types.
}
// Whatever happens here, don't abort the entire operation. It's likely we just don't have permissions, and if it is a critical network error, we will find out soon enough anyway.
- d.c.extraScope = &authScope{
+
+ // Checking candidateRepo, and mounting from it, requires an
+ // expanded token scope.
+ extraScope := &authScope{
remoteName: reference.Path(candidateRepo),
actions: "pull",
}
@@ -325,7 +320,7 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types.
// Even worse, docker/distribution does not actually reasonably implement canceling uploads
// (it would require a "delete" action in the token, and Quay does not give that to anyone, so we can't ask);
// so, be a nice client and don't create unnecesary upload sessions on the server.
- exists, size, err := d.blobExists(ctx, candidateRepo, candidate.Digest)
+ exists, size, err := d.blobExists(ctx, candidateRepo, candidate.Digest, extraScope)
if err != nil {
logrus.Debugf("... Failed: %v", err)
continue
@@ -335,7 +330,7 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types.
continue // logrus.Debug() already happened in blobExists
}
if candidateRepo.Name() != d.ref.ref.Name() {
- if err := d.mountBlob(ctx, candidateRepo, candidate.Digest); err != nil {
+ if err := d.mountBlob(ctx, candidateRepo, candidate.Digest, extraScope); err != nil {
logrus.Debugf("... Mount failed: %v", err)
continue
}
@@ -369,7 +364,7 @@ func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte) erro
if mimeType != "" {
headers["Content-Type"] = []string{mimeType}
}
- res, err := d.c.makeRequest(ctx, "PUT", path, headers, bytes.NewReader(m), v2Auth)
+ res, err := d.c.makeRequest(ctx, "PUT", path, headers, bytes.NewReader(m), v2Auth, nil)
if err != nil {
return err
}
@@ -396,14 +391,29 @@ func isManifestInvalidError(err error) bool {
if !ok || len(errors) == 0 {
return false
}
- ec, ok := errors[0].(errcode.ErrorCoder)
+ err = errors[0]
+ ec, ok := err.(errcode.ErrorCoder)
if !ok {
return false
}
+
+ switch ec.ErrorCode() {
// ErrorCodeManifestInvalid is returned by OpenShift with acceptschema2=false.
+ case v2.ErrorCodeManifestInvalid:
+ return true
// ErrorCodeTagInvalid is returned by docker/distribution (at least as of commit ec87e9b6971d831f0eff752ddb54fb64693e51cd)
// when uploading to a tag (because it can’t find a matching tag inside the manifest)
- return ec.ErrorCode() == v2.ErrorCodeManifestInvalid || ec.ErrorCode() == v2.ErrorCodeTagInvalid
+ case v2.ErrorCodeTagInvalid:
+ return true
+ // ErrorCodeUnsupported with 'Invalid JSON syntax' is returned by AWS ECR when
+ // uploading an OCI manifest that is (correctly, according to the spec) missing
+ // a top-level media type. See libpod issue #1719
+ // FIXME: remove this case when ECR behavior is fixed
+ case errcode.ErrorCodeUnsupported:
+ return strings.Contains(err.Error(), "Invalid JSON syntax")
+ default:
+ return false
+ }
}
func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
@@ -574,7 +584,7 @@ sigExists:
}
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(d.ref.ref), d.manifestDigest.String())
- res, err := d.c.makeRequest(ctx, "PUT", path, nil, bytes.NewReader(body), v2Auth)
+ res, err := d.c.makeRequest(ctx, "PUT", path, nil, bytes.NewReader(body), v2Auth, nil)
if err != nil {
return err
}
diff --git a/vendor/github.com/containers/image/docker/docker_image_src.go b/vendor/github.com/containers/image/docker/docker_image_src.go
index c88ff2f34..8367792bf 100644
--- a/vendor/github.com/containers/image/docker/docker_image_src.go
+++ b/vendor/github.com/containers/image/docker/docker_image_src.go
@@ -89,7 +89,7 @@ func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest strin
path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest)
headers := make(map[string][]string)
headers["Accept"] = manifest.DefaultRequestedManifestMIMETypes
- res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth)
+ res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth, nil)
if err != nil {
return nil, "", err
}
@@ -137,7 +137,7 @@ func (s *dockerImageSource) getExternalBlob(ctx context.Context, urls []string)
err error
)
for _, url := range urls {
- resp, err = s.c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth)
+ resp, err = s.c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth, nil)
if err == nil {
if resp.StatusCode != http.StatusOK {
err = errors.Errorf("error fetching external blob from %q: %d (%s)", url, resp.StatusCode, http.StatusText(resp.StatusCode))
@@ -147,10 +147,10 @@ func (s *dockerImageSource) getExternalBlob(ctx context.Context, urls []string)
break
}
}
- if resp.Body != nil && err == nil {
- return resp.Body, getBlobSize(resp), nil
+ if err != nil {
+ return nil, 0, err
}
- return nil, 0, err
+ return resp.Body, getBlobSize(resp), nil
}
func getBlobSize(resp *http.Response) int64 {
@@ -176,7 +176,7 @@ func (s *dockerImageSource) GetBlob(ctx context.Context, info types.BlobInfo, ca
path := fmt.Sprintf(blobsPath, reference.Path(s.ref.ref), info.Digest.String())
logrus.Debugf("Downloading %s", path)
- res, err := s.c.makeRequest(ctx, "GET", path, nil, nil, v2Auth)
+ res, err := s.c.makeRequest(ctx, "GET", path, nil, nil, v2Auth, nil)
if err != nil {
return nil, 0, err
}
@@ -340,7 +340,7 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere
return err
}
getPath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), refTail)
- get, err := c.makeRequest(ctx, "GET", getPath, headers, nil, v2Auth)
+ get, err := c.makeRequest(ctx, "GET", getPath, headers, nil, v2Auth, nil)
if err != nil {
return err
}
@@ -362,7 +362,7 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere
// When retrieving the digest from a registry >= 2.3 use the following header:
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"
- delete, err := c.makeRequest(ctx, "DELETE", deletePath, headers, nil, v2Auth)
+ delete, err := c.makeRequest(ctx, "DELETE", deletePath, headers, nil, v2Auth, nil)
if err != nil {
return err
}
diff --git a/vendor/github.com/containers/image/docker/tarfile/src.go b/vendor/github.com/containers/image/docker/tarfile/src.go
index 889e5f8e8..03735f8a4 100644
--- a/vendor/github.com/containers/image/docker/tarfile/src.go
+++ b/vendor/github.com/containers/image/docker/tarfile/src.go
@@ -9,6 +9,7 @@ import (
"io/ioutil"
"os"
"path"
+ "sync"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/manifest"
@@ -21,8 +22,10 @@ import (
// Source is a partial implementation of types.ImageSource for reading from tarPath.
type Source struct {
tarPath string
- removeTarPathOnClose bool // Remove temp file on close if true
+ removeTarPathOnClose bool // Remove temp file on close if true
+ cacheDataLock sync.Once // Atomic way to ensure that ensureCachedDataIsPresent is only invoked once
// The following data is only available after ensureCachedDataIsPresent() succeeds
+ cacheDataResult error // The return value of ensureCachedDataIsPresent, since it should be as safe to cache as the side effects
tarManifest *ManifestItem // nil if not available yet.
configBytes []byte
configDigest digest.Digest
@@ -199,43 +202,46 @@ func (s *Source) readTarComponent(path string) ([]byte, error) {
// ensureCachedDataIsPresent loads data necessary for any of the public accessors.
func (s *Source) ensureCachedDataIsPresent() error {
- if s.tarManifest != nil {
- return nil
- }
-
- // Read and parse manifest.json
- tarManifest, err := s.loadTarManifest()
- if err != nil {
- return err
- }
+ s.cacheDataLock.Do(func() {
+ // Read and parse manifest.json
+ tarManifest, err := s.loadTarManifest()
+ if err != nil {
+ s.cacheDataResult = err
+ return
+ }
- // Check to make sure length is 1
- if len(tarManifest) != 1 {
- return errors.Errorf("Unexpected tar manifest.json: expected 1 item, got %d", len(tarManifest))
- }
+ // Check to make sure length is 1
+ if len(tarManifest) != 1 {
+ s.cacheDataResult = errors.Errorf("Unexpected tar manifest.json: expected 1 item, got %d", len(tarManifest))
+ return
+ }
- // Read and parse config.
- configBytes, err := s.readTarComponent(tarManifest[0].Config)
- if err != nil {
- return err
- }
- var parsedConfig manifest.Schema2Image // There's a lot of info there, but we only really care about layer DiffIDs.
- if err := json.Unmarshal(configBytes, &parsedConfig); err != nil {
- return errors.Wrapf(err, "Error decoding tar config %s", tarManifest[0].Config)
- }
+ // Read and parse config.
+ configBytes, err := s.readTarComponent(tarManifest[0].Config)
+ if err != nil {
+ s.cacheDataResult = err
+ return
+ }
+ var parsedConfig manifest.Schema2Image // There's a lot of info there, but we only really care about layer DiffIDs.
+ if err := json.Unmarshal(configBytes, &parsedConfig); err != nil {
+ s.cacheDataResult = errors.Wrapf(err, "Error decoding tar config %s", tarManifest[0].Config)
+ return
+ }
- knownLayers, err := s.prepareLayerData(&tarManifest[0], &parsedConfig)
- if err != nil {
- return err
- }
+ knownLayers, err := s.prepareLayerData(&tarManifest[0], &parsedConfig)
+ if err != nil {
+ s.cacheDataResult = err
+ return
+ }
- // Success; commit.
- s.tarManifest = &tarManifest[0]
- s.configBytes = configBytes
- s.configDigest = digest.FromBytes(configBytes)
- s.orderedDiffIDList = parsedConfig.RootFS.DiffIDs
- s.knownLayers = knownLayers
- return nil
+ // Success; commit.
+ s.tarManifest = &tarManifest[0]
+ s.configBytes = configBytes
+ s.configDigest = digest.FromBytes(configBytes)
+ s.orderedDiffIDList = parsedConfig.RootFS.DiffIDs
+ s.knownLayers = knownLayers
+ })
+ return s.cacheDataResult
}
// loadTarManifest loads and decodes the manifest.json.
@@ -399,7 +405,7 @@ func (r uncompressedReadCloser) Close() error {
// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently.
func (s *Source) HasThreadSafeGetBlob() bool {
- return false
+ return true
}
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
diff --git a/vendor/github.com/containers/image/ostree/ostree_src.go b/vendor/github.com/containers/image/ostree/ostree_src.go
index df432c9f3..35d852139 100644
--- a/vendor/github.com/containers/image/ostree/ostree_src.go
+++ b/vendor/github.com/containers/image/ostree/ostree_src.go
@@ -17,7 +17,7 @@ import (
"github.com/containers/image/types"
"github.com/containers/storage/pkg/ioutils"
"github.com/klauspost/pgzip"
- "github.com/opencontainers/go-digest"
+ digest "github.com/opencontainers/go-digest"
glib "github.com/ostreedev/ostree-go/pkg/glibobject"
"github.com/pkg/errors"
"github.com/vbatts/tar-split/tar/asm"
@@ -313,24 +313,19 @@ func (s *ostreeImageSource) GetBlob(ctx context.Context, info types.BlobInfo, ca
if err != nil {
return nil, 0, err
}
- defer mfz.Close()
metaUnpacker := storage.NewJSONUnpacker(mfz)
getter, err := newOSTreePathFileGetter(s.repo, branch)
if err != nil {
+ mfz.Close()
return nil, 0, err
}
ots := asm.NewOutputTarStream(getter, metaUnpacker)
- pipeReader, pipeWriter := io.Pipe()
- go func() {
- io.Copy(pipeWriter, ots)
- pipeWriter.Close()
- }()
-
- rc := ioutils.NewReadCloserWrapper(pipeReader, func() error {
+ rc := ioutils.NewReadCloserWrapper(ots, func() error {
getter.Close()
+ mfz.Close()
return ots.Close()
})
return rc, layerSize, nil
diff --git a/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go b/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go
index 9e3e9cfe1..3d0bb0df2 100644
--- a/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go
+++ b/vendor/github.com/containers/image/pkg/sysregistriesv2/system_registries_v2.go
@@ -53,20 +53,23 @@ type Registry struct {
Prefix string `toml:"prefix"`
}
-// backwards compatability to sysregistries v1
-type v1TOMLregistries struct {
+// V1TOMLregistries is for backwards compatibility to sysregistries v1
+type V1TOMLregistries struct {
Registries []string `toml:"registries"`
}
+// V1TOMLConfig is for backwards compatibility to sysregistries v1
+type V1TOMLConfig struct {
+ Search V1TOMLregistries `toml:"search"`
+ Insecure V1TOMLregistries `toml:"insecure"`
+ Block V1TOMLregistries `toml:"block"`
+}
+
// tomlConfig is the data type used to unmarshal the toml config.
type tomlConfig struct {
Registries []Registry `toml:"registry"`
// backwards compatability to sysregistries v1
- V1Registries struct {
- Search v1TOMLregistries `toml:"search"`
- Insecure v1TOMLregistries `toml:"insecure"`
- Block v1TOMLregistries `toml:"block"`
- } `toml:"registries"`
+ V1TOMLConfig `toml:"registries"`
}
// InvalidRegistries represents an invalid registry configurations. An example
@@ -129,21 +132,21 @@ func getV1Registries(config *tomlConfig) ([]Registry, error) {
// Note: config.V1Registries.Search needs to be processed first to ensure registryOrder is populated in the right order
// if one of the search registries is also in one of the other lists.
- for _, search := range config.V1Registries.Search.Registries {
+ for _, search := range config.V1TOMLConfig.Search.Registries {
reg, err := getRegistry(search)
if err != nil {
return nil, err
}
reg.Search = true
}
- for _, blocked := range config.V1Registries.Block.Registries {
+ for _, blocked := range config.V1TOMLConfig.Block.Registries {
reg, err := getRegistry(blocked)
if err != nil {
return nil, err
}
reg.Blocked = true
}
- for _, insecure := range config.V1Registries.Insecure.Registries {
+ for _, insecure := range config.V1TOMLConfig.Insecure.Registries {
reg, err := getRegistry(insecure)
if err != nil {
return nil, err
diff --git a/vendor/github.com/containers/image/storage/storage_image.go b/vendor/github.com/containers/image/storage/storage_image.go
index b53fbdf6e..67dc6142b 100644
--- a/vendor/github.com/containers/image/storage/storage_image.go
+++ b/vendor/github.com/containers/image/storage/storage_image.go
@@ -14,6 +14,7 @@ import (
"sync"
"sync/atomic"
+ "github.com/containers/image/docker/reference"
"github.com/containers/image/image"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/manifest"
@@ -70,6 +71,13 @@ type storageImageCloser struct {
size int64
}
+// manifestBigDataKey returns a key suitable for recording a manifest with the specified digest using storage.Store.ImageBigData and related functions.
+// If a specific manifest digest is explicitly requested by the user, the key retruned function should be used preferably;
+// for compatibility, if a manifest is not available under this key, check also storage.ImageDigestBigDataKey
+func manifestBigDataKey(digest digest.Digest) string {
+ return storage.ImageDigestManifestBigDataNamePrefix + "-" + digest.String()
+}
+
// newImageSource sets up an image for reading.
func newImageSource(imageRef storageReference) (*storageImageSource, error) {
// First, locate the image.
@@ -177,12 +185,29 @@ func (s *storageImageSource) GetManifest(ctx context.Context, instanceDigest *di
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.image.ID, storage.ImageDigestBigDataKey)
- if err != nil {
- return nil, "", err
+ // The manifest is stored as a big data item.
+ // Prefer the manifest corresponding to the user-specified digest, if available.
+ if s.imageRef.named != nil {
+ if digested, ok := s.imageRef.named.(reference.Digested); ok {
+ key := manifestBigDataKey(digested.Digest())
+ blob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, key)
+ if err != nil && !os.IsNotExist(err) { // os.IsNotExist is true if the image exists but there is no data corresponding to key
+ return nil, "", err
+ }
+ if err == nil {
+ s.cachedManifest = blob
+ }
+ }
+ }
+ // If the user did not specify a digest, or this is an old image stored before manifestBigDataKey was introduced, use the default manifest.
+ // Note that the manifest may not match the expected digest, and that is likely to fail eventually, e.g. in c/image/image/UnparsedImage.Manifest().
+ if len(s.cachedManifest) == 0 {
+ cachedBlob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, storage.ImageDigestBigDataKey)
+ if err != nil {
+ return nil, "", err
+ }
+ s.cachedManifest = cachedBlob
}
- s.cachedManifest = cachedBlob
}
return s.cachedManifest, manifest.GuessMIMEType(s.cachedManifest), err
}
@@ -660,6 +685,7 @@ func (s *storageImageDestination) Commit(ctx context.Context) error {
}
lastLayer = layer.ID
}
+
// 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{}
@@ -667,9 +693,6 @@ func (s *storageImageDestination) Commit(ctx context.Context) error {
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 == "" {
@@ -735,8 +758,20 @@ func (s *storageImageDestination) Commit(ctx context.Context) error {
}
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.
+ // Save the manifest. Allow looking it up by digest by using the key convention defined by the Store.
+ // Record the manifest twice: using a digest-specific key to allow references to that specific digest instance,
+ // and using storage.ImageDigestBigDataKey for future users that don’t specify any digest and for compatibility with older readers.
+ manifestDigest, err := manifest.Digest(s.manifest)
+ if err != nil {
+ return errors.Wrapf(err, "error computing manifest digest")
+ }
+ if err := s.imageRef.transport.store.SetImageBigData(img.ID, manifestBigDataKey(manifestDigest), 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
+ }
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)
@@ -788,9 +823,21 @@ func (s *storageImageDestination) SupportedManifestMIMETypes() []string {
}
// PutManifest writes the manifest to the destination.
-func (s *storageImageDestination) PutManifest(ctx context.Context, manifest []byte) error {
- s.manifest = make([]byte, len(manifest))
- copy(s.manifest, manifest)
+func (s *storageImageDestination) PutManifest(ctx context.Context, manifestBlob []byte) error {
+ if s.imageRef.named != nil {
+ if digested, ok := s.imageRef.named.(reference.Digested); ok {
+ matches, err := manifest.MatchesDigest(manifestBlob, digested.Digest())
+ if err != nil {
+ return err
+ }
+ if !matches {
+ return fmt.Errorf("Manifest does not match expected digest %s", digested.Digest())
+ }
+ }
+ }
+
+ s.manifest = make([]byte, len(manifestBlob))
+ copy(s.manifest, manifestBlob)
return nil
}
diff --git a/vendor/github.com/containers/image/storage/storage_reference.go b/vendor/github.com/containers/image/storage/storage_reference.go
index 73306b972..c046d9f22 100644
--- a/vendor/github.com/containers/image/storage/storage_reference.go
+++ b/vendor/github.com/containers/image/storage/storage_reference.go
@@ -55,7 +55,7 @@ func imageMatchesRepo(image *storage.Image, ref reference.Named) bool {
// one present with the same name or ID, and return the image.
func (s *storageReference) resolveImage() (*storage.Image, error) {
var loadedImage *storage.Image
- if s.id == "" {
+ if s.id == "" && s.named != nil {
// Look for an image that has the expanded reference name as an explicit Name value.
image, err := s.transport.store.Image(s.named.String())
if image != nil && err == nil {
@@ -69,7 +69,7 @@ func (s *storageReference) resolveImage() (*storage.Image, error) {
// 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(digested.Digest())
- if images != nil && err == nil {
+ if err == nil && len(images) > 0 {
for _, image := range images {
if imageMatchesRepo(image, s.named) {
loadedImage = image
@@ -97,6 +97,24 @@ func (s *storageReference) resolveImage() (*storage.Image, error) {
return nil, ErrNoSuchImage
}
}
+ // Default to having the image digest that we hand back match the most recently
+ // added manifest...
+ if digest, ok := loadedImage.BigDataDigests[storage.ImageDigestBigDataKey]; ok {
+ loadedImage.Digest = digest
+ }
+ // ... unless the named reference says otherwise, and it matches one of the digests
+ // in the image. For those cases, set the Digest field to that value, for the
+ // sake of older consumers that don't know there's a whole list in there now.
+ if s.named != nil {
+ if digested, ok := s.named.(reference.Digested); ok {
+ for _, digest := range loadedImage.Digests {
+ if digest == digested.Digest() {
+ loadedImage.Digest = digest
+ break
+ }
+ }
+ }
+ }
return loadedImage, nil
}
diff --git a/vendor/github.com/containers/image/storage/storage_transport.go b/vendor/github.com/containers/image/storage/storage_transport.go
index b53c389bd..02d2f5c08 100644
--- a/vendor/github.com/containers/image/storage/storage_transport.go
+++ b/vendor/github.com/containers/image/storage/storage_transport.go
@@ -284,11 +284,6 @@ func (s storageTransport) GetStoreImage(store storage.Store, ref types.ImageRefe
}
}
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
diff --git a/vendor/github.com/containers/image/version/version.go b/vendor/github.com/containers/image/version/version.go
index 6644bcff3..10075992d 100644
--- a/vendor/github.com/containers/image/version/version.go
+++ b/vendor/github.com/containers/image/version/version.go
@@ -8,7 +8,7 @@ const (
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 1
// VersionPatch is for backwards-compatible bug fixes
- VersionPatch = 0
+ VersionPatch = 5
// VersionDev indicates development branch. Releases will be empty string.
VersionDev = "-dev"
diff --git a/vendor/github.com/containers/storage/images.go b/vendor/github.com/containers/storage/images.go
index d99842534..fa4a7c43b 100644
--- a/vendor/github.com/containers/storage/images.go
+++ b/vendor/github.com/containers/storage/images.go
@@ -5,8 +5,10 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "strings"
"time"
+ "github.com/containers/image/manifest"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/stringid"
"github.com/containers/storage/pkg/truncindex"
@@ -15,9 +17,13 @@ import (
)
const (
- // ImageDigestBigDataKey is the name of the big data item whose
- // contents we consider useful for computing a "digest" of the
- // image, by which we can locate the image later.
+ // ImageDigestManifestBigDataNamePrefix is a prefix of big data item
+ // names which we consider to be manifests, used for computing a
+ // "digest" value for the image as a whole, by which we can locate the
+ // image later.
+ ImageDigestManifestBigDataNamePrefix = "manifest"
+ // ImageDigestBigDataKey is provided for compatibility with older
+ // versions of the image library. It will be removed in the future.
ImageDigestBigDataKey = "manifest"
)
@@ -27,12 +33,19 @@ type Image struct {
// value which was generated by the library.
ID string `json:"id"`
- // Digest is a digest value that we can use to locate the image.
+ // Digest is a digest value that we can use to locate the image, if one
+ // was specified at creation-time.
Digest digest.Digest `json:"digest,omitempty"`
+ // Digests is a list of digest values of the image's manifests, and
+ // possibly a manually-specified value, that we can use to locate the
+ // image. If Digest is set, its value is also in this list.
+ Digests []digest.Digest `json:"-"`
+
// 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
- // unique among images.
+ // unique among images, and are often the text representation of tagged
+ // or canonical references.
Names []string `json:"names,omitempty"`
// TopLayer is the ID of the topmost layer of the image itself, if the
@@ -92,8 +105,10 @@ type ROImageStore interface {
// Images returns a slice enumerating the known images.
Images() ([]Image, error)
- // Images returns a slice enumerating the images which have a big data
- // item with the name ImageDigestBigDataKey and the specified digest.
+ // ByDigest returns a slice enumerating the images which have either an
+ // explicitly-set digest, or a big data item with a name that starts
+ // with ImageDigestManifestBigDataNamePrefix, which matches the
+ // specified digest.
ByDigest(d digest.Digest) ([]*Image, error)
}
@@ -111,7 +126,8 @@ type ImageStore interface {
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
- // supplied values.
+ // supplied values. The values are expected to be valid normalized
+ // named image references.
SetNames(id string, names []string) error
// Delete removes the record of the image.
@@ -135,6 +151,7 @@ func copyImage(i *Image) *Image {
return &Image{
ID: i.ID,
Digest: i.Digest,
+ Digests: copyDigestSlice(i.Digests),
Names: copyStringSlice(i.Names),
TopLayer: i.TopLayer,
MappedTopLayers: copyStringSlice(i.MappedTopLayers),
@@ -147,6 +164,17 @@ func copyImage(i *Image) *Image {
}
}
+func copyImageSlice(slice []*Image) []*Image {
+ if len(slice) > 0 {
+ cp := make([]*Image, len(slice))
+ for i := range slice {
+ cp[i] = copyImage(slice[i])
+ }
+ return cp
+ }
+ return nil
+}
+
func (r *imageStore) Images() ([]Image, error) {
images := make([]Image, len(r.images))
for i := range r.images {
@@ -167,6 +195,46 @@ func (r *imageStore) datapath(id, key string) string {
return filepath.Join(r.datadir(id), makeBigDataBaseName(key))
}
+// bigDataNameIsManifest determines if a big data item with the specified name
+// is considered to be representative of the image, in that its digest can be
+// said to also be the image's digest. Currently, if its name is, or begins
+// with, "manifest", we say that it is.
+func bigDataNameIsManifest(name string) bool {
+ return strings.HasPrefix(name, ImageDigestManifestBigDataNamePrefix)
+}
+
+// recomputeDigests takes a fixed digest and a name-to-digest map and builds a
+// list of the unique values that would identify the image.
+func (image *Image) recomputeDigests() error {
+ validDigests := make([]digest.Digest, 0, len(image.BigDataDigests)+1)
+ digests := make(map[digest.Digest]struct{})
+ if image.Digest != "" {
+ if err := image.Digest.Validate(); err != nil {
+ return errors.Wrapf(err, "error validating image digest %q", string(image.Digest))
+ }
+ digests[image.Digest] = struct{}{}
+ validDigests = append(validDigests, image.Digest)
+ }
+ for name, digest := range image.BigDataDigests {
+ if !bigDataNameIsManifest(name) {
+ continue
+ }
+ if digest.Validate() != nil {
+ return errors.Wrapf(digest.Validate(), "error validating digest %q for big data item %q", string(digest), name)
+ }
+ // Deduplicate the digest values.
+ if _, known := digests[digest]; !known {
+ digests[digest] = struct{}{}
+ validDigests = append(validDigests, digest)
+ }
+ }
+ if image.Digest == "" && len(validDigests) > 0 {
+ image.Digest = validDigests[0]
+ }
+ image.Digests = validDigests
+ return nil
+}
+
func (r *imageStore) Load() error {
shouldSave := false
rpath := r.imagespath()
@@ -189,17 +257,18 @@ func (r *imageStore) Load() error {
r.removeName(conflict, name)
shouldSave = true
}
- names[name] = images[n]
}
- // Implicit digest
- if digest, ok := image.BigDataDigests[ImageDigestBigDataKey]; ok {
- digests[digest] = append(digests[digest], images[n])
+ // Compute the digest list.
+ err = image.recomputeDigests()
+ if err != nil {
+ return errors.Wrapf(err, "error computing digests for image with ID %q (%v)", image.ID, image.Names)
}
- // 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])
+ for _, name := range image.Names {
+ names[name] = image
+ }
+ for _, digest := range image.Digests {
+ list := digests[digest]
+ digests[digest] = append(list, image)
}
}
}
@@ -333,12 +402,12 @@ func (r *imageStore) Create(id string, names []string, layer, metadata string, c
}
}
if _, idInUse := r.byid[id]; idInUse {
- return nil, ErrDuplicateID
+ return nil, errors.Wrapf(ErrDuplicateID, "an image with ID %q already exists", id)
}
names = dedupeNames(names)
for _, name := range names {
- if _, nameInUse := r.byname[name]; nameInUse {
- return nil, ErrDuplicateName
+ if image, nameInUse := r.byname[name]; nameInUse {
+ return nil, errors.Wrapf(ErrDuplicateName, "image name %q is already associated with image %q", name, image.ID)
}
}
if created.IsZero() {
@@ -348,6 +417,7 @@ func (r *imageStore) Create(id string, names []string, layer, metadata string, c
image = &Image{
ID: id,
Digest: searchableDigest,
+ Digests: nil,
Names: names,
TopLayer: layer,
Metadata: metadata,
@@ -357,16 +427,20 @@ func (r *imageStore) Create(id string, names []string, layer, metadata string, c
Created: created,
Flags: make(map[string]interface{}),
}
+ err := image.recomputeDigests()
+ if err != nil {
+ return nil, errors.Wrapf(err, "error validating digests for new image")
+ }
r.images = append(r.images, image)
r.idindex.Add(id)
r.byid[id] = image
- if searchableDigest != "" {
- list := r.bydigest[searchableDigest]
- r.bydigest[searchableDigest] = append(list, image)
- }
for _, name := range names {
r.byname[name] = image
}
+ for _, digest := range image.Digests {
+ list := r.bydigest[digest]
+ r.bydigest[digest] = append(list, image)
+ }
err = r.Save()
image = copyImage(image)
}
@@ -444,6 +518,14 @@ func (r *imageStore) Delete(id string) error {
for _, name := range image.Names {
delete(r.byname, name)
}
+ for _, digest := range image.Digests {
+ prunedList := imageSliceWithoutValue(r.bydigest[digest], image)
+ if len(prunedList) == 0 {
+ delete(r.bydigest, digest)
+ } else {
+ r.bydigest[digest] = prunedList
+ }
+ }
if toDeleteIndex != -1 {
// delete the image at toDeleteIndex
if toDeleteIndex == len(r.images)-1 {
@@ -452,28 +534,6 @@ func (r *imageStore) Delete(id string) error {
r.images = append(r.images[:toDeleteIndex], r.images[toDeleteIndex+1:]...)
}
}
- if digest, ok := image.BigDataDigests[ImageDigestBigDataKey]; ok {
- // remove the image from the digest-based index
- if list, ok := r.bydigest[digest]; ok {
- prunedList := imageSliceWithoutValue(list, image)
- if len(prunedList) == 0 {
- delete(r.bydigest, digest)
- } else {
- r.bydigest[digest] = prunedList
- }
- }
- }
- 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 {
return err
}
@@ -504,7 +564,7 @@ func (r *imageStore) Exists(id string) bool {
func (r *imageStore) ByDigest(d digest.Digest) ([]*Image, error) {
if images, ok := r.bydigest[d]; ok {
- return images, nil
+ return copyImageSlice(images), nil
}
return nil, ErrImageUnknown
}
@@ -606,10 +666,19 @@ func (r *imageStore) SetBigData(id, key string, data []byte) error {
if !ok {
return ErrImageUnknown
}
- if err := os.MkdirAll(r.datadir(image.ID), 0700); err != nil {
+ err := os.MkdirAll(r.datadir(image.ID), 0700)
+ if err != nil {
return err
}
- err := ioutils.AtomicWriteFile(r.datapath(image.ID, key), data, 0600)
+ var newDigest digest.Digest
+ if bigDataNameIsManifest(key) {
+ if newDigest, err = manifest.Digest(data); err != nil {
+ return errors.Wrapf(err, "error digesting manifest")
+ }
+ } else {
+ newDigest = digest.Canonical.FromBytes(data)
+ }
+ err = ioutils.AtomicWriteFile(r.datapath(image.ID, key), data, 0600)
if err == nil {
save := false
if image.BigDataSizes == nil {
@@ -621,7 +690,6 @@ func (r *imageStore) SetBigData(id, key string, data []byte) error {
image.BigDataDigests = make(map[string]digest.Digest)
}
oldDigest, digestOk := image.BigDataDigests[key]
- newDigest := digest.Canonical.FromBytes(data)
image.BigDataDigests[key] = newDigest
if !sizeOk || oldSize != image.BigDataSizes[key] || !digestOk || oldDigest != newDigest {
save = true
@@ -637,20 +705,21 @@ func (r *imageStore) SetBigData(id, key string, data []byte) error {
image.BigDataNames = append(image.BigDataNames, key)
save = true
}
- if key == ImageDigestBigDataKey {
- if oldDigest != "" && oldDigest != newDigest && oldDigest != image.Digest {
- // remove the image from the list of images in the digest-based
- // 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 {
- prunedList := imageSliceWithoutValue(list, image)
- if len(prunedList) == 0 {
- delete(r.bydigest, oldDigest)
- } else {
- r.bydigest[oldDigest] = prunedList
- }
+ for _, oldDigest := range image.Digests {
+ // remove the image from the list of images in the digest-based index
+ if list, ok := r.bydigest[oldDigest]; ok {
+ prunedList := imageSliceWithoutValue(list, image)
+ if len(prunedList) == 0 {
+ delete(r.bydigest, oldDigest)
+ } else {
+ r.bydigest[oldDigest] = prunedList
}
}
+ }
+ if err = image.recomputeDigests(); err != nil {
+ return errors.Wrapf(err, "error loading recomputing image digest information for %s", image.ID)
+ }
+ for _, newDigest := range image.Digests {
// add the image to the list of images in the digest-based index which
// corresponds to the new digest for this item, unless it's already there
list := r.bydigest[newDigest]
diff --git a/vendor/github.com/containers/storage/images_ffjson.go b/vendor/github.com/containers/storage/images_ffjson.go
index 539acfe93..6b40ebd59 100644
--- a/vendor/github.com/containers/storage/images_ffjson.go
+++ b/vendor/github.com/containers/storage/images_ffjson.go
@@ -1,5 +1,5 @@
// Code generated by ffjson <https://github.com/pquerna/ffjson>. DO NOT EDIT.
-// source: ./images.go
+// source: images.go
package storage
diff --git a/vendor/github.com/containers/storage/pkg/config/config.go b/vendor/github.com/containers/storage/pkg/config/config.go
new file mode 100644
index 000000000..bdb5fbcb8
--- /dev/null
+++ b/vendor/github.com/containers/storage/pkg/config/config.go
@@ -0,0 +1,96 @@
+package config
+
+// ThinpoolOptionsConfig represents the "storage.options.thinpool"
+// TOML config table.
+type ThinpoolOptionsConfig struct {
+ // AutoExtendPercent determines the amount by which pool needs to be
+ // grown. This is specified in terms of % of pool size. So a value of
+ // 20 means that when threshold is hit, pool will be grown by 20% of
+ // existing pool size.
+ AutoExtendPercent string `toml:"autoextend_percent"`
+
+ // AutoExtendThreshold determines the pool extension threshold in terms
+ // of percentage of pool size. For example, if threshold is 60, that
+ // means when pool is 60% full, threshold has been hit.
+ AutoExtendThreshold string `toml:"autoextend_threshold"`
+
+ // BaseSize specifies the size to use when creating the base device,
+ // which limits the size of images and containers.
+ BaseSize string `toml:"basesize"`
+
+ // BlockSize specifies a custom blocksize to use for the thin pool.
+ BlockSize string `toml:"blocksize"`
+
+ // DirectLvmDevice specifies a custom block storage device to use for
+ // the thin pool.
+ DirectLvmDevice string `toml:"directlvm_device"`
+
+ // DirectLvmDeviceForcewipes device even if device already has a
+ // filesystem
+ DirectLvmDeviceForce string `toml:"directlvm_device_force"`
+
+ // Fs specifies the filesystem type to use for the base device.
+ Fs string `toml:"fs"`
+
+ // log_level sets the log level of devicemapper.
+ LogLevel string `toml:"log_level"`
+
+ // MinFreeSpace specifies the min free space percent in a thin pool
+ // require for new device creation to
+ MinFreeSpace string `toml:"min_free_space"`
+
+ // MkfsArg specifies extra mkfs arguments to be used when creating the
+ // basedevice.
+ MkfsArg string `toml:"mkfsarg"`
+
+ // MountOpt specifies extra mount options used when mounting the thin
+ // devices.
+ MountOpt string `toml:"mountopt"`
+
+ // UseDeferredDeletion marks device for deferred deletion
+ UseDeferredDeletion string `toml:"use_deferred_deletion"`
+
+ // UseDeferredRemoval marks device for deferred removal
+ UseDeferredRemoval string `toml:"use_deferred_removal"`
+
+ // XfsNoSpaceMaxRetriesFreeSpace specifies the maximum number of
+ // retries XFS should attempt to complete IO when ENOSPC (no space)
+ // error is returned by underlying storage device.
+ XfsNoSpaceMaxRetries string `toml:"xfs_nospace_max_retries"`
+}
+
+// OptionsConfig represents the "storage.options" TOML config table.
+type OptionsConfig struct {
+ // AdditionalImagesStores is the location of additional read/only
+ // Image stores. Usually used to access Networked File System
+ // for shared image content
+ AdditionalImageStores []string `toml:"additionalimagestores"`
+
+ // Size
+ Size string `toml:"size"`
+
+ // RemapUIDs is a list of default UID mappings to use for layers.
+ RemapUIDs string `toml:"remap-uids"`
+ // RemapGIDs is a list of default GID mappings to use for layers.
+ RemapGIDs string `toml:"remap-gids"`
+
+ // RemapUser is the name of one or more entries in /etc/subuid which
+ // should be used to set up default UID mappings.
+ RemapUser string `toml:"remap-user"`
+ // RemapGroup is the name of one or more entries in /etc/subgid which
+ // should be used to set up default GID mappings.
+ RemapGroup string `toml:"remap-group"`
+ // Thinpool container options to be handed to thinpool drivers
+ Thinpool struct{ ThinpoolOptionsConfig } `toml:"thinpool"`
+ // OSTree repository
+ OstreeRepo string `toml:"ostree_repo"`
+
+ // Do not create a bind mount on the storage home
+ SkipMountHome string `toml:"skip_mount_home"`
+
+ // Alternative program to use for the mount of the file system
+ MountProgram string `toml:"mount_program"`
+
+ // MountOpt specifies extra mount options used when mounting
+ MountOpt string `toml:"mountopt"`
+}
diff --git a/vendor/github.com/containers/storage/store.go b/vendor/github.com/containers/storage/store.go
index 3fe305cc1..856c73e51 100644
--- a/vendor/github.com/containers/storage/store.go
+++ b/vendor/github.com/containers/storage/store.go
@@ -18,6 +18,7 @@ import (
"github.com/BurntSushi/toml"
drivers "github.com/containers/storage/drivers"
"github.com/containers/storage/pkg/archive"
+ "github.com/containers/storage/pkg/config"
"github.com/containers/storage/pkg/directory"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/ioutils"
@@ -842,12 +843,16 @@ func (s *store) PutLayer(id, parent string, names []string, mountLabel string, w
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return nil, -1, err
+ }
}
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, -1, err
+ }
}
if id == "" {
id = stringid.GenerateRandomID()
@@ -870,7 +875,9 @@ func (s *store) PutLayer(id, parent string, names []string, mountLabel string, w
lstore.Lock()
defer lstore.Unlock()
if modified, err := lstore.Modified(); modified || err != nil {
- lstore.Load()
+ if err = lstore.Load(); err != nil {
+ return nil, -1, err
+ }
}
}
if l, err := lstore.Get(parent); err == nil && l != nil {
@@ -946,7 +953,9 @@ func (s *store) CreateImage(id string, names []string, layer, metadata string, o
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
ilayer, err = store.Get(layer)
if err == nil {
@@ -966,7 +975,9 @@ func (s *store) CreateImage(id string, names []string, layer, metadata string, o
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err = ristore.Load(); err != nil {
+ return nil, err
+ }
}
creationDate := time.Now().UTC()
@@ -1004,7 +1015,9 @@ func (s *store) imageTopLayerForMapping(image *Image, ristore ROImageStore, crea
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
}
// Walk the top layer list.
@@ -1125,14 +1138,18 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return nil, err
+ }
}
var cimage *Image
for _, store := range append([]ROImageStore{istore}, istores...) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
cimage, err = store.Get(image)
if err == nil {
@@ -1162,7 +1179,9 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return nil, err
+ }
}
if !options.HostUIDMapping && len(options.UIDMap) == 0 {
uidMap = s.uidMap
@@ -1222,7 +1241,9 @@ func (s *store) CreateContainer(id string, names []string, image, layer, metadat
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, err
+ }
}
options.IDMappingOptions = IDMappingOptions{
HostUIDMapping: len(options.UIDMap) == 0,
@@ -1254,17 +1275,23 @@ func (s *store) SetMetadata(id, metadata string) error {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return err
+ }
}
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err := ristore.Load(); err != nil {
+ return err
+ }
}
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return err
+ }
}
if rlstore.Exists(id) {
@@ -1292,7 +1319,9 @@ func (s *store) Metadata(id string) (string, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return "", err
+ }
}
if store.Exists(id) {
return store.Metadata(id)
@@ -1311,7 +1340,9 @@ func (s *store) Metadata(id string) (string, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return "", err
+ }
}
if store.Exists(id) {
return store.Metadata(id)
@@ -1325,7 +1356,9 @@ func (s *store) Metadata(id string) (string, error) {
cstore.Lock()
defer cstore.Unlock()
if modified, err := cstore.Modified(); modified || err != nil {
- cstore.Load()
+ if err = cstore.Load(); err != nil {
+ return "", err
+ }
}
if cstore.Exists(id) {
return cstore.Metadata(id)
@@ -1346,7 +1379,9 @@ func (s *store) ListImageBigData(id string) ([]string, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
bigDataNames, err := store.BigDataNames(id)
if err == nil {
@@ -1369,7 +1404,9 @@ func (s *store) ImageBigDataSize(id, key string) (int64, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return -1, err
+ }
}
size, err := store.BigDataSize(id, key)
if err == nil {
@@ -1393,7 +1430,9 @@ func (s *store) ImageBigDataDigest(id, key string) (digest.Digest, error) {
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err = ristore.Load(); err != nil {
+ return "", nil
+ }
}
d, err := ristore.BigDataDigest(id, key)
if err == nil && d.Validate() == nil {
@@ -1416,7 +1455,9 @@ func (s *store) ImageBigData(id, key string) ([]byte, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
data, err := store.BigData(id, key)
if err == nil {
@@ -1435,7 +1476,9 @@ func (s *store) SetImageBigData(id, key string, data []byte) error {
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err = ristore.Load(); err != nil {
+ return nil
+ }
}
return ristore.SetBigData(id, key, data)
@@ -1456,7 +1499,9 @@ func (s *store) ImageSize(id string) (int64, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return -1, err
+ }
}
}
@@ -1475,7 +1520,9 @@ func (s *store) ImageSize(id string) (int64, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return -1, err
+ }
}
if image, err = store.Get(id); err == nil {
imageStore = store
@@ -1560,7 +1607,9 @@ func (s *store) ContainerSize(id string) (int64, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return -1, err
+ }
}
}
@@ -1582,7 +1631,9 @@ func (s *store) ContainerSize(id string) (int64, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return -1, err
+ }
}
// Read the container record.
@@ -1644,7 +1695,9 @@ func (s *store) ListContainerBigData(id string) ([]string, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, err
+ }
}
return rcstore.BigDataNames(id)
@@ -1658,7 +1711,9 @@ func (s *store) ContainerBigDataSize(id, key string) (int64, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return -1, err
+ }
}
return rcstore.BigDataSize(id, key)
}
@@ -1671,7 +1726,9 @@ func (s *store) ContainerBigDataDigest(id, key string) (digest.Digest, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return "", err
+ }
}
return rcstore.BigDataDigest(id, key)
}
@@ -1684,7 +1741,9 @@ func (s *store) ContainerBigData(id, key string) ([]byte, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, err
+ }
}
return rcstore.BigData(id, key)
}
@@ -1697,7 +1756,9 @@ func (s *store) SetContainerBigData(id, key string, data []byte) error {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return err
+ }
}
return rcstore.SetBigData(id, key, data)
}
@@ -1715,7 +1776,9 @@ func (s *store) Exists(id string) bool {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return false
+ }
}
if store.Exists(id) {
return true
@@ -1734,7 +1797,9 @@ func (s *store) Exists(id string) bool {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return false
+ }
}
if store.Exists(id) {
return true
@@ -1748,7 +1813,9 @@ func (s *store) Exists(id string) bool {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return false
+ }
}
if rcstore.Exists(id) {
return true
@@ -1779,7 +1846,9 @@ func (s *store) SetNames(id string, names []string) error {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return err
+ }
}
if rlstore.Exists(id) {
return rlstore.SetNames(id, deduped)
@@ -1792,7 +1861,9 @@ func (s *store) SetNames(id string, names []string) error {
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err = ristore.Load(); err != nil {
+ return err
+ }
}
if ristore.Exists(id) {
return ristore.SetNames(id, deduped)
@@ -1805,7 +1876,9 @@ func (s *store) SetNames(id string, names []string) error {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return err
+ }
}
if rcstore.Exists(id) {
return rcstore.SetNames(id, deduped)
@@ -1826,7 +1899,9 @@ func (s *store) Names(id string) ([]string, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
if l, err := store.Get(id); l != nil && err == nil {
return l.Names, nil
@@ -1845,7 +1920,9 @@ func (s *store) Names(id string) ([]string, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
if i, err := store.Get(id); i != nil && err == nil {
return i.Names, nil
@@ -1859,7 +1936,9 @@ func (s *store) Names(id string) ([]string, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, err
+ }
}
if c, err := rcstore.Get(id); c != nil && err == nil {
return c.Names, nil
@@ -1880,7 +1959,9 @@ func (s *store) Lookup(name string) (string, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return "", err
+ }
}
if l, err := store.Get(name); l != nil && err == nil {
return l.ID, nil
@@ -1899,7 +1980,9 @@ func (s *store) Lookup(name string) (string, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return "", err
+ }
}
if i, err := store.Get(name); i != nil && err == nil {
return i.ID, nil
@@ -1913,7 +1996,9 @@ func (s *store) Lookup(name string) (string, error) {
cstore.Lock()
defer cstore.Unlock()
if modified, err := cstore.Modified(); modified || err != nil {
- cstore.Load()
+ if err = cstore.Load(); err != nil {
+ return "", err
+ }
}
if c, err := cstore.Get(name); c != nil && err == nil {
return c.ID, nil
@@ -1939,17 +2024,23 @@ func (s *store) DeleteLayer(id string) error {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return err
+ }
}
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err = ristore.Load(); err != nil {
+ return err
+ }
}
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return err
+ }
}
if rlstore.Exists(id) {
@@ -2005,17 +2096,23 @@ func (s *store) DeleteImage(id string, commit bool) (layers []string, err error)
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return nil, err
+ }
}
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err = ristore.Load(); err != nil {
+ return nil, err
+ }
}
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, err
+ }
}
layersToRemove := []string{}
if ristore.Exists(id) {
@@ -2137,17 +2234,23 @@ func (s *store) DeleteContainer(id string) error {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return err
+ }
}
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err = ristore.Load(); err != nil {
+ return err
+ }
}
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return err
+ }
}
if rcstore.Exists(id) {
@@ -2192,17 +2295,23 @@ func (s *store) Delete(id string) error {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return err
+ }
}
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err := ristore.Load(); err != nil {
+ return err
+ }
}
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return err
+ }
}
if rcstore.Exists(id) {
@@ -2254,17 +2363,23 @@ func (s *store) Wipe() error {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return err
+ }
}
ristore.Lock()
defer ristore.Unlock()
if modified, err := ristore.Modified(); modified || err != nil {
- ristore.Load()
+ if err = ristore.Load(); err != nil {
+ return err
+ }
}
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return err
+ }
}
if err = rcstore.Wipe(); err != nil {
@@ -2306,7 +2421,9 @@ func (s *store) Mount(id, mountLabel string) (string, error) {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return "", err
+ }
}
if rlstore.Exists(id) {
options := drivers.MountOpts{
@@ -2331,7 +2448,9 @@ func (s *store) Mounted(id string) (int, error) {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return 0, err
+ }
}
return rlstore.Mounted(id)
@@ -2348,7 +2467,9 @@ func (s *store) Unmount(id string, force bool) (bool, error) {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return false, err
+ }
}
if rlstore.Exists(id) {
return rlstore.Unmount(id, force)
@@ -2369,7 +2490,9 @@ func (s *store) Changes(from, to string) ([]archive.Change, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
if store.Exists(to) {
return store.Changes(from, to)
@@ -2391,7 +2514,9 @@ func (s *store) DiffSize(from, to string) (int64, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return -1, err
+ }
}
if store.Exists(to) {
return store.DiffSize(from, to)
@@ -2412,7 +2537,9 @@ func (s *store) Diff(from, to string, options *DiffOptions) (io.ReadCloser, erro
for _, store := range append([]ROLayerStore{lstore}, lstores...) {
store.Lock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
if store.Exists(to) {
rc, err := store.Diff(from, to, options)
@@ -2440,7 +2567,9 @@ func (s *store) ApplyDiff(to string, diff io.Reader) (int64, error) {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return -1, err
+ }
}
if rlstore.Exists(to) {
return rlstore.ApplyDiff(to, diff)
@@ -2463,7 +2592,9 @@ func (s *store) layersByMappedDigest(m func(ROLayerStore, digest.Digest) ([]Laye
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
storeLayers, err := m(store, d)
if err != nil {
@@ -2507,7 +2638,9 @@ func (s *store) LayerSize(id string) (int64, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return -1, err
+ }
}
if store.Exists(id) {
return store.Size(id)
@@ -2524,7 +2657,9 @@ func (s *store) LayerParentOwners(id string) ([]int, []int, error) {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return nil, nil, err
+ }
}
if rlstore.Exists(id) {
return rlstore.ParentOwners(id)
@@ -2544,12 +2679,16 @@ func (s *store) ContainerParentOwners(id string) ([]int, []int, error) {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return nil, nil, err
+ }
}
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, nil, err
+ }
}
container, err := rcstore.Get(id)
if err != nil {
@@ -2577,7 +2716,9 @@ func (s *store) Layers() ([]Layer, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
storeLayers, err := store.Layers()
if err != nil {
@@ -2603,7 +2744,9 @@ func (s *store) Images() ([]Image, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
storeImages, err := store.Images()
if err != nil {
@@ -2623,7 +2766,9 @@ func (s *store) Containers() ([]Container, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, err
+ }
}
return rcstore.Containers()
@@ -2642,7 +2787,9 @@ func (s *store) Layer(id string) (*Layer, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
layer, err := store.Get(id)
if err == nil {
@@ -2665,7 +2812,9 @@ func (s *store) Image(id string) (*Image, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
image, err := store.Get(id)
if err == nil {
@@ -2695,7 +2844,9 @@ func (s *store) ImagesByTopLayer(id string) ([]*Image, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
imageList, err := store.Images()
if err != nil {
@@ -2726,7 +2877,9 @@ func (s *store) ImagesByDigest(d digest.Digest) ([]*Image, error) {
store.Lock()
defer store.Unlock()
if modified, err := store.Modified(); modified || err != nil {
- store.Load()
+ if err = store.Load(); err != nil {
+ return nil, err
+ }
}
imageList, err := store.ByDigest(d)
if err != nil && err != ErrImageUnknown {
@@ -2745,7 +2898,9 @@ func (s *store) Container(id string) (*Container, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, err
+ }
}
return rcstore.Get(id)
@@ -2759,7 +2914,9 @@ func (s *store) ContainerLayerID(id string) (string, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return "", err
+ }
}
container, err := rcstore.Get(id)
if err != nil {
@@ -2780,7 +2937,9 @@ func (s *store) ContainerByLayer(id string) (*Container, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return nil, err
+ }
}
containerList, err := rcstore.Containers()
if err != nil {
@@ -2803,7 +2962,9 @@ func (s *store) ContainerDirectory(id string) (string, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return "", err
+ }
}
id, err = rcstore.Lookup(id)
@@ -2828,7 +2989,9 @@ func (s *store) ContainerRunDirectory(id string) (string, error) {
rcstore.Lock()
defer rcstore.Unlock()
if modified, err := rcstore.Modified(); modified || err != nil {
- rcstore.Load()
+ if err = rcstore.Load(); err != nil {
+ return "", err
+ }
}
id, err = rcstore.Lookup(id)
@@ -2899,7 +3062,9 @@ func (s *store) Shutdown(force bool) ([]string, error) {
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
- rlstore.Load()
+ if err = rlstore.Load(); err != nil {
+ return nil, err
+ }
}
layers, err := rlstore.Layers()
@@ -2992,6 +3157,15 @@ func copyStringDigestMap(m map[string]digest.Digest) map[string]digest.Digest {
return ret
}
+func copyDigestSlice(slice []digest.Digest) []digest.Digest {
+ if len(slice) == 0 {
+ return nil
+ }
+ ret := make([]digest.Digest, len(slice))
+ copy(ret, slice)
+ return ret
+}
+
// copyStringInterfaceMap still forces us to assume that the interface{} is
// a non-pointer scalar value
func copyStringInterfaceMap(m map[string]interface{}) map[string]interface{} {
@@ -3005,108 +3179,13 @@ func copyStringInterfaceMap(m map[string]interface{}) map[string]interface{} {
// DefaultConfigFile path to the system wide storage.conf file
const DefaultConfigFile = "/etc/containers/storage.conf"
-// ThinpoolOptionsConfig represents the "storage.options.thinpool"
-// TOML config table.
-type ThinpoolOptionsConfig struct {
- // AutoExtendPercent determines the amount by which pool needs to be
- // grown. This is specified in terms of % of pool size. So a value of
- // 20 means that when threshold is hit, pool will be grown by 20% of
- // existing pool size.
- AutoExtendPercent string `toml:"autoextend_percent"`
-
- // AutoExtendThreshold determines the pool extension threshold in terms
- // of percentage of pool size. For example, if threshold is 60, that
- // means when pool is 60% full, threshold has been hit.
- AutoExtendThreshold string `toml:"autoextend_threshold"`
-
- // BaseSize specifies the size to use when creating the base device,
- // which limits the size of images and containers.
- BaseSize string `toml:"basesize"`
-
- // BlockSize specifies a custom blocksize to use for the thin pool.
- BlockSize string `toml:"blocksize"`
-
- // DirectLvmDevice specifies a custom block storage device to use for
- // the thin pool.
- DirectLvmDevice string `toml:"directlvm_device"`
-
- // DirectLvmDeviceForcewipes device even if device already has a
- // filesystem
- DirectLvmDeviceForce string `toml:"directlvm_device_force"`
-
- // Fs specifies the filesystem type to use for the base device.
- Fs string `toml:"fs"`
-
- // log_level sets the log level of devicemapper.
- LogLevel string `toml:"log_level"`
-
- // MinFreeSpace specifies the min free space percent in a thin pool
- // require for new device creation to
- MinFreeSpace string `toml:"min_free_space"`
-
- // MkfsArg specifies extra mkfs arguments to be used when creating the
- // basedevice.
- MkfsArg string `toml:"mkfsarg"`
-
- // MountOpt specifies extra mount options used when mounting the thin
- // devices.
- MountOpt string `toml:"mountopt"`
-
- // UseDeferredDeletion marks device for deferred deletion
- UseDeferredDeletion string `toml:"use_deferred_deletion"`
-
- // UseDeferredRemoval marks device for deferred removal
- UseDeferredRemoval string `toml:"use_deferred_removal"`
-
- // XfsNoSpaceMaxRetriesFreeSpace specifies the maximum number of
- // retries XFS should attempt to complete IO when ENOSPC (no space)
- // error is returned by underlying storage device.
- XfsNoSpaceMaxRetries string `toml:"xfs_nospace_max_retries"`
-}
-
-// OptionsConfig represents the "storage.options" TOML config table.
-type OptionsConfig struct {
- // AdditionalImagesStores is the location of additional read/only
- // Image stores. Usually used to access Networked File System
- // for shared image content
- AdditionalImageStores []string `toml:"additionalimagestores"`
-
- // Size
- Size string `toml:"size"`
-
- // RemapUIDs is a list of default UID mappings to use for layers.
- RemapUIDs string `toml:"remap-uids"`
- // RemapGIDs is a list of default GID mappings to use for layers.
- RemapGIDs string `toml:"remap-gids"`
-
- // RemapUser is the name of one or more entries in /etc/subuid which
- // should be used to set up default UID mappings.
- RemapUser string `toml:"remap-user"`
- // RemapGroup is the name of one or more entries in /etc/subgid which
- // should be used to set up default GID mappings.
- RemapGroup string `toml:"remap-group"`
- // Thinpool container options to be handed to thinpool drivers
- Thinpool struct{ ThinpoolOptionsConfig } `toml:"thinpool"`
- // OSTree repository
- OstreeRepo string `toml:"ostree_repo"`
-
- // Do not create a bind mount on the storage home
- SkipMountHome string `toml:"skip_mount_home"`
-
- // Alternative program to use for the mount of the file system
- MountProgram string `toml:"mount_program"`
-
- // MountOpt specifies extra mount options used when mounting
- MountOpt string `toml:"mountopt"`
-}
-
// TOML-friendly explicit tables used for conversions.
type tomlConfig struct {
Storage struct {
- Driver string `toml:"driver"`
- RunRoot string `toml:"runroot"`
- GraphRoot string `toml:"graphroot"`
- Options struct{ OptionsConfig } `toml:"options"`
+ Driver string `toml:"driver"`
+ RunRoot string `toml:"runroot"`
+ GraphRoot string `toml:"graphroot"`
+ Options struct{ config.OptionsConfig } `toml:"options"`
} `toml:"storage"`
}
diff --git a/vendor/github.com/containers/storage/vendor.conf b/vendor/github.com/containers/storage/vendor.conf
index 04af9010b..c143b049d 100644
--- a/vendor/github.com/containers/storage/vendor.conf
+++ b/vendor/github.com/containers/storage/vendor.conf
@@ -1,12 +1,18 @@
github.com/BurntSushi/toml master
github.com/Microsoft/go-winio 307e919c663683a9000576fdc855acaf9534c165
github.com/Microsoft/hcsshim a8d9cc56cbce765a7eebdf4792e6ceceeff3edb8
+github.com/containers/image master
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
github.com/docker/go-units 0dadbb0345b35ec7ef35e228dabb8de89a65bf52
+github.com/docker/libtrust master
+github.com/klauspost/compress v1.4.1
+github.com/klauspost/cpuid v1.2.0
+github.com/klauspost/pgzip v1.2.1
github.com/mattn/go-shellwords 753a2322a99f87c0eff284980e77f53041555bc6
github.com/mistifyio/go-zfs c0224de804d438efd11ea6e52ada8014537d6062
github.com/opencontainers/go-digest master
+github.com/opencontainers/image-spec master
github.com/opencontainers/runc 6c22e77604689db8725fa866f0f2ec0b3e8c3a07
github.com/opencontainers/selinux v1.1
github.com/ostreedev/ostree-go master
@@ -23,6 +29,3 @@ golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6
golang.org/x/sys 07c182904dbd53199946ba614a412c61d3c548f5
gotest.tools master
github.com/google/go-cmp master
-github.com/klauspost/pgzip v1.2.1
-github.com/klauspost/compress v1.4.1
-github.com/klauspost/cpuid v1.2.0