github.com/rentongzhang/docker@v1.8.2-rc1/api/client/run.go (about)

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