Victor Vieux 12 лет назад
Родитель
Сommit
4d1692726b

+ 4 - 0
.mailmap

@@ -19,3 +19,7 @@ Andy Smith <github@anarkystic.com>
 <victor.vieux@dotcloud.com> <dev@vvieux.com>
 <victor.vieux@dotcloud.com> <dev@vvieux.com>
 <dominik@honnef.co> <dominikh@fork-bomb.org>
 <dominik@honnef.co> <dominikh@fork-bomb.org>
 Thatcher Peskens <thatcher@dotcloud.com>
 Thatcher Peskens <thatcher@dotcloud.com>
+<ehanchrow@ine.com> <eric.hanchrow@gmail.com>
+Walter Stanish <walter@pratyeka.org>
+<daniel@gasienica.ch> <dgasienica@zynga.com>
+Roberto Hashioka <roberto_hashioka@hotmail.com>

+ 21 - 1
AUTHORS

@@ -6,6 +6,8 @@
 Al Tobey <al@ooyala.com>
 Al Tobey <al@ooyala.com>
 Alexey Shamrin <shamrin@gmail.com>
 Alexey Shamrin <shamrin@gmail.com>
 Andrea Luzzardi <aluzzardi@gmail.com>
 Andrea Luzzardi <aluzzardi@gmail.com>
+Andreas Tiefenthaler <at@an-ti.eu>
+Andrew Munsell <andrew@wizardapps.net>
 Andy Rothfusz <github@metaliveblog.com>
 Andy Rothfusz <github@metaliveblog.com>
 Andy Smith <github@anarkystic.com>
 Andy Smith <github@anarkystic.com>
 Antony Messerli <amesserl@rackspace.com>
 Antony Messerli <amesserl@rackspace.com>
@@ -14,7 +16,9 @@ Brandon Liu <bdon@bdon.org>
 Brian McCallister <brianm@skife.org>
 Brian McCallister <brianm@skife.org>
 Bruno Bigras <bigras.bruno@gmail.com>
 Bruno Bigras <bigras.bruno@gmail.com>
 Caleb Spare <cespare@gmail.com>
 Caleb Spare <cespare@gmail.com>
+Calen Pennington <cale@edx.org>
 Charles Hooper <charles.hooper@dotcloud.com>
 Charles Hooper <charles.hooper@dotcloud.com>
+Christopher Currie <codemonkey+github@gmail.com>
 Daniel Gasienica <daniel@gasienica.ch>
 Daniel Gasienica <daniel@gasienica.ch>
 Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
 Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
 Daniel Robinson <gottagetmac@gmail.com>
 Daniel Robinson <gottagetmac@gmail.com>
@@ -22,11 +26,14 @@ Daniel Von Fange <daniel@leancoder.com>
 Dominik Honnef <dominik@honnef.co>
 Dominik Honnef <dominik@honnef.co>
 Don Spaulding <donspauldingii@gmail.com>
 Don Spaulding <donspauldingii@gmail.com>
 Dr Nic Williams <drnicwilliams@gmail.com>
 Dr Nic Williams <drnicwilliams@gmail.com>
+Elias Probst <mail@eliasprobst.eu>
+Eric Hanchrow <ehanchrow@ine.com>
 Evan Wies <evan@neomantra.net>
 Evan Wies <evan@neomantra.net>
 ezbercih <cem.ezberci@gmail.com>
 ezbercih <cem.ezberci@gmail.com>
 Flavio Castelli <fcastelli@suse.com>
 Flavio Castelli <fcastelli@suse.com>
 Francisco Souza <f@souza.cc>
 Francisco Souza <f@souza.cc>
 Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
 Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
+Gareth Rushgrove <gareth@morethanseven.net>
 Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
 Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
 Harley Laue <losinggeneration@gmail.com>
 Harley Laue <losinggeneration@gmail.com>
 Hunter Blanks <hunter@twilio.com>
 Hunter Blanks <hunter@twilio.com>
@@ -34,15 +41,21 @@ Jeff Lindsay <progrium@gmail.com>
 Jeremy Grosser <jeremy@synack.me>
 Jeremy Grosser <jeremy@synack.me>
 Joffrey F <joffrey@dotcloud.com>
 Joffrey F <joffrey@dotcloud.com>
 John Costa <john.costa@gmail.com>
 John Costa <john.costa@gmail.com>
+Jon Wedaman <jweede@gmail.com>
 Jonas Pfenniger <jonas@pfenniger.name>
 Jonas Pfenniger <jonas@pfenniger.name>
 Jonathan Rudenberg <jonathan@titanous.com>
 Jonathan Rudenberg <jonathan@titanous.com>
+Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
 Julien Barbier <write0@gmail.com>
 Julien Barbier <write0@gmail.com>
 Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
 Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
 Ken Cochrane <kencochrane@gmail.com>
 Ken Cochrane <kencochrane@gmail.com>
 Kevin J. Lynagh <kevin@keminglabs.com>
 Kevin J. Lynagh <kevin@keminglabs.com>
+kim0 <email.ahmedkamal@googlemail.com>
+Kiran Gangadharan <kiran.daredevil@gmail.com>
 Louis Opter <kalessin@kalessin.fr>
 Louis Opter <kalessin@kalessin.fr>
 Marcus Farkas <toothlessgear@finitebox.com>
 Marcus Farkas <toothlessgear@finitebox.com>
+Mark McGranaghan <mmcgrana@gmail.com>
 Maxim Treskin <zerthurd@gmail.com>
 Maxim Treskin <zerthurd@gmail.com>
+meejah <meejah@meejah.ca>
 Michael Crosby <crosby.michael@gmail.com>
 Michael Crosby <crosby.michael@gmail.com>
 Mikhail Sobolev <mss@mawhrin.net>
 Mikhail Sobolev <mss@mawhrin.net>
 Nate Jones <nate@endot.org>
 Nate Jones <nate@endot.org>
@@ -51,18 +64,25 @@ Niall O'Higgins <niallo@unworkable.org>
 odk- <github@odkurzacz.org>
 odk- <github@odkurzacz.org>
 Paul Bowsher <pbowsher@globalpersonals.co.uk>
 Paul Bowsher <pbowsher@globalpersonals.co.uk>
 Paul Hammond <paul@paulhammond.org>
 Paul Hammond <paul@paulhammond.org>
+Phil Spitler <pspitler@gmail.com>
 Piotr Bogdan <ppbogdan@gmail.com>
 Piotr Bogdan <ppbogdan@gmail.com>
+Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
 Robert Obryk <robryk@gmail.com>
 Robert Obryk <robryk@gmail.com>
+Roberto Hashioka <roberto_hashioka@hotmail.com>
 Sam Alba <sam.alba@gmail.com>
 Sam Alba <sam.alba@gmail.com>
+Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
 Shawn Siefkas <shawn.siefkas@meredith.com>
 Shawn Siefkas <shawn.siefkas@meredith.com>
 Silas Sewell <silas@sewell.org>
 Silas Sewell <silas@sewell.org>
 Solomon Hykes <solomon@dotcloud.com>
 Solomon Hykes <solomon@dotcloud.com>
 Sridhar Ratnakumar <sridharr@activestate.com>
 Sridhar Ratnakumar <sridharr@activestate.com>
 Thatcher Peskens <thatcher@dotcloud.com>
 Thatcher Peskens <thatcher@dotcloud.com>
 Thomas Bikeev <thomas.bikeev@mac.com>
 Thomas Bikeev <thomas.bikeev@mac.com>
+Thomas Hansen <thomas.hansen@gmail.com>
 Tianon Gravi <admwiggin@gmail.com>
 Tianon Gravi <admwiggin@gmail.com>
 Tim Terhorst <mynamewastaken+git@gmail.com>
 Tim Terhorst <mynamewastaken+git@gmail.com>
-Troy Howard <thoward37@gmail.com>
+Tobias Bieniek <Tobias.Bieniek@gmx.de>
 unclejack <unclejacksons@gmail.com>
 unclejack <unclejacksons@gmail.com>
 Victor Vieux <victor.vieux@dotcloud.com>
 Victor Vieux <victor.vieux@dotcloud.com>
 Vivek Agarwal <me@vivek.im>
 Vivek Agarwal <me@vivek.im>
+Walter Stanish <walter@pratyeka.org>
+Will Dietz <w@wdtz.org>

+ 23 - 0
CHANGELOG.md

@@ -1,5 +1,28 @@
 # Changelog
 # Changelog
 
 
+## 0.4.4 (2013-06-19)
+ - Builder: fix a regression introduced in 0.4.3 which caused builds to fail on new clients.
+
+## 0.4.3 (2013-06-19)
+ + Builder: ADD of a local file will detect tar archives and unpack them
+ * Runtime: Remove bsdtar dependency
+ * Runtime: Add unix socket and multiple -H support
+ * Runtime: Prevent rm of running containers
+ * Runtime: Use go1.1 cookiejar
+ * Builder: ADD improvements: use tar for copy + automatically unpack local archives
+ * Builder: ADD uses tar/untar for copies instead of calling 'cp -ar'
+ * Builder: nicer output for 'docker build'
+ * Builder: fixed the behavior of ADD to be (mostly) reverse-compatible, predictable and well-documented.
+ * Client: HumanReadable ProgressBar sizes in pull
+ * Client: Fix docker version's git commit output
+ * API: Send all tags on History API call
+ * API: Add tag lookup to history command. Fixes #882
+ - Runtime: Fix issue detaching from running TTY container
+ - Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311
+ - Runtime: Fix race condition within Run command when attaching.
+ - Builder: fix a bug which caused builds to fail if ADD was the first command
+ - Documentation: fix missing command in irc bouncer example
+
 ## 0.4.2 (2013-06-17)
 ## 0.4.2 (2013-06-17)
  - Packaging: Bumped version to work around an Ubuntu bug
  - Packaging: Bumped version to work around an Ubuntu bug
 
 

+ 1 - 0
FIXME

@@ -33,3 +33,4 @@ to put them - so we put them here :)
 * Caching after an ADD
 * Caching after an ADD
 * entry point config
 * entry point config
 * bring back git revision info, looks like it was lost
 * bring back git revision info, looks like it was lost
+* Clean up the ProgressReader api, it's a PITA to use

+ 5 - 2
Makefile

@@ -17,7 +17,7 @@ endif
 GIT_COMMIT = $(shell git rev-parse --short HEAD)
 GIT_COMMIT = $(shell git rev-parse --short HEAD)
 GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
 GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "+CHANGES")
 
 
-BUILD_OPTIONS = -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS)"
+BUILD_OPTIONS = -a -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS) -d -w"
 
 
 SRC_DIR := $(GOPATH)/src
 SRC_DIR := $(GOPATH)/src
 
 
@@ -33,7 +33,7 @@ all: $(DOCKER_BIN)
 
 
 $(DOCKER_BIN): $(DOCKER_DIR)
 $(DOCKER_BIN): $(DOCKER_DIR)
 	@mkdir -p  $(dir $@)
 	@mkdir -p  $(dir $@)
-	@(cd $(DOCKER_MAIN); go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
+	@(cd $(DOCKER_MAIN); CGO_ENABLED=0 go build $(GO_OPTIONS) $(BUILD_OPTIONS) -o $@)
 	@echo $(DOCKER_BIN_RELATIVE) is created.
 	@echo $(DOCKER_BIN_RELATIVE) is created.
 
 
 $(DOCKER_DIR):
 $(DOCKER_DIR):
@@ -74,6 +74,9 @@ endif
 test: all
 test: all
 	@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
 	@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
 
 
+testall: all
+	@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
+
 fmt:
 fmt:
 	@gofmt -s -l -w .
 	@gofmt -s -l -w .
 
 

+ 1 - 1
README.md

@@ -97,7 +97,7 @@ Quick install on Ubuntu 12.04 and 12.10
 ---------------------------------------
 ---------------------------------------
 
 
 ```bash
 ```bash
-curl get.docker.io | sh -x
+curl get.docker.io | sudo sh -x
 ```
 ```
 
 
 Binary installs
 Binary installs

+ 73 - 21
api.go

@@ -7,13 +7,19 @@ import (
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"io"
 	"io"
+	"io/ioutil"
 	"log"
 	"log"
+	"net"
 	"net/http"
 	"net/http"
+	"os"
+	"os/exec"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
 )
 )
 
 
-const APIVERSION = 1.2
+const APIVERSION = 1.3
+const DEFAULTHTTPHOST string = "127.0.0.1"
+const DEFAULTHTTPPORT int = 4243
 
 
 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
 	conn, _, err := w.(http.Hijacker).Hijack()
 	conn, _, err := w.(http.Hijacker).Hijack()
@@ -49,6 +55,10 @@ func httpError(w http.ResponseWriter, err error) {
 		http.Error(w, err.Error(), http.StatusConflict)
 		http.Error(w, err.Error(), http.StatusConflict)
 	} else if strings.HasPrefix(err.Error(), "Impossible") {
 	} else if strings.HasPrefix(err.Error(), "Impossible") {
 		http.Error(w, err.Error(), http.StatusNotAcceptable)
 		http.Error(w, err.Error(), http.StatusNotAcceptable)
+	} else if strings.HasPrefix(err.Error(), "Wrong login/password") {
+		http.Error(w, err.Error(), http.StatusUnauthorized)
+	} else if strings.Contains(err.Error(), "hasn't been activated") {
+		http.Error(w, err.Error(), http.StatusForbidden)
 	} else {
 	} else {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 	}
 	}
