Browse code

Merge branch 'master' into 1237-improve_docker_top-feature

Victor Vieux authored on 2013/07/30 20:51:16
Showing 51 changed files
... ...
@@ -23,3 +23,5 @@ Thatcher Peskens <thatcher@dotcloud.com>
23 23
 Walter Stanish <walter@pratyeka.org>
24 24
 <daniel@gasienica.ch> <dgasienica@zynga.com>
25 25
 Roberto Hashioka <roberto_hashioka@hotmail.com>
26
+Konstantin Pelykh <kpelykh@zettaset.com>
27
+David Sissitka <me@dsissitka.com>
... ...
@@ -4,12 +4,15 @@
4 4
 # For a list of active project maintainers, see the MAINTAINERS file.
5 5
 #
6 6
 Al Tobey <al@ooyala.com>
7
+Alex Gaynor <alex.gaynor@gmail.com>
7 8
 Alexey Shamrin <shamrin@gmail.com>
8 9
 Andrea Luzzardi <aluzzardi@gmail.com>
9 10
 Andreas Tiefenthaler <at@an-ti.eu>
10 11
 Andrew Munsell <andrew@wizardapps.net>
12
+Andrews Medina <andrewsmedina@gmail.com>
11 13
 Andy Rothfusz <github@metaliveblog.com>
12 14
 Andy Smith <github@anarkystic.com>
15
+Anthony Bishopric <git@anthonybishopric.com>
13 16
 Antony Messerli <amesserl@rackspace.com>
14 17
 Barry Allard <barry.allard@gmail.com>
15 18
 Brandon Liu <bdon@bdon.org>
... ...
@@ -23,17 +26,23 @@ Daniel Gasienica <daniel@gasienica.ch>
23 23
 Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
24 24
 Daniel Robinson <gottagetmac@gmail.com>
25 25
 Daniel Von Fange <daniel@leancoder.com>
26
+Daniel YC Lin <dlin.tw@gmail.com>
27
+David Calavera <david.calavera@gmail.com>
28
+David Sissitka <me@dsissitka.com>
26 29
 Dominik Honnef <dominik@honnef.co>
27 30
 Don Spaulding <donspauldingii@gmail.com>
28 31
 Dr Nic Williams <drnicwilliams@gmail.com>
29 32
 Elias Probst <mail@eliasprobst.eu>
30 33
 Eric Hanchrow <ehanchrow@ine.com>
31
-Evan Wies <evan@neomantra.net>
32 34
 Eric Myhre <hash@exultant.us>
35
+Erno Hopearuoho <erno.hopearuoho@gmail.com>
36
+Evan Wies <evan@neomantra.net>
33 37
 ezbercih <cem.ezberci@gmail.com>
38
+Fabrizio Regini <freegenie@gmail.com>
34 39
 Flavio Castelli <fcastelli@suse.com>
35 40
 Francisco Souza <f@souza.cc>
36 41
 Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
42
+Gabriel Monroy <gabriel@opdemand.com>
37 43
 Gareth Rushgrove <gareth@morethanseven.net>
38 44
 Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
39 45
 Harley Laue <losinggeneration@gmail.com>
... ...
@@ -41,6 +50,7 @@ Hunter Blanks <hunter@twilio.com>
41 41
 Jeff Lindsay <progrium@gmail.com>
42 42
 Jeremy Grosser <jeremy@synack.me>
43 43
 Joffrey F <joffrey@dotcloud.com>
44
+Johan Euphrosine <proppy@google.com>
44 45
 John Costa <john.costa@gmail.com>
45 46
 Jon Wedaman <jweede@gmail.com>
46 47
 Jonas Pfenniger <jonas@pfenniger.name>
... ...
@@ -48,40 +58,54 @@ Jonathan Rudenberg <jonathan@titanous.com>
48 48
 Joseph Anthony Pasquale Holsten <joseph@josephholsten.com>
49 49
 Julien Barbier <write0@gmail.com>
50 50
 Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
51
+Karan Lyons <karan@karanlyons.com>
52
+Keli Hu <dev@keli.hu>
51 53
 Ken Cochrane <kencochrane@gmail.com>
52 54
 Kevin J. Lynagh <kevin@keminglabs.com>
53 55
 kim0 <email.ahmedkamal@googlemail.com>
56
+Kimbro Staken <kstaken@kstaken.com>
54 57
 Kiran Gangadharan <kiran.daredevil@gmail.com>
58
+Konstantin Pelykh <kpelykh@zettaset.com>
55 59
 Louis Opter <kalessin@kalessin.fr>
60
+Marco Hennings <marco.hennings@freiheit.com>
56 61
 Marcus Farkas <toothlessgear@finitebox.com>
57 62
 Mark McGranaghan <mmcgrana@gmail.com>
58 63
 Maxim Treskin <zerthurd@gmail.com>
59 64
 meejah <meejah@meejah.ca>
60 65
 Michael Crosby <crosby.michael@gmail.com>
66
+Mike Gaffney <mike@uberu.com>
61 67
 Mikhail Sobolev <mss@mawhrin.net>
68
+Nan Monnand Deng <monnand@gmail.com>
62 69
 Nate Jones <nate@endot.org>
63 70
 Nelson Chen <crazysim@gmail.com>
64 71
 Niall O'Higgins <niallo@unworkable.org>
72
+Nick Stenning <nick.stenning@digital.cabinet-office.gov.uk>
73
+Nick Stinemates <nick@stinemates.org>
65 74
 odk- <github@odkurzacz.org>
66 75
 Paul Bowsher <pbowsher@globalpersonals.co.uk>
67 76
 Paul Hammond <paul@paulhammond.org>
68 77
 Phil Spitler <pspitler@gmail.com>
69 78
 Piotr Bogdan <ppbogdan@gmail.com>
70 79
 Renato Riccieri Santos Zannon <renato.riccieri@gmail.com>
80
+Rhys Hiltner <rhys@twitch.tv>
71 81
 Robert Obryk <robryk@gmail.com>
72 82
 Roberto Hashioka <roberto_hashioka@hotmail.com>
83
+Ryan Fowler <rwfowler@gmail.com>
73 84
 Sam Alba <sam.alba@gmail.com>
74 85
 Sam J Sharpe <sam.sharpe@digital.cabinet-office.gov.uk>
75 86
 Shawn Siefkas <shawn.siefkas@meredith.com>
76 87
 Silas Sewell <silas@sewell.org>
77 88
 Solomon Hykes <solomon@dotcloud.com>
78 89
 Sridhar Ratnakumar <sridharr@activestate.com>
90
+Stefan Praszalowicz <stefan@greplin.com>
79 91
 Thatcher Peskens <thatcher@dotcloud.com>
80 92
 Thomas Bikeev <thomas.bikeev@mac.com>
81 93
 Thomas Hansen <thomas.hansen@gmail.com>
82 94
 Tianon Gravi <admwiggin@gmail.com>
83 95
 Tim Terhorst <mynamewastaken+git@gmail.com>
84 96
 Tobias Bieniek <Tobias.Bieniek@gmx.de>
97
+Tobias Schwab <tobias.schwab@dynport.de>
98
+Tom Hulihan <hulihan.tom159@gmail.com>
85 99
 unclejack <unclejacksons@gmail.com>
86 100
 Victor Vieux <victor.vieux@dotcloud.com>
87 101
 Vivek Agarwal <me@vivek.im>
... ...
@@ -11,7 +11,7 @@ BUILD_DIR := $(CURDIR)/.gopath
11 11
 GOPATH ?= $(BUILD_DIR)
12 12
 export GOPATH
13 13
 
14
-GO_OPTIONS ?=
14
+GO_OPTIONS ?= -a -ldflags='-w -d'
15 15
 ifeq ($(VERBOSE), 1)
16 16
 GO_OPTIONS += -v
17 17
 endif
... ...
@@ -80,10 +80,10 @@ test:
80 80
 	tar --exclude=${BUILD_SRC} -cz . | tar -xz -C ${BUILD_PATH}
81 81
 	GOPATH=${CURDIR}/${BUILD_SRC} go get -d
82 82
 	# Do the test
83
-	sudo -E GOPATH=${CURDIR}/${BUILD_SRC} go test ${GO_OPTIONS}
83
+	sudo -E GOPATH=${CURDIR}/${BUILD_SRC} CGO_ENABLED=0 go test ${GO_OPTIONS}
84 84
 
85 85
 testall: all
86
-	@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
86
+	@(cd $(DOCKER_DIR); CGO_ENABLED=0 sudo -E go test ./... $(GO_OPTIONS))
87 87
 
88 88
 fmt:
89 89
 	@gofmt -s -l -w .
... ...
@@ -1,80 +1,129 @@
1 1
 Docker: the Linux container engine
2 2
 ==================================
3 3
 
4
-Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers.
4
+Docker is an open-source engine which automates the deployment of
5
+applications as highly portable, self-sufficient containers.
5 6
 
6
-Docker containers are both *hardware-agnostic* and *platform-agnostic*. This means that they can run anywhere, from your
7
-laptop to the largest EC2 compute instance and everything in between - and they don't require that you use a particular
8
-language, framework or packaging system. That makes them great building blocks for deploying and scaling web apps, databases
9
-and backend services without depending on a particular stack or provider.
7
+Docker containers are both *hardware-agnostic* and
8
+*platform-agnostic*. This means that they can run anywhere, from your
9
+laptop to the largest EC2 compute instance and everything in between -
10
+and they don't require that you use a particular language, framework
11
+or packaging system. That makes them great building blocks for
12
+deploying and scaling web apps, databases and backend services without
13
+depending on a particular stack or provider.
10 14
 
