Implement docker exec with standalone client lib.
Signed-off-by: David Calavera <david.calavera@gmail.com>
This commit is contained in:
parent
bcb848c87f
commit
3f9f23114f
8 changed files with 87 additions and 67 deletions
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
api/client/lib/exec.go
Normal file
49
api/client/lib/exec.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue