Prechádzať zdrojové kódy

Adding 'exec' command to remote API and CLI.

Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)
Vishnu Kannan 10 rokov pred
rodič
commit
985d579586

+ 77 - 3
api/client/commands.go

@@ -625,7 +625,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 		v.Set("stderr", "1")
 		v.Set("stderr", "1")
 
 
 		cErr = utils.Go(func() error {
 		cErr = utils.Go(func() error {
-			return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil)
+			return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil)
 		})
 		})
 	}
 	}
 
 
@@ -1827,7 +1827,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 		defer signal.StopCatch(sigc)
 		defer signal.StopCatch(sigc)
 	}
 	}
 
 
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil); err != nil {
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil {
 		return err
 		return err
 	}
 	}
 
 
@@ -2109,7 +2109,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		}
 		}
 
 
 		errCh = utils.Go(func() error {
 		errCh = utils.Go(func() error {
-			return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked)
+			return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
 		})
 		})
 	} else {
 	} else {
 		close(hijacked)
 		close(hijacked)
@@ -2299,3 +2299,77 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+func (cli *DockerCli) CmdExec(args ...string) error {
+	cmd := cli.Subcmd("exec", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in an existing container")
+
+	execConfig, err := runconfig.ParseExec(cmd, args)
+	if err != nil {
+		return err
+	}
+	if execConfig.Container == "" {
+		cmd.Usage()
+		return nil
+	}
+
+	if execConfig.Detach {
+		_, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false)
+		return err
+	}
+	var (
+		out, stderr io.Writer
+		in          io.ReadCloser
+		// We need to instanciate the chan because the select needs it. It can
+		// be closed but can't be uninitialized.
+		hijacked = make(chan io.Closer)
+		errCh    chan error
+	)
+	// Block the return until the chan gets closed
+	defer func() {
+		log.Debugf("End of CmdExec(), Waiting for hijack to finish.")
+		if _, ok := <-hijacked; ok {
+			log.Errorf("Hijack did not finish (chan still open)")
+		}
+	}()
+
+	if execConfig.AttachStdin {
+		in = cli.in
+	}
+	if execConfig.AttachStdout {
+		out = cli.out
+	}
+	if execConfig.AttachStderr {
+		if execConfig.Tty {
+			stderr = cli.out
+		} else {
+			stderr = cli.err
+		}
+	}
+	errCh = utils.Go(func() error {
+		return cli.hijack("POST", "/containers/"+execConfig.Container+"/exec?", execConfig.Tty, in, out, stderr, hijacked, execConfig)
+	})
+
+	// Acknowledge the hijack before starting
+	select {
+	case closer := <-hijacked:
+		// Make sure that hijack gets closed when returning. (result
+		// in closing hijack chan and freeing server's goroutines.
+		if closer != nil {
+			defer closer.Close()
+		}
+	case err := <-errCh:
+		if err != nil {
+			log.Debugf("Error hijack: %s", err)
+			return err
+		}
+	}
+	// TODO(vishh): Enable tty size monitoring once the daemon can support that.
+	if errCh != nil {
+		if err := <-errCh; err != nil {
+			log.Debugf("Error hijack: %s", err)
+			return err
+		}
+	}
+
+	return nil
+}

+ 6 - 2
api/client/hijack.go

@@ -25,14 +25,18 @@ func (cli *DockerCli) dial() (net.Conn, error) {
 	return net.Dial(cli.proto, cli.addr)
 	return net.Dial(cli.proto, cli.addr)
 }
 }
 
 
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer) error {
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.ReadCloser, stdout, stderr io.Writer, started chan io.Closer, body interface{}) error {
 	defer func() {
 	defer func() {
 		if started != nil {
 		if started != nil {
 			close(started)
 			close(started)
 		}
 		}
 	}()
 	}()
 
 
-	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), nil)
+	params, err := cli.getUrlBody(body)
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}

+ 11 - 4
api/client/utils.go

@@ -40,24 +40,31 @@ func (cli *DockerCli) HTTPClient() *http.Client {
 	return &http.Client{Transport: tr}
 	return &http.Client{Transport: tr}
 }
 }
 
 
