Quellcode durchsuchen

Splitting the exec remote API into two separate APIs inorder to support resizing of tty sessions.
1. /container/<name>/exec - Creates a new exec command instance in the daemon and container '<name>'. Returns an unique ID for each exec command.
2. /exec/<name>/start - Starts an existing exec command instance. Removes the exec command from the daemon once it completes.

Adding /exec/<name>/resize to resize tty session of an exec command.

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

Vishnu Kannan vor 10 Jahren
Ursprung
Commit
bfebdfde78
5 geänderte Dateien mit 219 neuen und 44 gelöschten Zeilen
  1. 50 7
      api/server/server.go
  2. 3 2
      daemon/container.go
  3. 6 1
      daemon/daemon.go
  4. 136 34
      daemon/exec.go
  5. 24 0
      daemon/resize.go

+ 50 - 7
api/server/server.go

@@ -793,7 +793,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re
 	if vars == nil {
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 		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 err
 	}
 	}
 	return nil
 	return nil
@@ -1025,18 +1025,45 @@ 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 {
+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 {
 	if err := parseForm(r); err != nil {
 		return nil
 		return nil
 	}
 	}
 	var (
 	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
+	}
+
+	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 {
 	if err := job.DecodeEnv(r.Body); err != nil {
 		return err
 		return err
 	}
 	}
-	var errOut io.Writer = os.Stderr
 
 
 	if !job.GetenvBool("Detach") {
 	if !job.GetenvBool("Detach") {
 		// Setting up the streaming http interface.
 		// 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.
 	// Now run the user process in container.
 	if err := job.Run(); err != nil {
 	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
 		return err
 	}
 	}
 	w.WriteHeader(http.StatusNoContent)
 	w.WriteHeader(http.StatusNoContent)
@@ -1084,6 +1111,19 @@ func postContainersExec(eng *engine.Engine, version version.Version, w http.Resp
 	return nil
 	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 {
 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
@@ -1206,7 +1246,9 @@ 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,
+			"/containers/{name:.*}/exec":    postContainerExecCreate,
+			"/exec/{name:.*}/start":         postContainerExecStart,
+			"/exec/{name:.*}/resize":        postContainerExecResize,
 		},
 		},
 		"DELETE": {
 		"DELETE": {
 			"/containers/{name:.*}": deleteContainers,
 			"/containers/{name:.*}": deleteContainers,
@@ -1393,6 +1435,7 @@ func ListenAndServe(proto, addr string, job *engine.Job) error {
 					return err
 					return err
 				}
 				}
 			}
 			}
+
 		}
 		}
 		if err := os.Chmod(addr, 0660); err != nil {
 		if err := os.Chmod(addr, 0660); err != nil {
 			return err
 			return err

+ 3 - 2
daemon/container.go

@@ -86,8 +86,9 @@ type Container struct {
 	VolumesRW  map[string]bool
 	VolumesRW  map[string]bool
 	hostConfig *runconfig.HostConfig
 	hostConfig *runconfig.HostConfig
 
 
-	activeLinks map[string]*links.Link
-	monitor     *containerMonitor
+	activeLinks  map[string]*links.Link
+	monitor      *containerMonitor
+	execCommands *execStore
 }
 }
 
 
 func (container *Container) FromDisk() error {
 func (container *Container) FromDisk() error {

+ 6 - 1
daemon/daemon.go

@@ -85,6 +85,7 @@ type Daemon struct {
 	repository     string
 	repository     string
 	sysInitPath    string
 	sysInitPath    string
 	containers     *contStore
 	containers     *contStore
+	execCommands   *execStore
 	graph          *graph.Graph
 	graph          *graph.Graph
 	repositories   *graph.TagStore
 	repositories   *graph.TagStore
 	idIndex        *truncindex.TruncIndex
 	idIndex        *truncindex.TruncIndex
@@ -122,7 +123,9 @@ 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,
+		"execCreate":        daemon.ContainerExecCreate,
+		"execStart":         daemon.ContainerExecStart,
+		"execResize":        daemon.ContainerExecResize,
 	} {
 	} {
 		if err := eng.Register(name, method); err != nil {
 		if err := eng.Register(name, method); err != nil {
 			return err
 			return err
@@ -539,6 +542,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
 		Driver:          daemon.driver.String(),
 		Driver:          daemon.driver.String(),
 		ExecDriver:      daemon.execDriver.Name(),
 		ExecDriver:      daemon.execDriver.Name(),
 		State:           NewState(),
 		State:           NewState(),
+		execCommands:    newExecStore(),
 	}
 	}
 	container.root = daemon.containerRoot(container.ID)
 	container.root = daemon.containerRoot(container.ID)
 
 
@@ -847,6 +851,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
 	daemon := &Daemon{
 	daemon := &Daemon{
 		repository:     daemonRepo,
 		repository:     daemonRepo,
 		containers:     &contStore{s: make(map[string]*Container)},
 		containers:     &contStore{s: make(map[string]*Container)},
+		execCommands:   newExecStore(),
 		graph:          g,
 		graph:          g,
 		repositories:   repositories,
 		repositories:   repositories,
 		idIndex:        truncindex.NewTruncIndex([]string{}),
 		idIndex:        truncindex.NewTruncIndex([]string{}),

+ 136 - 34
daemon/exec.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
+	"sync"
 
 
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/daemon/execdriver"
 	"github.com/docker/docker/engine"
 	"github.com/docker/docker/engine"
@@ -16,52 +17,99 @@ import (
 	"github.com/docker/docker/utils"
 	"github.com/docker/docker/utils"
 )
 )
 
 
-type ExecConfig struct {
+type execConfig struct {
+	ID            string
 	ProcessConfig execdriver.ProcessConfig
 	ProcessConfig execdriver.ProcessConfig
 	StreamConfig
 	StreamConfig
-	OpenStdin bool
+	OpenStdin  bool
+	OpenStderr bool
+	OpenStdout bool
+	Container  *Container
 }
 }
 
 
-func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
-	if len(job.Args) != 1 {
-		return job.Errorf("Usage: %s [options] container command [args]", job.Name)
+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
 	}
 	}
 
 
-	var (
-		cStdin           io.ReadCloser
-		cStdout, cStderr io.Writer
-		cStdinCloser     io.Closer
-		name             = job.Args[0]
-	)
+	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)
 	container := d.Get(name)
 
 
 	if container == nil {
 	if container == nil {
-		return job.Errorf("No such container: %s", name)
+		return nil, fmt.Errorf("No such container: %s", name)
 	}
 	}
 
 
 	if !container.IsRunning() {
 	if !container.IsRunning() {
-		return job.Errorf("Container %s is not not running", name)
+		return nil, fmt.Errorf("Container %s is not not running", name)
 	}
 	}
 
 
-	config := runconfig.ExecConfigFromJob(job)
+	return container, nil
+}
 
 
-	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
+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)
 	}
 	}
