Просмотр исходного кода

use http://get.docker.io/latest

Victor Vieux 12 лет назад
Родитель
Сommit
e9011122fb
38 измененных файлов с 1431 добавлено и 671 удалено
  1. 30 0
      Dockerfile
  2. 9 9
      README.md
  3. 5 7
      api.go
  4. 26 86
      api_test.go
  5. 15 12
      archive.go
  6. 4 4
      archive_test.go
  7. 4 7
      auth/auth.go
  8. 1 1
      auth/auth_test.go
  9. 55 12
      buildfile.go
  10. 48 5
      buildfile_test.go
  11. 42 43
      commands.go
  12. 5 4
      commands_test.go
  13. 21 12
      container.go
  14. 1 1
      docker/docker.go
  15. 29 30
      docs/sources/contributing/devenvironment.rst
  16. 7 0
      docs/sources/use/builder.rst
  17. 1 2
      graph.go
  18. 4 1
      hack/dockerbuilder/Dockerfile
  19. 3 0
      image.go
  20. 135 96
      network.go
  21. 257 0
      network_proxy.go
  22. 221 0
      network_proxy_test.go
  23. 75 6
      network_test.go
  24. 107 66
      registry/registry.go
  25. 0 3
      runtime.go
  26. 125 59
      runtime_test.go
  27. 94 125
      server.go
  28. 3 3
      server_test.go
  29. 2 13
      state.go
  30. 7 10
      tags.go
  31. 2 2
      tags_test.go
  32. 1 1
      term/term.go
  33. 10 0
      testing/README.rst
  34. 3 1
      testing/Vagrantfile
  35. 30 16
      testing/buildbot/master.cfg
  36. 12 2
      testing/buildbot/setup.sh
  37. 5 4
      utils.go
  38. 32 28
      utils/utils.go

+ 30 - 0
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 <solomon@dotcloud.com>
+# 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"]

+ 9 - 9
README.md

@@ -23,15 +23,15 @@ happens, for a few reasons:
 
 
   * *Size*: VMs are very large which makes them impractical to store and transfer.
   * *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
   * *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.
   * *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:
   * *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,
 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
 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),
 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
 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,
 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:
 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,
   * *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 -
   * 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
 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.
 *replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers.
 
 

+ 5 - 7
api.go

@@ -170,7 +170,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r
 	name := vars["name"]
 	name := vars["name"]
 
 
 	if err := srv.ContainerExport(name, w); err != nil {
 	if err := srv.ContainerExport(name, w); err != nil {
-		utils.Debugf("%s", err.Error())
+		utils.Debugf("%s", err)
 		return err
 		return err
 	}
 	}
 	return nil
 	return nil
@@ -306,7 +306,7 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req
 	}
 	}
 	config := &Config{}
 	config := &Config{}
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
-		utils.Debugf("%s", err.Error())
+		utils.Debugf("%s", err)
 	}
 	}
 	repo := r.Form.Get("repo")
 	repo := r.Form.Get("repo")
 	tag := r.Form.Get("tag")
 	tag := r.Form.Get("tag")