@@ -719,34 +729,65 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *
 }
 }
 
 
 func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	if err := r.ParseMultipartForm(4096); err != nil {
-		return err
+	if version < 1.3 {
+		return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
 	}
 	}
-	remote := r.FormValue("t")
+	remoteURL := r.FormValue("remote")
+	repoName := r.FormValue("t")
 	tag := ""
 	tag := ""
-	if strings.Contains(remote, ":") {
-		remoteParts := strings.Split(remote, ":")
+	if strings.Contains(repoName, ":") {
+		remoteParts := strings.Split(repoName, ":")
 		tag = remoteParts[1]
 		tag = remoteParts[1]
-		remote = remoteParts[0]
+		repoName = remoteParts[0]
 	}
 	}
 
 
-	dockerfile, _, err := r.FormFile("Dockerfile")
-	if err != nil {
-		return err
-	}
+	var context io.Reader
 
 
-	context, _, err := r.FormFile("Context")
-	if err != nil {
-		if err != http.ErrMissingFile {
+	if remoteURL == "" {
+		context = r.Body
+	} else if utils.IsGIT(remoteURL) {
+		if !strings.HasPrefix(remoteURL, "git://") {
+			remoteURL = "https://" + remoteURL
+		}
+		root, err := ioutil.TempDir("", "docker-build-git")
+		if err != nil {
 			return err
 			return err
 		}
 		}
-	}
+		defer os.RemoveAll(root)
 
 
+		if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil {
+			return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
+		}
+
+		c, err := Tar(root, Bzip2)
+		if err != nil {
+			return err
+		}
+		context = c
+	} else if utils.IsURL(remoteURL) {
+		f, err := utils.Download(remoteURL, ioutil.Discard)
+		if err != nil {
+			return err
+		}
+		defer f.Body.Close()
+		dockerFile, err := ioutil.ReadAll(f.Body)
+		if err != nil {
+			return err
+		}
+		c, err := mkBuildContext(string(dockerFile), nil)
+		if err != nil {
+			return err
+		}
+		context = c
+	}
 	b := NewBuildFile(srv, utils.NewWriteFlusher(w))
 	b := NewBuildFile(srv, utils.NewWriteFlusher(w))
-	if id, err := b.Build(dockerfile, context); err != nil {
+	id, err := b.Build(context)
+	if err != nil {
 		fmt.Fprintf(w, "Error build: %s\n", err)
 		fmt.Fprintf(w, "Error build: %s\n", err)
-	} else if remote != "" {
-		srv.runtime.repositories.Set(remote, tag, id, false)
+		return err
+	}
+	if repoName != "" {
+		srv.runtime.repositories.Set(repoName, tag, id, false)
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -816,6 +857,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 			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", localMethod, localRoute)
+
 				if logging {
 				if logging {
 					log.Println(r.Method, r.RequestURI)
 					log.Println(r.Method, r.RequestURI)
 				}
 				}
@@ -836,6 +878,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 					w.WriteHeader(http.StatusNotFound)
 					w.WriteHeader(http.StatusNotFound)
 					return
 					return
 				}
 				}
+
 				if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
 				if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
 					httpError(w, err)
 					httpError(w, err)
 				}
 				}
@@ -852,12 +895,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 	return r, nil
 	return r, nil
 }
 }
 
 
-func ListenAndServe(addr string, srv *Server, logging bool) error {
-	log.Printf("Listening for HTTP on %s\n", addr)
+func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
+	log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
 
 
 	r, err := createRouter(srv, logging)
 	r, err := createRouter(srv, logging)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	return http.ListenAndServe(addr, r)
+	l, e := net.Listen(proto, addr)
+	if e != nil {
+		return e
+	}
+	//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
+	if proto == "unix" {
+		os.Chmod(addr, 0777)
+	}
+	httpSrv := http.Server{Addr: addr, Handler: r}
+	return httpSrv.Serve(l)
 }
 }

+ 50 - 38
archive.go

@@ -1,7 +1,9 @@
 package docker
 package docker
 
 
 import (
 import (
+	"archive/tar"
 	"bufio"
 	"bufio"
+	"bytes"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
@@ -10,6 +12,7 @@ import (
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
 	"path"
 	"path"
+	"path/filepath"
 )
 )
 
 
 type Archive io.Reader
 type Archive io.Reader
@@ -160,51 +163,60 @@ func CopyWithTar(src, dst string) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	var dstExists bool
-	dstSt, err := os.Stat(dst)
+	if !srcSt.IsDir() {
+		return CopyFileWithTar(src, dst)
+	}
+	// Create dst, copy src's content into it
+	utils.Debugf("Creating dest directory: %s", dst)
+	if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) {
+		return err
+	}
+	utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
+	return TarUntar(src, nil, dst)
+}
+
+// CopyFileWithTar emulates the behavior of the 'cp' command-line
+// for a single file. It copies a regular file from path `src` to
+// path `dst`, and preserves all its metadata.
+//
+// If `dst` ends with a trailing slash '/', the final destination path
+// will be `dst/base(src)`.
+func CopyFileWithTar(src, dst string) error {
+	utils.Debugf("CopyFileWithTar(%s, %s)", src, dst)
+	srcSt, err := os.Stat(src)
 	if err != nil {
 	if err != nil {
-		if !os.IsNotExist(err) {
-			return err
-		}
-	} else {
-		dstExists = true
+		return err
 	}
 	}
-	// Things that can go wrong if the source is a directory
 	if srcSt.IsDir() {
 	if srcSt.IsDir() {
-		// The destination exists and is a regular file
-		if dstExists && !dstSt.IsDir() {
-			return fmt.Errorf("Can't copy a directory over a regular file")
-		}
-		// Things that can go wrong if the source is a regular file
-	} else {
-		utils.Debugf("The destination exists, it's a directory, and doesn't end in /")
-		// The destination exists, it's a directory, and doesn't end in /
-		if dstExists && dstSt.IsDir() && dst[len(dst)-1] != '/' {
-			return fmt.Errorf("Can't copy a regular file over a directory %s |%s|", dst, dst[len(dst)-1])
-		}
+		return fmt.Errorf("Can't copy a directory")
 	}
 	}
-	// Create the destination
-	var dstDir string
-	if srcSt.IsDir() || dst[len(dst)-1] == '/' {
-		// The destination ends in /, or the source is a directory
-		//   --> dst is the holding directory and needs to be created for -C
-		dstDir = dst
-	} else {
-		// The destination doesn't end in /
-		//   --> dst is the file
-		dstDir = path.Dir(dst)
+	// Clean up the trailing /
+	if dst[len(dst)-1] == '/' {
+		dst = path.Join(dst, filepath.Base(src))
 	}
 	}
-	if !dstExists {
-		// Create the holding directory if necessary
-		utils.Debugf("Creating the holding directory %s", dstDir)
-		if err := os.MkdirAll(dstDir, 0700); err != nil && !os.IsExist(err) {
-			return err
-		}
+	// Create the holding directory if necessary
+	if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
+		return err
 	}
 	}
-	if !srcSt.IsDir() {
-		return TarUntar(path.Dir(src), []string{path.Base(src)}, dstDir)
+	buf := new(bytes.Buffer)
+	tw := tar.NewWriter(buf)
+	hdr, err := tar.FileInfoHeader(srcSt, "")
+	if err != nil {
+		return err
+	}
+	hdr.Name = filepath.Base(dst)
+	if err := tw.WriteHeader(hdr); err != nil {
+		return err
+	}
+	srcF, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	if _, err := io.Copy(tw, srcF); err != nil {
+		return err
 	}
 	}
-	return TarUntar(src, nil, dstDir)
+	tw.Close()
+	return Untar(buf, filepath.Dir(dst))
 }
 }
 
 
 // CmdStream executes a command, and returns its stdout as a stream.
 // CmdStream executes a command, and returns its stdout as a stream.

+ 4 - 3
auth/auth.go