-	if config.AttachStderr {
-		cStderr = job.Stderr
+
+	var name = job.Args[0]
+
+	container, err := d.getActiveContainer(name)
+	if err != nil {
+		return job.Error(err)
 	}
 	}
 
 
+	config := runconfig.ExecConfigFromJob(job)
+
 	entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
 	entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
 
 
 	processConfig := execdriver.ProcessConfig{
 	processConfig := execdriver.ProcessConfig{
@@ -72,10 +120,60 @@ func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
 		Arguments:  args,
 		Arguments:  args,
 	}
 	}
 
 
-	execConfig := &ExecConfig{
+	execConfig := &execConfig{
+		ID:            utils.GenerateRandomID(),
 		OpenStdin:     config.AttachStdin,
 		OpenStdin:     config.AttachStdin,
+		OpenStdout:    config.AttachStdout,
+		OpenStderr:    config.AttachStderr,
 		StreamConfig:  StreamConfig{},
 		StreamConfig:  StreamConfig{},
 		ProcessConfig: processConfig,
 		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()
 	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
 		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)
 	execErr := make(chan error)
+
+	// Remove exec from daemon and container.
+	defer d.unregisterExecCommand(execConfig)
+
 	go func() {
 	go func() {
 		err := container.Exec(execConfig)
 		err := container.Exec(execConfig)
 		if err != nil {
 		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
 	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()
 	container.Lock()
 	defer container.Unlock()
 	defer container.Unlock()
 
 
@@ -146,7 +248,7 @@ func (container *Container) Exec(execConfig *ExecConfig) error {
 	return nil
 	return nil
 }
 }
 
 
-func (container *Container) monitorExec(execConfig *ExecConfig, callback execdriver.StartCallback) error {
+func (container *Container) monitorExec(execConfig *execConfig, callback execdriver.StartCallback) error {
 	var (
 	var (
 		err      error
 		err      error
 		exitCode int
 		exitCode int

+ 24 - 0
daemon/resize.go

@@ -19,6 +19,7 @@ func (daemon *Daemon) ContainerResize(job *engine.Job) engine.Status {
 	if err != nil {
 	if err != nil {
 		return job.Error(err)
 		return job.Error(err)
 	}
 	}
+
 	if container := daemon.Get(name); container != nil {
 	if container := daemon.Get(name); container != nil {
 		if err := container.Resize(height, width); err != nil {
 		if err := container.Resize(height, width); err != nil {
 			return job.Error(err)
 			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)
 	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
+}