github.com/hamo/docker@v1.11.1/api/client/exec.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"golang.org/x/net/context"
     8  
     9  	"github.com/Sirupsen/logrus"
    10  	Cli "github.com/docker/docker/cli"
    11  	flag "github.com/docker/docker/pkg/mflag"
    12  	"github.com/docker/docker/pkg/promise"
    13  	"github.com/docker/engine-api/types"
    14  )
    15  
    16  // CmdExec runs a command in a running container.
    17  //
    18  // Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
    19  func (cli *DockerCli) CmdExec(args ...string) error {
    20  	cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
    21  	detachKeys := cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
    22  
    23  	execConfig, err := ParseExec(cmd, args)
    24  	// just in case the ParseExec does not exit
    25  	if execConfig.Container == "" || err != nil {
    26  		return Cli.StatusError{StatusCode: 1}
    27  	}
    28  
    29  	if *detachKeys != "" {
    30  		cli.configFile.DetachKeys = *detachKeys
    31  	}
    32  
    33  	// Send client escape keys
    34  	execConfig.DetachKeys = cli.configFile.DetachKeys
    35  
    36  	response, err := cli.client.ContainerExecCreate(context.Background(), *execConfig)
    37  	if err != nil {
    38  		return err
    39  	}
    40  
    41  	execID := response.ID
    42  	if execID == "" {
    43  		fmt.Fprintf(cli.out, "exec ID empty")
    44  		return nil
    45  	}
    46  
    47  	//Temp struct for execStart so that we don't need to transfer all the execConfig
    48  	if !execConfig.Detach {
    49  		if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil {
    50  			return err
    51  		}
    52  	} else {
    53  		execStartCheck := types.ExecStartCheck{
    54  			Detach: execConfig.Detach,
    55  			Tty:    execConfig.Tty,
    56  		}
    57  
    58  		if err := cli.client.ContainerExecStart(context.Background(), execID, execStartCheck); err != nil {
    59  			return err
    60  		}
    61  		// For now don't print this - wait for when we support exec wait()
    62  		// fmt.Fprintf(cli.out, "%s\n", execID)
    63  		return nil
    64  	}
    65  
    66  	// Interactive exec requested.
    67  	var (
    68  		out, stderr io.Writer
    69  		in          io.ReadCloser
    70  		errCh       chan error
    71  	)
    72  
    73  	if execConfig.AttachStdin {
    74  		in = cli.in
    75  	}
    76  	if execConfig.AttachStdout {
    77  		out = cli.out
    78  	}
    79  	if execConfig.AttachStderr {
    80  		if execConfig.Tty {
    81  			stderr = cli.out
    82  		} else {
    83  			stderr = cli.err
    84  		}
    85  	}
    86  
    87  	resp, err := cli.client.ContainerExecAttach(context.Background(), execID, *execConfig)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	defer resp.Close()
    92  	if in != nil && execConfig.Tty {
    93  		if err := cli.setRawTerminal(); err != nil {
    94  			return err
    95  		}
    96  		defer cli.restoreTerminal(in)
    97  	}
    98  	errCh = promise.Go(func() error {
    99  		return cli.holdHijackedConnection(execConfig.Tty, in, out, stderr, resp)
   100  	})
   101  
   102  	if execConfig.Tty && cli.isTerminalIn {
   103  		if err := cli.monitorTtySize(execID, true); err != nil {
   104  			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
   105  		}
   106  	}
   107  
   108  	if err := <-errCh; err != nil {
   109  		logrus.Debugf("Error hijack: %s", err)
   110  		return err
   111  	}
   112  
   113  	var status int
   114  	if _, status, err = getExecExitCode(cli, execID); err != nil {
   115  		return err
   116  	}
   117  
   118  	if status != 0 {
   119  		return Cli.StatusError{StatusCode: status}
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // ParseExec parses the specified args for the specified command and generates
   126  // an ExecConfig from it.
   127  // If the minimal number of specified args is not right or if specified args are
   128  // not valid, it will return an error.
   129  func ParseExec(cmd *flag.FlagSet, args []string) (*types.ExecConfig, error) {
   130  	var (
   131  		flStdin      = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
   132  		flTty        = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
   133  		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background")
   134  		flUser       = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: <name|uid>[:<group|gid>])")
   135  		flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to the command")
   136  		execCmd      []string
   137  		container    string
   138  	)
   139  	cmd.Require(flag.Min, 2)
   140  	if err := cmd.ParseFlags(args, true); err != nil {
   141  		return nil, err
   142  	}
   143  	container = cmd.Arg(0)
   144  	parsedArgs := cmd.Args()
   145  	execCmd = parsedArgs[1:]
   146  
   147  	execConfig := &types.ExecConfig{
   148  		User:       *flUser,
   149  		Privileged: *flPrivileged,
   150  		Tty:        *flTty,
   151  		Cmd:        execCmd,
   152  		Container:  container,
   153  		Detach:     *flDetach,
   154  	}
   155  
   156  	// If -d is not set, attach to everything by default
   157  	if !*flDetach {
   158  		execConfig.AttachStdout = true
   159  		execConfig.AttachStderr = true
   160  		if *flStdin {
   161  			execConfig.AttachStdin = true
   162  		}
   163  	}
   164  
   165  	return execConfig, nil
   166  }