diff --git a/api/client/commands.go b/api/client/commands.go index d3e975739a..f4ab5e2f14 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -653,7 +653,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { if *openStdin || *attach { if tty && cli.isTerminal { - if err := cli.monitorTtySize(cmd.Arg(0)); err != nil { + if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { log.Errorf("Error monitoring TTY size: %s", err) } } @@ -1805,7 +1805,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { ) if tty && cli.isTerminal { - if err := cli.monitorTtySize(cmd.Arg(0)); err != nil { + if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { log.Debugf("Error monitoring TTY size: %s", err) } } @@ -2136,7 +2136,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal { - if err := cli.monitorTtySize(runResult.Get("Id")); err != nil { + if err := cli.monitorTtySize(runResult.Get("Id"), false); err != nil { log.Errorf("Error monitoring TTY size: %s", err) } } @@ -2299,3 +2299,101 @@ func (cli *DockerCli) CmdLoad(args ...string) error { } return nil } + +func (cli *DockerCli) CmdExec(args ...string) error { + cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in an existing container") + + execConfig, err := runconfig.ParseExec(cmd, args) + if err != nil { + return err + } + if execConfig.Container == "" { + cmd.Usage() + return nil + } + + stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false) + if err != nil { + return err + } + + var execResult engine.Env + if err := execResult.Decode(stream); err != nil { + return err + } + + execID := execResult.Get("Id") + + if execID == "" { + fmt.Fprintf(cli.out, "exec ID empty") + return nil + } + + if execConfig.Detach { + if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil { + return err + } + return nil + } + + // Interactive exec requested. + var ( + out, stderr io.Writer + in io.ReadCloser + hijacked = make(chan io.Closer) + errCh chan error + ) + + // Block the return until the chan gets closed + defer func() { + log.Debugf("End of CmdExec(), Waiting for hijack to finish.") + if _, ok := <-hijacked; ok { + log.Errorf("Hijack did not finish (chan still open)") + } + }() + + if execConfig.AttachStdin { + in = cli.in + } + if execConfig.AttachStdout { + out = cli.out + } + if execConfig.AttachStderr { + if execConfig.Tty { + stderr = cli.out + } else { + stderr = cli.err + } + } + errCh = utils.Go(func() error { + return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig) + }) + + // Acknowledge the hijack before starting + select { + case closer := <-hijacked: + // Make sure that hijack gets closed when returning. (result + // in closing hijack chan and freeing server's goroutines. + if closer != nil { + defer closer.Close() + } + case err := <-errCh: + if err != nil { + log.Debugf("Error hijack: %s", err) + return err + } + } + + if execConfig.Tty && cli.isTerminal { + if err := cli.monitorTtySize(execID, true); err != nil { + log.Errorf("Error monitoring TTY size: %s", err) + } + } + + if err := <-errCh; err != nil { + log.Debugf("Error hijack: %s", err) + return err + } + + return nil +} diff --git a/api/client/utils.go b/api/client/utils.go index 7bbcf06cf6..dfff9717d3 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -115,6 +115,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b } return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) } + return resp.Body, resp.StatusCode, nil } @@ -179,7 +180,7 @@ func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in return nil } -func (cli *DockerCli) resizeTty(id string) { +func (cli *DockerCli) resizeTty(id string, isExec bool) { height, width := cli.getTtySize() if height == 0 && width == 0 { return @@ -187,7 +188,15 @@ func (cli *DockerCli) resizeTty(id string) { v := url.Values{} v.Set("h", strconv.Itoa(height)) v.Set("w", strconv.Itoa(width)) - if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil { + + path := "" + if !isExec { + path = "/containers/" + id + "/resize?" + } else { + path = "/exec/" + id + "/resize?" + } + + if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil { log.Debugf("Error resize: %s", err) } } @@ -226,14 +235,14 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { return state.GetBool("Running"), state.GetInt("ExitCode"), nil } -func (cli *DockerCli) monitorTtySize(id string) error { - cli.resizeTty(id) +func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { + cli.resizeTty(id, isExec) sigchan := make(chan os.Signal, 1) gosignal.Notify(sigchan, syscall.SIGWINCH) go func() { for _ = range sigchan { - cli.resizeTty(id) + cli.resizeTty(id, isExec) } }() return nil diff --git a/api/server/server.go b/api/server/server.go index 3df32955f2..2eee20d26b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -663,6 +663,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re } out.Set("Id", engine.Tail(stdoutBuffer, 1)) out.SetList("Warnings", outWarnings) + return writeJSON(w, http.StatusCreated, out) } @@ -793,7 +794,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re if vars == nil { return fmt.Errorf("Missing parameter") } - if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w"), r.Form.Get("exec")).Run(); err != nil { + if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { return err } return nil @@ -1060,11 +1061,9 @@ func postContainerExecStart(eng *engine.Engine, version version.Version, w http. job = eng.Job("execStart", name) errOut io.Writer = os.Stderr ) - if err := job.DecodeEnv(r.Body); err != nil { return err } - if !job.GetenvBool("Detach") { // Setting up the streaming http interface. inStream, outStream, err := hijackServer(w) @@ -1102,12 +1101,12 @@ func postContainerExecStart(eng *engine.Engine, version version.Version, w http. errOut = outStream } // Now run the user process in container. + job.SetCloseIO(false) if err := job.Run(); err != nil { fmt.Fprintf(errOut, "Error starting exec command in container %s: %s\n", name, err) return err } w.WriteHeader(http.StatusNoContent) - return nil } diff --git a/daemon/attach.go b/daemon/attach.go index c6163494c5..e257375e23 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -206,7 +206,7 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t }() } if stderr != nil { - nJobs += 1 + nJobs++ if p, err := streamConfig.StderrPipe(); err != nil { errors <- err } else { @@ -229,7 +229,6 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t if err != nil { log.Errorf("attach: stderr: %s", err) } - log.Debugf("stdout attach end") errors <- err }() } diff --git a/daemon/exec.go b/daemon/exec.go index 80c6a9e6d1..02f4804605 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -20,7 +20,7 @@ import ( type execConfig struct { sync.Mutex ID string - Running bool + Running bool ProcessConfig execdriver.ProcessConfig StreamConfig OpenStdin bool @@ -130,7 +130,7 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status { StreamConfig: StreamConfig{}, ProcessConfig: processConfig, Container: container, - Running: false, + Running: false, } d.registerExecCommand(execConfig) @@ -141,8 +141,8 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status { } func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status { - if len(job.Args) != 2 { - return job.Errorf("Usage: %s [options] container exec", job.Name) + if len(job.Args) != 1 { + return job.Errorf("Usage: %s [options] exec", job.Name) } var ( @@ -165,11 +165,11 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status { } execConfig.Running = true }() - if err != nil { return job.Error(err) } + log.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID) container := execConfig.Container if execConfig.OpenStdin { diff --git a/docs/man/docker-exec.md b/docs/man/docker-exec.md new file mode 100644 index 0000000000..23c6a1dbb3 --- /dev/null +++ b/docs/man/docker-exec.md @@ -0,0 +1,29 @@ +% DOCKER(1) Docker User Manuals +% Docker Community +% SEPT 2014 +# NAME +docker-exec - Run a command in an existing container + +# SYNOPSIS +**docker exec** +[**-d**|**--detach**[=*false*]] +[**-i**|**--interactive**[=*false*]] +[**-t**|**--tty**[=*false*]] + CONTAINER COMMAND [ARG...] + +# DESCRIPTION + +Run a process in an existing container. The existing CONTAINER needs to be active. + +# Options + +**-d**, **--detach**=*true*|*false* + Detached mode. This runs the new process in the background. + +**-i**, **--interactive**=*true*|*false* + When set to true, keep STDIN open even if not attached. The default is false. + +**-t**, **--tty**=*true*|*false* + When set to true Docker can allocate a pseudo-tty and attach to the standard +input of the process. This can be used, for example, to run a throwaway +interactive shell. The default value is false. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index fcd12ee08d..cae002994d 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1295,6 +1295,36 @@ It is even useful to cherry-pick particular tags of an image repository $ sudo docker save -o ubuntu.tar ubuntu:lucid ubuntu:saucy +## exec + + Usage: docker exec CONTAINER COMMAND [ARG...] + + Run a command in an existing container + + -d, --detach=false Detached mode: run the process in the background and exit + -i, --interactive=false Keep STDIN open even if not attached + -t, --tty=false Allocate a pseudo-TTY + +The `docker exec` command runs a user specified command as a new process in an existing +user specified container. The container needs to be active. + +The `docker exec` command will typically be used after `docker run`. + +### Examples: + + $ sudo docker run --name ubuntu_bash --rm -i -t ubuntu bash + +This will create a container named 'ubuntu_bash' and start a bash session. + + $ sudo docker exec -d ubuntu_bash touch /tmp/execWorks + +This will create a new file '/tmp/execWorks' inside the existing and active container +'ubuntu_bash', in the background. + + $ sudo docker exec ubuntu_bash -it bash + +This will create a new bash session in the container 'ubuntu_bash'. + ## search Search [Docker Hub](https://hub.docker.com) for images diff --git a/engine/engine.go b/engine/engine.go index e35acbbaf3..5c708d405f 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -115,13 +115,14 @@ func (eng *Engine) commands() []string { // This function mimics `Command` from the standard os/exec package. func (eng *Engine) Job(name string, args ...string) *Job { job := &Job{ - Eng: eng, - Name: name, - Args: args, - Stdin: NewInput(), - Stdout: NewOutput(), - Stderr: NewOutput(), - env: &Env{}, + Eng: eng, + Name: name, + Args: args, + Stdin: NewInput(), + Stdout: NewOutput(), + Stderr: NewOutput(), + env: &Env{}, + closeIO: true, } if eng.Logging { job.Stderr.Add(ioutils.NopWriteCloser(eng.Stderr)) diff --git a/engine/job.go b/engine/job.go index 5b0080167d..d032ff0215 100644 --- a/engine/job.go +++ b/engine/job.go @@ -31,6 +31,7 @@ type Job struct { handler Handler status Status end time.Time + closeIO bool } type Status int @@ -78,19 +79,22 @@ func (job *Job) Run() error { job.status = job.handler(job) job.end = time.Now() } - // Wait for all background tasks to complete - if err := job.Stdout.Close(); err != nil { - return err - } - if err := job.Stderr.Close(); err != nil { - return err - } - if err := job.Stdin.Close(); err != nil { - return err + if job.closeIO { + // Wait for all background tasks to complete + if err := job.Stdout.Close(); err != nil { + return err + } + if err := job.Stderr.Close(); err != nil { + return err + } + if err := job.Stdin.Close(); err != nil { + return err + } } if job.status != 0 { return fmt.Errorf("%s", Tail(errorMessage, 1)) } + return nil } @@ -228,3 +232,7 @@ func (job *Job) Error(err error) Status { func (job *Job) StatusCode() int { return int(job.status) } + +func (job *Job) SetCloseIO(val bool) { + job.closeIO = val +}