summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/containers/cp.go49
-rw-r--r--docs/source/markdown/podman-cp.1.md2
-rw-r--r--docs/source/markdown/podman-system-service.1.md4
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rw-r--r--libpod/container_copy_linux.go70
-rw-r--r--libpod/container_stat_linux.go42
-rw-r--r--libpod/runtime_img.go3
-rw-r--r--pkg/api/handlers/libpod/copy.go12
-rw-r--r--test/apiv2/60-auth.at29
-rwxr-xr-xtest/apiv2/test-apiv2115
-rw-r--r--test/e2e/cp_test.go1
-rw-r--r--test/system/065-cp.bats119
-rw-r--r--test/system/120-load.bats7
-rw-r--r--vendor/github.com/containers/buildah/buildah.go2
-rw-r--r--vendor/github.com/containers/buildah/copier/copier.go42
-rw-r--r--vendor/modules.txt2
17 files changed, 390 insertions, 115 deletions
diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go
index 7a62d982c..27aacc6e5 100644
--- a/cmd/podman/containers/cp.go
+++ b/cmd/podman/containers/cp.go
@@ -160,6 +160,25 @@ func copyFromContainer(container string, containerPath string, hostPath string)
}
}
+ // If we copy a directory via the "." notation and the host path does
+ // not exist, we need to make sure that the destination on the host
+ // gets created; otherwise the contents of the source directory will be
+ // written to the destination's parent directory.
+ //
+ // While we could cut it short on the host and do create the directory
+ // ourselves, we would run into problems trying to that the other way
+ // around when copying into a container. Instead, to keep both
+ // implementations symmetrical, we need to massage the code a bit to
+ // let Buildah's copier package create the destination.
+ //
+ // 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.
+ containerTarget := containerInfo.LinkTarget
+ if hostInfoErr != nil && containerInfo.IsDir && strings.HasSuffix(containerTarget, ".") {
+ containerTarget = filepath.Dir(containerTarget)
+ }
+
reader, writer := io.Pipe()
hostCopy := func() error {
defer reader.Close()
@@ -193,10 +212,10 @@ func copyFromContainer(container string, containerPath string, hostPath string)
ChownFiles: &idPair,
IgnoreDevices: true,
}
- if !containerInfo.IsDir && (!hostInfo.IsDir || hostInfoErr != nil) {
+ if (!containerInfo.IsDir && !hostInfo.IsDir) || hostInfoErr != nil {
// If we're having a file-to-file copy, make sure to
// rename accordingly.
- putOptions.Rename = map[string]string{filepath.Base(containerInfo.LinkTarget): hostBaseName}
+ putOptions.Rename = map[string]string{filepath.Base(containerTarget): hostBaseName}
}
dir := hostInfo.LinkTarget
if !hostInfo.IsDir {
@@ -210,7 +229,7 @@ func copyFromContainer(container string, containerPath string, hostPath string)
containerCopy := func() error {
defer writer.Close()
- copyFunc, err := registry.ContainerEngine().ContainerCopyToArchive(registry.GetContext(), container, containerInfo.LinkTarget, writer)
+ copyFunc, err := registry.ContainerEngine().ContainerCopyToArchive(registry.GetContext(), container, containerTarget, writer)
if err != nil {
return err
}
@@ -278,6 +297,19 @@ func copyToContainer(container string, containerPath string, hostPath string) er
containerBaseName = filepath.Base(containerInfo.LinkTarget)
}
+ // 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.
+ hostTarget := hostInfo.LinkTarget
+ if containerInfoErr != nil && hostInfo.IsDir && strings.HasSuffix(hostTarget, ".") {
+ hostTarget = filepath.Dir(hostTarget)
+ }
+
var stdinFile string
if isStdin {
if !containerInfo.IsDir {
@@ -318,15 +350,16 @@ func copyToContainer(container string, containerPath string, hostPath string) er
}
getOptions := buildahCopiah.GetOptions{
- // Unless the specified points to ".", we want to copy the base directory.
- KeepDirectoryNames: hostInfo.IsDir && filepath.Base(hostPath) != ".",
+ // Unless the specified path points to ".", we want to
+ // copy the base directory.
+ KeepDirectoryNames: hostInfo.IsDir && filepath.Base(hostTarget) != ".",
}
- if !hostInfo.IsDir && (!containerInfo.IsDir || containerInfoErr != nil) {
+ if (!hostInfo.IsDir && !containerInfo.IsDir) || containerInfoErr != nil {
// If we're having a file-to-file copy, make sure to
// rename accordingly.
- getOptions.Rename = map[string]string{filepath.Base(hostInfo.LinkTarget): containerBaseName}
+ getOptions.Rename = map[string]string{filepath.Base(hostTarget): containerBaseName}
}
- if err := buildahCopiah.Get("/", "", getOptions, []string{hostInfo.LinkTarget}, writer); err != nil {
+ if err := buildahCopiah.Get("/", "", getOptions, []string{hostTarget}, writer); err != nil {
return errors.Wrap(err, "error copying from host")
}
return nil
diff --git a/docs/source/markdown/podman-cp.1.md b/docs/source/markdown/podman-cp.1.md
index 56511c244..bafbbdf3b 100644
--- a/docs/source/markdown/podman-cp.1.md
+++ b/docs/source/markdown/podman-cp.1.md
@@ -57,6 +57,8 @@ If you use a : in a local machine path, you must be explicit with a relative or
Using `-` as the *src_path* streams the contents of STDIN as a tar archive. The command extracts the content of the tar to the *DEST_PATH* in the container. In this case, *dest_path* must specify a directory. Using `-` as the *dest_path* streams the contents of the resource (can be a directory) as a tar archive to STDOUT.
+Note that `podman cp` ignores permission errors when copying from a running rootless container. The TTY devices inside a rootless container are owned by the host's root user and hence cannot be read inside the container's user namespace.
+
## OPTIONS
## ALTERNATIVES
diff --git a/docs/source/markdown/podman-system-service.1.md b/docs/source/markdown/podman-system-service.1.md
index 54ce3f040..93f18adf1 100644
--- a/docs/source/markdown/podman-system-service.1.md
+++ b/docs/source/markdown/podman-system-service.1.md
@@ -13,6 +13,10 @@ If no endpoint is provided, defaults will be used. The default endpoint for a r
service is *unix:/run/podman/podman.sock* and rootless is *unix:/$XDG_RUNTIME_DIR/podman/podman.sock* (for
example *unix:/run/user/1000/podman/podman.sock*)
+To access the API service inside a container:
+- mount the socket as a volume
+- run the container with `--security-opt label:disable`
+
The REST API provided by **podman system service** is split into two parts: a compatibility layer offering support for the Docker v1.40 API, and a Podman-native Libpod layer.
Documentation for the latter is available at *https://docs.podman.io/en/latest/_static/api.html*.
Both APIs are versioned, but the server will not reject requests with an unsupported version set.
diff --git a/go.mod b/go.mod
index 4e8c6e8bf..8223d6ad9 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b
github.com/containernetworking/cni v0.8.1
github.com/containernetworking/plugins v0.9.1
- github.com/containers/buildah v1.19.7
+ github.com/containers/buildah v1.19.8
github.com/containers/common v0.35.0
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.10.2
diff --git a/go.sum b/go.sum
index 751d26ce3..d52b85817 100644
--- a/go.sum
+++ b/go.sum
@@ -94,8 +94,8 @@ github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ
github.com/containernetworking/plugins v0.8.7/go.mod h1:R7lXeZaBzpfqapcAbHRW8/CYwm0dHzbz0XEjofx0uB0=
github.com/containernetworking/plugins v0.9.1 h1:FD1tADPls2EEi3flPc2OegIY1M9pUa9r2Quag7HMLV8=
github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8=
-github.com/containers/buildah v1.19.7 h1:/g11GlhTo177xFex+5GHlF22hq01SyWaJuSA26UGFNU=
-github.com/containers/buildah v1.19.7/go.mod h1:VnyHWgNmfR1d89/zJ/F4cbwOzaQS+6sBky46W7dCo3E=
+github.com/containers/buildah v1.19.8 h1:4TzmetfKPQF5hh6GgMwbAfrD50j+PAcsRiWDnx+gCI8=
+github.com/containers/buildah v1.19.8/go.mod h1:VnyHWgNmfR1d89/zJ/F4cbwOzaQS+6sBky46W7dCo3E=
github.com/containers/common v0.33.4/go.mod h1:PhgL71XuC4jJ/1BIqeP7doke3aMFkCP90YBXwDeUr9g=
github.com/containers/common v0.35.0 h1:1OLZ2v+Tj/CN9BTQkKZ5VOriOiArJedinMMqfJRUI38=
github.com/containers/common v0.35.0/go.mod h1:gs1th7XFTOvVUl4LDPdQjOfOeNiVRDbQ7CNrZ0wS6F8=
diff --git a/libpod/container_copy_linux.go b/libpod/container_copy_linux.go
index 66ccd2f1f..5c275c641 100644
--- a/libpod/container_copy_linux.go
+++ b/libpod/container_copy_linux.go
@@ -14,7 +14,7 @@ import (
"github.com/containers/buildah/pkg/chrootuser"
"github.com/containers/buildah/util"
"github.com/containers/podman/v3/libpod/define"
- "github.com/containers/storage"
+ "github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/storage/pkg/idtools"
"github.com/docker/docker/pkg/archive"
"github.com/opencontainers/runtime-spec/specs-go"
@@ -62,15 +62,16 @@ func (c *Container) copyFromArchive(ctx context.Context, path string, reader io.
}
}
- decompressed, err := archive.DecompressStream(reader)
+ // Make sure we chown the files to the container's main user and group ID.
+ user, err := getContainerUser(c, mountPoint)
if err != nil {
unmount()
return nil, err
}
+ idPair := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
- idMappings, idPair, err := getIDMappingsAndPair(c, mountPoint)
+ decompressed, err := archive.DecompressStream(reader)
if err != nil {
- decompressed.Close()
unmount()
return nil, err
}
@@ -81,10 +82,10 @@ func (c *Container) copyFromArchive(ctx context.Context, path string, reader io.
defer unmount()
defer decompressed.Close()
putOptions := buildahCopiah.PutOptions{
- UIDMap: idMappings.UIDMap,
- GIDMap: idMappings.GIDMap,
- ChownDirs: idPair,
- ChownFiles: idPair,
+ UIDMap: c.config.IDMappings.UIDMap,
+ GIDMap: c.config.IDMappings.GIDMap,
+ ChownDirs: &idPair,
+ ChownFiles: &idPair,
}
return c.joinMountAndExec(ctx,
@@ -121,11 +122,25 @@ func (c *Container) copyToArchive(ctx context.Context, path string, writer io.Wr
return nil, err
}
- idMappings, idPair, err := getIDMappingsAndPair(c, mountPoint)
+ // We optimistically chown to the host user. In case of a hypothetical
+ // container-to-container copy, the reading side will chown back to the
+ // container user.
+ user, err := getContainerUser(c, mountPoint)
+ if err != nil {
+ unmount()
+ return nil, err
+ }
+ hostUID, hostGID, err := util.GetHostIDs(
+ idtoolsToRuntimeSpec(c.config.IDMappings.UIDMap),
+ idtoolsToRuntimeSpec(c.config.IDMappings.GIDMap),
+ user.UID,
+ user.GID,
+ )
if err != nil {
unmount()
return nil, err
}
+ idPair := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
logrus.Debugf("Container copy *from* %q (resolved: %q) on container %q (ID: %s)", path, resolvedPath, c.Name(), c.ID())
@@ -134,11 +149,16 @@ func (c *Container) copyToArchive(ctx context.Context, path string, writer io.Wr
getOptions := buildahCopiah.GetOptions{
// Unless the specified points to ".", we want to copy the base directory.
KeepDirectoryNames: statInfo.IsDir && filepath.Base(path) != ".",
- UIDMap: idMappings.UIDMap,
- GIDMap: idMappings.GIDMap,
- ChownDirs: idPair,
- ChownFiles: idPair,
+ UIDMap: c.config.IDMappings.UIDMap,
+ GIDMap: c.config.IDMappings.GIDMap,
+ ChownDirs: &idPair,
+ ChownFiles: &idPair,
Excludes: []string{"dev", "proc", "sys"},
+ // Ignore EPERMs when copying from rootless containers
+ // since we cannot read TTY devices. Those are owned
+ // by the host's root and hence "nobody" inside the
+ // container's user namespace.
+ IgnoreUnreadable: rootless.IsRootless() && c.state.State == define.ContainerStateRunning,
}
return c.joinMountAndExec(ctx,
func() error {
@@ -148,29 +168,7 @@ func (c *Container) copyToArchive(ctx context.Context, path string, writer io.Wr
}, nil
}
-// getIDMappingsAndPair returns the ID mappings for the container and the host
-// ID pair.
-func getIDMappingsAndPair(container *Container, containerMount string) (*storage.IDMappingOptions, *idtools.IDPair, error) {
- user, err := getContainerUser(container, containerMount)
- if err != nil {
- return nil, nil, err
- }
-
- idMappingOpts, err := container.IDMappings()
- if err != nil {
- return nil, nil, err
- }
-
- hostUID, hostGID, err := util.GetHostIDs(idtoolsToRuntimeSpec(idMappingOpts.UIDMap), idtoolsToRuntimeSpec(idMappingOpts.GIDMap), user.UID, user.GID)
- if err != nil {
- return nil, nil, err
- }
-
- idPair := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
- return &idMappingOpts, &idPair, nil
-}
-
-// getContainerUser returns the specs.User of the container.
+// getContainerUser returns the specs.User and ID mappings of the container.
func getContainerUser(container *Container, mountPoint string) (specs.User, error) {
userspec := container.Config().User
diff --git a/libpod/container_stat_linux.go b/libpod/container_stat_linux.go
index 307b75c14..0b4d9e2df 100644
--- a/libpod/container_stat_linux.go
+++ b/libpod/container_stat_linux.go
@@ -64,6 +64,13 @@ func (c *Container) stat(ctx context.Context, containerMountPoint string, contai
containerPath = "/."
}
+ // Wildcards are not allowed.
+ // TODO: it's now technically possible wildcards.
+ // We may consider enabling support in the future.
+ if strings.Contains(containerPath, "*") {
+ return nil, "", "", copy.ErrENOENT
+ }
+
if c.state.State == define.ContainerStateRunning {
// If the container is running, we need to join it's mount namespace
// and stat there.
@@ -88,7 +95,8 @@ func (c *Container) stat(ctx context.Context, containerMountPoint string, contai
}
if statInfo.IsSymlink {
- // Evaluated symlinks are always relative to the container's mount point.
+ // Symlinks are already evaluated and always relative to the
+ // container's mount point.
absContainerPath = statInfo.ImmediateTarget
} else if strings.HasPrefix(resolvedPath, containerMountPoint) {
// If the path is on the container's mount point, strip it off.
@@ -143,15 +151,31 @@ func secureStat(root string, path string) (*copier.StatForItem, error) {
if len(globStats) != 1 {
return nil, errors.Errorf("internal error: secureStat: expected 1 item but got %d", len(globStats))
}
-
- stat, exists := globStats[0].Results[glob] // only one glob passed, so that's okay
- if !exists {
- return nil, copy.ErrENOENT
+ if len(globStats) != 1 {
+ return nil, errors.Errorf("internal error: secureStat: expected 1 result but got %d", len(globStats[0].Results))
}
- var statErr error
- if stat.Error != "" {
- statErr = errors.New(stat.Error)
+ // NOTE: the key in the map differ from `glob` when hitting symlink.
+ // Hence, we just take the first (and only) key/value pair.
+ for _, stat := range globStats[0].Results {
+ var statErr error
+ if stat.Error != "" {
+ statErr = errors.New(stat.Error)
+ }
+ // If necessary evaluate the symlink
+ if stat.IsSymlink {
+ target, err := copier.Eval(root, path, copier.EvalOptions{})
+ if err != nil {
+ return nil, errors.Wrap(err, "error evaluating symlink in container")
+ }
+ // Need to make sure the symlink is relative to the root!
+ target = strings.TrimPrefix(target, root)
+ target = filepath.Join("/", target)
+ stat.ImmediateTarget = target
+ }
+ return stat, statErr
}
- return stat, statErr
+
+ // Nothing found!
+ return nil, copy.ErrENOENT
}
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index abefca788..90b11f8ca 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -316,7 +316,8 @@ func (r *Runtime) LoadImageFromSingleImageArchive(ctx context.Context, writer io
} {
src, err := referenceFn()
if err == nil && src != nil {
- if newImages, err := r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer); err == nil {
+ newImages, err := r.ImageRuntime().LoadFromArchiveReference(ctx, src, signaturePolicy, writer)
+ if err == nil {
return getImageNames(newImages), nil
}
saveErr = err
diff --git a/pkg/api/handlers/libpod/copy.go b/pkg/api/handlers/libpod/copy.go
deleted file mode 100644
index 4b345bacc..000000000
--- a/pkg/api/handlers/libpod/copy.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package libpod
-
-import (
- "net/http"
-
- "github.com/containers/podman/v3/pkg/api/handlers/utils"
- "github.com/pkg/errors"
-)
-
-func Archive(w http.ResponseWriter, r *http.Request) {
- utils.Error(w, "not implemented", http.StatusNotImplemented, errors.New("not implemented"))
-}
diff --git a/test/apiv2/60-auth.at b/test/apiv2/60-auth.at
new file mode 100644
index 000000000..378955cd7
--- /dev/null
+++ b/test/apiv2/60-auth.at
@@ -0,0 +1,29 @@
+# -*- sh -*-
+#
+# registry-related tests
+#
+
+start_registry
+
+# FIXME FIXME FIXME: remove the 'if false' for use with PR 9589
+if false; then
+
+# FIXME FIXME: please forgive the horrible POST params format; I have an
+# upcoming PR which should fix that.
+
+# Test with wrong password. Confirm bad status and appropriate error message
+t POST /v1.40/auth "\"username\":\"${REGISTRY_USERNAME}\",\"password\":\"WrOnGPassWord\",\"serveraddress\":\"localhost:$REGISTRY_PORT/\"" \
+ 400 \
+ .Status~'.* invalid username/password'
+
+# Test with the right password. Confirm status message and reasonable token
+t POST /v1.40/auth "\"username\":\"${REGISTRY_USERNAME}\",\"password\":\"${REGISTRY_PASSWORD}\",\"serveraddress\":\"localhost:$REGISTRY_PORT/\"" \
+ 200 \
+ .Status="Login Succeeded" \
+ .IdentityToken~[a-zA-Z0-9]
+
+# FIXME: now what? Try something-something using that token?
+token=$(jq -r .IdentityToken <<<"$output")
+# ...
+
+fi # FIXME FIXME FIXME: remove when working
diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2
index d545df245..e32d6bc62 100755
--- a/test/apiv2/test-apiv2
+++ b/test/apiv2/test-apiv2
@@ -17,6 +17,8 @@ PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODM
IMAGE=$PODMAN_TEST_IMAGE_FQN
+REGISTRY_IMAGE="${PODMAN_TEST_IMAGE_REGISTRY}/${PODMAN_TEST_IMAGE_USER}/registry:2.7"
+
# END stuff you can but probably shouldn't customize
###############################################################################
# BEGIN setup
@@ -313,13 +315,115 @@ function start_service() {
die "Cannot start service on non-localhost ($HOST)"
fi
- $PODMAN_BIN --root $WORKDIR system service --time 15 tcp:127.0.0.1:$PORT \
+ $PODMAN_BIN --root $WORKDIR/server_root system service \
+ --time 15 \
+ tcp:127.0.0.1:$PORT \
&> $WORKDIR/server.log &
service_pid=$!
wait_for_port $HOST $PORT
}
+function stop_service() {
+ # Stop the server
+ if [[ -n $service_pid ]]; then
+ kill $service_pid
+ wait $service_pid
+ fi
+}
+
+####################
+# start_registry # Run a local registry
+####################
+REGISTRY_PORT=
+REGISTRY_USERNAME=
+REGISTRY_PASSWORD=
+function start_registry() {
+ # We can be invoked multiple times, e.g. from different subtests, but
+ # let's assume that once started we only kill it at the end of tests.
+ if [[ -n "$REGISTRY_PORT" ]]; then
+ return
+ fi
+
+ REGISTRY_PORT=$(random_port)
+ REGISTRY_USERNAME=u$(random_string 7)
+ REGISTRY_PASSWORD=p$(random_string 7)
+
+ local REGDIR=$WORKDIR/registry
+ local AUTHDIR=$REGDIR/auth
+ mkdir -p $AUTHDIR
+
+ mkdir -p ${REGDIR}/{root,runroot}
+ local PODMAN_REGISTRY_ARGS="--root ${REGDIR}/root --runroot ${REGDIR}/runroot"
+
+ # Give it three tries, to compensate for network flakes
+ podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE ||
+ podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE ||
+ podman ${PODMAN_REGISTRY_ARGS} pull $REGISTRY_IMAGE
+
+ # Create a local cert and credentials
+ # FIXME: is there a hidden "--quiet" flag? This is too noisy.
+ openssl req -newkey rsa:4096 -nodes -sha256 \
+ -keyout $AUTHDIR/domain.key -x509 -days 2 \
+ -out $AUTHDIR/domain.crt \
+ -subj "/C=US/ST=Foo/L=Bar/O=Red Hat, Inc./CN=registry host certificate" \
+ -addext subjectAltName=DNS:localhost
+ htpasswd -Bbn ${REGISTRY_USERNAME} ${REGISTRY_PASSWORD} \
+ > $AUTHDIR/htpasswd
+
+ # Run the registry, and wait for it to come up
+ podman ${PODMAN_REGISTRY_ARGS} run -d \
+ -p ${REGISTRY_PORT}:5000 \
+ --name registry \
+ -v $AUTHDIR:/auth:Z \
+ -e "REGISTRY_AUTH=htpasswd" \
+ -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
+ -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
+ -e REGISTRY_HTTP_TLS_CERTIFICATE=/auth/domain.crt \
+ -e REGISTRY_HTTP_TLS_KEY=/auth/domain.key \
+ ${REGISTRY_IMAGE}
+
+ wait_for_port localhost $REGISTRY_PORT
+}
+
+function stop_registry() {
+ local REGDIR=${WORKDIR}/registry
+ if [[ -d $REGDIR ]]; then
+ local OPTS="--root ${REGDIR}/root --runroot ${REGDIR}/runroot"
+ podman $OPTS stop -f -t 0 -a
+
+ # rm/rmi are important when running rootless: without them we
+ # get EPERMS in tmpdir cleanup because files are owned by subuids.
+ podman $OPTS rm -f -a
+ podman $OPTS rmi -f -a
+ fi
+}
+
+#################
+# random_port # Random open port; arg is range (min-max), default 5000-5999
+#################
+function random_port() {
+ local range=${1:-5000-5999}
+
+ local port
+ for port in $(shuf -i ${range}); do
+ if ! { exec 5<> /dev/tcp/127.0.0.1/$port; } &>/dev/null; then
+ echo $port
+ return
+ fi
+ done
+
+ die "Could not find open port in range $range"
+}
+
+###################
+# random_string # Pseudorandom alphanumeric string of given length
+###################
+function random_string() {
+ local length=${1:-10}
+ head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length
+}
+
###################
# wait_for_port # Returns once port is available on host
###################
@@ -341,8 +445,8 @@ function wait_for_port() {
# podman # Needed by some test scripts to invoke the actual podman binary
############
function podman() {
- echo "\$ $PODMAN_BIN $*" >>$WORKDIR/output.log
- $PODMAN_BIN --root $WORKDIR "$@" >>$WORKDIR/output.log 2>&1
+ echo "\$ $PODMAN_BIN $*" >>$WORKDIR/output.log
+ $PODMAN_BIN --root $WORKDIR/server_root "$@" >>$WORKDIR/output.log 2>&1
}
####################
@@ -412,9 +516,8 @@ if [ -n "$service_pid" ]; then
podman rm -a
podman rmi -af
- # Stop the server
- kill $service_pid
- wait $service_pid
+ stop_registry
+ stop_service
fi
test_count=$(<$testcounter_file)
diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go
index c0fb61544..c0fb3f887 100644
--- a/test/e2e/cp_test.go
+++ b/test/e2e/cp_test.go
@@ -212,7 +212,6 @@ var _ = Describe("Podman cp", func() {
// Copy the root dir "/" of a container to the host.
It("podman cp the root directory from the ctr to an existing directory on the host ", func() {
- SkipIfRootless("cannot copy tty devices in rootless mode")
container := "copyroottohost"
session := podmanTest.RunTopContainer(container)
session.WaitWithDefaultTimeout()
diff --git a/test/system/065-cp.bats b/test/system/065-cp.bats
index 88ed983d8..73e807843 100644
--- a/test/system/065-cp.bats
+++ b/test/system/065-cp.bats
@@ -88,6 +88,7 @@ load helpers
run_podman rmi -f $cpimage
}
+
@test "podman cp file from host to container tmpfs mount" {
srcdir=$PODMAN_TMPDIR/cp-test-file-host-to-ctr
mkdir -p $srcdir
@@ -113,6 +114,22 @@ load helpers
}
+@test "podman cp file from host to container and check ownership" {
+ srcdir=$PODMAN_TMPDIR/cp-test-file-host-to-ctr
+ mkdir -p $srcdir
+ content=cp-user-test-$(random_string 10)
+ echo "content" > $srcdir/hostfile
+ userid=$(id -u)
+
+ run_podman run --user=$userid --userns=keep-id -d --name cpcontainer $IMAGE sleep infinity
+ run_podman cp $srcdir/hostfile cpcontainer:/tmp/hostfile
+ run_podman exec cpcontainer stat -c "%u" /tmp/hostfile
+ is "$output" "$userid" "copied file is chowned to the container user"
+ run_podman kill cpcontainer
+ run_podman rm -f cpcontainer
+}
+
+
@test "podman cp file from container to host" {
srcdir=$PODMAN_TMPDIR/cp-test-file-ctr-to-host
mkdir -p $srcdir
@@ -175,20 +192,19 @@ load helpers
@test "podman cp dir from host to container" {
- dirname=dir-test
- srcdir=$PODMAN_TMPDIR/$dirname
- mkdir -p $srcdir
+ srcdir=$PODMAN_TMPDIR
+ mkdir -p $srcdir/dir/sub
local -a randomcontent=(
random-0-$(random_string 10)
random-1-$(random_string 15)
)
- echo "${randomcontent[0]}" > $srcdir/hostfile0
- echo "${randomcontent[1]}" > $srcdir/hostfile1
+ echo "${randomcontent[0]}" > $srcdir/dir/sub/hostfile0
+ echo "${randomcontent[1]}" > $srcdir/dir/sub/hostfile1
# "." and "dir/." will copy the contents, so make sure that a dir ending
# with dot is treated correctly.
- mkdir -p $srcdir.
- cp $srcdir/* $srcdir./
+ mkdir -p $srcdir/dir.
+ cp -r $srcdir/dir/* $srcdir/dir.
run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
run_podman exec cpcontainer mkdir /srv/subdir
@@ -199,12 +215,15 @@ load helpers
# format is: <source arg to cp (appended to srcdir)> | <destination arg to cp> | <full dest path> | <test name>
tests="
- | / | /dir-test | copy to root
- . | / | /dir-test. | copy dotdir to root
- / | /tmp | /tmp/dir-test | copy to tmp
- /. | /usr/ | /usr/ | copy contents of dir to usr/
- | . | /srv/dir-test | copy to workdir (rel path)
- | subdir/. | /srv/subdir/dir-test | copy to workdir subdir (rel path)
+ dir | / | /dir/sub | copy dir to root
+ dir. | / | /dir./sub | copy dir. to root
+ dir/ | /tmp | /tmp/dir/sub | copy dir/ to tmp
+ dir/. | /usr/ | /usr/sub | copy dir/. usr/
+ dir/sub | . | /srv/sub | copy dir/sub to workdir (rel path)
+ dir/sub/. | subdir/. | /srv/subdir | copy dir/sub/. to workdir subdir (rel path)
+ dir | /newdir1 | /newdir1/sub | copy dir to newdir1
+ dir/ | /newdir2 | /newdir2/sub | copy dir/ to newdir2
+ dir/. | /newdir3 | /newdir3/sub | copy dir/. to newdir3
"
# RUNNING container
@@ -213,12 +232,10 @@ load helpers
if [[ $src == "''" ]];then
unset src
fi
- run_podman cp $srcdir$src cpcontainer:$dest
- run_podman exec cpcontainer ls $dest_fullname
- run_podman exec cpcontainer cat $dest_fullname/hostfile0
- is "$output" "${randomcontent[0]}" "$description (cp -> ctr:$dest)"
- run_podman exec cpcontainer cat $dest_fullname/hostfile1
- is "$output" "${randomcontent[1]}" "$description (cp -> ctr:$dest)"
+ run_podman cp $srcdir/$src cpcontainer:$dest
+ run_podman exec cpcontainer cat $dest_fullname/hostfile0 $dest_fullname/hostfile1
+ is "${lines[0]}" "${randomcontent[0]}" "$description (cp -> ctr:$dest)"
+ is "${lines[1]}" "${randomcontent[1]}" "$description (cp -> ctr:$dest)"
done < <(parse_table "$tests")
run_podman kill cpcontainer
run_podman rm -f cpcontainer
@@ -230,7 +247,7 @@ load helpers
unset src
fi
run_podman create --name cpcontainer --workdir=/srv $cpimage sleep infinity
- run_podman cp $srcdir$src cpcontainer:$dest
+ run_podman cp $srcdir/$src cpcontainer:$dest
run_podman start cpcontainer
run_podman exec cpcontainer cat $dest_fullname/hostfile0 $dest_fullname/hostfile1
is "${lines[0]}" "${randomcontent[0]}" "$description (cp -> ctr:$dest)"
@@ -263,17 +280,19 @@ load helpers
run_podman commit -q cpcontainer
cpimage="$output"
- # format is: <source arg to cp (appended to /srv)> | <full dest path> | <test name>
+ # format is: <source arg to cp (appended to /srv)> | <dest> | <full dest path> | <test name>
tests="
- /srv | /srv/subdir | copy /srv
- /srv/ | /srv/subdir | copy /srv/
- /srv/. | /subdir | copy /srv/.
- /srv/subdir/. | | copy /srv/subdir/.
- /tmp/subdir. | /subdir. | copy /tmp/subdir.
+/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.
"
# RUNNING container
- while read src dest_fullname description; do
+ while read src dest dest_fullname description; do
if [[ $src == "''" ]];then
unset src
fi
@@ -283,7 +302,7 @@ load helpers
if [[ $dest_fullname == "''" ]];then
unset dest_fullname
fi
- run_podman cp cpcontainer:$src $destdir
+ run_podman cp cpcontainer:$src $destdir$dest
is "$(< $destdir$dest_fullname/containerfile0)" "${randomcontent[0]}" "$description"
is "$(< $destdir$dest_fullname/containerfile1)" "${randomcontent[1]}" "$description"
rm -rf $destdir/*
@@ -293,7 +312,7 @@ load helpers
# CREATED container
run_podman create --name cpcontainer --workdir=/srv $cpimage
- while read src dest_fullname description; do
+ while read src dest dest_fullname description; do
if [[ $src == "''" ]];then
unset src
fi
@@ -303,7 +322,7 @@ load helpers
if [[ $dest_fullname == "''" ]];then
unset dest_fullname
fi
- run_podman cp cpcontainer:$src $destdir
+ run_podman cp cpcontainer:$src $destdir$dest
is "$(< $destdir$dest_fullname/containerfile0)" "${randomcontent[0]}" "$description"
is "$(< $destdir$dest_fullname/containerfile1)" "${randomcontent[1]}" "$description"
rm -rf $destdir/*
@@ -314,6 +333,46 @@ load helpers
}
+@test "podman cp symlinked directory from container" {
+ destdir=$PODMAN_TMPDIR/cp-weird-symlink
+ mkdir -p $destdir
+
+ # Create 3 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 $IMAGE sleep infinity
+ run_podman exec cpcontainer sh -c "echo ${randomcontent[0]} > /tmp/containerfile0"
+ run_podman exec cpcontainer sh -c "echo ${randomcontent[1]} > /tmp/containerfile1"
+ run_podman exec cpcontainer sh -c "mkdir /tmp/sub && cd /tmp/sub && ln -s .. weirdlink"
+
+ # Commit the image for testing non-running containers
+ run_podman commit -q cpcontainer
+ cpimage="$output"
+
+ # RUNNING container
+ # NOTE: /dest does not exist yet but is expected to be created during copy
+ run_podman cp cpcontainer:/tmp/sub/weirdlink $destdir/dest
+ run cat $destdir/dest/containerfile0 $destdir/dest/containerfile1
+ is "${lines[0]}" "${randomcontent[0]}" "eval symlink - running container"
+ is "${lines[1]}" "${randomcontent[1]}" "eval symlink - running container"
+
+ run_podman kill cpcontainer
+ run_podman rm -f cpcontainer
+ run rm -rf $srcdir/dest
+
+ # CREATED container
+ run_podman create --name cpcontainer $cpimage
+ run_podman cp cpcontainer:/tmp/sub/weirdlink $destdir/dest
+ run cat $destdir/dest/containerfile0 $destdir/dest/containerfile1
+ is "${lines[0]}" "${randomcontent[0]}" "eval symlink - created container"
+ is "${lines[1]}" "${randomcontent[1]}" "eval symlink - created container"
+ run_podman rm -f cpcontainer
+}
+
+
@test "podman cp file from host to container volume" {
srcdir=$PODMAN_TMPDIR/cp-test-volume
mkdir -p $srcdir
diff --git a/test/system/120-load.bats b/test/system/120-load.bats
index 902cd9f5e..936449bdb 100644
--- a/test/system/120-load.bats
+++ b/test/system/120-load.bats
@@ -26,6 +26,13 @@ verify_iid_and_name() {
is "$new_img_name" "$1" "Name & tag of restored image"
}
+@test "podman load invalid file" {
+ # Regression test for #9672 to make sure invalid input yields errors.
+ invalid=$PODMAN_TMPDIR/invalid
+ echo "I am an invalid file and should cause a podman-load error" > $invalid
+ run_podman 125 load -i $invalid
+}
+
@test "podman save to pipe and load" {
# Generate a random name and tag (must be lower-case)
local random_name=x0$(random_string 12 | tr A-Z a-z)
diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go
index 77d313c58..427950c5c 100644
--- a/vendor/github.com/containers/buildah/buildah.go
+++ b/vendor/github.com/containers/buildah/buildah.go
@@ -28,7 +28,7 @@ const (
Package = "buildah"
// Version for the Package. Bump version in contrib/rpm/buildah.spec
// too.
- Version = "1.19.7"
+ Version = "1.19.8"
// The value we use to identify what type of information, currently a
// serialized Builder structure, we are using as per-container state.
// This should only be changed when we make incompatible changes to
diff --git a/vendor/github.com/containers/buildah/copier/copier.go b/vendor/github.com/containers/buildah/copier/copier.go
index b5e107d4b..52d8133c7 100644
--- a/vendor/github.com/containers/buildah/copier/copier.go
+++ b/vendor/github.com/containers/buildah/copier/copier.go
@@ -284,6 +284,7 @@ type GetOptions struct {
KeepDirectoryNames bool // don't strip the top directory's basename from the paths of items in subdirectories
Rename map[string]string // rename items with the specified names, or under the specified names
NoDerefSymlinks bool // don't follow symlinks when globs match them
+ IgnoreUnreadable bool // ignore errors reading items, instead of returning an error
}
// Get produces an archive containing items that match the specified glob
@@ -1035,6 +1036,14 @@ func copierHandlerStat(req request, pm *fileutils.PatternMatcher) *response {
return &response{Stat: statResponse{Globs: stats}}
}
+func errorIsPermission(err error) bool {
+ err = errors.Cause(err)
+ if err == nil {
+ return false
+ }
+ return os.IsPermission(err) || strings.Contains(err.Error(), "permission denied")
+}
+
func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMatcher, idMappings *idtools.IDMappings) (*response, func() error, error) {
statRequest := req
statRequest.Request = requestStat
@@ -1111,6 +1120,12 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
options.ExpandArchives = false
walkfn := func(path string, info os.FileInfo, err error) error {
if err != nil {
+ if options.IgnoreUnreadable && errorIsPermission(err) {
+ if info != nil && info.IsDir() {
+ return filepath.SkipDir
+ }
+ return nil
+ }
return errors.Wrapf(err, "copier: get: error reading %q", path)
}
// compute the path of this item
@@ -1150,7 +1165,13 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
symlinkTarget = target
}
// add the item to the outgoing tar stream
- return copierHandlerGetOne(info, symlinkTarget, rel, path, options, tw, hardlinkChecker, idMappings)
+ if err := copierHandlerGetOne(info, symlinkTarget, rel, path, options, tw, hardlinkChecker, idMappings); err != nil {
+ if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) {
+ return nil
+ }
+ return err
+ }
+ return nil
}
// walk the directory tree, checking/adding items individually
if err := filepath.Walk(item, walkfn); err != nil {
@@ -1170,6 +1191,9 @@ func copierHandlerGet(bulkWriter io.Writer, req request, pm *fileutils.PatternMa
// dereferenced, be sure to use the name of the
// link.
if err := copierHandlerGetOne(info, "", filepath.Base(queue[i]), item, req.GetOptions, tw, hardlinkChecker, idMappings); err != nil {
+ if req.GetOptions.IgnoreUnreadable && errorIsPermission(err) {
+ continue
+ }
return errors.Wrapf(err, "copier: get: %q", queue[i])
}
itemsCopied++
@@ -1250,7 +1274,7 @@ func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath str
if options.ExpandArchives && isArchivePath(contentPath) {
f, err := os.Open(contentPath)
if err != nil {
- return errors.Wrapf(err, "error opening %s", contentPath)
+ return errors.Wrapf(err, "error opening file for reading archive contents")
}
defer f.Close()
rc, _, err := compression.AutoDecompress(f)
@@ -1321,17 +1345,21 @@ func copierHandlerGetOne(srcfi os.FileInfo, symlinkTarget, name, contentPath str
hdr.Mode = int64(*options.ChmodFiles)
}
}
+ var f *os.File
+ if hdr.Typeflag == tar.TypeReg {
+ // open the file first so that we don't write a header for it if we can't actually read it
+ f, err = os.Open(contentPath)
+ if err != nil {
+ return errors.Wrapf(err, "error opening file for adding its contents to archive")
+ }
+ defer f.Close()
+ }
// output the header
if err = tw.WriteHeader(hdr); err != nil {
return errors.Wrapf(err, "error writing header for %s (%s)", contentPath, hdr.Name)
}
if hdr.Typeflag == tar.TypeReg {
// output the content
- f, err := os.Open(contentPath)
- if err != nil {
- return errors.Wrapf(err, "error opening %s", contentPath)
- }
- defer f.Close()
n, err := io.Copy(tw, f)
if err != nil {
return errors.Wrapf(err, "error copying %s", contentPath)
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 2450446c3..c988d641b 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -72,7 +72,7 @@ github.com/containernetworking/plugins/pkg/utils/hwaddr
github.com/containernetworking/plugins/pkg/utils/sysctl
github.com/containernetworking/plugins/plugins/ipam/host-local/backend
github.com/containernetworking/plugins/plugins/ipam/host-local/backend/allocator
-# github.com/containers/buildah v1.19.7
+# github.com/containers/buildah v1.19.8
github.com/containers/buildah
github.com/containers/buildah/bind
github.com/containers/buildah/chroot