11
-Docker is an open-source implementation of the deployment engine which powers [dotCloud](http://dotcloud.com), a popular Platform-as-a-Service.
12
-It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
13
-of applications and databases.
15
+Docker is an open-source implementation of the deployment engine which
16
+powers [dotCloud](http://dotcloud.com), a popular
17
+Platform-as-a-Service.  It benefits directly from the experience
18
+accumulated over several years of large-scale operation and support of
19
+hundreds of thousands of applications and databases.
14 20
 
15
-![Docker L](docs/sources/concepts/images/lego_docker.jpg "Docker")
21
+![Docker L](docs/sources/concepts/images/dockerlogo-h.png "Docker")
16 22
 
17 23
 ## Better than VMs
18 24
 
19
-A common method for distributing applications and sandbox their execution is to use virtual machines, or VMs. Typical VM formats
20
-are VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory these formats should allow every developer to
21
-automatically package their application into a "machine" for easy distribution and deployment. In practice, that almost never
22
-happens, for a few reasons:
23
-
24
-  * *Size*: VMs are very large which makes them impractical to store and transfer.
25
-  * *Performance*: running VMs consumes significant CPU and memory, which makes them impractical in many scenarios, for example local development of multi-tier applications, and
26
-    large-scale deployment of cpu and memory-intensive applications on large numbers of machines.
27
-  * *Portability*: competing VM environments don't play well with each other. Although conversion tools do exist, they are limited and add even more overhead.
28
-  * *Hardware-centric*: VMs were designed with machine operators in mind, not software developers. As a result, they offer very limited tooling for what developers need most:
29
-    building, testing and running their software. For example, VMs offer no facilities for application versioning, monitoring, configuration, logging or service discovery.
30
-
31
-By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization,
32
-containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary
33
-for containerization, including Linux with [openvz](http://openvz.org), [vserver](http://linux-vserver.org) and more recently [lxc](http://lxc.sourceforge.net),
34
-    Solaris with [zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc) and FreeBSD with [Jails](http://www.freebsd.org/doc/handbook/jails.html).
35
-
36
-Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves
37
-all 4 problems. Docker containers are small (and their transfer can be optimized with layers), they have basically zero memory and cpu overhead,
38
-they are completely portable and are designed from the ground up with an application-centric design.
39
-
40
-The best part: because docker operates at the OS level, it can still be run inside a VM!
25
+A common method for distributing applications and sandbox their
26
+execution is to use virtual machines, or VMs. Typical VM formats are
27
+VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In
28
+theory these formats should allow every developer to automatically
29
+package their application into a "machine" for easy distribution and
30
+deployment. In practice, that almost never happens, for a few reasons:
31
+
32
+  * *Size*: VMs are very large which makes them impractical to store
33
+     and transfer.
34
+  * *Performance*: running VMs consumes significant CPU and memory,
35
+    which makes them impractical in many scenarios, for example local
36
+    development of multi-tier applications, and large-scale deployment
37
+    of cpu and memory-intensive applications on large numbers of
38
+    machines.
39
+  * *Portability*: competing VM environments don't play well with each
40
+     other. Although conversion tools do exist, they are limited and
41
+     add even more overhead.
42
+  * *Hardware-centric*: VMs were designed with machine operators in
43
+    mind, not software developers. As a result, they offer very
44
+    limited tooling for what developers need most: building, testing
45
+    and running their software. For example, VMs offer no facilities
46
+    for application versioning, monitoring, configuration, logging or
47
+    service discovery.
48
+
49
+By contrast, Docker relies on a different sandboxing method known as
50
+*containerization*. Unlike traditional virtualization,
51
+containerization takes place at the kernel level. Most modern
52
+operating system kernels now support the primitives necessary for
53
+containerization, including Linux with [openvz](http://openvz.org),
54
+[vserver](http://linux-vserver.org) and more recently
55
+[lxc](http://lxc.sourceforge.net), Solaris with
56
+[zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc)
57
+and FreeBSD with
58
+[Jails](http://www.freebsd.org/doc/handbook/jails.html).
59
+
60
+Docker builds on top of these low-level primitives to offer developers
61
+a portable format and runtime environment that solves all 4
62
+problems. Docker containers are small (and their transfer can be
63
+optimized with layers), they have basically zero memory and cpu
64
+overhead, they are completely portable and are designed from the
65
+ground up with an application-centric design.
66
+
67
+The best part: because ``docker`` operates at the OS level, it can
68
+still be run inside a VM!
41 69
 
42 70
 ## Plays well with others
43 71
 
44
-Docker does not require that you buy into a particular programming language, framework, packaging system or configuration language.
72
+Docker does not require that you buy into a particular programming
73
+language, framework, packaging system or configuration language.
45 74
 
46
-Is your application a unix process? Does it use files, tcp connections, environment variables, standard unix streams and command-line
47
-arguments as inputs and outputs? Then docker can run it.
75
+Is your application a Unix process? Does it use files, tcp
76
+connections, environment variables, standard Unix streams and
77
+command-line arguments as inputs and outputs? Then ``docker`` can run
78
+it.
48 79
 
49
-Can your application's build be expressed as a sequence of such commands? Then docker can build it.
80
+Can your application's build be expressed as a sequence of such
81
+commands? Then ``docker`` can build it.
50 82
 
51 83
 
52 84
 ## Escape dependency hell
53 85
 
54
-A common problem for developers is the difficulty of managing all their application's dependencies in a simple and automated way.
86
+A common problem for developers is the difficulty of managing all
87
+their application's dependencies in a simple and automated way.
55 88
 
56 89
 This is usually difficult for several reasons:
57 90
 
58
-  * *Cross-platform dependencies*. Modern applications often depend on a combination of system libraries and binaries, language-specific packages, framework-specific modules,
59
-    internal components developed for another project, etc. These dependencies live in different "worlds" and require different tools - these tools typically don't work
60
-    well with each other, requiring awkward custom integrations.
61
-
62
-  * Conflicting dependencies. Different applications may depend on different versions of the same dependency. Packaging tools handle these situations with various degrees of ease -
63
-    but they all handle them in different and incompatible ways, which again forces the developer to do extra work.
91
+  * *Cross-platform dependencies*. Modern applications often depend on
92
+    a combination of system libraries and binaries, language-specific
93
+    packages, framework-specific modules, internal components
94
+    developed for another project, etc. These dependencies live in
95
+    different "worlds" and require different tools - these tools
96
+    typically don't work well with each other, requiring awkward
97
+    custom integrations.
98
+
99
+  * Conflicting dependencies. Different applications may depend on
100
+    different versions of the same dependency. Packaging tools handle
101
+    these situations with various degrees of ease - but they all
102
+    handle them in different and incompatible ways, which again forces
103
+    the developer to do extra work.
64 104
   
65
-  * Custom dependencies. A developer may need to prepare a custom version of their application's dependency. Some packaging systems can handle custom versions of a dependency,
66
-    others can't - and all of them handle it differently.
105
+  * Custom dependencies. A developer may need to prepare a custom
106
+    version of their application's dependency. Some packaging systems
107
+    can handle custom versions of a dependency, others can't - and all
108
+    of them handle it differently.
67 109
 
68 110
 
69
-Docker solves dependency hell by giving the developer a simple way to express *all* their application's dependencies in one place,
70
-and streamline the process of assembling them. If this makes you think of [XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
71
-*replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers.
111
+Docker solves dependency hell by giving the developer a simple way to
112
+express *all* their application's dependencies in one place, and
113
+streamline the process of assembling them. If this makes you think of
114
+[XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't
115
+*replace* your favorite packaging systems. It simply orchestrates
116
+their use in a simple and repeatable way. How does it do that? With
117
+layers.
72 118
 
73
-Docker defines a build as running a sequence of unix commands, one after the other, in the same container. Build commands modify the contents of the container
74
-(usually by installing new files on the filesystem), the next command modifies it some more, etc. Since each build command inherits the result of the previous
75
-commands, the *order* in which the commands are executed expresses *dependencies*.
119
+Docker defines a build as running a sequence of Unix commands, one
120
+after the other, in the same container. Build commands modify the
121
+contents of the container (usually by installing new files on the
122
+filesystem), the next command modifies it some more, etc. Since each
123
+build command inherits the result of the previous commands, the
124
+*order* in which the commands are executed expresses *dependencies*.
76 125
 
77
-Here's a typical docker build process:
126
+Here's a typical Docker build process:
78 127
 
79 128
 ```bash
80 129
 from ubuntu:12.10
... ...
@@ -87,7 +136,8 @@ run curl -L https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xz
87 87
 run cd helloflask-master && pip install -r requirements.txt
88 88
 ```
89 89
 
90
-Note that Docker doesn't care *how* dependencies are built - as long as they can be built by running a unix command in a container.
90
+Note that Docker doesn't care *how* dependencies are built - as long
91
+as they can be built by running a Unix command in a container.
91 92
 
92 93
 
93 94
 Install instructions
... ...
@@ -103,8 +153,9 @@ curl get.docker.io | sudo sh -x
103 103
 Binary installs
104 104
 ----------------
105 105
 
106
-Docker supports the following binary installation methods.
107
-Note that some methods are community contributions and not yet officially supported.
106
+Docker supports the following binary installation methods.  Note that
107
+some methods are community contributions and not yet officially
108
+supported.
108 109
 
109 110
 * [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/)
110 111
 * [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/)
... ...
@@ -115,15 +166,15 @@ Note that some methods are community contributions and not yet officially suppor
115 115
 Installing from source
116 116
 ----------------------
117 117
 
118
-1. Make sure you have a [Go language](http://golang.org/doc/install) compiler and [git](http://git-scm.com) installed.
119
-
118
+1. Make sure you have a [Go language](http://golang.org/doc/install)
119
+compiler >= 1.1 and [git](http://git-scm.com) installed.
120 120
 2. Checkout the source code
121 121
 
122 122
    ```bash
123 123
    git clone http://github.com/dotcloud/docker
124 124
    ```
125 125
 
126
-3. Build the docker binary
126
+3. Build the ``docker`` binary
127 127
 
128 128
    ```bash
129 129
    cd docker
... ...
@@ -134,17 +185,20 @@ Installing from source
134 134
 Usage examples
135 135
 ==============
136 136
 
137
-First run the docker daemon
137
+First run the ``docker`` daemon
138
+-------------------------------
138 139
 
139
-All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
140
+All the examples assume your machine is running the ``docker``
141
+daemon. To run the ``docker`` daemon in the background, simply type:
140 142
 
141 143
 ```bash
142 144
 # On a production system you want this running in an init script
143 145
 sudo docker -d &
144 146
 ```
145 147
 
146
-Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client can run from any account.
148
+Now you can run ``docker`` in client mode: all commands will be
149
+forwarded to the ``docker`` daemon, so the client can run from any
150
+account.
147 151
 
148 152
 ```bash
149 153
 # Now you can run docker commands from any account.
... ...
@@ -152,7 +206,7 @@ docker help
152 152
 ```
153 153
 
154 154
 
155
-Throwaway shell in a base ubuntu image
155
+Throwaway shell in a base Ubuntu image
156 156
 --------------------------------------
157 157
 
158 158
 ```bash
... ...
@@ -202,7 +256,8 @@ docker commit -m "Installed curl" $CONTAINER $USER/betterbase
202 202
 docker push $USER/betterbase
203 203
 ```
204 204
 
205
-A list of publicly available images is [available here](https://github.com/dotcloud/docker/wiki/Public-docker-images).
205
+A list of publicly available images is [available
206
+here](https://github.com/dotcloud/docker/wiki/Public-docker-images).
206 207
 
207 208
 Expose a service on a TCP port
208 209
 ------------------------------
... ...
@@ -229,32 +284,40 @@ Under the hood
229 229
 
230 230
 Under the hood, Docker is built on the following components:
231 231
 
232
-
233
-* The [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel;
234
-
235
-* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities;
236
-
232
+* The
233
+  [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c)
234
+  and
235
+  [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part)
236
+  capabilities of the Linux kernel;
237
+* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union
238
+  filesystem with copy-on-write capabilities;
237 239
 * The [Go](http://golang.org) programming language;
238
-
239
-* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
240
+* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to
241
+  simplify the creation of Linux containers.
240 242
 
241 243
 
242 244
 
243 245
 Contributing to Docker
244 246
 ======================
245 247
 
246
-Want to hack on Docker? Awesome! There are instructions to get you started on the website: http://docs.docker.io/en/latest/contributing/contributing/
248
+Want to hack on Docker? Awesome! There are instructions to get you
249
+started on the website:
250
+http://docs.docker.io/en/latest/contributing/contributing/
247 251
 
248
-They are probably not perfect, please let us know if anything feels wrong or incomplete.
252
+They are probably not perfect, please let us know if anything feels
253
+wrong or incomplete.
249 254
 
250 255
 
251 256
 Note
252 257
 ----
253 258
 
254
-We also keep the documentation in this repository. The website documentation is generated using sphinx using these sources.
255
-Please find it under docs/sources/ and read more about it https://github.com/dotcloud/docker/tree/master/docs/README.md
259
+We also keep the documentation in this repository. The website
260
+documentation is generated using Sphinx using these sources.  Please
261
+find it under docs/sources/ and read more about it
262
+https://github.com/dotcloud/docker/tree/master/docs/README.md
256 263
 
257
-Please feel free to fix / update the documentation and send us pull requests. More tutorials are also welcome.
264
+Please feel free to fix / update the documentation and send us pull
265
+requests. More tutorials are also welcome.
258 266
 
259 267
 
260 268
 Setting up a dev environment
... ...
@@ -289,93 +352,104 @@ Run the `go install` command (above) to recompile docker.
289 289
 What is a Standard Container?
290 290
 =============================
291 291
 
292
-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
293
-a format that is self-describing and portable, so that any compliant runtime can run it without extra dependencies, regardless of the underlying machine and the contents of the container.
292
+Docker defines a unit of software delivery called a Standard
293
+Container. The goal of a Standard Container is to encapsulate a
294
+software component and all its dependencies in a format that is
295
+self-describing and portable, so that any compliant runtime can run it
296
+without extra dependencies, regardless of the underlying machine and
297
+the contents of the container.
294 298
 
295
-The spec for Standard Containers is currently a 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.
299
+The spec for Standard Containers is currently a work in progress, but
300
+it is very straightforward. It mostly defines 1) an image format, 2) a
301
+set of standard operations, and 3) an execution environment.
296 302
 
297
-A great analogy for this is the shipping container. Just like how 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.
303
+A great analogy for this is the shipping container. Just like how
304
+Standard Containers are a fundamental unit of software delivery,
305
+shipping containers are a fundamental unit of physical delivery.
298 306
 
299 307
 ### 1. STANDARD OPERATIONS
300 308
 
301
-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.
309
+Just like shipping containers, Standard Containers define a set of
310
+STANDARD OPERATIONS. Shipping containers can be lifted, stacked,
311
+locked, loaded, unloaded and labelled. Similarly, Standard Containers
312
+can be started, stopped, copied, snapshotted, downloaded, uploaded and
313
+tagged.
302 314
 
303 315
 
304 316
 ### 2. CONTENT-AGNOSTIC
305 317
 
306
-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.
318
+Just like shipping containers, Standard Containers are
319
+CONTENT-AGNOSTIC: all standard operations have the same effect
320
+regardless of the contents. A shipping container will be stacked in
321
+exactly the same way whether it contains Vietnamese powder coffee or
322
+spare Maserati parts. Similarly, Standard Containers are started or
323
+uploaded in the same way whether they contain a postgres database, a
324
+php application with its dependencies and application server, or Java
325
+build artifacts.
307 326
 
308 327
 
309 328
 ### 3. INFRASTRUCTURE-AGNOSTIC
310 329
 
311
-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.
330
+Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be
331
+transported to thousands of facilities around the world, and
332
+manipulated by a wide variety of equipment. A shipping container can
333
+be packed in a factory in Ukraine, transported by truck to the nearest
334
+routing center, stacked onto a train, loaded into a German boat by an
335
+Australian-built crane, stored in a warehouse at a US facility,
336
+etc. Similarly, a standard container can be bundled on my laptop,
337
+uploaded to S3, downloaded, run and snapshotted by a build server at
338
+Equinix in Virginia, uploaded to 10 staging servers in a home-made
339
+Openstack cluster, then sent to 30 production instances across 3 EC2
340
+regions.
312 341
 
313 342
 
314 343
 ### 4. DESIGNED FOR AUTOMATION
315 344
 
316
-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.
317
-
318
-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.
319
-
320
-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.
345
+Because they offer the same standard operations regardless of content
346
+and infrastructure, Standard Containers, just like their physical
347
+counterparts, are extremely well-suited for automation. In fact, you
348
+could say automation is their secret weapon.
349
+
350
+Many things that once required time-consuming and error-prone human
351
+effort can now be programmed. Before shipping containers, a bag of
352
+powder coffee was hauled, dragged, dropped, rolled and stacked by 10
353
+different people in 10 different locations by the time it reached its
354
+destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The
355
+process was slow, inefficient and cost a fortune - and was entirely
356
+different depending on the facility and the type of goods.
357
+
358
+Similarly, before Standard Containers, by the time a software
359
+component ran in production, it had been individually built,
360
+configured, bundled, documented, patched, vendored, templated, tweaked
361
+and instrumented by 10 different people on 10 different
362
+computers. Builds failed, libraries conflicted, mirrors crashed,
363
+post-it notes were lost, logs were misplaced, cluster updates were
364
+half-broken. The process was slow, inefficient and cost a fortune -
365
+and was entirely different depending on the language and
366
+infrastructure provider.
321 367
 
322 368
 
323 369
 ### 5. INDUSTRIAL-GRADE DELIVERY
324 370
 
325
-There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded onto 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.
326
-
327
-With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
328
-
329
-
330
-
331
-
332
-Standard Container Specification
333
-
334
-(TODO)
335
-
336
-### Image format
337
-
338
-
339
-### Standard operations
340
-
341
-* Copy
342
-* Run
343
-* Stop
344
-* Wait
345
-* Commit
346
-* Attach standard streams
347
-* List filesystem changes
348
-* ...
349
-
350
-### Execution environment
351
-
352
-#### Root filesystem
353
-
354
-#### Environment variables
355
-
356
-#### Process arguments
357
-
358
-#### Networking
359
-
360
-#### Process namespacing
361
-
362
-#### Resource limits
363
-
364
-#### Process monitoring
365
-
366
-#### Logging
367
-
368
-#### Signals
371
+There are 17 million shipping containers in existence, packed with
372
+every physical good imaginable. Every single one of them can be loaded
373
+onto the same boats, by the same cranes, in the same facilities, and
374
+sent anywhere in the World with incredible efficiency. It is
375
+embarrassing to think that a 30 ton shipment of coffee can safely
376
+travel half-way across the World in *less time* than it takes a
377
+software team to deliver its code from one datacenter to another
378
+sitting 10 miles away.
369 379
 
370
-#### Pseudo-terminal allocation
380
+With Standard Containers we can put an end to that embarrassment, by
381
+making INDUSTRIAL-GRADE DELIVERY of software a reality.
371 382
 
372
-#### Security
373 383
 
374 384
 ### Legal
375 385
 
376
-Transfers of Docker shall be in accordance with applicable export controls of any country and all other applicable
377
-legal requirements.  Docker shall not be distributed or downloaded to or in Cuba, Iran, North Korea, Sudan or Syria
378
-and shall not be distributed or downloaded to any person on the Denied Persons List administered by the U.S.
386
+Transfers of Docker shall be in accordance with applicable export
387
+controls of any country and all other applicable legal requirements.
388
+Docker shall not be distributed or downloaded to or in Cuba, Iran,
389
+North Korea, Sudan or Syria and shall not be distributed or downloaded
390
+to any person on the Denied Persons List administered by the U.S.
379 391
 Department of Commerce.
380 392
 
... ...
@@ -20,6 +20,8 @@ Vagrant::Config.run do |config|
20 20
     pkg_cmd = "apt-get update -qq; apt-get install -q -y python-software-properties; " \
21 21
       "add-apt-repository -y ppa:dotcloud/lxc-docker; apt-get update -qq; " \
22 22
       "apt-get install -q -y lxc-docker; "
23
+    # Listen on all interfaces so that the daemon is accessible from the host
24
+    pkg_cmd << "sed -i -E 's|    /usr/bin/docker -d|    /usr/bin/docker -d -H 0.0.0.0|' /etc/init/docker.conf;"
23 25
     # Add X.org Ubuntu backported 3.8 kernel
24 26
     pkg_cmd << "add-apt-repository -y ppa:ubuntu-x-swat/r-lts-backport; " \
25 27
       "apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; "
... ...
@@ -81,54 +81,15 @@ func getBoolParam(value string) (bool, error) {
81 81
 	return ret, nil
82 82
 }
83 83
 
84
-func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
85
-	if version > 1.1 {
86
-		w.WriteHeader(http.StatusNotFound)
87
-		return nil
88
-	}
89
-	authConfig, err := auth.LoadConfig(srv.runtime.root)
90
-	if err != nil {
91
-		if err != auth.ErrConfigFileMissing {
92
-			return err
93
-		}
94
-		authConfig = &auth.AuthConfig{}
95
-	}
96
-	b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email})
97
-	if err != nil {
98
-		return err
99
-	}
100
-	writeJSON(w, b)
101
-	return nil
102
-}
103
-
104 84
 func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
105 85
 	authConfig := &auth.AuthConfig{}
106 86
 	err := json.NewDecoder(r.Body).Decode(authConfig)
107 87
 	if err != nil {
108 88
 		return err
109 89
 	}
110
-	status := ""
111
-	if version > 1.1 {
112
-		status, err = auth.Login(authConfig, false)
113
-		if err != nil {
114
-			return err
115
-		}
116
-	} else {
117
-		localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
118
-		if err != nil {
119
-			if err != auth.ErrConfigFileMissing {
120
-				return err
121
-			}
122
-		}
123
-		if authConfig.Username == localAuthConfig.Username {
124
-			authConfig.Password = localAuthConfig.Password
125
-		}
126
-
127
-		newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root)
128
-		status, err = auth.Login(newAuthConfig, true)
129
-		if err != nil {
130
-			return err
131
-		}
90
+	status, err := auth.Login(authConfig)
91
+	if err != nil {
92
+		return err
132 93
 	}
133 94
 	if status != "" {
134 95
 		b, err := json.Marshal(&APIAuth{Status: status})
... ...
@@ -217,6 +178,64 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
217 217
 	return nil
218 218
 }
219 219
 
220
+func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
221
+	sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) error {
222
+		b, err := json.Marshal(event)
223
+		if err != nil {
224
+			return fmt.Errorf("JSON error")
225
+		}
226
+		_, err = wf.Write(b)
227
+		if err != nil {
228
+			// On error, evict the listener
229
+			utils.Debugf("%s", err)
230
+			srv.Lock()
231
+			delete(srv.listeners, r.RemoteAddr)
232
+			srv.Unlock()
233
+			return err
234
+		}
235
+		return nil
236
+	}
237
+
238
+	if err := parseForm(r); err != nil {
239
+		return err
240
+	}
241
+	listener := make(chan utils.JSONMessage)
242
+	srv.Lock()
243
+	srv.listeners[r.RemoteAddr] = listener
244
+	srv.Unlock()
245
+	since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0)
246
+	if err != nil {
247
+		since = 0
248
+	}
249
+	w.Header().Set("Content-Type", "application/json")
250
+	wf := utils.NewWriteFlusher(w)
251
+	if since != 0 {
252
+		// If since, send previous events that happened after the timestamp
253
+		for _, event := range srv.events {
254
+			if event.Time >= since {
255
+				err := sendEvent(wf, &event)
256
+				if err != nil && err.Error() == "JSON error" {
257
+					continue
258
+				}
259
+				if err != nil {
260
+					return err
261
+				}
262
+			}
263
+		}
264
+	}
265
+	for {
266
+		event := <-listener
267
+		err := sendEvent(wf, &event)
268
+		if err != nil && err.Error() == "JSON error" {
269
+			continue
270
+		}
271
+		if err != nil {
272
+			return err
273
+		}
274
+	}
275
+	return nil
276
+}
277
+
220 278
 func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
221 279
 	if vars == nil {
222 280
 		return fmt.Errorf("Missing parameter")
... ...
@@ -436,16 +455,8 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
436 436
 
437 437
 func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
438 438
 	authConfig := &auth.AuthConfig{}
439
-	if version > 1.1 {
440
-		if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
441
-			return err
442
-		}
443
-	} else {
444
-		localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
445
-		if err != nil && err != auth.ErrConfigFileMissing {
446
-			return err
447
-		}
448
-		authConfig = localAuthConfig
439
+	if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
440
+		return err
449 441
 	}
450 442
 	if err := parseForm(r); err != nil {
451 443
 		return err
... ...
@@ -861,9 +872,9 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
861 861
 
862 862
 	m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
863 863
 		"GET": {
864
-			"/auth":                         getAuth,
865
-			"/version":                      getVersion,
864
+			"/events":                       getEvents,
866 865
 			"/info":                         getInfo,
866
+			"/version":                      getVersion,
867 867
 			"/images/json":                  getImagesJSON,
868 868
 			"/images/viz":                   getImagesViz,
869 869
 			"/images/search":                getImagesSearch,
... ...
@@ -17,13 +17,16 @@ type APIImages struct {
17 17
 }
18 18
 
19 19
 type APIInfo struct {
20
-	Debug       bool
21
-	Containers  int
22
-	Images      int
23
-	NFd         int  `json:",omitempty"`
24
-	NGoroutines int  `json:",omitempty"`
25
-	MemoryLimit bool `json:",omitempty"`
26
-	SwapLimit   bool `json:",omitempty"`
20
+	Debug           bool
21
+	Containers      int
22
+	Images          int
23
+	NFd             int    `json:",omitempty"`
24
+	NGoroutines     int    `json:",omitempty"`
25
+	MemoryLimit     bool   `json:",omitempty"`
26
+	SwapLimit       bool   `json:",omitempty"`
27
+	LXCVersion      string `json:",omitempty"`
28
+	NEventsListener int    `json:",omitempty"`
29
+	KernelVersion   string `json:",omitempty"`
27 30
 }
28 31
 
29 32
 type APITop struct {
... ...
@@ -89,6 +89,44 @@ func TestGetInfo(t *testing.T) {
89 89
 	}
90 90
 }
91 91
 
92
+func TestGetEvents(t *testing.T) {
93
+	runtime := mkRuntime(t)
94
+	srv := &Server{
95
+		runtime:   runtime,
96
+		events:    make([]utils.JSONMessage, 0, 64),
97
+		listeners: make(map[string]chan utils.JSONMessage),
98
+	}
99
+
100
+	srv.LogEvent("fakeaction", "fakeid")
101
+	srv.LogEvent("fakeaction2", "fakeid")
102
+
103
+	req, err := http.NewRequest("GET", "/events?since=1", nil)
104
+	if err != nil {
105
+		t.Fatal(err)
106
+	}
107
+
108
+	r := httptest.NewRecorder()
109
+	setTimeout(t, "", 500*time.Millisecond, func() {
110
+		if err := getEvents(srv, APIVERSION, r, req, nil); err != nil {
111
+			t.Fatal(err)
112
+		}
113
+	})
114
+
115
+	dec := json.NewDecoder(r.Body)
116
+	for i := 0; i < 2; i++ {
117
+		var jm utils.JSONMessage
118
+		if err := dec.Decode(&jm); err == io.EOF {
119
+			break
120
+		} else if err != nil {
121
+			t.Fatal(err)
122
+		}
123
+		if jm != srv.events[i] {
124
+			t.Fatalf("Event received it different than expected")
125
+		}
126
+	}
127
+
128
+}
129
+
92 130
 func TestGetImagesJSON(t *testing.T) {
93 131
 	runtime := mkRuntime(t)
94 132
 	defer nuke(runtime)
... ...
@@ -866,6 +904,12 @@ func TestPostContainersAttach(t *testing.T) {
866 866
 	stdin, stdinPipe := io.Pipe()
867 867
 	stdout, stdoutPipe := io.Pipe()
868 868
 
869
+	// Try to avoid the timeoout in destroy. Best effort, don't check error
870
+	defer func() {
871
+		closeWrap(stdin, stdinPipe, stdout, stdoutPipe)
872
+		container.Kill()
873
+	}()
874
+
869 875
 	// Attach to it
870 876
 	c1 := make(chan struct{})
871 877
 	go func() {
... ...
@@ -905,7 +949,7 @@ func TestPostContainersAttach(t *testing.T) {
905 905
 	}
906 906
 
907 907
 	// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
908
-	setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
908
+	setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() {
909 909
 		<-c1
910 910
 	})
911 911
 
... ...
@@ -25,19 +25,15 @@ var (
25 25
 )
26 26
 
27 27
 type AuthConfig struct {
28
-	Username string `json:"username"`
29
-	Password string `json:"password"`
28
+	Username string `json:"username,omitempty"`
29
+	Password string `json:"password,omitempty"`
30
+	Auth     string `json:"auth"`
30 31
 	Email    string `json:"email"`
31
-	rootPath string
32 32
 }
33 33
 
34
-func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
35
-	return &AuthConfig{
36
-		Username: username,
37
-		Password: password,
38
-		Email:    email,
39
-		rootPath: rootPath,
40
-	}
34
+type ConfigFile struct {
35
+	Configs  map[string]AuthConfig `json:"configs,omitempty"`
36
+	rootPath string
41 37
 }
42 38
 
43 39
 func IndexServerAddress() string {
... ...
@@ -54,61 +50,89 @@ func encodeAuth(authConfig *AuthConfig) string {
54 54
 }
55 55
 
56 56
 // decode the auth string
57
-func decodeAuth(authStr string) (*AuthConfig, error) {
57
+func decodeAuth(authStr string) (string, string, error) {
58 58
 	decLen := base64.StdEncoding.DecodedLen(len(authStr))
59 59
 	decoded := make([]byte, decLen)
60 60
 	authByte := []byte(authStr)
61 61
 	n, err := base64.StdEncoding.Decode(decoded, authByte)
62 62
 	if err != nil {
63
-		return nil, err
63
+		return "", "", err
64 64
 	}
65 65
 	if n > decLen {
66
-		return nil, fmt.Errorf("Something went wrong decoding auth config")
66
+		return "", "", fmt.Errorf("Something went wrong decoding auth config")
67 67
 	}
68 68
 	arr := strings.Split(string(decoded), ":")
69 69
 	if len(arr) != 2 {
70
-		return nil, fmt.Errorf("Invalid auth configuration file")
70
+		return "", "", fmt.Errorf("Invalid auth configuration file")
71 71
 	}
72 72
 	password := strings.Trim(arr[1], "\x00")
73
-	return &AuthConfig{Username: arr[0], Password: password}, nil
73
+	return arr[0], password, nil
74 74
 }
75 75
 
76 76
 // load up the auth config information and return values
77 77
 // FIXME: use the internal golang config parser
78
-func LoadConfig(rootPath string) (*AuthConfig, error) {
78
+func LoadConfig(rootPath string) (*ConfigFile, error) {
79
+	configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath}
79 80
 	confFile := path.Join(rootPath, CONFIGFILE)
80 81
 	if _, err := os.Stat(confFile); err != nil {
81
-		return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
82
+		return &configFile, ErrConfigFileMissing
82 83
 	}
83 84
 	b, err := ioutil.ReadFile(confFile)
84 85
 	if err != nil {
85 86
 		return nil, err
86 87
 	}
87
-	arr := strings.Split(string(b), "\n")
88
-	if len(arr) < 2 {
89
-		return nil, fmt.Errorf("The Auth config file is empty")
90
-	}
91
-	origAuth := strings.Split(arr[0], " = ")
92
-	origEmail := strings.Split(arr[1], " = ")
93
-	authConfig, err := decodeAuth(origAuth[1])
94
-	if err != nil {
95
-		return nil, err
88
+
89
+	if err := json.Unmarshal(b, &configFile.Configs); err != nil {
90
+		arr := strings.Split(string(b), "\n")
91
+		if len(arr) < 2 {
92
+			return nil, fmt.Errorf("The Auth config file is empty")
93
+		}
94
+		authConfig := AuthConfig{}
95
+		origAuth := strings.Split(arr[0], " = ")
96
+		authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
97
+		if err != nil {
98
+			return nil, err
99
+		}
100
+		origEmail := strings.Split(arr[1], " = ")
101
+		authConfig.Email = origEmail[1]
102
+		configFile.Configs[IndexServerAddress()] = authConfig
103
+	} else {
104
+		for k, authConfig := range configFile.Configs {
105
+			authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
106
+			if err != nil {
107
+				return nil, err
108
+			}
109
+			authConfig.Auth = ""
110
+			configFile.Configs[k] = authConfig
111
+		}
96 112
 	}
97
-	authConfig.Email = origEmail[1]
98
-	authConfig.rootPath = rootPath
99
-	return authConfig, nil
113
+	return &configFile, nil
100 114
 }
101 115
 
102 116
 // save the auth config
103
-func SaveConfig(authConfig *AuthConfig) error {
104
-	confFile := path.Join(authConfig.rootPath, CONFIGFILE)
105
-	if len(authConfig.Email) == 0 {
117
+func SaveConfig(configFile *ConfigFile) error {
118
+	confFile := path.Join(configFile.rootPath, CONFIGFILE)
119
+	if len(configFile.Configs) == 0 {
106 120
 		os.Remove(confFile)
107 121
 		return nil
108 122
 	}
109
-	lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n"
110
-	b := []byte(lines)
111
-	err := ioutil.WriteFile(confFile, b, 0600)
123
+
124
+	configs := make(map[string]AuthConfig, len(configFile.Configs))
125
+	for k, authConfig := range configFile.Configs {
126
+		authCopy := authConfig
127
+
128
+		authCopy.Auth = encodeAuth(&authCopy)
129
+		authCopy.Username = ""
130
+		authCopy.Password = ""
131
+
132
+		configs[k] = authCopy
133
+	}
134
+
135
+	b, err := json.Marshal(configs)
136
+	if err != nil {
137
+		return err
138
+	}
139
+	err = ioutil.WriteFile(confFile, b, 0600)
112 140
 	if err != nil {
113 141
 		return err
114 142
 	}
... ...
@@ -116,8 +140,7 @@ func SaveConfig(authConfig *AuthConfig) error {
116 116
 }
117 117
 
118 118
 // try to register/login to the registry server
119
-func Login(authConfig *AuthConfig, store bool) (string, error) {
120
-	storeConfig := false
119
+func Login(authConfig *AuthConfig) (string, error) {
121 120
 	client := &http.Client{}
122 121
 	reqStatusCode := 0
123 122
 	var status string
... ...
@@ -143,7 +166,6 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
143 143
 	if reqStatusCode == 201 {
144 144
 		status = "Account created. Please use the confirmation link we sent" +
145 145
 			" to your e-mail to activate it."
146
-		storeConfig = true
147 146
 	} else if reqStatusCode == 403 {
148 147
 		return "", fmt.Errorf("Login: Your account hasn't been activated. " +
149 148
 			"Please check your e-mail for a confirmation link.")
... ...
@@ -162,14 +184,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
162 162
 			}
163 163
 			if resp.StatusCode == 200 {
164 164
 				status = "Login Succeeded"
165
-				storeConfig = true
166 165
 			} else if resp.StatusCode == 401 {
167
-				if store {
168
-					authConfig.Email = ""
169
-					if err := SaveConfig(authConfig); err != nil {
170
-						return "", err
171
-					}
172
-				}
173 166
 				return "", fmt.Errorf("Wrong login/password, please try again")
174 167
 			} else {
175 168
 				return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
... ...
@@ -181,10 +196,5 @@ func Login(authConfig *AuthConfig, store bool) (string, error) {
181 181
 	} else {
182 182
 		return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
183 183
 	}
184
-	if storeConfig && store {
185
-		if err := SaveConfig(authConfig); err != nil {
186
-			return "", err
187
-		}
188
-	}
189 184
 	return status, nil
190 185
 }
... ...
@@ -3,6 +3,7 @@ package auth
3 3
 import (
4 4
 	"crypto/rand"
5 5
 	"encoding/hex"
6
+	"io/ioutil"
6 7
 	"os"
7 8
 	"strings"
8 9
 	"testing"
... ...
@@ -11,7 +12,9 @@ import (
11 11
 func TestEncodeAuth(t *testing.T) {
12 12
 	newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"}
13 13
 	authStr := encodeAuth(newAuthConfig)
14
-	decAuthConfig, err := decodeAuth(authStr)
14
+	decAuthConfig := &AuthConfig{}
15
+	var err error
16
+	decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr)
15 17
 	if err != nil {
16 18
 		t.Fatal(err)
17 19
 	}
... ...
@@ -29,8 +32,8 @@ func TestEncodeAuth(t *testing.T) {
29 29
 func TestLogin(t *testing.T) {
30 30
 	os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
31 31
 	defer os.Setenv("DOCKER_INDEX_URL", "")
32
-	authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
33
-	status, err := Login(authConfig, false)
32
+	authConfig := &AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"}
33
+	status, err := Login(authConfig)
34 34
 	if err != nil {
35 35
 		t.Fatal(err)
36 36
 	}
... ...
@@ -49,8 +52,8 @@ func TestCreateAccount(t *testing.T) {
49 49
 	}
50 50
 	token := hex.EncodeToString(tokenBuffer)[:12]
51 51
 	username := "ut" + token
52
-	authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
53
-	status, err := Login(authConfig, false)
52
+	authConfig := &AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"}
53
+	status, err := Login(authConfig)
54 54
 	if err != nil {
55 55
 		t.Fatal(err)
56 56
 	}
... ...
@@ -60,7 +63,7 @@ func TestCreateAccount(t *testing.T) {
60 60
 		t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
61 61
 	}
62 62
 
63
-	status, err = Login(authConfig, false)
63
+	status, err = Login(authConfig)
64 64
 	if err == nil {
65 65
 		t.Fatalf("Expected error but found nil instead")
66 66
 	}
... ...
@@ -71,3 +74,39 @@ func TestCreateAccount(t *testing.T) {
71 71
 		t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err)
72 72
 	}
73 73
 }
74
+
75
+func TestSameAuthDataPostSave(t *testing.T) {
76
+	root, err := ioutil.TempDir("", "docker-test")
77
+	if err != nil {
78
+		t.Fatal(err)
79
+	}
80
+	configFile := &ConfigFile{
81
+		rootPath: root,
82
+		Configs:  make(map[string]AuthConfig, 1),
83
+	}
84
+
85
+	configFile.Configs["testIndex"] = AuthConfig{
86
+		Username: "docker-user",
87
+		Password: "docker-pass",
88
+		Email:    "docker@docker.io",
89
+	}
90
+
91
+	err = SaveConfig(configFile)
92
+	if err != nil {
93
+		t.Fatal(err)
94
+	}
95
+
96
+	authConfig := configFile.Configs["testIndex"]
97
+	if authConfig.Username != "docker-user" {
98
+		t.Fail()
99
+	}
100
+	if authConfig.Password != "docker-pass" {
101
+		t.Fail()
102
+	}
103
+	if authConfig.Email != "docker@docker.io" {
104
+		t.Fail()
105
+	}
106
+	if authConfig.Auth != "" {
107
+		t.Fail()
108
+	}
109
+}
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"os"
12 12
 	"path"
13 13
 	"reflect"
14
+	"regexp"
14 15
 	"strings"
15 16
 )
16 17
 
... ...
@@ -67,6 +68,9 @@ func (b *buildFile) CmdFrom(name string) error {
67 67
 	}
68 68
 	b.image = image.ID
69 69
 	b.config = &Config{}
70
+	if b.config.Env == nil || len(b.config.Env) == 0 {
71
+		b.config.Env = append(b.config.Env, "HOME=/", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
72
+	}
70 73
 	return nil
71 74
 }
72 75
 
... ...
@@ -112,6 +116,40 @@ func (b *buildFile) CmdRun(args string) error {
112 112
 	return nil
113 113
 }
114 114
 
115
+func (b *buildFile) FindEnvKey(key string) int {
116
+	for k, envVar := range b.config.Env {
117
+		envParts := strings.SplitN(envVar, "=", 2)
118
+		if key == envParts[0] {
119
+			return k
120
+		}
121
+	}
122
+	return -1
123
+}
124
+
125
+func (b *buildFile) ReplaceEnvMatches(value string) (string, error) {
126
+	exp, err := regexp.Compile("(\\\\\\\\+|[^\\\\]|\\b|\\A)\\$({?)([[:alnum:]_]+)(}?)")
127
+	if err != nil {
128
+		return value, err
129
+	}
130
+	matches := exp.FindAllString(value, -1)
131
+	for _, match := range matches {
132
+		match = match[strings.Index(match, "$"):]
133
+		matchKey := strings.Trim(match, "${}")
134
+
135
+		for _, envVar := range b.config.Env {
136
+			envParts := strings.SplitN(envVar, "=", 2)
137
+			envKey := envParts[0]
138
+			envValue := envParts[1]
139
+
140
+			if envKey == matchKey {
141
+				value = strings.Replace(value, match, envValue, -1)
142
+				break
143
+			}
144
+		}
145
+	}
146
+	return value, nil
147
+}
148
+
115 149
 func (b *buildFile) CmdEnv(args string) error {
116 150
 	tmp := strings.SplitN(args, " ", 2)
117 151
 	if len(tmp) != 2 {
... ...
@@ -120,14 +158,19 @@ func (b *buildFile) CmdEnv(args string) error {
120 120
 	key := strings.Trim(tmp[0], " \t")
121 121
 	value := strings.Trim(tmp[1], " \t")
122 122
 
123
-	for i, elem := range b.config.Env {
124
-		if strings.HasPrefix(elem, key+"=") {
125
-			b.config.Env[i] = key + "=" + value
126
-			return nil
127
-		}
123
+	envKey := b.FindEnvKey(key)
124
+	replacedValue, err := b.ReplaceEnvMatches(value)
125
+	if err != nil {
126
+		return err
128 127
 	}
129
-	b.config.Env = append(b.config.Env, key+"="+value)
130
-	return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
128
+	replacedVar := fmt.Sprintf("%s=%s", key, replacedValue)
129
+
130
+	if envKey >= 0 {
131
+		b.config.Env[envKey] = replacedVar
132
+		return nil
133
+	}
134
+	b.config.Env = append(b.config.Env, replacedVar)
135
+	return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s", replacedVar))
131 136
 }
132 137
 
133 138
 func (b *buildFile) CmdCmd(args string) error {
... ...
@@ -242,7 +285,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error {
242 242
 	} else if err := UntarPath(origPath, destPath); err != nil {
243 243
 		utils.Debugf("Couldn't untar %s to %s: %s", origPath, destPath, err)
244 244
 		// If that fails, just copy it as a regular file
245
-		if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
245
+		if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil {
246 246
 			return err
247 247
 		}
248 248
 		if err := CopyWithTar(origPath, destPath); err != nil {
... ...
@@ -260,8 +303,16 @@ func (b *buildFile) CmdAdd(args string) error {
260 260
 	if len(tmp) != 2 {
261 261
 		return fmt.Errorf("Invalid ADD format")
262 262
 	}
263
-	orig := strings.Trim(tmp[0], " \t")
264
-	dest := strings.Trim(tmp[1], " \t")
263
+
264
+	orig, err := b.ReplaceEnvMatches(strings.Trim(tmp[0], " \t"))
265
+	if err != nil {
266
+		return err
267
+	}
268
+
269
+	dest, err := b.ReplaceEnvMatches(strings.Trim(tmp[1], " \t"))
270
+	if err != nil {
271
+		return err
272
+	}
265 273
 
266 274
 	cmd := b.config.Cmd
267 275
 	b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
... ...
@@ -129,6 +129,38 @@ CMD Hello world
129 129
 		nil,
130 130
 		nil,
131 131
 	},
132
+
133
+	{
134
+		`
135
+from {IMAGE}
136
+env    FOO /foo/baz
137
+env    BAR /bar
138
+env    BAZ $BAR
139
+env    FOOPATH $PATH:$FOO
140
+run    [ "$BAR" = "$BAZ" ]
141
+run    [ "$FOOPATH" = "$PATH:/foo/baz" ]
142
+`,
143
+		nil,
144
+		nil,
145
+	},
146
+
147
+	{
148
+		`
149
+from {IMAGE}
150
+env    FOO /bar
151
+env    TEST testdir
152
+env    BAZ /foobar
153
+add    testfile $BAZ/
154
+add    $TEST $FOO
155
+run    [ "$(cat /foobar/testfile)" = "test1" ]
156
+run    [ "$(cat /bar/withfile)" = "test2" ]
157
+`,
158
+		[][2]string{
159
+			{"testfile", "test1"},
160
+			{"testdir/withfile", "test2"},
161
+		},
162
+		nil,
163
+	},
132 164
 }
133 165
 
134 166
 // FIXME: test building with 2 successive overlapping ADD commands
... ...
@@ -242,8 +274,14 @@ func TestBuildEnv(t *testing.T) {
242 242
         env port 4243
243 243
         `,
244 244
 		nil, nil}, t)
245
-
246
-	if img.Config.Env[0] != "port=4243" {
245
+	hasEnv := false
246
+	for _, envVar := range img.Config.Env {
247
+		if envVar == "port=4243" {
248
+			hasEnv = true
249
+			break
250
+		}
251
+	}
252
+	if !hasEnv {
247 253
 		t.Fail()
248 254
 	}
249 255
 }
... ...
@@ -78,6 +78,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
78 78
 		{"build", "Build a container from a Dockerfile"},
79 79
 		{"commit", "Create a new image from a container's changes"},
80 80
 		{"diff", "Inspect changes on a container's filesystem"},
81
+		{"events", "Get real time events from the server"},
81 82
 		{"export", "Stream the contents of a container as a tar archive"},
82 83
 		{"history", "Show the history of an image"},
83 84
 		{"images", "List images"},
... ...
@@ -185,6 +186,9 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
185 185
 	} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) {
186 186
 		isRemote = true
187 187
 	} else {
188
+		if _, err := os.Stat(cmd.Arg(0)); err != nil {
189
+			return err
190
+		}
188 191
 		context, err = Tar(cmd.Arg(0), Uncompressed)
189 192
 	}
190 193
 	var body io.Reader
... ...
@@ -310,16 +314,29 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
310 310
 		email    string
311 311
 	)
312 312
 
313
+	var promptDefault = func(prompt string, configDefault string) {
314
+		if configDefault == "" {
315
+			fmt.Fprintf(cli.out, "%s: ", prompt)
316
+		} else {
317
+			fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault)
318
+		}
319
+	}
320
+
321
+	authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()]
322
+	if !ok {
323
+		authconfig = auth.AuthConfig{}
324
+	}
325
+
313 326
 	if *flUsername == "" {
314
-		fmt.Fprintf(cli.out, "Username (%s): ", cli.authConfig.Username)
327
+		promptDefault("Username", authconfig.Username)
315 328
 		username = readAndEchoString(cli.in, cli.out)
316 329
 		if username == "" {
317
-			username = cli.authConfig.Username
330
+			username = authconfig.Username
318 331
 		}
319 332
 	} else {
320 333
 		username = *flUsername
321 334
 	}
322
-	if username != cli.authConfig.Username {
335
+	if username != authconfig.Username {
323 336
 		if *flPassword == "" {
324 337
 			fmt.Fprintf(cli.out, "Password: ")
325 338
 			password = readString(cli.in, cli.out)
... ...
@@ -331,31 +348,30 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
331 331
 		}
332 332
 
333 333
 		if *flEmail == "" {
334
-			fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email)
334
+			promptDefault("Email", authconfig.Email)
335 335
 			email = readAndEchoString(cli.in, cli.out)
336 336
 			if email == "" {
337
-				email = cli.authConfig.Email
337
+				email = authconfig.Email
338 338
 			}
339 339
 		} else {
340 340
 			email = *flEmail
341 341
 		}
342 342
 	} else {
343
-		password = cli.authConfig.Password
344
-		email = cli.authConfig.Email
343
+		password = authconfig.Password
344
+		email = authconfig.Email
345 345
 	}
346 346
 	if oldState != nil {
347 347
 		term.RestoreTerminal(cli.terminalFd, oldState)
348 348
 	}
349
-	cli.authConfig.Username = username
350
-	cli.authConfig.Password = password
351
-	cli.authConfig.Email = email
349
+	authconfig.Username = username
350
+	authconfig.Password = password
351
+	authconfig.Email = email
352
+	cli.configFile.Configs[auth.IndexServerAddress()] = authconfig
352 353
 
353
-	body, statusCode, err := cli.call("POST", "/auth", cli.authConfig)
354
+	body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[auth.IndexServerAddress()])
354 355
 	if statusCode == 401 {
355
-		cli.authConfig.Username = ""
356
-		cli.authConfig.Password = ""
357
-		cli.authConfig.Email = ""
358
-		auth.SaveConfig(cli.authConfig)
356
+		delete(cli.configFile.Configs, auth.IndexServerAddress())
357
+		auth.SaveConfig(cli.configFile)
359 358
 		return err
360 359
 	}
361 360
 	if err != nil {
... ...
@@ -365,10 +381,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
365 365
 	var out2 APIAuth
366 366
 	err = json.Unmarshal(body, &out2)
367 367
 	if err != nil {
368
-		auth.LoadConfig(os.Getenv("HOME"))
368
+		cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME"))
369 369
 		return err
370 370
 	}
371
-	auth.SaveConfig(cli.authConfig)
371
+	auth.SaveConfig(cli.configFile)
372 372
 	if out2.Status != "" {
373 373
 		fmt.Fprintf(cli.out, "%s\n", out2.Status)
374 374
 	}
... ...
@@ -463,6 +479,9 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
463 463
 		fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
464 464
 		fmt.Fprintf(cli.out, "Fds: %d\n", out.NFd)
465 465
 		fmt.Fprintf(cli.out, "Goroutines: %d\n", out.NGoroutines)
466
+		fmt.Fprintf(cli.out, "LXC Version: %s\n", out.LXCVersion)
467
+		fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener)
468
+		fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion)
466 469
 	}
467 470
 	if !out.MemoryLimit {
468 471
 		fmt.Fprintf(cli.err, "WARNING: No memory limit support\n")
... ...
@@ -761,7 +780,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
761 761
 }
762 762
 
763 763
 func (cli *DockerCli) CmdImport(args ...string) error {
764
-	cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
764
+	cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
765 765
 
766 766
 	if err := cmd.Parse(args); err != nil {
767 767
 		return nil
... ...
@@ -804,10 +823,10 @@ func (cli *DockerCli) CmdPush(args ...string) error {
804 804
 	// Custom repositories can have different rules, and we must also
805 805
 	// allow pushing by image ID.
806 806
 	if len(strings.SplitN(name, "/", 2)) == 1 {
807
-		return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name)
807
+		return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name)
808 808
 	}
809 809
 
810
-	buf, err := json.Marshal(cli.authConfig)
810
+	buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()])
811 811
 	if err != nil {
812 812
 		return err
813 813
 	}
... ...
@@ -1057,6 +1076,29 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
1057 1057
 	return nil
1058 1058
 }
1059 1059
 
1060
+func (cli *DockerCli) CmdEvents(args ...string) error {
1061
+	cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
1062
+	since := cmd.String("since", "", "Show events previously created (used for polling).")
1063
+	if err := cmd.Parse(args); err != nil {
1064
+		return nil
1065
+	}
1066
+
1067
+	if cmd.NArg() != 0 {
1068
+		cmd.Usage()
1069
+		return nil
1070
+	}
1071
+
1072
+	v := url.Values{}
1073
+	if *since != "" {
1074
+		v.Set("since", *since)
1075
+	}
1076
+
1077
+	if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out); err != nil {
1078
+		return err
1079
+	}
1080
+	return nil
1081
+}
1082
+
1060 1083
 func (cli *DockerCli) CmdExport(args ...string) error {
1061 1084
 	cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
1062 1085
 	if err := cmd.Parse(args); err != nil {
... ...
@@ -1380,7 +1422,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1380 1380
 	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
1381 1381
 		if config.Tty {
1382 1382
 			if err := cli.monitorTtySize(runResult.ID); err != nil {
1383
-				return err
1383
+				utils.Debugf("Error monitoring TTY size: %s\n", err)
1384 1384
 			}
1385 1385
 		}
1386 1386
 
... ...
@@ -1412,11 +1454,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1412 1412
 
1413 1413
 func (cli *DockerCli) checkIfLogged(action string) error {
1414 1414
 	// If condition AND the login failed
1415
-	if cli.authConfig.Username == "" {
1415
+	if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
1416 1416
 		if err := cli.CmdLogin(""); err != nil {
1417 1417
 			return err
1418 1418
 		}
1419
-		if cli.authConfig.Username == "" {
1419
+		if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" {
1420 1420
 			return fmt.Errorf("Please login prior to %s. ('docker login')", action)
1421 1421
 		}
1422 1422
 	}
... ...
@@ -1511,19 +1553,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1511 1511
 	if resp.Header.Get("Content-Type") == "application/json" {
1512 1512
 		dec := json.NewDecoder(resp.Body)
1513 1513
 		for {
1514
-			var m utils.JSONMessage
1515
-			if err := dec.Decode(&m); err == io.EOF {
1514
+			var jm utils.JSONMessage
1515
+			if err := dec.Decode(&jm); err == io.EOF {
1516 1516
 				break
1517 1517
 			} else if err != nil {
1518 1518
 				return err
1519 1519
 			}
1520
-			if m.Progress != "" {
1521
-				fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress)
1522
-			} else if m.Error != "" {
1523
-				return fmt.Errorf(m.Error)
1524
-			} else {
1525
-				fmt.Fprintf(out, "%s\n", m.Status)
1526
-			}
1520
+			jm.Display(out)
1527 1521
 		}
1528 1522
 	} else {
1529 1523
 		if _, err := io.Copy(out, resp.Body); err != nil {
... ...
@@ -1557,6 +1593,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
1557 1557
 
1558 1558
 	receiveStdout := utils.Go(func() error {
1559 1559
 		_, err := io.Copy(out, br)
1560
+		utils.Debugf("[hijack] End of stdout")
1560 1561
 		return err
1561 1562
 	})
1562 1563
 
... ...
@@ -1571,6 +1608,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
1571 1571
 	sendStdin := utils.Go(func() error {
1572 1572
 		if in != nil {
1573 1573
 			io.Copy(rwc, in)
1574
+			utils.Debugf("[hijack] End of stdin")
1574 1575
 		}
1575 1576
 		if tcpc, ok := rwc.(*net.TCPConn); ok {
1576 1577
 			if err := tcpc.CloseWrite(); err != nil {
... ...
@@ -1633,13 +1671,12 @@ func (cli *DockerCli) monitorTtySize(id string) error {
1633 1633
 	}
1634 1634
 	cli.resizeTty(id)
1635 1635
 
1636
-	c := make(chan os.Signal, 1)
1637
-	signal.Notify(c, syscall.SIGWINCH)
1636
+	sigchan := make(chan os.Signal, 1)
1637
+	signal.Notify(sigchan, syscall.SIGWINCH)
1638 1638
 	go func() {
1639
-		for sig := range c {
1640
-			if sig == syscall.SIGWINCH {
1641
-				cli.resizeTty(id)
1642
-			}
1639
+		for {
1640
+			<-sigchan
1641
+			cli.resizeTty(id)
1643 1642
 		}
1644 1643
 	}()
1645 1644
 	return nil
... ...
@@ -1672,11 +1709,11 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
1672 1672
 		err = out
1673 1673
 	}
1674 1674
 
1675
-	authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
1675
+	configFile, _ := auth.LoadConfig(os.Getenv("HOME"))
1676 1676
 	return &DockerCli{
1677 1677
 		proto:      proto,
1678 1678
 		addr:       addr,
1679
-		authConfig: authConfig,
1679
+		configFile: configFile,
1680 1680
 		in:         in,
1681 1681
 		out:        out,
1682 1682
 		err:        err,
... ...
@@ -1688,7 +1725,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc
1688 1688
 type DockerCli struct {
1689 1689
 	proto      string
1690 1690
 	addr       string
1691
-	authConfig *auth.AuthConfig
1691
+	configFile *auth.ConfigFile
1692 1692
 	in         io.ReadCloser
1693 1693
 	out        io.Writer
1694 1694
 	err        io.Writer
... ...
@@ -38,7 +38,7 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
38 38
 		f()
39 39
 		c <- false
40 40
 	}()
41
-	if <-c {
41
+	if <-c && msg != "" {
42 42
 		t.Fatal(msg)
43 43
 	}
44 44
 }
... ...
@@ -73,7 +73,7 @@ func TestRunHostname(t *testing.T) {
73 73
 			t.Fatal(err)
74 74
 		}
75 75
 	}()
76
-	utils.Debugf("--")
76
+
77 77
 	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
78 78
 		cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
79 79
 		if err != nil {
... ...
@@ -90,6 +90,157 @@ func TestRunHostname(t *testing.T) {
90 90
 
91 91
 }
92 92
 
93
+func TestRunExit(t *testing.T) {
94
+	stdin, stdinPipe := io.Pipe()
95
+	stdout, stdoutPipe := io.Pipe()
96
+
97
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
98
+	defer cleanup(globalRuntime)
99
+
100
+	c1 := make(chan struct{})
101
+	go func() {
102
+		cli.CmdRun("-i", unitTestImageID, "/bin/cat")
103
+		close(c1)
104
+	}()
105
+
106
+	setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
107
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
108
+			t.Fatal(err)
109
+		}
110
+	})
111
+
112
+	container := globalRuntime.List()[0]
113
+
114
+	// Closing /bin/cat stdin, expect it to exit
115
+	if err := stdin.Close(); err != nil {
116
+		t.Fatal(err)
117
+	}
118
+
119
+	// as the process exited, CmdRun must finish and unblock. Wait for it
120
+	setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
121
+		<-c1
122
+
123
+		go func() {
124
+			cli.CmdWait(container.ID)
125
+		}()
126
+
127
+		if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
128
+			t.Fatal(err)
129
+		}
130
+	})
131
+
132
+	// Make sure that the client has been disconnected
133
+	setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
134
+		// Expecting pipe i/o error, just check that read does not block
135
+		stdin.Read([]byte{})
136
+	})
137
+
138
+	// Cleanup pipes
139
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
140
+		t.Fatal(err)
141
+	}
142
+}
143
+
144
+// Expected behaviour: the process dies when the client disconnects
145
+func TestRunDisconnect(t *testing.T) {
146
+
147
+	stdin, stdinPipe := io.Pipe()
148
+	stdout, stdoutPipe := io.Pipe()
149
+
150
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
151
+	defer cleanup(globalRuntime)
152
+
153
+	c1 := make(chan struct{})
154
+	go func() {
155
+		// We're simulating a disconnect so the return value doesn't matter. What matters is the
156
+		// fact that CmdRun returns.
157
+		cli.CmdRun("-i", unitTestImageID, "/bin/cat")
158
+		close(c1)
159
+	}()
160
+
161
+	setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
162
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
163
+			t.Fatal(err)
164
+		}
165
+	})
166
+
167
+	// Close pipes (simulate disconnect)
168
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
169
+		t.Fatal(err)
170
+	}
171
+
172
+	// as the pipes are close, we expect the process to die,
173
+	// therefore CmdRun to unblock. Wait for CmdRun
174
+	setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
175
+		<-c1
176
+	})
177
+
178
+	// Client disconnect after run -i should cause stdin to be closed, which should
179
+	// cause /bin/cat to exit.
180
+	setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
181
+		container := globalRuntime.List()[0]
182
+		container.Wait()
183
+		if container.State.Running {
184
+			t.Fatalf("/bin/cat is still running after closing stdin")
185
+		}
186
+	})
187
+}
188
+
189
+// Expected behaviour: the process dies when the client disconnects
190
+func TestRunDisconnectTty(t *testing.T) {
191
+
192
+	stdin, stdinPipe := io.Pipe()
193
+	stdout, stdoutPipe := io.Pipe()
194
+
195
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
196
+	defer cleanup(globalRuntime)
197
+
198
+	c1 := make(chan struct{})
199
+	go func() {
200
+		// We're simulating a disconnect so the return value doesn't matter. What matters is the
201
+		// fact that CmdRun returns.
202
+		if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
203
+			utils.Debugf("Error CmdRun: %s\n", err)
204
+		}
205
+
206
+		close(c1)
207
+	}()
208
+
209
+	setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
210
+		for {
211
+			// Client disconnect after run -i should keep stdin out in TTY mode
212
+			l := globalRuntime.List()
213
+			if len(l) == 1 && l[0].State.Running {
214
+				break
215
+			}
216
+			time.Sleep(10 * time.Millisecond)
217
+		}
218
+	})
219
+
220
+	// Client disconnect after run -i should keep stdin out in TTY mode
221
+	container := globalRuntime.List()[0]
222
+
223
+	setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
224
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
225
+			t.Fatal(err)
226
+		}
227
+	})
228
+
229
+	// Close pipes (simulate disconnect)
230
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
231
+		t.Fatal(err)
232
+	}
233
+
234
+	// In tty mode, we expect the process to stay alive even after client's stdin closes.
235
+	// Do not wait for run to finish
236
+
237
+	// Give some time to monitor to do his thing
238
+	container.WaitTimeout(500 * time.Millisecond)
239
+	if !container.State.Running {
240
+		t.Fatalf("/bin/cat should  still be running after closing stdin (tty mode)")
241
+	}
242
+}
243
+
93 244
 // TestAttachStdin checks attaching to stdin without stdout and stderr.
