mirror of
https://github.com/containers/podman.git
synced 2025-06-05 14:06:01 +08:00
Implement --archive flag for podman cp
Signed-off-by: Matej Vasek <mvasek@redhat.com>
This commit is contained in:
@ -28,13 +28,13 @@ var (
|
|||||||
You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. 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 a directory.
|
You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. 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 a directory.
|
||||||
`
|
`
|
||||||
cpCommand = &cobra.Command{
|
cpCommand = &cobra.Command{
|
||||||
Use: "cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH",
|
Use: "cp [options] [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH",
|
||||||
Short: "Copy files/folders between a container and the local filesystem",
|
Short: "Copy files/folders between a container and the local filesystem",
|
||||||
Long: cpDescription,
|
Long: cpDescription,
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
RunE: cp,
|
RunE: cp,
|
||||||
ValidArgsFunction: common.AutocompleteCpCommand,
|
ValidArgsFunction: common.AutocompleteCpCommand,
|
||||||
Example: "podman cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH",
|
Example: "podman cp [options] [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH",
|
||||||
}
|
}
|
||||||
|
|
||||||
containerCpCommand = &cobra.Command{
|
containerCpCommand = &cobra.Command{
|
||||||
@ -50,12 +50,14 @@ var (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
cpOpts entities.ContainerCpOptions
|
cpOpts entities.ContainerCpOptions
|
||||||
|
chown bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func cpFlags(cmd *cobra.Command) {
|
func cpFlags(cmd *cobra.Command) {
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVar(&cpOpts.Extract, "extract", false, "Deprecated...")
|
flags.BoolVar(&cpOpts.Extract, "extract", false, "Deprecated...")
|
||||||
flags.BoolVar(&cpOpts.Pause, "pause", true, "Deprecated")
|
flags.BoolVar(&cpOpts.Pause, "pause", true, "Deprecated")
|
||||||
|
flags.BoolVarP(&chown, "archive", "a", true, `Chown copied files to the primary uid/gid of the destination container.`)
|
||||||
_ = flags.MarkHidden("extract")
|
_ = flags.MarkHidden("extract")
|
||||||
_ = flags.MarkHidden("pause")
|
_ = flags.MarkHidden("pause")
|
||||||
}
|
}
|
||||||
@ -378,7 +380,7 @@ func copyToContainer(container string, containerPath string, hostPath string) er
|
|||||||
target = filepath.Dir(target)
|
target = filepath.Dir(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
copyFunc, err := registry.ContainerEngine().ContainerCopyFromArchive(registry.GetContext(), container, target, reader)
|
copyFunc, err := registry.ContainerEngine().ContainerCopyFromArchive(registry.GetContext(), container, target, reader, entities.CopyOptions{Chown: chown})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
podman\-cp - Copy files/folders between a container and the local filesystem
|
podman\-cp - Copy files/folders between a container and the local filesystem
|
||||||
|
|
||||||
## SYNOPSIS
|
## SYNOPSIS
|
||||||
**podman cp** [*container*:]*src_path* [*container*:]*dest_path*
|
**podman cp** [*options*] [*container*:]*src_path* [*container*:]*dest_path*
|
||||||
|
|
||||||
**podman container cp** [*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.
|
||||||
@ -61,6 +61,13 @@ Note that `podman cp` ignores permission errors when copying from a running root
|
|||||||
|
|
||||||
## OPTIONS
|
## OPTIONS
|
||||||
|
|
||||||
|
#### **--archive**, **-a**
|
||||||
|
|
||||||
|
Archive mode (copy all uid/gid information).
|
||||||
|
When set to true, files copied to a container will have changed ownership to the primary uid/gid of the container.
|
||||||
|
When set to false, maintain uid/gid from archive sources instead of changing them to the primary uid/gid of the destination container.
|
||||||
|
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 copy files between host and container.
|
||||||
|
@ -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, tarStream io.Reader) (func() error, error) {
|
func (c *Container) CopyFromArchive(ctx context.Context, containerPath string, chown bool, 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, t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.copyFromArchive(ctx, containerPath, tarStream)
|
return c.copyFromArchive(ctx, containerPath, chown, 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, reader io.Reader) (func() error, error) {
|
func (c *Container) copyFromArchive(ctx context.Context, path string, chown bool, reader io.Reader) (func() error, error) {
|
||||||
var (
|
var (
|
||||||
mountPoint string
|
mountPoint string
|
||||||
resolvedRoot string
|
resolvedRoot string
|
||||||
@ -62,13 +62,16 @@ func (c *Container) copyFromArchive(ctx context.Context, path string, reader io.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we chown the files to the container's main user and group ID.
|
var idPair *idtools.IDPair
|
||||||
user, err := getContainerUser(c, mountPoint)
|
if chown {
|
||||||
if err != nil {
|
// Make sure we chown the files to the container's main user and group ID.
|
||||||
unmount()
|
user, err := getContainerUser(c, mountPoint)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
unmount()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
idPair = &idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
|
||||||
}
|
}
|
||||||
idPair := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
|
|
||||||
|
|
||||||
decompressed, err := archive.DecompressStream(reader)
|
decompressed, err := archive.DecompressStream(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,8 +87,8 @@ func (c *Container) copyFromArchive(ctx context.Context, path string, reader io.
|
|||||||
putOptions := buildahCopiah.PutOptions{
|
putOptions := buildahCopiah.PutOptions{
|
||||||
UIDMap: c.config.IDMappings.UIDMap,
|
UIDMap: c.config.IDMappings.UIDMap,
|
||||||
GIDMap: c.config.IDMappings.GIDMap,
|
GIDMap: c.config.IDMappings.GIDMap,
|
||||||
ChownDirs: &idPair,
|
ChownDirs: idPair,
|
||||||
ChownFiles: &idPair,
|
ChownFiles: idPair,
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.joinMountAndExec(ctx,
|
return c.joinMountAndExec(ctx,
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/containers/podman/v3/libpod/define"
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/podman/v3/pkg/api/handlers/utils"
|
"github.com/containers/podman/v3/pkg/api/handlers/utils"
|
||||||
"github.com/containers/podman/v3/pkg/copy"
|
"github.com/containers/podman/v3/pkg/copy"
|
||||||
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||||
"github.com/containers/podman/v3/pkg/domain/infra/abi"
|
"github.com/containers/podman/v3/pkg/domain/infra/abi"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -92,11 +93,13 @@ 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"`
|
||||||
// TODO handle params below
|
// TODO handle params below
|
||||||
NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"`
|
NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"`
|
||||||
CopyUIDGID bool `schema:"copyUIDGID"`
|
}{
|
||||||
}{}
|
Chown: utils.IsLibpodRequest(r), // backward compatibility
|
||||||
|
}
|
||||||
|
|
||||||
err := decoder.Decode(&query, r.URL.Query())
|
err := decoder.Decode(&query, r.URL.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,7 +110,7 @@ func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder,
|
|||||||
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)
|
copyFunc, err := containerEngine.ContainerCopyFromArchive(r.Context(), containerName, query.Path, r.Body, entities.CopyOptions{Chown: query.Chown})
|
||||||
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.
|
||||||
|
@ -50,11 +50,21 @@ func Stat(ctx context.Context, nameOrID string, path string) (*entities.Containe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CopyFromArchive(ctx context.Context, nameOrID string, path string, reader io.Reader) (entities.ContainerCopyFunc, error) {
|
func CopyFromArchive(ctx context.Context, nameOrID string, path string, reader io.Reader) (entities.ContainerCopyFunc, error) {
|
||||||
|
return CopyFromArchiveWithOptions(ctx, nameOrID, path, reader, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFromArchiveWithOptions FIXME: remove this function and make CopyFromArchive accept the option as the last parameter in podman 4.0
|
||||||
|
func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path string, reader io.Reader, options *CopyOptions) (entities.ContainerCopyFunc, error) {
|
||||||
conn, err := bindings.GetClient(ctx)
|
conn, err := bindings.GetClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
params := url.Values{}
|
|
||||||
|
params, err := options.ToParams()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
params.Set("path", path)
|
params.Set("path", path)
|
||||||
|
|
||||||
return func() error {
|
return func() error {
|
||||||
|
@ -251,3 +251,11 @@ type ExistsOptions struct {
|
|||||||
// External checks for containers created outside of Podman
|
// External checks for containers created outside of Podman
|
||||||
External *bool
|
External *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:generate go run ../generator/generator.go CopyOptions
|
||||||
|
// CopyOptions are options for copying to containers.
|
||||||
|
type CopyOptions struct {
|
||||||
|
// 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.
|
||||||
|
Chown *bool `schema:"copyUIDGID"`
|
||||||
|
}
|
||||||
|
37
pkg/bindings/containers/types_copy_options.go
Normal file
37
pkg/bindings/containers/types_copy_options.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v3/pkg/bindings/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
This file is generated automatically by go generate. Do not edit.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Changed
|
||||||
|
func (o *CopyOptions) Changed(fieldName string) bool {
|
||||||
|
return util.Changed(o, fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToParams
|
||||||
|
func (o *CopyOptions) ToParams() (url.Values, error) {
|
||||||
|
return util.ToParams(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithChown
|
||||||
|
func (o *CopyOptions) WithChown(value bool) *CopyOptions {
|
||||||
|
v := &value
|
||||||
|
o.Chown = v
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetChown
|
||||||
|
func (o *CopyOptions) GetChown() bool {
|
||||||
|
var chown bool
|
||||||
|
if o.Chown == nil {
|
||||||
|
return chown
|
||||||
|
}
|
||||||
|
return *o.Chown
|
||||||
|
}
|
@ -72,14 +72,18 @@ func ToParams(o interface{}) (url.Values, error) {
|
|||||||
if reflect.Ptr == f.Kind() {
|
if reflect.Ptr == f.Kind() {
|
||||||
f = f.Elem()
|
f = f.Elem()
|
||||||
}
|
}
|
||||||
|
paramName := fieldName
|
||||||
|
if pn, ok := sType.Field(i).Tag.Lookup("schema"); ok {
|
||||||
|
paramName = pn
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case IsSimpleType(f):
|
case IsSimpleType(f):
|
||||||
params.Set(fieldName, SimpleTypeToParam(f))
|
params.Set(paramName, SimpleTypeToParam(f))
|
||||||
case f.Kind() == reflect.Slice:
|
case f.Kind() == reflect.Slice:
|
||||||
for i := 0; i < f.Len(); i++ {
|
for i := 0; i < f.Len(); i++ {
|
||||||
elem := f.Index(i)
|
elem := f.Index(i)
|
||||||
if IsSimpleType(elem) {
|
if IsSimpleType(elem) {
|
||||||
params.Add(fieldName, SimpleTypeToParam(elem))
|
params.Add(paramName, SimpleTypeToParam(elem))
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("slices must contain only simple types")
|
return nil, errors.New("slices must contain only simple types")
|
||||||
}
|
}
|
||||||
@ -95,7 +99,7 @@ func ToParams(o interface{}) (url.Values, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
params.Set(fieldName, s)
|
params.Set(paramName, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return params, nil
|
return params, nil
|
||||||
|
@ -160,6 +160,13 @@ type CommitOptions struct {
|
|||||||
Writer io.Writer
|
Writer io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CopyOptions struct {
|
||||||
|
// If used with ContainerCopyFromArchive and set to true
|
||||||
|
// it will change ownership of files from the source tar archive
|
||||||
|
// to the primary uid/gid of the destination container.
|
||||||
|
Chown bool
|
||||||
|
}
|
||||||
|
|
||||||
type CommitReport struct {
|
type CommitReport struct {
|
||||||
Id string //nolint
|
Id string //nolint
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ type ContainerEngine interface {
|
|||||||
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
|
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
|
||||||
ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error)
|
ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error)
|
||||||
ContainerCommit(ctx context.Context, nameOrID string, options CommitOptions) (*CommitReport, error)
|
ContainerCommit(ctx context.Context, nameOrID string, options CommitOptions) (*CommitReport, error)
|
||||||
ContainerCopyFromArchive(ctx context.Context, nameOrID string, path string, reader io.Reader) (ContainerCopyFunc, error)
|
ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options CopyOptions) (ContainerCopyFunc, error)
|
||||||
ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (ContainerCopyFunc, error)
|
ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (ContainerCopyFunc, error)
|
||||||
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
|
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
|
||||||
ContainerDiff(ctx context.Context, nameOrID string, options DiffOptions) (*DiffReport, error)
|
ContainerDiff(ctx context.Context, nameOrID string, options DiffOptions) (*DiffReport, error)
|
||||||
|
@ -7,12 +7,12 @@ import (
|
|||||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID string, containerPath string, reader io.Reader) (entities.ContainerCopyFunc, error) {
|
func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID, containerPath string, reader io.Reader, options entities.CopyOptions) (entities.ContainerCopyFunc, error) {
|
||||||
container, err := ic.Libpod.LookupContainer(nameOrID)
|
container, err := ic.Libpod.LookupContainer(nameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return container.CopyFromArchive(ctx, containerPath, reader)
|
return container.CopyFromArchive(ctx, containerPath, options.Chown, 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) {
|
||||||
|
@ -833,8 +833,8 @@ func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrID string, o
|
|||||||
return reports, nil
|
return reports, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID string, path string, reader io.Reader) (entities.ContainerCopyFunc, error) {
|
func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options entities.CopyOptions) (entities.ContainerCopyFunc, error) {
|
||||||
return containers.CopyFromArchive(ic.ClientCtx, nameOrID, path, reader)
|
return containers.CopyFromArchiveWithOptions(ic.ClientCtx, nameOrID, path, reader, new(containers.CopyOptions).WithChown(options.Chown))
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -16,7 +16,7 @@ CTR="ArchiveTestingCtr"
|
|||||||
TMPD=$(mktemp -d podman-apiv2-test.archive.XXXXXXXX)
|
TMPD=$(mktemp -d podman-apiv2-test.archive.XXXXXXXX)
|
||||||
HELLO_TAR="${TMPD}/hello.tar"
|
HELLO_TAR="${TMPD}/hello.tar"
|
||||||
echo "Hello" > $TMPD/hello.txt
|
echo "Hello" > $TMPD/hello.txt
|
||||||
tar --format=posix -C $TMPD -cvf ${HELLO_TAR} hello.txt &> /dev/null
|
tar --owner=1042 --group=1043 --format=posix -C $TMPD -cvf ${HELLO_TAR} hello.txt &> /dev/null
|
||||||
|
|
||||||
podman run -d --name "${CTR}" "${IMAGE}" top
|
podman run -d --name "${CTR}" "${IMAGE}" top
|
||||||
|
|
||||||
@ -72,6 +72,20 @@ if [ "$(tar -xf "${TMPD}/body.tar" hello.txt --to-stdout)" != "Hello" ]; then
|
|||||||
ARCHIVE_TEST_ERROR="1"
|
ARCHIVE_TEST_ERROR="1"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# test if uid/gid was set correctly in the server
|
||||||
|
uidngid=$($PODMAN_BIN --root $WORKDIR/server_root exec "${CTR}" stat -c "%u:%g" "/tmp/hello.txt")
|
||||||
|
if [[ "${uidngid}" != "1042:1043" ]]; then
|
||||||
|
echo -e "${red}NOK: UID/GID of the file doesn't match.${nc}" 1>&2;
|
||||||
|
ARCHIVE_TEST_ERROR="1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# TODO: uid/gid should be also preserved on way back (GET request)
|
||||||
|
# right now it ends up as root:root instead of 1042:1043
|
||||||
|
#if [[ "$(tar -tvf "${TMPD}/body.tar")" != *"1042/1043"* ]]; then
|
||||||
|
# echo -e "${red}NOK: UID/GID of the file doesn't match.${nc}" 1>&2;
|
||||||
|
# ARCHIVE_TEST_ERROR="1"
|
||||||
|
#fi
|
||||||
|
|
||||||
cleanUpArchiveTest
|
cleanUpArchiveTest
|
||||||
if [[ "${ARCHIVE_TEST_ERROR}" ]] ; then
|
if [[ "${ARCHIVE_TEST_ERROR}" ]] ; then
|
||||||
exit 1;
|
exit 1;
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
|
import io
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
|
from typing import IO, Optional
|
||||||
|
|
||||||
from docker import DockerClient, errors
|
from docker import DockerClient, errors
|
||||||
|
from docker.models.containers import Container
|
||||||
|
|
||||||
from test.python.docker import Podman
|
from test.python.docker import Podman
|
||||||
from test.python.docker.compat import common, constant
|
from test.python.docker.compat import common, constant
|
||||||
|
|
||||||
|
import tarfile
|
||||||
|
|
||||||
|
|
||||||
class TestContainers(unittest.TestCase):
|
class TestContainers(unittest.TestCase):
|
||||||
podman = None # initialized podman configuration for tests
|
podman = None # initialized podman configuration for tests
|
||||||
@ -198,3 +203,37 @@ class TestContainers(unittest.TestCase):
|
|||||||
filters = {"name": "top"}
|
filters = {"name": "top"}
|
||||||
ctnrs = self.client.containers.list(all=True, filters=filters)
|
ctnrs = self.client.containers.list(all=True, filters=filters)
|
||||||
self.assertEqual(len(ctnrs), 1)
|
self.assertEqual(len(ctnrs), 1)
|
||||||
|
|
||||||
|
def test_copy_to_container(self):
|
||||||
|
ctr: Optional[Container] = None
|
||||||
|
try:
|
||||||
|
test_file_content = b"Hello World!"
|
||||||
|
ctr = self.client.containers.create(image="alpine", detach=True, command="top")
|
||||||
|
ctr.start()
|
||||||
|
|
||||||
|
buff: IO[bytes] = io.BytesIO()
|
||||||
|
with tarfile.open(fileobj=buff, mode="w:") as tf:
|
||||||
|
ti: tarfile.TarInfo = tarfile.TarInfo()
|
||||||
|
ti.uid = 1042
|
||||||
|
ti.gid = 1043
|
||||||
|
ti.name = "a.txt"
|
||||||
|
ti.path = "a.txt"
|
||||||
|
ti.mode = 0o644
|
||||||
|
ti.type = tarfile.REGTYPE
|
||||||
|
ti.size = len(test_file_content)
|
||||||
|
tf.addfile(ti, fileobj=io.BytesIO(test_file_content))
|
||||||
|
|
||||||
|
buff.seek(0)
|
||||||
|
ctr.put_archive("/tmp/", buff)
|
||||||
|
ret, out = ctr.exec_run(["stat", "-c", "%u:%g", "/tmp/a.txt"])
|
||||||
|
|
||||||
|
self.assertEqual(ret, 0)
|
||||||
|
self.assertTrue(out.startswith(b'1042:1043'), "assert correct uid/gid")
|
||||||
|
|
||||||
|
ret, out = ctr.exec_run(["cat", "/tmp/a.txt"])
|
||||||
|
self.assertEqual(ret, 0)
|
||||||
|
self.assertTrue(out.startswith(test_file_content), "assert file content")
|
||||||
|
finally:
|
||||||
|
if ctr is not None:
|
||||||
|
ctr.stop()
|
||||||
|
ctr.remove()
|
||||||
|
@ -114,7 +114,7 @@ load helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "podman cp file from host to container and check ownership" {
|
@test "podman cp (-a=true) file from host to container and check ownership" {
|
||||||
srcdir=$PODMAN_TMPDIR/cp-test-file-host-to-ctr
|
srcdir=$PODMAN_TMPDIR/cp-test-file-host-to-ctr
|
||||||
mkdir -p $srcdir
|
mkdir -p $srcdir
|
||||||
content=cp-user-test-$(random_string 10)
|
content=cp-user-test-$(random_string 10)
|
||||||
@ -129,6 +129,25 @@ load helpers
|
|||||||
run_podman rm -f cpcontainer
|
run_podman rm -f cpcontainer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "podman cp (-a=false) file from host to container and check ownership" {
|
||||||
|
local tmpdir="${PODMAN_TMPDIR}/cp-test-file-host-to-ctr"
|
||||||
|
mkdir -p "${tmpdir}"
|
||||||
|
|
||||||
|
pushd "${tmpdir}"
|
||||||
|
touch a.txt
|
||||||
|
tar --owner=1042 --group=1043 -cf a.tar a.txt
|
||||||
|
popd
|
||||||
|
|
||||||
|
userid=$(id -u)
|
||||||
|
|
||||||
|
run_podman run --user="$userid" --userns=keep-id -d --name cpcontainer $IMAGE sleep infinity
|
||||||
|
run_podman cp -a=false - cpcontainer:/tmp/ < "${tmpdir}/a.tar"
|
||||||
|
run_podman exec cpcontainer stat -c "%u:%g" /tmp/a.txt
|
||||||
|
is "$output" "1042:1043" "copied file retains uid/gid from the tar"
|
||||||
|
run_podman kill cpcontainer
|
||||||
|
run_podman rm -f cpcontainer
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@test "podman cp file from/to host while --pid=host" {
|
@test "podman cp file from/to host while --pid=host" {
|
||||||
if is_rootless && ! is_cgroupsv2; then
|
if is_rootless && ! is_cgroupsv2; then
|
||||||
|
Reference in New Issue
Block a user