diff --git a/api/client/client.go b/api/client/client.go index 376c18b25e..30e3bf9bb6 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -15,10 +15,14 @@ import ( // apiClient is an interface that clients that talk with a docker server must implement. type apiClient interface { - ContainerAttach(options types.ContainerAttachOptions) (*types.HijackedResponse, error) + ContainerAttach(options types.ContainerAttachOptions) (types.HijackedResponse, error) ContainerCommit(options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) ContainerCreate(config *runconfig.ContainerConfigWrapper, containerName string) (types.ContainerCreateResponse, error) ContainerDiff(containerID string) ([]types.ContainerChange, error) + ContainerExecAttach(execID string, config runconfig.ExecConfig) (types.HijackedResponse, error) + ContainerExecCreate(config runconfig.ExecConfig) (types.ContainerExecCreateResponse, error) + ContainerExecInspect(execID string) (types.ContainerExecInspect, error) + ContainerExecStart(execID string, config types.ExecStartCheck) error ContainerExport(containerID string) (io.ReadCloser, error) ContainerInspect(containerID string) (types.ContainerJSON, error) ContainerKill(containerID, signal string) error diff --git a/api/client/exec.go b/api/client/exec.go index a9c836c943..06cdd2e4c8 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -1,7 +1,6 @@ package client import ( - "encoding/json" "fmt" "io" @@ -24,37 +23,29 @@ func (cli *DockerCli) CmdExec(args ...string) error { return Cli.StatusError{StatusCode: 1} } - serverResp, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil) + response, err := cli.client.ContainerExecCreate(*execConfig) if err != nil { return err } - defer serverResp.body.Close() - - var response types.ContainerExecCreateResponse - if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil { - return err - } - execID := response.ID - if execID == "" { fmt.Fprintf(cli.out, "exec ID empty") return nil } //Temp struct for execStart so that we don't need to transfer all the execConfig - execStartCheck := &types.ExecStartCheck{ - Detach: execConfig.Detach, - Tty: execConfig.Tty, - } - if !execConfig.Detach { if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { return err } } else { - if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execStartCheck, nil)); err != nil { + execStartCheck := types.ExecStartCheck{ + Detach: execConfig.Detach, + Tty: execConfig.Tty, + } + + if err := cli.client.ContainerExecStart(execID, execStartCheck); err != nil { return err } // For now don't print this - wait for when we support exec wait() @@ -66,18 +57,9 @@ func (cli *DockerCli) CmdExec(args ...string) error { 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() { - logrus.Debugf("End of CmdExec(), Waiting for hijack to finish.") - if _, ok := <-hijacked; ok { - fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)") - } - }() - if execConfig.AttachStdin { in = cli.in } @@ -91,24 +73,15 @@ func (cli *DockerCli) CmdExec(args ...string) error { stderr = cli.err } } - errCh = promise.Go(func() error { - return cli.hijackWithContentType("POST", "/exec/"+execID+"/start", "application/json", 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 { - logrus.Debugf("Error hijack: %s", err) - return err - } + resp, err := cli.client.ContainerExecAttach(execID, *execConfig) + if err != nil { + return err } + defer resp.Close() + errCh = promise.Go(func() error { + return cli.holdHijackedConnection(execConfig.Tty, in, out, stderr, resp) + }) if execConfig.Tty && cli.isTerminalIn { if err := cli.monitorTtySize(execID, true); err != nil { diff --git a/api/client/hijack.go b/api/client/hijack.go index 1c495afe9e..696546dd27 100644 --- a/api/client/hijack.go +++ b/api/client/hijack.go @@ -21,7 +21,7 @@ import ( "github.com/docker/docker/pkg/term" ) -func (cli *DockerCli) holdHijackedConnection(setRawTerminal bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp *types.HijackedResponse) error { +func (cli *DockerCli) holdHijackedConnection(setRawTerminal bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error { var ( err error oldState *term.State diff --git a/api/client/lib/container_attach.go b/api/client/lib/container_attach.go index 4ed821fa1d..439ec2d0d7 100644 --- a/api/client/lib/container_attach.go +++ b/api/client/lib/container_attach.go @@ -10,7 +10,7 @@ import ( // It returns a types.HijackedConnection with the hijacked connection // and the a reader to get output. It's up to the called to close // the hijacked connection by calling types.HijackedResponse.Close. -func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (*types.HijackedResponse, error) { +func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (types.HijackedResponse, error) { query := url.Values{} if options.Stream { query.Set("stream", "1") diff --git a/api/client/lib/exec.go b/api/client/lib/exec.go new file mode 100644 index 0000000000..4b042c71f2 --- /dev/null +++ b/api/client/lib/exec.go @@ -0,0 +1,49 @@ +package lib + +import ( + "encoding/json" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/runconfig" +) + +// ContainerExecCreate creates a new exec configuration to run an exec process. +func (cli *Client) ContainerExecCreate(config runconfig.ExecConfig) (types.ContainerExecCreateResponse, error) { + var response types.ContainerExecCreateResponse + resp, err := cli.post("/containers/"+config.Container+"/exec", nil, config, nil) + if err != nil { + return response, err + } + defer ensureReaderClosed(resp) + err = json.NewDecoder(resp.body).Decode(&response) + return response, err +} + +// ContainerExecStart starts an exec process already create in the docker host. +func (cli *Client) ContainerExecStart(execID string, config types.ExecStartCheck) error { + resp, err := cli.post("/exec/"+execID+"/start", nil, config, nil) + ensureReaderClosed(resp) + return err +} + +// ContainerExecAttach attaches a connection to an exec process in the server. +// It returns a types.HijackedConnection with the hijacked connection +// and the a reader to get output. It's up to the called to close +// the hijacked connection by calling types.HijackedResponse.Close. +func (cli *Client) ContainerExecAttach(execID string, config runconfig.ExecConfig) (types.HijackedResponse, error) { + headers := map[string][]string{"Content-Type": {"application/json"}} + return cli.postHijacked("/exec/"+execID+"/start", nil, config, headers) +} + +// ContainerExecInspect returns information about a specific exec process on the docker host. +func (cli *Client) ContainerExecInspect(execID string) (types.ContainerExecInspect, error) { + var response types.ContainerExecInspect + resp, err := cli.get("/exec/"+execID+"/json", nil, nil) + if err != nil { + return response, err + } + defer ensureReaderClosed(resp) + + err = json.NewDecoder(resp.body).Decode(&response) + return response, err +} diff --git a/api/client/lib/hijack.go b/api/client/lib/hijack.go index 4c9475bffa..70ada0369b 100644 --- a/api/client/lib/hijack.go +++ b/api/client/lib/hijack.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "errors" "fmt" - "io" "net" "net/http/httputil" "net/url" @@ -30,15 +29,15 @@ func (c *tlsClientCon) CloseWrite() error { } // postHijacked sends a POST request and hijacks the connection. -func (cli *Client) postHijacked(path string, query url.Values, body io.Reader, headers map[string][]string) (*types.HijackedResponse, error) { +func (cli *Client) postHijacked(path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) { bodyEncoded, err := encodeData(body) if err != nil { - return nil, err + return types.HijackedResponse{}, err } req, err := cli.newRequest("POST", path, query, bodyEncoded, headers) if err != nil { - return nil, err + return types.HijackedResponse{}, err } req.Host = cli.Addr @@ -48,9 +47,9 @@ func (cli *Client) postHijacked(path string, query url.Values, body io.Reader, h conn, err := dial(cli.Proto, cli.Addr, cli.tlsConfig) if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") + return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") } - return nil, err + return types.HijackedResponse{}, err } // When we set up a TCP connection for hijack, there could be long periods @@ -71,7 +70,7 @@ func (cli *Client) postHijacked(path string, query url.Values, body io.Reader, h rwc, br := clientconn.Hijack() - return &types.HijackedResponse{rwc, br}, nil + return types.HijackedResponse{rwc, br}, nil } func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) { diff --git a/api/client/utils.go b/api/client/utils.go index 0de171db1e..afa0353b09 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -278,29 +278,16 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { // getExecExitCode perform an inspect on the exec command. It returns // the running state and the exit code. func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { - serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil) + resp, err := cli.client.ContainerExecInspect(execID) if err != nil { // If we can't connect, then the daemon probably died. - if err != errConnectionFailed { + if err != lib.ErrConnectionFailed { return false, -1, err } return false, -1, nil } - defer serverResp.body.Close() - - //TODO: Should we reconsider having a type in api/types? - //this is a response to exex/id/json not container - var c struct { - Running bool - ExitCode int - } - - if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil { - return false, -1, err - } - - return c.Running, c.ExitCode, nil + return resp.Running, resp.ExitCode, nil } func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { diff --git a/api/types/client.go b/api/types/client.go index dbe299cbdb..8ce653d590 100644 --- a/api/types/client.go +++ b/api/types/client.go @@ -31,6 +31,14 @@ type ContainerCommitOptions struct { JSONConfig string } +// ContainerExecInspect holds information returned by exec inspect. +type ContainerExecInspect struct { + ExecID string + ContainerID string + Running bool + ExitCode int +} + // ContainerListOptions holds parameters to list containers with. type ContainerListOptions struct { Quiet bool