94 245
 // 'docker run -i -a stdin' should sends the client's stdin to the command,
95 246
 // then detach from it and print the container id.
... ...
@@ -157,3 +308,73 @@ func TestRunAttachStdin(t *testing.T) {
157 157
 		}
158 158
 	}
159 159
 }
160
+
161
+// Expected behaviour, the process stays alive when the client disconnects
162
+func TestAttachDisconnect(t *testing.T) {
163
+	stdin, stdinPipe := io.Pipe()
164
+	stdout, stdoutPipe := io.Pipe()
165
+
166
+	cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
167
+	defer cleanup(globalRuntime)
168
+
169
+	go func() {
170
+		// Start a process in daemon mode
171
+		if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
172
+			utils.Debugf("Error CmdRun: %s\n", err)
173
+		}
174
+	}()
175
+
176
+	setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
177
+		if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
178
+			t.Fatal(err)
179
+		}
180
+	})
181
+
182
+	setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
183
+		for {
184
+			l := globalRuntime.List()
185
+			if len(l) == 1 && l[0].State.Running {
186
+				break
187
+			}
188
+			time.Sleep(10 * time.Millisecond)
189
+		}
190
+	})
191
+
192
+	container := globalRuntime.List()[0]
193
+
194
+	// Attach to it
195
+	c1 := make(chan struct{})
196
+	go func() {
197
+		// We're simulating a disconnect so the return value doesn't matter. What matters is the
198
+		// fact that CmdAttach returns.
199
+		cli.CmdAttach(container.ID)
200
+		close(c1)
201
+	}()
202
+
203
+	setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
204
+		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
205
+			t.Fatal(err)
206
+		}
207
+	})
208
+	// Close pipes (client disconnects)
209
+	if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
210
+		t.Fatal(err)
211
+	}
212
+
213
+	// Wait for attach to finish, the client disconnected, therefore, Attach finished his job
214
+	setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
215
+		<-c1
216
+	})
217
+
218
+	// We closed stdin, expect /bin/cat to still be running
219
+	// Wait a little bit to make sure container.monitor() did his thing
220
+	err := container.WaitTimeout(500 * time.Millisecond)
221
+	if err == nil || !container.State.Running {
222
+		t.Fatalf("/bin/cat is not running after closing stdin")
223
+	}
224
+
225
+	// Try to avoid the timeoout in destroy. Best effort, don't check error
226
+	cStdin, _ := container.StdinPipe()
227
+	cStdin.Close()
228
+	container.Wait()
229
+}
... ...
@@ -52,31 +52,32 @@ type Container struct {
52 52
 
53 53
 	waitLock chan struct{}
54 54
 	Volumes  map[string]string
55
-	// Store rw/ro in a separate structure to preserve reserve-compatibility on-disk.
55
+	// Store rw/ro in a separate structure to preserve reverse-compatibility on-disk.
56 56
 	// Easier than migrating older container configs :)
57 57
 	VolumesRW map[string]bool
58 58
 }
