From 27d67773768222ffc57f124b38c767f36a575f96 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 26 Jun 2013 12:50:20 -0700 Subject: [PATCH 01/59] Display containers logs in case of build failure --- buildfile.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/buildfile.go b/buildfile.go index 9cbaac4e7..355a99c6c 100644 --- a/buildfile.go +++ b/buildfile.go @@ -29,6 +29,7 @@ type buildFile struct { config *Config context string + lastContainer *Container tmpContainers map[string]struct{} tmpImages map[string]struct{} @@ -225,6 +226,7 @@ func (b *buildFile) CmdAdd(args string) error { return err } b.tmpContainers[container.ID] = struct{}{} + b.lastContainer = container if err := container.EnsureMounted(); err != nil { return err @@ -260,6 +262,7 @@ func (b *buildFile) run() (string, error) { return "", err } b.tmpContainers[c.ID] = struct{}{} + b.lastContainer = c fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID)) //start the container @@ -301,6 +304,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { return err } b.tmpContainers[container.ID] = struct{}{} + b.lastContainer = container fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID)) id = container.ID if err := container.EnsureMounted(); err != nil { @@ -328,6 +332,29 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { } func (b *buildFile) Build(context io.Reader) (string, error) { + defer func() { + // If we have an error and a container, the display the logs + if b.lastContainer != nil { + fmt.Fprintf(b.out, "Logs from last container (%s):\n", b.lastContainer.ShortID()) + + cLog, err := b.lastContainer.ReadLog("stdout") + if err != nil { + utils.Debugf("Error reading logs (stdout): %s", err) + } + if _, err := io.Copy(b.out, cLog); err != nil { + utils.Debugf("Error streaming logs (stdout): %s", err) + } + cLog, err = b.lastContainer.ReadLog("stderr") + if err != nil { + utils.Debugf("Error reading logs (stderr): %s", err) + } + if _, err := io.Copy(b.out, cLog); err != nil { + utils.Debugf("Error streaming logs (stderr): %s", err) + } + fmt.Fprintf(b.out, "End of logs for %s\n", b.lastContainer.ShortID()) + } + }() + // FIXME: @creack any reason for using /tmp instead of ""? // FIXME: @creack "name" is a terrible variable name name, err := ioutil.TempDir("/tmp", "docker-build") @@ -380,6 +407,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) { return "", ret.(error) } + b.lastContainer = nil fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image)) } if b.image != "" { From 2a20e852032339b4c91a01748119701c7ada234d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 27 Jun 2013 11:10:19 -0700 Subject: [PATCH 02/59] Improve last log output --- buildfile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildfile.go b/buildfile.go index 355a99c6c..e826203c8 100644 --- a/buildfile.go +++ b/buildfile.go @@ -335,7 +335,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) { defer func() { // If we have an error and a container, the display the logs if b.lastContainer != nil { - fmt.Fprintf(b.out, "Logs from last container (%s):\n", b.lastContainer.ShortID()) + fmt.Fprintf(b.out, "******** Logs from last container (%s) *******\n", b.lastContainer.ShortID()) cLog, err := b.lastContainer.ReadLog("stdout") if err != nil { @@ -351,7 +351,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) { if _, err := io.Copy(b.out, cLog); err != nil { utils.Debugf("Error streaming logs (stderr): %s", err) } - fmt.Fprintf(b.out, "End of logs for %s\n", b.lastContainer.ShortID()) + fmt.Fprintf(b.out, "************* End of logs for %s *************\n", b.lastContainer.ShortID()) } }() From 800d9006883228365a375800254871397cb1a011 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 27 Jun 2013 15:25:31 -0700 Subject: [PATCH 03/59] Ignore stderr while doing tests --- commands.go | 2 +- commands_test.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 3ce8c8eb9..dda7f13d4 100644 --- a/commands.go +++ b/commands.go @@ -1270,7 +1270,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } for _, warning := range runResult.Warnings { - fmt.Fprintln(cli.err, "WARNING: ", warning) + fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } //start the container diff --git a/commands_test.go b/commands_test.go index 87c4c02a5..483c5e70c 100644 --- a/commands_test.go +++ b/commands_test.go @@ -132,11 +132,12 @@ func TestImages(t *testing.T) { } */ + // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname func TestRunHostname(t *testing.T) { stdout, stdoutPipe := io.Pipe() - cli := NewDockerCli(nil, stdoutPipe, nil, testDaemonProto, testDaemonAddr) + cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) defer cleanup(globalRuntime) c := make(chan struct{}) @@ -329,7 +330,7 @@ func TestRunAttachStdin(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() - cli := NewDockerCli(stdin, stdoutPipe, nil, testDaemonProto, testDaemonAddr) + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) defer cleanup(globalRuntime) ch := make(chan struct{}) From 9bfec5a5389207cba4cdbdd7eafc692c65278ce0 Mon Sep 17 00:00:00 2001 From: Tobias Schwab Date: Fri, 28 Jun 2013 15:22:01 +0200 Subject: [PATCH 04/59] do not merge hostname from image --- utils.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/utils.go b/utils.go index 5a9d02c49..50615aa2c 100644 --- a/utils.go +++ b/utils.go @@ -49,9 +49,6 @@ func CompareConfig(a, b *Config) bool { } func MergeConfig(userConf, imageConf *Config) { - if userConf.Hostname == "" { - userConf.Hostname = imageConf.Hostname - } if userConf.User == "" { userConf.User = imageConf.User } From 1cf9c80e976fb60b4d5d489cd1c4c9959bcc4f7f Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Tue, 2 Jul 2013 15:46:32 -0700 Subject: [PATCH 05/59] Mutex style change. For structs protected by a single mutex, embed the mutex for more concise usage. Also use a sync.Mutex directly, rather than a pointer, to avoid the need for initialization (because a Mutex's zero-value is valid and ready to be used). --- buildfile_test.go | 2 -- container.go | 12 ++++++------ network.go | 10 +++++----- runtime.go | 3 --- runtime_test.go | 1 - server.go | 7 +++---- state.go | 14 +------------- utils/utils.go | 30 +++++++++++++++--------------- 8 files changed, 30 insertions(+), 49 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index 8dc041062..b1f0b88e7 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -3,7 +3,6 @@ package docker import ( "fmt" "io/ioutil" - "sync" "testing" ) @@ -105,7 +104,6 @@ func TestBuild(t *testing.T) { srv := &Server{ runtime: runtime, - lock: &sync.Mutex{}, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } diff --git a/container.go b/container.go index 508f39c0e..12afa7c66 100644 --- a/container.go +++ b/container.go @@ -466,8 +466,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s } func (container *Container) Start(hostConfig *HostConfig) error { - container.State.lock() - defer container.State.unlock() + container.State.Lock() + defer container.State.Unlock() if container.State.Running { return fmt.Errorf("The container %s is already running.", container.ID) @@ -821,8 +821,8 @@ func (container *Container) kill() error { } func (container *Container) Kill() error { - container.State.lock() - defer container.State.unlock() + container.State.Lock() + defer container.State.Unlock() if !container.State.Running { return nil } @@ -830,8 +830,8 @@ func (container *Container) Kill() error { } func (container *Container) Stop(seconds int) error { - container.State.lock() - defer container.State.unlock() + container.State.Lock() + defer container.State.Unlock() if !container.State.Running { return nil } diff --git a/network.go b/network.go index 37037dd14..dd79e6059 100644 --- a/network.go +++ b/network.go @@ -301,9 +301,9 @@ func newPortMapper() (*PortMapper, error) { // Port allocator: Atomatically allocate and release networking ports type PortAllocator struct { + sync.Mutex inUse map[int]struct{} fountain chan (int) - lock sync.Mutex } func (alloc *PortAllocator) runFountain() { @@ -317,9 +317,9 @@ func (alloc *PortAllocator) runFountain() { // FIXME: Release can no longer fail, change its prototype to reflect that. func (alloc *PortAllocator) Release(port int) error { utils.Debugf("Releasing %d", port) - alloc.lock.Lock() + alloc.Lock() delete(alloc.inUse, port) - alloc.lock.Unlock() + alloc.Unlock() return nil } @@ -334,8 +334,8 @@ func (alloc *PortAllocator) Acquire(port int) (int, error) { } return -1, fmt.Errorf("Port generator ended unexpectedly") } - alloc.lock.Lock() - defer alloc.lock.Unlock() + alloc.Lock() + defer alloc.Unlock() if _, inUse := alloc.inUse[port]; inUse { return -1, fmt.Errorf("Port already in use: %d", port) } diff --git a/runtime.go b/runtime.go index 06b1f8e1b..5b0f7b2b2 100644 --- a/runtime.go +++ b/runtime.go @@ -108,9 +108,6 @@ func (runtime *Runtime) Register(container *Container) error { // init the wait lock container.waitLock = make(chan struct{}) - // Even if not running, we init the lock (prevents races in start/stop/kill) - container.State.initLock() - container.runtime = runtime // Attach to stdout and stderr diff --git a/runtime_test.go b/runtime_test.go index c367ecd4c..5c2639471 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -89,7 +89,6 @@ func init() { srv := &Server{ runtime: runtime, enableCors: false, - lock: &sync.Mutex{}, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } diff --git a/server.go b/server.go index cedd06ad7..7e05b313d 100644 --- a/server.go +++ b/server.go @@ -450,8 +450,8 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re } func (srv *Server) poolAdd(kind, key string) error { - srv.lock.Lock() - defer srv.lock.Unlock() + srv.Lock() + defer srv.Unlock() if _, exists := srv.pullingPool[key]; exists { return fmt.Errorf("%s %s is already in progress", key, kind) @@ -1119,7 +1119,6 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( srv := &Server{ runtime: runtime, enableCors: enableCors, - lock: &sync.Mutex{}, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } @@ -1128,9 +1127,9 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) ( } type Server struct { + sync.Mutex runtime *Runtime enableCors bool - lock *sync.Mutex pullingPool map[string]struct{} pushingPool map[string]struct{} } diff --git a/state.go b/state.go index a972e376a..117659bf5 100644 --- a/state.go +++ b/state.go @@ -8,11 +8,11 @@ import ( ) type State struct { + sync.Mutex Running bool Pid int ExitCode int StartedAt time.Time - l *sync.Mutex Ghost bool } @@ -39,15 +39,3 @@ func (s *State) setStopped(exitCode int) { s.Pid = 0 s.ExitCode = exitCode } - -func (s *State) initLock() { - s.l = &sync.Mutex{} -} - -func (s *State) lock() { - s.l.Lock() -} - -func (s *State) unlock() { - s.l.Unlock() -} diff --git a/utils/utils.go b/utils/utils.go index 2f2a52867..52f8eefb9 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -188,10 +188,10 @@ func NopWriteCloser(w io.Writer) io.WriteCloser { } type bufReader struct { + sync.Mutex buf *bytes.Buffer reader io.Reader err error - l sync.Mutex wait sync.Cond } @@ -200,7 +200,7 @@ func NewBufReader(r io.Reader) *bufReader { buf: &bytes.Buffer{}, reader: r, } - reader.wait.L = &reader.l + reader.wait.L = &reader.Mutex go reader.drain() return reader } @@ -209,14 +209,14 @@ func (r *bufReader) drain() { buf := make([]byte, 1024) for { n, err := r.reader.Read(buf) - r.l.Lock() + r.Lock() if err != nil { r.err = err } else { r.buf.Write(buf[0:n]) } r.wait.Signal() - r.l.Unlock() + r.Unlock() if err != nil { break } @@ -224,8 +224,8 @@ func (r *bufReader) drain() { } func (r *bufReader) Read(p []byte) (n int, err error) { - r.l.Lock() - defer r.l.Unlock() + r.Lock() + defer r.Unlock() for { n, err = r.buf.Read(p) if n > 0 { @@ -247,27 +247,27 @@ func (r *bufReader) Close() error { } type WriteBroadcaster struct { - mu sync.Mutex + sync.Mutex writers map[io.WriteCloser]struct{} } func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) { - w.mu.Lock() + w.Lock() w.writers[writer] = struct{}{} - w.mu.Unlock() + 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.mu.Lock() + w.Lock() delete(w.writers, writer) - w.mu.Unlock() + w.Unlock() } func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { - w.mu.Lock() - defer w.mu.Unlock() + w.Lock() + defer w.Unlock() for writer := range w.writers { if n, err := writer.Write(p); err != nil || n != len(p) { // On error, evict the writer @@ -278,8 +278,8 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) { } func (w *WriteBroadcaster) CloseWriters() error { - w.mu.Lock() - defer w.mu.Unlock() + w.Lock() + defer w.Unlock() for writer := range w.writers { writer.Close() } From dd1b9e38e95dc719a7aeadbdee67a5f3a4873dec Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Tue, 2 Jul 2013 14:47:43 -0700 Subject: [PATCH 06/59] Typo correction: Excepted -> Expected' --- api_test.go | 24 ++++++++++++------------ server_test.go | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/api_test.go b/api_test.go index 487394a54..546a3c092 100644 --- a/api_test.go +++ b/api_test.go @@ -99,7 +99,7 @@ func TestGetVersion(t *testing.T) { t.Fatal(err) } if v.Version != VERSION { - t.Errorf("Excepted version %s, %s found", VERSION, v.Version) + t.Errorf("Expected version %s, %s found", VERSION, v.Version) } } @@ -129,7 +129,7 @@ func TestGetInfo(t *testing.T) { t.Fatal(err) } if infos.Images != len(initialImages) { - t.Errorf("Excepted images: %d, %d found", len(initialImages), infos.Images) + t.Errorf("Expected images: %d, %d found", len(initialImages), infos.Images) } } @@ -166,7 +166,7 @@ func TestGetImagesJSON(t *testing.T) { } if len(images) != len(initialImages) { - t.Errorf("Excepted %d image, %d found", len(initialImages), len(images)) + t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) } found := false @@ -177,7 +177,7 @@ func TestGetImagesJSON(t *testing.T) { } } if !found { - t.Errorf("Excepted image %s, %+v found", unitTestImageName, images) + t.Errorf("Expected image %s, %+v found", unitTestImageName, images) } r2 := httptest.NewRecorder() @@ -204,7 +204,7 @@ func TestGetImagesJSON(t *testing.T) { } if len(images2) != len(initialImages) { - t.Errorf("Excepted %d image, %d found", len(initialImages), len(images2)) + t.Errorf("Expected %d image, %d found", len(initialImages), len(images2)) } found = false @@ -236,7 +236,7 @@ func TestGetImagesJSON(t *testing.T) { } if len(images3) != 0 { - t.Errorf("Excepted 0 image, %d found", len(images3)) + t.Errorf("Expected 0 image, %d found", len(images3)) } r4 := httptest.NewRecorder() @@ -282,7 +282,7 @@ func TestGetImagesViz(t *testing.T) { t.Fatal(err) } if line != "digraph docker {\n" { - t.Errorf("Excepted digraph docker {\n, %s found", line) + t.Errorf("Expected digraph docker {\n, %s found", line) } } @@ -313,7 +313,7 @@ func TestGetImagesSearch(t *testing.T) { t.Fatal(err) } if len(results) < 2 { - t.Errorf("Excepted at least 2 lines, %d found", len(results)) + t.Errorf("Expected at least 2 lines, %d found", len(results)) } } @@ -337,7 +337,7 @@ func TestGetImagesHistory(t *testing.T) { t.Fatal(err) } if len(history) != 1 { - t.Errorf("Excepted 1 line, %d found", len(history)) + t.Errorf("Expected 1 line, %d found", len(history)) } } @@ -396,7 +396,7 @@ func TestGetContainersJSON(t *testing.T) { t.Fatal(err) } if len(containers) != 1 { - t.Fatalf("Excepted %d container, %d found", 1, len(containers)) + t.Fatalf("Expected %d container, %d found", 1, len(containers)) } if containers[0].ID != container.ID { t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID) @@ -1356,7 +1356,7 @@ func TestDeleteImages(t *testing.T) { } if len(images) != len(initialImages)+1 { - t.Errorf("Excepted %d images, %d found", len(initialImages)+1, len(images)) + t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images)) } req, err := http.NewRequest("DELETE", "/images/test:test", nil) @@ -1385,7 +1385,7 @@ func TestDeleteImages(t *testing.T) { } if len(images) != len(initialImages) { - t.Errorf("Excepted %d image, %d found", len(initialImages), len(images)) + t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) } /* if c := runtime.Get(container.Id); c != nil { diff --git a/server_test.go b/server_test.go index 254a4a0c9..cf3e3f0bc 100644 --- a/server_test.go +++ b/server_test.go @@ -31,7 +31,7 @@ func TestContainerTagImageDelete(t *testing.T) { } if len(images) != len(initialImages)+2 { - t.Errorf("Excepted %d images, %d found", len(initialImages)+2, len(images)) + t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images)) } if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil { @@ -44,7 +44,7 @@ func TestContainerTagImageDelete(t *testing.T) { } if len(images) != len(initialImages)+1 { - t.Errorf("Excepted %d images, %d found", len(initialImages)+1, len(images)) + t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images)) } if _, err := srv.ImageDelete("utest:tag1", true); err != nil { @@ -57,7 +57,7 @@ func TestContainerTagImageDelete(t *testing.T) { } if len(images) != len(initialImages) { - t.Errorf("Excepted %d image, %d found", len(initialImages), len(images)) + t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) } } From e93afcdd2bd9578f98508aaad11b695dae29726e Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Tue, 2 Jul 2013 14:47:58 -0700 Subject: [PATCH 07/59] Use fmt.Errorf when appropriate. --- archive.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/archive.go b/archive.go index 357fdd9a7..c9a650cb8 100644 --- a/archive.go +++ b/archive.go @@ -4,7 +4,6 @@ import ( "archive/tar" "bufio" "bytes" - "errors" "fmt" "github.com/dotcloud/docker/utils" "io" @@ -251,7 +250,7 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) { } errText := <-errChan if err := cmd.Wait(); err != nil { - pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText))) + pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errText)) } else { pipeW.Close() } From da3962266a4103f2dbf019180585d206fb9e25ff Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Tue, 2 Jul 2013 14:48:11 -0700 Subject: [PATCH 08/59] Gofmt -s (simplify) --- docker/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker.go b/docker/docker.go index c508d8905..fb7c46536 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -37,7 +37,7 @@ func main() { flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() if len(flHosts) > 1 { - flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage + flHosts = flHosts[1:] //trick to display a nice defaul value in the usage } for i, flHost := range flHosts { flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost) From 27ee261e6048c6fa0334bcce2116610dcd04aaed Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Tue, 2 Jul 2013 14:55:20 -0700 Subject: [PATCH 09/59] Simplify the NopWriter code. --- utils/utils.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 52f8eefb9..eee6685c8 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -170,10 +170,9 @@ func SelfPath() string { return path } -type NopWriter struct { -} +type NopWriter struct{} -func (w *NopWriter) Write(buf []byte) (int, error) { +func (*NopWriter) Write(buf []byte) (int, error) { return len(buf), nil } From 19121c16d9514b391ce8f15aeff3c47c1ea5a841 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Tue, 2 Jul 2013 15:27:22 -0700 Subject: [PATCH 10/59] Implement several golint suggestions, including: * Removing type declarations where they're inferred * Changing Url -> URL, Id -> ID in names * Fixing snake-case names --- api_test.go | 2 +- buildfile_test.go | 4 ++-- commands.go | 2 +- commands_test.go | 4 ++-- container.go | 4 ++-- registry/registry.go | 26 +++++++++++++------------- runtime_test.go | 8 ++++---- server.go | 36 ++++++++++++++++++------------------ tags.go | 6 +++--- tags_test.go | 4 ++-- 10 files changed, 48 insertions(+), 48 deletions(-) diff --git a/api_test.go b/api_test.go index 546a3c092..f82782643 100644 --- a/api_test.go +++ b/api_test.go @@ -359,7 +359,7 @@ func TestGetImagesByName(t *testing.T) { if err := json.Unmarshal(r.Body.Bytes(), img); err != nil { t.Fatal(err) } - if img.ID != unitTestImageId { + if img.ID != unitTestImageID { t.Errorf("Error inspecting image") } } diff --git a/buildfile_test.go b/buildfile_test.go index b1f0b88e7..b7cc7be8e 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -9,7 +9,7 @@ import ( // 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(fmt.Sprintf(dockerfile, unitTestImageID), files) if err != nil { t.Fatal(err) } @@ -26,7 +26,7 @@ type testContextTemplate struct { // A table of all the contexts to build and test. // A new docker runtime will be created and torn down for each context. -var testContexts []testContextTemplate = []testContextTemplate{ +var testContexts = []testContextTemplate{ { ` from %s diff --git a/commands.go b/commands.go index 6e1e5e88c..bee1058ae 100644 --- a/commands.go +++ b/commands.go @@ -1565,7 +1565,7 @@ func Subcmd(name, signature, description string) *flag.FlagSet { func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { var ( - isTerminal bool = false + isTerminal = false terminalFd uintptr ) diff --git a/commands_test.go b/commands_test.go index 87c4c02a5..31cd014e3 100644 --- a/commands_test.go +++ b/commands_test.go @@ -142,7 +142,7 @@ func TestRunHostname(t *testing.T) { c := make(chan struct{}) go func() { defer close(c) - if err := cli.CmdRun("-h", "foobar", unitTestImageId, "hostname"); err != nil { + if err := cli.CmdRun("-h", "foobar", unitTestImageID, "hostname"); err != nil { t.Fatal(err) } }() @@ -335,7 +335,7 @@ func TestRunAttachStdin(t *testing.T) { ch := make(chan struct{}) go func() { defer close(ch) - cli.CmdRun("-i", "-a", "stdin", unitTestImageId, "sh", "-c", "echo hello && cat") + cli.CmdRun("-i", "-a", "stdin", unitTestImageID, "sh", "-c", "echo hello && cat") }() // Send input to the command, close stdin diff --git a/container.go b/container.go index 12afa7c66..52a0517af 100644 --- a/container.go +++ b/container.go @@ -494,7 +494,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { // Create the requested bind mounts binds := make(map[string]BindMap) // Define illegal container destinations - illegal_dsts := []string{"/", "."} + illegalDsts := []string{"/", "."} for _, bind := range hostConfig.Binds { // FIXME: factorize bind parsing in parseBind @@ -513,7 +513,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } // Bail if trying to mount to an illegal destination - for _, illegal := range illegal_dsts { + for _, illegal := range illegalDsts { if dst == illegal { return fmt.Errorf("Illegal bind destination: %s", dst) } diff --git a/registry/registry.go b/registry/registry.go index 0853a68e9..584e38249 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -18,7 +18,7 @@ import ( var ErrAlreadyExists = errors.New("Image already exists") -func UrlScheme() string { +func URLScheme() string { u, err := url.Parse(auth.IndexServerAddress()) if err != nil { return "https" @@ -35,8 +35,8 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { // Retrieve the history of a given image from the Registry. // Return a list of the parent's json (requested image included) -func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]string, error) { - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/ancestry", nil) +func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) { + req, err := http.NewRequest("GET", registry+"/images/"+imgID+"/ancestry", nil) if err != nil { return nil, err } @@ -44,7 +44,7 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s res, err := r.client.Do(req) if err != nil || res.StatusCode != 200 { if res != nil { - return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgId) + return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID) } return nil, err } @@ -64,10 +64,10 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s } // Check if an image exists in the Registry -func (r *Registry) LookupRemoteImage(imgId, registry string, token []string) bool { +func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) bool { rt := &http.Transport{Proxy: http.ProxyFromEnvironment} - req, err := http.NewRequest("GET", registry+"/v1/images/"+imgId+"/json", nil) + req, err := http.NewRequest("GET", registry+"/v1/images/"+imgID+"/json", nil) if err != nil { return false } @@ -114,9 +114,9 @@ func (r *Registry) getImagesInRepository(repository string, authConfig *auth.Aut } // Retrieve an image from the Registry. -func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, int, error) { +func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) { // Get the JSON - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil) + req, err := http.NewRequest("GET", registry+"/images/"+imgID+"/json", nil) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) } @@ -142,8 +142,8 @@ func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([ return jsonString, imageSize, nil } -func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, error) { - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil) +func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (io.ReadCloser, error) { + req, err := http.NewRequest("GET", registry+"/images/"+imgID+"/layer", nil) if err != nil { return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } @@ -164,7 +164,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ for _, host := range registries { endpoint := fmt.Sprintf("%s/v1/repositories/%s/tags", host, repository) if !(strings.HasPrefix(endpoint, "http://") || strings.HasPrefix(endpoint, "https://")) { - endpoint = fmt.Sprintf("%s://%s", UrlScheme(), endpoint) + endpoint = fmt.Sprintf("%s://%s", URLScheme(), endpoint) } req, err := r.opaqueRequest("GET", endpoint, nil) if err != nil { @@ -295,9 +295,9 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return nil } -func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registry string, token []string) error { +func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) error { registry = registry + "/v1" - req, err := http.NewRequest("PUT", registry+"/images/"+imgId+"/layer", layer) + req, err := http.NewRequest("PUT", registry+"/images/"+imgID+"/layer", layer) if err != nil { return err } diff --git a/runtime_test.go b/runtime_test.go index 5c2639471..0791b2d58 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -18,7 +18,7 @@ import ( const ( unitTestImageName = "docker-unit-tests" - unitTestImageId = "e9aa60c60128cad1" + unitTestImageID = "e9aa60c60128cad1" unitTestStoreBase = "/var/lib/docker/unit-tests" testDaemonAddr = "127.0.0.1:4270" testDaemonProto = "tcp" @@ -49,7 +49,7 @@ func cleanup(runtime *Runtime) error { return err } for _, image := range images { - if image.ID != unitTestImageId { + if image.ID != unitTestImageID { runtime.graph.Delete(image.ID) } } @@ -135,11 +135,11 @@ func GetTestImage(runtime *Runtime) *Image { panic(err) } for i := range imgs { - if imgs[i].ID == unitTestImageId { + if imgs[i].ID == unitTestImageID { return imgs[i] } } - panic(fmt.Errorf("Test image %v not found", unitTestImageId)) + panic(fmt.Errorf("Test image %v not found", unitTestImageID)) } func TestRuntimeCreate(t *testing.T) { diff --git a/server.go b/server.go index 7ac315f18..4e30ad6c5 100644 --- a/server.go +++ b/server.go @@ -315,8 +315,8 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error { return nil } -func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, sf *utils.StreamFormatter) error { - history, err := r.GetRemoteHistory(imgId, endpoint, token) +func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error { + history, err := r.GetRemoteHistory(imgID, endpoint, token) if err != nil { return err } @@ -421,7 +421,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re success := false for _, ep := range repoData.Endpoints { if !(strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://")) { - ep = fmt.Sprintf("%s://%s", registry.UrlScheme(), ep) + ep = fmt.Sprintf("%s://%s", registry.URLScheme(), ep) } if err := srv.pullImage(r, out, img.ID, ep+"/v1", repoData.Tokens, sf); err != nil { out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err)) @@ -516,20 +516,20 @@ func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *util // - Check if the archive exists, if it does not, ask the registry // - If the archive does exists, process the checksum from it // - If the archive does not exists and not found on registry, process checksum from layer -func (srv *Server) getChecksum(imageId string) (string, error) { +func (srv *Server) getChecksum(imageID string) (string, error) { // FIXME: Use in-memory map instead of reading the file each time if sums, err := srv.runtime.graph.getStoredChecksums(); err != nil { return "", err - } else if checksum, exists := sums[imageId]; exists { + } else if checksum, exists := sums[imageID]; exists { return checksum, nil } - img, err := srv.runtime.graph.Get(imageId) + img, err := srv.runtime.graph.Get(imageID) if err != nil { return "", err } - if _, err := os.Stat(layerArchivePath(srv.runtime.graph.imageRoot(imageId))); err != nil { + if _, err := os.Stat(layerArchivePath(srv.runtime.graph.imageRoot(imageID))); err != nil { if os.IsNotExist(err) { // TODO: Ask the registry for the checksum // As the archive is not there, it is supposed to come from a pull. @@ -618,7 +618,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name, reg for _, ep := range repoData.Endpoints { if !(strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://")) { - ep = fmt.Sprintf("%s://%s", registry.UrlScheme(), ep) + ep = fmt.Sprintf("%s://%s", registry.URLScheme(), ep) } out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo))) // For each image within the repo, push them @@ -650,21 +650,21 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name, reg return nil } -func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string, sf *utils.StreamFormatter) error { +func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) - jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json")) + jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgID, "json")) if err != nil { - return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgId, err) + return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgID, err) } - out.Write(sf.FormatStatus("Pushing %s", imgId)) + out.Write(sf.FormatStatus("Pushing %s", imgID)) // Make sure we have the image's checksum - checksum, err := srv.getChecksum(imgId) + checksum, err := srv.getChecksum(imgID) if err != nil { return err } imgData := ®istry.ImgData{ - ID: imgId, + ID: imgID, Checksum: checksum, } @@ -680,11 +680,11 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, // Retrieve the tarball to be sent var layerData *TempArchive // If the archive exists, use it - file, err := os.Open(layerArchivePath(srv.runtime.graph.imageRoot(imgId))) + file, err := os.Open(layerArchivePath(srv.runtime.graph.imageRoot(imgID))) if err != nil { if os.IsNotExist(err) { // If the archive does not exist, create one from the layer - layerData, err = srv.runtime.graph.TempLayerArchive(imgId, Xz, out) + layerData, err = srv.runtime.graph.TempLayerArchive(imgID, Xz, out) if err != nil { return fmt.Errorf("Failed to generate layer archive: %s", err) } @@ -963,7 +963,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { return srv.deleteImage(img, name, tag) } -func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) { +func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) { // Retrieve all images images, err := srv.runtime.graph.All() @@ -981,7 +981,7 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) } // Loop on the children of the given image and check the config - for elem := range imageMap[imgId] { + for elem := range imageMap[imgID] { img, err := srv.runtime.graph.Get(elem) if err != nil { return nil, err diff --git a/tags.go b/tags.go index 33ec4e149..d1eb36aa7 100644 --- a/tags.go +++ b/tags.go @@ -197,7 +197,7 @@ func (store *TagStore) Get(repoName string) (Repository, error) { return nil, nil } -func (store *TagStore) GetImage(repoName, tagOrId string) (*Image, error) { +func (store *TagStore) GetImage(repoName, tagOrID string) (*Image, error) { repo, err := store.Get(repoName) if err != nil { return nil, err @@ -206,11 +206,11 @@ func (store *TagStore) GetImage(repoName, tagOrId string) (*Image, error) { } //go through all the tags, to see if tag is in fact an ID for _, revision := range repo { - if strings.HasPrefix(revision, tagOrId) { + if strings.HasPrefix(revision, tagOrID) { return store.graph.Get(revision) } } - if revision, exists := repo[tagOrId]; exists { + if revision, exists := repo[tagOrID]; exists { return store.graph.Get(revision) } return nil, nil diff --git a/tags_test.go b/tags_test.go index 90bc05640..1974e751b 100644 --- a/tags_test.go +++ b/tags_test.go @@ -35,13 +35,13 @@ func TestLookupImage(t *testing.T) { t.Errorf("Expected 0 image, 1 found") } - if img, err := runtime.repositories.LookupImage(unitTestImageId); err != nil { + if img, err := runtime.repositories.LookupImage(unitTestImageID); err != nil { t.Fatal(err) } else if img == nil { t.Errorf("Expected 1 image, none found") } - if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + unitTestImageId); err != nil { + if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + unitTestImageID); err != nil { t.Fatal(err) } else if img == nil { t.Errorf("Expected 1 image, none found") From 1e2ef274cdaa76e79435df52cdc196739ba8b3b1 Mon Sep 17 00:00:00 2001 From: Marco Hennings Date: Thu, 4 Jul 2013 10:50:37 +0200 Subject: [PATCH 11/59] Pushing an Image causes the docker client to give an error message instead of writing out streamed status. This is caused by a Buffering message that is not in the correct json format: [...] {"status" :"Pushing 6bba11a28f1ca247de9a47071355ce5923a45b8fea3182389f992f4 24b93edae"}Buffering to disk 244/? (n/a).. {"status":"Pushing",[...] The "Buffering to disk" message is originated in srv.runtime.graph.TempLayerArchive I am now using the StreamFormatter provided by the context from which the method is called. --- graph.go | 3 +-- server.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/graph.go b/graph.go index 0bf7eccdb..1417aade0 100644 --- a/graph.go +++ b/graph.go @@ -162,7 +162,7 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error { // The archive is stored on disk and will be automatically deleted as soon as has been read. // If output is not nil, a human-readable progress bar will be written to it. // FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives? -func (graph *Graph) TempLayerArchive(id string, compression Compression, output io.Writer) (*TempArchive, error) { +func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *utils.StreamFormatter, output io.Writer) (*TempArchive, error) { image, err := graph.Get(id) if err != nil { return nil, err @@ -175,7 +175,6 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output if err != nil { return nil, err } - sf := utils.NewStreamFormatter(false) return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root) } diff --git a/server.go b/server.go index 4e30ad6c5..a217cf7d8 100644 --- a/server.go +++ b/server.go @@ -684,7 +684,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, if err != nil { if os.IsNotExist(err) { // If the archive does not exist, create one from the layer - layerData, err = srv.runtime.graph.TempLayerArchive(imgID, Xz, out) + layerData, err = srv.runtime.graph.TempLayerArchive(imgID, Xz, sf, out) if err != nil { return fmt.Errorf("Failed to generate layer archive: %s", err) } From dd619d2bd6a3cf621106f2599fbf3bc903b5801a Mon Sep 17 00:00:00 2001 From: Karan Lyons Date: Thu, 4 Jul 2013 09:58:50 -0700 Subject: [PATCH 12/59] Mount /dev/shm as a tmpfs. Fixes #1122. --- lxc_template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxc_template.go b/lxc_template.go index 93b795e90..e76c63a65 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -76,7 +76,7 @@ lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0 #lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0 #lxc.mount.entry = varlock {{$ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0 -#lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 +lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 # Inject docker-init lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0 From ba9aef6f2c529ccea571deacc46d2a61ca74a8bc Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Thu, 4 Jul 2013 12:40:14 -0700 Subject: [PATCH 13/59] Typo fix Error message grammar tweak --- runtime_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime_test.go b/runtime_test.go index 0791b2d58..59b219195 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -73,7 +73,7 @@ func init() { } if uid := syscall.Geteuid(); uid != 0 { - log.Fatal("docker tests needs to be run as root") + log.Fatal("docker tests need to be run as root") } NetworkBridgeIface = "testdockbr0" From 1277dca3358874d0701685f7ebb52ce66be1b385 Mon Sep 17 00:00:00 2001 From: Caleb Spare Date: Thu, 4 Jul 2013 14:28:49 -0700 Subject: [PATCH 14/59] Style fixes for fmt + err usage. fmt.Printf and friends will automatically format using the error interface (.Error()) preferentially; no need to do err.Error(). --- api.go | 4 ++-- archive_test.go | 8 ++++---- auth/auth_test.go | 2 +- server.go | 12 ++++++------ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/api.go b/api.go index 8772994e4..1970feaec 100644 --- a/api.go +++ b/api.go @@ -170,7 +170,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r name := vars["name"] if err := srv.ContainerExport(name, w); err != nil { - utils.Debugf("%s", err.Error()) + utils.Debugf("%s", err) return err } return nil @@ -306,7 +306,7 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req } config := &Config{} if err := json.NewDecoder(r.Body).Decode(config); err != nil { - utils.Debugf("%s", err.Error()) + utils.Debugf("%s", err) } repo := r.Form.Get("repo") tag := r.Form.Get("tag") diff --git a/archive_test.go b/archive_test.go index bb4235ad5..9a0a8e1b9 100644 --- a/archive_test.go +++ b/archive_test.go @@ -16,7 +16,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") out, err := CmdStream(cmd) if err != nil { - t.Fatalf("Failed to start command: " + err.Error()) + t.Fatalf("Failed to start command: %s", err) } errCh := make(chan error) go func() { @@ -26,7 +26,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { select { case err := <-errCh: if err != nil { - t.Fatalf("Command should not have failed (err=%s...)", err.Error()[:100]) + t.Fatalf("Command should not have failed (err=%.100s...)", err) } case <-time.After(5 * time.Second): t.Fatalf("Command did not complete in 5 seconds; probable deadlock") @@ -37,12 +37,12 @@ func TestCmdStreamBad(t *testing.T) { badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") out, err := CmdStream(badCmd) if err != nil { - t.Fatalf("Failed to start command: " + err.Error()) + t.Fatalf("Failed to start command: %s", err) } if output, err := ioutil.ReadAll(out); err == nil { t.Fatalf("Command should have failed") } else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" { - t.Fatalf("Wrong error value (%s)", err.Error()) + t.Fatalf("Wrong error value (%s)", err) } else if s := string(output); s != "hello\n" { t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output) } diff --git a/auth/auth_test.go b/auth/auth_test.go index 8e7adaef8..d036de736 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -68,6 +68,6 @@ func TestCreateAccount(t *testing.T) { expectedError := "Login: Account is not Active" if !strings.Contains(err.Error(), expectedError) { - t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err.Error()) + t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err) } } diff --git a/server.go b/server.go index 4e30ad6c5..4a37a9268 100644 --- a/server.go +++ b/server.go @@ -29,7 +29,7 @@ func (srv *Server) DockerVersion() APIVersion { func (srv *Server) ContainerKill(name string) error { if container := srv.runtime.Get(name); container != nil { if err := container.Kill(); err != nil { - return fmt.Errorf("Error restarting container %s: %s", name, err.Error()) + return fmt.Errorf("Error restarting container %s: %s", name, err) } } else { return fmt.Errorf("No such container: %s", name) @@ -809,7 +809,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { func (srv *Server) ContainerRestart(name string, t int) error { if container := srv.runtime.Get(name); container != nil { if err := container.Restart(t); err != nil { - return fmt.Errorf("Error restarting container %s: %s", name, err.Error()) + return fmt.Errorf("Error restarting container %s: %s", name, err) } } else { return fmt.Errorf("No such container: %s", name) @@ -828,7 +828,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { volumes[volumeId] = struct{}{} } if err := srv.runtime.Destroy(container); err != nil { - return fmt.Errorf("Error destroying container %s: %s", name, err.Error()) + return fmt.Errorf("Error destroying container %s: %s", name, err) } if removeVolume { @@ -948,7 +948,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { } if !autoPrune { if err := srv.runtime.graph.Delete(img.ID); err != nil { - return nil, fmt.Errorf("Error deleting image %s: %s", name, err.Error()) + return nil, fmt.Errorf("Error deleting image %s: %s", name, err) } return nil, nil } @@ -996,7 +996,7 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { if container := srv.runtime.Get(name); container != nil { if err := container.Start(hostConfig); err != nil { - return fmt.Errorf("Error starting container %s: %s", name, err.Error()) + return fmt.Errorf("Error starting container %s: %s", name, err) } } else { return fmt.Errorf("No such container: %s", name) @@ -1007,7 +1007,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error { func (srv *Server) ContainerStop(name string, t int) error { if container := srv.runtime.Get(name); container != nil { if err := container.Stop(t); err != nil { - return fmt.Errorf("Error stopping container %s: %s", name, err.Error()) + return fmt.Errorf("Error stopping container %s: %s", name, err) } } else { return fmt.Errorf("No such container: %s", name) From ab3893ff4d202549bf79e5671cc9e6f18376cbfd Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Thu, 4 Jul 2013 20:28:54 -0700 Subject: [PATCH 15/59] testing, issue #776: Ensure docker-ci test docker code as it was at commit time --- testing/buildbot/master.cfg | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/testing/buildbot/master.cfg b/testing/buildbot/master.cfg index 48df5835f..dd3f7c5f1 100644 --- a/testing/buildbot/master.cfg +++ b/testing/buildbot/master.cfg @@ -5,6 +5,7 @@ from buildbot.schedulers.basic import SingleBranchScheduler from buildbot.changes import filter from buildbot.config import BuilderConfig from buildbot.process.factory import BuildFactory +from buildbot.process.properties import Interpolate from buildbot.steps.shell import ShellCommand from buildbot.status import html from buildbot.status.web import authz, auth @@ -15,20 +16,20 @@ PORT_MASTER = 9989 # Port where buildbot master listen buildworkers TEST_USER = 'buildbot' # Credential to authenticate build triggers TEST_PWD = 'docker' # Credential to authenticate build triggers BUILDER_NAME = 'docker' -BUILDPASSWORD = 'pass-docker' # Credential to authenticate buildworkers -GITHUB_DOCKER = "github.com/dotcloud/docker" -DOCKER_PATH = "/data/docker" -BUILDER_PATH = "/data/buildbot/slave/{0}/build".format(BUILDER_NAME) +GITHUB_DOCKER = 'github.com/dotcloud/docker' +DOCKER_PATH = '/data/docker' +BUILDER_PATH = '/data/buildbot/slave/{0}/build'.format(BUILDER_NAME) DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker' +BUILDBOT_PWD = 'pass-docker' c = BuildmasterConfig = {} c['title'] = "Docker" c['titleURL'] = "waterfall" -c['buildbotURL'] = "http://0.0.0.0:{0}/".format(PORT_WEB) +c['buildbotURL'] = "http://docker-ci.dotcloud.com/" c['db'] = {'db_url':"sqlite:///state.sqlite"} -c['slaves'] = [BuildSlave('buildworker', BUILDPASSWORD)] +c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)] c['slavePortnum'] = PORT_MASTER c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])] @@ -36,14 +37,12 @@ c['schedulers'].append(SingleBranchScheduler(name="all", change_filter=filter.ChangeFilter(branch='master'),treeStableTimer=None, builderNames=[BUILDER_NAME])) -# Docker test command -test_cmd = ("cd /tmp; rm -rf {0}; export GOPATH={0}; go get -d {1}; cd {2}; " - "go test").format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH) - # Builder factory = BuildFactory() -factory.addStep(ShellCommand(description='Docker',logEnviron=False, - usePTY=True,command=test_cmd)) +factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, + command=["sh", "-c", Interpolate("cd ..; rm -rf build; export GOPATH={0}; " + "go get -d {1}; cd {2}; git reset --hard %(src::revision:-unknown)s; " + "go test -v".format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH))])) c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], factory=factory)] From f7fed2ea5f811719bd8bd2e6111838ad50a53194 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Thu, 4 Jul 2013 21:43:46 -0700 Subject: [PATCH 16/59] testing, issue #775: Add automatic testing notifications to docker-ci --- testing/README.rst | 10 ++++++++++ testing/Vagrantfile | 4 +++- testing/buildbot/master.cfg | 25 ++++++++++++++++++++----- testing/buildbot/setup.sh | 14 ++++++++++++-- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/testing/README.rst b/testing/README.rst index 405adaed9..3b11092f9 100644 --- a/testing/README.rst +++ b/testing/README.rst @@ -30,6 +30,16 @@ Deployment export AWS_KEYPAIR_NAME=xxxxxxxxxxxx export AWS_SSH_PRIVKEY=xxxxxxxxxxxx + # Define email recipient and IRC channel + export EMAIL_RCP=xxxxxx@domain.com + export IRC_CHANNEL=docker + + # Define buildbot credentials + export BUILDBOT_PWD=xxxxxxxxxxxx + export IRC_PWD=xxxxxxxxxxxx + export SMTP_USER=xxxxxxxxxxxx + export SMTP_PWD=xxxxxxxxxxxx + # Checkout docker git clone git://github.com/dotcloud/docker.git diff --git a/testing/Vagrantfile b/testing/Vagrantfile index e3b25a6f9..47257201d 100644 --- a/testing/Vagrantfile +++ b/testing/Vagrantfile @@ -27,7 +27,9 @@ Vagrant::Config.run do |config| pkg_cmd << "apt-get install -q -y python-dev python-pip supervisor; " \ "pip install -r #{CFG_PATH}/requirements.txt; " \ "chown #{USER}.#{USER} /data; cd /data; " \ - "#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH}; " + "#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH} #{ENV['BUILDBOT_PWD']} " \ + "#{ENV['IRC_PWD']} #{ENV['IRC_CHANNEL']} #{ENV['SMTP_USER']} " \ + "#{ENV['SMTP_PWD']} #{ENV['EMAIL_RCP']}; " # Install docker dependencies pkg_cmd << "apt-get install -q -y python-software-properties; " \ "add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \ diff --git a/testing/buildbot/master.cfg b/testing/buildbot/master.cfg index dd3f7c5f1..65399bb1a 100644 --- a/testing/buildbot/master.cfg +++ b/testing/buildbot/master.cfg @@ -7,8 +7,9 @@ from buildbot.config import BuilderConfig from buildbot.process.factory import BuildFactory from buildbot.process.properties import Interpolate from buildbot.steps.shell import ShellCommand -from buildbot.status import html +from buildbot.status import html, words from buildbot.status.web import authz, auth +from buildbot.status.mail import MailNotifier PORT_WEB = 80 # Buildbot webserver port PORT_GITHUB = 8011 # Buildbot github hook port @@ -20,7 +21,14 @@ GITHUB_DOCKER = 'github.com/dotcloud/docker' DOCKER_PATH = '/data/docker' BUILDER_PATH = '/data/buildbot/slave/{0}/build'.format(BUILDER_NAME) DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker' -BUILDBOT_PWD = 'pass-docker' + +# Credentials set by setup.sh and Vagrantfile +BUILDBOT_PWD = '' +IRC_PWD = '' +IRC_CHANNEL = '' +SMTP_USER = '' +SMTP_PWD = '' +EMAIL_RCP = '' c = BuildmasterConfig = {} @@ -47,8 +55,15 @@ c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], factory=factory)] # Status -authz_cfg=authz.Authz(auth=auth.BasicAuth([(TEST_USER,TEST_PWD)]), +authz_cfg = authz.Authz(auth=auth.BasicAuth([(TEST_USER, TEST_PWD)]), forceBuild='auth') c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)] -c['status'].append(html.WebStatus(http_port=PORT_GITHUB,allowForce=True, - change_hook_dialects={ 'github' : True })) +c['status'].append(html.WebStatus(http_port=PORT_GITHUB, allowForce=True, + change_hook_dialects={ 'github': True })) +c['status'].append(MailNotifier(fromaddr='buildbot@docker.io', + sendToInterestedUsers=False, extraRecipients=[EMAIL_RCP], + mode='failing', relayhost='smtp.mailgun.org', smtpPort=587, useTls=True, + smtpUser=SMTP_USER, smtpPassword=SMTP_PWD)) +c['status'].append(words.IRC("irc.freenode.net", "dockerqabot", + channels=[IRC_CHANNEL], password=IRC_PWD, allowForce=True, + notify_events={'exception':1, 'successToFailure':1, 'failureToSuccess':1})) diff --git a/testing/buildbot/setup.sh b/testing/buildbot/setup.sh index 828ac3ebe..937533ba1 100755 --- a/testing/buildbot/setup.sh +++ b/testing/buildbot/setup.sh @@ -6,11 +6,16 @@ USER=$1 CFG_PATH=$2 +BUILDBOT_PWD=$3 +IRC_PWD=$4 +IRC_CHANNEL=$5 +SMTP_USER=$6 +SMTP_PWD=$7 +EMAIL_RCP=$8 BUILDBOT_PATH="/data/buildbot" DOCKER_PATH="/data/docker" SLAVE_NAME="buildworker" SLAVE_SOCKET="localhost:9989" -BUILDBOT_PWD="pass-docker" export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin" function run { su $USER -c "$1"; } @@ -23,7 +28,12 @@ run "mkdir -p $BUILDBOT_PATH" cd $BUILDBOT_PATH run "buildbot create-master master" run "cp $CFG_PATH/master.cfg master" -run "sed -i -E 's#(DOCKER_PATH = ).+#\1\"$DOCKER_PATH\"#' master/master.cfg" +run "sed -i -E 's#(BUILDBOT_PWD = ).+#\1\"$BUILDBOT_PWD\"#' master/master.cfg" +run "sed -i -E 's#(IRC_PWD = ).+#\1\"$IRC_PWD\"#' master/master.cfg" +run "sed -i -E 's#(IRC_CHANNEL = ).+#\1\"$IRC_CHANNEL\"#' master/master.cfg" +run "sed -i -E 's#(SMTP_USER = ).+#\1\"$SMTP_USER\"#' master/master.cfg" +run "sed -i -E 's#(SMTP_PWD = ).+#\1\"$SMTP_PWD\"#' master/master.cfg" +run "sed -i -E 's#(EMAIL_RCP = ).+#\1\"$EMAIL_RCP\"#' master/master.cfg" run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD" # Allow buildbot subprocesses (docker tests) to properly run in containers, From dea29e7c999b7ef76a816867a2cb75c2da658fa2 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 5 Jul 2013 16:58:39 +0000 Subject: [PATCH 17/59] Fix error in rmi when conflict --- api_test.go | 18 ++++++++++++++---- server.go | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/api_test.go b/api_test.go index f82782643..6eb2584b0 100644 --- a/api_test.go +++ b/api_test.go @@ -1359,21 +1359,31 @@ func TestDeleteImages(t *testing.T) { t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images)) } - req, err := http.NewRequest("DELETE", "/images/test:test", nil) + req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) if err != nil { t.Fatal(err) } r := httptest.NewRecorder() - if err := deleteImages(srv, APIVERSION, r, req, map[string]string{"name": "test:test"}); err != nil { + if err := deleteImages(srv, APIVERSION, r, req, map[string]string{"name": unitTestImageID}); err == nil { + t.Fatalf("Expected conflict error, got none") + } + + req2, err := http.NewRequest("DELETE", "/images/test:test", nil) + if err != nil { t.Fatal(err) } - if r.Code != http.StatusOK { + + r2 := httptest.NewRecorder() + if err := deleteImages(srv, APIVERSION, r2, req2, map[string]string{"name": "test:test"}); err != nil { + t.Fatal(err) + } + if r2.Code != http.StatusOK { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } var outs []APIRmi - if err := json.Unmarshal(r.Body.Bytes(), &outs); err != nil { + if err := json.Unmarshal(r2.Body.Bytes(), &outs); err != nil { t.Fatal(err) } if len(outs) != 1 { diff --git a/server.go b/server.go index 4e30ad6c5..9e573b232 100644 --- a/server.go +++ b/server.go @@ -919,7 +919,7 @@ func (srv *Server) deleteImageParents(img *Image, imgs *[]APIRmi) error { func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, error) { //Untag the current image - var imgs []APIRmi + imgs := []APIRmi{} tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag) if err != nil { return nil, err From 4e0cdc016a5eb7227d4b8d7daffba7335fcbe749 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 5 Jul 2013 10:47:00 -0700 Subject: [PATCH 18/59] Revert #1126. Remove mount shm --- lxc_template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxc_template.go b/lxc_template.go index e76c63a65..93b795e90 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -76,7 +76,7 @@ lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0 #lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0 #lxc.mount.entry = varlock {{$ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0 -lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 +#lxc.mount.entry = shm {{$ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 # Inject docker-init lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0 From 66a9d06d9fa7a382c6852cf047e1448e0d3e1782 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 5 Jul 2013 12:20:58 -0700 Subject: [PATCH 19/59] Adding support for nicer URLs to support standalone registry (+ some registry code cleaning) --- api.go | 6 +- auth/auth.go | 10 +-- buildfile.go | 2 +- commands.go | 35 +++-------- registry/registry.go | 141 +++++++++++++++++++++++++------------------ server.go | 133 ++++++++++++++++------------------------ 6 files changed, 150 insertions(+), 177 deletions(-) diff --git a/api.go b/api.go index 8772994e4..ff428885d 100644 --- a/api.go +++ b/api.go @@ -342,8 +342,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht } sf := utils.NewStreamFormatter(version > 1.0) if image != "" { //pull - registry := r.Form.Get("registry") - if err := srv.ImagePull(image, tag, registry, w, sf, &auth.AuthConfig{}); err != nil { + if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}); err != nil { if sf.Used() { w.Write(sf.FormatError(err)) return nil @@ -426,7 +425,6 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http if err := parseForm(r); err != nil { return err } - registry := r.Form.Get("registry") if vars == nil { return fmt.Errorf("Missing parameter") @@ -436,7 +434,7 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http w.Header().Set("Content-Type", "application/json") } sf := utils.NewStreamFormatter(version > 1.0) - if err := srv.ImagePush(name, registry, w, sf, authConfig); err != nil { + if err := srv.ImagePush(name, w, sf, authConfig); err != nil { if sf.Used() { w.Write(sf.FormatError(err)) return nil diff --git a/auth/auth.go b/auth/auth.go index 205b9479f..2e52af88d 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -15,8 +15,8 @@ import ( // Where we store the config file const CONFIGFILE = ".dockercfg" -// the registry server we want to login against -const INDEXSERVER = "https://index.docker.io/v1" +// Only used for user auth + account creation +const INDEXSERVER = "https://index.docker.io/v1/" //const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/" @@ -42,7 +42,7 @@ func NewAuthConfig(username, password, email, rootPath string) *AuthConfig { func IndexServerAddress() string { if os.Getenv("DOCKER_INDEX_URL") != "" { - return os.Getenv("DOCKER_INDEX_URL") + "/v1" + return os.Getenv("DOCKER_INDEX_URL") + "/v1/" } return INDEXSERVER } @@ -132,7 +132,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. b := strings.NewReader(string(jsonBody)) - req1, err := http.Post(IndexServerAddress()+"/users/", "application/json; charset=utf-8", b) + req1, err := http.Post(IndexServerAddress()+"users/", "application/json; charset=utf-8", b) if err != nil { return "", fmt.Errorf("Server Error: %s", err) } @@ -152,7 +152,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { "Please check your e-mail for a confirmation link.") } else if reqStatusCode == 400 { if string(reqBody) == "\"Username or email already exists\"" { - req, err := http.NewRequest("GET", IndexServerAddress()+"/users/", nil) + req, err := http.NewRequest("GET", IndexServerAddress()+"users/", nil) req.SetBasicAuth(authConfig.Username, authConfig.Password) resp, err := client.Do(req) if err != nil { diff --git a/buildfile.go b/buildfile.go index cdc2a6b04..570a4eb72 100644 --- a/buildfile.go +++ b/buildfile.go @@ -61,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error { remote = name } - if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false), nil); err != nil { + if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil { return err } diff --git a/commands.go b/commands.go index 6e1e5e88c..b85a52a3d 100644 --- a/commands.go +++ b/commands.go @@ -19,7 +19,6 @@ import ( "os/signal" "path/filepath" "reflect" - "regexp" "strconv" "strings" "syscall" @@ -721,7 +720,6 @@ func (cli *DockerCli) CmdImport(args ...string) error { func (cli *DockerCli) CmdPush(args ...string) error { cmd := Subcmd("push", "[OPTION] NAME", "Push an image or a repository to the registry") - registry := cmd.String("registry", "", "Registry host to push the image to") if err := cmd.Parse(args); err != nil { return nil } @@ -732,28 +730,16 @@ func (cli *DockerCli) CmdPush(args ...string) error { return nil } - if err := cli.checkIfLogged(*registry == "", "push"); err != nil { + if err := cli.checkIfLogged("push"); err != nil { return err } - if *registry == "" { - // If we're not using a custom registry, we know the restrictions - // applied to repository names and can warn the user in advance. - // Custom repositories can have different rules, and we must also - // allow pushing by image ID. - if len(strings.SplitN(name, "/", 2)) == 1 { - return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.authConfig.Username, name) - } - - nameParts := strings.SplitN(name, "/", 2) - validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`) - if !validNamespace.MatchString(nameParts[0]) { - return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", nameParts[0]) - } - validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`) - if !validRepo.MatchString(nameParts[1]) { - return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", nameParts[1]) - } + // If we're not using a custom registry, we know the restrictions + // applied to repository names and can warn the user in advance. + // Custom repositories can have different rules, and we must also + // allow pushing by image ID. + if len(strings.SplitN(name, "/", 2)) == 1 { + return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.authConfig.Username, name) } buf, err := json.Marshal(cli.authConfig) @@ -762,7 +748,6 @@ func (cli *DockerCli) CmdPush(args ...string) error { } v := url.Values{} - v.Set("registry", *registry) if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil { return err } @@ -772,7 +757,6 @@ func (cli *DockerCli) CmdPush(args ...string) error { func (cli *DockerCli) CmdPull(args ...string) error { cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry") tag := cmd.String("t", "", "Download tagged image in repository") - registry := cmd.String("registry", "", "Registry to download from. Necessary if image is pulled by ID") if err := cmd.Parse(args); err != nil { return nil } @@ -792,7 +776,6 @@ func (cli *DockerCli) CmdPull(args ...string) error { v := url.Values{} v.Set("fromImage", remote) v.Set("tag", *tag) - v.Set("registry", *registry) if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out); err != nil { return err @@ -1329,9 +1312,9 @@ func (cli *DockerCli) CmdRun(args ...string) error { return nil } -func (cli *DockerCli) checkIfLogged(condition bool, action string) error { +func (cli *DockerCli) checkIfLogged(action string) error { // If condition AND the login failed - if condition && cli.authConfig.Username == "" { + if cli.authConfig.Username == "" { if err := cli.CmdLogin(""); err != nil { return err } diff --git a/registry/registry.go b/registry/registry.go index 622c09b3f..e9d7b2b8d 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -12,18 +12,70 @@ import ( "net/http" "net/http/cookiejar" "net/url" + "regexp" "strconv" "strings" ) var ErrAlreadyExists = errors.New("Image already exists") -func UrlScheme() string { - u, err := url.Parse(auth.IndexServerAddress()) +func pingRegistryEndpoint(endpoint string) error { + // FIXME: implement the check to discover if it should be http or https + resp, err := http.Get(endpoint) if err != nil { - return "https" + return err } - return u.Scheme + if resp.Header.Get("X-Docker-Registry-Version") == "" { + return errors.New("This does not look like a Registry server (\"X-Docker-Registry-Version\" header not found in the response)") + } + return nil +} + +func validateRepositoryName(namespace, name string) error { + validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`) + if !validNamespace.MatchString(namespace) { + return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace) + } + validRepo := regexp.MustCompile(`^([a-zA-Z0-9-_.]+)$`) + if !validRepo.MatchString(name) { + return fmt.Errorf("Invalid repository name (%s), only [a-zA-Z0-9-_.] are allowed", name) + } + return nil +} + +// Resolves a repository name to a endpoint + name +func ResolveRepositoryName(reposName string) (string, string, error) { + nameParts := strings.SplitN(reposName, "/", 2) + if !strings.Contains(nameParts[0], ".") { + // This is a Docker Index repos (ex: samalba/hipache or ubuntu) + var err error + if len(nameParts) < 2 { + err = validateRepositoryName("library", nameParts[0]) + } else { + err = validateRepositoryName(nameParts[0], nameParts[1]) + } + return "https://index.docker.io/v1/", reposName, err + } + if len(nameParts) < 2 { + // There is a dot in repos name (and no registry address) + // Is it a Registry address without repos name? + return "", "", errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") + } + n := strings.LastIndex(reposName, "/") + hostname := nameParts[0] + path := reposName[len(nameParts[0]):n] + reposName = reposName[n+1:] + endpoint := fmt.Sprintf("https://%s%s/v1/", hostname, path) + if err := pingRegistryEndpoint(endpoint); err != nil { + utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) + endpoint = fmt.Sprintf("http://%s%s/v1/", hostname, path) + if err = pingRegistryEndpoint(endpoint); err != nil { + //TODO: triggering highland build can be done there without "failing" + return "", "", errors.New("Invalid Registry endpoint: " + err.Error()) + } + } + err := validateRepositoryName("library", reposName) + return endpoint, reposName, err } func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { @@ -36,7 +88,7 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { // Retrieve the history of a given image from the Registry. // Return a list of the parent's json (requested image included) func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]string, error) { - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/ancestry", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgId+"/ancestry", nil) if err != nil { return nil, err } @@ -67,7 +119,7 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s func (r *Registry) LookupRemoteImage(imgId, registry string, token []string) bool { rt := &http.Transport{Proxy: http.ProxyFromEnvironment} - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgId+"/json", nil) if err != nil { return false } @@ -79,44 +131,10 @@ func (r *Registry) LookupRemoteImage(imgId, registry string, token []string) boo return res.StatusCode == 200 } -func (r *Registry) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) { - u := auth.IndexServerAddress() + "/repositories/" + repository + "/images" - req, err := http.NewRequest("GET", u, nil) - if err != nil { - return nil, err - } - if authConfig != nil && len(authConfig.Username) > 0 { - req.SetBasicAuth(authConfig.Username, authConfig.Password) - } - res, err := r.client.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - // Repository doesn't exist yet - if res.StatusCode == 404 { - return nil, nil - } - - jsonData, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - - imageList := []map[string]string{} - if err := json.Unmarshal(jsonData, &imageList); err != nil { - utils.Debugf("Body: %s (%s)\n", res.Body, u) - return nil, err - } - - return imageList, nil -} - // Retrieve an image from the Registry. func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, int, error) { // Get the JSON - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgId+"/json", nil) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) } @@ -143,7 +161,7 @@ func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([ } func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, error) { - req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgId+"/layer", nil) if err != nil { return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } @@ -162,10 +180,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ repository = "library/" + repository } for _, host := range registries { - endpoint := fmt.Sprintf("%s/v1/repositories/%s/tags", host, repository) - if !(strings.HasPrefix(endpoint, "http://") || strings.HasPrefix(endpoint, "https://")) { - endpoint = fmt.Sprintf("%s://%s", UrlScheme(), endpoint) - } + endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository) req, err := r.opaqueRequest("GET", endpoint, nil) if err != nil { return nil, err @@ -198,8 +213,8 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, fmt.Errorf("Could not reach any registry endpoint") } -func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { - repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images" +func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, error) { + repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote) req, err := r.opaqueRequest("GET", repositoryTarget, nil) if err != nil { @@ -230,8 +245,12 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { } var endpoints []string + var urlScheme = indexEp[:strings.Index(indexEp, ":")] if res.Header.Get("X-Docker-Endpoints") != "" { - endpoints = res.Header["X-Docker-Endpoints"] + // The Registry's URL scheme has to match the Index' + for _, ep := range res.Header["X-Docker-Endpoints"] { + endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep)) + } } else { return nil, fmt.Errorf("Index response didn't contain any endpoints") } @@ -260,9 +279,8 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { // Push a local image to the registry func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error { - registry = registry + "/v1" // FIXME: try json with UTF8 - req, err := http.NewRequest("PUT", registry+"/images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw))) + req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw))) if err != nil { return err } @@ -296,8 +314,7 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis } func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registry string, token []string) error { - registry = registry + "/v1" - req, err := http.NewRequest("PUT", registry+"/images/"+imgId+"/layer", layer) + req, err := http.NewRequest("PUT", registry+"images/"+imgId+"/layer", layer) if err != nil { return err } @@ -334,9 +351,8 @@ func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.R func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error { // "jsonify" the string revision = "\"" + revision + "\"" - registry = registry + "/v1" - req, err := r.opaqueRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision)) + req, err := r.opaqueRequest("PUT", registry+"repositories/"+remote+"/tags/"+tag, strings.NewReader(revision)) if err != nil { return err } @@ -354,7 +370,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token return nil } -func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { +func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData, validate bool, regs []string) (*RepositoryData, error) { imgListJSON, err := json.Marshal(imgList) if err != nil { return nil, err @@ -364,9 +380,10 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat suffix = "images" } + u := fmt.Sprintf("%srepositories/%s/%s", indexEp, remote, suffix) + utils.Debugf("PUT %s", u) utils.Debugf("Image list pushed to index:\n%s\n", imgListJSON) - - req, err := r.opaqueRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJSON)) + req, err := r.opaqueRequest("PUT", u, bytes.NewReader(imgListJSON)) if err != nil { return nil, err } @@ -404,6 +421,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } var tokens, endpoints []string + var urlScheme = indexEp[:strings.Index(indexEp, ":")] if !validate { if res.StatusCode != 200 && res.StatusCode != 201 { errBody, err := ioutil.ReadAll(res.Body) @@ -420,7 +438,10 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } if res.Header.Get("X-Docker-Endpoints") != "" { - endpoints = res.Header["X-Docker-Endpoints"] + // The Registry's URL scheme has to match the Index' + for _, ep := range res.Header["X-Docker-Endpoints"] { + endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep)) + } } else { return nil, fmt.Errorf("Index response didn't contain any endpoints") } @@ -442,7 +463,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { - u := auth.IndexServerAddress() + "/search?q=" + url.QueryEscape(term) + u := auth.IndexServerAddress() + "search?q=" + url.QueryEscape(term) req, err := http.NewRequest("GET", u, nil) if err != nil { return nil, err diff --git a/server.go b/server.go index cedd06ad7..04f43870b 100644 --- a/server.go +++ b/server.go @@ -351,44 +351,32 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin return nil } -func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, remote, askedTag, registryEp string, sf *utils.StreamFormatter) error { - out.Write(sf.FormatStatus("Pulling repository %s from %s", local, auth.IndexServerAddress())) +func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, askedTag, indexEp string, sf *utils.StreamFormatter) error { + out.Write(sf.FormatStatus("Pulling repository %s from %s", name, indexEp)) - var repoData *registry.RepositoryData - var err error - if registryEp == "" { - repoData, err = r.GetRepositoryData(remote) - if err != nil { - return err - } + repoData, err := r.GetRepositoryData(indexEp, name) + if err != nil { + return err + } - utils.Debugf("Updating checksums") - // Reload the json file to make sure not to overwrite faster sums - if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil { - return err - } - } else { - repoData = ®istry.RepositoryData{ - Tokens: []string{}, - ImgList: make(map[string]*registry.ImgData), - Endpoints: []string{registryEp}, - } + utils.Debugf("Updating checksums") + // Reload the json file to make sure not to overwrite faster sums + if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil { + return err } utils.Debugf("Retrieving the tag list") - tagsList, err := r.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens) + tagsList, err := r.GetRemoteTags(repoData.Endpoints, name, repoData.Tokens) if err != nil { utils.Debugf("%v", err) return err } - if registryEp != "" { - for tag, id := range tagsList { - repoData.ImgList[id] = ®istry.ImgData{ - ID: id, - Tag: tag, - Checksum: "", - } + for tag, id := range tagsList { + repoData.ImgList[id] = ®istry.ImgData{ + ID: id, + Tag: tag, + Checksum: "", } } @@ -402,7 +390,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re // Otherwise, check that the tag exists and use only that one id, exists := tagsList[askedTag] if !exists { - return fmt.Errorf("Tag %s not found in repository %s", askedTag, local) + return fmt.Errorf("Tag %s not found in repository %s", askedTag, name) } repoData.ImgList[id].Tag = askedTag } @@ -417,13 +405,10 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID) continue } - out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, remote)) + out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, name)) success := false for _, ep := range repoData.Endpoints { - if !(strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://")) { - ep = fmt.Sprintf("%s://%s", registry.UrlScheme(), ep) - } - if err := srv.pullImage(r, out, img.ID, ep+"/v1", repoData.Tokens, sf); err != nil { + if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err)) continue } @@ -438,7 +423,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re if askedTag != "" && tag != askedTag { continue } - if err := srv.runtime.repositories.Set(local, tag, id, true); err != nil { + if err := srv.runtime.repositories.Set(name, tag, id, true); err != nil { return err } } @@ -483,7 +468,8 @@ func (srv *Server) poolRemove(kind, key string) error { } return nil } -func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { + +func (srv *Server) ImagePull(name string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { r, err := registry.NewRegistry(srv.runtime.root, authConfig) if err != nil { return err @@ -493,14 +479,16 @@ func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *util } defer srv.poolRemove("pull", name+":"+tag) - remote := name - parts := strings.Split(name, "/") - if len(parts) > 2 { - remote = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/"))) + // Resolve the Repository name from fqn to endpoint + name + var endpoint string + endpoint, name, err = registry.ResolveRepositoryName(name) + if err != nil { + return err } + out = utils.NewWriteFlusher(out) - err = srv.pullRepository(r, out, name, remote, tag, endpoint, sf) - if err != nil && endpoint != "" { + err = srv.pullRepository(r, out, name, tag, endpoint, sf) + if err != nil { if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil { return err } @@ -576,7 +564,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat return imgList, nil } -func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name, registryEp string, localRepo map[string]string, sf *utils.StreamFormatter) error { +func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) out.Write(sf.FormatStatus("Processing checksums")) imgList, err := srv.getImageList(localRepo) @@ -591,60 +579,32 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name, reg } var repoData *registry.RepositoryData - if registryEp == "" { - repoData, err = r.PushImageJSONIndex(name, imgList, false, nil) - if err != nil { - return err - } - } else { - repoData = ®istry.RepositoryData{ - ImgList: make(map[string]*registry.ImgData), - Tokens: []string{}, - Endpoints: []string{registryEp}, - } - tagsList, err := r.GetRemoteTags(repoData.Endpoints, name, repoData.Tokens) - if err != nil && err.Error() != "Repository not found" { - return err - } else if err == nil { - for tag, id := range tagsList { - repoData.ImgList[id] = ®istry.ImgData{ - ID: id, - Tag: tag, - Checksum: "", - } - } - } + repoData, err = r.PushImageJSONIndex(indexEp, name, imgList, false, nil) + if err != nil { + return err } for _, ep := range repoData.Endpoints { - if !(strings.HasPrefix(ep, "http://") || strings.HasPrefix(ep, "https://")) { - ep = fmt.Sprintf("%s://%s", registry.UrlScheme(), ep) - } out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo))) // For each image within the repo, push them for _, elem := range imgList { if _, exists := repoData.ImgList[elem.ID]; exists { out.Write(sf.FormatStatus("Image %s already on registry, skipping", name)) continue - } else if registryEp != "" && r.LookupRemoteImage(elem.ID, registryEp, repoData.Tokens) { - fmt.Fprintf(out, "Image %s already on registry, skipping\n", name) - continue } if err := srv.pushImage(r, out, name, elem.ID, ep, repoData.Tokens, sf); err != nil { // FIXME: Continue on error? return err } - out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"/repositories/"+srvName+"/tags/"+elem.Tag)) + out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+srvName+"/tags/"+elem.Tag)) if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { return err } } } - if registryEp == "" { - if _, err := r.PushImageJSONIndex(name, imgList, true, repoData.Endpoints); err != nil { - return err - } + if _, err := r.PushImageJSONIndex(indexEp, name, imgList, true, repoData.Endpoints); err != nil { + return err } return nil @@ -711,12 +671,22 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, } // FIXME: Allow to interupt current push when new push of same image is done. -func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { +func (srv *Server) ImagePush(name string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { if err := srv.poolAdd("push", name); err != nil { return err } defer srv.poolRemove("push", name) + // Resolve the Repository name from fqn to endpoint + name + var ( + endpoint string + e error + ) + endpoint, name, e = registry.ResolveRepositoryName(name) + if e != nil { + return e + } + out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(name) r, err2 := registry.NewRegistry(srv.runtime.root, authConfig) @@ -728,16 +698,17 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.Str out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name]))) // If it fails, try to get the repository if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists { - if err := srv.pushRepository(r, out, name, endpoint, localRepo, sf); err != nil { + if err := srv.pushRepository(r, out, name, localRepo, endpoint, sf); err != nil { return err } return nil } - return err } + + var token []string out.Write(sf.FormatStatus("The push refers to an image: [%s]", name)) - if err := srv.pushImage(r, out, name, img.ID, endpoint, nil, sf); err != nil { + if err := srv.pushImage(r, out, name, img.ID, endpoint, token, sf); err != nil { return err } return nil From cfc7684b7de542cfed9d8b90b654fe59c8aa4098 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 5 Jul 2013 12:37:07 -0700 Subject: [PATCH 20/59] Restoring old changeset lost by previous merge --- registry/registry.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index ee473493a..4bd2a5adc 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -87,8 +87,8 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { // Retrieve the history of a given image from the Registry. // Return a list of the parent's json (requested image included) -func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]string, error) { - req, err := http.NewRequest("GET", registry+"images/"+imgId+"/ancestry", nil) +func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) { + req, err := http.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil) if err != nil { return nil, err } @@ -119,7 +119,7 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) bool { rt := &http.Transport{Proxy: http.ProxyFromEnvironment} - req, err := http.NewRequest("GET", registry+"images/"+imgId+"/json", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgID+"/json", nil) if err != nil { return false } @@ -134,7 +134,7 @@ func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) boo // Retrieve an image from the Registry. func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) { // Get the JSON - req, err := http.NewRequest("GET", registry+"images/"+imgId+"/json", nil) + req, err := http.NewRequest("GET", registry+"images/"+imgID+"/json", nil) if err != nil { return nil, -1, fmt.Errorf("Failed to download json: %s", err) } @@ -160,8 +160,8 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([ return jsonString, imageSize, nil } -func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, error) { - req, err := http.NewRequest("GET", registry+"images/"+imgId+"/layer", nil) +func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (io.ReadCloser, error) { + req, err := http.NewRequest("GET", registry+"images/"+imgID+"/layer", nil) if err != nil { return nil, fmt.Errorf("Error while getting from the server: %s\n", err) } @@ -313,8 +313,8 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis return nil } -func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registry string, token []string) error { - req, err := http.NewRequest("PUT", registry+"images/"+imgId+"/layer", layer) +func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string) error { + req, err := http.NewRequest("PUT", registry+"images/"+imgID+"/layer", layer) if err != nil { return err } From 57a6c83547ba4671940c3134a3f68586db603048 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 5 Jul 2013 14:30:43 -0700 Subject: [PATCH 21/59] Allowing namespaces in standalone registry --- registry/registry.go | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 4bd2a5adc..72521a312 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -20,8 +20,7 @@ import ( var ErrAlreadyExists = errors.New("Image already exists") func pingRegistryEndpoint(endpoint string) error { - // FIXME: implement the check to discover if it should be http or https - resp, err := http.Get(endpoint) + resp, err := http.Get(endpoint + "/_ping") if err != nil { return err } @@ -31,7 +30,19 @@ func pingRegistryEndpoint(endpoint string) error { return nil } -func validateRepositoryName(namespace, name string) error { +func validateRepositoryName(repositoryName string) error { + var ( + namespace string + name string + ) + nameParts := strings.SplitN(repositoryName, "/", 2) + if len(nameParts) < 2 { + namespace = "library" + name = nameParts[0] + } else { + namespace = nameParts[0] + name = nameParts[1] + } validNamespace := regexp.MustCompile(`^([a-z0-9_]{4,30})$`) if !validNamespace.MatchString(namespace) { return fmt.Errorf("Invalid namespace name (%s), only [a-z0-9_] are allowed, size between 4 and 30", namespace) @@ -48,12 +59,7 @@ func ResolveRepositoryName(reposName string) (string, string, error) { nameParts := strings.SplitN(reposName, "/", 2) if !strings.Contains(nameParts[0], ".") { // This is a Docker Index repos (ex: samalba/hipache or ubuntu) - var err error - if len(nameParts) < 2 { - err = validateRepositoryName("library", nameParts[0]) - } else { - err = validateRepositoryName(nameParts[0], nameParts[1]) - } + err := validateRepositoryName(reposName) return "https://index.docker.io/v1/", reposName, err } if len(nameParts) < 2 { @@ -61,20 +67,18 @@ func ResolveRepositoryName(reposName string) (string, string, error) { // Is it a Registry address without repos name? return "", "", errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") } - n := strings.LastIndex(reposName, "/") hostname := nameParts[0] - path := reposName[len(nameParts[0]):n] - reposName = reposName[n+1:] - endpoint := fmt.Sprintf("https://%s%s/v1/", hostname, path) + reposName = nameParts[1] + endpoint := fmt.Sprintf("https://%s/v1/", hostname) if err := pingRegistryEndpoint(endpoint); err != nil { utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) - endpoint = fmt.Sprintf("http://%s%s/v1/", hostname, path) + endpoint = fmt.Sprintf("http://%s/v1/", hostname) if err = pingRegistryEndpoint(endpoint); err != nil { //TODO: triggering highland build can be done there without "failing" return "", "", errors.New("Invalid Registry endpoint: " + err.Error()) } } - err := validateRepositoryName("library", reposName) + err := validateRepositoryName(reposName) return endpoint, reposName, err } From 4c174e0bfb4c6e6333f39fff3c70058eaed37e3a Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 5 Jul 2013 14:55:48 -0700 Subject: [PATCH 22/59] Fixed ping URL --- registry/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 72521a312..730fcf6eb 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -20,7 +20,7 @@ import ( var ErrAlreadyExists = errors.New("Image already exists") func pingRegistryEndpoint(endpoint string) error { - resp, err := http.Get(endpoint + "/_ping") + resp, err := http.Get(endpoint + "_ping") if err != nil { return err } From 283ebf3ff92ef552ae9cdf23a17c1a375dac99bd Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 5 Jul 2013 14:56:56 -0700 Subject: [PATCH 23/59] fmt.Errorf instead of errors.New --- registry/registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 730fcf6eb..c458f616f 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -65,7 +65,7 @@ func ResolveRepositoryName(reposName string) (string, string, error) { if len(nameParts) < 2 { // There is a dot in repos name (and no registry address) // Is it a Registry address without repos name? - return "", "", errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") + return "", "", fmt.Errorf("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") } hostname := nameParts[0] reposName = nameParts[1] From d3125d8570de0f9c09de94c657f7e35755accdef Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 5 Jul 2013 15:26:08 -0700 Subject: [PATCH 24/59] Code cleaning --- server.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/server.go b/server.go index 5690bfa8c..615e93f9f 100644 --- a/server.go +++ b/server.go @@ -681,13 +681,9 @@ func (srv *Server) ImagePush(name string, out io.Writer, sf *utils.StreamFormatt defer srv.poolRemove("push", name) // Resolve the Repository name from fqn to endpoint + name - var ( - endpoint string - e error - ) - endpoint, name, e = registry.ResolveRepositoryName(name) - if e != nil { - return e + endpoint, name, err := registry.ResolveRepositoryName(name) + if err != nil { + return err } out = utils.NewWriteFlusher(out) From e2b8ee2723a4cf6a37cdb455011ec447f23f8f49 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Fri, 5 Jul 2013 16:03:22 -0700 Subject: [PATCH 25/59] Fixed runtime_test (ImagePull prototyped changed) --- runtime_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime_test.go b/runtime_test.go index 59b219195..d003426f2 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -93,7 +93,7 @@ func init() { pushingPool: make(map[string]struct{}), } // Retrieve the Image - if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil { + if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil { panic(err) } // Spawn a Daemon From 758ea61b77ab54403d2d3955b74b6c72cd96e2a5 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Sun, 7 Jul 2013 13:55:02 +1000 Subject: [PATCH 26/59] Replaced gendered language in the README --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 376ecea70..823e48496 100644 --- a/README.md +++ b/README.md @@ -23,15 +23,15 @@ happens, for a few reasons: * *Size*: VMs are very large which makes them impractical to store and transfer. * *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and - large-scale deployment of cpu and memory-intensive applications on large numbers of machines. + large-scale deployment of cpu and memory-intensive applications on large numbers of machines. * *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead. * *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most: - building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery. + building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery. By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization, containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary for containerization, including Linux with [openvz](http://openvz.org), [vserver](http://linux-vserver.org) and more recently [lxc](http://lxc.sourceforge.net), - Solaris with [zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) and FreeBSD with [Jails](http://www.freebsd.org/doc/handbook/jails.html). + Solaris with [zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) and FreeBSD with [Jails](http://www.freebsd.org/doc/handbook/jails.html). Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves all 4 problems. Docker containers are small (and their transfer can be optimized with layers), they have basically zero memory and cpu overhead, @@ -56,17 +56,17 @@ A common problem for developers is the difficulty of managing all their applicat This is usually difficult for several reasons: * *Cross-platform dependencies*. Modern applications often depend on a combination of system libraries and binaries, language-specific packages, framework-specific modules, - internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work - well with each other, requiring awkward custom integrations. + internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work + well with each other, requiring awkward custom integrations. * Conflicting dependencies. Different applications may depend on different versions of the same dependency. Packaging tools handle these situations with various degrees of ease - - but they all handle them in different and incompatible ways, which again forces the developer to do extra work. + but they all handle them in different and incompatible ways, which again forces the developer to do extra work. - * Custom dependencies. A developer may need to prepare a custom version of his application's dependency. Some packaging systems can handle custom versions of a dependency, - others can't - and all of them handle it differently. + * Custom dependencies. A developer may need to prepare a custom version of their application's dependency. Some packaging systems can handle custom versions of a dependency, + others can't - and all of them handle it differently. -Docker solves dependency hell by giving the developer a simple way to express *all* his application's dependencies in one place, +Docker solves dependency hell by giving the developer a simple way to express *all* their application's dependencies in one place, and streamline the process of assembling them. If this makes you think of [XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't *replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers. From f3d29695608d9ea9a3f92bcf872c62e158106919 Mon Sep 17 00:00:00 2001 From: Kimbro Staken Date: Mon, 8 Jul 2013 00:11:45 -0700 Subject: [PATCH 27/59] Override Entrypoint picked up from the base image that breaks run commands in builder --- buildfile.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 570a4eb72..df413a61f 100644 --- a/buildfile.go +++ b/buildfile.go @@ -108,7 +108,7 @@ func (b *buildFile) CmdRun(args string) error { } else { utils.Debugf("[BUILDER] Cache miss") } - + cid, err := b.run() if err != nil { return err @@ -279,6 +279,13 @@ func (b *buildFile) run() (string, error) { b.tmpContainers[c.ID] = struct{}{} fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID)) + // override the entry point that may have been picked up from the base image + c.Path = b.config.Cmd[0] + c.Args = b.config.Cmd[1:] + if err := c.ToDisk(); err != nil { + return "", err + } + //start the container hostConfig := &HostConfig{} if err := c.Start(hostConfig); err != nil { From 1d1d81b0bc52c6aff652f5515fd792f76e40f7c2 Mon Sep 17 00:00:00 2001 From: Kimbro Staken Date: Mon, 8 Jul 2013 00:18:47 -0700 Subject: [PATCH 28/59] Cleanup white space --- buildfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index df413a61f..303baac31 100644 --- a/buildfile.go +++ b/buildfile.go @@ -108,7 +108,7 @@ func (b *buildFile) CmdRun(args string) error { } else { utils.Debugf("[BUILDER] Cache miss") } - + cid, err := b.run() if err != nil { return err From a0f5fb7394e6d1bf96865b8ec20796e682b4505b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 8 Jul 2013 12:45:50 +0000 Subject: [PATCH 29/59] add remote addr in debug --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index fad4e7937..c4a5222dc 100644 --- a/api.go +++ b/api.go @@ -878,7 +878,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { localMethod := method localFct := fct f := func(w http.ResponseWriter, r *http.Request) { - utils.Debugf("Calling %s %s", localMethod, localRoute) + utils.Debugf("Calling %s %s from %s", localMethod, localRoute, r.RemoteAddr) if logging { log.Println(r.Method, r.RequestURI) From fd97190ee752c8de38be38a03f3cfdd4b4ce1460 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 8 Jul 2013 17:20:13 +0000 Subject: [PATCH 30/59] uses the terminal size to display search output, add -notrunc and fix bug in resize --- commands.go | 29 ++++++++++++++++++++++------- term/term.go | 2 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/commands.go b/commands.go index 35f41e13c..f62f96462 100644 --- a/commands.go +++ b/commands.go @@ -1101,6 +1101,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { func (cli *DockerCli) CmdSearch(args ...string) error { cmd := Subcmd("search", "NAME", "Search the docker index for images") + noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") if err := cmd.Parse(args); err != nil { return nil } @@ -1122,13 +1123,19 @@ func (cli *DockerCli) CmdSearch(args ...string) error { return err } fmt.Fprintf(cli.out, "Found %d results matching your query (\"%s\")\n", len(outs), cmd.Arg(0)) - w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + w := tabwriter.NewWriter(cli.out, 33, 1, 3, ' ', 0) fmt.Fprintf(w, "NAME\tDESCRIPTION\n") + _, width := cli.getTtySize() + if width == 0 { + width = 45 + } else { + width = width - 33 //remote the first column + } for _, out := range outs { desc := strings.Replace(out.Description, "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) - if len(desc) > 45 { - desc = utils.Trunc(desc, 42) + "..." + if !*noTrunc && len(desc) > width { + desc = utils.Trunc(desc, width-3) + "..." } fmt.Fprintf(w, "%s\t%s\n", out.Name, desc) } @@ -1502,17 +1509,25 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } -func (cli *DockerCli) resizeTty(id string) { +func (cli *DockerCli) getTtySize() (int, int) { if !cli.isTerminal { - return + return 0, 0 } ws, err := term.GetWinsize(cli.terminalFd) if err != nil { utils.Debugf("Error getting size: %s", err) } + return int(ws.Height), int(ws.Width) +} + +func (cli *DockerCli) resizeTty(id string) { + height, width := cli.getTtySize() + if height == 0 && width == 0 { + return + } v := url.Values{} - v.Set("h", strconv.Itoa(int(ws.Height))) - v.Set("w", strconv.Itoa(int(ws.Width))) + v.Set("h", strconv.Itoa(height)) + v.Set("w", strconv.Itoa(width)) if _, _, err := cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil); err != nil { utils.Debugf("Error resize: %s", err) } diff --git a/term/term.go b/term/term.go index 3f743d227..f4d66a71d 100644 --- a/term/term.go +++ b/term/term.go @@ -12,8 +12,8 @@ type State struct { } type Winsize struct { - Width uint16 Height uint16 + Width uint16 x uint16 y uint16 } From 3a20e4e15d27ab0061e5ffb427baf7162de9add9 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 8 Jul 2013 18:19:12 +0000 Subject: [PATCH 31/59] add if to prevent crash --- commands.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/commands.go b/commands.go index f62f96462..0bd7965f7 100644 --- a/commands.go +++ b/commands.go @@ -1516,6 +1516,9 @@ func (cli *DockerCli) getTtySize() (int, int) { ws, err := term.GetWinsize(cli.terminalFd) if err != nil { utils.Debugf("Error getting size: %s", err) + if ws == nil { + return 0, 0 + } } return int(ws.Height), int(ws.Width) } From 08a87d4b3b3f76ac9d73309b1042aa75d6634ed9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 8 Jul 2013 13:30:03 -0700 Subject: [PATCH 32/59] Fix #1162 - Remove bufio from Untar --- archive.go | 24 ++++++++++++++---------- image.go | 3 +++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/archive.go b/archive.go index c9a650cb8..01af86006 100644 --- a/archive.go +++ b/archive.go @@ -2,7 +2,6 @@ package docker import ( "archive/tar" - "bufio" "bytes" "fmt" "github.com/dotcloud/docker/utils" @@ -26,10 +25,6 @@ const ( ) func DetectCompression(source []byte) Compression { - for _, c := range source[:10] { - utils.Debugf("%x", c) - } - sourceLen := len(source) for compression, m := range map[Compression][]byte{ Bzip2: {0x42, 0x5A, 0x68}, @@ -110,17 +105,26 @@ func Untar(archive io.Reader, path string) error { if archive == nil { return fmt.Errorf("Empty archive") } - bufferedArchive := bufio.NewReaderSize(archive, 10) - buf, err := bufferedArchive.Peek(10) - if err != nil { - return err + + buf := make([]byte, 10) + totalN := 0 + for totalN < 10 { + if n, err := archive.Read(buf[totalN:]); err != nil { + if err == io.EOF { + return fmt.Errorf("Tarball too short") + } + return err + } else { + totalN += n + utils.Debugf("[tar autodetect] n: %d", n) + } } compression := DetectCompression(buf) utils.Debugf("Archive compression detected: %s", compression.Extension()) cmd := exec.Command("tar", "--numeric-owner", "-f", "-", "-C", path, "-x"+compression.Flag()) - cmd.Stdin = bufferedArchive + cmd.Stdin = io.MultiReader(bytes.NewReader(buf), archive) // Hardcode locale environment for predictable outcome regardless of host configuration. // (see https://github.com/dotcloud/docker/issues/355) cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"} diff --git a/image.go b/image.go index bb6598b26..e1b1ac041 100644 --- a/image.go +++ b/image.go @@ -94,9 +94,12 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error { } // If layerData is not nil, unpack it into the new layer if layerData != nil { + start := time.Now() + utils.Debugf("Start untar layer") if err := Untar(layerData, layer); err != nil { return err } + utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds()) } return StoreSize(img, root) From e43ef364cb99585d3285f51f7ab308f8a77fe09e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 8 Jul 2013 15:23:04 -0700 Subject: [PATCH 33/59] Remove all network dependencies from the test suite --- api_test.go | 70 ----------------------------------------------- buildfile_test.go | 2 +- runtime_test.go | 22 +++++++++------ 3 files changed, 14 insertions(+), 80 deletions(-) diff --git a/api_test.go b/api_test.go index 6eb2584b0..d111f258c 100644 --- a/api_test.go +++ b/api_test.go @@ -5,7 +5,6 @@ import ( "bufio" "bytes" "encoding/json" - "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/utils" "io" "net" @@ -41,44 +40,6 @@ func TestGetBoolParam(t *testing.T) { } } -func TestPostAuth(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } - defer nuke(runtime) - - srv := &Server{ - runtime: runtime, - } - - r := httptest.NewRecorder() - - authConfig := &auth.AuthConfig{ - Username: "utest", - Password: "utest", - Email: "utest@yopmail.com", - } - - authConfigJSON, err := json.Marshal(authConfig) - if err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/auth", bytes.NewReader(authConfigJSON)) - if err != nil { - t.Fatal(err) - } - - if err := postAuth(srv, APIVERSION, r, req, nil); err != nil { - t.Fatal(err) - } - - if r.Code != http.StatusOK && r.Code != 0 { - t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code) - } -} - func TestGetVersion(t *testing.T) { runtime, err := newTestRuntime() if err != nil { @@ -286,37 +247,6 @@ func TestGetImagesViz(t *testing.T) { } } -func TestGetImagesSearch(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } - defer nuke(runtime) - - srv := &Server{ - runtime: runtime, - } - - r := httptest.NewRecorder() - - req, err := http.NewRequest("GET", "/images/search?term=redis", nil) - if err != nil { - t.Fatal(err) - } - - if err := getImagesSearch(srv, APIVERSION, r, req, nil); err != nil { - t.Fatal(err) - } - - results := []APISearch{} - if err := json.Unmarshal(r.Body.Bytes(), &results); err != nil { - t.Fatal(err) - } - if len(results) < 2 { - t.Errorf("Expected at least 2 lines, %d found", len(results)) - } -} - func TestGetImagesHistory(t *testing.T) { runtime, err := newTestRuntime() if err != nil { diff --git a/buildfile_test.go b/buildfile_test.go index b7cc7be8e..8913284e8 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -84,7 +84,7 @@ run [ "$FOO" = "BAR" ] { ` -from docker-ut +from %s ENTRYPOINT /bin/echo CMD Hello world `, diff --git a/runtime_test.go b/runtime_test.go index d003426f2..07616ebce 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -17,11 +17,12 @@ import ( ) const ( - unitTestImageName = "docker-unit-tests" - unitTestImageID = "e9aa60c60128cad1" - unitTestStoreBase = "/var/lib/docker/unit-tests" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-unit-tests" + unitTestImageID = "e9aa60c60128cad1" + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var globalRuntime *Runtime @@ -76,7 +77,7 @@ func init() { log.Fatal("docker tests need to be run as root") } - NetworkBridgeIface = "testdockbr0" + NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) @@ -92,9 +93,12 @@ func init() { pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } - // Retrieve the Image - if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil { - panic(err) + // If the unit test is not found, try to download it. + if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID { + // Retrieve the Image + if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil { + panic(err) + } } // Spawn a Daemon go func() { From 2b5553144a9249bbfed9be2a181b051a66350cb1 Mon Sep 17 00:00:00 2001 From: Kimbro Staken Date: Mon, 8 Jul 2013 16:03:18 -0700 Subject: [PATCH 34/59] Removing the save to disk as it was not really necessary --- buildfile.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/buildfile.go b/buildfile.go index 303baac31..75e31dba7 100644 --- a/buildfile.go +++ b/buildfile.go @@ -281,10 +281,7 @@ func (b *buildFile) run() (string, error) { // override the entry point that may have been picked up from the base image c.Path = b.config.Cmd[0] - c.Args = b.config.Cmd[1:] - if err := c.ToDisk(); err != nil { - return "", err - } + c.Args = b.config.Cmd[1:] //start the container hostConfig := &HostConfig{} From f64dbdbe3a147f58d04b6c39c6469fdd97427098 Mon Sep 17 00:00:00 2001 From: Kimbro Staken Date: Mon, 8 Jul 2013 00:11:45 -0700 Subject: [PATCH 35/59] Override Entrypoint picked up from the base image that breaks run commands in builder --- buildfile.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/buildfile.go b/buildfile.go index 570a4eb72..75e31dba7 100644 --- a/buildfile.go +++ b/buildfile.go @@ -279,6 +279,10 @@ func (b *buildFile) run() (string, error) { b.tmpContainers[c.ID] = struct{}{} fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID)) + // override the entry point that may have been picked up from the base image + c.Path = b.config.Cmd[0] + c.Args = b.config.Cmd[1:] + //start the container hostConfig := &HostConfig{} if err := c.Start(hostConfig); err != nil { From 3e8626c4a171653971aebcb778087c89c6c7ab67 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Mon, 8 Jul 2013 17:20:41 -0700 Subject: [PATCH 36/59] Changed the tag parsing to it will work even if there is a port in the repos registry url (full qualified name for pushing on a standalone registry) --- buildfile.go | 12 +----------- commands.go | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/buildfile.go b/buildfile.go index 1c5e8289b..411f44bc3 100644 --- a/buildfile.go +++ b/buildfile.go @@ -52,20 +52,10 @@ func (b *buildFile) CmdFrom(name string) error { image, err := b.runtime.repositories.LookupImage(name) if err != nil { if b.runtime.graph.IsNotExist(err) { - - var tag, remote string - if strings.Contains(name, ":") { - remoteParts := strings.Split(name, ":") - tag = remoteParts[1] - remote = remoteParts[0] - } else { - remote = name - } - + tag, remote := parseRepositoryTag(name) if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil { return err } - image, err = b.runtime.repositories.LookupImage(name) if err != nil { return err diff --git a/commands.go b/commands.go index 0bd7965f7..3134011c0 100644 --- a/commands.go +++ b/commands.go @@ -754,6 +754,20 @@ func (cli *DockerCli) CmdPush(args ...string) error { return nil } +// Get a repos name and returns the right reposName + tag +// The tag can be confusing because of a port in a repository name. +// Ex: localhost.localdomain:5000/samalba/hipache:latest +func parseRepositoryTag(repos string) (string, string) { + n := strings.LastIndex(repos, ":") + if n < 0 { + return repos, "" + } + if tag := repos[n+1:]; !strings.Contains(tag, "/") { + return repos[:n], tag + } + return repos, "" +} + func (cli *DockerCli) CmdPull(args ...string) error { cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry") tag := cmd.String("t", "", "Download tagged image in repository") @@ -766,12 +780,8 @@ func (cli *DockerCli) CmdPull(args ...string) error { return nil } - remote := cmd.Arg(0) - if strings.Contains(remote, ":") { - remoteParts := strings.Split(remote, ":") - tag = &remoteParts[1] - remote = remoteParts[0] - } + remote, parsedTag := parseRepositoryTag(cmd.Arg(0)) + *tag = parsedTag v := url.Values{} v.Set("fromImage", remote) From e7d36c9590bfdc2e57cbbecce87729f51ac3b35f Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Mon, 8 Jul 2013 17:22:41 -0700 Subject: [PATCH 37/59] It is now possible to include a ":" in a local repository name (it will not be the case for a remote name). This adds support for full qualified repository name in order to support private registry server --- tags.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/tags.go b/tags.go index d1eb36aa7..f7307ed42 100644 --- a/tags.go +++ b/tags.go @@ -221,9 +221,6 @@ func validateRepoName(name string) error { if name == "" { return fmt.Errorf("Repository name can't be empty") } - if strings.Contains(name, ":") { - return fmt.Errorf("Illegal repository name: %s", name) - } return nil } From 31c66d5a00ee693a31902d1f75777dc9507c1416 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Mon, 8 Jul 2013 17:26:50 -0700 Subject: [PATCH 38/59] Re-implemented a notion of local and private repos. This allows to consider the full qualified name of the repos as the name for the local repository without breaking the calls to the Registry API. --- server.go | 73 ++++++++++++++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/server.go b/server.go index 71bcd29ec..f535da6fc 100644 --- a/server.go +++ b/server.go @@ -351,10 +351,10 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin return nil } -func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, askedTag, indexEp string, sf *utils.StreamFormatter) error { - out.Write(sf.FormatStatus("Pulling repository %s from %s", name, indexEp)) +func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag, indexEp string, sf *utils.StreamFormatter) error { + out.Write(sf.FormatStatus("Pulling repository %s", localName)) - repoData, err := r.GetRepositoryData(indexEp, name) + repoData, err := r.GetRepositoryData(indexEp, remoteName) if err != nil { return err } @@ -366,7 +366,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, ask } utils.Debugf("Retrieving the tag list") - tagsList, err := r.GetRemoteTags(repoData.Endpoints, name, repoData.Tokens) + tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens) if err != nil { utils.Debugf("%v", err) return err @@ -390,7 +390,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, ask // Otherwise, check that the tag exists and use only that one id, exists := tagsList[askedTag] if !exists { - return fmt.Errorf("Tag %s not found in repository %s", askedTag, name) + return fmt.Errorf("Tag %s not found in repository %s", askedTag, localName) } repoData.ImgList[id].Tag = askedTag } @@ -405,7 +405,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, ask utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID) continue } - out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, name)) + out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.ID, img.Tag, localName)) success := false for _, ep := range repoData.Endpoints { if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { @@ -423,7 +423,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, name, ask if askedTag != "" && tag != askedTag { continue } - if err := srv.runtime.repositories.Set(name, tag, id, true); err != nil { + if err := srv.runtime.repositories.Set(localName, tag, id, true); err != nil { return err } } @@ -469,27 +469,26 @@ func (srv *Server) poolRemove(kind, key string) error { return nil } -func (srv *Server) ImagePull(name string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { +func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { r, err := registry.NewRegistry(srv.runtime.root, authConfig) if err != nil { return err } - if err := srv.poolAdd("pull", name+":"+tag); err != nil { + if err := srv.poolAdd("pull", localName+":"+tag); err != nil { return err } - defer srv.poolRemove("pull", name+":"+tag) + defer srv.poolRemove("pull", localName+":"+tag) // Resolve the Repository name from fqn to endpoint + name - var endpoint string - endpoint, name, err = registry.ResolveRepositoryName(name) + endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { return err } out = utils.NewWriteFlusher(out) - err = srv.pullRepository(r, out, name, tag, endpoint, sf) + err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf) if err != nil { - if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil { + if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil { return err } return nil @@ -564,7 +563,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat return imgList, nil } -func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { +func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) out.Write(sf.FormatStatus("Processing checksums")) imgList, err := srv.getImageList(localRepo) @@ -572,41 +571,36 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name stri return err } out.Write(sf.FormatStatus("Sending image list")) - srvName := name - parts := strings.Split(name, "/") - if len(parts) > 2 { - srvName = fmt.Sprintf("src/%s", url.QueryEscape(strings.Join(parts, "/"))) - } var repoData *registry.RepositoryData - repoData, err = r.PushImageJSONIndex(indexEp, name, imgList, false, nil) + repoData, err = r.PushImageJSONIndex(indexEp, remoteName, imgList, false, nil) if err != nil { return err } for _, ep := range repoData.Endpoints { - out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo))) + out.Write(sf.FormatStatus("Pushing repository %s (%d tags)", localName, len(localRepo))) // For each image within the repo, push them for _, elem := range imgList { if _, exists := repoData.ImgList[elem.ID]; exists { - out.Write(sf.FormatStatus("Image %s already on registry, skipping", name)) + out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID)) continue } else if r.LookupRemoteImage(elem.ID, ep, repoData.Tokens) { - fmt.Fprintf(out, "Image %s already on registry, skipping\n", name) + out.Write(sf.FormatStatus("Image %s already pushed, skipping", elem.ID)) continue } - if err := srv.pushImage(r, out, name, elem.ID, ep, repoData.Tokens, sf); err != nil { + if err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil { // FIXME: Continue on error? return err } - out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+srvName+"/tags/"+elem.Tag)) - if err := r.PushRegistryTag(srvName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { + out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) + if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { return err } } } - if _, err := r.PushImageJSONIndex(indexEp, name, imgList, true, repoData.Endpoints); err != nil { + if _, err := r.PushImageJSONIndex(indexEp, remoteName, imgList, true, repoData.Endpoints); err != nil { return err } @@ -634,7 +628,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, // Send the json if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil { if err == registry.ErrAlreadyExists { - out.Write(sf.FormatStatus("Image %s already uploaded ; skipping", imgData.ID)) + out.Write(sf.FormatStatus("Image %s already pushed, skipping", imgData.ID)) return nil } return err @@ -674,30 +668,31 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } // FIXME: Allow to interupt current push when new push of same image is done. -func (srv *Server) ImagePush(name string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { - if err := srv.poolAdd("push", name); err != nil { +func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error { + if err := srv.poolAdd("push", localName); err != nil { return err } - defer srv.poolRemove("push", name) + defer srv.poolRemove("push", localName) // Resolve the Repository name from fqn to endpoint + name - endpoint, name, err := registry.ResolveRepositoryName(name) + endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { return err } out = utils.NewWriteFlusher(out) - img, err := srv.runtime.graph.Get(name) + img, err := srv.runtime.graph.Get(localName) r, err2 := registry.NewRegistry(srv.runtime.root, authConfig) if err2 != nil { return err2 } if err != nil { - out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name]))) + reposLen := len(srv.runtime.repositories.Repositories[localName]) + out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", localName, reposLen)) // If it fails, try to get the repository - if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists { - if err := srv.pushRepository(r, out, name, localRepo, endpoint, sf); err != nil { + if localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists { + if err := srv.pushRepository(r, out, localName, remoteName, localRepo, endpoint, sf); err != nil { return err } return nil @@ -706,8 +701,8 @@ func (srv *Server) ImagePush(name string, out io.Writer, sf *utils.StreamFormatt } var token []string - out.Write(sf.FormatStatus("The push refers to an image: [%s]", name)) - if err := srv.pushImage(r, out, name, img.ID, endpoint, token, sf); err != nil { + out.Write(sf.FormatStatus("The push refers to an image: [%s]", localName)) + if err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil { return err } return nil From 3be7bc38e034c94da7cd6da5f2e5ecffd1832d6b Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Mon, 8 Jul 2013 17:42:18 -0700 Subject: [PATCH 39/59] Fixed typo (thanks unit tests) --- buildfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 411f44bc3..febcaca8e 100644 --- a/buildfile.go +++ b/buildfile.go @@ -52,7 +52,7 @@ func (b *buildFile) CmdFrom(name string) error { image, err := b.runtime.repositories.LookupImage(name) if err != nil { if b.runtime.graph.IsNotExist(err) { - tag, remote := parseRepositoryTag(name) + remote, tag := parseRepositoryTag(name) if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil { return err } From 9f1fc40a64fd6863dec5298099041493fee87db5 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 21 Jun 2013 19:42:17 -0700 Subject: [PATCH 40/59] * Hack: standardized docker's build environment in a Dockerfile --- Dockerfile | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..46f9b585c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# This file describes the standard way to build Docker, using docker +docker-version 0.4.2 +from ubuntu:12.04 +maintainer Solomon Hykes +# Build dependencies +run apt-get install -y -q curl +run apt-get install -y -q git +# Install Go +run curl -s https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz | tar -v -C /usr/local -xz +env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin +env GOPATH /go +env CGO_ENABLED 0 +run cd /tmp && echo 'package main' > t.go && go test -a -i -v +# Download dependencies +run PKG=github.com/kr/pty REV=27435c699; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV +run PKG=github.com/gorilla/context/ REV=708054d61e5; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV +run PKG=github.com/gorilla/mux/ REV=9b36453141c; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV +# Run dependencies +run apt-get install -y iptables +# lxc requires updating ubuntu sources +run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list +run apt-get update +run apt-get install -y lxc +run apt-get install -y aufs-tools +# Upload docker source +add . /go/src/github.com/dotcloud/docker +# Build the binary +run cd /go/src/github.com/dotcloud/docker/docker && go install -ldflags "-X main.GITCOMMIT '??' -d -w" +env PATH /usr/local/go/bin:/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin +cmd ["docker"] From 05d7f85af9e5813ddb1c066e44c802dc33790547 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 9 Jul 2013 10:55:28 +0000 Subject: [PATCH 41/59] fix typo --- commands.go | 2 +- graph.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 0bd7965f7..0add994f6 100644 --- a/commands.go +++ b/commands.go @@ -72,7 +72,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { return nil } } - help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socker to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT) + help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT) for _, command := range [][]string{ {"attach", "Attach to a running container"}, {"build", "Build a container from a Dockerfile"}, diff --git a/graph.go b/graph.go index 1417aade0..42d1bdbd4 100644 --- a/graph.go +++ b/graph.go @@ -162,7 +162,7 @@ func (graph *Graph) Register(layerData Archive, store bool, img *Image) error { // The archive is stored on disk and will be automatically deleted as soon as has been read. // If output is not nil, a human-readable progress bar will be written to it. // FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives? -func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *utils.StreamFormatter, output io.Writer) (*TempArchive, error) { +func (graph *Graph) TempLayerArchive(id string, compression Compression, sf *utils.StreamFormatter, output io.Writer) (*TempArchive, error) { image, err := graph.Get(id) if err != nil { return nil, err From 019324015b0c64733ae4f39708aa49a08134cb48 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Tue, 9 Jul 2013 08:06:10 -0700 Subject: [PATCH 42/59] Moved parseRepositoryTag to the utils package --- buildfile.go | 2 +- commands.go | 16 +--------------- utils/utils.go | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/buildfile.go b/buildfile.go index febcaca8e..6e117b19e 100644 --- a/buildfile.go +++ b/buildfile.go @@ -52,7 +52,7 @@ func (b *buildFile) CmdFrom(name string) error { image, err := b.runtime.repositories.LookupImage(name) if err != nil { if b.runtime.graph.IsNotExist(err) { - remote, tag := parseRepositoryTag(name) + remote, tag := utils.ParseRepositoryTag(name) if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil { return err } diff --git a/commands.go b/commands.go index 3134011c0..ec8615995 100644 --- a/commands.go +++ b/commands.go @@ -754,20 +754,6 @@ func (cli *DockerCli) CmdPush(args ...string) error { return nil } -// Get a repos name and returns the right reposName + tag -// The tag can be confusing because of a port in a repository name. -// Ex: localhost.localdomain:5000/samalba/hipache:latest -func parseRepositoryTag(repos string) (string, string) { - n := strings.LastIndex(repos, ":") - if n < 0 { - return repos, "" - } - if tag := repos[n+1:]; !strings.Contains(tag, "/") { - return repos[:n], tag - } - return repos, "" -} - func (cli *DockerCli) CmdPull(args ...string) error { cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry") tag := cmd.String("t", "", "Download tagged image in repository") @@ -780,7 +766,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { return nil } - remote, parsedTag := parseRepositoryTag(cmd.Arg(0)) + remote, parsedTag := utils.ParseRepositoryTag(cmd.Arg(0)) *tag = parsedTag v := url.Values{} diff --git a/utils/utils.go b/utils/utils.go index eee6685c8..df615844a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -686,3 +686,17 @@ func ParseHost(host string, port int, addr string) string { } return fmt.Sprintf("tcp://%s:%d", host, port) } + +// Get a repos name and returns the right reposName + tag +// The tag can be confusing because of a port in a repository name. +// Ex: localhost.localdomain:5000/samalba/hipache:latest +func ParseRepositoryTag(repos string) (string, string) { + n := strings.LastIndex(repos, ":") + if n < 0 { + return repos, "" + } + if tag := repos[n+1:]; !strings.Contains(tag, "/") { + return repos[:n], tag + } + return repos, "" +} From 33d97e81ebc4d25ecc7366aeef625e51aa69478c Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Tue, 9 Jul 2013 08:10:43 -0700 Subject: [PATCH 43/59] Removed DOCKER_INDEX_URL --- auth/auth.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 2e52af88d..97df928b6 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -41,9 +41,6 @@ func NewAuthConfig(username, password, email, rootPath string) *AuthConfig { } func IndexServerAddress() string { - if os.Getenv("DOCKER_INDEX_URL") != "" { - return os.Getenv("DOCKER_INDEX_URL") + "/v1/" - } return INDEXSERVER } From 91520838fc72f4004d65844b8e57df85473808a1 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 9 Jul 2013 10:48:33 -0700 Subject: [PATCH 44/59] Make sure container is not marked as ghost when it starts --- state.go | 1 + 1 file changed, 1 insertion(+) diff --git a/state.go b/state.go index 117659bf5..6480b9b42 100644 --- a/state.go +++ b/state.go @@ -29,6 +29,7 @@ func (s *State) String() string { func (s *State) setRunning(pid int) { s.Running = true + s.Ghost = false s.ExitCode = 0 s.Pid = pid s.StartedAt = time.Now() From f44eac49fae1b33e6ff4c6f42c5e7305caf22252 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Tue, 9 Jul 2013 11:30:12 -0700 Subject: [PATCH 45/59] Fixed potential security issue (never try http on official index when polling the endpoint). Also fixed local repos name when pulling index.docker.io/foo/bar --- registry/registry.go | 14 ++++++++++++-- server.go | 5 +++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index c458f616f..2f225aed9 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -18,8 +18,14 @@ import ( ) var ErrAlreadyExists = errors.New("Image already exists") +var ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") func pingRegistryEndpoint(endpoint string) error { + if endpoint == auth.IndexServerAddress() { + // Skip the check, we now this one is valid + // (and we never want to fallback to http in case of error) + return nil + } resp, err := http.Get(endpoint + "_ping") if err != nil { return err @@ -56,16 +62,20 @@ func validateRepositoryName(repositoryName string) error { // Resolves a repository name to a endpoint + name func ResolveRepositoryName(reposName string) (string, string, error) { + if strings.Contains(reposName, "://") { + // It cannot contain a scheme! + return "", "", ErrInvalidRepositoryName + } nameParts := strings.SplitN(reposName, "/", 2) if !strings.Contains(nameParts[0], ".") { // This is a Docker Index repos (ex: samalba/hipache or ubuntu) err := validateRepositoryName(reposName) - return "https://index.docker.io/v1/", reposName, err + return auth.IndexServerAddress(), reposName, err } if len(nameParts) < 2 { // There is a dot in repos name (and no registry address) // Is it a Registry address without repos name? - return "", "", fmt.Errorf("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") + return "", "", ErrInvalidRepositoryName } hostname := nameParts[0] reposName = nameParts[1] diff --git a/server.go b/server.go index f535da6fc..f1c090951 100644 --- a/server.go +++ b/server.go @@ -485,6 +485,11 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut return err } + if endpoint == auth.IndexServerAddress() { + // If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar" + localName = remoteName + } + out = utils.NewWriteFlusher(out) err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf) if err != nil { From 1a1daca621233b4588c079e35851d0e542282124 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 12 Jun 2013 16:39:49 -0700 Subject: [PATCH 46/59] Fix a typo in runtime_test.go: Availalble -> Available --- runtime_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime_test.go b/runtime_test.go index 07616ebce..400886c80 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -321,7 +321,7 @@ func TestGet(t *testing.T) { } -func findAvailalblePort(runtime *Runtime, port int) (*Container, error) { +func findAvailablePort(runtime *Runtime, port int) (*Container, error) { strPort := strconv.Itoa(port) container, err := NewBuilder(runtime).Create(&Config{ Image: GetTestImage(runtime).ID, @@ -355,7 +355,7 @@ func TestAllocatePortLocalhost(t *testing.T) { port += 1 log.Println("Trying port", port) t.Log("Trying port", port) - container, err = findAvailalblePort(runtime, port) + container, err = findAvailablePort(runtime, port) if container != nil { break } From 59b785a2820e9ff07ce2a71c83eef8b6ccc467cc Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Tue, 9 Jul 2013 16:45:32 -0700 Subject: [PATCH 47/59] Fixing missing tag field when pulling containers which does not exist --- commands.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index bce0ba549..feab55825 100644 --- a/commands.go +++ b/commands.go @@ -1242,7 +1242,9 @@ func (cli *DockerCli) CmdRun(args ...string) error { //if image not found try to pull it if statusCode == 404 { v := url.Values{} - v.Set("fromImage", config.Image) + repos, tag := utils.ParseRepositoryTag(config.Image) + v.Set("fromImage", repos) + v.Set("tag", tag) err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err) if err != nil { return err From e8db0311120d5e9a545fde5862f8a16b07760697 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Tue, 9 Jul 2013 16:46:25 -0700 Subject: [PATCH 48/59] Fixed tag parsing when the repos name contains both a port and a tag --- tags.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tags.go b/tags.go index f7307ed42..9ad9d10d0 100644 --- a/tags.go +++ b/tags.go @@ -70,11 +70,11 @@ func (store *TagStore) LookupImage(name string) (*Image, error) { if err != nil { // FIXME: standardize on returning nil when the image doesn't exist, and err for everything else // (so we can pass all errors here) - repoAndTag := strings.SplitN(name, ":", 2) - if len(repoAndTag) == 1 { - repoAndTag = append(repoAndTag, DEFAULTTAG) + repos, tag := utils.ParseRepositoryTag(name) + if tag == "" { + tag = DEFAULTTAG } - if i, err := store.GetImage(repoAndTag[0], repoAndTag[1]); err != nil { + if i, err := store.GetImage(repos, tag); err != nil { return nil, err } else if i == nil { return nil, fmt.Errorf("Image does not exist: %s", name) From 316c8328aab8f9d518574b4bef9e537fed4185f6 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Tue, 9 Jul 2013 16:46:55 -0700 Subject: [PATCH 49/59] Hardened repos name validation --- registry/registry.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/registry/registry.go b/registry/registry.go index 2f225aed9..fc84f19ec 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -67,7 +67,7 @@ func ResolveRepositoryName(reposName string) (string, string, error) { return "", "", ErrInvalidRepositoryName } nameParts := strings.SplitN(reposName, "/", 2) - if !strings.Contains(nameParts[0], ".") { + if !strings.Contains(nameParts[0], ".") && !strings.Contains(nameParts[0], ":") { // This is a Docker Index repos (ex: samalba/hipache or ubuntu) err := validateRepositoryName(reposName) return auth.IndexServerAddress(), reposName, err @@ -79,6 +79,12 @@ func ResolveRepositoryName(reposName string) (string, string, error) { } hostname := nameParts[0] reposName = nameParts[1] + if strings.Contains(hostname, "index.docker.io") { + return "", "", fmt.Errorf("Invalid repository name, try \"%s\" instead", reposName) + } + if err := validateRepositoryName(reposName); err != nil { + return "", "", err + } endpoint := fmt.Sprintf("https://%s/v1/", hostname) if err := pingRegistryEndpoint(endpoint); err != nil { utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) From a839b36e55da231874b312ee184c9d8164460bdc Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 9 Jul 2013 16:48:16 -0700 Subject: [PATCH 50/59] Fix outdated docs explaining how to setup a dev environment. Building docker with docker ftw --- docs/sources/contributing/devenvironment.rst | 71 ++++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/docs/sources/contributing/devenvironment.rst b/docs/sources/contributing/devenvironment.rst index 1f39364cb..6f5d6c1dc 100644 --- a/docs/sources/contributing/devenvironment.rst +++ b/docs/sources/contributing/devenvironment.rst @@ -5,53 +5,52 @@ Setting Up a Dev Environment ============================ -Instructions that have been verified to work on Ubuntu Precise 12.04 (LTS) (64-bit), +To make it easier to contribute to Docker, we provide a standard development environment. It is important that +the same environment be used for all tests, builds and releases. The standard development environment defines +all build dependencies: system libraries and binaries, go environment, go dependencies, etc. -Dependencies ------------- +Step 1: install docker +---------------------- -**Linux kernel 3.8** +Docker's build environment itself is a docker container, so the first step is to install docker on your system. -Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel we install comes with AUFS built in. +You can follow the `install instructions most relevant to your system `. +Make sure you have a working, up-to-date docker installation, then continue to the next step. -.. code-block:: bash +Step 2: check out the source +---------------------------- - # install the backported kernel - sudo apt-get update && sudo apt-get install linux-image-generic-lts-raring +:: - # reboot - sudo reboot - - -Installation ------------- - -.. code-block:: bash - - sudo apt-get install python-software-properties - sudo add-apt-repository ppa:gophers/go - sudo apt-get update - sudo apt-get -y install lxc xz-utils curl golang-stable git aufs-tools - - export GOPATH=~/go/ - export PATH=$GOPATH/bin:$PATH - - mkdir -p $GOPATH/src/github.com/dotcloud - cd $GOPATH/src/github.com/dotcloud - git clone git://github.com/dotcloud/docker.git + git clone http://git@github.com/dotcloud/docker cd docker - go get -v github.com/dotcloud/docker/... - go install -v github.com/dotcloud/docker/... + +Step 3: build +------------- + +When you are ready to build docker, run this command: + +:: + + docker build -t docker . + +This will build the revision currently checked out in the repository. Feel free to check out the version +of your choice. + +If the build is successful, congratulations! You have produced a clean build of docker, neatly encapsulated +in a standard build environment. + +You can run an interactive session in the newly built container: + +:: + docker run -i -t docker bash -Then run the docker daemon, +To extract the binaries from the container: -.. code-block:: bash +:: + docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build - sudo $GOPATH/bin/docker -d - - -Run the ``go install`` command (above) to recompile docker. From fac0d87d00ada08309ea3b82cae69beeef637c89 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Tue, 11 Jun 2013 15:46:23 -0700 Subject: [PATCH 51/59] Add support for UDP (closes #33) API Changes ----------- The port notation is extended to support "/udp" or "/tcp" at the *end* of the specifier string (and defaults to tcp if "/tcp" or "/udp" are missing) `docker ps` now shows UDP ports as "frontend->backend/udp". Nothing changes for TCP ports. `docker inspect` now displays two sub-dictionaries: "Tcp" and "Udp", under "PortMapping" in "NetworkSettings". Theses changes stand true for the values returned by the HTTP API too. This changeset will definitely break tools built upon the API (or upon `docker inspect`). A less intrusive way to add UDP ports in `docker inspect` would be to simply add "/udp" for UDP ports but it will still break existing applications which tries to convert the whole field to an integer. I believe that having two TCP/UDP sub-dictionaries is better because it makes the whole thing more clear and more easy to parse right away (i.e: you don't have to check the format of the string, split it and convert the right part to an integer) Code Changes ------------ Significant changes in network.go: - A second PortAllocator is instantiated for the UDP range; - PortMapper maintains separate mapping for TCP and UDP; - The extPorts array in NetworkInterface is now an array of Nat objects (so we can know on which protocol a given port was mapped when NetworkInterface.Release() is called); - TCP proxying on localhost has been moved away in network_proxy.go. localhost proxy code rewrite in network_proxy.go: We have to proxy the traffic between localhost:frontend-port and container:backend-port because Netfilter doesn't work properly on the loopback interface and DNAT iptable rules aren't applied there. - Goroutines in the TCP proxying code are now explicitly stopped when the proxy is stopped; - UDP connection tracking using a map (more infos in [1]); - Support for IPv6 (to be more accurate, the code is transparent to the Go net package, so you can use, tcp/tcp4/tcp6/udp/udp4/udp6); - Single Proxy interface for both UDP and TCP proxying; - Full test suite. [1] https://github.com/dotcloud/docker/issues/33#issuecomment-20010400 --- container.go | 17 ++- network.go | 223 +++++++++++++++++++++--------------- network_proxy.go | 257 ++++++++++++++++++++++++++++++++++++++++++ network_proxy_test.go | 221 ++++++++++++++++++++++++++++++++++++ network_test.go | 81 ++++++++++++- runtime_test.go | 136 ++++++++++++++-------- 6 files changed, 785 insertions(+), 150 deletions(-) create mode 100644 network_proxy.go create mode 100644 network_proxy_test.go diff --git a/container.go b/container.go index 52a0517af..777c057f6 100644 --- a/container.go +++ b/container.go @@ -202,20 +202,25 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, return config, hostConfig, cmd, nil } +type portMapping map[string]string + type NetworkSettings struct { IPAddress string IPPrefixLen int Gateway string Bridge string - PortMapping map[string]string + PortMapping map[string]portMapping } // String returns a human-readable description of the port mapping defined in the settings func (settings *NetworkSettings) PortMappingHuman() string { var mapping []string - for private, public := range settings.PortMapping { + for private, public := range settings.PortMapping["Tcp"] { mapping = append(mapping, fmt.Sprintf("%s->%s", public, private)) } + for private, public := range settings.PortMapping["Udp"] { + mapping = append(mapping, fmt.Sprintf("%s->%s/udp", public, private)) + } sort.Strings(mapping) return strings.Join(mapping, ", ") } @@ -688,14 +693,18 @@ func (container *Container) allocateNetwork() error { if err != nil { return err } - container.NetworkSettings.PortMapping = make(map[string]string) + container.NetworkSettings.PortMapping = make(map[string]portMapping) + container.NetworkSettings.PortMapping["Tcp"] = make(portMapping) + container.NetworkSettings.PortMapping["Udp"] = make(portMapping) for _, spec := range container.Config.PortSpecs { nat, err := iface.AllocatePort(spec) if err != nil { iface.Release() return err } - container.NetworkSettings.PortMapping[strconv.Itoa(nat.Backend)] = strconv.Itoa(nat.Frontend) + proto := strings.Title(nat.Proto) + backend, frontend := strconv.Itoa(nat.Backend), strconv.Itoa(nat.Frontend) + container.NetworkSettings.PortMapping[proto][backend] = frontend } container.network = iface container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface diff --git a/network.go b/network.go index dd79e6059..0f98c899f 100644 --- a/network.go +++ b/network.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/utils" - "io" "log" "net" "os/exec" @@ -183,8 +182,10 @@ func getIfaceAddr(name string) (net.Addr, error) { // up iptables rules. // It keeps track of all mappings and is able to unmap at will type PortMapper struct { - mapping map[int]net.TCPAddr - proxies map[int]net.Listener + tcpMapping map[int]*net.TCPAddr + tcpProxies map[int]Proxy + udpMapping map[int]*net.UDPAddr + udpProxies map[int]Proxy } func (mapper *PortMapper) cleanup() error { @@ -197,8 +198,10 @@ func (mapper *PortMapper) cleanup() error { iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER") iptables("-t", "nat", "-F", "DOCKER") iptables("-t", "nat", "-X", "DOCKER") - mapper.mapping = make(map[int]net.TCPAddr) - mapper.proxies = make(map[int]net.Listener) + mapper.tcpMapping = make(map[int]*net.TCPAddr) + mapper.tcpProxies = make(map[int]Proxy) + mapper.udpMapping = make(map[int]*net.UDPAddr) + mapper.udpProxies = make(map[int]Proxy) return nil } @@ -215,76 +218,72 @@ func (mapper *PortMapper) setup() error { return nil } -func (mapper *PortMapper) iptablesForward(rule string, port int, dest net.TCPAddr) error { - return iptables("-t", "nat", rule, "DOCKER", "-p", "tcp", "--dport", strconv.Itoa(port), - "-j", "DNAT", "--to-destination", net.JoinHostPort(dest.IP.String(), strconv.Itoa(dest.Port))) +func (mapper *PortMapper) iptablesForward(rule string, port int, proto string, dest_addr string, dest_port int) error { + return iptables("-t", "nat", rule, "DOCKER", "-p", proto, "--dport", strconv.Itoa(port), + "-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))) } -func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error { - if err := mapper.iptablesForward("-A", port, dest); err != nil { - return err +func (mapper *PortMapper) Map(port int, backendAddr net.Addr) error { + if _, isTCP := backendAddr.(*net.TCPAddr); isTCP { + backendPort := backendAddr.(*net.TCPAddr).Port + backendIP := backendAddr.(*net.TCPAddr).IP + if err := mapper.iptablesForward("-A", port, "tcp", backendIP.String(), backendPort); err != nil { + return err + } + mapper.tcpMapping[port] = backendAddr.(*net.TCPAddr) + proxy, err := NewProxy(&net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port}, backendAddr) + if err != nil { + mapper.Unmap(port, "tcp") + return err + } + mapper.tcpProxies[port] = proxy + go proxy.Run() + } else { + backendPort := backendAddr.(*net.UDPAddr).Port + backendIP := backendAddr.(*net.UDPAddr).IP + if err := mapper.iptablesForward("-A", port, "udp", backendIP.String(), backendPort); err != nil { + return err + } + mapper.udpMapping[port] = backendAddr.(*net.UDPAddr) + proxy, err := NewProxy(&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port}, backendAddr) + if err != nil { + mapper.Unmap(port, "udp") + return err + } + mapper.udpProxies[port] = proxy + go proxy.Run() } - - mapper.mapping[port] = dest - listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) - if err != nil { - mapper.Unmap(port) - return err - } - mapper.proxies[port] = listener - go proxy(listener, "tcp", dest.String()) return nil } -// proxy listens for socket connections on `listener`, and forwards them unmodified -// to `proto:address` -func proxy(listener net.Listener, proto, address string) error { - utils.Debugf("proxying to %s:%s", proto, address) - defer utils.Debugf("Done proxying to %s:%s", proto, address) - for { - utils.Debugf("Listening on %s", listener) - src, err := listener.Accept() - if err != nil { +func (mapper *PortMapper) Unmap(port int, proto string) error { + if proto == "tcp" { + backendAddr, ok := mapper.tcpMapping[port] + if !ok { + return fmt.Errorf("Port tcp/%v is not mapped", port) + } + if proxy, exists := mapper.tcpProxies[port]; exists { + proxy.Close() + delete(mapper.tcpProxies, port) + } + if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { return err } - utils.Debugf("Connecting to %s:%s", proto, address) - dst, err := net.Dial(proto, address) - if err != nil { - log.Printf("Error connecting to %s:%s: %s", proto, address, err) - src.Close() - continue + delete(mapper.tcpMapping, port) + } else { + backendAddr, ok := mapper.udpMapping[port] + if !ok { + return fmt.Errorf("Port udp/%v is not mapped", port) } - utils.Debugf("Connected to backend, splicing") - splice(src, dst) + if proxy, exists := mapper.udpProxies[port]; exists { + proxy.Close() + delete(mapper.udpProxies, port) + } + if err := mapper.iptablesForward("-D", port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { + return err + } + delete(mapper.udpMapping, port) } -} - -func halfSplice(dst, src net.Conn) error { - _, err := io.Copy(dst, src) - // FIXME: on EOF from a tcp connection, pass WriteClose() - dst.Close() - src.Close() - return err -} - -func splice(a, b net.Conn) { - go halfSplice(a, b) - go halfSplice(b, a) -} - -func (mapper *PortMapper) Unmap(port int) error { - dest, ok := mapper.mapping[port] - if !ok { - return errors.New("Port is not mapped") - } - if proxy, exists := mapper.proxies[port]; exists { - proxy.Close() - delete(mapper.proxies, port) - } - if err := mapper.iptablesForward("-D", port, dest); err != nil { - return err - } - delete(mapper.mapping, port) return nil } @@ -453,7 +452,7 @@ type NetworkInterface struct { Gateway net.IP manager *NetworkManager - extPorts []int + extPorts []*Nat } // Allocate an external TCP port and map it to the interface @@ -462,17 +461,32 @@ func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) { if err != nil { return nil, err } - // Allocate a random port if Frontend==0 - extPort, err := iface.manager.portAllocator.Acquire(nat.Frontend) - if err != nil { - return nil, err + + if nat.Proto == "tcp" { + extPort, err := iface.manager.tcpPortAllocator.Acquire(nat.Frontend) + if err != nil { + return nil, err + } + backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend} + if err := iface.manager.portMapper.Map(extPort, backend); err != nil { + iface.manager.tcpPortAllocator.Release(extPort) + return nil, err + } + nat.Frontend = extPort + } else { + extPort, err := iface.manager.udpPortAllocator.Acquire(nat.Frontend) + if err != nil { + return nil, err + } + backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: nat.Backend} + if err := iface.manager.portMapper.Map(extPort, backend); err != nil { + iface.manager.udpPortAllocator.Release(extPort) + return nil, err + } + nat.Frontend = extPort } - nat.Frontend = extPort - if err := iface.manager.portMapper.Map(nat.Frontend, net.TCPAddr{IP: iface.IPNet.IP, Port: nat.Backend}); err != nil { - iface.manager.portAllocator.Release(nat.Frontend) - return nil, err - } - iface.extPorts = append(iface.extPorts, nat.Frontend) + iface.extPorts = append(iface.extPorts, nat) + return nat, nil } @@ -485,6 +499,21 @@ type Nat struct { func parseNat(spec string) (*Nat, error) { var nat Nat + if strings.Contains(spec, "/") { + specParts := strings.Split(spec, "/") + if len(specParts) != 2 { + return nil, fmt.Errorf("Invalid port format.") + } + proto := specParts[1] + spec = specParts[0] + if proto != "tcp" && proto != "udp" { + return nil, fmt.Errorf("Invalid port format: unknown protocol %v.", proto) + } + nat.Proto = proto + } else { + nat.Proto = "tcp" + } + if strings.Contains(spec, ":") { specParts := strings.Split(spec, ":") if len(specParts) != 2 { @@ -517,20 +546,24 @@ func parseNat(spec string) (*Nat, error) { } nat.Backend = int(port) } - nat.Proto = "tcp" + return &nat, nil } // Release: Network cleanup - release all resources func (iface *NetworkInterface) Release() { - for _, port := range iface.extPorts { - if err := iface.manager.portMapper.Unmap(port); err != nil { - log.Printf("Unable to unmap port %v: %v", port, err) + for _, nat := range iface.extPorts { + utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend) + if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil { + log.Printf("Unable to unmap port %v/%v: %v", nat.Proto, nat.Frontend, err) } - if err := iface.manager.portAllocator.Release(port); err != nil { - log.Printf("Unable to release port %v: %v", port, err) + if nat.Proto == "tcp" { + if err := iface.manager.tcpPortAllocator.Release(nat.Frontend); err != nil { + log.Printf("Unable to release port tcp/%v: %v", nat.Frontend, err) + } + } else if err := iface.manager.udpPortAllocator.Release(nat.Frontend); err != nil { + log.Printf("Unable to release port udp/%v: %v", nat.Frontend, err) } - } iface.manager.ipAllocator.Release(iface.IPNet.IP) @@ -542,9 +575,10 @@ type NetworkManager struct { bridgeIface string bridgeNetwork *net.IPNet - ipAllocator *IPAllocator - portAllocator *PortAllocator - portMapper *PortMapper + ipAllocator *IPAllocator + tcpPortAllocator *PortAllocator + udpPortAllocator *PortAllocator + portMapper *PortMapper } // Allocate a network interface @@ -577,7 +611,11 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) { ipAllocator := newIPAllocator(network) - portAllocator, err := newPortAllocator() + tcpPortAllocator, err := newPortAllocator() + if err != nil { + return nil, err + } + udpPortAllocator, err := newPortAllocator() if err != nil { return nil, err } @@ -588,11 +626,12 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) { } manager := &NetworkManager{ - bridgeIface: bridgeIface, - bridgeNetwork: network, - ipAllocator: ipAllocator, - portAllocator: portAllocator, - portMapper: portMapper, + bridgeIface: bridgeIface, + bridgeNetwork: network, + ipAllocator: ipAllocator, + tcpPortAllocator: tcpPortAllocator, + udpPortAllocator: udpPortAllocator, + portMapper: portMapper, } return manager, nil } diff --git a/network_proxy.go b/network_proxy.go new file mode 100644 index 000000000..905773e53 --- /dev/null +++ b/network_proxy.go @@ -0,0 +1,257 @@ +package docker + +import ( + "encoding/binary" + "fmt" + "github.com/dotcloud/docker/utils" + "io" + "log" + "net" + "sync" + "syscall" + "time" +) + +const ( + UDPConnTrackTimeout = 90 * time.Second + UDPBufSize = 2048 +) + +type Proxy interface { + // Start forwarding traffic back and forth the front and back-end + // addresses. + Run() + // Stop forwarding traffic and close both ends of the Proxy. + Close() + // Return the address on which the proxy is listening. + FrontendAddr() net.Addr + // Return the proxied address. + BackendAddr() net.Addr +} + +type TCPProxy struct { + listener *net.TCPListener + frontendAddr *net.TCPAddr + backendAddr *net.TCPAddr +} + +func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) { + listener, err := net.ListenTCP("tcp", frontendAddr) + if err != nil { + return nil, err + } + // If the port in frontendAddr was 0 then ListenTCP will have a picked + // a port to listen on, hence the call to Addr to get that actual port: + return &TCPProxy{ + listener: listener, + frontendAddr: listener.Addr().(*net.TCPAddr), + backendAddr: backendAddr, + }, nil +} + +func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { + backend, err := net.DialTCP("tcp", nil, proxy.backendAddr) + if err != nil { + log.Printf("Can't forward traffic to backend tcp/%v: %v\n", proxy.backendAddr, err.Error()) + client.Close() + return + } + + event := make(chan int64) + var broker = func(to, from *net.TCPConn) { + written, err := io.Copy(to, from) + if err != nil { + err, ok := err.(*net.OpError) + // If the socket we are writing to is shutdown with + // SHUT_WR, forward it to the other end of the pipe: + if ok && err.Err == syscall.EPIPE { + from.CloseWrite() + } + } + event <- written + } + utils.Debugf("Forwarding traffic between tcp/%v and tcp/%v", client.RemoteAddr(), backend.RemoteAddr()) + go broker(client, backend) + go broker(backend, client) + + var transferred int64 = 0 + for i := 0; i < 2; i++ { + select { + case written := <-event: + transferred += written + case <-quit: + // Interrupt the two brokers and "join" them. + client.Close() + backend.Close() + for ; i < 2; i++ { + transferred += <-event + } + goto done + } + } + client.Close() + backend.Close() +done: + utils.Debugf("%v bytes transferred between tcp/%v and tcp/%v", transferred, client.RemoteAddr(), backend.RemoteAddr()) +} + +func (proxy *TCPProxy) Run() { + quit := make(chan bool) + defer close(quit) + utils.Debugf("Starting proxy on tcp/%v for tcp/%v", proxy.frontendAddr, proxy.backendAddr) + for { + client, err := proxy.listener.Accept() + if err != nil { + utils.Debugf("Stopping proxy on tcp/%v for tcp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error()) + return + } + go proxy.clientLoop(client.(*net.TCPConn), quit) + } +} + +func (proxy *TCPProxy) Close() { proxy.listener.Close() } +func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } +func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr } + +// A net.Addr where the IP is split into two fields so you can use it as a key +// in a map: +type connTrackKey struct { + IPHigh uint64 + IPLow uint64 + Port int +} + +func newConnTrackKey(addr *net.UDPAddr) *connTrackKey { + if len(addr.IP) == net.IPv4len { + return &connTrackKey{ + IPHigh: 0, + IPLow: uint64(binary.BigEndian.Uint32(addr.IP)), + Port: addr.Port, + } + } + return &connTrackKey{ + IPHigh: binary.BigEndian.Uint64(addr.IP[:8]), + IPLow: binary.BigEndian.Uint64(addr.IP[8:]), + Port: addr.Port, + } +} + +type connTrackMap map[connTrackKey]*net.UDPConn + +type UDPProxy struct { + listener *net.UDPConn + frontendAddr *net.UDPAddr + backendAddr *net.UDPAddr + connTrackTable connTrackMap + connTrackLock sync.Mutex +} + +func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) { + listener, err := net.ListenUDP("udp", frontendAddr) + if err != nil { + return nil, err + } + return &UDPProxy{ + listener: listener, + frontendAddr: listener.LocalAddr().(*net.UDPAddr), + backendAddr: backendAddr, + connTrackTable: make(connTrackMap), + }, nil +} + +func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) { + defer func() { + proxy.connTrackLock.Lock() + delete(proxy.connTrackTable, *clientKey) + proxy.connTrackLock.Unlock() + utils.Debugf("Done proxying between udp/%v and udp/%v", clientAddr.String(), proxy.backendAddr.String()) + proxyConn.Close() + }() + + readBuf := make([]byte, UDPBufSize) + for { + proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout)) + again: + read, err := proxyConn.Read(readBuf) + if err != nil { + if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED { + // This will happen if the last write failed + // (e.g: nothing is actually listening on the + // proxied port on the container), ignore it + // and continue until UDPConnTrackTimeout + // expires: + goto again + } + return + } + for i := 0; i != read; { + written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr) + if err != nil { + return + } + i += written + utils.Debugf("Forwarded %v/%v bytes to udp/%v", i, read, clientAddr.String()) + } + } +} + +func (proxy *UDPProxy) Run() { + readBuf := make([]byte, UDPBufSize) + utils.Debugf("Starting proxy on udp/%v for udp/%v", proxy.frontendAddr, proxy.backendAddr) + for { + read, from, err := proxy.listener.ReadFromUDP(readBuf) + if err != nil { + // NOTE: Apparently ReadFrom doesn't return + // ECONNREFUSED like Read do (see comment in + // UDPProxy.replyLoop) + utils.Debugf("Stopping proxy on udp/%v for udp/%v (%v)", proxy.frontendAddr, proxy.backendAddr, err.Error()) + break + } + + fromKey := newConnTrackKey(from) + proxy.connTrackLock.Lock() + proxyConn, hit := proxy.connTrackTable[*fromKey] + if !hit { + proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr) + if err != nil { + log.Printf("Can't proxy a datagram to udp/%s: %v\n", proxy.backendAddr.String(), err) + continue + } + proxy.connTrackTable[*fromKey] = proxyConn + go proxy.replyLoop(proxyConn, from, fromKey) + } + proxy.connTrackLock.Unlock() + for i := 0; i != read; { + written, err := proxyConn.Write(readBuf[i:read]) + if err != nil { + log.Printf("Can't proxy a datagram to udp/%s: %v\n", proxy.backendAddr.String(), err) + break + } + i += written + utils.Debugf("Forwarded %v/%v bytes to udp/%v", i, read, proxy.backendAddr.String()) + } + } +} + +func (proxy *UDPProxy) Close() { + proxy.listener.Close() + proxy.connTrackLock.Lock() + defer proxy.connTrackLock.Unlock() + for _, conn := range proxy.connTrackTable { + conn.Close() + } +} + +func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } +func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr } + +func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { + switch frontendAddr.(type) { + case *net.UDPAddr: + return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr)) + case *net.TCPAddr: + return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr)) + default: + panic(fmt.Errorf("Unsupported protocol")) + } +} diff --git a/network_proxy_test.go b/network_proxy_test.go new file mode 100644 index 000000000..c27393eb5 --- /dev/null +++ b/network_proxy_test.go @@ -0,0 +1,221 @@ +package docker + +import ( + "bytes" + "fmt" + "io" + "net" + "strings" + "testing" + "time" +) + +var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo") +var testBufSize = len(testBuf) + +type EchoServer interface { + Run() + Close() + LocalAddr() net.Addr +} + +type TCPEchoServer struct { + listener net.Listener + testCtx *testing.T +} + +type UDPEchoServer struct { + conn net.PacketConn + testCtx *testing.T +} + +func NewEchoServer(t *testing.T, proto, address string) EchoServer { + var server EchoServer + if strings.HasPrefix(proto, "tcp") { + listener, err := net.Listen(proto, address) + if err != nil { + t.Fatal(err) + } + server = &TCPEchoServer{listener: listener, testCtx: t} + } else { + socket, err := net.ListenPacket(proto, address) + if err != nil { + t.Fatal(err) + } + server = &UDPEchoServer{conn: socket, testCtx: t} + } + t.Logf("EchoServer listening on %v/%v\n", proto, server.LocalAddr().String()) + return server +} + +func (server *TCPEchoServer) Run() { + go func() { + for { + client, err := server.listener.Accept() + if err != nil { + return + } + go func(client net.Conn) { + server.testCtx.Logf("TCP client accepted on the EchoServer\n") + written, err := io.Copy(client, client) + server.testCtx.Logf("%v bytes echoed back to the client\n", written) + if err != nil { + server.testCtx.Logf("can't echo to the client: %v\n", err.Error()) + } + client.Close() + }(client) + } + }() +} + +func (server *TCPEchoServer) LocalAddr() net.Addr { return server.listener.Addr() } +func (server *TCPEchoServer) Close() { server.listener.Addr() } + +func (server *UDPEchoServer) Run() { + go func() { + readBuf := make([]byte, 1024) + for { + read, from, err := server.conn.ReadFrom(readBuf) + if err != nil { + return + } + server.testCtx.Logf("Writing UDP datagram back") + for i := 0; i != read; { + written, err := server.conn.WriteTo(readBuf[i:read], from) + if err != nil { + break + } + i += written + } + } + }() +} + +func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() } +func (server *UDPEchoServer) Close() { server.conn.Close() } + +func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string) { + defer proxy.Close() + go proxy.Run() + client, err := net.Dial(proto, addr) + if err != nil { + t.Fatalf("Can't connect to the proxy: %v", err) + } + defer client.Close() + client.SetDeadline(time.Now().Add(10 * time.Second)) + if _, err = client.Write(testBuf); err != nil { + t.Fatal(err) + } + recvBuf := make([]byte, testBufSize) + if _, err = client.Read(recvBuf); err != nil { + t.Fatal(err) + } + if !bytes.Equal(testBuf, recvBuf) { + t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) + } +} + +func testProxy(t *testing.T, proto string, proxy Proxy) { + testProxyAt(t, proto, proxy, proxy.FrontendAddr().String()) +} + +func TestTCP4Proxy(t *testing.T) { + backend := NewEchoServer(t, "tcp", "127.0.0.1:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "tcp", proxy) +} + +func TestTCP6Proxy(t *testing.T) { + backend := NewEchoServer(t, "tcp", "[::1]:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "tcp", proxy) +} + +func TestTCPDualStackProxy(t *testing.T) { + // If I understand `godoc -src net favoriteAddrFamily` (used by the + // net.Listen* functions) correctly this should work, but it doesn't. + t.Skip("No support for dual stack yet") + backend := NewEchoServer(t, "tcp", "[::1]:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + ipv4ProxyAddr := &net.TCPAddr{ + IP: net.IPv4(127, 0, 0, 1), + Port: proxy.FrontendAddr().(*net.TCPAddr).Port, + } + testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String()) +} + +func TestUDP4Proxy(t *testing.T) { + backend := NewEchoServer(t, "udp", "127.0.0.1:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "udp", proxy) +} + +func TestUDP6Proxy(t *testing.T) { + backend := NewEchoServer(t, "udp", "[::1]:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "udp", proxy) +} + +func TestUDPWriteError(t *testing.T) { + frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + // Hopefully, this port will be free: */ + backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587} + proxy, err := NewProxy(frontendAddr, backendAddr) + if err != nil { + t.Fatal(err) + } + defer proxy.Close() + go proxy.Run() + client, err := net.Dial("udp", "127.0.0.1:25587") + if err != nil { + t.Fatalf("Can't connect to the proxy: %v", err) + } + defer client.Close() + // Make sure the proxy doesn't stop when there is no actual backend: + client.Write(testBuf) + client.Write(testBuf) + backend := NewEchoServer(t, "udp", "127.0.0.1:25587") + defer backend.Close() + backend.Run() + client.SetDeadline(time.Now().Add(10 * time.Second)) + if _, err = client.Write(testBuf); err != nil { + t.Fatal(err) + } + recvBuf := make([]byte, testBufSize) + if _, err = client.Read(recvBuf); err != nil { + t.Fatal(err) + } + if !bytes.Equal(testBuf, recvBuf) { + t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) + } +} diff --git a/network_test.go b/network_test.go index 8e56c04ac..8e6eaad77 100644 --- a/network_test.go +++ b/network_test.go @@ -20,28 +20,97 @@ func TestIptables(t *testing.T) { func TestParseNat(t *testing.T) { if nat, err := parseNat("4500"); err == nil { - if nat.Frontend != 0 || nat.Backend != 4500 { - t.Errorf("-p 4500 should produce 0->4500, got %d->%d", nat.Frontend, nat.Backend) + if nat.Frontend != 0 || nat.Backend != 4500 || nat.Proto != "tcp" { + t.Errorf("-p 4500 should produce 0->4500/tcp, got %d->%d/%s", + nat.Frontend, nat.Backend, nat.Proto) } } else { t.Fatal(err) } if nat, err := parseNat(":4501"); err == nil { - if nat.Frontend != 4501 || nat.Backend != 4501 { - t.Errorf("-p :4501 should produce 4501->4501, got %d->%d", nat.Frontend, nat.Backend) + if nat.Frontend != 4501 || nat.Backend != 4501 || nat.Proto != "tcp" { + t.Errorf("-p :4501 should produce 4501->4501/tcp, got %d->%d/%s", + nat.Frontend, nat.Backend, nat.Proto) } } else { t.Fatal(err) } if nat, err := parseNat("4502:4503"); err == nil { - if nat.Frontend != 4502 || nat.Backend != 4503 { - t.Errorf("-p 4502:4503 should produce 4502->4503, got %d->%d", nat.Frontend, nat.Backend) + if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { + t.Errorf("-p 4502:4503 should produce 4502->4503/tcp, got %d->%d/%s", + nat.Frontend, nat.Backend, nat.Proto) } } else { t.Fatal(err) } + + if nat, err := parseNat("4502:4503/tcp"); err == nil { + if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "tcp" { + t.Errorf("-p 4502:4503/tcp should produce 4502->4503/tcp, got %d->%d/%s", + nat.Frontend, nat.Backend, nat.Proto) + } + } else { + t.Fatal(err) + } + + if nat, err := parseNat("4502:4503/udp"); err == nil { + if nat.Frontend != 4502 || nat.Backend != 4503 || nat.Proto != "udp" { + t.Errorf("-p 4502:4503/udp should produce 4502->4503/udp, got %d->%d/%s", + nat.Frontend, nat.Backend, nat.Proto) + } + } else { + t.Fatal(err) + } + + if nat, err := parseNat(":4503/udp"); err == nil { + if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "udp" { + t.Errorf("-p :4503/udp should produce 4503->4503/udp, got %d->%d/%s", + nat.Frontend, nat.Backend, nat.Proto) + } + } else { + t.Fatal(err) + } + + if nat, err := parseNat(":4503/tcp"); err == nil { + if nat.Frontend != 4503 || nat.Backend != 4503 || nat.Proto != "tcp" { + t.Errorf("-p :4503/tcp should produce 4503->4503/tcp, got %d->%d/%s", + nat.Frontend, nat.Backend, nat.Proto) + } + } else { + t.Fatal(err) + } + + if nat, err := parseNat("4503/tcp"); err == nil { + if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "tcp" { + t.Errorf("-p 4503/tcp should produce 0->4503/tcp, got %d->%d/%s", + nat.Frontend, nat.Backend, nat.Proto) + } + } else { + t.Fatal(err) + } + + if nat, err := parseNat("4503/udp"); err == nil { + if nat.Frontend != 0 || nat.Backend != 4503 || nat.Proto != "udp" { + t.Errorf("-p 4503/udp should produce 0->4503/udp, got %d->%d/%s", + nat.Frontend, nat.Backend, nat.Proto) + } + } else { + t.Fatal(err) + } + + if _, err := parseNat("4503/tcpgarbage"); err == nil { + t.Fatal(err) + } + + if _, err := parseNat("4503/tcp/udp"); err == nil { + t.Fatal(err) + } + + if _, err := parseNat("4503/"); err == nil { + t.Fatal(err) + } } func TestPortAllocation(t *testing.T) { diff --git a/runtime_test.go b/runtime_test.go index 400886c80..6b94e5ce2 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -1,6 +1,7 @@ package docker import ( + "bytes" "fmt" "github.com/dotcloud/docker/utils" "io" @@ -17,12 +18,12 @@ import ( ) const ( - unitTestImageName = "docker-unit-tests" - unitTestImageID = "e9aa60c60128cad1" - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var globalRuntime *Runtime @@ -321,52 +322,47 @@ func TestGet(t *testing.T) { } -func findAvailablePort(runtime *Runtime, port int) (*Container, error) { - strPort := strconv.Itoa(port) - container, err := NewBuilder(runtime).Create(&Config{ - Image: GetTestImage(runtime).ID, - Cmd: []string{"sh", "-c", "echo well hello there | nc -l -p " + strPort}, - PortSpecs: []string{strPort}, - }, - ) - if err != nil { - return nil, err - } - hostConfig := &HostConfig{} - if err := container.Start(hostConfig); err != nil { - if strings.Contains(err.Error(), "address already in use") { - return nil, nil - } - return nil, err - } - return container, nil -} - -// Run a container with a TCP port allocated, and test that it can receive connections on localhost -func TestAllocatePortLocalhost(t *testing.T) { +func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, string) { runtime, err := newTestRuntime() if err != nil { t.Fatal(err) } - port := 5554 + port := 5554 var container *Container + var strPort string for { port += 1 - log.Println("Trying port", port) - t.Log("Trying port", port) - container, err = findAvailablePort(runtime, port) + strPort = strconv.Itoa(port) + var cmd string + if proto == "tcp" { + cmd = "socat TCP-LISTEN:" + strPort + ",reuseaddr,fork EXEC:/bin/cat" + } else if proto == "udp" { + cmd = "socat UDP-RECVFROM:" + strPort + ",fork EXEC:/bin/cat" + } else { + t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) + } + t.Log("Trying port", strPort) + container, err = NewBuilder(runtime).Create(&Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"sh", "-c", cmd}, + PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, + }) if container != nil { break } if err != nil { + nuke(runtime) t.Fatal(err) } - log.Println("Port", port, "already in use") - t.Log("Port", port, "already in use") + t.Logf("Port %v already in use", strPort) } - defer container.Kill() + hostConfig := &HostConfig{} + if err := container.Start(hostConfig); err != nil { + nuke(runtime) + t.Fatal(err) + } setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() { for !container.State.Running { @@ -377,26 +373,70 @@ func TestAllocatePortLocalhost(t *testing.T) { // Even if the state is running, lets give some time to lxc to spawn the process container.WaitTimeout(500 * time.Millisecond) - conn, err := net.Dial("tcp", - fmt.Sprintf( - "localhost:%s", container.NetworkSettings.PortMapping[strconv.Itoa(port)], - ), - ) + strPort = container.NetworkSettings.PortMapping[strings.Title(proto)][strPort] + return runtime, container, strPort +} + +// Run a container with a TCP port allocated, and test that it can receive connections on localhost +func TestAllocateTCPPortLocalhost(t *testing.T) { + runtime, container, port := startEchoServerContainer(t, "tcp") + defer nuke(runtime) + defer container.Kill() + + conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", port)) if err != nil { t.Fatal(err) } defer conn.Close() - output, err := ioutil.ReadAll(conn) + + input := bytes.NewBufferString("well hello there\n") + _, err = conn.Write(input.Bytes()) if err != nil { t.Fatal(err) } - if string(output) != "well hello there\n" { - t.Fatalf("Received wrong output from network connection: should be '%s', not '%s'", - "well hello there\n", - string(output), - ) + buf := make([]byte, 16) + read := 0 + conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + read, err = conn.Read(buf) + if err != nil { + t.Fatal(err) } - container.Wait() + output := string(buf[:read]) + if !strings.Contains(output, "well hello there") { + t.Fatal(fmt.Errorf("[%v] doesn't contain [well hello there]", output)) + } +} + +// Run a container with a TCP port allocated, and test that it can receive connections on localhost +func TestAllocateUDPPortLocalhost(t *testing.T) { + runtime, container, port := startEchoServerContainer(t, "udp") + defer nuke(runtime) + defer container.Kill() + + conn, err := net.Dial("udp", fmt.Sprintf("localhost:%v", port)) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + input := bytes.NewBufferString("well hello there\n") + buf := make([]byte, 16) + for i := 0; i != 10; i++ { + _, err := conn.Write(input.Bytes()) + if err != nil { + t.Fatal(err) + } + conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond)) + read, err := conn.Read(buf) + if err == nil { + output := string(buf[:read]) + if strings.Contains(output, "well hello there") { + return + } + } + } + + t.Fatal("No reply from the container") } func TestRestore(t *testing.T) { From d918c7d9de6a2949d4010ec08f08b0182d159fe7 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 10 Jul 2013 14:09:35 +0000 Subject: [PATCH 52/59] export portmapping in network.go --- container.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/container.go b/container.go index 777c057f6..48661a309 100644 --- a/container.go +++ b/container.go @@ -202,14 +202,14 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, return config, hostConfig, cmd, nil } -type portMapping map[string]string +type PortMapping map[string]string type NetworkSettings struct { IPAddress string IPPrefixLen int Gateway string Bridge string - PortMapping map[string]portMapping + PortMapping map[string]PortMapping } // String returns a human-readable description of the port mapping defined in the settings @@ -693,9 +693,9 @@ func (container *Container) allocateNetwork() error { if err != nil { return err } - container.NetworkSettings.PortMapping = make(map[string]portMapping) - container.NetworkSettings.PortMapping["Tcp"] = make(portMapping) - container.NetworkSettings.PortMapping["Udp"] = make(portMapping) + container.NetworkSettings.PortMapping = make(map[string]PortMapping) + container.NetworkSettings.PortMapping["Tcp"] = make(PortMapping) + container.NetworkSettings.PortMapping["Udp"] = make(PortMapping) for _, spec := range container.Config.PortSpecs { nat, err := iface.AllocatePort(spec) if err != nil { From eb9fef2c424178277cacd29d404cc8e774bb3891 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 3 Jul 2013 17:33:30 -0900 Subject: [PATCH 53/59] Add VOLUME instruction to buildfile --- buildfile.go | 21 +++++++++++++++++++++ buildfile_test.go | 9 +++++++++ docs/sources/use/builder.rst | 7 +++++++ utils.go | 3 +++ 4 files changed, 40 insertions(+) diff --git a/buildfile.go b/buildfile.go index a13dc82c5..38e6c330d 100644 --- a/buildfile.go +++ b/buildfile.go @@ -173,6 +173,27 @@ func (b *buildFile) CmdEntrypoint(args string) error { return nil } +func (b *buildFile) CmdVolume(args string) error { + if args == "" { + return fmt.Errorf("Volume cannot be empty") + } + + var volume []string + if err := json.Unmarshal([]byte(args), &volume); err != nil { + volume = []string{args} + } + if b.config.Volumes == nil { + b.config.Volumes = NewPathOpts() + } + for _, v := range volume { + b.config.Volumes[v] = struct{}{} + } + if err := b.commit("", b.config.Cmd, fmt.Sprintf("VOLUME %s", args)); err != nil { + return err + } + return nil +} + func (b *buildFile) addRemote(container *Container, orig, dest string) error { file, err := utils.Download(orig, ioutil.Discard) if err != nil { diff --git a/buildfile_test.go b/buildfile_test.go index 8913284e8..9f5c692cf 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -87,6 +87,15 @@ run [ "$FOO" = "BAR" ] from %s ENTRYPOINT /bin/echo CMD Hello world +`, + nil, + }, + + { + ` +from docker-ut +VOLUME /test +CMD Hello world `, nil, }, diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 6a12beadf..ab416281b 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -160,6 +160,13 @@ files and directories are created with mode 0700, uid and gid 0. The `ENTRYPOINT` instruction adds an entry command that will not be overwritten when arguments are passed to docker run, unlike the behavior of `CMD`. This allows arguments to be passed to the entrypoint. i.e. `docker run -d` will pass the "-d" argument to the entrypoint. +2.9 VOLUME +---------- + + ``VOLUME ["/data"]`` + +The `VOLUME` instruction will add one or more new volumes to any container created from the image. + 3. Dockerfile Examples ====================== diff --git a/utils.go b/utils.go index 103e76282..caef08628 100644 --- a/utils.go +++ b/utils.go @@ -89,4 +89,7 @@ func MergeConfig(userConf, imageConf *Config) { if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 { userConf.Entrypoint = imageConf.Entrypoint } + if userConf.Volumes == nil || len(userConf.Volumes) == 0 { + userConf.Volumes = imageConf.Volumes + } } From 1267e15b0f73f4c40b3a053f0bf02981881a2bdd Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 8 Jul 2013 04:02:06 -0900 Subject: [PATCH 54/59] Add unittest for volume config verification --- buildfile_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/buildfile_test.go b/buildfile_test.go index 9f5c692cf..89c0916ad 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -123,3 +123,40 @@ func TestBuild(t *testing.T) { } } } + +func TestVolume(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{ + runtime: runtime, + lock: &sync.Mutex{}, + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } + + buildfile := NewBuildFile(srv, ioutil.Discard) + imgId, err := buildfile.Build(mkTestContext(` +from docker-ut +VOLUME /test +CMD Hello world +`, nil, t)) + if err != nil { + t.Fatal(err) + } + img, err := srv.ImageInspect(imgId) + if err != nil { + t.Fatal(err) + } + if len(img.Config.Volumes) == 0 { + t.Fail() + } + for key, _ := range img.Config.Volumes { + if key != "/test" { + t.Fail() + } + } +} From 40f1e4edbecc841bd02b3a63b303d522299562c0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 10 Jul 2013 07:03:07 -0900 Subject: [PATCH 55/59] Rebased changes buildfile_test --- buildfile_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/buildfile_test.go b/buildfile_test.go index 89c0916ad..9250f7376 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -93,7 +93,7 @@ CMD Hello world { ` -from docker-ut +from %s VOLUME /test CMD Hello world `, @@ -133,14 +133,13 @@ func TestVolume(t *testing.T) { srv := &Server{ runtime: runtime, - lock: &sync.Mutex{}, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } buildfile := NewBuildFile(srv, ioutil.Discard) imgId, err := buildfile.Build(mkTestContext(` -from docker-ut +from %s VOLUME /test CMD Hello world `, nil, t)) From 8f36467107d623e94638e2dd4c625e34f670384d Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 10 Jul 2013 16:05:14 -0700 Subject: [PATCH 56/59] Raise the timeouts for the TCP/UDP localhost proxy tests Sometimes these tests fail, let's see if that improves the situation. --- runtime_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime_test.go b/runtime_test.go index 6b94e5ce2..6e64d9e39 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -396,7 +396,7 @@ func TestAllocateTCPPortLocalhost(t *testing.T) { } buf := make([]byte, 16) read := 0 - conn.SetReadDeadline(time.Now().Add(2 * time.Second)) + conn.SetReadDeadline(time.Now().Add(4 * time.Second)) read, err = conn.Read(buf) if err != nil { t.Fatal(err) @@ -421,7 +421,7 @@ func TestAllocateUDPPortLocalhost(t *testing.T) { input := bytes.NewBufferString("well hello there\n") buf := make([]byte, 16) - for i := 0; i != 10; i++ { + for i := 0; i != 20; i++ { _, err := conn.Write(input.Bytes()) if err != nil { t.Fatal(err) From f83c31e18834d57ec56e1249b80174fbe0a0f8c9 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 10 Jul 2013 16:00:35 -0700 Subject: [PATCH 57/59] Packaging, issue #960: Document PUBLISH_PPA for staging/production release --- hack/dockerbuilder/Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hack/dockerbuilder/Dockerfile b/hack/dockerbuilder/Dockerfile index f377f0be7..60cd93b17 100644 --- a/hack/dockerbuilder/Dockerfile +++ b/hack/dockerbuilder/Dockerfile @@ -5,8 +5,11 @@ # AUTHOR Solomon Hykes # Daniel Mizyrycki # BUILD_CMD docker build -t dockerbuilder . -# RUN_CMD docker run -e AWS_ID="$AWS_ID" -e AWS_KEY="$AWS_KEY" -e GPG_KEY="$GPG_KEY" dockerbuilder +# RUN_CMD docker run -e AWS_ID="$AWS_ID" -e AWS_KEY="$AWS_KEY" -e GPG_KEY="$GPG_KEY" -e PUBLISH_PPA="$PUBLISH_PPA" dockerbuilder # +# ENV_VARIABLES AWS_ID, AWS_KEY: S3 credentials for uploading Docker binary and tarball +# GPG_KEY: Signing key for docker package +# PUBLISH_PPA: 0 for staging release, 1 for production release # from ubuntu:12.04 maintainer Solomon Hykes From 5a411fa38e49e5d79602f01fa9aaf058c12e5627 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Wed, 10 Jul 2013 18:02:41 -0700 Subject: [PATCH 58/59] Make the TestAllocate{UDP,TCP}PortLocalhost more reliable - For the TCP test try again if socat wasn't listening yet; - For the UDP test raise the timeout to a minute to workaround what seems to be an issue with Linux. --- runtime_test.go | 69 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/runtime_test.go b/runtime_test.go index 6e64d9e39..d8033467c 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -383,31 +383,50 @@ func TestAllocateTCPPortLocalhost(t *testing.T) { defer nuke(runtime) defer container.Kill() - conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", port)) - if err != nil { - t.Fatal(err) - } - defer conn.Close() + for i := 0; i != 10; i++ { + conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", port)) + if err != nil { + t.Fatal(err) + } + defer conn.Close() - input := bytes.NewBufferString("well hello there\n") - _, err = conn.Write(input.Bytes()) - if err != nil { - t.Fatal(err) - } - buf := make([]byte, 16) - read := 0 - conn.SetReadDeadline(time.Now().Add(4 * time.Second)) - read, err = conn.Read(buf) - if err != nil { - t.Fatal(err) - } - output := string(buf[:read]) - if !strings.Contains(output, "well hello there") { - t.Fatal(fmt.Errorf("[%v] doesn't contain [well hello there]", output)) + input := bytes.NewBufferString("well hello there\n") + _, err = conn.Write(input.Bytes()) + if err != nil { + t.Fatal(err) + } + buf := make([]byte, 16) + read := 0 + conn.SetReadDeadline(time.Now().Add(3 * time.Second)) + read, err = conn.Read(buf) + if err != nil { + if err, ok := err.(*net.OpError); ok { + if err.Err == syscall.ECONNRESET { + t.Logf("Connection reset by the proxy, socat is probably not listening yet, trying again in a sec") + conn.Close() + time.Sleep(time.Second) + continue + } + if err.Timeout() { + t.Log("Timeout, trying again") + conn.Close() + continue + } + } + t.Fatal(err) + } + output := string(buf[:read]) + if !strings.Contains(output, "well hello there") { + t.Fatal(fmt.Errorf("[%v] doesn't contain [well hello there]", output)) + } else { + return + } } + + t.Fatal("No reply from the container") } -// Run a container with a TCP port allocated, and test that it can receive connections on localhost +// Run a container with an UDP port allocated, and test that it can receive connections on localhost func TestAllocateUDPPortLocalhost(t *testing.T) { runtime, container, port := startEchoServerContainer(t, "udp") defer nuke(runtime) @@ -421,12 +440,16 @@ func TestAllocateUDPPortLocalhost(t *testing.T) { input := bytes.NewBufferString("well hello there\n") buf := make([]byte, 16) - for i := 0; i != 20; i++ { + // Try for a minute, for some reason the select in socat may take ages + // to return even though everything on the path seems fine (i.e: the + // UDPProxy forwards the traffic correctly and you can see the packets + // on the interface from within the container). + for i := 0; i != 120; i++ { _, err := conn.Write(input.Bytes()) if err != nil { t.Fatal(err) } - conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond)) + conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) read, err := conn.Read(buf) if err == nil { output := string(buf[:read]) From 71d2ff494694d7f18310c7994daa34dce33af98b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 11 Jul 2013 17:31:07 -0700 Subject: [PATCH 59/59] Hotfix: check the length of entrypoint before comparing. --- utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils.go b/utils.go index caef08628..d55b1ff0b 100644 --- a/utils.go +++ b/utils.go @@ -20,7 +20,8 @@ func CompareConfig(a, b *Config) bool { if len(a.Cmd) != len(b.Cmd) || len(a.Dns) != len(b.Dns) || len(a.Env) != len(b.Env) || - len(a.PortSpecs) != len(b.PortSpecs) { + len(a.PortSpecs) != len(b.PortSpecs) || + len(a.Entrypoint) != len(b.Entrypoint) { return false }