@@ -82,7 +82,7 @@ func decodeAuth(authStr string) (*AuthConfig, error) {
 func LoadConfig(rootPath string) (*AuthConfig, error) {
 func LoadConfig(rootPath string) (*AuthConfig, error) {
 	confFile := path.Join(rootPath, CONFIGFILE)
 	confFile := path.Join(rootPath, CONFIGFILE)
 	if _, err := os.Stat(confFile); err != nil {
 	if _, err := os.Stat(confFile); err != nil {
-		return &AuthConfig{rootPath:rootPath}, ErrConfigFileMissing
+		return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
 	}
 	}
 	b, err := ioutil.ReadFile(confFile)
 	b, err := ioutil.ReadFile(confFile)
 	if err != nil {
 	if err != nil {
@@ -146,7 +146,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
 
 
 	if reqStatusCode == 201 {
 	if reqStatusCode == 201 {
 		status = "Account created. Please use the confirmation link we sent" +
 		status = "Account created. Please use the confirmation link we sent" +
-			" to your e-mail to activate it.\n"
+			" to your e-mail to activate it."
 		storeConfig = true
 		storeConfig = true
 	} else if reqStatusCode == 403 {
 	} else if reqStatusCode == 403 {
 		return "", fmt.Errorf("Login: Your account hasn't been activated. " +
 		return "", fmt.Errorf("Login: Your account hasn't been activated. " +
@@ -165,10 +165,11 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
 				return "", err
 				return "", err
 			}
 			}
 			if resp.StatusCode == 200 {
 			if resp.StatusCode == 200 {
-				status = "Login Succeeded\n"
+				status = "Login Succeeded"
 				storeConfig = true
 				storeConfig = true
 			} else if resp.StatusCode == 401 {
 			} else if resp.StatusCode == 401 {
 				if store {
 				if store {
+					authConfig.Email = ""
 					if err := SaveConfig(authConfig); err != nil {
 					if err := SaveConfig(authConfig); err != nil {
 						return "", err
 						return "", err
 					}
 					}

+ 7 - 7
auth/auth_test.go

@@ -10,8 +10,8 @@ import (
 
 
 func TestEncodeAuth(t *testing.T) {
 func TestEncodeAuth(t *testing.T) {
 	newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
 	newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
-	authStr := EncodeAuth(newAuthConfig)
-	decAuthConfig, err := DecodeAuth(authStr)
+	authStr := encodeAuth(newAuthConfig)
+	decAuthConfig, err := decodeAuth(authStr)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -30,11 +30,11 @@ func TestLogin(t *testing.T) {
 	os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
 	os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
 	defer os.Setenv("DOCKER_INDEX_URL", "")
 	defer os.Setenv("DOCKER_INDEX_URL", "")
 	authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
 	authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
-	status, err := Login(authConfig)
+	status, err := Login(authConfig, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	if status != "Login Succeeded\n" {
+	if status != "Login Succeeded" {
 		t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
 		t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
 	}
 	}
 }
 }
@@ -50,17 +50,17 @@ func TestCreateAccount(t *testing.T) {
 	token := hex.EncodeToString(tokenBuffer)[:12]
 	token := hex.EncodeToString(tokenBuffer)[:12]
 	username := "ut" + token
 	username := "ut" + token
 	authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
 	authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
-	status, err := Login(authConfig)
+	status, err := Login(authConfig, false)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	expectedStatus := "Account created. Please use the confirmation link we sent" +
 	expectedStatus := "Account created. Please use the confirmation link we sent" +
-		" to your e-mail to activate it.\n"
+		" to your e-mail to activate it."
 	if status != expectedStatus {
 	if status != expectedStatus {
 		t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
 		t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
 	}
 	}
 
 
-	status, err = Login(authConfig)
+	status, err = Login(authConfig, false)
 	if err == nil {
 	if err == nil {
 		t.Fatalf("Expected error but found nil instead")
 		t.Fatalf("Expected error but found nil instead")
 	}
 	}

+ 0 - 314
builder_client.go

@@ -1,314 +0,0 @@
-package docker
-
-import (
-	"bufio"
-	"encoding/json"
-	"fmt"
-	"github.com/dotcloud/docker/utils"
-	"io"
-	"net/url"
-	"os"
-	"reflect"
-	"strings"
-)
-
-type builderClient struct {
-	cli *DockerCli
-
-	image      string
-	maintainer string
-	config     *Config
-
-	tmpContainers map[string]struct{}
-	tmpImages     map[string]struct{}
-
-	needCommit bool
-}
-
-func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
-	for i := range images {
-		if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
-			utils.Debugf("%s", err)
-		}
-		utils.Debugf("Removing image %s", i)
-	}
-}
-
-func (b *builderClient) CmdFrom(name string) error {
-	obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
-	if statusCode == 404 {
-
-		remote := name
-		var tag string
-		if strings.Contains(remote, ":") {
-			remoteParts := strings.Split(remote, ":")
-			tag = remoteParts[1]
-			remote = remoteParts[0]
-		}
-		var out io.Writer
-		if os.Getenv("DEBUG") != "" {
-			out = os.Stdout
-		} else {
-			out = &utils.NopWriter{}
-		}
-		if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil {
-			return err
-		}
-		obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
-		if err != nil {
-			return err
-		}
-	}
-	if err != nil {
-		return err
-	}
-
-	img := &APIID{}
-	if err := json.Unmarshal(obj, img); err != nil {
-		return err
-	}
-	b.image = img.ID
-	utils.Debugf("Using image %s", b.image)
-	return nil
-}
-
-func (b *builderClient) CmdMaintainer(name string) error {
-	b.needCommit = true
-	b.maintainer = name
-	return nil
-}
-
-func (b *builderClient) CmdRun(args string) error {
-	if b.image == "" {
-		return fmt.Errorf("Please provide a source image with `from` prior to run")
-	}
-	config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
-	if err != nil {
-		return err
-	}
-
-	cmd, env := b.config.Cmd, b.config.Env
-	b.config.Cmd = nil
-	MergeConfig(b.config, config)
-
-	body, statusCode, err := b.cli.call("POST", "/images/getCache", &APIImageConfig{ID: b.image, Config: b.config})
-	if err != nil {
-		if statusCode != 404 {
-			return err
-		}
-	}
-	if statusCode != 404 {
-		apiID := &APIID{}
-		if err := json.Unmarshal(body, apiID); err != nil {
-			return err
-		}
-		utils.Debugf("Use cached version")
-		b.image = apiID.ID
-		return nil
-	}
-	cid, err := b.run()
-	if err != nil {
-		return err
-	}
-	b.config.Cmd, b.config.Env = cmd, env
-	return b.commit(cid)
-}
-
-func (b *builderClient) CmdEnv(args string) error {
-	b.needCommit = true
-	tmp := strings.SplitN(args, " ", 2)
-	if len(tmp) != 2 {
-		return fmt.Errorf("Invalid ENV format")
-	}
-	key := strings.Trim(tmp[0], " ")
-	value := strings.Trim(tmp[1], " ")
-
-	for i, elem := range b.config.Env {
-		if strings.HasPrefix(elem, key+"=") {
-			b.config.Env[i] = key + "=" + value
-			return nil
-		}
-	}
-	b.config.Env = append(b.config.Env, key+"="+value)
-	return nil
-}
-
-func (b *builderClient) CmdCmd(args string) error {
-	b.needCommit = true
-	var cmd []string
-	if err := json.Unmarshal([]byte(args), &cmd); err != nil {
-		utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
-		b.config.Cmd = []string{"/bin/sh", "-c", args}
-	} else {
-		b.config.Cmd = cmd
-	}
-	return nil
-}
-
-func (b *builderClient) CmdExpose(args string) error {
-	ports := strings.Split(args, " ")
-	b.config.PortSpecs = append(ports, b.config.PortSpecs...)
-	return nil
-}
-
-func (b *builderClient) CmdInsert(args string) error {
-	// tmp := strings.SplitN(args, "\t ", 2)
-	// sourceUrl, destPath := tmp[0], tmp[1]
-
-	// v := url.Values{}
-	// v.Set("url", sourceUrl)
-	// v.Set("path", destPath)
-	// body, _, err := b.cli.call("POST", "/images/insert?"+v.Encode(), nil)
-	// if err != nil {
-	// 	return err
-	// }
-
-	// apiId := &APIId{}
-	// if err := json.Unmarshal(body, apiId); err != nil {
-	// 	return err
-	// }
-
-	// FIXME: Reimplement this, we need to retrieve the resulting Id
-	return fmt.Errorf("INSERT not implemented")
-}
-
-func (b *builderClient) run() (string, error) {
-	if b.image == "" {
-		return "", fmt.Errorf("Please provide a source image with `from` prior to run")
-	}
-	b.config.Image = b.image
-	body, _, err := b.cli.call("POST", "/containers/create", b.config)
-	if err != nil {
-		return "", err
-	}
-
-	apiRun := &APIRun{}
-	if err := json.Unmarshal(body, apiRun); err != nil {
-		return "", err
-	}
-	for _, warning := range apiRun.Warnings {
-		fmt.Fprintln(os.Stderr, "WARNING: ", warning)
-	}
-
-	//start the container
-	_, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil)
-	if err != nil {
-		return "", err
-	}
-	b.tmpContainers[apiRun.ID] = struct{}{}
-
-	// Wait for it to finish
-	body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil)
-	if err != nil {
-		return "", err
-	}
-	apiWait := &APIWait{}
-	if err := json.Unmarshal(body, apiWait); err != nil {
-		return "", err
-	}
-	if apiWait.StatusCode != 0 {
-		return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
-	}
-
-	return apiRun.ID, nil
-}
-
-func (b *builderClient) commit(id string) error {
-	if b.image == "" {
-		return fmt.Errorf("Please provide a source image with `from` prior to run")
-	}
-	b.config.Image = b.image
-
-	if id == "" {
-		cmd := b.config.Cmd
-		b.config.Cmd = []string{"true"}
-		cid, err := b.run()
-		if err != nil {
-			return err
-		}
-		id = cid
-		b.config.Cmd = cmd
-	}
-
-	// Commit the container
-	v := url.Values{}
-	v.Set("container", id)
-	v.Set("author", b.maintainer)
-
-	body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config)
-	if err != nil {
-		return err
-	}
-	apiID := &APIID{}
-	if err := json.Unmarshal(body, apiID); err != nil {
-		return err
-	}
-	b.tmpImages[apiID.ID] = struct{}{}
-	b.image = apiID.ID
-	b.needCommit = false
-	return nil
-}
-
-func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
-	defer b.clearTmp(b.tmpContainers, b.tmpImages)
-	file := bufio.NewReader(dockerfile)
-	for {
-		line, err := file.ReadString('\n')
-		if err != nil {
-			if err == io.EOF {
-				break
-			}
-			return "", err
-		}
-		line = strings.Replace(strings.TrimSpace(line), "	", " ", 1)
-		// Skip comments and empty line
-		if len(line) == 0 || line[0] == '#' {
-			continue
-		}
-		tmp := strings.SplitN(line, " ", 2)
-		if len(tmp) != 2 {
-			return "", fmt.Errorf("Invalid Dockerfile format")
-		}
-		instruction := strings.ToLower(strings.Trim(tmp[0], " "))
-		arguments := strings.Trim(tmp[1], " ")
-
-		fmt.Fprintf(os.Stderr, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
-
-		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
-		if !exists {
-			fmt.Fprintf(os.Stderr, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
-		}
-		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
-		if ret != nil {
-			return "", ret.(error)
-		}
-
-		fmt.Fprintf(os.Stderr, "===> %v\n", b.image)
-	}
-	if b.needCommit {
-		if err := b.commit(""); err != nil {
-			return "", err
-		}
-	}
-	if b.image != "" {
-		// The build is successful, keep the temporary containers and images
-		for i := range b.tmpImages {
-			delete(b.tmpImages, i)
-		}
-		for i := range b.tmpContainers {
-			delete(b.tmpContainers, i)
-		}
-		fmt.Fprintf(os.Stderr, "Build finished. image id: %s\n", b.image)
-		return b.image, nil
-	}
-	return "", fmt.Errorf("An error occured during the build\n")
-}
-
-func NewBuilderClient(addr string, port int) BuildFile {
-	return &builderClient{
-		cli:           NewDockerCli(addr, port),
-		config:        &Config{},
-		tmpContainers: make(map[string]struct{}),
-		tmpImages:     make(map[string]struct{}),
-	}
-}

+ 69 - 43
buildfile.go

@@ -14,7 +14,7 @@ import (
 )
 )
 
 
 type BuildFile interface {
 type BuildFile interface {
-	Build(io.Reader, io.Reader) (string, error)
+	Build(io.Reader) (string, error)
 	CmdFrom(string) error
 	CmdFrom(string) error
 	CmdRun(string) error
 	CmdRun(string) error
 }
 }
@@ -125,8 +125,8 @@ func (b *buildFile) CmdEnv(args string) error {
 	if len(tmp) != 2 {
 	if len(tmp) != 2 {
 		return fmt.Errorf("Invalid ENV format")
 		return fmt.Errorf("Invalid ENV format")
 	}
 	}
-	key := strings.Trim(tmp[0], " ")
-	value := strings.Trim(tmp[1], " ")
+	key := strings.Trim(tmp[0], " \t")
+	value := strings.Trim(tmp[1], " \t")
 
 
 	for i, elem := range b.config.Env {
 	for i, elem := range b.config.Env {
 		if strings.HasPrefix(elem, key+"=") {
 		if strings.HasPrefix(elem, key+"=") {
@@ -165,34 +165,17 @@ func (b *buildFile) CmdCopy(args string) error {
 	return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
 	return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
 }
 }
 
 
-func (b *buildFile) CmdAdd(args string) error {
-	if b.context == "" {
-		return fmt.Errorf("No context given. Impossible to use ADD")
-	}
-	tmp := strings.SplitN(args, " ", 2)
-	if len(tmp) != 2 {
-		return fmt.Errorf("Invalid ADD format")
-	}
-	orig := strings.Trim(tmp[0], " ")
-	dest := strings.Trim(tmp[1], " ")
-
-	cmd := b.config.Cmd
-
-	// Create the container and start it
-	b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
-	b.config.Image = b.image
-	container, err := b.builder.Create(b.config)
+func (b *buildFile) addRemote(container *Container, orig, dest string) error {
+	file, err := utils.Download(orig, ioutil.Discard)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	b.tmpContainers[container.ID] = struct{}{}
-	fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
+	defer file.Body.Close()
 
 
-	if err := container.EnsureMounted(); err != nil {
-		return err
-	}
-	defer container.Unmount()
+	return container.Inject(file.Body, dest)
+}
 
 
+func (b *buildFile) addContext(container *Container, orig, dest string) error {
 	origPath := path.Join(b.context, orig)
 	origPath := path.Join(b.context, orig)
 	destPath := path.Join(container.RootfsPath(), dest)
 	destPath := path.Join(container.RootfsPath(), dest)
 	// Preserve the trailing '/'
 	// Preserve the trailing '/'
@@ -218,6 +201,46 @@ func (b *buildFile) CmdAdd(args string) error {
 			return err
 			return err
 		}
 		}
 	}
 	}
+	return nil
+}
+
+func (b *buildFile) CmdAdd(args string) error {
+	if b.context == "" {
+		return fmt.Errorf("No context given. Impossible to use ADD")
+	}
+	tmp := strings.SplitN(args, " ", 2)
+	if len(tmp) != 2 {
+		return fmt.Errorf("Invalid ADD format")
+	}
+	orig := strings.Trim(tmp[0], " \t")
+	dest := strings.Trim(tmp[1], " \t")
+
+	cmd := b.config.Cmd
+	b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
+
+	b.config.Image = b.image
+	// Create the container and start it
+	container, err := b.builder.Create(b.config)
+	if err != nil {
+		return err
+	}
+	b.tmpContainers[container.ID] = struct{}{}
+
+	if err := container.EnsureMounted(); err != nil {
+		return err
+	}
+	defer container.Unmount()
+
+	if utils.IsURL(orig) {
+		if err := b.addRemote(container, orig, dest); err != nil {
+			return err
+		}
+	} else {
+		if err := b.addContext(container, orig, dest); err != nil {
+			return err
+		}
+	}
+
 	if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
 	if err := b.commit(container.ID, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
 		return err
 		return err
 	}
 	}
@@ -259,7 +282,9 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
 	}
 	}
 	b.config.Image = b.image
 	b.config.Image = b.image
 	if id == "" {
 	if id == "" {
+		cmd := b.config.Cmd
 		b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
 		b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
+		defer func(cmd []string) { b.config.Cmd = cmd }(cmd)
 
 
 		if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
 		if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
 			return err
 			return err
@@ -271,21 +296,17 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
 		} else {
 		} else {
 			utils.Debugf("[BUILDER] Cache miss")
 			utils.Debugf("[BUILDER] Cache miss")
 		}
 		}
-
-		// Create the container and start it
 		container, err := b.builder.Create(b.config)
 		container, err := b.builder.Create(b.config)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
 		b.tmpContainers[container.ID] = struct{}{}
 		b.tmpContainers[container.ID] = struct{}{}
 		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
 		if err := container.EnsureMounted(); err != nil {
 		if err := container.EnsureMounted(); err != nil {
 			return err
 			return err
 		}
 		}
 		defer container.Unmount()
 		defer container.Unmount()
-
-		id = container.ID
 	}
 	}
 
 
 	container := b.runtime.Get(id)
 	container := b.runtime.Get(id)
@@ -306,18 +327,23 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
 	return nil
 	return nil
 }
 }
 
 
-func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
-	if context != nil {
-		name, err := ioutil.TempDir("/tmp", "docker-build")
-		if err != nil {
-			return "", err
-		}
-		if err := Untar(context, name); err != nil {
-			return "", err
-		}
-		defer os.RemoveAll(name)
-		b.context = name
+func (b *buildFile) Build(context io.Reader) (string, error) {
+	// FIXME: @creack any reason for using /tmp instead of ""?
+	// FIXME: @creack "name" is a terrible variable name
+	name, err := ioutil.TempDir("/tmp", "docker-build")
+	if err != nil {
+		return "", err
+	}
+	if err := Untar(context, name); err != nil {
+		return "", err
+	}
+	defer os.RemoveAll(name)
+	b.context = name
+	dockerfile, err := os.Open(path.Join(name, "Dockerfile"))
+	if err != nil {
+		return "", fmt.Errorf("Can't build a directory with no Dockerfile")
 	}
 	}
+	// FIXME: "file" is also a terrible variable name ;)
 	file := bufio.NewReader(dockerfile)
 	file := bufio.NewReader(dockerfile)
 	stepN := 0
 	stepN := 0
 	for {
 	for {
@@ -329,7 +355,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
 				return "", err
 				return "", err
 			}
 			}
 		}
 		}
-		line = strings.Replace(strings.TrimSpace(line), "	", " ", 1)
+		line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n")
 		// Skip comments and empty line
 		// Skip comments and empty line
 		if len(line) == 0 || line[0] == '#' {
 		if len(line) == 0 || line[0] == '#' {
 			continue
 			continue

+ 77 - 64
buildfile_test.go

@@ -1,37 +1,91 @@
 package docker
 package docker
 
 
 import (
 import (
-	"github.com/dotcloud/docker/utils"
-	"strings"
+	"io/ioutil"
 	"testing"
 	"testing"
 )
 )
 
 
-const Dockerfile = `
-# VERSION		0.1
-# DOCKER-VERSION	0.2
-
-from   ` + unitTestImageName + `
-run    sh -c 'echo root:testpass > /tmp/passwd'
-run    mkdir -p /var/run/sshd
-`
+// mkTestContext generates a build context from the contents of the provided dockerfile.
+// This context is suitable for use as an argument to BuildFile.Build()
+func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
+	context, err := mkBuildContext(dockerfile, files)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return context
+}
 
 
-const DockerfileNoNewLine = `
-# VERSION		0.1
-# DOCKER-VERSION	0.2
+// A testContextTemplate describes a build context and how to test it
+type testContextTemplate struct {
+	// Contents of the Dockerfile
+	dockerfile string
+	// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
+	files [][2]string
+}
 
 
-from   ` + unitTestImageName + `
+// A table of all the contexts to build and test.
+// A new docker runtime will be created and torn down for each context.
+var testContexts []testContextTemplate = []testContextTemplate{
+	{
+		`
+from   docker-ut
 run    sh -c 'echo root:testpass > /tmp/passwd'
 run    sh -c 'echo root:testpass > /tmp/passwd'
-run    mkdir -p /var/run/sshd`
-
-// FIXME: test building with a context
-
-// FIXME: test building with a local ADD as first command
+run    mkdir -p /var/run/sshd
+run    [ "$(cat /tmp/passwd)" = "root:testpass" ]
+run    [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
+`,
+		nil,
+	},
+
+	{
+		`
+from docker-ut
+add foo /usr/lib/bla/bar
+run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
+`,
+		[][2]string{{"foo", "hello world!"}},
+	},
+
+	{
+		`
+from docker-ut
+add f /
+run [ "$(cat /f)" = "hello" ]
+add f /abc
+run [ "$(cat /abc)" = "hello" ]
+add f /x/y/z
+run [ "$(cat /x/y/z)" = "hello" ]
+add f /x/y/d/
+run [ "$(cat /x/y/d/f)" = "hello" ]
+add d /
+run [ "$(cat /ga)" = "bu" ]
+add d /somewhere
+run [ "$(cat /somewhere/ga)" = "bu" ]
+add d /anotherplace/
+run [ "$(cat /anotherplace/ga)" = "bu" ]
+add d /somewheeeere/over/the/rainbooow
+run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
+`,
+		[][2]string{
+			{"f", "hello"},
+			{"d/ga", "bu"},
+		},
+	},
+
+	{
+		`
+from docker-ut
+env    FOO BAR
+run    [ "$FOO" = "BAR" ]
+`,
+		nil,
+	},
+}
 
 
 // FIXME: test building with 2 successive overlapping ADD commands
 // FIXME: test building with 2 successive overlapping ADD commands
 
 
 func TestBuild(t *testing.T) {
 func TestBuild(t *testing.T) {
-	dockerfiles := []string{Dockerfile, DockerfileNoNewLine}
-	for _, Dockerfile := range dockerfiles {
+	for _, ctx := range testContexts {
 		runtime, err := newTestRuntime()
 		runtime, err := newTestRuntime()
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
@@ -40,50 +94,9 @@ func TestBuild(t *testing.T) {
 
 
 		srv := &Server{runtime: runtime}
 		srv := &Server{runtime: runtime}
 
 
-		buildfile := NewBuildFile(srv, &utils.NopWriter{})
-
-		imgID, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
-		if err != nil {
-			t.Fatal(err)
-		}
-
-		builder := NewBuilder(runtime)
-		container, err := builder.Create(
-			&Config{
-				Image: imgID,
-				Cmd:   []string{"cat", "/tmp/passwd"},
-			},
-		)
-		if err != nil {
-			t.Fatal(err)
-		}
-		defer runtime.Destroy(container)
-
-		output, err := container.Output()
-		if err != nil {
+		buildfile := NewBuildFile(srv, ioutil.Discard)
+		if _, err := buildfile.Build(mkTestContext(ctx.dockerfile, ctx.files, t)); err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
-		if string(output) != "root:testpass\n" {
-			t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
-		}
-
-		container2, err := builder.Create(
-			&Config{
-				Image: imgID,
-				Cmd:   []string{"ls", "-d", "/var/run/sshd"},
-			},
-		)
-		if err != nil {
-			t.Fatal(err)
-		}
-		defer runtime.Destroy(container2)
-
-		output, err = container2.Output()
-		if err != nil {
-			t.Fatal(err)
-		}
-		if string(output) != "/var/run/sshd\n" {
-			t.Fatal("/var/run/sshd has not been created")
-		}
 	}
 	}
 }
 }

+ 107 - 97
commands.go

@@ -1,6 +1,7 @@
 package docker
 package docker
 
 
 import (
 import (
+	"archive/tar"
 	"bytes"
 	"bytes"
 	"encoding/json"
 	"encoding/json"
 	"flag"
 	"flag"
@@ -10,14 +11,12 @@ import (
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
-	"mime/multipart"
 	"net"
 	"net"
 	"net/http"
 	"net/http"
 	"net/http/httputil"
 	"net/http/httputil"
 	"net/url"
 	"net/url"
 	"os"
 	"os"
 	"os/signal"
 	"os/signal"
-	"path"
 	"path/filepath"
 	"path/filepath"
 	"reflect"
 	"reflect"
 	"regexp"
 	"regexp"
@@ -29,7 +28,7 @@ import (
 	"unicode"
 	"unicode"
 )
 )
 
 
-const VERSION = "0.4.2"
+const VERSION = "0.4.4"
 
 
 var (
 var (
 	GITCOMMIT string
 	GITCOMMIT string
@@ -40,8 +39,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
 	return reflect.TypeOf(cli).MethodByName(methodName)
 	return reflect.TypeOf(cli).MethodByName(methodName)
 }
 }
 
 
-func ParseCommands(addr string, port int, args ...string) error {
-	cli := NewDockerCli(addr, port)
+func ParseCommands(proto, addr string, args ...string) error {
+	cli := NewDockerCli(proto, addr)
 
 
 	if len(args) > 0 {
 	if len(args) > 0 {
 		method, exists := cli.getMethod(args[0])
 		method, exists := cli.getMethod(args[0])
@@ -74,7 +73,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
 			return nil
 			return nil
 		}
 		}
 	}
 	}
-	help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n  -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
+	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)
 	for _, command := range [][2]string{
 	for _, command := range [][2]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"},
@@ -131,8 +130,33 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
 	return nil
 	return nil
 }
 }
 
 
+// mkBuildContext returns an archive of an empty context with the contents
+// of `dockerfile` at the path ./Dockerfile
+func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) {
+	buf := new(bytes.Buffer)
+	tw := tar.NewWriter(buf)
+	files = append(files, [2]string{"Dockerfile", dockerfile})
+	for _, file := range files {
+		name, content := file[0], file[1]
+		hdr := &tar.Header{
+			Name: name,
+			Size: int64(len(content)),
+		}
+		if err := tw.WriteHeader(hdr); err != nil {
+			return nil, err
+		}
+		if _, err := tw.Write([]byte(content)); err != nil {
+			return nil, err
+		}
+	}
+	if err := tw.Close(); err != nil {
+		return nil, err
+	}
+	return buf, nil
+}
+
 func (cli *DockerCli) CmdBuild(args ...string) error {
 func (cli *DockerCli) CmdBuild(args ...string) error {
-	cmd := Subcmd("build", "[OPTIONS] PATH | -", "Build a new container image from the source code at PATH")
+	cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
 	tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
 	tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
 	if err := cmd.Parse(args); err != nil {
 	if err := cmd.Parse(args); err != nil {
 		return nil
 		return nil
@@ -143,76 +167,55 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 	}
 	}
 
 
 	var (
 	var (
-		multipartBody io.Reader
-		file          io.ReadCloser
-		contextPath   string
+		context  Archive
+		isRemote bool
+		err      error
 	)
 	)
 
 
-	// Init the needed component for the Multipart
-	buff := bytes.NewBuffer([]byte{})
-	multipartBody = buff
-	w := multipart.NewWriter(buff)
-	boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
-
-	compression := Bzip2
-
 	if cmd.Arg(0) == "-" {
 	if cmd.Arg(0) == "-" {
-		file = os.Stdin
-	} else {
-		// Send Dockerfile from arg/Dockerfile (deprecate later)
-		f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile"))
-		if err != nil {
-			return err
-		}
-		file = f
-		// Send context from arg
-		// Create a FormFile multipart for the context if needed
-		// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
-		context, err := Tar(cmd.Arg(0), compression)
-		if err != nil {
-			return err
-		}
-		// NOTE: Do this in case '.' or '..' is input
-		absPath, err := filepath.Abs(cmd.Arg(0))
+		// As a special case, 'docker build -' will build from an empty context with the
+		// contents of stdin as a Dockerfile
+		dockerfile, err := ioutil.ReadAll(os.Stdin)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension())
-		if err != nil {
-			return err
-		}
-		// FIXME: Find a way to have a progressbar for the upload too
-		sf := utils.NewStreamFormatter(false)
-		io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf))
-		multipartBody = io.MultiReader(multipartBody, boundary)
+		context, err = mkBuildContext(string(dockerfile), nil)
+	} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) {
+		isRemote = true
+	} else {
+		context, err = Tar(cmd.Arg(0), Uncompressed)
 	}
 	}
-	// Create a FormFile multipart for the Dockerfile
-	wField, err := w.CreateFormFile("Dockerfile", "Dockerfile")
-	if err != nil {
-		return err
+	var body io.Reader
+	// Setup an upload progress bar
+	// FIXME: ProgressReader shouldn't be this annoyning to use
+	if context != nil {
+		sf := utils.NewStreamFormatter(false)
+		body = utils.ProgressReader(ioutil.NopCloser(context), 0, os.Stderr, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf)
 	}
 	}
-	io.Copy(wField, file)
-	multipartBody = io.MultiReader(multipartBody, boundary)
-
+	// Upload the build context
 	v := &url.Values{}
 	v := &url.Values{}
 	v.Set("t", *tag)
 	v.Set("t", *tag)
-	// Send the multipart request with correct content-type
-	req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody)
+	if isRemote {
+		v.Set("remote", cmd.Arg(0))
+	}
+	req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), body)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	req.Header.Set("Content-Type", w.FormDataContentType())
-	if contextPath != "" {
-		req.Header.Set("X-Docker-Context-Compression", compression.Flag())
-		fmt.Println("Uploading Context...")
+	if context != nil {
+		req.Header.Set("Content-Type", "application/tar")
 	}
 	}
