github.com/olljanat/moby@v1.13.1/cli/command/container/run.go (about)

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