github.com/flavio/docker@v0.1.3-0.20170117145210-f63d1a6eec47/cli/command/container/exec.go (about)

     1  package container
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  
     7  	"github.com/Sirupsen/logrus"
     8  	"github.com/docker/docker/api/types"
     9  	"github.com/docker/docker/cli"
    10  	"github.com/docker/docker/cli/command"
    11  	apiclient "github.com/docker/docker/client"
    12  	options "github.com/docker/docker/opts"
    13  	"github.com/docker/docker/pkg/promise"
    14  	"github.com/spf13/cobra"
    15  	"golang.org/x/net/context"
    16  )
    17  
    18  type execOptions struct {
    19  	detachKeys  string
    20  	interactive bool
    21  	tty         bool
    22  	detach      bool
    23  	user        string
    24  	privileged  bool
    25  	env         *options.ListOpts
    26  }
    27  
    28  func newExecOptions() *execOptions {
    29  	var values []string
    30  	return &execOptions{
    31  		env: options.NewListOptsRef(&values, options.ValidateEnv),
    32  	}
    33  }
    34  
    35  // NewExecCommand creats a new cobra.Command for `docker exec`
    36  func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command {
    37  	opts := newExecOptions()
    38  
    39  	cmd := &cobra.Command{
    40  		Use:   "exec [OPTIONS] CONTAINER COMMAND [ARG...]",
    41  		Short: "Run a command in a running container",
    42  		Args:  cli.RequiresMinArgs(2),
    43  		RunE: func(cmd *cobra.Command, args []string) error {
    44  			container := args[0]
    45  			execCmd := args[1:]
    46  			return runExec(dockerCli, opts, container, execCmd)
    47  		},
    48  	}
    49  
    50  	flags := cmd.Flags()
    51  	flags.SetInterspersed(false)
    52  
    53  	flags.StringVarP(&opts.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching a container")
    54  	flags.BoolVarP(&opts.interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
    55  	flags.BoolVarP(&opts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
    56  	flags.BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: run command in the background")
    57  	flags.StringVarP(&opts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
    58  	flags.BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the command")
    59  	flags.VarP(opts.env, "env", "e", "Set environment variables")
    60  	flags.SetAnnotation("env", "version", []string{"1.25"})
    61  
    62  	return cmd
    63  }
    64  
    65  func runExec(dockerCli *command.DockerCli, opts *execOptions, container string, execCmd []string) error {
    66  	execConfig, err := parseExec(opts, execCmd)
    67  	// just in case the ParseExec does not exit
    68  	if container == "" || err != nil {
    69  		return cli.StatusError{StatusCode: 1}
    70  	}
    71  
    72  	if opts.detachKeys != "" {
    73  		dockerCli.ConfigFile().DetachKeys = opts.detachKeys
    74  	}
    75  
    76  	// Send client escape keys
    77  	execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys
    78  
    79  	ctx := context.Background()
    80  	client := dockerCli.Client()
    81  
    82  	response, err := client.ContainerExecCreate(ctx, container, *execConfig)
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	execID := response.ID
    88  	if execID == "" {
    89  		fmt.Fprintln(dockerCli.Out(), "exec ID empty")
    90  		return nil
    91  	}
    92  
    93  	//Temp struct for execStart so that we don't need to transfer all the execConfig
    94  	if !execConfig.Detach {
    95  		if err := dockerCli.In().CheckTty(execConfig.AttachStdin, execConfig.Tty); err != nil {
    96  			return err
    97  		}
    98  	} else {
    99  		execStartCheck := types.ExecStartCheck{
   100  			Detach: execConfig.Detach,
   101  			Tty:    execConfig.Tty,
   102  		}
   103  
   104  		if err := client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
   105  			return err
   106  		}
   107  		// For now don't print this - wait for when we support exec wait()
   108  		// fmt.Fprintf(dockerCli.Out(), "%s\n", execID)
   109  		return nil
   110  	}
   111  
   112  	// Interactive exec requested.
   113  	var (
   114  		out, stderr io.Writer
   115  		in          io.ReadCloser
   116  		errCh       chan error
   117  	)
   118  
   119  	if execConfig.AttachStdin {
   120  		in = dockerCli.In()
   121  	}
   122  	if execConfig.AttachStdout {
   123  		out = dockerCli.Out()
   124  	}
   125  	if execConfig.AttachStderr {
   126  		if execConfig.Tty {
   127  			stderr = dockerCli.Out()
   128  		} else {
   129  			stderr = dockerCli.Err()
   130  		}
   131  	}
   132  
   133  	resp, err := client.ContainerExecAttach(ctx, execID, *execConfig)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	defer resp.Close()
   138  	errCh = promise.Go(func() error {
   139  		return holdHijackedConnection(ctx, dockerCli, execConfig.Tty, in, out, stderr, resp)
   140  	})
   141  
   142  	if execConfig.Tty && dockerCli.In().IsTerminal() {
   143  		if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil {
   144  			fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
   145  		}
   146  	}
   147  
   148  	if err := <-errCh; err != nil {
   149  		logrus.Debugf("Error hijack: %s", err)
   150  		return err
   151  	}
   152  
   153  	var status int
   154  	if _, status, err = getExecExitCode(ctx, client, execID); err != nil {
   155  		return err
   156  	}
   157  
   158  	if status != 0 {
   159  		return cli.StatusError{StatusCode: status}
   160  	}
   161  
   162  	return nil
   163  }
   164  
   165  // getExecExitCode perform an inspect on the exec command. It returns
   166  // the running state and the exit code.
   167  func getExecExitCode(ctx context.Context, client apiclient.ContainerAPIClient, execID string) (bool, int, error) {
   168  	resp, err := client.ContainerExecInspect(ctx, execID)
   169  	if err != nil {
   170  		// If we can't connect, then the daemon probably died.
   171  		if !apiclient.IsErrConnectionFailed(err) {
   172  			return false, -1, err
   173  		}
   174  		return false, -1, nil
   175  	}
   176  
   177  	return resp.Running, resp.ExitCode, nil
   178  }
   179  
   180  // parseExec parses the specified args for the specified command and generates
   181  // an ExecConfig from it.
   182  func parseExec(opts *execOptions, execCmd []string) (*types.ExecConfig, error) {
   183  	execConfig := &types.ExecConfig{
   184  		User:       opts.user,
   185  		Privileged: opts.privileged,
   186  		Tty:        opts.tty,
   187  		Cmd:        execCmd,
   188  		Detach:     opts.detach,
   189  	}
   190  
   191  	// If -d is not set, attach to everything by default
   192  	if !opts.detach {
   193  		execConfig.AttachStdout = true
   194  		execConfig.AttachStderr = true
   195  		if opts.interactive {
   196  			execConfig.AttachStdin = true
   197  		}
   198  	}
   199  
   200  	if opts.env != nil {
   201  		execConfig.Env = opts.env.GetAll()
   202  	}
   203  
   204  	return execConfig, nil
   205  }