Signed-off-by: David Calavera <david.calavera@gmail.com>
| ... | ... |
@@ -15,10 +15,14 @@ import ( |
| 15 | 15 |
|
| 16 | 16 |
// apiClient is an interface that clients that talk with a docker server must implement. |
| 17 | 17 |
type apiClient interface {
|
| 18 |
- ContainerAttach(options types.ContainerAttachOptions) (*types.HijackedResponse, error) |
|
| 18 |
+ ContainerAttach(options types.ContainerAttachOptions) (types.HijackedResponse, error) |
|
| 19 | 19 |
ContainerCommit(options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) |
| 20 | 20 |
ContainerCreate(config *runconfig.ContainerConfigWrapper, containerName string) (types.ContainerCreateResponse, error) |
| 21 | 21 |
ContainerDiff(containerID string) ([]types.ContainerChange, error) |
| 22 |
+ ContainerExecAttach(execID string, config runconfig.ExecConfig) (types.HijackedResponse, error) |
|
| 23 |
+ ContainerExecCreate(config runconfig.ExecConfig) (types.ContainerExecCreateResponse, error) |
|
| 24 |
+ ContainerExecInspect(execID string) (types.ContainerExecInspect, error) |
|
| 25 |
+ ContainerExecStart(execID string, config types.ExecStartCheck) error |
|
| 22 | 26 |
ContainerExport(containerID string) (io.ReadCloser, error) |
| 23 | 27 |
ContainerInspect(containerID string) (types.ContainerJSON, error) |
| 24 | 28 |
ContainerKill(containerID, signal string) error |
| ... | ... |
@@ -1,7 +1,6 @@ |
| 1 | 1 |
package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "encoding/json" |
|
| 5 | 4 |
"fmt" |
| 6 | 5 |
"io" |
| 7 | 6 |
|
| ... | ... |
@@ -24,37 +23,29 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
| 24 | 24 |
return Cli.StatusError{StatusCode: 1}
|
| 25 | 25 |
} |
| 26 | 26 |
|
| 27 |
- serverResp, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
|
|
| 27 |
+ response, err := cli.client.ContainerExecCreate(*execConfig) |
|
| 28 | 28 |
if err != nil {
|
| 29 | 29 |
return err |
| 30 | 30 |
} |
| 31 | 31 |
|
| 32 |
- defer serverResp.body.Close() |
|
| 33 |
- |
|
| 34 |
- var response types.ContainerExecCreateResponse |
|
| 35 |
- if err := json.NewDecoder(serverResp.body).Decode(&response); err != nil {
|
|
| 36 |
- return err |
|
| 37 |
- } |
|
| 38 |
- |
|
| 39 | 32 |
execID := response.ID |
| 40 |
- |
|
| 41 | 33 |
if execID == "" {
|
| 42 | 34 |
fmt.Fprintf(cli.out, "exec ID empty") |
| 43 | 35 |
return nil |
| 44 | 36 |
} |
| 45 | 37 |
|
| 46 | 38 |
//Temp struct for execStart so that we don't need to transfer all the execConfig |
| 47 |
- execStartCheck := &types.ExecStartCheck{
|
|
| 48 |
- Detach: execConfig.Detach, |
|
| 49 |
- Tty: execConfig.Tty, |
|
| 50 |
- } |
|
| 51 |
- |
|
| 52 | 39 |
if !execConfig.Detach {
|
| 53 | 40 |
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
| 54 | 41 |
return err |
| 55 | 42 |
} |
| 56 | 43 |
} else {
|
| 57 |
- if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execStartCheck, nil)); err != nil {
|
|
| 44 |
+ execStartCheck := types.ExecStartCheck{
|
|
| 45 |
+ Detach: execConfig.Detach, |
|
| 46 |
+ Tty: execConfig.Tty, |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ if err := cli.client.ContainerExecStart(execID, execStartCheck); err != nil {
|
|
| 58 | 50 |
return err |
| 59 | 51 |
} |
| 60 | 52 |
// For now don't print this - wait for when we support exec wait() |
| ... | ... |
@@ -66,18 +57,9 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
| 66 | 66 |
var ( |
| 67 | 67 |
out, stderr io.Writer |
| 68 | 68 |
in io.ReadCloser |
| 69 |
- hijacked = make(chan io.Closer) |
|
| 70 | 69 |
errCh chan error |
| 71 | 70 |
) |
| 72 | 71 |
|
| 73 |
- // Block the return until the chan gets closed |
|
| 74 |
- defer func() {
|
|
| 75 |
- logrus.Debugf("End of CmdExec(), Waiting for hijack to finish.")
|
|
| 76 |
- if _, ok := <-hijacked; ok {
|
|
| 77 |
- fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)") |
|
| 78 |
- } |
|
| 79 |
- }() |
|
| 80 |
- |
|
| 81 | 72 |
if execConfig.AttachStdin {
|
| 82 | 73 |
in = cli.in |
| 83 | 74 |
} |
| ... | ... |
@@ -91,24 +73,15 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
| 91 | 91 |
stderr = cli.err |
| 92 | 92 |
} |
| 93 | 93 |
} |
| 94 |
- errCh = promise.Go(func() error {
|
|
| 95 |
- return cli.hijackWithContentType("POST", "/exec/"+execID+"/start", "application/json", execConfig.Tty, in, out, stderr, hijacked, execConfig)
|
|
| 96 |
- }) |
|
| 97 | 94 |
|
| 98 |
- // Acknowledge the hijack before starting |
|
| 99 |
- select {
|
|
| 100 |
- case closer := <-hijacked: |
|
| 101 |
- // Make sure that hijack gets closed when returning. (result |
|
| 102 |
- // in closing hijack chan and freeing server's goroutines. |
|
| 103 |
- if closer != nil {
|
|
| 104 |
- defer closer.Close() |
|
| 105 |
- } |
|
| 106 |
- case err := <-errCh: |
|
| 107 |
- if err != nil {
|
|
| 108 |
- logrus.Debugf("Error hijack: %s", err)
|
|
| 109 |
- return err |
|
| 110 |
- } |
|
| 95 |
+ resp, err := cli.client.ContainerExecAttach(execID, *execConfig) |
|
| 96 |
+ if err != nil {
|
|
| 97 |
+ return err |
|
| 111 | 98 |
} |
| 99 |
+ defer resp.Close() |
|
| 100 |
+ errCh = promise.Go(func() error {
|
|
| 101 |
+ return cli.holdHijackedConnection(execConfig.Tty, in, out, stderr, resp) |
|
| 102 |
+ }) |
|
| 112 | 103 |
|
| 113 | 104 |
if execConfig.Tty && cli.isTerminalIn {
|
| 114 | 105 |
if err := cli.monitorTtySize(execID, true); err != nil {
|
| ... | ... |
@@ -21,7 +21,7 @@ import ( |
| 21 | 21 |
"github.com/docker/docker/pkg/term" |
| 22 | 22 |
) |
| 23 | 23 |
|
| 24 |
-func (cli *DockerCli) holdHijackedConnection(setRawTerminal bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp *types.HijackedResponse) error {
|
|
| 24 |
+func (cli *DockerCli) holdHijackedConnection(setRawTerminal bool, inputStream io.ReadCloser, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
|
|
| 25 | 25 |
var ( |
| 26 | 26 |
err error |
| 27 | 27 |
oldState *term.State |
| ... | ... |
@@ -10,7 +10,7 @@ import ( |
| 10 | 10 |
// It returns a types.HijackedConnection with the hijacked connection |
| 11 | 11 |
// and the a reader to get output. It's up to the called to close |
| 12 | 12 |
// the hijacked connection by calling types.HijackedResponse.Close. |
| 13 |
-func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (*types.HijackedResponse, error) {
|
|
| 13 |
+func (cli *Client) ContainerAttach(options types.ContainerAttachOptions) (types.HijackedResponse, error) {
|
|
| 14 | 14 |
query := url.Values{}
|
| 15 | 15 |
if options.Stream {
|
| 16 | 16 |
query.Set("stream", "1")
|
| 17 | 17 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,49 @@ |
| 0 |
+package lib |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/api/types" |
|
| 6 |
+ "github.com/docker/docker/runconfig" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+// ContainerExecCreate creates a new exec configuration to run an exec process. |
|
| 10 |
+func (cli *Client) ContainerExecCreate(config runconfig.ExecConfig) (types.ContainerExecCreateResponse, error) {
|
|
| 11 |
+ var response types.ContainerExecCreateResponse |
|
| 12 |
+ resp, err := cli.post("/containers/"+config.Container+"/exec", nil, config, nil)
|
|
| 13 |
+ if err != nil {
|
|
| 14 |
+ return response, err |
|
| 15 |
+ } |
|
| 16 |
+ defer ensureReaderClosed(resp) |
|
| 17 |
+ err = json.NewDecoder(resp.body).Decode(&response) |
|
| 18 |
+ return response, err |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+// ContainerExecStart starts an exec process already create in the docker host. |
|
| 22 |
+func (cli *Client) ContainerExecStart(execID string, config types.ExecStartCheck) error {
|
|
| 23 |
+ resp, err := cli.post("/exec/"+execID+"/start", nil, config, nil)
|
|
| 24 |
+ ensureReaderClosed(resp) |
|
| 25 |
+ return err |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+// ContainerExecAttach attaches a connection to an exec process in the server. |
|
| 29 |
+// It returns a types.HijackedConnection with the hijacked connection |
|
| 30 |
+// and the a reader to get output. It's up to the called to close |
|
| 31 |
+// the hijacked connection by calling types.HijackedResponse.Close. |
|
| 32 |
+func (cli *Client) ContainerExecAttach(execID string, config runconfig.ExecConfig) (types.HijackedResponse, error) {
|
|
| 33 |
+ headers := map[string][]string{"Content-Type": {"application/json"}}
|
|
| 34 |
+ return cli.postHijacked("/exec/"+execID+"/start", nil, config, headers)
|
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+// ContainerExecInspect returns information about a specific exec process on the docker host. |
|
| 38 |
+func (cli *Client) ContainerExecInspect(execID string) (types.ContainerExecInspect, error) {
|
|
| 39 |
+ var response types.ContainerExecInspect |
|
| 40 |
+ resp, err := cli.get("/exec/"+execID+"/json", nil, nil)
|
|
| 41 |
+ if err != nil {
|
|
| 42 |
+ return response, err |
|
| 43 |
+ } |
|
| 44 |
+ defer ensureReaderClosed(resp) |
|
| 45 |
+ |
|
| 46 |
+ err = json.NewDecoder(resp.body).Decode(&response) |
|
| 47 |
+ return response, err |
|
| 48 |
+} |
| ... | ... |
@@ -4,7 +4,6 @@ import ( |
| 4 | 4 |
"crypto/tls" |
| 5 | 5 |
"errors" |
| 6 | 6 |
"fmt" |
| 7 |
- "io" |
|
| 8 | 7 |
"net" |
| 9 | 8 |
"net/http/httputil" |
| 10 | 9 |
"net/url" |
| ... | ... |
@@ -30,15 +29,15 @@ func (c *tlsClientCon) CloseWrite() error {
|
| 30 | 30 |
} |
| 31 | 31 |
|
| 32 | 32 |
// postHijacked sends a POST request and hijacks the connection. |
| 33 |
-func (cli *Client) postHijacked(path string, query url.Values, body io.Reader, headers map[string][]string) (*types.HijackedResponse, error) {
|
|
| 33 |
+func (cli *Client) postHijacked(path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) {
|
|
| 34 | 34 |
bodyEncoded, err := encodeData(body) |
| 35 | 35 |
if err != nil {
|
| 36 |
- return nil, err |
|
| 36 |
+ return types.HijackedResponse{}, err
|
|
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 | 39 |
req, err := cli.newRequest("POST", path, query, bodyEncoded, headers)
|
| 40 | 40 |
if err != nil {
|
| 41 |
- return nil, err |
|
| 41 |
+ return types.HijackedResponse{}, err
|
|
| 42 | 42 |
} |
| 43 | 43 |
req.Host = cli.Addr |
| 44 | 44 |
|
| ... | ... |
@@ -48,9 +47,9 @@ func (cli *Client) postHijacked(path string, query url.Values, body io.Reader, h |
| 48 | 48 |
conn, err := dial(cli.Proto, cli.Addr, cli.tlsConfig) |
| 49 | 49 |
if err != nil {
|
| 50 | 50 |
if strings.Contains(err.Error(), "connection refused") {
|
| 51 |
- return nil, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
|
| 51 |
+ return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
|
| 52 | 52 |
} |
| 53 |
- return nil, err |
|
| 53 |
+ return types.HijackedResponse{}, err
|
|
| 54 | 54 |
} |
| 55 | 55 |
|
| 56 | 56 |
// 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 |
| 71 | 71 |
|
| 72 | 72 |
rwc, br := clientconn.Hijack() |
| 73 | 73 |
|
| 74 |
- return &types.HijackedResponse{rwc, br}, nil
|
|
| 74 |
+ return types.HijackedResponse{rwc, br}, nil
|
|
| 75 | 75 |
} |
| 76 | 76 |
|
| 77 | 77 |
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
|
| ... | ... |
@@ -278,29 +278,16 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
| 278 | 278 |
// getExecExitCode perform an inspect on the exec command. It returns |
| 279 | 279 |
// the running state and the exit code. |
| 280 | 280 |
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
| 281 |
- serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
|
| 281 |
+ resp, err := cli.client.ContainerExecInspect(execID) |
|
| 282 | 282 |
if err != nil {
|
| 283 | 283 |
// If we can't connect, then the daemon probably died. |
| 284 |
- if err != errConnectionFailed {
|
|
| 284 |
+ if err != lib.ErrConnectionFailed {
|
|
| 285 | 285 |
return false, -1, err |
| 286 | 286 |
} |
| 287 | 287 |
return false, -1, nil |
| 288 | 288 |
} |
| 289 | 289 |
|
| 290 |
- defer serverResp.body.Close() |
|
| 291 |
- |
|
| 292 |
- //TODO: Should we reconsider having a type in api/types? |
|
| 293 |
- //this is a response to exex/id/json not container |
|
| 294 |
- var c struct {
|
|
| 295 |
- Running bool |
|
| 296 |
- ExitCode int |
|
| 297 |
- } |
|
| 298 |
- |
|
| 299 |
- if err := json.NewDecoder(serverResp.body).Decode(&c); err != nil {
|
|
| 300 |
- return false, -1, err |
|
| 301 |
- } |
|
| 302 |
- |
|
| 303 |
- return c.Running, c.ExitCode, nil |
|
| 290 |
+ return resp.Running, resp.ExitCode, nil |
|
| 304 | 291 |
} |
| 305 | 292 |
|
| 306 | 293 |
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
| ... | ... |
@@ -31,6 +31,14 @@ type ContainerCommitOptions struct {
|
| 31 | 31 |
JSONConfig string |
| 32 | 32 |
} |
| 33 | 33 |
|
| 34 |
+// ContainerExecInspect holds information returned by exec inspect. |
|
| 35 |
+type ContainerExecInspect struct {
|
|
| 36 |
+ ExecID string |
|
| 37 |
+ ContainerID string |
|
| 38 |
+ Running bool |
|
| 39 |
+ ExitCode int |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 34 | 42 |
// ContainerListOptions holds parameters to list containers with. |
| 35 | 43 |
type ContainerListOptions struct {
|
| 36 | 44 |
Quiet bool |