github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/api/client/run.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"runtime"
     8  	"strings"
     9  
    10  	"github.com/Sirupsen/logrus"
    11  	Cli "github.com/docker/docker/cli"
    12  	derr "github.com/docker/docker/errors"
    13  	"github.com/docker/docker/opts"
    14  	"github.com/docker/docker/pkg/promise"
    15  	"github.com/docker/docker/pkg/signal"
    16  	runconfigopts "github.com/docker/docker/runconfig/opts"
    17  	"github.com/docker/engine-api/types"
    18  	"github.com/docker/libnetwork/resolvconf/dns"
    19  )
    20  
    21  func (cid *cidFile) Close() error {
    22  	cid.file.Close()
    23  
    24  	if !cid.written {
    25  		if err := os.Remove(cid.path); err != nil {
    26  			return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
    27  		}
    28  	}
    29  
    30  	return nil
    31  }
    32  
    33  func (cid *cidFile) Write(id string) error {
    34  	if _, err := cid.file.Write([]byte(id)); err != nil {
    35  		return fmt.Errorf("Failed to write the container ID to the file: %s", err)
    36  	}
    37  	cid.written = true
    38  	return nil
    39  }
    40  
    41  // if container start fails with 'command not found' error, return 127
    42  // if container start fails with 'command cannot be invoked' error, return 126
    43  // return 125 for generic docker daemon failures
    44  func runStartContainerErr(err error) error {
    45  	trimmedErr := strings.Trim(err.Error(), "Error response from daemon: ")
    46  	statusError := Cli.StatusError{}
    47  	derrCmdNotFound := derr.ErrorCodeCmdNotFound.Message()
    48  	derrCouldNotInvoke := derr.ErrorCodeCmdCouldNotBeInvoked.Message()
    49  	derrNoSuchImage := derr.ErrorCodeNoSuchImageHash.Message()
    50  	derrNoSuchImageTag := derr.ErrorCodeNoSuchImageTag.Message()
    51  	switch trimmedErr {
    52  	case derrCmdNotFound:
    53  		statusError = Cli.StatusError{StatusCode: 127}
    54  	case derrCouldNotInvoke:
    55  		statusError = Cli.StatusError{StatusCode: 126}
    56  	case derrNoSuchImage, derrNoSuchImageTag:
    57  		statusError = Cli.StatusError{StatusCode: 125}
    58  	default:
    59  		statusError = Cli.StatusError{StatusCode: 125}
    60  	}
    61  	return statusError
    62  }
    63  
    64  // CmdRun runs a command in a new container.
    65  //
    66  // Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
    67  func (cli *DockerCli) CmdRun(args ...string) error {
    68  	cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
    69  	addTrustedFlags(cmd, true)
    70  
    71  	// These are flags not stored in Config/HostConfig
    72  	var (
    73  		flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
    74  		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
    75  		flSigProxy   = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
    76  		flName       = cmd.String([]string{"-name"}, "", "Assign a name to the container")
    77  		flDetachKeys = cmd.String([]string{"-detach-keys"}, "", "Override the key sequence for detaching a container")
    78  		flAttach     *opts.ListOpts
    79  
    80  		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
    81  		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
    82  		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
    83  	)
    84  
    85  	config, hostConfig, networkingConfig, cmd, err := runconfigopts.Parse(cmd, args)
    86  
    87  	// just in case the Parse does not exit
    88  	if err != nil {
    89  		cmd.ReportError(err.Error(), true)
    90  		os.Exit(125)
    91  	}
    92  
    93  	if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
    94  		fmt.Fprintf(cli.err, "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(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
   104  				break
   105  			}
   106  		}
   107  	}
   108  	if config.Image == "" {
   109  		cmd.Usage()
   110  		return nil
   111  	}
   112  
   113  	config.ArgsEscaped = false
   114  
   115  	if !*flDetach {
   116  		if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
   117  			return err
   118  		}
   119  	} else {
   120  		if fl := cmd.Lookup("-attach"); fl != nil {
   121  			flAttach = fl.Value.(*opts.ListOpts)
   122  			if flAttach.Len() != 0 {
   123  				return ErrConflictAttachDetach
   124  			}
   125  		}
   126  		if *flAutoRemove {
   127  			return ErrConflictDetachAutoRemove
   128  		}
   129  
   130  		config.AttachStdin = false
   131  		config.AttachStdout = false
   132  		config.AttachStderr = false
   133  		config.StdinOnce = false
   134  	}
   135  
   136  	// Disable flSigProxy when in TTY mode
   137  	sigProxy := *flSigProxy
   138  	if config.Tty {
   139  		sigProxy = false
   140  	}
   141  
   142  	// Telling the Windows daemon the initial size of the tty during start makes
   143  	// a far better user experience rather than relying on subsequent resizes
   144  	// to cause things to catch up.
   145  	if runtime.GOOS == "windows" {
   146  		hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
   147  	}
   148  
   149  	createResponse, err := cli.createContainer(config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, *flName)
   150  	if err != nil {
   151  		cmd.ReportError(err.Error(), true)
   152  		return runStartContainerErr(err)
   153  	}
   154  	if sigProxy {
   155  		sigc := cli.forwardAllSignals(createResponse.ID)
   156  		defer signal.StopCatch(sigc)
   157  	}
   158  	var (
   159  		waitDisplayID chan struct{}
   160  		errCh         chan error
   161  	)
   162  	if !config.AttachStdout && !config.AttachStderr {
   163  		// Make this asynchronous to allow the client to write to stdin before having to read the ID
   164  		waitDisplayID = make(chan struct{})
   165  		go func() {
   166  			defer close(waitDisplayID)
   167  			fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
   168  		}()
   169  	}
   170  	if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
   171  		return ErrConflictRestartPolicyAndAutoRemove
   172  	}
   173  
   174  	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
   175  		var (
   176  			out, stderr io.Writer
   177  			in          io.ReadCloser
   178  		)
   179  		if config.AttachStdin {
   180  			in = cli.in
   181  		}
   182  		if config.AttachStdout {
   183  			out = cli.out
   184  		}
   185  		if config.AttachStderr {
   186  			if config.Tty {
   187  				stderr = cli.out
   188  			} else {
   189  				stderr = cli.err
   190  			}
   191  		}
   192  
   193  		if *flDetachKeys != "" {
   194  			cli.configFile.DetachKeys = *flDetachKeys
   195  		}
   196  
   197  		options := types.ContainerAttachOptions{
   198  			ContainerID: createResponse.ID,
   199  			Stream:      true,
   200  			Stdin:       config.AttachStdin,
   201  			Stdout:      config.AttachStdout,
   202  			Stderr:      config.AttachStderr,
   203  			DetachKeys:  cli.configFile.DetachKeys,
   204  		}
   205  
   206  		resp, err := cli.client.ContainerAttach(options)
   207  		if err != nil {
   208  			return err
   209  		}
   210  		if in != nil && config.Tty {
   211  			if err := cli.setRawTerminal(); err != nil {
   212  				return err
   213  			}
   214  			defer cli.restoreTerminal(in)
   215  		}
   216  		errCh = promise.Go(func() error {
   217  			return cli.holdHijackedConnection(config.Tty, in, out, stderr, resp)
   218  		})
   219  	}
   220  
   221  	defer func() {
   222  		if *flAutoRemove {
   223  			options := types.ContainerRemoveOptions{
   224  				ContainerID:   createResponse.ID,
   225  				RemoveVolumes: true,
   226  			}
   227  			if err := cli.client.ContainerRemove(options); err != nil {
   228  				fmt.Fprintf(cli.err, "Error deleting container: %s\n", err)
   229  			}
   230  		}
   231  	}()
   232  
   233  	//start the container
   234  	if err := cli.client.ContainerStart(createResponse.ID); err != nil {
   235  		cmd.ReportError(err.Error(), false)
   236  		return runStartContainerErr(err)
   237  	}
   238  
   239  	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
   240  		if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
   241  			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
   242  		}
   243  	}
   244  
   245  	if errCh != nil {
   246  		if err := <-errCh; err != nil {
   247  			logrus.Debugf("Error hijack: %s", err)
   248  			return err
   249  		}
   250  	}
   251  
   252  	// Detached mode: wait for the id to be displayed and return.
   253  	if !config.AttachStdout && !config.AttachStderr {
   254  		// Detached mode
   255  		<-waitDisplayID
   256  		return nil
   257  	}
   258  
   259  	var status int
   260  
   261  	// Attached mode
   262  	if *flAutoRemove {
   263  		// Autoremove: wait for the container to finish, retrieve
   264  		// the exit code and remove the container
   265  		if status, err = cli.client.ContainerWait(createResponse.ID); err != nil {
   266  			return runStartContainerErr(err)
   267  		}
   268  		if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
   269  			return err
   270  		}
   271  	} else {
   272  		// No Autoremove: Simply retrieve the exit code
   273  		if !config.Tty {
   274  			// In non-TTY mode, we can't detach, so we must wait for container exit
   275  			if status, err = cli.client.ContainerWait(createResponse.ID); err != nil {
   276  				return err
   277  			}
   278  		} else {
   279  			// In TTY mode, there is a race: if the process dies too slowly, the state could
   280  			// be updated after the getExitCode call and result in the wrong exit code being reported
   281  			if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
   282  				return err
   283  			}
   284  		}
   285  	}
   286  	if status != 0 {
   287  		return Cli.StatusError{StatusCode: status}
   288  	}
   289  	return nil
   290  }