-
-	resp, err := http.DefaultClient.Do(req)
+	dial, err := net.Dial(cli.proto, cli.addr)
+	if err != nil {
+		return err
+	}
+	clientconn := httputil.NewClientConn(dial, nil)
+	resp, err := clientconn.Do(req)
+	defer clientconn.Close()
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
-
 	// Check for errors
 	// Check for errors
 	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
 	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
 		body, err := ioutil.ReadAll(resp.Body)
 		body, err := ioutil.ReadAll(resp.Body)
@@ -311,6 +314,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
 			email = cli.authConfig.Email
 			email = cli.authConfig.Email
 		}
 		}
 	} else {
 	} else {
+		password = cli.authConfig.Password
 		email = cli.authConfig.Email
 		email = cli.authConfig.Email
 	}
 	}
 	term.RestoreTerminal(oldState)
 	term.RestoreTerminal(oldState)
@@ -319,7 +323,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
 	cli.authConfig.Password = password
 	cli.authConfig.Password = password
 	cli.authConfig.Email = email
 	cli.authConfig.Email = email
 
 
-	body, _, err := cli.call("POST", "/auth", cli.authConfig)
+	body, statusCode, err := cli.call("POST", "/auth", cli.authConfig)
+	if statusCode == 401 {
+		cli.authConfig.Username = ""
+		cli.authConfig.Password = ""
+		cli.authConfig.Email = ""
+		auth.SaveConfig(cli.authConfig)
+		return err
+	}
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -332,7 +343,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
 	}
 	}
 	auth.SaveConfig(cli.authConfig)
 	auth.SaveConfig(cli.authConfig)
 	if out2.Status != "" {
 	if out2.Status != "" {
-		fmt.Print(out2.Status)
+		fmt.Println(out2.Status)
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -1044,10 +1055,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
 		return nil
 		return nil
 	}
 	}
 
 
