diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | libpod/runtime_pod_infra_linux.go | 67 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_archive.go | 373 | ||||
-rw-r--r-- | test/apiv2/23-containersArchive.at | 81 | ||||
-rw-r--r-- | test/system/010-images.bats | 6 | ||||
-rw-r--r-- | test/system/030-run.bats | 19 | ||||
-rw-r--r-- | test/system/040-ps.bats | 47 | ||||
-rw-r--r-- | test/system/070-build.bats | 17 | ||||
-rw-r--r-- | test/system/075-exec.bats | 3 | ||||
-rw-r--r-- | test/system/200-pod.bats | 4 |
10 files changed, 576 insertions, 43 deletions
@@ -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 |