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