diff --git a/api/server/server.go b/api/server/server.go index 6201b35d5e..3df32955f2 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -793,7 +793,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re if vars == nil { return fmt.Errorf("Missing parameter") } - if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { + if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w"), r.Form.Get("exec")).Run(); err != nil { return err } return nil @@ -1025,18 +1025,45 @@ 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 { +func postContainerExecCreate(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) + out engine.Env + name = vars["name"] + job = eng.Job("execCreate", name) + stdoutBuffer = bytes.NewBuffer(nil) ) if err := job.DecodeEnv(r.Body); err != nil { return err } - var errOut io.Writer = os.Stderr + + job.Stdout.Add(stdoutBuffer) + // Register an instance of Exec in container. + if err := job.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error setting up exec command in container %s: %s\n", name, err) + return err + } + // Return the ID + out.Set("Id", engine.Tail(stdoutBuffer, 1)) + + return writeJSON(w, http.StatusCreated, out) +} + +func postContainerExecStart(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("execStart", name) + errOut io.Writer = os.Stderr + ) + + if err := job.DecodeEnv(r.Body); err != nil { + return err + } if !job.GetenvBool("Detach") { // Setting up the streaming http interface. @@ -1076,7 +1103,7 @@ func postContainersExec(eng *engine.Engine, version version.Version, w http.Resp } // 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) + fmt.Fprintf(errOut, "Error starting exec command in container %s: %s\n", name, err) return err } w.WriteHeader(http.StatusNoContent) @@ -1084,6 +1111,19 @@ func postContainersExec(eng *engine.Engine, version version.Version, w http.Resp return nil } +func postContainerExecResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } + if vars == nil { + return fmt.Errorf("Missing parameter") + } + if err := eng.Job("execResize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { + return err + } + 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 @@ -1206,7 +1246,9 @@ 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, + "/containers/{name:.*}/exec": postContainerExecCreate, + "/exec/{name:.*}/start": postContainerExecStart, + "/exec/{name:.*}/resize": postContainerExecResize, }, "DELETE": { "/containers/{name:.*}": deleteContainers, @@ -1393,6 +1435,7 @@ func ListenAndServe(proto, addr string, job *engine.Job) error { return err } } + } if err := os.Chmod(addr, 0660); err != nil { return err diff --git a/daemon/container.go b/daemon/container.go index e45ec68d52..014899fc3c 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -86,8 +86,9 @@ type Container struct { VolumesRW map[string]bool hostConfig *runconfig.HostConfig - activeLinks map[string]*links.Link - monitor *containerMonitor + activeLinks map[string]*links.Link + monitor *containerMonitor + execCommands *execStore } func (container *Container) FromDisk() error { diff --git a/daemon/daemon.go b/daemon/daemon.go index 36e6bac58b..7ee7948a28 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -85,6 +85,7 @@ type Daemon struct { repository string sysInitPath string containers *contStore + execCommands *execStore graph *graph.Graph repositories *graph.TagStore idIndex *truncindex.TruncIndex @@ -122,7 +123,9 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "unpause": daemon.ContainerUnpause, "wait": daemon.ContainerWait, "image_delete": daemon.ImageDelete, // FIXME: see above - "exec": daemon.ContainerExec, + "execCreate": daemon.ContainerExecCreate, + "execStart": daemon.ContainerExecStart, + "execResize": daemon.ContainerExecResize, } { if err := eng.Register(name, method); err != nil { return err @@ -539,6 +542,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i Driver: daemon.driver.String(), ExecDriver: daemon.execDriver.Name(), State: NewState(), + execCommands: newExecStore(), } container.root = daemon.containerRoot(container.ID) @@ -847,6 +851,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) daemon := &Daemon{ repository: daemonRepo, containers: &contStore{s: make(map[string]*Container)}, + execCommands: newExecStore(), graph: g, repositories: repositories, idIndex: truncindex.NewTruncIndex([]string{}), diff --git a/daemon/exec.go b/daemon/exec.go index da7faa27cc..d32930d3ec 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "sync" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/engine" @@ -16,52 +17,99 @@ import ( "github.com/docker/docker/utils" ) -type ExecConfig struct { +type execConfig struct { + ID string ProcessConfig execdriver.ProcessConfig StreamConfig - OpenStdin bool + OpenStdin bool + OpenStderr bool + OpenStdout bool + Container *Container } -func (d *Daemon) ContainerExec(job *engine.Job) engine.Status { +type execStore struct { + s map[string]*execConfig + sync.Mutex +} + +func newExecStore() *execStore { + return &execStore{s: make(map[string]*execConfig, 0)} +} + +func (e *execStore) Add(id string, execConfig *execConfig) { + e.Lock() + e.s[id] = execConfig + e.Unlock() +} + +func (e *execStore) Get(id string) *execConfig { + e.Lock() + res := e.s[id] + e.Unlock() + return res +} + +func (e *execStore) Delete(id string) { + e.Lock() + delete(e.s, id) + e.Unlock() +} + +func (execConfig *execConfig) Resize(h, w int) error { + return execConfig.ProcessConfig.Terminal.Resize(h, w) +} + +func (d *Daemon) registerExecCommand(execConfig *execConfig) { + // Storing execs in container inorder to kill them gracefully whenever the container is stopped or removed. + execConfig.Container.execCommands.Add(execConfig.ID, execConfig) + // Storing execs in daemon for easy access via remote API. + d.execCommands.Add(execConfig.ID, execConfig) +} + +func (d *Daemon) getExecConfig(name string) (*execConfig, error) { + if execConfig := d.execCommands.Get(name); execConfig != nil { + if !execConfig.Container.IsRunning() { + return nil, fmt.Errorf("Container %s is not not running", execConfig.Container.ID) + } + return execConfig, nil + } + + return nil, fmt.Errorf("No exec '%s' in found in daemon", name) +} + +func (d *Daemon) unregisterExecCommand(execConfig *execConfig) { + execConfig.Container.execCommands.Delete(execConfig.ID) + d.execCommands.Delete(execConfig.ID) +} + +func (d *Daemon) getActiveContainer(name string) (*Container, error) { + container := d.Get(name) + + if container == nil { + return nil, fmt.Errorf("No such container: %s", name) + } + + if !container.IsRunning() { + return nil, fmt.Errorf("Container %s is not not running", name) + } + + return container, nil +} + +func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status { if len(job.Args) != 1 { return job.Errorf("Usage: %s [options] container command [args]", job.Name) } - var ( - cStdin io.ReadCloser - cStdout, cStderr io.Writer - cStdinCloser io.Closer - name = job.Args[0] - ) + var name = job.Args[0] - container := d.Get(name) - - if container == nil { - return job.Errorf("No such container: %s", name) - } - - if !container.IsRunning() { - return job.Errorf("Container %s is not not running", name) + container, err := d.getActiveContainer(name) + if err != nil { + return job.Error(err) } config := runconfig.ExecConfigFromJob(job) - if config.AttachStdin { - r, w := io.Pipe() - go func() { - defer w.Close() - io.Copy(w, job.Stdin) - }() - cStdin = r - cStdinCloser = job.Stdin - } - if config.AttachStdout { - cStdout = job.Stdout - } - if config.AttachStderr { - cStderr = job.Stderr - } - entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd) processConfig := execdriver.ProcessConfig{ @@ -72,10 +120,60 @@ func (d *Daemon) ContainerExec(job *engine.Job) engine.Status { Arguments: args, } - execConfig := &ExecConfig{ + execConfig := &execConfig{ + ID: utils.GenerateRandomID(), OpenStdin: config.AttachStdin, + OpenStdout: config.AttachStdout, + OpenStderr: config.AttachStderr, StreamConfig: StreamConfig{}, ProcessConfig: processConfig, + Container: container, + } + + d.registerExecCommand(execConfig) + + job.Printf("%s\n", execConfig.ID) + + return engine.StatusOK +} + +func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status { + if len(job.Args) != 2 { + return job.Errorf("Usage: %s [options] container exec", job.Name) + } + + var ( + cStdin io.ReadCloser + cStdout, cStderr io.Writer + cStdinCloser io.Closer + execName = job.Args[0] + ) + + if execName == "" { + return job.Errorf("ExecName not specified. Cannot start exec command") + } + + execConfig, err := d.getExecConfig(execName) + if err != nil { + return job.Error(err) + } + + container := execConfig.Container + + if execConfig.OpenStdin { + r, w := io.Pipe() + go func() { + defer w.Close() + io.Copy(w, job.Stdin) + }() + cStdin = r + cStdinCloser = job.Stdin + } + if execConfig.OpenStdout { + cStdout = job.Stdout + } + if execConfig.OpenStderr { + cStderr = job.Stderr } execConfig.StreamConfig.stderr = broadcastwriter.New() @@ -87,13 +185,17 @@ func (d *Daemon) ContainerExec(job *engine.Job) engine.Status { execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin } - attachErr := d.Attach(&execConfig.StreamConfig, config.AttachStdin, false, config.Tty, cStdin, cStdinCloser, cStdout, cStderr) + attachErr := d.Attach(&execConfig.StreamConfig, execConfig.OpenStdin, false, execConfig.ProcessConfig.Tty, cStdin, cStdinCloser, cStdout, cStderr) execErr := make(chan error) + + // Remove exec from daemon and container. + defer d.unregisterExecCommand(execConfig) + go func() { err := container.Exec(execConfig) if err != nil { - execErr <- fmt.Errorf("Cannot run in container %s: %s", name, err) + execErr <- fmt.Errorf("Cannot run exec command %s in container %s: %s", execName, container.ID, err) } }() @@ -110,11 +212,11 @@ func (d *Daemon) ContainerExec(job *engine.Job) engine.Status { return engine.StatusOK } -func (daemon *Daemon) Exec(c *Container, execConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { - return daemon.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback) +func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { + return d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback) } -func (container *Container) Exec(execConfig *ExecConfig) error { +func (container *Container) Exec(execConfig *execConfig) error { container.Lock() defer container.Unlock() @@ -146,7 +248,7 @@ func (container *Container) Exec(execConfig *ExecConfig) error { return nil } -func (container *Container) monitorExec(execConfig *ExecConfig, callback execdriver.StartCallback) error { +func (container *Container) monitorExec(execConfig *execConfig, callback execdriver.StartCallback) error { var ( err error exitCode int diff --git a/daemon/resize.go b/daemon/resize.go index dd196ff6c4..68c070370a 100644 --- a/daemon/resize.go +++ b/daemon/resize.go @@ -19,6 +19,7 @@ func (daemon *Daemon) ContainerResize(job *engine.Job) engine.Status { if err != nil { return job.Error(err) } + if container := daemon.Get(name); container != nil { if err := container.Resize(height, width); err != nil { return job.Error(err) @@ -27,3 +28,26 @@ func (daemon *Daemon) ContainerResize(job *engine.Job) engine.Status { } return job.Errorf("No such container: %s", name) } + +func (daemon *Daemon) ContainerExecResize(job *engine.Job) engine.Status { + if len(job.Args) != 3 { + return job.Errorf("Not enough arguments. Usage: %s EXEC HEIGHT WIDTH\n", job.Name) + } + name := job.Args[0] + height, err := strconv.Atoi(job.Args[1]) + if err != nil { + return job.Error(err) + } + width, err := strconv.Atoi(job.Args[2]) + if err != nil { + return job.Error(err) + } + execConfig, err := daemon.getExecConfig(name) + if err != nil { + return job.Error(err) + } + if err := execConfig.Resize(height, width); err != nil { + return job.Error(err) + } + return engine.StatusOK +}