@@ -342,8 +342,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
 	}
 	}
 	sf := utils.NewStreamFormatter(version > 1.0)
 	sf := utils.NewStreamFormatter(version > 1.0)
 	if image != "" { //pull
 	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() {
 			if sf.Used() {
 				w.Write(sf.FormatError(err))
 				w.Write(sf.FormatError(err))
 				return nil
 				return nil
@@ -426,7 +425,6 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
 	if err := parseForm(r); err != nil {
 	if err := parseForm(r); err != nil {
 		return err
 		return err
 	}
 	}
-	registry := r.Form.Get("registry")
 
 
 	if vars == nil {
 	if vars == nil {
 		return fmt.Errorf("Missing parameter")
 		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")
 		w.Header().Set("Content-Type", "application/json")
 	}
 	}
 	sf := utils.NewStreamFormatter(version > 1.0)
 	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() {
 		if sf.Used() {
 			w.Write(sf.FormatError(err))
 			w.Write(sf.FormatError(err))
 			return nil
 			return nil
@@ -880,7 +878,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 			localMethod := method
 			localMethod := method
 			localFct := fct
 			localFct := fct
 			f := func(w http.ResponseWriter, r *http.Request) {
 			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 {
 				if logging {
 					log.Println(r.Method, r.RequestURI)
 					log.Println(r.Method, r.RequestURI)

+ 26 - 86
api_test.go

@@ -5,7 +5,6 @@ import (
 	"bufio"
 	"bufio"
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
-	"github.com/dotcloud/docker/auth"
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io"
 	"net"
 	"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) {
 func TestGetVersion(t *testing.T) {
 	runtime, err := newTestRuntime()
 	runtime, err := newTestRuntime()
 	if err != nil {
 	if err != nil {
@@ -99,7 +60,7 @@ func TestGetVersion(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if v.Version != VERSION {
 	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 +90,7 @@ func TestGetInfo(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if infos.Images != len(initialImages) {
 	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 +127,7 @@ func TestGetImagesJSON(t *testing.T) {
 	}
 	}
 
 
 	if len(images) != len(initialImages) {
 	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
 	found := false
@@ -177,7 +138,7 @@ func TestGetImagesJSON(t *testing.T) {
 		}
 		}
 	}
 	}
 	if !found {
 	if !found {
-		t.Errorf("Excepted image %s, %+v found", unitTestImageName, images)
+		t.Errorf("Expected image %s, %+v found", unitTestImageName, images)
 	}
 	}
 
 
 	r2 := httptest.NewRecorder()
 	r2 := httptest.NewRecorder()
@@ -204,7 +165,7 @@ func TestGetImagesJSON(t *testing.T) {
 	}
 	}
 
 
 	if len(images2) != len(initialImages) {
 	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
 	found = false
@@ -236,7 +197,7 @@ func TestGetImagesJSON(t *testing.T) {
 	}
 	}
 
 
 	if len(images3) != 0 {
 	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()
 	r4 := httptest.NewRecorder()
@@ -282,38 +243,7 @@ func TestGetImagesViz(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if line != "digraph docker {\n" {
 	if line != "digraph docker {\n" {
-		t.Errorf("Excepted digraph docker {\n, %s found", line)
-	}
-}
-
-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("Excepted at least 2 lines, %d found", len(results))
+		t.Errorf("Expected digraph docker {\n, %s found", line)
 	}
 	}
 }
 }
 
 
@@ -337,7 +267,7 @@ func TestGetImagesHistory(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if len(history) != 1 {
 	if len(history) != 1 {
-		t.Errorf("Excepted 1 line, %d found", len(history))
+		t.Errorf("Expected 1 line, %d found", len(history))
 	}
 	}
 }
 }
 
 
@@ -359,7 +289,7 @@ func TestGetImagesByName(t *testing.T) {
 	if err := json.Unmarshal(r.Body.Bytes(), img); err != nil {
 	if err := json.Unmarshal(r.Body.Bytes(), img); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	if img.ID != unitTestImageId {
+	if img.ID != unitTestImageID {
 		t.Errorf("Error inspecting image")
 		t.Errorf("Error inspecting image")
 	}
 	}
 }
 }
@@ -396,7 +326,7 @@ func TestGetContainersJSON(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	if len(containers) != 1 {
 	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 {
 	if containers[0].ID != container.ID {
 		t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID)
 		t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", container.ID, containers[0].ID)
@@ -1356,24 +1286,34 @@ func TestDeleteImages(t *testing.T) {
 	}
 	}
 
 
 	if len(images) != len(initialImages)+1 {
 	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)
+	req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
 	r := httptest.NewRecorder()
 	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)
 		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)
 		t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
 	}
 	}
 
 
 	var outs []APIRmi
 	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)
 		t.Fatal(err)
 	}
 	}
 	if len(outs) != 1 {
 	if len(outs) != 1 {
@@ -1385,7 +1325,7 @@ func TestDeleteImages(t *testing.T) {
 	}
 	}
 
 
 	if len(images) != len(initialImages) {
 	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 {
 	/*	if c := runtime.Get(container.Id); c != nil {

+ 15 - 12
archive.go

@@ -2,9 +2,7 @@ package docker
 
 
 import (
 import (
 	"archive/tar"
 	"archive/tar"
-	"bufio"
 	"bytes"
 	"bytes"
-	"errors"
 	"fmt"
 	"fmt"
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io"
@@ -27,10 +25,6 @@ const (
 )
 )
 
 
 func DetectCompression(source []byte) Compression {
 func DetectCompression(source []byte) Compression {
-	for _, c := range source[:10] {
-		utils.Debugf("%x", c)
-	}
-
 	sourceLen := len(source)
 	sourceLen := len(source)
 	for compression, m := range map[Compression][]byte{
 	for compression, m := range map[Compression][]byte{
 		Bzip2: {0x42, 0x5A, 0x68},
 		Bzip2: {0x42, 0x5A, 0x68},
@@ -111,17 +105,26 @@ func Untar(archive io.Reader, path string) error {
 	if archive == nil {
 	if archive == nil {
 		return fmt.Errorf("Empty archive")
 		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)
 	compression := DetectCompression(buf)
 
 
 	utils.Debugf("Archive compression detected: %s", compression.Extension())
 	utils.Debugf("Archive compression detected: %s", compression.Extension())
 
 
 	cmd := exec.Command("tar", "--numeric-owner", "-f", "-", "-C", path, "-x"+compression.Flag())
 	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.
 	// Hardcode locale environment for predictable outcome regardless of host configuration.
 	//   (see https://github.com/dotcloud/docker/issues/355)
 	//   (see https://github.com/dotcloud/docker/issues/355)
 	cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
 	cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
@@ -251,7 +254,7 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
 		}
 		}
 		errText := <-errChan
 		errText := <-errChan
 		if err := cmd.Wait(); err != nil {
 		if err := cmd.Wait(); err != nil {
-			pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
+			pipeW.CloseWithError(fmt.Errorf("%s: %s", err, errText))
 		} else {
 		} else {
 			pipeW.Close()
 			pipeW.Close()
 		}
 		}

+ 4 - 4
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")
 	cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
 	out, err := CmdStream(cmd)
 	out, err := CmdStream(cmd)
 	if err != nil {
 	if err != nil {
-		t.Fatalf("Failed to start command: " + err.Error())
+		t.Fatalf("Failed to start command: %s", err)
 	}
 	}
 	errCh := make(chan error)
 	errCh := make(chan error)
 	go func() {
 	go func() {
@@ -26,7 +26,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
 	select {
 	select {
 	case err := <-errCh:
 	case err := <-errCh:
 		if err != nil {
 		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):
 	case <-time.After(5 * time.Second):
 		t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
 		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")
 	badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
 	out, err := CmdStream(badCmd)
 	out, err := CmdStream(badCmd)
 	if err != nil {
 	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 {
 	if output, err := ioutil.ReadAll(out); err == nil {
 		t.Fatalf("Command should have failed")
 		t.Fatalf("Command should have failed")
 	} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
 	} 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" {
 	} else if s := string(output); s != "hello\n" {
 		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
 		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
 	}
 	}

+ 4 - 7
auth/auth.go

@@ -15,8 +15,8 @@ import (
 // Where we store the config file
 // Where we store the config file
 const CONFIGFILE = ".dockercfg"
 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/"
 //const INDEXSERVER = "http://indexstaging-docker.dotcloud.com/"
 
 
@@ -41,9 +41,6 @@ func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
 }
 }
 
 
 func IndexServerAddress() string {
 func IndexServerAddress() string {
-	if os.Getenv("DOCKER_INDEX_URL") != "" {
-		return os.Getenv("DOCKER_INDEX_URL") + "/v1"
-	}
 	return INDEXSERVER
 	return INDEXSERVER
 }
 }
 
 
@@ -132,7 +129,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
 
 
 	// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
 	// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
 	b := strings.NewReader(string(jsonBody))
 	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 {
 	if err != nil {
 		return "", fmt.Errorf("Server Error: %s", err)
 		return "", fmt.Errorf("Server Error: %s", err)
 	}
 	}
@@ -152,7 +149,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
 			"Please check your e-mail for a confirmation link.")
 			"Please check your e-mail for a confirmation link.")
 	} else if reqStatusCode == 400 {
 	} else if reqStatusCode == 400 {
 		if string(reqBody) == "\"Username or email already exists\"" {
 		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)
 			req.SetBasicAuth(authConfig.Username, authConfig.Password)
 			resp, err := client.Do(req)
 			resp, err := client.Do(req)
 			if err != nil {
 			if err != nil {

+ 1 - 1
auth/auth_test.go

@@ -68,6 +68,6 @@ func TestCreateAccount(t *testing.T) {
 	expectedError := "Login: Account is not Active"
 	expectedError := "Login: Account is not Active"
 
 
 	if !strings.Contains(err.Error(), expectedError) {
 	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)
 	}
 	}
 }
 }

+ 55 - 12
buildfile.go

@@ -29,6 +29,7 @@ type buildFile struct {
 	config     *Config
 	config     *Config
 	context    string
 	context    string
 
 
+	lastContainer *Container
 	tmpContainers map[string]struct{}
 	tmpContainers map[string]struct{}
 	tmpImages     map[string]struct{}
 	tmpImages     map[string]struct{}
 
 
@@ -51,20 +52,10 @@ func (b *buildFile) CmdFrom(name string) error {
 	image, err := b.runtime.repositories.LookupImage(name)
 	image, err := b.runtime.repositories.LookupImage(name)
 	if err != nil {
 	if err != nil {
 		if b.runtime.graph.IsNotExist(err) {
 		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
-			}
-
-			if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false), nil); err != nil {
+			remote, tag := utils.ParseRepositoryTag(name)
+			if err := b.srv.ImagePull(remote, tag, b.out, utils.NewStreamFormatter(false), nil); err != nil {
 				return err
 				return err
 			}
 			}
-
 			image, err = b.runtime.repositories.LookupImage(name)
 			image, err = b.runtime.repositories.LookupImage(name)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
@@ -182,6 +173,27 @@ func (b *buildFile) CmdEntrypoint(args string) error {
 	return nil
 	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 {
 func (b *buildFile) addRemote(container *Container, orig, dest string) error {
 	file, err := utils.Download(orig, ioutil.Discard)
 	file, err := utils.Download(orig, ioutil.Discard)
 	if err != nil {
 	if err != nil {
@@ -242,6 +254,7 @@ func (b *buildFile) CmdAdd(args string) error {
 		return err
 		return err
 	}
 	}
 	b.tmpContainers[container.ID] = struct{}{}
 	b.tmpContainers[container.ID] = struct{}{}
+	b.lastContainer = container
 
 
 	if err := container.EnsureMounted(); err != nil {
 	if err := container.EnsureMounted(); err != nil {
 		return err
 		return err
@@ -277,8 +290,13 @@ func (b *buildFile) run() (string, error) {
 		return "", err
 		return "", err
 	}
 	}
 	b.tmpContainers[c.ID] = struct{}{}
 	b.tmpContainers[c.ID] = struct{}{}
+	b.lastContainer = c
 	fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
 	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
 	//start the container
 	hostConfig := &HostConfig{}
 	hostConfig := &HostConfig{}
 	if err := c.Start(hostConfig); err != nil {
 	if err := c.Start(hostConfig); err != nil {
@@ -319,6 +337,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
 			return err
 			return err
 		}
 		}
 		b.tmpContainers[container.ID] = struct{}{}
 		b.tmpContainers[container.ID] = struct{}{}
+		b.lastContainer = container
 		fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
 		fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
 		id = container.ID
 		id = container.ID
 		if err := container.EnsureMounted(); err != nil {
 		if err := container.EnsureMounted(); err != nil {
@@ -346,6 +365,29 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
 }
 }
 
 
 func (b *buildFile) Build(context io.Reader) (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 any reason for using /tmp instead of ""?
 	// FIXME: @creack "name" is a terrible variable name
 	// FIXME: @creack "name" is a terrible variable name
 	name, err := ioutil.TempDir("/tmp", "docker-build")
 	name, err := ioutil.TempDir("/tmp", "docker-build")
@@ -398,6 +440,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
 			return "", ret.(error)
 			return "", ret.(error)
 		}
 		}
 
 
+		b.lastContainer = nil
 		fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image))
 		fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image))
 	}
 	}
 	if b.image != "" {
 	if b.image != "" {

+ 48 - 5
buildfile_test.go

@@ -3,14 +3,13 @@ package docker
 import (
 import (
 	"fmt"
 	"fmt"
 	"io/ioutil"
 	"io/ioutil"
-	"sync"
 	"testing"
 	"testing"
 )
 )
 
 
 // mkTestContext generates a build context from the contents of the provided dockerfile.
 // mkTestContext generates a build context from the contents of the provided dockerfile.
 // This context is suitable for use as an argument to BuildFile.Build()
 // This context is suitable for use as an argument to BuildFile.Build()
 func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
 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 {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -27,7 +26,7 @@ type testContextTemplate struct {
 
 
 // A table of all the contexts to build and test.
 // A table of all the contexts to build and test.
 // A new docker runtime will be created and torn down for each context.
 // A new docker runtime will be created and torn down for each context.
-var testContexts []testContextTemplate = []testContextTemplate{
+var testContexts = []testContextTemplate{
 	{
 	{
 		`
 		`
 from   %s
 from   %s
@@ -85,9 +84,18 @@ run    [ "$FOO" = "BAR" ]
 
 
 	{
 	{
 		`
 		`
-from docker-ut
+from %s
 ENTRYPOINT /bin/echo
 ENTRYPOINT /bin/echo
 CMD Hello world
 CMD Hello world
+`,
+		nil,
+	},
+
+	{
+		`
+from %s
+VOLUME /test
+CMD Hello world
 `,
 `,
 		nil,
 		nil,
 	},
 	},
@@ -105,7 +113,6 @@ func TestBuild(t *testing.T) {
 
 
 		srv := &Server{
 		srv := &Server{
 			runtime:     runtime,
 			runtime:     runtime,
-			lock:        &sync.Mutex{},
 			pullingPool: make(map[string]struct{}),
 			pullingPool: make(map[string]struct{}),
 			pushingPool: make(map[string]struct{}),
 			pushingPool: make(map[string]struct{}),
 		}
 		}
@@ -116,3 +123,39 @@ 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,
+		pullingPool: make(map[string]struct{}),
+		pushingPool: make(map[string]struct{}),
+	}
+
+	buildfile := NewBuildFile(srv, ioutil.Discard)
+	imgId, err := buildfile.Build(mkTestContext(`
+from %s
+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()
+		}
+	}
+}

+ 42 - 43
commands.go

@@ -19,7 +19,6 @@ import (
 	"os/signal"
 	"os/signal"
 	"path/filepath"
 	"path/filepath"
 	"reflect"
 	"reflect"
-	"regexp"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 	"syscall"
 	"syscall"
@@ -73,7 +72,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
 			return nil
 			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{
 	for _, command := range [][]string{
 		{"attach", "Attach to a running container"},
 		{"attach", "Attach to a running container"},
 		{"build", "Build a container from a Dockerfile"},
 		{"build", "Build a container from a Dockerfile"},
@@ -730,7 +729,6 @@ func (cli *DockerCli) CmdImport(args ...string) error {
 
 
 func (cli *DockerCli) CmdPush(args ...string) error {
 func (cli *DockerCli) CmdPush(args ...string) error {
 	cmd := Subcmd("push", "[OPTION] NAME", "Push an image or a repository to the registry")
 	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 {
 	if err := cmd.Parse(args); err != nil {
 		return nil
 		return nil
 	}
 	}
@@ -741,28 +739,16 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 		return nil
 		return nil
 	}
 	}
 
 
-	if err := cli.checkIfLogged(*registry == "", "push"); err != nil {
+	if err := cli.checkIfLogged("push"); err != nil {
 		return err
 		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 <user>/<repo> (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 <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name)
 	}
 	}
 
 
 	buf, err := json.Marshal(cli.authConfig)
 	buf, err := json.Marshal(cli.authConfig)
@@ -771,7 +757,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 	}
 	}
 
 
 	v := url.Values{}
 	v := url.Values{}
-	v.Set("registry", *registry)
 	if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil {
 	if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out); err != nil {
 		return err
 		return err
 	}
 	}
@@ -781,7 +766,6 @@ func (cli *DockerCli) CmdPush(args ...string) error {
 func (cli *DockerCli) CmdPull(args ...string) error {
 func (cli *DockerCli) CmdPull(args ...string) error {
 	cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
 	cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
 	tag := cmd.String("t", "", "Download tagged image in repository")
 	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 {
 	if err := cmd.Parse(args); err != nil {
 		return nil
 		return nil
 	}
 	}
@@ -791,17 +775,12 @@ func (cli *DockerCli) CmdPull(args ...string) error {
 		return nil
 		return nil
 	}
 	}
 
 
-	remote := cmd.Arg(0)
-	if strings.Contains(remote, ":") {
-		remoteParts := strings.Split(remote, ":")
-		tag = &remoteParts[1]
-		remote = remoteParts[0]
-	}
+	remote, parsedTag := utils.ParseRepositoryTag(cmd.Arg(0))
+	*tag = parsedTag
 
 
 	v := url.Values{}
 	v := url.Values{}
 	v.Set("fromImage", remote)
 	v.Set("fromImage", remote)
 	v.Set("tag", *tag)
 	v.Set("tag", *tag)
-	v.Set("registry", *registry)
 
 
 	if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out); err != nil {
 	if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out); err != nil {
 		return err
 		return err
@@ -1127,6 +1106,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 
 
 func (cli *DockerCli) CmdSearch(args ...string) error {
 func (cli *DockerCli) CmdSearch(args ...string) error {
 	cmd := Subcmd("search", "NAME", "Search the docker index for images")
 	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 {
 	if err := cmd.Parse(args); err != nil {
 		return nil
 		return nil
 	}
 	}
@@ -1148,13 +1128,19 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
 		return err
 		return err
 	}
 	}
 	fmt.Fprintf(cli.out, "Found %d results matching your query (\"%s\")\n", len(outs), cmd.Arg(0))
 	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")
 	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 {
 	for _, out := range outs {
 		desc := strings.Replace(out.Description, "\n", " ", -1)
 		desc := strings.Replace(out.Description, "\n", " ", -1)
 		desc = strings.Replace(desc, "\r", " ", -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)
 		fmt.Fprintf(w, "%s\t%s\n", out.Name, desc)
 	}
 	}
@@ -1265,7 +1251,9 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	//if image not found try to pull it
 	//if image not found try to pull it
 	if statusCode == 404 {
 	if statusCode == 404 {
 		v := url.Values{}
 		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)
 		err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
@@ -1286,7 +1274,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	}
 	}
 
 
 	for _, warning := range runResult.Warnings {
 	for _, warning := range runResult.Warnings {
-		fmt.Fprintln(cli.err, "WARNING: ", warning)
+		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
 	}
 	}
 
 
 	//start the container
 	//start the container
@@ -1338,9 +1326,9 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	return nil
 	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 AND the login failed
-	if condition && cli.authConfig.Username == "" {
+	if cli.authConfig.Username == "" {
 		if err := cli.CmdLogin(""); err != nil {
 		if err := cli.CmdLogin(""); err != nil {
 			return err
 			return err
 		}
 		}
@@ -1528,17 +1516,28 @@ 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 {
 	if !cli.isTerminal {
-		return
+		return 0, 0
 	}
 	}
 	ws, err := term.GetWinsize(cli.terminalFd)
 	ws, err := term.GetWinsize(cli.terminalFd)
 	if err != nil {
 	if err != nil {
 		utils.Debugf("Error getting size: %s", err)
 		utils.Debugf("Error getting size: %s", err)
+		if ws == nil {
+			return 0, 0
+		}
+	}
+	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 := 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 {
 	if _, _, err := cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil); err != nil {
 		utils.Debugf("Error resize: %s", err)
 		utils.Debugf("Error resize: %s", err)
 	}
 	}
@@ -1574,7 +1573,7 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
 
 
 func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
 func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
 	var (
 	var (
-		isTerminal bool = false
+		isTerminal = false
 		terminalFd uintptr
 		terminalFd uintptr
 	)
 	)
 
 

+ 5 - 4
commands_test.go

@@ -132,17 +132,18 @@ func TestImages(t *testing.T) {
 }
 }
 
 
 */
 */
+
 // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
 // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
 func TestRunHostname(t *testing.T) {
 func TestRunHostname(t *testing.T) {
 	stdout, stdoutPipe := io.Pipe()
 	stdout, stdoutPipe := io.Pipe()
 
 
-	cli := NewDockerCli(nil, stdoutPipe, nil, testDaemonProto, testDaemonAddr)
+	cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
 	defer cleanup(globalRuntime)
 	defer cleanup(globalRuntime)
 
 
 	c := make(chan struct{})
 	c := make(chan struct{})
 	go func() {
 	go func() {
 		defer close(c)
 		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)
 			t.Fatal(err)
 		}
 		}
 	}()
 	}()
@@ -329,13 +330,13 @@ func TestRunAttachStdin(t *testing.T) {
 	stdin, stdinPipe := io.Pipe()
 	stdin, stdinPipe := io.Pipe()
 	stdout, stdoutPipe := io.Pipe()
 	stdout, stdoutPipe := io.Pipe()
 
 
-	cli := NewDockerCli(stdin, stdoutPipe, nil, testDaemonProto, testDaemonAddr)
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
 	defer cleanup(globalRuntime)
 	defer cleanup(globalRuntime)
 
 
 	ch := make(chan struct{})
 	ch := make(chan struct{})
 	go func() {
 	go func() {
 		defer close(ch)
 		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
 	// Send input to the command, close stdin

+ 21 - 12
container.go

@@ -202,20 +202,25 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
 	return config, hostConfig, cmd, nil
 	return config, hostConfig, cmd, nil
 }
 }
 
 
+type PortMapping map[string]string
+
 type NetworkSettings struct {
 type NetworkSettings struct {
 	IPAddress   string
 	IPAddress   string
 	IPPrefixLen int
 	IPPrefixLen int
 	Gateway     string
 	Gateway     string
 	Bridge      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
 // String returns a human-readable description of the port mapping defined in the settings
 func (settings *NetworkSettings) PortMappingHuman() string {
 func (settings *NetworkSettings) PortMappingHuman() string {
 	var mapping []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))
 		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)
 	sort.Strings(mapping)
 	return strings.Join(mapping, ", ")
 	return strings.Join(mapping, ", ")
 }
 }
@@ -466,8 +471,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
 }
 }
 
 
 func (container *Container) Start(hostConfig *HostConfig) error {
 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 {
 	if container.State.Running {
 		return fmt.Errorf("The container %s is already running.", container.ID)
 		return fmt.Errorf("The container %s is already running.", container.ID)
@@ -494,7 +499,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
 	// Create the requested bind mounts
 	// Create the requested bind mounts
 	binds := make(map[string]BindMap)
 	binds := make(map[string]BindMap)
 	// Define illegal container destinations
 	// Define illegal container destinations
-	illegal_dsts := []string{"/", "."}
+	illegalDsts := []string{"/", "."}
 
 
 	for _, bind := range hostConfig.Binds {
 	for _, bind := range hostConfig.Binds {
 		// FIXME: factorize bind parsing in parseBind
 		// FIXME: factorize bind parsing in parseBind
@@ -513,7 +518,7 @@ func (container *Container) Start(hostConfig *HostConfig) error {
 		}
 		}
 
 
 		// Bail if trying to mount to an illegal destination
 		// Bail if trying to mount to an illegal destination
-		for _, illegal := range illegal_dsts {
+		for _, illegal := range illegalDsts {
 			if dst == illegal {
 			if dst == illegal {
 				return fmt.Errorf("Illegal bind destination: %s", dst)
 				return fmt.Errorf("Illegal bind destination: %s", dst)
 			}
 			}
@@ -688,14 +693,18 @@ func (container *Container) allocateNetwork() error {
 	if err != nil {
 	if err != nil {
 		return err
 		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 {
 	for _, spec := range container.Config.PortSpecs {
 		nat, err := iface.AllocatePort(spec)
 		nat, err := iface.AllocatePort(spec)
 		if err != nil {
 		if err != nil {
 			iface.Release()
 			iface.Release()
 			return err
 			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.network = iface
 	container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
 	container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface
@@ -821,8 +830,8 @@ func (container *Container) kill() error {
 }
 }
 
 
 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 {
 	if !container.State.Running {
 		return nil
 		return nil
 	}
 	}
@@ -830,8 +839,8 @@ func (container *Container) Kill() error {
 }
 }
 
 
 func (container *Container) Stop(seconds int) 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 {
 	if !container.State.Running {
 		return nil
 		return nil
 	}
 	}

+ 1 - 1
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.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
 	flag.Parse()
 	flag.Parse()
 	if len(flHosts) > 1 {
 	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 {
 	for i, flHost := range flHosts {
 		flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
 		flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)

+ 29 - 30
docs/sources/contributing/devenvironment.rst

@@ -5,53 +5,52 @@
 Setting Up a Dev Environment
 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 <https://docs.docker.io/en/latest/installation/>`.
+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
+    git clone http://git@github.com/dotcloud/docker
+    cd docker
 
 
 
 
-Installation
-------------
+Step 3: build
+-------------
 
 
-.. 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
+When you are ready to build docker, run this command:
 
 
-    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
-    cd docker
+    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.
 
 
-    go get -v github.com/dotcloud/docker/...
-    go install -v github.com/dotcloud/docker/...
+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:
 
 
-Then run the docker daemon,
+::
+    docker run -i -t docker bash
 
 
-.. code-block:: bash
 
 
-    sudo $GOPATH/bin/docker -d
+To extract the binaries from the container:
 
 
+::
+    docker run docker sh -c 'cat $(which docker)' > docker-build && chmod +x docker-build
 
 
-Run the ``go install`` command (above) to recompile docker.

+ 7 - 0
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 <image> -d` will pass the "-d" argument to the entrypoint.
 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 <image> -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
 3. Dockerfile Examples
 ======================
 ======================
 
 

+ 1 - 2
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.
 //   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.
 //   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?
 //   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)
 	image, err := graph.Get(id)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -175,7 +175,6 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
 	if err != nil {
 	if err != nil {
 		return nil, err
 		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)
 	return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
 }
 }
 
 

+ 4 - 1
hack/dockerbuilder/Dockerfile

@@ -5,8 +5,11 @@
 # AUTHOR          Solomon Hykes <solomon@dotcloud.com>
 # AUTHOR          Solomon Hykes <solomon@dotcloud.com>
 #                 Daniel Mizyrycki <daniel@dotcloud.net>
 #                 Daniel Mizyrycki <daniel@dotcloud.net>
 # BUILD_CMD       docker build -t dockerbuilder .
 # 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
 from	ubuntu:12.04
 maintainer	Solomon Hykes <solomon@dotcloud.com>
 maintainer	Solomon Hykes <solomon@dotcloud.com>

+ 3 - 0
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 is not nil, unpack it into the new layer
 	if layerData != nil {
 	if layerData != nil {
+		start := time.Now()
+		utils.Debugf("Start untar layer")
 		if err := Untar(layerData, layer); err != nil {
 		if err := Untar(layerData, layer); err != nil {
 			return err
 			return err
 		}
 		}
+		utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds())
 	}
 	}
 
 
 	return StoreSize(img, root)
 	return StoreSize(img, root)

+ 135 - 96
network.go

@@ -5,7 +5,6 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
-	"io"
 	"log"
 	"log"
 	"net"
 	"net"
 	"os/exec"
 	"os/exec"
@@ -183,8 +182,10 @@ func getIfaceAddr(name string) (net.Addr, error) {
 // up iptables rules.
 // up iptables rules.
 // It keeps track of all mappings and is able to unmap at will
 // It keeps track of all mappings and is able to unmap at will
 type PortMapper struct {
 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 {
 func (mapper *PortMapper) cleanup() error {
@@ -197,8 +198,10 @@ func (mapper *PortMapper) cleanup() error {
 	iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
 	iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
 	iptables("-t", "nat", "-F", "DOCKER")
 	iptables("-t", "nat", "-F", "DOCKER")
 	iptables("-t", "nat", "-X", "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
 	return nil
 }
 }
 
 
@@ -215,76 +218,72 @@ func (mapper *PortMapper) setup() error {
 	return nil
 	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
-	}
-
-	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()
+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 {
 		if err != nil {
+			mapper.Unmap(port, "tcp")
 			return err
 			return err
 		}
 		}
-		utils.Debugf("Connecting to %s:%s", proto, address)
-		dst, err := net.Dial(proto, address)
+		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 {
 		if err != nil {
-			log.Printf("Error connecting to %s:%s: %s", proto, address, err)
-			src.Close()
-			continue
+			mapper.Unmap(port, "udp")
+			return err
 		}
 		}
-		utils.Debugf("Connected to backend, splicing")
-		splice(src, dst)
+		mapper.udpProxies[port] = proxy
+		go proxy.Run()
 	}
 	}
+	return nil
 }
 }
 
 
-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
+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
+		}
+		delete(mapper.tcpMapping, port)
+	} else {
+		backendAddr, ok := mapper.udpMapping[port]
+		if !ok {
+			return fmt.Errorf("Port udp/%v is not mapped", port)
+		}
+		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)
 	}
 	}
-	delete(mapper.mapping, port)
 	return nil
 	return nil
 }
 }
 
 
@@ -301,9 +300,9 @@ func newPortMapper() (*PortMapper, error) {
 
 
 // Port allocator: Atomatically allocate and release networking ports
 // Port allocator: Atomatically allocate and release networking ports
 type PortAllocator struct {
 type PortAllocator struct {
+	sync.Mutex
 	inUse    map[int]struct{}
 	inUse    map[int]struct{}
 	fountain chan (int)
 	fountain chan (int)
-	lock     sync.Mutex
 }
 }
 
 
 func (alloc *PortAllocator) runFountain() {
 func (alloc *PortAllocator) runFountain() {
@@ -317,9 +316,9 @@ func (alloc *PortAllocator) runFountain() {
 // FIXME: Release can no longer fail, change its prototype to reflect that.
 // FIXME: Release can no longer fail, change its prototype to reflect that.
 func (alloc *PortAllocator) Release(port int) error {
 func (alloc *PortAllocator) Release(port int) error {
 	utils.Debugf("Releasing %d", port)
 	utils.Debugf("Releasing %d", port)
-	alloc.lock.Lock()
+	alloc.Lock()
 	delete(alloc.inUse, port)
 	delete(alloc.inUse, port)
-	alloc.lock.Unlock()
+	alloc.Unlock()
 	return nil
 	return nil
 }
 }
 
 
@@ -334,8 +333,8 @@ func (alloc *PortAllocator) Acquire(port int) (int, error) {
 		}
 		}
 		return -1, fmt.Errorf("Port generator ended unexpectedly")
 		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 {
 	if _, inUse := alloc.inUse[port]; inUse {
 		return -1, fmt.Errorf("Port already in use: %d", port)
 		return -1, fmt.Errorf("Port already in use: %d", port)
 	}
 	}
@@ -453,7 +452,7 @@ type NetworkInterface struct {
 	Gateway net.IP
 	Gateway net.IP
 
 
 	manager  *NetworkManager
 	manager  *NetworkManager
-	extPorts []int
+	extPorts []*Nat
 }
 }
 
 
 // Allocate an external TCP port and map it to the interface
 // 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 {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	// Allocate a random port if Frontend==0
-	extPort, err := iface.manager.portAllocator.Acquire(nat.Frontend)
-	if err != nil {
-		return nil, err
-	}
-	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
+
+	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
 	}
 	}
-	iface.extPorts = append(iface.extPorts, nat.Frontend)
+	iface.extPorts = append(iface.extPorts, nat)
+
 	return nat, nil
 	return nat, nil
 }
 }
 
 
@@ -485,6 +499,21 @@ type Nat struct {
 func parseNat(spec string) (*Nat, error) {
 func parseNat(spec string) (*Nat, error) {
 	var nat Nat
 	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, ":") {
 	if strings.Contains(spec, ":") {
 		specParts := strings.Split(spec, ":")
 		specParts := strings.Split(spec, ":")
 		if len(specParts) != 2 {
 		if len(specParts) != 2 {
@@ -517,20 +546,24 @@ func parseNat(spec string) (*Nat, error) {
 		}
 		}
 		nat.Backend = int(port)
 		nat.Backend = int(port)
 	}
 	}
-	nat.Proto = "tcp"
+
 	return &nat, nil
 	return &nat, nil
 }
 }
 
 
 // Release: Network cleanup - release all resources
 // Release: Network cleanup - release all resources
 func (iface *NetworkInterface) Release() {
 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)
 	iface.manager.ipAllocator.Release(iface.IPNet.IP)
@@ -542,9 +575,10 @@ type NetworkManager struct {
 	bridgeIface   string
 	bridgeIface   string
 	bridgeNetwork *net.IPNet
 	bridgeNetwork *net.IPNet
 
 
-	ipAllocator   *IPAllocator
-	portAllocator *PortAllocator
-	portMapper    *PortMapper
+	ipAllocator      *IPAllocator
+	tcpPortAllocator *PortAllocator
+	udpPortAllocator *PortAllocator
+	portMapper       *PortMapper
 }
 }
 
 
 // Allocate a network interface
 // Allocate a network interface
@@ -577,7 +611,11 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
 
 
 	ipAllocator := newIPAllocator(network)
 	ipAllocator := newIPAllocator(network)
 
 
-	portAllocator, err := newPortAllocator()
+	tcpPortAllocator, err := newPortAllocator()
+	if err != nil {
+		return nil, err
+	}
+	udpPortAllocator, err := newPortAllocator()
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -588,11 +626,12 @@ func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
 	}
 	}
 
 
 	manager := &NetworkManager{
 	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
 	return manager, nil
 }
 }

+ 257 - 0
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"))
+	}
+}

+ 221 - 0
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))
+	}
+}

+ 75 - 6
network_test.go

@@ -20,28 +20,97 @@ func TestIptables(t *testing.T) {
 
 
 func TestParseNat(t *testing.T) {
 func TestParseNat(t *testing.T) {
 	if nat, err := parseNat("4500"); err == nil {
 	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 {
 	} else {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
 	if nat, err := parseNat(":4501"); err == nil {
 	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 {
 	} else {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
 	if nat, err := parseNat("4502:4503"); err == nil {
 	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 {
 	} else {
 		t.Fatal(err)
 		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) {
 func TestPortAllocation(t *testing.T) {

+ 107 - 66
registry/registry.go

@@ -12,18 +12,90 @@ import (
 	"net/http"
 	"net/http"
 	"net/http/cookiejar"
 	"net/http/cookiejar"
 	"net/url"
 	"net/url"
+	"regexp"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 )
 )
 
 
 var ErrAlreadyExists = errors.New("Image already exists")
 var ErrAlreadyExists = errors.New("Image already exists")
+var ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
 
 
-func UrlScheme() string {
-	u, err := url.Parse(auth.IndexServerAddress())
+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 {
 	if err != nil {
-		return "https"
+		return err
+	}
+	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 u.Scheme
+	return nil
+}
+
+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)
+	}
+	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) {
+	if strings.Contains(reposName, "://") {
+		// It cannot contain a scheme!
+		return "", "", ErrInvalidRepositoryName
+	}
+	nameParts := strings.SplitN(reposName, "/", 2)
+	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
+	}
+	if len(nameParts) < 2 {
+		// There is a dot in repos name (and no registry address)
+		// Is it a Registry address without repos name?
+		return "", "", ErrInvalidRepositoryName
+	}
+	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)
+		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(reposName)
+	return endpoint, reposName, err
 }
 }
 
 
 func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
 func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
@@ -35,8 +107,8 @@ func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
 
 
 // Retrieve the history of a given image from the Registry.
 // Retrieve the history of a given image from the Registry.
 // Return a list of the parent's json (requested image included)
 // 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 {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -44,7 +116,7 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s
 	res, err := r.client.Do(req)
 	res, err := r.client.Do(req)
 	if err != nil || res.StatusCode != 200 {
 	if err != nil || res.StatusCode != 200 {
 		if res != nil {
 		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
 		return nil, err
 	}
 	}
@@ -64,10 +136,10 @@ func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]s
 }
 }
 
 
 // Check if an image exists in the Registry
 // 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}
 	rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
 
 
-	req, err := http.NewRequest("GET", registry+"/v1/images/"+imgId+"/json", nil)
+	req, err := http.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
 	if err != nil {
 	if err != nil {
 		return false
 		return false
 	}
 	}
@@ -79,44 +151,10 @@ func (r *Registry) LookupRemoteImage(imgId, registry string, token []string) boo
 	return res.StatusCode == 200
 	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.
 // 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
 	// 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 {
 	if err != nil {
 		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
 		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
 	}
 	}
@@ -142,8 +180,8 @@ func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([
 	return jsonString, imageSize, nil
 	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 {
 	if err != nil {
 		return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
 		return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
 	}
 	}
@@ -162,10 +200,7 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
 		repository = "library/" + repository
 		repository = "library/" + repository
 	}
 	}
 	for _, host := range registries {
 	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)
 		req, err := r.opaqueRequest("GET", endpoint, nil)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
@@ -198,8 +233,8 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
 	return nil, fmt.Errorf("Could not reach any registry endpoint")
 	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)
 	req, err := r.opaqueRequest("GET", repositoryTarget, nil)
 	if err != nil {
 	if err != nil {
@@ -230,8 +265,12 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
 	}
 	}
 
 
 	var endpoints []string
 	var endpoints []string
+	var urlScheme = indexEp[:strings.Index(indexEp, ":")]
 	if res.Header.Get("X-Docker-Endpoints") != "" {
 	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 {
 	} else {
 		return nil, fmt.Errorf("Index response didn't contain any endpoints")
 		return nil, fmt.Errorf("Index response didn't contain any endpoints")
 	}
 	}
@@ -260,9 +299,8 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
 
 
 // Push a local image to the registry
 // Push a local image to the registry
 func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
 func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
-	registry = registry + "/v1"
 	// FIXME: try json with UTF8
 	// 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 {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -295,9 +333,8 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis
 	return nil
 	return nil
 }
 }
 
 
-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)
+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 {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -334,9 +371,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 {
 func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
 	// "jsonify" the string
 	// "jsonify" the string
 	revision = "\"" + revision + "\""
 	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 {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -354,7 +390,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
 	return nil
 	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)
 	imgListJSON, err := json.Marshal(imgList)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -364,9 +400,10 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
 		suffix = "images"
 		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)
 	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 {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -404,6 +441,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
 	}
 	}
 
 
 	var tokens, endpoints []string
 	var tokens, endpoints []string
+	var urlScheme = indexEp[:strings.Index(indexEp, ":")]
 	if !validate {
 	if !validate {
 		if res.StatusCode != 200 && res.StatusCode != 201 {
 		if res.StatusCode != 200 && res.StatusCode != 201 {
 			errBody, err := ioutil.ReadAll(res.Body)
 			errBody, err := ioutil.ReadAll(res.Body)
@@ -420,7 +458,10 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
 		}
 		}
 
 
 		if res.Header.Get("X-Docker-Endpoints") != "" {
 		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 {
 		} else {
 			return nil, fmt.Errorf("Index response didn't contain any endpoints")
 			return nil, fmt.Errorf("Index response didn't contain any endpoints")
 		}
 		}
@@ -442,7 +483,7 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat
 }
 }
 
 
 func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
 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)
 	req, err := http.NewRequest("GET", u, nil)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 0 - 3
runtime.go

@@ -108,9 +108,6 @@ func (runtime *Runtime) Register(container *Container) error {
 	// init the wait lock
 	// init the wait lock
 	container.waitLock = make(chan struct{})
 	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
 	container.runtime = runtime
 
 
 	// Attach to stdout and stderr
 	// Attach to stdout and stderr

+ 125 - 59
runtime_test.go

@@ -1,6 +1,7 @@
 package docker
 package docker
 
 
 import (
 import (
+	"bytes"
 	"fmt"
 	"fmt"
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io"
@@ -17,11 +18,12 @@ import (
 )
 )
 
 
 const (
 const (
-	unitTestImageName = "docker-unit-tests"
-	unitTestImageId   = "e9aa60c60128cad1"
-	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
 var globalRuntime *Runtime
@@ -49,7 +51,7 @@ func cleanup(runtime *Runtime) error {
 		return err
 		return err
 	}
 	}
 	for _, image := range images {
 	for _, image := range images {
-		if image.ID != unitTestImageId {
+		if image.ID != unitTestImageID {
 			runtime.graph.Delete(image.ID)
 			runtime.graph.Delete(image.ID)
 		}
 		}
 	}
 	}
@@ -73,10 +75,10 @@ func init() {
 	}
 	}
 
 
 	if uid := syscall.Geteuid(); uid != 0 {
 	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"
+	NetworkBridgeIface = unitTestNetworkBridge
 
 
 	// Make it our Store root
 	// Make it our Store root
 	runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false)
 	runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false)
@@ -89,13 +91,15 @@ func init() {
 	srv := &Server{
 	srv := &Server{
 		runtime:     runtime,
 		runtime:     runtime,
 		enableCors:  false,
 		enableCors:  false,
-		lock:        &sync.Mutex{},
 		pullingPool: make(map[string]struct{}),
 		pullingPool: make(map[string]struct{}),
 		pushingPool: 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
 	// Spawn a Daemon
 	go func() {
 	go func() {
@@ -136,11 +140,11 @@ func GetTestImage(runtime *Runtime) *Image {
 		panic(err)
 		panic(err)
 	}
 	}
 	for i := range imgs {
 	for i := range imgs {
-		if imgs[i].ID == unitTestImageId {
+		if imgs[i].ID == unitTestImageID {
 			return imgs[i]
 			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) {
 func TestRuntimeCreate(t *testing.T) {
@@ -318,52 +322,47 @@ func TestGet(t *testing.T) {
 
 
 }
 }
 
 
-func findAvailalblePort(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()
 	runtime, err := newTestRuntime()
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	port := 5554
 
 
+	port := 5554
 	var container *Container
 	var container *Container
+	var strPort string
 	for {
 	for {
 		port += 1
 		port += 1
-		log.Println("Trying port", port)
-		t.Log("Trying port", port)
-		container, err = findAvailalblePort(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 {
 		if container != nil {
 			break
 			break
 		}
 		}
 		if err != nil {
 		if err != nil {
+			nuke(runtime)
 			t.Fatal(err)
 			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() {
 	setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
 		for !container.State.Running {
 		for !container.State.Running {
@@ -374,26 +373,93 @@ func TestAllocatePortLocalhost(t *testing.T) {
 	// Even if the state is running, lets give some time to lxc to spawn the process
 	// Even if the state is running, lets give some time to lxc to spawn the process
 	container.WaitTimeout(500 * time.Millisecond)
 	container.WaitTimeout(500 * time.Millisecond)
 
 
-	conn, err := net.Dial("tcp",
-		fmt.Sprintf(
-			"localhost:%s", container.NetworkSettings.PortMapping[strconv.Itoa(port)],
-		),
-	)
-	if err != nil {
-		t.Fatal(err)
+	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()
+
+	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(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
+		}
 	}
 	}
-	defer conn.Close()
-	output, err := ioutil.ReadAll(conn)
+
+	t.Fatal("No reply from the container")
+}
+
+// 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)
+	defer container.Kill()
+
+	conn, err := net.Dial("udp", fmt.Sprintf("localhost:%v", port))
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		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),
-		)
+	defer conn.Close()
+
+	input := bytes.NewBufferString("well hello there\n")
+	buf := make([]byte, 16)
+	// 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(500 * time.Millisecond))
+		read, err := conn.Read(buf)
+		if err == nil {
+			output := string(buf[:read])
+			if strings.Contains(output, "well hello there") {
+				return
+			}
+		}
 	}
 	}
-	container.Wait()
+
+	t.Fatal("No reply from the container")
 }
 }
 
 
 func TestRestore(t *testing.T) {
 func TestRestore(t *testing.T) {

+ 94 - 125
server.go

@@ -29,7 +29,7 @@ func (srv *Server) DockerVersion() APIVersion {
 func (srv *Server) ContainerKill(name string) error {
 func (srv *Server) ContainerKill(name string) error {
 	if container := srv.runtime.Get(name); container != nil {
 	if container := srv.runtime.Get(name); container != nil {
 		if err := container.Kill(); err != 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 {
 	} else {
 		return fmt.Errorf("No such container: %s", name)
 		return fmt.Errorf("No such container: %s", name)
@@ -315,8 +315,8 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
 	return nil
 	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 {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -351,44 +351,32 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
 	return nil
 	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, localName, remoteName, askedTag, indexEp string, sf *utils.StreamFormatter) error {
+	out.Write(sf.FormatStatus("Pulling repository %s", localName))
 
 
-	var repoData *registry.RepositoryData
-	var err error
-	if registryEp == "" {
-		repoData, err = r.GetRepositoryData(remote)
-		if err != nil {
-			return err
-		}
+	repoData, err := r.GetRepositoryData(indexEp, remoteName)
+	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 = &registry.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")
 	utils.Debugf("Retrieving the tag list")
-	tagsList, err := r.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens)
+	tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens)
 	if err != nil {
 	if err != nil {
 		utils.Debugf("%v", err)
 		utils.Debugf("%v", err)
 		return err
 		return err
 	}
 	}
 
 
-	if registryEp != "" {
-		for tag, id := range tagsList {
-			repoData.ImgList[id] = &registry.ImgData{
-				ID:       id,
-				Tag:      tag,
-				Checksum: "",
-			}
+	for tag, id := range tagsList {
+		repoData.ImgList[id] = &registry.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
 		// Otherwise, check that the tag exists and use only that one
 		id, exists := tagsList[askedTag]
 		id, exists := tagsList[askedTag]
 		if !exists {
 		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, localName)
 		}
 		}
 		repoData.ImgList[id].Tag = askedTag
 		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)
 			utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
 			continue
 			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, localName))
 		success := false
 		success := false
 		for _, ep := range repoData.Endpoints {
 		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))
 				out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
 				continue
 				continue
 			}
 			}
@@ -438,7 +423,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
 		if askedTag != "" && tag != askedTag {
 		if askedTag != "" && tag != askedTag {
 			continue
 			continue
 		}
 		}
-		if err := srv.runtime.repositories.Set(local, tag, id, true); err != nil {
+		if err := srv.runtime.repositories.Set(localName, tag, id, true); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -450,8 +435,8 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
 }
 }
 
 
 func (srv *Server) poolAdd(kind, key string) error {
 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 {
 	if _, exists := srv.pullingPool[key]; exists {
 		return fmt.Errorf("%s %s is already in progress", key, kind)
 		return fmt.Errorf("%s %s is already in progress", key, kind)
@@ -483,25 +468,32 @@ func (srv *Server) poolRemove(kind, key string) error {
 	}
 	}
 	return nil
 	return nil
 }
 }
-func (srv *Server) ImagePull(name, tag, endpoint 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)
 	r, err := registry.NewRegistry(srv.runtime.root, authConfig)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	if err := srv.poolAdd("pull", name+":"+tag); err != nil {
+	if err := srv.poolAdd("pull", localName+":"+tag); err != nil {
 		return err
 		return err
 	}
 	}
-	defer srv.poolRemove("pull", name+":"+tag)
+	defer srv.poolRemove("pull", localName+":"+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
+	endpoint, remoteName, err := registry.ResolveRepositoryName(localName)
+	if err != nil {
+		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)
 	out = utils.NewWriteFlusher(out)
-	err = srv.pullRepository(r, out, name, remote, tag, endpoint, sf)
-	if err != nil && endpoint != "" {
-		if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
+	err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf)
+	if err != nil {
+		if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil {
 			return err
 			return err
 		}
 		}
 		return nil
 		return nil
@@ -516,20 +508,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
 // - 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 exists, process the checksum from it
 // - If the archive does not exists and not found on registry, process checksum from layer
 // - 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
 	// FIXME: Use in-memory map instead of reading the file each time
 	if sums, err := srv.runtime.graph.getStoredChecksums(); err != nil {
 	if sums, err := srv.runtime.graph.getStoredChecksums(); err != nil {
 		return "", err
 		return "", err
-	} else if checksum, exists := sums[imageId]; exists {
+	} else if checksum, exists := sums[imageID]; exists {
 		return checksum, nil
 		return checksum, nil
 	}
 	}
 
 
-	img, err := srv.runtime.graph.Get(imageId)
+	img, err := srv.runtime.graph.Get(imageID)
 	if err != nil {
 	if err != nil {
 		return "", err
 		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) {
 		if os.IsNotExist(err) {
 			// TODO: Ask the registry for the checksum
 			// TODO: Ask the registry for the checksum
 			//       As the archive is not there, it is supposed to come from a pull.
 			//       As the archive is not there, it is supposed to come from a pull.
@@ -576,7 +568,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
 	return imgList, nil
 	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, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error {
 	out = utils.NewWriteFlusher(out)
 	out = utils.NewWriteFlusher(out)
 	out.Write(sf.FormatStatus("Processing checksums"))
 	out.Write(sf.FormatStatus("Processing checksums"))
 	imgList, err := srv.getImageList(localRepo)
 	imgList, err := srv.getImageList(localRepo)
@@ -584,94 +576,64 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name, reg
 		return err
 		return err
 	}
 	}
 	out.Write(sf.FormatStatus("Sending image list"))
 	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
 	var repoData *registry.RepositoryData
-	if registryEp == "" {
-		repoData, err = r.PushImageJSONIndex(name, imgList, false, nil)
-		if err != nil {
-			return err
-		}
-	} else {
-		repoData = &registry.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] = &registry.ImgData{
-					ID:       id,
-					Tag:      tag,
-					Checksum: "",
-				}
-			}
-		}
+	repoData, err = r.PushImageJSONIndex(indexEp, remoteName, imgList, false, nil)
+	if err != nil {
+		return err
 	}
 	}
 
 
 	for _, ep := range repoData.Endpoints {
 	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)))
+		out.Write(sf.FormatStatus("Pushing repository %s (%d tags)", localName, len(localRepo)))
 		// For each image within the repo, push them
 		// For each image within the repo, push them
 		for _, elem := range imgList {
 		for _, elem := range imgList {
 			if _, exists := repoData.ImgList[elem.ID]; exists {
 			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
 				continue
 			} else if r.LookupRemoteImage(elem.ID, ep, repoData.Tokens) {
 			} 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
 				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?
 				// FIXME: Continue on error?
 				return err
 				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
 				return err
 			}
 			}
 		}
 		}
 	}
 	}
 
 
-	if registryEp == "" {
-		if _, err := r.PushImageJSONIndex(name, imgList, true, repoData.Endpoints); err != nil {
-			return err
-		}
+	if _, err := r.PushImageJSONIndex(indexEp, remoteName, imgList, true, repoData.Endpoints); err != nil {
+		return err
 	}
 	}
 
 
 	return nil
 	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)
 	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 {
 	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
 	// Make sure we have the image's checksum
-	checksum, err := srv.getChecksum(imgId)
+	checksum, err := srv.getChecksum(imgID)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	imgData := &registry.ImgData{
 	imgData := &registry.ImgData{
-		ID:       imgId,
+		ID:       imgID,
 		Checksum: checksum,
 		Checksum: checksum,
 	}
 	}
 
 
 	// Send the json
 	// Send the json
 	if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
 	if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
 		if err == registry.ErrAlreadyExists {
 		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 nil
 		}
 		}
 		return err
 		return err
@@ -680,11 +642,11 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
 	// Retrieve the tarball to be sent
 	// Retrieve the tarball to be sent
 	var layerData *TempArchive
 	var layerData *TempArchive
 	// If the archive exists, use it
 	// 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 err != nil {
 		if os.IsNotExist(err) {
 		if os.IsNotExist(err) {
 			// If the archive does not exist, create one from the layer
 			// 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 {
 			if err != nil {
 				return fmt.Errorf("Failed to generate layer archive: %s", err)
 				return fmt.Errorf("Failed to generate layer archive: %s", err)
 			}
 			}
@@ -711,33 +673,41 @@ 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.
 // 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 {
-	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", localName)
+
+	// Resolve the Repository name from fqn to endpoint + name
+	endpoint, remoteName, err := registry.ResolveRepositoryName(localName)
+	if err != nil {
 		return err
 		return err
 	}
 	}
-	defer srv.poolRemove("push", name)
 
 
 	out = utils.NewWriteFlusher(out)
 	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)
 	r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
 	if err2 != nil {
 	if err2 != nil {
 		return err2
 		return err2
 	}
 	}
 
 
 	if err != nil {
 	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 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 localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists {
+			if err := srv.pushRepository(r, out, localName, remoteName, localRepo, endpoint, sf); err != nil {
 				return err
 				return err
 			}
 			}
 			return nil
 			return nil
 		}
 		}
-
 		return err
 		return err
 	}
 	}
-	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 {
+
+	var token []string
+	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 err
 	}
 	}
 	return nil
 	return nil
@@ -809,7 +779,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
 func (srv *Server) ContainerRestart(name string, t int) error {
 func (srv *Server) ContainerRestart(name string, t int) error {
 	if container := srv.runtime.Get(name); container != nil {
 	if container := srv.runtime.Get(name); container != nil {
 		if err := container.Restart(t); err != 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 {
 	} else {
 		return fmt.Errorf("No such container: %s", name)
 		return fmt.Errorf("No such container: %s", name)
@@ -828,7 +798,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
 			volumes[volumeId] = struct{}{}
 			volumes[volumeId] = struct{}{}
 		}
 		}
 		if err := srv.runtime.Destroy(container); err != nil {
 		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 {
 		if removeVolume {
@@ -919,7 +889,7 @@ func (srv *Server) deleteImageParents(img *Image, imgs *[]APIRmi) error {
 
 
 func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, error) {
 func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, error) {
 	//Untag the current image
 	//Untag the current image
-	var imgs []APIRmi
+	imgs := []APIRmi{}
 	tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag)
 	tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -948,7 +918,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
 	}
 	}
 	if !autoPrune {
 	if !autoPrune {
 		if err := srv.runtime.graph.Delete(img.ID); err != nil {
 		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
 		return nil, nil
 	}
 	}
@@ -963,7 +933,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
 	return srv.deleteImage(img, name, tag)
 	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
 	// Retrieve all images
 	images, err := srv.runtime.graph.All()
 	images, err := srv.runtime.graph.All()
@@ -981,7 +951,7 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error)
 	}
 	}
 
 
 	// Loop on the children of the given image and check the config
 	// 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)
 		img, err := srv.runtime.graph.Get(elem)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
@@ -996,7 +966,7 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error)
 func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
 func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
 	if container := srv.runtime.Get(name); container != nil {
 	if container := srv.runtime.Get(name); container != nil {
 		if err := container.Start(hostConfig); err != 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 {
 	} else {
 		return fmt.Errorf("No such container: %s", name)
 		return fmt.Errorf("No such container: %s", name)
@@ -1007,7 +977,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
 func (srv *Server) ContainerStop(name string, t int) error {
 func (srv *Server) ContainerStop(name string, t int) error {
 	if container := srv.runtime.Get(name); container != nil {
 	if container := srv.runtime.Get(name); container != nil {
 		if err := container.Stop(t); err != 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 {
 	} else {
 		return fmt.Errorf("No such container: %s", name)
 		return fmt.Errorf("No such container: %s", name)
@@ -1119,7 +1089,6 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (
 	srv := &Server{
 	srv := &Server{
 		runtime:     runtime,
 		runtime:     runtime,
 		enableCors:  enableCors,
 		enableCors:  enableCors,
-		lock:        &sync.Mutex{},
 		pullingPool: make(map[string]struct{}),
 		pullingPool: make(map[string]struct{}),
 		pushingPool: make(map[string]struct{}),
 		pushingPool: make(map[string]struct{}),
 	}
 	}
@@ -1128,9 +1097,9 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (
 }
 }
 
 
 type Server struct {
 type Server struct {
+	sync.Mutex
 	runtime     *Runtime
 	runtime     *Runtime
 	enableCors  bool
 	enableCors  bool
-	lock        *sync.Mutex
 	pullingPool map[string]struct{}
 	pullingPool map[string]struct{}
 	pushingPool map[string]struct{}
 	pushingPool map[string]struct{}
 }
 }

+ 3 - 3
server_test.go

@@ -31,7 +31,7 @@ func TestContainerTagImageDelete(t *testing.T) {
 	}
 	}
 
 
 	if len(images) != len(initialImages)+2 {
 	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 {
 	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 {
 	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 {
 	if _, err := srv.ImageDelete("utest:tag1", true); err != nil {
@@ -57,7 +57,7 @@ func TestContainerTagImageDelete(t *testing.T) {
 	}
 	}
 
 
 	if len(images) != len(initialImages) {
 	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))
 	}
 	}
 }
 }
 
 

+ 2 - 13
state.go

@@ -8,11 +8,11 @@ import (
 )
 )
 
 
 type State struct {
 type State struct {
+	sync.Mutex
 	Running   bool
 	Running   bool
 	Pid       int
 	Pid       int
 	ExitCode  int
 	ExitCode  int
 	StartedAt time.Time
 	StartedAt time.Time
-	l         *sync.Mutex
 	Ghost     bool
 	Ghost     bool
 }
 }
 
 
@@ -29,6 +29,7 @@ func (s *State) String() string {
 
 
 func (s *State) setRunning(pid int) {
 func (s *State) setRunning(pid int) {
 	s.Running = true
 	s.Running = true
+	s.Ghost = false
 	s.ExitCode = 0
 	s.ExitCode = 0
 	s.Pid = pid
 	s.Pid = pid
 	s.StartedAt = time.Now()
 	s.StartedAt = time.Now()
@@ -39,15 +40,3 @@ func (s *State) setStopped(exitCode int) {
 	s.Pid = 0
 	s.Pid = 0
 	s.ExitCode = exitCode
 	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()
-}

+ 7 - 10
tags.go

@@ -70,11 +70,11 @@ func (store *TagStore) LookupImage(name string) (*Image, error) {
 	if err != nil {
 	if err != nil {
 		// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
 		// FIXME: standardize on returning nil when the image doesn't exist, and err for everything else
 		// (so we can pass all errors here)
 		// (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
 			return nil, err
 		} else if i == nil {
 		} else if i == nil {
 			return nil, fmt.Errorf("Image does not exist: %s", name)
 			return nil, fmt.Errorf("Image does not exist: %s", name)
@@ -197,7 +197,7 @@ func (store *TagStore) Get(repoName string) (Repository, error) {
 	return nil, nil
 	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)
 	repo, err := store.Get(repoName)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		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
 	//go through all the tags, to see if tag is in fact an ID
 	for _, revision := range repo {
 	for _, revision := range repo {
-		if strings.HasPrefix(revision, tagOrId) {
+		if strings.HasPrefix(revision, tagOrID) {
 			return store.graph.Get(revision)
 			return store.graph.Get(revision)
 		}
 		}
 	}
 	}
-	if revision, exists := repo[tagOrId]; exists {
+	if revision, exists := repo[tagOrID]; exists {
 		return store.graph.Get(revision)
 		return store.graph.Get(revision)
 	}
 	}
 	return nil, nil
 	return nil, nil
@@ -221,9 +221,6 @@ func validateRepoName(name string) error {
 	if name == "" {
 	if name == "" {
 		return fmt.Errorf("Repository name can't be empty")
 		return fmt.Errorf("Repository name can't be empty")
 	}
 	}
-	if strings.Contains(name, ":") {
-		return fmt.Errorf("Illegal repository name: %s", name)
-	}
 	return nil
 	return nil
 }
 }
 
 

+ 2 - 2
tags_test.go

@@ -35,13 +35,13 @@ func TestLookupImage(t *testing.T) {
 		t.Errorf("Expected 0 image, 1 found")
 		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)
 		t.Fatal(err)
 	} else if img == nil {
 	} else if img == nil {
 		t.Errorf("Expected 1 image, none found")
 		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)
 		t.Fatal(err)
 	} else if img == nil {
 	} else if img == nil {
 		t.Errorf("Expected 1 image, none found")
 		t.Errorf("Expected 1 image, none found")

+ 1 - 1
term/term.go

@@ -12,8 +12,8 @@ type State struct {
 }
 }
 
 
 type Winsize struct {
 type Winsize struct {
-	Width  uint16
 	Height uint16
 	Height uint16
+	Width  uint16
 	x      uint16
 	x      uint16
 	y      uint16
 	y      uint16
 }
 }

+ 10 - 0
testing/README.rst

@@ -30,6 +30,16 @@ Deployment
   export AWS_KEYPAIR_NAME=xxxxxxxxxxxx
   export AWS_KEYPAIR_NAME=xxxxxxxxxxxx
   export AWS_SSH_PRIVKEY=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
   # Checkout docker
   git clone git://github.com/dotcloud/docker.git
   git clone git://github.com/dotcloud/docker.git
 
 

+ 3 - 1
testing/Vagrantfile

@@ -27,7 +27,9 @@ Vagrant::Config.run do |config|
     pkg_cmd << "apt-get install -q -y python-dev python-pip supervisor; " \
     pkg_cmd << "apt-get install -q -y python-dev python-pip supervisor; " \
       "pip install -r #{CFG_PATH}/requirements.txt; " \
       "pip install -r #{CFG_PATH}/requirements.txt; " \
       "chown #{USER}.#{USER} /data; cd /data; " \
       "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
     # Install docker dependencies
     pkg_cmd << "apt-get install -q -y python-software-properties; " \
     pkg_cmd << "apt-get install -q -y python-software-properties; " \
       "add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \
       "add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \

+ 30 - 16
testing/buildbot/master.cfg

@@ -5,9 +5,11 @@ from buildbot.schedulers.basic import SingleBranchScheduler
 from buildbot.changes import filter
 from buildbot.changes import filter
 from buildbot.config import BuilderConfig
 from buildbot.config import BuilderConfig
 from buildbot.process.factory import BuildFactory
 from buildbot.process.factory import BuildFactory
+from buildbot.process.properties import Interpolate
 from buildbot.steps.shell import ShellCommand
 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.web import authz, auth
+from buildbot.status.mail import MailNotifier
 
 
 PORT_WEB = 80           # Buildbot webserver port
 PORT_WEB = 80           # Buildbot webserver port
 PORT_GITHUB = 8011      # Buildbot github hook port
 PORT_GITHUB = 8011      # Buildbot github hook port
@@ -15,20 +17,27 @@ PORT_MASTER = 9989      # Port where buildbot master listen buildworkers
 TEST_USER = 'buildbot'  # Credential to authenticate build triggers
 TEST_USER = 'buildbot'  # Credential to authenticate build triggers
 TEST_PWD = 'docker'     # Credential to authenticate build triggers
 TEST_PWD = 'docker'     # Credential to authenticate build triggers
 BUILDER_NAME = 'docker'
 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'
 DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker'
 
 
+# Credentials set by setup.sh and Vagrantfile
+BUILDBOT_PWD = ''
+IRC_PWD = ''
+IRC_CHANNEL = ''
+SMTP_USER = ''
+SMTP_PWD = ''
+EMAIL_RCP = ''
+
 
 
 c = BuildmasterConfig = {}
 c = BuildmasterConfig = {}
 
 
 c['title'] = "Docker"
 c['title'] = "Docker"
 c['titleURL'] = "waterfall"
 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['db'] = {'db_url':"sqlite:///state.sqlite"}
-c['slaves'] = [BuildSlave('buildworker', BUILDPASSWORD)]
+c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)]
 c['slavePortnum'] = PORT_MASTER
 c['slavePortnum'] = PORT_MASTER
 
 
 c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])]
 c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])]
@@ -36,20 +45,25 @@ c['schedulers'].append(SingleBranchScheduler(name="all",
     change_filter=filter.ChangeFilter(branch='master'),treeStableTimer=None,
     change_filter=filter.ChangeFilter(branch='master'),treeStableTimer=None,
     builderNames=[BUILDER_NAME]))
     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
 # Builder
 factory = BuildFactory()
 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'],
 c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'],
     factory=factory)]
     factory=factory)]
 
 
 # Status
 # 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')
     forceBuild='auth')
 c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)]
 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}))

+ 12 - 2
testing/buildbot/setup.sh

@@ -6,11 +6,16 @@
 
 
 USER=$1
 USER=$1
 CFG_PATH=$2
 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"
 BUILDBOT_PATH="/data/buildbot"
 DOCKER_PATH="/data/docker"
 DOCKER_PATH="/data/docker"
 SLAVE_NAME="buildworker"
 SLAVE_NAME="buildworker"
 SLAVE_SOCKET="localhost:9989"
 SLAVE_SOCKET="localhost:9989"
-BUILDBOT_PWD="pass-docker"
 export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin"
 export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin"
 
 
 function run { su $USER -c "$1"; }
 function run { su $USER -c "$1"; }
@@ -23,7 +28,12 @@ run "mkdir -p $BUILDBOT_PATH"
 cd $BUILDBOT_PATH
 cd $BUILDBOT_PATH
 run "buildbot create-master master"
 run "buildbot create-master master"
 run "cp $CFG_PATH/master.cfg 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"
 run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD"
 
 
 # Allow buildbot subprocesses (docker tests) to properly run in containers,
 # Allow buildbot subprocesses (docker tests) to properly run in containers,

+ 5 - 4
utils.go

@@ -20,7 +20,8 @@ func CompareConfig(a, b *Config) bool {
 	if len(a.Cmd) != len(b.Cmd) ||
 	if len(a.Cmd) != len(b.Cmd) ||
 		len(a.Dns) != len(b.Dns) ||
 		len(a.Dns) != len(b.Dns) ||
 		len(a.Env) != len(b.Env) ||
 		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
 		return false
 	}
 	}
 
 
@@ -53,9 +54,6 @@ func CompareConfig(a, b *Config) bool {
 }
 }
 
 
 func MergeConfig(userConf, imageConf *Config) {
 func MergeConfig(userConf, imageConf *Config) {
-	if userConf.Hostname == "" {
-		userConf.Hostname = imageConf.Hostname
-	}
 	if userConf.User == "" {
 	if userConf.User == "" {
 		userConf.User = imageConf.User
 		userConf.User = imageConf.User
 	}
 	}
@@ -92,4 +90,7 @@ func MergeConfig(userConf, imageConf *Config) {
 	if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
 	if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 {
 		userConf.Entrypoint = imageConf.Entrypoint
 		userConf.Entrypoint = imageConf.Entrypoint
 	}
 	}
+	if userConf.Volumes == nil || len(userConf.Volumes) == 0 {
+		userConf.Volumes = imageConf.Volumes
+	}
 }
 }

+ 32 - 28
utils/utils.go

@@ -170,10 +170,9 @@ func SelfPath() string {
 	return path
 	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
 	return len(buf), nil
 }
 }
 
 
@@ -188,10 +187,10 @@ func NopWriteCloser(w io.Writer) io.WriteCloser {
 }
 }
 
 
 type bufReader struct {
 type bufReader struct {
+	sync.Mutex
 	buf    *bytes.Buffer
 	buf    *bytes.Buffer
 	reader io.Reader
 	reader io.Reader
 	err    error
 	err    error
-	l      sync.Mutex
 	wait   sync.Cond
 	wait   sync.Cond
 }
 }
 
 
@@ -200,7 +199,7 @@ func NewBufReader(r io.Reader) *bufReader {
 		buf:    &bytes.Buffer{},
 		buf:    &bytes.Buffer{},
 		reader: r,
 		reader: r,
 	}
 	}
-	reader.wait.L = &reader.l
+	reader.wait.L = &reader.Mutex
 	go reader.drain()
 	go reader.drain()
 	return reader
 	return reader
 }
 }
@@ -209,14 +208,14 @@ func (r *bufReader) drain() {
 	buf := make([]byte, 1024)
 	buf := make([]byte, 1024)
 	for {
 	for {
 		n, err := r.reader.Read(buf)
 		n, err := r.reader.Read(buf)
-		r.l.Lock()
+		r.Lock()
 		if err != nil {
 		if err != nil {
 			r.err = err
 			r.err = err
 		} else {
 		} else {
 			r.buf.Write(buf[0:n])
 			r.buf.Write(buf[0:n])
 		}
 		}
 		r.wait.Signal()
 		r.wait.Signal()
-		r.l.Unlock()
+		r.Unlock()
 		if err != nil {
 		if err != nil {
 			break
 			break
 		}
 		}
@@ -224,8 +223,8 @@ func (r *bufReader) drain() {
 }
 }
 
 
 func (r *bufReader) Read(p []byte) (n int, err error) {
 func (r *bufReader) Read(p []byte) (n int, err error) {
-	r.l.Lock()
-	defer r.l.Unlock()
+	r.Lock()
+	defer r.Unlock()
 	for {
 	for {
 		n, err = r.buf.Read(p)
 		n, err = r.buf.Read(p)
 		if n > 0 {
 		if n > 0 {
@@ -247,27 +246,27 @@ func (r *bufReader) Close() error {
 }
 }
 
 
 type WriteBroadcaster struct {
 type WriteBroadcaster struct {
-	mu      sync.Mutex
+	sync.Mutex
 	writers map[io.WriteCloser]struct{}
 	writers map[io.WriteCloser]struct{}
 }
 }
 
 
 func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) {
 func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) {
-	w.mu.Lock()
+	w.Lock()
 	w.writers[writer] = struct{}{}
 	w.writers[writer] = struct{}{}
-	w.mu.Unlock()
+	w.Unlock()
 }
 }
 
 
 // FIXME: Is that function used?
 // FIXME: Is that function used?
 // FIXME: This relies on the concrete writer type used having equality operator
 // FIXME: This relies on the concrete writer type used having equality operator
 func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) {
 func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) {
-	w.mu.Lock()
+	w.Lock()
 	delete(w.writers, writer)
 	delete(w.writers, writer)
-	w.mu.Unlock()
+	w.Unlock()
 }
 }
 
 
 func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
 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 {
 	for writer := range w.writers {
 		if n, err := writer.Write(p); err != nil || n != len(p) {
 		if n, err := writer.Write(p); err != nil || n != len(p) {
 			// On error, evict the writer
 			// On error, evict the writer
@@ -278,8 +277,8 @@ func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
 }
 }
 
 
 func (w *WriteBroadcaster) CloseWriters() error {
 func (w *WriteBroadcaster) CloseWriters() error {
-	w.mu.Lock()
-	defer w.mu.Unlock()
+	w.Lock()
+	defer w.Unlock()
 	for writer := range w.writers {
 	for writer := range w.writers {
 		writer.Close()
 		writer.Close()
 	}
 	}
@@ -689,11 +688,7 @@ func ParseHost(host string, port int, addr string) string {
 }
 }
 
 
 func GetReleaseVersion() string {
 func GetReleaseVersion() string {
-	type githubTag struct {
-		Name string `json:"name"`
-	}
-
-	resp, err := http.Get("https://api.github.com/repos/dotcloud/docker/tags")
+	resp, err := http.Get("http://get.docker.io/latest")
 	if err != nil {
 	if err != nil {
 		return ""
 		return ""
 	}
 	}
@@ -702,10 +697,19 @@ func GetReleaseVersion() string {
 	if err != nil {
 	if err != nil {
 		return ""
 		return ""
 	}
 	}
-	var tags []githubTag
-	err = json.Unmarshal(body, &tags)
-	if err != nil || len(tags) == 0 {
-		return ""
+	return strings.TrimSpace(string(body))
+}
+
+// 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 strings.TrimPrefix(tags[0].Name, "v")
+	return repos, ""
 }
 }