-	if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil {
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, os.Stdout); err != nil {
 		return err
 		return err
 	}
 	}
-	if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil {
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, os.Stderr); err != nil {
 		return err
 		return err
 	}
 	}
 	return nil
 	return nil
@@ -1078,37 +1089,18 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 		return fmt.Errorf("Impossible to attach to a stopped container, start it first")
 		return fmt.Errorf("Impossible to attach to a stopped container, start it first")
 	}
 	}
 
 
-	splitStderr := container.Config.Tty
-
-	connections := 1
-	if splitStderr {
-		connections += 1
-	}
-	chErrors := make(chan error, connections)
 	if container.Config.Tty {
 	if container.Config.Tty {
 		cli.monitorTtySize(cmd.Arg(0))
 		cli.monitorTtySize(cmd.Arg(0))
 	}
 	}
-	if splitStderr {
-		go func() {
-			chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
-		}()
-	}
+
 	v := url.Values{}
 	v := url.Values{}
 	v.Set("stream", "1")
 	v.Set("stream", "1")
 	v.Set("stdin", "1")
 	v.Set("stdin", "1")
 	v.Set("stdout", "1")
 	v.Set("stdout", "1")
-	if !splitStderr {
-		v.Set("stderr", "1")
-	}
-	go func() {
-		chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
-	}()
-	for connections > 0 {
-		err := <-chErrors
-		if err != nil {
-			return err
-		}
-		connections -= 1
+	v.Set("stderr", "1")
+
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout); err != nil {
+		return err
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -1334,7 +1326,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
 		params = bytes.NewBuffer(buf)
 		params = bytes.NewBuffer(buf)
 	}
 	}
 
 
-	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params)
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
 	if err != nil {
 	if err != nil {
 		return nil, -1, err
 		return nil, -1, err
 	}
 	}
@@ -1344,7 +1336,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
 	} else if method == "POST" {
 	} else if method == "POST" {
 		req.Header.Set("Content-Type", "plain/text")
 		req.Header.Set("Content-Type", "plain/text")
 	}
 	}
-	resp, err := http.DefaultClient.Do(req)
+	dial, err := net.Dial(cli.proto, cli.addr)
+	if err != nil {
+		return nil, -1, err
+	}
+	clientconn := httputil.NewClientConn(dial, nil)
+	resp, err := clientconn.Do(req)
+	defer clientconn.Close()
 	if err != nil {
 	if err != nil {
 		if strings.Contains(err.Error(), "connection refused") {
 		if strings.Contains(err.Error(), "connection refused") {
 			return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
 			return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@@ -1369,7 +1367,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
 	if (method == "POST" || method == "PUT") && in == nil {
 	if (method == "POST" || method == "PUT") && in == nil {
 		in = bytes.NewReader([]byte{})
 		in = bytes.NewReader([]byte{})
 	}
 	}
-	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in)
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -1377,7 +1375,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
 	if method == "POST" {
 	if method == "POST" {
 		req.Header.Set("Content-Type", "plain/text")
 		req.Header.Set("Content-Type", "plain/text")
 	}
 	}
-	resp, err := http.DefaultClient.Do(req)
+	dial, err := net.Dial(cli.proto, cli.addr)
+	if err != nil {
+		return err
+	}
+	clientconn := httputil.NewClientConn(dial, nil)
+	resp, err := clientconn.Do(req)
+	defer clientconn.Close()
 	if err != nil {
 	if err != nil {
 		if strings.Contains(err.Error(), "connection refused") {
 		if strings.Contains(err.Error(), "connection refused") {
 			return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
 			return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
@@ -1385,6 +1389,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
 		return err
 		return err
 	}
 	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
+
 	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
 	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
 		body, err := ioutil.ReadAll(resp.Body)
 		body, err := ioutil.ReadAll(resp.Body)
 		if err != nil {
 		if err != nil {
@@ -1422,19 +1427,24 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
 }
 }
 
 
 func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
 func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
+
 	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
 	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+	req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
 	req.Header.Set("Content-Type", "plain/text")
 	req.Header.Set("Content-Type", "plain/text")
-	dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
+
+	dial, err := net.Dial(cli.proto, cli.addr)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	clientconn := httputil.NewClientConn(dial, nil)
 	clientconn := httputil.NewClientConn(dial, nil)
-	clientconn.Do(req)
 	defer clientconn.Close()
 	defer clientconn.Close()
 
 
+	// Server hijacks the connection, error 'connection closed' expected
+	clientconn.Do(req)
+
 	rwc, br := clientconn.Hijack()
 	rwc, br := clientconn.Hijack()
 	defer rwc.Close()
 	defer rwc.Close()
 
 
@@ -1510,13 +1520,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
 	return flags
 	return flags
 }
 }
 
 
-func NewDockerCli(addr string, port int) *DockerCli {
+func NewDockerCli(proto, addr string) *DockerCli {
 	authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
 	authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
-	return &DockerCli{addr, port, authConfig}
+	return &DockerCli{proto, addr, authConfig}
 }
 }
 
 
 type DockerCli struct {
 type DockerCli struct {
-	host       string
-	port       int
+	proto      string
+	addr       string
 	authConfig *auth.AuthConfig
 	authConfig *auth.AuthConfig
 }
 }

+ 1 - 4
container.go

@@ -76,7 +76,7 @@ type Config struct {
 }
 }
 
 
 func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
 func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
-	cmd := Subcmd("run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
+	cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
 	if len(args) > 0 && args[0] != "--help" {
 	if len(args) > 0 && args[0] != "--help" {
 		cmd.SetOutput(ioutil.Discard)
 		cmd.SetOutput(ioutil.Discard)
 	}
 	}
@@ -632,7 +632,6 @@ func (container *Container) waitLxc() error {
 		}
 		}
 		time.Sleep(500 * time.Millisecond)
 		time.Sleep(500 * time.Millisecond)
 	}
 	}
-	panic("Unreachable")
 }
 }
 
 
 func (container *Container) monitor() {
 func (container *Container) monitor() {
@@ -821,8 +820,6 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
 	case <-done:
 	case <-done:
 		return nil
 		return nil
 	}
 	}
-
-	panic("Unreachable")
 }
 }
 
 
 func (container *Container) EnsureMounted() error {
 func (container *Container) EnsureMounted() error {

+ 0 - 1
contrib/crashTest.go

@@ -116,7 +116,6 @@ func crashTest() error {
 			return err
 			return err
 		}
 		}
 	}
 	}
-	return nil
 }
 }
 
 
 func main() {
 func main() {

+ 41 - 28
docker/docker.go

@@ -24,40 +24,29 @@ func main() {
 		docker.SysInit()
 		docker.SysInit()
 		return
 		return
 	}
 	}
-	host := "127.0.0.1"
-	port := 4243
 	// FIXME: Switch d and D ? (to be more sshd like)
 	// FIXME: Switch d and D ? (to be more sshd like)
 	flDaemon := flag.Bool("d", false, "Daemon mode")
 	flDaemon := flag.Bool("d", false, "Daemon mode")
 	flDebug := flag.Bool("D", false, "Debug mode")
 	flDebug := flag.Bool("D", false, "Debug mode")
 	flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
 	flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
 	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
 	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
-	flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
 	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
 	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
 	flDns := flag.String("dns", "", "Set custom dns servers")
 	flDns := flag.String("dns", "", "Set custom dns servers")
+	flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
+	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 {
+		flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage
+	}
+	for i, flHost := range flHosts {
+		flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
+	}
+
 	if *bridgeName != "" {
 	if *bridgeName != "" {
 		docker.NetworkBridgeIface = *bridgeName
 		docker.NetworkBridgeIface = *bridgeName
 	} else {
 	} else {
 		docker.NetworkBridgeIface = docker.DefaultNetworkBridge
 		docker.NetworkBridgeIface = docker.DefaultNetworkBridge
 	}
 	}
-
-	if strings.Contains(*flHost, ":") {
-		hostParts := strings.Split(*flHost, ":")
-		if len(hostParts) != 2 {
-			log.Fatal("Invalid bind address format.")
-			os.Exit(-1)
-		}
-		if hostParts[0] != "" {
-			host = hostParts[0]
-		}
-		if p, err := strconv.Atoi(hostParts[1]); err == nil {
-			port = p
-		}
-	} else {
-		host = *flHost
-	}
-
 	if *flDebug {
 	if *flDebug {
 		os.Setenv("DEBUG", "1")
 		os.Setenv("DEBUG", "1")
 	}
 	}
@@ -67,12 +56,17 @@ func main() {
 			flag.Usage()
 			flag.Usage()
 			return
 			return
 		}
 		}
-		if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil {
+		if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
 			log.Fatal(err)
 			log.Fatal(err)
 			os.Exit(-1)
 			os.Exit(-1)
 		}
 		}
 	} else {
 	} else {
-		if err := docker.ParseCommands(host, port, flag.Args()...); err != nil {
+		if len(flHosts) > 1 {
+			log.Fatal("Please specify only one -H")
+			return
+		}
+		protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
+		if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
 			log.Fatal(err)
 			log.Fatal(err)
 			os.Exit(-1)
 			os.Exit(-1)
 		}
 		}
@@ -106,10 +100,7 @@ func removePidFile(pidfile string) {
 	}
 	}
 }
 }
 
 
-func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error {
-	if addr != "127.0.0.1" {
-		log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
-	}
+func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
 	if err := createPidFile(pidfile); err != nil {
 	if err := createPidFile(pidfile); err != nil {
 		log.Fatal(err)
 		log.Fatal(err)
 	}
 	}
@@ -131,6 +122,28 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-
-	return docker.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), server, true)
+	chErrors := make(chan error, len(protoAddrs))
+	for _, protoAddr := range protoAddrs {
+		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
+		if protoAddrParts[0] == "unix" {
+			syscall.Unlink(protoAddrParts[1])
+		} else if protoAddrParts[0] == "tcp" {
+			if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
+				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
+			}
+		} else {
+			log.Fatal("Invalid protocol format.")
+			os.Exit(-1)
+		}
+		go func() {
+			chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
+		}()
+	}
+	for i := 0; i < len(protoAddrs); i += 1 {
+		err := <-chErrors
+		if err != nil {
+			return err
+		}
+	}
+	return nil
 }
 }

+ 29 - 2
docs/sources/api/docker_remote_api.rst

@@ -19,13 +19,35 @@ Docker Remote API
 2. Versions
 2. Versions
 ===========
 ===========
 
 
-The current verson of the API is 1.2
-Calling /images/<name>/insert is the same as calling /v1.2/images/<name>/insert
+The current verson of the API is 1.3
+Calling /images/<name>/insert is the same as calling /v1.3/images/<name>/insert
 You can still call an old version of the api using /v1.0/images/<name>/insert
 You can still call an old version of the api using /v1.0/images/<name>/insert
 
 
+:doc:`docker_remote_api_v1.3`
+*****************************
+
+What's new
+----------
+
+Builder (/build):
+
+- Simplify the upload of the build context
+- Simply stream a tarball instead of multipart upload with 4 intermediary buffers
+- Simpler, less memory usage, less disk usage and faster
+
+.. Note::
+The /build improvements are not reverse-compatible. Pre 1.3 clients will break on /build.
+
+List containers (/containers/json):
+
+- You can use size=1 to get the size of the containers
+
+
 :doc:`docker_remote_api_v1.2`
 :doc:`docker_remote_api_v1.2`
 *****************************
 *****************************
 
 
+docker v0.4.2 2e7649b_
+
 What's new
 What's new
 ----------
 ----------
 
 
@@ -36,6 +58,7 @@ The client should send it's authConfig as POST on each call of /images/(name)/pu
 .. http:post:: /auth only checks the configuration but doesn't store it on the server
 .. http:post:: /auth only checks the configuration but doesn't store it on the server
 
 
 Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
 Deleting an image is now improved, will only untag the image if it has chidrens and remove all the untagged parents if has any.
+
 .. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
 .. http:post:: /images/<name>/delete now returns a JSON with the list of images deleted/untagged
 
 
 
 
@@ -64,6 +87,9 @@ Uses json stream instead of HTML hijack, it looks like this:
 	   ...
 	   ...
 
 
 
 
+:doc:`docker_remote_api_v1.0`
+*****************************
+
 docker v0.3.4 8d73740_
 docker v0.3.4 8d73740_
 
 
 What's new
 What's new
@@ -74,6 +100,7 @@ Initial version
 
 
 .. _a8ae398: https://github.com/dotcloud/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f
 .. _a8ae398: https://github.com/dotcloud/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f
 .. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4
 .. _8d73740: https://github.com/dotcloud/docker/commit/8d73740343778651c09160cde9661f5f387b36f4
+.. _2e7649b: https://github.com/dotcloud/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168
 
 
 ==================================
 ==================================
 Docker Remote API Client Libraries
 Docker Remote API Client Libraries

+ 12 - 2
docs/sources/api/docker_remote_api_v1.2.rst

@@ -847,7 +847,7 @@ Build an image from Dockerfile via stdin
 
 
 .. http:post:: /build
 .. http:post:: /build
 
 
-	Build an image from Dockerfile via stdin
+	Build an image from Dockerfile
 
 
 	**Example request**:
 	**Example request**:
 
 
