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

     1  package container
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"net/http/httputil"
     8  	"os"
     9  	"runtime"
    10  	"strings"
    11  	"syscall"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/cli"
    16  	"github.com/docker/docker/cli/command"
    17  	opttypes "github.com/docker/docker/opts"
    18  	"github.com/docker/docker/pkg/promise"
    19  	"github.com/docker/docker/pkg/signal"
    20  	"github.com/docker/libnetwork/resolvconf/dns"
    21  	"github.com/spf13/cobra"
    22  	"github.com/spf13/pflag"
    23  	"golang.org/x/net/context"
    24  )
    25  
    26  type runOptions struct {
    27  	detach     bool
    28  	sigProxy   bool
    29  	name       string
    30  	detachKeys string
    31  }
    32  
    33  // NewRunCommand create a new `docker run` command
    34  func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
    35  	var opts runOptions
    36  	var copts *containerOptions
    37  
    38  	cmd := &cobra.Command{
    39  		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
    40  		Short: "Run a command in a new container",
    41  		Args:  cli.RequiresMinArgs(1),
    42  		RunE: func(cmd *cobra.Command, args []string) error {
    43  			copts.Image = args[0]
    44  			if len(args) > 1 {
    45  				copts.Args = args[1:]
    46  			}
    47  			return runRun(dockerCli, cmd.Flags(), &opts, copts)
    48  		},
    49  	}
    50  
    51  	flags := cmd.Flags()
    52  	flags.SetInterspersed(false)
    53  
    54  	// These are flags not stored in Config/HostConfig
    55  	flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID")
    56  	flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process")
    57  	flags.StringVar(&opts.name, "name", "", "Assign a name to the container")
    58  	flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container")
    59  
    60  	// Add an explicit help that doesn't have a `-h` to prevent the conflict
    61  	// with hostname
    62  	flags.Bool("help", false, "Print usage")
    63  
    64  	command.AddTrustedFlags(flags, true)
    65  	copts = addFlags(flags)
    66  	return cmd
    67  }
    68  
    69  func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *containerOptions) error {
    70  	stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In()
    71  	client := dockerCli.Client()
    72  	// TODO: pass this as an argument
    73  	cmdPath := "run"
    74  
    75  	var (
    76  		flAttach                *opttypes.ListOpts
    77  		ErrConflictAttachDetach = errors.New("Conflicting options: -a and -d")
    78  	)
    79  
    80  	config, hostConfig, networkingConfig, err := parse(flags, copts)
    81  
    82  	// just in case the parse does not exit
    83  	if err != nil {
    84  		reportError(stderr, cmdPath, err.Error(), true)
    85  		return cli.StatusError{StatusCode: 125}
    86  	}
    87  
    88  	if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
    89  		fmt.Fprintln(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.")
    90  	}
    91  
    92  	if len(hostConfig.DNS) > 0 {
    93  		// check the DNS settings passed via --dns against
    94  		// localhost regexp to warn if they are trying to
    95  		// set a DNS to a localhost address
    96  		for _, dnsIP := range hostConfig.DNS {
    97  			if dns.IsLocalhost(dnsIP) {
    98  				fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
    99  				break
   100  			}
   101  		}
   102  	}
   103  
   104  	config.ArgsEscaped = false
   105  
   106  	if !opts.detach {
   107  		if err := dockerCli.In().CheckTty(config.AttachStdin, config.Tty); err != nil {
   108  			return err
   109  		}
   110  	} else {
   111  		if fl := flags.Lookup("attach"); fl != nil {
   112  			flAttach = fl.Value.(*opttypes.ListOpts)
   113  			if flAttach.Len() != 0 {
   114  				return ErrConflictAttachDetach
   115  			}
   116  		}
   117  
   118  		config.AttachStdin = false
   119  		config.AttachStdout = false
   120  		config.AttachStderr = false
   121  		config.StdinOnce = false
   122  	}
   123  
   124  	// Disable sigProxy when in TTY mode
   125  	if config.Tty {
   126  		opts.sigProxy = false
   127  	}
   128  
   129  	// Telling the Windows daemon the initial size of the tty during start makes
   130  	// a far better user experience rather than relying on subsequent resizes
   131  	// to cause things to catch up.
   132  	if runtime.GOOS == "windows" {
   133  		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.Out().GetTtySize()
   134  	}
   135  
   136  	ctx, cancelFun := context.WithCancel(context.Background())
   137  
   138  	createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
   139  	if err != nil {
   140  		reportError(stderr, cmdPath, err.Error(), true)
   141  		return runStartContainerErr(err)
   142  	}
   143  	if opts.sigProxy {
   144  		sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID)
   145  		defer signal.StopCatch(sigc)
   146  	}
   147  	var (
   148  		waitDisplayID chan struct{}
   149  		errCh         chan error
   150  	)
   151  	if !config.AttachStdout && !config.AttachStderr {
   152  		// Make this asynchronous to allow the client to write to stdin before having to read the ID
   153  		waitDisplayID = make(chan struct{})
   154  		go func() {
   155  			defer close(waitDisplayID)
   156  			fmt.Fprintln(stdout, createResponse.ID)
   157  		}()
   158  	}
   159  	attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
   160  	if attach {
   161  		var (
   162  			out, cerr io.Writer
   163  			in        io.ReadCloser
   164  		)
   165  		if config.AttachStdin {
   166  			in = stdin
   167  		}
   168  		if config.AttachStdout {
   169  			out = stdout
   170  		}
   171  		if config.AttachStderr {
   172  			if config.Tty {
   173  				cerr = stdout
   174  			} else {
   175  				cerr = stderr
   176  			}
   177  		}
   178  
   179  		if opts.detachKeys != "" {
   180  			dockerCli.ConfigFile().DetachKeys = opts.detachKeys
   181  		}
   182  
   183  		options := types.ContainerAttachOptions{
   184  			Stream:     true,
   185  			Stdin:      config.AttachStdin,
   186  			Stdout:     config.AttachStdout,
   187  			Stderr:     config.AttachStderr,
   188  			DetachKeys: dockerCli.ConfigFile().DetachKeys,
   189  		}
   190  
   191  		resp, errAttach := client.ContainerAttach(ctx, createResponse.ID, options)
   192  		if errAttach != nil && errAttach != httputil.ErrPersistEOF {
   193  			// ContainerAttach returns an ErrPersistEOF (connection closed)
   194  			// means server met an error and put it in Hijacked connection
   195  			// keep the error and read detailed error message from hijacked connection later
   196  			return errAttach
   197  		}
   198  		defer resp.Close()
   199  
   200  		errCh = promise.Go(func() error {
   201  			if errHijack := holdHijackedConnection(ctx, dockerCli, config.Tty, in, out, cerr, resp); errHijack != nil {
   202  				return errHijack
   203  			}
   204  			return errAttach
   205  		})
   206  	}
   207  
   208  	statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove)
   209  
   210  	//start the container
   211  	if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
   212  		// If we have holdHijackedConnection, we should notify
   213  		// holdHijackedConnection we are going to exit and wait
   214  		// to avoid the terminal are not restored.
   215  		if attach {
   216  			cancelFun()
   217  			<-errCh
   218  		}
   219  
   220  		reportError(stderr, cmdPath, err.Error(), false)
   221  		if copts.autoRemove {
   222  			// wait container to be removed
   223  			<-statusChan
   224  		}
   225  		return runStartContainerErr(err)
   226  	}
   227  
   228  	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.Out().IsTerminal() {
   229  		if err := MonitorTtySize(ctx, dockerCli, createResponse.ID, false); err != nil {
   230  			fmt.Fprintln(stderr, "Error monitoring TTY size:", err)
   231  		}
   232  	}
   233  
   234  	if errCh != nil {
   235  		if err := <-errCh; err != nil {
   236  			logrus.Debugf("Error hijack: %s", err)
   237  			return err
   238  		}
   239  	}
   240  
   241  	// Detached mode: wait for the id to be displayed and return.
   242  	if !config.AttachStdout && !config.AttachStderr {
   243  		// Detached mode
   244  		<-waitDisplayID
   245  		return nil
   246  	}
   247  
   248  	status := <-statusChan
   249  	if status != 0 {
   250  		return cli.StatusError{StatusCode: status}
   251  	}
   252  	return nil
   253  }
   254  
   255  // reportError is a utility method that prints a user-friendly message
   256  // containing the error that occurred during parsing and a suggestion to get help
   257  func reportError(stderr io.Writer, name string, str string, withHelp bool) {
   258  	if withHelp {
   259  		str += ".\nSee '" + os.Args[0] + " " + name + " --help'"
   260  	}
   261  	fmt.Fprintf(stderr, "%s: %s.\n", os.Args[0], str)
   262  }
   263  
   264  // if container start fails with 'not found'/'no such' error, return 127
   265  // if container start fails with 'permission denied' error, return 126
   266  // return 125 for generic docker daemon failures
   267  func runStartContainerErr(err error) error {
   268  	trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
   269  	statusError := cli.StatusError{StatusCode: 125}
   270  	if strings.Contains(trimmedErr, "executable file not found") ||
   271  		strings.Contains(trimmedErr, "no such file or directory") ||
   272  		strings.Contains(trimmedErr, "system cannot find the file specified") {
   273  		statusError = cli.StatusError{StatusCode: 127}
   274  	} else if strings.Contains(trimmedErr, syscall.EACCES.Error()) {
   275  		statusError = cli.StatusError{StatusCode: 126}
   276  	}
   277  
   278  	return statusError
   279  }