-func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
+func (cli *DockerCli) getUrlBody(data interface{}) (*bytes.Buffer, error) {
 	params := bytes.NewBuffer(nil)
 	params := bytes.NewBuffer(nil)
 	if data != nil {
 	if data != nil {
 		if env, ok := data.(engine.Env); ok {
 		if env, ok := data.(engine.Env); ok {
 			if err := env.Encode(params); err != nil {
 			if err := env.Encode(params); err != nil {
-				return nil, -1, err
+				return nil, err
 			}
 			}
 		} else {
 		} else {
 			buf, err := json.Marshal(data)
 			buf, err := json.Marshal(data)
 			if err != nil {
 			if err != nil {
-				return nil, -1, err
+				return nil, err
 			}
 			}
 			if _, err := params.Write(buf); err != nil {
 			if _, err := params.Write(buf); err != nil {
-				return nil, -1, err
+				return nil, err
 			}
 			}
 		}
 		}
 	}
 	}
+	return params, nil
+}
 
 
+func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) {
+	params, err := cli.getUrlBody(data)
+	if err != nil {
+		return nil, -1, err
+	}
 	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
 	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params)
 	if err != nil {
 	if err != nil {
 		return nil, -1, err
 		return nil, -1, err

+ 60 - 0
api/server/server.go

@@ -1025,6 +1025,65 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
 	return nil
 	return nil
 }
 }
 
 
+func postContainersExec(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if err := parseForm(r); err != nil {
+		return nil
+	}
+	var (
+		name = vars["name"]
+		job  = eng.Job("exec", name)
+	)
+	if err := job.DecodeEnv(r.Body); err != nil {
+		return err
+	}
+	var errOut io.Writer = os.Stderr
+
+	if !job.GetenvBool("Detach") {
+		// Setting up the streaming http interface.
+		inStream, outStream, err := hijackServer(w)
+		if err != nil {
+			return err
+		}
+
+		defer func() {
+			if tcpc, ok := inStream.(*net.TCPConn); ok {
+				tcpc.CloseWrite()
+			} else {
+				inStream.Close()
+			}
+		}()
+		defer func() {
+			if tcpc, ok := outStream.(*net.TCPConn); ok {
+				tcpc.CloseWrite()
+			} else if closer, ok := outStream.(io.Closer); ok {
+				closer.Close()
+			}
+		}()
+
+		var errStream io.Writer
+
+		fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
+		if !job.GetenvBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
+			errStream = utils.NewStdWriter(outStream, utils.Stderr)
+			outStream = utils.NewStdWriter(outStream, utils.Stdout)
+		} else {
+			errStream = outStream
+		}
+		job.Stdin.Add(inStream)
+		job.Stdout.Add(outStream)
+		job.Stderr.Set(errStream)
+		errOut = outStream
+	}
+	// Now run the user process in container.
+	if err := job.Run(); err != nil {
+		fmt.Fprintf(errOut, "Error running in container %s: %s\n", name, err)
+		return err
+	}
+	w.WriteHeader(http.StatusNoContent)
+
+	return nil
+}
+
 func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	return nil
 	return nil
@@ -1147,6 +1206,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
 			"/containers/{name:.*}/resize":  postContainersResize,
 			"/containers/{name:.*}/resize":  postContainersResize,
 			"/containers/{name:.*}/attach":  postContainersAttach,
 			"/containers/{name:.*}/attach":  postContainersAttach,
 			"/containers/{name:.*}/copy":    postContainersCopy,
 			"/containers/{name:.*}/copy":    postContainersCopy,
+			"/containers/{name:.*}/exec":    postContainersExec,
 		},
 		},
 		"DELETE": {
 		"DELETE": {
 			"/containers/{name:.*}": deleteContainers,
 			"/containers/{name:.*}": deleteContainers,

+ 1 - 0
daemon/daemon.go

@@ -122,6 +122,7 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
 		"unpause":           daemon.ContainerUnpause,
 		"unpause":           daemon.ContainerUnpause,
 		"wait":              daemon.ContainerWait,
 		"wait":              daemon.ContainerWait,
 		"image_delete":      daemon.ImageDelete, // FIXME: see above
 		"image_delete":      daemon.ImageDelete, // FIXME: see above
+		"exec":              daemon.ContainerExec,
 	} {
 	} {
 		if err := eng.Register(name, method); err != nil {
 		if err := eng.Register(name, method); err != nil {
 			return err
 			return err

+ 1 - 0
daemon/exec.go

@@ -92,6 +92,7 @@ func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
 		attachErr = d.Attach(&execConfig.StreamConfig, config.AttachStdin, false, config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
 		attachErr = d.Attach(&execConfig.StreamConfig, config.AttachStdin, false, config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
 	}()
 	}()
 
 
+	log.Debugf("Exec Config is %+v\n", execConfig)
 	go func() {
 	go func() {
 		err := container.Exec(execConfig)
 		err := container.Exec(execConfig)
 		if err != nil {
 		if err != nil {