1
0
Fork 0
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:
Vishnu Kannan 2014-09-09 04:19:32 +00:00
parent f3c767d798
commit 5130fe5d38
7 changed files with 278 additions and 21 deletions

View file

@ -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)
})
}

View file

@ -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)

View file

@ -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
View 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
}

View file

@ -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)

View file

@ -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
View 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
}