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)
... | ... |
@@ -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 |
+} |