зеркало из https://github.com/microsoft/docker.git
Merge github.com:dotcloud/docker into 333-redis-documentation
This commit is contained in:
Коммит
418ef43fbb
10
README.md
10
README.md
|
@ -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)"
|
||||||
|
|
27
commands.go
27
commands.go
|
@ -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 {
|
||||||
|
|
110
commands_test.go
110
commands_test.go
|
@ -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)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
102
container.go
102
container.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,3 +40,35 @@ Notes
|
||||||
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.
|
|
@ -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)"
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This example assumes you have Docker running in daemon mode. For more information please see :ref:`running_examples`
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:**
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -7,7 +7,7 @@
|
||||||
Create an ssh daemon service
|
Create an ssh daemon service
|
||||||
============================
|
============================
|
||||||
|
|
||||||
|
.. include:: example_header.inc
|
||||||
|
|
||||||
|
|
||||||
**Video:**
|
**Video:**
|
||||||
|
|
|
@ -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
|
||||||
===================== */
|
===================== */
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
38
rcli/http.go
38
rcli/http.go
|
@ -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
|
|
||||||
}
|
|
100
rcli/tcp.go
100
rcli/tcp.go
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
23
state.go
23
state.go
|
@ -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()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
43
utils.go
43
utils.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче