mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 20:26:54 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 40ebe78bb1 | |||
| 1b7115a337 | |||
| 2e6a5bc7ee | |||
| 72cef46e5e | |||
| 626bfd87a7 | |||
| 8f41f1fa60 | |||
| faa8843650 | |||
| 0d9e54367f | |||
| 1f70b1e15d | |||
| 3f63b87807 | |||
| 9c3d2b6a4e | |||
| 1716fccbcc | |||
| 9043e4c757 | |||
| 2e9a73c5d8 | |||
| 1eaaa6b744 | |||
| cb54e9c659 | |||
| 7c2b085d1a | |||
| d063d52cce | |||
| 64c1b6d9cd | |||
| 329f4449dc | |||
| 0767916ade | |||
| 10923c7890 | |||
| 2832ea0cfe | |||
| a7299a3f26 | |||
| 1601366cb6 | |||
| e9a68801ba | |||
| f73401fb9a | |||
| dcf4572a69 | |||
| d530d581f7 | |||
| bdf05d8368 | |||
| b71b226cc1 | |||
| 80f6b4587b | |||
| e6e9c1cd62 | |||
| 246eed52de | |||
| b306a60738 | |||
| 7d0ab3858e | |||
| 4e5001b46a | |||
| b8f9803459 | |||
| 0c018d3697 | |||
| 72fdb41069 | |||
| 6eb8a74ff9 | |||
| 81ebf4fcf6 | |||
| 9875a9b1f1 | |||
| 27feba4594 | |||
| c83393a541 | |||
| 7e1e7d14fa | |||
| 99b5bec069 | |||
| 7d8895545e | |||
| 33a5fe3bd4 | |||
| 847a8f45a4 | |||
| 8cf30395a1 | |||
| 22adb52c0a | |||
| 793c1ad990 | |||
| febaeebfb8 | |||
| d32f184696 | |||
| 20085794f0 | |||
| b76b329ef0 | |||
| bae6f95830 | |||
| cda9cf1539 | |||
| 1b370f9d8d | |||
| aa4bf4284b | |||
| d9a9bfc9c7 | |||
| 90a6e310fe | |||
| f39af7e05d |
@@ -134,6 +134,12 @@ docker pull base
|
||||
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
|
||||
--------------------------------------
|
||||
@@ -183,7 +189,9 @@ JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444)
|
||||
PORT=$(docker port $JOB 4444)
|
||||
|
||||
# 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
|
||||
echo "Daemon received: $(docker logs $JOB)"
|
||||
|
||||
+21
-6
@@ -18,7 +18,7 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const VERSION = "0.1.3"
|
||||
const VERSION = "0.1.4"
|
||||
|
||||
var GIT_COMMIT string
|
||||
|
||||
@@ -62,7 +62,7 @@ func (srv *Server) Help() string {
|
||||
}
|
||||
|
||||
// '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
|
||||
// sequences and echo.
|
||||
//
|
||||
@@ -113,6 +113,8 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout io.Writer, args ...strin
|
||||
return readStringOnRawTerminal(stdin, stdout, false)
|
||||
}
|
||||
|
||||
stdout.SetOptionRawTerminal()
|
||||
|
||||
cmd := rcli.Subcmd(stdout, "login", "", "Register or Login to the docker registry server")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -417,7 +419,8 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
|
||||
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")
|
||||
var archive io.Reader
|
||||
var resp *http.Response
|
||||
@@ -464,7 +467,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri
|
||||
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")
|
||||
if err := cmd.Parse(args); err != 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))
|
||||
}
|
||||
|
||||
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")
|
||||
if err := cmd.Parse(args); err != 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -884,6 +892,13 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
|
||||
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
|
||||
container, err := srv.runtime.Create(config)
|
||||
if err != nil {
|
||||
|
||||
+89
-21
@@ -2,8 +2,8 @@ package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
@@ -69,15 +69,27 @@ func TestRunHostname(t *testing.T) {
|
||||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
var stdin, stdout bytes.Buffer
|
||||
setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
|
||||
if err := srv.CmdRun(ioutil.NopCloser(&stdin), &nopWriteCloser{&stdout}, "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil {
|
||||
stdin, _ := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
if output := string(stdout.Bytes()); output != "foobar\n" {
|
||||
t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", output)
|
||||
close(c)
|
||||
}()
|
||||
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) {
|
||||
@@ -93,7 +105,7 @@ func TestRunExit(t *testing.T) {
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
c1 := make(chan struct{})
|
||||
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)
|
||||
}()
|
||||
|
||||
@@ -147,7 +159,7 @@ func TestRunDisconnect(t *testing.T) {
|
||||
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, stdoutPipe, "-i", GetTestImage(runtime).Id, "/bin/cat")
|
||||
srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", GetTestImage(runtime).Id, "/bin/cat")
|
||||
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.
|
||||
// 'docker run -i -a stdin' should sends the client's stdin to the command,
|
||||
// then detach from it and print the container id.
|
||||
func TestAttachStdin(t *testing.T) {
|
||||
func TestRunAttachStdin(t *testing.T) {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -190,31 +248,41 @@ func TestAttachStdin(t *testing.T) {
|
||||
defer nuke(runtime)
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
stdinR, stdinW := io.Pipe()
|
||||
var stdout bytes.Buffer
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdout, stdoutPipe := io.Pipe()
|
||||
|
||||
ch := make(chan struct{})
|
||||
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)
|
||||
}()
|
||||
|
||||
// Send input to the command, close stdin, wait for CmdRun to return
|
||||
setTimeout(t, "Read/Write timed out", 2*time.Second, func() {
|
||||
if _, err := stdinW.Write([]byte("hi there\n")); err != nil {
|
||||
// Send input to the command, close stdin
|
||||
setTimeout(t, "Write timed out", 2*time.Second, func() {
|
||||
if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := stdinPipe.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stdinW.Close()
|
||||
<-ch
|
||||
})
|
||||
|
||||
// Check output
|
||||
cmdOutput := string(stdout.Bytes())
|
||||
container := runtime.List()[0]
|
||||
|
||||
// Check output
|
||||
cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cmdOutput != container.ShortId()+"\n" {
|
||||
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() {
|
||||
container.Wait()
|
||||
})
|
||||
@@ -270,7 +338,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
go func() {
|
||||
// We're simulating a disconnect so the return value doesn't matter. What matters is the
|
||||
// fact that CmdAttach returns.
|
||||
srv.CmdAttach(stdin, stdoutPipe, container.Id)
|
||||
srv.CmdAttach(stdin, rcli.NewDockerLocalConn(stdoutPipe), container.Id)
|
||||
close(c1)
|
||||
}()
|
||||
|
||||
|
||||
+41
-63
@@ -40,11 +40,11 @@ type Container struct {
|
||||
stdin io.ReadCloser
|
||||
stdinPipe io.WriteCloser
|
||||
|
||||
ptyStdinMaster io.Closer
|
||||
ptyStdoutMaster io.Closer
|
||||
ptyStderrMaster io.Closer
|
||||
ptyMaster io.Closer
|
||||
|
||||
runtime *Runtime
|
||||
|
||||
waitLock chan struct{}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -135,6 +135,7 @@ type NetworkSettings struct {
|
||||
IpAddress string
|
||||
IpPrefixLen int
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]string
|
||||
}
|
||||
|
||||
@@ -179,63 +180,37 @@ func (container *Container) generateLXCConfig() error {
|
||||
}
|
||||
|
||||
func (container *Container) startPty() error {
|
||||
stdoutMaster, stdoutSlave, err := pty.Open()
|
||||
ptyMaster, ptySlave, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.ptyStdoutMaster = stdoutMaster
|
||||
container.cmd.Stdout = stdoutSlave
|
||||
|
||||
stderrMaster, stderrSlave, err := pty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
container.ptyStderrMaster = stderrMaster
|
||||
container.cmd.Stderr = stderrSlave
|
||||
container.ptyMaster = ptyMaster
|
||||
container.cmd.Stdout = ptySlave
|
||||
container.cmd.Stderr = ptySlave
|
||||
|
||||
// Copy the PTYs to our broadcasters
|
||||
go func() {
|
||||
defer container.stdout.CloseWriters()
|
||||
Debugf("[startPty] Begin of stdout pipe")
|
||||
io.Copy(container.stdout, stdoutMaster)
|
||||
io.Copy(container.stdout, ptyMaster)
|
||||
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
|
||||
var stdinSlave io.ReadCloser
|
||||
if container.Config.OpenStdin {
|
||||
var stdinMaster io.WriteCloser
|
||||
stdinMaster, stdinSlave, err = pty.Open()
|
||||
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}
|
||||
container.cmd.Stdin = ptySlave
|
||||
container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
|
||||
go func() {
|
||||
defer container.stdin.Close()
|
||||
Debugf("[startPty] Begin of stdin pipe")
|
||||
io.Copy(stdinMaster, container.stdin)
|
||||
io.Copy(ptyMaster, container.stdin)
|
||||
Debugf("[startPty] End of stdin pipe")
|
||||
}()
|
||||
}
|
||||
if err := container.cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
stdoutSlave.Close()
|
||||
stderrSlave.Close()
|
||||
if stdinSlave != nil {
|
||||
stdinSlave.Close()
|
||||
}
|
||||
ptySlave.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -277,10 +252,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
||||
if cStderr != nil {
|
||||
defer cStderr.Close()
|
||||
}
|
||||
if container.Config.StdinOnce {
|
||||
if container.Config.StdinOnce && !container.Config.Tty {
|
||||
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 {
|
||||
Debugf("[error] attach stdin: %s\n", err)
|
||||
}
|
||||
@@ -364,6 +343,9 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
||||
}
|
||||
|
||||
func (container *Container) Start() error {
|
||||
container.State.lock()
|
||||
defer container.State.unlock()
|
||||
|
||||
if container.State.Running {
|
||||
return fmt.Errorf("The container %s is already running.", container.Id)
|
||||
}
|
||||
@@ -430,6 +412,9 @@ func (container *Container) Start() error {
|
||||
// FIXME: save state on disk *first*, then converge
|
||||
// this way disk state is used as a journal, eg. we can restore after crash etc.
|
||||
container.State.setRunning(container.cmd.Process.Pid)
|
||||
|
||||
// Init the lock
|
||||
container.waitLock = make(chan struct{})
|
||||
container.ToDisk()
|
||||
go container.monitor()
|
||||
return nil
|
||||
@@ -491,6 +476,7 @@ func (container *Container) allocateNetwork() error {
|
||||
}
|
||||
}
|
||||
container.network = iface
|
||||
container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
|
||||
container.NetworkSettings.IpAddress = iface.IPNet.IP.String()
|
||||
container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()
|
||||
container.NetworkSettings.Gateway = iface.Gateway.String()
|
||||
@@ -528,19 +514,9 @@ func (container *Container) monitor() {
|
||||
Debugf("%s: Error close stderr: %s", container.Id, err)
|
||||
}
|
||||
|
||||
if container.ptyStdinMaster != nil {
|
||||
if err := container.ptyStdinMaster.Close(); err != nil {
|
||||
Debugf("%s: Error close pty stdin 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)
|
||||
if container.ptyMaster != nil {
|
||||
if err := container.ptyMaster.Close(); err != nil {
|
||||
Debugf("%s: Error closing Pty master: %s", container.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,6 +531,10 @@ func (container *Container) monitor() {
|
||||
|
||||
// Report status back
|
||||
container.State.setStopped(exitCode)
|
||||
|
||||
// Release the lock
|
||||
close(container.waitLock)
|
||||
|
||||
if err := container.ToDisk(); err != nil {
|
||||
// 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
|
||||
@@ -567,7 +547,7 @@ func (container *Container) monitor() {
|
||||
}
|
||||
|
||||
func (container *Container) kill() error {
|
||||
if container.cmd == nil {
|
||||
if !container.State.Running || container.cmd == nil {
|
||||
return nil
|
||||
}
|
||||
if err := container.cmd.Process.Kill(); err != nil {
|
||||
@@ -579,13 +559,14 @@ func (container *Container) kill() error {
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
container.State.lock()
|
||||
defer container.State.unlock()
|
||||
return container.kill()
|
||||
}
|
||||
|
||||
func (container *Container) Stop() error {
|
||||
container.State.lock()
|
||||
defer container.State.unlock()
|
||||
if !container.State.Running {
|
||||
return nil
|
||||
}
|
||||
@@ -594,7 +575,7 @@ func (container *Container) Stop() error {
|
||||
if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
|
||||
log.Print(string(output))
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -602,7 +583,7 @@ func (container *Container) Stop() error {
|
||||
// 2. Wait for the process to exit on its own
|
||||
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)
|
||||
if err := container.Kill(); err != nil {
|
||||
if err := container.kill(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -621,10 +602,7 @@ func (container *Container) Restart() error {
|
||||
|
||||
// Wait blocks until the container stops running, then returns its exit code.
|
||||
func (container *Container) Wait() int {
|
||||
|
||||
for container.State.Running {
|
||||
container.State.wait()
|
||||
}
|
||||
<-container.waitLock
|
||||
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
|
||||
cStdin, _ := container.StdinPipe()
|
||||
cStdin.Close()
|
||||
container.WaitTimeout(2 * time.Second)
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
|
||||
+19
-23
@@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
var GIT_COMMIT string
|
||||
@@ -22,7 +21,13 @@ func main() {
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
|
||||
flag.Parse()
|
||||
if *bridgeName != "" {
|
||||
docker.NetworkBridgeIface = *bridgeName
|
||||
} else {
|
||||
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
|
||||
}
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
@@ -51,29 +56,21 @@ func daemon() 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
|
||||
// CloseWrite(), which we need to cleanly signal that stdin is closed without
|
||||
// closing the connection.
|
||||
// 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 {
|
||||
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 {
|
||||
_, err := io.Copy(os.Stdout, conn)
|
||||
return err
|
||||
@@ -98,12 +95,11 @@ func runCommand(args []string) error {
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
if oldState != nil {
|
||||
term.Restore(int(os.Stdin.Fd()), oldState)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+33
-1
@@ -39,4 +39,36 @@ Notes
|
||||
* 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
|
||||
* 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
|
||||
# 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
|
||||
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
|
||||
===========
|
||||
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
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
Hello World Daemon
|
||||
==================
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
The most boring daemon ever written.
|
||||
|
||||
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")
|
||||
|
||||
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.
|
||||
- **"base"** is the image we want to run the command inside of.
|
||||
|
||||
@@ -12,6 +12,7 @@ Contents:
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
running_examples
|
||||
hello_world
|
||||
hello_world_daemon
|
||||
python_web_app
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
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.
|
||||
|
||||
**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)
|
||||
|
||||
- **"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.
|
||||
|
||||
.. 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.
|
||||
|
||||
.. 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:**
|
||||
|
||||
|
||||
@@ -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
|
||||
============================
|
||||
|
||||
|
||||
.. include:: example_header.inc
|
||||
|
||||
|
||||
**Video:**
|
||||
|
||||
+22
-1
@@ -82,7 +82,7 @@ h4 {
|
||||
.btn-custom {
|
||||
background-color: #292929 !important;
|
||||
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: -moz-linear-gradient(top, #515151, #282828);
|
||||
background-image: -ms-linear-gradient(top, #515151, #282828);
|
||||
@@ -131,6 +131,27 @@ section.header {
|
||||
margin: 15px 15px 15px 0;
|
||||
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
|
||||
===================== */
|
||||
|
||||
+26
@@ -179,7 +179,33 @@ section.header {
|
||||
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
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ lxc.utsname = {{.Id}}
|
||||
# network configuration
|
||||
lxc.network.type = veth
|
||||
lxc.network.flags = up
|
||||
lxc.network.link = lxcbr0
|
||||
lxc.network.link = {{.NetworkSettings.Bridge}}
|
||||
lxc.network.name = eth0
|
||||
lxc.network.mtu = 1500
|
||||
lxc.network.ipv4 = {{.NetworkSettings.IpAddress}}/{{.NetworkSettings.IpPrefixLen}}
|
||||
|
||||
+102
-8
@@ -12,10 +12,12 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var NetworkBridgeIface string
|
||||
|
||||
const (
|
||||
networkBridgeIface = "lxcbr0"
|
||||
portRangeStart = 49153
|
||||
portRangeEnd = 65535
|
||||
DefaultNetworkBridge = "docker0"
|
||||
portRangeStart = 49153
|
||||
portRangeEnd = 65535
|
||||
)
|
||||
|
||||
// Calculates the first and last IP addresses in an IPNet
|
||||
@@ -29,6 +31,19 @@ func networkRange(network *net.IPNet) (net.IP, net.IP) {
|
||||
return firstIP, lastIP
|
||||
}
|
||||
|
||||
// Detects overlap between one IPNet and another
|
||||
func networkOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
|
||||
firstIP, _ := networkRange(netX)
|
||||
if netY.Contains(firstIP) {
|
||||
return true
|
||||
}
|
||||
firstIP, _ = networkRange(netY)
|
||||
if netX.Contains(firstIP) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Converts a 4 bytes IP into a 32 bit integer
|
||||
func ipToInt(ip net.IP) int32 {
|
||||
return int32(binary.BigEndian.Uint32(ip.To4()))
|
||||
@@ -51,6 +66,19 @@ func networkSize(mask net.IPMask) int32 {
|
||||
return int32(binary.BigEndian.Uint32(m)) + 1
|
||||
}
|
||||
|
||||
//Wrapper around the ip command
|
||||
func ip(args ...string) (string, error) {
|
||||
path, err := exec.LookPath("ip")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("command not found: ip")
|
||||
}
|
||||
output, err := exec.Command(path, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("ip failed: ip %v", strings.Join(args, " "))
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// Wrapper around the iptables command
|
||||
func iptables(args ...string) error {
|
||||
path, err := exec.LookPath("iptables")
|
||||
@@ -63,6 +91,66 @@ func iptables(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
|
||||
output, err := ip("route")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
Debugf("Routes:\n\n%s", output)
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.Trim(line, "\r\n\t ") == "" || strings.Contains(line, "default") {
|
||||
continue
|
||||
}
|
||||
if _, network, err := net.ParseCIDR(strings.Split(line, " ")[0]); err != nil {
|
||||
return fmt.Errorf("Unexpected ip route output: %s (%s)", err, line)
|
||||
} else if networkOverlaps(dockerNetwork, network) {
|
||||
return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork.String(), line)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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"}
|
||||
|
||||
var ifaceAddr string
|
||||
for _, addr := range addrs {
|
||||
_, dockerNetwork, err := net.ParseCIDR(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkRouteOverlaps(dockerNetwork); err == nil {
|
||||
ifaceAddr = addr
|
||||
break
|
||||
} else {
|
||||
Debugf("%s: %s", addr, err)
|
||||
}
|
||||
}
|
||||
if ifaceAddr == "" {
|
||||
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 {
|
||||
Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
|
||||
}
|
||||
|
||||
if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil {
|
||||
return fmt.Errorf("Error creating bridge: %s (output: %s)", err, output)
|
||||
}
|
||||
|
||||
if output, err := ip("addr", "add", ifaceAddr, "dev", ifaceName); err != nil {
|
||||
return fmt.Errorf("Unable to add private network: %s (%s)", err, output)
|
||||
}
|
||||
if output, err := ip("link", "set", ifaceName, "up"); err != nil {
|
||||
return fmt.Errorf("Unable to start network bridge: %s (%s)", err, output)
|
||||
}
|
||||
if err := iptables("-t", "nat", "-A", "POSTROUTING", "-s", ifaceAddr,
|
||||
"!", "-d", ifaceAddr, "-j", "MASQUERADE"); err != nil {
|
||||
return fmt.Errorf("Unable to enable network bridge NAT: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the IPv4 address of a network interface
|
||||
func getIfaceAddr(name string) (net.Addr, error) {
|
||||
iface, err := net.InterfaceByName(name)
|
||||
@@ -167,10 +255,6 @@ type PortAllocator struct {
|
||||
}
|
||||
|
||||
func (alloc *PortAllocator) runFountain() {
|
||||
if alloc.fountain != nil {
|
||||
return
|
||||
}
|
||||
alloc.fountain = make(chan int)
|
||||
for {
|
||||
for port := portRangeStart; port < portRangeEnd; port++ {
|
||||
alloc.fountain <- port
|
||||
@@ -180,6 +264,7 @@ func (alloc *PortAllocator) runFountain() {
|
||||
|
||||
// FIXME: Release can no longer fail, change its prototype to reflect that.
|
||||
func (alloc *PortAllocator) Release(port int) error {
|
||||
Debugf("Releasing %d", port)
|
||||
alloc.lock.Lock()
|
||||
delete(alloc.inUse, port)
|
||||
alloc.lock.Unlock()
|
||||
@@ -187,6 +272,7 @@ func (alloc *PortAllocator) Release(port int) error {
|
||||
}
|
||||
|
||||
func (alloc *PortAllocator) Acquire(port int) (int, error) {
|
||||
Debugf("Acquiring %d", port)
|
||||
if port == 0 {
|
||||
// Allocate a port from the fountain
|
||||
for port := range alloc.fountain {
|
||||
@@ -208,6 +294,7 @@ func (alloc *PortAllocator) Acquire(port int) (int, error) {
|
||||
func newPortAllocator() (*PortAllocator, error) {
|
||||
allocator := &PortAllocator{
|
||||
inUse: make(map[int]struct{}),
|
||||
fountain: make(chan int),
|
||||
}
|
||||
go allocator.runFountain()
|
||||
return allocator, nil
|
||||
@@ -407,7 +494,14 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
|
||||
func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
|
||||
addr, err := getIfaceAddr(bridgeIface)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't find bridge interface %s (%s).\nPlease create it with 'ip link add lxcbr0 type bridge; ip addr add ADDRESS/MASK dev lxcbr0'", bridgeIface, err)
|
||||
// If the iface is not found, try to create it
|
||||
if err := CreateBridgeIface(bridgeIface); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr, err = getIfaceAddr(bridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
network := addr.(*net.IPNet)
|
||||
|
||||
|
||||
@@ -18,6 +18,42 @@ func TestIptables(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPortAllocation(t *testing.T) {
|
||||
allocator, err := newPortAllocator()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port, err := allocator.Acquire(80); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if port != 80 {
|
||||
t.Fatalf("Acquire(80) should return 80, not %d", port)
|
||||
}
|
||||
port, err := allocator.Acquire(0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if port <= 0 {
|
||||
t.Fatalf("Acquire(0) should return a non-zero port")
|
||||
}
|
||||
if _, err := allocator.Acquire(port); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if newPort, err := allocator.Acquire(0); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if newPort == port {
|
||||
t.Fatalf("Acquire(0) allocated the same port twice: %d", port)
|
||||
}
|
||||
if _, err := allocator.Acquire(80); err == nil {
|
||||
t.Fatalf("Acquiring a port already in use should return an error")
|
||||
}
|
||||
if err := allocator.Release(80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := allocator.Acquire(80); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkRange(t *testing.T) {
|
||||
// Simple class C test
|
||||
_, network, _ := net.ParseCIDR("192.168.0.1/24")
|
||||
@@ -217,3 +253,38 @@ func assertIPEquals(t *testing.T, ip1, ip2 net.IP) {
|
||||
t.Fatalf("Expected IP %s, got %s", ip1, ip2)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) {
|
||||
_, netX, _ := net.ParseCIDR(CIDRx)
|
||||
_, netY, _ := net.ParseCIDR(CIDRy)
|
||||
if !networkOverlaps(netX, netY) {
|
||||
t.Errorf("%v and %v should overlap", netX, netY)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) {
|
||||
_, netX, _ := net.ParseCIDR(CIDRx)
|
||||
_, netY, _ := net.ParseCIDR(CIDRy)
|
||||
if networkOverlaps(netX, netY) {
|
||||
t.Errorf("%v and %v should not overlap", netX, netY)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkOverlaps(t *testing.T) {
|
||||
//netY starts at same IP and ends within netX
|
||||
AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t)
|
||||
//netY starts within netX and ends at same IP
|
||||
AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t)
|
||||
//netY starts and ends within netX
|
||||
AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t)
|
||||
//netY starts at same IP and ends outside of netX
|
||||
AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t)
|
||||
//netY starts before and ends at same IP of netX
|
||||
AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t)
|
||||
//netY starts before and ends outside of netX
|
||||
AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t)
|
||||
//netY starts and ends before netX
|
||||
AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t)
|
||||
//netX starts and ends before netY
|
||||
AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+96
-4
@@ -2,6 +2,7 @@ package rcli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -15,22 +16,109 @@ import (
|
||||
var DEBUG_FLAG bool = false
|
||||
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`,
|
||||
// issue a single call, and return the result.
|
||||
// `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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := net.Dial(proto, addr)
|
||||
conn, err := dialDocker(proto, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := fmt.Fprintln(conn, string(cmd)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn.(*net.TCPConn), nil
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
} else {
|
||||
conn, err := newDockerServerConn(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
if DEBUG_FLAG {
|
||||
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
|
||||
// is valid.
|
||||
func Serve(conn io.ReadWriter, service Service) error {
|
||||
func Serve(conn DockerConn, service Service) error {
|
||||
r := bufio.NewReader(conn)
|
||||
var args []string
|
||||
if line, err := r.ReadString('\n'); err != nil {
|
||||
|
||||
+89
-5
@@ -8,15 +8,99 @@ package rcli
|
||||
// are the usual suspects.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/term"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"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 {
|
||||
Name() 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
|
||||
|
||||
// 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...)
|
||||
}
|
||||
|
||||
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 {
|
||||
args = []string{"help"}
|
||||
}
|
||||
@@ -49,7 +133,7 @@ func LocalCall(service Service, stdin io.ReadCloser, stdout io.Writer, args ...s
|
||||
if method != nil {
|
||||
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 {
|
||||
@@ -59,7 +143,7 @@ func getMethod(service Service, name string) Cmd {
|
||||
stdout.Write([]byte(service.Help()))
|
||||
} else {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
+7
-4
@@ -116,7 +116,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
|
||||
if err := container.FromDisk(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container.State.initLock()
|
||||
if 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?
|
||||
// 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 container.State.Running {
|
||||
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
|
||||
// Setup state lock (formerly in newState()
|
||||
container.State.initLock()
|
||||
|
||||
// Attach to stdout and stderr
|
||||
container.stderr = newWriteBroadcaster()
|
||||
container.stdout = newWriteBroadcaster()
|
||||
@@ -267,7 +267,10 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
|
||||
}
|
||||
netManager, err := newNetworkManager(networkBridgeIface)
|
||||
if NetworkBridgeIface == "" {
|
||||
NetworkBridgeIface = DefaultNetworkBridge
|
||||
}
|
||||
netManager, err := newNetworkManager(NetworkBridgeIface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,6 +1,7 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/rcli"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -77,7 +78,7 @@ func init() {
|
||||
runtime: runtime,
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -314,7 +315,7 @@ func TestRestore(t *testing.T) {
|
||||
// Simulate a crash/manual quit of dockerd: process dies, states stays 'Running'
|
||||
cStdin, _ := container2.StdinPipe()
|
||||
cStdin.Close()
|
||||
if err := container2.WaitTimeout(time.Second); err != nil {
|
||||
if err := container2.WaitTimeout(2 * time.Second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.State.Running = true
|
||||
@@ -358,4 +359,5 @@ func TestRestore(t *testing.T) {
|
||||
if err := container3.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.State.Running = false
|
||||
}
|
||||
|
||||
@@ -11,9 +11,7 @@ type State struct {
|
||||
Pid int
|
||||
ExitCode int
|
||||
StartedAt time.Time
|
||||
|
||||
stateChangeLock *sync.Mutex
|
||||
stateChangeCond *sync.Cond
|
||||
l *sync.Mutex
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the state
|
||||
@@ -29,31 +27,22 @@ func (s *State) setRunning(pid int) {
|
||||
s.ExitCode = 0
|
||||
s.Pid = pid
|
||||
s.StartedAt = time.Now()
|
||||
s.broadcast()
|
||||
}
|
||||
|
||||
func (s *State) setStopped(exitCode int) {
|
||||
s.Running = false
|
||||
s.Pid = 0
|
||||
s.ExitCode = exitCode
|
||||
s.broadcast()
|
||||
}
|
||||
|
||||
func (s *State) initLock() {
|
||||
if s.stateChangeLock == nil {
|
||||
s.stateChangeLock = &sync.Mutex{}
|
||||
s.stateChangeCond = sync.NewCond(s.stateChangeLock)
|
||||
}
|
||||
s.l = &sync.Mutex{}
|
||||
}
|
||||
|
||||
func (s *State) broadcast() {
|
||||
s.stateChangeLock.Lock()
|
||||
s.stateChangeCond.Broadcast()
|
||||
s.stateChangeLock.Unlock()
|
||||
func (s *State) lock() {
|
||||
s.l.Lock()
|
||||
}
|
||||
|
||||
func (s *State) wait() {
|
||||
s.stateChangeLock.Lock()
|
||||
s.stateChangeCond.Wait()
|
||||
s.stateChangeLock.Unlock()
|
||||
func (s *State) unlock() {
|
||||
s.l.Unlock()
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ void MakeRaw(int fd) {
|
||||
ioctl(fd, TCGETS, &t);
|
||||
|
||||
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 |= CS8;
|
||||
|
||||
|
||||
@@ -341,3 +341,46 @@ func TruncateId(id string) string {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user