Prechádzať zdrojové kódy

Merge branch 'master' into simpler-build-upload

Solomon Hykes 12 rokov pred
rodič
commit
79efcb545d
47 zmenil súbory, kde vykonal 597 pridanie a 543 odobranie
  1. 1 0
      AUTHORS
  2. 20 0
      CHANGELOG.md
  3. 0 1
      FIXME
  4. 3 1
      Makefile
  5. 6 5
      README.md
  6. 8 0
      Vagrantfile
  7. 2 1
      api_params.go
  8. 147 4
      archive.go
  9. 46 5
      archive_test.go
  10. 22 19
      buildfile.go
  11. 6 0
      buildfile_test.go
  12. 14 32
      commands.go
  13. 1 1
      contrib/install.sh
  14. 1 0
      docs/sources/api/docker_remote_api_v1.2.rst
  15. 0 8
      docs/sources/concepts/containers.rst
  16. 0 125
      docs/sources/concepts/introduction.rst
  17. 19 5
      docs/sources/conf.py
  18. 1 1
      docs/sources/contributing/devenvironment.rst
  19. 1 1
      docs/sources/examples/running_redis_service.rst
  20. 2 0
      docs/sources/examples/running_ssh_service.rst
  21. 0 27
      docs/sources/index/variable.rst
  22. 1 2
      docs/sources/installation/binaries.rst
  23. 2 1
      docs/sources/use/basics.rst
  24. 37 6
      docs/sources/use/builder.rst
  25. 1 0
      docs/sources/use/index.rst
  26. 25 0
      docs/sources/use/port_redirection.rst
  27. 25 0
      docs/sources/use/workingwithrepository.rst
  28. 5 7
      docs/theme/docker/layout.html
  29. 12 0
      docs/theme/docker/redirect_home.html
  30. 6 4
      docs/theme/docker/static/css/main.css
  31. 15 14
      docs/theme/docker/static/css/main.less
  32. BIN
      docs/theme/docker/static/favicon.ico
  33. BIN
      docs/theme/docker/static/favicon.png
  34. 12 0
      graph_test.go
  35. 1 1
      hack/Vagrantfile
  36. 11 4
      hack/dockerbuilder/Dockerfile
  37. 7 6
      hack/dockerbuilder/dockerbuilder
  38. 0 3
      hack/dockerbuilder/s3cfg
  39. 7 0
      lxc_template.go
  40. 8 10
      packaging/ubuntu/Makefile
  41. 0 222
      packaging/ubuntu/changelog
  42. 23 0
      packaging/ubuntu/parse_changelog.py
  43. 19 13
      registry/registry.go
  44. 5 1
      runtime_test.go
  45. 72 11
      server.go
  46. 1 1
      testing/Vagrantfile
  47. 2 1
      utils/utils.go

+ 1 - 0
AUTHORS

@@ -41,6 +41,7 @@ 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>
 Louis Opter <kalessin@kalessin.fr>
 Louis Opter <kalessin@kalessin.fr>
+Marcus Farkas <toothlessgear@finitebox.com>
 Maxim Treskin <zerthurd@gmail.com>
 Maxim Treskin <zerthurd@gmail.com>
 Michael Crosby <crosby.michael@gmail.com>
 Michael Crosby <crosby.michael@gmail.com>
 Mikhail Sobolev <mss@mawhrin.net>
 Mikhail Sobolev <mss@mawhrin.net>

+ 20 - 0
CHANGELOG.md

@@ -1,5 +1,25 @@
 # Changelog
 # Changelog
 
 
+## 0.4.2 (2013-06-17)
+ - Packaging: Bumped version to work around an Ubuntu bug
+
+## 0.4.1 (2013-06-17)
+ + Remote Api: Add flag to enable cross domain requests
+ + Remote Api/Client: Add images and containers sizes in docker ps and docker images
+ + Runtime: Configure dns configuration host-wide with 'docker -d -dns'
+ + Runtime: Detect faulty DNS configuration and replace it with a public default
+ + Runtime: allow docker run <name>:<id>
+ + Runtime: you can now specify public port (ex: -p 80:4500)
+ * Client: allow multiple params in inspect
+ * Client: Print the container id before the hijack in `docker run`
+ * Registry: add regexp check on repo's name
+ * Registry: Move auth to the client
+ * Runtime: improved image removal to garbage-collect unreferenced parents
+ * Vagrantfile: Add the rest api port to vagrantfile's port_forward
+ * Upgrade to Go 1.1
+ - Builder: don't ignore last line in Dockerfile when it doesn't end with \n
+ - Registry: Remove login check on pull
+
 ## 0.4.0 (2013-06-03)
 ## 0.4.0 (2013-06-03)
  + Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
  + Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
  + Introducing Remote API: control Docker programmatically using a simple HTTP/json API
  + Introducing Remote API: control Docker programmatically using a simple HTTP/json API

+ 0 - 1
FIXME

@@ -16,7 +16,6 @@ to put them - so we put them here :)
 * Unify build commands and regular commands
 * Unify build commands and regular commands
 * Move source code into src/ subdir for clarity
 * Move source code into src/ subdir for clarity
 * Clean up the Makefile, it's a mess
 * Clean up the Makefile, it's a mess
-* docker buidl: show short IDs
 * docker build: on non-existent local path for ADD, don't show full absolute path on the host
 * docker build: on non-existent local path for ADD, don't show full absolute path on the host
 * mount into /dockerinit rather than /sbin/init
 * mount into /dockerinit rather than /sbin/init
 * docker tag foo REPO:TAG
 * docker tag foo REPO:TAG

+ 3 - 1
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.GIT_COMMIT $(GIT_COMMIT)$(GIT_STATUS)"
+BUILD_OPTIONS = -ldflags "-X main.GITCOMMIT $(GIT_COMMIT)$(GIT_STATUS)"
 
 
 SRC_DIR := $(GOPATH)/src
 SRC_DIR := $(GOPATH)/src
 
 
@@ -46,6 +46,7 @@ whichrelease:
 
 
 release: $(BINRELEASE)
 release: $(BINRELEASE)
 	s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
 	s3cmd -P put $(BINRELEASE) s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-$(RELEASE_VERSION).tgz
+	s3cmd -P put docker-latest.tgz s3://get.docker.io/builds/`uname -s`/`uname -m`/docker-latest.tgz
 
 
 srcrelease: $(SRCRELEASE)
 srcrelease: $(SRCRELEASE)
 deps: $(DOCKER_DIR)
 deps: $(DOCKER_DIR)
@@ -60,6 +61,7 @@ $(SRCRELEASE):
 $(BINRELEASE): $(SRCRELEASE)
 $(BINRELEASE): $(SRCRELEASE)
 	rm -f $(BINRELEASE)
 	rm -f $(BINRELEASE)
 	cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION)
 	cd $(SRCRELEASE); make; cp -R bin docker-$(RELEASE_VERSION); tar -f ../$(BINRELEASE) -zv -c docker-$(RELEASE_VERSION)
+	cd $(SRCRELEASE); cp -R bin docker-latest; tar -f ../docker-latest.tgz -zv -c docker-latest
 
 
 clean:
 clean:
 	@rm -rf $(dir $(DOCKER_BIN))
 	@rm -rf $(dir $(DOCKER_BIN))

+ 6 - 5
README.md

@@ -108,7 +108,7 @@ Note that some methods are community contributions and not yet officially suppor
 
 
 * [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/)
 * [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/)
 * [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/)
 * [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/)
-* [MacOS X (with Vagrant)](http://docs.docker.io/en/latest/installation/macos/)
+* [Mac OS X (with Vagrant)](http://docs.docker.io/en/latest/installation/vagrant/)
 * [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/)
 * [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/)
 * [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/)
 * [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/)
 
 
@@ -181,7 +181,7 @@ Running an irc bouncer
 ----------------------
 ----------------------
 
 
 ```bash
 ```bash
-BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc $USER $PASSWORD)
+BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc zncrun $USER $PASSWORD)
 echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine"
 echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine"
 ```
 ```
 
 
@@ -216,7 +216,8 @@ PORT=$(docker port $JOB 4444)
 
 
 # Connect to the public port via the host's public address
 # Connect to the public port via the host's public address
 # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
 # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
-IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
+# Replace *eth0* according to your local interface name.
+IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
 echo hello world | nc $IP $PORT
 echo hello world | nc $IP $PORT
 
 
 # Verify that the network connection worked
 # Verify that the network connection worked
@@ -262,14 +263,14 @@ Setting up a dev environment
 Instructions that have been verified to work on Ubuntu 12.10,
 Instructions that have been verified to work on Ubuntu 12.10,
 
 
 ```bash
 ```bash
-sudo apt-get -y install lxc wget bsdtar curl golang git
+sudo apt-get -y install lxc curl xz-utils golang git
 
 
 export GOPATH=~/go/
 export GOPATH=~/go/
 export PATH=$GOPATH/bin:$PATH
 export PATH=$GOPATH/bin:$PATH
 
 
 mkdir -p $GOPATH/src/github.com/dotcloud
 mkdir -p $GOPATH/src/github.com/dotcloud
 cd $GOPATH/src/github.com/dotcloud
 cd $GOPATH/src/github.com/dotcloud
-git clone git@github.com:dotcloud/docker.git
+git clone https://github.com/dotcloud/docker.git
 cd docker
 cd docker
 
 
 go get -v github.com/dotcloud/docker/...
 go get -v github.com/dotcloud/docker/...

+ 8 - 0
Vagrantfile

@@ -3,6 +3,7 @@
 
 
 BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
 BOX_NAME = ENV['BOX_NAME'] || "ubuntu"
 BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
 BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64.box"
