Browse code

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 authored on 2014/09/16 07:56:47
Showing 5 changed files
... ...
@@ -793,7 +793,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re
793 793
 	if vars == nil {
794 794
 		return fmt.Errorf("Missing parameter")
795 795
 	}
796
-	if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil {
796
+	if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w"), r.Form.Get("exec")).Run(); err != nil {
797 797
 		return err
798 798
 	}
799 799
 	return nil
... ...
@@ -1025,18 +1025,45 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp
1025 1025
 	return nil
1026 1026
 }
1027 1027
 
1028
-func postContainersExec(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
1028
+func postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
1029 1029
 	if err := parseForm(r); err != nil {
1030 1030
 		return nil
1031 1031
 	}
1032 1032
 	var (
1033
-		name = vars["name"]
1034
-		job  = eng.Job("exec", name)
1033
+		out          engine.Env
1034
+		name         = vars["name"]
1035
+		job          = eng.Job("execCreate", name)
1036
+		stdoutBuffer = bytes.NewBuffer(nil)
1037
+	)
1038
+	if err := job.DecodeEnv(r.Body); err != nil {
1039
+		return err
1040
+	}
1041
+
1042
+	job.Stdout.Add(stdoutBuffer)
1043
+	// Register an instance of Exec in container.
1044
+	if err := job.Run(); err != nil {
1045
+		fmt.Fprintf(os.Stderr, "Error setting up exec command in container %s: %s\n", name, err)
1046
+		return err
1047
+	}
1048
+	// Return the ID
1049
+	out.Set("Id", engine.Tail(stdoutBuffer, 1))
1050
+
1051
+	return writeJSON(w, http.StatusCreated, out)
1052
+}
1053
+
1054
+func postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
1055
+	if err := parseForm(r); err != nil {
1056
+		return nil
1057
+	}
1058
+	var (
1059
+		name             = vars["name"]
1060
+		job              = eng.Job("execStart", name)
1061
+		errOut io.Writer = os.Stderr
1035 1062
 	)
1063
+
1036 1064
 	if err := job.DecodeEnv(r.Body); err != nil {
1037 1065
 		return err
1038 1066
 	}
1039
-	var errOut io.Writer = os.Stderr
1040 1067
 
1041 1068
 	if !job.GetenvBool("Detach") {
1042 1069
 		// Setting up the streaming http interface.
... ...
@@ -1076,7 +1103,7 @@ func postContainersExec(eng *engine.Engine, version version.Version, w http.Resp
1076 1076
 	}
1077 1077
 	// Now run the user process in container.
1078 1078
 	if err := job.Run(); err != nil {
1079
-		fmt.Fprintf(errOut, "Error running in container %s: %s\n", name, err)
1079
+		fmt.Fprintf(errOut, "Error starting exec command in container %s: %s\n", name, err)
1080 1080
 		return err
1081 1081
 	}
1082 1082
 	w.WriteHeader(http.StatusNoContent)
... ...
@@ -1084,6 +1111,19 @@ func postContainersExec(eng *engine.Engine, version version.Version, w http.Resp
1084 1084
 	return nil
1085 1085
 }
1086 1086
 
1087
+func postContainerExecResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
1088
+	if err := parseForm(r); err != nil {
1089
+		return err
1090
+	}
1091
+	if vars == nil {
1092
+		return fmt.Errorf("Missing parameter")
1093
+	}
1094
+	if err := eng.Job("execResize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil {
1095
+		return err
1096
+	}
1097
+	return nil
1098
+}
1099
+
1087 1100
 func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
1088 1101
 	w.WriteHeader(http.StatusOK)
1089 1102
 	return nil
... ...
@@ -1206,7 +1246,9 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
1206 1206
 			"/containers/{name:.*}/resize":  postContainersResize,
1207 1207
 			"/containers/{name:.*}/attach":  postContainersAttach,
1208 1208
 			"/containers/{name:.*}/copy":    postContainersCopy,
1209
-			"/containers/{name:.*}/exec":    postContainersExec,
1209
+			"/containers/{name:.*}/exec":    postContainerExecCreate,
1210
+			"/exec/{name:.*}/start":         postContainerExecStart,
1211
+			"/exec/{name:.*}/resize":        postContainerExecResize,
1210 1212
 		},
