Adding 'exec' command to remote API and CLI.

Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)
This commit is contained in:
Vishnu Kannan 2014-09-09 05:51:53 +00:00
parent 5130fe5d38
commit 985d579586
6 changed files with 156 additions and 9 deletions

View File

@ -625,7 +625,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
v.Set("stderr", "1")
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)
}
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
}
@ -2109,7 +2109,7 @@ func (cli *DockerCli) CmdRun(args ...string) 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 {
close(hijacked)
@ -2299,3 +2299,77 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
}
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
}

View File

@ -25,14 +25,18 @@ func (cli *DockerCli) dial() (net.Conn, error) {
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() {
if started != nil {
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 {
return err
}

View File

@ -40,24 +40,31 @@ func (cli *DockerCli) HTTPClient() *http.Client {
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)
if data != nil {
if env, ok := data.(engine.Env); ok {
if err := env.Encode(params); err != nil {
return nil, -1, err
return nil, err
}
} else {
buf, err := json.Marshal(data)
if err != nil {
return nil, -1, err
return nil, err
}
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)
if err != nil {
return nil, -1, err

View File

@ -1025,6 +1025,65 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
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 {
w.WriteHeader(http.StatusOK)
return nil
@ -1147,6 +1206,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
"/containers/{name:.*}/resize": postContainersResize,
"/containers/{name:.*}/attach": postContainersAttach,
"/containers/{name:.*}/copy": postContainersCopy,
"/containers/{name:.*}/exec": postContainersExec,
},
"DELETE": {
"/containers/{name:.*}": deleteContainers,

View File

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

View File

@ -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)
}()
log.Debugf("Exec Config is %+v\n", execConfig)
go func() {
err := container.Exec(execConfig)
if err != nil {