github.com/DaoCloud/dao@v0.0.0-20161212064103-c3dbfd13ee36/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  
    12  	"golang.org/x/net/context"
    13  
    14  	"github.com/Sirupsen/logrus"
    15  	"github.com/docker/docker/api/client"
    16  	"github.com/docker/docker/cli"
    17  	opttypes "github.com/docker/docker/opts"
    18  	"github.com/docker/docker/pkg/promise"
    19  	"github.com/docker/docker/pkg/signal"
    20  	runconfigopts "github.com/docker/docker/runconfig/opts"
    21  	"github.com/docker/engine-api/types"
    22  	"github.com/docker/libnetwork/resolvconf/dns"
    23  	"github.com/spf13/cobra"
    24  	"github.com/spf13/pflag"
    25  )
    26  
    27  const (
    28  	errCmdNotFound          = "not found or does not exist"
    29  	errCmdCouldNotBeInvoked = "could not be invoked"
    30  )
    31  
    32  type runOptions struct {
    33  	autoRemove bool
    34  	detach     bool
    35  	sigProxy   bool
    36  	name       string
    37  	detachKeys string
    38  }
    39  
    40  // NewRunCommand create a new `docker run` command
    41  func NewRunCommand(dockerCli *client.DockerCli) *cobra.Command {
    42  	var opts runOptions
    43  	var copts *runconfigopts.ContainerOptions
    44  
    45  	cmd := &cobra.Command{
    46  		Use:   "run [OPTIONS] IMAGE [COMMAND] [ARG...]",
    47  		Short: "在一个新的容器中运行一条命令",
    48  		Args:  cli.RequiresMinArgs(1),
    49  		RunE: func(cmd *cobra.Command, args []string) error {
    50  			copts.Image = args[0]
    51  			if len(args) > 1 {
    52  				copts.Args = args[1:]
    53  			}
    54  			return runRun(dockerCli, cmd.Flags(), &opts, copts)
    55  		},
    56  	}
    57  
    58  	flags := cmd.Flags()
    59  	flags.SetInterspersed(false)
    60  
    61  	// These are flags not stored in Config/HostConfig
    62  	flags.BoolVar(&opts.autoRemove, "rm", false, "当容器退出时自动删除容器")
    63  	flags.BoolVarP(&opts.detach, "detach", "d", false, "在后台运行容器并打印容器ID")
    64  	flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "代理指定的信号到容器运行进程")
    65  	flags.StringVar(&opts.name, "name", "", "为容器赋予一个名称")
    66  	flags.StringVar(&opts.detachKeys, "detach-keys", "", "覆盖从容器停止附加时的按键值顺序")
    67  
    68  	// Add an explicit help that doesn't have a `-h` to prevent the conflict
    69  	// with hostname
    70  	flags.Bool("help", false, "打印用途")
    71  
    72  	client.AddTrustedFlags(flags, true)
    73  	copts = runconfigopts.AddFlags(flags)
    74  	return cmd
    75  }
    76  
    77  func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error {
    78  	stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In()
    79  	client := dockerCli.Client()
    80  	// TODO: pass this as an argument
    81  	cmdPath := "run"
    82  
    83  	var (
    84  		flAttach                              *opttypes.ListOpts
    85  		ErrConflictAttachDetach               = fmt.Errorf("选项冲突: -a and -d")
    86  		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("选项冲突: --restart and --rm")
    87  		ErrConflictDetachAutoRemove           = fmt.Errorf("选项冲突: --rm and -d")
    88  	)
    89  
    90  	config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts)
    91  
    92  	// just in case the Parse does not exit
    93  	if err != nil {
    94  		reportError(stderr, cmdPath, err.Error(), true)
    95  		return cli.StatusError{StatusCode: 125}
    96  	}
    97  
    98  	if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 {
    99  		fmt.Fprintf(stderr, "警告: 在容器上禁用OOM killer时没有设定'-m/--memory'限制将带来危险.\n")
   100  	}
   101  
   102  	if len(hostConfig.DNS) > 0 {
   103  		// check the DNS settings passed via --dns against
   104  		// localhost regexp to warn if they are trying to
   105  		// set a DNS to a localhost address
   106  		for _, dnsIP := range hostConfig.DNS {
   107  			if dns.IsLocalhost(dnsIP) {
   108  				fmt.Fprintf(stderr, "警告: 本地的DNS设定(--dns=%s)在容器内有可能失效.\n", dnsIP)
   109  				break
   110  			}
   111  		}
   112  	}
   113  
   114  	config.ArgsEscaped = false
   115  
   116  	if !opts.detach {
   117  		if err := dockerCli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
   118  			return err
   119  		}
   120  	} else {
   121  		if fl := flags.Lookup("attach"); fl != nil {
   122  			flAttach = fl.Value.(*opttypes.ListOpts)
   123  			if flAttach.Len() != 0 {
   124  				return ErrConflictAttachDetach
   125  			}
   126  		}
   127  		if opts.autoRemove {
   128  			return ErrConflictDetachAutoRemove
   129  		}
   130  
   131  		config.AttachStdin = false
   132  		config.AttachStdout = false
   133  		config.AttachStderr = false
   134  		config.StdinOnce = false
   135  	}
   136  
   137  	// Disable sigProxy when in TTY mode
   138  	if config.Tty {
   139  		opts.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] = dockerCli.GetTtySize()
   147  	}
   148  
   149  	ctx, cancelFun := context.WithCancel(context.Background())
   150  
   151  	createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name)
   152  	if err != nil {
   153  		reportError(stderr, cmdPath, err.Error(), true)
   154  		return runStartContainerErr(err)
   155  	}
   156  	if opts.sigProxy {
   157  		sigc := dockerCli.ForwardAllSignals(ctx, createResponse.ID)
   158  		defer signal.StopCatch(sigc)
   159  	}
   160  	var (
   161  		waitDisplayID chan struct{}
   162  		errCh         chan error
   163  	)
   164  	if !config.AttachStdout && !config.AttachStderr {
   165  		// Make this asynchronous to allow the client to write to stdin before having to read the ID
   166  		waitDisplayID = make(chan struct{})
   167  		go func() {
   168  			defer close(waitDisplayID)
   169  			fmt.Fprintf(stdout, "%s\n", createResponse.ID)
   170  		}()
   171  	}
   172  	if opts.autoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
   173  		return ErrConflictRestartPolicyAndAutoRemove
   174  	}
   175  	attach := config.AttachStdin || config.AttachStdout || config.AttachStderr
   176  	if attach {
   177  		var (
   178  			out, cerr io.Writer
   179  			in        io.ReadCloser
   180  		)
   181  		if config.AttachStdin {
   182  			in = stdin
   183  		}
   184  		if config.AttachStdout {
   185  			out = stdout
   186  		}
   187  		if config.AttachStderr {
   188  			if config.Tty {
   189  				cerr = stdout
   190  			} else {
   191  				cerr = stderr
   192  			}
   193  		}
   194  
   195  		if opts.detachKeys != "" {
   196  			dockerCli.ConfigFile().DetachKeys = opts.detachKeys
   197  		}
   198  
   199  		options := types.ContainerAttachOptions{
   200  			Stream:     true,
   201  			Stdin:      config.AttachStdin,
   202  			Stdout:     config.AttachStdout,
   203  			Stderr:     config.AttachStderr,
   204  			DetachKeys: dockerCli.ConfigFile().DetachKeys,
   205  		}
   206  
   207  		resp, errAttach := client.ContainerAttach(ctx, createResponse.ID, options)
   208  		if errAttach != nil && errAttach != httputil.ErrPersistEOF {
   209  			// ContainerAttach returns an ErrPersistEOF (connection closed)
   210  			// means server met an error and put it in Hijacked connection
   211  			// keep the error and read detailed error message from hijacked connection later
   212  			return errAttach
   213  		}
   214  		defer resp.Close()
   215  
   216  		errCh = promise.Go(func() error {
   217  			errHijack := dockerCli.HoldHijackedConnection(ctx, config.Tty, in, out, cerr, resp)
   218  			if errHijack == nil {
   219  				return errAttach
   220  			}
   221  			return errHijack
   222  		})
   223  	}
   224  
   225  	if opts.autoRemove {
   226  		defer func() {
   227  			// Explicitly not sharing the context as it could be "Done" (by calling cancelFun)
   228  			// and thus the container would not be removed.
   229  			if err := removeContainer(dockerCli, context.Background(), createResponse.ID, true, false, true); err != nil {
   230  				fmt.Fprintf(stderr, "%v\n", err)
   231  			}
   232  		}()
   233  	}
   234  
   235  	//start the container
   236  	if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil {
   237  		// If we have holdHijackedConnection, we should notify
   238  		// holdHijackedConnection we are going to exit and wait
   239  		// to avoid the terminal are not restored.
   240  		if attach {
   241  			cancelFun()
   242  			<-errCh
   243  		}
   244  
   245  		reportError(stderr, cmdPath, err.Error(), false)
   246  		return runStartContainerErr(err)
   247  	}
   248  
   249  	if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.IsTerminalOut() {
   250  		if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil {
   251  			fmt.Fprintf(stderr, "监视终端大小出错: %s\n", err)
   252  		}
   253  	}
   254  
   255  	if errCh != nil {
   256  		if err := <-errCh; err != nil {
   257  			logrus.Debugf("Error hijack: %s", err)
   258  			return err
   259  		}
   260  	}
   261  
   262  	// Detached mode: wait for the id to be displayed and return.
   263  	if !config.AttachStdout && !config.AttachStderr {
   264  		// Detached mode
   265  		<-waitDisplayID
   266  		return nil
   267  	}
   268  
   269  	var status int
   270  
   271  	// Attached mode
   272  	if opts.autoRemove {
   273  		// Autoremove: wait for the container to finish, retrieve
   274  		// the exit code and remove the container
   275  		if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
   276  			return runStartContainerErr(err)
   277  		}
   278  		if _, status, err = getExitCode(dockerCli, ctx, createResponse.ID); err != nil {
   279  			return err
   280  		}
   281  	} else {
   282  		// No Autoremove: Simply retrieve the exit code
   283  		if !config.Tty && hostConfig.RestartPolicy.IsNone() {
   284  			// In non-TTY mode, we can't detach, so we must wait for container exit
   285  			if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil {
   286  				return err
   287  			}
   288  		} else {
   289  			// In TTY mode, there is a race: if the process dies too slowly, the state could
   290  			// be updated after the getExitCode call and result in the wrong exit code being reported
   291  			if _, status, err = getExitCode(dockerCli, ctx, createResponse.ID); err != nil {
   292  				return err
   293  			}
   294  		}
   295  	}
   296  	if status != 0 {
   297  		return cli.StatusError{StatusCode: status}
   298  	}
   299  	return nil
   300  }
   301  
   302  // reportError is a utility method that prints a user-friendly message
   303  // containing the error that occurred during parsing and a suggestion to get help
   304  func reportError(stderr io.Writer, name string, str string, withHelp bool) {
   305  	if withHelp {
   306  		str += ".\nSee '" + os.Args[0] + " " + name + " --help'"
   307  	}
   308  	fmt.Fprintf(stderr, "%s: %s.\n", os.Args[0], str)
   309  }
   310  
   311  // if container start fails with 'not found'/'no such' error, return 127
   312  // if container start fails with 'permission denied' error, return 126
   313  // return 125 for generic docker daemon failures
   314  func runStartContainerErr(err error) error {
   315  	trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ")
   316  	statusError := cli.StatusError{StatusCode: 125}
   317  	if strings.Contains(trimmedErr, "executable file not found") ||
   318  		strings.Contains(trimmedErr, "no such file or directory") ||
   319  		strings.Contains(trimmedErr, "system cannot find the file specified") {
   320  		statusError = cli.StatusError{StatusCode: 127}
   321  	} else if strings.Contains(trimmedErr, syscall.EACCES.Error()) {
   322  		statusError = cli.StatusError{StatusCode: 126}
   323  	}
   324  
   325  	return statusError
   326  }