Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)
| ... | ... |
@@ -653,7 +653,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
| 653 | 653 |
|
| 654 | 654 |
if *openStdin || *attach {
|
| 655 | 655 |
if tty && cli.isTerminal {
|
| 656 |
- if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
| 656 |
+ if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
|
| 657 | 657 |
log.Errorf("Error monitoring TTY size: %s", err)
|
| 658 | 658 |
} |
| 659 | 659 |
} |
| ... | ... |
@@ -1805,7 +1805,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
| 1805 | 1805 |
) |
| 1806 | 1806 |
|
| 1807 | 1807 |
if tty && cli.isTerminal {
|
| 1808 |
- if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
|
| 1808 |
+ if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil {
|
|
| 1809 | 1809 |
log.Debugf("Error monitoring TTY size: %s", err)
|
| 1810 | 1810 |
} |
| 1811 | 1811 |
} |
| ... | ... |
@@ -2136,7 +2136,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 2136 | 2136 |
} |
| 2137 | 2137 |
|
| 2138 | 2138 |
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal {
|
| 2139 |
- if err := cli.monitorTtySize(runResult.Get("Id")); err != nil {
|
|
| 2139 |
+ if err := cli.monitorTtySize(runResult.Get("Id"), false); err != nil {
|
|
| 2140 | 2140 |
log.Errorf("Error monitoring TTY size: %s", err)
|
| 2141 | 2141 |
} |
| 2142 | 2142 |
} |
| ... | ... |
@@ -2299,3 +2299,101 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
|
| 2299 | 2299 |
} |
| 2300 | 2300 |
return nil |
| 2301 | 2301 |
} |
| 2302 |
+ |
|
| 2303 |
+func (cli *DockerCli) CmdExec(args ...string) error {
|
|
| 2304 |
+ cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in an existing container")
|
|
| 2305 |
+ |
|
| 2306 |
+ execConfig, err := runconfig.ParseExec(cmd, args) |
|
| 2307 |
+ if err != nil {
|
|
| 2308 |
+ return err |
|
| 2309 |
+ } |
|
| 2310 |
+ if execConfig.Container == "" {
|
|
| 2311 |
+ cmd.Usage() |
|
| 2312 |
+ return nil |
|
| 2313 |
+ } |
|
| 2314 |
+ |
|
| 2315 |
+ stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false)
|
|
| 2316 |
+ if err != nil {
|
|
| 2317 |
+ return err |
|
| 2318 |
+ } |
|
| 2319 |
+ |
|
| 2320 |
+ var execResult engine.Env |
|
| 2321 |
+ if err := execResult.Decode(stream); err != nil {
|
|
| 2322 |
+ return err |
|
| 2323 |
+ } |
|
| 2324 |
+ |
|
| 2325 |
+ execID := execResult.Get("Id")
|
|
| 2326 |
+ |
|
| 2327 |
+ if execID == "" {
|
|
| 2328 |
+ fmt.Fprintf(cli.out, "exec ID empty") |
|
| 2329 |
+ return nil |
|
| 2330 |
+ } |
|
| 2331 |
+ |
|
| 2332 |
+ if execConfig.Detach {
|
|
| 2333 |
+ if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil {
|
|
| 2334 |
+ return err |
|
| 2335 |
+ } |
|
| 2336 |
+ return nil |
|
| 2337 |
+ } |
|
| 2338 |
+ |
|
| 2339 |
+ // Interactive exec requested. |
|
| 2340 |
+ var ( |
|
| 2341 |
+ out, stderr io.Writer |
|
| 2342 |
+ in io.ReadCloser |
|
| 2343 |
+ hijacked = make(chan io.Closer) |
|
| 2344 |
+ errCh chan error |
|
| 2345 |
+ ) |
|
| 2346 |
+ |
|
| 2347 |
+ // Block the return until the chan gets closed |
|
| 2348 |
+ defer func() {
|
|
| 2349 |
+ log.Debugf("End of CmdExec(), Waiting for hijack to finish.")
|
|
| 2350 |
+ if _, ok := <-hijacked; ok {
|
|
| 2351 |
+ log.Errorf("Hijack did not finish (chan still open)")
|
|
| 2352 |
+ } |
|
| 2353 |
+ }() |
|
| 2354 |
+ |
|
| 2355 |
+ if execConfig.AttachStdin {
|
|
| 2356 |
+ in = cli.in |
|
| 2357 |
+ } |
|
| 2358 |
+ if execConfig.AttachStdout {
|
|
| 2359 |
+ out = cli.out |
|
| 2360 |
+ } |
|
| 2361 |
+ if execConfig.AttachStderr {
|
|
| 2362 |
+ if execConfig.Tty {
|
|
| 2363 |
+ stderr = cli.out |
|
| 2364 |
+ } else {
|
|
| 2365 |
+ stderr = cli.err |
|
| 2366 |
+ } |
|
| 2367 |
+ } |
|
| 2368 |
+ errCh = utils.Go(func() error {
|
|
| 2369 |
+ return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig)
|
|
| 2370 |
+ }) |
|
| 2371 |
+ |
|
| 2372 |
+ // Acknowledge the hijack before starting |
|
| 2373 |
+ select {
|
|
| 2374 |
+ case closer := <-hijacked: |
|
| 2375 |
+ // Make sure that hijack gets closed when returning. (result |
|
| 2376 |
+ // in closing hijack chan and freeing server's goroutines. |
|
| 2377 |
+ if closer != nil {
|
|
| 2378 |
+ defer closer.Close() |
|
| 2379 |
+ } |
|
| 2380 |
+ case err := <-errCh: |
|
| 2381 |
+ if err != nil {
|
|
| 2382 |
+ log.Debugf("Error hijack: %s", err)
|
|
| 2383 |
+ return err |
|
| 2384 |
+ } |
|
| 2385 |
+ } |
|
| 2386 |
+ |
|
| 2387 |
+ if execConfig.Tty && cli.isTerminal {
|
|
| 2388 |
+ if err := cli.monitorTtySize(execID, true); err != nil {
|
|
| 2389 |
+ log.Errorf("Error monitoring TTY size: %s", err)
|
|
| 2390 |
+ } |
|
| 2391 |
+ } |
|
| 2392 |
+ |
|
| 2393 |
+ if err := <-errCh; err != nil {
|
|
| 2394 |
+ log.Debugf("Error hijack: %s", err)
|
|
| 2395 |
+ return err |
|
| 2396 |
+ } |
|
| 2397 |
+ |
|
| 2398 |
+ return nil |
|
| 2399 |
+} |
| ... | ... |
@@ -115,6 +115,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b
|
| 115 | 115 |
} |
| 116 | 116 |
return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body))
|
| 117 | 117 |
} |
| 118 |
+ |
|
| 118 | 119 |
return resp.Body, resp.StatusCode, nil |
| 119 | 120 |
} |
| 120 | 121 |
|
| ... | ... |
@@ -179,7 +180,7 @@ func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in |
| 179 | 179 |
return nil |
| 180 | 180 |
} |
| 181 | 181 |
|
| 182 |
-func (cli *DockerCli) resizeTty(id string) {
|
|
| 182 |
+func (cli *DockerCli) resizeTty(id string, isExec bool) {
|
|
| 183 | 183 |
height, width := cli.getTtySize() |
| 184 | 184 |
if height == 0 && width == 0 {
|
| 185 | 185 |
return |
| ... | ... |
@@ -187,7 +188,15 @@ func (cli *DockerCli) resizeTty(id string) {
|
| 187 | 187 |
v := url.Values{}
|
| 188 | 188 |
v.Set("h", strconv.Itoa(height))
|
| 189 | 189 |
v.Set("w", strconv.Itoa(width))
|
| 190 |
- if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil {
|
|
| 190 |
+ |
|
| 191 |
+ path := "" |
|
| 192 |
+ if !isExec {
|
|
| 193 |
+ path = "/containers/" + id + "/resize?" |
|
| 194 |
+ } else {
|
|
| 195 |
+ path = "/exec/" + id + "/resize?" |
|
| 196 |
+ } |
|
| 197 |
+ |
|
| 198 |
+ if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil {
|
|
| 191 | 199 |
log.Debugf("Error resize: %s", err)
|
| 192 | 200 |
} |
| 193 | 201 |
} |
| ... | ... |
@@ -226,14 +235,14 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
| 226 | 226 |
return state.GetBool("Running"), state.GetInt("ExitCode"), nil
|
| 227 | 227 |
} |
| 228 | 228 |
|
| 229 |
-func (cli *DockerCli) monitorTtySize(id string) error {
|
|
| 230 |
- cli.resizeTty(id) |
|
| 229 |
+func (cli *DockerCli) monitorTtySize(id string, isExec bool) error {
|
|
| 230 |
+ cli.resizeTty(id, isExec) |
|
| 231 | 231 |
|
| 232 | 232 |
sigchan := make(chan os.Signal, 1) |
| 233 | 233 |
gosignal.Notify(sigchan, syscall.SIGWINCH) |
| 234 | 234 |
go func() {
|
| 235 | 235 |
for _ = range sigchan {
|
| 236 |
- cli.resizeTty(id) |
|
| 236 |
+ cli.resizeTty(id, isExec) |
|
| 237 | 237 |
} |
| 238 | 238 |
}() |
| 239 | 239 |
return nil |
| ... | ... |
@@ -663,6 +663,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re |
| 663 | 663 |
} |
| 664 | 664 |
out.Set("Id", engine.Tail(stdoutBuffer, 1))
|
| 665 | 665 |
out.SetList("Warnings", outWarnings)
|
| 666 |
+ |
|
| 666 | 667 |
return writeJSON(w, http.StatusCreated, out) |
| 667 | 668 |
} |
| 668 | 669 |
|
| ... | ... |
@@ -793,7 +794,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re |
| 793 | 793 |
if vars == nil {
|
| 794 | 794 |
return fmt.Errorf("Missing parameter")
|
| 795 | 795 |
} |
| 796 |
- if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w"), r.Form.Get("exec")).Run(); err != nil {
|
|
| 796 |
+ if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil {
|
|
| 797 | 797 |
return err |
| 798 | 798 |
} |
| 799 | 799 |
return nil |
| ... | ... |
@@ -1060,11 +1061,9 @@ func postContainerExecStart(eng *engine.Engine, version version.Version, w http. |
| 1060 | 1060 |
job = eng.Job("execStart", name)
|
| 1061 | 1061 |
errOut io.Writer = os.Stderr |
| 1062 | 1062 |
) |
| 1063 |
- |
|
| 1064 | 1063 |
if err := job.DecodeEnv(r.Body); err != nil {
|
| 1065 | 1064 |
return err |
| 1066 | 1065 |
} |
| 1067 |
- |
|
| 1068 | 1066 |
if !job.GetenvBool("Detach") {
|
| 1069 | 1067 |
// Setting up the streaming http interface. |
| 1070 | 1068 |
inStream, outStream, err := hijackServer(w) |
| ... | ... |
@@ -1102,12 +1101,12 @@ func postContainerExecStart(eng *engine.Engine, version version.Version, w http. |
| 1102 | 1102 |
errOut = outStream |
| 1103 | 1103 |
} |
| 1104 | 1104 |
// Now run the user process in container. |
| 1105 |
+ job.SetCloseIO(false) |
|
| 1105 | 1106 |
if err := job.Run(); err != nil {
|
| 1106 | 1107 |
fmt.Fprintf(errOut, "Error starting exec command in container %s: %s\n", name, err) |
| 1107 | 1108 |
return err |
| 1108 | 1109 |
} |
| 1109 | 1110 |
w.WriteHeader(http.StatusNoContent) |
| 1110 |
- |
|
| 1111 | 1111 |
return nil |
| 1112 | 1112 |
} |
| 1113 | 1113 |
|
| ... | ... |
@@ -206,7 +206,7 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t |
| 206 | 206 |
}() |
| 207 | 207 |
} |
| 208 | 208 |
if stderr != nil {
|
| 209 |
- nJobs += 1 |
|
| 209 |
+ nJobs++ |
|
| 210 | 210 |
if p, err := streamConfig.StderrPipe(); err != nil {
|
| 211 | 211 |
errors <- err |
| 212 | 212 |
} else {
|
| ... | ... |
@@ -229,7 +229,6 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t |
| 229 | 229 |
if err != nil {
|
| 230 | 230 |
log.Errorf("attach: stderr: %s", err)
|
| 231 | 231 |
} |
| 232 |
- log.Debugf("stdout attach end")
|
|
| 233 | 232 |
errors <- err |
| 234 | 233 |
}() |
| 235 | 234 |
} |
| ... | ... |
@@ -20,7 +20,7 @@ import ( |
| 20 | 20 |
type execConfig struct {
|
| 21 | 21 |
sync.Mutex |
| 22 | 22 |
ID string |
| 23 |
- Running bool |
|
| 23 |
+ Running bool |
|
| 24 | 24 |
ProcessConfig execdriver.ProcessConfig |
| 25 | 25 |
StreamConfig |
| 26 | 26 |
OpenStdin bool |
| ... | ... |
@@ -130,7 +130,7 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status {
|
| 130 | 130 |
StreamConfig: StreamConfig{},
|
| 131 | 131 |
ProcessConfig: processConfig, |
| 132 | 132 |
Container: container, |
| 133 |
- Running: false, |
|
| 133 |
+ Running: false, |
|
| 134 | 134 |
} |
| 135 | 135 |
|
| 136 | 136 |
d.registerExecCommand(execConfig) |
| ... | ... |
@@ -141,8 +141,8 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status {
|
| 141 | 141 |
} |
| 142 | 142 |
|
| 143 | 143 |
func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
| 144 |
- if len(job.Args) != 2 {
|
|
| 145 |
- return job.Errorf("Usage: %s [options] container exec", job.Name)
|
|
| 144 |
+ if len(job.Args) != 1 {
|
|
| 145 |
+ return job.Errorf("Usage: %s [options] exec", job.Name)
|
|
| 146 | 146 |
} |
| 147 | 147 |
|
| 148 | 148 |
var ( |
| ... | ... |
@@ -165,11 +165,11 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
|
| 165 | 165 |
} |
| 166 | 166 |
execConfig.Running = true |
| 167 | 167 |
}() |
| 168 |
- |
|
| 169 | 168 |
if err != nil {
|
| 170 | 169 |
return job.Error(err) |
| 171 | 170 |
} |
| 172 | 171 |
|
| 172 |
+ log.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID)
|
|
| 173 | 173 |
container := execConfig.Container |
| 174 | 174 |
|
| 175 | 175 |
if execConfig.OpenStdin {
|
| 176 | 176 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,29 @@ |
| 0 |
+% DOCKER(1) Docker User Manuals |
|
| 1 |
+% Docker Community |
|
| 2 |
+% SEPT 2014 |
|
| 3 |
+# NAME |
|
| 4 |
+docker-exec - Run a command in an existing container |
|
| 5 |
+ |
|
| 6 |
+# SYNOPSIS |
|
| 7 |
+**docker exec** |
|
| 8 |
+[**-d**|**--detach**[=*false*]] |
|
| 9 |
+[**-i**|**--interactive**[=*false*]] |
|
| 10 |
+[**-t**|**--tty**[=*false*]] |
|
| 11 |
+ CONTAINER COMMAND [ARG...] |
|
| 12 |
+ |
|
| 13 |
+# DESCRIPTION |
|
| 14 |
+ |
|
| 15 |
+Run a process in an existing container. The existing CONTAINER needs to be active. |
|
| 16 |
+ |
|
| 17 |
+# Options |
|
| 18 |
+ |
|
| 19 |
+**-d**, **--detach**=*true*|*false* |
|
| 20 |
+ Detached mode. This runs the new process in the background. |
|
| 21 |
+ |
|
| 22 |
+**-i**, **--interactive**=*true*|*false* |
|
| 23 |
+ When set to true, keep STDIN open even if not attached. The default is false. |
|
| 24 |
+ |
|
| 25 |
+**-t**, **--tty**=*true*|*false* |
|
| 26 |
+ When set to true Docker can allocate a pseudo-tty and attach to the standard |
|
| 27 |
+input of the process. This can be used, for example, to run a throwaway |
|
| 28 |
+interactive shell. The default value is false. |
| ... | ... |
@@ -1295,6 +1295,36 @@ It is even useful to cherry-pick particular tags of an image repository |
| 1295 | 1295 |
|
| 1296 | 1296 |
$ sudo docker save -o ubuntu.tar ubuntu:lucid ubuntu:saucy |
| 1297 | 1297 |
|
| 1298 |
+## exec |
|
| 1299 |
+ |
|
| 1300 |
+ Usage: docker exec CONTAINER COMMAND [ARG...] |
|
| 1301 |
+ |
|
| 1302 |
+ Run a command in an existing container |
|
| 1303 |
+ |
|
| 1304 |
+ -d, --detach=false Detached mode: run the process in the background and exit |
|
| 1305 |
+ -i, --interactive=false Keep STDIN open even if not attached |
|
| 1306 |
+ -t, --tty=false Allocate a pseudo-TTY |
|
| 1307 |
+ |
|
| 1308 |
+The `docker exec` command runs a user specified command as a new process in an existing |
|
| 1309 |
+user specified container. The container needs to be active. |
|
| 1310 |
+ |
|
| 1311 |
+The `docker exec` command will typically be used after `docker run`. |
|
| 1312 |
+ |
|
| 1313 |
+### Examples: |
|
| 1314 |
+ |
|
| 1315 |
+ $ sudo docker run --name ubuntu_bash --rm -i -t ubuntu bash |
|
| 1316 |
+ |
|
| 1317 |
+This will create a container named 'ubuntu_bash' and start a bash session. |
|
| 1318 |
+ |
|
| 1319 |
+ $ sudo docker exec -d ubuntu_bash touch /tmp/execWorks |
|
| 1320 |
+ |
|
| 1321 |
+This will create a new file '/tmp/execWorks' inside the existing and active container |
|
| 1322 |
+'ubuntu_bash', in the background. |
|
| 1323 |
+ |
|
| 1324 |
+ $ sudo docker exec ubuntu_bash -it bash |
|
| 1325 |
+ |
|
| 1326 |
+This will create a new bash session in the container 'ubuntu_bash'. |
|
| 1327 |
+ |
|
| 1298 | 1328 |
## search |
| 1299 | 1329 |
|
| 1300 | 1330 |
Search [Docker Hub](https://hub.docker.com) for images |
| ... | ... |
@@ -115,13 +115,14 @@ func (eng *Engine) commands() []string {
|
| 115 | 115 |
// This function mimics `Command` from the standard os/exec package. |
| 116 | 116 |
func (eng *Engine) Job(name string, args ...string) *Job {
|
| 117 | 117 |
job := &Job{
|
| 118 |
- Eng: eng, |
|
| 119 |
- Name: name, |
|
| 120 |
- Args: args, |
|
| 121 |
- Stdin: NewInput(), |
|
| 122 |
- Stdout: NewOutput(), |
|
| 123 |
- Stderr: NewOutput(), |
|
| 124 |
- env: &Env{},
|
|
| 118 |
+ Eng: eng, |
|
| 119 |
+ Name: name, |
|
| 120 |
+ Args: args, |
|
| 121 |
+ Stdin: NewInput(), |
|
| 122 |
+ Stdout: NewOutput(), |
|
| 123 |
+ Stderr: NewOutput(), |
|
| 124 |
+ env: &Env{},
|
|
| 125 |
+ closeIO: true, |
|
| 125 | 126 |
} |
| 126 | 127 |
if eng.Logging {
|
| 127 | 128 |
job.Stderr.Add(ioutils.NopWriteCloser(eng.Stderr)) |
| ... | ... |
@@ -31,6 +31,7 @@ type Job struct {
|
| 31 | 31 |
handler Handler |
| 32 | 32 |
status Status |
| 33 | 33 |
end time.Time |
| 34 |
+ closeIO bool |
|
| 34 | 35 |
} |
| 35 | 36 |
|
| 36 | 37 |
type Status int |
| ... | ... |
@@ -78,19 +79,22 @@ func (job *Job) Run() error {
|
| 78 | 78 |
job.status = job.handler(job) |
| 79 | 79 |
job.end = time.Now() |
| 80 | 80 |
} |
| 81 |
- // Wait for all background tasks to complete |
|
| 82 |
- if err := job.Stdout.Close(); err != nil {
|
|
| 83 |
- return err |
|
| 84 |
- } |
|
| 85 |
- if err := job.Stderr.Close(); err != nil {
|
|
| 86 |
- return err |
|
| 87 |
- } |
|
| 88 |
- if err := job.Stdin.Close(); err != nil {
|
|
| 89 |
- return err |
|
| 81 |
+ if job.closeIO {
|
|
| 82 |
+ // Wait for all background tasks to complete |
|
| 83 |
+ if err := job.Stdout.Close(); err != nil {
|
|
| 84 |
+ return err |
|
| 85 |
+ } |
|
| 86 |
+ if err := job.Stderr.Close(); err != nil {
|
|
| 87 |
+ return err |
|
| 88 |
+ } |
|
| 89 |
+ if err := job.Stdin.Close(); err != nil {
|
|
| 90 |
+ return err |
|
| 91 |
+ } |
|
| 90 | 92 |
} |
| 91 | 93 |
if job.status != 0 {
|
| 92 | 94 |
return fmt.Errorf("%s", Tail(errorMessage, 1))
|
| 93 | 95 |
} |
| 96 |
+ |
|
| 94 | 97 |
return nil |
| 95 | 98 |
} |
| 96 | 99 |
|
| ... | ... |
@@ -228,3 +232,7 @@ func (job *Job) Error(err error) Status {
|
| 228 | 228 |
func (job *Job) StatusCode() int {
|
| 229 | 229 |
return int(job.status) |
| 230 | 230 |
} |
| 231 |
+ |
|
| 232 |
+func (job *Job) SetCloseIO(val bool) {
|
|
| 233 |
+ job.closeIO = val |
|
| 234 |
+} |