github.com/squaremo/docker@v1.3.2-0.20150516120342-42cfc9554972/api/client/run.go (about)

     1  package client
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/url"
     7  	"os"
     8  
     9  	"github.com/Sirupsen/logrus"
    10  	"github.com/docker/docker/opts"
    11  	"github.com/docker/docker/pkg/promise"
    12  	"github.com/docker/docker/pkg/resolvconf/dns"
    13  	"github.com/docker/docker/pkg/signal"
    14  	"github.com/docker/docker/runconfig"
    15  )
    16  
    17  func (cid *cidFile) Close() error {
    18  	cid.file.Close()
    19  
    20  	if !cid.written {
    21  		if err := os.Remove(cid.path); err != nil {
    22  			return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
    23  		}
    24  	}
    25  
    26  	return nil
    27  }
    28  
    29  func (cid *cidFile) Write(id string) error {
    30  	if _, err := cid.file.Write([]byte(id)); err != nil {
    31  		return fmt.Errorf("Failed to write the container ID to the file: %s", err)
    32  	}
    33  	cid.written = true
    34  	return nil
    35  }
    36  
    37  // CmdRun runs a command in a new container.
    38  //
    39  // Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
    40  func (cli *DockerCli) CmdRun(args ...string) error {
    41  	cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true)
    42  
    43  	// These are flags not stored in Config/HostConfig
    44  	var (
    45  		flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
    46  		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
    47  		flSigProxy   = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
    48  		flName       = cmd.String([]string{"-name"}, "", "Assign a name to the container")
    49  		flAttach     *opts.ListOpts
    50  
    51  		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
    52  		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
    53  		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
    54  	)
    55  
    56  	config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
    57  	// just in case the Parse does not exit
    58  	if err != nil {
    59  		cmd.ReportError(err.Error(), true)
    60  	}
    61  
    62  	if len(hostConfig.Dns) > 0 {
    63  		// check the DNS settings passed via --dns against
    64  		// localhost regexp to warn if they are trying to
    65  		// set a DNS to a localhost address
    66  		for _, dnsIP := range hostConfig.Dns {
    67  			if dns.IsLocalhost(dnsIP) {
    68  				fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
    69  				break
    70  			}
    71  		}
    72  	}
    73  	if config.Image == "" {
    74  		cmd.Usage()
    75  		return nil
    76  	}
    77  
    78  	if !*flDetach {
    79  		if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
    80  			return err
    81  		}
    82  	} else {
    83  		if fl := cmd.Lookup("-attach"); fl != nil {
    84  			flAttach = fl.Value.(*opts.ListOpts)
    85  			if flAttach.Len() != 0 {
    86  				return ErrConflictAttachDetach
    87  			}
    88  		}
    89  		if *flAutoRemove {
    90  			return ErrConflictDetachAutoRemove
    91  		}
    92  
    93  		config.AttachStdin = false
    94  		config.AttachStdout = false
    95  		config.AttachStderr = false
    96  		config.StdinOnce = false
    97  	}
    98  
    99  	// Disable flSigProxy when in TTY mode
   100  	sigProxy := *flSigProxy
   101  	if config.Tty {
   102  		sigProxy = false
   103  	}
   104  
   105  	createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	if sigProxy {
   110  		sigc := cli.forwardAllSignals(createResponse.ID)
   111  		defer signal.StopCatch(sigc)
   112  	}
   113  	var (
   114  		waitDisplayID chan struct{}
   115  		errCh         chan error
   116  	)
   117  	if !config.AttachStdout && !config.AttachStderr {
   118  		// Make this asynchronous to allow the client to write to stdin before having to read the ID
   119  		waitDisplayID = make(chan struct{})
   120  		go func() {
   121  			defer close(waitDisplayID)
   122  			fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
   123  		}()
   124  	}
   125  	if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") {
   126  		return ErrConflictRestartPolicyAndAutoRemove
   127  	}
   128  	// We need to instantiate the chan because the select needs it. It can
   129  	// be closed but can't be uninitialized.
   130  	hijacked := make(chan io.Closer)
   131  	// Block the return until the chan gets closed
   132  	defer func() {
   133  		logrus.Debugf("End of CmdRun(), Waiting for hijack to finish.")
   134  		if _, ok := <-hijacked; ok {
   135  			fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
   136  		}
   137  	}()
   138  	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
   139  		var (
   140  			out, stderr io.Writer
   141  			in          io.ReadCloser
   142  			v           = url.Values{}
   143  		)
   144  		v.Set("stream", "1")
   145  		if config.AttachStdin {
   146  			v.Set("stdin", "1")
   147  			in = cli.in
   148  		}
   149  		if config.AttachStdout {
   150  			v.Set("stdout", "1")
   151  			out = cli.out
   152  		}
   153  		if config.AttachStderr {
   154  			v.Set("stderr", "1")
   155  			if config.Tty {
   156  				stderr = cli.out
   157  			} else {
   158  				stderr = cli.err
   159  			}
   160  		}
   161  		errCh = promise.Go(func() error {
   162  			return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
   163  		})
   164  	} else {
   165  		close(hijacked)
   166  	}
   167  	// Acknowledge the hijack before starting
   168  	select {
   169  	case closer := <-hijacked:
   170  		// Make sure that the hijack gets closed when returning (results
   171  		// in closing the hijack chan and freeing server's goroutines)
   172  		if closer != nil {
   173  			defer closer.Close()
   174  		}
   175  	case err := <-errCh:
   176  		if err != nil {
   177  			logrus.Debugf("Error hijack: %s", err)
   178  			return err
   179  		}
   180  	}
   181  
   182  	defer func() {
   183  		if *flAutoRemove {
   184  			if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil {
   185  				fmt.Fprintf(cli.err, "Error deleting container: %s\n", err)
   186  			}
   187  		}
   188  	}()
   189  
   190  	//start the container
   191  	if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil {
   192  		return err
   193  	}
   194  
   195  	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
   196  		if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
   197  			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
   198  		}
   199  	}
   200  
   201  	if errCh != nil {
   202  		if err := <-errCh; err != nil {
   203  			logrus.Debugf("Error hijack: %s", err)
   204  			return err
   205  		}
   206  	}
   207  
   208  	// Detached mode: wait for the id to be displayed and return.
   209  	if !config.AttachStdout && !config.AttachStderr {
   210  		// Detached mode
   211  		<-waitDisplayID
   212  		return nil
   213  	}
   214  
   215  	var status int
   216  
   217  	// Attached mode
   218  	if *flAutoRemove {
   219  		// Autoremove: wait for the container to finish, retrieve
   220  		// the exit code and remove the container
   221  		if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil {
   222  			return err
   223  		}
   224  		if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
   225  			return err
   226  		}
   227  	} else {
   228  		// No Autoremove: Simply retrieve the exit code
   229  		if !config.Tty {
   230  			// In non-TTY mode, we can't detach, so we must wait for container exit
   231  			if status, err = waitForExit(cli, createResponse.ID); err != nil {
   232  				return err
   233  			}
   234  		} else {
   235  			// In TTY mode, there is a race: if the process dies too slowly, the state could
   236  			// be updated after the getExitCode call and result in the wrong exit code being reported
   237  			if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
   238  				return err
   239  			}
   240  		}
   241  	}
   242  	if status != 0 {
   243  		return StatusError{StatusCode: status}
   244  	}
   245  	return nil
   246  }