Browse code

Adding 'exec' command to remote API and CLI.

Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)

Vishnu Kannan authored on 2014/09/09 14:51:53
Showing 6 changed files
... ...
@@ -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 {