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