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:
parent
5130fe5d38
commit
985d579586
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue