Browse code

Adding support for docker exec in daemon.

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

Vishnu Kannan authored on 2014/09/09 13:19:32
Showing 7 changed files
... ...
@@ -407,7 +407,7 @@ func (b *Builder) run(c *daemon.Container) error {
407 407
 			// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach
408 408
 			// but without hijacking for stdin. Also, with attach there can be race
409 409
 			// condition because of some output already was printed before it.
410
-			return <-b.Daemon.Attach(c, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, nil, b.OutStream, b.ErrStream)
410
+			return <-b.Daemon.Attach(&c.StreamConfig, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, nil, b.OutStream, b.ErrStream)
411 411
 		})
412 412
 	}
413 413
 
... ...
@@ -8,9 +8,9 @@ import (
8 8
 	"time"
9 9
 
10 10
 	"github.com/docker/docker/engine"
11
+	"github.com/docker/docker/pkg/ioutils"
11 12
 	"github.com/docker/docker/pkg/jsonlog"
12 13
 	"github.com/docker/docker/pkg/log"
13
-	"github.com/docker/docker/pkg/ioutils"
14 14
 	"github.com/docker/docker/utils"
15 15
 )
16 16
 
... ...
@@ -103,7 +103,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
103 103
 			cStderr = job.Stderr
104 104
 		}
105 105
 