+VF_BOX_URI = ENV['BOX_URI'] || "http://files.vagrantup.com/precise64_vmware_fusion.box"
 AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
 AWS_REGION = ENV['AWS_REGION'] || "us-east-1"
 AWS_AMI    = ENV['AWS_AMI']    || "ami-d0f89fb9"
 AWS_AMI    = ENV['AWS_AMI']    || "ami-d0f89fb9"
 FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
 FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS']
@@ -67,6 +68,13 @@ Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
     rs.image    = /Ubuntu/
     rs.image    = /Ubuntu/
   end
   end
 
 
+  config.vm.provider :vmware_fusion do |f, override|
+    override.vm.box = BOX_NAME
+    override.vm.box_url = VF_BOX_URI
+    override.vm.synced_folder ".", "/vagrant", disabled: true
+    f.vmx["displayName"] = "docker"
+  end
+
   config.vm.provider :virtualbox do |vb|
   config.vm.provider :virtualbox do |vb|
     config.vm.box = BOX_NAME
     config.vm.box = BOX_NAME
     config.vm.box_url = BOX_URI
     config.vm.box_url = BOX_URI

+ 2 - 1
api_params.go

@@ -1,7 +1,8 @@
 package docker
 package docker
 
 
 type APIHistory struct {
 type APIHistory struct {
-	ID        string `json:"Id"`
+	ID        string   `json:"Id"`
+	Tags      []string `json:",omitempty"`
 	Created   int64
 	Created   int64
 	CreatedBy string `json:",omitempty"`
 	CreatedBy string `json:",omitempty"`
 }
 }

+ 147 - 4
archive.go

@@ -1,12 +1,15 @@
 package docker
 package docker
 
 
 import (
 import (
+	"bufio"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
+	"github.com/dotcloud/docker/utils"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
+	"path"
 )
 )
 
 
 type Archive io.Reader
 type Archive io.Reader
@@ -20,6 +23,37 @@ const (
 	Xz
 	Xz
 )
 )
 
 
+func DetectCompression(source []byte) Compression {
+	for _, c := range source[:10] {
+		utils.Debugf("%x", c)
+	}
+
+	sourceLen := len(source)
+	for compression, m := range map[Compression][]byte{
+		Bzip2: {0x42, 0x5A, 0x68},
+		Gzip:  {0x1F, 0x8B, 0x08},
+		Xz:    {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00},
+	} {
+		fail := false
+		if len(m) > sourceLen {
+			utils.Debugf("Len too short")
+			continue
+		}
+		i := 0
+		for _, b := range m {
+			if b != source[i] {
+				fail = true
+				break
+			}
+			i++
+		}
+		if !fail {
+			return compression
+		}
+	}
+	return Uncompressed
+}
+
 func (compression *Compression) Flag() string {
 func (compression *Compression) Flag() string {
 	switch *compression {
 	switch *compression {
 	case Bzip2:
 	case Bzip2:
@@ -46,14 +80,43 @@ func (compression *Compression) Extension() string {
 	return ""
 	return ""
 }
 }
 
 
+// Tar creates an archive from the directory at `path`, and returns it as a
+// stream of bytes.
 func Tar(path string, compression Compression) (io.Reader, error) {
 func Tar(path string, compression Compression) (io.Reader, error) {
-	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
-	return CmdStream(cmd)
+	return TarFilter(path, compression, nil)
+}
+
+// Tar creates an archive from the directory at `path`, only including files whose relative
+// paths are included in `filter`. If `filter` is nil, then all files are included.
+func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
+	args := []string{"tar", "-f", "-", "-C", path}
+	if filter == nil {
+		filter = []string{"."}
+	}
+	for _, f := range filter {
+		args = append(args, "-c"+compression.Flag(), f)
+	}
+	return CmdStream(exec.Command(args[0], args[1:]...))
 }
 }
 
 
+// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
+// and unpacks it into the directory at `path`.
+// The archive may be compressed with one of the following algorithgms:
+//  identity (uncompressed), gzip, bzip2, xz.
+// FIXME: specify behavior when target path exists vs. doesn't exist.
 func Untar(archive io.Reader, path string) error {
 func Untar(archive io.Reader, path string) error {
-	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
-	cmd.Stdin = archive
+
+	bufferedArchive := bufio.NewReaderSize(archive, 10)
+	buf, err := bufferedArchive.Peek(10)
+	if err != nil {
+		return err
+	}
+	compression := DetectCompression(buf)
+
+	utils.Debugf("Archive compression detected: %s", compression.Extension())
+
+	cmd := exec.Command("tar", "-f", "-", "-C", path, "-x"+compression.Flag())
+	cmd.Stdin = bufferedArchive
 	// Hardcode locale environment for predictable outcome regardless of host configuration.
 	// Hardcode locale environment for predictable outcome regardless of host configuration.
 	//   (see https://github.com/dotcloud/docker/issues/355)
 	//   (see https://github.com/dotcloud/docker/issues/355)
 	cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
 	cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
@@ -64,6 +127,86 @@ func Untar(archive io.Reader, path string) error {
 	return nil
 	return nil
 }
 }
 
 
+// TarUntar is a convenience function which calls Tar and Untar, with
+// the output of one piped into the other. If either Tar or Untar fails,
+// TarUntar aborts and returns the error.
+func TarUntar(src string, filter []string, dst string) error {
+	utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
+	archive, err := TarFilter(src, Uncompressed, filter)
+	if err != nil {
+		return err
+	}
+	return Untar(archive, dst)
+}
+
+// UntarPath is a convenience function which looks for an archive
+// at filesystem path `src`, and unpacks it at `dst`.
+func UntarPath(src, dst string) error {
+	if archive, err := os.Open(src); err != nil {
+		return err
+	} else if err := Untar(archive, dst); err != nil {
+		return err
+	}
+	return nil
+}
+
+// CopyWithTar creates a tar archive of filesystem path `src`, and
+// unpacks it at filesystem path `dst`.
+// The archive is streamed directly with fixed buffering and no
+// intermediary disk IO.
+//
+func CopyWithTar(src, dst string) error {
+	srcSt, err := os.Stat(src)
+	if err != nil {
+		return err
+	}
+	var dstExists bool
+	dstSt, err := os.Stat(dst)
+	if err != nil {
+		if !os.IsNotExist(err) {
+			return err
+		}
+	} else {
+		dstExists = true
+	}
+	// Things that can go wrong if the source is a directory
+	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])
+		}
+	}
+	// 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)
+	}
+	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
+		}
+	}
+	if !srcSt.IsDir() {
+		return TarUntar(path.Dir(src), []string{path.Base(src)}, dstDir)
+	}
+	return TarUntar(src, nil, dstDir)
+}
+
 // CmdStream executes a command, and returns its stdout as a stream.
 // CmdStream executes a command, and returns its stdout as a stream.
 // If the command fails to run or doesn't complete successfully, an error
 // If the command fails to run or doesn't complete successfully, an error
 // will be returned, including anything written on stderr.
 // will be returned, including anything written on stderr.

+ 46 - 5
archive_test.go

@@ -1,10 +1,13 @@
 package docker
 package docker
 
 
 import (
 import (
+	"bytes"
+	"fmt"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
+	"path"
 	"testing"
 	"testing"
 	"time"
 	"time"
 )
 )
@@ -58,20 +61,58 @@ func TestCmdStreamGood(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func TestTarUntar(t *testing.T) {
-	archive, err := Tar(".", Uncompressed)
+func tarUntar(t *testing.T, origin string, compression Compression) error {
+	archive, err := Tar(origin, compression)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
+
+	buf := make([]byte, 10)
+	if _, err := archive.Read(buf); err != nil {
+		return err
+	}
+	archive = io.MultiReader(bytes.NewReader(buf), archive)
+
+	detectedCompression := DetectCompression(buf)
+	if detectedCompression.Extension() != compression.Extension() {
+		return fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
+	}
+
 	tmp, err := ioutil.TempDir("", "docker-test-untar")
 	tmp, err := ioutil.TempDir("", "docker-test-untar")
 	if err != nil {
 	if err != nil {
-		t.Fatal(err)
+		return err
 	}
 	}
 	defer os.RemoveAll(tmp)
 	defer os.RemoveAll(tmp)
 	if err := Untar(archive, tmp); err != nil {
 	if err := Untar(archive, tmp); err != nil {
-		t.Fatal(err)
+		return err
 	}
 	}
 	if _, err := os.Stat(tmp); err != nil {
 	if _, err := os.Stat(tmp); err != nil {
-		t.Fatalf("Error stating %s: %s", tmp, err.Error())
+		return err
+	}
+	return nil
+}
+
+func TestTarUntar(t *testing.T) {
+	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(origin)
+	if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
+		t.Fatal(err)
+	}
+	if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
+		t.Fatal(err)
+	}
+
+	for _, c := range []Compression{
+		Uncompressed,
+		Gzip,
+		Bzip2,
+		Xz,
+	} {
+		if err := tarUntar(t, origin, c); err != nil {
+			t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
+		}
 	}
 	}
 }
 }

+ 22 - 19
buildfile.go