59 59
 
60 60
 type Config struct {
61
-	Hostname     string
62
-	User         string
63
-	Memory       int64 // Memory limit (in bytes)
64
-	MemorySwap   int64 // Total memory usage (memory + swap); set `-1' to disable swap
65
-	CpuShares    int64 // CPU shares (relative weight vs. other containers)
66
-	AttachStdin  bool
67
-	AttachStdout bool
68
-	AttachStderr bool
69
-	PortSpecs    []string
70
-	Tty          bool // Attach standard streams to a tty, including stdin if it is not closed.
71
-	OpenStdin    bool // Open stdin
72
-	StdinOnce    bool // If true, close stdin after the 1 attached client disconnects.
73
-	Env          []string
74
-	Cmd          []string
75
-	Dns          []string
76
-	Image        string // Name of the image as it was passed by the operator (eg. could be symbolic)
77
-	Volumes      map[string]struct{}
78
-	VolumesFrom  string
79
-	Entrypoint   []string
61
+	Hostname        string
62
+	User            string
63
+	Memory          int64 // Memory limit (in bytes)
64
+	MemorySwap      int64 // Total memory usage (memory + swap); set `-1' to disable swap
65
+	CpuShares       int64 // CPU shares (relative weight vs. other containers)
66
+	AttachStdin     bool
67
+	AttachStdout    bool
68
+	AttachStderr    bool
69
+	PortSpecs       []string
70
+	Tty             bool // Attach standard streams to a tty, including stdin if it is not closed.
71
+	OpenStdin       bool // Open stdin
72
+	StdinOnce       bool // If true, close stdin after the 1 attached client disconnects.
73
+	Env             []string
74
+	Cmd             []string
75
+	Dns             []string
76
+	Image           string // Name of the image as it was passed by the operator (eg. could be symbolic)
77
+	Volumes         map[string]struct{}
78
+	VolumesFrom     string
79
+	Entrypoint      []string
80
+	NetworkDisabled bool
80 81
 }
81 82
 
