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