Merge github.com:dotcloud/docker into 333-redis-documentation

This commit is contained in:
John Costa 2013-04-09 16:07:40 -04:00
commit 418ef43fbb
27 changed files with 594 additions and 191 deletions

View File

@ -134,6 +134,12 @@ docker pull base
docker run -i -t base /bin/bash docker run -i -t base /bin/bash
``` ```
Detaching from the interactive shell
------------------------------------
```
# In order to detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q
# Note: this works only in tty mode (run with -t option).
```
Starting a long-running worker process Starting a long-running worker process
-------------------------------------- --------------------------------------
@ -183,7 +189,9 @@ JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
PORT=$(docker port $JOB 4444) PORT=$(docker port $JOB 4444)
# Connect to the public port via the host's public address # Connect to the public port via the host's public address
echo hello world | nc $(hostname) $PORT # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
echo hello world | nc $IP $PORT
# Verify that the network connection worked # Verify that the network connection worked
echo "Daemon received: $(docker logs $JOB)" echo "Daemon received: $(docker logs $JOB)"

View File

@ -18,7 +18,7 @@ import (
"unicode" "unicode"
) )
const VERSION = "0.1.3" const VERSION = "0.1.4"
var GIT_COMMIT string var GIT_COMMIT string
@ -62,7 +62,7 @@ func (srv *Server) Help() string {
} }
// 'docker login': login / register a user to registry service. // 'docker login': login / register a user to registry service.
func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
// Read a line on raw terminal with support for simple backspace // Read a line on raw terminal with support for simple backspace
// sequences and echo. // sequences and echo.
// //
@ -113,6 +113,8 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
return readStringOnRawTerminal(stdin, stdout, false) return readStringOnRawTerminal(stdin, stdout, false)
} }
stdout.SetOptionRawTerminal()
cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server") cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
return nil return nil
@ -417,7 +419,8 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
return nil return nil
} }
func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
stdout.Flush()
cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball") cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
var archive io.Reader var archive io.Reader
var resp *http.Response var resp *http.Response
@ -464,7 +467,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
return nil return nil
} }
func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdPush(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry") cmd := rcli.Subcmd(stdout, "push", "NAME", "Push an image or a repository to the registry")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
return nil return nil
@ -784,7 +787,7 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
return fmt.Errorf("No such container: %s", cmd.Arg(0)) return fmt.Errorf("No such container: %s", cmd.Arg(0))
} }
func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container") cmd := rcli.Subcmd(stdout, "attach", "CONTAINER", "Attach to a running container")
if err := cmd.Parse(args); err != nil { if err := cmd.Parse(args); err != nil {
return nil return nil
@ -799,6 +802,11 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
return fmt.Errorf("No such container: %s", name) return fmt.Errorf("No such container: %s", name)
} }
if container.Config.Tty {
stdout.SetOptionRawTerminal()
}
// Flush the options to make sure the client sets the raw mode
stdout.Flush()
return <-container.Attach(stdin, nil, stdout, stdout) return <-container.Attach(stdin, nil, stdout, stdout)
} }
@ -870,7 +878,7 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string)
return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force) return srv.runtime.repositories.Set(cmd.Arg(1), cmd.Arg(2), cmd.Arg(0), *force)
} }
func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error { func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
config, err := ParseRun(args, stdout) config, err := ParseRun(args, stdout)
if err != nil { if err != nil {
return err return err
@ -884,6 +892,13 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
return fmt.Errorf("Command not specified") return fmt.Errorf("Command not specified")
} }
if config.Tty {
stdout.SetOptionRawTerminal()
}
// Flush the options to make sure the client sets the raw mode
// or tell the client there is no options
stdout.Flush()
// Create new container // Create new container
container, err := srv.runtime.Create(config) container, err := srv.runtime.Create(config)
if err != nil { if err != nil {

View File

@ -2,8 +2,8 @@ package docker
import ( import (
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"github.com/dotcloud/docker/rcli"
"io" "io"
"io/ioutil" "io/ioutil"
"strings" "strings"
@ -69,15 +69,27 @@ func TestRunHostname(t *testing.T) {
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
var stdin, stdout bytes.Buffer stdin, _ := io.Pipe()
setTimeout(t, "CmdRun timed out", 2*time.Second, func() { stdout, stdoutPipe := io.Pipe()
if err := srv.CmdRun(ioutil.NopCloser(&stdin), &nopWriteCloser{&stdout}, "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
c := make(chan struct{})
go func() {
if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
t.Fatal(err) t.Fatal(err)
} }
}) close(c)
if output := string(stdout.Bytes()); output != "foobar\n" { }()
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", output) cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
} }
if cmdOutput != "foobar\n" {
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
}
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
<-c
})
} }
func TestRunExit(t *testing.T) { func TestRunExit(t *testing.T) {
@ -93,7 +105,7 @@ func TestRunExit(t *testing.T) {
stdout, stdoutPipe := io.Pipe() stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{}) c1 := make(chan struct{})
go func() { go func() {
srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat") srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
close(c1) close(c1)
}() }()
@ -147,7 +159,7 @@ func TestRunDisconnect(t *testing.T) {
go func() { go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the // We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdRun returns. // fact that CmdRun returns.
srv.CmdRun(stdin, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat") srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
close(c1) close(c1)
}() }()
@ -179,10 +191,56 @@ func TestRunDisconnect(t *testing.T) {
}) })
} }
// Expected behaviour: the process dies when the client disconnects
func TestRunDisconnectTty(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{runtime: runtime}
stdin, stdinPipe := io.Pipe()
stdout, stdoutPipe := io.Pipe()
c1 := make(chan struct{})
go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdRun returns.
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-t", GetTestImage(runtime).Id, "/bin/cat")
close(c1)
}()
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
})
// Close pipes (simulate disconnect)
if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
t.Fatal(err)
}
// as the pipes are close, we expect the process to die,
// therefore CmdRun to unblock. Wait for CmdRun
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
<-c1
})
// Client disconnect after run -i should keep stdin out in TTY mode
container := runtime.List()[0]
// Give some time to monitor to do his thing
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
}
}
// TestAttachStdin checks attaching to stdin without stdout and stderr. // TestAttachStdin checks attaching to stdin without stdout and stderr.
// 'docker run -i -a stdin' should sends the client's stdin to the command, // 'docker run -i -a stdin' should sends the client's stdin to the command,
// then detach from it and print the container id. // then detach from it and print the container id.
func TestAttachStdin(t *testing.T) { func TestRunAttachStdin(t *testing.T) {
runtime, err := newTestRuntime() runtime, err := newTestRuntime()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -190,31 +248,41 @@ func TestAttachStdin(t *testing.T) {
defer nuke(runtime) defer nuke(runtime)
srv := &Server{runtime: runtime} srv := &Server{runtime: runtime}
stdinR, stdinW := io.Pipe() stdin, stdinPipe := io.Pipe()
var stdout bytes.Buffer stdout, stdoutPipe := io.Pipe()
ch := make(chan struct{}) ch := make(chan struct{})
go func() { go func() {
srv.CmdRun(stdinR, &stdout, "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat") srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat")
close(ch) close(ch)
}() }()
// Send input to the command, close stdin, wait for CmdRun to return // Send input to the command, close stdin
setTimeout(t, "Read/Write timed out", 2*time.Second, func() { setTimeout(t, "Write timed out", 2*time.Second, func() {
if _, err := stdinW.Write([]byte("hi there\n")); err != nil { if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
t.Fatal(err)
}
if err := stdinPipe.Close(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
stdinW.Close()
<-ch
}) })
// Check output
cmdOutput := string(stdout.Bytes())
container := runtime.List()[0] container := runtime.List()[0]
// Check output
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
if err != nil {
t.Fatal(err)
}
if cmdOutput != container.ShortId()+"\n" { if cmdOutput != container.ShortId()+"\n" {
t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput) t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput)
} }
// wait for CmdRun to return
setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
<-ch
})
setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() { setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() {
container.Wait() container.Wait()
}) })
@ -270,7 +338,7 @@ func TestAttachDisconnect(t *testing.T) {
go func() { go func() {
// We're simulating a disconnect so the return value doesn't matter. What matters is the // We're simulating a disconnect so the return value doesn't matter. What matters is the
// fact that CmdAttach returns. // fact that CmdAttach returns.
srv.CmdAttach(stdin, stdoutPipe, container.Id) srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
close(c1) close(c1)
}() }()

View File

@ -40,11 +40,11 @@ type Container struct {
stdin io.ReadCloser stdin io.ReadCloser
stdinPipe io.WriteCloser stdinPipe io.WriteCloser
ptyStdinMaster io.Closer ptyMaster io.Closer
ptyStdoutMaster io.Closer
ptyStderrMaster io.Closer
runtime *Runtime runtime *Runtime
waitLock chan struct{}
} }
type Config struct { type Config struct {
@ -180,63 +180,37 @@ func (container *Container) generateLXCConfig() error {
} }
func (container *Container) startPty() error { func (container *Container) startPty() error {
stdoutMaster, stdoutSlave, err := pty.Open() ptyMaster, ptySlave, err := pty.Open()
if err != nil { if err != nil {
return err return err
} }
container.ptyStdoutMaster = stdoutMaster container.ptyMaster = ptyMaster
container.cmd.Stdout = stdoutSlave container.cmd.Stdout = ptySlave
container.cmd.Stderr = ptySlave
stderrMaster, stderrSlave, err := pty.Open()
if err != nil {
return err
}
container.ptyStderrMaster = stderrMaster
container.cmd.Stderr = stderrSlave
// Copy the PTYs to our broadcasters // Copy the PTYs to our broadcasters
go func() { go func() {
defer container.stdout.CloseWriters() defer container.stdout.CloseWriters()
Debugf("[startPty] Begin of stdout pipe") Debugf("[startPty] Begin of stdout pipe")
io.Copy(container.stdout, stdoutMaster) io.Copy(container.stdout, ptyMaster)
Debugf("[startPty] End of stdout pipe") Debugf("[startPty] End of stdout pipe")
}() }()
go func() {
defer container.stderr.CloseWriters()
Debugf("[startPty] Begin of stderr pipe")
io.Copy(container.stderr, stderrMaster)
Debugf("[startPty] End of stderr pipe")
}()
// stdin // stdin
var stdinSlave io.ReadCloser
if container.Config.OpenStdin { if container.Config.OpenStdin {
var stdinMaster io.WriteCloser container.cmd.Stdin = ptySlave
stdinMaster, stdinSlave, err = pty.Open() container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
if err != nil {
return err
}
container.ptyStdinMaster = stdinMaster
container.cmd.Stdin = stdinSlave
// FIXME: The following appears to be broken.
// "cannot set terminal process group (-1): Inappropriate ioctl for device"
// container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
go func() { go func() {
defer container.stdin.Close() defer container.stdin.Close()
Debugf("[startPty] Begin of stdin pipe") Debugf("[startPty] Begin of stdin pipe")
io.Copy(stdinMaster, container.stdin) io.Copy(ptyMaster, container.stdin)
Debugf("[startPty] End of stdin pipe") Debugf("[startPty] End of stdin pipe")
}() }()
} }
if err := container.cmd.Start(); err != nil { if err := container.cmd.Start(); err != nil {
return err return err
} }
stdoutSlave.Close() ptySlave.Close()
stderrSlave.Close()
if stdinSlave != nil {
stdinSlave.Close()
}
return nil return nil
} }
@ -278,10 +252,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
if cStderr != nil { if cStderr != nil {
defer cStderr.Close() defer cStderr.Close()
} }
if container.Config.StdinOnce { if container.Config.StdinOnce && !container.Config.Tty {
defer cStdin.Close() defer cStdin.Close()
} }
_, err := io.Copy(cStdin, stdin) if container.Config.Tty {
_, err = CopyEscapable(cStdin, stdin)
} else {
_, err = io.Copy(cStdin, stdin)
}
if err != nil { if err != nil {
Debugf("[error] attach stdin: %s\n", err) Debugf("[error] attach stdin: %s\n", err)
} }
@ -365,6 +343,9 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
} }
func (container *Container) Start() error { func (container *Container) Start() error {
container.State.lock()
defer container.State.unlock()
if container.State.Running { if container.State.Running {
return fmt.Errorf("The container %s is already running.", container.Id) return fmt.Errorf("The container %s is already running.", container.Id)
} }
@ -431,6 +412,9 @@ func (container *Container) Start() error {
// FIXME: save state on disk *first*, then converge // FIXME: save state on disk *first*, then converge
// this way disk state is used as a journal, eg. we can restore after crash etc. // this way disk state is used as a journal, eg. we can restore after crash etc.
container.State.setRunning(container.cmd.Process.Pid) container.State.setRunning(container.cmd.Process.Pid)
// Init the lock
container.waitLock = make(chan struct{})
container.ToDisk() container.ToDisk()
go container.monitor() go container.monitor()
return nil return nil
@ -530,19 +514,9 @@ func (container *Container) monitor() {
Debugf("%s: Error close stderr: %s", container.Id, err) Debugf("%s: Error close stderr: %s", container.Id, err)
} }
if container.ptyStdinMaster != nil { if container.ptyMaster != nil {
if err := container.ptyStdinMaster.Close(); err != nil { if err := container.ptyMaster.Close(); err != nil {
Debugf("%s: Error close pty stdin master: %s", container.Id, err) Debugf("%s: Error closing Pty master: %s", container.Id, err)
}
}
if container.ptyStdoutMaster != nil {
if err := container.ptyStdoutMaster.Close(); err != nil {
Debugf("%s: Error close pty stdout master: %s", container.Id, err)
}
}
if container.ptyStderrMaster != nil {
if err := container.ptyStderrMaster.Close(); err != nil {
Debugf("%s: Error close pty stderr master: %s", container.Id, err)
} }
} }
@ -557,6 +531,10 @@ func (container *Container) monitor() {
// Report status back // Report status back
container.State.setStopped(exitCode) container.State.setStopped(exitCode)
// Release the lock
close(container.waitLock)
if err := container.ToDisk(); err != nil { if err := container.ToDisk(); err != nil {
// FIXME: there is a race condition here which causes this to fail during the unit tests. // FIXME: there is a race condition here which causes this to fail during the unit tests.
// If another goroutine was waiting for Wait() to return before removing the container's root // If another goroutine was waiting for Wait() to return before removing the container's root
@ -569,7 +547,7 @@ func (container *Container) monitor() {
} }
func (container *Container) kill() error { func (container *Container) kill() error {
if container.cmd == nil { if !container.State.Running || container.cmd == nil {
return nil return nil
} }
if err := container.cmd.Process.Kill(); err != nil { if err := container.cmd.Process.Kill(); err != nil {
@ -581,13 +559,14 @@ func (container *Container) kill() error {
} }
func (container *Container) Kill() error { func (container *Container) Kill() error {
if !container.State.Running { container.State.lock()
return nil defer container.State.unlock()
}
return container.kill() return container.kill()
} }
func (container *Container) Stop() error { func (container *Container) Stop() error {
container.State.lock()
defer container.State.unlock()
if !container.State.Running { if !container.State.Running {
return nil return nil
} }
@ -596,7 +575,7 @@ func (container *Container) Stop() error {
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil { if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
log.Print(string(output)) log.Print(string(output))
log.Print("Failed to send SIGTERM to the process, force killing") log.Print("Failed to send SIGTERM to the process, force killing")
if err := container.Kill(); err != nil { if err := container.kill(); err != nil {
return err return err
} }
} }
@ -604,7 +583,7 @@ func (container *Container) Stop() error {
// 2. Wait for the process to exit on its own // 2. Wait for the process to exit on its own
if err := container.WaitTimeout(10 * time.Second); err != nil { if err := container.WaitTimeout(10 * time.Second); err != nil {
log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id) log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
if err := container.Kill(); err != nil { if err := container.kill(); err != nil {
return err return err
} }
} }
@ -623,10 +602,7 @@ func (container *Container) Restart() error {
// Wait blocks until the container stops running, then returns its exit code. // Wait blocks until the container stops running, then returns its exit code.
func (container *Container) Wait() int { func (container *Container) Wait() int {
<-container.waitLock
for container.State.Running {
container.State.wait()
}
return container.State.ExitCode return container.State.ExitCode
} }

View File

@ -267,6 +267,7 @@ func TestStart(t *testing.T) {
// Try to avoid the timeoout in destroy. Best effort, don't check error // Try to avoid the timeoout in destroy. Best effort, don't check error
cStdin, _ := container.StdinPipe() cStdin, _ := container.StdinPipe()
cStdin.Close() cStdin.Close()
container.WaitTimeout(2 * time.Second)
} }
func TestRun(t *testing.T) { func TestRun(t *testing.T) {

View File

@ -8,7 +8,6 @@ import (
"io" "io"
"log" "log"
"os" "os"
"os/signal"
) )
var GIT_COMMIT string var GIT_COMMIT string
@ -57,29 +56,21 @@ func daemon() error {
} }
func runCommand(args []string) error { func runCommand(args []string) error {
var oldState *term.State
var err error
if term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
oldState, err = term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return err
}
defer term.Restore(int(os.Stdin.Fd()), oldState)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for _ = range c {
term.Restore(int(os.Stdin.Fd()), oldState)
log.Printf("\nSIGINT received\n")
os.Exit(0)
}
}()
}
// FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose // FIXME: we want to use unix sockets here, but net.UnixConn doesn't expose
// CloseWrite(), which we need to cleanly signal that stdin is closed without // CloseWrite(), which we need to cleanly signal that stdin is closed without
// closing the connection. // closing the connection.
// See http://code.google.com/p/go/issues/detail?id=3345 // See http://code.google.com/p/go/issues/detail?id=3345
if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil { if conn, err := rcli.Call("tcp", "127.0.0.1:4242", args...); err == nil {
options := conn.GetOptions()
if options.RawTerminal &&
term.IsTerminal(int(os.Stdin.Fd())) &&
os.Getenv("NORAW") == "" {
if oldState, err := rcli.SetRawTerminal(); err != nil {
return err
} else {
defer rcli.RestoreTerminal(oldState)
}
}
receiveStdout := docker.Go(func() error { receiveStdout := docker.Go(func() error {
_, err := io.Copy(os.Stdout, conn) _, err := io.Copy(os.Stdout, conn)
return err return err
@ -104,12 +95,11 @@ func runCommand(args []string) error {
if err != nil { if err != nil {
return err return err
} }
if err := rcli.LocalCall(service, os.Stdin, os.Stdout, args...); err != nil { dockerConn := rcli.NewDockerLocalConn(os.Stdout)
defer dockerConn.Close()
if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil {
return err return err
} }
} }
if oldState != nil {
term.Restore(int(os.Stdin.Fd()), oldState)
}
return nil return nil
} }

View File

@ -39,4 +39,36 @@ Notes
* The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification. * The index.html and gettingstarted.html files are copied from the source dir to the output dir without modification.
So changes to those pages should be made directly in html So changes to those pages should be made directly in html
* For the template the css is compiled from less. When changes are needed they can be compiled using * For the template the css is compiled from less. When changes are needed they can be compiled using
lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css`` lessc ``lessc main.less`` or watched using watch-lessc ``watch-lessc -i main.less -o main.css``
Guides on using sphinx
----------------------
* To make links to certain pages create a link target like so:
```
.. _hello_world:
Hello world
===========
This is.. (etc.)
```
The ``_hello_world:`` will make it possible to link to this position (page and marker) from all other pages.
* Notes, warnings and alarms
```
# a note (use when something is important)
.. note::
# a warning (orange)
.. warning::
# danger (red, use sparsely)
.. danger::
* Code examples
Start without $, so it's easy to copy and paste.

View File

@ -69,7 +69,8 @@ Expose a service on a TCP port
# Connect to the public port via the host's public address # Connect to the public port via the host's public address
# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work. # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
echo hello world | nc $(hostname) $PORT IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
echo hello world | nc $IP $PORT
# Verify that the network connection worked # Verify that the network connection worked
echo "Daemon received: $(docker logs $JOB)" echo "Daemon received: $(docker logs $JOB)"

View File

@ -0,0 +1,4 @@
.. note::
This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`

View File

@ -6,8 +6,10 @@
Hello World Hello World
=========== ===========
This is the most basic example available for using Docker. The example assumes you have Docker installed.
.. include:: example_header.inc
This is the most basic example available for using Docker.
Download the base container Download the base container

View File

@ -6,6 +6,9 @@
Hello World Daemon Hello World Daemon
================== ==================
.. include:: example_header.inc
The most boring daemon ever written. The most boring daemon ever written.
This example assumes you have Docker installed and with the base image already imported ``docker pull base``. This example assumes you have Docker installed and with the base image already imported ``docker pull base``.
@ -18,7 +21,7 @@ out every second. It will continue to do this until we stop it.
CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done") CONTAINER_ID=$(docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done")
We are going to run a simple hello world daemon in a new container made from the busybox daemon. We are going to run a simple hello world daemon in a new container made from the base image.
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon. - **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
- **"base"** is the image we want to run the command inside of. - **"base"** is the image we want to run the command inside of.

View File

@ -12,6 +12,7 @@ Contents:
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
running_examples
hello_world hello_world
hello_world_daemon hello_world_daemon
python_web_app python_web_app

View File

@ -6,6 +6,9 @@
Building a python web app Building a python web app
========================= =========================
.. include:: example_header.inc
The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image. The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image.
**Steps:** **Steps:**
@ -45,6 +48,11 @@ Save the changed we just made in the container to a new image called "_/builds/g
WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp) WEB_WORKER=$(docker run -d -p 5000 $BUILD_IMG /usr/local/bin/runapp)
- **"docker run -d "** run a command in a new container. We pass "-d" so it runs as a daemon.
**"-p 5000"* the web app is going to listen on this port, so it must be mapped from the container to the host system.
- **"$BUILD_IMG"** is the image we want to run the command inside of.
- **/usr/local/bin/runapp** is the command which starts the web app.
Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable. Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable.
.. code-block:: bash .. code-block:: bash
@ -54,6 +62,18 @@ Use the new image we just created and create a new container with network port 5
view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output. view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
.. code-block:: bash
WEB_PORT=$(docker port $WEB_WORKER 5000)
lookup the public-facing port which is NAT-ed store the private port used by the container and store it inside of the WEB_PORT variable.
.. code-block:: bash
curl http://`hostname`:$WEB_PORT
Hello world!
access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.
**Video:** **Video:**

View File

@ -0,0 +1,33 @@
:title: Running the Examples
:description: An overview on how to run the docker examples
:keywords: docker, examples, how to
.. _running_examples:
Running The Examples
--------------------
There are two ways to run docker, daemon mode and standalone mode.
When you run the docker command it will first check if there is a docker daemon running in the background it can connect to.
* If it exists it will use that daemon to run all of the commands.
* If it does not exist docker will run in standalone mode (docker will exit after each command).
Docker needs to be run from a privileged account (root).
1. The most common (and recommended) way is to run a docker daemon as root in the background, and then connect to it from the docker client from any account.
.. code-block:: bash
# starting docker daemon in the background
sudo docker -d &
# now you can run docker commands from any account.
docker <command>
2. Standalone: You need to run every command as root, or using sudo
.. code-block:: bash
sudo docker <command>

View File

@ -7,7 +7,7 @@
Create an ssh daemon service Create an ssh daemon service
============================ ============================
.. include:: example_header.inc
**Video:** **Video:**

View File

@ -82,7 +82,7 @@ h4 {
.btn-custom { .btn-custom {
background-color: #292929 !important; background-color: #292929 !important;
background-repeat: repeat-x; background-repeat: repeat-x;
filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828"); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828)); background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
background-image: -moz-linear-gradient(top, #515151, #282828); background-image: -moz-linear-gradient(top, #515151, #282828);
background-image: -ms-linear-gradient(top, #515151, #282828); background-image: -ms-linear-gradient(top, #515151, #282828);
@ -131,6 +131,27 @@ section.header {
margin: 15px 15px 15px 0; margin: 15px 15px 15px 0;
border: 2px solid gray; border: 2px solid gray;
} }
.admonition {
padding: 10px;
border: 1px solid grey;
margin-bottom: 10px;
margin-top: 10px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.admonition .admonition-title {
font-weight: bold;
}
.admonition.note {
background-color: #f1ebba;
}
.admonition.warning {
background-color: #eed9af;
}
.admonition.danger {
background-color: #e9bcab;
}
/* =================== /* ===================
left navigation left navigation
===================== */ ===================== */

View File

@ -179,7 +179,33 @@ section.header {
border: 2px solid gray; border: 2px solid gray;
} }
.admonition {
padding: 10px;
border: 1px solid grey;
margin-bottom: 10px;
margin-top: 10px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.admonition .admonition-title {
font-weight: bold;
}
.admonition.note {
background-color: rgb(241, 235, 186);
}
.admonition.warning {
background-color: rgb(238, 217, 175);
}
.admonition.danger {
background-color: rgb(233, 188, 171);
}
/* =================== /* ===================
left navigation left navigation

View File

@ -111,6 +111,8 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
} }
func CreateBridgeIface(ifaceName string) error { func CreateBridgeIface(ifaceName string) error {
// FIXME: try more IP ranges
// FIXME: try bigger ranges! /24 is too small.
addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"} addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"}
var ifaceAddr string var ifaceAddr string
@ -127,7 +129,7 @@ func CreateBridgeIface(ifaceName string) error {
} }
} }
if ifaceAddr == "" { if ifaceAddr == "" {
return fmt.Errorf("Impossible to create a bridge. Please create a bridge manually and restart docker with -br <bridgeName>") return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
} else { } else {
Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr) Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
} }

View File

@ -1,38 +0,0 @@
package rcli
import (
"fmt"
"net/http"
"net/url"
"path"
)
// Use this key to encode an RPC call into an URL,
// eg. domain.tld/path/to/method?q=get_user&q=gordon
const ARG_URL_KEY = "q"
func URLToCall(u *url.URL) (method string, args []string) {
return path.Base(u.Path), u.Query()[ARG_URL_KEY]
}
func ListenAndServeHTTP(addr string, service Service) error {
return http.ListenAndServe(addr, http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
cmd, args := URLToCall(r.URL)
if err := call(service, r.Body, &AutoFlush{w}, append([]string{cmd}, args...)...); err != nil {
fmt.Fprintln(w, "Error:", err.Error())
}
}))
}
type AutoFlush struct {
http.ResponseWriter
}
func (w *AutoFlush) Write(data []byte) (int, error) {
ret, err := w.ResponseWriter.Write(data)
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
}
return ret, err
}

View File

@ -2,6 +2,7 @@ package rcli
import ( import (
"bufio" "bufio"
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@ -15,22 +16,109 @@ import (
var DEBUG_FLAG bool = false var DEBUG_FLAG bool = false
var CLIENT_SOCKET io.Writer = nil var CLIENT_SOCKET io.Writer = nil
type DockerTCPConn struct {
conn *net.TCPConn
options *DockerConnOptions
optionsBuf *[]byte
handshaked bool
client bool
}
func NewDockerTCPConn(conn *net.TCPConn, client bool) *DockerTCPConn {
return &DockerTCPConn{
conn: conn,
options: &DockerConnOptions{},
client: client,
}
}
func (c *DockerTCPConn) SetOptionRawTerminal() {
c.options.RawTerminal = true
}
func (c *DockerTCPConn) GetOptions() *DockerConnOptions {
if c.client && !c.handshaked {
// Attempt to parse options encoded as a JSON dict and store
// the reminder of what we read from the socket in a buffer.
//
// bufio (and its ReadBytes method) would have been nice here,
// but if json.Unmarshal() fails (which will happen if we speak
// to a version of docker that doesn't send any option), then
// we can't put the data back in it for the next Read().
c.handshaked = true
buf := make([]byte, 4096)
if n, _ := c.conn.Read(buf); n > 0 {
buf = buf[:n]
if nl := bytes.IndexByte(buf, '\n'); nl != -1 {
if err := json.Unmarshal(buf[:nl], c.options); err == nil {
buf = buf[nl+1:]
}
}
c.optionsBuf = &buf
}
}
return c.options
}
func (c *DockerTCPConn) Read(b []byte) (int, error) {
if c.optionsBuf != nil {
// Consume what we buffered in GetOptions() first:
optionsBuf := *c.optionsBuf
optionsBuflen := len(optionsBuf)
copied := copy(b, optionsBuf)
if copied < optionsBuflen {
optionsBuf = optionsBuf[copied:]
c.optionsBuf = &optionsBuf
return copied, nil
}
c.optionsBuf = nil
return copied, nil
}
return c.conn.Read(b)
}
func (c *DockerTCPConn) Write(b []byte) (int, error) {
optionsLen := 0
if !c.client && !c.handshaked {
c.handshaked = true
options, _ := json.Marshal(c.options)
options = append(options, '\n')
if optionsLen, err := c.conn.Write(options); err != nil {
return optionsLen, err
}
}
n, err := c.conn.Write(b)
return n + optionsLen, err
}
func (c *DockerTCPConn) Flush() error {
_, err := c.Write([]byte{})
return err
}
func (c *DockerTCPConn) Close() error { return c.conn.Close() }
func (c *DockerTCPConn) CloseWrite() error { return c.conn.CloseWrite() }
func (c *DockerTCPConn) CloseRead() error { return c.conn.CloseRead() }
// Connect to a remote endpoint using protocol `proto` and address `addr`, // Connect to a remote endpoint using protocol `proto` and address `addr`,
// issue a single call, and return the result. // issue a single call, and return the result.
// `proto` may be "tcp", "unix", etc. See the `net` package for available protocols. // `proto` may be "tcp", "unix", etc. See the `net` package for available protocols.
func Call(proto, addr string, args ...string) (*net.TCPConn, error) { func Call(proto, addr string, args ...string) (DockerConn, error) {
cmd, err := json.Marshal(args) cmd, err := json.Marshal(args)
if err != nil { if err != nil {
return nil, err return nil, err
} }
conn, err := net.Dial(proto, addr) conn, err := dialDocker(proto, addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil { if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
return nil, err return nil, err
} }
return conn.(*net.TCPConn), nil return conn, nil
} }
// Listen on `addr`, using protocol `proto`, for incoming rcli calls, // Listen on `addr`, using protocol `proto`, for incoming rcli calls,
@ -46,6 +134,10 @@ func ListenAndServe(proto, addr string, service Service) error {
if conn, err := listener.Accept(); err != nil { if conn, err := listener.Accept(); err != nil {
return err return err
} else { } else {
conn, err := newDockerServerConn(conn)
if err != nil {
return err
}
go func() { go func() {
if DEBUG_FLAG { if DEBUG_FLAG {
CLIENT_SOCKET = conn CLIENT_SOCKET = conn
@ -63,7 +155,7 @@ func ListenAndServe(proto, addr string, service Service) error {
// Parse an rcli call on a new connection, and pass it to `service` if it // Parse an rcli call on a new connection, and pass it to `service` if it
// is valid. // is valid.
func Serve(conn io.ReadWriter, service Service) error { func Serve(conn DockerConn, service Service) error {
r := bufio.NewReader(conn) r := bufio.NewReader(conn)
var args []string var args []string
if line, err := r.ReadString('\n'); err != nil { if line, err := r.ReadString('\n'); err != nil {

View File

@ -8,15 +8,99 @@ package rcli
// are the usual suspects. // are the usual suspects.
import ( import (
"errors"
"flag" "flag"
"fmt" "fmt"
"github.com/dotcloud/docker/term"
"io" "io"
"log" "log"
"net"
"os"
"reflect" "reflect"
"strings" "strings"
) )
type DockerConnOptions struct {
RawTerminal bool
}
type DockerConn interface {
io.ReadWriteCloser
CloseWrite() error
CloseRead() error
GetOptions() *DockerConnOptions
SetOptionRawTerminal()
Flush() error
}
type DockerLocalConn struct {
writer io.WriteCloser
savedState *term.State
}
func NewDockerLocalConn(w io.WriteCloser) *DockerLocalConn {
return &DockerLocalConn{
writer: w,
}
}
func (c *DockerLocalConn) Read(b []byte) (int, error) {
return 0, fmt.Errorf("DockerLocalConn does not implement Read()")
}
func (c *DockerLocalConn) Write(b []byte) (int, error) { return c.writer.Write(b) }
func (c *DockerLocalConn) Close() error {
if c.savedState != nil {
RestoreTerminal(c.savedState)
c.savedState = nil
}
return c.writer.Close()
}
func (c *DockerLocalConn) Flush() error { return nil }
func (c *DockerLocalConn) CloseWrite() error { return nil }
func (c *DockerLocalConn) CloseRead() error { return nil }
func (c *DockerLocalConn) GetOptions() *DockerConnOptions { return nil }
func (c *DockerLocalConn) SetOptionRawTerminal() {
if state, err := SetRawTerminal(); err != nil {
if os.Getenv("DEBUG") != "" {
log.Printf("Can't set the terminal in raw mode: %s", err)
}
} else {
c.savedState = state
}
}
var UnknownDockerProto = fmt.Errorf("Only TCP is actually supported by Docker at the moment")
func dialDocker(proto string, addr string) (DockerConn, error) {
conn, err := net.Dial(proto, addr)
if err != nil {
return nil, err
}
switch i := conn.(type) {
case *net.TCPConn:
return NewDockerTCPConn(i, true), nil
}
return nil, UnknownDockerProto
}
func newDockerFromConn(conn net.Conn, client bool) (DockerConn, error) {
switch i := conn.(type) {
case *net.TCPConn:
return NewDockerTCPConn(i, client), nil
}
return nil, UnknownDockerProto
}
func newDockerServerConn(conn net.Conn) (DockerConn, error) {
return newDockerFromConn(conn, false)
}
type Service interface { type Service interface {
Name() string Name() string
Help() string Help() string
@ -26,11 +110,11 @@ type Cmd func(io.ReadCloser, io.Writer, ...string) error
type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error type CmdMethod func(Service, io.ReadCloser, io.Writer, ...string) error
// FIXME: For reverse compatibility // FIXME: For reverse compatibility
func call(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { func call(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
return LocalCall(service, stdin, stdout, args...) return LocalCall(service, stdin, stdout, args...)
} }
func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...string) error { func LocalCall(service Service, stdin io.ReadCloser, stdout DockerConn, args ...string) error {
if len(args) == 0 { if len(args) == 0 {
args = []string{"help"} args = []string{"help"}
} }
@ -49,7 +133,7 @@ func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...s
if method != nil { if method != nil {
return method(stdin, stdout, flags.Args()[1:]...) return method(stdin, stdout, flags.Args()[1:]...)
} }
return errors.New("No such command: " + cmd) return fmt.Errorf("No such command: %s", cmd)
} }
func getMethod(service Service, name string) Cmd { func getMethod(service Service, name string) Cmd {
@ -59,7 +143,7 @@ func getMethod(service Service, name string) Cmd {
stdout.Write([]byte(service.Help())) stdout.Write([]byte(service.Help()))
} else { } else {
if method := getMethod(service, args[0]); method == nil { if method := getMethod(service, args[0]); method == nil {
return errors.New("No such command: " + args[0]) return fmt.Errorf("No such command: %s", args[0])
} else { } else {
method(stdin, stdout, "--help") method(stdin, stdout, "--help")
} }

27
rcli/utils.go Normal file
View File

@ -0,0 +1,27 @@
package rcli
import (
"github.com/dotcloud/docker/term"
"os"
"os/signal"
)
//FIXME: move these function to utils.go (in rcli to avoid import loop)
func SetRawTerminal() (*term.State, error) {
oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
return nil, err
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
_ = <-c
term.Restore(int(os.Stdin.Fd()), oldState)
os.Exit(0)
}()
return oldState, err
}
func RestoreTerminal(state *term.State) {
term.Restore(int(os.Stdin.Fd()), state)
}

View File

@ -116,7 +116,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
if err := container.FromDisk(); err != nil { if err := container.FromDisk(); err != nil {
return nil, err return nil, err
} }
container.State.initLock()
if container.Id != id { if container.Id != id {
return container, fmt.Errorf("Container %s is stored at %s", container.Id, id) return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
} }
@ -136,6 +135,7 @@ func (runtime *Runtime) Register(container *Container) error {
} }
// FIXME: if the container is supposed to be running but is not, auto restart it? // FIXME: if the container is supposed to be running but is not, auto restart it?
// if so, then we need to restart monitor and init a new lock
// If the container is supposed to be running, make sure of it // If the container is supposed to be running, make sure of it
if container.State.Running { if container.State.Running {
if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil { if output, err := exec.Command("lxc-info", "-n", container.Id).CombinedOutput(); err != nil {
@ -150,10 +150,10 @@ func (runtime *Runtime) Register(container *Container) error {
} }
} }
} }
container.State.initLock()
container.runtime = runtime container.runtime = runtime
// Setup state lock (formerly in newState()
container.State.initLock()
// Attach to stdout and stderr // Attach to stdout and stderr
container.stderr = newWriteBroadcaster() container.stderr = newWriteBroadcaster()
container.stdout = newWriteBroadcaster() container.stdout = newWriteBroadcaster()

View File

@ -1,6 +1,7 @@
package docker package docker
import ( import (
"github.com/dotcloud/docker/rcli"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -77,7 +78,7 @@ func init() {
runtime: runtime, runtime: runtime,
} }
// Retrieve the Image // Retrieve the Image
if err := srv.CmdPull(os.Stdin, os.Stdout, unitTestImageName); err != nil { if err := srv.CmdPull(os.Stdin, rcli.NewDockerLocalConn(os.Stdout), unitTestImageName); err != nil {
panic(err) panic(err)
} }
} }
@ -314,7 +315,7 @@ func TestRestore(t *testing.T) {
// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running' // Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
cStdin, _ := container2.StdinPipe() cStdin, _ := container2.StdinPipe()
cStdin.Close() cStdin.Close()
if err := container2.WaitTimeout(time.Second); err != nil { if err := container2.WaitTimeout(2 * time.Second); err != nil {
t.Fatal(err) t.Fatal(err)
} }
container2.State.Running = true container2.State.Running = true
@ -358,4 +359,5 @@ func TestRestore(t *testing.T) {
if err := container3.Run(); err != nil { if err := container3.Run(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
container2.State.Running = false
} }

View File

@ -11,9 +11,7 @@ type State struct {
Pid int Pid int
ExitCode int ExitCode int
StartedAt time.Time StartedAt time.Time
l *sync.Mutex
stateChangeLock *sync.Mutex
stateChangeCond *sync.Cond
} }
// String returns a human-readable description of the state // String returns a human-readable description of the state
@ -29,31 +27,22 @@ func (s *State) setRunning(pid int) {
s.ExitCode = 0 s.ExitCode = 0
s.Pid = pid s.Pid = pid
s.StartedAt = time.Now() s.StartedAt = time.Now()
s.broadcast()
} }
func (s *State) setStopped(exitCode int) { func (s *State) setStopped(exitCode int) {
s.Running = false s.Running = false
s.Pid = 0 s.Pid = 0
s.ExitCode = exitCode s.ExitCode = exitCode
s.broadcast()
} }
func (s *State) initLock() { func (s *State) initLock() {
if s.stateChangeLock == nil { s.l = &sync.Mutex{}
s.stateChangeLock = &sync.Mutex{}
s.stateChangeCond = sync.NewCond(s.stateChangeLock)
}
} }
func (s *State) broadcast() { func (s *State) lock() {
s.stateChangeLock.Lock() s.l.Lock()
s.stateChangeCond.Broadcast()
s.stateChangeLock.Unlock()
} }
func (s *State) wait() { func (s *State) unlock() {
s.stateChangeLock.Lock() s.l.Unlock()
s.stateChangeCond.Wait()
s.stateChangeLock.Unlock()
} }

View File

@ -15,7 +15,8 @@ void MakeRaw(int fd) {
ioctl(fd, TCGETS, &t); ioctl(fd, TCGETS, &t);
t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); t.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN); t.c_oflag &= ~OPOST;
t.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
t.c_cflag &= ~(CSIZE | PARENB); t.c_cflag &= ~(CSIZE | PARENB);
t.c_cflag |= CS8; t.c_cflag |= CS8;

View File

@ -341,3 +341,46 @@ func TruncateId(id string) string {
} }
return id[:shortLen] return id[:shortLen]
} }
// Code c/c from io.Copy() modified to handle escape sequence
func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
buf := make([]byte, 32*1024)
for {
nr, er := src.Read(buf)
if nr > 0 {
// ---- Docker addition
// char 16 is C-p
if nr == 1 && buf[0] == 16 {
nr, er = src.Read(buf)
// char 17 is C-q
if nr == 1 && buf[0] == 17 {
if err := src.Close(); err != nil {
return 0, err
}
return 0, io.EOF
}
}
// ---- End of docker
nw, ew := dst.Write(buf[0:nr])
if nw > 0 {
written += int64(nw)
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
}
if er == io.EOF {
break
}
if er != nil {
err = er
break
}
}
return written, err
}