github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/cli/command/container/exec.go (about)

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