diff options
-rwxr-xr-x | API.md | 8 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | RELEASE_NOTES.md | 2 | ||||
-rw-r--r-- | changelog.txt | 8 | ||||
-rw-r--r-- | cmd/podman/run.go | 1 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 4 | ||||
-rw-r--r-- | contrib/spec/podman.spec.in | 2 | ||||
-rw-r--r-- | docs/tutorials/rootless_tutorial.md | 4 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 6 | ||||
-rw-r--r-- | libpod/runtime.go | 56 | ||||
-rw-r--r-- | libpod/volume_inspect.go | 3 | ||||
-rw-r--r-- | pkg/adapter/containers.go | 4 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 9 | ||||
-rw-r--r-- | pkg/adapter/runtime_remote.go | 37 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.go | 89 | ||||
-rw-r--r-- | pkg/rootless/rootless_unsupported.go | 6 | ||||
-rw-r--r-- | pkg/util/utils.go | 2 | ||||
-rw-r--r-- | pkg/varlinkapi/attach.go | 1 | ||||
-rw-r--r-- | pkg/varlinkapi/virtwriter/virtwriter.go | 24 | ||||
-rw-r--r-- | pkg/varlinkapi/volumes.go | 19 | ||||
-rw-r--r-- | test/e2e/play_kube_test.go | 209 | ||||
-rw-r--r-- | test/e2e/volume_inspect_test.go | 13 | ||||
-rw-r--r-- | test/system/075-exec.bats | 20 | ||||
-rw-r--r-- | test/system/helpers.bash | 14 | ||||
-rw-r--r-- | troubleshooting.md | 19 | ||||
-rw-r--r-- | version/version.go | 2 |
27 files changed, 334 insertions, 232 deletions
@@ -107,6 +107,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func InspectPod(name: string) string](#InspectPod) +[func InspectVolume(name: string) string](#InspectVolume) + [func KillContainer(name: string, signal: int) string](#KillContainer) [func KillPod(name: string, signal: int) string](#KillPod) @@ -804,6 +806,12 @@ method InspectPod(name: [string](https://godoc.org/builtin#string)) [string](htt InspectPod takes the name or ID of an image and returns a string representation of data associated with the pod. You must serialize the string into JSON to use it further. A [PodNotFound](#PodNotFound) error will be returned if the pod cannot be found. +### <a name="InspectVolume"></a>func InspectVolume +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method InspectVolume(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +InspectVolume inspects a single volume. Returns inspect JSON in the form of a +string. ### <a name="KillContainer"></a>func KillContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -3,7 +3,7 @@ export GOPROXY=https://proxy.golang.org GO ?= go DESTDIR ?= -EPOCH_TEST_COMMIT ?= dc1f8b62b168e0815ed5e7eb7c61a26ec3a0c88c +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 0f2e748fa..235871273 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -22,9 +22,11 @@ - 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 diff --git a/changelog.txt b/changelog.txt index dd3fcec82..615e2a135 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,11 @@ +- 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 diff --git a/cmd/podman/run.go b/cmd/podman/run.go index 4836c99dc..7aa4cb3c4 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -45,7 +45,6 @@ func runCmd(c *cliconfig.RunValues) error { span, _ := opentracing.StartSpanFromContext(Ctx, "runCmd") defer span.Finish() } - if err := createInit(&c.PodmanCommand); err != nil { return err } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 13e8394fb..dca366bc5 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -1268,6 +1268,10 @@ method VolumeRemove(options: VolumeRemoveOpts) -> (successes: []string, failures # GetVolumes gets slice of the volumes on a remote host method GetVolumes(args: []string, all: bool) -> (volumes: []Volume) +# InspectVolume inspects a single volume. Returns inspect JSON in the form of a +# string. +method InspectVolume(name: string) -> (volume: string) + # VolumesPrune removes unused volumes on the host method VolumesPrune() -> (prunedNames: []string, prunedErrors: []string) 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/tutorials/rootless_tutorial.md b/docs/tutorials/rootless_tutorial.md index ed700485a..9a31826bd 100644 --- a/docs/tutorials/rootless_tutorial.md +++ b/docs/tutorials/rootless_tutorial.md @@ -13,7 +13,7 @@ The alternative OCI runtime support for cgroup V2 can be turned on at the comma ``` sudo podman --runtime /usr/bin/crun ``` -or by changing the value for the "Default OCI runtime" in the libpod.conf file either at the system level or at the [#user-configuration-files](user level) from `runtime = "runc"` to `runtime = "crun"`. +or by changing the value for the "Default OCI runtime" in the libpod.conf file either at the system level or at the [user level](#user-configuration-files) from `runtime = "runc"` to `runtime = "crun"`. ## Administrator Actions @@ -59,7 +59,7 @@ The format of this file is USERNAME:UID:RANGE This means the user johndoe is allocated UIDS 100000-165535 as well as their standard UID in the /etc/passwd file. NOTE: this is not currently supported with network installs. These files must be available locally to the host machine. It is not possible to configure this with LDAP or Active Directory. -If you update either the /etc/subuid or the /etc/subgid file, you need to stop all the running containers owned by the user and kill the pause process that is running on the system for that user. This can be done automatically by using the `[podman system migrate](https://github.com/containers/libpod/blob/master/docs/podman-system-migrate.1.md)` command which will stop all the containers for the user and will kill the pause process. +If you update either the /etc/subuid or the /etc/subgid file, you need to stop all the running containers owned by the user and kill the pause process that is running on the system for that user. This can be done automatically by using the [`podman system migrate`](https://github.com/containers/libpod/blob/master/docs/podman-system-migrate.1.md) command which will stop all the containers for the user and will kill the pause process. Rather than updating the files directly, the usermod program can be used to assign UIDs and GIDs to a user. diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 658a2fe4e..448e05bdf 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -602,7 +602,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options if err != nil { return -1, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) } - if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe, sessionID); err != nil { + if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { return -1, nil, err } @@ -986,7 +986,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co if err != nil { return err } - if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe, ctr.ID()); err != nil { + if err := r.moveConmonToCgroupAndSignal(ctr, cmd, parentStartPipe); err != nil { return err } /* Wait for initial setup and fork, and reap child */ @@ -1213,7 +1213,7 @@ func startCommandGivenSelinux(cmd *exec.Cmd) error { // moveConmonToCgroupAndSignal gets a container's cgroupParent and moves the conmon process to that cgroup // it then signals for conmon to start by sending nonse data down the start fd -func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File, uuid string) error { +func (r *ConmonOCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File) error { mustCreateCgroup := true // If cgroup creation is disabled - just signal. if ctr.config.NoCgroups { diff --git a/libpod/runtime.go b/libpod/runtime.go index a0cf0ad7c..8f145a809 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") } } @@ -528,6 +509,17 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. return nil, err } + // storage.conf + storageConfFile, err := storage.DefaultConfigFile(rootless.IsRootless()) + if err != nil { + return nil, err + } + + createStorageConfFile := false + if _, err := os.Stat(storageConfFile); os.IsNotExist(err) { + createStorageConfFile = true + } + defRunConf, err := defaultRuntimeConfig() if err != nil { return nil, err @@ -702,27 +694,21 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. } if rootless.IsRootless() && configPath == "" { - configPath, err := getRootlessConfigPath() - if err != nil { - return nil, err - } - - // storage.conf - storageConfFile, err := storage.DefaultConfigFile(rootless.IsRootless()) - if err != nil { - return nil, err - } - if _, err := os.Stat(storageConfFile); os.IsNotExist(err) { + if createStorageConfFile { if err := util.WriteStorageConfigFile(&runtime.config.StorageConfig, storageConfFile); err != nil { return nil, errors.Wrapf(err, "cannot write config file %s", storageConfFile) } } + configPath, err := getRootlessConfigPath() + if err != nil { + return nil, err + } if configPath != "" { - if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil { + if err := os.MkdirAll(filepath.Dir(configPath), 0711); err != nil { return nil, err } - file, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + file, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if err != nil && !os.IsExist(err) { return nil, errors.Wrapf(err, "cannot open file %s", configPath) } diff --git a/libpod/volume_inspect.go b/libpod/volume_inspect.go index 87ed9d340..c333b8961 100644 --- a/libpod/volume_inspect.go +++ b/libpod/volume_inspect.go @@ -62,6 +62,9 @@ func (v *Volume) Inspect() (*InspectVolumeData, error) { } data.Scope = v.Scope() data.Options = make(map[string]string) + for k, v := range v.config.Options { + data.Options[k] = v + } data.UID = v.config.UID data.GID = v.config.GID data.ContainerSpecific = v.config.IsCtrSpecific diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index a39a345f1..bff93cc9e 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 diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index f7cb28b0c..f4e83a975 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -1092,6 +1092,7 @@ func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, std // These are the special writers that encode input from the client. varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin) varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize) + varlinkHangupWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.HangUpFromClient) go func() { // Read from the wire and direct to stdout or stderr @@ -1117,7 +1118,6 @@ func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, std } } }() - if stdin != nil { // Takes stdinput and sends it over the wire after being encoded go func() { @@ -1126,7 +1126,12 @@ func configureVarlinkAttachStdio(reader *bufio.Reader, writer *bufio.Writer, std sendGenericError(ecChan) errChan <- err } - + _, err := varlinkHangupWriter.Write([]byte("EOF")) + if err != nil { + logrus.Errorf("unable to notify server to hangup: %q", err) + } + err = varlinkStdinWriter.Close() + errChan <- err }() } return errChan diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 3b808a2ee..870e86896 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -659,12 +659,39 @@ func (r *LocalRuntime) Push(ctx context.Context, srcName, destination, manifestM } // InspectVolumes returns a slice of volumes based on an arg list or --all -func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*Volume, error) { - reply, err := iopodman.GetVolumes().Call(r.Conn, c.InputArgs, c.All) - if err != nil { - return nil, err +func (r *LocalRuntime) InspectVolumes(ctx context.Context, c *cliconfig.VolumeInspectValues) ([]*libpod.InspectVolumeData, error) { + var ( + inspectData []*libpod.InspectVolumeData + volumes []string + ) + + if c.All { + allVolumes, err := r.Volumes(ctx) + if err != nil { + return nil, err + } + for _, vol := range allVolumes { + volumes = append(volumes, vol.Name()) + } + } else { + for _, arg := range c.InputArgs { + volumes = append(volumes, arg) + } } - return varlinkVolumeToVolume(r, reply), nil + + for _, vol := range volumes { + jsonString, err := iopodman.InspectVolume().Call(r.Conn, vol) + if err != nil { + return nil, err + } + inspectJSON := new(libpod.InspectVolumeData) + if err := json.Unmarshal([]byte(jsonString), inspectJSON); err != nil { + return nil, errors.Wrapf(err, "error unmarshalling inspect JSON for volume %s", vol) + } + inspectData = append(inspectData, inspectJSON) + } + + return inspectData, nil } // Volumes returns a slice of adapter.volumes based on information about libpod diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 59f2880c3..94c42f7d0 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -11,16 +11,13 @@ import ( "os/exec" gosignal "os/signal" "os/user" - "path/filepath" "runtime" "strconv" - "strings" "sync" "unsafe" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/storage/pkg/idtools" - "github.com/godbus/dbus" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/unix" @@ -212,92 +209,6 @@ func getUserNSFirstChild(fd uintptr) (*os.File, error) { } } -// 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. 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/pkg/util/utils.go b/pkg/util/utils.go index 0190b106d..d9a84e4e5 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -318,7 +318,7 @@ func WriteStorageConfigFile(storageOpts *storage.StoreOptions, storageConf strin if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil { return err } - storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + storageFile, err := os.OpenFile(storageConf, os.O_RDWR|os.O_TRUNC, 0600) if err != nil { return errors.Wrapf(err, "cannot open %s", storageConf) } diff --git a/pkg/varlinkapi/attach.go b/pkg/varlinkapi/attach.go index f8557ae0c..37adbbf55 100644 --- a/pkg/varlinkapi/attach.go +++ b/pkg/varlinkapi/attach.go @@ -70,7 +70,6 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st } reader, writer, _, pw, streams := setupStreams(call) - go func() { if err := virtwriter.Reader(reader, nil, nil, pw, resize, nil); err != nil { errChan <- err diff --git a/pkg/varlinkapi/virtwriter/virtwriter.go b/pkg/varlinkapi/virtwriter/virtwriter.go index 27ecd1f52..dd171943f 100644 --- a/pkg/varlinkapi/virtwriter/virtwriter.go +++ b/pkg/varlinkapi/virtwriter/virtwriter.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "encoding/json" "io" + "time" "github.com/pkg/errors" "k8s.io/client-go/tools/remotecommand" @@ -26,8 +27,14 @@ const ( TerminalResize SocketDest = iota // Quit and detach Quit SocketDest = iota + // Quit from the client + HangUpFromClient SocketDest = iota ) +// ClientHangup signifies that the client wants to drop its +// connection from the server +var ClientHangup = errors.New("client hangup") + // IntToSocketDest returns a socketdest based on integer input func IntToSocketDest(i int) SocketDest { switch i { @@ -41,6 +48,8 @@ func IntToSocketDest(i int) SocketDest { return TerminalResize case Quit.Int(): return Quit + case HangUpFromClient.Int(): + return HangUpFromClient default: return ToStderr } @@ -65,7 +74,7 @@ func NewVirtWriteCloser(w *bufio.Writer, dest SocketDest) VirtWriteCloser { // Close is a required method for a writecloser func (v VirtWriteCloser) Close() error { - return nil + return v.writer.Flush() } // Write prepends a header to the input message. The header is @@ -96,7 +105,6 @@ func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remote if r == nil { return errors.Errorf("Reader must not be nil") } - for { n, err := io.ReadFull(r, headerBytes) if err != nil { @@ -107,7 +115,6 @@ func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remote } messageSize = int64(binary.BigEndian.Uint32(headerBytes[4:8])) - switch IntToSocketDest(int(headerBytes[0])) { case ToStdout: if output != nil { @@ -161,7 +168,16 @@ func Reader(r *bufio.Reader, output, errput, input io.Writer, resize chan remote execEcChan <- int(ecInt) } return nil - + case HangUpFromClient: + // This sleep allows the pipes to flush themselves before tearing everything down. + // It makes me sick to do it but after a full day I cannot put my finger on the race + // that occurs when closing things up. It would require a significant rewrite of code + // to make the pipes close down properly. Given that we are currently discussing a + // rewrite of all things remote, this hardly seems worth resolving. + // + // reproducer: echo hello | (podman-remote run -i alpine cat) + time.Sleep(1 * time.Second) + return ClientHangup default: // Something really went wrong return errors.New("unknown multiplex destination") diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index b41eb5086..0ba76902e 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -3,6 +3,8 @@ package varlinkapi import ( + "encoding/json" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" @@ -80,6 +82,23 @@ func (i *LibpodAPI) GetVolumes(call iopodman.VarlinkCall, args []string, all boo return call.ReplyGetVolumes(volumes) } +// InspectVolume inspects a single volume, returning its JSON as a string. +func (i *LibpodAPI) InspectVolume(call iopodman.VarlinkCall, name string) error { + vol, err := i.Runtime.LookupVolume(name) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + inspectOut, err := vol.Inspect() + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + inspectJSON, err := json.Marshal(inspectOut) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyInspectVolume(string(inspectJSON)) +} + // VolumesPrune removes unused images via a varlink call func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error { var errs []string 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/volume_inspect_test.go b/test/e2e/volume_inspect_test.go index 0683c6bbf..5015e0535 100644 --- a/test/e2e/volume_inspect_test.go +++ b/test/e2e/volume_inspect_test.go @@ -2,6 +2,7 @@ package integration import ( "os" + "strings" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -74,4 +75,16 @@ var _ = Describe("Podman volume inspect", func() { Expect(session.OutputToStringArray()[0]).To(Equal(volName1)) Expect(session.OutputToStringArray()[1]).To(Equal(volName2)) }) + + It("inspect volume finds options", func() { + volName := "testvol" + session := podmanTest.Podman([]string{"volume", "create", "--opt", "type=tmpfs", volName}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + inspect := podmanTest.Podman([]string{"volume", "inspect", volName}) + inspect.WaitWithDefaultTimeout() + Expect(inspect.ExitCode()).To(Equal(0)) + Expect(strings.Contains(inspect.OutputToString(), "tmpfs")).To(BeTrue()) + }) }) 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/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/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 |