82 83
 type HostConfig struct {
... ...
@@ -99,13 +100,14 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
99 99
 
100 100
 	flHostname := cmd.String("h", "", "Container host name")
101 101
 	flUser := cmd.String("u", "", "Username or UID")
102
-	flDetach := cmd.Bool("d", false, "Detached mode: leave the container running in the background")
102
+	flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
103 103
 	flAttach := NewAttachOpts()
104 104
 	cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
105 105
 	flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
106 106
 	flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
107 107
 	flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
108 108
 	flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
109
+	flNetwork := cmd.Bool("n", true, "Enable networking for this container")
109 110
 
110 111
 	if capabilities != nil && *flMemory > 0 && !capabilities.MemoryLimit {
111 112
 		//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
... ...
@@ -174,23 +176,24 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig,
174 174
 	}
175 175
 
176 176
 	config := &Config{
177
-		Hostname:     *flHostname,
178
-		PortSpecs:    flPorts,
179
-		User:         *flUser,
180
-		Tty:          *flTty,
181
-		OpenStdin:    *flStdin,
182
-		Memory:       *flMemory,
183
-		CpuShares:    *flCpuShares,
184
-		AttachStdin:  flAttach.Get("stdin"),
185
-		AttachStdout: flAttach.Get("stdout"),
186
-		AttachStderr: flAttach.Get("stderr"),
187
-		Env:          flEnv,
188
-		Cmd:          runCmd,
189
-		Dns:          flDns,
190
-		Image:        image,
191
-		Volumes:      flVolumes,
192
-		VolumesFrom:  *flVolumesFrom,
193
-		Entrypoint:   entrypoint,
177
+		Hostname:        *flHostname,
178
+		PortSpecs:       flPorts,
179
+		User:            *flUser,
180
+		Tty:             *flTty,
181
+		NetworkDisabled: !*flNetwork,
182
+		OpenStdin:       *flStdin,
183
+		Memory:          *flMemory,
184
+		CpuShares:       *flCpuShares,
185
+		AttachStdin:     flAttach.Get("stdin"),
186
+		AttachStdout:    flAttach.Get("stdout"),
187
+		AttachStderr:    flAttach.Get("stderr"),
188
+		Env:             flEnv,
189
+		Cmd:             runCmd,
190
+		Dns:             flDns,
191
+		Image:           image,
192
+		Volumes:         flVolumes,
193
+		VolumesFrom:     *flVolumesFrom,
194
+		Entrypoint:      entrypoint,
194 195
 	}
195 196
 	hostConfig := &HostConfig{
196 197
 		Binds:           binds,
... ...
@@ -376,14 +379,15 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
376 376
 				utils.Debugf("[start] attach stdin\n")
377 377
 				defer utils.Debugf("[end] attach stdin\n")
378 378
 				// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
379
-				if cStdout != nil {
380
-					defer cStdout.Close()
381
-				}
382
-				if cStderr != nil {
383
-					defer cStderr.Close()
384
-				}
385 379
 				if container.Config.StdinOnce && !container.Config.Tty {
386 380
 					defer cStdin.Close()
381
+				} else {
382
+					if cStdout != nil {
383
+						defer cStdout.Close()
384
+					}
385
+					if cStderr != nil {
386
+						defer cStderr.Close()
387
+					}
387 388
 				}
388 389
 				if container.Config.Tty {
389 390
 					_, err = utils.CopyEscapable(cStdin, stdin)
... ...
@@ -511,8 +515,12 @@ func (container *Container) Start(hostConfig *HostConfig) error {
511 511
 	if err := container.EnsureMounted(); err != nil {
512 512
 		return err
513 513
 	}
514
-	if err := container.allocateNetwork(); err != nil {
515
-		return err
514
+	if container.runtime.networkManager.disabled {
515
+		container.Config.NetworkDisabled = true
516
+	} else {
517
+		if err := container.allocateNetwork(); err != nil {
518
+			return err
519
+		}
516 520
 	}
517 521
 
518 522
 	// Make sure the config is compatible with the current kernel
... ...
@@ -626,7 +634,9 @@ func (container *Container) Start(hostConfig *HostConfig) error {
626 626
 	}
627 627
 
628 628
 	// Networking
629
-	params = append(params, "-g", container.network.Gateway.String())
629
+	if !container.Config.NetworkDisabled {
630
+		params = append(params, "-g", container.network.Gateway.String())
631
+	}
630 632
 
631 633
 	// User
632 634
 	if container.Config.User != "" {
... ...
@@ -728,6 +738,10 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
728 728
 }
729 729
 
730 730
 func (container *Container) allocateNetwork() error {
731
+	if container.Config.NetworkDisabled {
732
+		return nil
733
+	}
734
+
731 735
 	iface, err := container.runtime.networkManager.Allocate()
732 736
 	if err != nil {
733 737
 		return err
... ...
@@ -754,6 +768,9 @@ func (container *Container) allocateNetwork() error {
754 754
 }
755 755
 
756 756
 func (container *Container) releaseNetwork() {
757
+	if container.Config.NetworkDisabled {
758
+		return
759
+	}
757 760
 	container.network.Release()
758 761
 	container.network = nil
759 762
 	container.NetworkSettings = &NetworkSettings{}
... ...
@@ -789,7 +806,9 @@ func (container *Container) monitor() {
789 789
 		}
790 790
 	}
791 791
 	utils.Debugf("Process finished")
792
-
792
+	if container.runtime != nil && container.runtime.srv != nil {
793
+		container.runtime.srv.LogEvent("die", container.ShortID())
794
+	}
793 795
 	exitCode := -1
794 796
 	if container.cmd != nil {
795 797
 		exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
... ...
@@ -959,6 +959,7 @@ func TestEnv(t *testing.T) {
959 959
 	goodEnv := []string{
960 960
 		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
961 961
 		"HOME=/",
962
+		"container=lxc",
962 963
 	}
963 964
 	sort.Strings(goodEnv)
964 965
 	if len(goodEnv) != len(actualEnv) {
... ...
@@ -1251,3 +1252,41 @@ func TestRestartWithVolumes(t *testing.T) {
1251 1251
 		t.Fatalf("Expected volume path: %s Actual path: %s", expected, actual)
1252 1252
 	}
1253 1253
 }
1254
+
1255
+func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) {
1256
+	runtime := mkRuntime(t)
1257
+	defer nuke(runtime)
1258
+
1259
+	config, hc, _, err := ParseRun([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil)
1260
+	if err != nil {
1261
+		t.Fatal(err)
1262
+	}
1263
+	c, err := NewBuilder(runtime).Create(config)
1264
+	if err != nil {
1265
+		t.Fatal(err)
1266
+	}
1267
+	stdout, err := c.StdoutPipe()
1268
+	if err != nil {
1269
+		t.Fatal(err)
1270
+	}
1271
+
1272
+	defer runtime.Destroy(c)
1273
+	if err := c.Start(hc); err != nil {
1274
+		t.Fatal(err)
1275
+	}
1276
+	c.WaitTimeout(500 * time.Millisecond)
1277
+	c.Wait()
1278
+	output, err := ioutil.ReadAll(stdout)
1279
+	if err != nil {
1280
+		t.Fatal(err)
1281
+	}
1282
+
1283
+	interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(string(output), -1)
1284
+	if len(interfaces) != 1 {
1285
+		t.Fatalf("Wrong interface count in test container: expected [1: lo], got [%s]", interfaces)
1286
+	}
1287
+	if interfaces[0] != "1: lo" {
1288
+		t.Fatalf("Wrong interface in test container: expected [1: lo], got [%s]", interfaces)
1289
+	}
1290
+
1291
+}
... ...
@@ -28,7 +28,7 @@ func main() {
28 28
 	flDaemon := flag.Bool("d", false, "Daemon mode")
29 29
 	flDebug := flag.Bool("D", false, "Debug mode")
30 30
 	flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
31
-	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
31
+	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
32 32
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
33 33
 	flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
34 34
 	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
... ...
@@ -56,6 +56,10 @@ What's new
56 56
 
57 57
    List the processes running inside a container.
58 58
 
59
+.. http:get:: /events:
60
+
61
+   **New!** Monitor docker's events via streaming or via polling
62
+
59 63
 Builder (/build):
60 64
 
61 65
 - Simplify the upload of the build context
... ...
@@ -989,7 +989,10 @@ Display system-wide information
989 989
 		"NFd": 11,
990 990
 		"NGoroutines":21,
991 991
 		"MemoryLimit":true,
992
-		"SwapLimit":false
992
+		"SwapLimit":false,
993
+		"EventsListeners":"0",
994
+		"LXCVersion":"0.7.5",
995
+		"KernelVersion":"3.8.0-19-generic"
993 996
 	   }
994 997
 
995 998
         :statuscode 200: no error
... ...
@@ -1059,6 +1062,36 @@ Create a new image from a container's changes
1059 1059
         :statuscode 500: server error
1060 1060
 
1061 1061
 
1062
+Monitor Docker's events
1063
+***********************
1064
+
1065
+.. http:get:: /events
1066
+
1067
+	Get events from docker, either in real time via streaming, or via polling (using `since`)
1068
+
1069
+	**Example request**:
1070
+
1071
+	.. sourcecode:: http
1072
+
1073
+           POST /events?since=1374067924
1074
+
1075
+        **Example response**:
1076
+
1077
+        .. sourcecode:: http
1078
+
1079
+           HTTP/1.1 200 OK
1080
+	   Content-Type: application/json
1081
+
1082
+	   {"status":"create","id":"dfdf82bd3881","time":1374067924}
1083
+	   {"status":"start","id":"dfdf82bd3881","time":1374067924}
1084
+	   {"status":"stop","id":"dfdf82bd3881","time":1374067966}
1085
+	   {"status":"destroy","id":"dfdf82bd3881","time":1374067970}
1086
+
1087
+	:query since: timestamp used for polling
1088
+        :statuscode 200: no error
1089
+        :statuscode 500: server error
1090
+
1091
+
1062 1092
 3. Going further
1063 1093
 ================
1064 1094
 
... ...
@@ -12,8 +12,9 @@
12 12
 
13 13
     Create a new filesystem image from the contents of a tarball
14 14
 
15
-At this time, the URL must start with ``http`` and point to a single file archive (.tar, .tar.gz, .bzip) 
16
-containing a root filesystem. If you would like to import from a local directory or archive, 
15
+At this time, the URL must start with ``http`` and point to a single file archive
16
+(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz)
17
+containing a root filesystem. If you would like to import from a local directory or archive,
17 18
 you can use the ``-`` parameter to take the data from standard in.
18 19
 
19 20
 Examples
... ...
@@ -30,7 +31,7 @@ Import from a local file
30 30
 Import to docker via pipe and standard in
31 31
 
32 32
 ``$ cat exampleimage.tgz | docker import - exampleimagelocal``
33
-  
33
+
34 34
 Import from a local directory
35 35
 .............................
36 36
 
... ...
@@ -15,11 +15,12 @@
15 15
       -a=map[]: Attach to stdin, stdout or stderr.
16 16
       -c=0: CPU shares (relative weight)
17 17
       -cidfile="": Write the container ID to the file
18
-      -d=false: Detached mode: leave the container running in the background
18
+      -d=false: Detached mode: Run container in the background, print new container id
19 19
       -e=[]: Set environment variables
20 20
       -h="": Container host name
21 21
       -i=false: Keep stdin open even if not attached
22 22
       -m=0: Memory limit (in bytes)
23
+      -n=true: Enable networking for this container
23 24
       -p=[]: Map a network port to the container
24 25
       -t=false: Allocate a pseudo-tty
25 26
       -u="": Username or UID
... ...
@@ -4,10 +4,6 @@
4 4
 
5 5
 .. _dockermanifesto:
6 6
 
7
-*(This was our original Welcome page, but it is a bit forward-looking
8
-for docs, and maybe not enough vision for a true manifesto. We'll
9
-reveal more vision in the future to make it more Manifesto-y.)*
10
-
11 7
 Docker Manifesto
12 8
 ----------------
13 9
 
... ...
@@ -131,60 +127,3 @@ sitting 10 miles away.
131 131
 
132 132
 With Standard Containers we can put an end to that embarrassment, by
133 133
 making INDUSTRIAL-GRADE DELIVERY of software a reality.
134
-
135
-Standard Container Specification
136
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
137
-
138
-(TODO)
139
-
140
-Image format
141
-~~~~~~~~~~~~
142
-
143
-Standard operations
144
-~~~~~~~~~~~~~~~~~~~
145
-
146
--  Copy
147
--  Run
148
--  Stop
149
--  Wait
150
--  Commit
151
--  Attach standard streams
152
--  List filesystem changes
153
--  ...
154
-
155
-Execution environment
156
-~~~~~~~~~~~~~~~~~~~~~
157
-
158
-Root filesystem
159
-^^^^^^^^^^^^^^^
160
-
161
-Environment variables
162
-^^^^^^^^^^^^^^^^^^^^^
163
-
164
-Process arguments
165
-^^^^^^^^^^^^^^^^^
166
-
167
-Networking
168
-^^^^^^^^^^
169
-
170
-Process namespacing
171
-^^^^^^^^^^^^^^^^^^^
172
-
173
-Resource limits
174
-^^^^^^^^^^^^^^^
175
-
176
-Process monitoring
177
-^^^^^^^^^^^^^^^^^^
178
-
179
-Logging
180
-^^^^^^^
181
-
182
-Signals
183
-^^^^^^^
184
-
185
-Pseudo-terminal allocation
186
-^^^^^^^^^^^^^^^^^^^^^^^^^^
187
-
188
-Security
189
-^^^^^^^^
190
-
... ...
@@ -39,7 +39,7 @@ This time, we're requesting shared access to $COUCH1's volumes.
39 39
 
40 40
 .. code-block:: bash
41 41
 
42
-    COUCH2=$(docker run -d -volumes-from $COUCH1) shykes/couchdb:2013-05-03)
42
+    COUCH2=$(docker run -d -volumes-from $COUCH1 shykes/couchdb:2013-05-03)
43 43
 
44 44
 Browse data on the second database
45 45
 ----------------------------------
... ...
@@ -48,6 +48,6 @@ Browse data on the second database
48 48
 
49 49
     HOST=localhost
50 50
     URL="http://$HOST:$(docker port $COUCH2 5984)/_utils/"
51
-    echo "Navigate to $URL in your browser. You should see the same data as in the first database!"
51
+    echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!'
52 52
 
53 53
 Congratulations, you are running 2 Couchdb containers, completely isolated from each other *except* for their data.
... ...
@@ -51,7 +51,7 @@ For example:
51 51
 .. code-block:: bash
52 52
 
53 53
    # Run docker in daemon mode
54
-   sudo <path to>/docker -H 0.0.0.0:5555 &
54
+   sudo <path to>/docker -H 0.0.0.0:5555 -d &
55 55
    # Download a base image
56 56
    docker -H :5555 pull base
57 57
 
... ...
@@ -61,7 +61,7 @@ on both tcp and a unix socket
61 61
 .. code-block:: bash
62 62
 
63 63
    # Run docker in daemon mode
64
-   sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
64
+   sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock -d &
65 65
    # Download a base image
66 66
    docker pull base
67 67
    # OR
... ...
@@ -187,7 +187,7 @@ The copy obeys the following rules:
187 187
 3.8 ENTRYPOINT
188 188
 --------------
189 189
 
190
-    ``ENTRYPOINT /bin/echo``
190
+    ``ENTRYPOINT ["/bin/echo"]``
191 191
 
192 192
 The ``ENTRYPOINT`` instruction adds an entry command that will not be
193 193
 overwritten when arguments are passed to docker run, unlike the
... ...
@@ -1 +1 @@
1
-Thatcher Penskens <thatcher@dotcloud.com>
1
+Thatcher Peskens <thatcher@dotcloud.com>
... ...
@@ -68,19 +68,18 @@
68 68
 
69 69
             <div style="float: right" class="pull-right">
70 70
                 <ul class="nav">
71
-                    <li id="nav-introduction"><a href="http://www.docker.io/">Introduction</a></li>
71
+                    <li id="nav-introduction"><a href="http://www.docker.io/" title="Docker Homepage">Home</a></li>
72
+                    <li id="nav-about"><a href="http://www.docker.io/about/" title="About">About</a></li>
73
+                    <li id="nav-community"><a href="http://www.docker.io/community/" title="Community">Community</a></li>
72 74
                     <li id="nav-gettingstarted"><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
73 75
                     <li id="nav-documentation" class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
74
-                    <li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
76
+                    <li id="nav-blog"><a href="http://blog.docker.io/" title="Docker Blog">Blog</a></li>
77
+                    <li id="nav-index"><a href="http://index.docker.io/" title="Docker Image Index, find images here">INDEX <img class="inline-icon" src="{{ pathto('_static/img/external-link-icon.png', 1) }}" title="external link"> </a></li>
75 78
                 </ul>
76
-                <!--<div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">-->
77
-                    <!--<a class="twitter" href="http://twitter.com/getdocker">Twitter</a>-->
78
-                    <!--<a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>-->
79
-                <!--</div>-->
80 79
             </div>
81 80
 
82 81
             <div style="margin-left: -12px; float: left;">
83
-                <a href="http://www.docker.io"><img style="margin-top: 12px; height: 38px" src="{{ pathto('_static/img/docker-letters-logo.gif', 1) }}"></a>
82
+                <a href="http://www.docker.io" title="Docker Homepage"><img style="margin-top: 0px; height: 60px; margin-left: 10px;" src="{{ pathto('_static/img/docker-top-logo.png', 1) }}"></a>
84 83
             </div>
85 84
         </div>
86 85
 
... ...
@@ -96,7 +95,7 @@
96 96
             <div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
97 97
                 <a  href="http://github.com/dotcloud/docker/"><img src="{{ pathto('_static/img/fork-us.png', 1) }}"> Fork us on Github</a>
98 98
             </div>
99
-            <h1 class="pageheader">DOCUMENTATION</h1>
99
+            <h1 class="pageheader"><a href="http://docs.docker.io/en/latest/" title="Documentation" style="color: white;">DOCUMENTATION</a></h1>
100 100
 
101 101
         </div>
102 102
     </div>
... ...
@@ -34,12 +34,12 @@ h4 {
34 34
 .navbar .nav li a {
35 35
   padding: 22px 15px 22px;
36 36
 }
37
-.navbar .brand {
38
-  padding: 13px 10px 13px 28px ;
39
-}
40 37
 .navbar-dotcloud .container {
41 38
   border-bottom: 2px #000000 solid;
42 39
 }
40
+.inline-icon {
41
+  margin-bottom: 6px;
42
+}
43 43
 /*
44 44
 * Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
45 45
 * http://www.jonsuh.com
... ...
@@ -82,7 +82,7 @@ h4 {
82 82
 .btn-custom {
83 83
   background-color: #292929 !important;
84 84
   background-repeat: repeat-x;
85
-  filter: progid:dximagetransform.microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
85
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#515151", endColorstr="#282828");
86 86
   background-image: -khtml-gradient(linear, left top, left bottom, from(#515151), to(#282828));
87 87
   background-image: -moz-linear-gradient(top, #515151, #282828);
88 88
   background-image: -ms-linear-gradient(top, #515151, #282828);
... ...
@@ -301,7 +301,7 @@ section.header {
301 301
   height: 28px;
302 302
   line-height: 28px;
303 303
   background-color: #43484c;
304
-  filter: progid:dximagetransform.microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
304
+  filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#FFFF6E56', endColorstr='#FFED4F35');
305 305
   background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #747474), color-stop(100%, #43484c));
306 306
   background-image: -webkit-linear-gradient(top, #747474 0%, #43484c 100%);
307 307
   background-image: -moz-linear-gradient(top, #747474 0%, #43484c 100%);
... ...
@@ -53,13 +53,6 @@ h1, h2, h3, h4 {
53 53
       padding: 22px 15px 22px;
54 54
     }
55 55
   }
56
-
57
-  .brand {
58
-    padding: 13px 10px 13px 28px ;
59
-  // padding-left: 30px;
60
-
61
-  }
62
-
63 56
   background-color: white;
64 57
 }
65 58
 
... ...
@@ -67,6 +60,9 @@ h1, h2, h3, h4 {
67 67
   border-bottom: 2px @black solid;
68 68
 }
69 69
 
70
+.inline-icon {
71
+  margin-bottom: 6px;
72
+}
70 73
 
71 74
 /*
72 75
 * Responsive YouTube, Vimeo, Embed, and HTML5 Videos with CSS
73 76
new file mode 100644
74 77
Binary files /dev/null and b/docs/theme/docker/static/img/docker-top-logo.png differ
75 78
new file mode 100644
76 79
Binary files /dev/null and b/docs/theme/docker/static/img/external-link-icon.png differ
77 80
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-Thatcher Penskens <thatcher@dotcloud.com>
2 1
deleted file mode 100644
... ...
@@ -1,2 +0,0 @@
1
-www:
2
-  type: static
3 1
\ No newline at end of file
4 2
deleted file mode 100644
... ...
@@ -1,220 +0,0 @@
1
-<!DOCTYPE html>
2
-<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3
-<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4
-<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
5
-<!--[if gt IE 8]><!-->
6
-<html class="no-js" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
7
-<head>
8
-    <meta charset="utf-8">
9
-    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
10
-    <title>Docker - the Linux container runtime</title>
11
-
12
-    <meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
13
-    <meta name="viewport" content="width=device-width">
14
-
15
-    <!-- twitter bootstrap -->
16
-    <link rel="stylesheet" href="../static/css/bootstrap.min.css">
17
-    <link rel="stylesheet" href="../static/css/bootstrap-responsive.min.css">
18
-
19
-    <!-- main style file -->
20
-    <link rel="stylesheet" href="../static/css/main.css">
21
-
22
-    <!-- vendor scripts -->
23
-    <script src="../static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
24
-    <script src="../static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
25
-
26
-</head>
27
-
28
-
29
-<body>
30
-
31
-<div class="navbar navbar-fixed-top">
32
-    <div class="navbar-dotcloud">
33
-        <div class="container" style="text-align: center;">
34
-
35
-            <div style="float: right" class="pull-right">
36
-                <ul class="nav">
37
-                    <li id="nav-introduction"><a href="../">Introduction</a></li>
38
-                    <li id="nav-gettingstarted" class="active"><a href="">Getting started</a></li>
39
-                    <li id="nav-documentation" class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
40
-                    <li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
41
-                </ul>
42
-            </div>
43
-
44
-            <div style="margin-left: -12px; float: left;">
45
-                <a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../static/img/docker-letters-logo.gif"></a>
46
-            </div>
47
-        </div>
48
-    </div>
49
-</div>
50
-
51
-
52
-<div class="container">
53
-    <div class="row">
54
-
55
-        <div class="span12 titlebar">
56
-
57
-            <div class="pull-right" id="fork-us" style="margin-top: 16px; margin-right: 16px;">
58
-                <a  href="http://github.com/dotcloud/docker/"><img src="../static/img/fork-us.png"> Fork us on Github</a>
59
-            </div>
60
-
61
-            <h1 class="pageheader"> GETTING STARTED</h1>
62
-        </div>
63
-
64
-    </div>
65
-
66
-</div>
67
-
68
-<div class="container">
69
-    <div class="alert alert-info" style="margin-bottom: 0;">
70
-        <strong>Docker is still under heavy development.</strong> It should not yet be used in production. Check <a href="http://github.com/dotcloud/docker">the repo</a> for recent progress.
71
-    </div>
72
-    <div class="row">
73
-        <div class="span6">
74
-            <section class="contentblock">
75
-                <h2>
76
-                    <a name="installing-on-ubuntu-1204-and-1210" class="anchor" href="#installing-on-ubuntu-1204-and-1210"><span class="mini-icon mini-icon-link"></span>
77
-                    </a>Installing on Ubuntu</h2>
78
-
79
-                    <p><strong>Requirements</strong></p>
80
-                    <ul>
81
-                        <li>Ubuntu 12.04 (LTS) (64-bit)</li>
82
-                        <li> or Ubuntu 12.10 (quantal) (64-bit)</li>
83
-                        <li>The 3.8 Linux Kernel</li>
84
-                    </ul>
85
-                <ol>
86
-                    <li>
87
-                    <p><strong>Install dependencies</strong></p>
88
-                    The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
89
-                    <pre>sudo apt-get install linux-image-extra-`uname -r`</pre>
90
-
91
-
92
-                    </li>
93
-                    <li>
94
-                        <p><strong>Install Docker</strong></p>
95
-                        <p>Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.</p>
96
-                        <p>This may import a new GPG key (key 63561DC6: public key "Launchpad PPA for dotcloud team" imported).</p>
97
-                        <div class="highlight">
98
-                            <pre>sudo apt-get install software-properties-common</pre>
99
-                            <pre>sudo add-apt-repository ppa:dotcloud/lxc-docker</pre>
100
-                            <pre>sudo apt-get update</pre>
101
-                            <pre>sudo apt-get install lxc-docker</pre>
102
-                        </div>
103
-
104
-
105
-                    </li>
106
-
107
-                    <li>
108
-                        <p><strong>Run!</strong></p>
109
-
110
-                        <div class="highlight">
111
-                            <pre>docker run -i -t ubuntu /bin/bash</pre>
112
-                        </div>
113
-                    </li>
114
-                    Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.<br>
115
-                    Or check <a href="http://docs.docker.io/en/latest/installation/ubuntulinux/">more detailed installation instructions</a>
116
-                </ol>
117
-            </section>
118
-
119
-            <section class="contentblock">
120
-                <h2>Contributing to Docker</h2>
121
-
122
-                <p>Want to hack on Docker? Awesome! We have some <a href="http://docs.docker.io/en/latest/contributing/contributing/">instructions to get you started</a>. They are probably not perfect, please let us know if anything feels wrong or incomplete.</p>
123
-            </section>
124
-
125
-        </div>
126
-        <div class="span6">
127
-            <section class="contentblock">
128
-                <h2>Quick install on other operating systems</h2>
129
-                <p><strong>For other operating systems we recommend and provide a streamlined install with virtualbox,
130
-                    vagrant and an Ubuntu virtual machine.</strong></p>
131
-
132
-                <ul>
133
-                    <li><a href="http://docs.docker.io/en/latest/installation/vagrant/">Mac OS X and other linuxes</a></li>
134
-                    <li><a href="http://docs.docker.io/en/latest/installation/windows/">Windows</a></li>
135
-                </ul>
136
-
137
-            </section>
138
-
139
-            <section class="contentblock">
140
-                <h2>Questions? Want to get in touch?</h2>
141
-                <p>There are several ways to get in touch:</p>
142
-                <p><strong>Join the discussion on IRC.</strong> We can be found in the <a href="irc://chat.freenode.net#docker">#docker</a> channel on chat.freenode.net</p>
143
-                <p><strong>Discussions</strong> happen on our google group: <a href="https://groups.google.com/d/forum/docker-club">docker-club at googlegroups.com</a></p>
144
-                <p>All our <strong>development and decisions</strong> are made out in the open on Github <a href="http://www.github.com/dotcloud/docker">github.com/dotcloud/docker</a></p>
145
-                <p><strong>Get help on using Docker</strong> by asking on <a href="http://stackoverflow.com/tags/docker/">Stackoverflow</a></p>
146
-                <p>And of course, <strong>tweet</strong> your tweets to <a href="http://twitter.com/getdocker/">twitter.com/getdocker</a></p>
147
-            </section>
148
-
149
-
150
-            <section class="contentblock">
151
-                <div id="wufoo-z7x3p3">
152
-                    Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
153
-                </div>
154
-                <script type="text/javascript">var z7x3p3;(function(d, t) {
155
-                    var s = d.createElement(t), options = {
156
-                        'userName':'dotclouddocker',
157
-                        'formHash':'z7x3p3',
158
-                        'autoResize':true,
159
-                        'height':'577',
160
-                        'async':true,
161
-                        'header':'show'};
162
-                    s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
163
-                    s.onload = s.onreadystatechange = function() {
164
-                        var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
165
-                        try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
166
-                    var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
167
-                })(document, 'script');</script>
168
-            </section>
169
-
170
-        </div>
171
-    </div>
172
-</div>
173
-
174
-
175
-<div class="container">
176
-    <footer id="footer" class="footer">
177
-        <div class="row">
178
-            <div class="span12 social">
179
-                <div class="tbox textright forceleftmargin social links pull-right">
180
-                    <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
181
-                    <a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
182
-                </div>
183
-                Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
184
-
185
-            </div>
186
-        </div>
187
-
188
-        <div class="row">
189
-            <div class="emptyspace" style="height: 40px">
190
-
191
-            </div>
192
-        </div>
193
-
194
-    </footer>
195
-</div>
196
-
197
-
198
-<!-- bootstrap javascipts -->
199
-<script src="../static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
200
-
201
-<!-- Google analytics -->
202
-<script type="text/javascript">
203
-
204
-    var _gaq = _gaq || [];
205
-    _gaq.push(['_setAccount', 'UA-6096819-11']);
206
-    _gaq.push(['_setDomainName', 'docker.io']);
207
-    _gaq.push(['_setAllowLinker', true]);
208
-    _gaq.push(['_trackPageview']);
209
-
210
-    (function() {
211
-        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
212
-        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
213
-        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
214
-    })();
215
-
216
-</script>
217
-
218
-
219
-</body>
220
-</html>
221 1
deleted file mode 100644
... ...
@@ -1,359 +0,0 @@
1
-<!DOCTYPE html>
2
-<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3
-<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4
-<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
5
-<!--[if gt IE 8]><!-->
6
-<html class="no-js" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
7
-<head>
8
-    <meta charset="utf-8">
9
-    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
10
-    <meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
11
-    <title>Docker - the Linux container engine</title>
12
-
13
-    <meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
14
-    <meta name="viewport" content="width=device-width">
15
-
16
-    <!-- twitter bootstrap -->
17
-    <link rel="stylesheet" href="static/css/bootstrap.min.css">
18
-    <link rel="stylesheet" href="static/css/bootstrap-responsive.min.css">
19
-
20
-    <!-- main style file -->
21
-    <link rel="stylesheet" href="static/css/main.css">
22
-
23
-    <!-- vendor scripts -->
24
-    <script src="static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
25
-    <script src="static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
26
-
27
-    <style>
28
-        .indexlabel {
29
-            float: left;
30
-            width: 150px;
31
-            display: block;
32
-            padding: 10px 20px 10px;
33
-            font-size: 20px;
34
-            font-weight: 200;
35
-            background-color: #a30000;
36
-            color: white;
37
-            height: 22px;
38
-        }
39
-        .searchbutton {
40
-            font-size: 20px;
41
-            height: 40px;
42
-        }
43
-
44
-        .debug {
45
-            border: 1px red dotted;
46
-        }
47
-        .twitterblock {
48
-            min-height: 75px;
49
-        }
50
-
51
-        .twitterblock img {
52
-            float: left;
53
-            margin-right: 10px;
54
-        }
55
-
56
-    </style>
57
-
58
-
59
-</head>
60
-
61
-
62
-<body>
63
-
64
-<div class="navbar navbar-fixed-top">
65
-    <div class="navbar-dotcloud">
66
-        <div class="container" style="text-align: center;">
67
-
68
-
69
-            <div class="pull-left" id="fork-us" style="margin-top: 16px;">
70
-                <a  href="http://github.com/dotcloud/docker/"><img src="static/img/fork-us.png" alt="fork-icon"> Fork us on Github</a>
71
-            </div>
72
-
73
-            <div class="pull-right" >
74
-                <ul class="nav">
75
-                    <li id="nav-introduction" class="active"><a href="/">Introduction</a></li>
76
-                    <li id="nav-gettingstarted"><a href="gettingstarted">Getting started</a></li>
77
-                    <li id="nav-documentation" class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
78
-                    <li id="nav-blog"><a href="http://blog.docker.io/">Blog</a></li>
79
-                </ul>
80
-            </div>
81
-        </div>
82
-    </div>
83
-</div>
84
-
85
-
86
-<div class="container" style="margin-top: 30px;">
87
-    <div class="row">
88
-
89
-        <div class="span12">
90
-            <section class="contentblock header">
91
-
92
-                <div class="span5" style="margin-bottom: 15px;">
93
-                    <div style="text-align: center;" >
94
-                        <img src="static/img/docker_letters_500px.png" alt="docker letters">
95
-
96
-                        <h2>The Linux container engine</h2>
97
-                    </div>
98
-
99
-                    <div style="display: block; text-align: center; margin-top: 20px;">
100
-
101
-                        <h5>
102
-                            Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider.
103
-                        </h5>
104
-
105
-                    </div>
106
-
107
-
108
-                    <div style="display: block; text-align: center; margin-top: 30px;">
109
-                        <a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
110
-                    </div>
111
-
112
-                </div>
113
-
114
-                <div class="span6" >
115
-                    <div class="js-video" >
116
-                        <iframe width="600" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
117
-                    </div>
118
-                </div>
119
-
120
-                <br style="clear: both"/>
121
-            </section>
122
-        </div>
123
-    </div>
124
-</div>
125
-
126
-<div class="container">
127
-    <div class="row">
128
-
129
-        <div class="span6">
130
-            <section class="contentblock">
131
-                <h4>Heterogeneous payloads</h4>
132
-                <p>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.</p>
133
-                <h4>Any server</h4>
134
-                <p>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.</p>
135
-                <h4>Isolation</h4>
136
-                <p>Docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
137
-                <h4>Repeatability</h4>
138
-                <p>Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
139
-            </section>
140
-            <section class="contentblock">
141
-                <div class="container">
142
-                <div class="span2" style="margin-left: 0" >
143
-                    <a href="http://dotcloud.theresumator.com/apply/mWjkD4/Software-Engineer.html" title="Job description"><img src="static/img/hiring_graphic.png" alt="we're hiring" width="140" style="margin-top: 25px"></a>
144
-                </div>
145
-                <div class="span4" style="margin-left: 0">
146
-                    <h4>Do you think it is cool to hack on docker? Join us!</h4>
147
-                    <ul>
148
-                        <li>Work on open source</li>
149
-                        <li>Program in Go</li>
150
-                    </ul>
151
-                    <a href="http://dotcloud.theresumator.com/apply/mWjkD4/Software-Engineer.html" title="Job description">read more</a>
152
-                </div>
153
-                </div>
154
-
155
-            </section>
156
-        </div>
157
-        <div class="span6">
158
-            <section class="contentblock">
159
-                <h1>New! Docker Index</h1>
160
-                On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them.
161
-
162
-                <br><br>
163
-                <a href="https://index.docker.io" target="_blank">
164
-                    <div class="indexlabel">
165
-                        DOCKER index
166
-                    </div>
167
-                </a>
168
-                &nbsp;
169
-                <input type="button" class="searchbutton" value="Search images"
170
-                       onClick="window.open('https://index.docker.io')" />
171
-
172
-            </section>
173
-            <section class="contentblock">
174
-                <div id="wufoo-z7x3p3">
175
-                    Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
176
-                </div>
177
-                <script type="text/javascript">var z7x3p3;(function(d, t) {
178
-                    var s = d.createElement(t), options = {
179
-                        'userName':'dotclouddocker',
180
-                        'formHash':'z7x3p3',
181
-                        'autoResize':true,
182
-                        'height':'577',
183
-                        'async':true,
184
-                        'header':'show'};
185
-                    s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
186
-                    s.onload = s.onreadystatechange = function() {
187
-                        var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
188
-                        try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
189
-                    var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
190
-                })(document, 'script');</script>
191
-            </section>
192
-        </div>
193
-    </div>
194
-
195
-</div>
196
-
197
-<div class="container">
198
-
199
-    <div class="row">
200
-        <div class="span6">
201
-            <section class="contentblock twitterblock">
202
-                <img src="https://si0.twimg.com/profile_images/2707460527/252a64411a339184ff375a96fb68dcb0_bigger.png">
203
-                <em>Mitchell Hashimoto ‏@mitchellh:</em> Docker launched today. It is incredible. They’re also working RIGHT NOW on a Vagrant provider. LXC is COMING!!
204
-            </section>
205
-        </div>
206
-        <div class="span6">
207
-            <section class="contentblock twitterblock">
208
-                <img src="https://si0.twimg.com/profile_images/1108290260/Adam_Jacob-114x150_original_bigger.jpg">
209
-                <em>Adam Jacob ‏@adamhjk:</em> Docker is clearly the right idea. @solomonstre absolutely killed it. Containerized app deployment is the future, I think.
210
-            </section>
211
-        </div>
212
-    </div>
213
-    <div class="row">
214
-        <div class="span6">
215
-            <section class="contentblock twitterblock">
216
-                <img src="https://si0.twimg.com/profile_images/14872832/twitter_pic_bigger.jpg">
217
-                <em>Matt Townsend ‏@mtownsend:</em> I have a serious code crush on docker.io - it's Lego for PaaS. Motherfucking awesome Lego.
218
-            </section>
219
-        </div>
220
-        <div class="span6">
221
-            <section class="contentblock twitterblock">
222
-                <img src="https://si0.twimg.com/profile_images/1312352395/rupert-259x300_bigger.jpg">
223
-                <em>Rob Harrop ‏@robertharrop:</em> Impressed by @getdocker - it's all kinds of magic. Serious rethink of AWS architecture happening @skillsmatter.
224
-            </section>
225
-        </div>
226
-    </div>
227
-    <div class="row">
228
-        <div class="span6">
229
-            <section class="contentblock twitterblock">
230
-                <img src="https://twimg0-a.akamaihd.net/profile_images/2491994496/rbevyyq6ykp6bnoby2je_bigger.jpeg">
231
-                <em>John Willis @botchagalupe:</em> IMHO docker is to paas what chef was to Iaas 4 years ago
232
-            </section>
233
-        </div>
234
-        <div class="span6">
235
-            <section class="contentblock twitterblock">
236
-                <img src="https://twimg0-a.akamaihd.net/profile_images/3348427561/9d7f08f1e103a16c8debd169301b9944_bigger.jpeg">
237
-                <em>John Feminella ‏@superninjarobot:</em> So, @getdocker is pure excellence. If you've ever wished for arbitrary, PaaS-agnostic, lxc/aufs Linux containers, this is your jam!
238
-            </section>
239
-        </div>
240
-    </div>
241
-    <div class="row">
242
-        <div class="span6">
243
-            <section class="contentblock twitterblock">
244
-                <img src="https://si0.twimg.com/profile_images/3408403010/4496ccdd14e9b7285eca04c31a740207_bigger.jpeg">
245
-                <em>David Romulan ‏@destructuring:</em> I haven't had this much fun since AWS
246
-            </section>
247
-        </div>
248
-        <div class="span6">
249
-            <section class="contentblock twitterblock">
250
-                <img src="https://si0.twimg.com/profile_images/780893320/My_Avatar_bigger.jpg">
251
-                <em>Ricardo Gladwell ‏@rgladwell:</em> wow @getdocker is either amazing or totally stupid
252
-            </section>
253
-        </div>
254
-
255
-    </div>
256
-</div>
257
-
258
-<div class="container">
259
-    <div class="row">
260
-        <div class="span6">
261
-
262
-            <section class="contentblock">
263
-
264
-                <h2>Notable features</h2>
265
-
266
-                <ul>
267
-                    <li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
268
-                    <li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
269
-                    <li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
270
-                    <li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.</li>
271
-                    <li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
272
-                    <li>Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.</li>
273
-                    <li>Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.</li>
274
-                </ul>
275
-
276
-                <h2>Under the hood</h2>
277
-
278
-                <p>Under the hood, Docker is built on the following components:</p>
279
-
280
-                <ul>
281
-                    <li>The <a href="http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c">cgroup</a> and <a href="http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part">namespacing</a> capabilities of the Linux kernel;</li>
282
-                    <li><a href="http://aufs.sourceforge.net/aufs.html">AUFS</a>, a powerful union filesystem with copy-on-write capabilities;</li>
283
-                    <li>The <a href="http://golang.org">Go</a> programming language;</li>
284
-                    <li><a href="http://lxc.sourceforge.net/">lxc</a>, a set of convenience scripts to simplify the creation of linux containers.</li>
285
-                </ul>
286
-
287
-                <h2>Who started it</h2>
288
-                <p>
289
-                    Docker is an open-source implementation of the deployment engine which powers <a href="http://dotcloud.com">dotCloud</a>, a popular Platform-as-a-Service.</p>
290
-
291
-                <p>It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
292
-                    of applications and databases.
293
-                </p>
294
-
295
-            </section>
296
-        </div>
297
-
298
-        <div class="span6">
299
-
300
-
301
-            <section class="contentblock">
302
-                <h3 id="twitter">Twitter</h3>
303
-                <a class="twitter-timeline" href="https://twitter.com/getdocker" data-widget-id="312730839718957056">Tweets by @getdocker</a>
304
-                <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
305
-            </section>
306
-
307
-        </div>
308
-    </div>
309
-
310
-</div> <!-- end container -->
311
-
312
-
313
-<div class="container">
314
-    <footer id="footer" class="footer">
315
-        <div class="row">
316
-            <div class="span12">
317
-                <div class="tbox textright forceleftmargin social links pull-right">
318
-                    <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
319
-                    <a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
320
-                </div>
321
-                Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
322
-
323
-            </div>
324
-        </div>
325
-
326
-        <div class="row">
327
-            <div class="emptyspace" style="height: 40px">
328
-
329
-            </div>
330
-        </div>
331
-
332
-    </footer>
333
-</div>
334
-
335
-
336
-
337
-<!-- bootstrap javascipts -->
338
-<script src="static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
339
-
340
-<!-- Google analytics -->
341
-<script type="text/javascript">
342
-
343
-    var _gaq = _gaq || [];
344
-    _gaq.push(['_setAccount', 'UA-6096819-11']);
345
-    _gaq.push(['_setDomainName', 'docker.io']);
346
-    _gaq.push(['_setAllowLinker', true]);
347
-    _gaq.push(['_trackPageview']);
348
-
349
-    (function() {
350
-        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
351
-        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
352
-        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
353
-    })();
354
-
355
-</script>
356
-
357
-
358
-</body>
359
-</html>
360 1
deleted file mode 100644
... ...
@@ -1,6 +0,0 @@
1
-
2
-# rule to redirect original links created when hosted on github pages
3
-rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent;
4
-
5
-# rewrite the stuff which was on the current page
6
-rewrite ^/gettingstarted.html$ /gettingstarted/ permanent;
7 1
deleted file mode 120000
... ...
@@ -1 +0,0 @@
1
-../theme/docker/static
2 1
\ No newline at end of file
3 2
new file mode 100644
... ...
@@ -0,0 +1,91 @@
0
+# Docker maintainer bootcamp
1
+
2
+## Introduction: we need more maintainers
3
+
4
+Docker is growing incredibly fast. At the time of writing, it has received over 200 contributions from 90 people,
5
+and its API is used by dozens of 3rd-party tools. Over 1,000 issues have been opened. As the first production deployments
6
+start going live, the growth will only accelerate.
7
+
8
+Also at the time of writing, Docker has 3 full-time maintainers, and 7 part-time subsystem maintainers. If docker
9
+is going to live up to the expectations, we need more than that.
10
+
11
+This document describes a *bootcamp* to guide and train volunteers interested in helping the project, either with individual
12
+contributions, maintainer work, or both.
13
+
14
+This bootcamp is an experiment. If you decide to go through it, consider yourself an alpha-tester. You should expect quirks,
15
+and report them to us as you encounter them to help us smooth out the process.
16
+
17
+
18
+## How it works
19
+
20
+The maintainer bootcamp is a 12-step program - one step for each of the maintainer's responsibilities. The aspiring maintainer must
21
+validate all 12 steps by 1) studying it, 2) practicing it, and 3) getting endorsed for it.
22
+
23
+Steps are all equally important and can be validated in any order. Validating all 12 steps is a pre-requisite for becoming a core
24
+maintainer, but even 1 step will make you a better contributor!
25
+
26
+### List of steps
27
+
28
+#### 1) Be a power user
29
+
30
+Use docker daily, build cool things with it, know its quirks inside and out.
31
+
32
+
33
+#### 2) Help users
34
+
35
+Answer questions on irc, twitter, email, in person.
36
+
37
+
38
+#### 3) Manage the bug tracker
39
+
40
+Help triage tickets - ask the right questions, find duplicates, reference relevant resources, know when to close a ticket when necessary, take the time to go over older tickets.
41
+
42
+
43
+#### 4) Improve the documentation
44
+
45
+Follow the documentation from scratch regularly and make sure it is still up-to-date. Find and fix inconsistencies. Remove stale information. Find a frequently asked question that is not documented. Simplify the content and the form.
46
+
47
+
48
+#### 5) Evangelize the principles of docker
49
+
50
+Understand what the underlying goals and principle of docker are. Explain design decisions based on what docker is, and what it is not. When someone is not using docker, find how docker can be valuable to them. If they are using docker, find how they can use it better.
51
+
52
+
53
+#### 6) Fix bugs
54
+
55
+Self-explanatory. Contribute improvements to docker which solve defects. Bugfixes should be well-tested, and prioritized by impact to the user.
56
+
57
+
58
+#### 7) Improve the testing infrastructure
59
+
60
+Automated testing is complicated and should be perpetually improved. Invest time to improve the current tooling. Refactor existing tests, create new ones, make testing more accessible to developers, add new testing capabilities (integration tests, mocking, stress test...), improve integration between tests and documentation...
61
+
62
+
63
+#### 8) Contribute features
64
+
65
+Improve docker to do more things, or get better at doing the same things. Features should be well-tested, not break existing APIs, respect the project goals. They should make the user's life measurably better. Features should be discussed ahead of time to avoid wasting time and duplicating effort.
66
+
67
+
68
+#### 9) Refactor internals
69
+
70
+Improve docker to repay technical debt. Simplify code layout, improve performance, add missing comments, reduce the number of files and functions, rename functions and variables to be more readable, go over FIXMEs, etc.
71
+
72
+#### 10) Review and merge contributions
73
+
74
+Review pull requests in a timely manner, review code in detail and offer feedback. Keep a high bar without being pedantic. Share the load of testing and merging pull requests.
75
+
76
+#### 11) Release
77
+
78
+Manage a release of docker from beginning to end. Tests, final review, tags, builds, upload to mirrors, distro packaging, etc.
79
+
80
+#### 12) Train other maintainers
81
+
82
+Contribute to training other maintainers. Give advice, delegate work, help organize the bootcamp. This also means contribute to the maintainer's manual, look for ways to improve the project organization etc.
83
+
84
+### How to study a step
85
+
86
+### How to practice a step
87
+
88
+### How to get endorsed for a step
89
+
90
+
... ...
@@ -68,7 +68,7 @@ func StoreImage(img *Image, layerData Archive, root string, store bool) error {
68 68
 	}
69 69
 	// Store the layer
70 70
 	layer := layerPath(root)
71
-	if err := os.MkdirAll(layer, 0700); err != nil {
71
+	if err := os.MkdirAll(layer, 0755); err != nil {
72 72
 		return err
73 73
 	}
74 74
 
... ...
@@ -13,6 +13,10 @@ lxc.utsname = {{.Id}}
13 13
 {{end}}
14 14
 #lxc.aa_profile = unconfined
15 15
 
16
+{{if .Config.NetworkDisabled}}
17
+# network is disabled (-n=false)
18
+lxc.network.type = empty
19
+{{else}}
16 20
 # network configuration
17 21
 lxc.network.type = veth
18 22
 lxc.network.flags = up
... ...
@@ -20,6 +24,7 @@ lxc.network.link = {{.NetworkSettings.Bridge}}
20 20
 lxc.network.name = eth0
21 21
 lxc.network.mtu = 1500
22 22
 lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen}}
23
+{{end}}
23 24
 
24 25
 # root filesystem
25 26
 {{$ROOTFS := .RootfsPath}}
... ...
@@ -17,6 +17,7 @@ var NetworkBridgeIface string
17 17
 
18 18
 const (
19 19
 	DefaultNetworkBridge = "docker0"
20
+	DisableNetworkBridge = "none"
20 21
 	portRangeStart       = 49153
21 22
 	portRangeEnd         = 65535
22 23
 )
... ...
@@ -111,10 +112,29 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
111 111
 	return nil
112 112
 }
113 113
 
114
+// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`,
115
+// and attempts to configure it with an address which doesn't conflict with any other interface on the host.
116
+// If it can't find an address which doesn't conflict, it will return an error.
114 117
 func CreateBridgeIface(ifaceName string) error {
115
-	// FIXME: try more IP ranges
116
-	// FIXME: try bigger ranges! /24 is too small.
117
-	addrs := []string{"172.16.42.1/24", "10.0.42.1/24", "192.168.42.1/24"}
118
+	addrs := []string{
119
+		// Here we don't follow the convention of using the 1st IP of the range for the gateway.
120
+		// This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges.
121
+		// In theory this shouldn't matter - in practice there's bound to be a few scripts relying
122
+		// on the internal addressing or other stupid things like that.
123
+		// The shouldn't, but hey, let's not break them unless we really have to.
124
+		"172.16.42.1/16",
125
+		"10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive
126
+		"10.1.42.1/16",
127
+		"10.42.42.1/16",
128
+		"172.16.42.1/24",
129
+		"172.16.43.1/24",
130
+		"172.16.44.1/24",
131
+		"10.0.42.1/24",
132
+		"10.0.43.1/24",
133
+		"192.168.42.1/24",
134
+		"192.168.43.1/24",
135
+		"192.168.44.1/24",
136
+	}
118 137
 
119 138
 	var ifaceAddr string
120 139
 	for _, addr := range addrs {
... ...
@@ -453,10 +473,16 @@ type NetworkInterface struct {
453 453
 
454 454
 	manager  *NetworkManager
455 455
 	extPorts []*Nat
456
+	disabled bool
456 457
 }
457 458
 
458 459
 // Allocate an external TCP port and map it to the interface
459 460
 func (iface *NetworkInterface) AllocatePort(spec string) (*Nat, error) {
461
+
462
+	if iface.disabled {
463
+		return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME
464
+	}
465
+
460 466
 	nat, err := parseNat(spec)
461 467
 	if err != nil {
462 468
 		return nil, err
... ...
@@ -552,6 +578,11 @@ func parseNat(spec string) (*Nat, error) {
552 552
 
553 553
 // Release: Network cleanup - release all resources
554 554
 func (iface *NetworkInterface) Release() {
555
+
556
+	if iface.disabled {
557
+		return
558
+	}
559
+
555 560
 	for _, nat := range iface.extPorts {
556 561
 		utils.Debugf("Unmaping %v/%v", nat.Proto, nat.Frontend)
557 562
 		if err := iface.manager.portMapper.Unmap(nat.Frontend, nat.Proto); err != nil {
... ...
@@ -579,10 +610,17 @@ type NetworkManager struct {
579 579
 	tcpPortAllocator *PortAllocator
580 580
 	udpPortAllocator *PortAllocator
581 581
 	portMapper       *PortMapper
582
+
583
+	disabled bool
582 584
 }
583 585
 
584 586
 // Allocate a network interface
585 587
 func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
588
+
589
+	if manager.disabled {
590
+		return &NetworkInterface{disabled: true}, nil
591
+	}
592
+
586 593
 	ip, err := manager.ipAllocator.Acquire()
587 594
 	if err != nil {
588 595
 		return nil, err
... ...
@@ -596,6 +634,14 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
596 596
 }
597 597
 
598 598
 func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
599
+
600
+	if bridgeIface == DisableNetworkBridge {
601
+		manager := &NetworkManager{
602
+			disabled: true,
603
+		}
604
+		return manager, nil
605
+	}
606
+
599 607
 	addr, err := getIfaceAddr(bridgeIface)
600 608
 	if err != nil {
601 609
 		// If the iface is not found, try to create it
... ...
@@ -923,6 +923,12 @@ List images
923 923
 Usage: docker import [OPTIONS] URL|\- [REPOSITORY [TAG]]
924 924
 .sp
925 925
 Create a new filesystem image from the contents of a tarball
926
+
927
+At this time, the URL must start with ``http`` and point to a single file archive
928
+(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz)
929
+containing a root filesystem. If you would like to import from a local directory or archive,
930
+you can use the ``-`` parameter to take the data from standard in.
931
+
926 932
 .SS info
927 933
 .sp
928 934
 .nf
... ...
@@ -1,9 +1,8 @@
1 1
 description     "Run docker"
2 2
 
3
-start on runlevel [2345]
4
-stop on starting rc RUNLEVEL=[016]
3
+start on filesystem or runlevel [2345]
4
+stop on runlevel [!2345]
5
+
5 6
 respawn
6 7
 
7
-script
8
-    /usr/bin/docker -d
9
-end script
8
+exec /usr/bin/docker -d
... ...
@@ -98,11 +98,39 @@ func ResolveRepositoryName(reposName string) (string, string, error) {
98 98
 	return endpoint, reposName, err
99 99
 }
100 100
 
101
+// VersionInfo is used to model entities which has a version.
102
+// It is basically a tupple with name and version.
103
+type VersionInfo interface {
104
+	Name() string
105
+	Version() string
106
+}
107
+
101 108
 func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
102 109
 	for _, cookie := range c.Jar.Cookies(req.URL) {
103 110
 		req.AddCookie(cookie)
104 111
 	}
105
-	return c.Do(req)
112
+	res, err := c.Do(req)
113
+	if err != nil {
114
+		return nil, err
115
+	}
116
+	if len(res.Cookies()) > 0 {
117
+		c.Jar.SetCookies(req.URL, res.Cookies())
118
+	}
119
+	return res, err
120
+}
121
+
122
+// Set the user agent field in the header based on the versions provided
123
+// in NewRegistry() and extra.
124
+func (r *Registry) setUserAgent(req *http.Request, extra ...VersionInfo) {
125
+	if len(r.baseVersions)+len(extra) == 0 {
126
+		return
127
+	}
128
+	if len(extra) == 0 {
129
+		req.Header.Set("User-Agent", r.baseVersionsStr)
130
+	} else {
131
+		req.Header.Set("User-Agent", appendVersions(r.baseVersionsStr, extra...))
132
+	}
133
+	return
106 134
 }
107 135
 
108 136
 // Retrieve the history of a given image from the Registry.
... ...
@@ -113,7 +141,8 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s
113 113
 		return nil, err
114 114
 	}
115 115
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
116
-	res, err := r.client.Do(req)
116
+	r.setUserAgent(req)
117
+	res, err := doWithCookies(r.client, req)
117 118
 	if err != nil || res.StatusCode != 200 {
118 119
 		if res != nil {
119 120
 			return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID)
... ...
@@ -159,7 +188,8 @@ func (r *Registry) GetRemoteImageJSON(imgID, registry string, token []string) ([
159 159
 		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
160 160
 	}
161 161
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
162
-	res, err := r.client.Do(req)
162
+	r.setUserAgent(req)
163
+	res, err := doWithCookies(r.client, req)
163 164
 	if err != nil {
164 165
 		return nil, -1, fmt.Errorf("Failed to download json: %s", err)
165 166
 	}
... ...
@@ -186,7 +216,8 @@ func (r *Registry) GetRemoteImageLayer(imgID, registry string, token []string) (
186 186
 		return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
187 187
 	}
188 188
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
189
-	res, err := r.client.Do(req)
189
+	r.setUserAgent(req)
190
+	res, err := doWithCookies(r.client, req)
190 191
 	if err != nil {
191 192
 		return nil, err
192 193
 	}
... ...
@@ -206,7 +237,8 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [
206 206
 			return nil, err
207 207
 		}
208 208
 		req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
209
-		res, err := r.client.Do(req)
209
+		r.setUserAgent(req)
210
+		res, err := doWithCookies(r.client, req)
210 211
 		if err != nil {
211 212
 			return nil, err
212 213
 		}
... ...
@@ -244,6 +276,7 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e
244 244
 		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
245 245
 	}
246 246
 	req.Header.Set("X-Docker-Token", "true")
247
+	r.setUserAgent(req)
247 248
 
248 249
 	res, err := r.client.Do(req)
249 250
 	if err != nil {
... ...
@@ -300,13 +333,14 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e
300 300
 // Push a local image to the registry
301 301
 func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
302 302
 	// FIXME: try json with UTF8
303
-	req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", strings.NewReader(string(jsonRaw)))
303
+	req, err := http.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw))
304 304
 	if err != nil {
305 305
 		return err
306 306
 	}
307 307
 	req.Header.Add("Content-type", "application/json")
308 308
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
309 309
 	req.Header.Set("X-Docker-Checksum", imgData.Checksum)
310
+	r.setUserAgent(req)
310 311
 
311 312
 	utils.Debugf("Setting checksum for %s: %s", imgData.ID, imgData.Checksum)
312 313
 	res, err := doWithCookies(r.client, req)
... ...
@@ -314,9 +348,6 @@ func (r *Registry) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regis
314 314
 		return fmt.Errorf("Failed to upload metadata: %s", err)
315 315
 	}
316 316
 	defer res.Body.Close()
317
-	if len(res.Cookies()) > 0 {
318
-		r.client.Jar.SetCookies(req.URL, res.Cookies())
319
-	}
320 317
 	if res.StatusCode != 200 {
321 318
 		errBody, err := ioutil.ReadAll(res.Body)
322 319
 		if err != nil {
... ...
@@ -341,6 +372,7 @@ func (r *Registry) PushImageLayerRegistry(imgID string, layer io.Reader, registr
341 341
 	req.ContentLength = -1
342 342
 	req.TransferEncoding = []string{"chunked"}
343 343
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
344
+	r.setUserAgent(req)
344 345
 	res, err := doWithCookies(r.client, req)
345 346
 	if err != nil {
346 347
 		return fmt.Errorf("Failed to upload layer: %s", err)
... ...
@@ -378,6 +410,7 @@ func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token
378 378
 	}
379 379
 	req.Header.Add("Content-type", "application/json")
380 380
 	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
381
+	r.setUserAgent(req)
381 382
 	req.ContentLength = int64(len(revision))
382 383
 	res, err := doWithCookies(r.client, req)
383 384
 	if err != nil {
... ...
@@ -410,6 +443,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData
410 410
 	req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
411 411
 	req.ContentLength = int64(len(imgListJSON))
412 412
 	req.Header.Set("X-Docker-Token", "true")
413
+	r.setUserAgent(req)
413 414
 	if validate {
414 415
 		req.Header["X-Docker-Endpoints"] = regs
415 416
 	}
... ...
@@ -430,6 +464,7 @@ func (r *Registry) PushImageJSONIndex(indexEp, remote string, imgList []*ImgData
430 430
 		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
431 431
 		req.ContentLength = int64(len(imgListJSON))
432 432
 		req.Header.Set("X-Docker-Token", "true")
433
+		r.setUserAgent(req)
433 434
 		if validate {
434 435
 			req.Header["X-Docker-Endpoints"] = regs
435 436
 		}
... ...
@@ -536,11 +571,52 @@ type ImgData struct {
536 536
 }
537 537
 
538 538
 type Registry struct {
539
-	client     *http.Client
540
-	authConfig *auth.AuthConfig
539
+	client          *http.Client
540
+	authConfig      *auth.AuthConfig
541
+	baseVersions    []VersionInfo
542
+	baseVersionsStr string
543
+}
544
+
545
+func validVersion(version VersionInfo) bool {
546
+	stopChars := " \t\r\n/"
547
+	if strings.ContainsAny(version.Name(), stopChars) {
548
+		return false
549
+	}
550
+	if strings.ContainsAny(version.Version(), stopChars) {
551
+		return false
552
+	}
553
+	return true
541 554
 }
542 555
 
543
-func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err error) {
556
+// Convert versions to a string and append the string to the string base.
557
+//
558
+// Each VersionInfo will be converted to a string in the format of
559
+// "product/version", where the "product" is get from the Name() method, while
560
+// version is get from the Version() method. Several pieces of verson information
561
+// will be concatinated and separated by space.
562
+func appendVersions(base string, versions ...VersionInfo) string {
563
+	if len(versions) == 0 {
564
+		return base
565
+	}
566
+
567
+	var buf bytes.Buffer
568
+	if len(base) > 0 {
569
+		buf.Write([]byte(base))
570
+	}
571
+
572
+	for _, v := range versions {
573
+		if !validVersion(v) {
574
+			continue
575
+		}
576
+		buf.Write([]byte(v.Name()))
577
+		buf.Write([]byte("/"))
578
+		buf.Write([]byte(v.Version()))
579
+		buf.Write([]byte(" "))
580
+	}
581
+	return buf.String()
582
+}
583
+
584
+func NewRegistry(root string, authConfig *auth.AuthConfig, baseVersions ...VersionInfo) (r *Registry, err error) {
544 585
 	httpTransport := &http.Transport{
545 586
 		DisableKeepAlives: true,
546 587
 		Proxy:             http.ProxyFromEnvironment,
... ...
@@ -553,5 +629,10 @@ func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err err
553 553
 		},
554 554
 	}
555 555
 	r.client.Jar, err = cookiejar.New(nil)
556
-	return r, err
556
+	if err != nil {
557
+		return nil, err
558
+	}
559
+	r.baseVersions = baseVersions
560
+	r.baseVersionsStr = appendVersions("", baseVersions...)
561
+	return r, nil
557 562
 }
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"log"
9 9
 	"net"
10 10
 	"os"
11
+	"runtime"
11 12
 	"strconv"
12 13
 	"strings"
13 14
 	"sync"
... ...
@@ -17,15 +18,19 @@ import (
17 17
 )
18 18
 
19 19
 const (
20
-	unitTestImageName	= "docker-test-image"
21
-	unitTestImageID		= "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
22
-	unitTestNetworkBridge	= "testdockbr0"
23
-	unitTestStoreBase	= "/var/lib/docker/unit-tests"
24
-	testDaemonAddr		= "127.0.0.1:4270"
25
-	testDaemonProto		= "tcp"
20
+	unitTestImageName     = "docker-test-image"
21
+	unitTestImageID       = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
22
+	unitTestNetworkBridge = "testdockbr0"
23
+	unitTestStoreBase     = "/var/lib/docker/unit-tests"
24
+	testDaemonAddr        = "127.0.0.1:4270"
25
+	testDaemonProto       = "tcp"
26 26
 )
27 27
 
28
-var globalRuntime *Runtime
28
+var (
29
+	globalRuntime   *Runtime
30
+	startFds        int
31
+	startGoroutines int
32
+)
29 33
 
30 34
 func nuke(runtime *Runtime) error {
31 35
 	var wg sync.WaitGroup
... ...
@@ -80,21 +85,21 @@ func init() {
80 80
 	NetworkBridgeIface = unitTestNetworkBridge
81 81
 
82 82
 	// Make it our Store root
83
-	runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false)
84
-	if err != nil {
83
+	if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false); err != nil {
85 84
 		panic(err)
85
+	} else {
86
+		globalRuntime = runtime
86 87
 	}
87
-	globalRuntime = runtime
88 88
 
89 89
 	// Create the "Server"
90 90
 	srv := &Server{
91
-		runtime:     runtime,
91
+		runtime:     globalRuntime,
92 92
 		enableCors:  false,
93 93
 		pullingPool: make(map[string]struct{}),
94 94
 		pushingPool: make(map[string]struct{}),
95 95
 	}
96 96
 	// If the unit test is not found, try to download it.
97
-	if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID {
97
+	if img, err := globalRuntime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID {
98 98
 		// Retrieve the Image
99 99
 		if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {
100 100
 			panic(err)
... ...
@@ -109,6 +114,8 @@ func init() {
109 109
 
110 110
 	// Give some time to ListenAndServer to actually start
111 111
 	time.Sleep(time.Second)
112
+
113
+	startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine()
112 114
 }
113 115
 
114 116
 // FIXME: test that ImagePull(json=true) send correct json output
... ...
@@ -19,6 +19,7 @@ import (
19 19
 	"runtime"
20 20
 	"strings"
21 21
 	"sync"
22
+	"time"
22 23
 )
23 24
 
24 25
 func (srv *Server) DockerVersion() APIVersion {
... ...
@@ -29,11 +30,53 @@ func (srv *Server) DockerVersion() APIVersion {
29 29
 	}
30 30
 }
31 31
 
32
+// simpleVersionInfo is a simple implementation of
33
+// the interface VersionInfo, which is used
34
+// to provide version information for some product,
35
+// component, etc. It stores the product name and the version
36
+// in string and returns them on calls to Name() and Version().
37
+type simpleVersionInfo struct {
38
+	name    string
39
+	version string
40
+}
41
+
42
+func (v *simpleVersionInfo) Name() string {
43
+	return v.name
44
+}
45
+
46
+func (v *simpleVersionInfo) Version() string {
47
+	return v.version
48
+}
49
+
50
+// versionCheckers() returns version informations of:
51
+// docker, go, git-commit (of the docker) and the host's kernel.
52
+//
53
+// Such information will be used on call to NewRegistry().
54
+func (srv *Server) versionInfos() []registry.VersionInfo {
55
+	v := srv.DockerVersion()
56
+	ret := make([]registry.VersionInfo, 0, 4)
57
+	ret = append(ret, &simpleVersionInfo{"docker", v.Version})
58
+
59
+	if len(v.GoVersion) > 0 {
60
+		ret = append(ret, &simpleVersionInfo{"go", v.GoVersion})
61
+	}
62
+	if len(v.GitCommit) > 0 {
63
+		ret = append(ret, &simpleVersionInfo{"git-commit", v.GitCommit})
64
+	}
65
+	kernelVersion, err := utils.GetKernelVersion()
66
+	if err == nil {
67
+		ret = append(ret, &simpleVersionInfo{"kernel", kernelVersion.String()})
68
+	}
69
+
70
+	return ret
71
+}
72
+
32 73
 func (srv *Server) ContainerKill(name string) error {
33 74
 	if container := srv.runtime.Get(name); container != nil {
34 75
 		if err := container.Kill(); err != nil {
35
-			return fmt.Errorf("Error restarting container %s: %s", name, err)
76
+			return fmt.Errorf("Error killing container %s: %s", name, err)
36 77
 		}
78
+		srv.LogEvent("kill", name)
37 79
 	} else {
38 80
 		return fmt.Errorf("No such container: %s", name)
39 81
 	}
... ...
@@ -52,13 +95,14 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
52 52
 		if _, err := io.Copy(out, data); err != nil {
53 53
 			return err
54 54
 		}
55
+		srv.LogEvent("export", name)
55 56
 		return nil
56 57
 	}
57 58
 	return fmt.Errorf("No such container: %s", name)
58 59
 }
59 60
 
60 61
 func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
61
-	r, err := registry.NewRegistry(srv.runtime.root, nil)
62
+	r, err := registry.NewRegistry(srv.runtime.root, nil, srv.versionInfos()...)
62 63
 	if err != nil {
63 64
 		return nil, err
64 65
 	}
... ...
@@ -208,14 +252,29 @@ func (srv *Server) DockerInfo() *APIInfo {
208 208
 	} else {
209 209
 		imgcount = len(images)
210 210
 	}
211
+	lxcVersion := ""
212
+	if output, err := exec.Command("lxc-version").CombinedOutput(); err == nil {
213
+		outputStr := string(output)
214
+		if len(strings.SplitN(outputStr, ":", 2)) == 2 {
215
+			lxcVersion = strings.TrimSpace(strings.SplitN(string(output), ":", 2)[1])
216
+		}
217
+	}
218
+	kernelVersion := "<unknown>"
219
+	if kv, err := utils.GetKernelVersion(); err == nil {
220
+		kernelVersion = kv.String()
221
+	}
222
+
211 223
 	return &APIInfo{
212
-		Containers:  len(srv.runtime.List()),
213
-		Images:      imgcount,
214
-		MemoryLimit: srv.runtime.capabilities.MemoryLimit,
215
-		SwapLimit:   srv.runtime.capabilities.SwapLimit,
216
-		Debug:       os.Getenv("DEBUG") != "",
217
-		NFd:         utils.GetTotalUsedFds(),
218
-		NGoroutines: runtime.NumGoroutine(),
224
+		Containers:      len(srv.runtime.List()),
225
+		Images:          imgcount,
226
+		MemoryLimit:     srv.runtime.capabilities.MemoryLimit,
227
+		SwapLimit:       srv.runtime.capabilities.SwapLimit,
228
+		Debug:           os.Getenv("DEBUG") != "",
229
+		NFd:             utils.GetTotalUsedFds(),
230
+		NGoroutines:     runtime.NumGoroutine(),
231
+		LXCVersion:      lxcVersion,
232
+		NEventsListener: len(srv.events),
233
+		KernelVersion:   kernelVersion,
219 234
 	}
220 235
 }
221 236
 
... ...
@@ -506,7 +565,7 @@ func (srv *Server) poolRemove(kind, key string) error {
506 506
 }
507 507
 
508 508
 func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
509
-	r, err := registry.NewRegistry(srv.runtime.root, authConfig)
509
+	r, err := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionInfos()...)
510 510
 	if err != nil {
511 511
 		return err
512 512
 	}
... ...
@@ -723,7 +782,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo
723 723
 
724 724
 	out = utils.NewWriteFlusher(out)
725 725
 	img, err := srv.runtime.graph.Get(localName)
726
-	r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
726
+	r, err2 := registry.NewRegistry(srv.runtime.root, authConfig, srv.versionInfos()...)
727 727
 	if err2 != nil {
728 728
 		return err2
729 729
 	}
... ...
@@ -809,6 +868,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
809 809
 		}
810 810
 		return "", err
811 811
 	}
812
+	srv.LogEvent("create", container.ShortID())
812 813
 	return container.ShortID(), nil
813 814
 }
814 815
 
... ...
@@ -817,6 +877,7 @@ func (srv *Server) ContainerRestart(name string, t int) error {
817 817
 		if err := container.Restart(t); err != nil {
818 818
 			return fmt.Errorf("Error restarting container %s: %s", name, err)
819 819
 		}
820
+		srv.LogEvent("restart", name)
820 821
 	} else {
821 822
 		return fmt.Errorf("No such container: %s", name)
822 823
 	}
... ...
@@ -836,6 +897,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
836 836
 		if err := srv.runtime.Destroy(container); err != nil {
837 837
 			return fmt.Errorf("Error destroying container %s: %s", name, err)
838 838
 		}
839
+		srv.LogEvent("destroy", name)
839 840
 
840 841
 		if removeVolume {
841 842
 			// Retrieve all volumes from all remaining containers
... ...
@@ -902,6 +964,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
902 902
 			return err
903 903
 		}
904 904
 		*imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)})
905
+		srv.LogEvent("delete", utils.TruncateID(id))
905 906
 		return nil
906 907
 	}
907 908
 	return nil
... ...
@@ -931,6 +994,9 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro
931 931
 			parsedRepo := strings.Split(repoAndTag, ":")[0]
932 932
 			if strings.Contains(img.ID, repoName) {
933 933
 				repoName = parsedRepo
934
+				if len(srv.runtime.repositories.ByID()[img.ID]) == 1 && len(strings.Split(repoAndTag, ":")) > 1 {
935
+					tag = strings.Split(repoAndTag, ":")[1]
936
+				}
934 937
 			} else if repoName != parsedRepo {
935 938
 				// the id belongs to multiple repos, like base:latest and user:test,
936 939
 				// in that case return conflict
... ...
@@ -945,6 +1011,7 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro
945 945
 	}
946 946
 	if tagDeleted {
947 947
 		imgs = append(imgs, APIRmi{Untagged: img.ShortID()})
948
+		srv.LogEvent("untag", img.ShortID())
948 949
 	}
949 950
 	if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
950 951
 		if err := srv.deleteImageAndChildren(img.ID, &imgs); err != nil {
... ...
@@ -1017,6 +1084,7 @@ func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
1017 1017
 		if err := container.Start(hostConfig); err != nil {
1018 1018
 			return fmt.Errorf("Error starting container %s: %s", name, err)
1019 1019
 		}
1020
+		srv.LogEvent("start", name)
1020 1021
 	} else {
1021 1022
 		return fmt.Errorf("No such container: %s", name)
1022 1023
 	}
... ...
@@ -1028,6 +1096,7 @@ func (srv *Server) ContainerStop(name string, t int) error {
1028 1028
 		if err := container.Stop(t); err != nil {
1029 1029
 			return fmt.Errorf("Error stopping container %s: %s", name, err)
1030 1030
 		}
1031
+		srv.LogEvent("stop", name)
1031 1032
 	} else {
1032 1033
 		return fmt.Errorf("No such container: %s", name)
1033 1034
 	}
... ...
@@ -1161,15 +1230,31 @@ func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (
1161 1161
 		enableCors:  enableCors,
1162 1162
 		pullingPool: make(map[string]struct{}),
1163 1163
 		pushingPool: make(map[string]struct{}),
1164
+		events:      make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events
1165
+		listeners:   make(map[string]chan utils.JSONMessage),
1164 1166
 	}
1165 1167
 	runtime.srv = srv
1166 1168
 	return srv, nil
1167 1169
 }
1168 1170
 
1171
+func (srv *Server) LogEvent(action, id string) {
1172
+	now := time.Now().Unix()
1173
+	jm := utils.JSONMessage{Status: action, ID: id, Time: now}
1174
+	srv.events = append(srv.events, jm)
1175
+	for _, c := range srv.listeners {
1176
+		select { // non blocking channel
1177
+		case c <- jm:
1178
+		default:
1179
+		}
1180
+	}
1181
+}
1182
+
1169 1183
 type Server struct {
1170 1184
 	sync.Mutex
1171 1185
 	runtime     *Runtime
1172 1186
 	enableCors  bool
1173 1187
 	pullingPool map[string]struct{}
1174 1188
 	pushingPool map[string]struct{}
1189
+	events      []utils.JSONMessage
1190
+	listeners   map[string]chan utils.JSONMessage
1175 1191
 }
... ...
@@ -1,7 +1,10 @@
1 1
 package docker
2 2
 
3 3
 import (
4
+	"github.com/dotcloud/docker/utils"
5
+	"strings"
4 6
 	"testing"
7
+	"time"
5 8
 )
6 9
 
7 10
 func TestContainerTagImageDelete(t *testing.T) {
... ...
@@ -163,3 +166,126 @@ func TestRunWithTooLowMemoryLimit(t *testing.T) {
163 163
 	}
164 164
 
165 165
 }
166
+
167
+func TestLogEvent(t *testing.T) {
168
+	runtime := mkRuntime(t)
169
+	srv := &Server{
170
+		runtime:   runtime,
171
+		events:    make([]utils.JSONMessage, 0, 64),
172
+		listeners: make(map[string]chan utils.JSONMessage),
173
+	}
174
+
175
+	srv.LogEvent("fakeaction", "fakeid")
176
+
177
+	listener := make(chan utils.JSONMessage)
178
+	srv.Lock()
179
+	srv.listeners["test"] = listener
180
+	srv.Unlock()
181
+
182
+	srv.LogEvent("fakeaction2", "fakeid")
183
+
184
+	if len(srv.events) != 2 {
185
+		t.Fatalf("Expected 2 events, found %d", len(srv.events))
186
+	}
187
+	go func() {
188
+		time.Sleep(200 * time.Millisecond)
189
+		srv.LogEvent("fakeaction3", "fakeid")
190
+		time.Sleep(200 * time.Millisecond)
191
+		srv.LogEvent("fakeaction4", "fakeid")
192
+	}()
193
+
194
+	setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
195
+		for i := 2; i < 4; i++ {
196
+			event := <-listener
197
+			if event != srv.events[i] {
198
+				t.Fatalf("Event received it different than expected")
199
+			}
200
+		}
201
+	})
202
+
203
+}
204
+
205
+func TestRmi(t *testing.T) {
206
+	runtime := mkRuntime(t)
207
+	defer nuke(runtime)
208
+	srv := &Server{runtime: runtime}
209
+
210
+	initialImages, err := srv.Images(false, "")
211
+	if err != nil {
212
+		t.Fatal(err)
213
+	}
214
+
215
+	config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
216
+	if err != nil {
217
+		t.Fatal(err)
218
+	}
219
+
220
+	containerID, err := srv.ContainerCreate(config)
221
+	if err != nil {
222
+		t.Fatal(err)
223
+	}
224
+
225
+	//To remove
226
+	err = srv.ContainerStart(containerID, hostConfig)
227
+	if err != nil {
228
+		t.Fatal(err)
229
+	}
230
+
231
+	imageID, err := srv.ContainerCommit(containerID, "test", "", "", "", nil)
232
+	if err != nil {
233
+		t.Fatal(err)
234
+	}
235
+
236
+	err = srv.ContainerTag(imageID, "test", "0.1", false)
237
+	if err != nil {
238
+		t.Fatal(err)
239
+	}
240
+
241
+	containerID, err = srv.ContainerCreate(config)
242
+	if err != nil {
243
+		t.Fatal(err)
244
+	}
245
+
246
+	//To remove
247
+	err = srv.ContainerStart(containerID, hostConfig)
248
+	if err != nil {
249
+		t.Fatal(err)
250
+	}
251
+
252
+	_, err = srv.ContainerCommit(containerID, "test", "", "", "", nil)
253
+	if err != nil {
254
+		t.Fatal(err)
255
+	}
256
+
257
+	images, err := srv.Images(false, "")
258
+	if err != nil {
259
+		t.Fatal(err)
260
+	}
261
+
262
+	if len(images)-len(initialImages) != 2 {
263
+		t.Fatalf("Expected 2 new images, found %d.", len(images)-len(initialImages))
264
+	}
265
+
266
+	_, err = srv.ImageDelete(imageID, true)
267
+	if err != nil {
268
+		t.Fatal(err)
269
+	}
270
+
271
+	images, err = srv.Images(false, "")
272
+	if err != nil {
273
+		t.Fatal(err)
274
+	}
275
+
276
+	if len(images)-len(initialImages) != 1 {
277
+		t.Fatalf("Expected 1 new image, found %d.", len(images)-len(initialImages))
278
+	}
279
+
280
+	for _, image := range images {
281
+		if strings.Contains(unitTestImageID, image.ID) {
282
+			continue
283
+		}
284
+		if image.Repository == "" {
285
+			t.Fatalf("Expected tagged image, got untagged one.")
286
+		}
287
+	}
288
+}
... ...
@@ -78,7 +78,7 @@ func MergeConfig(userConf, imageConf *Config) {
78 78
 			imageNat, _ := parseNat(imagePortSpec)
79 79
 			for _, userPortSpec := range userConf.PortSpecs {
80 80
 				userNat, _ := parseNat(userPortSpec)
81
-				if imageNat.Proto == userNat.Proto && imageNat.Frontend == userNat.Frontend {
81
+				if imageNat.Proto == userNat.Proto && imageNat.Backend == userNat.Backend {
82 82
 					found = true
83 83
 				}
84 84
 			}
... ...
@@ -611,6 +611,24 @@ type JSONMessage struct {
611 611
 	Status   string `json:"status,omitempty"`
612 612
 	Progress string `json:"progress,omitempty"`
613 613
 	Error    string `json:"error,omitempty"`
614
+	ID       string `json:"id,omitempty"`
615
+	Time     int64  `json:"time,omitempty"`
616
+}
617
+
618
+func (jm *JSONMessage) Display(out io.Writer) error {
619
+	if jm.Time != 0 {
620
+		fmt.Fprintf(out, "[%s] ", time.Unix(jm.Time, 0))
621
+	}
622
+	if jm.Progress != "" {
623
+		fmt.Fprintf(out, "%s %s\r", jm.Status, jm.Progress)
624
+	} else if jm.Error != "" {
625
+		return fmt.Errorf(jm.Error)
626
+	} else if jm.ID != "" {
627
+		fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status)
628
+	} else {
629
+		fmt.Fprintf(out, "%s\n", jm.Status)
630
+	}
631
+	return nil
614 632
 }
615 633
 
616 634
 type StreamFormatter struct {
... ...
@@ -155,7 +155,7 @@ func TestMergeConfig(t *testing.T) {
155 155
 	volumesUser["/test3"] = struct{}{}
156 156
 	configUser := &Config{
157 157
 		Dns:       []string{"3.3.3.3"},
158
-		PortSpecs: []string{"2222:3333", "3333:3333"},
158
+		PortSpecs: []string{"3333:2222", "3333:3333"},
159 159
 		Env:       []string{"VAR2=3", "VAR3=3"},
160 160
 		Volumes:   volumesUser,
161 161
 	}
... ...
@@ -172,11 +172,11 @@ func TestMergeConfig(t *testing.T) {
172 172
 	}
173 173
 
174 174
 	if len(configUser.PortSpecs) != 3 {
175
-		t.Fatalf("Expected 3 portSpecs, 1111:1111, 2222:3333 and 3333:3333, found %d", len(configUser.PortSpecs))
175
+		t.Fatalf("Expected 3 portSpecs, 1111:1111, 3333:2222 and 3333:3333, found %d", len(configUser.PortSpecs))
176 176
 	}
177 177
 	for _, portSpecs := range configUser.PortSpecs {
178
-		if portSpecs != "1111:1111" && portSpecs != "2222:3333" && portSpecs != "3333:3333" {
179
-			t.Fatalf("Expected 1111:1111 or 2222:3333 or 3333:3333, found %s", portSpecs)
178
+		if portSpecs != "1111:1111" && portSpecs != "3333:2222" && portSpecs != "3333:3333" {
179
+			t.Fatalf("Expected 1111:1111 or 3333:2222 or 3333:3333, found %s", portSpecs)
180 180
 		}
181 181
 	}
182 182
 	if len(configUser.Env) != 3 {
... ...
@@ -197,3 +197,45 @@ func TestMergeConfig(t *testing.T) {
197 197
 		}
198 198
 	}
199 199
 }
200
+
201
+func TestMergeConfigPublicPortNotHonored(t *testing.T) {
202
+	volumesImage := make(map[string]struct{})
203
+	volumesImage["/test1"] = struct{}{}
204
+	volumesImage["/test2"] = struct{}{}
205
+	configImage := &Config{
206
+		Dns:       []string{"1.1.1.1", "2.2.2.2"},
207
+		PortSpecs: []string{"1111", "2222"},
208
+		Env:       []string{"VAR1=1", "VAR2=2"},
209
+		Volumes:   volumesImage,
210
+	}
211
+
212
+	volumesUser := make(map[string]struct{})
213
+	volumesUser["/test3"] = struct{}{}
214
+	configUser := &Config{
215
+		Dns:       []string{"3.3.3.3"},
216
+		PortSpecs: []string{"1111:3333"},
217
+		Env:       []string{"VAR2=3", "VAR3=3"},
218
+		Volumes:   volumesUser,
219
+	}
220
+
221
+	MergeConfig(configUser, configImage)
222
+
223
+	contains := func(a []string, expect string) bool {
224
+		for _, p := range a {
225
+			if p == expect {
226
+				return true
227
+			}
228
+		}
229
+		return false
230
+	}
231
+
232
+	if !contains(configUser.PortSpecs, "2222") {
233
+		t.Logf("Expected '2222' Ports: %v", configUser.PortSpecs)
234
+		t.Fail()
235
+	}
236
+
237
+	if !contains(configUser.PortSpecs, "1111:3333") {
238
+		t.Logf("Expected '1111:3333' Ports: %v", configUser.PortSpecs)
239
+		t.Fail()
240
+	}
241
+}
... ...
@@ -6,7 +6,12 @@ import (
6 6
 	"testing"
7 7
 )
8 8
 
9
+func displayFdGoroutines(t *testing.T) {
10
+	t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine())
11
+}
12
+
9 13
 func TestFinal(t *testing.T) {
10 14
 	cleanup(globalRuntime)
11
-	t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine())
15
+	t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines)
16
+	displayFdGoroutines(t)
12 17
 }