@@ -101,6 +101,7 @@ func (b *buildFile) CmdRun(args string) error {
 	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
 	} else if cache != nil {
 	} else if cache != nil {
+		fmt.Fprintf(b.out, " ---> Using cache\n")
 		utils.Debugf("[BUILDER] Use cached version")
 		utils.Debugf("[BUILDER] Use cached version")
 		b.image = cache.ID
 		b.image = cache.ID
 		return nil
 		return nil
@@ -185,6 +186,7 @@ func (b *buildFile) CmdAdd(args string) error {
 		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))
 
 
 	if err := container.EnsureMounted(); err != nil {
 	if err := container.EnsureMounted(); err != nil {
 		return err
 		return err
@@ -193,30 +195,26 @@ func (b *buildFile) CmdAdd(args 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 '/'
+	if dest[len(dest)-1] == '/' {
+		destPath = destPath + "/"
+	}
 	fi, err := os.Stat(origPath)
 	fi, err := os.Stat(origPath)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 	if fi.IsDir() {
 	if fi.IsDir() {
-		if err := os.MkdirAll(destPath, 0700); err != nil {
-			return err
-		}
-
-		files, err := ioutil.ReadDir(path.Join(b.context, orig))
-		if err != nil {
+		if err := CopyWithTar(origPath, destPath); err != nil {
 			return err
 			return err
 		}
 		}
-		for _, fi := range files {
-			if err := utils.CopyDirectory(path.Join(origPath, fi.Name()), path.Join(destPath, fi.Name())); err != nil {
-				return err
-			}
-		}
-	} else {
+		// First try to unpack the source as an archive
+	} else if err := UntarPath(origPath, destPath); err != nil {
+		utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
+		// If that fails, just copy it as a regular file
 		if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
 		if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
 			return err
 			return err
 		}
 		}
-		if err := utils.CopyDirectory(origPath, destPath); err != nil {
+		if err := CopyWithTar(origPath, destPath); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -239,6 +237,7 @@ func (b *buildFile) run() (string, error) {
 		return "", err
 		return "", err
 	}
 	}
 	b.tmpContainers[c.ID] = struct{}{}
 	b.tmpContainers[c.ID] = struct{}{}
+	fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
 
 
 	//start the container
 	//start the container
 	if err := c.Start(); err != nil {
 	if err := c.Start(); err != nil {
@@ -265,6 +264,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
 		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
 		} else if cache != nil {
 		} else if cache != nil {
+			fmt.Fprintf(b.out, " ---> Using cache\n")
 			utils.Debugf("[BUILDER] Use cached version")
 			utils.Debugf("[BUILDER] Use cached version")
 			b.image = cache.ID
 			b.image = cache.ID
 			return nil
 			return nil
@@ -278,6 +278,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
 			return err
 			return err
 		}
 		}
 		b.tmpContainers[container.ID] = struct{}{}
 		b.tmpContainers[container.ID] = struct{}{}
+		fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(container.ID))
 
 
 		if err := container.EnsureMounted(); err != nil {
 		if err := container.EnsureMounted(); err != nil {
 			return err
 			return err
@@ -323,6 +324,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
 	}
 	}
 	// FIXME: "file" is also a terrible variable name ;)
 	// FIXME: "file" is also a terrible variable name ;)
 	file := bufio.NewReader(dockerfile)
 	file := bufio.NewReader(dockerfile)
+	stepN := 0
 	for {
 	for {
 		line, err := file.ReadString('\n')
 		line, err := file.ReadString('\n')
 		if err != nil {
 		if err != nil {
@@ -343,12 +345,13 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
 		}
 		}
 		instruction := strings.ToLower(strings.Trim(tmp[0], " "))
 		instruction := strings.ToLower(strings.Trim(tmp[0], " "))
 		arguments := strings.Trim(tmp[1], " ")
 		arguments := strings.Trim(tmp[1], " ")
-
-		fmt.Fprintf(b.out, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
+		stepN += 1
+		// FIXME: only count known instructions as build steps
+		fmt.Fprintf(b.out, "Step %d : %s %s\n", stepN, strings.ToUpper(instruction), arguments)
 
 
 		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
 		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
 		if !exists {
 		if !exists {
-			fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
+			fmt.Fprintf(b.out, "# Skipping unknown instruction %s\n", strings.ToUpper(instruction))
 			continue
 			continue
 		}
 		}
 		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
 		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
@@ -356,10 +359,10 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
 			return "", ret.(error)
 			return "", ret.(error)
 		}
 		}
 
 
-		fmt.Fprintf(b.out, "===> %v\n", b.image)
+		fmt.Fprintf(b.out, " ---> %v\n", utils.TruncateID(b.image))
 	}
 	}
 	if b.image != "" {
 	if b.image != "" {
-		fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image)
+		fmt.Fprintf(b.out, "Successfully built %s\n", utils.TruncateID(b.image))
 		return b.image, nil
 		return b.image, nil
 	}
 	}
 	return "", fmt.Errorf("An error occured during the build\n")
 	return "", fmt.Errorf("An error occured during the build\n")

+ 6 - 0
buildfile_test.go

