mirror of
https://github.com/containers/podman.git
synced 2025-07-03 17:27:18 +08:00
'podman cp' copy between host and container
Signed-off-by: Qi Wang <qiwan@redhat.com>
This commit is contained in:
@ -20,3 +20,7 @@ type BuildValues struct {
|
|||||||
*buildahcli.NameSpaceResults
|
*buildahcli.NameSpaceResults
|
||||||
*buildahcli.LayerResults
|
*buildahcli.LayerResults
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CpValues struct {
|
||||||
|
PodmanCommand
|
||||||
|
}
|
||||||
|
257
cmd/podman/cp.go
Normal file
257
cmd/podman/cp.go
Normal file
@ -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
|
||||||
|
}
|
@ -16,7 +16,7 @@
|
|||||||
| [podman-container-refresh(1)](/docs/podman-container-refresh.1.md) | Refresh all containers state in database ||
|
| [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-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-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-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 |[](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)|
|
| [podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem |[](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)|
|
||||||
| [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container
|
| [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container
|
||||||
|
@ -3,20 +3,70 @@
|
|||||||
## NAME
|
## NAME
|
||||||
podman\-cp - Copy files/folders between a container and the local filesystem
|
podman\-cp - Copy files/folders between a container and the local filesystem
|
||||||
|
|
||||||
## Description
|
## SYNOPSIS
|
||||||
We chose not to implement the `cp` feature in `podman` even though the upstream Docker
|
**podman cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH**
|
||||||
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
|
## 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.
|
then just cp.
|
||||||
|
|
||||||
If a user wants to copy contents out of a container or into a container, they
|
If a user wants to copy contents out of a container or into a container, they can execute a few simple commands.
|
||||||
can execute a few simple commands.
|
|
||||||
|
|
||||||
You can copy from the container's file system to the local machine or the
|
You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container.
|
||||||
reverse, from the local filesystem to the container.
|
|
||||||
|
|
||||||
If you want to copy the /etc/foobar directory out of a container and onto /tmp
|
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:
|
||||||
on the host, you could execute the following commands:
|
|
||||||
|
|
||||||
mnt=$(podman mount CONTAINERID)
|
mnt=$(podman mount CONTAINERID)
|
||||||
cp -R ${mnt}/etc/foobar /tmp
|
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
|
standard linux tools for moving files into and out of containers, not just
|
||||||
the cp command.
|
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
|
## SEE ALSO
|
||||||
podman(1), podman-mount(1), podman-umount(1)
|
podman(1), podman-mount(1), podman-umount(1)
|
||||||
|
115
test/e2e/cp_test.go
Normal file
115
test/e2e/cp_test.go
Normal file
@ -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"))
|
||||||
|
})
|
||||||
|
})
|
@ -37,11 +37,11 @@ There are other equivalents for these tools
|
|||||||
|
|
||||||
| Existing Step | `Podman` (and friends) |
|
| 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 build` | [`podman build`](./docs/podman-build.1.md) |
|
||||||
| `docker commit` | [`podman commit`](./docs/podman-commit.1.md) |
|
| `docker commit` | [`podman commit`](./docs/podman-commit.1.md) |
|
||||||
| `docker container`|[`podman container`](./docs/podman-container.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 create` | [`podman create`](./docs/podman-create.1.md) |
|
||||||
| `docker diff` | [`podman diff`](./docs/podman-diff.1.md) |
|
| `docker diff` | [`podman diff`](./docs/podman-diff.1.md) |
|
||||||
| `docker export` | [`podman export`](./docs/podman-export.1.md) |
|
| `docker export` | [`podman export`](./docs/podman-export.1.md) |
|
||||||
|
Reference in New Issue
Block a user