mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Adding support for docker exec in daemon.
Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)
This commit is contained in:
parent
f3c767d798
commit
5130fe5d38
7 changed files with 278 additions and 21 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, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, nil, b.OutStream, b.ErrStream)
|
return <-b.Daemon.Attach(&c.StreamConfig, 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, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
|
<-daemon.Attach(&container.StreamConfig, 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,7 +119,7 @@ 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, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error {
|
func (daemon *Daemon) Attach(streamConfig *StreamConfig, 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
|
||||||
|
@ -130,7 +130,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
||||||
if stdin != nil && openStdin {
|
if stdin != nil && openStdin {
|
||||||
nJobs += 1
|
nJobs += 1
|
||||||
// Get the stdin pipe.
|
// Get the stdin pipe.
|
||||||
if cStdin, err := container.StdinPipe(); err != nil {
|
if cStdin, err := streamConfig.StdinPipe(); err != nil {
|
||||||
errors <- err
|
errors <- err
|
||||||
} else {
|
} else {
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -168,7 +168,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
||||||
if stdout != nil {
|
if stdout != nil {
|
||||||
nJobs += 1
|
nJobs += 1
|
||||||
// Get a reader end of a pipe that is attached as stdout to the container.
|
// 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 := streamConfig.StdoutPipe(); err != nil {
|
||||||
errors <- err
|
errors <- err
|
||||||
} else {
|
} else {
|
||||||
cStdout = p
|
cStdout = p
|
||||||
|
@ -198,7 +198,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
||||||
if stdinCloser != nil {
|
if stdinCloser != nil {
|
||||||
defer stdinCloser.Close()
|
defer stdinCloser.Close()
|
||||||
}
|
}
|
||||||
if cStdout, err := container.StdoutPipe(); err != nil {
|
if cStdout, err := streamConfig.StdoutPipe(); err != nil {
|
||||||
log.Errorf("attach: stdout pipe: %s", err)
|
log.Errorf("attach: stdout pipe: %s", err)
|
||||||
} else {
|
} else {
|
||||||
io.Copy(&ioutils.NopWriter{}, cStdout)
|
io.Copy(&ioutils.NopWriter{}, cStdout)
|
||||||
|
@ -207,7 +207,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
||||||
}
|
}
|
||||||
if stderr != nil {
|
if stderr != nil {
|
||||||
nJobs += 1
|
nJobs += 1
|
||||||
if p, err := container.StderrPipe(); err != nil {
|
if p, err := streamConfig.StderrPipe(); err != nil {
|
||||||
errors <- err
|
errors <- err
|
||||||
} else {
|
} else {
|
||||||
cStderr = p
|
cStderr = p
|
||||||
|
@ -240,7 +240,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
||||||
defer stdinCloser.Close()
|
defer stdinCloser.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
if cStderr, err := container.StderrPipe(); err != nil {
|
if cStderr, err := streamConfig.StderrPipe(); err != nil {
|
||||||
log.Errorf("attach: stdout pipe: %s", err)
|
log.Errorf("attach: stdout pipe: %s", err)
|
||||||
} else {
|
} else {
|
||||||
io.Copy(&ioutils.NopWriter{}, cStderr)
|
io.Copy(&ioutils.NopWriter{}, cStderr)
|
||||||
|
|
|
@ -496,17 +496,17 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (daemon *Daemon) getEntrypointAndArgs(config *runconfig.Config) (string, []string) {
|
func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) (string, []string) {
|
||||||
var (
|
var (
|
||||||
entrypoint string
|
entrypoint string
|
||||||
args []string
|
args []string
|
||||||
)
|
)
|
||||||
if len(config.Entrypoint) != 0 {
|
if len(configEntrypoint) != 0 {
|
||||||
entrypoint = config.Entrypoint[0]
|
entrypoint = configEntrypoint[0]
|
||||||
args = append(config.Entrypoint[1:], config.Cmd...)
|
args = append(configEntrypoint[1:], configCmd...)
|
||||||
} else {
|
} else {
|
||||||
entrypoint = config.Cmd[0]
|
entrypoint = configCmd[0]
|
||||||
args = config.Cmd[1:]
|
args = configCmd[1:]
|
||||||
}
|
}
|
||||||
return entrypoint, args
|
return entrypoint, args
|
||||||
}
|
}
|
||||||
|
@ -522,7 +522,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
|
||||||
}
|
}
|
||||||
|
|
||||||
daemon.generateHostname(id, config)
|
daemon.generateHostname(id, config)
|
||||||
entrypoint, args := daemon.getEntrypointAndArgs(config)
|
entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd)
|
||||||
|
|
||||||
container := &Container{
|
container := &Container{
|
||||||
// FIXME: we should generate the ID here instead of receiving it as an argument
|
// FIXME: we should generate the ID here instead of receiving it as an argument
|
||||||
|
|
180
daemon/exec.go
Normal file
180
daemon/exec.go
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
// build linux
|
||||||
|
|
||||||
|
package daemon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
|
"github.com/docker/docker/engine"
|
||||||
|
"github.com/docker/docker/pkg/broadcastwriter"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
|
"github.com/docker/docker/pkg/log"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExecConfig struct {
|
||||||
|
ProcessConfig execdriver.ProcessConfig
|
||||||
|
StreamConfig StreamConfig
|
||||||
|
OpenStdin bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Daemon) ContainerExec(job *engine.Job) engine.Status {
|
||||||
|
if len(job.Args) != 1 {
|
||||||
|
return job.Errorf("Usage: %s container_id command", job.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cStdin io.ReadCloser
|
||||||
|
cStdout, cStderr io.Writer
|
||||||
|
cStdinCloser io.Closer
|
||||||
|
name = job.Args[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
container := d.Get(name)
|
||||||
|
|
||||||
|
if container == nil {
|
||||||
|
return job.Errorf("No such container: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !container.State.IsRunning() {
|
||||||
|
return job.Errorf("Container %s is not not running", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := runconfig.ExecConfigFromJob(job)
|
||||||
|
|
||||||
|
if config.AttachStdin {
|
||||||
|
r, w := io.Pipe()
|
||||||
|
go func() {
|
||||||
|
defer w.Close()
|
||||||
|
io.Copy(w, job.Stdin)
|
||||||
|
}()
|
||||||
|
cStdin = r
|
||||||
|
cStdinCloser = job.Stdin
|
||||||
|
}
|
||||||
|
if config.AttachStdout {
|
||||||
|
cStdout = job.Stdout
|
||||||
|
}
|
||||||
|
if config.AttachStderr {
|
||||||
|
cStderr = job.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd)
|
||||||
|
|
||||||
|
processConfig := execdriver.ProcessConfig{
|
||||||
|
Privileged: config.Privileged,
|
||||||
|
User: config.User,
|
||||||
|
Tty: config.Tty,
|
||||||
|
Entrypoint: entrypoint,
|
||||||
|
Arguments: args,
|
||||||
|
}
|
||||||
|
|
||||||
|
execConfig := &ExecConfig{
|
||||||
|
OpenStdin: config.AttachStdin,
|
||||||
|
StreamConfig: StreamConfig{},
|
||||||
|
ProcessConfig: processConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
execConfig.StreamConfig.stderr = broadcastwriter.New()
|
||||||
|
execConfig.StreamConfig.stdout = broadcastwriter.New()
|
||||||
|
// Attach to stdin
|
||||||
|
if execConfig.OpenStdin {
|
||||||
|
execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdinPipe = io.Pipe()
|
||||||
|
} else {
|
||||||
|
execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
|
||||||
|
}
|
||||||
|
|
||||||
|
var execErr, attachErr chan error
|
||||||
|
go func() {
|
||||||
|
attachErr = d.Attach(&execConfig.StreamConfig, config.AttachStdin, false, config.Tty, cStdin, cStdinCloser, cStdout, cStderr)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := container.Exec(execConfig)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Cannot run in container %s: %s", name, err)
|
||||||
|
}
|
||||||
|
execErr <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-attachErr:
|
||||||
|
return job.Errorf("attach failed with error: %s", err)
|
||||||
|
case err := <-execErr:
|
||||||
|
return job.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (daemon *Daemon) Exec(c *Container, execConfig *ExecConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
|
||||||
|
return daemon.execDriver.Exec(c.command, &execConfig.ProcessConfig, pipes, startCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (container *Container) Exec(execConfig *ExecConfig) error {
|
||||||
|
container.Lock()
|
||||||
|
defer container.Unlock()
|
||||||
|
|
||||||
|
waitStart := make(chan struct{})
|
||||||
|
|
||||||
|
callback := func(processConfig *execdriver.ProcessConfig, pid int) {
|
||||||
|
if processConfig.Tty {
|
||||||
|
// The callback is called after the process Start()
|
||||||
|
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace
|
||||||
|
// which we close here.
|
||||||
|
if c, ok := processConfig.Stdout.(io.Closer); ok {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(waitStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a callback here instead of a goroutine and an chan for
|
||||||
|
// syncronization purposes
|
||||||
|
cErr := utils.Go(func() error { return container.monitorExec(execConfig, callback) })
|
||||||
|
|
||||||
|
// Exec should not return until the process is actually running
|
||||||
|
select {
|
||||||
|
case <-waitStart:
|
||||||
|
case err := <-cErr:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (container *Container) monitorExec(execConfig *ExecConfig, callback execdriver.StartCallback) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
exitCode int
|
||||||
|
)
|
||||||
|
|
||||||
|
pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin)
|
||||||
|
exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error running command in existing container %s: %s", container.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode)
|
||||||
|
if execConfig.OpenStdin {
|
||||||
|
if err := execConfig.StreamConfig.stdin.Close(); err != nil {
|
||||||
|
log.Errorf("Error closing stdin while running in %s: %s", container.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := execConfig.StreamConfig.stdout.Clean(); err != nil {
|
||||||
|
log.Errorf("Error closing stdout while running in %s: %s", container.ID, err)
|
||||||
|
}
|
||||||
|
if err := execConfig.StreamConfig.stderr.Clean(); err != nil {
|
||||||
|
log.Errorf("Error closing stderr while running in %s: %s", container.ID, err)
|
||||||
|
}
|
||||||
|
if execConfig.ProcessConfig.Terminal != nil {
|
||||||
|
if err := execConfig.ProcessConfig.Terminal.Close(); err != nil {
|
||||||
|
log.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/docker/docker/daemon/execdriver"
|
||||||
|
"github.com/docker/docker/reexec"
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/namespaces"
|
"github.com/docker/libcontainer/namespaces"
|
||||||
"github.com/docker/docker/daemon/execdriver"
|
|
||||||
"github.com/docker/docker/reexec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const commandName = "nsenter-exec"
|
const commandName = "nsenter-exec"
|
||||||
|
@ -59,7 +59,7 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo
|
||||||
|
|
||||||
args := append([]string{processConfig.Entrypoint}, processConfig.Arguments...)
|
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,
|
return namespaces.ExecIn(active.container, state, args, os.Args[0], "exec", processConfig.Stdin, processConfig.Stdout, processConfig.Stderr, processConfig.Console,
|
||||||
func(cmd *exec.Cmd) {
|
func(cmd *exec.Cmd) {
|
||||||
if startCallback != nil {
|
if startCallback != nil {
|
||||||
startCallback(&c.ProcessConfig, cmd.Process.Pid)
|
startCallback(&c.ProcessConfig, cmd.Process.Pid)
|
||||||
|
|
|
@ -4,7 +4,7 @@ package native
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/syncpipe"
|
"github.com/docker/libcontainer/syncpipe"
|
||||||
)
|
)
|
||||||
|
@ -37,4 +37,3 @@ func loadConfigFromFd() (*libcontainer.Config, error) {
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
78
runconfig/exec.go
Normal file
78
runconfig/exec.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package runconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/engine"
|
||||||
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExecConfig struct {
|
||||||
|
User string
|
||||||
|
Privileged bool
|
||||||
|
Tty bool
|
||||||
|
Container string
|
||||||
|
AttachStdin bool
|
||||||
|
AttachStderr bool
|
||||||
|
AttachStdout bool
|
||||||
|
Detach bool
|
||||||
|
Cmd []string
|
||||||
|
Hostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecConfigFromJob(job *engine.Job) *ExecConfig {
|
||||||
|
execConfig := &ExecConfig{
|
||||||
|
User: job.Getenv("User"),
|
||||||
|
Privileged: job.GetenvBool("Privileged"),
|
||||||
|
Tty: job.GetenvBool("Tty"),
|
||||||
|
Container: job.Getenv("Container"),
|
||||||
|
AttachStdin: job.GetenvBool("AttachStdin"),
|
||||||
|
AttachStderr: job.GetenvBool("AttachStderr"),
|
||||||
|
AttachStdout: job.GetenvBool("AttachStdout"),
|
||||||
|
}
|
||||||
|
if Cmd := job.GetenvList("Cmd"); Cmd != nil {
|
||||||
|
execConfig.Cmd = Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
return execConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) {
|
||||||
|
var (
|
||||||
|
flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
|
||||||
|
flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached")
|
||||||
|
flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY")
|
||||||
|
flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name")
|
||||||
|
flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID")
|
||||||
|
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background")
|
||||||
|
execCmd []string
|
||||||
|
container string
|
||||||
|
)
|
||||||
|
if err := cmd.Parse(args); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
parsedArgs := cmd.Args()
|
||||||
|
if len(parsedArgs) > 1 {
|
||||||
|
container = cmd.Arg(0)
|
||||||
|
execCmd = parsedArgs[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
execConfig := &ExecConfig{
|
||||||
|
User: *flUser,
|
||||||
|
Privileged: *flPrivileged,
|
||||||
|
Tty: *flTty,
|
||||||
|
Cmd: execCmd,
|
||||||
|
Container: container,
|
||||||
|
Hostname: *flHostname,
|
||||||
|
Detach: *flDetach,
|
||||||
|
}
|
||||||
|
|
||||||
|
// If -d is not set, attach to everything by default
|
||||||
|
if !*flDetach {
|
||||||
|
execConfig.AttachStdout = true
|
||||||
|
execConfig.AttachStderr = true
|
||||||
|
if *flStdin {
|
||||||
|
execConfig.AttachStdin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return execConfig, nil
|
||||||
|
}
|
Loading…
Reference in a new issue