1211 1213
 		"DELETE": {
1212 1214
 			"/containers/{name:.*}": deleteContainers,
... ...
@@ -1393,6 +1435,7 @@ func ListenAndServe(proto, addr string, job *engine.Job) error {
1393 1393
 					return err
1394 1394
 				}
1395 1395
 			}
1396
+
1396 1397
 		}
1397 1398
 		if err := os.Chmod(addr, 0660); err != nil {
1398 1399
 			return err
... ...
@@ -86,8 +86,9 @@ type Container struct {
86 86
 	VolumesRW  map[string]bool
87 87
 	hostConfig *runconfig.HostConfig
88 88
 
89
-	activeLinks map[string]*links.Link
90
-	monitor     *containerMonitor
89
+	activeLinks  map[string]*links.Link
90
+	monitor      *containerMonitor
91
+	execCommands *execStore
91 92
 }
92 93
 
93 94
 func (container *Container) FromDisk() error {
... ...
@@ -85,6 +85,7 @@ type Daemon struct {
85 85
 	repository     string
86 86
 	sysInitPath    string
87 87
 	containers     *contStore
88
+	execCommands   *execStore
88 89
 	graph          *graph.Graph
89 90
 	repositories   *graph.TagStore
90 91
 	idIndex        *truncindex.TruncIndex
... ...
@@ -122,7 +123,9 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
122 122
 		"unpause":           daemon.ContainerUnpause,
123 123
 		"wait":              daemon.ContainerWait,
124 124
 		"image_delete":      daemon.ImageDelete, // FIXME: see above
125
-		"exec":              daemon.ContainerExec,
125
+		"execCreate":        daemon.ContainerExecCreate,
126
+		"execStart":         daemon.ContainerExecStart,
127
+		"execResize":        daemon.ContainerExecResize,
126 128
 	} {
127 129
 		if err := eng.Register(name, method); err != nil {
128 130
 			return err
... ...
@@ -539,6 +542,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
539 539
 		Driver:          daemon.driver.String(),
540 540
 		ExecDriver:      daemon.execDriver.Name(),
541 541
 		State:           NewState(),
542
+		execCommands:    newExecStore(),
542 543
 	}
543 544
 	container.root = daemon.containerRoot(container.ID)
544 545
 
... ...
@@ -847,6 +851,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
847 847
 	daemon := &Daemon{
848 848
 		repository:     daemonRepo,
849 849
 		containers:     &contStore{s: make(map[string]*Container)},
850
+		execCommands:   newExecStore(),
850 851
 		graph:          g,
851 852
 		repositories:   repositories,
852 853
 		idIndex:        truncindex.NewTruncIndex([]string{}),
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"fmt"
7 7
 	"io"
8 8
 	"io/ioutil"
9
+	"sync"
9 10
 
10 11
 	"github.com/docker/docker/daemon/execdriver"
11 12
 	"github.com/docker/docker/engine"
... ...
@@ -16,52 +17,99 @@ import (
16 16
 	"github.com/docker/docker/utils"
17 17
 )
18 18
 
19
-type ExecConfig struct {
19
+type execConfig struct {
20
+	ID            string
20 21
 	ProcessConfig execdriver.ProcessConfig
21 22
 	StreamConfig
22
-	OpenStdin bool
23
+	OpenStdin  bool
24
+	OpenStderr bool
25
+	OpenStdout bool
26
+	Container  *Container
23 27
 }
24 28
 
25
-func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
26
-	if len(job.Args) != 1 {
27
-		return job.Errorf("Usage: %s [options] container command [args]", job.Name)
29
+type execStore struct {
30
+	s map[string]*execConfig
31
+	sync.Mutex
32
+}
33
+
34
+func newExecStore() *execStore {
35
+	return &execStore{s: make(map[string]*execConfig, 0)}
36
+}
37
+
38
+func (e *execStore) Add(id string, execConfig *execConfig) {
39
+	e.Lock()
40
+	e.s[id] = execConfig
41
+	e.Unlock()
42
+}
43
+
44
+func (e *execStore) Get(id string) *execConfig {
45
+	e.Lock()
46
+	res := e.s[id]
47
+	e.Unlock()
48
+	return res
49
+}
50
+
51
+func (e *execStore) Delete(id string) {
52
+	e.Lock()
53
+	delete(e.s, id)
54
+	e.Unlock()
55
+}
56
+
57
+func (execConfig *execConfig) Resize(h, w int) error {
58
+	return execConfig.ProcessConfig.Terminal.Resize(h, w)
59
+}
60
+
61
+func (d *Daemon) registerExecCommand(execConfig *execConfig) {
62
+	// Storing execs in container inorder to kill them gracefully whenever the container is stopped or removed.
63
+	execConfig.Container.execCommands.Add(execConfig.ID, execConfig)
64
+	// Storing execs in daemon for easy access via remote API.
65
+	d.execCommands.Add(execConfig.ID, execConfig)
66
+}
67
+
68
+func (d *Daemon) getExecConfig(name string) (*execConfig, error) {
69
+	if execConfig := d.execCommands.Get(name); execConfig != nil {
70
+		if !execConfig.Container.IsRunning() {
71
+			return nil, fmt.Errorf("Container %s is not not running", execConfig.Container.ID)
72
+		}
73
+		return execConfig, nil
28 74
 	}
29 75
 
30
-	var (
31
-		cStdin           io.ReadCloser
32
-		cStdout, cStderr io.Writer
33
-		cStdinCloser     io.Closer
34
-		name             = job.Args[0]
35
-	)
76
+	return nil, fmt.Errorf("No exec '%s' in found in daemon", name)
77
+}
36 78
 
79
+func (d *Daemon) unregisterExecCommand(execConfig *execConfig) {
80
+	execConfig.Container.execCommands.Delete(execConfig.ID)
81
+	d.execCommands.Delete(execConfig.ID)
82
+}
83
+
84
+func (d *Daemon) getActiveContainer(name string) (*Container, error) {
37 85
 	container := d.Get(name)
38 86
 
39 87
 	if container == nil {
40
-		return job.Errorf("No such container: %s", name)
88
+		return nil, fmt.Errorf("No such container: %s", name)
41 89
 	}
42 90
 
43 91
 	if !container.IsRunning() {
44
-		return job.Errorf("Container %s is not not running", name)
92
+		return nil, fmt.Errorf("Container %s is not not running", name)
45 93
 	}
46 94
 
47
-	config := runconfig.ExecConfigFromJob(job)
95
+	return container, nil
96
+}
48 97
 
49
-	if config.AttachStdin {
50
-		r, w := io.Pipe()
51
-		go func() {
52
-			defer w.Close()
53
-			io.Copy(w, job.Stdin)
54
-		}()
55
-		cStdin = r
56
-		cStdinCloser = job.Stdin
57
-	}
58
-	if config.AttachStdout {
59
-		cStdout = job.Stdout
98
+func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status {
99
+	if len(job.Args) != 1 {
100
+		return job.Errorf("Usage: %s [options] container command [args]", job.Name)
60 101
 	}
61
-	if config.AttachStderr {
62
-		cStderr = job.Stderr
102
+
103
+	var name = job.Args[0]
104
+
105
+	container, err := d.getActiveContainer(name)
106
+	if err != nil {
107
+		return job.Error(err)
63 108
 	}
64 109
 
110
+	config := runconfig.ExecConfigFromJob(job)
111
+
65 112
 	entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
66 113
 
67 114
 	processConfig := execdriver.ProcessConfig{
... ...
@@ -72,10 +120,60 @@ func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
72 72
 		Arguments:  args,
73 73
 	}
74 74
 
75
-	execConfig := &ExecConfig{
75
+	execConfig := &execConfig{
76
+		ID:            utils.GenerateRandomID(),
76 77
 		OpenStdin:     config.AttachStdin,
78
+		OpenStdout:    config.AttachStdout,
79
+		OpenStderr:    config.AttachStderr,
77 80
 		StreamConfig:  StreamConfig{},
78 81
 		ProcessConfig: processConfig,
82
+		Container:     container,
83
+	}
84
+
85
+	d.registerExecCommand(execConfig)
86
+
87
+	job.Printf("%s\n", execConfig.ID)
88
+
89
+	return engine.StatusOK
90
+}
91
+
92
+func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status {
93
+	if len(job.Args) != 2 {
94
+		return job.Errorf("Usage: %s [options] container exec", job.Name)
95
+	}
96
+
97
+	var (
98
+		cStdin           io.ReadCloser
99
+		cStdout, cStderr io.Writer
100
+		cStdinCloser     io.Closer
101
+		execName         = job.Args[0]
102
+	)
103
+
104
+	if execName == "" {
105
+		return job.Errorf("ExecName not specified. Cannot start exec command")
106
+	}
107
+
108
+	execConfig, err := d.getExecConfig(execName)
109
+	if err != nil {
110
+		return job.Error(err)
111
+	}
112
+
113
+	container := execConfig.Container
114
+
115
+	if execConfig.OpenStdin {
116
+		r, w := io.Pipe()
117
+		go func() {
118
+			defer w.Close()
119
+			io.Copy(w, job.Stdin)
120
+		}()
121
+		cStdin = r
122
+		cStdinCloser = job.Stdin
123
+	}
124
+	if execConfig.OpenStdout {
125
+		cStdout = job.Stdout
126
+	}
127
+	if execConfig.OpenStderr {
128
+		cStderr = job.Stderr
79 129
 	}
80 130
 
81 131
 	execConfig.StreamConfig.stderr = broadcastwriter.New()
... ...
@@ -87,13 +185,17 @@ func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
87 87
 		execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
88 88
 	}
89 89
 
90
-	attachErr := d.Attach(&execConfig.StreamConfig, config.AttachStdin, false, config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
90
+	attachErr := d.Attach(&execConfig.StreamConfig, execConfig.OpenStdin, false, execConfig.ProcessConfig.Tty, cStdin, cStdinCloser, cStdout, cStderr)
91 91
 
92 92
 	execErr := make(chan error)
93
+
94
+	// Remove exec from daemon and container.
95
+	defer d.unregisterExecCommand(execConfig)
96
+
93 97
 	go func() {
94 98
 		err := container.Exec(execConfig)
95 99
 		if err != nil {
96
-			execErr <- fmt.Errorf("Cannot run in container %s: %s", name, err)
100
+			execErr <- fmt.Errorf("Cannot run exec command %s in container %s: %s", execName, container.ID, err)
97 101
 		}
98 102
 	}()
99 103
 
... ...
@@ -110,11 +212,11 @@ func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
110 110
 	return engine.StatusOK
111 111
 }
112 112
 
113
-func (daemon *Daemon) Exec(c *Container, execConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
114
-	return daemon.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
113
+func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
114
+	return d.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
115 115
 }
116 116
 
117
-func (container *Container) Exec(execConfig *ExecConfig) error {
117
+func (container *Container) Exec(execConfig *execConfig) error {
118 118
 	container.Lock()
119 119
 	defer container.Unlock()
120 120
 
... ...
@@ -146,7 +248,7 @@ func (container *Container) Exec(execConfig *ExecConfig) error {
146 146
 	return nil
147 147
 }
148 148
 
149
-func (container *Container) monitorExec(execConfig *ExecConfig, callback execdriver.StartCallback) error {
149
+func (container *Container) monitorExec(execConfig *execConfig, callback execdriver.StartCallback) error {
150 150
 	var (
151 151
 		err      error
152 152
 		exitCode int
... ...
@@ -19,6 +19,7 @@ func (daemon *Daemon) ContainerResize(job *engine.Job) engine.Status {
19 19
 	if err != nil {
20 20
 		return job.Error(err)
21 21
 	}
22
+
22 23
 	if container := daemon.Get(name); container != nil {
23 24
 		if err := container.Resize(height, width); err != nil {
24 25
 			return job.Error(err)
... ...
@@ -27,3 +28,26 @@ func (daemon *Daemon) ContainerResize(job *engine.Job) engine.Status {
27 27
 	}
28 28
 	return job.Errorf("No such container: %s", name)
29 29
 }
30
+
31
+func (daemon *Daemon) ContainerExecResize(job *engine.Job) engine.Status {
32
+	if len(job.Args) != 3 {
33
+		return job.Errorf("Not enough arguments. Usage: %s EXEC HEIGHT WIDTH\n", job.Name)
34
+	}
35
+	name := job.Args[0]
36
+	height, err := strconv.Atoi(job.Args[1])
37
+	if err != nil {
38
+		return job.Error(err)
39
+	}
40
+	width, err := strconv.Atoi(job.Args[2])
41
+	if err != nil {
42
+		return job.Error(err)
43
+	}
44
+	execConfig, err := daemon.getExecConfig(name)
45
+	if err != nil {
46
+		return job.Error(err)
47
+	}
48
+	if err := execConfig.Resize(height, width); err != nil {
49
+		return job.Error(err)
50
+	}
51
+	return engine.StatusOK
52
+}