106
-		<-daemon.Attach(container, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
106
+		<-daemon.Attach(&container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
107 107
 		// If we are in stdinonce mode, wait for the process to end
108 108
 		// otherwise, simply return
109 109
 		if container.Config.StdinOnce && !container.Config.Tty {
... ...
@@ -119,7 +119,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
119 119
 // Attach and ContainerAttach.
120 120
 //
121 121
 // This method is in use by builder/builder.go.
122
-func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
122
+func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
123 123
 	var (
124 124
 		cStdout, cStderr io.ReadCloser
125 125
 		nJobs            int
... ...
@@ -130,7 +130,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
130 130
 	if stdin != nil && openStdin {
131 131
 		nJobs += 1
132 132
 		// Get the stdin pipe.
133
-		if cStdin, err := container.StdinPipe(); err != nil {
133
+		if cStdin, err := streamConfig.StdinPipe(); err != nil {
134 134
 			errors <- err
135 135
 		} else {
136 136
 			go func() {
... ...
@@ -168,7 +168,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
168 168
 	if stdout != nil {
169 169
 		nJobs += 1
170 170
 		// Get a reader end of a pipe that is attached as stdout to the container.
171
-		if p, err := container.StdoutPipe(); err != nil {
171
+		if p, err := streamConfig.StdoutPipe(); err != nil {
172 172
 			errors <- err
173 173
 		} else {
174 174
 			cStdout = p
... ...
@@ -198,7 +198,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
198 198
 			if stdinCloser != nil {
199 199
 				defer stdinCloser.Close()
200 200
 			}
201
-			if cStdout, err := container.StdoutPipe(); err != nil {
201
+			if cStdout, err := streamConfig.StdoutPipe(); err != nil {
202 202
 				log.Errorf("attach: stdout pipe: %s", err)
203 203
 			} else {
204 204
 				io.Copy(&ioutils.NopWriter{}, cStdout)
... ...
@@ -207,7 +207,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
207 207
 	}
208 208
 	if stderr != nil {
209 209
 		nJobs += 1
210
-		if p, err := container.StderrPipe(); err != nil {
210
+		if p, err := streamConfig.StderrPipe(); err != nil {
211 211
 			errors <- err
212 212
 		} else {
213 213
 			cStderr = p
... ...
@@ -240,7 +240,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
240 240
 				defer stdinCloser.Close()
241 241
 			}
242 242
 
243
-			if cStderr, err := container.StderrPipe(); err != nil {
243
+			if cStderr, err := streamConfig.StderrPipe(); err != nil {
244 244
 				log.Errorf("attach: stdout pipe: %s", err)
245 245
 			} else {
246 246
 				io.Copy(&ioutils.NopWriter{}, cStderr)
... ...
@@ -496,17 +496,17 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) {
496 496
 	}
497 497
 }
498 498
 
499
-func (daemon *Daemon) getEntrypointAndArgs(config *runconfig.Config) (string, []string) {
499
+func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) (string, []string) {
500 500
 	var (
501 501
 		entrypoint string
502 502
 		args       []string
503 503
 	)
504
-	if len(config.Entrypoint) != 0 {
505
-		entrypoint = config.Entrypoint[0]
506
-		args = append(config.Entrypoint[1:], config.Cmd...)
504
+	if len(configEntrypoint) != 0 {
505
+		entrypoint = configEntrypoint[0]
506
+		args = append(configEntrypoint[1:], configCmd...)
507 507
 	} else {
508
-		entrypoint = config.Cmd[0]
509
-		args = config.Cmd[1:]
508
+		entrypoint = configCmd[0]
509
+		args = configCmd[1:]
510 510
 	}
511 511
 	return entrypoint, args
512 512
 }
... ...
@@ -522,7 +522,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
522 522
 	}
523 523
 
524 524
 	daemon.generateHostname(id, config)
525
-	entrypoint, args := daemon.getEntrypointAndArgs(config)
525
+	entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd)
526 526
 
527 527
 	container := &Container{
528 528
 		// FIXME: we should generate the ID here instead of receiving it as an argument
529 529
new file mode 100644
... ...
@@ -0,0 +1,180 @@
0
+// build linux
1
+
2
+package daemon
3
+
4
+import (
5
+	"fmt"
6
+	"io"
7
+	"io/ioutil"
8
+
9
+	"github.com/docker/docker/daemon/execdriver"
10
+	"github.com/docker/docker/engine"
11
+	"github.com/docker/docker/pkg/broadcastwriter"
12
+	"github.com/docker/docker/pkg/ioutils"
13
+	"github.com/docker/docker/pkg/log"
14
+	"github.com/docker/docker/runconfig"
15
+	"github.com/docker/docker/utils"
16
+)
17
+
18
+type ExecConfig struct {
19
+	ProcessConfig execdriver.ProcessConfig
20
+	StreamConfig  StreamConfig
21
+	OpenStdin     bool
22
+}
23
+
24
+func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
25
+	if len(job.Args) != 1 {
26
+		return job.Errorf("Usage: %s container_id command", job.Name)
27
+	}
28
+
29
+	var (
30
+		cStdin           io.ReadCloser
31
+		cStdout, cStderr io.Writer
32
+		cStdinCloser     io.Closer
33
+		name             = job.Args[0]
34
+	)
35
+
36
+	container := d.Get(name)
37
+
38
+	if container == nil {
39
+		return job.Errorf("No such container: %s", name)
40
+	}
41
+
42
+	if !container.State.IsRunning() {
43
+		return job.Errorf("Container %s is not not running", name)
44
+	}
45
+
46
+	config := runconfig.ExecConfigFromJob(job)
47
+
48
+	if config.AttachStdin {
49
+		r, w := io.Pipe()
50
+		go func() {
51
+			defer w.Close()
52
+			io.Copy(w, job.Stdin)
53
+		}()
54
+		cStdin = r
55
+		cStdinCloser = job.Stdin
56
+	}
57
+	if config.AttachStdout {
58
+		cStdout = job.Stdout
59
+	}
60
+	if config.AttachStderr {
61
+		cStderr = job.Stderr
62
+	}
63
+
64
+	entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
65
+
66
+	processConfig := execdriver.ProcessConfig{
67
+		Privileged: config.Privileged,
68
+		User:       config.User,
69
+		Tty:        config.Tty,
70
+		Entrypoint: entrypoint,
71
+		Arguments:  args,
72
+	}
73
+
74
+	execConfig := &ExecConfig{
75
+		OpenStdin:     config.AttachStdin,
76
+		StreamConfig:  StreamConfig{},
77
+		ProcessConfig: processConfig,
78
+	}
79
+
80
+	execConfig.StreamConfig.stderr = broadcastwriter.New()
81
+	execConfig.StreamConfig.stdout = broadcastwriter.New()
82
+	// Attach to stdin
83
+	if execConfig.OpenStdin {
84
+		execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdinPipe = io.Pipe()
85
+	} else {
86
+		execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
87
+	}
88
+
89
+	var execErr, attachErr chan error
90
+	go func() {
91
+		attachErr = d.Attach(&execConfig.StreamConfig, config.AttachStdin, false, config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
92
+	}()
93
+
94
+	go func() {
95
+		err := container.Exec(execConfig)
96
+		if err != nil {
97
+			err = fmt.Errorf("Cannot run in container %s: %s", name, err)
98
+		}
99
+		execErr <- err
100
+	}()
101
+
102
+	select {
103
+	case err := <-attachErr:
104
+		return job.Errorf("attach failed with error: %s", err)
105
+	case err := <-execErr:
106
+		return job.Error(err)
107
+	}
108
+
109
+	return engine.StatusOK
110
+}
111
+
112
+func (daemon *Daemon) Exec(c *Container, execConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
113
+	return daemon.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
114
+}
115
+
116
+func (container *Container) Exec(execConfig *ExecConfig) error {
117
+	container.Lock()
118
+	defer container.Unlock()
119
+
120
+	waitStart := make(chan struct{})
121
+
122
+	callback := func(processConfig *execdriver.ProcessConfig, pid int) {
123
+		if processConfig.Tty {
124
+			// The callback is called after the process Start()
125
+			// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace
126
+			// which we close here.
127
+			if c, ok := processConfig.Stdout.(io.Closer); ok {
128
+				c.Close()
129
+			}
130
+		}
131
+		close(waitStart)
132
+	}
133
+
134
+	// We use a callback here instead of a goroutine and an chan for
135
+	// syncronization purposes
136
+	cErr := utils.Go(func() error { return container.monitorExec(execConfig, callback) })
137
+
138
+	// Exec should not return until the process is actually running
139
+	select {
140
+	case <-waitStart:
141
+	case err := <-cErr:
142
+		return err
143
+	}
144
+
145
+	return nil
146
+}
147
+
148
+func (container *Container) monitorExec(execConfig *ExecConfig, callback execdriver.StartCallback) error {
149
+	var (
150
+		err      error
151
+		exitCode int
152
+	)
153
+
154
+	pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin)
155
+	exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback)
156
+	if err != nil {
157
+		log.Errorf("Error running command in existing container %s: %s", container.ID, err)
158
+	}
159
+
160
+	log.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode)
161
+	if execConfig.OpenStdin {
162
+		if err := execConfig.StreamConfig.stdin.Close(); err != nil {
163
+			log.Errorf("Error closing stdin while running in %s: %s", container.ID, err)
164
+		}
165
+	}
166
+	if err := execConfig.StreamConfig.stdout.Clean(); err != nil {
167
+		log.Errorf("Error closing stdout while running in %s: %s", container.ID, err)
168
+	}
169
+	if err := execConfig.StreamConfig.stderr.Clean(); err != nil {
170
+		log.Errorf("Error closing stderr while running in %s: %s", container.ID, err)
171
+	}
172
+	if execConfig.ProcessConfig.Terminal != nil {
173
+		if err := execConfig.ProcessConfig.Terminal.Close(); err != nil {
174
+			log.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
175
+		}
176
+	}
177
+
178
+	return err
179
+}
... ...
@@ -10,10 +10,10 @@ import (
10 10
 	"path/filepath"
11 11
 	"runtime"
12 12
 
13
+	"github.com/docker/docker/daemon/execdriver"
14
+	"github.com/docker/docker/reexec"
13 15
 	"github.com/docker/libcontainer"
14 16
 	"github.com/docker/libcontainer/namespaces"
15
-	"github.com/docker/docker/daemon/execdriver"
16
-	"github.com/docker/docker/reexec"	
17 17
 )
18 18
 
19 19
 const commandName = "nsenter-exec"
... ...
@@ -59,7 +59,7 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
59 59
 
60 60
 	args := append([]string{processConfig.Entrypoint}, processConfig.Arguments...)
61 61
 
62
-	return namespaces.ExecIn(active.container, state, args, os.Args[0], "exec", processConfig.Stdin, processConfig.Stdout, processConfig.Stderr, processConfig.Console, 
62
+	return namespaces.ExecIn(active.container, state, args, os.Args[0], "exec", processConfig.Stdin, processConfig.Stdout, processConfig.Stderr, processConfig.Console,
63 63
 		func(cmd *exec.Cmd) {
64 64
 			if startCallback != nil {
65 65
 				startCallback(&c.ProcessConfig, cmd.Process.Pid)
... ...
@@ -4,7 +4,7 @@ package native
4 4
 
5 5
 import (
6 6
 	"os"
7
-	
7
+
8 8
 	"github.com/docker/libcontainer"
9 9
 	"github.com/docker/libcontainer/syncpipe"
10 10
 )
... ...
@@ -37,4 +37,3 @@ func loadConfigFromFd() (*libcontainer.Config, error) {
37 37
 
38 38
 	return config, nil
39 39
 }
40
-
41 40
new file mode 100644
... ...
@@ -0,0 +1,78 @@
0
+package runconfig
1
+
2
+import (
3
+	"github.com/docker/docker/engine"
4
+	flag "github.com/docker/docker/pkg/mflag"
5
+)
6
+
7
+type ExecConfig struct {
8
+	User         string
9
+	Privileged   bool
10
+	Tty          bool
11
+	Container    string
12
+	AttachStdin  bool
13
+	AttachStderr bool
14
+	AttachStdout bool
15
+	Detach       bool
16
+	Cmd          []string
17
+	Hostname     string
18
+}
19
+
20
+func ExecConfigFromJob(job *engine.Job) *ExecConfig {
21
+	execConfig := &ExecConfig{
22
+		User:         job.Getenv("User"),
23
+		Privileged:   job.GetenvBool("Privileged"),
24
+		Tty:          job.GetenvBool("Tty"),
25
+		Container:    job.Getenv("Container"),
26
+		AttachStdin:  job.GetenvBool("AttachStdin"),
27
+		AttachStderr: job.GetenvBool("AttachStderr"),
28
+		AttachStdout: job.GetenvBool("AttachStdout"),
29
+	}
30
+	if Cmd := job.GetenvList("Cmd"); Cmd != nil {
31
+		execConfig.Cmd = Cmd
32
+	}
33
+
34
+	return execConfig
35
+}
36
+
37
+func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) {
38
+	var (
39
+		flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
40
+		flStdin      = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
41
+		flTty        = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
42
+		flHostname   = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
43
+		flUser       = cmd.String([]string{"u", "-user"}, "", "Username or UID")
44
+		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background")
45
+		execCmd      []string
46
+		container    string
47
+	)
48
+	if err := cmd.Parse(args); err != nil {
49
+		return nil, err
50
+	}
51
+	parsedArgs := cmd.Args()
52
+	if len(parsedArgs) > 1 {
53
+		container = cmd.Arg(0)
54
+		execCmd = parsedArgs[1:]
55
+	}
56
+
57
+	execConfig := &ExecConfig{
58
+		User:       *flUser,
59
+		Privileged: *flPrivileged,
60
+		Tty:        *flTty,
61
+		Cmd:        execCmd,
62
+		Container:  container,
63
+		Hostname:   *flHostname,
64
+		Detach:     *flDetach,
65
+	}
66
+
67
+	// If -d is not set, attach to everything by default
68
+	if !*flDetach {
69
+		execConfig.AttachStdout = true
70
+		execConfig.AttachStderr = true
71
+		if *flStdin {
72
+			execConfig.AttachStdin = true
73
+		}
74
+	}
75
+
76
+	return execConfig, nil
77
+}