github.com/tsuna/docker@v1.7.0-rc3/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/signal" 13 "github.com/docker/docker/runconfig" 14 "github.com/docker/libnetwork/resolvconf/dns" 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 os.Exit(1) 61 } 62 63 if len(hostConfig.Dns) > 0 { 64 // check the DNS settings passed via --dns against 65 // localhost regexp to warn if they are trying to 66 // set a DNS to a localhost address 67 for _, dnsIP := range hostConfig.Dns { 68 if dns.IsLocalhost(dnsIP) { 69 fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) 70 break 71 } 72 } 73 } 74 if config.Image == "" { 75 cmd.Usage() 76 return nil 77 } 78 79 if !*flDetach { 80 if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil { 81 return err 82 } 83 } else { 84 if fl := cmd.Lookup("-attach"); fl != nil { 85 flAttach = fl.Value.(*opts.ListOpts) 86 if flAttach.Len() != 0 { 87 return ErrConflictAttachDetach 88 } 89 } 90 if *flAutoRemove { 91 return ErrConflictDetachAutoRemove 92 } 93 94 config.AttachStdin = false 95 config.AttachStdout = false 96 config.AttachStderr = false 97 config.StdinOnce = false 98 } 99 100 // Disable flSigProxy when in TTY mode 101 sigProxy := *flSigProxy 102 if config.Tty { 103 sigProxy = false 104 } 105 106 createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) 107 if err != nil { 108 return err 109 } 110 if sigProxy { 111 sigc := cli.forwardAllSignals(createResponse.ID) 112 defer signal.StopCatch(sigc) 113 } 114 var ( 115 waitDisplayID chan struct{} 116 errCh chan error 117 ) 118 if !config.AttachStdout && !config.AttachStderr { 119 // Make this asynchronous to allow the client to write to stdin before having to read the ID 120 waitDisplayID = make(chan struct{}) 121 go func() { 122 defer close(waitDisplayID) 123 fmt.Fprintf(cli.out, "%s\n", createResponse.ID) 124 }() 125 } 126 if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) { 127 return ErrConflictRestartPolicyAndAutoRemove 128 } 129 // We need to instantiate the chan because the select needs it. It can 130 // be closed but can't be uninitialized. 131 hijacked := make(chan io.Closer) 132 // Block the return until the chan gets closed 133 defer func() { 134 logrus.Debugf("End of CmdRun(), Waiting for hijack to finish.") 135 if _, ok := <-hijacked; ok { 136 fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)") 137 } 138 }() 139 if config.AttachStdin || config.AttachStdout || config.AttachStderr { 140 var ( 141 out, stderr io.Writer 142 in io.ReadCloser 143 v = url.Values{} 144 ) 145 v.Set("stream", "1") 146 if config.AttachStdin { 147 v.Set("stdin", "1") 148 in = cli.in 149 } 150 if config.AttachStdout { 151 v.Set("stdout", "1") 152 out = cli.out 153 } 154 if config.AttachStderr { 155 v.Set("stderr", "1") 156 if config.Tty { 157 stderr = cli.out 158 } else { 159 stderr = cli.err 160 } 161 } 162 errCh = promise.Go(func() error { 163 return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil) 164 }) 165 } else { 166 close(hijacked) 167 } 168 // Acknowledge the hijack before starting 169 select { 170 case closer := <-hijacked: 171 // Make sure that the hijack gets closed when returning (results 172 // in closing the hijack chan and freeing server's goroutines) 173 if closer != nil { 174 defer closer.Close() 175 } 176 case err := <-errCh: 177 if err != nil { 178 logrus.Debugf("Error hijack: %s", err) 179 return err 180 } 181 } 182 183 defer func() { 184 if *flAutoRemove { 185 if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil { 186 fmt.Fprintf(cli.err, "Error deleting container: %s\n", err) 187 } 188 } 189 }() 190 191 //start the container 192 if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil { 193 return err 194 } 195 196 if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { 197 if err := cli.monitorTtySize(createResponse.ID, false); err != nil { 198 fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err) 199 } 200 } 201 202 if errCh != nil { 203 if err := <-errCh; err != nil { 204 logrus.Debugf("Error hijack: %s", err) 205 return err 206 } 207 } 208 209 // Detached mode: wait for the id to be displayed and return. 210 if !config.AttachStdout && !config.AttachStderr { 211 // Detached mode 212 <-waitDisplayID 213 return nil 214 } 215 216 var status int 217 218 // Attached mode 219 if *flAutoRemove { 220 // Autoremove: wait for the container to finish, retrieve 221 // the exit code and remove the container 222 if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil { 223 return err 224 } 225 if _, status, err = getExitCode(cli, createResponse.ID); err != nil { 226 return err 227 } 228 } else { 229 // No Autoremove: Simply retrieve the exit code 230 if !config.Tty { 231 // In non-TTY mode, we can't detach, so we must wait for container exit 232 if status, err = waitForExit(cli, createResponse.ID); err != nil { 233 return err 234 } 235 } else { 236 // In TTY mode, there is a race: if the process dies too slowly, the state could 237 // be updated after the getExitCode call and result in the wrong exit code being reported 238 if _, status, err = getExitCode(cli, createResponse.ID); err != nil { 239 return err 240 } 241 } 242 } 243 if status != 0 { 244 return StatusError{StatusCode: status} 245 } 246 return nil 247 }