@@ -866,9 +866,12 @@ Build an image from Dockerfile via stdin
 	   {{ STREAM }}
 	   {{ STREAM }}
 
 
 	:query t: tag to be applied to the resulting image in case of success
 	:query t: tag to be applied to the resulting image in case of success
+	:query remote: resource to fetch, as URI
 	:statuscode 200: no error
 	:statuscode 200: no error
         :statuscode 500: server error
         :statuscode 500: server error
 
 
+{{ STREAM }} is the raw text output of the build command. It uses the HTTP Hijack method in order to stream.
+
 
 
 Check auth configuration
 Check auth configuration
 ************************
 ************************
@@ -895,9 +898,16 @@ Check auth configuration
         .. sourcecode:: http
         .. sourcecode:: http
 
 
            HTTP/1.1 200 OK
            HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"Status": "Login Succeeded"
+	   }
 
 
         :statuscode 200: no error
         :statuscode 200: no error
         :statuscode 204: no error
         :statuscode 204: no error
+        :statuscode 401: unauthorized
+        :statuscode 403: forbidden
         :statuscode 500: server error
         :statuscode 500: server error
 
 
 
 
@@ -1027,5 +1037,5 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a
 
 
 To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
 To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
     
     
-    docker -d -H="192.168.1.9:4243" -api-enable-cors
+    docker -d -H="tcp://192.168.1.9:4243" -api-enable-cors
 
 

+ 1039 - 0
docs/sources/api/docker_remote_api_v1.3.rst

@@ -0,0 +1,1039 @@
+:title: Remote API v1.3
+:description: API Documentation for Docker
+:keywords: API, Docker, rcli, REST, documentation
+
+======================
+Docker Remote API v1.3
+======================
+
+.. contents:: Table of Contents
+
+1. Brief introduction
+=====================
+
+- The Remote API is replacing rcli
+- Default port in the docker deamon is 4243 
+- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
+
+2. Endpoints
+============
+
+2.1 Containers
+--------------
+
+List containers
+***************
+
+.. http:get:: /containers/json
+
+	List containers
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Id": "8dfafdbc3a40",
+			"Image": "base:latest",
+			"Command": "echo 1",
+			"Created": 1367854155,
+			"Status": "Exit 0",
+			"Ports":"",
+			"SizeRw":12288,
+			"SizeRootFs":0
+		},
+		{
+			"Id": "9cd87474be90",
+			"Image": "base:latest",
+			"Command": "echo 222222",
+			"Created": 1367854155,
+			"Status": "Exit 0",
+			"Ports":"",
+			"SizeRw":12288,
+			"SizeRootFs":0
+		},
+		{
+			"Id": "3176a2479c92",
+			"Image": "base:latest",
+			"Command": "echo 3333333333333333",
+			"Created": 1367854154,
+			"Status": "Exit 0",
+			"Ports":"",
+			"SizeRw":12288,
+			"SizeRootFs":0
+		},
+		{
+			"Id": "4cb07b47f9fb",
+			"Image": "base:latest",
+			"Command": "echo 444444444444444444444444444444444",
+			"Created": 1367854152,
+			"Status": "Exit 0",
+			"Ports":"",
+			"SizeRw":12288,
+			"SizeRootFs":0
+		}
+	   ]
+ 
+	:query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default
+	:query limit: Show ``limit`` last created containers, include non-running ones.
+	:query since: Show only containers created since Id, include non-running ones.
+	:query before: Show only containers created before Id, include non-running ones.
+	:query size: 1/True/true or 0/False/false, Show the containers sizes
+	:statuscode 200: no error
+	:statuscode 400: bad parameter
+	:statuscode 500: server error
+
+
+Create a container
+******************
+
+.. http:post:: /containers/create
+
+	Create a container
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/create HTTP/1.1
+	   Content-Type: application/json
+
+	   {
+		"Hostname":"",
+		"User":"",
+		"Memory":0,
+		"MemorySwap":0,
+		"AttachStdin":false,
+		"AttachStdout":true,
+		"AttachStderr":true,
+		"PortSpecs":null,
+		"Tty":false,
+		"OpenStdin":false,
+		"StdinOnce":false,
+		"Env":null,
+		"Cmd":[
+			"date"
+		],
+		"Dns":null,
+		"Image":"base",
+		"Volumes":{},
+		"VolumesFrom":""
+	   }
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 201 OK
+	   Content-Type: application/json
+
+	   {
+		"Id":"e90e34656806"
+		"Warnings":[]
+	   }
+	
+	:jsonparam config: the container's configuration
+	:statuscode 201: no error
+	:statuscode 404: no such container
+	:statuscode 406: impossible to attach (container not running)
+	:statuscode 500: server error
+
+
+Inspect a container
+*******************
+
+.. http:get:: /containers/(id)/json
+
+	Return low-level information on the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/json HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+			"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
+			"Created": "2013-05-07T14:51:42.041847+02:00",
+			"Path": "date",
+			"Args": [],
+			"Config": {
+				"Hostname": "4fa6e0f0c678",
+				"User": "",
+				"Memory": 0,
+				"MemorySwap": 0,
+				"AttachStdin": false,
+				"AttachStdout": true,
+				"AttachStderr": true,
+				"PortSpecs": null,
+				"Tty": false,
+				"OpenStdin": false,
+				"StdinOnce": false,
+				"Env": null,
+				"Cmd": [
+					"date"
+				],
+				"Dns": null,
+				"Image": "base",
+				"Volumes": {},
+				"VolumesFrom": ""
+			},
+			"State": {
+				"Running": false,
+				"Pid": 0,
+				"ExitCode": 0,
+				"StartedAt": "2013-05-07T14:51:42.087658+02:01360",
+				"Ghost": false
+			},
+			"Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+			"NetworkSettings": {
+				"IpAddress": "",
+				"IpPrefixLen": 0,
+				"Gateway": "",
+				"Bridge": "",
+				"PortMapping": null
+			},
+			"SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker",
+			"ResolvConfPath": "/etc/resolv.conf",
+			"Volumes": {}
+	   }
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Inspect changes on a container's filesystem
+*******************************************
+
+.. http:get:: /containers/(id)/changes
+
+	Inspect changes on container ``id`` 's filesystem
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/changes HTTP/1.1
+
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Path":"/dev",
+			"Kind":0
+		},
+		{
+			"Path":"/dev/kmsg",
+			"Kind":1
+		},
+		{
+			"Path":"/test",
+			"Kind":1
+		}
+	   ]
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Export a container
+******************
+
+.. http:get:: /containers/(id)/export
+
+	Export the contents of container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/export HTTP/1.1
+
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/octet-stream
+	   
+	   {{ STREAM }}
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Start a container
+*****************
+
+.. http:post:: /containers/(id)/start
+
+	Start the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/start HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   	
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Stop a contaier
+***************
+
+.. http:post:: /containers/(id)/stop
+
+	Stop the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/stop?t=5 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+	   	
+	:query t: number of seconds to wait before killing the container
+	:statuscode 204: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Restart a container
+*******************
+
+.. http:post:: /containers/(id)/restart
+
+	Restart the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/restart?t=5 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+	   	
+	:query t: number of seconds to wait before killing the container
+	:statuscode 204: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Kill a container
+****************
+
+.. http:post:: /containers/(id)/kill
+
+	Kill the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/kill HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+	   	
+	:statuscode 204: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Attach to a container
+*********************
+
+.. http:post:: /containers/(id)/attach
+
+	Attach to the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/vnd.docker.raw-stream
+
+	   {{ STREAM }}
+	   	
+	:query logs: 1/True/true or 0/False/false, return logs. Default false
+	:query stream: 1/True/true or 0/False/false, return stream. Default false
+	:query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false
+	:query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false
+	:query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false
+	:statuscode 200: no error
+	:statuscode 400: bad parameter
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Wait a container
+****************
+
+.. http:post:: /containers/(id)/wait
+
+	Block until container ``id`` stops, then returns the exit code
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/16253994b7c4/wait HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {"StatusCode":0}
+	   	
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Remove a container
+*******************
+
+.. http:delete:: /containers/(id)
+
+	Remove the container ``id`` from the filesystem
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           DELETE /containers/16253994b7c4?v=1 HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+
+	:query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false
+        :statuscode 204: no error
+	:statuscode 400: bad parameter
+        :statuscode 404: no such container
+        :statuscode 500: server error
+
+
+2.2 Images
+----------
+
+List Images
+***********
+
+.. http:get:: /images/(format)
+
+	List images ``format`` could be json or viz (json default)
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /images/json?all=0 HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Repository":"base",
+			"Tag":"ubuntu-12.10",
+			"Id":"b750fe79269d",
+			"Created":1364102658,
+			"Size":24653,
+			"VirtualSize":180116135
+		},
+		{
+			"Repository":"base",
+			"Tag":"ubuntu-quantal",
+			"Id":"b750fe79269d",
+			"Created":1364102658,
+			"Size":24653,
+			"VirtualSize":180116135
+		}
+	   ]
+
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /images/viz HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: text/plain
+
+	   digraph docker {
+	   "d82cbacda43a" -> "074be284591f"
+	   "1496068ca813" -> "08306dc45919"
+	   "08306dc45919" -> "0e7893146ac2"
+	   "b750fe79269d" -> "1496068ca813"
+	   base -> "27cf78414709" [style=invis]
+	   "f71189fff3de" -> "9a33b36209ed"
+	   "27cf78414709" -> "b750fe79269d"
+	   "0e7893146ac2" -> "d6434d954665"
+	   "d6434d954665" -> "d82cbacda43a"
+	   base -> "e9aa60c60128" [style=invis]
+	   "074be284591f" -> "f71189fff3de"
+	   "b750fe79269d" [label="b750fe79269d\nbase",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
+	   "e9aa60c60128" [label="e9aa60c60128\nbase2",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
+	   "9a33b36209ed" [label="9a33b36209ed\ntest",shape=box,fillcolor="paleturquoise",style="filled,rounded"];
+	   base [style=invisible]
+	   }
+ 
+	:query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default
+	:statuscode 200: no error
+	:statuscode 400: bad parameter
+	:statuscode 500: server error
+
+
+Create an image
+***************
+
+.. http:post:: /images/create
+
+	Create an image, either by pull it from the registry or by importing it
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /images/create?fromImage=base HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {"status":"Pulling..."}
+	   {"status":"Pulling", "progress":"1/? (n/a)"}
+	   {"error":"Invalid..."}
+	   ...
+
+        :query fromImage: name of the image to pull
+	:query fromSrc: source to import, - means stdin
+        :query repo: repository
+	:query tag: tag
+	:query registry: the registry to pull from
+        :statuscode 200: no error
+        :statuscode 500: server error
+
+
+Insert a file in a image
+************************
+
+.. http:post:: /images/(name)/insert
+
+	Insert a file from ``url`` in the image ``name`` at ``path``
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {"status":"Inserting..."}
+	   {"status":"Inserting", "progress":"1/? (n/a)"}
+	   {"error":"Invalid..."}
+	   ...
+
+	:statuscode 200: no error
+        :statuscode 500: server error
+
+
+Inspect an image
+****************
+
+.. http:get:: /images/(name)/json
+
+	Return low-level information on the image ``name``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /images/base/json HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+		"parent":"27cf784147099545",
+		"created":"2013-03-23T22:24:18.818426-07:00",
+		"container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0",
+		"container_config":
+			{
+				"Hostname":"",
+				"User":"",
+				"Memory":0,
+				"MemorySwap":0,
+				"AttachStdin":false,
+				"AttachStdout":false,
+				"AttachStderr":false,
+				"PortSpecs":null,
+				"Tty":true,
+				"OpenStdin":true,
+				"StdinOnce":false,
+				"Env":null,
+				"Cmd": ["/bin/bash"]
+				,"Dns":null,
+				"Image":"base",
+				"Volumes":null,
+				"VolumesFrom":""
+			},
+		"Size": 6824592
+	   }
+
+	:statuscode 200: no error
+	:statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Get the history of an image
+***************************
+
+.. http:get:: /images/(name)/history
+
+        Return the history of the image ``name``
+
+        **Example request**:
+
+        .. sourcecode:: http
+
+           GET /images/base/history HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   [
+		{
+			"Id":"b750fe79269d",
+			"Created":1364102658,
+			"CreatedBy":"/bin/bash"
+		},
+		{
+			"Id":"27cf78414709",
+			"Created":1364068391,
+			"CreatedBy":""
+		}
+	   ]
+
+        :statuscode 200: no error
+        :statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Push an image on the registry
+*****************************
+
+.. http:post:: /images/(name)/push
+
+	Push the image ``name`` on the registry
+
+	 **Example request**:
+
+	 .. sourcecode:: http
+
+	    POST /images/test/push HTTP/1.1
+	    {{ authConfig }}
+
+	 **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {"status":"Pushing..."}
+	   {"status":"Pushing", "progress":"1/? (n/a)"}
+	   {"error":"Invalid..."}
+	   ...
+
+	:query registry: the registry you wan to push, optional
+	:statuscode 200: no error
+        :statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Tag an image into a repository
+******************************
+
+.. http:post:: /images/(name)/tag
+
+	Tag the image ``name`` into a repository
+
+        **Example request**:
+
+        .. sourcecode:: http
+			
+	   POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+
+	:query repo: The repository to tag in
+	:query force: 1/True/true or 0/False/false, default false
+	:statuscode 200: no error
+	:statuscode 400: bad parameter
+	:statuscode 404: no such image
+	:statuscode 409: conflict
+        :statuscode 500: server error
+
+
+Remove an image
+***************
+
+.. http:delete:: /images/(name)
+
+	Remove the image ``name`` from the filesystem 
+	
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   DELETE /images/test HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-type: application/json
+
+	   [
+	    {"Untagged":"3e2f21a89f"},
+	    {"Deleted":"3e2f21a89f"},
+	    {"Deleted":"53b4f83ac9"}
+	   ]
+
+	:statuscode 204: no error
+        :statuscode 404: no such image
+	:statuscode 409: conflict
+        :statuscode 500: server error
+
+
+Search images
+*************
+
+.. http:get:: /images/search
+
+	Search for an image in the docker index
+	
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /images/search?term=sshd HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Name":"cespare/sshd",
+			"Description":""
+		},
+		{
+			"Name":"johnfuller/sshd",
+			"Description":""
+		},
+		{
+			"Name":"dhrp/mongodb-sshd",
+			"Description":""
+		}
+	   ]
+
+	   :query term: term to search
+	   :statuscode 200: no error
+	   :statuscode 500: server error
+
+
+2.3 Misc
+--------
+
+Build an image from Dockerfile via stdin
+****************************************
+
+.. http:post:: /build
+
+	Build an image from Dockerfile via stdin
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /build HTTP/1.1
+	   
+	   {{ STREAM }}
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   
+	   {{ STREAM }}
+
+
+        The stream must be a tar archive compressed with one of the following algorithms:
+        identity (no compression), gzip, bzip2, xz. The archive must include a file called
+        `Dockerfile` at its root. It may include any number of other files, which will be
+        accessible in the build context (See the ADD build command).
+
+        The Content-type header should be set to "application/tar".
+
+	:query t: tag to be applied to the resulting image in case of success
+	:statuscode 200: no error
+        :statuscode 500: server error
+
+
+Check auth configuration
+************************
+
+.. http:post:: /auth
+
+        Get the default username and email
+
+        **Example request**:
+
+        .. sourcecode:: http
+
+           POST /auth HTTP/1.1
+	   Content-Type: application/json
+
+	   {
+		"username":"hannibal",
+		"password:"xxxx",
+		"email":"hannibal@a-team.com"
+	   }
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+
+        :statuscode 200: no error
+        :statuscode 204: no error
+        :statuscode 500: server error
+
+
+Display system-wide information
+*******************************
+
+.. http:get:: /info
+
+	Display system-wide information
+	
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /info HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"Containers":11,
+		"Images":16,
+		"Debug":false,
+		"NFd": 11,
+		"NGoroutines":21,
+		"MemoryLimit":true,
+		"SwapLimit":false
+	   }
+
+        :statuscode 200: no error
+        :statuscode 500: server error
+
+
+Show the docker version information
+***********************************
+
+.. http:get:: /version
+
+	Show the docker version information
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /version HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"Version":"0.2.2",
+		"GitCommit":"5a2a5cc+CHANGES",
+		"GoVersion":"go1.0.3"
+	   }
+
+        :statuscode 200: no error
+	:statuscode 500: server error
+
+
+Create a new image from a container's changes
+*********************************************
+
+.. http:post:: /commit
+
+	Create a new image from a container's changes
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 201 OK
+	   Content-Type: application/vnd.docker.raw-stream
+
+           {"Id":"596069db4bf5"}
+
+	:query container: source container
+	:query repo: repository
+	:query tag: tag
+	:query m: commit message
+	:query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
+	:query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
+        :statuscode 201: no error
+	:statuscode 404: no such container
+        :statuscode 500: server error
+
+
+3. Going further
+================
+
+3.1 Inside 'docker run'
+-----------------------
+
+Here are the steps of 'docker run' :
+
+* Create the container
+* If the status code is 404, it means the image doesn't exists:
+        * Try to pull it
+        * Then retry to create the container
+* Start the container
+* If you are not in detached mode:
+        * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1
+* If in detached mode or only stdin is attached:
+	* Display the container's id
+
+
+3.2 Hijacking
+-------------
+
+In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future.
+
+3.3 CORS Requests
+-----------------
+
+To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
+    
+    docker -d -H="192.168.1.9:4243" -api-enable-cors
+

