summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--libpod/runtime_pod_infra_linux.go67
-rw-r--r--pkg/api/handlers/compat/containers_archive.go373
-rw-r--r--test/apiv2/23-containersArchive.at81
-rw-r--r--test/system/010-images.bats6
-rw-r--r--test/system/030-run.bats19
-rw-r--r--test/system/040-ps.bats47
-rw-r--r--test/system/070-build.bats17
-rw-r--r--test/system/075-exec.bats3
-rw-r--r--test/system/200-pod.bats4
10 files changed, 576 insertions, 43 deletions
diff --git a/README.md b/README.md
index 857d43fe8..3c25e53cb 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
Podman (the POD MANager) is a tool for managing containers and images, volumes mounted into those containers, and pods made from groups of containers.
Podman is based on libpod, a library for container lifecycle management that is also contained in this repository. The libpod library provides APIs for managing containers, pods, container images, and volumes.
-* [Latest Version: 2.1.1](https://github.com/containers/podman/releases/latest)
+* [Latest Version: 2.2.0](https://github.com/containers/podman/releases/latest)
* Latest Remote client for Windows
* Latest Remote client for MacOs
* Latest Static Remote client for Linux
diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go
index 76419587a..3e4185db1 100644
--- a/libpod/runtime_pod_infra_linux.go
+++ b/libpod/runtime_pod_infra_linux.go
@@ -34,40 +34,56 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
// Set Pod hostname
g.Config.Hostname = p.config.Hostname
+ var options []CtrCreateOption
+
+ // Command: If user-specified, use that preferentially.
+ // If not set and the config file is set, fall back to that.
+ var infraCtrCommand []string
+ if p.config.InfraContainer.InfraCommand != nil {
+ logrus.Debugf("User-specified infra container entrypoint %v", p.config.InfraContainer.InfraCommand)
+ infraCtrCommand = p.config.InfraContainer.InfraCommand
+ } else if r.config.Engine.InfraCommand != "" {
+ logrus.Debugf("Config-specified infra container entrypoint %s", r.config.Engine.InfraCommand)
+ infraCtrCommand = []string{r.config.Engine.InfraCommand}
+ }
+ // Only if set by the user or containers.conf, we set entrypoint for the
+ // infra container.
+ // This is only used by commit, so it shouldn't matter... But someone
+ // may eventually want to commit an infra container?
+ // TODO: Should we actually do this if set by containers.conf?
+ if infraCtrCommand != nil {
+ // Need to duplicate the array - we are going to add Cmd later
+ // so the current array will be changed.
+ newArr := make([]string, 0, len(infraCtrCommand))
+ newArr = append(newArr, infraCtrCommand...)
+ options = append(options, WithEntrypoint(newArr))
+ }
+
isRootless := rootless.IsRootless()
- entrypointSet := len(p.config.InfraContainer.InfraCommand) > 0
- entryPoint := p.config.InfraContainer.InfraCommand
- entryCmd := []string{}
- var options []CtrCreateOption
// I've seen circumstances where config is being passed as nil.
// Let's err on the side of safety and make sure it's safe to use.
if config != nil {
- // default to entrypoint in image if there is one
- if !entrypointSet {
- if len(config.Entrypoint) > 0 {
- entrypointSet = true
- entryPoint = config.Entrypoint
- entryCmd = config.Entrypoint
+ if infraCtrCommand == nil {
+ // If we have no entrypoint and command from the image,
+ // we can't go on - the infra container has no command.
+ if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 {
+ return nil, errors.Errorf("infra container has no command")
}
- } else { // so use the InfraCommand
- entrypointSet = true
- entryCmd = entryPoint
- }
-
- if len(config.Cmd) > 0 {
- // We can't use the default pause command, since we're
- // sourcing from the image. If we didn't already set an
- // entrypoint, set one now.
- if !entrypointSet {
+ if len(config.Entrypoint) > 0 {
+ infraCtrCommand = config.Entrypoint
+ } else {
// Use the Docker default "/bin/sh -c"
// entrypoint, as we're overriding command.
// If an image doesn't want this, it can
// override entrypoint too.
- entryCmd = []string{"/bin/sh", "-c"}
+ infraCtrCommand = []string{"/bin/sh", "-c"}
}
- entryCmd = append(entryCmd, config.Cmd...)
}
+ if len(config.Cmd) > 0 {
+ infraCtrCommand = append(infraCtrCommand, config.Cmd...)
+ }
+
if len(config.Env) > 0 {
for _, nameValPair := range config.Env {
nameValSlice := strings.Split(nameValPair, "=")
@@ -127,9 +143,9 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
}
g.SetRootReadonly(true)
- g.SetProcessArgs(entryCmd)
+ g.SetProcessArgs(infraCtrCommand)
- logrus.Debugf("Using %q as infra container entrypoint", entryCmd)
+ logrus.Debugf("Using %q as infra container command", infraCtrCommand)
g.RemoveMount("/dev/shm")
if isRootless {
@@ -148,9 +164,6 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
options = append(options, WithRootFSFromImage(imgID, imgName, rawImageName))
options = append(options, WithName(containerName))
options = append(options, withIsInfra())
- if entrypointSet {
- options = append(options, WithEntrypoint(entryPoint))
- }
if len(p.config.InfraContainer.ConmonPidFile) > 0 {
options = append(options, WithConmonPidFile(p.config.InfraContainer.ConmonPidFile))
}
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go
index 293a17e0f..1dd563393 100644
--- a/pkg/api/handlers/compat/containers_archive.go
+++ b/pkg/api/handlers/compat/containers_archive.go
@@ -1,12 +1,379 @@
package compat
import (
- "errors"
- "net/http"
+ "bytes"
+ "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/gorilla/schema"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
func Archive(w http.ResponseWriter, r *http.Request) {
- utils.Error(w, "not implemented", http.StatusNotImplemented, errors.New("not implemented"))
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+
+ switch r.Method {
+ case http.MethodPut:
+ handlePut(w, r, decoder, runtime)
+ case http.MethodGet, http.MethodHead:
+ handleHeadOrGet(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)))
+ }
+}
+
+func handleHeadOrGet(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) {
+ query := struct {
+ Path string `schema:"path"`
+ }{}
+
+ 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
+ }
+
+ if query.Path == "" {
+ utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.New("missing `path` parameter"))
+ return
+ }
+
+ containerName := utils.GetName(r)
+
+ ctr, err := runtime.LookupContainer(containerName)
+ if errors.Cause(err) == define.ErrNoSuchCtr {
+ utils.Error(w, "Not found.", http.StatusNotFound, errors.Wrap(err, "the container doesn't exists"))
+ return
+ } else if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+
+ mountPoint, err := ctr.Mount()
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to mount the container"))
+ 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)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
+ return
+ }
+
+ stats, err := copier.Stat(mountPoint, "", opts, []string{filepath.Join(mountPoint, path)})
+ 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)
+ 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 {
+ 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)
+ 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))
+ return
+ }
+}
+
+func statsToHeader(stats *copier.StatForItem) (string, error) {
+ statsDTO := 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,
+ }
+
+ jsonBytes, err := json.Marshal(&statsDTO)
+ if err != nil {
+ return "", errors.Wrap(err, "failed to serialize file stats")
+ }
+
+ buff := bytes.NewBuffer(make([]byte, 0, 128))
+ base64encoder := base64.NewEncoder(base64.StdEncoding, buff)
+
+ _, err = base64encoder.Write(jsonBytes)
+ if err != nil {
+ return "", err
+ }
+
+ err = base64encoder.Close()
+ if err != nil {
+ return "", err
+ }
+
+ 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 pathWithVolumeMount(runtime *libpod.Runtime, volDestName, volName, path string) (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)
+ }
+
+ 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))
+ }
+
+ 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, string) {
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(string(os.PathSeparator), path)
+ }
+
+ return m.Source, strings.TrimPrefix(path, m.Destination)
}
diff --git a/test/apiv2/23-containersArchive.at b/test/apiv2/23-containersArchive.at
new file mode 100644
index 000000000..459800196
--- /dev/null
+++ b/test/apiv2/23-containersArchive.at
@@ -0,0 +1,81 @@
+# -*- sh -*-
+#
+# test more container-related endpoints
+#
+
+red='\e[31m'
+nc='\e[0m'
+
+podman pull $IMAGE &>/dev/null
+
+# Ensure clean slate
+podman rm -a -f &>/dev/null
+
+CTR="ArchiveTestingCtr"
+
+TMPD=$(mktemp -d)
+pushd "${TMPD}"
+echo "Hello" > "hello.txt"
+tar --format=posix -cvf "hello.tar" "hello.txt" &> /dev/null
+popd
+
+HELLO_TAR="${TMPD}/hello.tar"
+
+podman run -d --name "${CTR}" "${IMAGE}" top
+
+function cleanUpArchiveTest() {
+ podman container stop "${CTR}" &> /dev/null
+ podman container rm "${CTR}" &> /dev/null
+ rm -fr "${TMPD}" &> /dev/null
+}
+
+t HEAD "containers/nonExistentCtr/archive?path=%2F" 404
+t HEAD "containers/${CTR}/archive?path=%2Fnon%2Fexistent%2Fpath" 404
+t HEAD "containers/${CTR}/archive?path=%2Fetc%2Fpasswd" 200
+
+curl "http://$HOST:$PORT/containers/${CTR}/archive?path=%2Ftmp%2F" \
+ -X PUT \
+ -H "Content-Type: application/x-tar" \
+ --upload-file "${HELLO_TAR}" &> /dev/null
+
+if ! podman exec -it "${CTR}" "grep" "Hello" "/tmp/hello.txt" &> /dev/null ; then
+ echo -e "${red}NOK: The hello.txt file has not been uploaded.${nc}" 1>&2;
+ cleanUpArchiveTest
+ exit 1
+fi
+
+t HEAD "containers/${CTR}/archive?path=%2Ftmp%2Fhello.txt" 200
+
+curl "http://$HOST:$PORT/containers/${CTR}/archive?path=%2Ftmp%2Fhello.txt" \
+ --dump-header "${TMPD}/headers.txt" \
+ -o "${TMPD}/body.tar" \
+ -X GET &> /dev/null
+
+PATH_STAT="$(grep X-Docker-Container-Path-Stat "${TMPD}/headers.txt" | cut -d " " -f 2 | base64 -d --ignore-garbage)"
+
+ARCHIVE_TEST_ERROR=""
+
+if [ "$(echo "${PATH_STAT}" | jq ".name")" != '"hello.txt"' ]; then
+ echo -e "${red}NOK: Wrong name in X-Docker-Container-Path-Stat header.${nc}" 1>&2;
+ ARCHIVE_TEST_ERROR="1"
+fi
+
+if [ "$(echo "${PATH_STAT}" | jq ".size")" != "6" ]; then
+ echo -e "${red}NOK: Wrong size in X-Docker-Container-Path-Stat header.${nc}" 1>&2;
+ ARCHIVE_TEST_ERROR="1"
+fi
+
+if ! tar -tf "${TMPD}/body.tar" | grep "hello.txt" &> /dev/null; then
+ echo -e "${red}NOK: Body doesn't contain expected file.${nc}" 1>&2;
+ ARCHIVE_TEST_ERROR="1"
+fi
+
+if [ "$(tar -xf "${TMPD}/body.tar" hello.txt --to-stdout)" != "Hello" ]; then
+ echo -e "${red}NOK: Content of file doesn't match.${nc}" 1>&2;
+ ARCHIVE_TEST_ERROR="1"
+fi
+
+cleanUpArchiveTest
+if [[ "${ARCHIVE_TEST_ERROR}" ]] ; then
+ exit 1;
+fi
diff --git a/test/system/010-images.bats b/test/system/010-images.bats
index 98bb0cc57..ee6da30ec 100644
--- a/test/system/010-images.bats
+++ b/test/system/010-images.bats
@@ -59,7 +59,8 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
@test "podman images - history output" {
# podman history is persistent: it permanently alters our base image.
# Create a dummy image here so we leave our setup as we found it.
- run_podman run --name my-container $IMAGE true
+ # Multiple --name options confirm command-line override (last one wins)
+ run_podman run --name ignore-me --name my-container $IMAGE true
run_podman commit my-container my-test-image
run_podman images my-test-image --format '{{ .History }}'
@@ -87,7 +88,8 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z
}
@test "podman images - filter" {
- run_podman inspect --format '{{.ID}}' $IMAGE
+ # Multiple --format options confirm command-line override (last one wins)
+ run_podman inspect --format '{{.XYZ}}' --format '{{.ID}}' $IMAGE
iid=$output
run_podman images --noheading --filter=after=$iid
diff --git a/test/system/030-run.bats b/test/system/030-run.bats
index 71831da10..37695f205 100644
--- a/test/system/030-run.bats
+++ b/test/system/030-run.bats
@@ -449,7 +449,9 @@ json-file | f
msg=$(random_string 20)
pidfile="${PODMAN_TMPDIR}/$(random_string 20)"
- run_podman run --name myctr --log-driver journald --conmon-pidfile $pidfile $IMAGE echo $msg
+ # Multiple --log-driver options to confirm that last one wins
+ run_podman run --name myctr --log-driver=none --log-driver journald \
+ --conmon-pidfile $pidfile $IMAGE echo $msg
journalctl --output cat _PID=$(cat $pidfile)
is "$output" "$msg" "check that journalctl output equals the container output"
@@ -464,7 +466,9 @@ json-file | f
run_podman run --rm $IMAGE date -r $testfile
is "$output" "Sun Sep 13 12:26:40 UTC 2020" "podman run with no TZ"
- run_podman run --rm --tz=MST7MDT $IMAGE date -r $testfile
+ # Multiple --tz options; confirm that the last one wins
+ run_podman run --rm --tz=US/Eastern --tz=Iceland --tz=MST7MDT \
+ $IMAGE date -r $testfile
is "$output" "Sun Sep 13 06:26:40 MDT 2020" "podman run with --tz=MST7MDT"
# --tz=local pays attention to /etc/localtime, not $TZ. We set TZ anyway,
@@ -533,8 +537,15 @@ json-file | f
}
@test "podman run with --net=host and --port prints warning" {
- run_podman run -d --rm -p 8080 --net=host $IMAGE ls > /dev/null
- is "$output" ".*Port mappings have been discarded as one of the Host, Container, Pod, and None network modes are in use"
+ rand=$(random_string 10)
+
+ # Please keep the duplicate "--net" options; this tests against #8507,
+ # a regression in which subsequent --net options did not override earlier.
+ run_podman run --rm -p 8080 --net=none --net=host $IMAGE echo $rand
+ is "${lines[0]}" \
+ "Port mappings have been discarded as one of the Host, Container, Pod, and None network modes are in use" \
+ "Warning is emitted before container output"
+ is "${lines[1]}" "$rand" "Container runs successfully despite warning"
}
# vim: filetype=sh
diff --git a/test/system/040-ps.bats b/test/system/040-ps.bats
index dec2df4d5..1ed2779b2 100644
--- a/test/system/040-ps.bats
+++ b/test/system/040-ps.bats
@@ -35,4 +35,51 @@ load helpers
run_podman rm $cid
}
+@test "podman ps --filter" {
+ run_podman run -d --name runner $IMAGE top
+ cid_runner=$output
+
+ run_podman run -d --name stopped $IMAGE true
+ cid_stopped=$output
+ run_podman wait stopped
+
+ run_podman run -d --name failed $IMAGE false
+ cid_failed=$output
+ run_podman wait failed
+
+ run_podman create --name created $IMAGE echo hi
+ cid_created=$output
+
+ run_podman ps --filter name=runner --format '{{.ID}}'
+ is "$output" "${cid_runner:0:12}" "filter: name=runner"
+
+ # Stopped container should not appear (because we're not using -a)
+ run_podman ps --filter name=stopped --format '{{.ID}}'
+ is "$output" "" "filter: name=stopped (without -a)"
+
+ # Again, but with -a
+ run_podman ps -a --filter name=stopped --format '{{.ID}}'
+ is "$output" "${cid_stopped:0:12}" "filter: name=stopped (with -a)"
+
+ run_podman ps --filter status=stopped --format '{{.Names}}' --sort names
+ is "${lines[0]}" "failed" "status=stopped: 1 of 2"
+ is "${lines[1]}" "stopped" "status=stopped: 2 of 2"
+
+ run_podman ps --filter status=exited --filter exited=0 --format '{{.Names}}'
+ is "$output" "stopped" "exited=0"
+
+ run_podman ps --filter status=exited --filter exited=1 --format '{{.Names}}'
+ is "$output" "failed" "exited=1"
+
+ # Multiple statuses allowed; and test sort=created
+ run_podman ps -a --filter status=exited --filter status=running \
+ --format '{{.Names}}' --sort created
+ is "${lines[0]}" "runner" "status=stopped: 1 of 3"
+ is "${lines[1]}" "stopped" "status=stopped: 2 of 3"
+ is "${lines[2]}" "failed" "status=stopped: 3 of 3"
+
+ run_podman stop -t 1 runner
+ run_podman rm -a
+}
+
# vim: filetype=sh
diff --git a/test/system/070-build.bats b/test/system/070-build.bats
index 83bcd13eb..59da503a6 100644
--- a/test/system/070-build.bats
+++ b/test/system/070-build.bats
@@ -135,10 +135,13 @@ echo "\$1"
printenv | grep MYENV | sort | sed -e 's/^MYENV.=//'
EOF
- # For overriding with --env-file
- cat >$PODMAN_TMPDIR/env-file <<EOF
+ # For overriding with --env-file; using multiple files confirms that
+ # the --env-file option is cumulative, not last-one-wins.
+ cat >$PODMAN_TMPDIR/env-file1 <<EOF
MYENV3=$s_env3
http_proxy=http-proxy-in-env-file
+EOF
+ cat >$PODMAN_TMPDIR/env-file2 <<EOF
https_proxy=https-proxy-in-env-file
EOF
@@ -185,7 +188,8 @@ EOF
export MYENV2="$s_env2"
export MYENV3="env-file-should-override-env-host!"
run_podman run --rm \
- --env-file=$PODMAN_TMPDIR/env-file \
+ --env-file=$PODMAN_TMPDIR/env-file1 \
+ --env-file=$PODMAN_TMPDIR/env-file2 \
${ENVHOST} \
-e MYENV4="$s_env4" \
build_test
@@ -205,7 +209,9 @@ EOF
# Proxies - environment should override container, but not env-file
http_proxy=http-proxy-from-env ftp_proxy=ftp-proxy-from-env \
- run_podman run --rm --env-file=$PODMAN_TMPDIR/env-file \
+ run_podman run --rm \
+ --env-file=$PODMAN_TMPDIR/env-file1 \
+ --env-file=$PODMAN_TMPDIR/env-file2 \
build_test \
printenv http_proxy https_proxy ftp_proxy
is "${lines[0]}" "http-proxy-in-env-file" "env-file overrides env"
@@ -222,7 +228,8 @@ EOF
is "$output" "$workdir" "pwd command in container"
# Determine buildah version, so we can confirm it gets into Labels
- run_podman info --format '{{ .Host.BuildahVersion }}'
+ # Multiple --format options confirm command-line override (last one wins)
+ run_podman info --format '{{.Ignore}}' --format '{{ .Host.BuildahVersion }}'
is "$output" "[1-9][0-9.-]\+" ".Host.BuildahVersion is reasonable"
buildah_version=$output
diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats
index edd7dedc4..c028e16c9 100644
--- a/test/system/075-exec.bats
+++ b/test/system/075-exec.bats
@@ -91,7 +91,8 @@ load helpers
# #6829 : add username to /etc/passwd inside container if --userns=keep-id
@test "podman exec - with keep-id" {
- run_podman run -d --userns=keep-id $IMAGE sh -c \
+ # Multiple --userns options confirm command-line override (last one wins)
+ run_podman run -d --userns=private --userns=keep-id $IMAGE sh -c \
"echo READY;while [ ! -f /tmp/stop ]; do sleep 1; done"
cid="$output"
wait_for_ready $cid
diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats
index b0f645c53..51835e4a3 100644
--- a/test/system/200-pod.bats
+++ b/test/system/200-pod.bats
@@ -286,6 +286,10 @@ EOF
is "$output" "nc: bind: Address in use" \
"two containers cannot bind to same port"
+ # make sure we can ping; failure here might mean that capabilities are wrong
+ run_podman run --rm --pod mypod $IMAGE ping -c1 127.0.0.1
+ run_podman run --rm --pod mypod $IMAGE ping -c1 $hostname
+
# While the container is still running, run 'podman ps' (no --format)
# and confirm that the output includes the published port
run_podman ps --filter id=$cid