mirror of
https://github.com/containers/podman.git
synced 2025-05-20 00:27:03 +08:00
support container to container copy
Implement container to container copy. Previously data could only be copied from/to the host. Fixes: #7370 Co-authored-by: Mehul Arora <aroram18@mcmaster.ca> Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:

committed by
Valentin Rothberg

parent
b6c279be22
commit
6fe03b25ab
@ -82,7 +82,9 @@ func cp(cmd *cobra.Command, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sourceContainerStr) > 0 {
|
if len(sourceContainerStr) > 0 && len(destContainerStr) > 0 {
|
||||||
|
return copyContainerToContainer(sourceContainerStr, sourcePath, destContainerStr, destPath)
|
||||||
|
} else if len(sourceContainerStr) > 0 {
|
||||||
return copyFromContainer(sourceContainerStr, sourcePath, destPath)
|
return copyFromContainer(sourceContainerStr, sourcePath, destPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +117,110 @@ func doCopy(funcA func() error, funcB func() error) error {
|
|||||||
return errorhandling.JoinErrors(copyErrors)
|
return errorhandling.JoinErrors(copyErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyContainerToContainer(sourceContainer string, sourcePath string, destContainer string, destPath string) error {
|
||||||
|
if err := containerMustExist(sourceContainer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := containerMustExist(destContainer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceContainerInfo, err := registry.ContainerEngine().ContainerStat(registry.GetContext(), sourceContainer, sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "%q could not be found on container %s", sourcePath, sourceContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var destContainerBaseName string
|
||||||
|
destContainerInfo, destContainerInfoErr := registry.ContainerEngine().ContainerStat(registry.GetContext(), destContainer, destPath)
|
||||||
|
if destContainerInfoErr != nil {
|
||||||
|
if strings.HasSuffix(destPath, "/") {
|
||||||
|
return errors.Wrapf(destContainerInfoErr, "%q could not be found on container %s", destPath, destContainer)
|
||||||
|
}
|
||||||
|
// NOTE: containerInfo may actually be set. That happens when
|
||||||
|
// the container path is a symlink into nirvana. In that case,
|
||||||
|
// we must use the symlinked path instead.
|
||||||
|
path := destPath
|
||||||
|
if destContainerInfo != nil {
|
||||||
|
destContainerBaseName = filepath.Base(destContainerInfo.LinkTarget)
|
||||||
|
path = destContainerInfo.LinkTarget
|
||||||
|
} else {
|
||||||
|
destContainerBaseName = filepath.Base(destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
parentDir, err := containerParentDir(destContainer, path)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not determine parent dir of %q on container %s", path, destContainer)
|
||||||
|
}
|
||||||
|
destContainerInfo, err = registry.ContainerEngine().ContainerStat(registry.GetContext(), destContainer, parentDir)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "%q could not be found on container %s", destPath, destContainer)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the specified path exists on the container, we must use
|
||||||
|
// its base path as it may have changed due to symlink
|
||||||
|
// evaluations.
|
||||||
|
destContainerBaseName = filepath.Base(destContainerInfo.LinkTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceContainerInfo.IsDir && !destContainerInfo.IsDir {
|
||||||
|
return errors.New("destination must be a directory when copying a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceContainerTarget, destContainerTarget := sourceContainerInfo.LinkTarget, destContainerInfo.LinkTarget
|
||||||
|
if !destContainerInfo.IsDir {
|
||||||
|
destContainerTarget = filepath.Dir(destPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we copy a directory via the "." notation and the container path
|
||||||
|
// does not exist, we need to make sure that the destination on the
|
||||||
|
// container gets created; otherwise the contents of the source
|
||||||
|
// directory will be written to the destination's parent directory.
|
||||||
|
//
|
||||||
|
// Hence, whenever "." is the source and the destination does not
|
||||||
|
// exist, we copy the source's parent and let the copier package create
|
||||||
|
// the destination via the Rename option.
|
||||||
|
if destContainerInfoErr != nil && sourceContainerInfo.IsDir && strings.HasSuffix(sourcePath, ".") {
|
||||||
|
sourceContainerTarget = filepath.Dir(sourceContainerTarget)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
|
sourceContainerCopy := func() error {
|
||||||
|
defer writer.Close()
|
||||||
|
copyFunc, err := registry.ContainerEngine().ContainerCopyToArchive(registry.GetContext(), sourceContainer, sourceContainerTarget, writer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := copyFunc(); err != nil {
|
||||||
|
return errors.Wrap(err, "error copying from container")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
destContainerCopy := func() error {
|
||||||
|
defer reader.Close()
|
||||||
|
|
||||||
|
copyOptions := entities.CopyOptions{Chown: chown}
|
||||||
|
if (!sourceContainerInfo.IsDir && !destContainerInfo.IsDir) || destContainerInfoErr != nil {
|
||||||
|
// If we're having a file-to-file copy, make sure to
|
||||||
|
// rename accordingly.
|
||||||
|
copyOptions.Rename = map[string]string{filepath.Base(sourceContainerTarget): destContainerBaseName}
|
||||||
|
}
|
||||||
|
|
||||||
|
copyFunc, err := registry.ContainerEngine().ContainerCopyFromArchive(registry.GetContext(), destContainer, destContainerTarget, reader, copyOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := copyFunc(); err != nil {
|
||||||
|
return errors.Wrap(err, "error copying to container")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return doCopy(sourceContainerCopy, destContainerCopy)
|
||||||
|
}
|
||||||
|
|
||||||
// copyFromContainer copies from the containerPath on the container to hostPath.
|
// copyFromContainer copies from the containerPath on the container to hostPath.
|
||||||
func copyFromContainer(container string, containerPath string, hostPath string) error {
|
func copyFromContainer(container string, containerPath string, hostPath string) error {
|
||||||
if err := containerMustExist(container); err != nil {
|
if err := containerMustExist(container); err != nil {
|
||||||
|
@ -9,10 +9,10 @@ podman\-cp - Copy files/folders between a container and the local filesystem
|
|||||||
**podman container cp** [*options*] [*container*:]*src_path* [*container*:]*dest_path*
|
**podman container cp** [*options*] [*container*:]*src_path* [*container*:]*dest_path*
|
||||||
|
|
||||||
## DESCRIPTION
|
## DESCRIPTION
|
||||||
Copy the contents of **src_path** to the **dest_path**. You can copy from the container's filesystem to the local machine or the reverse, from the local filesystem to the container.
|
Copy the contents of **src_path** to the **dest_path**. You can copy from the container's filesystem to the local machine or the reverse, from the local filesystem to the container or between two containers.
|
||||||
If `-` is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT.
|
If `-` is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT.
|
||||||
|
|
||||||
The CONTAINER can be a running or stopped container. The **src_path** or **dest_path** can be a file or directory.
|
The containers can be a running or stopped. 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 (i.e., `/`).
|
The **podman cp** command assumes container paths are relative to the container's root directory (i.e., `/`).
|
||||||
|
|
||||||
@ -70,10 +70,9 @@ The default is *true*.
|
|||||||
|
|
||||||
## ALTERNATIVES
|
## ALTERNATIVES
|
||||||
|
|
||||||
Podman has much stronger capabilities than just `podman cp` to achieve copy files between host and container.
|
Podman has much stronger capabilities than just `podman cp` to achieve copying files between the host and containers.
|
||||||
|
|
||||||
Using standard podman-mount and podman-umount takes advantage of the entire linux tool chain, rather
|
Using standard podman-mount and podman-umount takes advantage of the entire linux tool chain, rather than just cp.
|
||||||
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.
|
||||||
|
|
||||||
@ -113,6 +112,8 @@ podman cp containerID:/myapp/ /myapp/
|
|||||||
|
|
||||||
podman cp containerID:/home/myuser/. /home/myuser/
|
podman cp containerID:/home/myuser/. /home/myuser/
|
||||||
|
|
||||||
|
podman cp containerA:/myapp containerB:/yourapp
|
||||||
|
|
||||||
podman cp - containerID:/myfiles.tar.gz < myfiles.tar.gz
|
podman cp - containerID:/myfiles.tar.gz < myfiles.tar.gz
|
||||||
|
|
||||||
## SEE ALSO
|
## SEE ALSO
|
||||||
|
@ -840,7 +840,7 @@ func (c *Container) ShouldRestart(ctx context.Context) bool {
|
|||||||
|
|
||||||
// CopyFromArchive copies the contents from the specified tarStream to path
|
// CopyFromArchive copies the contents from the specified tarStream to path
|
||||||
// *inside* the container.
|
// *inside* the container.
|
||||||
func (c *Container) CopyFromArchive(ctx context.Context, containerPath string, chown bool, tarStream io.Reader) (func() error, error) {
|
func (c *Container) CopyFromArchive(ctx context.Context, containerPath string, chown bool, rename map[string]string, tarStream io.Reader) (func() error, error) {
|
||||||
if !c.batched {
|
if !c.batched {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
@ -850,7 +850,7 @@ func (c *Container) CopyFromArchive(ctx context.Context, containerPath string, c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.copyFromArchive(ctx, containerPath, chown, tarStream)
|
return c.copyFromArchive(ctx, containerPath, chown, rename, tarStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyToArchive copies the contents from the specified path *inside* the
|
// CopyToArchive copies the contents from the specified path *inside* the
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Container) copyFromArchive(ctx context.Context, path string, chown bool, reader io.Reader) (func() error, error) {
|
func (c *Container) copyFromArchive(ctx context.Context, path string, chown bool, rename map[string]string, reader io.Reader) (func() error, error) {
|
||||||
var (
|
var (
|
||||||
mountPoint string
|
mountPoint string
|
||||||
resolvedRoot string
|
resolvedRoot string
|
||||||
@ -89,6 +89,7 @@ func (c *Container) copyFromArchive(ctx context.Context, path string, chown bool
|
|||||||
GIDMap: c.config.IDMappings.GIDMap,
|
GIDMap: c.config.IDMappings.GIDMap,
|
||||||
ChownDirs: idPair,
|
ChownDirs: idPair,
|
||||||
ChownFiles: idPair,
|
ChownFiles: idPair,
|
||||||
|
Rename: rename,
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.joinMountAndExec(ctx,
|
return c.joinMountAndExec(ctx,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package compat
|
package compat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -93,8 +94,9 @@ func handleHeadAndGet(w http.ResponseWriter, r *http.Request, decoder *schema.De
|
|||||||
|
|
||||||
func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
|
func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
|
||||||
query := struct {
|
query := struct {
|
||||||
Path string `schema:"path"`
|
Path string `schema:"path"`
|
||||||
Chown bool `schema:"copyUIDGID"`
|
Chown bool `schema:"copyUIDGID"`
|
||||||
|
Rename string `schema:"rename"`
|
||||||
// TODO handle params below
|
// TODO handle params below
|
||||||
NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"`
|
NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"`
|
||||||
}{
|
}{
|
||||||
@ -107,10 +109,19 @@ func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rename map[string]string
|
||||||
|
if query.Rename != "" {
|
||||||
|
if err := json.Unmarshal([]byte(query.Rename), &rename); err != nil {
|
||||||
|
utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
containerName := utils.GetName(r)
|
containerName := utils.GetName(r)
|
||||||
containerEngine := abi.ContainerEngine{Libpod: runtime}
|
containerEngine := abi.ContainerEngine{Libpod: runtime}
|
||||||
|
|
||||||
copyFunc, err := containerEngine.ContainerCopyFromArchive(r.Context(), containerName, query.Path, r.Body, entities.CopyOptions{Chown: query.Chown})
|
copyOptions := entities.CopyOptions{Chown: query.Chown, Rename: rename}
|
||||||
|
copyFunc, err := containerEngine.ContainerCopyFromArchive(r.Context(), containerName, query.Path, r.Body, copyOptions)
|
||||||
if errors.Cause(err) == define.ErrNoSuchCtr || os.IsNotExist(err) {
|
if errors.Cause(err) == define.ErrNoSuchCtr || os.IsNotExist(err) {
|
||||||
// 404 is returned for an absent container and path. The
|
// 404 is returned for an absent container and path. The
|
||||||
// clients must deal with it accordingly.
|
// clients must deal with it accordingly.
|
||||||
|
@ -151,6 +151,10 @@ func (s *APIServer) registerArchiveHandlers(r *mux.Router) error {
|
|||||||
// type: string
|
// type: string
|
||||||
// description: Path to a directory in the container to extract
|
// description: Path to a directory in the container to extract
|
||||||
// required: true
|
// required: true
|
||||||
|
// - in: query
|
||||||
|
// name: rename
|
||||||
|
// type: string
|
||||||
|
// description: JSON encoded map[string]string to translate paths
|
||||||
// responses:
|
// responses:
|
||||||
// 200:
|
// 200:
|
||||||
// description: no error
|
// description: no error
|
||||||
|
@ -263,4 +263,6 @@ type CopyOptions struct {
|
|||||||
// If used with CopyFromArchive and set to true it will change ownership of files from the source tar archive
|
// If used with CopyFromArchive and set to true it will change ownership of files from the source tar archive
|
||||||
// to the primary uid/gid of the target container.
|
// to the primary uid/gid of the target container.
|
||||||
Chown *bool `schema:"copyUIDGID"`
|
Chown *bool `schema:"copyUIDGID"`
|
||||||
|
// Map to translate path names.
|
||||||
|
Rename map[string]string
|
||||||
}
|
}
|
||||||
|
@ -35,3 +35,19 @@ func (o *CopyOptions) GetChown() bool {
|
|||||||
}
|
}
|
||||||
return *o.Chown
|
return *o.Chown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithRename
|
||||||
|
func (o *CopyOptions) WithRename(value map[string]string) *CopyOptions {
|
||||||
|
v := value
|
||||||
|
o.Rename = v
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRename
|
||||||
|
func (o *CopyOptions) GetRename() map[string]string {
|
||||||
|
var rename map[string]string
|
||||||
|
if o.Rename == nil {
|
||||||
|
return rename
|
||||||
|
}
|
||||||
|
return o.Rename
|
||||||
|
}
|
||||||
|
@ -18,18 +18,6 @@ func ParseSourceAndDestination(source, destination string) (string, string, stri
|
|||||||
sourceContainer, sourcePath := parseUserInput(source)
|
sourceContainer, sourcePath := parseUserInput(source)
|
||||||
destContainer, destPath := parseUserInput(destination)
|
destContainer, destPath := parseUserInput(destination)
|
||||||
|
|
||||||
numContainers := 0
|
|
||||||
if len(sourceContainer) > 0 {
|
|
||||||
numContainers++
|
|
||||||
}
|
|
||||||
if len(destContainer) > 0 {
|
|
||||||
numContainers++
|
|
||||||
}
|
|
||||||
|
|
||||||
if numContainers != 1 {
|
|
||||||
return "", "", "", "", errors.Errorf("invalid arguments %q, %q: exactly 1 container expected but %d specified", source, destination, numContainers)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sourcePath) == 0 || len(destPath) == 0 {
|
if len(sourcePath) == 0 || len(destPath) == 0 {
|
||||||
return "", "", "", "", errors.Errorf("invalid arguments %q, %q: you must specify paths", source, destination)
|
return "", "", "", "", errors.Errorf("invalid arguments %q, %q: you must specify paths", source, destination)
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,8 @@ type CopyOptions struct {
|
|||||||
// it will change ownership of files from the source tar archive
|
// it will change ownership of files from the source tar archive
|
||||||
// to the primary uid/gid of the destination container.
|
// to the primary uid/gid of the destination container.
|
||||||
Chown bool
|
Chown bool
|
||||||
|
// Map to translate path names.
|
||||||
|
Rename map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommitReport struct {
|
type CommitReport struct {
|
||||||
|
@ -12,7 +12,7 @@ func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrI
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return container.CopyFromArchive(ctx, containerPath, options.Chown, reader)
|
return container.CopyFromArchive(ctx, containerPath, options.Chown, options.Rename, reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, containerPath string, writer io.Writer) (entities.ContainerCopyFunc, error) {
|
func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, containerPath string, writer io.Writer) (entities.ContainerCopyFunc, error) {
|
||||||
|
@ -853,7 +853,8 @@ func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrID string, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options entities.CopyOptions) (entities.ContainerCopyFunc, error) {
|
func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options entities.CopyOptions) (entities.ContainerCopyFunc, error) {
|
||||||
return containers.CopyFromArchiveWithOptions(ic.ClientCtx, nameOrID, path, reader, new(containers.CopyOptions).WithChown(options.Chown))
|
copyOptions := new(containers.CopyOptions).WithChown(options.Chown).WithRename(options.Rename)
|
||||||
|
return containers.CopyFromArchiveWithOptions(ic.ClientCtx, nameOrID, path, reader, copyOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (entities.ContainerCopyFunc, error) {
|
func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (entities.ContainerCopyFunc, error) {
|
||||||
|
@ -226,6 +226,96 @@ load helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@test "podman cp file from container to container" {
|
||||||
|
# Create 3 files with random content in the container.
|
||||||
|
local -a randomcontent=(
|
||||||
|
random-0-$(random_string 10)
|
||||||
|
random-1-$(random_string 15)
|
||||||
|
random-2-$(random_string 20)
|
||||||
|
)
|
||||||
|
run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
|
||||||
|
run_podman exec cpcontainer sh -c "echo ${randomcontent[0]} > /tmp/containerfile"
|
||||||
|
run_podman exec cpcontainer sh -c "echo ${randomcontent[0]} > /tmp/dotfile."
|
||||||
|
run_podman exec cpcontainer sh -c "echo ${randomcontent[1]} > /srv/containerfile1"
|
||||||
|
run_podman exec cpcontainer sh -c "mkdir /srv/subdir; echo ${randomcontent[2]} > /srv/subdir/containerfile2"
|
||||||
|
|
||||||
|
# Commit the image for testing non-running containers
|
||||||
|
run_podman commit -q cpcontainer
|
||||||
|
cpimage="$output"
|
||||||
|
|
||||||
|
# format is: <id> | <source arg to cp> | <destination arg (appended to $srcdir) to cp> | <full dest path (appended to $srcdir)> | <test name>
|
||||||
|
tests="
|
||||||
|
0 | /tmp/containerfile | | /containerfile | /
|
||||||
|
0 | /tmp/dotfile. | | /dotfile. | /
|
||||||
|
0 | /tmp/containerfile | / | /containerfile | /
|
||||||
|
0 | /tmp/containerfile | /. | /containerfile | /.
|
||||||
|
0 | /tmp/containerfile | /newfile | /newfile | /newfile
|
||||||
|
1 | containerfile1 | / | /containerfile1 | copy from workdir (rel path) to /
|
||||||
|
2 | subdir/containerfile2 | / | /containerfile2 | copy from workdir/subdir (rel path) to /
|
||||||
|
"
|
||||||
|
|
||||||
|
# From RUNNING container
|
||||||
|
while read id src dest dest_fullname description; do
|
||||||
|
# dest may be "''" for empty table cells
|
||||||
|
if [[ $dest == "''" ]];then
|
||||||
|
unset dest
|
||||||
|
fi
|
||||||
|
|
||||||
|
# To RUNNING container
|
||||||
|
run_podman run -d $IMAGE sleep infinity
|
||||||
|
destcontainer="$output"
|
||||||
|
run_podman cp cpcontainer:$src $destcontainer:"/$dest"
|
||||||
|
run_podman exec $destcontainer cat "/$dest_fullname"
|
||||||
|
is "$output" "${randomcontent[$id]}" "$description (cp ctr:$src to /$dest)"
|
||||||
|
run_podman kill $destcontainer
|
||||||
|
run_podman rm -f $destcontainer
|
||||||
|
|
||||||
|
# To CREATED container
|
||||||
|
run_podman create $IMAGE sleep infinity
|
||||||
|
destcontainer="$output"
|
||||||
|
run_podman cp cpcontainer:$src $destcontainer:"/$dest"
|
||||||
|
run_podman start $destcontainer
|
||||||
|
run_podman exec $destcontainer cat "/$dest_fullname"
|
||||||
|
is "$output" "${randomcontent[$id]}" "$description (cp ctr:$src to /$dest)"
|
||||||
|
run_podman kill $destcontainer
|
||||||
|
run_podman rm -f $destcontainer
|
||||||
|
done < <(parse_table "$tests")
|
||||||
|
run_podman kill cpcontainer
|
||||||
|
run_podman rm -f cpcontainer
|
||||||
|
|
||||||
|
# From CREATED container
|
||||||
|
run_podman create --name cpcontainer --workdir=/srv $cpimage
|
||||||
|
while read id src dest dest_fullname description; do
|
||||||
|
# dest may be "''" for empty table cells
|
||||||
|
if [[ $dest == "''" ]];then
|
||||||
|
unset dest
|
||||||
|
fi
|
||||||
|
|
||||||
|
# To RUNNING container
|
||||||
|
run_podman run -d $IMAGE sleep infinity
|
||||||
|
destcontainer="$output"
|
||||||
|
run_podman cp cpcontainer:$src $destcontainer:"/$dest"
|
||||||
|
run_podman exec $destcontainer cat "/$dest_fullname"
|
||||||
|
is "$output" "${randomcontent[$id]}" "$description (cp ctr:$src to /$dest)"
|
||||||
|
run_podman kill $destcontainer
|
||||||
|
run_podman rm -f $destcontainer
|
||||||
|
|
||||||
|
# To CREATED container
|
||||||
|
run_podman create $IMAGE sleep infinity
|
||||||
|
destcontainer="$output"
|
||||||
|
run_podman cp cpcontainer:$src $destcontainer:"/$dest"
|
||||||
|
run_podman start $destcontainer
|
||||||
|
run_podman exec $destcontainer cat "/$dest_fullname"
|
||||||
|
is "$output" "${randomcontent[$id]}" "$description (cp ctr:$src to /$dest)"
|
||||||
|
run_podman kill $destcontainer
|
||||||
|
run_podman rm -f $destcontainer
|
||||||
|
done < <(parse_table "$tests")
|
||||||
|
run_podman rm -f cpcontainer
|
||||||
|
|
||||||
|
run_podman rmi -f $cpimage
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "podman cp dir from host to container" {
|
@test "podman cp dir from host to container" {
|
||||||
srcdir=$PODMAN_TMPDIR
|
srcdir=$PODMAN_TMPDIR
|
||||||
mkdir -p $srcdir/dir/sub
|
mkdir -p $srcdir/dir/sub
|
||||||
@ -377,6 +467,110 @@ load helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@test "podman cp dir from container to container" {
|
||||||
|
# Create 2 files with random content in the container.
|
||||||
|
local -a randomcontent=(
|
||||||
|
random-0-$(random_string 10)
|
||||||
|
random-1-$(random_string 15)
|
||||||
|
)
|
||||||
|
run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
|
||||||
|
run_podman exec cpcontainer sh -c "mkdir /srv/subdir; echo ${randomcontent[0]} > /srv/subdir/containerfile0"
|
||||||
|
run_podman exec cpcontainer sh -c "echo ${randomcontent[1]} > /srv/subdir/containerfile1"
|
||||||
|
# "." and "dir/." will copy the contents, so make sure that a dir ending
|
||||||
|
# with dot is treated correctly.
|
||||||
|
run_podman exec cpcontainer sh -c 'mkdir /tmp/subdir.; cp /srv/subdir/* /tmp/subdir./'
|
||||||
|
|
||||||
|
# Commit the image for testing non-running containers
|
||||||
|
run_podman commit -q cpcontainer
|
||||||
|
cpimage="$output"
|
||||||
|
|
||||||
|
# format is: <source arg to cp (appended to /srv)> | <dest> | <full dest path> | <test name>
|
||||||
|
tests="
|
||||||
|
/srv | | /srv/subdir | copy /srv
|
||||||
|
/srv | /newdir | /newdir/subdir | copy /srv to /newdir
|
||||||
|
/srv/ | | /srv/subdir | copy /srv/
|
||||||
|
/srv/. | | /subdir | copy /srv/.
|
||||||
|
/srv/. | /newdir | /newdir/subdir | copy /srv/. to /newdir
|
||||||
|
/srv/subdir/. | | | copy /srv/subdir/.
|
||||||
|
/tmp/subdir. | | /subdir. | copy /tmp/subdir.
|
||||||
|
"
|
||||||
|
|
||||||
|
# From RUNNING container
|
||||||
|
while read src dest dest_fullname description; do
|
||||||
|
if [[ $src == "''" ]];then
|
||||||
|
unset src
|
||||||
|
fi
|
||||||
|
if [[ $dest == "''" ]];then
|
||||||
|
unset dest
|
||||||
|
fi
|
||||||
|
if [[ $dest_fullname == "''" ]];then
|
||||||
|
unset dest_fullname
|
||||||
|
fi
|
||||||
|
|
||||||
|
# To RUNNING container
|
||||||
|
run_podman run -d $IMAGE sleep infinity
|
||||||
|
destcontainer="$output"
|
||||||
|
run_podman cp cpcontainer:$src $destcontainer:"/$dest"
|
||||||
|
run_podman exec $destcontainer cat "/$dest_fullname/containerfile0" "/$dest_fullname/containerfile1"
|
||||||
|
is "$output" "${randomcontent[0]}
|
||||||
|
${randomcontent[1]}" "$description"
|
||||||
|
run_podman kill $destcontainer
|
||||||
|
run_podman rm -f $destcontainer
|
||||||
|
|
||||||
|
# To CREATED container
|
||||||
|
run_podman create $IMAGE sleep infinity
|
||||||
|
destcontainer="$output"
|
||||||
|
run_podman cp cpcontainer:$src $destcontainer:"/$dest"
|
||||||
|
run_podman start $destcontainer
|
||||||
|
run_podman exec $destcontainer cat "/$dest_fullname/containerfile0" "/$dest_fullname/containerfile1"
|
||||||
|
is "$output" "${randomcontent[0]}
|
||||||
|
${randomcontent[1]}" "$description"
|
||||||
|
run_podman kill $destcontainer
|
||||||
|
run_podman rm -f $destcontainer
|
||||||
|
done < <(parse_table "$tests")
|
||||||
|
run_podman kill cpcontainer
|
||||||
|
run_podman rm -f cpcontainer
|
||||||
|
|
||||||
|
# From CREATED container
|
||||||
|
run_podman create --name cpcontainer --workdir=/srv $cpimage
|
||||||
|
while read src dest dest_fullname description; do
|
||||||
|
if [[ $src == "''" ]];then
|
||||||
|
unset src
|
||||||
|
fi
|
||||||
|
if [[ $dest == "''" ]];then
|
||||||
|
unset dest
|
||||||
|
fi
|
||||||
|
if [[ $dest_fullname == "''" ]];then
|
||||||
|
unset dest_fullname
|
||||||
|
fi
|
||||||
|
|
||||||
|
# To RUNNING container
|
||||||
|
run_podman run -d $IMAGE sleep infinity
|
||||||
|
destcontainer="$output"
|
||||||
|
run_podman cp cpcontainer:$src $destcontainer:"/$dest"
|
||||||
|
run_podman exec $destcontainer cat "/$dest_fullname/containerfile0" "/$dest_fullname/containerfile1"
|
||||||
|
is "$output" "${randomcontent[0]}
|
||||||
|
${randomcontent[1]}" "$description"
|
||||||
|
run_podman kill $destcontainer
|
||||||
|
run_podman rm -f $destcontainer
|
||||||
|
|
||||||
|
# To CREATED container
|
||||||
|
run_podman create $IMAGE sleep infinity
|
||||||
|
destcontainer="$output"
|
||||||
|
run_podman start $destcontainer
|
||||||
|
run_podman cp cpcontainer:$src $destcontainer:"/$dest"
|
||||||
|
run_podman exec $destcontainer cat "/$dest_fullname/containerfile0" "/$dest_fullname/containerfile1"
|
||||||
|
is "$output" "${randomcontent[0]}
|
||||||
|
${randomcontent[1]}" "$description"
|
||||||
|
run_podman kill $destcontainer
|
||||||
|
run_podman rm -f $destcontainer
|
||||||
|
done < <(parse_table "$tests")
|
||||||
|
|
||||||
|
run_podman rm -f cpcontainer
|
||||||
|
run_podman rmi -f $cpimage
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "podman cp symlinked directory from container" {
|
@test "podman cp symlinked directory from container" {
|
||||||
destdir=$PODMAN_TMPDIR/cp-weird-symlink
|
destdir=$PODMAN_TMPDIR/cp-weird-symlink
|
||||||
mkdir -p $destdir
|
mkdir -p $destdir
|
||||||
|
Reference in New Issue
Block a user