+ 1 - 1
docs/sources/commandline/cli.rst

@@ -15,7 +15,7 @@ To list available commands, either run ``docker`` with no parameters or execute
 
 
   $ docker
   $ docker
     Usage: docker [OPTIONS] COMMAND [arg...]
     Usage: docker [OPTIONS] COMMAND [arg...]
-      -H="127.0.0.1:4243": Host:port to bind/connect to
+      -H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use
 
 
     A self-sufficient runtime for linux containers.
     A self-sufficient runtime for linux containers.
 
 

+ 12 - 2
docs/sources/commandline/command/build.rst

@@ -8,9 +8,11 @@
 
 
 ::
 ::
 
 
-    Usage: docker build [OPTIONS] PATH | -
+    Usage: docker build [OPTIONS] PATH | URL | -
     Build a new container image from the source code at PATH
     Build a new container image from the source code at PATH
       -t="": Tag to be applied to the resulting image in case of success.
       -t="": Tag to be applied to the resulting image in case of success.
+    When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
+
 
 
 Examples
 Examples
 --------
 --------
@@ -27,7 +29,15 @@ Examples
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
-    docker build -
+    docker build - < Dockerfile
 
 
 | This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon.
 | This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon.
 | ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container.
 | ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container.
+
+
+.. code-block:: bash
+
+    docker build github.com/creack/docker-firefox
+
+| This will clone the github repository and use it as context. The Dockerfile at the root of the repository is used as Dockerfile.
+| Note that you can specify an arbitrary git repository by using the 'git://' schema.

+ 1 - 1
docs/sources/commandline/command/run.rst

@@ -8,7 +8,7 @@
 
 
 ::
 ::
 
 
-    Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
+    Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
 
 
     Run a command in a new container
     Run a command in a new container
 
 

+ 2 - 2
docs/sources/installation/kernel.rst

@@ -100,7 +100,7 @@ Memory and Swap Accounting on Debian/Ubuntu
 If you use Debian or Ubuntu kernels, and want to enable memory and swap
 If you use Debian or Ubuntu kernels, and want to enable memory and swap
 accounting, you must add the following command-line parameters to your kernel::
 accounting, you must add the following command-line parameters to your kernel::
 
 
-    cgroup_enable=memory swapaccount
+    cgroup_enable=memory swapaccount=1
 
 
 On Debian or Ubuntu systems, if you use the default GRUB bootloader, you can
 On Debian or Ubuntu systems, if you use the default GRUB bootloader, you can
 add those parameters by editing ``/etc/default/grub`` and extending
 add those parameters by editing ``/etc/default/grub`` and extending
@@ -110,6 +110,6 @@ add those parameters by editing ``/etc/default/grub`` and extending
 
 
 And replace it by the following one::
 And replace it by the following one::
 
 
-    GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount"
+    GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1"
 
 
 Then run ``update-grub``, and reboot.
 Then run ``update-grub``, and reboot.

+ 24 - 4
docs/sources/use/basics.rst

@@ -33,11 +33,20 @@ Running an interactive shell
   # allocate a tty, attach stdin and stdout
   # allocate a tty, attach stdin and stdout
   docker run -i -t base /bin/bash
   docker run -i -t base /bin/bash
 
 
-Bind Docker to another host/port
---------------------------------
+Bind Docker to another host/port or a unix socket
+-------------------------------------------------
 
 
-If you want Docker to listen to another port and bind to another ip
-use -host and -port on both deamon and client
+With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody.
+
+Similarly, the Docker client can use -H to connect to a custom port.
+
+-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path
+For example:
+
+* tcp://host -> tcp connection on host:4243
+* tcp://host:port -> tcp connection on host:port
+* tcp://:port -> tcp connection on 127.0.0.1:port
+* unix://path/to/socket -> unix socket located at path/to/socket
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 
@@ -46,6 +55,17 @@ use -host and -port on both deamon and client
    # Download a base image
    # Download a base image
    docker -H :5555 pull base
    docker -H :5555 pull base
 
 
+You can use multiple -H, for example, if you want to listen
+on both tcp and a unix socket
+
+.. code-block:: bash
+
+   # Run docker in daemon mode
+   sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
+   # Download a base image
+   docker pull base
+   # OR
+   docker -H unix:///var/run/docker.sock pull base
 
 
 Starting a long-running worker process
 Starting a long-running worker process
 --------------------------------------
 --------------------------------------

+ 2 - 15
docs/sources/use/builder.rst

@@ -121,19 +121,7 @@ functionally equivalent to prefixing the command with `<key>=<value>`
 .. note::
 .. note::
     The environment variables will persist when a container is run from the resulting image.
     The environment variables will persist when a container is run from the resulting image.
 
 
-2.7 INSERT
-----------
-
-    ``INSERT <file url> <path>``
-
-The `INSERT` instruction will download the file from the given url to the given
-path within the image. It is similar to `RUN curl -o <path> <url>`, assuming 
-curl was installed within the image.
-
-.. note::
-    The path must include the file name.
-
-2.8 ADD
+2.7 ADD
 -------
 -------
 
 
     ``ADD <src> <dest>``
     ``ADD <src> <dest>``
@@ -141,7 +129,7 @@ curl was installed within the image.
 The `ADD` instruction will copy new files from <src> and add them to the container's filesystem at path `<dest>`.
 The `ADD` instruction will copy new files from <src> and add them to the container's filesystem at path `<dest>`.
 
 
 `<src>` must be the path to a file or directory relative to the source directory being built (also called the
 `<src>` must be the path to a file or directory relative to the source directory being built (also called the
-context of the build).
+context of the build) or a remote file URL.
 
 
 `<dest>` is the path at which the source will be copied in the destination container.
 `<dest>` is the path at which the source will be copied in the destination container.
 
 
@@ -182,7 +170,6 @@ files and directories are created with mode 0700, uid and gid 0.
     RUN apt-get update
     RUN apt-get update
     
     
     RUN apt-get install -y inotify-tools nginx apache2 openssh-server
     RUN apt-get install -y inotify-tools nginx apache2 openssh-server
-    INSERT https://raw.github.com/creack/docker-vps/master/nginx-wrapper.sh /usr/sbin/nginx-wrapper
 
 
 .. code-block:: bash
 .. code-block:: bash
 
 

+ 0 - 10
getKernelVersion_darwin.go

@@ -1,10 +0,0 @@
-package docker
-
-import (
-	"fmt"
-	"github.com/dotcloud/docker/utils"
-)
-
-func getKernelVersion() (*utils.KernelVersionInfo, error) {
-	return nil, fmt.Errorf("Kernel version detection is not available on darwin")
-}

+ 0 - 71
getKernelVersion_linux.go

@@ -1,71 +0,0 @@
-package docker
-
-import (
-	"bytes"
-	"github.com/dotcloud/docker/utils"
-	"strconv"
-	"strings"
-	"syscall"
-)
-
-// FIXME: Move this to utils package
-func getKernelVersion() (*utils.KernelVersionInfo, error) {
-	var (
-		uts                  syscall.Utsname
-		flavor               string
-		kernel, major, minor int
-		err                  error
-	)
-
-	if err := syscall.Uname(&uts); err != nil {
-		return nil, err
-	}
-
-	release := make([]byte, len(uts.Release))
-
-	i := 0
-	for _, c := range uts.Release {
-		release[i] = byte(c)
-		i++
-	}
-
-	// Remove the \x00 from the release for Atoi to parse correctly
-	release = release[:bytes.IndexByte(release, 0)]
-
-	tmp := strings.SplitN(string(release), "-", 2)
-	tmp2 := strings.SplitN(tmp[0], ".", 3)
-
-	if len(tmp2) > 0 {
-		kernel, err = strconv.Atoi(tmp2[0])
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	if len(tmp2) > 1 {
-		major, err = strconv.Atoi(tmp2[1])
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	if len(tmp2) > 2 {
-		minor, err = strconv.Atoi(tmp2[2])
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	if len(tmp) == 2 {
-		flavor = tmp[1]
-	} else {
-		flavor = ""
-	}
-
-	return &utils.KernelVersionInfo{
-		Kernel: kernel,
-		Major:  major,
-		Minor:  minor,
-		Flavor: flavor,
-	}, nil
-}

+ 3 - 8
hack/dockerbuilder/Dockerfile

@@ -19,19 +19,14 @@ run	add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc)
 run	add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu
 run	add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu
 run	apt-get update
 run	apt-get update
 # Packages required to checkout, build and upload docker
 # Packages required to checkout, build and upload docker
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
+run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd curl
 run	curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz
 run	curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz
 run	tar -C /usr/local -xzf /go.tar.gz
 run	tar -C /usr/local -xzf /go.tar.gz
 run	echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
 run	echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
 run	echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
 run	echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
+run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q git build-essential
 # Packages required to build an ubuntu package
 # Packages required to build an ubuntu package
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
-run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
-run	apt-get install -y -q devscripts
+run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable debhelper autotools-dev devscripts
 # Copy dockerbuilder files into the container
 # Copy dockerbuilder files into the container
 add	.       /src
 add	.       /src
 run	cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
 run	cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder

+ 0 - 1
network.go

@@ -257,7 +257,6 @@ func proxy(listener net.Listener, proto, address string) error {
 		utils.Debugf("Connected to backend, splicing")
 		utils.Debugf("Connected to backend, splicing")
 		splice(src, dst)
 		splice(src, dst)
 	}
 	}
-	panic("Unreachable")
 }
 }
 
 
 func halfSplice(dst, src net.Conn) error {
 func halfSplice(dst, src net.Conn) error {

+ 10 - 8
packaging/ubuntu/Makefile

@@ -2,11 +2,11 @@
 #
 #
 # Dependencies:  debhelper autotools-dev devscripts golang-stable
 # Dependencies:  debhelper autotools-dev devscripts golang-stable
 # Notes:
 # Notes:
-# Use 'make ubuntu' to create the ubuntu package
-# GPG_KEY environment variable needs to contain a GPG private key for package to be signed
-# and uploaded to docker PPA.
-# If GPG_KEY is not defined, make ubuntu will create docker package and exit with
-# status code 2
+# Use 'make ubuntu' to create the ubuntu package and push it to stating PPA by
+# default. To push to production, set PUBLISH_PPA=1 before doing 'make ubuntu'
+# GPG_KEY environment variable needs to contain a GPG private key for package
+# to be signed and uploaded to docker PPA. If GPG_KEY is not defined,
+# make ubuntu will create docker package and exit with status code 2
 
 
 PKG_NAME=lxc-docker
 PKG_NAME=lxc-docker
 GITHUB_PATH=github.com/dotcloud/docker
 GITHUB_PATH=github.com/dotcloud/docker
@@ -15,7 +15,7 @@ VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md)
 
 
 all:
 all:
 	# Compile docker. Used by dpkg-buildpackage.
 	# Compile docker. Used by dpkg-buildpackage.
-	cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build
+	cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} CGO_ENABLED=0 go build -a -ldflags '-d -w'
 
 
 install:
 install:
 	# Used by dpkg-buildpackage
 	# Used by dpkg-buildpackage
