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