Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)
| ... | ... |
@@ -625,7 +625,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
| 625 | 625 |
v.Set("stderr", "1")
|
| 626 | 626 |
|
| 627 | 627 |
cErr = utils.Go(func() error {
|
| 628 |
- return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil)
|
|
| 628 |
+ return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil)
|
|
| 629 | 629 |
}) |
| 630 | 630 |
} |
| 631 | 631 |
|
| ... | ... |
@@ -1827,7 +1827,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
| 1827 | 1827 |
defer signal.StopCatch(sigc) |
| 1828 | 1828 |
} |
| 1829 | 1829 |
|
| 1830 |
- if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil); err != nil {
|
|
| 1830 |
+ if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil {
|
|
| 1831 | 1831 |
return err |
| 1832 | 1832 |
} |
| 1833 | 1833 |
|
| ... | ... |
@@ -2109,7 +2109,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
| 2109 | 2109 |
} |
| 2110 | 2110 |
|
| 2111 | 2111 |
errCh = utils.Go(func() error {
|
| 2112 |
- return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked)
|
|
| 2112 |
+ return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
|
|
| 2113 | 2113 |
}) |
| 2114 | 2114 |
} else {
|
| 2115 | 2115 |
close(hijacked) |
| ... | ... |
@@ -2299,3 +2299,77 @@ 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", "[OPTIONS] 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 |
+ if execConfig.Detach {
|
|
| 2316 |
+ _, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false)
|
|
| 2317 |
+ return err |
|
| 2318 |
+ } |
|
| 2319 |
+ var ( |
|
| 2320 |
+ out, stderr io.Writer |
|
| 2321 |
+ in io.ReadCloser |
|
| 2322 |
+ // We need to instanciate the chan because the select needs it. It can |
|
| 2323 |
+ // be closed but can't be uninitialized. |
|
| 2324 |
+ hijacked = make(chan io.Closer) |
|
| 2325 |
+ errCh chan error |
|
| 2326 |
+ ) |
|
| 2327 |
+ // Block the return until the chan gets closed |
|
| 2328 |
+ defer func() {
|
|
| 2329 |
+ log.Debugf("End of CmdExec(), Waiting for hijack to finish.")
|
|
| 2330 |
+ if _, ok := <-hijacked; ok {
|
|
| 2331 |
+ log.Errorf("Hijack did not finish (chan still open)")
|
|
| 2332 |
+ } |
|
| 2333 |
+ }() |
|
| 2334 |
+ |
|
| 2335 |
+ if execConfig.AttachStdin {
|
|
| 2336 |
+ in = cli.in |
|
| 2337 |
+ } |
|
| 2338 |
+ if execConfig.AttachStdout {
|
|
| 2339 |
+ out = cli.out |
|
| 2340 |
+ } |
|
| 2341 |
+ if execConfig.AttachStderr {
|
|
| 2342 |
+ if execConfig.Tty {
|
|
| 2343 |
+ stderr = cli.out |
|
| 2344 |
+ } else {
|
|
| 2345 |
+ stderr = cli.err |
|
| 2346 |
+ } |
|
| 2347 |
+ } |
|
| 2348 |
+ errCh = utils.Go(func() error {
|
|
| 2349 |
+ return cli.hijack("POST", "/containers/"+execConfig.Container+"/exec?", execConfig.Tty, in, out, stderr, hijacked, execConfig)
|
|
| 2350 |
+ }) |
|
| 2351 |
+ |
|
| 2352 |
+ // Acknowledge the hijack before starting |
|
| 2353 |
+ select {
|
|
| 2354 |
+ case closer := <-hijacked: |
|
| 2355 |
+ // Make sure that hijack gets closed when returning. (result |
|
| 2356 |
+ // in closing hijack chan and freeing server's goroutines. |
|
| 2357 |
+ if closer != nil {
|
|
| 2358 |
+ defer closer.Close() |
|
| 2359 |
+ } |
|
| 2360 |
+ case err := <-errCh: |
|
| 2361 |
+ if err != nil {
|
|
| 2362 |
+ log.Debugf("Error hijack: %s", err)
|
|
| 2363 |
+ return err |
|
| 2364 |
+ } |
|
| 2365 |
+ } |
|
| 2366 |
+ // TODO(vishh): Enable tty size monitoring once the daemon can support that. |
|
| 2367 |
+ if errCh != nil {
|
|
| 2368 |
+ if err := <-errCh; err != nil {
|
|
| 2369 |
+ log.Debugf("Error hijack: %s", err)
|
|
| 2370 |
+ return err |
|
| 2371 |
+ } |
|
| 2372 |
+ } |
|
| 2373 |
+ |
|
| 2374 |
+ return nil |
|
| 2375 |
+} |
| ... | ... |
@@ -25,14 +25,18 @@ func (cli *DockerCli) dial() (net.Conn, error) {
|
| 25 | 25 |
return net.Dial(cli.proto, cli.addr) |
| 26 | 26 |
} |
| 27 | 27 |
|
| 28 |
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
|
|
| 28 |
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, body interface{}) error {
|
|
| 29 | 29 |
defer func() {
|
| 30 | 30 |
if started != nil {
|
| 31 | 31 |
close(started) |
| 32 | 32 |
} |
| 33 | 33 |
}() |
| 34 | 34 |
|
| 35 |
- req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil)
|
|
| 35 |
+ params, err := cli.getUrlBody(body) |
|
| 36 |
+ if err != nil {
|
|
| 37 |
+ return err |
|
| 38 |
+ } |
|
| 39 |
+ req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
|
| 36 | 40 |
if err != nil {
|
| 37 | 41 |
return err |
| 38 | 42 |
} |
| ... | ... |
@@ -40,24 +40,31 @@ func (cli *DockerCli) HTTPClient() *http.Client {
|
| 40 | 40 |
return &http.Client{Transport: tr}
|
| 41 | 41 |
} |
| 42 | 42 |
|
| 43 |
-func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
|
| 43 |
+func (cli *DockerCli) getUrlBody(data interface{}) (*bytes.Buffer, error) {
|
|
| 44 | 44 |
params := bytes.NewBuffer(nil) |
| 45 | 45 |
if data != nil {
|
| 46 | 46 |
if env, ok := data.(engine.Env); ok {
|
| 47 | 47 |
if err := env.Encode(params); err != nil {
|
| 48 |
- return nil, -1, err |
|
| 48 |
+ return nil, err |
|
| 49 | 49 |
} |
| 50 | 50 |
} else {
|
| 51 | 51 |
buf, err := json.Marshal(data) |
| 52 | 52 |
if err != nil {
|
| 53 |
- return nil, -1, err |
|
| 53 |
+ return nil, err |
|
| 54 | 54 |
} |
| 55 | 55 |
if _, err := params.Write(buf); err != nil {
|
| 56 |
- return nil, -1, err |
|
| 56 |
+ return nil, err |
|
| 57 | 57 |
} |
| 58 | 58 |
} |
| 59 | 59 |
} |
| 60 |
+ return params, nil |
|
| 61 |
+} |
|
| 60 | 62 |
|
| 63 |
+func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
|
|
| 64 |
+ params, err := cli.getUrlBody(data) |
|
| 65 |
+ if err != nil {
|
|
| 66 |
+ return nil, -1, err |
|
| 67 |
+ } |
|
| 61 | 68 |
req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
|
| 62 | 69 |
if err != nil {
|
| 63 | 70 |
return nil, -1, err |
| ... | ... |
@@ -1025,6 +1025,65 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp |
| 1025 | 1025 |
return nil |
| 1026 | 1026 |
} |
| 1027 | 1027 |
|
| 1028 |
+func postContainersExec(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 1029 |
+ if err := parseForm(r); err != nil {
|
|
| 1030 |
+ return nil |
|
| 1031 |
+ } |
|
| 1032 |
+ var ( |
|
| 1033 |
+ name = vars["name"] |
|
| 1034 |
+ job = eng.Job("exec", name)
|
|
| 1035 |
+ ) |
|
| 1036 |
+ if err := job.DecodeEnv(r.Body); err != nil {
|
|
| 1037 |
+ return err |
|
| 1038 |
+ } |
|
| 1039 |
+ var errOut io.Writer = os.Stderr |
|
| 1040 |
+ |
|
| 1041 |
+ if !job.GetenvBool("Detach") {
|
|
| 1042 |
+ // Setting up the streaming http interface. |
|
| 1043 |
+ inStream, outStream, err := hijackServer(w) |
|
| 1044 |
+ if err != nil {
|
|
| 1045 |
+ return err |
|
| 1046 |
+ } |
|
| 1047 |
+ |
|
| 1048 |
+ defer func() {
|
|
| 1049 |
+ if tcpc, ok := inStream.(*net.TCPConn); ok {
|
|
| 1050 |
+ tcpc.CloseWrite() |
|
| 1051 |
+ } else {
|
|
| 1052 |
+ inStream.Close() |
|
| 1053 |
+ } |
|
| 1054 |
+ }() |
|
| 1055 |
+ defer func() {
|
|
| 1056 |
+ if tcpc, ok := outStream.(*net.TCPConn); ok {
|
|
| 1057 |
+ tcpc.CloseWrite() |
|
| 1058 |
+ } else if closer, ok := outStream.(io.Closer); ok {
|
|
| 1059 |
+ closer.Close() |
|
| 1060 |
+ } |
|
| 1061 |
+ }() |
|
| 1062 |
+ |
|
| 1063 |
+ var errStream io.Writer |
|
| 1064 |
+ |
|
| 1065 |
+ fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") |
|
| 1066 |
+ if !job.GetenvBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
|
|
| 1067 |
+ errStream = utils.NewStdWriter(outStream, utils.Stderr) |
|
| 1068 |
+ outStream = utils.NewStdWriter(outStream, utils.Stdout) |
|
| 1069 |
+ } else {
|
|
| 1070 |
+ errStream = outStream |
|
| 1071 |
+ } |
|
| 1072 |
+ job.Stdin.Add(inStream) |
|
| 1073 |
+ job.Stdout.Add(outStream) |
|
| 1074 |
+ job.Stderr.Set(errStream) |
|
| 1075 |
+ errOut = outStream |
|
| 1076 |
+ } |
|
| 1077 |
+ // Now run the user process in container. |
|
| 1078 |
+ if err := job.Run(); err != nil {
|
|
| 1079 |
+ fmt.Fprintf(errOut, "Error running in container %s: %s\n", name, err) |
|
| 1080 |
+ return err |
|
| 1081 |
+ } |
|
| 1082 |
+ w.WriteHeader(http.StatusNoContent) |
|
| 1083 |
+ |
|
| 1084 |
+ return nil |
|
| 1085 |
+} |
|
| 1086 |
+ |
|
| 1028 | 1087 |
func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
| 1029 | 1088 |
w.WriteHeader(http.StatusOK) |
| 1030 | 1089 |
return nil |
| ... | ... |
@@ -1147,6 +1206,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st |
| 1147 | 1147 |
"/containers/{name:.*}/resize": postContainersResize,
|
| 1148 | 1148 |
"/containers/{name:.*}/attach": postContainersAttach,
|
| 1149 | 1149 |
"/containers/{name:.*}/copy": postContainersCopy,
|
| 1150 |
+ "/containers/{name:.*}/exec": postContainersExec,
|
|
| 1150 | 1151 |
}, |
| 1151 | 1152 |
"DELETE": {
|
| 1152 | 1153 |
"/containers/{name:.*}": deleteContainers,
|
| ... | ... |
@@ -122,6 +122,7 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
|
| 122 | 122 |
"unpause": daemon.ContainerUnpause, |
| 123 | 123 |
"wait": daemon.ContainerWait, |
| 124 | 124 |
"image_delete": daemon.ImageDelete, // FIXME: see above |
| 125 |
+ "exec": daemon.ContainerExec, |
|
| 125 | 126 |
} {
|
| 126 | 127 |
if err := eng.Register(name, method); err != nil {
|
| 127 | 128 |
return err |
| ... | ... |
@@ -92,6 +92,7 @@ func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
|
| 92 | 92 |
attachErr = d.Attach(&execConfig.StreamConfig, config.AttachStdin, false, config.Tty, cStdin, cStdinCloser, cStdout, cStderr) |
| 93 | 93 |
}() |
| 94 | 94 |
|
| 95 |
+ log.Debugf("Exec Config is %+v\n", execConfig)
|
|
| 95 | 96 |
go func() {
|
| 96 | 97 |
err := container.Exec(execConfig) |
| 97 | 98 |
if err != nil {
|