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 }