Adding docker exec support in CLI.

Fixed a bug in daemon that resulted in accessing of a closed pipe.

Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)
This commit is contained in:
Vishnu Kannan
2014-09-16 19:24:25 +00:00
parent 39030382c4
commit c786a8ee5e
9 changed files with 208 additions and 35 deletions
+101 -3
View File
@@ -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
}
+14 -5
View File
@@ -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
+3 -4
View File
@@ -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
}