mirror of https://github.com/docker/docs.git
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
|
@ -15,10 +15,14 @@ import (
|
||||||
|
|
||||||
// apiClient is an interface that clients that talk with a docker server must implement.
|
// apiClient is an interface that clients that talk with a docker server must implement.
|
||||||
type apiClient interface {
|
type apiClient interface {
|
||||||
ContainerAttach(options types.ContainerAttachOptions) (*types.HijackedResponse, error)
|
ContainerAttach(options types.ContainerAttachOptions) (types.HijackedResponse, error)
|
||||||
ContainerCommit(options types.ContainerCommitOptions) (types.ContainerCommitResponse, error)
|
ContainerCommit(options types.ContainerCommitOptions) (types.ContainerCommitResponse, error)
|
||||||
ContainerCreate(config *runconfig.ContainerConfigWrapper, containerName string) (types.ContainerCreateResponse, error)
|
ContainerCreate(config *runconfig.ContainerConfigWrapper, containerName string) (types.ContainerCreateResponse, error)
|
||||||
ContainerDiff(containerID string) ([]types.ContainerChange, 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)
|
ContainerExport(containerID string) (io.ReadCloser, error)
|
||||||
ContainerInspect(containerID string) (types.ContainerJSON, error)
|
ContainerInspect(containerID string) (types.ContainerJSON, error)
|
||||||
ContainerKill(containerID, signal string) error
|
ContainerKill(containerID, signal string) error
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
@ -24,37 +23,29 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||||
return Cli.StatusError{StatusCode: 1}
|
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 {
|
if err != nil {
|
||||||
return err
|
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
|
execID := response.ID
|
||||||
|
|
||||||
if execID == "" {
|
if execID == "" {
|
||||||
fmt.Fprintf(cli.out, "exec ID empty")
|
fmt.Fprintf(cli.out, "exec ID empty")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//Temp struct for execStart so that we don't need to transfer all the execConfig
|
//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 !execConfig.Detach {
|
||||||
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
return err
|
||||||
}
|
}
|
||||||
// For now don't print this - wait for when we support exec wait()
|
// 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 (
|
var (
|
||||||
out, stderr io.Writer
|
out, stderr io.Writer
|
||||||
in io.ReadCloser
|
in io.ReadCloser
|
||||||
hijacked = make(chan io.Closer)
|
|
||||||
errCh chan error
|
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 {
|
if execConfig.AttachStdin {
|
||||||
in = cli.in
|
in = cli.in
|
||||||
}
|
}
|
||||||
|
@ -91,24 +73,15 @@ func (cli *DockerCli) CmdExec(args ...string) error {
|
||||||
stderr = cli.err
|
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
|
resp, err := cli.client.ContainerExecAttach(execID, *execConfig)
|
||||||
select {
|
if err != nil {
|
||||||
case closer := <-hijacked:
|
return err
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
defer resp.Close()
|
||||||
|
errCh = promise.Go(func() error {
|
||||||
|
return cli.holdHijackedConnection(execConfig.Tty, in, out, stderr, resp)
|
||||||
|
})
|
||||||
|
|
||||||
if execConfig.Tty && cli.isTerminalIn {
|
if execConfig.Tty && cli.isTerminalIn {
|
||||||
if err := cli.monitorTtySize(execID, true); err != nil {
|
if err := cli.monitorTtySize(execID, true); err != nil {
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"github.com/docker/docker/pkg/term"
|
"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 (
|
var (
|
||||||
err error
|
err error
|
||||||
oldState *term.State
|
oldState *term.State
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
// It returns a types.HijackedConnection with the hijacked connection
|
// It returns a types.HijackedConnection with the hijacked connection
|
||||||
// and the a reader to get output. It's up to the called to close
|
// and the a reader to get output. It's up to the called to close
|
||||||
// the hijacked connection by calling types.HijackedResponse.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{}
|
query := url.Values{}
|
||||||
if options.Stream {
|
if options.Stream {
|
||||||
query.Set("stream", "1")
|
query.Set("stream", "1")
|
||||||
|
|
|
@ -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"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -30,15 +29,15 @@ func (c *tlsClientCon) CloseWrite() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// postHijacked sends a POST request and hijacks the connection.
|
// 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)
|
bodyEncoded, err := encodeData(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return types.HijackedResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := cli.newRequest("POST", path, query, bodyEncoded, headers)
|
req, err := cli.newRequest("POST", path, query, bodyEncoded, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return types.HijackedResponse{}, err
|
||||||
}
|
}
|
||||||
req.Host = cli.Addr
|
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)
|
conn, err := dial(cli.Proto, cli.Addr, cli.tlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
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
|
// 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()
|
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) {
|
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
|
// getExecExitCode perform an inspect on the exec command. It returns
|
||||||
// the running state and the exit code.
|
// the running state and the exit code.
|
||||||
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
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 err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != errConnectionFailed {
|
if err != lib.ErrConnectionFailed {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
return false, -1, nil
|
return false, -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
defer serverResp.body.Close()
|
return resp.Running, resp.ExitCode, nil
|
||||||
|
|
||||||
//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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
||||||
|
|
|
@ -31,6 +31,14 @@ type ContainerCommitOptions struct {
|
||||||
JSONConfig string
|
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.
|
// ContainerListOptions holds parameters to list containers with.
|
||||||
type ContainerListOptions struct {
|
type ContainerListOptions struct {
|
||||||
Quiet bool
|
Quiet bool
|
||||||
|
|
Loading…
Reference in New Issue