github.com/circular-dark/docker@v1.7.0/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/signal"
    13  	"github.com/docker/docker/runconfig"
    14  	"github.com/docker/libnetwork/resolvconf/dns"
    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  		os.Exit(1)
    61  	}
    62  
    63  	if len(hostConfig.Dns) > 0 {
    64  		// check the DNS settings passed via --dns against
    65  		// localhost regexp to warn if they are trying to
    66  		// set a DNS to a localhost address
    67  		for _, dnsIP := range hostConfig.Dns {
    68  			if dns.IsLocalhost(dnsIP) {
    69  				fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
    70  				break
    71  			}
    72  		}
    73  	}
    74  	if config.Image == "" {
    75  		cmd.Usage()
    76  		return nil
    77  	}
    78  
    79  	if !*flDetach {
    80  		if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
    81  			return err
    82  		}
    83  	} else {
    84  		if fl := cmd.Lookup("-attach"); fl != nil {
    85  			flAttach = fl.Value.(*opts.ListOpts)
    86  			if flAttach.Len() != 0 {
    87  				return ErrConflictAttachDetach
    88  			}
    89  		}
    90  		if *flAutoRemove {
    91  			return ErrConflictDetachAutoRemove
    92  		}
    93  
    94  		config.AttachStdin = false
    95  		config.AttachStdout = false
    96  		config.AttachStderr = false
    97  		config.StdinOnce = false
    98  	}
    99  
   100  	// Disable flSigProxy when in TTY mode
   101  	sigProxy := *flSigProxy
   102  	if config.Tty {
   103  		sigProxy = false
   104  	}
   105  
   106  	createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	if sigProxy {
   111  		sigc := cli.forwardAllSignals(createResponse.ID)
   112  		defer signal.StopCatch(sigc)
   113  	}
   114  	var (
   115  		waitDisplayID chan struct{}
   116  		errCh         chan error
   117  	)
   118  	if !config.AttachStdout && !config.AttachStderr {
   119  		// Make this asynchronous to allow the client to write to stdin before having to read the ID
   120  		waitDisplayID = make(chan struct{})
   121  		go func() {
   122  			defer close(waitDisplayID)
   123  			fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
   124  		}()
   125  	}
   126  	if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
   127  		return ErrConflictRestartPolicyAndAutoRemove
   128  	}
   129  	// We need to instantiate the chan because the select needs it. It can
   130  	// be closed but can't be uninitialized.
   131  	hijacked := make(chan io.Closer)
   132  	// Block the return until the chan gets closed
   133  	defer func() {
   134  		logrus.Debugf("End of CmdRun(), Waiting for hijack to finish.")
   135  		if _, ok := <-hijacked; ok {
   136  			fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
   137  		}
   138  	}()
   139  	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
   140  		var (
   141  			out, stderr io.Writer
   142  			in          io.ReadCloser
   143  			v           = url.Values{}
   144  		)
   145  		v.Set("stream", "1")
   146  		if config.AttachStdin {
   147  			v.Set("stdin", "1")
   148  			in = cli.in
   149  		}
   150  		if config.AttachStdout {
   151  			v.Set("stdout", "1")
   152  			out = cli.out
   153  		}
   154  		if config.AttachStderr {
   155  			v.Set("stderr", "1")
   156  			if config.Tty {
   157  				stderr = cli.out
   158  			} else {
   159  				stderr = cli.err
   160  			}
   161  		}
   162  		errCh = promise.Go(func() error {
   163  			return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
   164  		})
   165  	} else {
   166  		close(hijacked)
   167  	}
   168  	// Acknowledge the hijack before starting
   169  	select {
   170  	case closer := <-hijacked:
   171  		// Make sure that the hijack gets closed when returning (results
   172  		// in closing the hijack chan and freeing server's goroutines)
   173  		if closer != nil {
   174  			defer closer.Close()
   175  		}
   176  	case err := <-errCh:
   177  		if err != nil {
   178  			logrus.Debugf("Error hijack: %s", err)
   179  			return err
   180  		}
   181  	}
   182  
   183  	defer func() {
   184  		if *flAutoRemove {
   185  			if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil {
   186  				fmt.Fprintf(cli.err, "Error deleting container: %s\n", err)
   187  			}
   188  		}
   189  	}()
   190  
   191  	//start the container
   192  	if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil {
   193  		return err
   194  	}
   195  
   196  	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
   197  		if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
   198  			fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
   199  		}
   200  	}
   201  
   202  	if errCh != nil {
   203  		if err := <-errCh; err != nil {
   204  			logrus.Debugf("Error hijack: %s", err)
   205  			return err
   206  		}
   207  	}
   208  
   209  	// Detached mode: wait for the id to be displayed and return.
   210  	if !config.AttachStdout && !config.AttachStderr {
   211  		// Detached mode
   212  		<-waitDisplayID
   213  		return nil
   214  	}
   215  
   216  	var status int
   217  
   218  	// Attached mode
   219  	if *flAutoRemove {
   220  		// Autoremove: wait for the container to finish, retrieve
   221  		// the exit code and remove the container
   222  		if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil {
   223  			return err
   224  		}
   225  		if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
   226  			return err
   227  		}
   228  	} else {
   229  		// No Autoremove: Simply retrieve the exit code
   230  		if !config.Tty {
   231  			// In non-TTY mode, we can't detach, so we must wait for container exit
   232  			if status, err = waitForExit(cli, createResponse.ID); err != nil {
   233  				return err
   234  			}
   235  		} else {
   236  			// In TTY mode, there is a race: if the process dies too slowly, the state could
   237  			// be updated after the getExitCode call and result in the wrong exit code being reported
   238  			if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
   239  				return err
   240  			}
   241  		}
   242  	}
   243  	if status != 0 {
   244  		return StatusError{StatusCode: status}
   245  	}
   246  	return nil
   247  }