From 941e3e2ef09092306a7a287ce62b6fb518af9c56 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 11 Jul 2013 17:18:28 +0000 Subject: [PATCH 01/20] wip --- container.go | 18 ++++++++++++----- runtime.go | 4 ++-- utils/utils.go | 54 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/container.go b/container.go index 48661a3098..e54440a5ae 100644 --- a/container.go +++ b/container.go @@ -617,13 +617,21 @@ func (container *Container) Start(hostConfig *HostConfig) error { container.cmd = exec.Command("lxc-start", params...) // Setup logging of stdout and stderr to disk - if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil { + /* + if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout"), ""); err != nil { return err } - if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr")); err != nil { + if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr"), ""); err != nil { return err } - + */ + if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { + return err + } + if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { + return err + } + var err error if container.Config.Tty { err = container.startPty() @@ -678,13 +686,13 @@ func (container *Container) StdinPipe() (io.WriteCloser, error) { func (container *Container) StdoutPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() - container.stdout.AddWriter(writer) + container.stdout.AddWriter(writer, "") return utils.NewBufReader(reader), nil } func (container *Container) StderrPipe() (io.ReadCloser, error) { reader, writer := io.Pipe() - container.stderr.AddWriter(writer) + container.stderr.AddWriter(writer, "") return utils.NewBufReader(reader), nil } diff --git a/runtime.go b/runtime.go index 5b0f7b2b2a..d73dd16f70 100644 --- a/runtime.go +++ b/runtime.go @@ -168,12 +168,12 @@ func (runtime *Runtime) Register(container *Container) error { return nil } -func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst string) error { +func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst, stream string) error { log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) if err != nil { return err } - src.AddWriter(log) + src.AddWriter(log, stream) return nil } diff --git a/utils/utils.go b/utils/utils.go index df615844a7..3139e380a6 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -247,30 +247,52 @@ func (r *bufReader) Close() error { type WriteBroadcaster struct { sync.Mutex - writers map[io.WriteCloser]struct{} + writers map[StreamWriter][]byte } -func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) { +type StreamWriter struct { + wc io.WriteCloser + stream string +} + +func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser, stream string) { w.Lock() - w.writers[writer] = struct{}{} + sw := StreamWriter{wc: writer, stream: stream} + w.writers[sw] = []byte{} w.Unlock() } -// FIXME: Is that function used? -// FIXME: This relies on the concrete writer type used having equality operator -func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) { - w.Lock() - delete(w.writers, writer) - w.Unlock() +type JSONLog struct { + Log string `json:"log,omitempty"` + Stream string `json:"stream,omitempty"` + Created time.Time `json:"time"` } func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { w.Lock() defer w.Unlock() - for writer := range w.writers { - if n, err := writer.Write(p); err != nil || n != len(p) { + for sw := range w.writers { + lp := p + if sw.stream != "" { + w.writers[sw] = append(w.writers[sw], p...) + s := string(p) + if s[len(s)-1] == '\n' { + /* lp, err = json.Marshal(&JSONLog{Log: s, Stream: sw.stream, Created: time.Now()}) + if err != nil { + // On error, evict the writer + delete(w.writers, sw) + continue + } + */ + lp = []byte("[" + time.Now().String() + "] [" + sw.stream + "] " + s) + w.writers[sw] = []byte{} + } else { + continue + } + } + if n, err := sw.wc.Write(lp); err != nil || n != len(lp) { // On error, evict the writer - delete(w.writers, writer) + delete(w.writers, sw) } } return len(p), nil @@ -279,15 +301,15 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { func (w *WriteBroadcaster) CloseWriters() error { w.Lock() defer w.Unlock() - for writer := range w.writers { - writer.Close() + for sw := range w.writers { + sw.wc.Close() } - w.writers = make(map[io.WriteCloser]struct{}) + w.writers = make(map[StreamWriter][]byte) return nil } func NewWriteBroadcaster() *WriteBroadcaster { - return &WriteBroadcaster{writers: make(map[io.WriteCloser]struct{})} + return &WriteBroadcaster{writers: make(map[StreamWriter][]byte)} } func GetTotalUsedFds() int { From 080243f0407a90cdacf128dc3b53a802549d7797 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 12 Jul 2013 17:56:55 -0700 Subject: [PATCH 02/20] Hack: use helper functions in tests for less copy-pasting --- container_test.go | 117 ++++++---------------------------------------- runtime_test.go | 72 ++++------------------------ utils_test.go | 25 +++++++--- 3 files changed, 39 insertions(+), 175 deletions(-) diff --git a/container_test.go b/container_test.go index e7f6818eb6..bc1eaf99ec 100644 --- a/container_test.go +++ b/container_test.go @@ -39,16 +39,11 @@ func TestIDFormat(t *testing.T) { func TestMultipleAttachRestart(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/sh", "-c", - "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"}, - }, + container, hostConfig, _ := mkContainer( + runtime, + []string{"_", "/bin/sh", "-c", "i=1; while [ $i -le 5 ]; do i=`expr $i + 1`; echo hello; done"}, + t, ) - if err != nil { - t.Fatal(err) - } defer runtime.Destroy(container) // Simulate 3 client attaching to the container and stop/restart @@ -65,7 +60,6 @@ func TestMultipleAttachRestart(t *testing.T) { if err != nil { t.Fatal(err) } - hostConfig := &HostConfig{} if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -140,19 +134,8 @@ func TestMultipleAttachRestart(t *testing.T) { func TestDiff(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - - builder := NewBuilder(runtime) - // Create a container and remove a file - container1, err := builder.Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/rm", "/etc/passwd"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t) defer runtime.Destroy(container1) if err := container1.Run(); err != nil { @@ -185,15 +168,7 @@ func TestDiff(t *testing.T) { } // Create a new container from the commited image - container2, err := builder.Create( - &Config{ - Image: img.ID, - Cmd: []string{"cat", "/etc/passwd"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, _, _ := mkContainer(runtime, []string{img.ID, "cat", "/etc/passwd"}, t) defer runtime.Destroy(container2) if err := container2.Run(); err != nil { @@ -212,15 +187,7 @@ func TestDiff(t *testing.T) { } // Create a new containere - container3, err := builder.Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"rm", "/bin/httpd"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container3, _, _ := mkContainer(runtime, []string{"_", "rm", "/bin/httpd"}, t) defer runtime.Destroy(container3) if err := container3.Run(); err != nil { @@ -246,17 +213,7 @@ func TestDiff(t *testing.T) { func TestCommitAutoRun(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - - builder := NewBuilder(runtime) - container1, err := builder.Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t) defer runtime.Destroy(container1) if container1.State.Running { @@ -279,14 +236,7 @@ func TestCommitAutoRun(t *testing.T) { } // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world - container2, err := builder.Create( - &Config{ - Image: img.ID, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, hostConfig, _ := mkContainer(runtime, []string{img.ID}, t) defer runtime.Destroy(container2) stdout, err := container2.StdoutPipe() if err != nil { @@ -296,7 +246,6 @@ func TestCommitAutoRun(t *testing.T) { if err != nil { t.Fatal(err) } - hostConfig := &HostConfig{} if err := container2.Start(hostConfig); err != nil { t.Fatal(err) } @@ -324,17 +273,7 @@ func TestCommitRun(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - builder := NewBuilder(runtime) - - container1, err := builder.Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"/bin/sh", "-c", "echo hello > /world"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, hostConfig, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t) defer runtime.Destroy(container1) if container1.State.Running { @@ -357,16 +296,7 @@ func TestCommitRun(t *testing.T) { } // FIXME: Make a TestCommit that stops here and check docker.root/layers/img.id/world - - container2, err := builder.Create( - &Config{ - Image: img.ID, - Cmd: []string{"cat", "/world"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, hostConfig, _ := mkContainer(runtime, []string{img.ID, "cat", "/world"}, t) defer runtime.Destroy(container2) stdout, err := container2.StdoutPipe() if err != nil { @@ -376,7 +306,6 @@ func TestCommitRun(t *testing.T) { if err != nil { t.Fatal(err) } - hostConfig := &HostConfig{} if err := container2.Start(hostConfig); err != nil { t.Fatal(err) } @@ -403,18 +332,7 @@ func TestCommitRun(t *testing.T) { func TestStart(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( - &Config{ - Image: GetTestImage(runtime).ID, - Memory: 33554432, - CpuShares: 1000, - Cmd: []string{"/bin/cat"}, - OpenStdin: true, - }, - ) - if err != nil { - t.Fatal(err) - } + container, hostConfig, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t) defer runtime.Destroy(container) cStdin, err := container.StdinPipe() @@ -422,7 +340,6 @@ func TestStart(t *testing.T) { t.Fatal(err) } - hostConfig := &HostConfig{} if err := container.Start(hostConfig); err != nil { t.Fatal(err) } @@ -445,15 +362,7 @@ func TestStart(t *testing.T) { func TestRun(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( - &Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t) defer runtime.Destroy(container) if container.State.Running { diff --git a/runtime_test.go b/runtime_test.go index 9d43bd46e5..66d92c8100 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/dotcloud/docker/utils" "io" - "io/ioutil" "log" "net" "os" @@ -247,36 +246,13 @@ func TestGet(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - builder := NewBuilder(runtime) - - container1, err := builder.Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t) defer runtime.Destroy(container1) - container2, err := builder.Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t) defer runtime.Destroy(container2) - container3, err := builder.Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container3, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t) defer runtime.Destroy(container3) if runtime.Get(container1.ID) != container1 { @@ -431,46 +407,14 @@ func TestAllocateUDPPortLocalhost(t *testing.T) { } func TestRestore(t *testing.T) { - - root, err := ioutil.TempDir("", "docker-test") - if err != nil { - t.Fatal(err) - } - if err := os.Remove(root); err != nil { - t.Fatal(err) - } - if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil { - t.Fatal(err) - } - - runtime1, err := NewRuntimeFromDirectory(root, false) - if err != nil { - t.Fatal(err) - } - - builder := NewBuilder(runtime1) - + runtime1 := mkRuntime(t) + defer nuke(runtime1) // Create a container with one instance of docker - container1, err := builder.Create(&Config{ - Image: GetTestImage(runtime1).ID, - Cmd: []string{"ls", "-al"}, - }, - ) - if err != nil { - t.Fatal(err) - } + container1, _, _ := mkContainer(runtime1, []string{"_", "ls", "-al"}, t) defer runtime1.Destroy(container1) // Create a second container meant to be killed - container2, err := builder.Create(&Config{ - Image: GetTestImage(runtime1).ID, - Cmd: []string{"/bin/cat"}, - OpenStdin: true, - }, - ) - if err != nil { - t.Fatal(err) - } + container2, _, _ := mkContainer(runtime1, []string{"-i", "_", "/bin/cat"}, t) defer runtime1.Destroy(container2) // Start the container non blocking @@ -505,7 +449,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(root, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) if err != nil { t.Fatal(err) } diff --git a/utils_test.go b/utils_test.go index 4951d3a02d..d1859ff8b4 100644 --- a/utils_test.go +++ b/utils_test.go @@ -84,20 +84,28 @@ func readFile(src string, t *testing.T) (content string) { } // Create a test container from the given runtime `r` and run arguments `args`. -// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image. +// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is +// dynamically replaced by the current test image. // The caller is responsible for destroying the container. // Call t.Fatal() at the first error. -func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig) { +func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConfig, error) { config, hostConfig, _, err := ParseRun(args, nil) + defer func() { + if err != nil && t != nil { + t.Fatal(err) + } + }() if err != nil { - t.Fatal(err) + return nil, nil, err + } + if config.Image == "_" { + config.Image = GetTestImage(r).ID } - config.Image = GetTestImage(r).ID c, err := NewBuilder(r).Create(config) if err != nil { - t.Fatal(err) + return nil, nil, err } - return c, hostConfig + return c, hostConfig, nil } // Create a test container, start it, wait for it to complete, destroy it, @@ -110,7 +118,10 @@ func runContainer(r *Runtime, args []string, t *testing.T) (output string, err e t.Fatal(err) } }() - container, hostConfig := mkContainer(r, args, t) + container, hostConfig, err := mkContainer(r, args, t) + if err != nil { + return "", err + } defer r.Destroy(container) stdout, err := container.StdoutPipe() if err != nil { From 599f85d4e4362f24dc2850c71a689671122c456b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 15 Jul 2013 16:17:58 +0000 Subject: [PATCH 03/20] store both logs in a same file, as JSON --- commands_test.go | 12 ++++++------ container.go | 10 +--------- server.go | 28 +++++++++++++++------------- utils/utils.go | 34 ++++++++++++++++++---------------- 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/commands_test.go b/commands_test.go index 3f4c53db03..233c6337d4 100644 --- a/commands_test.go +++ b/commands_test.go @@ -59,7 +59,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } - // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname func TestRunHostname(t *testing.T) { stdout, stdoutPipe := io.Pipe() @@ -91,7 +90,6 @@ func TestRunHostname(t *testing.T) { } - // 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. @@ -144,15 +142,17 @@ func TestRunAttachStdin(t *testing.T) { }) // Check logs - if cmdLogs, err := container.ReadLog("stdout"); err != nil { + if cmdLogs, err := container.ReadLog("json"); err != nil { t.Fatal(err) } else { if output, err := ioutil.ReadAll(cmdLogs); err != nil { t.Fatal(err) } else { - expectedLog := "hello\nhi there\n" - if string(output) != expectedLog { - t.Fatalf("Unexpected logs: should be '%s', not '%s'\n", expectedLog, output) + expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""} + for _, expectedLog := range expectedLogs { + if !strings.Contains(string(output), expectedLog) { + t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output) + } } } } diff --git a/container.go b/container.go index 7b0070094a..1011f7a6e3 100644 --- a/container.go +++ b/container.go @@ -640,21 +640,13 @@ func (container *Container) Start(hostConfig *HostConfig) error { container.cmd = exec.Command("lxc-start", params...) // Setup logging of stdout and stderr to disk - /* - if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout"), ""); err != nil { - return err - } - if err := container.runtime.LogToDisk(container.stderr, container.logPath("stderr"), ""); err != nil { - return err - } - */ if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { return err } if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { return err } - + var err error if container.Config.Tty { err = container.startPty() diff --git a/server.go b/server.go index c43cae0c38..6129e3eb95 100644 --- a/server.go +++ b/server.go @@ -2,6 +2,7 @@ package docker import ( "bufio" + "encoding/json" "errors" "fmt" "github.com/dotcloud/docker/auth" @@ -1042,20 +1043,21 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std } //logs if logs { - if stdout { - cLog, err := container.ReadLog("stdout") - if err != nil { - utils.Debugf("Error reading logs (stdout): %s", err) - } else if _, err := io.Copy(out, cLog); err != nil { - utils.Debugf("Error streaming logs (stdout): %s", err) - } + cLog, err := container.ReadLog("json") + if err != nil { + utils.Debugf("Error reading logs (json): %s", err) } - if stderr { - cLog, err := container.ReadLog("stderr") - if err != nil { - utils.Debugf("Error reading logs (stderr): %s", err) - } else if _, err := io.Copy(out, cLog); err != nil { - utils.Debugf("Error streaming logs (stderr): %s", err) + dec := json.NewDecoder(cLog) + for { + var l utils.JSONLog + if err := dec.Decode(&l); err == io.EOF { + break + } else if err != nil { + utils.Debugf("Error streaming logs: %s", err) + break + } + if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) { + fmt.Fprintf(out, "%s", l.Log) } } } diff --git a/utils/utils.go b/utils/utils.go index 3ec853bf68..9e6f0c9c0d 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -247,47 +247,49 @@ func (r *bufReader) Close() error { type WriteBroadcaster struct { sync.Mutex - writers map[StreamWriter][]byte + buf *bytes.Buffer + writers map[StreamWriter]bool } type StreamWriter struct { - wc io.WriteCloser + wc io.WriteCloser stream string } func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser, stream string) { w.Lock() sw := StreamWriter{wc: writer, stream: stream} - w.writers[sw] = []byte{} + w.writers[sw] = true w.Unlock() } type JSONLog struct { - Log string `json:"log,omitempty"` - Stream string `json:"stream,omitempty"` + Log string `json:"log,omitempty"` + Stream string `json:"stream,omitempty"` Created time.Time `json:"time"` } func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { w.Lock() defer w.Unlock() + w.buf.Write(p) for sw := range w.writers { lp := p if sw.stream != "" { - w.writers[sw] = append(w.writers[sw], p...) - s := string(p) - if s[len(s)-1] == '\n' { - /* lp, err = json.Marshal(&JSONLog{Log: s, Stream: sw.stream, Created: time.Now()}) + lp = nil + for { + line, err := w.buf.ReadString('\n') + if err != nil { + w.buf.Write([]byte(line)) + break + } + b, err := json.Marshal(&JSONLog{Log: line, Stream: sw.stream, Created: time.Now()}) if err != nil { // On error, evict the writer delete(w.writers, sw) continue } - */ - lp = []byte("[" + time.Now().String() + "] [" + sw.stream + "] " + s) - w.writers[sw] = []byte{} - } else { - continue + lp = append(lp, b...) } } if n, err := sw.wc.Write(lp); err != nil || n != len(lp) { @@ -304,12 +306,12 @@ func (w *WriteBroadcaster) CloseWriters() error { for sw := range w.writers { sw.wc.Close() } - w.writers = make(map[StreamWriter][]byte) + w.writers = make(map[StreamWriter]bool) return nil } func NewWriteBroadcaster() *WriteBroadcaster { - return &WriteBroadcaster{writers: make(map[StreamWriter][]byte)} + return &WriteBroadcaster{writers: make(map[StreamWriter]bool), buf: bytes.NewBuffer(nil)} } func GetTotalUsedFds() int { From a926cd4d880904258e01ea521ecd9e1b908f2b97 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 13:25:47 +0000 Subject: [PATCH 04/20] add legacy support --- server.go | 45 ++++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/server.go b/server.go index 6129e3eb95..d275fe814b 100644 --- a/server.go +++ b/server.go @@ -1044,20 +1044,39 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std //logs if logs { cLog, err := container.ReadLog("json") - if err != nil { - utils.Debugf("Error reading logs (json): %s", err) - } - dec := json.NewDecoder(cLog) - for { - var l utils.JSONLog - if err := dec.Decode(&l); err == io.EOF { - break - } else if err != nil { - utils.Debugf("Error streaming logs: %s", err) - break + if err != nil && os.IsNotExist(err) { + // Legacy logs + if stdout { + cLog, err := container.ReadLog("stdout") + if err != nil { + utils.Debugf("Error reading logs (stdout): %s", err) + } else if _, err := io.Copy(out, cLog); err != nil { + utils.Debugf("Error streaming logs (stdout): %s", err) + } } - if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) { - fmt.Fprintf(out, "%s", l.Log) + if stderr { + cLog, err := container.ReadLog("stderr") + if err != nil { + utils.Debugf("Error reading logs (stderr): %s", err) + } else if _, err := io.Copy(out, cLog); err != nil { + utils.Debugf("Error streaming logs (stderr): %s", err) + } + } + } else if err != nil { + utils.Debugf("Error reading logs (json): %s", err) + } else { + dec := json.NewDecoder(cLog) + for { + var l utils.JSONLog + if err := dec.Decode(&l); err == io.EOF { + break + } else if err != nil { + utils.Debugf("Error streaming logs: %s", err) + break + } + if (l.Stream == "stdout" && stdout) || (l.Stream == "stderr" && stderr) { + fmt.Fprintf(out, "%s", l.Log) + } } } } From 1b0fd7ead33722d8782634d54cbd797f284aa085 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 18 Jul 2013 13:29:40 +0000 Subject: [PATCH 05/20] add debug and simplify docker logs --- commands.go | 5 +---- server.go | 1 + 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index b581590bc2..def2ff72d7 100644 --- a/commands.go +++ b/commands.go @@ -1099,10 +1099,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error { return nil } - if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, cli.out); err != nil { - return err - } - if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, cli.err); err != nil { + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1&stderr=1", false, nil, cli.out); err != nil { return err } return nil diff --git a/server.go b/server.go index d275fe814b..958dc75663 100644 --- a/server.go +++ b/server.go @@ -1046,6 +1046,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std cLog, err := container.ReadLog("json") if err != nil && os.IsNotExist(err) { // Legacy logs + utils.Debugf("Old logs format") if stdout { cLog, err := container.ReadLog("stdout") if err != nil { From 54f9cdb0c30ef7d192c4ae59c669a7a5fd7d1003 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Thu, 18 Jul 2013 19:04:51 -0700 Subject: [PATCH 06/20] Make docs build without warnings or errors. Minor additional cleanup. --- docs/sources/api/docker_remote_api.rst | 61 +++++++++++++++------ docs/sources/api/docker_remote_api_v1.0.rst | 9 ++- docs/sources/api/docker_remote_api_v1.1.rst | 4 ++ docs/sources/api/docker_remote_api_v1.2.rst | 5 ++ docs/sources/api/docker_remote_api_v1.3.rst | 5 ++ docs/sources/api/index_api.rst | 2 +- docs/sources/api/registry_index_spec.rst | 5 +- docs/sources/index.rst | 2 - docs/sources/use/builder.rst | 8 +-- docs/sources/use/workingwithrepository.rst | 2 +- 10 files changed, 73 insertions(+), 30 deletions(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 183347c23b..f8a1381c53 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -2,6 +2,9 @@ :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation +.. COMMENT use http://pythonhosted.org/sphinxcontrib-httpdomain/ to +.. document the REST API. + ================= Docker Remote API ================= @@ -13,15 +16,23 @@ Docker Remote API - The Remote API is replacing rcli - Default port in the docker deamon is 4243 -- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr -- Since API version 1.2, the auth configuration is now handled client side, so the client has to send the authConfig as POST in /images/(name)/push +- The API tends to be REST, but for some complex commands, like attach + or pull, the HTTP connection is hijacked to transport stdout stdin + and stderr +- Since API version 1.2, the auth configuration is now handled client + side, so the client has to send the authConfig as POST in + /images/(name)/push 2. Versions =========== -The current verson of the API is 1.3 -Calling /images//insert is the same as calling /v1.3/images//insert -You can still call an old version of the api using /v1.0/images//insert +The current verson of the API is 1.3 + +Calling /images//insert is the same as calling +/v1.3/images//insert + +You can still call an old version of the api using +/v1.0/images//insert :doc:`docker_remote_api_v1.3` ***************************** @@ -29,19 +40,21 @@ You can still call an old version of the api using /v1.0/images//insert What's new ---------- -Listing processes (/top): - -- List the processes inside a container +.. http:get:: /containers/(id)/top + **New!** List the processes running inside a container. Builder (/build): - Simplify the upload of the build context -- Simply stream a tarball instead of multipart upload with 4 intermediary buffers +- Simply stream a tarball instead of multipart upload with 4 + intermediary buffers - Simpler, less memory usage, less disk usage and faster -.. Note:: -The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build. +.. Warning:: + + The /build improvements are not reverse-compatible. Pre 1.3 clients + will break on /build. List containers (/containers/json): @@ -49,7 +62,8 @@ List containers (/containers/json): Start containers (/containers//start): -- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls +- You can now pass host-specific configuration (e.g. bind mounts) in + the POST body for start calls :doc:`docker_remote_api_v1.2` ***************************** @@ -60,14 +74,25 @@ What's new ---------- The auth configuration is now handled by the client. -The client should send it's authConfig as POST on each call of /images/(name)/push -.. http:get:: /auth is now deprecated -.. http:post:: /auth only checks the configuration but doesn't store it on the server +The client should send it's authConfig as POST on each call of +/images/(name)/push -Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any. +.. http:get:: /auth -.. http:post:: /images//delete now returns a JSON with the list of images deleted/untagged + **Deprecated.** + +.. http:post:: /auth + + Only checks the configuration but doesn't store it on the server + + Deleting an image is now improved, will only untag the image if it + has chidren and remove all the untagged parents if has any. + +.. http:post:: /images//delete + + Now returns a JSON structure with the list of images + deleted/untagged. :doc:`docker_remote_api_v1.1` @@ -82,7 +107,7 @@ What's new .. http:post:: /images/(name)/insert .. http:post:: /images/(name)/push -Uses json stream instead of HTML hijack, it looks like this: + Uses json stream instead of HTML hijack, it looks like this: .. sourcecode:: http diff --git a/docs/sources/api/docker_remote_api_v1.0.rst b/docs/sources/api/docker_remote_api_v1.0.rst index a789337093..5aa98cbe59 100644 --- a/docs/sources/api/docker_remote_api_v1.0.rst +++ b/docs/sources/api/docker_remote_api_v1.0.rst @@ -1,3 +1,8 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: + :title: Remote API v1.0 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation @@ -300,8 +305,8 @@ Start a container :statuscode 500: server error -Stop a contaier -*************** +Stop a container +**************** .. http:post:: /containers/(id)/stop diff --git a/docs/sources/api/docker_remote_api_v1.1.rst b/docs/sources/api/docker_remote_api_v1.1.rst index 3e0ef34eba..e0159ddb65 100644 --- a/docs/sources/api/docker_remote_api_v1.1.rst +++ b/docs/sources/api/docker_remote_api_v1.1.rst @@ -1,3 +1,7 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: :title: Remote API v1.1 :description: API Documentation for Docker diff --git a/docs/sources/api/docker_remote_api_v1.2.rst b/docs/sources/api/docker_remote_api_v1.2.rst index a6c2c31920..96ee6bb9bb 100644 --- a/docs/sources/api/docker_remote_api_v1.2.rst +++ b/docs/sources/api/docker_remote_api_v1.2.rst @@ -1,3 +1,8 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: + :title: Remote API v1.2 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/api/docker_remote_api_v1.3.rst index 9f33365e81..273ec2e98d 100644 --- a/docs/sources/api/docker_remote_api_v1.3.rst +++ b/docs/sources/api/docker_remote_api_v1.3.rst @@ -1,3 +1,8 @@ +.. use orphan to suppress "WARNING: document isn't included in any toctree" +.. per http://sphinx-doc.org/markup/misc.html#file-wide-metadata + +:orphan: + :title: Remote API v1.3 :description: API Documentation for Docker :keywords: API, Docker, rcli, REST, documentation diff --git a/docs/sources/api/index_api.rst b/docs/sources/api/index_api.rst index 42dc49a5d7..1d4f475caf 100644 --- a/docs/sources/api/index_api.rst +++ b/docs/sources/api/index_api.rst @@ -452,7 +452,7 @@ User Register "username": "foobar"'} :jsonparameter email: valid email address, that needs to be confirmed - :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9_]. + :jsonparameter username: min 4 character, max 30 characters, must match the regular expression [a-z0-9\_]. :jsonparameter password: min 5 characters **Example Response**: diff --git a/docs/sources/api/registry_index_spec.rst b/docs/sources/api/registry_index_spec.rst index c1854194b2..3ae39e37d9 100644 --- a/docs/sources/api/registry_index_spec.rst +++ b/docs/sources/api/registry_index_spec.rst @@ -367,7 +367,8 @@ POST /v1/users {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'} **Validation**: - - **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_]. + - **username**: min 4 character, max 30 characters, must match the regular + expression [a-z0-9\_]. - **password**: min 5 characters **Valid**: return HTTP 200 @@ -566,4 +567,4 @@ Next request:: --------------------- - 1.0 : May 6th 2013 : initial release -- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace. \ No newline at end of file +- 1.1 : June 1st 2013 : Added Delete Repository and way to handle new source namespace. diff --git a/docs/sources/index.rst b/docs/sources/index.rst index 05e69dd8e5..ba8f60c3fa 100644 --- a/docs/sources/index.rst +++ b/docs/sources/index.rst @@ -2,8 +2,6 @@ :description: An overview of the Docker Documentation :keywords: containers, lxc, concepts, explanation -.. _introduction: - Welcome ======= diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 9ea8033b98..7f370609c8 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -1,6 +1,6 @@ -:title: Dockerfile Builder -:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image. -:keywords: builder, docker, Docker Builder, automation, image creation +:title: Dockerfiles for Images +:description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image. +:keywords: builder, docker, Dockerfile, automation, image creation ================== Dockerfile Builder @@ -177,7 +177,7 @@ The copy obeys the following rules: with mode 0700, uid and gid 0. 3.8 ENTRYPOINT -------------- +-------------- ``ENTRYPOINT /bin/echo`` diff --git a/docs/sources/use/workingwithrepository.rst b/docs/sources/use/workingwithrepository.rst index 3cdbfe49d6..4a2e39aea1 100644 --- a/docs/sources/use/workingwithrepository.rst +++ b/docs/sources/use/workingwithrepository.rst @@ -119,7 +119,7 @@ your container to an image within your username namespace. Pushing a container to its repository ------------------------------------- +------------------------------------- In order to push an image to its repository you need to have committed your container to a named image (see above) From 64e74cefb746caa7f2a581149bbd523dd1ac9215 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 8 Jul 2013 11:01:16 +0300 Subject: [PATCH 07/20] add support for container ID files (a la pidfile) --- commands.go | 14 ++++++++++++++ container.go | 7 +++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 936b23fea2..42cccf6b96 100644 --- a/commands.go +++ b/commands.go @@ -1341,6 +1341,20 @@ func (cli *DockerCli) CmdRun(args ...string) error { for _, warning := range runResult.Warnings { fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } + if len(hostConfig.ContainerIDFile) > 0 { + if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil { + return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile) + } + file, err := os.Create(hostConfig.ContainerIDFile) + if err != nil { + return fmt.Errorf("failed to create the container ID file: %s", err) + } + + defer file.Close() + if _, err = file.WriteString(runResult.ID); err != nil { + return fmt.Errorf("failed to write the container ID to the file: %s", err) + } + } //start the container if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil { diff --git a/container.go b/container.go index 95c5ba0f72..4443ad52a3 100644 --- a/container.go +++ b/container.go @@ -80,7 +80,8 @@ type Config struct { } type HostConfig struct { - Binds []string + Binds []string + ContainerIDFile string } type BindMap struct { @@ -103,6 +104,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached") flTty := cmd.Bool("t", false, "Allocate a pseudo-tty") flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)") + flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file") if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") @@ -190,7 +192,8 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, Entrypoint: entrypoint, } hostConfig := &HostConfig{ - Binds: binds, + Binds: binds, + ContainerIDFile: *flContainerIDFile, } if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit { From 221ee504aa06d06eb868898cca2fcc020a861e84 Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 11 Jul 2013 23:38:43 +0300 Subject: [PATCH 08/20] docs - add cidfile flag to run docs --- docs/sources/commandline/command/run.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index 1a14a9b616..bfd35738fa 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -14,6 +14,7 @@ -a=map[]: Attach to stdin, stdout or stderr. -c=0: CPU shares (relative weight) + -cidfile="": Write the container ID to the file -d=false: Detached mode: leave the container running in the background -e=[]: Set environment variables -h="": Container host name From 2a3b91e3b66c48c6a26dbd673957a46c1afacbbe Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 12 Jul 2013 00:50:03 +0300 Subject: [PATCH 09/20] docs - add example for cidfile --- docs/sources/commandline/command/run.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index bfd35738fa..19efd85821 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -27,3 +27,13 @@ -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "host-dir" is missing, then docker creates a new volume. -volumes-from="": Mount all volumes from the given container. -entrypoint="": Overwrite the default entrypoint set by the image. + + +Examples +-------- + +.. code-block:: bash + + docker run -cidfile /tmp/docker_test.cid ubuntu echo "test" + +| This will create a container and print "test" to the console. The cidfile flag makes docker attempt to create a new file and write the container ID to it. If the file exists already, docker will return an error. Docker will close this file when docker run exits. From 25be79208a1473a65be883989ae49b7c71081a83 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 12 Jul 2013 01:11:44 +0300 Subject: [PATCH 10/20] create the cidfile before creating the container This change makes docker attempt to create the container ID file and open it before attempting to create the container. This avoids leaving a stale container behind if docker has failed to create and open the container ID file. The container ID is written to the file after the container is created. --- commands.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/commands.go b/commands.go index 42cccf6b96..7f8f9eec0b 100644 --- a/commands.go +++ b/commands.go @@ -1311,6 +1311,18 @@ func (cli *DockerCli) CmdRun(args ...string) error { return nil } + var containerIDFile *os.File + if len(hostConfig.ContainerIDFile) > 0 { + if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil { + return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile) + } + containerIDFile, err = os.Create(hostConfig.ContainerIDFile) + if err != nil { + return fmt.Errorf("failed to create the container ID file: %s", err) + } + defer containerIDFile.Close() + } + //create the container body, statusCode, err := cli.call("POST", "/containers/create", config) //if image not found try to pull it @@ -1342,16 +1354,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } if len(hostConfig.ContainerIDFile) > 0 { - if _, err := ioutil.ReadFile(hostConfig.ContainerIDFile); err == nil { - return fmt.Errorf("cid file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile) - } - file, err := os.Create(hostConfig.ContainerIDFile) - if err != nil { - return fmt.Errorf("failed to create the container ID file: %s", err) - } - - defer file.Close() - if _, err = file.WriteString(runResult.ID); err != nil { + if _, err = containerIDFile.WriteString(runResult.ID); err != nil { return fmt.Errorf("failed to write the container ID to the file: %s", err) } } From 2e3b660dd0d49dc78f4c486e952ea6db9c007d6a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 13:56:36 +0000 Subject: [PATCH 11/20] fix error in utils tests --- utils/utils_test.go | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/utils/utils_test.go b/utils/utils_test.go index 2c68be50e3..5caa809f67 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -60,9 +60,9 @@ func TestWriteBroadcaster(t *testing.T) { // Test 1: Both bufferA and bufferB should contain "foo" bufferA := &dummyWriter{} - writer.AddWriter(bufferA) + writer.AddWriter(bufferA, "") bufferB := &dummyWriter{} - writer.AddWriter(bufferB) + writer.AddWriter(bufferB, "") writer.Write([]byte("foo")) if bufferA.String() != "foo" { @@ -76,7 +76,7 @@ func TestWriteBroadcaster(t *testing.T) { // Test2: bufferA and bufferB should contain "foobar", // while bufferC should only contain "bar" bufferC := &dummyWriter{} - writer.AddWriter(bufferC) + writer.AddWriter(bufferC, "") writer.Write([]byte("bar")) if bufferA.String() != "foobar" { @@ -91,35 +91,22 @@ func TestWriteBroadcaster(t *testing.T) { t.Errorf("Buffer contains %v", bufferC.String()) } - // Test3: Test removal - writer.RemoveWriter(bufferB) - writer.Write([]byte("42")) - if bufferA.String() != "foobar42" { - t.Errorf("Buffer contains %v", bufferA.String()) - } - if bufferB.String() != "foobar" { - t.Errorf("Buffer contains %v", bufferB.String()) - } - if bufferC.String() != "bar42" { - t.Errorf("Buffer contains %v", bufferC.String()) - } - - // Test4: Test eviction on failure + // Test3: Test eviction on failure bufferA.failOnWrite = true writer.Write([]byte("fail")) - if bufferA.String() != "foobar42" { + if bufferA.String() != "foobar" { t.Errorf("Buffer contains %v", bufferA.String()) } - if bufferC.String() != "bar42fail" { + if bufferC.String() != "barfail" { t.Errorf("Buffer contains %v", bufferC.String()) } // Even though we reset the flag, no more writes should go in there bufferA.failOnWrite = false writer.Write([]byte("test")) - if bufferA.String() != "foobar42" { + if bufferA.String() != "foobar" { t.Errorf("Buffer contains %v", bufferA.String()) } - if bufferC.String() != "bar42failtest" { + if bufferC.String() != "barfailtest" { t.Errorf("Buffer contains %v", bufferC.String()) } @@ -141,7 +128,7 @@ func TestRaceWriteBroadcaster(t *testing.T) { writer := NewWriteBroadcaster() c := make(chan bool) go func() { - writer.AddWriter(devNullCloser(0)) + writer.AddWriter(devNullCloser(0), "") c <- true }() writer.Write([]byte("hello")) From ea1258852493a83754553163db1db52a72ffb8fc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 15:56:00 +0000 Subject: [PATCH 12/20] remove usage from tests --- container.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container.go b/container.go index d0a0dd7714..f18aa0fe74 100644 --- a/container.go +++ b/container.go @@ -94,6 +94,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container") if len(args) > 0 && args[0] != "--help" { cmd.SetOutput(ioutil.Discard) + cmd.Usage = nil } flHostname := cmd.String("h", "", "Container host name") From 67f1e3f5ed4d3061e07e910e28ac866b7bb13e18 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 19 Jul 2013 17:22:16 +0000 Subject: [PATCH 13/20] add container=lxc in default env --- container.go | 1 + 1 file changed, 1 insertion(+) diff --git a/container.go b/container.go index d0a0dd7714..f472b199ea 100644 --- a/container.go +++ b/container.go @@ -640,6 +640,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { params = append(params, "-e", "HOME=/", "-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "-e", "container=lxc", ) for _, elem := range container.Config.Env { From 32663bf431b64d1169509bacba36e3c90e131b44 Mon Sep 17 00:00:00 2001 From: dsissitka Date: Sat, 20 Jul 2013 21:27:55 -0400 Subject: [PATCH 14/20] Fixed a couple of minor syntax errors. --- docs/sources/contributing/devenvironment.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/contributing/devenvironment.rst b/docs/sources/contributing/devenvironment.rst index 6f5d6c1dc1..869cc43749 100644 --- a/docs/sources/contributing/devenvironment.rst +++ b/docs/sources/contributing/devenvironment.rst @@ -46,11 +46,13 @@ in a standard build environment. You can run an interactive session in the newly built container: :: + docker run -i -t docker bash To extract the binaries from the container: :: + docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build From 788935175e8500b451a844f6971ac62dd099bdfc Mon Sep 17 00:00:00 2001 From: dsissitka Date: Sun, 21 Jul 2013 18:30:51 -0400 Subject: [PATCH 15/20] Added top to the list of commands in the sidebar. --- docs/sources/commandline/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/commandline/index.rst b/docs/sources/commandline/index.rst index f1a3e2da45..a7296b27da 100644 --- a/docs/sources/commandline/index.rst +++ b/docs/sources/commandline/index.rst @@ -37,5 +37,6 @@ Contents: start stop tag + top version - wait \ No newline at end of file + wait From 1d02a7ffb63915055f5fd9bda420bd08a8679da1 Mon Sep 17 00:00:00 2001 From: David Sissitka Date: Sun, 21 Jul 2013 19:00:18 -0400 Subject: [PATCH 16/20] Updated the stop command's docs. --- commands.go | 2 +- docs/sources/commandline/command/stop.rst | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index f0e1695b3f..86385f4187 100644 --- a/commands.go +++ b/commands.go @@ -475,7 +475,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { func (cli *DockerCli) CmdStop(args ...string) error { cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container") - nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Default=10") + nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.") if err := cmd.Parse(args); err != nil { return nil } diff --git a/docs/sources/commandline/command/stop.rst b/docs/sources/commandline/command/stop.rst index 3d571563ec..6a64908eae 100644 --- a/docs/sources/commandline/command/stop.rst +++ b/docs/sources/commandline/command/stop.rst @@ -8,6 +8,8 @@ :: - Usage: docker stop [OPTIONS] NAME + Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] Stop a running container + + -t=10: Number of seconds to wait for the container to stop before killing it. From f236e62d9d288ac695129157d1753512f5cd2b0a Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Sun, 7 Jul 2013 23:24:52 -0700 Subject: [PATCH 17/20] Test pulling remote files using ADD in a buildfile. --- buildfile_test.go | 117 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 30 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index 14edbc088f..602af5061b 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -3,13 +3,17 @@ package docker import ( "fmt" "io/ioutil" + "net" + "net/http" + "net/http/httptest" + "strings" "testing" ) // mkTestContext generates a build context from the contents of the provided dockerfile. // This context is suitable for use as an argument to BuildFile.Build() func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive { - context, err := mkBuildContext(fmt.Sprintf(dockerfile, unitTestImageID), files) + context, err := mkBuildContext(dockerfile, files) if err != nil { t.Fatal(err) } @@ -22,6 +26,8 @@ type testContextTemplate struct { dockerfile string // Additional files in the context, eg [][2]string{"./passwd", "gordon"} files [][2]string + // Additional remote files to host on a local HTTP server. + remoteFiles [][2]string } // A table of all the contexts to build and test. @@ -29,27 +35,31 @@ type testContextTemplate struct { var testContexts = []testContextTemplate{ { ` -from %s +from {IMAGE} run sh -c 'echo root:testpass > /tmp/passwd' run mkdir -p /var/run/sshd run [ "$(cat /tmp/passwd)" = "root:testpass" ] run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] `, nil, + nil, }, { ` -from %s +from {IMAGE} add foo /usr/lib/bla/bar -run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ] +run [ "$(cat /usr/lib/bla/bar)" = 'hello' ] +add http://{SERVERADDR}/baz /usr/lib/baz/quux +run [ "$(cat /usr/lib/baz/quux)" = 'world!' ] `, - [][2]string{{"foo", "hello world!"}}, + [][2]string{{"foo", "hello"}}, + [][2]string{{"/baz", "world!"}}, }, { ` -from %s +from {IMAGE} add f / run [ "$(cat /f)" = "hello" ] add f /abc @@ -71,38 +81,70 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ] {"f", "hello"}, {"d/ga", "bu"}, }, + nil, }, { ` -from %s +from {IMAGE} env FOO BAR run [ "$FOO" = "BAR" ] `, nil, - }, - - { - ` -from %s -ENTRYPOINT /bin/echo -CMD Hello world -`, nil, }, { ` -from %s +from {IMAGE} +ENTRYPOINT /bin/echo +CMD Hello world +`, + nil, + nil, + }, + + { + ` +from {IMAGE} VOLUME /test CMD Hello world `, nil, + nil, }, } // FIXME: test building with 2 successive overlapping ADD commands +func constructDockerfile(template string, ip net.IP, port string) string { + serverAddr := fmt.Sprintf("%s:%s", ip, port) + replacer := strings.NewReplacer("{IMAGE}", unitTestImageID, "{SERVERADDR}", serverAddr) + return replacer.Replace(template) +} + +func mkTestingFileServer(files [][2]string) (*httptest.Server, error) { + mux := http.NewServeMux() + for _, file := range files { + name, contents := file[0], file[1] + mux.HandleFunc(name, func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(contents)) + }) + } + + // This is how httptest.NewServer sets up a net.Listener, except that our listener must accept remote + // connections (from the container). + listener, err := net.Listen("tcp", ":0") + if err != nil { + return nil, err + } + + s := httptest.NewUnstartedServer(mux) + s.Listener = listener + s.Start() + return s, nil +} + func TestBuild(t *testing.T) { for _, ctx := range testContexts { buildImage(ctx, t) @@ -121,9 +163,24 @@ func buildImage(context testContextTemplate, t *testing.T) *Image { pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } - buildfile := NewBuildFile(srv, ioutil.Discard, false) - id, err := buildfile.Build(mkTestContext(context.dockerfile, context.files, t)) + httpServer, err := mkTestingFileServer(context.remoteFiles) + if err != nil { + t.Fatal(err) + } + defer httpServer.Close() + + idx := strings.LastIndex(httpServer.URL, ":") + if idx < 0 { + t.Fatalf("could not get port from test http server address %s", httpServer.URL) + } + port := httpServer.URL[idx+1:] + + ip := runtime.networkManager.bridgeNetwork.IP + dockerfile := constructDockerfile(context.dockerfile, ip, port) + + buildfile := NewBuildFile(srv, ioutil.Discard, false) + id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err != nil { t.Fatal(err) } @@ -137,10 +194,10 @@ func buildImage(context testContextTemplate, t *testing.T) *Image { func TestVolume(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} volume /test cmd Hello world - `, nil}, t) + `, nil, nil}, t) if len(img.Config.Volumes) == 0 { t.Fail() @@ -154,9 +211,9 @@ func TestVolume(t *testing.T) { func TestBuildMaintainer(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} maintainer dockerio - `, nil}, t) + `, nil, nil}, t) if img.Author != "dockerio" { t.Fail() @@ -165,10 +222,10 @@ func TestBuildMaintainer(t *testing.T) { func TestBuildEnv(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} env port 4243 `, - nil}, t) + nil, nil}, t) if img.Config.Env[0] != "port=4243" { t.Fail() @@ -177,10 +234,10 @@ func TestBuildEnv(t *testing.T) { func TestBuildCmd(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} cmd ["/bin/echo", "Hello World"] `, - nil}, t) + nil, nil}, t) if img.Config.Cmd[0] != "/bin/echo" { t.Log(img.Config.Cmd[0]) @@ -194,10 +251,10 @@ func TestBuildCmd(t *testing.T) { func TestBuildExpose(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} expose 4243 `, - nil}, t) + nil, nil}, t) if img.Config.PortSpecs[0] != "4243" { t.Fail() @@ -206,10 +263,10 @@ func TestBuildExpose(t *testing.T) { func TestBuildEntrypoint(t *testing.T) { img := buildImage(testContextTemplate{` - from %s + from {IMAGE} entrypoint ["/bin/echo"] `, - nil}, t) + nil, nil}, t) if img.Config.Entrypoint[0] != "/bin/echo" { } From 2b0ebf5d32c65276ce50fce168f32483ffb9c311 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 8 Jul 2013 00:43:22 -0700 Subject: [PATCH 18/20] Buildfile: for ADD command, determine filename from URL. This is used if the destination is a directory. This makes the URL download behavior more closely match file copying. Fixes #1142. --- buildfile.go | 21 ++++++++++++++++++++- buildfile_test.go | 16 ++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 7ade058c69..75ebdd7a7c 100644 --- a/buildfile.go +++ b/buildfile.go @@ -7,6 +7,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" + "net/url" "os" "path" "reflect" @@ -201,6 +202,24 @@ func (b *buildFile) addRemote(container *Container, orig, dest string) error { } defer file.Body.Close() + // If the destination is a directory, figure out the filename. + if strings.HasSuffix(dest, "/") { + u, err := url.Parse(orig) + if err != nil { + return err + } + path := u.Path + if strings.HasSuffix(path, "/") { + path = path[:len(path)-1] + } + parts := strings.Split(path, "/") + filename := parts[len(parts)-1] + if filename == "" { + return fmt.Errorf("cannot determine filename from url: %s", u) + } + dest = dest + filename + } + return container.Inject(file.Body, dest) } @@ -208,7 +227,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error { origPath := path.Join(b.context, orig) destPath := path.Join(container.RootfsPath(), dest) // Preserve the trailing '/' - if dest[len(dest)-1] == '/' { + if strings.HasSuffix(dest, "/") { destPath = destPath + "/" } fi, err := os.Stat(origPath) diff --git a/buildfile_test.go b/buildfile_test.go index 602af5061b..b7eca52336 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -84,6 +84,22 @@ run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ] nil, }, + { + ` +from {IMAGE} +add http://{SERVERADDR}/x /a/b/c +run [ "$(cat /a/b/c)" = "hello" ] +add http://{SERVERADDR}/x?foo=bar / +run [ "$(cat /x)" = "hello" ] +add http://{SERVERADDR}/x /d/ +run [ "$(cat /d/x)" = "hello" ] +add http://{SERVERADDR} /e +run [ "$(cat /e)" = "blah" ] +`, + nil, + [][2]string{{"/x", "hello"}, {"/", "blah"}}, + }, + { ` from {IMAGE} From 416fdaa3d5d7b84f7e36fdfcff7153e3a38262c9 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 8 Jul 2013 01:14:01 -0700 Subject: [PATCH 19/20] Remove some trailing whitespace. --- docs/sources/use/builder.rst | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 7f370609c8..ea98541dbf 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -30,7 +30,7 @@ build succeeds: ``docker build -t shykes/myapp .`` -Docker will run your steps one-by-one, committing the result if necessary, +Docker will run your steps one-by-one, committing the result if necessary, before finally outputting the ID of your new image. 2. Format @@ -43,7 +43,7 @@ The Dockerfile format is quite simple: # Comment INSTRUCTION arguments -The Instruction is not case-sensitive, however convention is for them to be +The Instruction is not case-sensitive, however convention is for them to be UPPERCASE in order to distinguish them from arguments more easily. Docker evaluates the instructions in a Dockerfile in order. **The first @@ -106,7 +106,7 @@ The ``CMD`` instruction sets the command to be executed when running the image. This is functionally equivalent to running ``docker commit -run '{"Cmd": }'`` outside the builder. -.. note:: +.. note:: Don't confuse `RUN` with `CMD`. `RUN` actually runs a command and commits the result; `CMD` does not execute anything at build time, but specifies the intended command for the image. @@ -131,7 +131,7 @@ value ````. This value will be passed to all future ``RUN`` instructions. This is functionally equivalent to prefixing the command with ``=`` -.. note:: +.. note:: The environment variables will persist when a container is run from the resulting image. @@ -158,10 +158,10 @@ The copy obeys the following rules: (identity, gzip, bzip2 or xz), it is unpacked as a directory. When a directory is copied or unpacked, it has the same behavior as - ``tar -x``: the result is the union of + ``tar -x``: the result is the union of 1. whatever existed at the destination path and - 2. the contents of the source tree, + 2. the contents of the source tree, with conflicts resolved in favor of 2) on a file-by-file basis. @@ -203,14 +203,14 @@ container created from the image. # Nginx # # VERSION 0.0.1 - + FROM ubuntu MAINTAINER Guillaume J. Charmes "guillaume@dotcloud.com" - + # make sure the package repository is up to date RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update - + RUN apt-get install -y inotify-tools nginx apache2 openssh-server .. code-block:: bash @@ -218,12 +218,12 @@ container created from the image. # Firefox over VNC # # VERSION 0.3 - + FROM ubuntu # make sure the package repository is up to date RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update - + # Install vnc, xvfb in order to create a 'fake' display and firefox RUN apt-get install -y x11vnc xvfb firefox RUN mkdir /.vnc @@ -231,7 +231,7 @@ container created from the image. RUN x11vnc -storepasswd 1234 ~/.vnc/passwd # Autostart firefox (might not be the best way, but it does the trick) RUN bash -c 'echo "firefox" >> /.bashrc' - + EXPOSE 5900 CMD ["x11vnc", "-forever", "-usepw", "-create"] From c383d598809daec6bccd60bd211c10f6a4ef60b0 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Mon, 8 Jul 2013 01:29:13 -0700 Subject: [PATCH 20/20] Update ADD documentation to specify new behavior. --- docs/sources/use/builder.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index ea98541dbf..302527a740 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -152,6 +152,14 @@ destination container. The copy obeys the following rules: +* If ```` is a URL and ```` does not end with a trailing slash, + then a file is downloaded from the URL and copied to ````. +* If ```` is a URL and ```` does end with a trailing slash, + then the filename is inferred from the URL and the file is downloaded to + ``/``. For instance, ``ADD http://example.com/foobar /`` + would create the file ``/foobar``. The URL must have a nontrivial path + so that an appropriate filename can be discovered in this case + (``http://example.com`` will not work). * If ```` is a directory, the entire directory is copied, including filesystem metadata. * If ````` is a tar archive in a recognized compression format