github.com/walkingsparrow/docker@v1.4.2-0.20151218153551-b708a2249bfa/api/client/run.go (about) 1 package client 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "runtime" 8 "strings" 9 10 "github.com/Sirupsen/logrus" 11 "github.com/docker/docker/api/types" 12 Cli "github.com/docker/docker/cli" 13 derr "github.com/docker/docker/errors" 14 "github.com/docker/docker/opts" 15 "github.com/docker/docker/pkg/promise" 16 "github.com/docker/docker/pkg/signal" 17 "github.com/docker/docker/runconfig" 18 "github.com/docker/libnetwork/resolvconf/dns" 19 ) 20 21 func (cid *cidFile) Close() error { 22 cid.file.Close() 23 24 if !cid.written { 25 if err := os.Remove(cid.path); err != nil { 26 return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err) 27 } 28 } 29 30 return nil 31 } 32 33 func (cid *cidFile) Write(id string) error { 34 if _, err := cid.file.Write([]byte(id)); err != nil { 35 return fmt.Errorf("Failed to write the container ID to the file: %s", err) 36 } 37 cid.written = true 38 return nil 39 } 40 41 // if container start fails with 'command not found' error, return 127 42 // if container start fails with 'command cannot be invoked' error, return 126 43 // return 125 for generic docker daemon failures 44 func runStartContainerErr(err error) error { 45 trimmedErr := strings.Trim(err.Error(), "Error response from daemon: ") 46 statusError := Cli.StatusError{} 47 derrCmdNotFound := derr.ErrorCodeCmdNotFound.Message() 48 derrCouldNotInvoke := derr.ErrorCodeCmdCouldNotBeInvoked.Message() 49 derrNoSuchImage := derr.ErrorCodeNoSuchImageHash.Message() 50 derrNoSuchImageTag := derr.ErrorCodeNoSuchImageTag.Message() 51 switch trimmedErr { 52 case derrCmdNotFound: 53 statusError = Cli.StatusError{StatusCode: 127} 54 case derrCouldNotInvoke: 55 statusError = Cli.StatusError{StatusCode: 126} 56 case derrNoSuchImage, derrNoSuchImageTag: 57 statusError = Cli.StatusError{StatusCode: 125} 58 default: 59 statusError = Cli.StatusError{StatusCode: 125} 60 } 61 return statusError 62 } 63 64 // CmdRun runs a command in a new container. 65 // 66 // Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 67 func (cli *DockerCli) CmdRun(args ...string) error { 68 cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true) 69 addTrustedFlags(cmd, true) 70 71 // These are flags not stored in Config/HostConfig 72 var ( 73 flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits") 74 flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID") 75 flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process") 76 flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") 77 flAttach *opts.ListOpts 78 79 ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") 80 ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") 81 ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") 82 ) 83 84 config, hostConfig, cmd, err := runconfig.Parse(cmd, args) 85 // just in case the Parse does not exit 86 if err != nil { 87 cmd.ReportError(err.Error(), true) 88 os.Exit(125) 89 } 90 91 if hostConfig.OomKillDisable && hostConfig.Memory == 0 { 92 fmt.Fprintf(cli.err, "WARNING: Dangerous only disable the OOM Killer on containers but not set the '-m/--memory' option\n") 93 } 94 95 if len(hostConfig.DNS) > 0 { 96 // check the DNS settings passed via --dns against 97 // localhost regexp to warn if they are trying to 98 // set a DNS to a localhost address 99 for _, dnsIP := range hostConfig.DNS { 100 if dns.IsLocalhost(dnsIP) { 101 fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) 102 break 103 } 104 } 105 } 106 if config.Image == "" { 107 cmd.Usage() 108 return nil 109 } 110 111 config.ArgsEscaped = false 112 113 if !*flDetach { 114 if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil { 115 return err 116 } 117 } else { 118 if fl := cmd.Lookup("-attach"); fl != nil { 119 flAttach = fl.Value.(*opts.ListOpts) 120 if flAttach.Len() != 0 { 121 return ErrConflictAttachDetach 122 } 123 } 124 if *flAutoRemove { 125 return ErrConflictDetachAutoRemove 126 } 127 128 config.AttachStdin = false 129 config.AttachStdout = false 130 config.AttachStderr = false 131 config.StdinOnce = false 132 } 133 134 // Disable flSigProxy when in TTY mode 135 sigProxy := *flSigProxy 136 if config.Tty { 137 sigProxy = false 138 } 139 140 // Telling the Windows daemon the initial size of the tty during start makes 141 // a far better user experience rather than relying on subsequent resizes 142 // to cause things to catch up. 143 if runtime.GOOS == "windows" { 144 hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize() 145 } 146 147 createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) 148 if err != nil { 149 cmd.ReportError(err.Error(), true) 150 return runStartContainerErr(err) 151 } 152 if sigProxy { 153 sigc := cli.forwardAllSignals(createResponse.ID) 154 defer signal.StopCatch(sigc) 155 } 156 var ( 157 waitDisplayID chan struct{} 158 errCh chan error 159 ) 160 if !config.AttachStdout && !config.AttachStderr { 161 // Make this asynchronous to allow the client to write to stdin before having to read the ID 162 waitDisplayID = make(chan struct{}) 163 go func() { 164 defer close(waitDisplayID) 165 fmt.Fprintf(cli.out, "%s\n", createResponse.ID) 166 }() 167 } 168 if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) { 169 return ErrConflictRestartPolicyAndAutoRemove 170 } 171 172 if config.AttachStdin || config.AttachStdout || config.AttachStderr { 173 var ( 174 out, stderr io.Writer 175 in io.ReadCloser 176 ) 177 if config.AttachStdin { 178 in = cli.in 179 } 180 if config.AttachStdout { 181 out = cli.out 182 } 183 if config.AttachStderr { 184 if config.Tty { 185 stderr = cli.out 186 } else { 187 stderr = cli.err 188 } 189 } 190 191 options := types.ContainerAttachOptions{ 192 ContainerID: createResponse.ID, 193 Stream: true, 194 Stdin: config.AttachStdin, 195 Stdout: config.AttachStdout, 196 Stderr: config.AttachStderr, 197 } 198 199 resp, err := cli.client.ContainerAttach(options) 200 if err != nil { 201 return err 202 } 203 errCh = promise.Go(func() error { 204 return cli.holdHijackedConnection(config.Tty, in, out, stderr, resp) 205 }) 206 } 207 208 defer func() { 209 if *flAutoRemove { 210 options := types.ContainerRemoveOptions{ 211 ContainerID: createResponse.ID, 212 RemoveVolumes: true, 213 } 214 if err := cli.client.ContainerRemove(options); err != nil { 215 fmt.Fprintf(cli.err, "Error deleting container: %s\n", err) 216 } 217 } 218 }() 219 220 //start the container 221 if err := cli.client.ContainerStart(createResponse.ID); err != nil { 222 cmd.ReportError(err.Error(), false) 223 return runStartContainerErr(err) 224 } 225 226 if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { 227 if err := cli.monitorTtySize(createResponse.ID, false); err != nil { 228 fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) 229 } 230 } 231 232 if errCh != nil { 233 if err := <-errCh; err != nil { 234 logrus.Debugf("Error hijack: %s", err) 235 return err 236 } 237 } 238 239 // Detached mode: wait for the id to be displayed and return. 240 if !config.AttachStdout && !config.AttachStderr { 241 // Detached mode 242 <-waitDisplayID 243 return nil 244 } 245 246 var status int 247 248 // Attached mode 249 if *flAutoRemove { 250 // Autoremove: wait for the container to finish, retrieve 251 // the exit code and remove the container 252 if status, err = cli.client.ContainerWait(createResponse.ID); err != nil { 253 return runStartContainerErr(err) 254 } 255 if _, status, err = getExitCode(cli, createResponse.ID); err != nil { 256 return err 257 } 258 } else { 259 // No Autoremove: Simply retrieve the exit code 260 if !config.Tty { 261 // In non-TTY mode, we can't detach, so we must wait for container exit 262 if status, err = cli.client.ContainerWait(createResponse.ID); err != nil { 263 return err 264 } 265 } else { 266 // In TTY mode, there is a race: if the process dies too slowly, the state could 267 // be updated after the getExitCode call and result in the wrong exit code being reported 268 if _, status, err = getExitCode(cli, createResponse.ID); err != nil { 269 return err 270 } 271 } 272 } 273 if status != 0 { 274 return Cli.StatusError{StatusCode: status} 275 } 276 return nil 277 }