diff options
62 files changed, 1488 insertions, 641 deletions
@@ -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) @@ -131,6 +131,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func VolumeRemove(options: VolumeRemoveOpts) []string](#VolumeRemove) +[func VolumesPrune() []string, []string](#VolumesPrune) + [func WaitContainer(name: string) int](#WaitContainer) [type BuildInfo](#BuildInfo) @@ -530,7 +532,7 @@ GetVersion returns version and build information of the podman service <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method GetVolumes(args: [[]string](#[]string), all: [bool](https://godoc.org/builtin#bool)) [Volume](#Volume)</div> - +GetVolumes gets slice of the volumes on a remote host ### <a name="HistoryImage"></a>func HistoryImage <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -773,13 +775,13 @@ the image cannot be found in local storage; otherwise it will return a [MoreResp <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method ReceiveFile(path: [string](https://godoc.org/builtin#string), delete: [bool](https://godoc.org/builtin#bool)) [int](https://godoc.org/builtin#int)</div> - +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 ~~~ @@ -856,7 +858,7 @@ search results per registry. <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method SendFile(type: [string](https://godoc.org/builtin#string), length: [int](https://godoc.org/builtin#int)) [string](https://godoc.org/builtin#string)</div> - +Sendfile allows a remote client to send a file to the host ### <a name="StartContainer"></a>func StartContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -956,12 +958,17 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.UnpausePod '{"name": "foo <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method VolumeCreate(options: [VolumeCreateOpts](#VolumeCreateOpts)) [string](https://godoc.org/builtin#string)</div> - +VolumeCreate creates a volume on a remote host ### <a name="VolumeRemove"></a>func VolumeRemove <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> method VolumeRemove(options: [VolumeRemoveOpts](#VolumeRemoveOpts)) [[]string](#[]string)</div> +VolumeRemove removes a volume on a remote host +### <a name="VolumesPrune"></a>func VolumesPrune +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> +method VolumesPrune() [[]string](#[]string), [[]string](#[]string)</div> +VolumesPrune removes unused volumes on the host ### <a name="WaitContainer"></a>func WaitContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> 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 baa49d2af..fa3839a53 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -112,18 +112,6 @@ func getPodSubCommands() []*cobra.Command { } } -// Commands that the local client implements -func getVolumeSubCommands() []*cobra.Command { - return []*cobra.Command{ - _volumeCreateCommand, - _volumeLsCommand, - _volumeRmCommand, - _volumeInspectCommand, - _volumePruneCommand, - } -} - -// Commands that the local client implements func getGenerateSubCommands() []*cobra.Command { return []*cobra.Command{ _containerKubeCommand, diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go index 7bdba1c19..ba0a4d47e 100644 --- a/cmd/podman/commands_remoteclient.go +++ b/cmd/podman/commands_remoteclient.go @@ -32,16 +32,6 @@ func getPodSubCommands() []*cobra.Command { } // commands that only the remoteclient implements -func getVolumeSubCommands() []*cobra.Command { - return []*cobra.Command{ - _volumeCreateCommand, - _volumeRmCommand, - _volumeLsCommand, - _volumeInspectCommand, - } -} - -// commands that only the remoteclient implements func getGenerateSubCommands() []*cobra.Command { return []*cobra.Command{} } 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 f514afe30..7bfb070c7 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -648,9 +648,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/login.go b/cmd/podman/login.go index 5c47b8c65..3eacab54a 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -46,6 +46,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") } @@ -92,8 +93,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) @@ -102,7 +121,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/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 dc6a25c44..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). @@ -1070,15 +1070,24 @@ method ContainerInspectData(name: string) -> (config: string) # development of Podman only and generally should not be used. method ContainerStateData(name: string) -> (config: string) +# Sendfile allows a remote client to send a file to the host method SendFile(type: string, length: int) -> (file_handle: string) + +# ReceiveFile allows the host to send a remote client a file method ReceiveFile(path: string, delete: bool) -> (len: int) +# VolumeCreate creates a volume on a remote host method VolumeCreate(options: VolumeCreateOpts) -> (volumeName: string) +# VolumeRemove removes a volume on a remote host method VolumeRemove(options: VolumeRemoveOpts) -> (volumeNames: []string) +# GetVolumes gets slice of the volumes on a remote host method GetVolumes(args: []string, all: bool) -> (volumes: []Volume) +# VolumesPrune removes unused volumes on the host +method VolumesPrune() -> (prunedNames: []string, prunedErrors: []string) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (id: string) diff --git a/cmd/podman/volume.go b/cmd/podman/volume.go index 36798a19e..8a8664151 100644 --- a/cmd/podman/volume.go +++ b/cmd/podman/volume.go @@ -16,8 +16,16 @@ var volumeCommand = cliconfig.PodmanCommand{ Long: volumeDescription, }, } +var volumeSubcommands = []*cobra.Command{ + _volumeCreateCommand, + _volumeLsCommand, + _volumeRmCommand, + _volumeInspectCommand, + _volumePruneCommand, +} func init() { - volumeCommand.AddCommand(getVolumeSubCommands()...) volumeCommand.SetUsageTemplate(UsageTemplate()) + volumeCommand.AddCommand(volumeSubcommands...) + rootCmd.AddCommand(volumeCommand.Command) } diff --git a/cmd/podman/volume_prune.go b/cmd/podman/volume_prune.go index 74228fa5b..a2205140f 100644 --- a/cmd/podman/volume_prune.go +++ b/cmd/podman/volume_prune.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -44,23 +43,20 @@ func init() { } func volumePrune(runtime *adapter.LocalRuntime, ctx context.Context) error { - var lastError error - - volumes, err := runtime.GetAllVolumes() - if err != nil { - return err + prunedNames, prunedErrors := runtime.PruneVolumes(ctx) + for _, name := range prunedNames { + fmt.Println(name) + } + if len(prunedErrors) == 0 { + return nil } + // Grab the last error + lastError := prunedErrors[len(prunedErrors)-1] + // Remove the last error from the error slice + prunedErrors = prunedErrors[:len(prunedErrors)-1] - for _, vol := range volumes { - err = runtime.RemoveVolume(ctx, vol, false, true) - if err == nil { - fmt.Println(vol.Name()) - } else if err != libpod.ErrVolumeBeingUsed { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "failed to remove volume %q", vol.Name()) - } + for _, err := range prunedErrors { + logrus.Errorf("%q", err) } return lastError } @@ -85,6 +81,5 @@ func volumePruneCmd(c *cliconfig.VolumePruneValues) error { return nil } } - return volumePrune(runtime, getContext()) } 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.go b/libpod/adapter/runtime.go index 3146cf5db..02ef9af07 100644 --- a/libpod/adapter/runtime.go +++ b/libpod/adapter/runtime.go @@ -305,3 +305,8 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti return r.Runtime.Build(ctx, options, dockerfiles...) } + +// PruneVolumes is a wrapper function for libpod PruneVolumes +func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { + return r.Runtime.PruneVolumes(ctx) +} diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go index 66ff5ed51..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 } @@ -642,3 +642,17 @@ func varlinkVolumeToVolume(r *LocalRuntime, volumes []iopodman.Volume) []*Volume } return vols } + +// PruneVolumes removes all unused volumes from the remote system +func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { + var errs []error + prunedNames, prunedErrors, err := iopodman.VolumesPrune().Call(r.Conn) + if err != nil { + return []string{}, []error{err} + } + // We need to transform the string results of the error into actual error types + for _, e := range prunedErrors { + errs = append(errs, errors.New(e)) + } + return prunedNames, errs +} 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/runtime_volume.go b/libpod/runtime_volume.go index 485f64bf1..beae50ac9 100644 --- a/libpod/runtime_volume.go +++ b/libpod/runtime_volume.go @@ -154,3 +154,27 @@ func (r *Runtime) GetAllVolumes() ([]*Volume, error) { return r.state.AllVolumes() } + +// PruneVolumes removes unused volumes from the system +func (r *Runtime) PruneVolumes(ctx context.Context) ([]string, []error) { + var ( + prunedIDs []string + pruneErrors []error + ) + vols, err := r.GetAllVolumes() + if err != nil { + pruneErrors = append(pruneErrors, err) + return nil, pruneErrors + } + + for _, vol := range vols { + if err := r.RemoveVolume(ctx, vol, false, true); err != nil { + if err != ErrVolumeBeingUsed { + pruneErrors = append(pruneErrors, err) + } + continue + } + prunedIDs = append(prunedIDs, vol.Name()) + } + return prunedIDs, pruneErrors +} 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/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index d41b07065..02874d2b1 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -72,3 +72,19 @@ func (i *LibpodAPI) GetVolumes(call iopodman.VarlinkCall, args []string, all boo } return call.ReplyGetVolumes(volumes) } + +// VolumesPrune removes unused images via a varlink call +func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error { + var errs []string + prunedNames, prunedErrors := i.Runtime.PruneVolumes(getContext()) + if len(prunedErrors) == 0 { + return call.ReplyVolumesPrune(prunedNames, []string{}) + } + + // We need to take the errors and capture their strings to go back over + // varlink + for _, e := range prunedErrors { + errs = append(errs, e.Error()) + } + return call.ReplyVolumesPrune(prunedNames, errs) +} 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/test/e2e/volume_create_test.go b/test/e2e/volume_create_test.go index 9e525786e..50ee63f2a 100644 --- a/test/e2e/volume_create_test.go +++ b/test/e2e/volume_create_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( diff --git a/test/e2e/volume_inspect_test.go b/test/e2e/volume_inspect_test.go index aacdbe8be..d0d5a601e 100644 --- a/test/e2e/volume_inspect_test.go +++ b/test/e2e/volume_inspect_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go index d2ee558c1..119d29d9b 100644 --- a/test/e2e/volume_ls_test.go +++ b/test/e2e/volume_ls_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 295b290e4..6a1e7d0e8 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( @@ -50,6 +48,7 @@ var _ = Describe("Podman volume rm", func() { }) It("podman rm with --force flag", func() { + SkipIfRemote() session := podmanTest.Podman([]string{"create", "-v", "myvol:/myvol", ALPINE, "ls"}) cid := session.OutputToString() session.WaitWithDefaultTimeout() 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 |