@@ -63,6 +63,12 @@ add foo /usr/lib/bla/bar`,
 	},
 	},
 }
 }
 
 
+// FIXME: test building with a context
+
+// FIXME: test building with a local ADD as first command
+
+// FIXME: test building with 2 successive overlapping ADD commands
+
 func TestBuild(t *testing.T) {
 func TestBuild(t *testing.T) {
 	for _, ctx := range testContexts {
 	for _, ctx := range testContexts {
 		runtime, err := newTestRuntime()
 		runtime, err := newTestRuntime()

+ 14 - 32
commands.go

@@ -28,7 +28,7 @@ import (
 	"unicode"
 	"unicode"
 )
 )
 
 
-const VERSION = "0.4.0"
+const VERSION = "0.4.2"
 
 
 var (
 var (
 	GITCOMMIT string
 	GITCOMMIT string
@@ -617,7 +617,10 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
 	fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
 	fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
 
 
 	for _, out := range outs {
 	for _, out := range outs {
-		fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
+		if out.Tags != nil {
+			out.ID = out.Tags[0]
+		}
+		fmt.Fprintf(w, "%s \t%s ago\t%s\n", out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
 	}
 	}
 	w.Flush()
 	w.Flush()
 	return nil
 	return nil
@@ -1048,6 +1051,10 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
 		return err
 		return err
 	}
 	}
 
 
+	if !container.State.Running {
+		return fmt.Errorf("Impossible to attach to a stopped container, start it first")
+	}
+
 	splitStderr := container.Config.Tty
 	splitStderr := container.Config.Tty
 
 
 	connections := 1
 	connections := 1
@@ -1247,16 +1254,6 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		fmt.Fprintln(os.Stderr, "WARNING: ", warning)
 		fmt.Fprintln(os.Stderr, "WARNING: ", warning)
 	}
 	}
 
 
-	splitStderr := !config.Tty
-
-	connections := 0
-	if config.AttachStdin || config.AttachStdout || (!splitStderr && config.AttachStderr) {
-		connections += 1
-	}
-	if splitStderr && config.AttachStderr {
-		connections += 1
-	}
-
 	//start the container
 	//start the container
 	_, _, err = cli.call("POST", "/containers/"+out.ID+"/start", nil)
 	_, _, err = cli.call("POST", "/containers/"+out.ID+"/start", nil)
 	if err != nil {
 	if err != nil {
@@ -1265,19 +1262,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 
 
 	if !config.AttachStdout && !config.AttachStderr {
 	if !config.AttachStdout && !config.AttachStderr {
 		fmt.Println(out.ID)
 		fmt.Println(out.ID)
-	}
-	if connections > 0 {
-		chErrors := make(chan error, connections)
+	} else {
 		if config.Tty {
 		if config.Tty {
 			cli.monitorTtySize(out.ID)
 			cli.monitorTtySize(out.ID)
 		}
 		}
 
 
-		if splitStderr && config.AttachStderr {
-			go func() {
-				chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
-			}()
-		}
-
 		v := url.Values{}
 		v := url.Values{}
 		v.Set("logs", "1")
 		v.Set("logs", "1")
 		v.Set("stream", "1")
 		v.Set("stream", "1")
@@ -1288,19 +1277,12 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		if config.AttachStdout {
 		if config.AttachStdout {
 			v.Set("stdout", "1")
 			v.Set("stdout", "1")
 		}
 		}
-		if !splitStderr && config.AttachStderr {
+		if config.AttachStderr {
 			v.Set("stderr", "1")
 			v.Set("stderr", "1")
 		}
 		}
-		go func() {
-			chErrors <- cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
-		}()
-		for connections > 0 {
-			err := <-chErrors
-			if err != nil {
-				utils.Debugf("Error hijack: %s", err)
-				return err
-			}
-			connections -= 1
+		if err := cli.hijack("POST", "/containers/"+out.ID+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout); err != nil {
+			utils.Debugf("Error hijack: %s", err)
+			return err
 		}
 		}
 	}
 	}
 	return nil
 	return nil

+ 1 - 1
contrib/install.sh

@@ -8,7 +8,7 @@
 
 
 echo "Ensuring basic dependencies are installed..."
 echo "Ensuring basic dependencies are installed..."
 apt-get -qq update
 apt-get -qq update
-apt-get -qq install lxc wget bsdtar
+apt-get -qq install lxc wget
 
 
 echo "Looking in /proc/filesystems to see if we have AUFS support..."
 echo "Looking in /proc/filesystems to see if we have AUFS support..."
 if grep -q aufs /proc/filesystems
 if grep -q aufs /proc/filesystems

+ 1 - 0
docs/sources/api/docker_remote_api_v1.2.rst

@@ -691,6 +691,7 @@ Get the history of an image
 	   [
 	   [
 		{
 		{
 			"Id":"b750fe79269d",
 			"Id":"b750fe79269d",
+			"Tag":["base:latest"],
 			"Created":1364102658,
 			"Created":1364102658,
 			"CreatedBy":"/bin/bash"
 			"CreatedBy":"/bin/bash"
 		},
 		},

+ 0 - 8
docs/sources/concepts/containers.rst

@@ -1,8 +0,0 @@
-:title: Introduction
-:description: An introduction to docker and standard containers?
-:keywords: containers, lxc, concepts, explanation, docker, documentation
-
-
-:note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated
-
-This document has been moved to  :ref:`introduction`, please update your bookmarks.

+ 0 - 125
docs/sources/concepts/introduction.rst

@@ -1,125 +0,0 @@
-:title: Introduction
-:description: An introduction to docker and standard containers?
-:keywords: containers, lxc, concepts, explanation
-
-Introduction
-============
-
-Docker -- The Linux container runtime
--------------------------------------
-
-Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
-
-Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
-
-
-- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
-- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
-- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
-- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
-
-.. image:: images/lego_docker.jpg
-
-
-What is a Standard Container?
------------------------------
-
-Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
-a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
-
-The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
-
-A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
-
-Standard operations
-~~~~~~~~~~~~~~~~~~~
-
-Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
-
-
-Content-agnostic
-~~~~~~~~~~~~~~~~~~~
-
-Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
-
-
-Infrastructure-agnostic
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
-
-
-Designed for automation
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
-
-Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
-
-Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
-
-
-Industrial-grade delivery
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
-
-With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
-
-
-Standard Container Specification
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-(TODO)
-
-Image format
-~~~~~~~~~~~~
-
-Standard operations
-~~~~~~~~~~~~~~~~~~~
-
--  Copy
--  Run
--  Stop
--  Wait
--  Commit
--  Attach standard streams
--  List filesystem changes
--  ...
-
-Execution environment
-~~~~~~~~~~~~~~~~~~~~~
-
-Root filesystem
-^^^^^^^^^^^^^^^
-
-Environment variables
-^^^^^^^^^^^^^^^^^^^^^
-
-Process arguments
-^^^^^^^^^^^^^^^^^
-
-Networking
-^^^^^^^^^^
-
-Process namespacing
-^^^^^^^^^^^^^^^^^^^
-
-Resource limits
-^^^^^^^^^^^^^^^
-
-Process monitoring
-^^^^^^^^^^^^^^^^^^
-
-Logging
-^^^^^^^
-
-Signals
-^^^^^^^
-
-Pseudo-terminal allocation
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Security
-^^^^^^^^
-

+ 19 - 5
docs/sources/conf.py

@@ -20,6 +20,20 @@ import sys, os
 
 
 # -- General configuration -----------------------------------------------------
 # -- General configuration -----------------------------------------------------
 
 
+
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# the 'redirect_home.html' page redirects using a http meta refresh which, according
+# to official sources is more or less equivalent of a 301.
+
+html_additional_pages = {
+    'concepts/containers': 'redirect_home.html',
+    'concepts/introduction': 'redirect_home.html',
+    }
+
+
+
 # If your documentation needs a minimal Sphinx version, state it here.
 # If your documentation needs a minimal Sphinx version, state it here.
 #needs_sphinx = '1.0'
 #needs_sphinx = '1.0'
 
 
@@ -120,7 +134,11 @@ html_theme_path = ['../theme']
 # The name of an image file (within the static path) to use as favicon of the
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
 # pixels large.
-#html_favicon = None
+
+# We use a png favicon. This is not compatible with internet explorer, but looks
+# much better on all other browsers. However, sphynx doesn't like it (it likes
+# .ico better) so we have just put it in the template rather than used this setting
+# html_favicon = 'favicon.png'
 
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # relative to this directory. They are copied after the builtin static files,
@@ -138,10 +156,6 @@ html_static_path = ['static_files']
 # Custom sidebar templates, maps document names to template names.
 # Custom sidebar templates, maps document names to template names.
 #html_sidebars = {}
 #html_sidebars = {}
 
 
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
 # If false, no module index is generated.
 # If false, no module index is generated.
 #html_domain_indices = True
 #html_domain_indices = True
 
 

+ 1 - 1
docs/sources/contributing/devenvironment.rst

@@ -33,7 +33,7 @@ Installation
     sudo apt-get install python-software-properties
     sudo apt-get install python-software-properties
     sudo add-apt-repository ppa:gophers/go
     sudo add-apt-repository ppa:gophers/go
     sudo apt-get update
     sudo apt-get update
-    sudo apt-get -y install lxc wget bsdtar curl golang-stable git aufs-tools
+    sudo apt-get -y install lxc xz-utils curl golang-stable git aufs-tools
 
 
     export GOPATH=~/go/
     export GOPATH=~/go/
     export PATH=$GOPATH/bin:$PATH
     export PATH=$GOPATH/bin:$PATH

+ 1 - 1
docs/sources/examples/running_redis_service.rst

@@ -72,7 +72,7 @@ Connect to the host os with the redis-cli.
 
 
     docker ps  # grab the new container id
     docker ps  # grab the new container id
     docker port <container_id> 6379  # grab the external port
     docker port <container_id> 6379  # grab the external port
-    ifconfig   # grab the host ip address
+    ip addr show   # grab the host ip address
     redis-cli -h <host ipaddress> -p <external port>
     redis-cli -h <host ipaddress> -p <external port>
     redis 192.168.0.1:49153> set docker awesome
     redis 192.168.0.1:49153> set docker awesome
     OK
     OK

+ 2 - 0
docs/sources/examples/running_ssh_service.rst

@@ -59,6 +59,7 @@ The password is 'screencast'
 	 # it has now given us a port to connect to
 	 # it has now given us a port to connect to
 	 # we have to connect using a public ip of our host
 	 # we have to connect using a public ip of our host
 	 $ hostname
 	 $ hostname
+	 # *ifconfig* is deprecated, better use *ip addr show* now
 	 $ ifconfig
 	 $ ifconfig
 	 $ ssh root@192.168.33.10 -p 49153
 	 $ ssh root@192.168.33.10 -p 49153
 	 # Ah! forgot to set root passwd
 	 # Ah! forgot to set root passwd
@@ -70,6 +71,7 @@ The password is 'screencast'
 	 $ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
 	 $ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
 	 $ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
 	 $ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
 	 $ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
 	 $ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
+	 # *ifconfig* is deprecated, better use *ip addr show* now
 	 $ ifconfig
 	 $ ifconfig
 	 $ ssh root@192.168.33.10 -p 49154
 	 $ ssh root@192.168.33.10 -p 49154
 	 # Thanks for watching, Thatcher thatcher@dotcloud.com
 	 # Thanks for watching, Thatcher thatcher@dotcloud.com

+ 0 - 27
docs/sources/index/variable.rst

@@ -1,27 +0,0 @@
-:title: Index Environment Variable
-:description: Setting this environment variable on the docker server will change the URL docker index.
-:keywords: docker, index environment variable, documentation 
-
-=================================
-Docker Index Environment Variable
-=================================
-
-Variable
---------
-
-.. code-block:: sh
-
-    DOCKER_INDEX_URL
-
-Setting this environment variable on the docker server will change the URL docker index.
-This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``.
-The docker daemon doesn't need to be restarted for this parameter to take effect.
-
-Example
--------
-
-.. code-block:: sh
-
-    docker -d &
-    export DOCKER_INDEX_URL="https://index.docker.io"
-

+ 1 - 2
docs/sources/installation/binaries.rst

@@ -30,8 +30,7 @@ Dependencies:
 * 3.8 Kernel (read more about :ref:`kernel`)
 * 3.8 Kernel (read more about :ref:`kernel`)
 * AUFS filesystem support
 * AUFS filesystem support
 * lxc
 * lxc
-* bsdtar
-
+* xz-utils
 
 
 Get the docker binary:
 Get the docker binary:
 ----------------------
 ----------------------

+ 2 - 1
docs/sources/use/basics.rst

@@ -82,7 +82,8 @@ Expose a service on a TCP port
 
 
   # Connect to the public port via the host's public address
   # Connect to the public port via the host's public address
   # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
   # Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work.
-  IP=$(ifconfig eth0 | perl -n -e 'if (m/inet addr:([\d\.]+)/g) { print $1 }')
+  # Replace *eth0* according to your local interface name.
+  IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }')
   echo hello world | nc $IP $PORT
   echo hello world | nc $IP $PORT
 
 
   # Verify that the network connection worked
   # Verify that the network connection worked

+ 37 - 6
docs/sources/use/builder.rst

@@ -15,10 +15,18 @@ steps and commit them along the way, giving you a final image.
 1. Usage
 1. Usage
 ========
 ========
 
 
-To use Docker Builder, assemble the steps into a text file (commonly referred to
-as a Dockerfile) and supply this to `docker build` on STDIN, like so:
+To build an image from a source repository, create a description file called `Dockerfile`
+at the root of your repository. This file will describe the steps to assemble
+the image.
 
 
-    ``docker build - < Dockerfile``
+Then call `docker build` with the path of your source repository as argument:
+
+    ``docker build .``
+
+You can specify a repository and tag at which to save the new image if the
+build succeeds:
+
+    ``docker build -t shykes/myapp .``
 
 
 Docker will run your steps one-by-one, committing the result if necessary, 
 Docker will run your steps one-by-one, committing the result if necessary, 
 before finally outputting the ID of your new image.
 before finally outputting the ID of your new image.
@@ -130,9 +138,32 @@ curl was installed within the image.
 
 
     ``ADD <src> <dest>``
     ``ADD <src> <dest>``
 
 
-The `ADD` instruction will insert the files from the `<src>` path of the context into `<dest>` path 
-of the container.
-The context must be set in order to use this instruction. (see examples)
+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
+context of the build).
+
+`<dest>` is the path at which the source will be copied in the destination container.
+
+The copy obeys the following rules:
+
+If `<src>` is a directory, the entire directory is copied, including filesystem metadata.
+
+If `<src>` is a tar archive in a recognized compression format (identity, gzip, bzip2 or xz), it
+is unpacked as a directory.
+
+When a directory is copied or unpacked, it has the same behavior as 'tar -x': the result is the union of
+a) whatever existed at the destination path and b) the contents of the source tree, with conflicts resolved
+in favor of b on a file-by-file basis.
+
+If `<src>` is any other kind of file, it is copied individually along with its metadata. In this case,
+if `<dst>` ends with a trailing slash '/', it will be considered a directory and the contents of `<src>`
+will be written at `<dst>/base(<src>)`.
+If `<dst>` does not end with a trailing slash, it will be considered a regular file and the contents
+of `<src>` will be written at `<dst>`.
+
+If `<dest>` doesn't exist, it is created along with all missing directories in its path. All new
+files and directories are created with mode 0700, uid and gid 0.
 
 
 3. Dockerfile Examples
 3. Dockerfile Examples
 ======================
 ======================

+ 1 - 0
docs/sources/use/index.rst

@@ -14,6 +14,7 @@ Contents:
 
 
    basics
    basics
    workingwithrepository
    workingwithrepository
+   port_redirection
    builder
    builder
    puppet
    puppet
 
 

+ 25 - 0
docs/sources/use/port_redirection.rst

@@ -0,0 +1,25 @@
+:title: Port redirection
+:description: usage about port redirection
+:keywords: Usage, basic port, docker, documentation, examples
+
+
+Port redirection
+================
+
+Docker can redirect public tcp ports to your container, so it can be reached over the network.
+Port redirection is done on ``docker run`` using the -p flag.
+
+A port redirect is specified as PUBLIC:PRIVATE, where tcp port PUBLIC will be redirected to
+tcp port PRIVATE. As a special case, the public port can be omitted, in which case a random
+public port will be allocated.
+
+.. code-block:: bash
+
+    # A random PUBLIC port is redirected to PRIVATE port 80 on the container
+    docker run -p 80 <image> <cmd>
+
+    # PUBLIC port 80 is redirected to PRIVATE port 80
+    docker run -p 80:80 <image> <cmd>
+
+
+Default port redirects can be built into a container with the EXPOSE build command.

+ 25 - 0
docs/sources/use/workingwithrepository.rst

@@ -77,3 +77,28 @@ Now you can commit this image to the repository
     # for example docker push dhrp/kickassapp
     # for example docker push dhrp/kickassapp
     docker push <image-name>
     docker push <image-name>
 
 
+
+Changing the server to connect to
+----------------------------------
+
+When you are running your own index and/or registry, You can change the server the docker client will connect to.
+
+Variable
+^^^^^^^^
+
+.. code-block:: sh
+
+    DOCKER_INDEX_URL
+
+Setting this environment variable on the docker server will change the URL docker index.
+This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``.
+The docker daemon doesn't need to be restarted for this parameter to take effect.
+
+Example
+^^^^^^^
+
+.. code-block:: sh
+
+    docker -d &
+    export DOCKER_INDEX_URL="https://index.docker.io"
+

+ 5 - 7
docs/theme/docker/layout.html

@@ -40,6 +40,8 @@
 
 
     {%- set script_files = script_files + ['_static/js/docs.js'] %}
     {%- set script_files = script_files + ['_static/js/docs.js'] %}
 
 
+    <link rel="canonical" href="http://docs.docker.io/en/latest/{{ pagename }}/">
+
     {%- for cssfile in css_files %}
     {%- for cssfile in css_files %}
     <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
     <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
     {%- endfor %}
     {%- endfor %}
@@ -48,9 +50,8 @@
     <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
     <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
     {%- endfor %}
     {%- endfor %}
 
 
-    {%- if favicon %}
-    <link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
-    {%- endif %}
+    <link rel="shortcut icon" href="{{ pathto('_static/favicon.png', 1) }}"/>
+
 
 
     {%- block extrahead %}{% endblock %}
     {%- block extrahead %}{% endblock %}
 
 
@@ -104,11 +105,8 @@
     <!-- Docs nav
     <!-- Docs nav
      ================================================== -->
      ================================================== -->
     <div class="row" style="position: relative">
     <div class="row" style="position: relative">
-        <div class="span3" style="height:100%;" >
-
-        </div>
 
 
-        <div class="span3 sidebar bs-docs-sidebar" style="position: absolute">
+        <div class="span3 sidebar bs-docs-sidebar">
             {{ toctree(collapse=False, maxdepth=3) }}
             {{ toctree(collapse=False, maxdepth=3) }}
         </div>
         </div>
 
 

+ 12 - 0
docs/theme/docker/redirect_home.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Page Moved</title>
+    <meta http-equiv="refresh" content="0; url=http://docks.docker.io/en/latest/">
+</head>
+<body>
+
+This page has moved. Perhaps you should visit the <a href="http://docs.docker.io/" title="documentation homepage">Documentation Homepage</a>
+
+</body>
+</html>

+ 6 - 4
docs/theme/docker/static/css/main.css

@@ -168,10 +168,13 @@ section.header {
 .sidebar {
 .sidebar {
   font-weight: normal;
   font-weight: normal;
   float: left;
   float: left;
-  min-height: 475px;
+  /*  min-height: 475px;*/
+
   background: #ececec;
   background: #ececec;
-  border-left: 1px solid #bbbbbb;
-  border-right: 1px solid #cccccc;
+  /*  border-left: 1px solid #bbbbbb;*/
+
+  /*  border-right: 1px solid #cccccc;*/
+
   position: relative;
   position: relative;
 }
 }
 .sidebar ul {
 .sidebar ul {
@@ -357,7 +360,6 @@ section.header {
   #global {
   #global {
     /* TODO: Fix this to be relative to the navigation size */
     /* TODO: Fix this to be relative to the navigation size */
   
   
-    padding-top: 600px;
   }
   }
   #fork-us {
   #fork-us {
     display: none;
     display: none;

+ 15 - 14
docs/theme/docker/static/css/main.less

@@ -226,20 +226,21 @@ section.header {
 
 
 }
 }
 
 
+  .sidebar {
+  //  font-family: "Maven Pro";
+    font-weight: normal;
+  //  margin-top: 38px;
+    float: left;
+  //  width: 220px;
+  /*  min-height: 475px;*/
+  //  margin-bottom: 28px;
+  //  padding-bottom: 120px;
+    background: #ececec;
+  /*  border-left: 1px solid #bbbbbb;*/
+  /*  border-right: 1px solid #cccccc;*/
+    position: relative;
+
 
 
-.sidebar {
-//  font-family: "Maven Pro";
-  font-weight: normal;
-//  margin-top: 38px;
-  float: left;
-//  width: 220px;
-  min-height: 475px;
-//  margin-bottom: 28px;
-//  padding-bottom: 120px;
-  background: #ececec;
-  border-left: 1px solid #bbbbbb;
-  border-right: 1px solid #cccccc;
-  position: relative;
 
 
   ul {
   ul {
     padding: 0px;
     padding: 0px;
@@ -471,7 +472,7 @@ section.header {
   }
   }
   #global {
   #global {
   /* TODO: Fix this to be relative to the navigation size */
   /* TODO: Fix this to be relative to the navigation size */
-    padding-top: 600px;
+//    padding-top: 600px;
   }
   }
   #fork-us {
   #fork-us {
     display: none;
     display: none;

BIN
docs/theme/docker/static/favicon.ico


BIN
docs/theme/docker/static/favicon.png


+ 12 - 0
graph_test.go

@@ -192,11 +192,19 @@ func TestDelete(t *testing.T) {
 	}
 	}
 	assertNImages(graph, t, 0)
 	assertNImages(graph, t, 0)
 
 
+	archive, err = fakeTar()
+	if err != nil {
+		t.Fatal(err)
+	}
 	// Test 2 create (same name) / 1 delete
 	// Test 2 create (same name) / 1 delete
 	img1, err := graph.Create(archive, nil, "Testing", "", nil)
 	img1, err := graph.Create(archive, nil, "Testing", "", nil)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
+	archive, err = fakeTar()
+	if err != nil {
+		t.Fatal(err)
+	}
 	if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
 	if _, err = graph.Create(archive, nil, "Testing", "", nil); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -212,6 +220,10 @@ func TestDelete(t *testing.T) {
 	}
 	}
 	assertNImages(graph, t, 1)
 	assertNImages(graph, t, 1)
 
 
+	archive, err = fakeTar()
+	if err != nil {
+		t.Fatal(err)
+	}
 	// Test delete twice (pull -> rm -> pull -> rm)
 	// Test delete twice (pull -> rm -> pull -> rm)
 	if err := graph.Register(archive, false, img1); err != nil {
 	if err := graph.Register(archive, false, img1); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)

+ 1 - 1
hack/Vagrantfile

@@ -22,7 +22,7 @@ Vagrant::Config.run do |config|
   pkg_cmd = "touch #{DOCKER_PATH}; "
   pkg_cmd = "touch #{DOCKER_PATH}; "
   # Install docker dependencies
   # Install docker dependencies
   pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
   pkg_cmd << "export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; " \
-    "apt-get install -q -y lxc bsdtar git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
+    "apt-get install -q -y lxc git aufs-tools golang make linux-image-extra-3.8.0-19-generic; " \
     "chown -R #{USER}.#{USER} #{GOPATH}; " \
     "chown -R #{USER}.#{USER} #{GOPATH}; " \
     "install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
     "install -m 0664 #{CFG_PATH}/bash_profile /home/#{USER}/.bash_profile"
   config.vm.provision :shell, :inline => pkg_cmd
   config.vm.provision :shell, :inline => pkg_cmd

+ 11 - 4
hack/dockerbuilder/Dockerfile

@@ -1,5 +1,13 @@
-# This will build a container capable of producing an official binary build of docker and
-# uploading it to S3
+# DESCRIPTION     Build a container capable of producing official binary and
+#                 PPA packages and uploading them to S3 and Launchpad
+# VERSION         1.2
+# DOCKER_VERSION  0.4
+# AUTHOR          Solomon Hykes <solomon@dotcloud.com>
+#                 Daniel Mizyrycki <daniel@dotcloud.net>
+# BUILD_CMD       docker build -t dockerbuilder .
+# RUN_CMD         docker run -e AWS_ID="$AWS_ID" -e AWS_KEY="$AWS_KEY" -e GPG_KEY="$GPG_KEY" dockerbuilder
+#
+#
 from	ubuntu:12.04
 from	ubuntu:12.04
 maintainer	Solomon Hykes <solomon@dotcloud.com>
 maintainer	Solomon Hykes <solomon@dotcloud.com>
 # Workaround the upstart issue
 # Workaround the upstart issue
@@ -8,7 +16,7 @@ run ln -s /bin/true /sbin/initctl
 # Enable universe and gophers PPA
 # Enable universe and gophers PPA
 run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties
 run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties
 run	add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
 run	add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
-run	add-apt-repository -y ppa:gophers/go/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 s3cmd
@@ -27,5 +35,4 @@ run	apt-get install -y -q 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
-run	cp /src/s3cfg /.s3cfg
 cmd	["dockerbuilder"]
 cmd	["dockerbuilder"]

+ 7 - 6
hack/dockerbuilder/dockerbuilder

@@ -13,12 +13,10 @@ fi
 
 
 export REVISION=$1
 export REVISION=$1
 
 
-if [ -z "$AWS_ID" ]; then
-	echo "Warning: environment variable AWS_ID is not set. Won't upload to S3."
-fi
-
-if [ -z "$AWS_KEY" ]; then
-	echo "Warning: environment variable AWS_KEY is not set. Won't upload to S3."
+if [ -z "$AWS_ID" -o -z "$AWS_KEY" ]; then
+	echo "Warning: either AWS_ID or AWS_KEY environment variable not set. Won't upload to S3."
+else
+	/bin/echo -e "[default]\naccess_key = $AWS_ID\nsecret_key = $AWS_KEY\n" > /.s3cfg
 fi
 fi
 
 
 if [ -z "$GPG_KEY" ]; then
 if [ -z "$GPG_KEY" ]; then
@@ -35,6 +33,9 @@ else
 	make release RELEASE_VERSION=$REVISION
 	make release RELEASE_VERSION=$REVISION
 fi
 fi
 
 
+# Remove credentials from container
+rm -f /.s3cfg
+
 if [ -z "$NO_UBUNTU" ]; then
 if [ -z "$NO_UBUNTU" ]; then
 	export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'`
 	export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'`
 	(cd packaging/ubuntu && make ubuntu)
 	(cd packaging/ubuntu && make ubuntu)