@@ -52,9 +52,11 @@ ubuntu:
 	if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi
 	if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi
 	mkdir ${BUILD_SRC}
 	mkdir ${BUILD_SRC}
 	# Import gpg signing key
 	# Import gpg signing key
-	echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import
+	echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import || true
 	# Sign the package
 	# Sign the package
 	cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${PKG_NAME}_${VERSION}-1.dsc
 	cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${PKG_NAME}_${VERSION}-1.dsc
 	cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa
 	cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa
-	cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes
+	# Upload to PPA
+	if [ "${PUBLISH_PPA}" = "1" ];  then cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes; fi
+	if [ "${PUBLISH_PPA}" != "1" ]; then cd ${BUILD_SRC};dput ppa:dotcloud/docker-staging ${PKG_NAME}_${VERSION}-1_source.changes; fi
 	rm -rf ${BUILD_SRC}
 	rm -rf ${BUILD_SRC}

+ 6 - 11
registry/registry.go

@@ -7,10 +7,10 @@ import (
 	"fmt"
 	"fmt"
 	"github.com/dotcloud/docker/auth"
 	"github.com/dotcloud/docker/auth"
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
-	"github.com/shin-/cookiejar"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
+	"net/http/cookiejar"
 	"net/url"
 	"net/url"
 	"strconv"
 	"strconv"
 	"strings"
 	"strings"
@@ -314,7 +314,7 @@ func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.R
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
-	req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme + ":", "", 1)
+	req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme+":", "", 1)
 	return req, err
 	return req, err
 }
 }
 
 
@@ -453,11 +453,6 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
 	return result, err
 	return result, err
 }
 }
 
 
-func (r *Registry) ResetClient(authConfig *auth.AuthConfig) {
-	r.authConfig = authConfig
-	r.client.Jar = cookiejar.NewCookieJar()
-}
-
 func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
 func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
 	password := ""
 	password := ""
 	if withPasswd {
 	if withPasswd {
@@ -493,18 +488,18 @@ type Registry struct {
 	authConfig *auth.AuthConfig
 	authConfig *auth.AuthConfig
 }
 }
 
 
-func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry {
+func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err error) {
 	httpTransport := &http.Transport{
 	httpTransport := &http.Transport{
 		DisableKeepAlives: true,
 		DisableKeepAlives: true,
 		Proxy:             http.ProxyFromEnvironment,
 		Proxy:             http.ProxyFromEnvironment,
 	}
 	}
 
 
-	r := &Registry{
+	r = &Registry{
 		authConfig: authConfig,
 		authConfig: authConfig,
 		client: &http.Client{
 		client: &http.Client{
 			Transport: httpTransport,
 			Transport: httpTransport,
 		},
 		},
 	}
 	}
-	r.client.Jar = cookiejar.NewCookieJar()
-	return r
+	r.client.Jar, err = cookiejar.New(nil)
+	return r, err
 }
 }

+ 20 - 9
server.go

@@ -55,8 +55,11 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
 }
 }
 
 
 func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
 func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
-
-	results, err := registry.NewRegistry(srv.runtime.root, nil).SearchRepositories(term)
+	r, err := registry.NewRegistry(srv.runtime.root, nil)
+	if err != nil {
+		return nil, err
+	}
+	results, err := r.SearchRepositories(term)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -451,12 +454,15 @@ func (srv *Server) poolRemove(kind, key string) error {
 }
 }
 
 
 func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
 func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
+	r, err := registry.NewRegistry(srv.runtime.root, authConfig)
+	if err != nil {
+		return err
+	}
 	if err := srv.poolAdd("pull", name+":"+tag); err != nil {
 	if err := srv.poolAdd("pull", name+":"+tag); err != nil {
 		return err
 		return err
 	}
 	}
 	defer srv.poolRemove("pull", name+":"+tag)
 	defer srv.poolRemove("pull", name+":"+tag)
 
 
-	r := registry.NewRegistry(srv.runtime.root, authConfig)
 	out = utils.NewWriteFlusher(out)
 	out = utils.NewWriteFlusher(out)
 	if endpoint != "" {
 	if endpoint != "" {
 		if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
 		if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
@@ -655,8 +661,10 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.Str
 
 
 	out = utils.NewWriteFlusher(out)
 	out = utils.NewWriteFlusher(out)
 	img, err := srv.runtime.graph.Get(name)
 	img, err := srv.runtime.graph.Get(name)
-	r := registry.NewRegistry(srv.runtime.root, authConfig)
-
+	r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
+	if err2 != nil {
+		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])))
 		out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
 		// If it fails, try to get the repository
 		// If it fails, try to get the repository
@@ -752,6 +760,9 @@ func (srv *Server) ContainerRestart(name string, t int) error {
 
 
 func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
 func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
 	if container := srv.runtime.Get(name); container != nil {
 	if container := srv.runtime.Get(name); container != nil {
+		if container.State.Running {
+			return fmt.Errorf("Impossible to remove a running container, please stop it first")
+		}
 		volumes := make(map[string]struct{})
 		volumes := make(map[string]struct{})
 		// Store all the deleted containers volumes
 		// Store all the deleted containers volumes
 		for _, volumeId := range container.Volumes {
 		for _, volumeId := range container.Volumes {
@@ -969,17 +980,17 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
 		if stdout {
 		if stdout {
 			cLog, err := container.ReadLog("stdout")
 			cLog, err := container.ReadLog("stdout")
 			if err != nil {
 			if err != nil {
-				utils.Debugf(err.Error())
+				utils.Debugf("Error reading logs (stdout): %s", err)
 			} else if _, err := io.Copy(out, cLog); err != nil {
 			} else if _, err := io.Copy(out, cLog); err != nil {
-				utils.Debugf(err.Error())
+				utils.Debugf("Error streaming logs (stdout): %s", err)
 			}
 			}
 		}
 		}
 		if stderr {
 		if stderr {
 			cLog, err := container.ReadLog("stderr")
 			cLog, err := container.ReadLog("stderr")
 			if err != nil {
 			if err != nil {
-				utils.Debugf(err.Error())
+				utils.Debugf("Error reading logs (stderr): %s", err)
 			} else if _, err := io.Copy(out, cLog); err != nil {
 			} else if _, err := io.Copy(out, cLog); err != nil {
-				utils.Debugf(err.Error())
+				utils.Debugf("Error streaming logs (stderr): %s", err)
 			}
 			}
 		}
 		}
 	}
 	}

+ 10 - 10
term/termios_darwin.go

@@ -9,16 +9,16 @@ const (
 	getTermios = syscall.TIOCGETA
 	getTermios = syscall.TIOCGETA
 	setTermios = syscall.TIOCSETA
 	setTermios = syscall.TIOCSETA
 
 
-	ECHO    = 0x00000008
-	ONLCR   = 0x2
-	ISTRIP  = 0x20
-	INLCR   = 0x40
-	ISIG    = 0x80
-	IGNCR   = 0x80
-	ICANON  = 0x100
-	ICRNL   = 0x100
-	IXOFF   = 0x400
-	IXON    = 0x200
+	ECHO   = 0x00000008
+	ONLCR  = 0x2
+	ISTRIP = 0x20
+	INLCR  = 0x40
+	ISIG   = 0x80
+	IGNCR  = 0x80
+	ICANON = 0x100
+	ICRNL  = 0x100
+	IXOFF  = 0x400
+	IXON   = 0x200
 )
 )
 
 
 type Termios struct {
 type Termios struct {

+ 36 - 3
utils/utils.go

@@ -10,6 +10,7 @@ import (
 	"index/suffixarray"
 	"index/suffixarray"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
+	"log"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
@@ -86,7 +87,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
 	}
 	}
 	if r.readProgress-r.lastUpdate > updateEvery || err != nil {
 	if r.readProgress-r.lastUpdate > updateEvery || err != nil {
 		if r.readTotal > 0 {
 		if r.readTotal > 0 {
-			fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
+			fmt.Fprintf(r.output, r.template, HumanSize(int64(r.readProgress)), HumanSize(int64(r.readTotal)), fmt.Sprintf("%2.0f%%",float64(r.readProgress)/float64(r.readTotal)*100))
 		} else {
 		} else {
 			fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
 			fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
 		}
 		}
@@ -146,7 +147,7 @@ func HumanSize(size int64) string {
 		sizef = sizef / 1000.0
 		sizef = sizef / 1000.0
 		i++
 		i++
 	}
 	}
-	return fmt.Sprintf("%.4g %s", sizef, units[i])
+	return fmt.Sprintf("%5.4g %s", sizef, units[i])
 }
 }
 
 
 func Trunc(s string, maxlen int) string {
 func Trunc(s string, maxlen int) string {
@@ -235,7 +236,6 @@ func (r *bufReader) Read(p []byte) (n int, err error) {
 		}
 		}
 		r.wait.Wait()
 		r.wait.Wait()
 	}
 	}
-	panic("unreachable")
 }
 }
 
 
 func (r *bufReader) Close() error {
 func (r *bufReader) Close() error {
@@ -636,6 +636,14 @@ func (sf *StreamFormatter) Used() bool {
 	return sf.used
 	return sf.used
 }
 }
 
 
+func IsURL(str string) bool {
+	return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://")
+}
+
+func IsGIT(str string) bool {
+	return strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "github.com/")
+}
+
 func CheckLocalDns() bool {
 func CheckLocalDns() bool {
 	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
 	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
 	if err != nil {
 	if err != nil {
@@ -652,3 +660,28 @@ func CheckLocalDns() bool {
 	}
 	}
 	return false
 	return false
 }
 }
+
+func ParseHost(host string, port int, addr string) string {
+	if strings.HasPrefix(addr, "unix://") {
+		return addr
+	}
+	if strings.HasPrefix(addr, "tcp://") {
+		addr = strings.TrimPrefix(addr, "tcp://")
+	}
+	if strings.Contains(addr, ":") {
+		hostParts := strings.Split(addr, ":")
+		if len(hostParts) != 2 {
+			log.Fatal("Invalid bind address format.")
+			os.Exit(-1)
+		}
+		if hostParts[0] != "" {
+			host = hostParts[0]
+		}
+		if p, err := strconv.Atoi(hostParts[1]); err == nil {
+			port = p
+		}
+	} else {
+		host = addr
+	}
+	return fmt.Sprintf("tcp://%s:%d", host, port)
+}

+ 18 - 0
utils/utils_test.go

@@ -274,3 +274,21 @@ func TestHumanSize(t *testing.T) {
 		t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
 		t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
 	}
 	}
 }
 }
+
+func TestParseHost(t *testing.T) {
+	if addr := ParseHost("127.0.0.1", 4243, "0.0.0.0"); addr != "tcp://0.0.0.0:4243" {
+		t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
+	}
+	if addr := ParseHost("127.0.0.1", 4243, "0.0.0.1:5555"); addr != "tcp://0.0.0.1:5555" {
+		t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
+	}
+	if addr := ParseHost("127.0.0.1", 4243, ":6666"); addr != "tcp://127.0.0.1:6666" {
+		t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
+	}
+	if addr := ParseHost("127.0.0.1", 4243, "tcp://:7777"); addr != "tcp://127.0.0.1:7777" {
+		t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
+	}
+	if addr := ParseHost("127.0.0.1", 4243, "unix:///var/run/docker.sock"); addr != "unix:///var/run/docker.sock" {
+		t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
+	}
+}