'docker run -i' optionally opens stdin. 'docker attach' attaches to a running container (including stdin). 'docker run -t' allocates a tty (still buggy)

This commit is contained in:
Solomon Hykes 2013-01-28 17:50:12 -08:00
parent 9906a9af8f
commit 7a50153c32
2 changed files with 166 additions and 12 deletions

View File

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

View File

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