+ 0 - 3
hack/dockerbuilder/s3cfg

@@ -1,3 +0,0 @@
-[default]
-access_key = $AWS_ID
-secret_key = $AWS_KEY

+ 7 - 0
lxc_template.go

@@ -67,7 +67,11 @@ lxc.cgroup.devices.allow = c 10:200 rwm
 
 
 
 
 # standard mount point
 # standard mount point
+#  WARNING: procfs is a known attack vector and should probably be disabled
+#           if your userspace allows it. eg. see http://blog.zx2c4.com/749
 lxc.mount.entry = proc {{$ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
 lxc.mount.entry = proc {{$ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
+#  WARNING: sysfs is a known attack vector and should probably be disabled
+#           if your userspace allows it. eg. see http://bit.ly/T9CkqJ
 lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
 lxc.mount.entry = sysfs {{$ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
 lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
 lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
 #lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
 #lxc.mount.entry = varrun {{$ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
@@ -86,6 +90,9 @@ lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0
 {{end}}
 {{end}}
 
 
 # drop linux capabilities (apply mainly to the user root in the container)
 # drop linux capabilities (apply mainly to the user root in the container)
+#  (Note: 'lxc.cap.keep' is coming soon and should replace this under the
+#         security principle 'deny all unless explicitly permitted', see
+#         http://sourceforge.net/mailarchive/message.php?msg_id=31054627 )
 lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config
 lxc.cap.drop = audit_control audit_write mac_admin mac_override mknod setfcap setpcap sys_admin sys_boot sys_module sys_nice sys_pacct sys_rawio sys_resource sys_time sys_tty_config
 
 
 # limits
 # limits

+ 8 - 10
packaging/ubuntu/Makefile

@@ -1,6 +1,6 @@
 # Ubuntu package Makefile
 # Ubuntu package Makefile
 #
 #
-# Dependencies:  debhelper autotools-dev devscripts golang
+# Dependencies:  debhelper autotools-dev devscripts golang-stable
 # Notes:
 # Notes:
 # Use 'make ubuntu' to create the ubuntu package
 # Use 'make ubuntu' to create the ubuntu package
 # GPG_KEY environment variable needs to contain a GPG private key for package to be signed
 # GPG_KEY environment variable needs to contain a GPG private key for package to be signed
@@ -9,12 +9,9 @@
 # status code 2
 # status code 2
 
 
 PKG_NAME=lxc-docker
 PKG_NAME=lxc-docker
-VERSION=$(shell head -1 changelog | sed 's/^.\+(\(.\+\)..).\+$$/\1/')
 GITHUB_PATH=github.com/dotcloud/docker
 GITHUB_PATH=github.com/dotcloud/docker
-DOCKER_VERSION=${PKG_NAME}_${VERSION}
-DOCKER_FVERSION=${PKG_NAME}_$(shell head -1 changelog | sed 's/^.\+(\(.\+\)).\+$$/\1/')
 BUILD_SRC=${CURDIR}/../../build_src
 BUILD_SRC=${CURDIR}/../../build_src
-VERSION_TAG=v$(shell head -1 changelog | sed 's/^.\+(\(.\+\)-[0-9]\+).\+$$/\1/')
+VERSION=$(shell sed -En '0,/^\#\# /{s/^\#\# ([^ ]+).+/\1/p}' ../../CHANGELOG.md)
 
 
 all:
 all:
 	# Compile docker. Used by dpkg-buildpackage.
 	# Compile docker. Used by dpkg-buildpackage.
@@ -35,18 +32,19 @@ ubuntu:
 	# Retrieve docker project and its go structure from internet
 	# Retrieve docker project and its go structure from internet
 	rm -rf ${BUILD_SRC}
 	rm -rf ${BUILD_SRC}
 	git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH}
 	git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH}
-	cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout ${VERSION_TAG} && GOPATH=${BUILD_SRC} go get -d
+	cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout v${VERSION} && GOPATH=${BUILD_SRC} go get -d
 	# Add debianization
 	# Add debianization
 	mkdir ${BUILD_SRC}/debian
 	mkdir ${BUILD_SRC}/debian
 	cp Makefile ${BUILD_SRC}
 	cp Makefile ${BUILD_SRC}
 	cp -r * ${BUILD_SRC}/debian
 	cp -r * ${BUILD_SRC}/debian
 	cp ../../README.md ${BUILD_SRC}
 	cp ../../README.md ${BUILD_SRC}
+	./parse_changelog.py < ../../CHANGELOG.md  > ${BUILD_SRC}/debian/changelog
 	# Cleanup
 	# Cleanup
 	for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done
 	for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done
-	rm -rf ${BUILD_SRC}/../${DOCKER_VERSION}.orig.tar.gz
+	rm -rf ${BUILD_SRC}/../${PKG_NAME}_${VERSION}.orig.tar.gz
 	rm -rf ${BUILD_SRC}/pkg
 	rm -rf ${BUILD_SRC}/pkg
 	# Create docker debian files
 	# Create docker debian files
-	cd ${BUILD_SRC}; tar czf ../${DOCKER_VERSION}.orig.tar.gz .
+	cd ${BUILD_SRC}; tar czf ../${PKG_NAME}_${VERSION}.orig.tar.gz .
 	cd ${BUILD_SRC}; dpkg-buildpackage -us -uc
 	cd ${BUILD_SRC}; dpkg-buildpackage -us -uc
 	rm -rf ${BUILD_SRC}
 	rm -rf ${BUILD_SRC}
 	# Sign package and upload it to PPA if GPG_KEY environment variable
 	# Sign package and upload it to PPA if GPG_KEY environment variable
@@ -56,7 +54,7 @@ ubuntu:
 	# 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
 	# Sign the package
 	# Sign the package
-	cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${DOCKER_FVERSION}.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 ${DOCKER_FVERSION}_source.changes
+	cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${PKG_NAME}_${VERSION}-1_source.changes
 	rm -rf ${BUILD_SRC}
 	rm -rf ${BUILD_SRC}

+ 0 - 222
packaging/ubuntu/changelog

@@ -1,222 +0,0 @@
-lxc-docker (0.4.0-1) precise; urgency=low
-  - Introducing Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
-  - Introducing Remote API: control Docker programmatically using a simple HTTP/json API
-  - Runtime: various reliability and usability improvements
-
- -- dotCloud <ops@dotcloud.com>  Mon, 03 Jun 2013 00:00:00 -0700
-
-lxc-docker (0.3.4-1) precise; urgency=low
-  - Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
-  - Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
-  - Runtime: interactive TTYs correctly handle window resize
-  - Runtime: fix how configuration is merged between layers
-  - Remote API: split stdout and stderr on 'docker run'
-  - Remote API: optionally listen on a different IP and port (use at your own risk)
-  - Documentation: improved install instructions.
-
- -- dotCloud <ops@dotcloud.com>  Thu, 30 May 2013 00:00:00 -0700
-
-
-lxc-docker (0.3.3-1) precise; urgency=low
-  - Registry: Fix push regression
-  - Various bugfixes
-
- -- dotCloud <ops@dotcloud.com>  Thu, 23 May 2013 00:00:00 -0700
-
-
-lxc-docker (0.3.2-1) precise; urgency=low
-  - Runtime: Store the actual archive on commit
-  - Registry: Improve the checksum process
-  - Registry: Use the size to have a good progress bar while pushing
-  - Registry: Use the actual archive if it exists in order to speed up the push
-  - Registry: Fix error 400 on push
-
- -- dotCloud <ops@dotcloud.com>  Fri, 9 May 2013 00:00:00 -0700
-
-
-lxc-docker (0.3.1-1) precise; urgency=low
-  - Builder: Implement the autorun capability within docker builder
-  - Builder: Add caching to docker builder
-  - Builder: Add support for docker builder with native API as top level command
-  - Runtime: Add go version to debug infos
-  - Builder: Implement ENV within docker builder
-  - Registry: Add docker search top level command in order to search a repository
-  - Images: output graph of images to dot (graphviz)
-  - Documentation: new introduction and high-level overview
-  - Documentation: Add the documentation for docker builder
-  - Website: new high-level overview
-  - Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
-  - Images: fix ByParent function
-  - Builder: Check the command existance prior create and add Unit tests for the case
-  - Registry: Fix pull for official images with specific tag
-  - Registry: Fix issue when login in with a different user and trying to push
-  - Documentation: CSS fix for docker documentation to make REST API docs look better.
-  - Documentation: Fixed CouchDB example page header mistake
-  - Documentation: fixed README formatting
-  - Registry: Improve checksum - async calculation
-  - Runtime: kernel version - don't show the dash if flavor is empty
-  - Documentation: updated www.docker.io website.
-  - Builder: use any whitespaces instead of tabs
-  - Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
-
- -- dotCloud <ops@dotcloud.com>  Fri, 8 May 2013 00:00:00 -0700
-
-
-lxc-docker (0.3.0-1) precise; urgency=low
-  - Registry: Implement the new registry
-  - Documentation: new example: sharing data between 2 couchdb databases
-  - Runtime: Fix the command existance check
-  - Runtime: strings.Split may return an empty string on no match
-  - Runtime: Fix an index out of range crash if cgroup memory is not
-  - Documentation: Various improvments
-  - Vagrant: Use only one deb line in /etc/apt
-
- -- dotCloud <ops@dotcloud.com>  Fri, 5 May 2013 00:00:00 -0700
-
-
-lxc-docker (0.2.2-1) precise; urgency=low
-  - Support for data volumes ('docker run -v=PATH')
-  - Share data volumes between containers ('docker run -volumes-from')
-  - Improved documentation
-  - Upgrade to Go 1.0.3
-  - Various upgrades to the dev environment for contributors
-
- -- dotCloud <ops@dotcloud.com>  Fri, 3 May 2013 00:00:00 -0700
-
-
-lxc-docker (0.2.1-1) precise; urgency=low
-
-  - 'docker commit -run' bundles a layer with default runtime options: command, ports etc.
-  - Improve install process on Vagrant
-  - New Dockerfile operation: "maintainer"
-  - New Dockerfile operation: "expose"
-  - New Dockerfile operation: "cmd"
-  - Contrib script to build a Debian base layer
-  - 'docker -d -r': restart crashed containers at daemon startup
-  - Runtime: improve test coverage
-
- -- dotCloud <ops@dotcloud.com>  Wed, 1 May 2013 00:00:00 -0700
-
-
-lxc-docker (0.2.0-1) precise; urgency=low
-
-  - Runtime: ghost containers can be killed and waited for
-  - Documentation: update install intructions
-  - Packaging: fix Vagrantfile
-  - Development: automate releasing binaries and ubuntu packages
-  - Add a changelog
-  - Various bugfixes
-
- -- dotCloud <ops@dotcloud.com>  Mon, 23 Apr 2013 00:00:00 -0700
-
-
-lxc-docker (0.1.8-1) precise; urgency=low
-
-  - Dynamically detect cgroup capabilities
-  - Issue stability warning on kernels <3.8
-  - 'docker push' buffers on disk instead of memory
-  - Fix 'docker diff' for removed files
-  - Fix 'docker stop' for ghost containers
-  - Fix handling of pidfile
-  - Various bugfixes and stability improvements
-
- -- dotCloud <ops@dotcloud.com>  Mon, 22 Apr 2013 00:00:00 -0700
-
-
-lxc-docker (0.1.7-1) precise; urgency=low
-
-  - Container ports are available on localhost
-  - 'docker ps' shows allocated TCP ports
-  - Contributors can run 'make hack' to start a continuous integration VM
-  - Streamline ubuntu packaging & uploading
-  - Various bugfixes and stability improvements
-
- -- dotCloud <ops@dotcloud.com>  Thu, 18 Apr 2013 00:00:00 -0700
-
-
-lxc-docker (0.1.6-1) precise; urgency=low
-
-  - Record the author an image with 'docker commit -author'
-
- -- dotCloud <ops@dotcloud.com>  Wed, 17 Apr 2013 00:00:00 -0700
-
-
-lxc-docker (0.1.5-1) precise; urgency=low
-
-  - Disable standalone mode
-  - Use a custom DNS resolver with 'docker -d -dns'
-  - Detect ghost containers
-  - Improve diagnosis of missing system capabilities
-  - Allow disabling memory limits at compile time
-  - Add debian packaging
-  - Documentation: installing on Arch Linux
-  - Documentation: running Redis on docker
-  - Fixed lxc 0.9 compatibility
-  - Automatically load aufs module
-  - Various bugfixes and stability improvements
-
- -- dotCloud <ops@dotcloud.com>  Wed, 17 Apr 2013 00:00:00 -0700
-
-
-lxc-docker (0.1.4-1) precise; urgency=low
-
-  - Full support for TTY emulation
-  - Detach from a TTY session with the escape sequence `C-p C-q`
-  - Various bugfixes and stability improvements
-  - Minor UI improvements
-  - Automatically create our own bridge interface 'docker0'
-
- -- dotCloud <ops@dotcloud.com>  Tue,  9 Apr 2013 00:00:00 -0700
-
-
-lxc-docker (0.1.3-1) precise; urgency=low
-
-  - Choose TCP frontend port with '-p :PORT'
-  - Layer format is versioned
-  - Major reliability improvements to the process manager
-  - Various bugfixes and stability improvements
-
- -- dotCloud <ops@dotcloud.com>  Thu,  4 Apr 2013 00:00:00 -0700
-
-
-lxc-docker (0.1.2-1) precise; urgency=low
-
-  - Set container hostname with 'docker run -h'
-  - Selective attach at run with 'docker run -a [stdin[,stdout[,stderr]]]'
-  - Various bugfixes and stability improvements
-  - UI polish
-  - Progress bar on push/pull
-  - Use XZ compression by default
-  - Make IP allocator lazy
-
- -- dotCloud <ops@dotcloud.com>  Wed,  3 Apr 2013 00:00:00 -0700
-
-
-lxc-docker (0.1.1-1) precise; urgency=low
-
-  - Display shorthand IDs for convenience
-  - Stabilize process management
-  - Layers can include a commit message
-  - Simplified 'docker attach'
-  - Fixed support for re-attaching
-  - Various bugfixes and stability improvements
-  - Auto-download at run
-  - Auto-login on push
-  - Beefed up documentation
-
- -- dotCloud <ops@dotcloud.com>  Sun, 31 Mar 2013 00:00:00 -0700
-
-
-lxc-docker (0.1.0-1) precise; urgency=low
-
-  - First release
-  - Implement registry in order to push/pull images
-  - TCP port allocation
-  - Fix termcaps on Linux
-  - Add documentation
-  - Add Vagrant support with Vagrantfile
-  - Add unit tests
-  - Add repository/tags to ease image management
-  - Improve the layer implementation
-
- -- dotCloud <ops@dotcloud.com>  Sat, 23 Mar 2013 00:00:00 -0700

+ 23 - 0
packaging/ubuntu/parse_changelog.py

@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+'Parse main CHANGELOG.md from stdin outputing on stdout the ubuntu changelog'
+
+import sys,re, datetime
+
+on_block=False
+for line in sys.stdin.readlines():
+    line = line.strip()
+    if line.startswith('# ') or len(line) == 0:
+        continue
+    if line.startswith('## '):
+        if on_block:
+            print '\n -- dotCloud <ops@dotcloud.com>  {0}\n'.format(date)
+        version, date = line[3:].split()
+        date = datetime.datetime.strptime(date, '(%Y-%m-%d)').strftime(
+            '%a, %d %b %Y 00:00:00 -0700')
+        on_block = True
+        print 'lxc-docker ({0}-1) precise; urgency=low'.format(version)
+        continue
+    if on_block:
+        print '  ' + line
+print '\n -- dotCloud <ops@dotcloud.com>  {0}'.format(date)

+ 19 - 13
registry/registry.go

@@ -12,6 +12,7 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
+	"strconv"
 	"strings"
 	"strings"
 )
 )
 
 
@@ -106,40 +107,45 @@ func (r *Registry) getImagesInRepository(repository string, authConfig *auth.Aut
 }
 }
 
 
 // Retrieve an image from the Registry.
 // Retrieve an image from the Registry.
-// Returns the Image object as well as the layer as an Archive (io.Reader)
-func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, error) {
+func (r *Registry) GetRemoteImageJSON(imgId, registry string, token []string) ([]byte, int, error) {
 	// Get the JSON
 	// Get the JSON
 	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
 	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
 	if err != nil {
 	if err != nil {
-		return nil, fmt.Errorf("Failed to download json: %s", err)
+		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
 	}
 	}
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
 	res, err := r.client.Do(req)
 	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
-		return nil, fmt.Errorf("Failed to download json: %s", err)
+		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
 	}
 	}
 	defer res.Body.Close()
 	defer res.Body.Close()
 	if res.StatusCode != 200 {
 	if res.StatusCode != 200 {
-		return nil, fmt.Errorf("HTTP code %d", res.StatusCode)
+		return nil, -1, fmt.Errorf("HTTP code %d", res.StatusCode)
 	}
 	}
