diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | cmd/podman/run.go | 1 | ||||
-rw-r--r-- | libpod/runtime.go | 25 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 9 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.go | 89 | ||||
-rw-r--r-- | pkg/rootless/rootless_unsupported.go | 6 | ||||
-rw-r--r-- | pkg/varlinkapi/attach.go | 1 | ||||
-rw-r--r-- | pkg/varlinkapi/virtwriter/virtwriter.go | 24 | ||||
-rw-r--r-- | test/e2e/play_kube_test.go | 209 | ||||
-rw-r--r-- | test/system/075-exec.bats | 20 | ||||
-rw-r--r-- | test/system/helpers.bash | 14 | ||||
-rw-r--r-- | troubleshooting.md | 19 |
12 files changed, 215 insertions, 204 deletions
@@ -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/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/libpod/runtime.go b/libpod/runtime.go index e8c80c67e..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") } } 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/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/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/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/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 |