From 7a50153c321ff96c16947b3bab9b8e385f2f2a5e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 28 Jan 2013 17:50:12 -0800 Subject: [PATCH] 'docker run -i' optionally opens stdin. 'docker attach' attaches to a running container (including stdin). 'docker run -t' allocates a tty (still buggy) --- container.go | 84 ++++++++++++++++++++++++++++++++++++++++- dockerd/dockerd.go | 94 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 166 insertions(+), 12 deletions(-) diff --git a/container.go b/container.go index 959879cea6..08f9e70e6e 100644 --- a/container.go +++ b/container.go @@ -13,6 +13,7 @@ import ( "strings" "syscall" "time" + "github.com/kr/pty" ) type Container struct { @@ -32,6 +33,8 @@ type Container struct { cmd *exec.Cmd stdout *writeBroadcaster stderr *writeBroadcaster + stdin io.ReadCloser + stdinPipe io.WriteCloser stdoutLog *bytes.Buffer stderrLog *bytes.Buffer @@ -40,6 +43,8 @@ type Container struct { type Config struct { Hostname string Ram int64 + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin } func createContainer(id string, root string, command string, args []string, layers []string, config *Config) (*Container, error) { @@ -59,6 +64,11 @@ func createContainer(id string, root string, command string, args []string, laye stdoutLog: new(bytes.Buffer), stderrLog: new(bytes.Buffer), } + if container.Config.OpenStdin { + container.stdin, container.stdinPipe = io.Pipe() + } else { + container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin + } container.stdout.AddWriter(NopWriteCloser(container.stdoutLog)) container.stderr.AddWriter(NopWriteCloser(container.stderrLog)) @@ -94,6 +104,11 @@ func loadContainer(containerPath string) (*Container, error) { if err := container.Filesystem.createMountPoints(); err != nil { return nil, err } + if container.Config.OpenStdin { + container.stdin, container.stdinPipe = io.Pipe() + } else { + container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin + } container.State = newState() return container, nil } @@ -180,9 +195,67 @@ func (container *Container) Start() error { params = append(params, container.Args...) container.cmd = exec.Command("/usr/bin/lxc-start", params...) - container.cmd.Stdout = container.stdout - container.cmd.Stderr = container.stderr + if container.Config.Tty { + Pty, tty, err := pty.Open() + if err != nil { + return err + } + container.cmd.Stdout = tty + container.cmd.Stderr = tty + if container.stdin != nil { + container.cmd.Stdin = tty + } + container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true} + if err := container.cmd.Start(); err != nil { + Pty.Close() + return err + } + tty.Close() + // Attach Pty to stdout + go func() { + defer container.stdout.Close() + for { + data := make([]byte, 1024) + n, err := Pty.Read(data) + if err != nil { + return + } + log.Printf("STDOUT <%s>\n", data) + if _, err = container.stdout.Write(data[:n]); err != nil { + return + } + log.Printf("STDOUT SENT\n") + } + //io.Copy(container.stdout, Pty) + //container.stdout.Close() + }() + // Attach Pty to stderr + go func() { + io.Copy(container.stderr, Pty) + container.stderr.Close() + }() + // Attach Pty to stdin + if container.stdin != nil { + go func() { + defer Pty.Close() + io.Copy(Pty, container.stdin) + }() + } + } else { + container.cmd.Stdout = container.stdout + container.cmd.Stderr = container.stderr + if container.stdin != nil { + stdin, err := container.cmd.StdinPipe() + if err != nil { + return err + } + go func() { + defer stdin.Close() + io.Copy(stdin, container.stdin) + }() + } + } if err := container.cmd.Start(); err != nil { return err } @@ -214,6 +287,13 @@ func (container *Container) Output() (output []byte, err error) { return output, err } +// StdinPipe() returns a pipe connected to the standard input of the container's +// active process. +// +func (container *Container) StdinPipe() (io.WriteCloser, error) { + return container.stdinPipe, nil +} + func (container *Container) StdoutPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() container.stdout.AddWriter(writer) diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go index c9e334f6d9..e24b1c88f0 100644 --- a/dockerd/dockerd.go +++ b/dockerd/dockerd.go @@ -18,6 +18,7 @@ import ( "net/http" "encoding/json" "bytes" + "sync" ) @@ -42,6 +43,7 @@ func (srv *Server) Help() string { {"info", "Display system-wide information"}, {"tar", "Stream the contents of a container as a tar archive"}, {"web", "Generate a web UI"}, + {"attach", "Attach to a running container"}, } { help += fmt.Sprintf(" %-10.10s%s\n", cmd...) } @@ -507,9 +509,10 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string } -func (srv *Server) CreateContainer(img *image.Image, cmd string, args ...string) (*docker.Container, error) { +func (srv *Server) CreateContainer(img *image.Image, tty bool, openStdin bool, cmd string, args ...string) (*docker.Container, error) { id := future.RandomId() - container, err := srv.containers.Create(id, cmd, args, img.Layers, &docker.Config{Hostname: id}) + container, err := srv.containers.Create(id, cmd, args, img.Layers, + &docker.Config{Hostname: id, Tty: tty, OpenStdin: openStdin}) if err != nil { return nil, err } @@ -520,10 +523,57 @@ func (srv *Server) CreateContainer(img *image.Image, cmd string, args ...string) return container, nil } +func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + cmd := rcli.Subcmd(stdout, "attach", "[OPTIONS]", "Attach to a running container") + fl_i := cmd.Bool("i", false, "Attach to stdin") + fl_o := cmd.Bool("o", true, "Attach to stdout") + fl_e := cmd.Bool("e", true, "Attach to stderr") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() != 1 { + cmd.Usage() + return nil + } + name := cmd.Arg(0) + container := srv.containers.Get(name) + if container == nil { + return errors.New("No such container: " + name) + } + var wg sync.WaitGroup + if *fl_i { + c_stdin, err := container.StdinPipe() + if err != nil { + return err + } + wg.Add(1) + go func() { io.Copy(c_stdin, stdin); wg.Add(-1); }() + } + if *fl_o { + c_stdout, err := container.StdoutPipe() + if err != nil { + return err + } + wg.Add(1) + go func() { io.Copy(stdout, c_stdout); wg.Add(-1); }() + } + if *fl_e { + c_stderr, err := container.StderrPipe() + if err != nil { + return err + } + wg.Add(1) + go func() { io.Copy(stdout, c_stderr); wg.Add(-1); }() + } + wg.Wait() + return nil +} + func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error { flags := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container") fl_attach := flags.Bool("a", false, "Attach stdin and stdout") - //fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty") + fl_stdin := flags.Bool("i", false, "Keep stdin open even if not attached") + fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty") if err := flags.Parse(args); err != nil { return nil } @@ -538,31 +588,55 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) return errors.New("No such image: " + name) } // Create new container - container, err := srv.CreateContainer(img, cmd[0], cmd[1:]...) + container, err := srv.CreateContainer(img, *fl_tty, *fl_stdin, cmd[0], cmd[1:]...) if err != nil { return errors.New("Error creating container: " + err.Error()) } - // Run the container - if *fl_attach { - cmd_stdout, err := container.StdoutPipe() + if *fl_stdin { + cmd_stdin, err := container.StdinPipe() if err != nil { return err } + if *fl_attach { + future.Go(func() error { + log.Printf("CmdRun(): start receiving stdin\n") + _, err := io.Copy(cmd_stdin, stdin); + log.Printf("CmdRun(): done receiving stdin\n") + cmd_stdin.Close() + return err + }) + } + } + // Run the container + if *fl_attach { cmd_stderr, err := container.StderrPipe() if err != nil { return err } + cmd_stdout, err := container.StdoutPipe() + if err != nil { + return err + } if err := container.Start(); err != nil { return err } - sending_stdout := future.Go(func() error { _, err := io.Copy(stdout, cmd_stdout); return err }) - sending_stderr := future.Go(func() error { _, err := io.Copy(stdout, cmd_stderr); return err }) + sending_stdout := future.Go(func() error { + _, err := io.Copy(stdout, cmd_stdout); + return err + }) + sending_stderr := future.Go(func() error { + _, err := io.Copy(stdout, cmd_stderr); + return err + }) err_sending_stdout := <-sending_stdout err_sending_stderr := <-sending_stderr if err_sending_stdout != nil { return err_sending_stdout } - return err_sending_stderr + if err_sending_stderr != nil { + return err_sending_stderr + } + container.Wait() } else { if err := container.Start(); err != nil { return err