diff options
73 files changed, 778 insertions, 356 deletions
@@ -3,7 +3,7 @@ export GOPROXY=https://proxy.golang.org GO ?= go DESTDIR ?= -EPOCH_TEST_COMMIT ?= 960f07b0f79e6d6f94842fd4892e775c319f0a39 +EPOCH_TEST_COMMIT ?= 2b0892e757c878cdb087dd22b8986bccef0276ed HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -5,7 +5,7 @@ Libpod provides a library for applications looking to use the Container Pod concept, popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes. -* [Latest Version: 1.6.0](https://github.com/containers/libpod/releases/latest) +* [Latest Version: 1.6.2](https://github.com/containers/libpod/releases/latest) * [Continuous Integration:](contrib/cirrus/README.md) [![Build Status](https://api.cirrus-ci.com/github/containers/libpod.svg)](https://cirrus-ci.com/github/containers/libpod/master) * [GoDoc: ![GoDoc](https://godoc.org/github.com/containers/libpod/libpod?status.svg)](https://godoc.org/github.com/containers/libpod/libpod) * Automated continuous release downloads (including remote-client): diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index bff9a5f14..235871273 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,38 @@ # Release Notes +## 1.6.2 +### Features +- Added a `--runtime` flag to `podman system migrate` to allow the OCI runtime for all containers to be reset, to ease transition to the `crun` runtime on CGroups V2 systems until `runc` gains full support +- The `podman rm` command can now remove containers in broken states which previously could not be removed +- The `podman info` command, when run without root, now shows information on UID and GID mappings in the rootless user namespace +- Added `podman build --squash-all` flag, which squashes all layers (including those of the base image) into one layer +- The `--systemd` flag to `podman run` and `podman create` now accepts a string argument and allows a new value, `always`, which forces systemd support without checking if the the container entrypoint is systemd + +### Bugfixes +- Fixed a bug where the `podman top` command did not work on systems using CGroups V2 ([#4192](https://github.com/containers/libpod/issues/4192)) +- Fixed a bug where rootless Podman could double-close a file, leading to a panic +- Fixed a bug where rootless Podman could fail to retrieve some containers while refreshing the state +- Fixed a bug where `podman start --attach --sig-proxy=false` would still proxy signals into the container +- Fixed a bug where Podman would unconditionally use a non-default path for authentication credentials (`auth.json`), breaking `podman login` integration with `skopeo` and other tools using the containers/image library +- Fixed a bug where `podman ps --format=json` and `podman images --format=json` would display `null` when no results were returned, instead of valid JSON +- Fixed a bug where `podman build --squash` was incorrectly squashing all layers into one, instead of only new layers +- Fixed a bug where rootless Podman would allow volumes with options to be mounted (mounting volumes requires root), creating an inconsistent state where volumes reported as mounted but were not ([#4248](https://github.com/containers/libpod/issues/4248)) +- Fixed a bug where volumes which failed to unmount could not be removed ([#4247](https://github.com/containers/libpod/issues/4247)) +- Fixed a bug where Podman incorrectly handled some errors relating to unmounted or missing containers in containers/storage +- Fixed a bug where `podman stats` was broken on systems running CGroups V2 when run rootless ([#4268](https://github.com/containers/libpod/issues/4268)) +- Fixed a bug where the `podman start` command would print the short container ID, instead of the full ID +- Fixed a bug where containers created with an OCI runtime that is no longer available (uninstalled or removed from the config file) would not appear in `podman ps` and could not be removed via `podman rm` +- Fixed a bug where containers restored via `podman container restore --import` would retain the CGroup path of the original container, even if their container ID changed; thus, multiple containers created from the same checkpoint would all share the same CGroup + +### Misc +- The default PID limit for containers is now set to 4096. It can be adjusted back to the old default (unlimited) by passing `--pids-limit 0` to `podman create` and `podman run` +- The `podman start --attach` command now automatically attaches `STDIN` if the container was created with `-i` +- The `podman network create` command now validates network names using the same regular expression as container and pod names +- The `--systemd` flag to `podman run` and `podman create` will now only enable systemd mode when the binary being run inside the container is `/sbin/init`, `/usr/sbin/init`, or ends in `systemd` (previously detected any path ending in `init` or `systemd`) +- Updated vendored Buildah to 1.11.3 +- Updated vendored containers/storage to 1.13.5 +- Updated vendored containers/image to 4.0.1 + ## 1.6.1 ### Bugfixes - Fixed a bug where rootless Podman on systems using CGroups V2 would not function with the `cgroupfs` CGroups manager diff --git a/changelog.txt b/changelog.txt index 8508d0d1c..615e2a135 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,73 @@ +- Changelog for v1.6.2 (2019-10-17) + * Finalize release notes for v1.6.2 + * rootless: drop dependency on docker + * Bump gitvalidation epoch + * Bump to v1.6.2-dev + * Refactor tests when checking for error exit codes + * Attach stdin to container at start if it was created with --interactive + +- Changelog for v1.6.2-rc1 (2019-10-16) + * Add release notes for Podman 1.6.2 + * start: print full container ID + * Add a MissingRuntime implementation + * rootless v2 cannot collect network stats + * inspect: rename ImageID go field to Image + * systemd: accept also /sbin/init + * Unwrap errors before comparing them + * vendor github.com/containers/storage@v1.13.5 + * Ensure volumes can be removed when they fail to unmount + * Fix sample's JSON syntax error in oci-hooks.5.md + * change error wording when conmon fails without logs + * images: empty list is valid json with --format=json + * Allow giving path to Podman for cleanup command + * Touch up bad math in run man page + * Add squash-all, fix squash option in build + * tests: enable ps --size tests for rootless + * container: initialize results list + * Make user io.podman.service unit WantedBy=default.target + * rootless: do not set PIDs limit if --cgroup-manager=cgroupfs + * Update build man page with latest Buildah changes + * Fix default path for auth.json + * When restoring containers, reset cgroup path + * Migrate can move containers to a new runtime + * Move OCI runtime implementation behind an interface + * show uid_map in podman info + * cli: support --systemd=always + * systemd: expect full path /usr/sbin/init + * catch runc v2 error + * Respect --sig-proxy flag with podman start --attach + * rootless: automatically recreate the pause.pid file + * rootless: do not close files twice + * refresh: do not access network ns if not in the namespace + * Cirrus: Produce and collect varlink output + * io.podman.socket: drop Also=multi-user.target + * Cirrus: Remove broken/failing testing_crun task + * Cirrus: Use new VM cache images + * Cirrus: Install conmon in Fedora VMs + * vendor c/psgo@v1.3.2 + * troubleshooting: fix useradd no-log-init argument + * Setup a reasonable default for pids-limit 4096 + * Update c/image to v4.0.1 and buildah to 1.11.3 + * When evicting containers, perform a normal remove first + * Bump gopkg.in/yaml.v2 from 2.2.3 to 2.2.4 + * podman network create: validate user input + * Cirrus: Simplify package NVR logging + * Docs: Update links, add links to latest + * Cirrus: Fix log URIs & add optional $ALSO_FILENAME + * Raise start_test polling interval + * system tests: info: deal with hyphen in username + * Bump gitvalidation epoch + * Bump to v1.6.2-dev + * Apply changes also to the windows implementation + * System-tests: Use bash explicitly + * Podman 1.6.0 has been released, update the README + * Add api link to tutorials + * Bump gopkg.in/yaml.v2 from 2.2.2 to 2.2.3 + * Allow setting default parameters with env vars + * Avoid hard-coding path to varlink and podman + * Allow changing IdentityFile and to IgnoreHosts + * rm: add containers eviction with `rm --force` + - Changelog for v1.6.1 (2019-10-02) * Update release notes for v1.6.1 * Bump gitvalidation epoch diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index cff221cb0..872b59561 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -104,6 +104,9 @@ func inspectCmd(c *cliconfig.InspectValues) error { if strings.Contains(outputFormat, ".Dst") { outputFormat = strings.Replace(outputFormat, ".Dst", ".Destination", -1) } + if strings.Contains(outputFormat, ".ImageID") { + outputFormat = strings.Replace(outputFormat, ".ImageID", ".Image", -1) + } if latestContainer { lc, err := runtime.GetLatestContainer() if err != nil { diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index bd2cff3f6..d5247f689 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -39,7 +39,7 @@ %global shortcommit_conmon %(c=%{commit_conmon}; echo ${c:0:7}) Name: podman -Version: 1.6.2 +Version: 1.6.3 Release: #COMMITDATE#.git%{shortcommit0}%{?dist} Summary: Manage Pods, Containers and Container Images License: ASL 2.0 diff --git a/docs/podman-stats.1.md b/docs/podman-stats.1.md index e0cff0dc2..741873c3f 100644 --- a/docs/podman-stats.1.md +++ b/docs/podman-stats.1.md @@ -15,6 +15,9 @@ Note: Podman stats will not work in rootless environments that use CGroups V1. Podman stats relies on CGroup information for statistics, and CGroup v1 is not supported for rootless use cases. +Note: Rootless environments that use CGroups V2 are not able to report statistics +about their networking usage. + ## OPTIONS **--all**, **-a** diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index ed87373e9..3347a3648 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -396,7 +396,11 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt. ociRuntime, ok := s.runtime.ociRuntimes[runtimeName] if !ok { - return errors.Wrapf(define.ErrOCIRuntimeUnavailable, "cannot find OCI runtime %q for container %s", ctr.config.OCIRuntime, ctr.ID()) + // Use a MissingRuntime implementation + ociRuntime, err = getMissingRuntime(runtimeName, s.runtime) + if err != nil { + return err + } } ctr.ociRuntime = ociRuntime } diff --git a/libpod/container_api.go b/libpod/container_api.go index 04c796410..759a7067e 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -656,7 +656,7 @@ func (c *Container) Sync() error { (c.state.State != define.ContainerStateConfigured) && (c.state.State != define.ContainerStateExited) { oldState := c.state.State - if err := c.ociRuntime.UpdateContainerStatus(c, true); err != nil { + if err := c.ociRuntime.UpdateContainerStatus(c); err != nil { return err } // Only save back to DB if state changed diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 5a92b3e54..70b51960b 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -96,7 +96,7 @@ type InspectContainerData struct { Path string `json:"Path"` Args []string `json:"Args"` State *InspectContainerState `json:"State"` - ImageID string `json:"Image"` + Image string `json:"Image"` ImageName string `json:"ImageName"` Rootfs string `json:"Rootfs"` Pod string `json:"Pod"` @@ -718,7 +718,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) StartedAt: runtimeInfo.StartedTime, FinishedAt: runtimeInfo.FinishedTime, }, - ImageID: config.RootfsImageID, + Image: config.RootfsImageID, ImageName: config.RootfsImageName, ExitCommand: config.ExitCommand, Namespace: config.Namespace, diff --git a/libpod/container_internal.go b/libpod/container_internal.go index a7ac23f73..0043c9651 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -252,7 +252,7 @@ func (c *Container) waitForExitFileAndSync() error { return err } - if err := c.ociRuntime.UpdateContainerStatus(c, false); err != nil { + if err := c.checkExitFile(); err != nil { return err } @@ -386,10 +386,11 @@ func (c *Container) syncContainer() error { (c.state.State != define.ContainerStateConfigured) && (c.state.State != define.ContainerStateExited) { oldState := c.state.State - // TODO: optionally replace this with a stat for the exit file - if err := c.ociRuntime.UpdateContainerStatus(c, false); err != nil { + + if err := c.checkExitFile(); err != nil { return err } + // Only save back to DB if state changed if c.state.State != oldState { // Check for a restart policy match @@ -1811,3 +1812,35 @@ func (c *Container) sortUserVolumes(ctrSpec *spec.Spec) ([]*ContainerNamedVolume } return namedUserVolumes, userMounts } + +// Check for an exit file, and handle one if present +func (c *Container) checkExitFile() error { + // If the container's not running, nothing to do. + if c.state.State != define.ContainerStateRunning && c.state.State != define.ContainerStatePaused { + return nil + } + + exitFile, err := c.exitFilePath() + if err != nil { + return err + } + + // Check for the exit file + info, err := os.Stat(exitFile) + if err != nil { + if os.IsNotExist(err) { + // Container is still running, no error + return nil + } + + return errors.Wrapf(err, "error running stat on container %s exit file", c.ID()) + } + + // Alright, it exists. Transition to Stopped state. + c.state.State = define.ContainerStateStopped + c.state.PID = 0 + c.state.ConmonPID = 0 + + // Read the exit file to get our stopped time and exit code. + return c.handleExitFile(exitFile, info) +} diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 5392fbc62..523062866 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -65,6 +65,10 @@ var ( // CGroup. ErrNoCgroups = errors.New("this container does not have a cgroup") + // ErrRootless indicates that the given command cannot but run without + // root. + ErrRootless = errors.New("operation requires root privileges") + // ErrRuntimeStopped indicates that the runtime has already been shut // down and no further operations can be performed on it ErrRuntimeStopped = errors.New("runtime has already been stopped") diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 8181cbc8a..4360c8c15 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -462,6 +462,12 @@ func getContainerNetNS(ctr *Container) (string, error) { func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { var netStats *netlink.LinkStatistics + // rootless v2 cannot seem to resolve its network connection to + // collect statistics. For now, we allow stats to at least run + // by returning nil + if rootless.IsRootless() { + return netStats, nil + } netNSPath, netPathErr := getContainerNetNS(ctr) if netPathErr != nil { return nil, netPathErr diff --git a/libpod/oci.go b/libpod/oci.go index 37d04349f..9e761788e 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -26,7 +26,7 @@ type OCIRuntime interface { // It includes a switch for whether to perform a hard query of the // runtime. If unset, the exit file (if supported by the implementation) // will be used. - UpdateContainerStatus(ctr *Container, useRuntime bool) error + UpdateContainerStatus(ctr *Container) error // StartContainer starts the given container. StartContainer(ctr *Container) error // KillContainer sends the given signal to the given container. diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 3606a9634..658a2fe4e 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -216,8 +216,8 @@ func (r *ConmonOCIRuntime) CreateContainer(ctr *Container, restoreOptions *Conta // If useRuntime is false, we will not directly hit runc to see the container's // status, but will instead only check for the existence of the conmon exit file // and update state to stopped if it exists. -func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container, useRuntime bool) error { - exitFile, err := ctr.exitFilePath() +func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container) error { + exitFile, err := r.ExitFilePath(ctr) if err != nil { return err } @@ -227,33 +227,6 @@ func (r *ConmonOCIRuntime) UpdateContainerStatus(ctr *Container, useRuntime bool return err } - // If not using the OCI runtime, we don't need to do most of this. - if !useRuntime { - // If the container's not running, nothing to do. - if ctr.state.State != define.ContainerStateRunning && ctr.state.State != define.ContainerStatePaused { - return nil - } - - // Check for the exit file conmon makes - info, err := os.Stat(exitFile) - if err != nil { - if os.IsNotExist(err) { - // Container is still running, no error - return nil - } - - return errors.Wrapf(err, "error running stat on container %s exit file", ctr.ID()) - } - - // Alright, it exists. Transition to Stopped state. - ctr.state.State = define.ContainerStateStopped - ctr.state.PID = 0 - ctr.state.ConmonPID = 0 - - // Read the exit file to get our stopped time and exit code. - return ctr.handleExitFile(exitFile, info) - } - // Store old state so we know if we were already stopped oldState := ctr.state.State @@ -825,6 +798,7 @@ func (r *ConmonOCIRuntime) RuntimeInfo() (map[string]interface{}, error) { "version": conmonVersion, } info["OCIRuntime"] = map[string]interface{}{ + "name": r.name, "path": r.path, "package": runtimePackage, "version": runtimeVersion, diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go new file mode 100644 index 000000000..d4524cd34 --- /dev/null +++ b/libpod/oci_missing.go @@ -0,0 +1,189 @@ +package libpod + +import ( + "fmt" + "path/filepath" + "sync" + + "github.com/containers/libpod/libpod/define" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var ( + // Only create each missing runtime once. + // Creation makes error messages we don't want to duplicate. + missingRuntimes map[string]*MissingRuntime + // We need a lock for this + missingRuntimesLock sync.Mutex +) + +// MissingRuntime is used when the OCI runtime requested by the container is +// missing (not installed or not in the configuration file). +type MissingRuntime struct { + // Name is the name of the missing runtime. Will be used in errors. + name string + // exitsDir is the directory for exit files. + exitsDir string +} + +// Get a new MissingRuntime for the given name. +// Requires a libpod Runtime so we can make a sane path for the exits dir. +func getMissingRuntime(name string, r *Runtime) (OCIRuntime, error) { + missingRuntimesLock.Lock() + defer missingRuntimesLock.Unlock() + + if missingRuntimes == nil { + missingRuntimes = make(map[string]*MissingRuntime) + } + + runtime, ok := missingRuntimes[name] + if ok { + return runtime, nil + } + + // Once for each missing runtime, we want to error. + logrus.Errorf("OCI Runtime %s is in use by a container, but is not available (not in configuration file or not installed)", name) + + newRuntime := new(MissingRuntime) + newRuntime.name = name + newRuntime.exitsDir = filepath.Join(r.config.TmpDir, "exits") + + missingRuntimes[name] = newRuntime + + return newRuntime, nil +} + +// Name is the name of the missing runtime +func (r *MissingRuntime) Name() string { + return fmt.Sprintf("%s (missing/not available)", r.name) +} + +// Path is not available as the runtime is missing +func (r *MissingRuntime) Path() string { + return "(missing/not available)" +} + +// CreateContainer is not available as the runtime is missing +func (r *MissingRuntime) CreateContainer(ctr *Container, restoreOptions *ContainerCheckpointOptions) error { + return r.printError() +} + +// UpdateContainerStatus is not available as the runtime is missing +func (r *MissingRuntime) UpdateContainerStatus(ctr *Container) error { + return r.printError() +} + +// StartContainer is not available as the runtime is missing +func (r *MissingRuntime) StartContainer(ctr *Container) error { + return r.printError() +} + +// KillContainer is not available as the runtime is missing +// TODO: We could attempt to unix.Kill() the PID as recorded in the state if we +// really want to smooth things out? Won't be perfect, but if the container has +// a PID namespace it could be enough? +func (r *MissingRuntime) KillContainer(ctr *Container, signal uint, all bool) error { + return r.printError() +} + +// StopContainer is not available as the runtime is missing +func (r *MissingRuntime) StopContainer(ctr *Container, timeout uint, all bool) error { + return r.printError() +} + +// DeleteContainer is not available as the runtime is missing +func (r *MissingRuntime) DeleteContainer(ctr *Container) error { + return r.printError() +} + +// PauseContainer is not available as the runtime is missing +func (r *MissingRuntime) PauseContainer(ctr *Container) error { + return r.printError() +} + +// UnpauseContainer is not available as the runtime is missing +func (r *MissingRuntime) UnpauseContainer(ctr *Container) error { + return r.printError() +} + +// ExecContainer is not available as the runtime is missing +func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error) { + return -1, nil, r.printError() +} + +// ExecStopContainer is not available as the runtime is missing. +// TODO: We can also investigate using unix.Kill() on the PID of the exec +// session here if we want to make stopping containers possible. Won't be +// perfect, though. +func (r *MissingRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error { + return r.printError() +} + +// ExecContainerCleanup is not available as the runtime is missing +func (r *MissingRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error { + return r.printError() +} + +// CheckpointContainer is not available as the runtime is missing +func (r *MissingRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error { + return r.printError() +} + +// SupportsCheckpoint returns false as checkpointing requires a working runtime +func (r *MissingRuntime) SupportsCheckpoint() bool { + return false +} + +// SupportsJSONErrors returns false as there is no runtime to give errors +func (r *MissingRuntime) SupportsJSONErrors() bool { + return false +} + +// SupportsNoCgroups returns false as there is no runtime to create containers +func (r *MissingRuntime) SupportsNoCgroups() bool { + return false +} + +// AttachSocketPath does not work as there is no runtime to attach to. +// (Theoretically we could follow ExitFilePath but there is no guarantee the +// container is running and thus has an attach socket...) +func (r *MissingRuntime) AttachSocketPath(ctr *Container) (string, error) { + return "", r.printError() +} + +// ExecAttachSocketPath does not work as there is no runtime to attach to. +// (Again, we could follow ExitFilePath, but no guarantee there is an existing +// and running exec session) +func (r *MissingRuntime) ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) { + return "", r.printError() +} + +// ExitFilePath returns the exit file path for containers. +// Here, we mimic what ConmonOCIRuntime does, because there is a chance that the +// container in question is still running happily (config file modified to +// remove a runtime, for example). We can't find the runtime to do anything to +// the container, but Conmon should still place an exit file for it. +func (r *MissingRuntime) ExitFilePath(ctr *Container) (string, error) { + if ctr == nil { + return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid container to get exit file path") + } + return filepath.Join(r.exitsDir, ctr.ID()), nil +} + +// RuntimeInfo returns information on the missing runtime +func (r *MissingRuntime) RuntimeInfo() (map[string]interface{}, error) { + info := make(map[string]interface{}) + info["OCIRuntime"] = map[string]interface{}{ + "name": r.name, + "path": "missing", + "package": "missing", + "version": "missing", + } + return info, nil +} + +// Return an error indicating the runtime is missing +func (r *MissingRuntime) printError() error { + return errors.Wrapf(define.ErrOCIRuntimeNotFound, "runtime %s is missing", r.name) +} diff --git a/libpod/runtime.go b/libpod/runtime.go index a0cf0ad7c..107e8e3d0 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -14,7 +14,6 @@ import ( "strings" "sync" "syscall" - "time" "github.com/BurntSushi/toml" is "github.com/containers/image/v4/storage" @@ -353,10 +352,6 @@ func defaultRuntimeConfig() (RuntimeConfig, error) { // SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set. // containers/image uses XDG_RUNTIME_DIR to locate the auth file, XDG_CONFIG_HOME is // use for the libpod.conf configuration file. -// SetXdgDirs internally calls EnableLinger() so that the user's processes are not -// killed once the session is terminated. EnableLinger() also attempts to -// get the runtime directory when XDG_RUNTIME_DIR is not specified. -// This function should only be called when running rootless. func SetXdgDirs() error { if !rootless.IsRootless() { return nil @@ -365,21 +360,6 @@ func SetXdgDirs() error { // Setup XDG_RUNTIME_DIR runtimeDir := os.Getenv("XDG_RUNTIME_DIR") - runtimeDirLinger, err := rootless.EnableLinger() - if err != nil { - return errors.Wrapf(err, "error enabling user session") - } - if runtimeDir == "" && runtimeDirLinger != "" { - if _, err := os.Stat(runtimeDirLinger); err != nil && os.IsNotExist(err) { - chWait := make(chan error) - defer close(chWait) - if _, err := WaitForFile(runtimeDirLinger, chWait, time.Second*10); err != nil { - return errors.Wrapf(err, "waiting for directory '%s'", runtimeDirLinger) - } - } - runtimeDir = runtimeDirLinger - } - if runtimeDir == "" { var err error runtimeDir, err = util.GetRuntimeDir() @@ -400,10 +380,11 @@ func SetXdgDirs() error { // Setup XDG_CONFIG_HOME if cfgHomeDir := os.Getenv("XDG_CONFIG_HOME"); cfgHomeDir == "" { - if cfgHomeDir, err = util.GetRootlessConfigHomeDir(); err != nil { + cfgHomeDir, err := util.GetRootlessConfigHomeDir() + if err != nil { return err } - if err = os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil { + if err := os.Setenv("XDG_CONFIG_HOME", cfgHomeDir); err != nil { return errors.Wrapf(err, "cannot set XDG_CONFIG_HOME") } } diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index 9df93faf3..ba4fff4be 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -157,7 +157,14 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error // If the volume is still mounted - force unmount it if err := v.unmount(true); err != nil { - return errors.Wrapf(err, "error unmounting volume %s", v.Name()) + if force { + // If force is set, evict the volume, even if errors + // occur. Otherwise we'll never be able to get rid of + // them. + logrus.Errorf("Error unmounting volume %s: %v", v.Name(), err) + } else { + return errors.Wrapf(err, "error unmounting volume %s", v.Name()) + } } // Set volume as invalid so it can no longer be used diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go index 9ae4dcf69..4c0332018 100644 --- a/libpod/volume_internal_linux.go +++ b/libpod/volume_internal_linux.go @@ -6,6 +6,8 @@ import ( "io/ioutil" "os/exec" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" @@ -24,6 +26,11 @@ func (v *Volume) mount() error { return nil } + // We cannot mount volumes as rootless. + if rootless.IsRootless() { + return errors.Wrapf(define.ErrRootless, "cannot mount volumes without root privileges") + } + // Update the volume from the DB to get an accurate mount counter. if err := v.update(); err != nil { return err @@ -108,6 +115,20 @@ func (v *Volume) unmount(force bool) error { return nil } + // We cannot unmount volumes as rootless. + if rootless.IsRootless() { + // If force is set, just clear the counter and bail without + // error, so we can remove volumes from the state if they are in + // an awkward configuration. + if force { + logrus.Errorf("Volume %s is mounted despite being rootless - state is not sane", v.Name()) + v.state.MountCount = 0 + return v.save() + } + + return errors.Wrapf(define.ErrRootless, "cannot mount or unmount volumes without root privileges") + } + if !force { v.state.MountCount = v.state.MountCount - 1 } else { @@ -119,6 +140,10 @@ func (v *Volume) unmount(force bool) error { if v.state.MountCount == 0 { // Unmount the volume if err := unix.Unmount(v.config.MountPoint, unix.MNT_DETACH); err != nil { + if err == unix.EINVAL { + // Ignore EINVAL - the mount no longer exists. + return nil + } return errors.Wrapf(err, "error unmounting volume %s", v.Name()) } logrus.Debugf("Unmounted volume %s", v.Name()) diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index e67cc03ba..5c33467a7 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -612,7 +612,9 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP if c.Attach { inputStream := os.Stdin if !c.Interactive { - inputStream = nil + if !ctr.Stdin() { + inputStream = nil + } } // attach to the container and also start it not already running @@ -663,7 +665,7 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP lastError = errors.Wrapf(err, "unable to start container %q", container) continue } - fmt.Println(container) + fmt.Println(ctr.ID()) } return exitCode, lastError } diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 99307e8c4..94c42f7d0 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -11,20 +11,16 @@ import ( "os/exec" gosignal "os/signal" "os/user" - "path/filepath" "runtime" "strconv" - "strings" "sync" - "syscall" "unsafe" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/storage/pkg/idtools" - "github.com/docker/docker/pkg/signal" - "github.com/godbus/dbus" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) /* @@ -130,7 +126,7 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) func readUserNs(path string) (string, error) { b := make([]byte, 256) - _, err := syscall.Readlink(path, b) + _, err := unix.Readlink(path, b) if err != nil { return "", err } @@ -143,7 +139,7 @@ func readUserNsFd(fd uintptr) (string, error) { func getParentUserNs(fd uintptr) (uintptr, error) { const nsGetParent = 0xb702 - ret, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(nsGetParent), 0) + ret, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(nsGetParent), 0) if errno != 0 { return 0, errno } @@ -179,7 +175,7 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { for { nextFd, err := getParentUserNs(fd) if err != nil { - if err == syscall.ENOTTY { + if err == unix.ENOTTY { return os.NewFile(fd, "userns child"), nil } return nil, errors.Wrapf(err, "cannot get parent user namespace") @@ -191,14 +187,14 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { } if ns == currentNS { - if err := syscall.Close(int(nextFd)); err != nil { + if err := unix.Close(int(nextFd)); err != nil { return nil, err } // Drop O_CLOEXEC for the fd. - _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, fd, syscall.F_SETFD, 0) + _, _, errno := unix.Syscall(unix.SYS_FCNTL, fd, unix.F_SETFD, 0) if errno != 0 { - if err := syscall.Close(int(fd)); err != nil { + if err := unix.Close(int(fd)); err != nil { logrus.Errorf("failed to close file descriptor %d", fd) } return nil, errno @@ -206,99 +202,13 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { return os.NewFile(fd, "userns child"), nil } - if err := syscall.Close(int(fd)); err != nil { + if err := unix.Close(int(fd)); err != nil { return nil, err } fd = nextFd } } -// EnableLinger configures the system to not kill the user processes once the session -// terminates -func EnableLinger() (string, error) { - uid := fmt.Sprintf("%d", GetRootlessUID()) - - conn, err := dbus.SystemBus() - if err == nil { - defer func() { - if err := conn.Close(); err != nil { - logrus.Errorf("unable to close dbus connection: %q", err) - } - }() - } - - lingerEnabled := false - - // If we have a D-BUS connection, attempt to read the LINGER property from it. - if conn != nil { - path := dbus.ObjectPath(fmt.Sprintf("/org/freedesktop/login1/user/_%s", uid)) - ret, err := conn.Object("org.freedesktop.login1", path).GetProperty("org.freedesktop.login1.User.Linger") - if err == nil && ret.Value().(bool) { - lingerEnabled = true - } - } - - xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR") - lingerFile := "" - if xdgRuntimeDir != "" && !lingerEnabled { - lingerFile = filepath.Join(xdgRuntimeDir, "libpod/linger") - _, err := os.Stat(lingerFile) - if err == nil { - lingerEnabled = true - } - } - - if !lingerEnabled { - // First attempt with D-BUS, if it fails, then attempt with "loginctl enable-linger" - if conn != nil { - o := conn.Object("org.freedesktop.login1", "/org/freedesktop/login1") - ret := o.Call("org.freedesktop.login1.Manager.SetUserLinger", 0, uint32(GetRootlessUID()), true, true) - if ret.Err == nil { - lingerEnabled = true - } - } - if !lingerEnabled { - err := exec.Command("loginctl", "enable-linger", uid).Run() - if err == nil { - lingerEnabled = true - } else { - logrus.Debugf("cannot run `loginctl enable-linger` for the current user: %v", err) - } - } - if lingerEnabled && lingerFile != "" { - f, err := os.Create(lingerFile) - if err == nil { - if err := f.Close(); err != nil { - logrus.Errorf("failed to close %s", f.Name()) - } - } else { - logrus.Debugf("could not create linger file: %v", err) - } - } - } - - if !lingerEnabled { - return "", nil - } - - // If we have a D-BUS connection, attempt to read the RUNTIME PATH from it. - if conn != nil { - path := dbus.ObjectPath(fmt.Sprintf("/org/freedesktop/login1/user/_%s", uid)) - ret, err := conn.Object("org.freedesktop.login1", path).GetProperty("org.freedesktop.login1.User.RuntimePath") - if err == nil { - return strings.Trim(ret.String(), "\"\n"), nil - } - } - - // If XDG_RUNTIME_DIR is not set and the D-BUS call didn't work, try to get the runtime path with "loginctl" - output, err := exec.Command("loginctl", "-pRuntimePath", "show-user", uid).Output() - if err != nil { - logrus.Debugf("could not get RuntimePath using loginctl: %v", err) - return "", nil - } - return strings.Trim(strings.Replace(string(output), "RuntimePath=", "", -1), "\"\n"), nil -} - // joinUserAndMountNS re-exec podman in a new userNS and join the user and mount // namespace of the specified PID without looking up its parent. Useful to join directly // the conmon process. @@ -394,7 +304,7 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, runtime.LockOSThread() defer runtime.UnlockOSThread() - fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0) + fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) if err != nil { return false, -1, err } @@ -491,21 +401,21 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, signals := []os.Signal{} for sig := 0; sig < numSig; sig++ { - if sig == int(syscall.SIGTSTP) { + if sig == int(unix.SIGTSTP) { continue } - signals = append(signals, syscall.Signal(sig)) + signals = append(signals, unix.Signal(sig)) } gosignal.Notify(c, signals...) defer gosignal.Reset() go func() { for s := range c { - if s == signal.SIGCHLD || s == signal.SIGPIPE { + if s == unix.SIGCHLD || s == unix.SIGPIPE { continue } - if err := syscall.Kill(int(pidC), s.(syscall.Signal)); err != nil { + if err := unix.Kill(int(pidC), s.(unix.Signal)); err != nil { logrus.Errorf("failed to kill %d", int(pidC)) } } @@ -560,7 +470,7 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st lastErr = nil break } else { - fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0) + fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) if err != nil { lastErr = err continue diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index ce488f364..1499b737f 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -37,12 +37,6 @@ func GetRootlessGID() int { return -1 } -// EnableLinger configures the system to not kill the user processes once the session -// terminates -func EnableLinger() (string, error) { - return "", nil -} - // TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths. // This is useful when there are already running containers and we // don't have a pause process yet. We can use the paths to the conmon diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 1caefd299..4b43ceb30 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -67,13 +67,13 @@ var _ = Describe("Podman checkpoint", func() { It("podman checkpoint bogus container", func() { session := podmanTest.Podman([]string{"container", "checkpoint", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman restore bogus container", func() { session := podmanTest.Podman([]string{"container", "restore", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman checkpoint a running container by id", func() { diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index 3317683de..8d4c3dee7 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -53,7 +53,7 @@ var _ = Describe("Podman cp", func() { session = podmanTest.Podman([]string{"cp", srcPath, name + ":foo/"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"cp", srcPath, name + ":foo"}) session.WaitWithDefaultTimeout() @@ -205,7 +205,7 @@ var _ = Describe("Podman cp", func() { session = podmanTest.Podman([]string{"cp", "--pause=false", srcPath, name + ":/test1/"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman cp volume", func() { diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go index 709e56665..72a0638f9 100644 --- a/test/e2e/create_staticip_test.go +++ b/test/e2e/create_staticip_test.go @@ -40,13 +40,13 @@ var _ = Describe("Podman create with --ip flag", func() { It("Podman create --ip with garbage address", func() { result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "114232346", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman create --ip with v6 address", func() { result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman create --ip with non-allocatable IP", func() { @@ -56,7 +56,7 @@ var _ = Describe("Podman create with --ip flag", func() { result = podmanTest.Podman([]string{"start", "test"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman create with specified static IP has correct IP", func() { @@ -88,6 +88,6 @@ var _ = Describe("Podman create with --ip flag", func() { Expect(result.ExitCode()).To(Equal(0)) result = podmanTest.Podman([]string{"start", "test2"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) }) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 2918cce78..65b747880 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -235,7 +235,7 @@ var _ = Describe("Podman create", func() { It("podman create --pull", func() { session := podmanTest.PodmanNoCache([]string{"create", "--pull", "never", "--name=foo", "nginx"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.PodmanNoCache([]string{"create", "--pull", "always", "--name=foo", "nginx"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 13fdabb81..1c4a9adb9 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -203,11 +203,11 @@ var _ = Describe("Podman exec", func() { session := podmanTest.Podman([]string{"exec", "--workdir", "/missing", "test1", "pwd"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"exec", "-w", "/missing", "test1", "pwd"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman exec cannot be invoked", func() { diff --git a/test/e2e/export_test.go b/test/e2e/export_test.go index 8406b0e73..1c84c6f4d 100644 --- a/test/e2e/export_test.go +++ b/test/e2e/export_test.go @@ -72,6 +72,6 @@ var _ = Describe("Podman export", func() { outfile := filepath.Join(podmanTest.TempDir, "container:with:colon.tar") result := podmanTest.Podman([]string{"export", "-o", outfile, cid}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) }) }) diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 49d2c12a8..5d3b1238a 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -40,13 +40,13 @@ var _ = Describe("Podman generate kube", func() { It("podman generate pod kube on bogus object", func() { session := podmanTest.Podman([]string{"generate", "kube", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate service kube on bogus object", func() { session := podmanTest.Podman([]string{"generate", "kube", "-s", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate kube on container", func() { diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go index 314743a92..91072b023 100644 --- a/test/e2e/generate_systemd_test.go +++ b/test/e2e/generate_systemd_test.go @@ -37,19 +37,19 @@ var _ = Describe("Podman generate systemd", func() { It("podman generate systemd on bogus container/pod", func() { session := podmanTest.Podman([]string{"generate", "systemd", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate systemd bad restart policy", func() { session := podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "never", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate systemd bad timeout value", func() { session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "-1", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman generate systemd good timeout value", func() { diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index e10aef427..4acea06eb 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -38,7 +38,7 @@ var _ = Describe("Podman healthcheck run", func() { It("podman healthcheck run bogus container", func() { session := podmanTest.Podman([]string{"healthcheck", "run", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman healthcheck on valid container", func() { diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index 790115133..7d029c52f 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -46,7 +46,7 @@ var _ = Describe("Podman inspect", func() { SkipIfRemote() session := podmanTest.Podman([]string{"inspect", "foobar4321"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman inspect with GO format", func() { @@ -100,6 +100,23 @@ var _ = Describe("Podman inspect", func() { Expect(len(result.OutputToStringArray())).To(Equal(2)) }) + It("podman inspect container and filter for Image{ID}", func() { + SkipIfRemote() + ls, ec, _ := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + cid := ls.OutputToString() + + result := podmanTest.Podman([]string{"inspect", "--format={{.ImageID}}", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(Equal(1)) + + result = podmanTest.Podman([]string{"inspect", "--format={{.Image}}", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(Equal(1)) + }) + It("podman inspect -l with additional input should fail", func() { SkipIfRemote() result := podmanTest.Podman([]string{"inspect", "-l", "1234foobar"}) diff --git a/test/e2e/kill_test.go b/test/e2e/kill_test.go index 017fe4a3f..834f86b77 100644 --- a/test/e2e/kill_test.go +++ b/test/e2e/kill_test.go @@ -35,7 +35,7 @@ var _ = Describe("Podman kill", func() { It("podman kill bogus container", func() { session := podmanTest.Podman([]string{"kill", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman container kill a running container by id", func() { diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go index 9209e1770..9ff358d26 100644 --- a/test/e2e/load_test.go +++ b/test/e2e/load_test.go @@ -143,7 +143,7 @@ var _ = Describe("Podman load", func() { It("podman load bogus file", func() { save := podmanTest.PodmanNoCache([]string{"load", "-i", "foobar.tar"}) save.WaitWithDefaultTimeout() - Expect(save.ExitCode()).ToNot(Equal(0)) + Expect(save).To(ExitWithError()) }) It("podman load multiple tags", func() { diff --git a/test/e2e/login_logout_test.go b/test/e2e/login_logout_test.go index 4d476e05f..14cfed5db 100644 --- a/test/e2e/login_logout_test.go +++ b/test/e2e/login_logout_test.go @@ -109,7 +109,7 @@ var _ = Describe("Podman login and logout", func() { session = podmanTest.Podman([]string{"push", ALPINE, testImg}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman login and logout with flag --authfile", func() { @@ -198,7 +198,7 @@ var _ = Describe("Podman login and logout", func() { session = podmanTest.Podman([]string{"push", ALPINE, "localhost:9001/test-alpine"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"login", "--username", "podmantest", "--password", "test", "localhost:9001"}) session.WaitWithDefaultTimeout() @@ -218,7 +218,7 @@ var _ = Describe("Podman login and logout", func() { session = podmanTest.Podman([]string{"push", ALPINE, testImg}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"push", ALPINE, "localhost:9001/test-alpine"}) session.WaitWithDefaultTimeout() @@ -234,10 +234,10 @@ var _ = Describe("Podman login and logout", func() { session = podmanTest.Podman([]string{"push", ALPINE, testImg}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"push", ALPINE, "localhost:9001/test-alpine"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) }) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index d17f60a5d..f34d85d76 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -108,7 +108,7 @@ var _ = Describe("Podman logs", func() { It("podman logs latest and container name should fail", func() { results := podmanTest.Podman([]string{"logs", "-l", "foobar"}) results.WaitWithDefaultTimeout() - Expect(results.ExitCode()).ToNot(Equal(0)) + Expect(results).To(ExitWithError()) }) It("podman logs two containers and should display short container IDs", func() { diff --git a/test/e2e/negative_test.go b/test/e2e/negative_test.go index 3cb54a20a..957609b7e 100644 --- a/test/e2e/negative_test.go +++ b/test/e2e/negative_test.go @@ -33,6 +33,6 @@ var _ = Describe("Podman negative command-line", func() { It("podman snuffleupagus exits non-zero", func() { session := podmanTest.Podman([]string{"snuffleupagus"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) }) diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index 264219178..b83757cc0 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -182,19 +182,19 @@ var _ = Describe("Podman network create", func() { It("podman network create with invalid subnet", func() { nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/17000", "fail"}) nc.WaitWithDefaultTimeout() - Expect(nc.ExitCode()).ToNot(BeZero()) + Expect(nc).To(ExitWithError()) }) It("podman network create with invalid IP", func() { nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.0/17000", "fail"}) nc.WaitWithDefaultTimeout() - Expect(nc.ExitCode()).ToNot(BeZero()) + Expect(nc).To(ExitWithError()) }) It("podman network create with invalid gateway for subnet", func() { nc := podmanTest.Podman([]string{"network", "create", "--subnet", "10.11.12.0/24", "--gateway", "192.168.1.1", "fail"}) nc.WaitWithDefaultTimeout() - Expect(nc.ExitCode()).ToNot(BeZero()) + Expect(nc).To(ExitWithError()) }) It("podman network create two networks with same name should fail", func() { @@ -205,13 +205,13 @@ var _ = Describe("Podman network create", func() { ncFail := podmanTest.Podman([]string{"network", "create", "samename"}) ncFail.WaitWithDefaultTimeout() - Expect(ncFail.ExitCode()).ToNot(BeZero()) + Expect(ncFail).To(ExitWithError()) }) It("podman network create with invalid network name", func() { nc := podmanTest.Podman([]string{"network", "create", "foo "}) nc.WaitWithDefaultTimeout() - Expect(nc.ExitCode()).ToNot(BeZero()) + Expect(nc).To(ExitWithError()) }) }) diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index c61131078..39e08e2e8 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -52,13 +52,13 @@ var _ = Describe("Podman pause", func() { It("podman pause bogus container", func() { session := podmanTest.Podman([]string{"pause", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman unpause bogus container", func() { session := podmanTest.Podman([]string{"unpause", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pause a created container by id", func() { @@ -70,7 +70,7 @@ var _ = Describe("Podman pause", func() { result := podmanTest.Podman([]string{"pause", cid}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(createdState)) }) diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 5d59f0eb0..7069e049d 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -23,7 +23,7 @@ metadata: spec: hostname: {{ .Hostname }} containers: -{{ with .Containers }} +{{ with .Ctrs }} {{ range . }} - command: {{ range .Cmd }} @@ -67,47 +67,128 @@ spec: status: {} ` -type Pod struct { - Name string - Hostname string - Containers []Container -} - -type Container struct { - Cmd []string - Image string - Name string - SecurityContext bool - Caps bool - CapAdd []string - CapDrop []string -} +var ( + defaultCtrName = "testCtr" + defaultCtrCmd = []string{"top"} + defaultCtrImage = ALPINE + defaultPodName = "testPod" +) -func generateKubeYaml(name string, hostname string, ctrs []Container, fileName string) error { +func generateKubeYaml(pod *Pod, fileName string) error { f, err := os.Create(fileName) if err != nil { return err } defer f.Close() - testPod := Pod{name, hostname, ctrs} t, err := template.New("pod").Parse(yamlTemplate) if err != nil { return err } - if err := t.Execute(f, testPod); err != nil { + if err := t.Execute(f, pod); err != nil { return err } return nil } +// Pod describes the options a kube yaml can be configured at pod level +type Pod struct { + Name string + Hostname string + Ctrs []*Ctr +} + +// getPod takes a list of podOptions and returns a pod with sane defaults +// and the configured options +// if no containers are added, it will add the default container +func getPod(options ...podOption) *Pod { + p := Pod{defaultPodName, "", make([]*Ctr, 0)} + for _, option := range options { + option(&p) + } + if len(p.Ctrs) == 0 { + p.Ctrs = []*Ctr{getCtr()} + } + return &p +} + +type podOption func(*Pod) + +func withHostname(h string) podOption { + return func(pod *Pod) { + pod.Hostname = h + } +} + +func withCtr(c *Ctr) podOption { + return func(pod *Pod) { + pod.Ctrs = append(pod.Ctrs, c) + } +} + +// Ctr describes the options a kube yaml can be configured at container level +type Ctr struct { + Name string + Image string + Cmd []string + SecurityContext bool + Caps bool + CapAdd []string + CapDrop []string +} + +// getCtr takes a list of ctrOptions and returns a Ctr with sane defaults +// and the configured options +func getCtr(options ...ctrOption) *Ctr { + c := Ctr{defaultCtrName, defaultCtrImage, defaultCtrCmd, true, false, nil, nil} + for _, option := range options { + option(&c) + } + return &c +} + +type ctrOption func(*Ctr) + +func withCmd(cmd []string) ctrOption { + return func(c *Ctr) { + c.Cmd = cmd + } +} + +func withImage(img string) ctrOption { + return func(c *Ctr) { + c.Image = img + } +} + +func withSecurityContext(sc bool) ctrOption { + return func(c *Ctr) { + c.SecurityContext = sc + } +} + +func withCapAdd(caps []string) ctrOption { + return func(c *Ctr) { + c.CapAdd = caps + c.Caps = true + } +} + +func withCapDrop(caps []string) ctrOption { + return func(c *Ctr) { + c.CapDrop = caps + c.Caps = true + } +} + var _ = Describe("Podman generate kube", func() { var ( tempdir string err error podmanTest *PodmanTestIntegration + kubeYaml string ) BeforeEach(func() { @@ -118,6 +199,8 @@ var _ = Describe("Podman generate kube", func() { podmanTest = PodmanTestCreate(tempdir) podmanTest.Setup() podmanTest.SeedImages() + + kubeYaml = filepath.Join(podmanTest.TempDir, "kube.yaml") }) AfterEach(func() { @@ -127,123 +210,98 @@ var _ = Describe("Podman generate kube", func() { }) It("podman play kube test correct command", func() { - ctrName := "testCtr" - ctrCmd := []string{"top"} - testContainer := Container{ctrCmd, ALPINE, ctrName, true, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") - - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) - Expect(inspect.OutputToString()).To(ContainSubstring(ctrCmd[0])) + Expect(inspect.OutputToString()).To(ContainSubstring(defaultCtrCmd[0])) }) It("podman play kube test correct output", func() { - ctrName := "testCtr" - ctrCmd := []string{"echo", "hello"} - testContainer := Container{ctrCmd, ALPINE, ctrName, true, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") + p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"})))) - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(p, kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - logs := podmanTest.Podman([]string{"logs", ctrName}) + logs := podmanTest.Podman([]string{"logs", defaultCtrName}) logs.WaitWithDefaultTimeout() Expect(logs.ExitCode()).To(Equal(0)) Expect(logs.OutputToString()).To(ContainSubstring("hello")) - inspect := podmanTest.Podman([]string{"inspect", ctrName, "--format", "'{{ .Config.Cmd }}'"}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName, "--format", "'{{ .Config.Cmd }}'"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(ContainSubstring("hello")) }) It("podman play kube test hostname", func() { - podName := "test" - ctrName := "testCtr" - ctrCmd := []string{"top"} - testContainer := Container{ctrCmd, ALPINE, ctrName, true, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") - - err := generateKubeYaml(podName, "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName, "--format", "{{ .Config.Hostname }}"}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName, "--format", "{{ .Config.Hostname }}"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) - Expect(inspect.OutputToString()).To(Equal(podName)) + Expect(inspect.OutputToString()).To(Equal(defaultPodName)) }) It("podman play kube test with customized hostname", func() { hostname := "myhostname" - ctrName := "testCtr" - ctrCmd := []string{"top"} - testContainer := Container{ctrCmd, ALPINE, ctrName, true, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") - - err := generateKubeYaml("test", hostname, []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(withHostname(hostname)), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName, "--format", "{{ .Config.Hostname }}"}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName, "--format", "{{ .Config.Hostname }}"}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(Equal(hostname)) }) It("podman play kube cap add", func() { - ctrName := "testCtr" - ctrCmd := []string{"cat", "/proc/self/status"} capAdd := "CAP_SYS_ADMIN" - testContainer := Container{ctrCmd, ALPINE, ctrName, true, true, []string{capAdd}, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") + ctr := getCtr(withCapAdd([]string{capAdd}), withCmd([]string{"cat", "/proc/self/status"})) - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(withCtr(ctr)), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(ContainSubstring(capAdd)) }) - It("podman play kube cap add", func() { - ctrName := "testCtr" - ctrCmd := []string{"cat", "/proc/self/status"} - capDrop := "CAP_SYS_ADMIN" - testContainer := Container{ctrCmd, ALPINE, ctrName, true, true, []string{capDrop}, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") + It("podman play kube cap drop", func() { + capDrop := "CAP_CHOWN" + ctr := getCtr(withCapDrop([]string{capDrop})) - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(withCtr(ctr)), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) Expect(inspect.OutputToString()).To(ContainSubstring(capDrop)) @@ -251,19 +309,14 @@ var _ = Describe("Podman generate kube", func() { It("podman play kube no security context", func() { // expect play kube to not fail if no security context is specified - ctrName := "testCtr" - ctrCmd := "ls" - testContainer := Container{[]string{ctrCmd}, ALPINE, ctrName, false, false, nil, nil} - tempFile := filepath.Join(podmanTest.TempDir, "kube.yaml") - - err := generateKubeYaml("test", "", []Container{testContainer}, tempFile) + err := generateKubeYaml(getPod(withCtr(getCtr(withSecurityContext(false)))), kubeYaml) Expect(err).To(BeNil()) - kube := podmanTest.Podman([]string{"play", "kube", tempFile}) + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) kube.WaitWithDefaultTimeout() Expect(kube.ExitCode()).To(Equal(0)) - inspect := podmanTest.Podman([]string{"inspect", ctrName}) + inspect := podmanTest.Podman([]string{"inspect", defaultCtrName}) inspect.WaitWithDefaultTimeout() Expect(inspect.ExitCode()).To(Equal(0)) }) diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index 3897aa851..c8072f308 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -122,7 +122,7 @@ var _ = Describe("Podman pod create", func() { session = podmanTest.Podman([]string{"run", fedoraMinimal, "curl", "localhost"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pod correctly sets up IPCNS", func() { @@ -218,7 +218,7 @@ var _ = Describe("Podman pod create", func() { session = podmanTest.Podman([]string{"run", "--pod", podID, "--network", "bridge", nginx, "curl", "localhost"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pod container can override pod pid NS", func() { @@ -309,7 +309,7 @@ var _ = Describe("Podman pod create", func() { session = podmanTest.Podman([]string{"rm", infraID}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"pod", "rm", podID}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/pod_inspect_test.go b/test/e2e/pod_inspect_test.go index 488dd1685..49c647528 100644 --- a/test/e2e/pod_inspect_test.go +++ b/test/e2e/pod_inspect_test.go @@ -35,7 +35,7 @@ var _ = Describe("Podman pod inspect", func() { It("podman inspect bogus pod", func() { session := podmanTest.Podman([]string{"pod", "inspect", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).Should(ExitWithError()) }) It("podman inspect a pod", func() { diff --git a/test/e2e/pod_kill_test.go b/test/e2e/pod_kill_test.go index 7cf67bbfc..a3efec46c 100644 --- a/test/e2e/pod_kill_test.go +++ b/test/e2e/pod_kill_test.go @@ -36,7 +36,7 @@ var _ = Describe("Podman pod kill", func() { It("podman pod kill bogus", func() { session := podmanTest.Podman([]string{"pod", "kill", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pod kill a pod by id", func() { diff --git a/test/e2e/pod_pause_test.go b/test/e2e/pod_pause_test.go index 619ee6f12..73707926d 100644 --- a/test/e2e/pod_pause_test.go +++ b/test/e2e/pod_pause_test.go @@ -38,13 +38,13 @@ var _ = Describe("Podman pod pause", func() { It("podman pod pause bogus pod", func() { session := podmanTest.Podman([]string{"pod", "pause", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman unpause bogus pod", func() { session := podmanTest.Podman([]string{"pod", "unpause", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pod pause a created pod by id", func() { diff --git a/test/e2e/pod_ps_test.go b/test/e2e/pod_ps_test.go index 6d5873caa..aa07be55c 100644 --- a/test/e2e/pod_ps_test.go +++ b/test/e2e/pod_ps_test.go @@ -107,7 +107,7 @@ var _ = Describe("Podman ps", func() { It("podman pod ps mutually exclusive flags", func() { session := podmanTest.Podman([]string{"pod", "ps", "-q", "--format", "{{.ID}}"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go index f0689f152..de68e885a 100644 --- a/test/e2e/pod_rm_test.go +++ b/test/e2e/pod_rm_test.go @@ -135,7 +135,7 @@ var _ = Describe("Podman pod rm", func() { fmt.Printf("Removing all empty pods\n") result := podmanTest.Podman([]string{"pod", "rm", "-a"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) foundExpectedError, _ := result.ErrorGrepString("contains containers and cannot be removed") Expect(foundExpectedError).To(Equal(true)) diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go index 01176f97c..4d573a2c7 100644 --- a/test/e2e/pod_stats_test.go +++ b/test/e2e/pod_stats_test.go @@ -169,7 +169,7 @@ var _ = Describe("Podman pod stats", func() { Expect(session.ExitCode()).To(Equal(0)) stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.ID}} \""}) stats.WaitWithDefaultTimeout() - Expect(stats.ExitCode()).ToNot(Equal(0)) + Expect(stats).To(ExitWithError()) }) }) diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go index 53fc33a01..5bb86d558 100644 --- a/test/e2e/port_test.go +++ b/test/e2e/port_test.go @@ -39,13 +39,13 @@ var _ = Describe("Podman port", func() { It("podman port all and latest", func() { result := podmanTest.Podman([]string{"port", "-a", "-l"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("podman port all and extra", func() { result := podmanTest.Podman([]string{"port", "-a", "foobar"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("podman port -l nginx", func() { diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index 4130f409e..a436d4f09 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -230,11 +230,11 @@ var _ = Describe("Podman ps", func() { It("podman ps mutually exclusive flags", func() { session := podmanTest.Podman([]string{"ps", "-aqs"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"ps", "-a", "--ns", "-s"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman --sort by size", func() { diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index 68fcaf133..537084220 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -40,7 +40,7 @@ var _ = Describe("Podman pull", func() { It("podman pull from docker a not existing image", func() { session := podmanTest.PodmanNoCache([]string{"pull", "ibetthisdoesntexistthere:foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pull from docker with tag", func() { @@ -96,7 +96,7 @@ var _ = Describe("Podman pull", func() { It("podman pull bogus image", func() { session := podmanTest.PodmanNoCache([]string{"pull", "umohnani/get-started"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman pull from docker-archive", func() { diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go index 4360eeece..50f0ca6d9 100644 --- a/test/e2e/push_test.go +++ b/test/e2e/push_test.go @@ -140,7 +140,7 @@ var _ = Describe("Podman push", func() { push := podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:test", ALPINE, "localhost:5000/tlstest"}) push.WaitWithDefaultTimeout() - Expect(push.ExitCode()).To(Not(Equal(0))) + Expect(push).To(ExitWithError()) push = podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:test", "--tls-verify=false", ALPINE, "localhost:5000/tlstest"}) push.WaitWithDefaultTimeout() @@ -151,11 +151,11 @@ var _ = Describe("Podman push", func() { push = podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:wrongpasswd", ALPINE, "localhost:5000/credstest"}) push.WaitWithDefaultTimeout() - Expect(push.ExitCode()).To(Not(Equal(0))) + Expect(push).To(ExitWithError()) push = podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:test", "--cert-dir=fakedir", ALPINE, "localhost:5000/certdirtest"}) push.WaitWithDefaultTimeout() - Expect(push.ExitCode()).To(Not(Equal(0))) + Expect(push).To(ExitWithError()) push = podmanTest.PodmanNoCache([]string{"push", "--creds=podmantest:test", ALPINE, "localhost:5000/defaultflags"}) push.WaitWithDefaultTimeout() diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index 506adee7e..80e877de1 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -102,7 +102,7 @@ var _ = Describe("Podman rmi", func() { // Trying without --force should fail result := podmanTest.PodmanNoCache([]string{"rmi", alpineId}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) // With --force it should work resultForce := podmanTest.PodmanNoCache([]string{"rmi", "-f", alpineId}) diff --git a/test/e2e/run_cpu_test.go b/test/e2e/run_cpu_test.go index 42f17985c..29ceb4e67 100644 --- a/test/e2e/run_cpu_test.go +++ b/test/e2e/run_cpu_test.go @@ -164,12 +164,12 @@ var _ = Describe("Podman run cpu", func() { It("podman run cpus and cpu-period", func() { result := podmanTest.Podman([]string{"run", "--rm", "--cpu-period=5000", "--cpus=0.5", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) }) It("podman run cpus and cpu-quota", func() { result := podmanTest.Podman([]string{"run", "--rm", "--cpu-quota=5000", "--cpus=0.5", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).To(Not(Equal(0))) + Expect(result).To(ExitWithError()) }) }) diff --git a/test/e2e/run_device_test.go b/test/e2e/run_device_test.go index d3b4b0e32..eae3f574c 100644 --- a/test/e2e/run_device_test.go +++ b/test/e2e/run_device_test.go @@ -37,7 +37,7 @@ var _ = Describe("Podman run device", func() { It("podman run bad device test", func() { session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/baddevice", ALPINE, "true"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run device test", func() { @@ -70,7 +70,7 @@ var _ = Describe("Podman run device", func() { It("podman run device rename and bad permission test", func() { session := podmanTest.Podman([]string{"run", "-q", "--security-opt", "label=disable", "--device", "/dev/kmsg:/dev/kmsg1:rd", ALPINE, "ls", "--color=never", "/dev/kmsg1"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run device host device and container device parameter are directories", func() { diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go index dc0f4a8fb..02b9ff8d1 100644 --- a/test/e2e/run_dns_test.go +++ b/test/e2e/run_dns_test.go @@ -51,7 +51,7 @@ var _ = Describe("Podman run dns", func() { It("podman run add bad dns server", func() { session := podmanTest.Podman([]string{"run", "--dns=foobar", ALPINE, "ls"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run add dns server", func() { @@ -71,7 +71,7 @@ var _ = Describe("Podman run dns", func() { It("podman run add bad host", func() { session := podmanTest.Podman([]string{"run", "--add-host=foo:1.2", ALPINE, "ls"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run add host", func() { @@ -105,15 +105,15 @@ var _ = Describe("Podman run dns", func() { It("podman run mutually excludes --dns* and --network", func() { session := podmanTest.Podman([]string{"run", "--dns=1.2.3.4", "--network", "container:ALPINE", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"run", "--dns-opt=1.2.3.4", "--network", "container:ALPINE", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"run", "--dns-search=foobar.com", "--network", "none", ALPINE}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"run", "--dns=1.2.3.4", "--network", "host", ALPINE}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 31291d373..ec12f709a 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -74,7 +74,7 @@ var _ = Describe("Podman run networking", func() { Expect(results.OutputToString()).To(ContainSubstring("8000")) ncBusy := SystemExec("nc", []string{"-l", "-p", "80"}) - Expect(ncBusy.ExitCode()).ToNot(Equal(0)) + Expect(ncBusy).To(ExitWithError()) }) It("podman run network expose ports in image metadata", func() { @@ -229,7 +229,7 @@ var _ = Describe("Podman run networking", func() { It("podman run network in bogus user created network namespace", func() { session := podmanTest.Podman([]string{"run", "-dt", "--net", "ns:/run/netns/xxy", ALPINE, "wget", "www.podman.io"}) session.Wait(90) - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("stat /run/netns/xxy: no such file or directory")) }) }) diff --git a/test/e2e/run_ns_test.go b/test/e2e/run_ns_test.go index e3e86fc66..c8ba68efc 100644 --- a/test/e2e/run_ns_test.go +++ b/test/e2e/run_ns_test.go @@ -48,7 +48,7 @@ var _ = Describe("Podman run ns", func() { session = podmanTest.Podman([]string{"run", "--pid=badpid", fedoraMinimal, "bash", "-c", "echo $$"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run --cgroup private test", func() { @@ -102,6 +102,6 @@ var _ = Describe("Podman run ns", func() { It("podman run bad ipc pid test", func() { session := podmanTest.Podman([]string{"run", "--ipc=badpid", fedoraMinimal, "bash", "-c", "echo $$"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) }) }) diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go index 7a877ebdc..5b4842fea 100644 --- a/test/e2e/run_staticip_test.go +++ b/test/e2e/run_staticip_test.go @@ -40,19 +40,19 @@ var _ = Describe("Podman run with --ip flag", func() { It("Podman run --ip with garbage address", func() { result := podmanTest.Podman([]string{"run", "-ti", "--ip", "114232346", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman run --ip with v6 address", func() { result := podmanTest.Podman([]string{"run", "-ti", "--ip", "2001:db8:bad:beef::1", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman run --ip with non-allocatable IP", func() { result := podmanTest.Podman([]string{"run", "-ti", "--ip", "203.0.113.124", ALPINE, "ls"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("Podman run with specified static IP has correct IP", func() { @@ -70,6 +70,6 @@ var _ = Describe("Podman run with --ip flag", func() { Expect(result.ExitCode()).To(Equal(0)) result = podmanTest.Podman([]string{"run", "-ti", "--ip", ip, ALPINE, "ip", "addr"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 1e6f1d97d..874aa498e 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -170,7 +170,7 @@ var _ = Describe("Podman run", func() { session := podmanTest.Podman([]string{"run", "-it", "--security-opt", strings.Join([]string{"seccomp=", jsonFile}, ""), ALPINE, "pwd"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) match, _ := session.GrepString("Operation not permitted") Expect(match).Should(BeTrue()) }) @@ -730,11 +730,11 @@ USER mail` session := podmanTest.Podman([]string{"run", "--volume", ":/myvol1:z", ALPINE, "touch", "/myvol2/foo.txt"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("directory cannot be empty")) session = podmanTest.Podman([]string{"run", "--volume", vol1 + ":", ALPINE, "touch", "/myvol2/foo.txt"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("directory cannot be empty")) }) @@ -815,7 +815,7 @@ USER mail` It("podman run --rm failed container should delete itself", func() { session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) numContainers := podmanTest.NumberOfContainers() Expect(numContainers).To(Equal(0)) @@ -824,7 +824,7 @@ USER mail` It("podman run failed container should NOT delete itself", func() { session := podmanTest.Podman([]string{"run", ALPINE, "foo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) numContainers := podmanTest.NumberOfContainers() Expect(numContainers).To(Equal(1)) @@ -840,28 +840,28 @@ USER mail` It("podman run with bad healthcheck retries", func() { session := podmanTest.Podman([]string{"run", "-dt", "--health-cmd", "[\"foo\"]", "--health-retries", "0", ALPINE, "top"}) session.Wait() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("healthcheck-retries must be greater than 0")) }) It("podman run with bad healthcheck timeout", func() { session := podmanTest.Podman([]string{"run", "-dt", "--health-cmd", "[\"foo\"]", "--health-timeout", "0s", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("healthcheck-timeout must be at least 1 second")) }) It("podman run with bad healthcheck start-period", func() { session := podmanTest.Podman([]string{"run", "-dt", "--health-cmd", "[\"foo\"]", "--health-start-period", "-1s", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring("healthcheck-start-period must be 0 seconds or greater")) }) It("podman run with --add-host and --no-hosts fails", func() { session := podmanTest.Podman([]string{"run", "-dt", "--add-host", "test1:127.0.0.1", "--no-hosts", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session).To(ExitWithError()) }) It("podman run --http-proxy test", func() { @@ -990,6 +990,6 @@ USER mail` It("podman run with cgroups=garbage errors", func() { session := podmanTest.Podman([]string{"run", "-d", "--cgroups=garbage", ALPINE, "top"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) }) diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index bc3a14b66..94bfebab7 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -155,7 +155,7 @@ var _ = Describe("Podman run with volumes", func() { session = podmanTest.Podman([]string{"run", "--rm", "--mount", fmt.Sprintf("type=bind,src=%s,target=/run/test,ro=true,rw=false", mountPath), ALPINE, "grep", "/run/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run with volume flag and multiple named volumes", func() { @@ -191,7 +191,7 @@ var _ = Describe("Podman run with volumes", func() { It("podman run with noexec can't exec", func() { session := podmanTest.Podman([]string{"run", "--rm", "-v", "/bin:/hostbin:noexec", ALPINE, "/hostbin/ls", "/"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) It("podman run with tmpfs named volume mounts and unmounts", func() { diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go index 4e2cb501e..52a011efb 100644 --- a/test/e2e/runlabel_test.go +++ b/test/e2e/runlabel_test.go @@ -75,12 +75,12 @@ var _ = Describe("podman container runlabel", func() { It("podman container runlabel bogus label should result in non-zero exit code", func() { result := podmanTest.Podman([]string{"container", "runlabel", "RUN", ALPINE}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) It("podman container runlabel bogus label in remote image should result in non-zero exit", func() { result := podmanTest.Podman([]string{"container", "runlabel", "RUN", "docker.io/library/ubuntu:latest"}) result.WaitWithDefaultTimeout() - Expect(result.ExitCode()).ToNot(Equal(0)) + Expect(result).To(ExitWithError()) }) diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index be1ede962..52dab923b 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -72,7 +72,7 @@ var _ = Describe("Podman save", func() { save := podmanTest.PodmanNoCache([]string{"save", "-o", outfile, "FOOBAR"}) save.WaitWithDefaultTimeout() - Expect(save.ExitCode()).To(Not(Equal(0))) + Expect(save).To(ExitWithError()) }) It("podman save to directory with oci format", func() { @@ -113,7 +113,7 @@ var _ = Describe("Podman save", func() { save := podmanTest.PodmanNoCache([]string{"save", "--compress", "--format", "docker-dir", "-o", outdir, ALPINE}) save.WaitWithDefaultTimeout() - Expect(save.ExitCode()).To(Not(Equal(0))) + Expect(save).To(ExitWithError()) }) }) diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 13f14183b..da581f158 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -108,7 +108,7 @@ var _ = Describe("Podman start", func() { start := podmanTest.Podman([]string{"start", "-l"}) start.WaitWithDefaultTimeout() - Expect(start.ExitCode()).Should(BeNumerically(">", 0)) + Expect(start).To(ExitWithError()) Eventually(podmanTest.NumberOfContainers(), defaultWaitTimeout, 3.0).Should(BeZero()) }) @@ -120,7 +120,7 @@ var _ = Describe("Podman start", func() { start := podmanTest.Podman([]string{"start", "-l"}) start.WaitWithDefaultTimeout() - Expect(start.ExitCode()).Should(BeNumerically(">", 0)) + Expect(start).To(ExitWithError()) Eventually(podmanTest.NumberOfContainers(), defaultWaitTimeout, 3.0).Should(Equal(1)) }) diff --git a/test/e2e/volume_create_test.go b/test/e2e/volume_create_test.go index 77e8abbd4..41107b5ba 100644 --- a/test/e2e/volume_create_test.go +++ b/test/e2e/volume_create_test.go @@ -61,6 +61,6 @@ var _ = Describe("Podman volume create", func() { It("podman create volume with bad volume option", func() { session := podmanTest.Podman([]string{"volume", "create", "--opt", "badOpt=bad"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) }) }) diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 61cf9b893..6f2020828 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -56,7 +56,7 @@ var _ = Describe("Podman volume rm", func() { session = podmanTest.Podman([]string{"volume", "rm", "myvol"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) Expect(session.ErrorToString()).To(ContainSubstring(cid)) session = podmanTest.Podman([]string{"volume", "rm", "-f", "myvol"}) @@ -116,7 +116,7 @@ var _ = Describe("Podman volume rm", func() { session = podmanTest.Podman([]string{"volume", "rm", "myv"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session).To(ExitWithError()) session = podmanTest.Podman([]string{"volume", "ls"}) session.WaitWithDefaultTimeout() diff --git a/test/system/005-info.bats b/test/system/005-info.bats index 5df6033fc..f229b0886 100644 --- a/test/system/005-info.bats +++ b/test/system/005-info.bats @@ -11,7 +11,7 @@ load helpers BuildahVersion: *[0-9.]\\\+ Conmon:\\\s\\\+package: Distribution: -OCIRuntime:\\\s\\\+package: +OCIRuntime:\\\s\\\+name: os: rootless: registries: diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats index 11cb98269..472fdd1ab 100644 --- a/test/system/075-exec.bats +++ b/test/system/075-exec.bats @@ -29,4 +29,24 @@ load helpers run_podman rm $cid } +@test "podman exec - leak check" { + skip_if_remote + + # Start a container in the background then run exec command + # three times and make sure no any exec pid hash file leak + run_podman run -td $IMAGE /bin/sh + cid="$output" + + is "$(check_exec_pid)" "" "exec pid hash file indeed doesn't exist" + + for i in {1..3}; do + run_podman exec $cid /bin/true + done + + is "$(check_exec_pid)" "" "there isn't any exec pid hash file leak" + + run_podman stop --time 1 $cid + run_podman rm -f $cid +} + # vim: filetype=sh diff --git a/test/system/helpers.bash b/test/system/helpers.bash index 3d607f4bd..8c061d2c9 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -373,5 +373,19 @@ function random_string() { head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length } + +######################### +# find_exec_pid_files # Returns nothing or exec_pid hash files +######################### +# +# Return exec_pid hash files if exists, otherwise, return nothing +# +function find_exec_pid_files() { + run_podman info --format '{{.store.RunRoot}}' + local storage_path="$output" + if [ -d $storage_path ]; then + find $storage_path -type f -iname 'exec_pid_*' + fi +} # END miscellaneous tools ############################################################################### diff --git a/test/utils/matchers.go b/test/utils/matchers.go new file mode 100644 index 000000000..07c1232e7 --- /dev/null +++ b/test/utils/matchers.go @@ -0,0 +1,61 @@ +package utils + +import ( + "fmt" + + "github.com/onsi/gomega/format" + "github.com/onsi/gomega/gexec" +) + +// ExitWithError matches when assertion is > argument. Default 0 +// Modeled after the gomega Exit() matcher +func ExitWithError(optionalExitCode ...int) *exitMatcher { + exitCode := 0 + if len(optionalExitCode) > 0 { + exitCode = optionalExitCode[0] + } + return &exitMatcher{exitCode: exitCode} +} + +type exitMatcher struct { + exitCode int + actualExitCode int +} + +func (m *exitMatcher) Match(actual interface{}) (success bool, err error) { + exiter, ok := actual.(gexec.Exiter) + if !ok { + return false, fmt.Errorf("ExitWithError must be passed a gexec.Exiter (Missing method ExitCode() int) Got:\n#{format.Object(actual, 1)}") + } + + m.actualExitCode = exiter.ExitCode() + if m.actualExitCode == -1 { + return false, nil + } + return m.actualExitCode > m.exitCode, nil +} + +func (m *exitMatcher) FailureMessage(actual interface{}) (message string) { + if m.actualExitCode == -1 { + return "Expected process to exit. It did not." + } + return format.Message(m.actualExitCode, "to be greater than exit code:", m.exitCode) +} + +func (m *exitMatcher) NegatedFailureMessage(actual interface{}) (message string) { + if m.actualExitCode == -1 { + return "you really shouldn't be able to see this!" + } else { + if m.exitCode == -1 { + return "Expected process not to exit. It did." + } + return format.Message(m.actualExitCode, "is less than or equal to exit code:", m.exitCode) + } +} +func (m *exitMatcher) MatchMayChangeInTheFuture(actual interface{}) bool { + session, ok := actual.(*gexec.Session) + if ok { + return session.ExitCode() == -1 + } + return true +} diff --git a/test/utils/utils.go b/test/utils/utils.go index 7d373bd56..ad78d9792 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -268,7 +268,7 @@ func (s *PodmanSession) ErrorGrepString(term string) (bool, []string) { return matches, greps } -//LineInOutputStartsWith returns true if a line in a +// LineInOutputStartsWith returns true if a line in a // session output starts with the supplied string func (s *PodmanSession) LineInOuputStartsWith(term string) bool { for _, i := range s.OutputToStringArray() { @@ -279,7 +279,7 @@ func (s *PodmanSession) LineInOuputStartsWith(term string) bool { return false } -//LineInOutputContains returns true if a line in a +// LineInOutputContains returns true if a line in a // session output contains the supplied string func (s *PodmanSession) LineInOutputContains(term string) bool { for _, i := range s.OutputToStringArray() { @@ -290,7 +290,7 @@ func (s *PodmanSession) LineInOutputContains(term string) bool { return false } -//LineInOutputContainsTag returns true if a line in the +// LineInOutputContainsTag returns true if a line in the // session's output contains the repo-tag pair as returned // by podman-images(1). func (s *PodmanSession) LineInOutputContainsTag(repo, tag string) bool { @@ -348,7 +348,7 @@ func StringInSlice(s string, sl []string) bool { return false } -//tagOutPutToMap parses each string in imagesOutput and returns +// tagOutPutToMap parses each string in imagesOutput and returns // a map of repo:tag pairs. Notice, the first array item will // be skipped as it's considered to be the header. func tagOutputToMap(imagesOutput []string) map[string]string { @@ -371,7 +371,7 @@ func tagOutputToMap(imagesOutput []string) map[string]string { return m } -//GetHostDistributionInfo returns a struct with its distribution name and version +// GetHostDistributionInfo returns a struct with its distribution name and version func GetHostDistributionInfo() HostOS { f, err := os.Open(OSReleasePath) defer f.Close() @@ -415,7 +415,7 @@ func IsKernelNewerThan(version string) (bool, error) { } -//IsCommandAvaible check if command exist +// IsCommandAvaible check if command exist func IsCommandAvailable(command string) bool { check := exec.Command("bash", "-c", strings.Join([]string{"command -v", command}, " ")) err := check.Run() diff --git a/troubleshooting.md b/troubleshooting.md index 6fed719f7..c4e577645 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -410,3 +410,22 @@ You'll need to either: * configure the host to use cgroups v1 * update the image to use an updated version of systemd. + +### 17) rootless containers exit once the user session exits + + +You need to set lingering mode through loginctl to prevent user processes to be killed once +the user session completed. + +#### Symptom + +Once the user logs out all the containers exit. + +#### Solution +You'll need to either: + +* loginctl enable-linger $UID + +or as root if your user has not enough privileges. + +* sudo loginctl enable-linger $UID diff --git a/vendor/modules.txt b/vendor/modules.txt index 712bfb69a..65a99869e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -346,12 +346,12 @@ github.com/onsi/ginkgo/internal/specrunner # github.com/onsi/gomega v1.7.0 github.com/onsi/gomega github.com/onsi/gomega/gexec +github.com/onsi/gomega/format github.com/onsi/gomega/internal/assertion github.com/onsi/gomega/internal/asyncassertion github.com/onsi/gomega/internal/testingtsupport github.com/onsi/gomega/matchers github.com/onsi/gomega/types -github.com/onsi/gomega/format github.com/onsi/gomega/gbytes github.com/onsi/gomega/internal/oraclematcher github.com/onsi/gomega/matchers/support/goraph/bipartitegraph diff --git a/version/version.go b/version/version.go index 2c4d69b78..c0dbeadfe 100644 --- a/version/version.go +++ b/version/version.go @@ -4,7 +4,7 @@ package version // NOTE: remember to bump the version at the top // of the top-level README.md file when this is // bumped. -const Version = "1.6.2-dev" +const Version = "1.6.3-dev" // RemoteAPIVersion is the version for the remote // client API. It is used to determine compatibility |