summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--OWNERS14
-rw-r--r--cmd/podman/common/completion.go2
-rw-r--r--cmd/podman/common/specgen.go14
-rw-r--r--cmd/podman/containers/cp.go19
-rw-r--r--cmd/podman/generate/kube.go8
-rw-r--r--cmd/podman/root.go2
-rw-r--r--docs/source/markdown/podman-cp.1.md30
-rw-r--r--docs/source/markdown/podman-generate-kube.1.md8
-rw-r--r--docs/source/markdown/podman-image-sign.1.md10
-rw-r--r--docs/source/markdown/podman-network-connect.1.md2
-rw-r--r--docs/source/markdown/podman-network-disconnect.1.md2
-rw-r--r--docs/source/markdown/podman-network-inspect.1.md2
-rw-r--r--docs/source/markdown/podman-network-ls.1.md2
-rw-r--r--docs/source/markdown/podman-network.1.md2
-rw-r--r--docs/source/markdown/podman-run.1.md34
-rw-r--r--go.mod6
-rw-r--r--go.sum6
-rwxr-xr-xhack/podman-socat122
-rw-r--r--install.md4
-rw-r--r--libpod/container.go30
-rw-r--r--libpod/container_config.go8
-rw-r--r--libpod/container_inspect.go6
-rw-r--r--libpod/container_internal.go2
-rw-r--r--libpod/container_internal_linux.go26
-rw-r--r--libpod/events/config.go2
-rw-r--r--libpod/image/image.go25
-rw-r--r--libpod/info.go1
-rw-r--r--libpod/kube.go28
-rw-r--r--libpod/networking_linux.go119
-rw-r--r--libpod/oci_conmon_exec_linux.go2
-rw-r--r--libpod/oci_conmon_linux.go8
-rw-r--r--libpod/reset.go2
-rw-r--r--libpod/runtime.go17
-rw-r--r--libpod/runtime_cstorage.go6
-rw-r--r--libpod/runtime_migrate.go6
-rw-r--r--libpod/runtime_migrate_unsupported.go2
-rw-r--r--libpod/runtime_pod_linux.go4
-rw-r--r--libpod/shutdown/handler.go6
-rw-r--r--libpod/stats.go2
-rw-r--r--pkg/api/handlers/compat/containers.go7
-rw-r--r--pkg/api/handlers/compat/containers_archive.go319
-rw-r--r--pkg/api/handlers/compat/containers_create.go6
-rw-r--r--pkg/api/handlers/compat/containers_pause.go2
-rw-r--r--pkg/api/handlers/compat/containers_restart.go2
-rw-r--r--pkg/api/handlers/compat/containers_start.go4
-rw-r--r--pkg/api/handlers/compat/containers_stop.go4
-rw-r--r--pkg/api/handlers/compat/containers_unpause.go2
-rw-r--r--pkg/api/handlers/compat/images_build.go4
-rw-r--r--pkg/api/handlers/compat/images_push.go1
-rw-r--r--pkg/api/handlers/compat/networks.go9
-rw-r--r--pkg/api/handlers/compat/ping.go1
-rw-r--r--pkg/api/handlers/compat/system.go82
-rw-r--r--pkg/api/handlers/compat/volumes.go4
-rw-r--r--pkg/api/handlers/libpod/generate.go5
-rw-r--r--pkg/api/handlers/types.go71
-rw-r--r--pkg/api/server/register_generate.go12
-rw-r--r--pkg/bindings/connection.go10
-rw-r--r--pkg/bindings/containers/attach.go4
-rw-r--r--pkg/bindings/generate/generate.go11
-rw-r--r--pkg/copy/copy.go188
-rw-r--r--pkg/copy/item.go601
-rw-r--r--pkg/domain/entities/containers.go10
-rw-r--r--pkg/domain/entities/engine_container.go4
-rw-r--r--pkg/domain/entities/images.go22
-rw-r--r--pkg/domain/infra/abi/cp.go439
-rw-r--r--pkg/domain/infra/abi/generate.go54
-rw-r--r--pkg/domain/infra/abi/images.go154
-rw-r--r--pkg/domain/infra/abi/images_list.go13
-rw-r--r--pkg/domain/infra/abi/system.go17
-rw-r--r--pkg/domain/infra/tunnel/containers.go4
-rw-r--r--pkg/domain/infra/tunnel/generate.go4
-rw-r--r--pkg/specgen/generate/config_linux.go53
-rw-r--r--pkg/specgen/generate/container_create.go1
-rw-r--r--pkg/specgen/generate/namespaces.go2
-rw-r--r--pkg/specgen/generate/oci.go4
-rw-r--r--pkg/specgen/namespaces.go16
-rw-r--r--pkg/specgen/specgen.go7
-rw-r--r--pkg/systemd/generate/pods.go2
-rw-r--r--pkg/trust/trust.go25
-rw-r--r--pkg/util/utils.go5
-rw-r--r--pkg/util/utils_supported.go13
-rw-r--r--pkg/util/utils_windows.go6
-rw-r--r--test/apiv2/20-containers.at2
-rw-r--r--test/apiv2/25-containersMore.at13
-rw-r--r--test/apiv2/rest_api/test_rest_v2_0_0.py254
-rw-r--r--test/e2e/build_test.go2
-rw-r--r--test/e2e/common_test.go2
-rw-r--r--test/e2e/config/containers.conf4
-rw-r--r--test/e2e/containers_conf_test.go7
-rw-r--r--test/e2e/cp_test.go322
-rw-r--r--test/e2e/generate_kube_test.go70
-rw-r--r--test/e2e/network_test.go8
-rw-r--r--test/e2e/run_memory_test.go6
-rw-r--r--test/e2e/run_networking_test.go23
-rw-r--r--test/e2e/run_test.go35
-rw-r--r--test/system/030-run.bats37
-rw-r--r--test/system/035-logs.bats3
-rw-r--r--test/system/065-cp.bats397
-rw-r--r--test/system/120-load.bats30
-rw-r--r--test/system/400-unprivileged-access.bats2
-rw-r--r--troubleshooting.md5
-rw-r--r--utils/utils_supported.go45
-rw-r--r--vendor/github.com/containers/common/pkg/config/config.go15
-rw-r--r--vendor/github.com/containers/common/pkg/config/containers.conf16
-rw-r--r--vendor/github.com/containers/common/pkg/config/default.go6
-rw-r--r--vendor/github.com/containers/common/pkg/config/libpodConfig.go407
-rw-r--r--vendor/github.com/containers/common/version/version.go2
-rw-r--r--vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go6
-rw-r--r--vendor/modules.txt4
109 files changed, 2704 insertions, 1814 deletions
diff --git a/OWNERS b/OWNERS
index 7cbef7f37..88dedae25 100644
--- a/OWNERS
+++ b/OWNERS
@@ -3,20 +3,26 @@ approvers:
- edsantiago
- giuseppe
- jwhonce
+ - Luap99
- mheon
- rhatdan
+ - saschagrunert
- TomSweeneyRedHat
- - vrothberg
- umohnani8
+ - vrothberg
+ - zhangguanzhang
reviewers:
+ - ashley-cui
- baude
- edsantiago
- giuseppe
- jwhonce
+ - Luap99
- mheon
+ - QiWang19
- rhatdan
+ - saschagrunert
- TomSweeneyRedHat
- - vrothberg
- - ashley-cui
- - QiWang19
- umohnani8
+ - vrothberg
+ - zhangguanzhang
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go
index 7fed15e5e..25f4d0f79 100644
--- a/cmd/podman/common/completion.go
+++ b/cmd/podman/common/completion.go
@@ -22,7 +22,7 @@ var (
// ChangeCmds is the list of valid Change commands to passed to the Commit call
ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"}
// LogLevels supported by podman
- LogLevels = []string{"debug", "info", "warn", "error", "fatal", "panic"}
+ LogLevels = []string{"debug", "info", "warn", "warning", "error", "fatal", "panic"}
)
type completeType int
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go
index 0bb6e79e5..e0da142ad 100644
--- a/cmd/podman/common/specgen.go
+++ b/cmd/podman/common/specgen.go
@@ -517,18 +517,22 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
}
switch con[0] {
- case "proc-opts":
- s.ProcOpts = strings.Split(con[1], ",")
+ case "apparmor":
+ s.ContainerSecurityConfig.ApparmorProfile = con[1]
+ s.Annotations[define.InspectAnnotationApparmor] = con[1]
case "label":
// TODO selinux opts and label opts are the same thing
s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1])
s.Annotations[define.InspectAnnotationLabel] = strings.Join(s.ContainerSecurityConfig.SelinuxOpts, ",label=")
- case "apparmor":
- s.ContainerSecurityConfig.ApparmorProfile = con[1]
- s.Annotations[define.InspectAnnotationApparmor] = con[1]
+ case "mask":
+ s.ContainerSecurityConfig.Mask = append(s.ContainerSecurityConfig.Mask, strings.Split(con[1], ":")...)
+ case "proc-opts":
+ s.ProcOpts = strings.Split(con[1], ",")
case "seccomp":
s.SeccompProfilePath = con[1]
s.Annotations[define.InspectAnnotationSeccomp] = con[1]
+ case "unmask":
+ s.ContainerSecurityConfig.Unmask = append(s.ContainerSecurityConfig.Unmask, strings.Split(con[1], ":")...)
default:
return fmt.Errorf("invalid --security-opt 2: %q", opt)
}
diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go
index a74ea89d2..fd3aa7680 100644
--- a/cmd/podman/containers/cp.go
+++ b/cmd/podman/containers/cp.go
@@ -3,10 +3,7 @@ package containers
import (
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
- "github.com/containers/podman/v2/pkg/cgroups"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/podman/v2/pkg/rootless"
- "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -43,7 +40,7 @@ var (
func cpFlags(cmd *cobra.Command) {
flags := cmd.Flags()
flags.BoolVar(&cpOpts.Extract, "extract", false, "Extract the tar file into the destination directory.")
- flags.BoolVar(&cpOpts.Pause, "pause", copyPause(), "Pause the container while copying")
+ flags.BoolVar(&cpOpts.Pause, "pause", true, "Pause the container while copying")
}
func init() {
@@ -62,17 +59,5 @@ func init() {
}
func cp(cmd *cobra.Command, args []string) error {
- _, err := registry.ContainerEngine().ContainerCp(registry.GetContext(), args[0], args[1], cpOpts)
- return err
-}
-
-func copyPause() bool {
- if rootless.IsRootless() {
- cgroupv2, _ := cgroups.IsCgroup2UnifiedMode()
- if !cgroupv2 {
- logrus.Debugf("defaulting to pause==false on rootless cp in cgroupv1 systems")
- return false
- }
- }
- return true
+ return registry.ContainerEngine().ContainerCp(registry.GetContext(), args[0], args[1], cpOpts)
}
diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go
index e47bd35b5..0517db19a 100644
--- a/cmd/podman/generate/kube.go
+++ b/cmd/podman/generate/kube.go
@@ -17,16 +17,16 @@ import (
var (
kubeOptions = entities.GenerateKubeOptions{}
kubeFile = ""
- kubeDescription = `Command generates Kubernetes pod and service YAML (v1 specification) from a Podman container or pod.
+ kubeDescription = `Command generates Kubernetes pod and service YAML (v1 specification) from Podman containers or a pod.
Whether the input is for a container or pod, Podman will always generate the specification as a pod.`
kubeCmd = &cobra.Command{
- Use: "kube [options] CONTAINER | POD",
+ Use: "kube [options] CONTAINER... | POD",
Short: "Generate Kubernetes YAML from a container or pod.",
Long: kubeDescription,
RunE: kube,
- Args: cobra.ExactArgs(1),
+ Args: cobra.MinimumNArgs(1),
ValidArgsFunction: common.AutocompleteContainersAndPods,
Example: `podman generate kube ctrID
podman generate kube podID
@@ -51,7 +51,7 @@ func init() {
}
func kube(cmd *cobra.Command, args []string) error {
- report, err := registry.ContainerEngine().GenerateKube(registry.GetContext(), args[0], kubeOptions)
+ report, err := registry.ContainerEngine().GenerateKube(registry.GetContext(), args, kubeOptions)
if err != nil {
return err
}
diff --git a/cmd/podman/root.go b/cmd/podman/root.go
index 7840e6100..0830a62a5 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -71,7 +71,7 @@ var (
DisableFlagsInUseLine: true,
}
- logLevel = "error"
+ logLevel = "warn"
useSyslog bool
requireCleanup = true
)
diff --git a/docs/source/markdown/podman-cp.1.md b/docs/source/markdown/podman-cp.1.md
index 8f63c00ee..241a74c42 100644
--- a/docs/source/markdown/podman-cp.1.md
+++ b/docs/source/markdown/podman-cp.1.md
@@ -9,12 +9,12 @@ podman\-cp - Copy files/folders between a container and the local filesystem
**podman container cp** [*options*] [*container*:]*src_path* [*container*:]*dest_path*
## DESCRIPTION
-Copies 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.
-If - is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT.
+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.
+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 **podman cp** command assumes container paths are relative to the container's / (root) directory.
+The **podman cp** command assumes container paths are relative to the container's root directory (i.e., `/`).
This means supplying the initial forward slash is optional;
@@ -27,24 +27,22 @@ Assuming a path separator of /, a first argument of **src_path** and second argu
**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 /
- - Error condition: the destination directory must exist.
+ - the file is saved to a file created at **dest_path** (note that parent directory must exist)
- **dest_path** exists and is a file
- - the destination is overwritten with the source file's contents
+ - 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**
+ - 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** 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
+ - 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
+ - **src_path** ends with `/`
+ - the source directory is copied into this directory
+ - **src_path** ends with `/.` (i.e., 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.
@@ -57,11 +55,13 @@ You can also use : when specifying paths to a **src_path** or **dest_path** on a
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`
+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.
+
## OPTIONS
#### **--extract**
-Extract the tar file into the destination directory. If the destination directory is not provided, extract the tar file into the root directory.
+If the source is a tar archive, extract it to the provided destination (must be a directory). If the source is not a tar archive, follow the above rules.
#### **--pause**
diff --git a/docs/source/markdown/podman-generate-kube.1.md b/docs/source/markdown/podman-generate-kube.1.md
index 6fad89b1f..ed2143388 100644
--- a/docs/source/markdown/podman-generate-kube.1.md
+++ b/docs/source/markdown/podman-generate-kube.1.md
@@ -3,12 +3,12 @@
podman-generate-kube - Generate Kubernetes YAML based on a pod or container
## SYNOPSIS
-**podman generate kube** [*options*] *container* | *pod*
+**podman generate kube** [*options*] *container...* | *pod*
## DESCRIPTION
-**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from a Podman container or pod. Whether
-the input is for a container or pod, Podman will always generate the specification as a Pod. The input may be in the form
-of a pod or container name or ID.
+**podman generate kube** will generate Kubernetes Pod YAML (v1 specification) from Podman one or more containers or a single pod. Whether
+the input is for containers or a pod, Podman will always generate the specification as a Pod. The input may be in the form
+of a pod or one or more container names or IDs.
Note that the generated Kubernetes YAML file can be used to re-run the deployment via podman-play-kube(1).
diff --git a/docs/source/markdown/podman-image-sign.1.md b/docs/source/markdown/podman-image-sign.1.md
index 1bd6e5b9d..7a924b80b 100644
--- a/docs/source/markdown/podman-image-sign.1.md
+++ b/docs/source/markdown/podman-image-sign.1.md
@@ -9,7 +9,9 @@ podman-image-sign - Create a signature for an image
## DESCRIPTION
**podman image sign** will create a local signature for one or more local images that have
been pulled from a registry. The signature will be written to a directory
-derived from the registry configuration files in /etc/containers/registries.d. By default, the signature will be written into /var/lib/containers/sigstore directory.
+derived from the registry configuration files in `$HOME/.config/containers/registries.d` if it exists,
+otherwise `/etc/containers/registries.d` (unless overridden at compile-time), see **containers-registries.d(5)** for more information.
+By default, the signature will be written into `/var/lib/containers/sigstore` for root and `$HOME/.local/share/containers/sigstore` for non-root users
## OPTIONS
@@ -38,7 +40,8 @@ Sign the busybox image with the identify of foo@bar.com with a user's keyring an
## RELATED CONFIGURATION
The write (and read) location for signatures is defined in YAML-based
-configuration files in /etc/containers/registries.d/. When you sign
+configuration files in /etc/containers/registries.d/ for root,
+or $HOME/.config/containers/registries.d for non-root users. When you sign
an image, Podman will use those configuration files to determine
where to write the signature based on the the name of the originating
registry or a default storage value unless overridden with the --directory
@@ -53,5 +56,8 @@ the signature will be written into sub-directories of
/var/lib/containers/sigstore/privateregistry.example.com. The use of 'sigstore' also means
the signature will be 'read' from that same location on a pull-related function.
+## SEE ALSO
+containers-registries.d(5)
+
## HISTORY
November 2018, Originally compiled by Qi Wang (qiwan at redhat dot com)
diff --git a/docs/source/markdown/podman-network-connect.1.md b/docs/source/markdown/podman-network-connect.1.md
index 58b6e5c44..a31a415dc 100644
--- a/docs/source/markdown/podman-network-connect.1.md
+++ b/docs/source/markdown/podman-network-connect.1.md
@@ -10,6 +10,8 @@ podman\-network\-connect - Connect a container to a network
Connects a container to a network. A container can be connected to a network by name or by ID.
Once connected, the container can communicate with other containers in the same network.
+This command is not available for rootless users.
+
## OPTIONS
#### **--alias**
Add network-scoped alias for the container. If the network is using the `dnsname` CNI plugin, these aliases
diff --git a/docs/source/markdown/podman-network-disconnect.1.md b/docs/source/markdown/podman-network-disconnect.1.md
index 95c7018a8..8b7125282 100644
--- a/docs/source/markdown/podman-network-disconnect.1.md
+++ b/docs/source/markdown/podman-network-disconnect.1.md
@@ -9,6 +9,8 @@ podman\-network\-disconnect - Disconnect a container from a network
## DESCRIPTION
Disconnects a container from a network.
+This command is not available for rootless users.
+
## OPTIONS
#### **--force**, **-f**
diff --git a/docs/source/markdown/podman-network-inspect.1.md b/docs/source/markdown/podman-network-inspect.1.md
index 47d647b3f..56515d0c1 100644
--- a/docs/source/markdown/podman-network-inspect.1.md
+++ b/docs/source/markdown/podman-network-inspect.1.md
@@ -7,7 +7,7 @@ podman\-network\-inspect - Displays the raw CNI network configuration for one or
**podman network inspect** [*options*] [*network* ...]
## DESCRIPTION
-Display the raw (JSON format) network configuration. This command is not available for rootless users.
+Display the raw (JSON format) network configuration.
## OPTIONS
#### **--format**, **-f**
diff --git a/docs/source/markdown/podman-network-ls.1.md b/docs/source/markdown/podman-network-ls.1.md
index a964c97e8..9d2dd52a8 100644
--- a/docs/source/markdown/podman-network-ls.1.md
+++ b/docs/source/markdown/podman-network-ls.1.md
@@ -7,7 +7,7 @@ podman\-network\-ls - Display a summary of CNI networks
**podman network ls** [*options*]
## DESCRIPTION
-Displays a list of existing podman networks. This command is not available for rootless users.
+Displays a list of existing podman networks.
## OPTIONS
#### **--filter**, **-f**
diff --git a/docs/source/markdown/podman-network.1.md b/docs/source/markdown/podman-network.1.md
index d11f0d7b6..41e2ae885 100644
--- a/docs/source/markdown/podman-network.1.md
+++ b/docs/source/markdown/podman-network.1.md
@@ -7,7 +7,7 @@ podman\-network - Manage Podman CNI networks
**podman network** *subcommand*
## DESCRIPTION
-The network command manages CNI networks for Podman. It is not supported for rootless users.
+The network command manages CNI networks for Podman.
## COMMANDS
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 83aaa33e8..53c5b2d4b 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -26,9 +26,12 @@ Several files will be automatically created within the container. These include
_/etc/hosts_, _/etc/hostname_, and _/etc/resolv.conf_ to manage networking.
These will be based on the host's version of the files, though they can be
customized with options (for example, **--dns** will override the host's DNS
-servers in the created _resolv.conf_). Additionally, an empty file is created in
-each container to indicate to programs they are running in a container. This file
-is located at _/run/.containerenv_.
+servers in the created _resolv.conf_). Additionally, a container environment
+file is created in each container to indicate to programs they are running in a
+container. This file is located at _/run/.containerenv_. When using the
+--privileged flag the .containerenv contains name/value pairs indicating the
+container engine version, whether the engine is running in rootless mode, the
+container name and id, as well as the image name and id that the container is based on.
When running from a user defined network namespace, the _/etc/netns/NSNAME/resolv.conf_
will be used if it exists, otherwise _/etc/resolv.conf_ will be used.
@@ -885,11 +888,16 @@ Security Options
- **label=level:**_LEVEL_: Set the label level for the container processes
- **label=filetype:**TYPE_: Set the label file type for the container files
- **label=disable**: Turn off label separation for the container
+- **mask**=_/path/1:/path/2_: The paths to mask separated by a colon. A masked path
+ cannot be accessed inside the container.
- **no-new-privileges**: Disable container processes from gaining additional privileges
- **seccomp=unconfined**: Turn off seccomp confinement for the container
- **seccomp**=_profile.json_: Allowed syscall list seccomp JSON file to be used as a seccomp filter
- **proc-opts**=_OPTIONS_ : Comma separated list of options to use for the /proc mount. More details
for the possible mount options are specified at **proc(5)** man page.
+- **unmask**=_ALL_ or _/path/1:/path/2_: Paths to unmask separated by a colon. If set to **ALL**, it will
+ unmask all the paths that are masked by default.
+ The default masked paths are **/proc/acpi, /proc/kcore, /proc/keys, /proc/latency_stats, /proc/sched_debug, /proc/scsi, /proc/timer_list, /proc/timer_stats, /sys/firmware, and /sys/fs/selinux.**
Note: Labeling can be disabled for all containers by setting **label=false** in the **containers.conf**(5) file.
@@ -1479,6 +1487,26 @@ $ podman run --security-opt label=type:svirt_apache_t -i -t centos bash
Note you would have to write policy defining a **svirt_apache_t** type.
+To mask additional specific paths in the container, specify the paths
+separated by a colon using the **mask** option with the **--security-opt**
+flag.
+
+```
+$ podman run --security-opt mask=/foo/bar:/second/path fedora bash
+```
+
+To unmask all the paths that are masked by default, set the **unmask** option to
+**ALL**. Or to only unmask specific paths, specify the paths as shown above with
+the **mask** option.
+
+```
+$ podman run --security-opt unmask=ALL fedora bash
+```
+
+```
+$ podman run --security-opt unmask=/foo/bar:/sys/firmware fedora bash
+```
+
### Setting device weight
If you want to set _/dev/sda_ device weight to **200**, you can specify the device
diff --git a/go.mod b/go.mod
index e44c5843d..f822e7994 100644
--- a/go.mod
+++ b/go.mod
@@ -11,13 +11,13 @@ require (
github.com/containernetworking/cni v0.8.0
github.com/containernetworking/plugins v0.8.7
github.com/containers/buildah v1.18.1-0.20201125084616-dd26b137459c
- github.com/containers/common v0.29.0
+ github.com/containers/common v0.31.0
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.8.1
github.com/containers/psgo v1.5.1
github.com/containers/storage v1.24.1
github.com/coreos/go-systemd/v22 v22.1.0
- github.com/cri-o/ocicni v0.2.1-0.20201102180012-75c612fda1a2
+ github.com/cri-o/ocicni v0.2.1-0.20201125151022-df072ea5421c
github.com/cyphar/filepath-securejoin v0.2.2
github.com/davecgh/go-spew v1.1.1
github.com/docker/distribution v2.7.1+incompatible
@@ -73,5 +73,3 @@ require (
k8s.io/apimachinery v0.19.4
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab
)
-
-replace github.com/cri-o/ocicni => github.com/cri-o/ocicni v0.2.1-0.20201109200316-afdc16ba66df
diff --git a/go.sum b/go.sum
index 8c588de4d..111e9f7d2 100644
--- a/go.sum
+++ b/go.sum
@@ -95,6 +95,8 @@ github.com/containers/buildah v1.18.1-0.20201125084616-dd26b137459c h1:vyc2iYz9b
github.com/containers/buildah v1.18.1-0.20201125084616-dd26b137459c/go.mod h1:B+0OkXUogxdwsEy4ax3a5/vDtJjL6vCisiV6frQZJ4A=
github.com/containers/common v0.29.0 h1:hTMC+urdkk5bKfhL/OgCixIX5xjJgQ2l2jPG745ECFQ=
github.com/containers/common v0.29.0/go.mod h1:yT4GTUHsKRmpaDb+mecXRnIMre7W3ZgwXqaYMywXlaA=
+github.com/containers/common v0.31.0 h1:SRnjfoqbjfaojpY9YJq9JBPEslwB5hoXJbaE+5zMFwM=
+github.com/containers/common v0.31.0/go.mod h1:yT4GTUHsKRmpaDb+mecXRnIMre7W3ZgwXqaYMywXlaA=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.8.1 h1:aHW8a/Kd0dTJ7PTL/fc6y12sJqHxWgqilu+XyHfjD8Q=
@@ -124,8 +126,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/cri-o/ocicni v0.2.1-0.20201109200316-afdc16ba66df h1:c35uRFkER07nAkB1X21e+PI5xO21SOyI6G7tdfvz1z4=
-github.com/cri-o/ocicni v0.2.1-0.20201109200316-afdc16ba66df/go.mod h1:vingr1ztOAzP2WyTgGbpMov9dFhbjNxdLtDv0+PhAvY=
+github.com/cri-o/ocicni v0.2.1-0.20201125151022-df072ea5421c h1:iGaCU6d3oVT0pl8tmvyDhoA/vTDL3IX08akfsKZIy9o=
+github.com/cri-o/ocicni v0.2.1-0.20201125151022-df072ea5421c/go.mod h1:vingr1ztOAzP2WyTgGbpMov9dFhbjNxdLtDv0+PhAvY=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
diff --git a/hack/podman-socat b/hack/podman-socat
new file mode 100755
index 000000000..7bc571816
--- /dev/null
+++ b/hack/podman-socat
@@ -0,0 +1,122 @@
+#!/bin/bash -e
+# Execute podman while capturing the API stream
+#
+# Script will run an instance of podman sand-boxed, the API stream will be captured and then formatted for readability.
+
+if [[ $(id -u) != 0 ]]; then
+ echo >&2 "$0 must be run as root."
+ exit 2
+fi
+
+if ! command -v socat >/dev/null 2>&1; then
+ echo 1>&2 "socat not found on PATH"
+fi
+
+PODMAN=${PODMAN:-podman}
+if ! command -v "$PODMAN" >/dev/null 2>&1; then
+ echo 1>&2 "$PODMAN not found on PATH"
+fi
+
+function usage() {
+ echo 1>&2 $0 '[-v] [-h]'
+}
+
+while getopts "vh" arg; do
+ case $arg in
+ v)
+ VERBOSE='-v'
+ export PODMAN_LOG_LEVEL=debug
+ ;;
+ h)
+ usage
+ exit 0
+ ;;
+ \?)
+ usage
+ exit 2
+ ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+function cleanup() {
+ set +xeuo pipefail
+ rm -r "$1"
+ kill -9 $REAP_PIDS
+
+ sed -e 's/^> /\nClient Request> /' -e 's/^< /\nServer Response< /' -i /tmp/podman-socat.log
+}
+
+# Create temporary directory for storage
+export TMPDIR=$(mktemp -d /tmp/podman.XXXXXXXXXX)
+trap "cleanup $TMPDIR" EXIT
+
+# Need locations to store stuff
+mkdir -p "${TMPDIR}"/{podman,crio,crio-run,cni/net.d,ctnr,tunnel}
+
+export REGISTRIES_CONFIG_PATH=${TMPDIR}/registry.conf
+cat >"$REGISTRIES_CONFIG_PATH" <<-EOT
+ [registries.search]
+ registries = ['docker.io']
+ [registries.insecure]
+ registries = []
+ [registries.block]
+ registries = []
+EOT
+
+export CNI_CONFIG_PATH=${TMPDIR}/cni/net.d
+cat >"$CNI_CONFIG_PATH"/87-podman-bridge.conflist <<-EOT
+{
+ "cniVersion": "0.3.0",
+ "name": "podman",
+ "plugins": [{
+ "type": "bridge",
+ "bridge": "cni0",
+ "isGateway": true,
+ "ipMasq": true,
+ "ipam": {
+ "type": "host-local",
+ "subnet": "10.88.0.0/16",
+ "routes": [{
+ "dst": "0.0.0.0/0"
+ }]
+ }
+ },
+ {
+ "type": "portmap",
+ "capabilities": {
+ "portMappings": true
+ }
+ }
+ ]
+}
+EOT
+
+PODMAN_ARGS="--storage-driver=vfs \
+ --root=${TMPDIR}/crio \
+ --runroot=${TMPDIR}/crio-run \
+ --cni-config-dir=$CNI_CONFIG_PATH \
+ --cgroup-manager=systemd \
+ "
+if [[ -n $VERBOSE ]]; then
+ PODMAN_ARGS="$PODMAN_ARGS --log-level=$PODMAN_LOG_LEVEL --syslog=true"
+fi
+PODMAN="$PODMAN $PODMAN_ARGS"
+
+PODMAN_HOST="${TMPDIR}/podman/podman-socat.sock"
+SOCAT_HOST="${TMPDIR}/podman/podman.sock"
+
+cat <<-EOT
+Podman service running at unix:$SOCAT_HOST
+See /tmp/podman-socat.log for API stream capture
+See /tmp/podman-service.log for service logging
+
+usage: sudo bin/podman-remote --url unix:$SOCAT_HOST images
+
+^C to exit
+EOT
+
+$PODMAN system service --timeout=0 "unix:$PODMAN_HOST" >/tmp/podman-service.log 2>&1 &
+REAP_PIDS=$!
+
+socat -v "UNIX-LISTEN:$SOCAT_HOST",fork,reuseaddr,unlink-early "UNIX-CONNECT:$PODMAN_HOST" >/tmp/podman-socat.log 2>&1
diff --git a/install.md b/install.md
index 2ef6eae2c..d09abec3a 100644
--- a/install.md
+++ b/install.md
@@ -1,5 +1,5 @@
# libpod Installation Instructions
-The installation instructions for Podman and libpod now reside **[here](https://podman.io/getting-started/installation)** in the **[podman.io](https://podman.io)** site. From the homepage, the installation instructions can be found under "Get Started->Installing Podman".
+The installation instructions for Podman and libpod now reside **[here](https://podman.io/getting-started/installation)** on the **[podman.io](https://podman.io)** site.
-The podman.io site resides in a GitHub under the Containers repository at [https://github.com/containers/podman.io](https://github.com/containers/podman.io). If you see a change that needs to happen to the installation instructions, please feel free to open a pull request there, we're always happy to have new contributors!
+The podman.io site resides in a GitHub repository under the containers organization at [https://github.com/containers/podman.io](https://github.com/containers/podman.io). If you see a change that needs to happen to the installation instructions, please feel free to open a pull request there. We're always happy to have new contributors!
diff --git a/libpod/container.go b/libpod/container.go
index e954d84eb..96a21736c 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -267,6 +267,11 @@ func (c *Container) Config() *ContainerConfig {
return returnConfig
}
+// Runtime returns the container's Runtime.
+func (c *Container) Runtime() *Runtime {
+ return c.runtime
+}
+
// Spec returns the container's OCI runtime spec
// The spec returned is the one used to create the container. The running
// spec may differ slightly as mounts are added based on the image
@@ -916,13 +921,33 @@ func (c *Container) CgroupManager() string {
return cgroupManager
}
-// CGroupPath returns a cgroups "path" for a given container.
+// CGroupPath returns a cgroups "path" for the given container.
+// Note that the container must be running. Otherwise, an error
+// is returned.
func (c *Container) CGroupPath() (string, error) {
+ if !c.batched {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ if err := c.syncContainer(); err != nil {
+ return "", errors.Wrapf(err, "error updating container %s state", c.ID())
+ }
+ }
+ return c.cGroupPath()
+}
+
+// cGroupPath returns a cgroups "path" for the given container.
+// Note that the container must be running. Otherwise, an error
+// is returned.
+// NOTE: only call this when owning the container's lock.
+func (c *Container) cGroupPath() (string, error) {
if c.config.NoCgroups || c.config.CgroupsMode == "disabled" {
return "", errors.Wrapf(define.ErrNoCgroups, "this container is not creating cgroups")
}
+ if c.state.State != define.ContainerStateRunning && c.state.State != define.ContainerStatePaused {
+ return "", errors.Wrapf(define.ErrCtrStopped, "cannot get cgroup path unless container %s is running", c.ID())
+ }
- // Read /proc/[PID]/cgroup and find the *longest* cgroup entry. That's
+ // Read /proc/{PID}/cgroup and find the *longest* cgroup entry. That's
// needed to account for hacks in cgroups v1, where each line in the
// file could potentially point to a cgroup. The longest one, however,
// is the libpod-specific one we're looking for.
@@ -947,7 +972,6 @@ func (c *Container) CGroupPath() (string, error) {
if len(path) > len(cgroupPath) {
cgroupPath = path
}
-
}
if len(cgroupPath) == 0 {
diff --git a/libpod/container_config.go b/libpod/container_config.go
index cc3ad25ea..c95be9b55 100644
--- a/libpod/container_config.go
+++ b/libpod/container_config.go
@@ -135,7 +135,13 @@ type ContainerRootFSConfig struct {
// OverlayVolumes lists the overlay volumes to mount into the container.
OverlayVolumes []*ContainerOverlayVolume `json:"overlayVolumes,omitempty"`
// ImageVolumes lists the image volumes to mount into the container.
- ImageVolumes []*ContainerImageVolume `json:"imageVolumes,omitempty"`
+ // Please note that this is named ctrImageVolumes in JSON to
+ // distinguish between these and the old `imageVolumes` field in Podman
+ // pre-1.8, which was used in very old Podman versions to determine how
+ // image volumes were handled in Libpod (support for these eventually
+ // moved out of Libpod into pkg/specgen).
+ // Please DO NOT re-use the `imageVolumes` name in container JSON again.
+ ImageVolumes []*ContainerImageVolume `json:"ctrImageVolumes,omitempty"`
// CreateWorkingDir indicates that Libpod should create the container's
// working directory if it does not exist. Some OCI runtimes do this by
// default, but others do not.
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index f78d74ef7..2ce3e8e68 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -488,7 +488,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
// Weight? For now, ignore anything
// without Weight set.
if dev.Weight == nil {
- logrus.Warnf("Ignoring weight device %s as it lacks a weight", key)
+ logrus.Infof("Ignoring weight device %s as it lacks a weight", key)
continue
}
if deviceNodes == nil {
@@ -500,7 +500,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
}
path, ok := deviceNodes[key]
if !ok {
- logrus.Warnf("Could not locate weight device %s in system devices", key)
+ logrus.Infof("Could not locate weight device %s in system devices", key)
continue
}
weightDev := define.InspectBlkioWeightDevice{}
@@ -522,7 +522,7 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
}
path, ok := deviceNodes[key]
if !ok {
- logrus.Warnf("Could not locate throttle device %s in system devices", key)
+ logrus.Infof("Could not locate throttle device %s in system devices", key)
continue
}
throttleDev := define.InspectBlkioThrottleDevice{}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index b6a3244ea..c751d775d 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -525,7 +525,7 @@ func (c *Container) teardownStorage() error {
// Potentially another tool using containers/storage already
// removed it?
if errors.Cause(err) == storage.ErrNotAContainer || errors.Cause(err) == storage.ErrContainerUnknown {
- logrus.Warnf("Storage for container %s already removed", c.ID())
+ logrus.Infof("Storage for container %s already removed", c.ID())
return nil
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index a9a789ad8..1bf044f9d 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -35,6 +35,7 @@ import (
"github.com/containers/podman/v2/pkg/rootless"
"github.com/containers/podman/v2/pkg/util"
"github.com/containers/podman/v2/utils"
+ "github.com/containers/podman/v2/version"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/idtools"
securejoin "github.com/cyphar/filepath-securejoin"
@@ -365,7 +366,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
if !MountExists(g.Mounts(), dstPath) {
g.AddMount(newMount)
} else {
- logrus.Warnf("User mount overriding libpod mount at %q", dstPath)
+ logrus.Infof("User mount overriding libpod mount at %q", dstPath)
}
}
@@ -1436,11 +1437,26 @@ func (c *Container) makeBindMounts() error {
}
}
- // Make .containerenv
- // Empty file, so no need to recreate if it exists
+ // Make .containerenv if it does not exist
if _, ok := c.state.BindMounts["/run/.containerenv"]; !ok {
- // Empty string for now, but we may consider populating this later
- containerenvPath, err := c.writeStringToRundir(".containerenv", "")
+ var containerenv string
+ isRootless := 0
+ if rootless.IsRootless() {
+ isRootless = 1
+ }
+ imageID, imageName := c.Image()
+
+ if c.Privileged() {
+ // Populate the .containerenv with container information
+ containerenv = fmt.Sprintf(`engine="podman-%s"
+name=%q
+id=%q
+image=%q
+imageid=%q
+rootless=%d
+`, version.Version.String(), c.Name(), c.ID(), imageName, imageID, isRootless)
+ }
+ containerenvPath, err := c.writeStringToRundir(".containerenv", containerenv)
if err != nil {
return errors.Wrapf(err, "error creating containerenv file for container %s", c.ID())
}
diff --git a/libpod/events/config.go b/libpod/events/config.go
index fc1457289..085fa9d52 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -121,6 +121,8 @@ const (
Cleanup Status = "cleanup"
// Commit ...
Commit Status = "commit"
+ // Copy ...
+ Copy Status = "copy"
// Create ...
Create Status = "create"
// Exec ...
diff --git a/libpod/image/image.go b/libpod/image/image.go
index cecd64eb7..5c3f3b9e4 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -1222,6 +1222,11 @@ func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.Image
}
}
+ parent, err := i.ParentID(ctx)
+ if err != nil {
+ return nil, err
+ }
+
repoTags, err := i.RepoTags()
if err != nil {
return nil, err
@@ -1248,6 +1253,7 @@ func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.Image
data := &inspect.ImageData{
ID: i.ID(),
+ Parent: parent,
RepoTags: repoTags,
RepoDigests: repoDigests,
Comment: comment,
@@ -1258,10 +1264,12 @@ func (i *Image) inspect(ctx context.Context, calculateSize bool) (*inspect.Image
Config: &ociv1Img.Config,
Version: info.DockerVersion,
Size: size,
- VirtualSize: size,
- Annotations: annotations,
- Digest: i.Digest(),
- Labels: info.Labels,
+ // This is good enough for now, but has to be
+ // replaced later with correct calculation logic
+ VirtualSize: size,
+ Annotations: annotations,
+ Digest: i.Digest(),
+ Labels: info.Labels,
RootFS: &inspect.RootFS{
Type: ociv1Img.RootFS.Type,
Layers: ociv1Img.RootFS.DiffIDs,
@@ -1505,6 +1513,15 @@ func (i *Image) GetParent(ctx context.Context) (*Image, error) {
return tree.parent(ctx, i)
}
+// ParentID returns the image ID of the parent. Return empty string if a parent is not found.
+func (i *Image) ParentID(ctx context.Context) (string, error) {
+ parent, err := i.GetParent(ctx)
+ if err == nil && parent != nil {
+ return parent.ID(), nil
+ }
+ return "", err
+}
+
// GetChildren returns a list of the imageIDs that depend on the image
func (i *Image) GetChildren(ctx context.Context) ([]string, error) {
children, err := i.getChildren(ctx, true)
diff --git a/libpod/info.go b/libpod/info.go
index dd7a521c1..2f64a107e 100644
--- a/libpod/info.go
+++ b/libpod/info.go
@@ -117,7 +117,6 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
if rootless.IsRootless() {
if path, err := exec.LookPath("slirp4netns"); err == nil {
- logrus.Warnf("Failed to retrieve program version for %s: %v", path, err)
version, err := programVersion(path)
if err != nil {
logrus.Warnf("Failed to retrieve program version for %s: %v", path, err)
diff --git a/libpod/kube.go b/libpod/kube.go
index 067e7827d..bf041112a 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -21,9 +21,9 @@ import (
// GenerateForKube takes a slice of libpod containers and generates
// one v1.Pod description that includes just a single container.
-func (c *Container) GenerateForKube() (*v1.Pod, error) {
+func GenerateForKube(ctrs []*Container) (*v1.Pod, error) {
// Generate the v1.Pod yaml description
- return simplePodWithV1Container(c)
+ return simplePodWithV1Containers(ctrs)
}
// GenerateForKube takes a slice of libpod containers and generates
@@ -236,14 +236,20 @@ func addContainersAndVolumesToPodObject(containers []v1.Container, volumes []v1.
return &p
}
-// simplePodWithV1Container is a function used by inspect when kube yaml needs to be generated
+// simplePodWithV1Containers is a function used by inspect when kube yaml needs to be generated
// for a single container. we "insert" that container description in a pod.
-func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) {
- kubeCtr, kubeVols, err := containerToV1Container(ctr)
- if err != nil {
- return nil, err
+func simplePodWithV1Containers(ctrs []*Container) (*v1.Pod, error) {
+ kubeCtrs := make([]v1.Container, 0, len(ctrs))
+ kubeVolumes := make([]v1.Volume, 0)
+ for _, ctr := range ctrs {
+ kubeCtr, kubeVols, err := containerToV1Container(ctr)
+ if err != nil {
+ return nil, err
+ }
+ kubeCtrs = append(kubeCtrs, kubeCtr)
+ kubeVolumes = append(kubeVolumes, kubeVols...)
}
- return addContainersAndVolumesToPodObject([]v1.Container{kubeCtr}, kubeVols, ctr.Name()), nil
+ return addContainersAndVolumesToPodObject(kubeCtrs, kubeVolumes, strings.ReplaceAll(ctrs[0].Name(), "_", "")), nil
}
@@ -294,6 +300,12 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, error) {
_, image := c.Image()
kubeContainer.Image = image
kubeContainer.Stdin = c.Stdin()
+
+ // prepend the entrypoint of the container to command
+ if ep := c.Entrypoint(); len(c.Entrypoint()) > 0 {
+ ep = append(ep, containerCommands...)
+ containerCommands = ep
+ }
kubeContainer.Command = containerCommands
// TODO need to figure out how we handle command vs entry point. Kube appears to prefer entrypoint.
// right now we just take the container's command
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index c7db95eb3..2171a9208 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -246,7 +246,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error {
// setupSlirp4netns can be called in rootful as well as in rootless
func (r *Runtime) setupSlirp4netns(ctr *Container) error {
path := r.config.Engine.NetworkCmdPath
-
+ slirpOptions := r.config.Engine.NetworkCmdOptions
if path == "" {
var err error
path, err = exec.LookPath("slirp4netns")
@@ -274,68 +274,69 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error {
outboundAddr6 := ""
if ctr.config.NetworkOptions != nil {
- slirpOptions := ctr.config.NetworkOptions["slirp4netns"]
- for _, o := range slirpOptions {
- parts := strings.SplitN(o, "=", 2)
- if len(parts) < 2 {
- return errors.Errorf("unknown option for slirp4netns: %q", o)
+ slirpOptions = append(slirpOptions, ctr.config.NetworkOptions["slirp4netns"]...)
+ }
+
+ for _, o := range slirpOptions {
+ parts := strings.SplitN(o, "=", 2)
+ if len(parts) < 2 {
+ return errors.Errorf("unknown option for slirp4netns: %q", o)
+ }
+ option, value := parts[0], parts[1]
+ switch option {
+ case "cidr":
+ ipv4, _, err := net.ParseCIDR(value)
+ if err != nil || ipv4.To4() == nil {
+ return errors.Errorf("invalid cidr %q", value)
}
- option, value := parts[0], parts[1]
- switch option {
- case "cidr":
- ipv4, _, err := net.ParseCIDR(value)
- if err != nil || ipv4.To4() == nil {
- return errors.Errorf("invalid cidr %q", value)
- }
- cidr = value
- case "port_handler":
- switch value {
- case "slirp4netns":
- isSlirpHostForward = true
- case "rootlesskit":
- isSlirpHostForward = false
- default:
- return errors.Errorf("unknown port_handler for slirp4netns: %q", value)
- }
- case "allow_host_loopback":
- switch value {
- case "true":
- disableHostLoopback = false
- case "false":
- disableHostLoopback = true
- default:
- return errors.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value)
- }
- case "enable_ipv6":
- switch value {
- case "true":
- enableIPv6 = true
- case "false":
- enableIPv6 = false
- default:
- return errors.Errorf("invalid value of enable_ipv6 for slirp4netns: %q", value)
- }
- case "outbound_addr":
- ipv4 := net.ParseIP(value)
- if ipv4 == nil || ipv4.To4() == nil {
- _, err := net.InterfaceByName(value)
- if err != nil {
- return errors.Errorf("invalid outbound_addr %q", value)
- }
+ cidr = value
+ case "port_handler":
+ switch value {
+ case "slirp4netns":
+ isSlirpHostForward = true
+ case "rootlesskit":
+ isSlirpHostForward = false
+ default:
+ return errors.Errorf("unknown port_handler for slirp4netns: %q", value)
+ }
+ case "allow_host_loopback":
+ switch value {
+ case "true":
+ disableHostLoopback = false
+ case "false":
+ disableHostLoopback = true
+ default:
+ return errors.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value)
+ }
+ case "enable_ipv6":
+ switch value {
+ case "true":
+ enableIPv6 = true
+ case "false":
+ enableIPv6 = false
+ default:
+ return errors.Errorf("invalid value of enable_ipv6 for slirp4netns: %q", value)
+ }
+ case "outbound_addr":
+ ipv4 := net.ParseIP(value)
+ if ipv4 == nil || ipv4.To4() == nil {
+ _, err := net.InterfaceByName(value)
+ if err != nil {
+ return errors.Errorf("invalid outbound_addr %q", value)
}
- outboundAddr = value
- case "outbound_addr6":
- ipv6 := net.ParseIP(value)
- if ipv6 == nil || ipv6.To4() != nil {
- _, err := net.InterfaceByName(value)
- if err != nil {
- return errors.Errorf("invalid outbound_addr6: %q", value)
- }
+ }
+ outboundAddr = value
+ case "outbound_addr6":
+ ipv6 := net.ParseIP(value)
+ if ipv6 == nil || ipv6.To4() != nil {
+ _, err := net.InterfaceByName(value)
+ if err != nil {
+ return errors.Errorf("invalid outbound_addr6: %q", value)
}
- outboundAddr6 = value
- default:
- return errors.Errorf("unknown option for slirp4netns: %q", o)
}
+ outboundAddr6 = value
+ default:
+ return errors.Errorf("unknown option for slirp4netns: %q", o)
}
}
diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go
index 7068bf87a..f8e7020f7 100644
--- a/libpod/oci_conmon_exec_linux.go
+++ b/libpod/oci_conmon_exec_linux.go
@@ -231,7 +231,7 @@ func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, t
// Wait for the PID to stop
if err := waitPidStop(pid, time.Duration(timeout)*time.Second); err != nil {
- logrus.Warnf("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL", ctr.ID(), sessionID)
+ logrus.Infof("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL: %v", ctr.ID(), sessionID, err)
} else {
// No error, container is dead
return nil
diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go
index bd58610a2..307b9bc54 100644
--- a/libpod/oci_conmon_linux.go
+++ b/libpod/oci_conmon_linux.go
@@ -442,7 +442,7 @@ func (r *ConmonOCIRuntime) StopContainer(ctr *Container, timeout uint, all bool)
}
if err := waitContainerStop(ctr, time.Duration(timeout)*time.Second); err != nil {
- logrus.Warnf("Timed out stopping container %s, resorting to SIGKILL", ctr.ID())
+ logrus.Infof("Timed out stopping container %s, resorting to SIGKILL: %v", ctr.ID(), err)
} else {
// No error, the container is dead
return nil
@@ -1009,7 +1009,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
if ctr.config.SdNotifyMode == define.SdNotifyModeIgnore {
if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil {
- logrus.Warnf("Error unsetting NOTIFY_SOCKET %s", err.Error())
+ logrus.Warnf("Error unsetting NOTIFY_SOCKET %v", err)
}
}
@@ -1155,14 +1155,14 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile)
if err != nil {
- logrus.Warnf("error reading conmon pid file for container %s: %s", ctr.ID(), err.Error())
+ logrus.Warnf("error reading conmon pid file for container %s: %v", ctr.ID(), err)
} else if conmonPID > 0 {
// conmon not having a pid file is a valid state, so don't set it if we don't have it
logrus.Infof("Got Conmon PID as %d", conmonPID)
ctr.state.ConmonPID = conmonPID
if ctr.config.SdNotifyMode != define.SdNotifyModeIgnore {
if sent, err := daemon.SdNotify(false, fmt.Sprintf("MAINPID=%d", conmonPID)); err != nil {
- logrus.Errorf("Error notifying systemd of Conmon PID: %s", err.Error())
+ logrus.Errorf("Error notifying systemd of Conmon PID: %v", err)
} else if sent {
logrus.Debugf("Notify MAINPID sent successfully")
}
diff --git a/libpod/reset.go b/libpod/reset.go
index f8828fed4..6d2842723 100644
--- a/libpod/reset.go
+++ b/libpod/reset.go
@@ -46,7 +46,7 @@ func (r *Runtime) Reset(ctx context.Context) error {
}
}
- if err := stopPauseProcess(); err != nil {
+ if err := r.stopPauseProcess(); err != nil {
logrus.Errorf("Error stopping pause process: %v", err)
}
diff --git a/libpod/runtime.go b/libpod/runtime.go
index df3dfae2b..1004e4fa7 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -190,7 +190,7 @@ func newRuntimeFromConfig(ctx context.Context, conf *config.Config, options ...R
if err := shutdown.Register("libpod", func(sig os.Signal) error {
os.Exit(1)
return nil
- }); err != nil {
+ }); err != nil && errors.Cause(err) != shutdown.ErrHandlerExists {
logrus.Errorf("Error registering shutdown handler for libpod: %v", err)
}
@@ -387,8 +387,8 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
// Don't fatally error.
// This will allow us to ship configs including optional
// runtimes that might not be installed (crun, kata).
- // Only a warnf so default configs don't spec errors.
- logrus.Warnf("Error initializing configured OCI runtime %s: %v", name, err)
+ // Only a infof so default configs don't spec errors.
+ logrus.Infof("Error initializing configured OCI runtime %s: %v", name, err)
continue
}
@@ -472,7 +472,7 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
// we will need to access the storage.
if os.Geteuid() != 0 {
aliveLock.Unlock() // Unlock to avoid deadlock as BecomeRootInUserNS will reexec.
- pausePid, err := util.GetRootlessPauseProcessPidPath()
+ pausePid, err := util.GetRootlessPauseProcessPidPathGivenDir(runtime.config.Engine.TmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
@@ -538,6 +538,15 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
return nil
}
+// TmpDir gets the current Libpod temporary files directory.
+func (r *Runtime) TmpDir() (string, error) {
+ if !r.valid {
+ return "", define.ErrRuntimeStopped
+ }
+
+ return r.config.Engine.TmpDir, nil
+}
+
// GetConfig returns a copy of the configuration used by the runtime
func (r *Runtime) GetConfig() (*config.Config, error) {
r.lock.RLock()
diff --git a/libpod/runtime_cstorage.go b/libpod/runtime_cstorage.go
index 61fdd42d3..6ee8a9354 100644
--- a/libpod/runtime_cstorage.go
+++ b/libpod/runtime_cstorage.go
@@ -103,7 +103,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error {
if errors.Cause(err) == storage.ErrContainerUnknown {
// Container was removed from under us.
// It's gone, so don't bother erroring.
- logrus.Warnf("Storage for container %s already removed", ctr.ID)
+ logrus.Infof("Storage for container %s already removed", ctr.ID)
return nil
}
return errors.Wrapf(err, "error looking up container %q mounts", idOrName)
@@ -114,7 +114,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error {
} else if _, err := r.store.Unmount(ctr.ID, true); err != nil {
if errors.Cause(err) == storage.ErrContainerUnknown {
// Container again gone, no error
- logrus.Warnf("Storage for container %s already removed", ctr.ID)
+ logrus.Infof("Storage for container %s already removed", ctr.ID)
return nil
}
return errors.Wrapf(err, "error unmounting container %q", idOrName)
@@ -123,7 +123,7 @@ func (r *Runtime) removeStorageContainer(idOrName string, force bool) error {
if err := r.store.DeleteContainer(ctr.ID); err != nil {
if errors.Cause(err) == storage.ErrContainerUnknown {
// Container again gone, no error
- logrus.Warnf("Storage for container %s already removed", ctr.ID)
+ logrus.Infof("Storage for container %s already removed", ctr.ID)
return nil
}
return errors.Wrapf(err, "error removing storage for container %q", idOrName)
diff --git a/libpod/runtime_migrate.go b/libpod/runtime_migrate.go
index 1ad32fe9c..f0f800ef0 100644
--- a/libpod/runtime_migrate.go
+++ b/libpod/runtime_migrate.go
@@ -18,9 +18,9 @@ import (
"github.com/sirupsen/logrus"
)
-func stopPauseProcess() error {
+func (r *Runtime) stopPauseProcess() error {
if rootless.IsRootless() {
- pausePidPath, err := util.GetRootlessPauseProcessPidPath()
+ pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(r.config.Engine.TmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
@@ -98,5 +98,5 @@ func (r *Runtime) migrate(ctx context.Context) error {
}
}
- return stopPauseProcess()
+ return r.stopPauseProcess()
}
diff --git a/libpod/runtime_migrate_unsupported.go b/libpod/runtime_migrate_unsupported.go
index e362cca63..a9d351318 100644
--- a/libpod/runtime_migrate_unsupported.go
+++ b/libpod/runtime_migrate_unsupported.go
@@ -10,6 +10,6 @@ func (r *Runtime) migrate(ctx context.Context) error {
return nil
}
-func stopPauseProcess() error {
+func (r *Runtime) stopPauseProcess() error {
return nil
}
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index 25598ce4d..1eb42660c 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -117,7 +117,7 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (_ *Po
return nil, errors.Errorf("Pods must have an infra container to share namespaces")
}
if pod.HasInfraContainer() && !pod.SharesNamespaces() {
- logrus.Warnf("Pod has an infra container, but shares no namespaces")
+ logrus.Infof("Pod has an infra container, but shares no namespaces")
}
if err := r.state.AddPod(pod); err != nil {
@@ -212,7 +212,7 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool)
// Don't try if we failed to retrieve the cgroup
if err == nil {
if err := conmonCgroup.Update(resLimits); err != nil {
- logrus.Warnf("Error updating pod %s conmon cgroup %s PID limit: %v", p.ID(), conmonCgroupPath, err)
+ logrus.Warnf("Error updating pod %s conmon cgroup PID limit: %v", p.ID(), err)
}
}
}
diff --git a/libpod/shutdown/handler.go b/libpod/shutdown/handler.go
index 87538dec9..f0f228b19 100644
--- a/libpod/shutdown/handler.go
+++ b/libpod/shutdown/handler.go
@@ -11,6 +11,10 @@ import (
)
var (
+ ErrHandlerExists error = errors.New("handler with given name already exists")
+)
+
+var (
stopped bool
sigChan chan os.Signal
cancelChan chan bool
@@ -98,7 +102,7 @@ func Register(name string, handler func(os.Signal) error) error {
}
if _, ok := handlers[name]; ok {
- return errors.Errorf("handler with name %s already exists", name)
+ return ErrHandlerExists
}
handlers[name] = handler
diff --git a/libpod/stats.go b/libpod/stats.go
index e34739626..09d990017 100644
--- a/libpod/stats.go
+++ b/libpod/stats.go
@@ -34,7 +34,7 @@ func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*de
return stats, define.ErrCtrStateInvalid
}
- cgroupPath, err := c.CGroupPath()
+ cgroupPath, err := c.cGroupPath()
if err != nil {
return nil, err
}
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 5886455e7..7a3e5dd84 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -17,6 +17,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
+ "github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -73,7 +74,7 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
func ListContainers(w http.ResponseWriter, r *http.Request) {
@@ -207,7 +208,7 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
}
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
func WaitContainer(w http.ResponseWriter, r *http.Request) {
@@ -215,8 +216,10 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) {
// /{version}/containers/(name)/wait
exitCode, err := utils.WaitContainer(w, r)
if err != nil {
+ logrus.Warnf("failed to wait on container %q: %v", mux.Vars(r)["name"], err)
return
}
+
utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
StatusCode: int(exitCode),
Error: struct {
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go
index 1dd563393..223eb2cd5 100644
--- a/pkg/api/handlers/compat/containers_archive.go
+++ b/pkg/api/handlers/compat/containers_archive.go
@@ -5,24 +5,16 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
- "path/filepath"
- "strings"
-
- "github.com/containers/buildah/copier"
- "github.com/containers/buildah/pkg/chrootuser"
- "github.com/containers/podman/v2/libpod"
- "github.com/containers/podman/v2/libpod/define"
- "github.com/containers/podman/v2/pkg/api/handlers/utils"
- "github.com/containers/storage/pkg/idtools"
- "github.com/opencontainers/runtime-spec/specs-go"
-
"net/http"
"os"
"time"
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/copy"
"github.com/gorilla/schema"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
func Archive(w http.ResponseWriter, r *http.Request) {
@@ -32,14 +24,14 @@ func Archive(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPut:
handlePut(w, r, decoder, runtime)
- case http.MethodGet, http.MethodHead:
- handleHeadOrGet(w, r, decoder, runtime)
+ case http.MethodHead, http.MethodGet:
+ handleHeadAndGet(w, r, decoder, runtime)
default:
- utils.Error(w, fmt.Sprintf("not implemented, method: %v", r.Method), http.StatusNotImplemented, errors.New(fmt.Sprintf("not implemented, method: %v", r.Method)))
+ utils.Error(w, fmt.Sprintf("unsupported method: %v", r.Method), http.StatusNotImplemented, errors.New(fmt.Sprintf("unsupported method: %v", r.Method)))
}
}
-func handleHeadOrGet(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
+func handleHeadAndGet(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
query := struct {
Path string `schema:"path"`
}{}
@@ -66,170 +58,62 @@ func handleHeadOrGet(w http.ResponseWriter, r *http.Request, decoder *schema.Dec
return
}
- mountPoint, err := ctr.Mount()
+ source, err := copy.CopyItemForContainer(ctr, query.Path, true, true)
+ defer source.CleanUp()
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to mount the container"))
+ utils.Error(w, "Not found.", http.StatusNotFound, errors.Wrapf(err, "error stating container path %q", query.Path))
return
}
- defer func() {
- if err := ctr.Unmount(false); err != nil {
- logrus.Warnf("failed to unmount container %s: %q", containerName, err)
- }
- }()
-
- opts := copier.StatOptions{}
-
- mountPoint, path, err := fixUpMountPointAndPath(runtime, ctr, mountPoint, query.Path)
+ // NOTE: Docker always sets the header.
+ info, err := source.Stat()
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ utils.Error(w, "Not found.", http.StatusNotFound, errors.Wrapf(err, "error stating container path %q", query.Path))
return
}
-
- stats, err := copier.Stat(mountPoint, "", opts, []string{filepath.Join(mountPoint, path)})
+ statHeader, err := fileInfoToDockerStats(info)
if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to get stats about file"))
- return
- }
-
- if len(stats) <= 0 || len(stats[0].Globbed) <= 0 {
- errs := make([]string, 0, len(stats))
-
- for _, stat := range stats {
- if stat.Error != "" {
- errs = append(errs, stat.Error)
- }
- }
-
- utils.Error(w, "Not found.", http.StatusNotFound, fmt.Errorf("file doesn't exist (errs: %q)", strings.Join(errs, ";")))
-
- return
- }
-
- statHeader, err := statsToHeader(stats[0].Results[stats[0].Globbed[0]])
- if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
-
w.Header().Add("X-Docker-Container-Path-Stat", statHeader)
- if r.Method == http.MethodGet {
- idMappingOpts, err := ctr.IDMappings()
- if err != nil {
- utils.Error(w, "Not found.", http.StatusInternalServerError,
- errors.Wrapf(err, "error getting IDMappingOptions"))
- return
- }
-
- destOwner := idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()}
-
- opts := copier.GetOptions{
- UIDMap: idMappingOpts.UIDMap,
- GIDMap: idMappingOpts.GIDMap,
- ChownDirs: &destOwner,
- ChownFiles: &destOwner,
- KeepDirectoryNames: true,
- }
-
- w.WriteHeader(http.StatusOK)
-
- err = copier.Get(mountPoint, "", opts, []string{filepath.Join(mountPoint, path)}, w)
- if err != nil {
- logrus.Error(errors.Wrapf(err, "failed to copy from the %s container path %s", containerName, query.Path))
- return
- }
- } else {
+ // Our work is done when the user is interested in the header only.
+ if r.Method == http.MethodHead {
w.WriteHeader(http.StatusOK)
- }
-}
-
-func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
- query := struct {
- Path string `schema:"path"`
- // TODO handle params below
- NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"`
- CopyUIDGID bool `schema:"copyUIDGID"`
- }{}
-
- err := decoder.Decode(&query, r.URL.Query())
- if err != nil {
- utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query"))
return
}
- ctrName := utils.GetName(r)
-
- ctr, err := runtime.LookupContainer(ctrName)
- if err != nil {
- utils.Error(w, "Not found", http.StatusNotFound, errors.Wrapf(err, "the %s container doesn't exists", ctrName))
- return
- }
-
- mountPoint, err := ctr.Mount()
- if err != nil {
- utils.Error(w, "Something went wrong", http.StatusInternalServerError, errors.Wrapf(err, "failed to mount the %s container", ctrName))
- return
- }
-
- defer func() {
- if err := ctr.Unmount(false); err != nil {
- logrus.Warnf("failed to unmount container %s", ctrName)
- }
- }()
-
- user, err := getUser(mountPoint, ctr.User())
- if err != nil {
- utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
- return
- }
-
- idMappingOpts, err := ctr.IDMappings()
- if err != nil {
- utils.Error(w, "Something went wrong", http.StatusInternalServerError, errors.Wrapf(err, "error getting IDMappingOptions"))
- return
- }
-
- destOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
-
- opts := copier.PutOptions{
- UIDMap: idMappingOpts.UIDMap,
- GIDMap: idMappingOpts.GIDMap,
- ChownDirs: &destOwner,
- ChownFiles: &destOwner,
- }
-
- mountPoint, path, err := fixUpMountPointAndPath(runtime, ctr, mountPoint, query.Path)
+ // Alright, the users wants data from the container.
+ destination, err := copy.CopyItemForWriter(w)
if err != nil {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
w.WriteHeader(http.StatusOK)
-
- err = copier.Put(mountPoint, filepath.Join(mountPoint, path), opts, r.Body)
- if err != nil {
- logrus.Error(errors.Wrapf(err, "failed to copy to the %s container path %s", ctrName, query.Path))
+ if err := copy.Copy(&source, &destination, false); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
}
-func statsToHeader(stats *copier.StatForItem) (string, error) {
- statsDTO := struct {
+func fileInfoToDockerStats(info *copy.FileInfo) (string, error) {
+ dockerStats := struct {
Name string `json:"name"`
Size int64 `json:"size"`
Mode os.FileMode `json:"mode"`
ModTime time.Time `json:"mtime"`
LinkTarget string `json:"linkTarget"`
}{
- Name: filepath.Base(stats.Name),
- Size: stats.Size,
- Mode: stats.Mode,
- ModTime: stats.ModTime,
- LinkTarget: stats.ImmediateTarget,
+ Name: info.Name,
+ Size: info.Size,
+ Mode: info.Mode,
+ ModTime: info.ModTime,
+ LinkTarget: info.LinkTarget,
}
- jsonBytes, err := json.Marshal(&statsDTO)
+ jsonBytes, err := json.Marshal(&dockerStats)
if err != nil {
return "", errors.Wrap(err, "failed to serialize file stats")
}
@@ -250,130 +134,45 @@ func statsToHeader(stats *copier.StatForItem) (string, error) {
return buff.String(), nil
}
-// the utility functions below are copied from abi/cp.go
-
-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 fixUpMountPointAndPath(runtime *libpod.Runtime, ctr *libpod.Container, mountPoint, ctrPath string) (string, string, error) {
- if !filepath.IsAbs(ctrPath) {
- endsWithSep := strings.HasSuffix(ctrPath, string(filepath.Separator))
- ctrPath = filepath.Join(ctr.WorkingDir(), ctrPath)
-
- if endsWithSep {
- ctrPath = ctrPath + string(filepath.Separator)
- }
- }
- if isVol, volDestName, volName := isVolumeDestName(ctrPath, ctr); isVol { //nolint(gocritic)
- newMountPoint, path, err := pathWithVolumeMount(runtime, volDestName, volName, ctrPath)
- if err != nil {
- return "", "", errors.Wrapf(err, "error getting source path from volume %s", volDestName)
- }
-
- mountPoint = newMountPoint
- ctrPath = path
- } else if isBindMount, mount := isBindMountDestName(ctrPath, ctr); isBindMount { //nolint(gocritic)
- newMountPoint, path := pathWithBindMountSource(mount, ctrPath)
- mountPoint = newMountPoint
- ctrPath = path
- }
-
- return mountPoint, ctrPath, nil
-}
-
-func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) {
- separator := string(os.PathSeparator)
-
- if filepath.IsAbs(path) {
- path = strings.TrimPrefix(path, separator)
- }
-
- if path == "" {
- return false, "", ""
- }
-
- for _, vol := range ctr.Config().NamedVolumes {
- volNamePath := strings.TrimPrefix(vol.Dest, separator)
- if matchVolumePath(path, volNamePath) {
- return true, vol.Dest, vol.Name
- }
- }
-
- return false, "", ""
-}
+func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
+ query := struct {
+ Path string `schema:"path"`
+ // TODO handle params below
+ NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"`
+ CopyUIDGID bool `schema:"copyUIDGID"`
+ }{}
-func pathWithVolumeMount(runtime *libpod.Runtime, volDestName, volName, path string) (string, string, error) {
- destVolume, err := runtime.GetVolume(volName)
+ err := decoder.Decode(&query, r.URL.Query())
if err != nil {
- return "", "", errors.Wrapf(err, "error getting volume destination %s", volName)
- }
-
- if !filepath.IsAbs(path) {
- path = filepath.Join(string(os.PathSeparator), path)
+ utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query"))
+ return
}
- return destVolume.MountPoint(), strings.TrimPrefix(path, volDestName), err
-}
-
-func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) {
- separator := string(os.PathSeparator)
-
- if filepath.IsAbs(path) {
- path = strings.TrimPrefix(path, string(os.PathSeparator))
- }
+ ctrName := utils.GetName(r)
- if path == "" {
- return false, specs.Mount{}
+ ctr, err := runtime.LookupContainer(ctrName)
+ if err != nil {
+ utils.Error(w, "Not found", http.StatusNotFound, errors.Wrapf(err, "the %s container doesn't exists", ctrName))
+ return
}
- for _, m := range ctr.Config().Spec.Mounts {
- if m.Type != "bind" {
- continue
- }
-
- mDest := strings.TrimPrefix(m.Destination, separator)
- if matchVolumePath(path, mDest) {
- return true, m
- }
+ destination, err := copy.CopyItemForContainer(ctr, query.Path, true, false)
+ defer destination.CleanUp()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
}
- return false, specs.Mount{}
-}
-
-func matchVolumePath(path, target string) bool {
- pathStr := filepath.Clean(path)
- target = filepath.Clean(target)
-
- for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) {
- pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))]
+ source, err := copy.CopyItemForReader(r.Body)
+ defer source.CleanUp()
+ if err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
}
- return pathStr == target
-}
-
-func pathWithBindMountSource(m specs.Mount, path string) (string, string) {
- if !filepath.IsAbs(path) {
- path = filepath.Join(string(os.PathSeparator), path)
+ w.WriteHeader(http.StatusOK)
+ if err := copy.Copy(&source, &destination, false); err != nil {
+ utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
+ return
}
-
- return m.Source, strings.TrimPrefix(path, m.Destination)
}
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index 729639928..409a74de2 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -37,6 +37,9 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
return
}
+ // Override the container name in the body struct
+ body.Name = query.Name
+
if len(body.HostConfig.Links) > 0 {
utils.Error(w, utils.ErrLinkNotSupport.Error(), http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter"))
return
@@ -69,9 +72,6 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
return
}
- // Override the container name in the body struct
- body.Name = query.Name
-
ic := abi.ContainerEngine{Libpod: runtime}
report, err := ic.ContainerCreate(r.Context(), sg)
if err != nil {
diff --git a/pkg/api/handlers/compat/containers_pause.go b/pkg/api/handlers/compat/containers_pause.go
index 8712969c0..a7e0a66f1 100644
--- a/pkg/api/handlers/compat/containers_pause.go
+++ b/pkg/api/handlers/compat/containers_pause.go
@@ -24,5 +24,5 @@ func PauseContainer(w http.ResponseWriter, r *http.Request) {
return
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/containers_restart.go b/pkg/api/handlers/compat/containers_restart.go
index f4d8f06a1..e8928596a 100644
--- a/pkg/api/handlers/compat/containers_restart.go
+++ b/pkg/api/handlers/compat/containers_restart.go
@@ -41,5 +41,5 @@ func RestartContainer(w http.ResponseWriter, r *http.Request) {
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/containers_start.go b/pkg/api/handlers/compat/containers_start.go
index 6236b1357..726da6f99 100644
--- a/pkg/api/handlers/compat/containers_start.go
+++ b/pkg/api/handlers/compat/containers_start.go
@@ -39,12 +39,12 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
return
}
if state == define.ContainerStateRunning {
- utils.WriteResponse(w, http.StatusNotModified, "")
+ utils.WriteResponse(w, http.StatusNotModified, nil)
return
}
if err := con.Start(r.Context(), len(con.PodID()) > 0); err != nil {
utils.InternalServerError(w, err)
return
}
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/containers_stop.go b/pkg/api/handlers/compat/containers_stop.go
index 13fe25338..8bc58cf59 100644
--- a/pkg/api/handlers/compat/containers_stop.go
+++ b/pkg/api/handlers/compat/containers_stop.go
@@ -40,7 +40,7 @@ func StopContainer(w http.ResponseWriter, r *http.Request) {
}
// If the Container is stopped already, send a 304
if state == define.ContainerStateStopped || state == define.ContainerStateExited {
- utils.WriteResponse(w, http.StatusNotModified, "")
+ utils.WriteResponse(w, http.StatusNotModified, nil)
return
}
@@ -56,5 +56,5 @@ func StopContainer(w http.ResponseWriter, r *http.Request) {
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/containers_unpause.go b/pkg/api/handlers/compat/containers_unpause.go
index f87b95b64..760e85814 100644
--- a/pkg/api/handlers/compat/containers_unpause.go
+++ b/pkg/api/handlers/compat/containers_unpause.go
@@ -24,5 +24,5 @@ func UnpauseContainer(w http.ResponseWriter, r *http.Request) {
}
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index 149050209..43478c1d3 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -263,7 +263,7 @@ loop:
failed = true
m.Error = string(e)
if err := enc.Encode(m); err != nil {
- logrus.Warnf("Failed to json encode error %q", err.Error())
+ logrus.Warnf("Failed to json encode error %v", err)
}
flush()
case <-runCtx.Done():
@@ -271,7 +271,7 @@ loop:
if !utils.IsLibpodRequest(r) {
m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID)
if err := enc.Encode(m); err != nil {
- logrus.Warnf("Failed to json encode error %q", err.Error())
+ logrus.Warnf("Failed to json encode error %v", err)
}
flush()
}
diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go
index 12593a68c..0f3da53e8 100644
--- a/pkg/api/handlers/compat/images_push.go
+++ b/pkg/api/handlers/compat/images_push.go
@@ -81,5 +81,4 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, "")
-
}
diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go
index b4f3aa2f1..fe13971b0 100644
--- a/pkg/api/handlers/compat/networks.go
+++ b/pkg/api/handlers/compat/networks.go
@@ -271,11 +271,16 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
return
}
+ net, err := getNetworkResourceByNameOrID(name, runtime, nil)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
body := struct {
Id string
Warning []string
}{
- Id: name,
+ Id: net.ID,
}
utils.WriteResponse(w, http.StatusCreated, body)
}
@@ -320,7 +325,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) {
return
}
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
// Connect adds a container to a network
diff --git a/pkg/api/handlers/compat/ping.go b/pkg/api/handlers/compat/ping.go
index 9f6611b30..5513e902e 100644
--- a/pkg/api/handlers/compat/ping.go
+++ b/pkg/api/handlers/compat/ping.go
@@ -15,6 +15,7 @@ import (
func Ping(w http.ResponseWriter, r *http.Request) {
// Note API-Version and Libpod-API-Version are set in handler_api.go
w.Header().Set("BuildKit-Version", "")
+ w.Header().Set("Builder-Version", "")
w.Header().Set("Docker-Experimental", "true")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Pragma", "no-cache")
diff --git a/pkg/api/handlers/compat/system.go b/pkg/api/handlers/compat/system.go
index 322bfa7ed..e21ae160a 100644
--- a/pkg/api/handlers/compat/system.go
+++ b/pkg/api/handlers/compat/system.go
@@ -2,17 +2,91 @@ package compat
import (
"net/http"
+ "strings"
+ "github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/pkg/api/handlers"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/domain/infra/abi"
docker "github.com/docker/docker/api/types"
)
func GetDiskUsage(w http.ResponseWriter, r *http.Request) {
+ options := entities.SystemDfOptions{}
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ ic := abi.ContainerEngine{Libpod: runtime}
+ df, err := ic.SystemDf(r.Context(), options)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ }
+
+ imgs := make([]*docker.ImageSummary, len(df.Images))
+ for i, o := range df.Images {
+ t := docker.ImageSummary{
+ Containers: int64(o.Containers),
+ Created: o.Created.Unix(),
+ ID: o.ImageID,
+ Labels: map[string]string{},
+ ParentID: "",
+ RepoDigests: nil,
+ RepoTags: []string{o.Tag},
+ SharedSize: o.SharedSize,
+ Size: o.Size,
+ VirtualSize: o.Size - o.UniqueSize,
+ }
+ imgs[i] = &t
+ }
+
+ ctnrs := make([]*docker.Container, len(df.Containers))
+ for i, o := range df.Containers {
+ t := docker.Container{
+ ID: o.ContainerID,
+ Names: []string{o.Names},
+ Image: o.Image,
+ ImageID: o.Image,
+ Command: strings.Join(o.Command, " "),
+ Created: o.Created.Unix(),
+ Ports: nil,
+ SizeRw: o.RWSize,
+ SizeRootFs: o.Size,
+ Labels: map[string]string{},
+ State: o.Status,
+ Status: o.Status,
+ HostConfig: struct {
+ NetworkMode string `json:",omitempty"`
+ }{},
+ NetworkSettings: nil,
+ Mounts: nil,
+ }
+ ctnrs[i] = &t
+ }
+
+ vols := make([]*docker.Volume, len(df.Volumes))
+ for i, o := range df.Volumes {
+ t := docker.Volume{
+ CreatedAt: "",
+ Driver: "",
+ Labels: map[string]string{},
+ Mountpoint: "",
+ Name: o.VolumeName,
+ Options: nil,
+ Scope: "local",
+ Status: nil,
+ UsageData: &docker.VolumeUsageData{
+ RefCount: 1,
+ Size: o.Size,
+ },
+ }
+ vols[i] = &t
+ }
+
utils.WriteResponse(w, http.StatusOK, handlers.DiskUsage{DiskUsage: docker.DiskUsage{
- LayersSize: 0,
- Images: nil,
- Containers: nil,
- Volumes: nil,
+ LayersSize: 0,
+ Images: imgs,
+ Containers: ctnrs,
+ Volumes: vols,
+ BuildCache: []*docker.BuildCache{},
+ BuilderSize: 0,
}})
}
diff --git a/pkg/api/handlers/compat/volumes.go b/pkg/api/handlers/compat/volumes.go
index a3c9fbd2f..71b848932 100644
--- a/pkg/api/handlers/compat/volumes.go
+++ b/pkg/api/handlers/compat/volumes.go
@@ -223,7 +223,7 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) {
}
} else {
// Success
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
} else {
if !query.Force {
@@ -232,7 +232,7 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) {
// Volume does not exist and `force` is truthy - this emulates what
// Docker would do when told to `force` removal of a nonextant
// volume
- utils.WriteResponse(w, http.StatusNoContent, "")
+ utils.WriteResponse(w, http.StatusNoContent, nil)
}
}
}
diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go
index 33bb75391..b3b8c1f16 100644
--- a/pkg/api/handlers/libpod/generate.go
+++ b/pkg/api/handlers/libpod/generate.go
@@ -60,7 +60,8 @@ func GenerateKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
- Service bool `schema:"service"`
+ Names []string `schema:"names"`
+ Service bool `schema:"service"`
}{
// Defaults would go here.
}
@@ -73,7 +74,7 @@ func GenerateKube(w http.ResponseWriter, r *http.Request) {
containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.GenerateKubeOptions{Service: query.Service}
- report, err := containerEngine.GenerateKube(r.Context(), utils.GetName(r), options)
+ report, err := containerEngine.GenerateKube(r.Context(), query.Names, options)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error generating YAML"))
return
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 40cf16807..c9adde09d 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -145,13 +145,14 @@ type PodCreateConfig struct {
Share string `json:"share"`
}
+// HistoryResponse provides details on image layers
type HistoryResponse struct {
- ID string `json:"Id"`
- Created int64 `json:"Created"`
- CreatedBy string `json:"CreatedBy"`
- Tags []string `json:"Tags"`
- Size int64 `json:"Size"`
- Comment string `json:"Comment"`
+ ID string `json:"Id"`
+ Created int64
+ CreatedBy string
+ Tags []string
+ Size int64
+ Comment string
}
type ImageLayer struct{}
@@ -177,55 +178,34 @@ type ExecStartConfig struct {
}
func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
- containers, err := l.Containers()
- if err != nil {
- return nil, errors.Wrapf(err, "failed to obtain Containers for image %s", l.ID())
- }
- containerCount := len(containers)
-
- // FIXME: GetParent() panics
- // parent, err := l.GetParent(context.TODO())
- // if err != nil {
- // return nil, errors.Wrapf(err, "failed to obtain ParentID for image %s", l.ID())
- // }
-
- labels, err := l.Labels(context.TODO())
- if err != nil {
- return nil, errors.Wrapf(err, "failed to obtain Labels for image %s", l.ID())
- }
-
- size, err := l.Size(context.TODO())
+ imageData, err := l.Inspect(context.TODO())
if err != nil {
- return nil, errors.Wrapf(err, "failed to obtain Size for image %s", l.ID())
+ return nil, errors.Wrapf(err, "failed to obtain summary for image %s", l.ID())
}
- repoTags, err := l.RepoTags()
+ containers, err := l.Containers()
if err != nil {
- return nil, errors.Wrapf(err, "failed to obtain RepoTags for image %s", l.ID())
- }
-
- digests := make([]string, len(l.Digests()))
- for i, d := range l.Digests() {
- digests[i] = string(d)
+ return nil, errors.Wrapf(err, "failed to obtain Containers for image %s", l.ID())
}
+ containerCount := len(containers)
is := entities.ImageSummary{
ID: l.ID(),
- ParentId: l.Parent,
- RepoTags: repoTags,
+ ParentId: imageData.Parent,
+ RepoTags: imageData.RepoTags,
+ RepoDigests: imageData.RepoDigests,
Created: l.Created().Unix(),
- Size: int64(*size),
+ Size: imageData.Size,
SharedSize: 0,
- VirtualSize: l.VirtualSize,
- Labels: labels,
+ VirtualSize: imageData.VirtualSize,
+ Labels: imageData.Labels,
Containers: containerCount,
ReadOnly: l.IsReadOnly(),
Dangling: l.Dangling(),
Names: l.Names(),
- Digest: string(l.Digest()),
- Digests: digests,
+ Digest: string(imageData.Digest),
ConfigDigest: string(l.ConfigDigest),
- History: l.NamesHistory(),
+ History: imageData.NamesHistory,
}
return &is, nil
}
@@ -282,8 +262,8 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI
}
}
dockerImageInspect := docker.ImageInspect{
- Architecture: l.Architecture,
- Author: l.Author,
+ Architecture: info.Architecture,
+ Author: info.Author,
Comment: info.Comment,
Config: &config,
Created: l.Created().Format(time.RFC3339Nano),
@@ -291,9 +271,9 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI
GraphDriver: docker.GraphDriverData{},
ID: fmt.Sprintf("sha256:%s", l.ID()),
Metadata: docker.ImageMetadata{},
- Os: l.Os,
- OsVersion: l.Version,
- Parent: l.Parent,
+ Os: info.Os,
+ OsVersion: info.Version,
+ Parent: info.Parent,
RepoDigests: info.RepoDigests,
RepoTags: info.RepoTags,
RootFS: rootfs,
@@ -329,7 +309,6 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI
dockerImageInspect.Parent = d.Parent.String()
}
return &ImageInspect{dockerImageInspect}, nil
-
}
// portsToPortSet converts libpods exposed ports to dockers structs
diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go
index 60e5b03f7..bce5484ab 100644
--- a/pkg/api/server/register_generate.go
+++ b/pkg/api/server/register_generate.go
@@ -70,7 +70,7 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error {
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/generate/{name:.*}/systemd"), s.APIHandler(libpod.GenerateSystemd)).Methods(http.MethodGet)
- // swagger:operation GET /libpod/generate/{name:.*}/kube libpod libpodGenerateKube
+ // swagger:operation GET /libpod/generate/kube libpod libpodGenerateKube
// ---
// tags:
// - containers
@@ -78,9 +78,11 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error {
// summary: Generate a Kubernetes YAML file.
// description: Generate Kubernetes YAML based on a pod or container.
// parameters:
- // - in: path
- // name: name:.*
- // type: string
+ // - in: query
+ // name: names
+ // type: array
+ // items:
+ // type: string
// required: true
// description: Name or ID of the container or pod.
// - in: query
@@ -98,6 +100,6 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error {
// format: binary
// 500:
// $ref: "#/responses/InternalError"
- r.HandleFunc(VersionedPath("/libpod/generate/{name:.*}/kube"), s.APIHandler(libpod.GenerateKube)).Methods(http.MethodGet)
+ r.HandleFunc(VersionedPath("/libpod/generate/kube"), s.APIHandler(libpod.GenerateKube)).Methods(http.MethodGet)
return nil
}
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index 31435ae91..f2cb3147c 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -152,7 +152,7 @@ func pingNewConnection(ctx context.Context) error {
return err
}
// the ping endpoint sits at / in this case
- response, err := client.DoRequest(nil, http.MethodGet, "../../../_ping", nil, nil)
+ response, err := client.DoRequest(nil, http.MethodGet, "/_ping", nil, nil)
if err != nil {
return err
}
@@ -207,11 +207,11 @@ func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) (
authMethods = append(authMethods, ssh.Password(pw))
}
if len(authMethods) == 0 {
- pass, err := terminal.ReadPassword("Login password:")
- if err != nil {
- return Connection{}, err
+ callback := func() (string, error) {
+ pass, err := terminal.ReadPassword("Login password:")
+ return string(pass), err
}
- authMethods = append(authMethods, ssh.Password(string(pass)))
+ authMethods = append(authMethods, ssh.PasswordCallback(callback))
}
port := _url.Port()
diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go
index 7b321af93..91b155fc4 100644
--- a/pkg/bindings/containers/attach.go
+++ b/pkg/bindings/containers/attach.go
@@ -332,7 +332,7 @@ func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, i
case <-winChange:
h, w, err := terminal.GetSize(int(file.Fd()))
if err != nil {
- logrus.Warnf("failed to obtain TTY size: " + err.Error())
+ logrus.Warnf("failed to obtain TTY size: %v", err)
}
var resizeErr error
@@ -342,7 +342,7 @@ func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, i
resizeErr = ResizeContainerTTY(ctx, id, &h, &w)
}
if resizeErr != nil {
- logrus.Warnf("failed to resize TTY: " + resizeErr.Error())
+ logrus.Warnf("failed to resize TTY: %v", err)
}
}
}
diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go
index dde1cc29c..8d0146ec1 100644
--- a/pkg/bindings/generate/generate.go
+++ b/pkg/bindings/generate/generate.go
@@ -2,6 +2,7 @@ package generate
import (
"context"
+ "errors"
"net/http"
"net/url"
"strconv"
@@ -37,15 +38,21 @@ func Systemd(ctx context.Context, nameOrID string, options entities.GenerateSyst
return report, response.Process(&report.Units)
}
-func Kube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
+func Kube(ctx context.Context, nameOrIDs []string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
+ if len(nameOrIDs) < 1 {
+ return nil, errors.New("must provide the name or ID of one container or pod")
+ }
params := url.Values{}
+ for _, name := range nameOrIDs {
+ params.Add("names", name)
+ }
params.Set("service", strconv.FormatBool(options.Service))
- response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/kube", params, nil, nameOrID)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/generate/kube", params, nil)
if err != nil {
return nil, err
}
diff --git a/pkg/copy/copy.go b/pkg/copy/copy.go
new file mode 100644
index 000000000..0e68eb450
--- /dev/null
+++ b/pkg/copy/copy.go
@@ -0,0 +1,188 @@
+package copy
+
+import (
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ buildahCopiah "github.com/containers/buildah/copier"
+ "github.com/containers/storage/pkg/archive"
+ securejoin "github.com/cyphar/filepath-securejoin"
+ "github.com/pkg/errors"
+)
+
+// ********************************* NOTE *************************************
+//
+// Most security bugs are caused by attackers playing around with symlinks
+// trying to escape from the container onto the host and/or trick into data
+// corruption on the host. Hence, file operations on containers (including
+// *stat) should always be handled by `github.com/containers/buildah/copier`
+// which makes sure to evaluate files in a chroot'ed environment.
+//
+// Please make sure to add verbose comments when changing code to make the
+// lives of future readers easier.
+//
+// ****************************************************************************
+
+// Copy the source item to destination. Use extract to untar the source if
+// it's a tar archive.
+func Copy(source *CopyItem, destination *CopyItem, extract bool) error {
+ // First, do the man-page dance. See podman-cp(1) for details.
+ if err := enforceCopyRules(source, destination); err != nil {
+ return err
+ }
+
+ // Destination is a stream (e.g., stdout or an http body).
+ if destination.info.IsStream {
+ // Source is a stream (e.g., stdin or an http body).
+ if source.info.IsStream {
+ _, err := io.Copy(destination.writer, source.reader)
+ return err
+ }
+ root, glob, err := source.buildahGlobs()
+ if err != nil {
+ return err
+ }
+ return buildahCopiah.Get(root, "", source.getOptions(), []string{glob}, destination.writer)
+ }
+
+ // Destination is either a file or a directory.
+ if source.info.IsStream {
+ return buildahCopiah.Put(destination.root, destination.resolved, source.putOptions(), source.reader)
+ }
+
+ tarOptions := &archive.TarOptions{
+ Compression: archive.Uncompressed,
+ CopyPass: true,
+ }
+
+ root := destination.root
+ dir := destination.resolved
+ if !source.info.IsDir {
+ // When copying a file, make sure to rename the
+ // destination base path.
+ nameMap := make(map[string]string)
+ nameMap[filepath.Base(source.resolved)] = filepath.Base(destination.resolved)
+ tarOptions.RebaseNames = nameMap
+ dir = filepath.Dir(dir)
+ }
+
+ var tarReader io.ReadCloser
+ if extract && archive.IsArchivePath(source.resolved) {
+ if !destination.info.IsDir {
+ return errors.Errorf("cannot extract archive %q to file %q", source.original, destination.original)
+ }
+
+ reader, err := os.Open(source.resolved)
+ if err != nil {
+ return err
+ }
+ defer reader.Close()
+
+ // The stream from stdin may be compressed (e.g., via gzip).
+ decompressedStream, err := archive.DecompressStream(reader)
+ if err != nil {
+ return err
+ }
+
+ defer decompressedStream.Close()
+ tarReader = decompressedStream
+ } else {
+ reader, err := archive.TarWithOptions(source.resolved, tarOptions)
+ if err != nil {
+ return err
+ }
+ defer reader.Close()
+ tarReader = reader
+ }
+
+ return buildahCopiah.Put(root, dir, source.putOptions(), tarReader)
+}
+
+// enforceCopyRules enforces the rules for copying from a source to a
+// destination as mentioned in the podman-cp(1) man page. Please refer to the
+// man page and/or the inline comments for further details. Note that source
+// and destination are passed by reference and the their data may be changed.
+func enforceCopyRules(source, destination *CopyItem) error {
+ if source.statError != nil {
+ return source.statError
+ }
+
+ // We can copy everything to a stream.
+ if destination.info.IsStream {
+ return nil
+ }
+
+ // Source is a *stream*.
+ if source.info.IsStream {
+ if !(destination.info.IsDir || destination.info.IsStream) {
+ return errors.New("destination must be a directory or stream when copying from a stream")
+ }
+ return nil
+ }
+
+ // Source is a *directory*.
+ if source.info.IsDir {
+ if destination.statError != nil {
+ // It's okay if the destination does not exist. We
+ // made sure before that it's parent exists, so it
+ // would be created while copying.
+ if os.IsNotExist(destination.statError) {
+ return nil
+ }
+ // Could be a permission error.
+ return destination.statError
+ }
+
+ // If the destination exists and is not a directory, we have a
+ // problem.
+ if !destination.info.IsDir {
+ return errors.Errorf("cannot copy directory %q to file %q", source.original, destination.original)
+ }
+
+ // If the destination exists and is a directory, we need to
+ // append the source base directory to it. This makes sure
+ // that copying "/foo/bar" "/tmp" will copy to "/tmp/bar" (and
+ // not "/tmp").
+ newDestination, err := securejoin.SecureJoin(destination.resolved, filepath.Base(source.resolved))
+ if err != nil {
+ return err
+ }
+ destination.resolved = newDestination
+ return nil
+ }
+
+ // Source is a *file*.
+ if destination.statError != nil {
+ // It's okay if the destination does not exist, unless it ends
+ // with "/".
+ if !os.IsNotExist(destination.statError) {
+ return destination.statError
+ } else if strings.HasSuffix(destination.resolved, "/") {
+ // Note: this is practically unreachable code as the
+ // existence of parent directories is enforced early
+ // on. It's left here as an extra security net.
+ return errors.Errorf("destination directory %q must exist (trailing %q)", destination.original, "/")
+ }
+ // Does not exist and does not end with "/".
+ return nil
+ }
+
+ // If the destination is a file, we're good. We will overwrite the
+ // contents while copying.
+ if !destination.info.IsDir {
+ return nil
+ }
+
+ // If the destination exists and is a directory, we need to append the
+ // source base directory to it. This makes sure that copying
+ // "/foo/bar" "/tmp" will copy to "/tmp/bar" (and not "/tmp").
+ newDestination, err := securejoin.SecureJoin(destination.resolved, filepath.Base(source.resolved))
+ if err != nil {
+ return err
+ }
+
+ destination.resolved = newDestination
+ return nil
+}
diff --git a/pkg/copy/item.go b/pkg/copy/item.go
new file mode 100644
index 000000000..db6bca610
--- /dev/null
+++ b/pkg/copy/item.go
@@ -0,0 +1,601 @@
+package copy
+
+import (
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ buildahCopiah "github.com/containers/buildah/copier"
+ "github.com/containers/buildah/pkg/chrootuser"
+ "github.com/containers/buildah/util"
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/pkg/cgroups"
+ "github.com/containers/podman/v2/pkg/rootless"
+ "github.com/containers/storage"
+ "github.com/containers/storage/pkg/archive"
+ "github.com/containers/storage/pkg/idtools"
+ securejoin "github.com/cyphar/filepath-securejoin"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// ********************************* NOTE *************************************
+//
+// Most security bugs are caused by attackers playing around with symlinks
+// trying to escape from the container onto the host and/or trick into data
+// corruption on the host. Hence, file operations on containers (including
+// *stat) should always be handled by `github.com/containers/buildah/copier`
+// which makes sure to evaluate files in a chroot'ed environment.
+//
+// Please make sure to add verbose comments when changing code to make the
+// lives of future readers easier.
+//
+// ****************************************************************************
+
+var (
+ _stdin = os.Stdin.Name()
+ _stdout = os.Stdout.Name()
+)
+
+// CopyItem is the source or destination of a copy operation. Use the
+// CopyItemFrom* functions to create one for the specific source/destination
+// item.
+type CopyItem struct {
+ // The original path provided by the caller. Useful in error messages.
+ original string
+ // The resolved path on the host or container. Maybe altered at
+ // multiple stages when copying.
+ resolved string
+ // The root for copying data in a chroot'ed environment.
+ root string
+
+ // IDPair of the resolved path.
+ idPair *idtools.IDPair
+ // Storage ID mappings.
+ idMappings *storage.IDMappingOptions
+
+ // Internal FileInfo. We really don't want users to mess with a
+ // CopyItem but only plug and play with it.
+ info FileInfo
+ // Error when creating the upper FileInfo. Some errors are non-fatal,
+ // for instance, when a destination *base* path does not exist.
+ statError error
+
+ writer io.Writer
+ reader io.Reader
+
+ // Needed to clean up resources (e.g., unmount a container).
+ cleanUpFuncs []deferFunc
+}
+
+// deferFunc allows for returning functions that must be deferred at call sites.
+type deferFunc func()
+
+// FileInfo describes a file or directory and is returned by
+// (*CopyItem).Stat().
+type FileInfo struct {
+ Name string `json:"name"`
+ Size int64 `json:"size"`
+ Mode os.FileMode `json:"mode"`
+ ModTime time.Time `json:"mtime"`
+ IsDir bool `json:"isDir"`
+ IsStream bool `json:"isStream"`
+ LinkTarget string `json:"linkTarget"`
+}
+
+// Stat returns the FileInfo.
+func (item *CopyItem) Stat() (*FileInfo, error) {
+ return &item.info, item.statError
+}
+
+// CleanUp releases resources such as the container mounts. It *must* be
+// called even in case of errors.
+func (item *CopyItem) CleanUp() {
+ for _, f := range item.cleanUpFuncs {
+ f()
+ }
+}
+
+// CopyItemForWriter returns a CopyItem for the specified io.WriteCloser. Note
+// that the returned item can only act as a copy destination.
+func CopyItemForWriter(writer io.Writer) (item CopyItem, _ error) {
+ item.writer = writer
+ item.info.IsStream = true
+ return item, nil
+}
+
+// CopyItemForReader returns a CopyItem for the specified io.ReaderCloser. Note
+// that the returned item can only act as a copy source.
+//
+// Note that the specified reader will be auto-decompressed if needed.
+func CopyItemForReader(reader io.Reader) (item CopyItem, _ error) {
+ item.info.IsStream = true
+ decompressed, err := archive.DecompressStream(reader)
+ if err != nil {
+ return item, err
+ }
+ item.reader = decompressed
+ item.cleanUpFuncs = append(item.cleanUpFuncs, func() {
+ if err := decompressed.Close(); err != nil {
+ logrus.Errorf("Error closing decompressed reader of copy item: %v", err)
+ }
+ })
+ return item, nil
+}
+
+// CopyItemForHost creates a CopyItem for the specified host path. It's a
+// destination by default. Use isSource to set it as a destination.
+//
+// Note that callers *must* call (CopyItem).CleanUp(), even in case of errors.
+func CopyItemForHost(hostPath string, isSource bool) (item CopyItem, _ error) {
+ if hostPath == "-" {
+ if isSource {
+ hostPath = _stdin
+ } else {
+ hostPath = _stdout
+ }
+ }
+
+ if hostPath == _stdin {
+ return CopyItemForReader(os.Stdin)
+ }
+
+ if hostPath == _stdout {
+ return CopyItemForWriter(os.Stdout)
+ }
+
+ // Now do the dance for the host data.
+ resolvedHostPath, err := filepath.Abs(hostPath)
+ if err != nil {
+ return item, err
+ }
+
+ resolvedHostPath = preserveBasePath(hostPath, resolvedHostPath)
+ item.original = hostPath
+ item.resolved = resolvedHostPath
+ item.root = "/"
+
+ statInfo, statError := os.Stat(resolvedHostPath)
+ item.statError = statError
+
+ // It exists, we're done.
+ if statError == nil {
+ item.info.Name = statInfo.Name()
+ item.info.Size = statInfo.Size()
+ item.info.Mode = statInfo.Mode()
+ item.info.ModTime = statInfo.ModTime()
+ item.info.IsDir = statInfo.IsDir()
+ item.info.LinkTarget = resolvedHostPath
+ return item, nil
+ }
+
+ // The source must exist, but let's try to give some human-friendly
+ // errors.
+ if isSource {
+ if os.IsNotExist(item.statError) {
+ return item, errors.Wrapf(os.ErrNotExist, "%q could not be found on the host", hostPath)
+ }
+ return item, item.statError // could be a permission error
+ }
+
+ // If we're a destination, we need to make sure that the parent
+ // directory exists.
+ parent := filepath.Dir(resolvedHostPath)
+ if _, err := os.Stat(parent); err != nil {
+ if os.IsNotExist(err) {
+ return item, errors.Wrapf(os.ErrNotExist, "%q could not be found on the host", parent)
+ }
+ return item, err
+ }
+
+ return item, nil
+}
+
+// CopyItemForContainer creates a CopyItem for the specified path on the
+// container. It's a destination by default. Use isSource to set it as a
+// destination. Note that the container path may resolve to a path outside of
+// the container's mount point if the path hits a volume or mount on the
+// container.
+//
+// Note that callers *must* call (CopyItem).CleanUp(), even in case of errors.
+func CopyItemForContainer(container *libpod.Container, containerPath string, pause bool, isSource bool) (item CopyItem, _ error) {
+ // Mount and pause the container.
+ containerMountPoint, err := item.mountAndPauseContainer(container, pause)
+ if err != nil {
+ return item, err
+ }
+
+ // Make sure that "/" copies the *contents* of the mount point and not
+ // the directory.
+ if containerPath == "/" {
+ containerPath += "/."
+ }
+
+ // Now resolve the container's path. It may hit a volume, it may hit a
+ // bind mount, it may be relative.
+ resolvedRoot, resolvedContainerPath, err := resolveContainerPaths(container, containerMountPoint, containerPath)
+ if err != nil {
+ return item, err
+ }
+ resolvedContainerPath = preserveBasePath(containerPath, resolvedContainerPath)
+
+ idMappings, idPair, err := getIDMappingsAndPair(container, containerMountPoint)
+ if err != nil {
+ return item, err
+ }
+
+ item.original = containerPath
+ item.resolved = resolvedContainerPath
+ item.root = resolvedRoot
+ item.idMappings = idMappings
+ item.idPair = idPair
+
+ statInfo, statError := secureStat(resolvedRoot, resolvedContainerPath)
+ item.statError = statError
+
+ // It exists, we're done.
+ if statError == nil {
+ item.info.IsDir = statInfo.IsDir
+ item.info.Name = filepath.Base(statInfo.Name)
+ item.info.Size = statInfo.Size
+ item.info.Mode = statInfo.Mode
+ item.info.ModTime = statInfo.ModTime
+ item.info.IsDir = statInfo.IsDir
+ item.info.LinkTarget = resolvedContainerPath
+ return item, nil
+ }
+
+ // The source must exist, but let's try to give some human-friendly
+ // errors.
+ if isSource {
+ if os.IsNotExist(statError) {
+ return item, errors.Wrapf(os.ErrNotExist, "%q could not be found on container %s (resolved to %q)", containerPath, container.ID(), resolvedContainerPath)
+ }
+ return item, item.statError // could be a permission error
+ }
+
+ // If we're a destination, we need to make sure that the parent
+ // directory exists.
+ parent := filepath.Dir(resolvedContainerPath)
+ if _, err := secureStat(resolvedRoot, parent); err != nil {
+ if os.IsNotExist(err) {
+ return item, errors.Wrapf(os.ErrNotExist, "%q could not be found on container %s (resolved to %q)", containerPath, container.ID(), resolvedContainerPath)
+ }
+ return item, err
+ }
+
+ return item, nil
+}
+
+// putOptions returns PUT options for buildah's copier package.
+func (item *CopyItem) putOptions() buildahCopiah.PutOptions {
+ options := buildahCopiah.PutOptions{}
+ if item.idMappings != nil {
+ options.UIDMap = item.idMappings.UIDMap
+ options.GIDMap = item.idMappings.GIDMap
+ }
+ if item.idPair != nil {
+ options.ChownDirs = item.idPair
+ options.ChownFiles = item.idPair
+ }
+ return options
+}
+
+// getOptions returns GET options for buildah's copier package.
+func (item *CopyItem) getOptions() buildahCopiah.GetOptions {
+ options := buildahCopiah.GetOptions{}
+ if item.idMappings != nil {
+ options.UIDMap = item.idMappings.UIDMap
+ options.GIDMap = item.idMappings.GIDMap
+ }
+ if item.idPair != nil {
+ options.ChownDirs = item.idPair
+ options.ChownFiles = item.idPair
+ }
+ return options
+
+}
+
+// mount and pause the container. Also set the item's cleanUpFuncs. Those
+// *must* be invoked by callers, even in case of errors.
+func (item *CopyItem) mountAndPauseContainer(container *libpod.Container, pause bool) (string, error) {
+ // Make sure to pause and unpause the container. We cannot pause on
+ // cgroupsv1 as rootless user, in which case we turn off pausing.
+ if pause && rootless.IsRootless() {
+ cgroupv2, _ := cgroups.IsCgroup2UnifiedMode()
+ if !cgroupv2 {
+ logrus.Debugf("Cannot pause container for copying as a rootless user on cgroupsv1: default to not pause")
+ pause = false
+ }
+ }
+
+ // Mount and unmount the container.
+ mountPoint, err := container.Mount()
+ if err != nil {
+ return "", err
+ }
+
+ item.cleanUpFuncs = append(item.cleanUpFuncs, func() {
+ if err := container.Unmount(false); err != nil {
+ logrus.Errorf("Error unmounting container after copy operation: %v", err)
+ }
+ })
+
+ // Pause and unpause the container.
+ if pause {
+ if err := container.Pause(); err != nil {
+ // Ignore errors when the container isn't running. No
+ // need to pause.
+ if errors.Cause(err) != define.ErrCtrStateInvalid {
+ return "", err
+ }
+ } else {
+ item.cleanUpFuncs = append(item.cleanUpFuncs, func() {
+ if err := container.Unpause(); err != nil {
+ logrus.Errorf("Error unpausing container after copy operation: %v", err)
+ }
+ })
+ }
+ }
+
+ return mountPoint, nil
+}
+
+// buildahGlobs returns the root, dir and glob used in buildah's copier
+// package.
+//
+// Note that dir is always empty.
+func (item *CopyItem) buildahGlobs() (root string, glob string, err error) {
+ root = item.root
+
+ // If the root and the resolved path are equal, then dir must be empty
+ // and the glob must be ".".
+ if filepath.Clean(root) == filepath.Clean(item.resolved) {
+ glob = "."
+ return
+ }
+
+ glob, err = filepath.Rel(root, item.resolved)
+ return
+}
+
+// preserveBasePath makes sure that the original base path (e.g., "/" or "./")
+// is preserved. The filepath API among tends to clean up a bit too much but
+// we *must* preserve this data by all means.
+func preserveBasePath(original, resolved string) string {
+ // Handle "/"
+ if strings.HasSuffix(original, "/") {
+ if !strings.HasSuffix(resolved, "/") {
+ resolved += "/"
+ }
+ return resolved
+ }
+
+ // Handle "/."
+ if strings.HasSuffix(original, "/.") {
+ if strings.HasSuffix(resolved, "/") { // could be root!
+ resolved += "."
+ } else if !strings.HasSuffix(resolved, "/.") {
+ resolved += "/."
+ }
+ return resolved
+ }
+
+ return resolved
+}
+
+// secureStat extracts file info for path in a chroot'ed environment in root.
+func secureStat(root string, path string) (*buildahCopiah.StatForItem, error) {
+ var glob string
+ var err error
+
+ // If root and path are equal, then dir must be empty and the glob must
+ // be ".".
+ if filepath.Clean(root) == filepath.Clean(path) {
+ glob = "."
+ } else {
+ glob, err = filepath.Rel(root, path)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ globStats, err := buildahCopiah.Stat(root, "", buildahCopiah.StatOptions{}, []string{glob})
+ if err != nil {
+ return nil, err
+ }
+
+ if len(globStats) != 1 {
+ return nil, errors.Errorf("internal libpod 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 stat, os.ErrNotExist
+ }
+
+ var statErr error
+ if stat.Error != "" {
+ statErr = errors.New(stat.Error)
+ }
+ return stat, statErr
+}
+
+// resolveContainerPaths resolves the container's mount point and the container
+// path as specified by the user. Both may resolve to paths outside of the
+// container's mount point when the container path hits a volume or bind mount.
+//
+// NOTE: We must take volumes and bind mounts into account as, regrettably, we
+// can copy to/from stopped containers. In that case, the volumes and bind
+// mounts are not present. For running containers, the runtime (e.g., runc or
+// crun) takes care of these mounts. For stopped ones, we need to do quite
+// some dance, as done below.
+func resolveContainerPaths(container *libpod.Container, mountPoint string, containerPath string) (string, string, error) {
+ // Let's first make sure we have a path relative to the mount point.
+ pathRelativeToContainerMountPoint := containerPath
+ if !filepath.IsAbs(containerPath) {
+ // If the containerPath is not absolute, it's relative to the
+ // container's working dir. To be extra careful, let's first
+ // join the working dir with "/", and the add the containerPath
+ // to it.
+ pathRelativeToContainerMountPoint = filepath.Join(filepath.Join("/", container.WorkingDir()), containerPath)
+ }
+ // NOTE: the secure join makes sure that we follow symlinks. This way,
+ // we catch scenarios where the container path symlinks to a volume or
+ // bind mount.
+ resolvedPathOnTheContainerMountPoint, err := securejoin.SecureJoin(mountPoint, pathRelativeToContainerMountPoint)
+ if err != nil {
+ return "", "", err
+ }
+ pathRelativeToContainerMountPoint = strings.TrimPrefix(pathRelativeToContainerMountPoint, mountPoint)
+ pathRelativeToContainerMountPoint = filepath.Join("/", pathRelativeToContainerMountPoint)
+
+ // Now we have an "absolute container Path" but not yet resolved on the
+ // host (e.g., "/foo/bar/file.txt"). As mentioned above, we need to
+ // check if "/foo/bar/file.txt" is on a volume or bind mount. To do
+ // that, we need to walk *down* the paths to the root. Assuming
+ // volume-1 is mounted to "/foo" and volume-2 is mounted to "/foo/bar",
+ // we must select "/foo/bar". Once selected, we need to rebase the
+ // remainder (i.e, "/file.txt") on the volume's mount point on the
+ // host. Same applies to bind mounts.
+
+ searchPath := pathRelativeToContainerMountPoint
+ for {
+ volume, err := findVolume(container, searchPath)
+ if err != nil {
+ return "", "", err
+ }
+ if volume != nil {
+ logrus.Debugf("Container path %q resolved to volume %q on path %q", containerPath, volume.Name(), searchPath)
+ // We found a matching volume for searchPath. We now
+ // need to first find the relative path of our input
+ // path to the searchPath, and then join it with the
+ // volume's mount point.
+ pathRelativeToVolume := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
+ absolutePathOnTheVolumeMount, err := securejoin.SecureJoin(volume.MountPoint(), pathRelativeToVolume)
+ if err != nil {
+ return "", "", err
+ }
+ return volume.MountPoint(), absolutePathOnTheVolumeMount, nil
+ }
+
+ if mount := findBindMount(container, searchPath); mount != nil {
+ logrus.Debugf("Container path %q resolved to bind mount %q:%q on path %q", containerPath, mount.Source, mount.Destination, searchPath)
+ // We found a matching bind mount for searchPath. We
+ // now need to first find the relative path of our
+ // input path to the searchPath, and then join it with
+ // the source of the bind mount.
+ pathRelativeToBindMount := strings.TrimPrefix(pathRelativeToContainerMountPoint, searchPath)
+ absolutePathOnTheBindMount, err := securejoin.SecureJoin(mount.Source, pathRelativeToBindMount)
+ if err != nil {
+ return "", "", err
+ }
+ return mount.Source, absolutePathOnTheBindMount, nil
+
+ }
+
+ if searchPath == "/" {
+ // Cannot go beyond "/", so we're done.
+ break
+ }
+
+ // Walk *down* the path (e.g., "/foo/bar/x" -> "/foo/bar").
+ searchPath = filepath.Dir(searchPath)
+ }
+
+ // No volume, no bind mount but just a normal path on the container.
+ return mountPoint, resolvedPathOnTheContainerMountPoint, nil
+}
+
+// findVolume checks if the specified container path matches a volume inside
+// the container. It returns a matching volume or nil.
+func findVolume(c *libpod.Container, containerPath string) (*libpod.Volume, error) {
+ runtime := c.Runtime()
+ cleanedContainerPath := filepath.Clean(containerPath)
+ for _, vol := range c.Config().NamedVolumes {
+ if cleanedContainerPath == filepath.Clean(vol.Dest) {
+ return runtime.GetVolume(vol.Name)
+ }
+ }
+ return nil, nil
+}
+
+// findBindMount checks if the specified container path matches a bind mount
+// inside the container. It returns a matching mount or nil.
+func findBindMount(c *libpod.Container, containerPath string) *specs.Mount {
+ cleanedPath := filepath.Clean(containerPath)
+ for _, m := range c.Config().Spec.Mounts {
+ if m.Type != "bind" {
+ continue
+ }
+ if cleanedPath == filepath.Clean(m.Destination) {
+ mount := m
+ return &mount
+ }
+ }
+ return nil
+}
+
+// getIDMappingsAndPair returns the ID mappings for the container and the host
+// ID pair.
+func getIDMappingsAndPair(container *libpod.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.
+func getContainerUser(container *libpod.Container, mountPoint string) (specs.User, error) {
+ userspec := container.Config().User
+
+ 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
+}
+
+// idtoolsToRuntimeSpec converts idtools ID mapping to the one of the runtime spec.
+func idtoolsToRuntimeSpec(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) {
+ for _, idmap := range idMaps {
+ tempIDMap := specs.LinuxIDMapping{
+ ContainerID: uint32(idmap.ContainerID),
+ HostID: uint32(idmap.HostID),
+ Size: uint32(idmap.Size),
+ }
+ convertedIDMap = append(convertedIDMap, tempIDMap)
+ }
+ return convertedIDMap
+}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 3fd7c79f4..39d679eaf 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -403,16 +403,14 @@ type ContainerPortReport struct {
Ports []ocicni.PortMapping
}
-// ContainerCpOptions describes input options for cp
+// ContainerCpOptions describes input options for cp.
type ContainerCpOptions struct {
- Pause bool
+ // Pause the container while copying.
+ Pause bool
+ // Extract the tarfile into the destination directory.
Extract bool
}
-// ContainerCpReport describes the output from a cp operation
-type ContainerCpReport struct {
-}
-
// ContainerStatsOptions describes input options for getting
// stats on containers
type ContainerStatsOptions struct {
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index a4f6c08e9..5ad475133 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -16,7 +16,7 @@ type ContainerEngine interface {
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error)
ContainerCommit(ctx context.Context, nameOrID string, options CommitOptions) (*CommitReport, error)
- ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) (*ContainerCpReport, error)
+ ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) error
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
ContainerDiff(ctx context.Context, nameOrID string, options DiffOptions) (*DiffReport, error)
ContainerExec(ctx context.Context, nameOrID string, options ExecOptions, streams define.AttachStreams) (int, error)
@@ -46,7 +46,7 @@ type ContainerEngine interface {
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
Events(ctx context.Context, opts EventsOptions) error
GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
- GenerateKube(ctx context.Context, nameOrID string, opts GenerateKubeOptions) (*GenerateKubeReport, error)
+ GenerateKube(ctx context.Context, nameOrIDs []string, opts GenerateKubeOptions) (*GenerateKubeReport, error)
SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error)
HealthCheckRun(ctx context.Context, nameOrID string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error)
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index ab545d882..81f12bff7 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -51,22 +51,22 @@ func (i *Image) Id() string { // nolint
}
type ImageSummary struct {
- ID string `json:"Id"`
- ParentId string // nolint
- RepoTags []string `json:",omitempty"`
+ ID string `json:"Id"`
+ ParentId string // nolint
+ RepoTags []string
+ RepoDigests []string
Created int64
- Size int64 `json:",omitempty"`
- SharedSize int `json:",omitempty"`
- VirtualSize int64 `json:",omitempty"`
- Labels map[string]string `json:",omitempty"`
- Containers int `json:",omitempty"`
- ReadOnly bool `json:",omitempty"`
- Dangling bool `json:",omitempty"`
+ Size int64
+ SharedSize int
+ VirtualSize int64
+ Labels map[string]string
+ Containers int
+ ReadOnly bool `json:",omitempty"`
+ Dangling bool `json:",omitempty"`
// Podman extensions
Names []string `json:",omitempty"`
Digest string `json:",omitempty"`
- Digests []string `json:",omitempty"`
ConfigDigest string `json:",omitempty"`
History []string `json:",omitempty"`
}
diff --git a/pkg/domain/infra/abi/cp.go b/pkg/domain/infra/abi/cp.go
index 8f4f5d3d7..9409df743 100644
--- a/pkg/domain/infra/abi/cp.go
+++ b/pkg/domain/infra/abi/cp.go
@@ -1,195 +1,70 @@
package abi
import (
- "archive/tar"
"context"
- "fmt"
- "io"
- "os"
- "path/filepath"
"strings"
- "github.com/containers/buildah/pkg/chrootuser"
- "github.com/containers/buildah/util"
"github.com/containers/podman/v2/libpod"
- "github.com/containers/podman/v2/libpod/define"
+ "github.com/containers/podman/v2/pkg/copy"
"github.com/containers/podman/v2/pkg/domain/entities"
- "github.com/containers/storage"
- "github.com/containers/storage/pkg/chrootarchive"
- "github.com/containers/storage/pkg/idtools"
- securejoin "github.com/cyphar/filepath-securejoin"
- "github.com/docker/docker/pkg/archive"
- "github.com/opencontainers/go-digest"
- "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
-func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) {
- extract := options.Extract
-
+func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) error {
srcCtr, srcPath := parsePath(ic.Libpod, source)
destCtr, destPath := parsePath(ic.Libpod, dest)
- if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) {
- return nil, errors.Errorf("invalid arguments %s, %s you must use just one container", source, dest)
+ if srcCtr != nil && destCtr != nil {
+ return errors.Errorf("invalid arguments %q, %q: you must use just one container", source, dest)
}
-
- if len(srcPath) == 0 || len(destPath) == 0 {
- return nil, errors.Errorf("invalid arguments %s, %s you must specify paths", source, dest)
+ if srcCtr == nil && destCtr == nil {
+ return errors.Errorf("invalid arguments %q, %q: you must specify one container", source, dest)
}
- ctr := srcCtr
- isFromHostToCtr := ctr == nil
- if isFromHostToCtr {
- ctr = destCtr
+ if len(srcPath) == 0 || len(destPath) == 0 {
+ return errors.Errorf("invalid arguments %q, %q: you must specify paths", source, dest)
}
- mountPoint, err := ctr.Mount()
- if err != nil {
- return nil, err
- }
- defer func() {
- if err := ctr.Unmount(false); err != nil {
- logrus.Errorf("unable to umount container '%s': %q", ctr.ID(), err)
+ var sourceItem, destinationItem copy.CopyItem
+ var err error
+ // Copy from the container to the host.
+ if srcCtr != nil {
+ sourceItem, err = copy.CopyItemForContainer(srcCtr, srcPath, options.Pause, true)
+ defer sourceItem.CleanUp()
+ if err != nil {
+ return err
}
- }()
-
- if options.Pause {
- if err := ctr.Pause(); err != nil {
- // An invalid state error is fine.
- // The container isn't running or is already paused.
- // TODO: We can potentially start the container while
- // the copy is running, which still allows a race where
- // malicious code could mess with the symlink.
- if errors.Cause(err) != define.ErrCtrStateInvalid {
- return nil, err
- }
- } else {
- // Only add the defer if we actually paused
- defer func() {
- if err := ctr.Unpause(); err != nil {
- logrus.Errorf("Error unpausing container after copying: %v", err)
- }
- }()
+ } else {
+ sourceItem, err = copy.CopyItemForHost(srcPath, true)
+ if err != nil {
+ return err
}
}
- user, err := getUser(mountPoint, ctr.User())
- if err != nil {
- return nil, err
- }
- idMappingOpts, err := ctr.IDMappings()
- if err != nil {
- return nil, errors.Wrapf(err, "error getting IDMappingOptions")
- }
- destOwner := 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 nil, err
- }
-
- hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
-
- if isFromHostToCtr {
- if isVol, volDestName, volName := isVolumeDestName(destPath, ctr); isVol { //nolint(gocritic)
- path, err := pathWithVolumeMount(ic.Libpod, volDestName, volName, destPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting destination path from volume %s", volDestName)
- }
- destPath = path
- } else if isBindMount, mount := isBindMountDestName(destPath, ctr); isBindMount { //nolint(gocritic)
- path, err := pathWithBindMountSource(mount, destPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting destination path from bind mount %s", mount.Destination)
- }
- destPath = path
- } else if filepath.IsAbs(destPath) { //nolint(gocritic)
- cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath)
- if err != nil {
- return nil, err
- }
- destPath = cleanedPath
- } else { //nolint(gocritic)
- ctrWorkDir, err := securejoin.SecureJoin(mountPoint, ctr.WorkingDir())
- if err != nil {
- return nil, err
- }
- if err = idtools.MkdirAllAndChownNew(ctrWorkDir, 0755, hostOwner); err != nil {
- return nil, err
- }
- cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), destPath))
- if err != nil {
- return nil, err
- }
- destPath = cleanedPath
+ if destCtr != nil {
+ destinationItem, err = copy.CopyItemForContainer(destCtr, destPath, options.Pause, false)
+ defer destinationItem.CleanUp()
+ if err != nil {
+ return err
}
} else {
- destOwner = idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()}
- if isVol, volDestName, volName := isVolumeDestName(srcPath, ctr); isVol { //nolint(gocritic)
- path, err := pathWithVolumeMount(ic.Libpod, volDestName, volName, srcPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting source path from volume %s", volDestName)
- }
- srcPath = path
- } else if isBindMount, mount := isBindMountDestName(srcPath, ctr); isBindMount { //nolint(gocritic)
- path, err := pathWithBindMountSource(mount, srcPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting source path from bind mount %s", mount.Destination)
- }
- srcPath = path
- } else if filepath.IsAbs(srcPath) { //nolint(gocritic)
- cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath)
- if err != nil {
- return nil, err
- }
- srcPath = cleanedPath
- } else { //nolint(gocritic)
- cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), srcPath))
- if err != nil {
- return nil, err
- }
- srcPath = cleanedPath
- }
- }
-
- if !filepath.IsAbs(destPath) {
- dir, err := os.Getwd()
+ destinationItem, err = copy.CopyItemForHost(destPath, false)
+ defer destinationItem.CleanUp()
if err != nil {
- return nil, errors.Wrapf(err, "err getting current working directory")
+ return err
}
- destPath = filepath.Join(dir, destPath)
}
- if source == "-" {
- srcPath = os.Stdin.Name()
- extract = true
- }
- err = containerCopy(srcPath, destPath, source, dest, idMappingOpts, &destOwner, extract, isFromHostToCtr)
- return &entities.ContainerCpReport{}, err
+ // Copy from the host to the container.
+ return copy.Copy(&sourceItem, &destinationItem, options.Extract)
}
-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,
+func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) {
+ if len(path) == 0 {
+ return nil, ""
}
- 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
- }
-
+ if path[0] == '.' || path[0] == '/' { // A path cannot point to a container.
+ return nil, path
}
- 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])
@@ -199,247 +74,3 @@ func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string)
}
return nil, path
}
-
-func evalSymlinks(path string) (string, error) {
- if path == os.Stdin.Name() {
- return path, nil
- }
- return filepath.EvalSymlinks(path)
-}
-
-func getPathInfo(path string) (string, os.FileInfo, error) {
- path, err := 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, err
- }
- return path, srcfi, nil
-}
-
-func containerCopy(srcPath, destPath, src, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair, extract, isFromHostToCtr bool) error {
- srcPath, err := evalSymlinks(srcPath)
- if err != nil {
- return errors.Wrapf(err, "error evaluating symlinks %q", srcPath)
- }
-
- srcPath, srcfi, err := getPathInfo(srcPath)
- if err != nil {
- return err
- }
-
- filename := filepath.Base(destPath)
- if filename == "-" && !isFromHostToCtr {
- err := streamFileToStdout(srcPath, srcfi)
- if err != nil {
- return errors.Wrapf(err, "error streaming source file %s to Stdout", srcPath)
- }
- return nil
- }
-
- destdir := destPath
- if !srcfi.IsDir() {
- destdir = filepath.Dir(destPath)
- }
- _, err = os.Stat(destdir)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- destDirIsExist := err == nil
- if err = os.MkdirAll(destdir, 0755); err != nil {
- return err
- }
-
- // 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 destDirIsExist && !strings.HasSuffix(src, fmt.Sprintf("%s.", string(os.PathSeparator))) {
- srcPathBase := filepath.Base(srcPath)
- if !isFromHostToCtr {
- pathArr := strings.SplitN(src, ":", 2)
- if len(pathArr) != 2 {
- return errors.Errorf("invalid arguments %s, you must specify source path", src)
- }
- if pathArr[1] == "/" {
- // If `srcPath` is the root directory of the container,
- // `srcPath` will be `.../${sha256_ID}/merged/`, so do not join it
- srcPathBase = ""
- }
- }
- destPath = filepath.Join(destPath, srcPathBase)
- }
- if err = copyWithTar(srcPath, destPath); err != nil {
- return errors.Wrapf(err, "error copying %q to %q", srcPath, dest)
- }
- return nil
- }
-
- if extract {
- // 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
- }
-
- destfi, err := os.Stat(destPath)
- if err != nil {
- if !os.IsNotExist(err) || strings.HasSuffix(dest, string(os.PathSeparator)) {
- return err
- }
- }
- 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
-}
-
-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
-}
-
-func streamFileToStdout(srcPath string, srcfi os.FileInfo) error {
- if srcfi.IsDir() {
- tw := tar.NewWriter(os.Stdout)
- err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
- if err != nil || !info.Mode().IsRegular() || path == srcPath {
- return err
- }
- hdr, err := tar.FileInfoHeader(info, "")
- if err != nil {
- return err
- }
-
- if err = tw.WriteHeader(hdr); err != nil {
- return err
- }
- fh, err := os.Open(path)
- if err != nil {
- return err
- }
- defer fh.Close()
-
- _, err = io.Copy(tw, fh)
- return err
- })
- if err != nil {
- return errors.Wrapf(err, "error streaming directory %s to Stdout", srcPath)
- }
- return nil
- }
-
- file, err := os.Open(srcPath)
- if err != nil {
- return err
- }
- defer file.Close()
- if !archive.IsArchivePath(srcPath) {
- tw := tar.NewWriter(os.Stdout)
- hdr, err := tar.FileInfoHeader(srcfi, "")
- if err != nil {
- return err
- }
- err = tw.WriteHeader(hdr)
- if err != nil {
- return err
- }
- _, err = io.Copy(tw, file)
- if err != nil {
- return errors.Wrapf(err, "error streaming archive %s to Stdout", srcPath)
- }
- return nil
- }
-
- _, err = io.Copy(os.Stdout, file)
- if err != nil {
- return errors.Wrapf(err, "error streaming file to Stdout")
- }
- return nil
-}
-
-func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) {
- separator := string(os.PathSeparator)
- if filepath.IsAbs(path) {
- path = strings.TrimPrefix(path, separator)
- }
- if path == "" {
- return false, "", ""
- }
- for _, vol := range ctr.Config().NamedVolumes {
- volNamePath := strings.TrimPrefix(vol.Dest, separator)
- if matchVolumePath(path, volNamePath) {
- return true, vol.Dest, vol.Name
- }
- }
- return false, "", ""
-}
-
-// if SRCPATH or DESTPATH is from volume mount's destination -v or --mount type=volume, generates the path with volume mount point
-func pathWithVolumeMount(runtime *libpod.Runtime, volDestName, volName, path string) (string, error) {
- destVolume, err := runtime.GetVolume(volName)
- if err != nil {
- return "", errors.Wrapf(err, "error getting volume destination %s", volName)
- }
- if !filepath.IsAbs(path) {
- path = filepath.Join(string(os.PathSeparator), path)
- }
- path, err = securejoin.SecureJoin(destVolume.MountPoint(), strings.TrimPrefix(path, volDestName))
- return path, err
-}
-
-func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) {
- separator := string(os.PathSeparator)
- if filepath.IsAbs(path) {
- path = strings.TrimPrefix(path, string(os.PathSeparator))
- }
- if path == "" {
- return false, specs.Mount{}
- }
- for _, m := range ctr.Config().Spec.Mounts {
- if m.Type != "bind" {
- continue
- }
- mDest := strings.TrimPrefix(m.Destination, separator)
- if matchVolumePath(path, mDest) {
- return true, m
- }
- }
- return false, specs.Mount{}
-}
-
-func matchVolumePath(path, target string) bool {
- pathStr := filepath.Clean(path)
- target = filepath.Clean(target)
- for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) {
- pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))]
- }
- return pathStr == target
-}
-
-func pathWithBindMountSource(m specs.Mount, path string) (string, error) {
- if !filepath.IsAbs(path) {
- path = filepath.Join(string(os.PathSeparator), path)
- }
- return securejoin.SecureJoin(m.Source, strings.TrimPrefix(path, m.Destination))
-}
diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go
index 79bf2291e..79f55e2bd 100644
--- a/pkg/domain/infra/abi/generate.go
+++ b/pkg/domain/infra/abi/generate.go
@@ -41,28 +41,48 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string,
return &entities.GenerateSystemdReport{Units: units}, nil
}
-func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
+func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
var (
- pod *libpod.Pod
+ pods []*libpod.Pod
podYAML *k8sAPI.Pod
err error
- ctr *libpod.Container
+ ctrs []*libpod.Container
servicePorts []k8sAPI.ServicePort
serviceYAML k8sAPI.Service
)
- // Get the container in question.
- ctr, err = ic.Libpod.LookupContainer(nameOrID)
- if err != nil {
- pod, err = ic.Libpod.LookupPod(nameOrID)
+ for _, nameOrID := range nameOrIDs {
+ // Get the container in question
+ ctr, err := ic.Libpod.LookupContainer(nameOrID)
if err != nil {
- return nil, err
+ pod, err := ic.Libpod.LookupPod(nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ pods = append(pods, pod)
+ if len(pods) > 1 {
+ return nil, errors.New("can only generate single pod at a time")
+ }
+ } else {
+ if len(ctr.Dependencies()) > 0 {
+ return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies")
+ }
+ // we cannot deal with ctrs already in a pod
+ if len(ctr.PodID()) > 0 {
+ return nil, errors.Errorf("container %s is associated with pod %s: use generate on the pod itself", ctr.ID(), ctr.PodID())
+ }
+ ctrs = append(ctrs, ctr)
}
- podYAML, servicePorts, err = pod.GenerateForKube()
+ }
+
+ // check our inputs
+ if len(pods) > 0 && len(ctrs) > 0 {
+ return nil, errors.New("cannot generate pods and containers at the same time")
+ }
+
+ if len(pods) == 1 {
+ podYAML, servicePorts, err = pods[0].GenerateForKube()
} else {
- if len(ctr.Dependencies()) > 0 {
- return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies")
- }
- podYAML, err = ctr.GenerateForKube()
+ podYAML, err = libpod.GenerateForKube(ctrs)
}
if err != nil {
return nil, err
@@ -72,7 +92,7 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, op
serviceYAML = libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts)
}
- content, err := generateKubeOutput(podYAML, &serviceYAML)
+ content, err := generateKubeOutput(podYAML, &serviceYAML, options.Service)
if err != nil {
return nil, err
}
@@ -80,7 +100,7 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, op
return &entities.GenerateKubeReport{Reader: bytes.NewReader(content)}, nil
}
-func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service) ([]byte, error) {
+func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service, hasService bool) ([]byte, error) {
var (
output []byte
marshalledPod []byte
@@ -93,7 +113,7 @@ func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service) ([]byt
return nil, err
}
- if serviceYAML != nil {
+ if hasService {
marshalledService, err = yaml.Marshal(serviceYAML)
if err != nil {
return nil, err
@@ -114,7 +134,7 @@ func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service) ([]byt
output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...)
output = append(output, marshalledPod...)
- if serviceYAML != nil {
+ if hasService {
output = append(output, []byte("---\n")...)
output = append(output, marshalledService...)
}
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 1b523f06a..57a2bc4cf 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -26,7 +26,6 @@ import (
"github.com/containers/podman/v2/pkg/domain/entities"
domainUtils "github.com/containers/podman/v2/pkg/domain/utils"
"github.com/containers/podman/v2/pkg/rootless"
- "github.com/containers/podman/v2/pkg/trust"
"github.com/containers/podman/v2/pkg/util"
"github.com/containers/storage"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
@@ -34,9 +33,6 @@ import (
"github.com/sirupsen/logrus"
)
-// SignatureStoreDir defines default directory to store signatures
-const SignatureStoreDir = "/var/lib/containers/sigstore"
-
func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.BoolReport, error) {
_, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID)
if err != nil {
@@ -707,90 +703,79 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie
sc := ir.Libpod.SystemContext()
sc.DockerCertPath = options.CertDir
- systemRegistriesDirPath := trust.RegistriesDirPath(sc)
- registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath)
- if err != nil {
- return nil, errors.Wrapf(err, "error reading registry configuration")
- }
-
for _, signimage := range names {
- srcRef, err := alltransports.ParseImageName(signimage)
- if err != nil {
- return nil, errors.Wrapf(err, "error parsing image name")
- }
- rawSource, err := srcRef.NewImageSource(ctx, sc)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting image source")
- }
- err = rawSource.Close()
- if err != nil {
- logrus.Errorf("unable to close new image source %q", err)
- }
- getManifest, _, err := rawSource.GetManifest(ctx, nil)
- if err != nil {
- return nil, errors.Wrapf(err, "error getting getManifest")
- }
- dockerReference := rawSource.Reference().DockerReference()
- if dockerReference == nil {
- return nil, errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference()))
- }
- var sigStoreDir string
- if options.Directory != "" {
- sigStoreDir = options.Directory
- }
- if sigStoreDir == "" {
- if rootless.IsRootless() {
- sigStoreDir = filepath.Join(filepath.Dir(ir.Libpod.StorageConfig().GraphRoot), "sigstore")
- } else {
- var sigStoreURI string
- registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs)
- if registryInfo != nil {
- if sigStoreURI = registryInfo.SigStoreStaging; sigStoreURI == "" {
- sigStoreURI = registryInfo.SigStore
- }
+ err = func() error {
+ srcRef, err := alltransports.ParseImageName(signimage)
+ if err != nil {
+ return errors.Wrapf(err, "error parsing image name")
+ }
+ rawSource, err := srcRef.NewImageSource(ctx, sc)
+ if err != nil {
+ return errors.Wrapf(err, "error getting image source")
+ }
+ defer func() {
+ if err = rawSource.Close(); err != nil {
+ logrus.Errorf("unable to close %s image source %q", srcRef.DockerReference().Name(), err)
}
- if sigStoreURI == "" {
- return nil, errors.Errorf("no signature storage configuration found for %s", rawSource.Reference().DockerReference().String())
-
+ }()
+ getManifest, _, err := rawSource.GetManifest(ctx, nil)
+ if err != nil {
+ return errors.Wrapf(err, "error getting getManifest")
+ }
+ dockerReference := rawSource.Reference().DockerReference()
+ if dockerReference == nil {
+ return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference()))
+ }
+ var sigStoreDir string
+ if options.Directory != "" {
+ repo := reference.Path(dockerReference)
+ if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
+ return errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String())
+ }
+ sigStoreDir = filepath.Join(options.Directory, repo)
+ } else {
+ signatureURL, err := docker.SignatureStorageBaseURL(sc, rawSource.Reference(), true)
+ if err != nil {
+ return err
}
- sigStoreDir, err = localPathFromURI(sigStoreURI)
+ sigStoreDir, err = localPathFromURI(signatureURL)
if err != nil {
- return nil, errors.Wrapf(err, "invalid signature storage %s", sigStoreURI)
+ return err
}
}
- }
- manifestDigest, err := manifest.Digest(getManifest)
- if err != nil {
- return nil, err
- }
- repo := reference.Path(dockerReference)
- if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references
- return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String())
- }
+ manifestDigest, err := manifest.Digest(getManifest)
+ if err != nil {
+ return err
+ }
- // create signature
- newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy)
- if err != nil {
- return nil, errors.Wrapf(err, "error creating new signature")
- }
- // create the signstore file
- signatureDir := fmt.Sprintf("%s@%s=%s", filepath.Join(sigStoreDir, repo), manifestDigest.Algorithm(), manifestDigest.Hex())
- if err := os.MkdirAll(signatureDir, 0751); err != nil {
- // The directory is allowed to exist
- if !os.IsExist(err) {
- logrus.Error(err)
- continue
+ // create signature
+ newSig, err := signature.SignDockerManifest(getManifest, dockerReference.String(), mech, options.SignBy)
+ if err != nil {
+ return errors.Wrapf(err, "error creating new signature")
}
- }
- sigFilename, err := getSigFilename(signatureDir)
- if err != nil {
- logrus.Errorf("error creating sigstore file: %v", err)
- continue
- }
- err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644)
+ // create the signstore file
+ signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, manifestDigest.Algorithm(), manifestDigest.Hex())
+ if err := os.MkdirAll(signatureDir, 0751); err != nil {
+ // The directory is allowed to exist
+ if !os.IsExist(err) {
+ logrus.Error(err)
+ return nil
+ }
+ }
+ sigFilename, err := getSigFilename(signatureDir)
+ if err != nil {
+ logrus.Errorf("error creating sigstore file: %v", err)
+ return nil
+ }
+ err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644)
+ if err != nil {
+ logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String())
+ return nil
+ }
+ return nil
+ }()
if err != nil {
- logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String())
- continue
+ return nil, err
}
}
return nil, nil
@@ -815,14 +800,9 @@ func getSigFilename(sigStoreDirPath string) (string, error) {
}
}
-func localPathFromURI(sigStoreDir string) (string, error) {
- url, err := url.Parse(sigStoreDir)
- if err != nil {
- return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir)
- }
+func localPathFromURI(url *url.URL) (string, error) {
if url.Scheme != "file" {
- return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir)
+ return "", errors.Errorf("writing to %s is not supported. Use a supported scheme", url.String())
}
- sigStoreDir = url.Path
- return sigStoreDir, nil
+ return url.Path, nil
}
diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go
index 281b04294..c4b0b7712 100644
--- a/pkg/domain/infra/abi/images_list.go
+++ b/pkg/domain/infra/abi/images_list.go
@@ -35,13 +35,11 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
Created: img.Created().Unix(),
Dangling: img.Dangling(),
Digest: string(img.Digest()),
- Digests: digests,
+ RepoDigests: digests,
History: img.NamesHistory(),
Names: img.Names(),
- ParentId: img.Parent,
ReadOnly: img.IsReadOnly(),
SharedSize: 0,
- VirtualSize: img.VirtualSize,
RepoTags: img.Names(), // may include tags and digests
}
e.Labels, err = img.Labels(ctx)
@@ -60,6 +58,15 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
return nil, errors.Wrapf(err, "error retrieving size of image %q: you may need to remove the image to resolve the error", img.ID())
}
e.Size = int64(*sz)
+ // This is good enough for now, but has to be
+ // replaced later with correct calculation logic
+ e.VirtualSize = int64(*sz)
+
+ parent, err := img.ParentID(ctx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving parent of image %q: you may need to remove the image to resolve the error", img.ID())
+ }
+ e.ParentId = parent
summaries = append(summaries, &e)
}
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index 72fd98ac1..ec2532bea 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -11,6 +11,7 @@ import (
"strings"
"github.com/containers/common/pkg/config"
+ "github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/cgroups"
"github.com/containers/podman/v2/pkg/domain/entities"
@@ -86,7 +87,11 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command)
return nil
}
- pausePidPath, err := util.GetRootlessPauseProcessPidPath()
+ tmpDir, err := ic.Libpod.TmpDir()
+ if err != nil {
+ return err
+ }
+ pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
@@ -112,7 +117,7 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command)
}
became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths)
- if err := movePauseProcessToScope(); err != nil {
+ if err := movePauseProcessToScope(ic.Libpod); err != nil {
conf, err := ic.Config(context.Background())
if err != nil {
return err
@@ -133,8 +138,12 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command)
return nil
}
-func movePauseProcessToScope() error {
- pausePidPath, err := util.GetRootlessPauseProcessPidPath()
+func movePauseProcessToScope(r *libpod.Runtime) error {
+ tmpDir, err := r.TmpDir()
+ if err != nil {
+ return err
+ }
+ pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir)
if err != nil {
return errors.Wrapf(err, "could not get pause process pid file path")
}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 63677719b..3584668c7 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -731,8 +731,8 @@ func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrID string, o
return reports, nil
}
-func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) {
- return nil, errors.New("not implemented")
+func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) error {
+ return errors.New("not implemented")
}
// Shutdown Libpod engine
diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go
index 966f707b1..ebbfa143f 100644
--- a/pkg/domain/infra/tunnel/generate.go
+++ b/pkg/domain/infra/tunnel/generate.go
@@ -11,6 +11,6 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string,
return generate.Systemd(ic.ClientCxt, nameOrID, options)
}
-func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
- return generate.Kube(ic.ClientCxt, nameOrID, options)
+func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
+ return generate.Kube(ic.ClientCxt, nameOrIDs, options)
}
diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go
index 2d40dba8f..1808f99b8 100644
--- a/pkg/specgen/generate/config_linux.go
+++ b/pkg/specgen/generate/config_linux.go
@@ -4,13 +4,16 @@ import (
"fmt"
"io/ioutil"
"os"
+ "path"
"path/filepath"
"strings"
"github.com/containers/podman/v2/pkg/rootless"
+ "github.com/containers/podman/v2/pkg/util"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
@@ -137,22 +140,33 @@ func DevicesFromPath(g *generate.Generator, devicePath string) error {
return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":"))
}
-func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) {
+func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, mask, unmask []string, g *generate.Generator) {
+ defaultMaskPaths := []string{"/proc/acpi",
+ "/proc/kcore",
+ "/proc/keys",
+ "/proc/latency_stats",
+ "/proc/timer_list",
+ "/proc/timer_stats",
+ "/proc/sched_debug",
+ "/proc/scsi",
+ "/sys/firmware",
+ "/sys/fs/selinux",
+ "/sys/dev/block",
+ }
+
+ unmaskAll := false
+ if unmask != nil && unmask[0] == "ALL" {
+ unmaskAll = true
+ }
+
if !privileged {
- for _, mp := range []string{
- "/proc/acpi",
- "/proc/kcore",
- "/proc/keys",
- "/proc/latency_stats",
- "/proc/timer_list",
- "/proc/timer_stats",
- "/proc/sched_debug",
- "/proc/scsi",
- "/sys/firmware",
- "/sys/fs/selinux",
- "/sys/dev",
- } {
- g.AddLinuxMaskedPaths(mp)
+ if !unmaskAll {
+ for _, mp := range defaultMaskPaths {
+ // check that the path to mask is not in the list of paths to unmask
+ if !util.StringInSlice(mp, unmask) {
+ g.AddLinuxMaskedPaths(mp)
+ }
+ }
}
if pidModeIsHost && rootless.IsRootless() {
@@ -170,6 +184,15 @@ func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.
g.AddLinuxReadonlyPaths(rp)
}
}
+
+ // mask the paths provided by the user
+ for _, mp := range mask {
+ if !path.IsAbs(mp) && mp != "" {
+ logrus.Errorf("Path %q is not an absolute path, skipping...", mp)
+ continue
+ }
+ g.AddLinuxMaskedPaths(mp)
+ }
}
// based on getDevices from runc (libcontainer/devices/devices.go)
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 95e4eeb8f..4f36744ca 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -98,7 +98,6 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
// present.
imgName := newImage.InputName
if s.Image == newImage.InputName && strings.HasPrefix(newImage.ID(), s.Image) {
- imgName = ""
names := newImage.Names()
if len(names) > 0 {
imgName = names[0]
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index ddc73ca61..036c7b7a1 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -233,6 +233,8 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value)
}
toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil))
+ case specgen.Private:
+ fallthrough
case specgen.Bridge:
portMappings, err := createPortMappings(ctx, s, img)
if err != nil {
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 8454458a8..c24dcf4c0 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -165,7 +165,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
inUserNS = true
}
}
- if inUserNS && s.NetNS.IsHost() {
+ if inUserNS && s.NetNS.NSMode != specgen.NoNetwork {
canMountSys = false
}
@@ -298,7 +298,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
}
}
- BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g)
+ BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g)
for name, val := range s.Env {
g.AddProcessEnv(name, val)
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
index d15745fa0..9d78a0210 100644
--- a/pkg/specgen/namespaces.go
+++ b/pkg/specgen/namespaces.go
@@ -258,24 +258,22 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) {
var cniNetworks []string
// Net defaults to Slirp on rootless
switch {
- case ns == "slirp4netns", strings.HasPrefix(ns, "slirp4netns:"):
+ case ns == string(Slirp), strings.HasPrefix(ns, string(Slirp)+":"):
toReturn.NSMode = Slirp
- case ns == "pod":
+ case ns == string(FromPod):
toReturn.NSMode = FromPod
- case ns == "":
+ case ns == "" || ns == string(Default) || ns == string(Private):
if rootless.IsRootless() {
toReturn.NSMode = Slirp
} else {
toReturn.NSMode = Bridge
}
- case ns == "bridge":
+ case ns == string(Bridge):
toReturn.NSMode = Bridge
- case ns == "none":
+ case ns == string(NoNetwork):
toReturn.NSMode = NoNetwork
- case ns == "host":
+ case ns == string(Host):
toReturn.NSMode = Host
- case ns == "private":
- toReturn.NSMode = Private
case strings.HasPrefix(ns, "ns:"):
split := strings.SplitN(ns, ":", 2)
if len(split) != 2 {
@@ -283,7 +281,7 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) {
}
toReturn.NSMode = Path
toReturn.Value = split[1]
- case strings.HasPrefix(ns, "container:"):
+ case strings.HasPrefix(ns, string(FromContainer)+":"):
split := strings.SplitN(ns, ":", 2)
if len(split) != 2 {
return toReturn, nil, errors.Errorf("must provide name or ID or a container when specifying container:")
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index fad2406e5..964b89fa4 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -307,6 +307,13 @@ type ContainerSecurityConfig struct {
Umask string `json:"umask,omitempty"`
// ProcOpts are the options used for the proc mount.
ProcOpts []string `json:"procfs_opts,omitempty"`
+ // Mask is the path we want to mask in the container. This masks the paths
+ // given in addition to the default list.
+ // Optional
+ Mask []string `json:"mask,omitempty"`
+ // Unmask is the path we want to unmask in the container. To override
+ // all the default paths that are masked, set unmask=ALL.
+ Unmask []string `json:"unmask,omitempty"`
}
// ContainerCgroupConfig contains configuration information about a container's
diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go
index c0acba37d..234a60380 100644
--- a/pkg/systemd/generate/pods.go
+++ b/pkg/systemd/generate/pods.go
@@ -224,7 +224,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
executable, err := os.Executable()
if err != nil {
executable = "/usr/bin/podman"
- logrus.Warnf("Could not obtain podman executable location, using default %s", executable)
+ logrus.Warnf("Could not obtain podman executable location, using default %s: %v", executable, err)
}
info.Executable = executable
}
diff --git a/pkg/trust/trust.go b/pkg/trust/trust.go
index a61e0ef10..a30611b74 100644
--- a/pkg/trust/trust.go
+++ b/pkg/trust/trust.go
@@ -12,6 +12,7 @@ import (
"strings"
"github.com/containers/image/v5/types"
+ "github.com/docker/docker/pkg/homedir"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -60,6 +61,12 @@ type ShowOutput struct {
Sigstore string
}
+// systemRegistriesDirPath is the path to registries.d.
+const systemRegistriesDirPath = "/etc/containers/registries.d"
+
+// userRegistriesDir is the path to the per user registries.d.
+var userRegistriesDir = filepath.FromSlash(".config/containers/registries.d")
+
// DefaultPolicyPath returns a path to the default policy of the system.
func DefaultPolicyPath(sys *types.SystemContext) string {
systemDefaultPolicyPath := "/etc/containers/policy.json"
@@ -76,15 +83,17 @@ func DefaultPolicyPath(sys *types.SystemContext) string {
// RegistriesDirPath returns a path to registries.d
func RegistriesDirPath(sys *types.SystemContext) string {
- systemRegistriesDirPath := "/etc/containers/registries.d"
- if sys != nil {
- if sys.RegistriesDirPath != "" {
- return sys.RegistriesDirPath
- }
- if sys.RootForImplicitAbsolutePaths != "" {
- return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
- }
+ if sys != nil && sys.RegistriesDirPath != "" {
+ return sys.RegistriesDirPath
+ }
+ userRegistriesDirPath := filepath.Join(homedir.Get(), userRegistriesDir)
+ if _, err := os.Stat(userRegistriesDirPath); err == nil {
+ return userRegistriesDirPath
}
+ if sys != nil && sys.RootForImplicitAbsolutePaths != "" {
+ return filepath.Join(sys.RootForImplicitAbsolutePaths, systemRegistriesDirPath)
+ }
+
return systemRegistriesDirPath
}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index f6a084c00..e0f631eb4 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -530,6 +530,11 @@ func ParseInputTime(inputTime string) (time.Time, error) {
}
}
+ unix_timestamp, err := strconv.ParseInt(inputTime, 10, 64)
+ if err == nil {
+ return time.Unix(unix_timestamp, 0), nil
+ }
+
// input might be a duration
duration, err := time.ParseDuration(inputTime)
if err != nil {
diff --git a/pkg/util/utils_supported.go b/pkg/util/utils_supported.go
index 2d636a7cb..a63c76415 100644
--- a/pkg/util/utils_supported.go
+++ b/pkg/util/utils_supported.go
@@ -99,7 +99,8 @@ func GetRootlessConfigHomeDir() (string, error) {
}
// GetRootlessPauseProcessPidPath returns the path to the file that holds the pid for
-// the pause process
+// the pause process.
+// DEPRECATED - switch to GetRootlessPauseProcessPidPathGivenDir
func GetRootlessPauseProcessPidPath() (string, error) {
runtimeDir, err := GetRuntimeDir()
if err != nil {
@@ -107,3 +108,13 @@ func GetRootlessPauseProcessPidPath() (string, error) {
}
return filepath.Join(runtimeDir, "libpod", "pause.pid"), nil
}
+
+// GetRootlessPauseProcessPidPathGivenDir returns the path to the file that
+// holds the PID of the pause process, given the location of Libpod's temporary
+// files.
+func GetRootlessPauseProcessPidPathGivenDir(libpodTmpDir string) (string, error) {
+ if libpodTmpDir == "" {
+ return "", errors.Errorf("must provide non-empty tmporary directory")
+ }
+ return filepath.Join(libpodTmpDir, "pause.pid"), nil
+}
diff --git a/pkg/util/utils_windows.go b/pkg/util/utils_windows.go
index 9bba2d1ee..46ca5e7f1 100644
--- a/pkg/util/utils_windows.go
+++ b/pkg/util/utils_windows.go
@@ -25,6 +25,12 @@ func GetRootlessPauseProcessPidPath() (string, error) {
return "", errors.Wrap(errNotImplemented, "GetRootlessPauseProcessPidPath")
}
+// GetRootlessPauseProcessPidPath returns the path to the file that holds the pid for
+// the pause process
+func GetRootlessPauseProcessPidPathGivenDir(unused string) (string, error) {
+ return "", errors.Wrap(errNotImplemented, "GetRootlessPauseProcessPidPath")
+}
+
// GetRuntimeDir returns the runtime directory
func GetRuntimeDir() (string, error) {
return "", errors.New("this function is not implemented for windows")
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
index b35c27215..5c35edf2b 100644
--- a/test/apiv2/20-containers.at
+++ b/test/apiv2/20-containers.at
@@ -169,7 +169,7 @@ t GET containers/$cid/json 200 \
.Args[1]="param2"
t DELETE containers/$cid 204
-# test only set the entrpoint, Cmd should be []
+# test only set the entrypoint, Cmd should be []
t POST containers/create '"Image":"'$IMAGE'","Entrypoint":["echo","param1"]' 201 \
.Id~[0-9a-f]\\{64\\}
cid=$(jq -r '.Id' <<<"$output")
diff --git a/test/apiv2/25-containersMore.at b/test/apiv2/25-containersMore.at
index 4f6b80a5f..b88c798eb 100644
--- a/test/apiv2/25-containersMore.at
+++ b/test/apiv2/25-containersMore.at
@@ -65,13 +65,13 @@ t GET libpod/containers/json?last=1 200 \
cid=$(jq -r '.[0].Id' <<<"$output")
-t GET libpod/generate/$cid/kube 200
+t GET libpod/generate/kube?names=$cid 200
like "$output" ".*apiVersion:.*" "Check generated kube yaml - apiVersion"
like "$output" ".*kind:\\sPod.*" "Check generated kube yaml - kind: Pod"
like "$output" ".*metadata:.*" "Check generated kube yaml - metadata"
like "$output" ".*spec:.*" "Check generated kube yaml - spec"
-t GET libpod/generate/$cid/kube?service=true 200
+t GET "libpod/generate/kube?service=true&names=$cid" 200
like "$output" ".*apiVersion:.*" "Check generated kube yaml(service=true) - apiVersion"
like "$output" ".*kind:\\sPod.*" "Check generated kube yaml(service=true) - kind: Pod"
like "$output" ".*metadata:.*" "Check generated kube yaml(service=true) - metadata"
@@ -79,4 +79,13 @@ like "$output" ".*spec:.*" "Check generated kube yaml(service=true) - spec"
like "$output" ".*kind:\\sService.*" "Check generated kube yaml(service=true) - kind: Service"
t DELETE libpod/containers/$cid 204
+
+# Create 3 stopped containers to test containers prune
+podman run $IMAGE true
+podman run $IMAGE true
+podman run $IMAGE true
+
+t POST libpod/containers/prune '' 200
+t GET libpod/containers/json 200 \
+ length=0
# vim: filetype=sh
diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py
index 52348d4f4..2f9e62149 100644
--- a/test/apiv2/rest_api/test_rest_v2_0_0.py
+++ b/test/apiv2/rest_api/test_rest_v2_0_0.py
@@ -1,11 +1,13 @@
import json
+import random
+import string
import subprocess
-import sys
-import time
import unittest
from multiprocessing import Process
import requests
+import sys
+import time
from dateutil.parser import parse
from test.apiv2.rest_api import Podman
@@ -91,14 +93,21 @@ class TestApi(unittest.TestCase):
self.assertIsNotNone(r.content)
_ = json.loads(r.text)
+ info = requests.get(PODMAN_URL + "/v1.40/info")
+ self.assertEqual(info.status_code, 200, info.content)
+ _ = json.loads(info.text)
+
def test_events(self):
r = requests.get(_url("/events?stream=false"))
self.assertEqual(r.status_code, 200, r.text)
self.assertIsNotNone(r.content)
- for line in r.text.splitlines():
+
+ report = r.text.splitlines()
+ self.assertGreater(len(report), 0, "No events found!")
+ for line in report:
obj = json.loads(line)
# Actor.ID is uppercase for compatibility
- _ = obj["Actor"]["ID"]
+ self.assertIn("ID", obj["Actor"])
def test_containers(self):
r = requests.get(_url("/containers/json"), timeout=5)
@@ -172,22 +181,26 @@ class TestApi(unittest.TestCase):
self.assertEqual(net_default.status_code, 201, net_default.text)
create = requests.post(
- PODMAN_URL + "/v1.40/containers/create?name=postCreate",
+ PODMAN_URL + "/v1.40/containers/create?name=postCreateConnect",
json={
"Cmd": ["top"],
"Image": "alpine:latest",
"NetworkDisabled": False,
# FIXME adding these 2 lines cause: (This is sampled from docker-py)
# "network already exists","message":"container
- # 01306e499df5441560d70071a54342611e422a94de20865add50a9565fd79fb9 is already connected to CNI network \"TestDefaultNetwork\": network already exists"
+ # 01306e499df5441560d70071a54342611e422a94de20865add50a9565fd79fb9 is already connected to CNI
+ # network \"TestDefaultNetwork\": network already exists"
# "HostConfig": {"NetworkMode": "TestDefaultNetwork"},
# "NetworkingConfig": {"EndpointsConfig": {"TestDefaultNetwork": None}},
# FIXME These two lines cause:
- # CNI network \"TestNetwork\" not found","message":"error configuring network namespace for container 369ddfa7d3211ebf1fbd5ddbff91bd33fa948858cea2985c133d6b6507546dff: CNI network \"TestNetwork\" not found"
+ # CNI network \"TestNetwork\" not found","message":"error configuring network namespace for container
+ # 369ddfa7d3211ebf1fbd5ddbff91bd33fa948858cea2985c133d6b6507546dff: CNI network \"TestNetwork\" not
+ # found"
# "HostConfig": {"NetworkMode": "TestNetwork"},
# "NetworkingConfig": {"EndpointsConfig": {"TestNetwork": None}},
# FIXME no networking defined cause: (note this error is from the container inspect below)
- # "internal libpod error","message":"network inspection mismatch: asked to join 2 CNI network(s) [TestDefaultNetwork podman], but have information on 1 network(s): internal libpod error"
+ # "internal libpod error","message":"network inspection mismatch: asked to join 2 CNI network(s) [
+ # TestDefaultNetwork podman], but have information on 1 network(s): internal libpod error"
},
)
self.assertEqual(create.status_code, 201, create.text)
@@ -255,23 +268,68 @@ class TestApi(unittest.TestCase):
def test_commit(self):
r = requests.post(_url(ctnr("/commit?container={}")))
self.assertEqual(r.status_code, 200, r.text)
- validateObjectFields(r.text)
- def test_images(self):
- r = requests.get(_url("/images/json"))
+ obj = json.loads(r.content)
+ self.assertIsInstance(obj, dict)
+ self.assertIn("Id", obj)
+
+ def test_images_compat(self):
+ r = requests.get(PODMAN_URL + "/v1.40/images/json")
self.assertEqual(r.status_code, 200, r.text)
- validateObjectFields(r.content)
- def test_inspect_image(self):
- r = requests.get(_url("/images/alpine/json"))
+ # See https://docs.docker.com/engine/api/v1.40/#operation/ImageList
+ required_keys = (
+ "Id",
+ "ParentId",
+ "RepoTags",
+ "RepoDigests",
+ "Created",
+ "Size",
+ "SharedSize",
+ "VirtualSize",
+ "Labels",
+ "Containers",
+ )
+ objs = json.loads(r.content)
+ self.assertIn(type(objs), (list,))
+ for o in objs:
+ self.assertIsInstance(o, dict)
+ for k in required_keys:
+ self.assertIn(k, o)
+
+ def test_inspect_image_compat(self):
+ r = requests.get(PODMAN_URL + "/v1.40/images/alpine/json")
self.assertEqual(r.status_code, 200, r.text)
- obj = validateObjectFields(r.content)
+
+ # See https://docs.docker.com/engine/api/v1.40/#operation/ImageInspect
+ required_keys = (
+ "Id",
+ "Parent",
+ "Comment",
+ "Created",
+ "Container",
+ "DockerVersion",
+ "Author",
+ "Architecture",
+ "Os",
+ "Size",
+ "VirtualSize",
+ "GraphDriver",
+ "RootFS",
+ "Metadata",
+ )
+
+ obj = json.loads(r.content)
+ self.assertIn(type(obj), (dict,))
+ for k in required_keys:
+ self.assertIn(k, obj)
_ = parse(obj["Created"])
- def test_delete_image(self):
- r = requests.delete(_url("/images/alpine?force=true"))
+ def test_delete_image_compat(self):
+ r = requests.delete(PODMAN_URL + "/v1.40/images/alpine?force=true")
self.assertEqual(r.status_code, 200, r.text)
- json.loads(r.text)
+ obj = json.loads(r.content)
+ self.assertIn(type(obj), (list,))
def test_pull(self):
r = requests.post(_url("/images/pull?reference=alpine"), timeout=15)
@@ -295,12 +353,13 @@ class TestApi(unittest.TestCase):
self.assertTrue(keys["images"], "Expected to find images stanza")
self.assertTrue(keys["stream"], "Expected to find stream progress stanza's")
- def test_search(self):
+ def test_search_compat(self):
# Had issues with this test hanging when repositories not happy
def do_search():
- r = requests.get(_url("/images/search?term=alpine"), timeout=5)
+ r = requests.get(PODMAN_URL + "/v1.40/images/search?term=alpine", timeout=5)
self.assertEqual(r.status_code, 200, r.text)
- json.loads(r.text)
+ objs = json.loads(r.text)
+ self.assertIn(type(objs), (list,))
search = Process(target=do_search)
search.start()
@@ -308,17 +367,168 @@ class TestApi(unittest.TestCase):
self.assertFalse(search.is_alive(), "/images/search took too long")
def test_ping(self):
+ required_headers = (
+ "API-Version",
+ "Builder-Version",
+ "Docker-Experimental",
+ "Cache-Control",
+ "Pragma",
+ "Pragma",
+ )
+
+ def check_headers(req):
+ for k in required_headers:
+ self.assertIn(k, req.headers)
+
r = requests.get(PODMAN_URL + "/_ping")
self.assertEqual(r.status_code, 200, r.text)
+ self.assertEqual(r.text, "OK")
+ check_headers(r)
r = requests.head(PODMAN_URL + "/_ping")
self.assertEqual(r.status_code, 200, r.text)
+ self.assertEqual(r.text, "")
+ check_headers(r)
r = requests.get(_url("/_ping"))
self.assertEqual(r.status_code, 200, r.text)
+ self.assertEqual(r.text, "OK")
+ check_headers(r)
- r = requests.get(_url("/_ping"))
+ r = requests.head(_url("/_ping"))
self.assertEqual(r.status_code, 200, r.text)
+ self.assertEqual(r.text, "")
+ check_headers(r)
+
+ def test_history_compat(self):
+ r = requests.get(PODMAN_URL + "/v1.40/images/alpine/history")
+ self.assertEqual(r.status_code, 200, r.text)
+
+ # See https://docs.docker.com/engine/api/v1.40/#operation/ImageHistory
+ required_keys = ("Id", "Created", "CreatedBy", "Tags", "Size", "Comment")
+
+ objs = json.loads(r.content)
+ self.assertIn(type(objs), (list,))
+ for o in objs:
+ self.assertIsInstance(o, dict)
+ for k in required_keys:
+ self.assertIn(k, o)
+
+ def test_network_compat(self):
+ name = "Network_" + "".join(random.choice(string.ascii_letters) for i in range(10))
+
+ # Cannot test for 0 existing networks because default "podman" network always exists
+
+ create = requests.post(PODMAN_URL + "/v1.40/networks/create", json={"Name": name})
+ self.assertEqual(create.status_code, 201, create.content)
+ obj = json.loads(create.content)
+ self.assertIn(type(obj), (dict,))
+ self.assertIn("Id", obj)
+ ident = obj["Id"]
+ self.assertNotEqual(name, ident)
+
+ ls = requests.get(PODMAN_URL + "/v1.40/networks")
+ self.assertEqual(ls.status_code, 200, ls.content)
+ objs = json.loads(ls.content)
+ self.assertIn(type(objs), (list,))
+
+ found = False
+ for network in objs:
+ if network["Name"] == name:
+ found = True
+ self.assertTrue(found, f"Network {name} not found")
+
+ inspect = requests.get(PODMAN_URL + f"/v1.40/networks/{ident}")
+ self.assertEqual(inspect.status_code, 200, inspect.content)
+ obj = json.loads(create.content)
+ self.assertIn(type(obj), (dict,))
+
+ inspect = requests.delete(PODMAN_URL + f"/v1.40/networks/{ident}")
+ self.assertEqual(inspect.status_code, 204, inspect.content)
+ inspect = requests.get(PODMAN_URL + f"/v1.40/networks/{ident}")
+ self.assertEqual(inspect.status_code, 404, inspect.content)
+
+ prune = requests.post(PODMAN_URL + "/v1.40/networks/prune")
+ self.assertEqual(prune.status_code, 405, prune.content)
+
+ def test_volumes_compat(self):
+ name = "Volume_" + "".join(random.choice(string.ascii_letters) for i in range(10))
+
+ ls = requests.get(PODMAN_URL + "/v1.40/volumes")
+ self.assertEqual(ls.status_code, 200, ls.content)
+
+ # See https://docs.docker.com/engine/api/v1.40/#operation/VolumeList
+ required_keys = (
+ "Volumes",
+ "Warnings",
+ )
+
+ obj = json.loads(ls.content)
+ self.assertIn(type(obj), (dict,))
+ for k in required_keys:
+ self.assertIn(k, obj)
+
+ create = requests.post(PODMAN_URL + "/v1.40/volumes/create", json={"Name": name})
+ self.assertEqual(create.status_code, 201, create.content)
+
+ # See https://docs.docker.com/engine/api/v1.40/#operation/VolumeCreate
+ # and https://docs.docker.com/engine/api/v1.40/#operation/VolumeInspect
+ required_keys = (
+ "Name",
+ "Driver",
+ "Mountpoint",
+ "Labels",
+ "Scope",
+ "Options",
+ )
+
+ obj = json.loads(create.content)
+ self.assertIn(type(obj), (dict,))
+ for k in required_keys:
+ self.assertIn(k, obj)
+ self.assertEqual(obj["Name"], name)
+
+ inspect = requests.get(PODMAN_URL + f"/v1.40/volumes/{name}")
+ self.assertEqual(inspect.status_code, 200, inspect.content)
+
+ obj = json.loads(create.content)
+ self.assertIn(type(obj), (dict,))
+ for k in required_keys:
+ self.assertIn(k, obj)
+
+ rm = requests.delete(PODMAN_URL + f"/v1.40/volumes/{name}")
+ self.assertEqual(rm.status_code, 204, rm.content)
+
+ prune = requests.post(PODMAN_URL + "/v1.40/volumes/prune")
+ self.assertEqual(prune.status_code, 200, prune.content)
+
+ def test_auth_compat(self):
+ r = requests.post(
+ PODMAN_URL + "/v1.40/auth",
+ json={
+ "username": "bozo",
+ "password": "wedontneednopasswords",
+ "serveraddress": "https://localhost/v1.40/",
+ },
+ )
+ self.assertEqual(r.status_code, 404, r.content)
+
+ def test_version(self):
+ r = requests.get(PODMAN_URL + "/v1.40/version")
+ self.assertEqual(r.status_code, 200, r.content)
+
+ r = requests.get(_url("/version"))
+ self.assertEqual(r.status_code, 200, r.content)
+
+ def test_df_compat(self):
+ r = requests.get(PODMAN_URL + "/v1.40/system/df")
+ self.assertEqual(r.status_code, 200, r.content)
+
+ obj = json.loads(r.content)
+ self.assertIn("Images", obj)
+ self.assertIn("Containers", obj)
+ self.assertIn("Volumes", obj)
+ self.assertIn("BuildCache", obj)
if __name__ == "__main__":
diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go
index 63a2df67a..ac9481797 100644
--- a/test/e2e/build_test.go
+++ b/test/e2e/build_test.go
@@ -239,7 +239,7 @@ RUN printenv http_proxy`
Expect(session.ExitCode()).To(Equal(0))
// Verify that OS and Arch are being set
- inspect := podmanTest.PodmanNoCache([]string{"image", "inspect", "--format", "{{ index .Config.Labels }}", "test"})
+ inspect := podmanTest.Podman([]string{"image", "inspect", "--format", "{{ index .Config.Labels }}", "test"})
inspect.WaitWithDefaultTimeout()
data := inspect.OutputToString()
Expect(data).To(ContainSubstring(buildah.Version))
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index 16d8bb770..d7bbdc633 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -317,7 +317,7 @@ func (p *PodmanTestIntegration) createArtifact(image string) {
fmt.Printf("Caching %s at %s...", image, destName)
if _, err := os.Stat(destName); os.IsNotExist(err) {
pull := p.PodmanNoCache([]string{"pull", image})
- pull.Wait(90)
+ pull.Wait(240)
Expect(pull.ExitCode()).To(Equal(0))
save := p.PodmanNoCache([]string{"save", "-o", destName, image})
diff --git a/test/e2e/config/containers.conf b/test/e2e/config/containers.conf
index 5f852468d..35153ba05 100644
--- a/test/e2e/config/containers.conf
+++ b/test/e2e/config/containers.conf
@@ -52,3 +52,7 @@ dns_options=[ "debug", ]
tz = "Pacific/Honolulu"
umask = "0002"
+
+[engine]
+
+network_cmd_options=["allow_host_loopback=true"]
diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go
index 866162f7f..28672cfc6 100644
--- a/test/e2e/containers_conf_test.go
+++ b/test/e2e/containers_conf_test.go
@@ -258,6 +258,12 @@ var _ = Describe("Podman run", func() {
Expect(session.OutputToString()).To(Equal("0002"))
})
+ It("podman set network cmd options slirp options to allow host loopback", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "slirp4netns", ALPINE, "ping", "-c1", "10.0.2.2"})
+ session.Wait(30)
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
It("podman-remote test localcontainers.conf versus remote containers.conf", func() {
if !IsRemote() {
Skip("this test is only for remote")
@@ -311,4 +317,5 @@ var _ = Describe("Podman run", func() {
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(Equal("0022"))
})
+
})
diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go
index c1d3be5ab..33908b60e 100644
--- a/test/e2e/cp_test.go
+++ b/test/e2e/cp_test.go
@@ -4,14 +4,18 @@ import (
"io/ioutil"
"os"
"os/exec"
+ "os/user"
"path/filepath"
- "strings"
. "github.com/containers/podman/v2/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
+// NOTE: Only smoke tests. The system tests (i.e., "./test/system/*") take
+// care of function and regression tests. Please consider adding system tests
+// rather than e2e tests. System tests are used in RHEL gating.
+
var _ = Describe("Podman cp", func() {
var (
tempdir string
@@ -37,240 +41,108 @@ var _ = Describe("Podman cp", func() {
})
+ // Copy a file to the container, then back to the host and make sure
+ // that the contents match.
It("podman cp file", func() {
- srcPath := filepath.Join(podmanTest.RunRoot, "cp_test.txt")
- dstPath := filepath.Join(podmanTest.RunRoot, "cp_from_container")
- fromHostToContainer := []byte("copy from host to container")
-
- session := podmanTest.Podman([]string{"create", ALPINE, "cat", "foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- name := session.OutputToString()
-
- err := ioutil.WriteFile(srcPath, fromHostToContainer, 0644)
+ srcFile, err := ioutil.TempFile("", "")
Expect(err).To(BeNil())
+ defer srcFile.Close()
+ defer os.Remove(srcFile.Name())
- session = podmanTest.Podman([]string{"cp", srcPath, name + ":foo/"})
- session.WaitWithDefaultTimeout()
- Expect(session).To(ExitWithError())
-
- session = podmanTest.Podman([]string{"cp", srcPath, name + ":foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"cp", name + ":foo", dstPath})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"start", name})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- })
-
- It("podman cp file to dir", func() {
- name := "testctr"
- setup := podmanTest.RunTopContainer(name)
- setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
-
- srcPath := "/tmp/cp_test.txt"
- fromHostToContainer := []byte("copy from host to container directory")
- err := ioutil.WriteFile(srcPath, fromHostToContainer, 0644)
+ originalContent := []byte("podman cp file test")
+ err = ioutil.WriteFile(srcFile.Name(), originalContent, 0644)
Expect(err).To(BeNil())
- session := podmanTest.Podman([]string{"exec", name, "mkdir", "foodir"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"cp", srcPath, name + ":foodir/"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"exec", name, "ls", "foodir/cp_test.txt"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- os.Remove("/tmp/cp_test.txt")
- })
-
- It("podman cp dir to dir", func() {
- testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir1")
-
- session := podmanTest.Podman([]string{"create", ALPINE, "ls", "/foodir"})
+ // Create a container. NOTE that container mustn't be running for copying.
+ session := podmanTest.Podman([]string{"create", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
name := session.OutputToString()
- err := os.Mkdir(testDirPath, 0755)
- Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
-
- session = podmanTest.Podman([]string{"cp", testDirPath, name + ":/foodir"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"cp", testDirPath, name + ":/foodir"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- testctr := "testctr"
- setup := podmanTest.RunTopContainer(testctr)
- setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
+ // Copy TO the container.
- session = podmanTest.Podman([]string{"exec", testctr, "mkdir", "foo"})
+ // Cannot copy to a non-existent path (note the trailing "/").
+ session = podmanTest.Podman([]string{"cp", srcFile.Name(), name + ":foo/"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"cp", testDirPath + "/.", testctr + ":/foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"exec", testctr, "ls", "foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(len(session.OutputToString())).To(Equal(0))
+ Expect(session).To(ExitWithError())
- session = podmanTest.Podman([]string{"cp", testctr + ":/foo/.", testDirPath})
+ // The file will now be created (and written to).
+ session = podmanTest.Podman([]string{"cp", srcFile.Name(), name + ":foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- cmd := exec.Command("ls", testDirPath)
- res, err := cmd.Output()
- Expect(err).To(BeNil())
- Expect(len(res)).To(Equal(0))
- })
- It("podman cp stdin/stdout", func() {
- SkipIfRemote("FIXME: podman-remote cp not implemented yet")
- session := podmanTest.Podman([]string{"create", ALPINE, "ls", "foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- name := session.OutputToString()
+ // Copy FROM the container.
- testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir2")
- err := os.Mkdir(testDirPath, 0755)
- Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
- cmd := exec.Command("tar", "-zcvf", "file.tar.gz", testDirPath)
- _, err = cmd.Output()
+ destFile, err := ioutil.TempFile("", "")
Expect(err).To(BeNil())
+ defer destFile.Close()
+ defer os.Remove(destFile.Name())
- data, err := ioutil.ReadFile("foo.tar.gz")
- reader := strings.NewReader(string(data))
- cmd.Stdin = reader
- session = podmanTest.Podman([]string{"cp", "-", name + ":/foo"})
+ session = podmanTest.Podman([]string{"cp", name + ":foo", destFile.Name()})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", "file.tar.gz", name + ":/foo.tar.gz"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", name + ":/foo.tar.gz", "-"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- os.Remove("file.tar.gz")
- })
-
- It("podman cp tar", func() {
- testctr := "testctr"
- setup := podmanTest.RunTopContainer(testctr)
- setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
-
- session := podmanTest.Podman([]string{"exec", testctr, "mkdir", "foo"})
+ session = podmanTest.Podman([]string{"start", name})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- path, err := os.Getwd()
- Expect(err).To(BeNil())
- testDirPath := filepath.Join(path, "TestDir3")
- err = os.Mkdir(testDirPath, 0777)
+ // Now make sure the content matches.
+ roundtripContent, err := ioutil.ReadFile(destFile.Name())
Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
- cmd := exec.Command("tar", "-cvf", "file.tar", testDirPath)
- _, err = cmd.Output()
- Expect(err).To(BeNil())
-
- session = podmanTest.Podman([]string{"cp", "file.tar", "testctr:/foo/"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"exec", testctr, "ls", "-l", "foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("file.tar"))
-
- os.Remove("file.tar")
+ Expect(roundtripContent).To(Equal(originalContent))
})
- It("podman cp tar --extract", func() {
- testctr := "testctr"
- setup := podmanTest.RunTopContainer(testctr)
- setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
-
- session := podmanTest.Podman([]string{"exec", testctr, "mkdir", "/foo"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- path, err := os.Getwd()
- Expect(err).To(BeNil())
- testDirPath := filepath.Join(path, "TestDir4")
- err = os.Mkdir(testDirPath, 0777)
- Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
- f, err := os.Create(filepath.Join(testDirPath, "a.txt"))
- Expect(err).To(BeNil())
- _, err = f.Write([]byte("Hello World!!!\n"))
- f.Close()
- cmd := exec.Command("tar", "-cvf", "file.tar", "TestDir4")
- exec.Command("tar", "-cvf", "/home/mvasek/file.tar", testDirPath)
- _, err = cmd.Output()
+ // Create a symlink in the container, use it as a copy destination and
+ // make sure that the link and the resolved path are accessible and
+ // give the right content.
+ It("podman cp symlink", func() {
+ srcFile, err := ioutil.TempFile("", "")
Expect(err).To(BeNil())
- defer os.Remove("file.tar")
+ defer srcFile.Close()
+ defer os.Remove(srcFile.Name())
- session = podmanTest.Podman([]string{"cp", "--extract", "file.tar", "testctr:/foo/"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"exec", testctr, "cat", "/foo/TestDir4/a.txt"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(session.OutputToString()).To(ContainSubstring("Hello World!!!"))
- })
+ originalContent := []byte("podman cp symlink test")
+ err = ioutil.WriteFile(srcFile.Name(), originalContent, 0644)
+ Expect(err).To(BeNil())
- It("podman cp symlink", func() {
session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
name := session.OutputToString()
- srcPath := filepath.Join(podmanTest.RunRoot, "cp_test.txt")
- fromHostToContainer := []byte("copy from host to container")
- err := ioutil.WriteFile(srcPath, fromHostToContainer, 0644)
- Expect(err).To(BeNil())
-
session = podmanTest.Podman([]string{"exec", name, "ln", "-s", "/tmp", "/test"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", "--pause=false", srcPath, name + ":/test"})
+ session = podmanTest.Podman([]string{"cp", "--pause=false", srcFile.Name(), name + ":/test"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- _, err = os.Stat("/tmp/cp_test.txt")
- Expect(err).To(Not(BeNil()))
-
- session = podmanTest.Podman([]string{"exec", name, "ln", "-s", "/tmp/nonesuch", "/test1"})
+ session = podmanTest.Podman([]string{"exec", name, "cat", "/tmp/" + filepath.Base(srcFile.Name())})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(string(originalContent)))
- session = podmanTest.Podman([]string{"cp", "--pause=false", srcPath, name + ":/test1/"})
+ session = podmanTest.Podman([]string{"exec", name, "cat", "/test/" + filepath.Base(srcFile.Name())})
session.WaitWithDefaultTimeout()
- Expect(session).To(ExitWithError())
-
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(string(originalContent)))
})
+
+ // Copy a file to a volume in the container. The tricky part is that
+ // containers mustn't be running for copying, so Podman has to do some
+ // intense Yoga and 1) detect volume paths on the container, 2) resolve
+ // the path to the volume's mount point on the host, and 3) copy the
+ // data to the volume and not the container.
It("podman cp volume", func() {
+ srcFile, err := ioutil.TempFile("", "")
+ Expect(err).To(BeNil())
+ defer srcFile.Close()
+ defer os.Remove(srcFile.Name())
+
+ originalContent := []byte("podman cp volume")
+ err = ioutil.WriteFile(srcFile.Name(), originalContent, 0644)
+ Expect(err).To(BeNil())
session := podmanTest.Podman([]string{"volume", "create", "data"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -279,23 +151,31 @@ var _ = Describe("Podman cp", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- err = ioutil.WriteFile("cp_vol", []byte("copy to the volume"), 0644)
- if err != nil {
- os.Exit(1)
- }
- session = podmanTest.Podman([]string{"cp", "cp_vol", "container1" + ":/data/cp_vol1"})
+ session = podmanTest.Podman([]string{"cp", srcFile.Name(), "container1" + ":/data/file.txt"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", "container1" + ":/data/cp_vol1", "cp_vol2"})
+ // Now get the volume's mount point, read the file and make
+ // sure the contents match.
+ session = podmanTest.Podman([]string{"volume", "inspect", "data", "--format", "{{.Mountpoint}}"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- os.Remove("cp_vol")
- os.Remove("cp_vol2")
+ volumeMountPoint := session.OutputToString()
+ copiedContent, err := ioutil.ReadFile(filepath.Join(volumeMountPoint, "file.txt"))
+ Expect(err).To(BeNil())
+ Expect(copiedContent).To(Equal(originalContent))
})
+ // Create another user in the container, let them create a file, copy
+ // it to the host and back to the container and make sure that we can
+ // access it, and (roughly) the right users own it.
It("podman cp from ctr chown ", func() {
+ srcFile, err := ioutil.TempFile("", "")
+ Expect(err).To(BeNil())
+ defer srcFile.Close()
+ defer os.Remove(srcFile.Name())
+
setup := podmanTest.RunTopContainer("testctr")
setup.WaitWithDefaultTimeout()
Expect(setup.ExitCode()).To(Equal(0))
@@ -308,17 +188,19 @@ var _ = Describe("Podman cp", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- session = podmanTest.Podman([]string{"cp", "--pause=false", "testctr:/tmp/testfile", "testfile1"})
+ session = podmanTest.Podman([]string{"cp", "--pause=false", "testctr:/tmp/testfile", srcFile.Name()})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
// owner of the file copied to local machine is not testuser
- cmd := exec.Command("ls", "-l", "testfile1")
+ u, err := user.Current()
+ Expect(err).To(BeNil())
+ cmd := exec.Command("ls", "-l", srcFile.Name())
cmdRet, err := cmd.Output()
Expect(err).To(BeNil())
- Expect(strings.Contains(string(cmdRet), "testuser")).To(BeFalse())
+ Expect(string(cmdRet)).To(ContainSubstring(u.Username))
- session = podmanTest.Podman([]string{"cp", "--pause=false", "testfile1", "testctr:testfile2"})
+ session = podmanTest.Podman([]string{"cp", "--pause=false", srcFile.Name(), "testctr:testfile2"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -327,45 +209,35 @@ var _ = Describe("Podman cp", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring("root"))
-
- os.Remove("testfile1")
})
- It("podman cp the root directory from the ctr to an existing directory on the host ", func() {
- imgName := "test-cp-root-dir:latest"
- DockerfileName := "Dockerfile.test-cp-root-dir"
- ctrName := "test-container-cp-root"
- session := podmanTest.Podman([]string{"build", "-f", "build/" + DockerfileName, "-t", imgName, "build/"})
+ // 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() {
+ container := "copyroottohost"
+ session := podmanTest.RunTopContainer(container)
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- testDirPath := filepath.Join(podmanTest.RunRoot, "TestDirForCp")
-
- session = podmanTest.Podman([]string{"create", "--name", ctrName, imgName, "dummy"})
+ session = podmanTest.Podman([]string{"exec", container, "touch", "/dummy.txt"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- err := os.Mkdir(testDirPath, 0755)
+ tmpDir, err := ioutil.TempDir("", "")
Expect(err).To(BeNil())
- defer os.RemoveAll(testDirPath)
- // Copy the root directory of the container to an existing directory
- session = podmanTest.Podman([]string{"cp", ctrName + ":/", testDirPath})
+ session = podmanTest.Podman([]string{"cp", container + ":/", tmpDir})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- // The file should be in the directory,
- // not one layer too much of the directory called merged
- checkFile := filepath.Join(testDirPath, DockerfileName)
- _, err = os.Stat(checkFile)
+ cmd := exec.Command("ls", "-la", tmpDir)
+ output, err := cmd.Output()
+ lsOutput := string(output)
Expect(err).To(BeNil())
-
- session = podmanTest.Podman([]string{"container", "rm", ctrName})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- session = podmanTest.Podman([]string{"rmi", "-f", imgName})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(lsOutput).To(ContainSubstring("dummy.txt"))
+ Expect(lsOutput).To(ContainSubstring("tmp"))
+ Expect(lsOutput).To(ContainSubstring("etc"))
+ Expect(lsOutput).To(ContainSubstring("var"))
+ Expect(lsOutput).To(ContainSubstring("bin"))
+ Expect(lsOutput).To(ContainSubstring("usr"))
})
})
diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go
index c8782c743..0950a9321 100644
--- a/test/e2e/generate_kube_test.go
+++ b/test/e2e/generate_kube_test.go
@@ -469,4 +469,74 @@ var _ = Describe("Podman generate kube", func() {
Expect(inspect.ExitCode()).To(Equal(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`"pid"`))
})
+
+ It("podman generate kube multiple pods should fail", func() {
+ pod1 := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:pod1", ALPINE, "top"})
+ pod1.WaitWithDefaultTimeout()
+ Expect(pod1.ExitCode()).To(Equal(0))
+
+ pod2 := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:pod2", ALPINE, "top"})
+ pod2.WaitWithDefaultTimeout()
+ Expect(pod2.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", "pod1", "pod2"})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("podman generate kube with pods and containers should fail", func() {
+ pod1 := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:pod1", ALPINE, "top"})
+ pod1.WaitWithDefaultTimeout()
+ Expect(pod1.ExitCode()).To(Equal(0))
+
+ pod2 := podmanTest.Podman([]string{"run", "-dt", "--name", "top", ALPINE, "top"})
+ pod2.WaitWithDefaultTimeout()
+ Expect(pod2.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", "pod1", "top"})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("podman generate kube with containers in a pod should fail", func() {
+ pod1 := podmanTest.Podman([]string{"pod", "create", "--name", "pod1"})
+ pod1.WaitWithDefaultTimeout()
+ Expect(pod1.ExitCode()).To(Equal(0))
+
+ con := podmanTest.Podman([]string{"run", "-dt", "--pod", "pod1", "--name", "top", ALPINE, "top"})
+ con.WaitWithDefaultTimeout()
+ Expect(con.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", "top"})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).ToNot(Equal(0))
+ })
+
+ It("podman generate kube with multiple containers", func() {
+ con1 := podmanTest.Podman([]string{"run", "-dt", "--name", "con1", ALPINE, "top"})
+ con1.WaitWithDefaultTimeout()
+ Expect(con1.ExitCode()).To(Equal(0))
+
+ con2 := podmanTest.Podman([]string{"run", "-dt", "--name", "con2", ALPINE, "top"})
+ con2.WaitWithDefaultTimeout()
+ Expect(con2.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", "con1", "con2"})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+ })
+
+ It("podman generate kube with containers in a pod should fail", func() {
+ pod1 := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:pod1", "--name", "top1", ALPINE, "top"})
+ pod1.WaitWithDefaultTimeout()
+ Expect(pod1.ExitCode()).To(Equal(0))
+
+ pod2 := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:pod2", "--name", "top2", ALPINE, "top"})
+ pod2.WaitWithDefaultTimeout()
+ Expect(pod2.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", "pod1", "pod2"})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).ToNot(Equal(0))
+ })
})
diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go
index ffc914bc2..4e8ab5ad5 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -119,7 +119,13 @@ var _ = Describe("Podman network", func() {
})
It("podman network list --filter invalid value", func() {
- session := podmanTest.Podman([]string{"network", "ls", "--filter", "namr=ab"})
+ net := "net" + stringid.GenerateNonCryptoID()
+ session := podmanTest.Podman([]string{"network", "create", net})
+ session.WaitWithDefaultTimeout()
+ defer podmanTest.removeCNINetwork(net)
+ Expect(session.ExitCode()).To(BeZero())
+
+ session = podmanTest.Podman([]string{"network", "ls", "--filter", "namr=ab"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError())
Expect(session.ErrorToString()).To(ContainSubstring(`invalid filter "namr"`))
diff --git a/test/e2e/run_memory_test.go b/test/e2e/run_memory_test.go
index b3913c1e6..ad3a2b54f 100644
--- a/test/e2e/run_memory_test.go
+++ b/test/e2e/run_memory_test.go
@@ -38,7 +38,7 @@ var _ = Describe("Podman run memory", func() {
var session *PodmanSessionIntegration
if CGROUPSV2 {
- session = podmanTest.Podman([]string{"run", "--memory=40m", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.max"})
+ session = podmanTest.Podman([]string{"run", "--memory=40m", "--net=none", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.max"})
} else {
session = podmanTest.Podman([]string{"run", "--memory=40m", ALPINE, "cat", "/sys/fs/cgroup/memory/memory.limit_in_bytes"})
}
@@ -55,7 +55,7 @@ var _ = Describe("Podman run memory", func() {
var session *PodmanSessionIntegration
if CGROUPSV2 {
- session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.low"})
+ session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", "--net=none", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.low"})
} else {
session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", ALPINE, "cat", "/sys/fs/cgroup/memory/memory.soft_limit_in_bytes"})
}
@@ -81,7 +81,7 @@ var _ = Describe("Podman run memory", func() {
var session *PodmanSessionIntegration
if CGROUPSV2 {
- session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.low"})
+ session = podmanTest.Podman([]string{"run", "--net=none", "--memory-reservation=40m", ALPINE, "sh", "-c", "cat /sys/fs/cgroup/$(sed -e 's|0::||' < /proc/self/cgroup)/memory.low"})
} else {
session = podmanTest.Podman([]string{"run", "--memory-reservation=40m", ALPINE, "cat", "/sys/fs/cgroup/memory/memory.soft_limit_in_bytes"})
}
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index 3e80e953e..3fb00a28b 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -49,9 +49,28 @@ var _ = Describe("Podman run networking", func() {
Expect(session.ExitCode()).To(Equal(0))
})
+ It("podman run network connection with default", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "default", ALPINE, "wget", "www.podman.io"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman run network connection with none", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "none", ALPINE, "wget", "www.podman.io"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(1))
+ Expect(session.ErrorToString()).To(ContainSubstring("wget: bad address 'www.podman.io'"))
+ })
+
+ It("podman run network connection with private", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "private", ALPINE, "wget", "www.podman.io"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
It("podman run network connection with loopback", func() {
- session := podmanTest.Podman([]string{"run", "-dt", "--network", "host", ALPINE, "wget", "www.podman.io"})
- session.Wait(90)
+ session := podmanTest.Podman([]string{"run", "--network", "host", ALPINE, "wget", "www.podman.io"})
+ session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 0d65a3e59..58ef9a647 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -233,6 +233,39 @@ var _ = Describe("Podman run", func() {
return jsonFile
}
+ It("podman run mask and unmask path test", func() {
+ session := podmanTest.Podman([]string{"run", "-d", "--name=maskCtr1", "--security-opt", "unmask=ALL", "--security-opt", "mask=/proc/acpi", ALPINE, "sleep", "200"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr1", "ls", "/sys/firmware"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(Not(BeEmpty()))
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr1", "ls", "/proc/acpi"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(BeEmpty())
+
+ session = podmanTest.Podman([]string{"run", "-d", "--name=maskCtr2", "--security-opt", "unmask=/proc/acpi:/sys/firmware", ALPINE, "sleep", "200"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr2", "ls", "/sys/firmware"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(Not(BeEmpty()))
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr2", "ls", "/proc/acpi"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(Not(BeEmpty()))
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"run", "-d", "--name=maskCtr3", "--security-opt", "mask=/sys/power/disk", ALPINE, "sleep", "200"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"exec", "maskCtr3", "cat", "/sys/power/disk"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.OutputToString()).To(BeEmpty())
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
It("podman run seccomp test", func() {
session := podmanTest.Podman([]string{"run", "-it", "--security-opt", strings.Join([]string{"seccomp=", forbidGetCWDSeccompProfile()}, ""), ALPINE, "pwd"})
session.WaitWithDefaultTimeout()
@@ -1267,7 +1300,7 @@ USER mail`
It("podman run verify pids-limit", func() {
SkipIfCgroupV1("pids-limit not supported on cgroup V1")
limit := "4321"
- session := podmanTest.Podman([]string{"run", "--pids-limit", limit, "--rm", ALPINE, "cat", "/sys/fs/cgroup/pids.max"})
+ session := podmanTest.Podman([]string{"run", "--pids-limit", limit, "--net=none", "--rm", ALPINE, "cat", "/sys/fs/cgroup/pids.max"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(limit))
diff --git a/test/system/030-run.bats b/test/system/030-run.bats
index 37695f205..3ee141f5f 100644
--- a/test/system/030-run.bats
+++ b/test/system/030-run.bats
@@ -536,6 +536,43 @@ json-file | f
run_podman untag $IMAGE $newtag $newtag2
}
+# Regression test for issue #8558
+@test "podman run on untagged image: make sure that image metadata is set" {
+ run_podman inspect $IMAGE --format "{{.ID}}"
+ imageID="$output"
+
+ # prior to #8623 `podman run` would error out on untagged images with:
+ # Error: both RootfsImageName and RootfsImageID must be set if either is set: invalid argument
+ run_podman untag $IMAGE
+ run_podman run --rm $imageID ls
+
+ run_podman tag $imageID $IMAGE
+}
+
+@test "Verify /run/.containerenv exist" {
+ run_podman run --rm $IMAGE ls -1 /run/.containerenv
+ is "$output" "/run/.containerenv"
+
+ run_podman run --privileged --rm $IMAGE sh -c '. /run/.containerenv; echo $engine'
+ is "$output" ".*podman.*" "failed to identify engine"
+
+ run_podman run --privileged --name "testcontainerenv" --rm $IMAGE sh -c '. /run/.containerenv; echo $name'
+ is "$output" ".*testcontainerenv.*"
+
+ run_podman run --privileged --rm $IMAGE sh -c '. /run/.containerenv; echo $image'
+ is "$output" ".*$IMAGE.*" "failed to idenitfy image"
+
+ run_podman run --privileged --rm $IMAGE sh -c '. /run/.containerenv; echo $rootless'
+ # FIXME: on some CI systems, 'run --privileged' emits a spurious
+ # warning line about dup devices. Ignore it.
+ remove_same_dev_warning
+ if is_rootless; then
+ is "$output" "1"
+ else
+ is "$output" "0"
+ fi
+}
+
@test "podman run with --net=host and --port prints warning" {
rand=$(random_string 10)
diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats
index a3d6a5800..a081a7ce1 100644
--- a/test/system/035-logs.bats
+++ b/test/system/035-logs.bats
@@ -21,6 +21,9 @@ load helpers
run_podman logs $cid
is "$output" "$rand_string" "output from podman-logs after container is run"
+ # test --since with Unix timestamps
+ run_podman logs --since 1000 $cid
+
run_podman rm $cid
}
diff --git a/test/system/065-cp.bats b/test/system/065-cp.bats
index 6bf897790..43bdf217d 100644
--- a/test/system/065-cp.bats
+++ b/test/system/065-cp.bats
@@ -7,6 +7,290 @@
load helpers
+@test "podman cp file from host to container" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-file-host-to-ctr
+ mkdir -p $srcdir
+ local -a randomcontent=(
+ random-0-$(random_string 10)
+ random-1-$(random_string 15)
+ random-2-$(random_string 20)
+ )
+ echo "${randomcontent[0]}" > $srcdir/hostfile0
+ echo "${randomcontent[1]}" > $srcdir/hostfile1
+ echo "${randomcontent[2]}" > $srcdir/hostfile2
+
+ run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
+ run_podman exec cpcontainer mkdir /srv/subdir
+
+ # format is: <id> | <destination arg to cp> | <full dest path> | <test name>
+ # where:
+ # id is 0-2, one of the random strings/files
+ # dest arg is the right-hand argument to 'podman cp' (may be implicit)
+ # dest path is the full explicit path we expect to see
+ # test name is a short description of what we're testing here
+ tests="
+0 | / | /hostfile0 | copy to root
+0 | /anotherbase.txt | /anotherbase.txt | copy to root, new name
+0 | /tmp | /tmp/hostfile0 | copy to /tmp
+1 | /tmp/ | /tmp/hostfile1 | copy to /tmp/
+2 | /tmp/. | /tmp/hostfile2 | copy to /tmp/.
+0 | /tmp/hostfile2 | /tmp/hostfile2 | overwrite previous copy
+0 | /tmp/anotherbase.txt | /tmp/anotherbase.txt | copy to /tmp, new name
+0 | . | /srv/hostfile0 | copy to workdir (rel path), new name
+1 | ./ | /srv/hostfile1 | copy to workdir (rel path), new name
+0 | anotherbase.txt | /srv/anotherbase.txt | copy to workdir (rel path), new name
+0 | subdir | /srv/subdir/hostfile0 | copy to workdir/subdir
+"
+
+ # Copy one of the files into container, exec+cat, confirm the file
+ # is there and matches what we expect
+ while read id dest dest_fullname description; do
+ run_podman cp $srcdir/hostfile$id cpcontainer:$dest
+ run_podman exec cpcontainer cat $dest_fullname
+ is "$output" "${randomcontent[$id]}" "$description (cp -> ctr:$dest)"
+ done < <(parse_table "$tests")
+
+ # Host path does not exist.
+ run_podman 125 cp $srcdir/IdoNotExist cpcontainer:/tmp
+ is "$output" 'Error: ".*/IdoNotExist" could not be found on the host' \
+ "copy nonexistent host path"
+
+ # Container path does not exist. Notice that the error message shows how
+ # the specified container is resolved.
+ run_podman 125 cp $srcdir/hostfile0 cpcontainer:/IdoNotExist/
+ is "$output" 'Error: "/IdoNotExist/" could not be found on container.*(resolved to .*/IdoNotExist.*' \
+ "copy into nonexistent path in container"
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp --extract=true tar archive to container" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ # Create tempfile with random name and content
+ dirname=cp-test-extract
+ srcdir=$PODMAN_TMPDIR/$dirname
+ mkdir -p $srcdir
+ rand_filename=$(random_string 20)
+ rand_content=$(random_string 50)
+ echo $rand_content > $srcdir/$rand_filename
+ chmod 644 $srcdir/$rand_filename
+
+ # Now tar it up!
+ tar_file=$PODMAN_TMPDIR/archive.tar.gz
+ tar -C $PODMAN_TMPDIR -zvcf $tar_file $dirname
+
+ run_podman run -d --name cpcontainer $IMAGE sleep infinity
+
+ # First just copy without extracting the archive.
+ run_podman cp $tar_file cpcontainer:/tmp
+ # Now remove the archive which will also test if it exists and is a file.
+ # To save expensive exec'ing, create a file for the next tests.
+ run_podman exec cpcontainer sh -c "rm /tmp/archive.tar.gz; touch /tmp/file.txt"
+
+ # Now copy with extracting the archive. NOTE that Podman should
+ # auto-decompress the file if needed.
+ run_podman cp --extract=true $tar_file cpcontainer:/tmp
+ run_podman exec cpcontainer cat /tmp/$dirname/$rand_filename
+ is "$output" "$rand_content"
+
+ # Test extract on non archive.
+ run_podman cp --extract=true $srcdir/$rand_filename cpcontainer:/foo.txt
+
+ # Cannot extract an archive to a file!
+ run_podman 125 cp --extract=true $tar_file cpcontainer:/tmp/file.txt
+ is "$output" 'Error: cannot extract archive .* to file "/tmp/file.txt"'
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp file from container to host" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-file-ctr-to-host
+ mkdir -p $srcdir
+
+ # 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[1]} > /srv/containerfile1"
+ run_podman exec cpcontainer sh -c "mkdir /srv/subdir; echo ${randomcontent[2]} > /srv/subdir/containerfile2"
+
+ # 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 | copy to srcdir/
+0 | /tmp/containerfile | / | /containerfile | copy to srcdir/
+0 | /tmp/containerfile | /. | /containerfile | copy to srcdir/.
+0 | /tmp/containerfile | /newfile | /newfile | copy to srcdir/newfile
+1 | containerfile1 | / | /containerfile1 | copy from workdir (rel path) to srcdir
+2 | subdir/containerfile2 | / | /containerfile2 | copy from workdir/subdir (rel path) to srcdir
+"
+
+ # Copy one of the files to the host, cat, confirm the file
+ # is there and matches what we expect
+ while read id src dest dest_fullname description; do
+ # dest may be "''" for empty table cells
+ if [[ $dest == "''" ]];then
+ unset dest
+ fi
+ run_podman cp cpcontainer:$src "$srcdir$dest"
+ run cat $srcdir$dest_fullname
+ is "$output" "${randomcontent[$id]}" "$description (cp ctr:$src to \$srcdir$dest)"
+ rm $srcdir/$dest_fullname
+ done < <(parse_table "$tests")
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp dir from host to container" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ dirname=dir-test
+ srcdir=$PODMAN_TMPDIR/$dirname
+ mkdir -p $srcdir
+ local -a randomcontent=(
+ random-0-$(random_string 10)
+ random-1-$(random_string 15)
+ )
+ echo "${randomcontent[0]}" > $srcdir/hostfile0
+ echo "${randomcontent[1]}" > $srcdir/hostfile1
+
+ run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
+ run_podman exec cpcontainer mkdir /srv/subdir
+
+ # format is: <source arg to cp (appended to srcdir)> | <destination arg to cp> | <full dest path> | <test name>
+ tests="
+ | / | /dir-test | copy 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)
+"
+
+ while read src dest dest_fullname description; do
+ # src may be "''" for empty table cells
+ 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)"
+ done < <(parse_table "$tests")
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp dir from container to host" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/dir-test
+ mkdir -p $srcdir
+
+ run_podman run -d --name cpcontainer --workdir=/srv $IMAGE sleep infinity
+ run_podman exec cpcontainer sh -c 'mkdir /srv/subdir; echo "This first file is on the container" > /srv/subdir/containerfile1'
+ run_podman exec cpcontainer sh -c 'echo "This second file is on the container as well" > /srv/subdir/containerfile2'
+
+ run_podman cp cpcontainer:/srv $srcdir
+ run cat $srcdir/srv/subdir/containerfile1
+ is "$output" "This first file is on the container"
+ run cat $srcdir/srv/subdir/containerfile2
+ is "$output" "This second file is on the container as well"
+ rm -rf $srcdir/srv/subdir
+
+ run_podman cp cpcontainer:/srv/. $srcdir
+ run ls $srcdir/subdir
+ run cat $srcdir/subdir/containerfile1
+ is "$output" "This first file is on the container"
+ run cat $srcdir/subdir/containerfile2
+ is "$output" "This second file is on the container as well"
+ rm -rf $srcdir/subdir
+
+ run_podman cp cpcontainer:/srv/subdir/. $srcdir
+ run cat $srcdir/containerfile1
+ is "$output" "This first file is on the container"
+ run cat $srcdir/containerfile2
+ is "$output" "This second file is on the container as well"
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp file from host to container volume" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-volume
+ mkdir -p $srcdir
+ echo "This file should be in volume2" > $srcdir/hostfile
+ volume1=$(random_string 20)
+ volume2=$(random_string 20)
+
+ run_podman volume create $volume1
+ run_podman volume inspect $volume1 --format "{{.Mountpoint}}"
+ volume1_mount="$output"
+ run_podman volume create $volume2
+ run_podman volume inspect $volume2 --format "{{.Mountpoint}}"
+ volume2_mount="$output"
+
+ # Create a container using the volume. Note that copying on not-running
+ # containers is allowed, so Podman has to analyze the container paths and
+ # check if they are hitting a volume, and eventually resolve to the path on
+ # the *host*.
+ # This test is extra tricky, as volume2 is mounted into a sub-directory of
+ # volume1. Podman must copy the file into volume2 and not volume1.
+ run_podman create --name cpcontainer -v $volume1:/tmp/volume -v $volume2:/tmp/volume/sub-volume $IMAGE
+
+ run_podman cp $srcdir/hostfile cpcontainer:/tmp/volume/sub-volume
+
+ run cat $volume2_mount/hostfile
+ is "$output" "This file should be in volume2"
+
+ # Volume 1 must be empty.
+ run ls $volume1_mount
+ is "$output" ""
+
+ run_podman rm -f cpcontainer
+ run_podman volume rm $volume1 $volume2
+}
+
+
+@test "podman cp file from host to container mount" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-mount-src
+ mountdir=$PODMAN_TMPDIR/cp-test-mount
+ mkdir -p $srcdir $mountdir
+ echo "This file should be in the mount" > $srcdir/hostfile
+
+ volume=$(random_string 20)
+ run_podman volume create $volume
+
+ # Make it a bit more complex and put the mount on a volume.
+ run_podman create --name cpcontainer -v $volume:/tmp/volume -v $mountdir:/tmp/volume/mount $IMAGE
+
+ run_podman cp $srcdir/hostfile cpcontainer:/tmp/volume/mount
+
+ run cat $mountdir/hostfile
+ is "$output" "This file should be in the mount"
+
+ run_podman rm -f cpcontainer
+ run_podman volume rm $volume
+}
+
+
# Create two random-name random-content files in /tmp in the container
# podman-cp them into the host using '/tmp/*', i.e. asking podman to
# perform wildcard expansion in the container. We should get both
@@ -51,8 +335,7 @@ load helpers
run_podman 125 cp 'cpcontainer:/tmp/*' $dstdir/
# FIXME: this might not be the exactly correct error message
- is "$output" ".*error evaluating symlinks.*lstat.*no such file or dir" \
- "Expected error from copying invalid symlink"
+ is "$output" 'Error: "/tmp/\*" could not be found on container.*'
# make sure there are no files in dstdir
is "$(/bin/ls -1 $dstdir)" "" "incorrectly copied symlink from host"
@@ -78,8 +361,7 @@ load helpers
sh -c "ln -s $srcdir/hostfile file1;ln -s file\* copyme"
run_podman 125 cp cpcontainer:copyme $dstdir
- is "$output" ".*error evaluating symlinks.*lstat.*no such file or dir" \
- "Expected error from copying invalid symlink"
+ is "$output" 'Error: "copyme*" could not be found on container.*'
# make sure there are no files in dstdir
is "$(/bin/ls -1 $dstdir)" "" "incorrectly copied symlink from host"
@@ -101,8 +383,7 @@ load helpers
sh -c "ln -s $srcdir/hostfile /tmp/\*"
run_podman 125 cp 'cpcontainer:/tmp/*' $dstdir
- is "$output" ".*error evaluating symlinks.*lstat.*no such file or dir" \
- "Expected error from copying invalid symlink"
+ is "$output" 'Error: "/tmp/\*" could not be found on container.*'
# dstdir must be empty
is "$(/bin/ls -1 $dstdir)" "" "incorrectly copied symlink from host"
@@ -110,8 +391,6 @@ load helpers
run_podman rm cpcontainer
}
-###############################################################################
-# cp INTO container
# THIS IS EXTREMELY WEIRD. Podman expands symlinks in weird ways.
@test "podman cp into container: weird symlink expansion" {
@@ -148,7 +427,7 @@ load helpers
is "$output" "" "output from podman cp 1"
run_podman 125 cp --pause=false $srcdir/$rand_filename2 cpcontainer:/tmp/d2/x/
- is "$output" ".*stat.* no such file or directory" "cp will not create nonexistent destination directory"
+ is "$output" 'Error: "/tmp/d2/x/" could not be found on container.*' "cp will not create nonexistent destination directory"
run_podman cp --pause=false $srcdir/$rand_filename3 cpcontainer:/tmp/d3/x
is "$output" "" "output from podman cp 3"
@@ -160,6 +439,7 @@ load helpers
run_podman exec cpcontainer cat /tmp/nonesuch1
is "$output" "$rand_content1" "cp creates destination file"
+
# cp into nonexistent directory should not mkdir nonesuch2 directory
run_podman 1 exec cpcontainer test -e /tmp/nonesuch2
@@ -168,8 +448,6 @@ load helpers
is "$output" "$rand_content3" "cp creates file named x"
run_podman rm -f cpcontainer
-
-
}
@@ -212,6 +490,103 @@ load helpers
}
+@test "podman cp from stdin to container" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ # Create tempfile with random name and content
+ srcdir=$PODMAN_TMPDIR/cp-test-stdin
+ mkdir -p $srcdir
+ rand_filename=$(random_string 20)
+ rand_content=$(random_string 50)
+ echo $rand_content > $srcdir/$rand_filename
+ chmod 644 $srcdir/$rand_filename
+
+ # Now tar it up!
+ tar_file=$PODMAN_TMPDIR/archive.tar.gz
+ tar -zvcf $tar_file $srcdir
+
+ run_podman run -d --name cpcontainer $IMAGE sleep infinity
+
+ # NOTE: podman is supposed to auto-detect the gzip compression and
+ # decompress automatically.
+ #
+ # "-" will evaluate to "/dev/stdin" when used a source.
+ run_podman cp - cpcontainer:/tmp < $tar_file
+ run_podman exec cpcontainer cat /tmp/$srcdir/$rand_filename
+ is "$output" "$rand_content"
+ run_podman exec cpcontainer rm -rf /tmp/$srcdir
+
+ # Now for "/dev/stdin".
+ run_podman cp /dev/stdin cpcontainer:/tmp < $tar_file
+ run_podman exec cpcontainer cat /tmp/$srcdir/$rand_filename
+ is "$output" "$rand_content"
+
+ # Error checks below ...
+
+ # Input stream must be a (compressed) tar archive.
+ run_podman 125 cp - cpcontainer:/tmp < $srcdir/$rand_filename
+ is "$output" "Error:.*: error reading tar stream.*" "input stream must be a (compressed) tar archive"
+
+ # Destination must be a directory (on an existing file).
+ run_podman exec cpcontainer touch /tmp/file.txt
+ run_podman 125 cp /dev/stdin cpcontainer:/tmp/file.txt < $tar_file
+ is "$output" 'Error: destination must be a directory or stream when copying from a stream'
+
+ # Destination must be a directory (on an absent path).
+ run_podman 125 cp /dev/stdin cpcontainer:/tmp/IdoNotExist < $tar_file
+ is "$output" 'Error: destination must be a directory or stream when copying from a stream'
+
+ run_podman rm -f cpcontainer
+}
+
+
+@test "podman cp from container to stdout" {
+ skip_if_remote "podman-remote does not yet handle cp"
+
+ srcdir=$PODMAN_TMPDIR/cp-test-stdout
+ mkdir -p $srcdir
+ rand_content=$(random_string 50)
+
+ run_podman run -d --name cpcontainer $IMAGE sleep infinity
+
+ run_podman exec cpcontainer sh -c "echo '$rand_content' > /tmp/file.txt"
+ run_podman exec cpcontainer touch /tmp/empty.txt
+
+ # Copying from stdout will always compress. So let's copy the previously
+ # created file from the container via stdout, untar the archive and make
+ # sure the file exists with the expected content.
+ #
+ # NOTE that we can't use run_podman because that uses the BATS 'run'
+ # function which redirects stdout and stderr. Here we need to guarantee
+ # that podman's stdout is a pipe, not any other form of redirection.
+
+ # Copy file.
+ $PODMAN cp cpcontainer:/tmp/file.txt - > $srcdir/stdout.tar
+ if [ $? -ne 0 ]; then
+ die "Command failed: podman cp ... - | cat"
+ fi
+
+ tar xvf $srcdir/stdout.tar -C $srcdir
+ run cat $srcdir/file.txt
+ is "$output" "$rand_content"
+ run 1 ls $srcfir/empty.txt
+ rm -f $srcdir/*
+
+ # Copy directory.
+ $PODMAN cp cpcontainer:/tmp - > $srcdir/stdout.tar
+ if [ $? -ne 0 ]; then
+ die "Command failed: podman cp ... - | cat : $output"
+ fi
+
+ tar xvf $srcdir/stdout.tar -C $srcdir
+ run cat $srcdir/file.txt
+ is "$output" "$rand_content"
+ run cat $srcdir/empty.txt
+ is "$output" ""
+
+ run_podman rm -f cpcontainer
+}
+
function teardown() {
# In case any test fails, clean up the container we left behind
run_podman rm -f cpcontainer
diff --git a/test/system/120-load.bats b/test/system/120-load.bats
index 8ea9b1c69..272e2ae93 100644
--- a/test/system/120-load.bats
+++ b/test/system/120-load.bats
@@ -28,12 +28,15 @@ verify_iid_and_name() {
@test "podman save to pipe and load" {
# Generate a random name and tag (must be lower-case)
- local random_name=x$(random_string 12 | tr A-Z a-z)
- local random_tag=t$(random_string 7 | tr A-Z a-z)
+ local random_name=x0$(random_string 12 | tr A-Z a-z)
+ local random_tag=t0$(random_string 7 | tr A-Z a-z)
local fqin=localhost/$random_name:$random_tag
run_podman tag $IMAGE $fqin
- archive=$PODMAN_TMPDIR/myimage-$(random_string 8).tar
+ # Believe it or not, 'podman load' would barf if any path element
+ # included a capital letter
+ archive=$PODMAN_TMPDIR/MySubDirWithCaps/MyImage-$(random_string 8).tar
+ mkdir -p $(dirname $archive)
# We can't use run_podman because that uses the BATS 'run' function
# which redirects stdout and stderr. Here we need to guarantee
@@ -51,19 +54,20 @@ verify_iid_and_name() {
run_podman images $fqin --format '{{.Repository}}:{{.Tag}}'
is "$output" "$fqin" "image preserves name across save/load"
- # FIXME: when/if 7337 gets fixed, load with a new tag
- if false; then
- local new_name=x$(random_string 14 | tr A-Z a-z)
- local new_tag=t$(random_string 6 | tr A-Z a-z)
+ # Load with a new tag
+ local new_name=x1$(random_string 14 | tr A-Z a-z)
+ local new_tag=t1$(random_string 6 | tr A-Z a-z)
run_podman rmi $fqin
- fqin=localhost/$new_name:$new_tag
- run_podman load -i $archive $fqin
- run_podman images $fqin --format '{{.Repository}}:{{.Tag}}'
- is "$output" "$fqin" "image can be loaded with new name:tag"
- fi
+
+ new_fqin=localhost/$new_name:$new_tag
+ run_podman load -i $archive $new_fqin
+ run_podman images --format '{{.Repository}}:{{.Tag}}' --sort tag
+ is "${lines[0]}" "$IMAGE" "image is preserved"
+ is "${lines[1]}" "$fqin" "image is reloaded with old fqin"
+ is "${lines[2]}" "$new_fqin" "image is reloaded with new fqin too"
# Clean up
- run_podman rmi $fqin
+ run_podman rmi $fqin $new_fqin
}
diff --git a/test/system/400-unprivileged-access.bats b/test/system/400-unprivileged-access.bats
index 142d7dcd9..20fdd068f 100644
--- a/test/system/400-unprivileged-access.bats
+++ b/test/system/400-unprivileged-access.bats
@@ -118,7 +118,7 @@ EOF
/proc/scsi
/sys/firmware
/sys/fs/selinux
- /sys/dev
+ /sys/dev/block
)
# Some of the above may not exist on our host. Find only the ones that do.
diff --git a/troubleshooting.md b/troubleshooting.md
index 3ff578142..78e22fa2f 100644
--- a/troubleshooting.md
+++ b/troubleshooting.md
@@ -248,7 +248,10 @@ This means johndoe is allocated UIDS 100000-165535 as well as his standard UID i
/etc/passwd file.
You should ensure that each user has a unique range of uids, because overlapping UIDs,
-would potentially allow one user to attack another user.
+would potentially allow one user to attack another user. In addition, make sure
+that the range of uids you allocate can cover all uids that the container
+requires. For example, if the container has a user with uid 10000, ensure you
+have at least 10001 subuids.
You could also use the usermod program to assign UIDs to a user.
diff --git a/utils/utils_supported.go b/utils/utils_supported.go
index bcaa2c61a..6f517dc72 100644
--- a/utils/utils_supported.go
+++ b/utils/utils_supported.go
@@ -43,6 +43,15 @@ func RunUnderSystemdScope(pid int, slice string, unitName string) error {
ch := make(chan string)
_, err = conn.StartTransientUnit(unitName, "replace", properties, ch)
if err != nil {
+ // On errors check if the cgroup already exists, if it does move the process there
+ if props, err := conn.GetUnitTypeProperties(unitName, "Scope"); err == nil {
+ if cgroup, ok := props["ControlGroup"].(string); ok && cgroup != "" {
+ if err := moveUnderCgroup(cgroup, "", []uint32{uint32(pid)}); err != nil {
+ return err
+ }
+ return nil
+ }
+ }
return err
}
defer conn.Close()
@@ -101,6 +110,13 @@ func GetCgroupProcess(pid int) (string, error) {
// MoveUnderCgroupSubtree moves the PID under a cgroup subtree.
func MoveUnderCgroupSubtree(subtree string) error {
+ return moveUnderCgroup("", subtree, nil)
+}
+
+// moveUnderCgroup moves a group of processes to a new cgroup.
+// If cgroup is the empty string, then the current calling process cgroup is used.
+// If processes is empty, then the processes from the current cgroup are moved.
+func moveUnderCgroup(cgroup, subtree string, processes []uint32) error {
procFile := "/proc/self/cgroup"
f, err := os.Open(procFile)
if err != nil {
@@ -140,13 +156,12 @@ func MoveUnderCgroupSubtree(subtree string) error {
cgroupRoot = filepath.Join(cgroupRoot, controller)
}
- processes, err := ioutil.ReadFile(filepath.Join(cgroupRoot, parts[2], "cgroup.procs"))
- if err != nil {
- return err
+ parentCgroup := cgroup
+ if parentCgroup == "" {
+ parentCgroup = parts[2]
}
-
- newCgroup := filepath.Join(cgroupRoot, parts[2], subtree)
- if err := os.Mkdir(newCgroup, 0755); err != nil {
+ newCgroup := filepath.Join(cgroupRoot, parentCgroup, subtree)
+ if err := os.Mkdir(newCgroup, 0755); err != nil && !os.IsExist(err) {
return err
}
@@ -156,9 +171,21 @@ func MoveUnderCgroupSubtree(subtree string) error {
}
defer f.Close()
- for _, pid := range bytes.Split(processes, []byte("\n")) {
- if _, err := f.Write(pid); err != nil {
- logrus.Warnf("Cannot move process %s to cgroup %q", pid, newCgroup)
+ if len(processes) > 0 {
+ for _, pid := range processes {
+ if _, err := f.Write([]byte(fmt.Sprintf("%d\n", pid))); err != nil {
+ logrus.Warnf("Cannot move process %d to cgroup %q", pid, newCgroup)
+ }
+ }
+ } else {
+ processesData, err := ioutil.ReadFile(filepath.Join(cgroupRoot, parts[2], "cgroup.procs"))
+ if err != nil {
+ return err
+ }
+ for _, pid := range bytes.Split(processesData, []byte("\n")) {
+ if _, err := f.Write(pid); err != nil {
+ logrus.Warnf("Cannot move process %s to cgroup %q", string(pid), newCgroup)
+ }
}
}
}
diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go
index 2769781f2..320d5e0e5 100644
--- a/vendor/github.com/containers/common/pkg/config/config.go
+++ b/vendor/github.com/containers/common/pkg/config/config.go
@@ -268,6 +268,10 @@ type EngineConfig struct {
// NetworkCmdPath is the path to the slirp4netns binary.
NetworkCmdPath string `toml:"network_cmd_path,omitempty"`
+ // NetworkCmdOptions is the default options to pass to the slirp4netns binary.
+ // For example "allow_host_loopback=true"
+ NetworkCmdOptions []string `toml:"network_cmd_options,omitempty"`
+
// NoPivotRoot sets whether to set no-pivot-root in the OCI runtime.
NoPivotRoot bool `toml:"no_pivot_root,omitempty"`
@@ -359,6 +363,12 @@ type EngineConfig struct {
// under. This convention is followed by the default volume driver, but
// may not be by other drivers.
VolumePath string `toml:"volume_path,omitempty"`
+
+ // VolumePlugins is a set of plugins that can be used as the backend for
+ // Podman named volumes. Each volume is specified as a name (what Podman
+ // will refer to the plugin as) mapped to a path, which must point to a
+ // Unix socket that conforms to the Volume Plugin specification.
+ VolumePlugins map[string]string `toml:"volume_plugins,omitempty"`
}
// SetOptions contains a subset of options in a Config. It's used to indicate if
@@ -441,11 +451,6 @@ func NewConfig(userConfigPath string) (*Config, error) {
return nil, err
}
- // read libpod.conf and convert the config to *Config
- if err = newLibpodConfig(config); err != nil && !os.IsNotExist(err) {
- logrus.Errorf("error reading libpod.conf: %v", err)
- }
-
// Now, gather the system configs and merge them as needed.
configs, err := systemConfigs()
if err != nil {
diff --git a/vendor/github.com/containers/common/pkg/config/containers.conf b/vendor/github.com/containers/common/pkg/config/containers.conf
index ed7c91931..12fbecc22 100644
--- a/vendor/github.com/containers/common/pkg/config/containers.conf
+++ b/vendor/github.com/containers/common/pkg/config/containers.conf
@@ -348,6 +348,11 @@ default_sysctls = [
#
# network_cmd_path=""
+# Default options to pass to the slirp4netns binary.
+# For example "allow_host_loopback=true"
+#
+# network_cmd_options=[]
+
# Whether to use chroot instead of pivot_root in the runtime
#
# no_pivot_root = false
@@ -386,7 +391,7 @@ default_sysctls = [
# Default OCI runtime
#
-# runtime = "runc"
+# runtime = "crun"
# List of the OCI runtimes that support --format=json. When json is supported
# engine will use it for reporting nicer errors.
@@ -453,8 +458,11 @@ default_sysctls = [
# "/usr/bin/kata-fc",
# ]
-# The [engine.runtimes] table MUST be the last entry in this file.
+[engine.volume_plugins]
+# testplugin = "/run/podman/plugins/test.sock"
+
+# The [engine.volume_plugins] table MUST be the last entry in this file.
# (Unless another table is added)
# TOML does not provide a way to end a table other than a further table being
-# defined, so every key hereafter will be part of [runtimes] and not the main
-# config.
+# defined, so every key hereafter will be part of [volume_plugins] and not the
+# main config.
diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go
index 4f1460e3b..2b3a098a7 100644
--- a/vendor/github.com/containers/common/pkg/config/default.go
+++ b/vendor/github.com/containers/common/pkg/config/default.go
@@ -242,11 +242,7 @@ func defaultConfigFromMemory() (*EngineConfig, error) {
c.ImageDefaultTransport = _defaultTransport
c.StateType = BoltDBStateStore
- c.OCIRuntime = "runc"
- // If we're running on cgroupv2 v2, default to using crun.
- if cgroup2, _ := cgroupv2.Enabled(); cgroup2 {
- c.OCIRuntime = "crun"
- }
+ c.OCIRuntime = "crun"
c.ImageBuildFormat = "oci"
c.CgroupManager = defaultCgroupManager()
diff --git a/vendor/github.com/containers/common/pkg/config/libpodConfig.go b/vendor/github.com/containers/common/pkg/config/libpodConfig.go
deleted file mode 100644
index 2df3d6077..000000000
--- a/vendor/github.com/containers/common/pkg/config/libpodConfig.go
+++ /dev/null
@@ -1,407 +0,0 @@
-package config
-
-/* libpodConfig.go contains deprecated functionality and should not be used any longer */
-
-import (
- "os"
- "os/exec"
- "path/filepath"
-
- "github.com/BurntSushi/toml"
- "github.com/containers/common/pkg/cgroupv2"
- "github.com/containers/storage/pkg/unshare"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
-)
-
-const (
- // _rootlessConfigPath is the path to the rootless libpod.conf in $HOME.
- _rootlessConfigPath = ".config/containers/libpod.conf"
-
- // _rootConfigPath is the path to the libpod configuration file
- // This file is loaded to replace the builtin default config before
- // runtime options (e.g. WithStorageConfig) are applied.
- // If it is not present, the builtin default config is used instead
- // This path can be overridden when the runtime is created by using
- // NewRuntimeFromConfig() instead of NewRuntime().
- _rootConfigPath = _installPrefix + "/share/containers/libpod.conf"
-
- // _rootOverrideConfigPath is the path to an override for the default libpod
- // configuration file. If OverrideConfigPath exists, it will be used in
- // place of the configuration file pointed to by ConfigPath.
- _rootOverrideConfigPath = _etcDir + "/containers/libpod.conf"
-)
-
-// ConfigFromLibpod contains configuration options used to set up a libpod runtime
-type ConfigFromLibpod struct {
- // NOTE: when changing this struct, make sure to update (*Config).Merge().
-
- // SetOptions contains a subset of config options. It's used to indicate if
- // a given option has either been set by the user or by a parsed libpod
- // configuration file. If not, the corresponding option might be
- // overwritten by values from the database. This behavior guarantees
- // backwards compat with older version of libpod and Podman.
- SetOptions
-
- // VolumePath is the default location that named volumes will be created
- // under. This convention is followed by the default volume driver, but
- // may not be by other drivers.
- VolumePath string `toml:"volume_path,omitempty"`
-
- // ImageDefaultTransport is the default transport method used to fetch
- // images.
- ImageDefaultTransport string `toml:"image_default_transport,omitempty"`
-
- // SignaturePolicyPath is the path to a signature policy to use for
- // validating images. If left empty, the containers/image default signature
- // policy will be used.
- SignaturePolicyPath string `toml:"signature_policy_path,omitempty"`
-
- // OCIRuntime is the OCI runtime to use.
- OCIRuntime string `toml:"runtime,omitempty"`
-
- // OCIRuntimes are the set of configured OCI runtimes (default is runc).
- OCIRuntimes map[string][]string `toml:"runtimes,omitempty"`
-
- // RuntimeSupportsJSON is the list of the OCI runtimes that support
- // --format=json.
- RuntimeSupportsJSON []string `toml:"runtime_supports_json,omitempty"`
-
- // RuntimeSupportsNoCgroups is a list of OCI runtimes that support
- // running containers without CGroups.
- RuntimeSupportsNoCgroups []string `toml:"runtime_supports_nocgroupv2,omitempty"`
-
- // RuntimePath is the path to OCI runtime binary for launching containers.
- // The first path pointing to a valid file will be used This is used only
- // when there are no OCIRuntime/OCIRuntimes defined. It is used only to be
- // backward compatible with older versions of Podman.
- RuntimePath []string `toml:"runtime_path,omitempty"`
-
- // ConmonPath is the path to the Conmon binary used for managing containers.
- // The first path pointing to a valid file will be used.
- ConmonPath []string `toml:"conmon_path,omitempty"`
-
- // ConmonEnvVars are environment variables to pass to the Conmon binary
- // when it is launched.
- ConmonEnvVars []string `toml:"conmon_env_vars,omitempty"`
-
- // CGroupManager is the CGroup Manager to use Valid values are "cgroupfs"
- // and "systemd".
- CgroupManager string `toml:"cgroup_manager,omitempty"`
-
- // InitPath is the path to the container-init binary.
- InitPath string `toml:"init_path,omitempty"`
-
- // StaticDir is the path to a persistent directory to store container
- // files.
- StaticDir string `toml:"static_dir,omitempty"`
-
- // TmpDir is the path to a temporary directory to store per-boot container
- // files. Must be stored in a tmpfs.
- TmpDir string `toml:"tmp_dir,omitempty"`
-
- // MaxLogSize is the maximum size of container logfiles.
- MaxLogSize int64 `toml:"max_log_size,omitempty"`
-
- // NoPivotRoot sets whether to set no-pivot-root in the OCI runtime.
- NoPivotRoot bool `toml:"no_pivot_root,omitempty"`
-
- // CNIConfigDir sets the directory where CNI configuration files are
- // stored.
- CNIConfigDir string `toml:"cni_config_dir,omitempty"`
-
- // CNIPluginDir sets a number of directories where the CNI network
- // plugins can be located.
- CNIPluginDir []string `toml:"cni_plugin_dir,omitempty"`
-
- // CNIDefaultNetwork is the network name of the default CNI network
- // to attach pods to.
- CNIDefaultNetwork string `toml:"cni_default_network,omitempty"`
-
- // HooksDir holds paths to the directories containing hooks
- // configuration files. When the same filename is present in in
- // multiple directories, the file in the directory listed last in
- // this slice takes precedence.
- HooksDir []string `toml:"hooks_dir,omitempty"`
-
- // Namespace is the libpod namespace to use. Namespaces are used to create
- // scopes to separate containers and pods in the state. When namespace is
- // set, libpod will only view containers and pods in the same namespace. All
- // containers and pods created will default to the namespace set here. A
- // namespace of "", the empty string, is equivalent to no namespace, and all
- // containers and pods will be visible. The default namespace is "".
- Namespace string `toml:"namespace,omitempty"`
-
- // InfraImage is the image a pod infra container will use to manage
- // namespaces.
- InfraImage string `toml:"infra_image,omitempty"`
-
- // InfraCommand is the command run to start up a pod infra container.
- InfraCommand string `toml:"infra_command,omitempty"`
-
- // EnablePortReservation determines whether libpod will reserve ports on the
- // host when they are forwarded to containers. When enabled, when ports are
- // forwarded to containers, they are held open by conmon as long as the
- // container is running, ensuring that they cannot be reused by other
- // programs on the host. However, this can cause significant memory usage if
- // a container has many ports forwarded to it. Disabling this can save
- // memory.
- EnablePortReservation bool `toml:"enable_port_reservation,omitempty"`
-
- // EnableLabeling indicates whether libpod will support container labeling.
- EnableLabeling bool `toml:"label,omitempty"`
-
- // NetworkCmdPath is the path to the slirp4netns binary.
- NetworkCmdPath string `toml:"network_cmd_path,omitempty"`
-
- // NumLocks is the number of locks to make available for containers and
- // pods.
- NumLocks uint32 `toml:"num_locks,omitempty"`
-
- // LockType is the type of locking to use.
- LockType string `toml:"lock_type,omitempty"`
-
- // EventsLogger determines where events should be logged.
- EventsLogger string `toml:"events_logger,omitempty"`
-
- // EventsLogFilePath is where the events log is stored.
- EventsLogFilePath string `toml:"events_logfile_path,omitempty"`
-
- // DetachKeys is the sequence of keys used to detach a container.
- DetachKeys string `toml:"detach_keys,omitempty"`
-
- // SDNotify tells Libpod to allow containers to notify the host systemd of
- // readiness using the SD_NOTIFY mechanism.
- SDNotify bool `toml:",omitempty"`
-
- // CgroupCheck indicates the configuration has been rewritten after an
- // upgrade to Fedora 31 to change the default OCI runtime for cgroupv2v2.
- CgroupCheck bool `toml:"cgroup_check,omitempty"`
-}
-
-// newLibpodConfig creates a new ConfigFromLibpod and converts it to Config.
-// Depending if we're running as root or rootless, we then merge the system configuration followed
-// by merging the default config (hard-coded default in memory).
-// Note that the OCI runtime is hard-set to `crun` if we're running on a system
-// with cgroupv2v2. Other OCI runtimes are not yet supporting cgroupv2v2. This
-// might change in the future.
-func newLibpodConfig(c *Config) error {
- // Start with the default config and iteratively merge
- // fields in the system configs.
- config := c.libpodConfig()
-
- // Now, check if the user can access system configs and merge them if needed.
- configs, err := systemLibpodConfigs()
- if err != nil {
- return errors.Wrapf(err, "error finding config on system")
- }
-
- if len(configs) == 0 {
- return nil
- }
-
- for _, path := range configs {
- config, err = readLibpodConfigFromFile(path, config)
- if err != nil {
- return errors.Wrapf(err, "error reading system config %q", path)
- }
- }
-
- // Since runc does not currently support cgroupV2
- // Change to default crun on first running of libpod.conf
- // TODO Once runc has support for cgroupv2, this function should be removed.
- if !config.CgroupCheck && unshare.IsRootless() {
- cgroup2, err := cgroupv2.Enabled()
- if err != nil {
- return err
- }
- if cgroup2 {
- path, err := exec.LookPath("crun")
- if err != nil {
- // Can't find crun path so do nothing
- logrus.Warnf("Can not find crun package on the host, containers might fail to run on cgroup V2 systems without crun: %q", err)
- } else {
- config.CgroupCheck = true
- config.OCIRuntime = path
- }
- }
- }
-
- // hard code EventsLogger to "file" to match older podman versions.
- if config.EventsLogger != "file" {
- logrus.Warnf("Ignoring libpod.conf EventsLogger setting %q. Use %q if you want to change this setting and remove libpod.conf files.", config.EventsLogger, Path())
- config.EventsLogger = "file"
- }
-
- c.libpodToContainersConfig(config)
-
- return nil
-}
-
-// readConfigFromFile reads the specified config file at `path` and attempts to
-// unmarshal its content into a Config. The config param specifies the previous
-// default config. If the path, only specifies a few fields in the Toml file
-// the defaults from the config parameter will be used for all other fields.
-func readLibpodConfigFromFile(path string, config *ConfigFromLibpod) (*ConfigFromLibpod, error) {
- logrus.Debugf("Reading configuration file %q", path)
- _, err := toml.DecodeFile(path, config)
- if err != nil {
- return nil, errors.Wrapf(err, "decode configuration %s", path)
- }
-
- return config, err
-}
-
-func systemLibpodConfigs() ([]string, error) {
- if unshare.IsRootless() {
- path, err := rootlessLibpodConfigPath()
- if err != nil {
- return nil, err
- }
- if _, err := os.Stat(path); err == nil {
- containersConfPath, err := rootlessConfigPath()
- if err != nil {
- containersConfPath = filepath.Join("$HOME", UserOverrideContainersConfig)
- }
- logrus.Warnf("Found deprecated file %s, please remove. Use %s to override defaults.\n", path, containersConfPath)
- return []string{path}, nil
- }
- return nil, err
- }
-
- configs := []string{}
- if _, err := os.Stat(_rootConfigPath); err == nil {
- logrus.Warnf("Found deprecated file %s, please remove. Use %s to override defaults.\n", _rootConfigPath, OverrideContainersConfig)
- configs = append(configs, _rootConfigPath)
- }
- if _, err := os.Stat(_rootOverrideConfigPath); err == nil {
- logrus.Warnf("Found deprecated file %s, please remove. Use %s to override defaults.\n", _rootOverrideConfigPath, OverrideContainersConfig)
- configs = append(configs, _rootOverrideConfigPath)
- }
- return configs, nil
-}
-
-func rootlessLibpodConfigPath() (string, error) {
- home, err := unshare.HomeDir()
- if err != nil {
- return "", err
- }
-
- return filepath.Join(home, _rootlessConfigPath), nil
-}
-
-func (c *Config) libpodConfig() *ConfigFromLibpod {
- return &ConfigFromLibpod{
- InitPath: c.Containers.InitPath,
- MaxLogSize: c.Containers.LogSizeMax,
- EnableLabeling: c.Containers.EnableLabeling,
-
- SetOptions: c.Engine.SetOptions,
- VolumePath: c.Engine.VolumePath,
- ImageDefaultTransport: c.Engine.ImageDefaultTransport,
- OCIRuntime: c.Engine.OCIRuntime,
- OCIRuntimes: c.Engine.OCIRuntimes,
- RuntimeSupportsJSON: c.Engine.RuntimeSupportsJSON,
- RuntimeSupportsNoCgroups: c.Engine.RuntimeSupportsNoCgroups,
- RuntimePath: c.Engine.RuntimePath,
- ConmonPath: c.Engine.ConmonPath,
- ConmonEnvVars: c.Engine.ConmonEnvVars,
- CgroupManager: c.Engine.CgroupManager,
- StaticDir: c.Engine.StaticDir,
- TmpDir: c.Engine.TmpDir,
- NoPivotRoot: c.Engine.NoPivotRoot,
- HooksDir: c.Engine.HooksDir,
- Namespace: c.Engine.Namespace,
- InfraImage: c.Engine.InfraImage,
- InfraCommand: c.Engine.InfraCommand,
- EnablePortReservation: c.Engine.EnablePortReservation,
- NetworkCmdPath: c.Engine.NetworkCmdPath,
- NumLocks: c.Engine.NumLocks,
- LockType: c.Engine.LockType,
- EventsLogger: c.Engine.EventsLogger,
- EventsLogFilePath: c.Engine.EventsLogFilePath,
- DetachKeys: c.Engine.DetachKeys,
- SDNotify: c.Engine.SDNotify,
- CgroupCheck: c.Engine.CgroupCheck,
- SignaturePolicyPath: c.Engine.SignaturePolicyPath,
-
- CNIConfigDir: c.Network.NetworkConfigDir,
- CNIPluginDir: c.Network.CNIPluginDirs,
- CNIDefaultNetwork: c.Network.DefaultNetwork,
- }
-}
-
-func (c *Config) libpodToContainersConfig(libpodConf *ConfigFromLibpod) {
-
- if libpodConf.InitPath != "" {
- c.Containers.InitPath = libpodConf.InitPath
- }
- c.Containers.LogSizeMax = libpodConf.MaxLogSize
- c.Containers.EnableLabeling = libpodConf.EnableLabeling
-
- if libpodConf.SignaturePolicyPath != "" {
- c.Engine.SignaturePolicyPath = libpodConf.SignaturePolicyPath
- }
- c.Engine.SetOptions = libpodConf.SetOptions
- if libpodConf.VolumePath != "" {
- c.Engine.VolumePath = libpodConf.VolumePath
- }
- if libpodConf.ImageDefaultTransport != "" {
- c.Engine.ImageDefaultTransport = libpodConf.ImageDefaultTransport
- }
- if libpodConf.OCIRuntime != "" {
- c.Engine.OCIRuntime = libpodConf.OCIRuntime
- }
- c.Engine.OCIRuntimes = libpodConf.OCIRuntimes
- c.Engine.RuntimeSupportsJSON = libpodConf.RuntimeSupportsJSON
- c.Engine.RuntimeSupportsNoCgroups = libpodConf.RuntimeSupportsNoCgroups
- c.Engine.RuntimePath = libpodConf.RuntimePath
- c.Engine.ConmonPath = libpodConf.ConmonPath
- c.Engine.ConmonEnvVars = libpodConf.ConmonEnvVars
- if libpodConf.CgroupManager != "" {
- c.Engine.CgroupManager = libpodConf.CgroupManager
- }
- if libpodConf.StaticDir != "" {
- c.Engine.StaticDir = libpodConf.StaticDir
- }
- if libpodConf.TmpDir != "" {
- c.Engine.TmpDir = libpodConf.TmpDir
- }
- c.Engine.NoPivotRoot = libpodConf.NoPivotRoot
- c.Engine.HooksDir = libpodConf.HooksDir
- if libpodConf.Namespace != "" {
- c.Engine.Namespace = libpodConf.Namespace
- }
- if libpodConf.InfraImage != "" {
- c.Engine.InfraImage = libpodConf.InfraImage
- }
- if libpodConf.InfraCommand != "" {
- c.Engine.InfraCommand = libpodConf.InfraCommand
- }
-
- c.Engine.EnablePortReservation = libpodConf.EnablePortReservation
- if libpodConf.NetworkCmdPath != "" {
- c.Engine.NetworkCmdPath = libpodConf.NetworkCmdPath
- }
- c.Engine.NumLocks = libpodConf.NumLocks
- c.Engine.LockType = libpodConf.LockType
- if libpodConf.EventsLogger != "" {
- c.Engine.EventsLogger = libpodConf.EventsLogger
- }
- if libpodConf.EventsLogFilePath != "" {
- c.Engine.EventsLogFilePath = libpodConf.EventsLogFilePath
- }
- if libpodConf.DetachKeys != "" {
- c.Engine.DetachKeys = libpodConf.DetachKeys
- }
- c.Engine.SDNotify = libpodConf.SDNotify
- c.Engine.CgroupCheck = libpodConf.CgroupCheck
-
- if libpodConf.CNIConfigDir != "" {
- c.Network.NetworkConfigDir = libpodConf.CNIConfigDir
- }
- c.Network.CNIPluginDirs = libpodConf.CNIPluginDir
- if libpodConf.CNIDefaultNetwork != "" {
- c.Network.DefaultNetwork = libpodConf.CNIDefaultNetwork
- }
-}
diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go
index 72f4e00f7..8df453484 100644
--- a/vendor/github.com/containers/common/version/version.go
+++ b/vendor/github.com/containers/common/version/version.go
@@ -1,4 +1,4 @@
package version
// Version is the version of the build.
-const Version = "0.29.0"
+const Version = "0.31.0"
diff --git a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
index ee09adc8d..7a9f97d1c 100644
--- a/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
+++ b/vendor/github.com/cri-o/ocicni/pkg/ocicni/ocicni.go
@@ -149,7 +149,7 @@ func (plugin *cniNetworkPlugin) monitorConfDir(start *sync.WaitGroup) {
for {
select {
case event := <-plugin.watcher.Events:
- logrus.Warningf("CNI monitoring event %v", event)
+ logrus.Infof("CNI monitoring event %v", event)
var defaultDeleted bool
createWrite := (event.Op&fsnotify.Create == fsnotify.Create ||
@@ -295,7 +295,7 @@ func loadNetworks(confDir string, cni *libcni.CNIConfig) (map[string]*cniNetwork
}
}
if len(confList.Plugins) == 0 {
- logrus.Warningf("CNI config list %s has no networks, skipping", confFile)
+ logrus.Infof("CNI config list %s has no networks, skipping", confFile)
continue
}
@@ -350,7 +350,7 @@ func (plugin *cniNetworkPlugin) syncNetworkConfig() error {
plugin.defaultNetName.name = defaultNetName
logrus.Infof("Update default CNI network name to %s", defaultNetName)
} else {
- logrus.Warnf("Default CNI network name %s is unchangeable", plugin.defaultNetName.name)
+ logrus.Debugf("Default CNI network name %s is unchangeable", plugin.defaultNetName.name)
}
plugin.networks = networks
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 9de30e604..3ad53c73c 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -86,7 +86,7 @@ github.com/containers/buildah/pkg/parse
github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/supplemented
github.com/containers/buildah/util
-# github.com/containers/common v0.29.0
+# github.com/containers/common v0.31.0
github.com/containers/common/pkg/apparmor
github.com/containers/common/pkg/apparmor/internal/supported
github.com/containers/common/pkg/auth
@@ -220,7 +220,7 @@ github.com/coreos/go-systemd/v22/dbus
github.com/coreos/go-systemd/v22/internal/dlopen
github.com/coreos/go-systemd/v22/journal
github.com/coreos/go-systemd/v22/sdjournal
-# github.com/cri-o/ocicni v0.2.1-0.20201102180012-75c612fda1a2 => github.com/cri-o/ocicni v0.2.1-0.20201109200316-afdc16ba66df
+# github.com/cri-o/ocicni v0.2.1-0.20201125151022-df072ea5421c
github.com/cri-o/ocicni/pkg/ocicni
# github.com/cyphar/filepath-securejoin v0.2.2
github.com/cyphar/filepath-securejoin