github.com/noxiouz/docker@v0.7.3-0.20160629055221-3d231c78e8c5/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 type runOptions struct { 28 autoRemove bool 29 detach bool 30 sigProxy bool 31 name string 32 detachKeys string 33 } 34 35 // NewRunCommand create a new `docker run` command 36 func NewRunCommand(dockerCli *client.DockerCli) *cobra.Command { 37 var opts runOptions 38 var copts *runconfigopts.ContainerOptions 39 40 cmd := &cobra.Command{ 41 Use: "run [OPTIONS] IMAGE [COMMAND] [ARG...]", 42 Short: "Run a command in a new container", 43 Args: cli.RequiresMinArgs(1), 44 RunE: func(cmd *cobra.Command, args []string) error { 45 copts.Image = args[0] 46 if len(args) > 1 { 47 copts.Args = args[1:] 48 } 49 return runRun(dockerCli, cmd.Flags(), &opts, copts) 50 }, 51 } 52 cmd.SetFlagErrorFunc(flagErrorFunc) 53 54 flags := cmd.Flags() 55 flags.SetInterspersed(false) 56 57 // These are flags not stored in Config/HostConfig 58 flags.BoolVar(&opts.autoRemove, "rm", false, "Automatically remove the container when it exits") 59 flags.BoolVarP(&opts.detach, "detach", "d", false, "Run container in background and print container ID") 60 flags.BoolVar(&opts.sigProxy, "sig-proxy", true, "Proxy received signals to the process") 61 flags.StringVar(&opts.name, "name", "", "Assign a name to the container") 62 flags.StringVar(&opts.detachKeys, "detach-keys", "", "Override the key sequence for detaching a container") 63 64 // Add an explicit help that doesn't have a `-h` to prevent the conflict 65 // with hostname 66 flags.Bool("help", false, "Print usage") 67 68 client.AddTrustedFlags(flags, true) 69 copts = runconfigopts.AddFlags(flags) 70 return cmd 71 } 72 73 func flagErrorFunc(cmd *cobra.Command, err error) error { 74 return cli.StatusError{ 75 Status: cli.FlagErrorFunc(cmd, err).Error(), 76 StatusCode: 125, 77 } 78 } 79 80 func runRun(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *runconfigopts.ContainerOptions) error { 81 stdout, stderr, stdin := dockerCli.Out(), dockerCli.Err(), dockerCli.In() 82 client := dockerCli.Client() 83 // TODO: pass this as an argument 84 cmdPath := "run" 85 86 var ( 87 flAttach *opttypes.ListOpts 88 ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") 89 ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") 90 ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") 91 ) 92 93 config, hostConfig, networkingConfig, err := runconfigopts.Parse(flags, copts) 94 95 // just in case the Parse does not exit 96 if err != nil { 97 reportError(stderr, cmdPath, err.Error(), true) 98 return cli.StatusError{StatusCode: 125} 99 } 100 101 if hostConfig.OomKillDisable != nil && *hostConfig.OomKillDisable && hostConfig.Memory == 0 { 102 fmt.Fprintf(stderr, "WARNING: Disabling the OOM killer on containers without setting a '-m/--memory' limit may be dangerous.\n") 103 } 104 105 if len(hostConfig.DNS) > 0 { 106 // check the DNS settings passed via --dns against 107 // localhost regexp to warn if they are trying to 108 // set a DNS to a localhost address 109 for _, dnsIP := range hostConfig.DNS { 110 if dns.IsLocalhost(dnsIP) { 111 fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) 112 break 113 } 114 } 115 } 116 117 config.ArgsEscaped = false 118 119 if !opts.detach { 120 if err := dockerCli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil { 121 return err 122 } 123 } else { 124 if fl := flags.Lookup("attach"); fl != nil { 125 flAttach = fl.Value.(*opttypes.ListOpts) 126 if flAttach.Len() != 0 { 127 return ErrConflictAttachDetach 128 } 129 } 130 if opts.autoRemove { 131 return ErrConflictDetachAutoRemove 132 } 133 134 config.AttachStdin = false 135 config.AttachStdout = false 136 config.AttachStderr = false 137 config.StdinOnce = false 138 } 139 140 // Disable sigProxy when in TTY mode 141 if config.Tty { 142 opts.sigProxy = false 143 } 144 145 // Telling the Windows daemon the initial size of the tty during start makes 146 // a far better user experience rather than relying on subsequent resizes 147 // to cause things to catch up. 148 if runtime.GOOS == "windows" { 149 hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = dockerCli.GetTtySize() 150 } 151 152 ctx, cancelFun := context.WithCancel(context.Background()) 153 154 createResponse, err := createContainer(ctx, dockerCli, config, hostConfig, networkingConfig, hostConfig.ContainerIDFile, opts.name) 155 if err != nil { 156 reportError(stderr, cmdPath, err.Error(), true) 157 return runStartContainerErr(err) 158 } 159 if opts.sigProxy { 160 sigc := dockerCli.ForwardAllSignals(ctx, createResponse.ID) 161 defer signal.StopCatch(sigc) 162 } 163 var ( 164 waitDisplayID chan struct{} 165 errCh chan error 166 ) 167 if !config.AttachStdout && !config.AttachStderr { 168 // Make this asynchronous to allow the client to write to stdin before having to read the ID 169 waitDisplayID = make(chan struct{}) 170 go func() { 171 defer close(waitDisplayID) 172 fmt.Fprintf(stdout, "%s\n", createResponse.ID) 173 }() 174 } 175 if opts.autoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) { 176 return ErrConflictRestartPolicyAndAutoRemove 177 } 178 attach := config.AttachStdin || config.AttachStdout || config.AttachStderr 179 if attach { 180 var ( 181 out, cerr io.Writer 182 in io.ReadCloser 183 ) 184 if config.AttachStdin { 185 in = stdin 186 } 187 if config.AttachStdout { 188 out = stdout 189 } 190 if config.AttachStderr { 191 if config.Tty { 192 cerr = stdout 193 } else { 194 cerr = stderr 195 } 196 } 197 198 if opts.detachKeys != "" { 199 dockerCli.ConfigFile().DetachKeys = opts.detachKeys 200 } 201 202 options := types.ContainerAttachOptions{ 203 Stream: true, 204 Stdin: config.AttachStdin, 205 Stdout: config.AttachStdout, 206 Stderr: config.AttachStderr, 207 DetachKeys: dockerCli.ConfigFile().DetachKeys, 208 } 209 210 resp, errAttach := client.ContainerAttach(ctx, createResponse.ID, options) 211 if errAttach != nil && errAttach != httputil.ErrPersistEOF { 212 // ContainerAttach returns an ErrPersistEOF (connection closed) 213 // means server met an error and put it in Hijacked connection 214 // keep the error and read detailed error message from hijacked connection later 215 return errAttach 216 } 217 defer resp.Close() 218 219 errCh = promise.Go(func() error { 220 errHijack := dockerCli.HoldHijackedConnection(ctx, config.Tty, in, out, cerr, resp) 221 if errHijack == nil { 222 return errAttach 223 } 224 return errHijack 225 }) 226 } 227 228 if opts.autoRemove { 229 defer func() { 230 // Explicitly not sharing the context as it could be "Done" (by calling cancelFun) 231 // and thus the container would not be removed. 232 if err := removeContainer(dockerCli, context.Background(), createResponse.ID, true, false, true); err != nil { 233 fmt.Fprintf(stderr, "%v\n", err) 234 } 235 }() 236 } 237 238 //start the container 239 if err := client.ContainerStart(ctx, createResponse.ID, types.ContainerStartOptions{}); err != nil { 240 // If we have holdHijackedConnection, we should notify 241 // holdHijackedConnection we are going to exit and wait 242 // to avoid the terminal are not restored. 243 if attach { 244 cancelFun() 245 <-errCh 246 } 247 248 reportError(stderr, cmdPath, err.Error(), false) 249 return runStartContainerErr(err) 250 } 251 252 if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && dockerCli.IsTerminalOut() { 253 if err := dockerCli.MonitorTtySize(ctx, createResponse.ID, false); err != nil { 254 fmt.Fprintf(stderr, "Error monitoring TTY size: %s\n", err) 255 } 256 } 257 258 if errCh != nil { 259 if err := <-errCh; err != nil { 260 logrus.Debugf("Error hijack: %s", err) 261 return err 262 } 263 } 264 265 // Detached mode: wait for the id to be displayed and return. 266 if !config.AttachStdout && !config.AttachStderr { 267 // Detached mode 268 <-waitDisplayID 269 return nil 270 } 271 272 var status int 273 274 // Attached mode 275 if opts.autoRemove { 276 // Autoremove: wait for the container to finish, retrieve 277 // the exit code and remove the container 278 if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil { 279 return runStartContainerErr(err) 280 } 281 if _, status, err = getExitCode(dockerCli, ctx, createResponse.ID); err != nil { 282 return err 283 } 284 } else { 285 // No Autoremove: Simply retrieve the exit code 286 if !config.Tty && hostConfig.RestartPolicy.IsNone() { 287 // In non-TTY mode, we can't detach, so we must wait for container exit 288 if status, err = client.ContainerWait(ctx, createResponse.ID); err != nil { 289 return err 290 } 291 } else { 292 // In TTY mode, there is a race: if the process dies too slowly, the state could 293 // be updated after the getExitCode call and result in the wrong exit code being reported 294 if _, status, err = getExitCode(dockerCli, ctx, createResponse.ID); err != nil { 295 return err 296 } 297 } 298 } 299 if status != 0 { 300 return cli.StatusError{StatusCode: status} 301 } 302 return nil 303 } 304 305 // reportError is a utility method that prints a user-friendly message 306 // containing the error that occurred during parsing and a suggestion to get help 307 func reportError(stderr io.Writer, name string, str string, withHelp bool) { 308 if withHelp { 309 str += ".\nSee '" + os.Args[0] + " " + name + " --help'" 310 } 311 fmt.Fprintf(stderr, "%s: %s.\n", os.Args[0], str) 312 } 313 314 // if container start fails with 'not found'/'no such' error, return 127 315 // if container start fails with 'permission denied' error, return 126 316 // return 125 for generic docker daemon failures 317 func runStartContainerErr(err error) error { 318 trimmedErr := strings.TrimPrefix(err.Error(), "Error response from daemon: ") 319 statusError := cli.StatusError{StatusCode: 125} 320 if strings.Contains(trimmedErr, "executable file not found") || 321 strings.Contains(trimmedErr, "no such file or directory") || 322 strings.Contains(trimmedErr, "system cannot find the file specified") { 323 statusError = cli.StatusError{StatusCode: 127} 324 } else if strings.Contains(trimmedErr, syscall.EACCES.Error()) { 325 statusError = cli.StatusError{StatusCode: 126} 326 } 327 328 return statusError 329 }