Skip to content

Commit 5130fe5

Browse files
committed
Adding support for docker exec in daemon.
Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <[email protected]> (github: vishh)
1 parent f3c767d commit 5130fe5

7 files changed

Lines changed: 278 additions & 21 deletions

File tree

builder/internals.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ func (b *Builder) run(c *daemon.Container) error {
407407
// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach
408408
// but without hijacking for stdin. Also, with attach there can be race
409409
// 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)
411411
})
412412
}
413413

daemon/attach.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import (
88
"time"
99

1010
"github.com/docker/docker/engine"
11+
"github.com/docker/docker/pkg/ioutils"
1112
"github.com/docker/docker/pkg/jsonlog"
1213
"github.com/docker/docker/pkg/log"
13-
"github.com/docker/docker/pkg/ioutils"
1414
"github.com/docker/docker/utils"
1515
)
1616

@@ -103,7 +103,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
103103
cStderr = job.Stderr
104104
}
105105

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)
107107
// If we are in stdinonce mode, wait for the process to end
108108
// otherwise, simply return
109109
if container.Config.StdinOnce && !container.Config.Tty {
@@ -119,7 +119,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
119119
// Attach and ContainerAttach.
120120
//
121121
// 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 {
123123
var (
124124
cStdout, cStderr io.ReadCloser
125125
nJobs int
@@ -130,7 +130,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
130130
if stdin != nil && openStdin {
131131
nJobs += 1
132132
// Get the stdin pipe.
133-
if cStdin, err := container.StdinPipe(); err != nil {
133+
if cStdin, err := streamConfig.StdinPipe(); err != nil {
134134
errors <- err
135135
} else {
136136
go func() {
@@ -168,7 +168,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
168168
if stdout != nil {
169169
nJobs += 1
170170
// 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 {
172172
errors <- err
173173
} else {
174174
cStdout = p
@@ -198,7 +198,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
198198
if stdinCloser != nil {
199199
defer stdinCloser.Close()
200200
}
201-
if cStdout, err := container.StdoutPipe(); err != nil {
201+
if cStdout, err := streamConfig.StdoutPipe(); err != nil {
202202
log.Errorf("attach: stdout pipe: %s", err)
203203
} else {
204204
io.Copy(&ioutils.NopWriter{}, cStdout)
@@ -207,7 +207,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
207207
}
208208
if stderr != nil {
209209
nJobs += 1
210-
if p, err := container.StderrPipe(); err != nil {
210+
if p, err := streamConfig.StderrPipe(); err != nil {
211211
errors <- err
212212
} else {
213213
cStderr = p
@@ -240,7 +240,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
240240
defer stdinCloser.Close()
241241
}
242242

243-
if cStderr, err := container.StderrPipe(); err != nil {
243+
if cStderr, err := streamConfig.StderrPipe(); err != nil {
244244
log.Errorf("attach: stdout pipe: %s", err)
245245
} else {
246246
io.Copy(&ioutils.NopWriter{}, cStderr)

daemon/daemon.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -496,17 +496,17 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) {
496496
}
497497
}
498498

499-
func (daemon *Daemon) getEntrypointAndArgs(config *runconfig.Config) (string, []string) {
499+
func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) (string, []string) {
500500
var (
501501
entrypoint string
502502
args []string
503503
)
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...)
507507
} else {
508-
entrypoint = config.Cmd[0]
509-
args = config.Cmd[1:]
508+
entrypoint = configCmd[0]
509+
args = configCmd[1:]
510510
}
511511
return entrypoint, args
512512
}
@@ -522,7 +522,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
522522
}
523523

524524
daemon.generateHostname(id, config)
525-
entrypoint, args := daemon.getEntrypointAndArgs(config)
525+
entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd)
526526

527527
container := &Container{
528528
// FIXME: we should generate the ID here instead of receiving it as an argument

daemon/exec.go

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// build linux
2+
3+
package daemon
4+
5+
import (
6+
"fmt"
7+
"io"
8+
"io/ioutil"
9+
10+
"github.com/docker/docker/daemon/execdriver"
11+
"github.com/docker/docker/engine"
12+
"github.com/docker/docker/pkg/broadcastwriter"
13+
"github.com/docker/docker/pkg/ioutils"
14+
"github.com/docker/docker/pkg/log"
15+
"github.com/docker/docker/runconfig"
16+
"github.com/docker/docker/utils"
17+
)
18+
19+
type ExecConfig struct {
20+
ProcessConfig execdriver.ProcessConfig
21+
StreamConfig StreamConfig
22+
OpenStdin bool
23+
}
24+
25+
func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
26+
if len(job.Args) != 1 {
27+
return job.Errorf("Usage: %s container_id command", job.Name)
28+
}
29+
30+
var (
31+
cStdin io.ReadCloser
32+
cStdout, cStderr io.Writer
33+
cStdinCloser io.Closer
34+
name = job.Args[0]
35+
)
36+
37+
container := d.Get(name)
38+
39+
if container == nil {
40+
return job.Errorf("No such container: %s", name)
41+
}
42+
43+
if !container.State.IsRunning() {
44+
return job.Errorf("Container %s is not not running", name)
45+
}
46+
47+
config := runconfig.ExecConfigFromJob(job)
48+
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
60+
}
61+
if config.AttachStderr {
62+
cStderr = job.Stderr
63+
}
64+
65+
entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
66+
67+
processConfig := execdriver.ProcessConfig{
68+
Privileged: config.Privileged,
69+
User: config.User,
70+
Tty: config.Tty,
71+
Entrypoint: entrypoint,
72+
Arguments: args,
73+
}
74+
75+
execConfig := &ExecConfig{
76+
OpenStdin: config.AttachStdin,
77+
StreamConfig: StreamConfig{},
78+
ProcessConfig: processConfig,
79+
}
80+
81+
execConfig.StreamConfig.stderr = broadcastwriter.New()
82+
execConfig.StreamConfig.stdout = broadcastwriter.New()
83+
// Attach to stdin
84+
if execConfig.OpenStdin {
85+
execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdinPipe = io.Pipe()
86+
} else {
87+
execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
88+
}
89+
90+
var execErr, attachErr chan error
91+
go func() {
92+
attachErr = d.Attach(&execConfig.StreamConfig, config.AttachStdin, false, config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
93+
}()
94+
95+
go func() {
96+
err := container.Exec(execConfig)
97+
if err != nil {
98+
err = fmt.Errorf("Cannot run in container %s: %s", name, err)
99+
}
100+
execErr <- err
101+
}()
102+
103+
select {
104+
case err := <-attachErr:
105+
return job.Errorf("attach failed with error: %s", err)
106+
case err := <-execErr:
107+
return job.Error(err)
108+
}
109+
110+
return engine.StatusOK
111+
}
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)
115+
}
116+
117+
func (container *Container) Exec(execConfig *ExecConfig) error {
118+
container.Lock()
119+
defer container.Unlock()
120+
121+
waitStart := make(chan struct{})
122+
123+
callback := func(processConfig *execdriver.ProcessConfig, pid int) {
124+
if processConfig.Tty {
125+
// The callback is called after the process Start()
126+
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace
127+
// which we close here.
128+
if c, ok := processConfig.Stdout.(io.Closer); ok {
129+
c.Close()
130+
}
131+
}
132+
close(waitStart)
133+
}
134+
135+
// We use a callback here instead of a goroutine and an chan for
136+
// syncronization purposes
137+
cErr := utils.Go(func() error { return container.monitorExec(execConfig, callback) })
138+
139+
// Exec should not return until the process is actually running
140+
select {
141+
case <-waitStart:
142+
case err := <-cErr:
143+
return err
144+
}
145+
146+
return nil
147+
}
148+
149+
func (container *Container) monitorExec(execConfig *ExecConfig, callback execdriver.StartCallback) error {
150+
var (
151+
err error
152+
exitCode int
153+
)
154+
155+
pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin)
156+
exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback)
157+
if err != nil {
158+
log.Errorf("Error running command in existing container %s: %s", container.ID, err)
159+
}
160+
161+
log.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode)
162+
if execConfig.OpenStdin {
163+
if err := execConfig.StreamConfig.stdin.Close(); err != nil {
164+
log.Errorf("Error closing stdin while running in %s: %s", container.ID, err)
165+
}
166+
}
167+
if err := execConfig.StreamConfig.stdout.Clean(); err != nil {
168+
log.Errorf("Error closing stdout while running in %s: %s", container.ID, err)
169+
}
170+
if err := execConfig.StreamConfig.stderr.Clean(); err != nil {
171+
log.Errorf("Error closing stderr while running in %s: %s", container.ID, err)
172+
}
173+
if execConfig.ProcessConfig.Terminal != nil {
174+
if err := execConfig.ProcessConfig.Terminal.Close(); err != nil {
175+
log.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
176+
}
177+
}
178+
179+
return err
180+
}

daemon/execdriver/native/exec.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import (
1010
"path/filepath"
1111
"runtime"
1212

13+
"github.com/docker/docker/daemon/execdriver"
14+
"github.com/docker/docker/reexec"
1315
"github.com/docker/libcontainer"
1416
"github.com/docker/libcontainer/namespaces"
15-
"github.com/docker/docker/daemon/execdriver"
16-
"github.com/docker/docker/reexec"
1717
)
1818

1919
const commandName = "nsenter-exec"
@@ -59,7 +59,7 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
5959

6060
args := append([]string{processConfig.Entrypoint}, processConfig.Arguments...)
6161

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,
6363
func(cmd *exec.Cmd) {
6464
if startCallback != nil {
6565
startCallback(&c.ProcessConfig, cmd.Process.Pid)

daemon/execdriver/native/utils.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package native
44

55
import (
66
"os"
7-
7+
88
"github.com/docker/libcontainer"
99
"github.com/docker/libcontainer/syncpipe"
1010
)
@@ -37,4 +37,3 @@ func loadConfigFromFd() (*libcontainer.Config, error) {
3737

3838
return config, nil
3939
}
40-

0 commit comments

Comments
 (0)