+
+	imageSize, err := strconv.Atoi(res.Header.Get("X-Docker-Size"))
+	if err != nil {
+		return nil, -1, err
+	}
+
 	jsonString, err := ioutil.ReadAll(res.Body)
 	jsonString, err := ioutil.ReadAll(res.Body)
 	if err != nil {
 	if err != nil {
-		return nil, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
+		return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
 	}
 	}
-	return jsonString, nil
+	return jsonString, imageSize, nil
 }
 }
 
 
-func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, int, error) {
+func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, error) {
 	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
 	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
 	if err != nil {
 	if err != nil {
-		return nil, -1, fmt.Errorf("Error while getting from the server: %s\n", err)
+		return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
 	}
 	}
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
 	res, err := r.client.Do(req)
 	res, err := r.client.Do(req)
 	if err != nil {
 	if err != nil {
-		return nil, -1, err
+		return nil, err
 	}
 	}
-	return res.Body, int(res.ContentLength), nil
+	return res.Body, nil
 }
 }
 
 
 func (r *Registry) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
 func (r *Registry) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
@@ -156,10 +162,10 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
 		}
 		}
 		req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
 		req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
 		res, err := r.client.Do(req)
 		res, err := r.client.Do(req)
-		utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		}
 		}
+		utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
 		defer res.Body.Close()
 		defer res.Body.Close()
 
 
 		if res.StatusCode != 200 && res.StatusCode != 404 {
 		if res.StatusCode != 200 && res.StatusCode != 404 {
@@ -481,7 +487,7 @@ type Registry struct {
 func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry {
 func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry {
 	httpTransport := &http.Transport{
 	httpTransport := &http.Transport{
 		DisableKeepAlives: true,
 		DisableKeepAlives: true,
-		Proxy: http.ProxyFromEnvironment,
+		Proxy:             http.ProxyFromEnvironment,
 	}
 	}
 
 
 	r := &Registry{
 	r := &Registry{

+ 5 - 1
runtime_test.go

@@ -65,7 +65,11 @@ func init() {
 
 
 	// Create the "Server"
 	// Create the "Server"
 	srv := &Server{
 	srv := &Server{
-		runtime: runtime,
+		runtime:     runtime,
+		enableCors:  false,
+		lock:        &sync.Mutex{},
+		pullingPool: make(map[string]struct{}),
+		pushingPool: make(map[string]struct{}),
 	}
 	}
 	// Retrieve the Image
 	// Retrieve the Image
 	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {
 	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {

+ 72 - 11
server.go

@@ -15,6 +15,7 @@ import (
 	"path"
 	"path"
 	"runtime"
 	"runtime"
 	"strings"
 	"strings"
+	"sync"
 )
 )
 
 
 func (srv *Server) DockerVersion() APIVersion {
 func (srv *Server) DockerVersion() APIVersion {
@@ -218,12 +219,24 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 
 
+	lookupMap := make(map[string][]string)
+	for name, repository := range srv.runtime.repositories.Repositories {
+		for tag, id := range repository {
+			// If the ID already has a reverse lookup, do not update it unless for "latest"
+			if _, exists := lookupMap[id]; !exists {
+				lookupMap[id] = []string{}
+			}
+			lookupMap[id] = append(lookupMap[id], name+":"+tag)
+		}
+	}
+
 	outs := []APIHistory{} //produce [] when empty instead of 'null'
 	outs := []APIHistory{} //produce [] when empty instead of 'null'
 	err = image.WalkHistory(func(img *Image) error {
 	err = image.WalkHistory(func(img *Image) error {
 		var out APIHistory
 		var out APIHistory
 		out.ID = srv.runtime.repositories.ImageName(img.ShortID())
 		out.ID = srv.runtime.repositories.ImageName(img.ShortID())
 		out.Created = img.Created.Unix()
 		out.Created = img.Created.Unix()
 		out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
 		out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ")
+		out.Tags = lookupMap[img.ID]
 		outs = append(outs, out)
 		outs = append(outs, out)
 		return nil
 		return nil
 	})
 	})
@@ -309,7 +322,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
 	for _, id := range history {
 	for _, id := range history {
 		if !srv.runtime.graph.Exists(id) {
 		if !srv.runtime.graph.Exists(id) {
 			out.Write(sf.FormatStatus("Pulling %s metadata", id))
 			out.Write(sf.FormatStatus("Pulling %s metadata", id))
-			imgJSON, err := r.GetRemoteImageJSON(id, endpoint, token)
+			imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token)
 			if err != nil {
 			if err != nil {
 				// FIXME: Keep goging in case of error?
 				// FIXME: Keep goging in case of error?
 				return err
 				return err
@@ -321,12 +334,12 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoin
 
 
 			// Get the layer
 			// Get the layer
 			out.Write(sf.FormatStatus("Pulling %s fs layer", id))
 			out.Write(sf.FormatStatus("Pulling %s fs layer", id))
-			layer, contentLength, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
+			layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
 			if err != nil {
 			if err != nil {
 				return err
 				return err
 			}
 			}
 			defer layer.Close()
 			defer layer.Close()
-			if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
+			if err := srv.runtime.graph.Register(utils.ProgressReader(layer, imgSize, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
 				return err
 				return err
 			}
 			}
 		}
 		}
@@ -401,7 +414,47 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
 	return nil
 	return nil
 }
 }
 
 
+func (srv *Server) poolAdd(kind, key string) error {
+	srv.lock.Lock()
+	defer srv.lock.Unlock()
+
+	if _, exists := srv.pullingPool[key]; exists {
+		return fmt.Errorf("%s %s is already in progress", key, kind)
+	}
+
+	switch kind {
+	case "pull":
+		srv.pullingPool[key] = struct{}{}
+		break
+	case "push":
+		srv.pushingPool[key] = struct{}{}
+		break
+	default:
+		return fmt.Errorf("Unkown pool type")
+	}
+	return nil
+}
+
+func (srv *Server) poolRemove(kind, key string) error {
+	switch kind {
+	case "pull":
+		delete(srv.pullingPool, key)
+		break
+	case "push":
+		delete(srv.pushingPool, key)
+		break
+	default:
+		return fmt.Errorf("Unkown pool type")
+	}
+	return nil
+}
+
 func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
 func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
+	if err := srv.poolAdd("pull", name+":"+tag); err != nil {
+		return err
+	}
+	defer srv.poolRemove("pull", name+":"+tag)
+
 	r := registry.NewRegistry(srv.runtime.root, authConfig)
 	r := registry.NewRegistry(srv.runtime.root, authConfig)
 	out = utils.NewWriteFlusher(out)
 	out = utils.NewWriteFlusher(out)
 	if endpoint != "" {
 	if endpoint != "" {
@@ -418,7 +471,6 @@ func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *util
 	if err := srv.pullRepository(r, out, name, remote, tag, sf); err != nil {
 	if err := srv.pullRepository(r, out, name, remote, tag, sf); err != nil {
 		return err
 		return err
 	}
 	}
-
 	return nil
 	return nil
 }
 }
 
 
@@ -593,7 +645,13 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
 	return nil
 	return nil
 }
 }
 
 
+// FIXME: Allow to interupt current push when new push of same image is done.
 func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
 func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
+	if err := srv.poolAdd("push", name); err != nil {
+		return err
+	}
+	defer srv.poolRemove("push", name)
+
 	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 := registry.NewRegistry(srv.runtime.root, authConfig)
@@ -930,9 +988,6 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
 		if container.State.Ghost {
 		if container.State.Ghost {
 			return fmt.Errorf("Impossible to attach to a ghost container")
 			return fmt.Errorf("Impossible to attach to a ghost container")
 		}
 		}
-		if !container.State.Running {
-			return fmt.Errorf("Impossible to attach to a stopped container, start it first")
-		}
 
 
 		var (
 		var (
 			cStdin           io.ReadCloser
 			cStdin           io.ReadCloser
@@ -991,14 +1046,20 @@ func NewServer(autoRestart, enableCors bool, dns ListOpts) (*Server, error) {
 		return nil, err
 		return nil, err
 	}
 	}
 	srv := &Server{
 	srv := &Server{
-		runtime:    runtime,
-		enableCors: enableCors,
+		runtime:     runtime,
+		enableCors:  enableCors,
+		lock:        &sync.Mutex{},
+		pullingPool: make(map[string]struct{}),
+		pushingPool: make(map[string]struct{}),
 	}
 	}
 	runtime.srv = srv
 	runtime.srv = srv
 	return srv, nil
 	return srv, nil
 }
 }
 
 
 type Server struct {
 type Server struct {
-	runtime    *Runtime
-	enableCors bool
+	runtime     *Runtime
+	enableCors  bool
+	lock        *sync.Mutex
+	pullingPool map[string]struct{}
+	pushingPool map[string]struct{}
 }
 }

+ 1 - 1
testing/Vagrantfile

@@ -30,7 +30,7 @@ Vagrant::Config.run do |config|
     # Install docker dependencies
     # Install docker dependencies
     pkg_cmd << "apt-get install -q -y python-software-properties; " \
     pkg_cmd << "apt-get install -q -y python-software-properties; " \
       "add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
       "add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
-      "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable aufs-tools make; "
+      "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc git golang-stable aufs-tools make; "
     # Activate new kernel
     # Activate new kernel
     pkg_cmd << "shutdown -r +1; "
     pkg_cmd << "shutdown -r +1; "
     config.vm.provision :shell, :inline => pkg_cmd
     config.vm.provision :shell, :inline => pkg_cmd

+ 2 - 1
utils/utils.go

@@ -86,7 +86,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, r.readProgress, 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("%.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")
 		}
 		}
@@ -548,6 +548,7 @@ func GetKernelVersion() (*KernelVersionInfo, error) {
 	}, nil
 	}, nil
 }
 }
 
 
+// FIXME: this is deprecated by CopyWithTar in archive.go
 func CopyDirectory(source, dest string) error {
 func CopyDirectory(source, dest string) error {
 	if output, err := exec.Command("cp", "-ra", source, dest).CombinedOutput(); err != nil {
 	if output, err := exec.Command("cp", "-ra", source, dest).CombinedOutput(); err != nil {
 		return fmt.Errorf("Error copy: %s (%s)", err, output)
 		return fmt.Errorf("Error copy: %s (%s)", err, output)