diff options
-rw-r--r-- | cmd/podman/remoteclientconfig/config_darwin.go | 2 | ||||
-rw-r--r-- | cmd/podman/remoteclientconfig/config_linux.go | 2 | ||||
-rw-r--r-- | cmd/podman/remoteclientconfig/config_windows.go | 2 | ||||
-rw-r--r-- | docs/source/markdown/podman-generate-systemd.1.md | 12 | ||||
-rw-r--r-- | docs/tutorials/rootless_tutorial.md | 28 | ||||
-rw-r--r-- | libpod/container_api.go | 35 | ||||
-rw-r--r-- | libpod/container_internal.go | 22 | ||||
-rw-r--r-- | libpod/define/exec_codes.go | 6 | ||||
-rw-r--r-- | libpod/oci.go | 9 | ||||
-rw-r--r-- | libpod/oci_attach_linux.go | 4 | ||||
-rw-r--r-- | libpod/oci_conmon_linux.go | 84 | ||||
-rw-r--r-- | libpod/oci_missing.go | 4 | ||||
-rw-r--r-- | pkg/resolvconf/resolvconf.go | 2 | ||||
-rw-r--r-- | pkg/systemd/generate/systemdgen.go | 2 | ||||
-rw-r--r-- | pkg/systemd/generate/systemdgen_test.go | 10 |
15 files changed, 81 insertions, 143 deletions
diff --git a/cmd/podman/remoteclientconfig/config_darwin.go b/cmd/podman/remoteclientconfig/config_darwin.go index b94941381..dddb217ac 100644 --- a/cmd/podman/remoteclientconfig/config_darwin.go +++ b/cmd/podman/remoteclientconfig/config_darwin.go @@ -3,7 +3,7 @@ package remoteclientconfig import ( "path/filepath" - "github.com/docker/docker/pkg/homedir" + "github.com/containers/storage/pkg/homedir" ) func getConfigFilePath() string { diff --git a/cmd/podman/remoteclientconfig/config_linux.go b/cmd/podman/remoteclientconfig/config_linux.go index 5d27f19f2..afcf73e6d 100644 --- a/cmd/podman/remoteclientconfig/config_linux.go +++ b/cmd/podman/remoteclientconfig/config_linux.go @@ -4,7 +4,7 @@ import ( "os" "path/filepath" - "github.com/docker/docker/pkg/homedir" + "github.com/containers/storage/pkg/homedir" ) func getConfigFilePath() string { diff --git a/cmd/podman/remoteclientconfig/config_windows.go b/cmd/podman/remoteclientconfig/config_windows.go index fa6ffca63..3a8f3bc7a 100644 --- a/cmd/podman/remoteclientconfig/config_windows.go +++ b/cmd/podman/remoteclientconfig/config_windows.go @@ -3,7 +3,7 @@ package remoteclientconfig import ( "path/filepath" - "github.com/docker/docker/pkg/homedir" + "github.com/containers/storage/pkg/homedir" ) func getConfigFilePath() string { diff --git a/docs/source/markdown/podman-generate-systemd.1.md b/docs/source/markdown/podman-generate-systemd.1.md index 4d3f9ba48..2bcfdb954 100644 --- a/docs/source/markdown/podman-generate-systemd.1.md +++ b/docs/source/markdown/podman-generate-systemd.1.md @@ -42,8 +42,8 @@ Create and print a systemd unit file for a container running nginx with an *alwa $ podman create --name nginx nginx:latest $ podman generate systemd --restart-policy=always -t 1 nginx # container-de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6.service -# autogenerated by Podman 1.5.2 -# Wed Aug 21 09:46:45 CEST 2019 +# autogenerated by Podman 1.8.0 +# Wed Mar 09 09:46:45 CEST 2020 [Unit] Description=Podman container-de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6.service @@ -58,7 +58,7 @@ Type=forking PIDFile=/run/user/1000/overlay-containers/de1e3223b1b888bc02d0962dd6cb5855eb00734061013ffdd3479d225abacdc6/userdata/conmon.pid [Install] -WantedBy=multi-user.target +WantedBy=multi-user.target default.target ``` Create systemd unit files for a pod with two simple alpine containers. Note that these container services cannot be started or stopped individually via `systemctl`; they are managed by the pod service. You can still use `systemctl status` or journalctl to examine them. @@ -72,8 +72,8 @@ $ podman generate systemd --files --name systemd-pod /home/user/container-jolly_shtern.service $ cat pod-systemd-pod.service # pod-systemd-pod.service -# autogenerated by Podman 1.5.2 -# Wed Aug 21 09:52:37 CEST 2019 +# autogenerated by Podman 1.8.0 +# Wed Mar 09 09:52:37 CEST 2020 [Unit] Description=Podman pod-systemd-pod.service @@ -90,7 +90,7 @@ Type=forking PIDFile=/run/user/1000/overlay-containers/ccfd5c71a088768774ca7bd05888d55cc287698dde06f475c8b02f696a25adcd/userdata/conmon.pid [Install] -WantedBy=multi-user.target +WantedBy=multi-user.target default.target ``` ## SEE ALSO diff --git a/docs/tutorials/rootless_tutorial.md b/docs/tutorials/rootless_tutorial.md index 5978d1210..8e048c746 100644 --- a/docs/tutorials/rootless_tutorial.md +++ b/docs/tutorials/rootless_tutorial.md @@ -110,34 +110,6 @@ The Podman configuration files for root reside in `/usr/share/containers` with o The default authorization file used by the `podman login` and `podman logout` commands reside in `${XDG_RUNTIME_DIR}/containers/auth.json`. -## Systemd unit for rootless container - -``` -[Unit] -Description=nginx -Requires=user@1001.service -After=user@1001.service -[Service] -Type=simple -KillMode=none -MemoryMax=200M -ExecStartPre=-/usr/bin/podman rm -f nginx -ExecStartPre=/usr/bin/podman pull nginx -ExecStart=/usr/bin/podman run --name=nginx -p 8080:80 -v /home/nginx/html:/usr/share/nginx/html:Z nginx -ExecStop=/usr/bin/podman stop nginx -Restart=always -User=nginx -Group=nginx -[Install] -WantedBy=multi-user.target -``` - -This example unit will launch a nginx container using the existing user nginx with id 1001, serving static content from /home/nginx/html and limited to 200MB of RAM. - -You can use all the usual systemd flags to control the process, including capabilities and cgroup directives to limit memory or CPU. - -See #3866 for more details. - ## More information If you are still experiencing problems running Podman in a rootless environment, please refer to the [Shortcomings of Rootless Podman](https://github.com/containers/libpod/blob/master/rootless.md) page which lists known issues and solutions to known issues in this environment. diff --git a/libpod/container_api.go b/libpod/container_api.go index 5e8fcea47..039619ea6 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -282,24 +282,13 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri opts.Resize = resize opts.DetachKeys = detachKeys - pid := 0 - pipeDataChan, attachChan, err := c.ociRuntime.ExecContainer(c, sessionID, opts) - // if pipeDataChan isn't nil, we should set the err - if pipeDataChan != nil { - pidData := <-pipeDataChan - if pidData.err != nil { - err = pidData.err - } - pid = pidData.data - } + pid, attachChan, err := c.ociRuntime.ExecContainer(c, sessionID, opts) if err != nil { ec := define.ExecErrorCodeGeneric // Conmon will pass a non-zero exit code from the runtime as a pid here. // we differentiate a pid with an exit code by sending it as negative, so reverse // that change and return the exit code the runtime failed with. - // Make sure the value is not ErrorConmonRead, as that is a podman set bogus value - // and not sent by conmon (and thus has no special meaning) - if pid < 0 && pid != define.ErrorConmonRead { + if pid < 0 { ec = -1 * pid } return ec, err @@ -329,24 +318,18 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri lastErr := <-attachChan - exitCodeData := <-pipeDataChan - if exitCodeData.err != nil { + exitCode, err := c.readExecExitCode(sessionID) + if err != nil { if lastErr != nil { logrus.Errorf(lastErr.Error()) } - lastErr = exitCodeData.err + lastErr = err } - if exitCodeData.data != 0 { + if exitCode != 0 { if lastErr != nil { logrus.Errorf(lastErr.Error()) } - // ErrorConmonRead is a bogus value set by podman to indicate reading a value from - // conmon failed. Since it is specifically not a valid exit code, we should set - // a generic error here - if exitCodeData.data == define.ErrorConmonRead { - exitCodeData.data = define.ExecErrorCodeGeneric - } - lastErr = errors.Wrapf(define.ErrOCIRuntime, "non zero exit code: %d", exitCodeData.data) + lastErr = errors.Wrapf(define.ErrOCIRuntime, "non zero exit code: %d", exitCode) } // Lock again @@ -357,7 +340,7 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri // Sync the container again to pick up changes in state if err := c.syncContainer(); err != nil { logrus.Errorf("error syncing container %s state to remove exec session %s", c.ID(), sessionID) - return exitCodeData.data, lastErr + return exitCode, lastErr } // Remove the exec session from state @@ -365,7 +348,7 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri if err := c.save(); err != nil { logrus.Errorf("Error removing exec session %s from container %s state: %v", sessionID, c.ID(), err) } - return exitCodeData.data, lastErr + return exitCode, lastErr } // AttachStreams contains streams that will be attached to the container diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 60b13f125..a0805c1fa 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -206,6 +206,28 @@ func (c *Container) execOCILog(sessionID string) string { return filepath.Join(c.execBundlePath(sessionID), "oci-log") } +// readExecExitCode reads the exit file for an exec session and returns +// the exit code +func (c *Container) readExecExitCode(sessionID string) (int, error) { + exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID()) + chWait := make(chan error) + defer close(chWait) + + _, err := WaitForFile(exitFile, chWait, time.Second*5) + if err != nil { + return -1, err + } + ec, err := ioutil.ReadFile(exitFile) + if err != nil { + return -1, err + } + ecInt, err := strconv.Atoi(string(ec)) + if err != nil { + return -1, err + } + return ecInt, nil +} + // Wait for the container's exit file to appear. // When it does, update our state based on it. func (c *Container) waitForExitFileAndSync() error { diff --git a/libpod/define/exec_codes.go b/libpod/define/exec_codes.go index c2ec08666..f94616b33 100644 --- a/libpod/define/exec_codes.go +++ b/libpod/define/exec_codes.go @@ -1,7 +1,6 @@ package define import ( - "math" "strings" "github.com/pkg/errors" @@ -18,11 +17,6 @@ const ( ExecErrorCodeCannotInvoke = 126 // ExecErrorCodeNotFound is the error code to return when a command cannot be found ExecErrorCodeNotFound = 127 - // ErrorConmonRead is a bogus value that can neither be a valid PID or exit code. It is - // used because conmon will send a negative value when sending a PID back over a pipe FD - // to signify something went wrong in the runtime. We need to differentiate between that - // value and a failure on the podman side of reading that value. Thus, we use ErrorConmonRead - ErrorConmonRead = math.MinInt32 - 1 ) // TranslateExecErrorToExitCode takes an error and checks whether it diff --git a/libpod/oci.go b/libpod/oci.go index 41d420664..27edebefc 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -70,7 +70,7 @@ type OCIRuntime interface { // ExecContainer executes a command in a running container. // Returns an int (exit code), error channel (errors from attach), and // error (errors that occurred attempting to start the exec session). - ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) + ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error) // ExecStopContainer stops a given exec session in a running container. // SIGTERM with be sent initially, then SIGKILL after the given timeout. // If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will @@ -159,10 +159,3 @@ type HTTPAttachStreams struct { Stdout bool Stderr bool } - -// DataAndErr is a generic structure for passing around an int and an error -// it is especially useful for getting information from conmon -type DataAndErr struct { - data int - err error -} diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index 5a8198d05..46c70e7eb 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -119,8 +119,8 @@ func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-c socketPath := buildSocketPath(sockPath) // 2: read from attachFd that the parent process has set up the console socket - if pipeData := readConmonPipeData(attachFd, ""); pipeData.err != nil { - return pipeData.err + if _, err := readConmonPipeData(attachFd, ""); err != nil { + return err } // 2: then attach diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index f260e3a39..800f89603 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -595,29 +595,31 @@ func (r *ConmonOCIRuntime) AttachResize(ctr *Container, newSize remotecommand.Te // ExecContainer executes a command in a running container // TODO: Split into Create/Start/Attach/Wait -func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) { +func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions) (int, chan error, error) { if options == nil { - return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") } if len(options.Cmd) == 0 { - return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") } if sessionID == "" { - return nil, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") + return -1, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") } // create sync pipe to receive the pid parentSyncPipe, childSyncPipe, err := newPipe() if err != nil { - return nil, nil, errors.Wrapf(err, "error creating socket pair") + return -1, nil, errors.Wrapf(err, "error creating socket pair") } + defer errorhandling.CloseQuiet(parentSyncPipe) + // create start pipe to set the cgroup before running // attachToExec is responsible for closing parentStartPipe childStartPipe, parentStartPipe, err := newPipe() if err != nil { - return nil, nil, errors.Wrapf(err, "error creating socket pair") + return -1, nil, errors.Wrapf(err, "error creating socket pair") } // We want to make sure we close the parent{Start,Attach}Pipes if we fail @@ -636,7 +638,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options // attachToExec is responsible for closing parentAttachPipe parentAttachPipe, childAttachPipe, err := newPipe() if err != nil { - return nil, nil, errors.Wrapf(err, "error creating socket pair") + return -1, nil, errors.Wrapf(err, "error creating socket pair") } defer func() { @@ -656,7 +658,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options runtimeDir, err := util.GetRuntimeDir() if err != nil { - return nil, nil, err + return -1, nil, err } finalEnv := make([]string, 0, len(options.Env)) @@ -666,7 +668,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID) if err != nil { - return nil, nil, err + return -1, nil, err } var ociLog string @@ -715,7 +717,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir) if err != nil { - return nil, nil, err + return -1, nil, err } if options.PreserveFDs > 0 { @@ -746,10 +748,10 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options childrenClosed = true if err != nil { - return nil, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) + return -1, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) } if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { - return nil, nil, err + return -1, nil, err } if options.PreserveFDs > 0 { @@ -772,16 +774,9 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options }() attachToExecCalled = true - dataChan := make(chan DataAndErr) - go func() { - // read the exec pid - dataChan <- readConmonPipeData(parentSyncPipe, ociLog) - // read the exec exit code - dataChan <- readConmonPipeData(parentSyncPipe, ociLog) - errorhandling.CloseQuiet(parentSyncPipe) - }() + pid, err := readConmonPipeData(parentSyncPipe, ociLog) - return dataChan, attachChan, err + return pid, attachChan, err } // ExecStopContainer stops a given exec session in a running container. @@ -1211,14 +1206,14 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co return err } - pipeData := readConmonPipeData(parentSyncPipe, ociLog) - if pipeData.err != nil { + pid, err := readConmonPipeData(parentSyncPipe, ociLog) + if err != nil { if err2 := r.DeleteContainer(ctr); err2 != nil { logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID()) } - return pipeData.err + return err } - ctr.state.PID = pipeData.data + ctr.state.PID = pid conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile) if err != nil { @@ -1530,7 +1525,7 @@ func readConmonPidFile(pidFile string) (int, error) { } // readConmonPipeData attempts to read a syncInfo struct from the pipe -func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr { +func readConmonPipeData(pipe *os.File, ociLog string) (int, error) { // syncInfo is used to return data from monitor process to daemon type syncInfo struct { Data int `json:"data"` @@ -1557,7 +1552,7 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr { ch <- syncStruct{si: si} }() - data := define.ErrorConmonRead + data := -1 select { case ss := <-ch: if ss.err != nil { @@ -1566,17 +1561,11 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr { if err == nil { var ociErr ociError if err := json.Unmarshal(ociLogData, &ociErr); err == nil { - return DataAndErr{ - data: data, - err: getOCIRuntimeError(ociErr.Msg), - } + return -1, getOCIRuntimeError(ociErr.Msg) } } } - return DataAndErr{ - data: data, - err: errors.Wrapf(ss.err, "container create failed (no logs from conmon)"), - } + return -1, errors.Wrapf(ss.err, "container create failed (no logs from conmon)") } logrus.Debugf("Received: %d", ss.si.Data) if ss.si.Data < 0 { @@ -1585,36 +1574,21 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr { if err == nil { var ociErr ociError if err := json.Unmarshal(ociLogData, &ociErr); err == nil { - return DataAndErr{ - data: ss.si.Data, - err: getOCIRuntimeError(ociErr.Msg), - } + return ss.si.Data, getOCIRuntimeError(ociErr.Msg) } } } // If we failed to parse the JSON errors, then print the output as it is if ss.si.Message != "" { - return DataAndErr{ - data: ss.si.Data, - err: getOCIRuntimeError(ss.si.Message), - } - } - return DataAndErr{ - data: ss.si.Data, - err: errors.Wrapf(define.ErrInternal, "container create failed"), + return ss.si.Data, getOCIRuntimeError(ss.si.Message) } + return ss.si.Data, errors.Wrapf(define.ErrInternal, "container create failed") } data = ss.si.Data case <-time.After(define.ContainerCreateTimeout): - return DataAndErr{ - data: data, - err: errors.Wrapf(define.ErrInternal, "container creation timeout"), - } - } - return DataAndErr{ - data: data, - err: nil, + return -1, errors.Wrapf(define.ErrInternal, "container creation timeout") } + return data, nil } // writeConmonPipeData writes nonse data to a pipe diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index 1b7c1979d..ff7eea625 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -121,8 +121,8 @@ func (r *MissingRuntime) AttachResize(ctr *Container, newSize remotecommand.Term } // ExecContainer is not available as the runtime is missing -func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) { - return nil, nil, r.printError() +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. diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go index e85bcb377..20618e2dc 100644 --- a/pkg/resolvconf/resolvconf.go +++ b/pkg/resolvconf/resolvconf.go @@ -10,7 +10,7 @@ import ( "sync" "github.com/containers/libpod/pkg/resolvconf/dns" - "github.com/docker/docker/pkg/ioutils" + "github.com/containers/storage/pkg/ioutils" "github.com/sirupsen/logrus" ) diff --git a/pkg/systemd/generate/systemdgen.go b/pkg/systemd/generate/systemdgen.go index 00ddc63f3..4410e1395 100644 --- a/pkg/systemd/generate/systemdgen.go +++ b/pkg/systemd/generate/systemdgen.go @@ -110,7 +110,7 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target` +WantedBy=multi-user.target default.target` // Options include different options to control the unit file generation. type Options struct { diff --git a/pkg/systemd/generate/systemdgen_test.go b/pkg/systemd/generate/systemdgen_test.go index 145296ea9..3749d89ce 100644 --- a/pkg/systemd/generate/systemdgen_test.go +++ b/pkg/systemd/generate/systemdgen_test.go @@ -52,7 +52,7 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target` +WantedBy=multi-user.target default.target` goodName := `# container-foobar.service # autogenerated by Podman CI @@ -72,7 +72,7 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target` +WantedBy=multi-user.target default.target` goodNameBoundTo := `# container-foobar.service # autogenerated by Podman CI @@ -96,7 +96,7 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target` +WantedBy=multi-user.target default.target` podGoodName := `# pod-123abc.service # autogenerated by Podman CI @@ -118,7 +118,7 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target` +WantedBy=multi-user.target default.target` goodNameNew := `# jadda-jadda.service # autogenerated by Podman CI @@ -140,7 +140,7 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target` +WantedBy=multi-user.target default.target` tests := []struct { name string |