mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Adding Exec method to native execdriver.
Modified Attach() method to support docker exec. Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)
This commit is contained in:
parent
88a786d3c4
commit
f3c767d798
6 changed files with 135 additions and 13 deletions
|
@ -407,7 +407,7 @@ func (b *Builder) run(c *daemon.Container) error {
|
||||||
// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach
|
// FIXME (LK4D4): Also, maybe makes sense to call "logs" job, it is like attach
|
||||||
// but without hijacking for stdin. Also, with attach there can be race
|
// but without hijacking for stdin. Also, with attach there can be race
|
||||||
// condition because of some output already was printed before it.
|
// condition because of some output already was printed before it.
|
||||||
return <-b.Daemon.Attach(c, nil, nil, b.OutStream, b.ErrStream)
|
return <-b.Daemon.Attach(c, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, nil, b.OutStream, b.ErrStream)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/engine"
|
"github.com/docker/docker/engine"
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/jsonlog"
|
"github.com/docker/docker/pkg/jsonlog"
|
||||||
"github.com/docker/docker/pkg/log"
|
"github.com/docker/docker/pkg/log"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
cStderr = job.Stderr
|
cStderr = job.Stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
<-daemon.Attach(container, cStdin, cStdinCloser, cStdout, cStderr)
|
<-daemon.Attach(container, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
|
||||||
// If we are in stdinonce mode, wait for the process to end
|
// If we are in stdinonce mode, wait for the process to end
|
||||||
// otherwise, simply return
|
// otherwise, simply return
|
||||||
if container.Config.StdinOnce && !container.Config.Tty {
|
if container.Config.StdinOnce && !container.Config.Tty {
|
||||||
|
@ -119,15 +119,17 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
||||||
// Attach and ContainerAttach.
|
// Attach and ContainerAttach.
|
||||||
//
|
//
|
||||||
// This method is in use by builder/builder.go.
|
// This method is in use by builder/builder.go.
|
||||||
func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
||||||
var (
|
var (
|
||||||
cStdout, cStderr io.ReadCloser
|
cStdout, cStderr io.ReadCloser
|
||||||
nJobs int
|
nJobs int
|
||||||
errors = make(chan error, 3)
|
errors = make(chan error, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
if stdin != nil && container.Config.OpenStdin {
|
// Connect stdin of container to the http conn.
|
||||||
nJobs++
|
if stdin != nil && openStdin {
|
||||||
|
nJobs += 1
|
||||||
|
// Get the stdin pipe.
|
||||||
if cStdin, err := container.StdinPipe(); err != nil {
|
if cStdin, err := container.StdinPipe(); err != nil {
|
||||||
errors <- err
|
errors <- err
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,7 +137,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
||||||
log.Debugf("attach: stdin: begin")
|
log.Debugf("attach: stdin: begin")
|
||||||
defer log.Debugf("attach: stdin: end")
|
defer log.Debugf("attach: stdin: end")
|
||||||
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
|
||||||
if container.Config.StdinOnce && !container.Config.Tty {
|
if stdinOnce && !tty {
|
||||||
defer cStdin.Close()
|
defer cStdin.Close()
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -147,10 +149,11 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if container.Config.Tty {
|
if tty {
|
||||||
_, err = utils.CopyEscapable(cStdin, stdin)
|
_, err = utils.CopyEscapable(cStdin, stdin)
|
||||||
} else {
|
} else {
|
||||||
_, err = io.Copy(cStdin, stdin)
|
_, err = io.Copy(cStdin, stdin)
|
||||||
|
|
||||||
}
|
}
|
||||||
if err == io.ErrClosedPipe {
|
if err == io.ErrClosedPipe {
|
||||||
err = nil
|
err = nil
|
||||||
|
@ -163,7 +166,8 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if stdout != nil {
|
if stdout != nil {
|
||||||
nJobs++
|
nJobs += 1
|
||||||
|
// Get a reader end of a pipe that is attached as stdout to the container.
|
||||||
if p, err := container.StdoutPipe(); err != nil {
|
if p, err := container.StdoutPipe(); err != nil {
|
||||||
errors <- err
|
errors <- err
|
||||||
} else {
|
} else {
|
||||||
|
@ -172,7 +176,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
||||||
log.Debugf("attach: stdout: begin")
|
log.Debugf("attach: stdout: begin")
|
||||||
defer log.Debugf("attach: stdout: end")
|
defer log.Debugf("attach: stdout: end")
|
||||||
// If we are in StdinOnce mode, then close stdin
|
// If we are in StdinOnce mode, then close stdin
|
||||||
if container.Config.StdinOnce && stdin != nil {
|
if stdinOnce && stdin != nil {
|
||||||
defer stdin.Close()
|
defer stdin.Close()
|
||||||
}
|
}
|
||||||
if stdinCloser != nil {
|
if stdinCloser != nil {
|
||||||
|
@ -189,6 +193,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Point stdout of container to a no-op writer.
|
||||||
go func() {
|
go func() {
|
||||||
if stdinCloser != nil {
|
if stdinCloser != nil {
|
||||||
defer stdinCloser.Close()
|
defer stdinCloser.Close()
|
||||||
|
@ -201,7 +206,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if stderr != nil {
|
if stderr != nil {
|
||||||
nJobs++
|
nJobs += 1
|
||||||
if p, err := container.StderrPipe(); err != nil {
|
if p, err := container.StderrPipe(); err != nil {
|
||||||
errors <- err
|
errors <- err
|
||||||
} else {
|
} else {
|
||||||
|
@ -210,7 +215,8 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
||||||
log.Debugf("attach: stderr: begin")
|
log.Debugf("attach: stderr: begin")
|
||||||
defer log.Debugf("attach: stderr: end")
|
defer log.Debugf("attach: stderr: end")
|
||||||
// If we are in StdinOnce mode, then close stdin
|
// If we are in StdinOnce mode, then close stdin
|
||||||
if container.Config.StdinOnce && stdin != nil {
|
// Why are we closing stdin here and above while handling stdout?
|
||||||
|
if stdinOnce && stdin != nil {
|
||||||
defer stdin.Close()
|
defer stdin.Close()
|
||||||
}
|
}
|
||||||
if stdinCloser != nil {
|
if stdinCloser != nil {
|
||||||
|
@ -223,10 +229,12 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("attach: stderr: %s", err)
|
log.Errorf("attach: stderr: %s", err)
|
||||||
}
|
}
|
||||||
|
log.Debugf("stdout attach end")
|
||||||
errors <- err
|
errors <- err
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Point stderr at a no-op writer.
|
||||||
go func() {
|
go func() {
|
||||||
if stdinCloser != nil {
|
if stdinCloser != nil {
|
||||||
defer stdinCloser.Close()
|
defer stdinCloser.Close()
|
||||||
|
@ -252,7 +260,7 @@ func (daemon *Daemon) Attach(container *Container, stdin io.ReadCloser, stdinClo
|
||||||
|
|
||||||
// FIXME: how to clean up the stdin goroutine without the unwanted side effect
|
// FIXME: how to clean up the stdin goroutine without the unwanted side effect
|
||||||
// of closing the passed stdin? Add an intermediary io.Pipe?
|
// of closing the passed stdin? Add an intermediary io.Pipe?
|
||||||
for i := 0; i < nJobs; i++ {
|
for i := 0; i < nJobs; i += 1 {
|
||||||
log.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
log.Debugf("attach: waiting for job %d/%d", i+1, nJobs)
|
||||||
if err := <-errors; err != nil {
|
if err := <-errors; err != nil {
|
||||||
log.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err)
|
log.Errorf("attach: job %d returned error %s, aborting all jobs", i+1, err)
|
||||||
|
|
|
@ -42,6 +42,8 @@ type TtyTerminal interface {
|
||||||
|
|
||||||
type Driver interface {
|
type Driver interface {
|
||||||
Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code
|
Run(c *Command, pipes *Pipes, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code
|
||||||
|
// Exec executes the process in an existing container, blocks until the process exits and returns the exit code
|
||||||
|
Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, startCallback StartCallback) (int, error)
|
||||||
Kill(c *Command, sig int) error
|
Kill(c *Command, sig int) error
|
||||||
Pause(c *Command) error
|
Pause(c *Command) error
|
||||||
Unpause(c *Command) error
|
Unpause(c *Command) error
|
||||||
|
|
|
@ -527,3 +527,7 @@ func (t *TtyConsole) Close() error {
|
||||||
t.SlavePty.Close()
|
t.SlavePty.Close()
|
||||||
return t.MasterPty.Close()
|
return t.MasterPty.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||||
|
return -1, fmt.Errorf("Unsupported: Exec is not supported by the lxc driver")
|
||||||
|
}
|
||||||
|
|
68
daemon/execdriver/native/exec.go
Normal file
68
daemon/execdriver/native/exec.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer"
|
||||||
|
"github.com/docker/libcontainer/namespaces"
|
||||||
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
|
"github.com/docker/docker/reexec"
|
||||||
|
)
|
||||||
|
|
||||||
|
const commandName = "nsenter-exec"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
reexec.Register(commandName, nsenterExec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nsenterExec() {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
userArgs := findUserArgs()
|
||||||
|
|
||||||
|
config, err := loadConfigFromFd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("docker-exec: unable to receive config from sync pipe: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := namespaces.FinalizeSetns(config, userArgs); err != nil {
|
||||||
|
log.Fatalf("docker-exec: failed to exec: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||||
|
active := d.activeContainers[c.ID]
|
||||||
|
if active == nil {
|
||||||
|
return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
|
||||||
|
}
|
||||||
|
state, err := libcontainer.GetState(filepath.Join(d.root, c.ID))
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("State unavailable for container with ID %s. The container may have been cleaned up already. Error: %s", c.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var term execdriver.Terminal
|
||||||
|
|
||||||
|
if processConfig.Tty {
|
||||||
|
term, err = NewTtyConsole(processConfig, pipes)
|
||||||
|
} else {
|
||||||
|
term, err = execdriver.NewStdConsole(processConfig, pipes)
|
||||||
|
}
|
||||||
|
|
||||||
|
processConfig.Terminal = term
|
||||||
|
|
||||||
|
args := append([]string{processConfig.Entrypoint}, processConfig.Arguments...)
|
||||||
|
|
||||||
|
return namespaces.ExecIn(active.container, state, args, os.Args[0], "exec", processConfig.Stdin, processConfig.Stdout, processConfig.Stderr, processConfig.Console,
|
||||||
|
func(cmd *exec.Cmd) {
|
||||||
|
if startCallback != nil {
|
||||||
|
startCallback(&c.ProcessConfig, cmd.Process.Pid)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
40
daemon/execdriver/native/utils.go
Normal file
40
daemon/execdriver/native/utils.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package native
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer"
|
||||||
|
"github.com/docker/libcontainer/syncpipe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findUserArgs() []string {
|
||||||
|
i := 0
|
||||||
|
for _, a := range os.Args {
|
||||||
|
i++
|
||||||
|
|
||||||
|
if a == "--" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Args[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfigFromFd loads a container's config from the sync pipe that is provided by
|
||||||
|
// fd 3 when running a process
|
||||||
|
func loadConfigFromFd() (*libcontainer.Config, error) {
|
||||||
|
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config *libcontainer.Config
|
||||||
|
if err := syncPipe.ReadFromParent(&config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue