github.com/squaremo/docker@v1.3.2-0.20150516120342-42cfc9554972/api/client/exec.go (about)

     1  package client
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/Sirupsen/logrus"
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/pkg/promise"
    11  	"github.com/docker/docker/runconfig"
    12  )
    13  
    14  // CmdExec runs a command in a running container.
    15  //
    16  // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
    17  func (cli *DockerCli) CmdExec(args ...string) error {
    18  	cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true)
    19  
    20  	execConfig, err := runconfig.ParseExec(cmd, args)
    21  	// just in case the ParseExec does not exit
    22  	if execConfig.Container == "" || err != nil {
    23  		return StatusError{StatusCode: 1}
    24  	}
    25  
    26  	stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil)
    27  	if err != nil {
    28  		return err
    29  	}
    30  
    31  	var response types.ContainerExecCreateResponse
    32  	if err := json.NewDecoder(stream).Decode(&response); err != nil {
    33  		return err
    34  	}
    35  
    36  	execID := response.ID
    37  
    38  	if execID == "" {
    39  		fmt.Fprintf(cli.out, "exec ID empty")
    40  		return nil
    41  	}
    42  
    43  	//Temp struct for execStart so that we don't need to transfer all the execConfig
    44  	execStartCheck := &types.ExecStartCheck{
    45  		Detach: execConfig.Detach,
    46  		Tty:    execConfig.Tty,
    47  	}
    48  
    49  	if !execConfig.Detach {
    50  		if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
    51  			return err
    52  		}
    53  	} else {
    54  		if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execStartCheck, nil)); err != nil {
    55  			return err
    56  		}
    57  		// For now don't print this - wait for when we support exec wait()
    58  		// fmt.Fprintf(cli.out, "%s\n", execID)
    59  		return nil
    60  	}
    61  
    62  	// Interactive exec requested.
    63  	var (
    64  		out, stderr io.Writer
    65  		in          io.ReadCloser
    66  		hijacked    = make(chan io.Closer)
    67  		errCh       chan error
    68  	)
    69  
    70  	// Block the return until the chan gets closed
    71  	defer func() {
    72  		logrus.Debugf("End of CmdExec(), Waiting for hijack to finish.")
    73  		if _, ok := <-hijacked; ok {
    74  			fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
    75  		}
    76  	}()
    77  
    78  	if execConfig.AttachStdin {
    79  		in = cli.in
    80  	}
    81  	if execConfig.AttachStdout {
    82  		out = cli.out
    83  	}
    84  	if execConfig.AttachStderr {
    85  		if execConfig.Tty {
    86  			stderr = cli.out
    87  		} else {
    88  			stderr = cli.err
    89  		}
    90  	}
    91  	errCh = promise.Go(func() error {
    92  		return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig)
    93  	})
    94  
    95  	// Acknowledge the hijack before starting
    96  	select {
    97  	case closer := <-hijacked:
    98  		// Make sure that hijack gets closed when returning. (result
    99  		// in closing hijack chan and freeing server's goroutines.
   100  		if closer != nil {
   101  			defer closer.Close()
   102  		}
   103  	case err := <-errCh:
   104  		if err != nil {
   105  			logrus.Debugf("Error hijack: %s", err)
   106  			return err
   107  		}
   108  	}
   109  
   110  	if execConfig.Tty && cli.isTerminalIn {
   111  		if err := cli.monitorTtySize(execID, true); err != nil {
   112  			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
   113  		}
   114  	}
   115  
   116  	if err := <-errCh; err != nil {
   117  		logrus.Debugf("Error hijack: %s", err)
   118  		return err
   119  	}
   120  
   121  	var status int
   122  	if _, status, err = getExecExitCode(cli, execID); err != nil {
   123  		return err
   124  	}
   125  
   126  	if status != 0 {
   127  		return StatusError{StatusCode: status}
   128  	}
   129  
   130  	return nil
   131  }