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
|
||||
// but without hijacking for stdin. Also, with attach there can be race
|
||||
// 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"
|
||||
|
||||
"github.com/docker/docker/engine"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/jsonlog"
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
|
@ -103,7 +103,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
|||
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
|
||||
// otherwise, simply return
|
||||
if container.Config.StdinOnce && !container.Config.Tty {
|
||||
|
@ -119,7 +119,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status {
|
|||
// Attach and ContainerAttach.
|
||||
//
|
||||
// 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 (
|
||||
cStdout, cStderr io.ReadCloser
|
||||
nJobs int
|
||||
|
@ -130,7 +130,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
|||
if stdin != nil && openStdin {
|
||||
nJobs += 1
|
||||
// Get the stdin pipe.
|
||||
if cStdin, err := container.StdinPipe(); err != nil {
|
||||
if cStdin, err := streamConfig.StdinPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
go func() {
|
||||
|
@ -168,7 +168,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
|||
if stdout != nil {
|
||||
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 := streamConfig.StdoutPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStdout = p
|
||||
|
@ -198,7 +198,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
|||
if stdinCloser != nil {
|
||||
defer stdinCloser.Close()
|
||||
}
|
||||
if cStdout, err := container.StdoutPipe(); err != nil {
|
||||
if cStdout, err := streamConfig.StdoutPipe(); err != nil {
|
||||
log.Errorf("attach: stdout pipe: %s", err)
|
||||
} else {
|
||||
io.Copy(&ioutils.NopWriter{}, cStdout)
|
||||
|
@ -207,7 +207,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
|||
}
|
||||
if stderr != nil {
|
||||
nJobs += 1
|
||||
if p, err := container.StderrPipe(); err != nil {
|
||||
if p, err := streamConfig.StderrPipe(); err != nil {
|
||||
errors <- err
|
||||
} else {
|
||||
cStderr = p
|
||||
|
@ -240,7 +240,7 @@ func (daemon *Daemon) Attach(container *Container, openStdin, stdinOnce, tty boo
|
|||
defer stdinCloser.Close()
|
||||
}
|
||||
|
||||
if cStderr, err := container.StderrPipe(); err != nil {
|
||||
if cStderr, err := streamConfig.StderrPipe(); err != nil {
|
||||
log.Errorf("attach: stdout pipe: %s", err)
|
||||
} else {
|
||||
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 (
|
||||
entrypoint string
|
||||
args []string
|
||||
)
|
||||
if len(config.Entrypoint) != 0 {
|
||||
entrypoint = config.Entrypoint[0]
|
||||
args = append(config.Entrypoint[1:], config.Cmd...)
|
||||
if len(configEntrypoint) != 0 {
|
||||
entrypoint = configEntrypoint[0]
|
||||
args = append(configEntrypoint[1:], configCmd...)
|
||||
} else {
|
||||
entrypoint = config.Cmd[0]
|
||||
args = config.Cmd[1:]
|
||||
entrypoint = configCmd[0]
|
||||
args = configCmd[1:]
|
||||
}
|
||||
return entrypoint, args
|
||||
}
|
||||
|
@ -522,7 +522,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, img *i
|
|||
}
|
||||
|
||||
daemon.generateHostname(id, config)
|
||||
entrypoint, args := daemon.getEntrypointAndArgs(config)
|
||||
entrypoint, args := daemon.getEntrypointAndArgs(config.Entrypoint, config.Cmd)
|
||||
|
||||
container := &Container{
|
||||
// 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"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/reexec"
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/namespaces"
|
||||
"github.com/docker/docker/daemon/execdriver"
|
||||
"github.com/docker/docker/reexec"
|
||||
)
|
||||
|
||||
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...)
|
||||
|
||||
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) {
|
||||
if startCallback != nil {
|
||||
startCallback(&c.ProcessConfig, cmd.Process.Pid)
|
||||
|
|
|
@ -4,7 +4,7 @@ package native
|
|||
|
||||
import (
|
||||
"os"
|
||||
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/syncpipe"
|
||||
)
|
||||
|
@ -37,4 +37,3 @@ func loadConfigFromFd() (*libcontainer.Config, error) {
|
|||
|
||||
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