Explorar o código

Adding support for docker exec in daemon.

Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)
Vishnu Kannan %!s(int64=11) %!d(string=hai) anos
pai
achega
5130fe5d38

+ 1 - 1
builder/internals.go

@@ -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 - 8
daemon/attach.go

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

+ 7 - 7
daemon/daemon.go

@@ -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 - 0
daemon/exec.go

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

+ 3 - 3
daemon/execdriver/native/exec.go

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

+ 1 - 2
daemon/execdriver/native/utils.go

@@ -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 - 0
runconfig/exec.go

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