Explorar el Código

Implement docker exec with standalone client lib.

Signed-off-by: David Calavera <david.calavera@gmail.com>
David Calavera hace 9 años
padre
commit
3f9f23114f

+ 5 - 1
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

+ 14 - 41
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 {

+ 1 - 1
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

+ 1 - 1
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")

+ 49 - 0
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
+}

+ 6 - 7
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) {

+ 3 - 16
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 {

+ 8 - 0
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