Browse code

Merged changes

Thatcher Peskens authored on 2013/04/23 10:38:42
Showing 87 changed files
... ...
@@ -14,3 +14,6 @@ Joffrey F <joffrey@dotcloud.com>
14 14
 <joffrey@dotcloud.com> <f.joffrey@gmail.com>
15 15
 Tim Terhorst <mynamewastaken+git@gmail.com>
16 16
 Andy Smith <github@anarkystic.com>
17
+<kalessin@kalessin.fr> <louis@dotcloud.com>
18
+<victor.vieux@dotcloud.com> <victor@dotcloud.com>
19
+<dominik@honnef.co> <dominikh@fork-bomb.org>
... ...
@@ -10,6 +10,8 @@ Daniel Robinson <gottagetmac@gmail.com>
10 10
 Dominik Honnef <dominik@honnef.co>
11 11
 Don Spaulding <donspauldingii@gmail.com>
12 12
 ezbercih <cem.ezberci@gmail.com>
13
+Flavio Castelli <fcastelli@suse.com>
14
+Francisco Souza <f@souza.cc>
13 15
 Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
14 16
 Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
15 17
 Hunter Blanks <hunter@twilio.com>
... ...
@@ -23,10 +25,13 @@ Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
23 23
 Ken Cochrane <kencochrane@gmail.com>
24 24
 Kevin J. Lynagh <kevin@keminglabs.com>
25 25
 Louis Opter <kalessin@kalessin.fr>
26
+Maxim Treskin <zerthurd@gmail.com>
26 27
 Mikhail Sobolev <mss@mawhrin.net>
27 28
 Nelson Chen <crazysim@gmail.com>
28 29
 Niall O'Higgins <niallo@unworkable.org>
30
+Paul Hammond <paul@paulhammond.org>
29 31
 Piotr Bogdan <ppbogdan@gmail.com>
32
+Robert Obryk <robryk@gmail.com>
30 33
 Sam Alba <sam.alba@gmail.com>
31 34
 Shawn Siefkas <shawn.siefkas@meredith.com>
32 35
 Silas Sewell <silas@sewell.org>
... ...
@@ -35,4 +40,6 @@ Sridhar Ratnakumar <sridharr@activestate.com>
35 35
 Thatcher Peskens <thatcher@dotcloud.com>
36 36
 Tim Terhorst <mynamewastaken+git@gmail.com>
37 37
 Troy Howard <thoward37@gmail.com>
38
+unclejack <unclejacksons@gmail.com>
39
+Victor Vieux <victor.vieux@dotcloud.com>
38 40
 Vivek Agarwal <me@vivek.im>
... ...
@@ -23,7 +23,7 @@ DOCKER_MAIN := $(DOCKER_DIR)/docker
23 23
 DOCKER_BIN_RELATIVE := bin/docker
24 24
 DOCKER_BIN := $(CURDIR)/$(DOCKER_BIN_RELATIVE)
25 25
 
26
-.PHONY: all clean test
26
+.PHONY: all clean test hack
27 27
 
28 28
 all: $(DOCKER_BIN)
29 29
 
... ...
@@ -49,3 +49,6 @@ test: all
49 49
 
50 50
 fmt:
51 51
 	@gofmt -s -l -w .
52
+
53
+hack:
54
+	cd $(CURDIR)/buildbot && vagrant up
... ...
@@ -33,123 +33,85 @@ Notable features
33 33
 
34 34
 * 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.
35 35
 
36
-
37
-
38
-Under the hood
39
-
40
-Under the hood, Docker is built on the following components:
41
-
42
-
43
-* 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;
44
-
45
-* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities;
46
-
47
-* The [Go](http://golang.org) programming language;
48
-
49
-* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
50
-
51
-
52 36
 Install instructions
53 37
 ==================
54 38
 
55
-Building from source
56
-
57
-1. Make sure you have a [Go language](http://golang.org) compiler.
58
-
59
-    On a Debian/wheezy or Ubuntu 12.10 install the package:
60
-
61
-    ```bash
62
-
63
-    $ sudo apt-get install golang-go
64
-    ```
65
-
66
-2. Execute ``make``
67
-
68
-   This command will install all necessary dependencies and build the
69
-   executable that you can find in ``bin/docker``
70
-
71
-3. Should you like to see what's happening, run ``make`` with ``VERBOSE=1`` parameter:
72
-
73
-    ```bash
39
+Quick install on Ubuntu 12.04 and 12.10
40
+---------------------------------------
74 41
 
75
-    $ make VERBOSE=1
76
-    ```
42
+```bash
43
+curl get.docker.io | sh -x
44
+```
77 45
 
78
-Installing on Ubuntu 12.04 and 12.10
46
+Binary installs
47
+----------------
79 48
 
80
-1. Install dependencies:
49
+Docker supports the following binary installation methods.
50
+Note that some methods are community contributions and not yet officially supported.
81 51
 
82
-    ```bash
83
-    sudo apt-get install lxc wget bsdtar curl
84
-    sudo apt-get install linux-image-extra-`uname -r`
85
-    ```
52
+* [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/)
53
+* [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/)
54
+* [MacOS X (with Vagrant)](http://docs.docker.io/en/latest/installation/macos/)
55
+* [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/)
56
+* [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/)
86 57
 
87
-    The `linux-image-extra` package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
58
+Installing from source
59
+----------------------
88 60
 
89
-2. Install the latest docker binary:
61
+1. Make sure you have a [Go language](http://golang.org/doc/install) compiler and [git](http://git-scm.com) installed.
90 62
 
91
-    ```bash
92
-    wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-master.tgz
93
-    tar -xf docker-master.tgz
94
-    ```
63
+2. Checkout the source code
95 64
 
96
-3. Run your first container!
65
+   ```bash
66
+   git clone http://github.com/dotcloud/docker
67
+   ```
97 68
 
98
-    ```bash
99
-    cd docker-master
100
-    sudo ./docker pull base
101
-    sudo ./docker run -i -t base /bin/bash
102
-    ```
69
+3. Build the docker binary
103 70
 
104
-    Consider adding docker to your `PATH` for simplicity.
71
+   ```bash
72
+   cd docker
73
+   make VERBOSE=1
74
+   sudo cp ./bin/docker /usr/local/bin/docker
75
+   ```
105 76
 
106
-Installing on other Linux distributions
77
+Usage examples
78
+==============
107 79
 
108
-Right now, the officially supported distributions are:
80
+First run the docker daemon
81
+---------------------------
109 82
 
110
-* Ubuntu 12.04 (precise LTS)
111
-* Ubuntu 12.10 (quantal)
83
+All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
112 84
 
113
-Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested.
85
+```bash
86
+# On a production system you want this running in an init script
87
+sudo docker -d &
88
+```
114 89
 
115
-Some streamlined (but possibly outdated) installation paths' are available from the website: http://docker.io/documentation/ 
90
+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.
116 91
 
92
+```bash
93
+# Now you can run docker commands from any account.
94
+docker help
95
+```
117 96
 
118
-Usage examples
119
-==============
120 97
 
121
-Running an interactive shell
98
+Throwaway shell in a base ubuntu image
99
+--------------------------------------
122 100
 
123 101
 ```bash
124
-# Download a base image
125
-docker pull base
126
-
127
-# Run an interactive shell in the base image,
128
-# allocate a tty, attach stdin and stdout
129
-docker run -i -t base /bin/bash
130
-```
102
+docker pull ubuntu:12.10
131 103
 
132
-Detaching from the interactive shell
133
-```
134
-# In order to detach without killing the shell, you can use the escape sequence Ctrl-p + Ctrl-q
135
-# Note: this works only in tty mode (run with -t option).
104
+# Run an interactive shell, allocate a tty, attach stdin and stdout
105
+# To detach the tty without exiting the shell, use the escape sequence Ctrl-p + Ctrl-q
106
+docker run -i -t ubuntu:12.10 /bin/bash
136 107
 ```
137 108
 
138 109
 Starting a long-running worker process
139 110
 --------------------------------------
140 111
 
141 112
 ```bash
142
-# Run docker in daemon mode
143
-(docker -d || echo "Docker daemon already running") &
144
-
145 113
 # Start a very useful long-running process
146
-JOB=$(docker run -d base /bin/sh -c "while true; do echo Hello world; sleep 1; done")
114
+JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done")
147 115
 
148 116
 # Collect the output of the job so far
149 117
 docker logs $JOB
... ...
@@ -158,25 +120,32 @@ docker logs $JOB
158 158
 docker kill $JOB
159 159
 ```
160 160
 
161
-
162
-Listing all running containers
161
+Running an irc bouncer
162
+----------------------
163 163
 
164 164
 ```bash
165
-docker ps
165
+BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc $USER $PASSWORD)
166
+echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine"
166 167
 ```
167 168
 
169
+Running Redis
170
+-------------
171
+
172
+```bash
173
+REDIS_ID=$(docker run -d -p 6379 shykes/redis redis-server)
174
+echo "Configure your redis client to connect to port $(docker port $REDIS_ID 6379) of this machine"
175
+```
168 176
 
169 177
 Share your own image!
170 178
 ---------------------
171 179
 
172 180
 ```bash
173
-docker pull base
174
-CONTAINER=$(docker run -d base apt-get install -y curl)
181
+CONTAINER=$(docker run -d ubuntu:12.10 apt-get install -y curl)
175 182
 docker commit -m "Installed curl" $CONTAINER $USER/betterbase
176 183
 docker push $USER/betterbase
177 184
 ```
178 185
 
186
+A list of publicly available images is [available here](https://github.com/dotcloud/docker/wiki/Public-docker-images).
179 187
 
180 188
 Expose a service on a TCP port
181 189
 ------------------------------
... ...
@@ -197,6 +166,22 @@ echo hello world | nc $IP $PORT
197 197
 echo "Daemon received: $(docker logs $JOB)"
198 198
 ```
199 199
 
200
+Under the hood
201
+--------------
202
+
203
+Under the hood, Docker is built on the following components:
204
+
205
+
206
+* 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;
207
+
208
+* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities;
209
+
210
+* The [Go](http://golang.org) programming language;
211
+
212
+* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
213
+
214
+
215
+
200 216
 Contributing to Docker
201 217
 ======================
202 218
 
203 219
new file mode 100644
... ...
@@ -0,0 +1,71 @@
0
+
1
+## Spec for data volumes
2
+
3
+Spec owner: Solomon Hykes <solomon@dotcloud.com>
4
+
5
+Data volumes (issue #111) are a much-requested feature which trigger much discussion and debate. Below is the current authoritative spec for implementing data volumes.
6
+This spec will be deprecated once the feature is fully implemented.
7
+
8
+Discussion, requests, trolls, demands, offerings, threats and other forms of supplications concerning this spec should be addressed to Solomon here: https://github.com/dotcloud/docker/issues/111
9
+
10
+
11
+### 1. Creating data volumes
12
+
13
+At container creation, parts of a container's filesystem can be mounted as separate data volumes. Volumes are defined with the -v flag.
14
+
15
+For example:
16
+
17
+```bash
18
+$ docker run -v /var/lib/postgres -v /var/log postgres /usr/bin/postgres
19
+```
20
+
21
+In this example, a new container is created from the 'postgres' image. At the same time, docker creates 2 new data volumes: one will be mapped to the container at /var/lib/postgres, the other at /var/log.
22
+
23
+2 important notes:
24
+
25
+1) Volumes don't have top-level names. At no point does the user provide a name, or is a name given to him. Volumes are identified by the path at which they are mounted inside their container.
26
+
27
+2) The user doesn't choose the source of the volume. Docker only mounts volumes it created itself, in the same way that it only runs containers that it created itself. That is by design.
28
+
29
+
30
+### 2. Sharing data volumes
31
+
32
+Instead of creating its own volumes, a container can share another container's volumes. For example:
33
+
34
+```bash
35
+$ docker run --volumes-from $OTHER_CONTAINER_ID postgres /usr/local/bin/postgres-backup
36
+```
37
+
38
+In this example, a new container is created from the 'postgres' example. At the same time, docker will *re-use* the 2 data volumes created in the previous example. One volume will be mounted on the /var/lib/postgres of *both* containers, and the other will be mounted on the /var/log of both containers.
39
+
40
+### 3. Under the hood
41
+
42
+Docker stores volumes in /var/lib/docker/volumes. Each volume receives a globally unique ID at creation, and is stored at /var/lib/docker/volumes/ID.
43
+
44
+At creation, volumes are attached to a single container - the source of truth for this mapping will be the container's configuration.
45
+
46
+Mounting a volume consists of calling "mount --bind" from the volume's directory to the appropriate sub-directory of the container mountpoint. This may be done by Docker itself, or farmed out to lxc (which supports mount-binding) if possible.
47
+
48
+
49
+### 4. Backups, transfers and other volume operations
50
+
51
+Volumes sometimes need to be backed up, transfered between hosts, synchronized, etc. These operations typically are application-specific or site-specific, eg. rsync vs. S3 upload vs. replication vs...
52
+
53
+Rather than attempting to implement all these scenarios directly, Docker will allow for custom implementations using an extension mechanism.
54
+
55
+### 5. Custom volume handlers
56
+
57
+Docker allows for arbitrary code to be executed against a container's volumes, to implement any custom action: backup, transfer, synchronization across hosts, etc.
58
+
59
+Here's an example:
60
+
61
+```bash
62
+$ DB=$(docker run -d -v /var/lib/postgres -v /var/log postgres /usr/bin/postgres)
63
+
64
+$ BACKUP_JOB=$(docker run -d --volumes-from $DB shykes/backuper /usr/local/bin/backup-postgres --s3creds=$S3CREDS)
65
+
66
+$ docker wait $BACKUP_JOB
67
+```
68
+
69
+Congratulations, you just implemented a custom volume handler, using Docker's built-in ability to 1) execute arbitrary code and 2) share volumes between containers.
70
+
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"errors"
5 5
 	"io"
6 6
 	"io/ioutil"
7
+	"os"
7 8
 	"os/exec"
8 9
 )
9 10
 
... ...
@@ -86,3 +87,38 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
86 86
 	}
87 87
 	return pipeR, nil
88 88
 }
89
+
90
+// NewTempArchive reads the content of src into a temporary file, and returns the contents
91
+// of that file as an archive. The archive can only be read once - as soon as reading completes,
92
+// the file will be deleted.
93
+func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
94
+	f, err := ioutil.TempFile(dir, "")
95
+	if err != nil {
96
+		return nil, err
97
+	}
98
+	if _, err := io.Copy(f, src); err != nil {
99
+		return nil, err
100
+	}
101
+	if _, err := f.Seek(0, 0); err != nil {
102
+		return nil, err
103
+	}
104
+	st, err := f.Stat()
105
+	if err != nil {
106
+		return nil, err
107
+	}
108
+	size := st.Size()
109
+	return &TempArchive{f, size}, nil
110
+}
111
+
112
+type TempArchive struct {
113
+	*os.File
114
+	Size int64 // Pre-computed from Stat().Size() as a convenience
115
+}
116
+
117
+func (archive *TempArchive) Read(data []byte) (int, error) {
118
+	n, err := archive.File.Read(data)
119
+	if err != nil {
120
+		os.Remove(archive.File.Name())
121
+	}
122
+	return n, err
123
+}
89 124
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+Buildbot
1
+========
2
+
3
+Buildbot is a continuous integration system designed to automate the
4
+build/test cycle. By automatically rebuilding and testing the tree each time
5
+something has changed, build problems are pinpointed quickly, before other
6
+developers are inconvenienced by the failure.
7
+
8
+When running 'make hack' at the docker root directory, it spawns a virtual
9
+machine in the background running a buildbot instance and adds a git
10
+post-commit hook that automatically run docker tests for you.
11
+
12
+You can check your buildbot instance at http://192.168.33.21:8010/waterfall
13
+
14
+
15
+Buildbot dependencies
16
+---------------------
17
+
18
+vagrant, virtualbox packages and python package requests
19
+
0 20
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+# -*- mode: ruby -*-
1
+# vi: set ft=ruby :
2
+
3
+$BUILDBOT_IP = '192.168.33.21'
4
+
5
+def v10(config)
6
+  config.vm.box = "quantal64_3.5.0-25"
7
+  config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
8
+  config.vm.share_folder 'v-data', '/data/docker', File.dirname(__FILE__) + '/..'
9
+  config.vm.network :hostonly, $BUILDBOT_IP
10
+
11
+  # Ensure puppet is installed on the instance
12
+  config.vm.provision :shell, :inline => 'apt-get -qq update; apt-get install -y puppet'
13
+
14
+  config.vm.provision :puppet do |puppet|
15
+    puppet.manifests_path = '.'
16
+    puppet.manifest_file  = 'buildbot.pp'
17
+    puppet.options = ['--templatedir','.']
18
+  end
19
+end
20
+
21
+Vagrant::VERSION < '1.1.0' and Vagrant::Config.run do |config|
22
+  v10(config)
23
+end
24
+
25
+Vagrant::VERSION >= '1.1.0' and Vagrant.configure('1') do |config|
26
+  v10(config)
27
+end
0 28
new file mode 100755
... ...
@@ -0,0 +1,43 @@
0
+#!/bin/bash
1
+
2
+# Auto setup of buildbot configuration. Package installation is being done
3
+# on buildbot.pp
4
+# Dependencies: buildbot, buildbot-slave, supervisor
5
+
6
+SLAVE_NAME='buildworker'
7
+SLAVE_SOCKET='localhost:9989'
8
+BUILDBOT_PWD='pass-docker'
9
+USER='vagrant'
10
+ROOT_PATH='/data/buildbot'
11
+DOCKER_PATH='/data/docker'
12
+BUILDBOT_CFG="$DOCKER_PATH/buildbot/buildbot-cfg"
13
+IP=$(grep BUILDBOT_IP /data/docker/buildbot/Vagrantfile | awk -F "'" '{ print $2; }')
14
+
15
+function run { su $USER -c "$1"; }
16
+
17
+export PATH=/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin
18
+
19
+# Exit if buildbot has already been installed
20
+[ -d "$ROOT_PATH" ] && exit 0
21
+
22
+# Setup buildbot
23
+run "mkdir -p ${ROOT_PATH}"
24
+cd ${ROOT_PATH}
25
+run "buildbot create-master master"
26
+run "cp $BUILDBOT_CFG/master.cfg master"
27
+run "sed -i 's/localhost/$IP/' master/master.cfg"
28
+run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD"
29
+
30
+# Allow buildbot subprocesses (docker tests) to properly run in containers,
31
+# in particular with docker -u
32
+run "sed -i 's/^umask = None/umask = 000/' ${ROOT_PATH}/slave/buildbot.tac"
33
+
34
+# Setup supervisor
35
+cp $BUILDBOT_CFG/buildbot.conf /etc/supervisor/conf.d/buildbot.conf
36
+sed -i "s/^chmod=0700.*0700./chmod=0770\nchown=root:$USER/" /etc/supervisor/supervisord.conf
37
+kill -HUP `pgrep -f "/usr/bin/python /usr/bin/supervisord"`
38
+
39
+# Add git hook
40
+cp $BUILDBOT_CFG/post-commit $DOCKER_PATH/.git/hooks
41
+sed -i "s/localhost/$IP/" $DOCKER_PATH/.git/hooks/post-commit
42
+
0 43
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+[program:buildmaster]
1
+command=su vagrant -c "buildbot start master"
2
+directory=/data/buildbot
3
+chown= root:root
4
+redirect_stderr=true
5
+stdout_logfile=/var/log/supervisor/buildbot-master.log
6
+stderr_logfile=/var/log/supervisor/buildbot-master.log
7
+
8
+[program:buildworker]
9
+command=buildslave start slave
10
+directory=/data/buildbot
11
+chown= root:root
12
+redirect_stderr=true
13
+stdout_logfile=/var/log/supervisor/buildbot-slave.log
14
+stderr_logfile=/var/log/supervisor/buildbot-slave.log
15
+
16
+[group:buildbot]
17
+programs=buildmaster,buildworker
0 18
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+import os
1
+from buildbot.buildslave import BuildSlave
2
+from buildbot.schedulers.forcesched import ForceScheduler
3
+from buildbot.config import BuilderConfig
4
+from buildbot.process.factory import BuildFactory
5
+from buildbot.steps.shell import ShellCommand
6
+from buildbot.status import html
7
+from buildbot.status.web import authz, auth
8
+
9
+PORT_WEB = 8010         # Buildbot webserver port
10
+PORT_MASTER = 9989      # Port where buildbot master listen buildworkers
11
+TEST_USER = 'buildbot'  # Credential to authenticate build triggers
12
+TEST_PWD = 'docker'     # Credential to authenticate build triggers
13
+BUILDER_NAME = 'docker'
14
+BUILDPASSWORD = 'pass-docker'  # Credential to authenticate buildworkers
15
+DOCKER_PATH = '/data/docker'
16
+
17
+
18
+c = BuildmasterConfig = {}
19
+
20
+c['title'] = "Docker"
21
+c['titleURL'] = "waterfall"
22
+c['buildbotURL'] = "http://localhost:{0}/".format(PORT_WEB)
23
+c['db'] = {'db_url':"sqlite:///state.sqlite"}
24
+c['slaves'] = [BuildSlave('buildworker', BUILDPASSWORD)]
25
+c['slavePortnum'] = PORT_MASTER
26
+
27
+c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])]
28
+
29
+# Docker test command
30
+test_cmd = """(
31
+    cd {0}/..; rm -rf docker-tmp; git clone docker docker-tmp;
32
+    cd docker-tmp; make test; exit_status=$?;
33
+    cd ..; rm -rf docker-tmp; exit $exit_status)""".format(DOCKER_PATH)
34
+
35
+# Builder
36
+factory = BuildFactory()
37
+factory.addStep(ShellCommand(description='Docker',logEnviron=False,
38
+    usePTY=True,command=test_cmd))
39
+c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'],
40
+    factory=factory)]
41
+
42
+# Status
43
+authz_cfg=authz.Authz(auth=auth.BasicAuth([(TEST_USER,TEST_PWD)]),
44
+    forceBuild='auth')
45
+c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)]
0 46
new file mode 100755
... ...
@@ -0,0 +1,21 @@
0
+#!/usr/bin/env python
1
+
2
+'''Trigger buildbot docker test build
3
+
4
+   post-commit git hook designed to automatically trigger buildbot on
5
+   the provided vagrant docker VM.'''
6
+
7
+import requests
8
+
9
+USERNAME = 'buildbot'
10
+PASSWORD = 'docker'
11
+BASE_URL = 'http://localhost:8010'
12
+path = lambda s: BASE_URL + '/' + s
13
+
14
+try:
15
+    session = requests.session()
16
+    session.post(path('login'),data={'username':USERNAME,'passwd':PASSWORD})
17
+    session.post(path('builders/docker/force'),
18
+        data={'forcescheduler':'trigger','reason':'Test commit'})
19
+except:
20
+    pass
0 21
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+node default {
1
+    $USER = 'vagrant'
2
+    $ROOT_PATH = '/data/buildbot'
3
+    $DOCKER_PATH = '/data/docker'
4
+
5
+    exec {'apt_update': command => '/usr/bin/apt-get update' }
6
+    Package { require => Exec['apt_update'] }
7
+    group {'puppet': ensure => 'present'}
8
+
9
+    # Install dependencies
10
+    Package { ensure => 'installed' }
11
+    package { ['python-dev','python-pip','supervisor','lxc','bsdtar','git','golang']: }
12
+
13
+    file{[ '/data' ]:
14
+        owner => $USER, group => $USER, ensure => 'directory' }
15
+
16
+    file {'/var/tmp/requirements.txt':
17
+        content => template('requirements.txt') }
18
+
19
+    exec {'requirements':
20
+        require => [ Package['python-dev'], Package['python-pip'],
21
+            File['/var/tmp/requirements.txt'] ],
22
+        cwd     => '/var/tmp',
23
+        command => "/bin/sh -c '(/usr/bin/pip install -r requirements.txt;
24
+            rm /var/tmp/requirements.txt)'" }
25
+
26
+    exec {'buildbot-cfg-sh':
27
+        require => [ Package['supervisor'], Exec['requirements']],
28
+        path    => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin',
29
+        cwd     => '/data',
30
+        command => "$DOCKER_PATH/buildbot/buildbot-cfg/buildbot-cfg.sh" }
31
+}
0 32
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+sqlalchemy<=0.7.9
1
+sqlalchemy-migrate>=0.7.2
2
+buildbot==0.8.7p1
3
+buildbot_slave==0.8.7p1
4
+nose==1.2.1
5
+requests==1.1.0
... ...
@@ -18,9 +18,11 @@ import (
18 18
 	"unicode"
19 19
 )
20 20
 
21
-const VERSION = "0.1.4"
21
+const VERSION = "0.1.7"
22 22
 
23
-var GIT_COMMIT string
23
+var (
24
+	GIT_COMMIT string
25
+)
24 26
 
25 27
 func (srv *Server) Name() string {
26 28
 	return "docker"
... ...
@@ -79,7 +81,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ..
79 79
 			n, err := stdin.Read(char)
80 80
 			if n > 0 {
81 81
 				if char[0] == '\r' || char[0] == '\n' {
82
-					stdout.Write([]byte{'\n'})
82
+					stdout.Write([]byte{'\r', '\n'})
83 83
 					break
84 84
 				} else if char[0] == 127 || char[0] == '\b' {
85 85
 					if i > 0 {
... ...
@@ -99,7 +101,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ..
99 99
 			}
100 100
 			if err != nil {
101 101
 				if err != io.EOF {
102
-					fmt.Fprintf(stdout, "Read error: %v\n", err)
102
+					fmt.Fprintf(stdout, "Read error: %v\r\n", err)
103 103
 				}
104 104
 				break
105 105
 			}
... ...
@@ -149,7 +151,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ..
149 149
 	newAuthConfig := auth.NewAuthConfig(username, password, email, srv.runtime.root)
150 150
 	status, err := auth.Login(newAuthConfig)
151 151
 	if err != nil {
152
-		fmt.Fprintln(stdout, "Error:", err)
152
+		fmt.Fprintf(stdout, "Error: %s\r\n", err)
153 153
 	} else {
154 154
 		srv.runtime.authConfig = newAuthConfig
155 155
 	}
... ...
@@ -161,7 +163,7 @@ func (srv *Server) CmdLogin(stdin io.ReadCloser, stdout rcli.DockerConn, args ..
161 161
 
162 162
 // 'docker wait': block until a container stops
163 163
 func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
164
-	cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.")
164
+	cmd := rcli.Subcmd(stdout, "wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
165 165
 	if err := cmd.Parse(args); err != nil {
166 166
 		return nil
167 167
 	}
... ...
@@ -181,8 +183,15 @@ func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string
181 181
 
182 182
 // 'docker version': show version information
183 183
 func (srv *Server) CmdVersion(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
184
-	fmt.Fprintf(stdout, "Version:%s\n", VERSION)
185
-	fmt.Fprintf(stdout, "Git Commit:%s\n", GIT_COMMIT)
184
+	fmt.Fprintf(stdout, "Version: %s\n", VERSION)
185
+	fmt.Fprintf(stdout, "Git Commit: %s\n", GIT_COMMIT)
186
+	fmt.Fprintf(stdout, "Kernel: %s\n", srv.runtime.kernelVersion)
187
+	if !srv.runtime.capabilities.MemoryLimit {
188
+		fmt.Fprintf(stdout, "WARNING: No memory limit support\n")
189
+	}
190
+	if !srv.runtime.capabilities.SwapLimit {
191
+		fmt.Fprintf(stdout, "WARNING: No swap limit support\n")
192
+	}
186 193
 	return nil
187 194
 }
188 195
 
... ...
@@ -217,7 +226,8 @@ func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string
217 217
 }
218 218
 
219 219
 func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
220
-	cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container")
220
+	cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
221
+	nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
221 222
 	if err := cmd.Parse(args); err != nil {
222 223
 		return nil
223 224
 	}
... ...
@@ -227,7 +237,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
227 227
 	}
228 228
 	for _, name := range cmd.Args() {
229 229
 		if container := srv.runtime.Get(name); container != nil {
230
-			if err := container.Stop(); err != nil {
230
+			if err := container.Stop(*nSeconds); err != nil {
231 231
 				return err
232 232
 			}
233 233
 			fmt.Fprintln(stdout, container.ShortId())
... ...
@@ -239,7 +249,8 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
239 239
 }
240 240
 
241 241
 func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
242
-	cmd := rcli.Subcmd(stdout, "restart", "[OPTIONS] NAME", "Restart a running container")
242
+	cmd := rcli.Subcmd(stdout, "restart", "CONTAINER [CONTAINER...]", "Restart a running container")
243
+	nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
243 244
 	if err := cmd.Parse(args); err != nil {
244 245
 		return nil
245 246
 	}
... ...
@@ -249,7 +260,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str
249 249
 	}
250 250
 	for _, name := range cmd.Args() {
251 251
 		if container := srv.runtime.Get(name); container != nil {
252
-			if err := container.Restart(); err != nil {
252
+			if err := container.Restart(*nSeconds); err != nil {
253 253
 				return err
254 254
 			}
255 255
 			fmt.Fprintln(stdout, container.ShortId())
... ...
@@ -261,7 +272,7 @@ func (srv *Server) CmdRestart(stdin io.ReadCloser, stdout io.Writer, args ...str
261 261
 }
262 262
 
263 263
 func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
264
-	cmd := rcli.Subcmd(stdout, "start", "[OPTIONS] NAME", "Start a stopped container")
264
+	cmd := rcli.Subcmd(stdout, "start", "CONTAINER [CONTAINER...]", "Start a stopped container")
265 265
 	if err := cmd.Parse(args); err != nil {
266 266
 		return nil
267 267
 	}
... ...
@@ -283,7 +294,7 @@ func (srv *Server) CmdStart(stdin io.ReadCloser, stdout io.Writer, args ...strin
283 283
 }
284 284
 
285 285
 func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
286
-	cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container")
286
+	cmd := rcli.Subcmd(stdout, "inspect", "CONTAINER", "Return low-level information on a container")
287 287
 	if err := cmd.Parse(args); err != nil {
288 288
 		return nil
289 289
 	}
... ...
@@ -318,7 +329,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
318 318
 }
319 319
 
320 320
 func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
321
-	cmd := rcli.Subcmd(stdout, "port", "[OPTIONS] CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
321
+	cmd := rcli.Subcmd(stdout, "port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
322 322
 	if err := cmd.Parse(args); err != nil {
323 323
 		return nil
324 324
 	}
... ...
@@ -340,9 +351,9 @@ func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string
340 340
 	return nil
341 341
 }
342 342
 
343
-// 'docker rmi NAME' removes all images with the name NAME
343
+// 'docker rmi IMAGE' removes all images with the name IMAGE
344 344
 func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) (err error) {
345
-	cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image")
345
+	cmd := rcli.Subcmd(stdout, "rmimage", "IMAGE [IMAGE...]", "Remove an image")
346 346
 	if err := cmd.Parse(args); err != nil {
347 347
 		return nil
348 348
 	}
... ...
@@ -351,7 +362,11 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string)
351 351
 		return nil
352 352
 	}
353 353
 	for _, name := range cmd.Args() {
354
-		if err := srv.runtime.graph.Delete(name); err != nil {
354
+		img, err := srv.runtime.repositories.LookupImage(name)
355
+		if err != nil {
356
+			return err
357
+		}
358
+		if err := srv.runtime.graph.Delete(img.Id); err != nil {
355 359
 			return err
356 360
 		}
357 361
 	}
... ...
@@ -359,7 +374,7 @@ func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string)
359 359
 }
360 360
 
361 361
 func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
362
-	cmd := rcli.Subcmd(stdout, "history", "[OPTIONS] IMAGE", "Show the history of an image")
362
+	cmd := rcli.Subcmd(stdout, "history", "IMAGE", "Show the history of an image")
363 363
 	if err := cmd.Parse(args); err != nil {
364 364
 		return nil
365 365
 	}
... ...
@@ -385,10 +400,14 @@ func (srv *Server) CmdHistory(stdin io.ReadCloser, stdout io.Writer, args ...str
385 385
 }
386 386
 
387 387
 func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
388
-	cmd := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
388
+	cmd := rcli.Subcmd(stdout, "rm", "CONTAINER [CONTAINER...]", "Remove a container")
389 389
 	if err := cmd.Parse(args); err != nil {
390 390
 		return nil
391 391
 	}
392
+	if cmd.NArg() < 1 {
393
+		cmd.Usage()
394
+		return nil
395
+	}
392 396
 	for _, name := range cmd.Args() {
393 397
 		container := srv.runtime.Get(name)
394 398
 		if container == nil {
... ...
@@ -403,10 +422,14 @@ func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string)
403 403
 
404 404
 // 'docker kill NAME' kills a running container
405 405
 func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
406
-	cmd := rcli.Subcmd(stdout, "kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container")
406
+	cmd := rcli.Subcmd(stdout, "kill", "CONTAINER [CONTAINER...]", "Kill a running container")
407 407
 	if err := cmd.Parse(args); err != nil {
408 408
 		return nil
409 409
 	}
410
+	if cmd.NArg() < 1 {
411
+		cmd.Usage()
412
+		return nil
413
+	}
410 414
 	for _, name := range cmd.Args() {
411 415
 		container := srv.runtime.Get(name)
412 416
 		if container == nil {
... ...
@@ -421,17 +444,19 @@ func (srv *Server) CmdKill(stdin io.ReadCloser, stdout io.Writer, args ...string
421 421
 
422 422
 func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
423 423
 	stdout.Flush()
424
-	cmd := rcli.Subcmd(stdout, "import", "[OPTIONS] URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
424
+	cmd := rcli.Subcmd(stdout, "import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
425 425
 	var archive io.Reader
426 426
 	var resp *http.Response
427 427
 
428 428
 	if err := cmd.Parse(args); err != nil {
429 429
 		return nil
430 430
 	}
431
+	if cmd.NArg() < 1 {
432
+		cmd.Usage()
433
+		return nil
434
+	}
431 435
 	src := cmd.Arg(0)
432
-	if src == "" {
433
-		return fmt.Errorf("Not enough arguments")
434
-	} else if src == "-" {
436
+	if src == "-" {
435 437
 		archive = stdin
436 438
 	} else {
437 439
 		u, err := url.Parse(src)
... ...
@@ -450,9 +475,9 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout rcli.DockerConn, args .
450 450
 		if err != nil {
451 451
 			return err
452 452
 		}
453
-		archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout)
453
+		archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout, "Importing %v/%v (%v)")
454 454
 	}
455
-	img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src)
455
+	img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "")
456 456
 	if err != nil {
457 457
 		return err
458 458
 	}
... ...
@@ -569,7 +594,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
569 569
 	}
570 570
 	w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
571 571
 	if !*quiet {
572
-		fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED\tPARENT")
572
+		fmt.Fprintln(w, "REPOSITORY\tTAG\tID\tCREATED")
573 573
 	}
574 574
 	var allImages map[string]*Image
575 575
 	var err error
... ...
@@ -598,7 +623,6 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
598 598
 					/* TAG */ tag,
599 599
 					/* ID */ TruncateId(id),
600 600
 					/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
601
-					/* PARENT */ srv.runtime.repositories.ImageName(image.Parent),
602 601
 				} {
603 602
 					if idx == 0 {
604 603
 						w.Write([]byte(field))
... ...
@@ -621,7 +645,6 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri
621 621
 					/* TAG */ "<none>",
622 622
 					/* ID */ TruncateId(id),
623 623
 					/* CREATED */ HumanDuration(time.Now().Sub(image.Created)) + " ago",
624
-					/* PARENT */ srv.runtime.repositories.ImageName(image.Parent),
625 624
 				} {
626 625
 					if idx == 0 {
627 626
 						w.Write([]byte(field))
... ...
@@ -647,17 +670,25 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
647 647
 	quiet := cmd.Bool("q", false, "Only display numeric IDs")
648 648
 	flAll := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.")
649 649
 	flFull := cmd.Bool("notrunc", false, "Don't truncate output")
650
+	latest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.")
651
+	nLast := cmd.Int("n", -1, "Show n last created containers, include non-running ones.")
650 652
 	if err := cmd.Parse(args); err != nil {
651 653
 		return nil
652 654
 	}
655
+	if *nLast == -1 && *latest {
656
+		*nLast = 1
657
+	}
653 658
 	w := tabwriter.NewWriter(stdout, 12, 1, 3, ' ', 0)
654 659
 	if !*quiet {
655
-		fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT")
660
+		fmt.Fprintln(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tCOMMENT\tPORTS")
656 661
 	}
657
-	for _, container := range srv.runtime.List() {
658
-		if !container.State.Running && !*flAll {
662
+	for i, container := range srv.runtime.List() {
663
+		if !container.State.Running && !*flAll && *nLast == -1 {
659 664
 			continue
660 665
 		}
666
+		if i == *nLast {
667
+			break
668
+		}
661 669
 		if !*quiet {
662 670
 			command := fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
663 671
 			if !*flFull {
... ...
@@ -670,6 +701,7 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string)
670 670
 				/* CREATED */ HumanDuration(time.Now().Sub(container.Created)) + " ago",
671 671
 				/* STATUS */ container.State.String(),
672 672
 				/* COMMENT */ "",
673
+				/* PORTS */ container.NetworkSettings.PortMappingHuman(),
673 674
 			} {
674 675
 				if idx == 0 {
675 676
 					w.Write([]byte(field))
... ...
@@ -693,6 +725,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
693 693
 		"commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]",
694 694
 		"Create a new image from a container's changes")
695 695
 	flComment := cmd.String("m", "", "Commit message")
696
+	flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
696 697
 	if err := cmd.Parse(args); err != nil {
697 698
 		return nil
698 699
 	}
... ...
@@ -701,7 +734,7 @@ func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...stri
701 701
 		cmd.Usage()
702 702
 		return nil
703 703
 	}
704
-	img, err := srv.runtime.Commit(containerName, repository, tag, *flComment)
704
+	img, err := srv.runtime.Commit(containerName, repository, tag, *flComment, *flAuthor)
705 705
 	if err != nil {
706 706
 		return err
707 707
 	}
... ...
@@ -733,13 +766,14 @@ func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...stri
733 733
 
734 734
 func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
735 735
 	cmd := rcli.Subcmd(stdout,
736
-		"diff", "CONTAINER [OPTIONS]",
736
+		"diff", "CONTAINER",
737 737
 		"Inspect changes on a container's filesystem")
738 738
 	if err := cmd.Parse(args); err != nil {
739 739
 		return nil
740 740
 	}
741 741
 	if cmd.NArg() < 1 {
742
-		return fmt.Errorf("Not enough arguments")
742
+		cmd.Usage()
743
+		return nil
743 744
 	}
744 745
 	if container := srv.runtime.Get(cmd.Arg(0)); container == nil {
745 746
 		return fmt.Errorf("No such container")
... ...
@@ -756,7 +790,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
756 756
 }
757 757
 
758 758
 func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
759
-	cmd := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
759
+	cmd := rcli.Subcmd(stdout, "logs", "CONTAINER", "Fetch the logs of a container")
760 760
 	if err := cmd.Parse(args); err != nil {
761 761
 		return nil
762 762
 	}
... ...
@@ -879,7 +913,7 @@ func (srv *Server) CmdTag(stdin io.ReadCloser, stdout io.Writer, args ...string)
879 879
 }
880 880
 
881 881
 func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...string) error {
882
-	config, err := ParseRun(args, stdout)
882
+	config, err := ParseRun(args, stdout, srv.runtime.capabilities)
883 883
 	if err != nil {
884 884
 		return err
885 885
 	}
... ...
@@ -904,7 +938,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
904 904
 	if err != nil {
905 905
 		// If container not found, try to pull it
906 906
 		if srv.runtime.graph.IsNotExist(err) {
907
-			fmt.Fprintf(stdout, "Image %s not found, trying to pull it from registry.\n", config.Image)
907
+			fmt.Fprintf(stdout, "Image %s not found, trying to pull it from registry.\r\n", config.Image)
908 908
 			if err = srv.CmdPull(stdin, stdout, config.Image); err != nil {
909 909
 				return err
910 910
 			}
... ...
@@ -946,6 +980,12 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout rcli.DockerConn, args ...s
946 946
 	Debugf("Waiting for attach to return\n")
947 947
 	<-attachErr
948 948
 	// Expecting I/O pipe error, discarding
949
+
950
+	// If we are in stdinonce mode, wait for the process to end
951
+	// otherwise, simply return
952
+	if config.StdinOnce && !config.Tty {
953
+		container.Wait()
954
+	}
949 955
 	return nil
950 956
 }
951 957
 
... ...
@@ -59,6 +59,20 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error
59 59
 	return nil
60 60
 }
61 61
 
62
+func cmdWait(srv *Server, container *Container) error {
63
+	stdout, stdoutPipe := io.Pipe()
64
+
65
+	go func() {
66
+		srv.CmdWait(nil, stdoutPipe, container.Id)
67
+	}()
68
+
69
+	if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
70
+		return err
71
+	}
72
+	// Cleanup pipes
73
+	return closeWrap(stdout, stdoutPipe)
74
+}
75
+
62 76
 // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
63 77
 func TestRunHostname(t *testing.T) {
64 78
 	runtime, err := newTestRuntime()
... ...
@@ -89,7 +103,9 @@ func TestRunHostname(t *testing.T) {
89 89
 
90 90
 	setTimeout(t, "CmdRun timed out", 2*time.Second, func() {
91 91
 		<-c
92
+		cmdWait(srv, srv.runtime.List()[0])
92 93
 	})
94
+
93 95
 }
94 96
 
95 97
 func TestRunExit(t *testing.T) {
... ...
@@ -129,6 +145,7 @@ func TestRunExit(t *testing.T) {
129 129
 	// as the process exited, CmdRun must finish and unblock. Wait for it
130 130
 	setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
131 131
 		<-c1
132
+		cmdWait(srv, container)
132 133
 	})
133 134
 
134 135
 	// Make sure that the client has been disconnected
... ...
@@ -211,6 +228,21 @@ func TestRunDisconnectTty(t *testing.T) {
211 211
 		close(c1)
212 212
 	}()
213 213
 
214
+	setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
215
+		for {
216
+			// Client disconnect after run -i should keep stdin out in TTY mode
217
+			l := runtime.List()
218
+			if len(l) == 1 && l[0].State.Running {
219
+				break
220
+			}
221
+
222
+			time.Sleep(10 * time.Millisecond)
223
+		}
224
+	})
225
+
226
+	// Client disconnect after run -i should keep stdin out in TTY mode
227
+	container := runtime.List()[0]
228
+
214 229
 	setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
215 230
 		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
216 231
 			t.Fatal(err)
... ...
@@ -222,14 +254,9 @@ func TestRunDisconnectTty(t *testing.T) {
222 222
 		t.Fatal(err)
223 223
 	}
224 224
 
225
-	// as the pipes are close, we expect the process to die,
226
-	// therefore CmdRun to unblock. Wait for CmdRun
227
-	setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
228
-		<-c1
229
-	})
225
+	// In tty mode, we expect the process to stay alive even after client's stdin closes.
226
+	// Do not wait for run to finish
230 227
 
231
-	// Client disconnect after run -i should keep stdin out in TTY mode
232
-	container := runtime.List()[0]
233 228
 	// Give some time to monitor to do his thing
234 229
 	container.WaitTimeout(500 * time.Millisecond)
235 230
 	if !container.State.Running {
... ...
@@ -11,7 +11,9 @@ import (
11 11
 	"os"
12 12
 	"os/exec"
13 13
 	"path"
14
+	"sort"
14 15
 	"strconv"
16
+	"strings"
15 17
 	"syscall"
16 18
 	"time"
17 19
 )
... ...
@@ -33,13 +35,14 @@ type Container struct {
33 33
 	network         *NetworkInterface
34 34
 	NetworkSettings *NetworkSettings
35 35
 
36
-	SysInitPath string
37
-	cmd         *exec.Cmd
38
-	stdout      *writeBroadcaster
39
-	stderr      *writeBroadcaster
40
-	stdin       io.ReadCloser
41
-	stdinPipe   io.WriteCloser
36
+	SysInitPath    string
37
+	ResolvConfPath string
42 38
 
39
+	cmd       *exec.Cmd
40
+	stdout    *writeBroadcaster
41
+	stderr    *writeBroadcaster
42
+	stdin     io.ReadCloser
43
+	stdinPipe io.WriteCloser
43 44
 	ptyMaster io.Closer
44 45
 
45 46
 	runtime *Runtime
... ...
@@ -61,10 +64,11 @@ type Config struct {
61 61
 	StdinOnce    bool // If true, close stdin after the 1 attached client disconnects.
62 62
 	Env          []string
63 63
 	Cmd          []string
64
+	Dns          []string
64 65
 	Image        string // Name of the image as it was passed by the operator (eg. could be symbolic)
65 66
 }
66 67
 
67
-func ParseRun(args []string, stdout io.Writer) (*Config, error) {
68
+func ParseRun(args []string, stdout io.Writer, capabilities *Capabilities) (*Config, error) {
68 69
 	cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
69 70
 	if len(args) > 0 && args[0] != "--help" {
70 71
 		cmd.SetOutput(ioutil.Discard)
... ...
@@ -79,12 +83,20 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
79 79
 	flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
80 80
 	flMemory := cmd.Int64("m", 0, "Memory limit (in bytes)")
81 81
 
82
+	if *flMemory > 0 && !capabilities.MemoryLimit {
83
+		fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
84
+		*flMemory = 0
85
+	}
86
+
82 87
 	var flPorts ListOpts
83 88
 	cmd.Var(&flPorts, "p", "Expose a container's port to the host (use 'docker port' to see the actual mapping)")
84 89
 
85 90
 	var flEnv ListOpts
86 91
 	cmd.Var(&flEnv, "e", "Set environment variables")
87 92
 
93
+	var flDns ListOpts
94
+	cmd.Var(&flDns, "dns", "Set custom dns servers")
95
+
88 96
 	if err := cmd.Parse(args); err != nil {
89 97
 		return nil, err
90 98
 	}
... ...
@@ -122,8 +134,15 @@ func ParseRun(args []string, stdout io.Writer) (*Config, error) {
122 122
 		AttachStderr: flAttach.Get("stderr"),
123 123
 		Env:          flEnv,
124 124
 		Cmd:          runCmd,
125
+		Dns:          flDns,
125 126
 		Image:        image,
126 127
 	}
128
+
129
+	if *flMemory > 0 && !capabilities.SwapLimit {
130
+		fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
131
+		config.MemorySwap = -1
132
+	}
133
+
127 134
 	// When allocating stdin in attached mode, close stdin at client disconnect
128 135
 	if config.OpenStdin && config.AttachStdin {
129 136
 		config.StdinOnce = true
... ...
@@ -139,6 +158,16 @@ type NetworkSettings struct {
139 139
 	PortMapping map[string]string
140 140
 }
141 141
 
142
+// String returns a human-readable description of the port mapping defined in the settings
143
+func (settings *NetworkSettings) PortMappingHuman() string {
144
+	var mapping []string
145
+	for private, public := range settings.PortMapping {
146
+		mapping = append(mapping, fmt.Sprintf("%s->%s", public, private))
147
+	}
148
+	sort.Strings(mapping)
149
+	return strings.Join(mapping, ", ")
150
+}
151
+
142 152
 func (container *Container) Cmd() *exec.Cmd {
143 153
 	return container.cmd
144 154
 }
... ...
@@ -355,6 +384,17 @@ func (container *Container) Start() error {
355 355
 	if err := container.allocateNetwork(); err != nil {
356 356
 		return err
357 357
 	}
358
+
359
+	// Make sure the config is compatible with the current kernel
360
+	if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit {
361
+		log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
362
+		container.Config.Memory = 0
363
+	}
364
+	if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit {
365
+		log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
366
+		container.Config.MemorySwap = -1
367
+	}
368
+
358 369
 	if err := container.generateLXCConfig(); err != nil {
359 370
 		return err
360 371
 	}
... ...
@@ -373,21 +413,26 @@ func (container *Container) Start() error {
373 373
 		params = append(params, "-u", container.Config.User)
374 374
 	}
375 375
 
376
+	if container.Config.Tty {
377
+		params = append(params, "-e", "TERM=xterm")
378
+	}
379
+
380
+	// Setup environment
381
+	params = append(params,
382
+		"-e", "HOME=/",
383
+		"-e", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
384
+	)
385
+
386
+	for _, elem := range container.Config.Env {
387
+		params = append(params, "-e", elem)
388
+	}
389
+
376 390
 	// Program
377 391
 	params = append(params, "--", container.Path)
378 392
 	params = append(params, container.Args...)
379 393
 
380 394
 	container.cmd = exec.Command("lxc-start", params...)
381 395
 
382
-	// Setup environment
383
-	container.cmd.Env = append(
384
-		[]string{
385
-			"HOME=/",
386
-			"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
387
-		},
388
-		container.Config.Env...,
389
-	)
390
-
391 396
 	// Setup logging of stdout and stderr to disk
392 397
 	if err := container.runtime.LogToDisk(container.stdout, container.logPath("stdout")); err != nil {
393 398
 		return err
... ...
@@ -398,10 +443,6 @@ func (container *Container) Start() error {
398 398
 
399 399
 	var err error
400 400
 	if container.Config.Tty {
401
-		container.cmd.Env = append(
402
-			[]string{"TERM=xterm"},
403
-			container.cmd.Env...,
404
-		)
405 401
 		err = container.startPty()
406 402
 	} else {
407 403
 		err = container.start()
... ...
@@ -550,9 +591,21 @@ func (container *Container) kill() error {
550 550
 	if !container.State.Running || container.cmd == nil {
551 551
 		return nil
552 552
 	}
553
-	if err := container.cmd.Process.Kill(); err != nil {
554
-		return err
553
+
554
+	// Sending SIGKILL to the process via lxc
555
+	output, err := exec.Command("lxc-kill", "-n", container.Id, "9").CombinedOutput()
556
+	if err != nil {
557
+		log.Printf("error killing container %s (%s, %s)", container.Id, output, err)
555 558
 	}
559
+
560
+	// 2. Wait for the process to die, in last resort, try to kill the process directly
561
+	if err := container.WaitTimeout(10 * time.Second); err != nil {
562
+		log.Printf("Container %s failed to exit within 10 seconds of lxc SIGKILL - trying direct SIGKILL", container.Id)
563
+		if err := container.cmd.Process.Kill(); err != nil {
564
+			return err
565
+		}
566
+	}
567
+
556 568
 	// Wait for the container to be actually stopped
557 569
 	container.Wait()
558 570
 	return nil
... ...
@@ -561,15 +614,24 @@ func (container *Container) kill() error {
561 561
 func (container *Container) Kill() error {
562 562
 	container.State.lock()
563 563
 	defer container.State.unlock()
564
+	if !container.State.Running {
565
+		return nil
566
+	}
567
+	if container.State.Ghost {
568
+		return fmt.Errorf("Can't kill ghost container")
569
+	}
564 570
 	return container.kill()
565 571
 }
566 572
 
567
-func (container *Container) Stop() error {
573
+func (container *Container) Stop(seconds int) error {
568 574
 	container.State.lock()
569 575
 	defer container.State.unlock()
570 576
 	if !container.State.Running {
571 577
 		return nil
572 578
 	}
579
+	if container.State.Ghost {
580
+		return fmt.Errorf("Can't stop ghost container")
581
+	}
573 582
 
574 583
 	// 1. Send a SIGTERM
575 584
 	if output, err := exec.Command("lxc-kill", "-n", container.Id, "15").CombinedOutput(); err != nil {
... ...
@@ -581,8 +643,8 @@ func (container *Container) Stop() error {
581 581
 	}
582 582
 
583 583
 	// 2. Wait for the process to exit on its own
584
-	if err := container.WaitTimeout(10 * time.Second); err != nil {
585
-		log.Printf("Container %v failed to exit within 10 seconds of SIGTERM - using the force", container.Id)
584
+	if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil {
585
+		log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.Id, seconds)
586 586
 		if err := container.kill(); err != nil {
587 587
 			return err
588 588
 		}
... ...
@@ -590,8 +652,8 @@ func (container *Container) Stop() error {
590 590
 	return nil
591 591
 }
592 592
 
593
-func (container *Container) Restart() error {
594
-	if err := container.Stop(); err != nil {
593
+func (container *Container) Restart(seconds int) error {
594
+	if err := container.Stop(seconds); err != nil {
595 595
 		return err
596 596
 	}
597 597
 	if err := container.Start(); err != nil {
... ...
@@ -97,7 +97,7 @@ func TestMultipleAttachRestart(t *testing.T) {
97 97
 		t.Fatalf("Unexpected output. Expected [%s], received [%s]", "hello", l3)
98 98
 	}
99 99
 
100
-	if err := container.Stop(); err != nil {
100
+	if err := container.Stop(10); err != nil {
101 101
 		t.Fatal(err)
102 102
 	}
103 103
 
... ...
@@ -182,7 +182,7 @@ func TestCommitRun(t *testing.T) {
182 182
 	if err != nil {
183 183
 		t.Error(err)
184 184
 	}
185
-	img, err := runtime.graph.Create(rwTar, container1, "unit test commited image")
185
+	img, err := runtime.graph.Create(rwTar, container1, "unit test commited image", "")
186 186
 	if err != nil {
187 187
 		t.Error(err)
188 188
 	}
... ...
@@ -324,6 +324,54 @@ func TestOutput(t *testing.T) {
324 324
 	}
325 325
 }
326 326
 
327
+func TestKillDifferentUser(t *testing.T) {
328
+	runtime, err := newTestRuntime()
329
+	if err != nil {
330
+		t.Fatal(err)
331
+	}
332
+	defer nuke(runtime)
333
+	container, err := runtime.Create(&Config{
334
+		Image: GetTestImage(runtime).Id,
335
+		Cmd:   []string{"tail", "-f", "/etc/resolv.conf"},
336
+		User:  "daemon",
337
+	},
338
+	)
339
+	if err != nil {
340
+		t.Fatal(err)
341
+	}
342
+	defer runtime.Destroy(container)
343
+
344
+	if container.State.Running {
345
+		t.Errorf("Container shouldn't be running")
346
+	}
347
+	if err := container.Start(); err != nil {
348
+		t.Fatal(err)
349
+	}
350
+
351
+	// Give some time to lxc to spawn the process (setuid might take some time)
352
+	container.WaitTimeout(500 * time.Millisecond)
353
+
354
+	if !container.State.Running {
355
+		t.Errorf("Container should be running")
356
+	}
357
+
358
+	if err := container.Kill(); err != nil {
359
+		t.Fatal(err)
360
+	}
361
+
362
+	if container.State.Running {
363
+		t.Errorf("Container shouldn't be running")
364
+	}
365
+	container.Wait()
366
+	if container.State.Running {
367
+		t.Errorf("Container shouldn't be running")
368
+	}
369
+	// Try stopping twice
370
+	if err := container.Kill(); err != nil {
371
+		t.Fatal(err)
372
+	}
373
+}
374
+
327 375
 func TestKill(t *testing.T) {
328 376
 	runtime, err := newTestRuntime()
329 377
 	if err != nil {
... ...
@@ -346,6 +394,10 @@ func TestKill(t *testing.T) {
346 346
 	if err := container.Start(); err != nil {
347 347
 		t.Fatal(err)
348 348
 	}
349
+
350
+	// Give some time to lxc to spawn the process
351
+	container.WaitTimeout(500 * time.Millisecond)
352
+
349 353
 	if !container.State.Running {
350 354
 		t.Errorf("Container should be running")
351 355
 	}
... ...
@@ -657,6 +709,10 @@ func TestMultipleContainers(t *testing.T) {
657 657
 		t.Fatal(err)
658 658
 	}
659 659
 
660
+	// Make sure they are running before trying to kill them
661
+	container1.WaitTimeout(250 * time.Millisecond)
662
+	container2.WaitTimeout(250 * time.Millisecond)
663
+
660 664
 	// If we are here, both containers should be running
661 665
 	if !container1.State.Running {
662 666
 		t.Fatal("Container not running")
663 667
new file mode 100644
... ...
@@ -0,0 +1,96 @@
0
+package main
1
+
2
+import (
3
+	"io"
4
+	"log"
5
+	"os"
6
+	"os/exec"
7
+	"time"
8
+)
9
+
10
+const DOCKER_PATH = "/home/creack/dotcloud/docker/docker/docker"
11
+
12
+func runDaemon() (*exec.Cmd, error) {
13
+	os.Remove("/var/run/docker.pid")
14
+	cmd := exec.Command(DOCKER_PATH, "-d")
15
+	outPipe, err := cmd.StdoutPipe()
16
+	if err != nil {
17
+		return nil, err
18
+	}
19
+	errPipe, err := cmd.StderrPipe()
20
+	if err != nil {
21
+		return nil, err
22
+	}
23
+	if err := cmd.Start(); err != nil {
24
+		return nil, err
25
+	}
26
+	go func() {
27
+		io.Copy(os.Stdout, outPipe)
28
+	}()
29
+	go func() {
30
+		io.Copy(os.Stderr, errPipe)
31
+	}()
32
+	return cmd, nil
33
+}
34
+
35
+func crashTest() error {
36
+	if err := exec.Command("/bin/bash", "-c", "while true; do true; done").Start(); err != nil {
37
+		return err
38
+	}
39
+
40
+	for {
41
+		daemon, err := runDaemon()
42
+		if err != nil {
43
+			return err
44
+		}
45
+		//		time.Sleep(5000 * time.Millisecond)
46
+		var stop bool
47
+		go func() error {
48
+			stop = false
49
+			for i := 0; i < 100 && !stop; i++ {
50
+				func() error {
51
+					cmd := exec.Command(DOCKER_PATH, "run", "base", "echo", "hello", "world")
52
+					log.Printf("%d", i)
53
+					outPipe, err := cmd.StdoutPipe()
54
+					if err != nil {
55
+						return err
56
+					}
57
+					inPipe, err := cmd.StdinPipe()
58
+					if err != nil {
59
+						return err
60
+					}
61
+					if err := cmd.Start(); err != nil {
62
+						return err
63
+					}
64
+					go func() {
65
+						io.Copy(os.Stdout, outPipe)
66
+					}()
67
+					// Expecting error, do not check
68
+					inPipe.Write([]byte("hello world!!!!!\n"))
69
+					go inPipe.Write([]byte("hello world!!!!!\n"))
70
+					go inPipe.Write([]byte("hello world!!!!!\n"))
71
+					inPipe.Close()
72
+
73
+					if err := cmd.Wait(); err != nil {
74
+						return err
75
+					}
76
+					outPipe.Close()
77
+					return nil
78
+				}()
79
+			}
80
+			return nil
81
+		}()
82
+		time.Sleep(20 * time.Second)
83
+		stop = true
84
+		if err := daemon.Process.Kill(); err != nil {
85
+			return err
86
+		}
87
+	}
88
+	return nil
89
+}
90
+
91
+func main() {
92
+	if err := crashTest(); err != nil {
93
+		log.Println(err)
94
+	}
95
+}
0 96
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+# docker-build: build your software with docker
1
+
2
+## Description
3
+
4
+docker-build is a script to build docker images from source. It will be deprecated once the 'build' feature is incorporated into docker itself (See https://github.com/dotcloud/docker/issues/278)
5
+
6
+Author: Solomon Hykes <solomon@dotcloud.com>
7
+
8
+
9
+## Install
10
+
11
+docker-builder requires:
12
+
13
+1) A reasonably recent Python setup (tested on 2.7.2).
14
+
15
+2) A running docker daemon at version 0.1.4 or more recent (http://www.docker.io/gettingstarted)
16
+
17
+
18
+## Usage
19
+
20
+First create a valid Changefile, which defines a sequence of changes to apply to a base image.
21
+
22
+    $ cat Changefile
23
+    # Start build from a know base image
24
+    from	base:ubuntu-12.10
25
+    # Update ubuntu sources
26
+    run	echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
27
+    run	apt-get update
28
+    # Install system packages
29
+    run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
30
+    run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
31
+    run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
32
+    # Insert files from the host (./myscript must be present in the current directory)
33
+    copy	myscript /usr/local/bin/myscript
34
+
35
+
36
+Run docker-build, and pass the contents of your Changefile as standard input.
37
+
38
+    $ IMG=$(./docker-build < Changefile)
39
+
40
+This will take a while: for each line of the changefile, docker-build will:
41
+
42
+1. Create a new container to execute the given command or insert the given file
43
+2. Wait for the container to complete execution
44
+3. Commit the resulting changes as a new image
45
+4. Use the resulting image as the input of the next step
46
+
47
+
48
+If all the steps succeed, the result will be an image containing the combined results of each build step.
49
+You can trace back those build steps by inspecting the image's history:
50
+
51
+    $ docker history $IMG
52
+    ID                  CREATED             CREATED BY
53
+    1e9e2045de86        A few seconds ago   /bin/sh -c cat > /usr/local/bin/myscript; chmod +x /usr/local/bin/git
54
+    77db140aa62a        A few seconds ago   /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
55
+    77db140aa62a        A few seconds ago   /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
56
+    77db140aa62a        A few seconds ago   /bin/sh -c DEBIAN_FRONTEND=noninteractive apt-get install -y -q git 
57
+    83e85d155451        A few seconds ago   /bin/sh -c apt-get update
58
+    bfd53b36d9d3        A few seconds ago   /bin/sh -c echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
59
+    base		2 weeks ago         /bin/bash
60
+    27cf78414709        2 weeks ago
61
+
62
+
63
+Note that your build started from 'base', as instructed by your Changefile. But that base image itself seems to have been built in 2 steps - hence the extra step in the history.
64
+
65
+
66
+You can use this build technique to create any image you want: a database, a web application, or anything else that can be build by a sequence of unix commands - in other words, anything else.
67
+
0 68
new file mode 100755
... ...
@@ -0,0 +1,104 @@
0
+#!/usr/bin/env python
1
+
2
+# docker-build is a script to build docker images from source.
3
+# It will be deprecated once the 'build' feature is incorporated into docker itself.
4
+# (See https://github.com/dotcloud/docker/issues/278)
5
+#
6
+# Author: Solomon Hykes <solomon@dotcloud.com>
7
+
8
+
9
+
10
+# First create a valid Changefile, which defines a sequence of changes to apply to a base image.
11
+# 
12
+#     $ cat Changefile
13
+#     # Start build from a know base image
14
+#     from	base:ubuntu-12.10
15
+#     # Update ubuntu sources
16
+#     run	echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
17
+#     run	apt-get update
18
+#     # Install system packages
19
+#     run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
20
+#     run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
21
+#     run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
22
+#     # Insert files from the host (./myscript must be present in the current directory)
23
+#     copy	myscript /usr/local/bin/myscript
24
+# 
25
+# 
26
+# Run docker-build, and pass the contents of your Changefile as standard input.
27
+# 
28
+#     $ IMG=$(./docker-build < Changefile)
29
+# 
30
+# This will take a while: for each line of the changefile, docker-build will:
31
+# 
32
+# 1. Create a new container to execute the given command or insert the given file
33
+# 2. Wait for the container to complete execution
34
+# 3. Commit the resulting changes as a new image
35
+# 4. Use the resulting image as the input of the next step
36
+
37
+
38
+import sys
39
+import subprocess
40
+import json
41
+import hashlib
42
+
43
+def docker(args, stdin=None):
44
+	print "# docker " + " ".join(args)
45
+	p = subprocess.Popen(["docker"] + list(args), stdin=stdin, stdout=subprocess.PIPE)
46
+	return p.stdout
47
+
48
+def image_exists(img):
49
+	return docker(["inspect", img]).read().strip() != ""
50
+
51
+def run_and_commit(img_in, cmd, stdin=None):
52
+	run_id = docker(["run"] + (["-i", "-a", "stdin"] if stdin else ["-d"]) + [img_in, "/bin/sh", "-c", cmd], stdin=stdin).read().rstrip()
53
+	print "---> Waiting for " + run_id
54
+	result=int(docker(["wait", run_id]).read().rstrip())
55
+	if result != 0:
56
+		print "!!! '{}' return non-zero exit code '{}'. Aborting.".format(cmd, result)
57
+		sys.exit(1)
58
+	return docker(["commit", run_id]).read().rstrip()
59
+
60
+def insert(base, src, dst):
61
+	print "COPY {} to {} in {}".format(src, dst, base)
62
+	if dst == "":
63
+		raise Exception("Missing destination path")
64
+	stdin = file(src)
65
+	stdin.seek(0)
66
+	return run_and_commit(base, "cat > {0}; chmod +x {0}".format(dst), stdin=stdin)
67
+	
68
+
69
+def main():
70
+	base=""
71
+	steps = []
72
+	try:
73
+		for line in sys.stdin.readlines():
74
+			line = line.strip()
75
+			# Skip comments and empty lines
76
+			if line == "" or line[0] == "#":
77
+				continue
78
+			op, param = line.split("	", 1)
79
+			if op == "from":
80
+				print "FROM " + param
81
+				base = param
82
+				steps.append(base)
83
+			elif op == "run":
84
+				print "RUN " + param
85
+				result = run_and_commit(base, param)
86
+				steps.append(result)
87
+				base = result
88
+				print "===> " + base
89
+			elif op == "copy":
90
+				src, dst = param.split("	", 1)
91
+				result = insert(base, src, dst)
92
+				steps.append(result)
93
+				base = result
94
+				print "===> " + base
95
+			else:
96
+				print "Skipping uknown op " + op
97
+	except:
98
+		docker(["rmi"] + steps[1:])
99
+		raise
100
+	print base
101
+
102
+if __name__ == "__main__":
103
+	main()
0 104
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+# Start build from a know base image
1
+from	base:ubuntu-12.10
2
+# Update ubuntu sources
3
+run	echo 'deb http://archive.ubuntu.com/ubuntu quantal main universe multiverse' > /etc/apt/sources.list
4
+run	apt-get update
5
+# Install system packages
6
+run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
7
+run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
8
+run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang
9
+# Insert files from the host (./myscript must be present in the current directory)
10
+copy	myscript /usr/local/bin/myscript
... ...
@@ -45,7 +45,7 @@ then
45 45
   echo "Upstart script already exists."
46 46
 else
47 47
   echo "Creating /etc/init/dockerd.conf..."
48
-  echo "exec /usr/local/bin/docker -d" > /etc/init/dockerd.conf
48
+  echo "exec env LANG=\"en_US.UTF-8\" /usr/local/bin/docker -d" > /etc/init/dockerd.conf
49 49
 fi
50 50
 
51 51
 echo "Starting dockerd..."
52 52
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+# Vagrant-docker
1
+
2
+This is a placeholder for the official vagrant-docker, a plugin for Vagrant (http://vagrantup.com) which exposes Docker as a provider.
0 3
deleted file mode 120000
... ...
@@ -1 +0,0 @@
1
-../Makefile
2 1
\ No newline at end of file
3 2
deleted file mode 100644
... ...
@@ -1,73 +0,0 @@
1
-PKG_NAME=dotcloud-docker
2
-PKG_ARCH=amd64
3
-PKG_VERSION=1
4
-ROOT_PATH:=$(PWD)
5
-BUILD_PATH=build	# Do not change, decided by dpkg-buildpackage
6
-BUILD_SRC=build_src
7
-GITHUB_PATH=src/github.com/dotcloud/docker
8
-INSDIR=usr/bin
9
-SOURCE_PACKAGE=$(PKG_NAME)_$(PKG_VERSION).orig.tar.gz
10
-DEB_PACKAGE=$(PKG_NAME)_$(PKG_VERSION)_$(PKG_ARCH).deb
11
-EXTRA_GO_PKG=./auth
12
-
13
-TMPDIR=$(shell mktemp -d -t XXXXXX)
14
-
15
-
16
-# Build a debian source package
17
-all: clean build_in_deb
18
-
19
-build_in_deb:
20
-	echo "GOPATH = " $(ROOT_PATH)
21
-	mkdir bin
22
-	cd $(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH) go build -o $(ROOT_PATH)/bin/docker
23
-
24
-# DESTDIR provided by Debian packaging
25
-install:
26
-	# Call this from a go environment (as packaged for deb source package)
27
-	mkdir -p $(DESTDIR)/$(INSDIR)
28
-	mkdir -p $(DESTDIR)/etc/init
29
-	install -m 0755 bin/docker $(DESTDIR)/$(INSDIR)
30
-	install -o root -m 0755 etc/docker.upstart $(DESTDIR)/etc/init/docker.conf
31
-
32
-$(BUILD_SRC): clean
33
-	# Copy ourselves into $BUILD_SRC to comply with unusual golang constraints
34
-	tar --exclude=*.tar.gz --exclude=checkout.tgz -f checkout.tgz -cz *
35
-	mkdir -p $(BUILD_SRC)/$(GITHUB_PATH)
36
-	tar -f checkout.tgz -C $(BUILD_SRC)/$(GITHUB_PATH) -xz
37
-	cd $(BUILD_SRC)/$(GITHUB_PATH)/docker; GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go get -d
38
-	for d in `find $(BUILD_SRC) -name '.git*'`; do rm -rf $$d; done
39
-	# Populate source build with debian stuff
40
-	cp -R -L ./deb/* $(BUILD_SRC)
41
-
42
-$(SOURCE_PACKAGE): $(BUILD_SRC)
43
-	rm -f $(SOURCE_PACKAGE)
44
-	# Create the debian source package
45
-	tar -f $(SOURCE_PACKAGE) -C ${ROOT_PATH}/${BUILD_SRC} -cz .
46
-
47
-# Build deb package fetching go dependencies and cleaning up git repositories
48
-deb: $(DEB_PACKAGE)
49
-
50
-$(DEB_PACKAGE): $(SOURCE_PACKAGE)
51
-	# dpkg-buildpackage looks for source package tarball in ../
52
-	cd $(BUILD_SRC); dpkg-buildpackage
53
-	rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files
54
-
55
-debsrc: $(SOURCE_PACKAGE)
56
-
57
-# Build local sources
58
-#$(PKG_NAME): build_local
59
-
60
-build_local:
61
-	-@mkdir -p bin
62
-	cd docker && go build -o ../bin/docker
63
-
64
-gotest:
65
-	@echo "\033[36m[Testing]\033[00m docker..."
66
-	@sudo -E GOPATH=$(ROOT_PATH)/$(BUILD_SRC) go test -v . $(EXTRA_GO_PKG) && \
67
-		echo -n "\033[32m[OK]\033[00m" || \
68
-		echo -n "\033[31m[FAIL]\033[00m"; \
69
-		echo " docker"
70
-	@sudo rm -rf /tmp/docker-*
71
-
72
-clean:
73
-	rm -rf $(BUILD_PATH) debian/$(PKG_NAME)* debian/files $(BUILD_SRC) checkout.tgz bin
74 1
deleted file mode 120000
... ...
@@ -1 +0,0 @@
1
-../README.md
2 1
\ No newline at end of file
3 2
deleted file mode 100644
... ...
@@ -1,5 +0,0 @@
1
-dotcloud-docker (1) precise; urgency=low
2
-
3
-  * Initial release
4
-
5
- -- dotCloud <ops@dotcloud.com>  Mon, 14 Mar 2013 04:43:21 -0700
6 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-8
2 1
deleted file mode 100644
... ...
@@ -1,20 +0,0 @@
1
-Source: dotcloud-docker
2
-Section: misc
3
-Priority: extra
4
-Homepage: https://github.com/dotcloud/docker
5
-Maintainer: Daniel Mizyrycki <daniel@dotcloud.com>
6
-Build-Depends: debhelper (>= 8.0.0), git, golang
7
-Vcs-Git: https://github.com/dotcloud/docker.git
8
-Standards-Version: 3.9.2
9
-
10
-Package: dotcloud-docker
11
-Architecture: amd64
12
-Provides: dotcloud-docker
13
-Depends: lxc, wget, bsdtar, curl
14
-Conflicts: docker
15
-Description: A process manager with superpowers
16
-    It encapsulates heterogeneous payloads in Standard Containers, and runs
17
-    them on any server with strong guarantees of isolation and repeatability.
18
-    Is is a great building block for automating distributed systems:
19
-    large-scale web deployments, database clusters, continuous deployment
20
-    systems, private PaaS, service-oriented architectures, etc.
21 1
deleted file mode 100644
... ...
@@ -1,209 +0,0 @@
1
-Format: http://dep.debian.net/deps/dep5
2
-Upstream-Name: dotcloud-docker
3
-Source: https://github.com/dotcloud/docker
4
-
5
-Files: *
6
-Copyright: 2012 DotCloud Inc (opensource@dotcloud.com)
7
-License: Apache License Version 2.0
8
-
9
-                                Apache License
10
-                           Version 2.0, January 2004
11
-                        http://www.apache.org/licenses/
12
-
13
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
14
-
15
-   1. Definitions.
16
-
17
-      "License" shall mean the terms and conditions for use, reproduction,
18
-      and distribution as defined by Sections 1 through 9 of this document.
19
-
20
-      "Licensor" shall mean the copyright owner or entity authorized by
21
-      the copyright owner that is granting the License.
22
-
23
-      "Legal Entity" shall mean the union of the acting entity and all
24
-      other entities that control, are controlled by, or are under common
25
-      control with that entity. For the purposes of this definition,
26
-      "control" means (i) the power, direct or indirect, to cause the
27
-      direction or management of such entity, whether by contract or
28
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
29
-      outstanding shares, or (iii) beneficial ownership of such entity.
30
-
31
-      "You" (or "Your") shall mean an individual or Legal Entity
32
-      exercising permissions granted by this License.
33
-
34
-      "Source" form shall mean the preferred form for making modifications,
35
-      including but not limited to software source code, documentation
36
-      source, and configuration files.
37
-
38
-      "Object" form shall mean any form resulting from mechanical
39
-      transformation or translation of a Source form, including but
40
-      not limited to compiled object code, generated documentation,
41
-      and conversions to other media types.
42
-
43
-      "Work" shall mean the work of authorship, whether in Source or
44
-      Object form, made available under the License, as indicated by a
45
-      copyright notice that is included in or attached to the work
46
-      (an example is provided in the Appendix below).
47
-
48
-      "Derivative Works" shall mean any work, whether in Source or Object
49
-      form, that is based on (or derived from) the Work and for which the
50
-      editorial revisions, annotations, elaborations, or other modifications
51
-      represent, as a whole, an original work of authorship. For the purposes
52
-      of this License, Derivative Works shall not include works that remain
53
-      separable from, or merely link (or bind by name) to the interfaces of,
54
-      the Work and Derivative Works thereof.
55
-
56
-      "Contribution" shall mean any work of authorship, including
57
-      the original version of the Work and any modifications or additions
58
-      to that Work or Derivative Works thereof, that is intentionally
59
-      submitted to Licensor for inclusion in the Work by the copyright owner
60
-      or by an individual or Legal Entity authorized to submit on behalf of
61
-      the copyright owner. For the purposes of this definition, "submitted"
62
-      means any form of electronic, verbal, or written communication sent
63
-      to the Licensor or its representatives, including but not limited to
64
-      communication on electronic mailing lists, source code control systems,
65
-      and issue tracking systems that are managed by, or on behalf of, the
66
-      Licensor for the purpose of discussing and improving the Work, but
67
-      excluding communication that is conspicuously marked or otherwise
68
-      designated in writing by the copyright owner as "Not a Contribution."
69
-
70
-      "Contributor" shall mean Licensor and any individual or Legal Entity
71
-      on behalf of whom a Contribution has been received by Licensor and
72
-      subsequently incorporated within the Work.
73
-
74
-   2. Grant of Copyright License. Subject to the terms and conditions of
75
-      this License, each Contributor hereby grants to You a perpetual,
76
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
-      copyright license to reproduce, prepare Derivative Works of,
78
-      publicly display, publicly perform, sublicense, and distribute the
79
-      Work and such Derivative Works in Source or Object form.
80
-
81
-   3. Grant of Patent License. Subject to the terms and conditions of
82
-      this License, each Contributor hereby grants to You a perpetual,
83
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
84
-      (except as stated in this section) patent license to make, have made,
85
-      use, offer to sell, sell, import, and otherwise transfer the Work,
86
-      where such license applies only to those patent claims licensable
87
-      by such Contributor that are necessarily infringed by their
88
-      Contribution(s) alone or by combination of their Contribution(s)
89
-      with the Work to which such Contribution(s) was submitted. If You
90
-      institute patent litigation against any entity (including a
91
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
92
-      or a Contribution incorporated within the Work constitutes direct
93
-      or contributory patent infringement, then any patent licenses
94
-      granted to You under this License for that Work shall terminate
95
-      as of the date such litigation is filed.
96
-
97
-   4. Redistribution. You may reproduce and distribute copies of the
98
-      Work or Derivative Works thereof in any medium, with or without
99
-      modifications, and in Source or Object form, provided that You
100
-      meet the following conditions:
101
-
102
-      (a) You must give any other recipients of the Work or
103
-          Derivative Works a copy of this License; and
104
-
105
-      (b) You must cause any modified files to carry prominent notices
106
-          stating that You changed the files; and
107
-
108
-      (c) You must retain, in the Source form of any Derivative Works
109
-          that You distribute, all copyright, patent, trademark, and
110
-          attribution notices from the Source form of the Work,
111
-          excluding those notices that do not pertain to any part of
112
-          the Derivative Works; and
113
-
114
-      (d) If the Work includes a "NOTICE" text file as part of its
115
-          distribution, then any Derivative Works that You distribute must
116
-          include a readable copy of the attribution notices contained
117
-          within such NOTICE file, excluding those notices that do not
118
-          pertain to any part of the Derivative Works, in at least one
119
-          of the following places: within a NOTICE text file distributed
120
-          as part of the Derivative Works; within the Source form or
121
-          documentation, if provided along with the Derivative Works; or,
122
-          within a display generated by the Derivative Works, if and
123
-          wherever such third-party notices normally appear. The contents
124
-          of the NOTICE file are for informational purposes only and
125
-          do not modify the License. You may add Your own attribution
126
-          notices within Derivative Works that You distribute, alongside
127
-          or as an addendum to the NOTICE text from the Work, provided
128
-          that such additional attribution notices cannot be construed
129
-          as modifying the License.
130
-
131
-      You may add Your own copyright statement to Your modifications and
132
-      may provide additional or different license terms and conditions
133
-      for use, reproduction, or distribution of Your modifications, or
134
-      for any such Derivative Works as a whole, provided Your use,
135
-      reproduction, and distribution of the Work otherwise complies with
136
-      the conditions stated in this License.
137
-
138
-   5. Submission of Contributions. Unless You explicitly state otherwise,
139
-      any Contribution intentionally submitted for inclusion in the Work
140
-      by You to the Licensor shall be under the terms and conditions of
141
-      this License, without any additional terms or conditions.
142
-      Notwithstanding the above, nothing herein shall supersede or modify
143
-      the terms of any separate license agreement you may have executed
144
-      with Licensor regarding such Contributions.
145
-
146
-   6. Trademarks. This License does not grant permission to use the trade
147
-      names, trademarks, service marks, or product names of the Licensor,
148
-      except as required for reasonable and customary use in describing the
149
-      origin of the Work and reproducing the content of the NOTICE file.
150
-
151
-   7. Disclaimer of Warranty. Unless required by applicable law or
152
-      agreed to in writing, Licensor provides the Work (and each
153
-      Contributor provides its Contributions) on an "AS IS" BASIS,
154
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
155
-      implied, including, without limitation, any warranties or conditions
156
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
157
-      PARTICULAR PURPOSE. You are solely responsible for determining the
158
-      appropriateness of using or redistributing the Work and assume any
159
-      risks associated with Your exercise of permissions under this License.
160
-
161
-   8. Limitation of Liability. In no event and under no legal theory,
162
-      whether in tort (including negligence), contract, or otherwise,
163
-      unless required by applicable law (such as deliberate and grossly
164
-      negligent acts) or agreed to in writing, shall any Contributor be
165
-      liable to You for damages, including any direct, indirect, special,
166
-      incidental, or consequential damages of any character arising as a
167
-      result of this License or out of the use or inability to use the
168
-      Work (including but not limited to damages for loss of goodwill,
169
-      work stoppage, computer failure or malfunction, or any and all
170
-      other commercial damages or losses), even if such Contributor
171
-      has been advised of the possibility of such damages.
172
-
173
-   9. Accepting Warranty or Additional Liability. While redistributing
174
-      the Work or Derivative Works thereof, You may choose to offer,
175
-      and charge a fee for, acceptance of support, warranty, indemnity,
176
-      or other liability obligations and/or rights consistent with this
177
-      License. However, in accepting such obligations, You may act only
178
-      on Your own behalf and on Your sole responsibility, not on behalf
179
-      of any other Contributor, and only if You agree to indemnify,
180
-      defend, and hold each Contributor harmless for any liability
181
-      incurred by, or claims asserted against, such Contributor by reason
182
-      of your accepting any such warranty or additional liability.
183
-
184
-   END OF TERMS AND CONDITIONS
185
-
186
-   APPENDIX: How to apply the Apache License to your work.
187
-
188
-      To apply the Apache License to your work, attach the following
189
-      boilerplate notice, with the fields enclosed by brackets "[]"
190
-      replaced with your own identifying information. (Don't include
191
-      the brackets!) The text should be enclosed in the appropriate
192
-      comment syntax for the file format. We also recommend that a
193
-      file or class name and description of purpose be included on the
194
-      same "printed page" as the copyright notice for easier
195
-      identification within third-party archives.
196
-
197
-   Copyright 2012 DotCloud Inc (opensource@dotcloud.com)
198
-
199
-   Licensed under the Apache License, Version 2.0 (the "License");
200
-   you may not use this file except in compliance with the License.
201
-   You may obtain a copy of the License at
202
-
203
-       http://www.apache.org/licenses/LICENSE-2.0
204
-
205
-   Unless required by applicable law or agreed to in writing, software
206
-   distributed under the License is distributed on an "AS IS" BASIS,
207
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
208
-   See the License for the specific language governing permissions and
209
-   limitations under the License.
210 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-README.md
2 1
deleted file mode 100755
... ...
@@ -1,13 +0,0 @@
1
-#!/usr/bin/make -f
2
-# -*- makefile -*-
3
-# Sample debian/rules that uses debhelper.
4
-# This file was originally written by Joey Hess and Craig Small.
5
-# As a special exception, when this file is copied by dh-make into a
6
-# dh-make output file, you may use that output file without restriction.
7
-# This special exception was added by Craig Small in version 0.37 of dh-make.
8
-
9
-# Uncomment this to turn on verbose mode.
10
-#export DH_VERBOSE=1
11
-
12
-%:
13
-	dh $@
14 1
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-3.0 (quilt)
2 1
deleted file mode 100644
... ...
@@ -1,10 +0,0 @@
1
-description     "Run docker"
2
-
3
-start on runlevel [2345]
4
-stop on starting rc RUNLEVEL=[016]
5
-respawn
6
-
7
-script
8
-    test -f /etc/default/locale && . /etc/default/locale || true
9
-    LANG=$LANG LC_ALL=$LANG /usr/bin/docker -d
10
-end script
11 1
deleted file mode 100644
... ...
@@ -1,10 +0,0 @@
1
-description     "Run docker"
2
-
3
-start on runlevel [2345]
4
-stop on starting rc RUNLEVEL=[016]
5
-respawn
6
-
7
-script
8
-    test -f /etc/default/locale && . /etc/default/locale || true
9
-    LANG=$LANG LC_ALL=$LANG /usr/bin/docker -d
10
-end script
... ...
@@ -2,15 +2,20 @@ package main
2 2
 
3 3
 import (
4 4
 	"flag"
5
+	"fmt"
5 6
 	"github.com/dotcloud/docker"
6 7
 	"github.com/dotcloud/docker/rcli"
7 8
 	"github.com/dotcloud/docker/term"
8 9
 	"io"
9 10
 	"log"
10 11
 	"os"
12
+	"os/signal"
13
+	"syscall"
11 14
 )
12 15
 
13
-var GIT_COMMIT string
16
+var (
17
+	GIT_COMMIT string
18
+)
14 19
 
15 20
 func main() {
16 21
 	if docker.SelfPath() == "/sbin/init" {
... ...
@@ -22,6 +27,7 @@ func main() {
22 22
 	flDaemon := flag.Bool("d", false, "Daemon mode")
23 23
 	flDebug := flag.Bool("D", false, "Debug mode")
24 24
 	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
25
+	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
25 26
 	flag.Parse()
26 27
 	if *bridgeName != "" {
27 28
 		docker.NetworkBridgeIface = *bridgeName
... ...
@@ -37,7 +43,7 @@ func main() {
37 37
 			flag.Usage()
38 38
 			return
39 39
 		}
40
-		if err := daemon(); err != nil {
40
+		if err := daemon(*pidfile); err != nil {
41 41
 			log.Fatal(err)
42 42
 		}
43 43
 	} else {
... ...
@@ -47,7 +53,43 @@ func main() {
47 47
 	}
48 48
 }
49 49
 
50
-func daemon() error {
50
+func createPidFile(pidfile string) error {
51
+	if _, err := os.Stat(pidfile); err == nil {
52
+		return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile)
53
+	}
54
+
55
+	file, err := os.Create(pidfile)
56
+	if err != nil {
57
+		return err
58
+	}
59
+
60
+	defer file.Close()
61
+
62
+	_, err = fmt.Fprintf(file, "%d", os.Getpid())
63
+	return err
64
+}
65
+
66
+func removePidFile(pidfile string) {
67
+	if err := os.Remove(pidfile); err != nil {
68
+		log.Printf("Error removing %s: %s", pidfile, err)
69
+	}
70
+}
71
+
72
+func daemon(pidfile string) error {
73
+	if err := createPidFile(pidfile); err != nil {
74
+		log.Fatal(err)
75
+	}
76
+	defer removePidFile(pidfile)
77
+
78
+	c := make(chan os.Signal, 1)
79
+	signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
80
+	go func() {
81
+		sig := <-c
82
+		log.Printf("Received signal '%v', exiting\n", sig)
83
+		removePidFile(pidfile)
84
+		os.Exit(0)
85
+	}()
86
+
51 87
 	service, err := docker.NewServer()
52 88
 	if err != nil {
53 89
 		return err
... ...
@@ -91,15 +133,7 @@ func runCommand(args []string) error {
91 91
 			}
92 92
 		}
93 93
 	} else {
94
-		service, err := docker.NewServer()
95
-		if err != nil {
96
-			return err
97
-		}
98
-		dockerConn := rcli.NewDockerLocalConn(os.Stdout)
99
-		defer dockerConn.Close()
100
-		if err := rcli.LocalCall(service, os.Stdin, dockerConn, args...); err != nil {
101
-			return err
102
-		}
94
+		return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
103 95
 	}
104 96
 	return nil
105 97
 }
... ...
@@ -7,27 +7,16 @@
7 7
 Running The Examples
8 8
 --------------------
9 9
 
10
-There are two ways to run docker, daemon mode and standalone mode.
11
-
12
-When you run the docker command it will first check if there is a docker daemon running in the background it can connect to.
13
-
14
-* If it exists it will use that daemon to run all of the commands.
15
-* If it does not exist docker will run in standalone mode (docker will exit after each command).
16
-
17
-Docker needs to be run from a privileged account (root).
18
-
19
-1. The most common (and recommended) way is to run a docker daemon as root in the background, and then connect to it from the docker client from any account.
10
+All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
20 11
 
21 12
    .. code-block:: bash
22 13
 
23
-      # starting docker daemon in the background
24 14
       sudo docker -d &
25 15
 
26
-      # now you can run docker commands from any account.
27
-      docker <command>
28
-
29
-2. Standalone: You need to run every command as root, or using sudo
16
+Now you can run docker in client mode: all commands will be forwarded to the docker daemon, so the client
17
+can run from any account.
30 18
 
31 19
    .. code-block:: bash
32 20
 
33
-       sudo docker <command>
21
+      # now you can run docker commands from any account.
22
+      docker help
... ...
@@ -1,8 +1,9 @@
1 1
 Amazon EC2
2 2
 ==========
3 3
 
4
-    Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version
5
-    may be out of date because it depends on some binaries to be updated and published
4
+  Please note this is a community contributed installation path. The only 'official' installation is using the
5
+  :ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
6
+
6 7
 
7 8
 Installation
8 9
 ------------
... ...
@@ -17,7 +18,7 @@ Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant
17 17
        vagrant plugin install vagrant-aws
18 18
 
19 19
 
20
-3. Get the docker sources, this will give you the latest Vagrantfile and puppet manifests.
20
+3. Get the docker sources, this will give you the latest Vagrantfile.
21 21
 
22 22
    ::
23 23
 
24 24
new file mode 100644
... ...
@@ -0,0 +1,64 @@
0
+.. _arch_linux:
1
+
2
+Arch Linux
3
+==========
4
+
5
+  Please note this is a community contributed installation path. The only 'official' installation is using the
6
+  :ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
7
+
8
+
9
+Installing on Arch Linux is not officially supported but can be handled via 
10
+either of the following AUR packages:
11
+
12
+* `lxc-docker <https://aur.archlinux.org/packages/lxc-docker/>`_
13
+* `lxc-docker-git <https://aur.archlinux.org/packages/lxc-docker-git/>`_
14
+
15
+The lxc-docker package will install the latest tagged version of docker. 
16
+The lxc-docker-git package will build from the current master branch.
17
+
18
+Dependencies
19
+------------
20
+
21
+Docker depends on several packages which are specified as dependencies in
22
+either AUR package.
23
+
24
+* aufs3
25
+* bridge-utils
26
+* go
27
+* iproute2
28
+* linux-aufs_friendly
29
+* lxc
30
+
31
+Installation
32
+------------
33
+
34
+The instructions here assume **yaourt** is installed.  See 
35
+`Arch User Repository <https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages>`_
36
+for information on building and installing packages from the AUR if you have not
37
+done so before.
38
+
39
+Keep in mind that if **linux-aufs_friendly** is not already installed that a
40
+new kernel will be compiled and this can take quite a while.
41
+
42
+::
43
+
44
+    yaourt -S lxc-docker-git
45
+
46
+Starting Docker
47
+---------------
48
+
49
+Prior to starting docker modify your bootloader to use the 
50
+**linux-aufs_friendly** kernel and reboot your system.
51
+
52
+There is a systemd service unit created for docker.  To start the docker service:
53
+
54
+::
55
+
56
+    sudo systemctl start docker
57
+
58
+
59
+To start on system boot:
60
+
61
+::
62
+
63
+    sudo systemctl enable docker
... ...
@@ -13,6 +13,7 @@ Contents:
13 13
    :maxdepth: 1
14 14
 
15 15
    ubuntulinux
16
+   archlinux
16 17
    vagrant
17 18
    windows
18 19
    amazon
... ...
@@ -1,5 +1,10 @@
1
-Docker on Ubuntu
2
-================
1
+.. _ubuntu_linux:
2
+
3
+Ubuntu Linux
4
+============
5
+
6
+  **Please note this project is currently under heavy development. It should not be used in production.**
7
+
3 8
 
4 9
 Docker is now available as a Ubuntu PPA (Personal Package Archive),
5 10
 `hosted on launchpad  <https://launchpad.net/~dotcloud/+archive/lxc-docker>`_
... ...
@@ -15,8 +20,7 @@ Add the custom package sources to your apt sources list. Copy and paste both the
15 15
 
16 16
 .. code-block:: bash
17 17
 
18
-   sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' \
19
-   >> /etc/apt/sources.list"
18
+   sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"
20 19
 
21 20
 
22 21
 Update your sources. You will see a warning that GPG signatures cannot be verified.
... ...
@@ -33,12 +37,11 @@ Now install it, you will see another warning that the package cannot be authenti
33 33
    sudo apt-get install lxc-docker
34 34
 
35 35
 
36
-**Run!**
36
+Verify it worked
37 37
 
38 38
 .. code-block:: bash
39 39
 
40 40
    docker
41 41
 
42 42
 
43
-
44
-Probably you would like to continue with the :ref:`hello_world` example.
45 43
\ No newline at end of file
44
+**Done!**, now continue with the :ref:`hello_world` example.
... ...
@@ -7,7 +7,7 @@ Install using Vagrant
7 7
   Please note this is a community contributed installation path. The only 'official' installation is using the
8 8
   :ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
9 9
 
10
-**requirements**
10
+**Requirements:**
11 11
 This guide will setup a new virtual machine with docker installed on your computer. This works on most operating
12 12
 systems, including MacOX, Windows, Linux, FreeBSD and others. If you can install these and have at least 400Mb RAM
13 13
 to spare you should be good.
... ...
@@ -22,10 +22,10 @@ Install Vagrant and Virtualbox
22 22
    ``git`` in a terminal window
23 23
 
24 24
 
25
-Spin up your machine
25
+Spin it up
26
+----------
26 27
 
27
-1. Fetch the docker sources (this includes the instructions for machine setup).
28
+1. Fetch the docker sources (this includes the Vagrantfile for machine setup).
28 29
 
29 30
 .. code-block:: bash
30 31
 
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"io"
5 6
 	"io/ioutil"
6 7
 	"os"
7 8
 	"path"
... ...
@@ -83,12 +84,13 @@ func (graph *Graph) Get(name string) (*Image, error) {
83 83
 }
84 84
 
85 85
 // Create creates a new image and registers it in the graph.
86
-func (graph *Graph) Create(layerData Archive, container *Container, comment string) (*Image, error) {
86
+func (graph *Graph) Create(layerData Archive, container *Container, comment, author string) (*Image, error) {
87 87
 	img := &Image{
88 88
 		Id:            GenerateId(),
89 89
 		Comment:       comment,
90 90
 		Created:       time.Now(),
91 91
 		DockerVersion: VERSION,
92
+		Author:        author,
92 93
 	}
93 94
 	if container != nil {
94 95
 		img.Parent = container.Image
... ...
@@ -111,7 +113,7 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
111 111
 	if graph.Exists(img.Id) {
112 112
 		return fmt.Errorf("Image %s already exists", img.Id)
113 113
 	}
114
-	tmp, err := graph.Mktemp(img.Id)
114
+	tmp, err := graph.Mktemp("")
115 115
 	defer os.RemoveAll(tmp)
116 116
 	if err != nil {
117 117
 		return fmt.Errorf("Mktemp failed: %s", err)
... ...
@@ -128,12 +130,32 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
128 128
 	return nil
129 129
 }
130 130
 
131
+// TempLayerArchive creates a temporary archive of the given image's filesystem layer.
132
+//   The archive is stored on disk and will be automatically deleted as soon as has been read.
133
+//   If output is not nil, a human-readable progress bar will be written to it.
134
+//   FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives?
135
+func (graph *Graph) TempLayerArchive(id string, compression Compression, output io.Writer) (*TempArchive, error) {
136
+	image, err := graph.Get(id)
137
+	if err != nil {
138
+		return nil, err
139
+	}
140
+	tmp, err := graph.tmp()
141
+	if err != nil {
142
+		return nil, err
143
+	}
144
+	archive, err := image.TarLayer(compression)
145
+	if err != nil {
146
+		return nil, err
147
+	}
148
+	return NewTempArchive(ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)"), tmp.Root)
149
+}
150
+
131 151
 // Mktemp creates a temporary sub-directory inside the graph's filesystem.
132 152
 func (graph *Graph) Mktemp(id string) (string, error) {
133 153
 	if id == "" {
134 154
 		id = GenerateId()
135 155
 	}
136
-	tmp, err := NewGraph(path.Join(graph.Root, ":tmp:"))
156
+	tmp, err := graph.tmp()
137 157
 	if err != nil {
138 158
 		return "", fmt.Errorf("Couldn't create temp: %s", err)
139 159
 	}
... ...
@@ -143,6 +165,10 @@ func (graph *Graph) Mktemp(id string) (string, error) {
143 143
 	return tmp.imageRoot(id), nil
144 144
 }
145 145
 
146
+func (graph *Graph) tmp() (*Graph, error) {
147
+	return NewGraph(path.Join(graph.Root, ":tmp:"))
148
+}
149
+
146 150
 // Check if given error is "not empty".
147 151
 // Note: this is the way golang does it internally with os.IsNotExists.
148 152
 func isNotEmpty(err error) bool {
... ...
@@ -3,6 +3,7 @@ package docker
3 3
 import (
4 4
 	"archive/tar"
5 5
 	"bytes"
6
+	"errors"
6 7
 	"io"
7 8
 	"io/ioutil"
8 9
 	"os"
... ...
@@ -26,6 +27,32 @@ func TestInit(t *testing.T) {
26 26
 	}
27 27
 }
28 28
 
29
+// Test that Register can be interrupted cleanly without side effects
30
+func TestInterruptedRegister(t *testing.T) {
31
+	graph := tempGraph(t)
32
+	defer os.RemoveAll(graph.Root)
33
+	badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data
34
+	image := &Image{
35
+		Id:      GenerateId(),
36
+		Comment: "testing",
37
+		Created: time.Now(),
38
+	}
39
+	go graph.Register(badArchive, image)
40
+	time.Sleep(200 * time.Millisecond)
41
+	w.CloseWithError(errors.New("But I'm not a tarball!")) // (Nobody's perfect, darling)
42
+	if _, err := graph.Get(image.Id); err == nil {
43
+		t.Fatal("Image should not exist after Register is interrupted")
44
+	}
45
+	// Registering the same image again should succeed if the first register was interrupted
46
+	goodArchive, err := fakeTar()
47
+	if err != nil {
48
+		t.Fatal(err)
49
+	}
50
+	if err := graph.Register(goodArchive, image); err != nil {
51
+		t.Fatal(err)
52
+	}
53
+}
54
+
29 55
 // FIXME: Do more extensive tests (ex: create multiple, delete, recreate;
30 56
 //       create multiple, check the amount of images and paths, etc..)
31 57
 func TestGraphCreate(t *testing.T) {
... ...
@@ -35,7 +62,7 @@ func TestGraphCreate(t *testing.T) {
35 35
 	if err != nil {
36 36
 		t.Fatal(err)
37 37
 	}
38
-	image, err := graph.Create(archive, nil, "Testing")
38
+	image, err := graph.Create(archive, nil, "Testing", "")
39 39
 	if err != nil {
40 40
 		t.Fatal(err)
41 41
 	}
... ...
@@ -95,7 +122,7 @@ func TestMount(t *testing.T) {
95 95
 	if err != nil {
96 96
 		t.Fatal(err)
97 97
 	}
98
-	image, err := graph.Create(archive, nil, "Testing")
98
+	image, err := graph.Create(archive, nil, "Testing", "")
99 99
 	if err != nil {
100 100
 		t.Fatal(err)
101 101
 	}
... ...
@@ -139,7 +166,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image {
139 139
 	if err != nil {
140 140
 		t.Fatal(err)
141 141
 	}
142
-	img, err := graph.Create(archive, nil, "Test image")
142
+	img, err := graph.Create(archive, nil, "Test image", "")
143 143
 	if err != nil {
144 144
 		t.Fatal(err)
145 145
 	}
... ...
@@ -154,7 +181,7 @@ func TestDelete(t *testing.T) {
154 154
 		t.Fatal(err)
155 155
 	}
156 156
 	assertNImages(graph, t, 0)
157
-	img, err := graph.Create(archive, nil, "Bla bla")
157
+	img, err := graph.Create(archive, nil, "Bla bla", "")
158 158
 	if err != nil {
159 159
 		t.Fatal(err)
160 160
 	}
... ...
@@ -165,11 +192,11 @@ func TestDelete(t *testing.T) {
165 165
 	assertNImages(graph, t, 0)
166 166
 
167 167
 	// Test 2 create (same name) / 1 delete
168
-	img1, err := graph.Create(archive, nil, "Testing")
168
+	img1, err := graph.Create(archive, nil, "Testing", "")
169 169
 	if err != nil {
170 170
 		t.Fatal(err)
171 171
 	}
172
-	if _, err = graph.Create(archive, nil, "Testing"); err != nil {
172
+	if _, err = graph.Create(archive, nil, "Testing", ""); err != nil {
173 173
 		t.Fatal(err)
174 174
 	}
175 175
 	assertNImages(graph, t, 2)
176 176
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+This directory contains material helpful for hacking on docker.
0 1
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+#!/bin/sh
1
+
2
+# This pre-commit hook will abort if a committed file doesn't pass gofmt.
3
+# By Even Shaw <edsrzf@gmail.com>
4
+# http://github.com/edsrzf/gofmt-git-hook
5
+
6
+test_fmt() {
7
+    hash gofmt 2>&- || { echo >&2 "gofmt not in PATH."; exit 1; }
8
+    IFS='
9
+'
10
+    for file in `git diff --cached --name-only --diff-filter=ACM | grep '\.go$'`
11
+    do
12
+        output=`git cat-file -p :$file | gofmt -l 2>&1`
13
+        if test $? -ne 0
14
+        then
15
+            output=`echo "$output" | sed "s,<standard input>,$file,"`
16
+            syntaxerrors="${list}${output}\n"
17
+        elif test -n "$output"
18
+        then
19
+            list="${list}${file}\n"
20
+        fi
21
+    done
22
+    exitcode=0
23
+    if test -n "$syntaxerrors"
24
+    then
25
+        echo >&2 "gofmt found syntax errors:"
26
+        printf "$syntaxerrors"
27
+        exitcode=1
28
+    fi
29
+    if test -n "$list"
30
+    then
31
+        echo >&2 "gofmt needs to format these files (run gofmt -w and git add):"
32
+        printf "$list"
33
+        exitcode=1
34
+    fi
35
+    exit $exitcode
36
+}
37
+
38
+case "$1" in
39
+    --about )
40
+        echo "Check Go code formatting"
41
+        ;;
42
+    * )
43
+        test_fmt
44
+        ;;
45
+esac
... ...
@@ -7,7 +7,9 @@ import (
7 7
 	"fmt"
8 8
 	"io"
9 9
 	"io/ioutil"
10
+	"log"
10 11
 	"os"
12
+	"os/exec"
11 13
 	"path"
12 14
 	"strings"
13 15
 	"time"
... ...
@@ -21,6 +23,7 @@ type Image struct {
21 21
 	Container       string    `json:"container,omitempty"`
22 22
 	ContainerConfig Config    `json:"container_config,omitempty"`
23 23
 	DockerVersion   string    `json:"docker_version,omitempty"`
24
+	Author          string    `json:"author,omitempty"`
24 25
 	graph           *Graph
25 26
 }
26 27
 
... ...
@@ -92,7 +95,28 @@ func MountAUFS(ro []string, rw string, target string) error {
92 92
 		roBranches += fmt.Sprintf("%v=ro:", layer)
93 93
 	}
94 94
 	branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
95
-	return mount("none", target, "aufs", 0, branches)
95
+
96
+	//if error, try to load aufs kernel module
97
+	if err := mount("none", target, "aufs", 0, branches); err != nil {
98
+		log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...")
99
+		if err := exec.Command("modprobe", "aufs").Run(); err != nil {
100
+			return fmt.Errorf("Unable to load the AUFS module")
101
+		}
102
+		log.Printf("...module loaded.")
103
+		if err := mount("none", target, "aufs", 0, branches); err != nil {
104
+			return fmt.Errorf("Unable to mount using aufs")
105
+		}
106
+	}
107
+	return nil
108
+}
109
+
110
+// TarLayer returns a tar archive of the image's filesystem layer.
111
+func (image *Image) TarLayer(compression Compression) (Archive, error) {
112
+	layerPath, err := image.layer()
113
+	if err != nil {
114
+		return nil, err
115
+	}
116
+	return Tar(layerPath, compression)
96 117
 }
97 118
 
98 119
 func (image *Image) Mount(root, rw string) error {
... ...
@@ -78,7 +78,7 @@ lxc.mount.entry = devpts {{$ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,no
78 78
 lxc.mount.entry = {{.SysInitPath}} {{$ROOTFS}}/sbin/init none bind,ro 0 0
79 79
 
80 80
 # In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
81
-lxc.mount.entry = /etc/resolv.conf {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0
81
+lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0 0
82 82
 
83 83
 
84 84
 # drop linux capabilities (apply mainly to the user root in the container)
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"encoding/binary"
5 5
 	"errors"
6 6
 	"fmt"
7
+	"io"
7 8
 	"log"
8 9
 	"net"
9 10
 	"os/exec"
... ...
@@ -183,18 +184,21 @@ func getIfaceAddr(name string) (net.Addr, error) {
183 183
 // It keeps track of all mappings and is able to unmap at will
184 184
 type PortMapper struct {
185 185
 	mapping map[int]net.TCPAddr
186
+	proxies map[int]net.Listener
186 187
 }
187 188
 
188 189
 func (mapper *PortMapper) cleanup() error {
189 190
 	// Ignore errors - This could mean the chains were never set up
190 191
 	iptables("-t", "nat", "-D", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
191
-	iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER")
192
+	iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER")
193
+	iptables("-t", "nat", "-D", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER") // Created in versions <= 0.1.6
192 194
 	// Also cleanup rules created by older versions, or -X might fail.
193 195
 	iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
194 196
 	iptables("-t", "nat", "-D", "OUTPUT", "-j", "DOCKER")
195 197
 	iptables("-t", "nat", "-F", "DOCKER")
196 198
 	iptables("-t", "nat", "-X", "DOCKER")
197 199
 	mapper.mapping = make(map[int]net.TCPAddr)
200
+	mapper.proxies = make(map[int]net.Listener)
198 201
 	return nil
199 202
 }
200 203
 
... ...
@@ -205,7 +209,7 @@ func (mapper *PortMapper) setup() error {
205 205
 	if err := iptables("-t", "nat", "-A", "PREROUTING", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
206 206
 		return fmt.Errorf("Failed to inject docker in PREROUTING chain: %s", err)
207 207
 	}
208
-	if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "-j", "DOCKER"); err != nil {
208
+	if err := iptables("-t", "nat", "-A", "OUTPUT", "-m", "addrtype", "--dst-type", "LOCAL", "!", "--dst", "127.0.0.0/8", "-j", "DOCKER"); err != nil {
209 209
 		return fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err)
210 210
 	}
211 211
 	return nil
... ...
@@ -220,15 +224,64 @@ func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error {
220 220
 	if err := mapper.iptablesForward("-A", port, dest); err != nil {
221 221
 		return err
222 222
 	}
223
+
223 224
 	mapper.mapping[port] = dest
225
+	listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
226
+	if err != nil {
227
+		mapper.Unmap(port)
228
+		return err
229
+	}
230
+	mapper.proxies[port] = listener
231
+	go proxy(listener, "tcp", dest.String())
232
+	return nil
233
+}
234
+
235
+// proxy listens for socket connections on `listener`, and forwards them unmodified
236
+// to `proto:address`
237
+func proxy(listener net.Listener, proto, address string) error {
238
+	Debugf("proxying to %s:%s", proto, address)
239
+	defer Debugf("Done proxying to %s:%s", proto, address)
240
+	for {
241
+		Debugf("Listening on %s", listener)
242
+		src, err := listener.Accept()
243
+		if err != nil {
244
+			return err
245
+		}
246
+		Debugf("Connecting to %s:%s", proto, address)
247
+		dst, err := net.Dial(proto, address)
248
+		if err != nil {
249
+			log.Printf("Error connecting to %s:%s: %s", proto, address, err)
250
+			src.Close()
251
+			continue
252
+		}
253
+		Debugf("Connected to backend, splicing")
254
+		splice(src, dst)
255
+	}
224 256
 	return nil
225 257
 }
226 258
 
259
+func halfSplice(dst, src net.Conn) error {
260
+	_, err := io.Copy(dst, src)
261
+	// FIXME: on EOF from a tcp connection, pass WriteClose()
262
+	dst.Close()
263
+	src.Close()
264
+	return err
265
+}
266
+
267
+func splice(a, b net.Conn) {
268
+	go halfSplice(a, b)
269
+	go halfSplice(b, a)
270
+}
271
+
227 272
 func (mapper *PortMapper) Unmap(port int) error {
228 273
 	dest, ok := mapper.mapping[port]
229 274
 	if !ok {
230 275
 		return errors.New("Port is not mapped")
231 276
 	}
277
+	if proxy, exists := mapper.proxies[port]; exists {
278
+		proxy.Close()
279
+		delete(mapper.proxies, port)
280
+	}
232 281
 	if err := mapper.iptablesForward("-D", port, dest); err != nil {
233 282
 		return err
234 283
 	}
... ...
@@ -293,7 +346,7 @@ func (alloc *PortAllocator) Acquire(port int) (int, error) {
293 293
 
294 294
 func newPortAllocator() (*PortAllocator, error) {
295 295
 	allocator := &PortAllocator{
296
-		inUse: make(map[int]struct{}),
296
+		inUse:    make(map[int]struct{}),
297 297
 		fountain: make(chan int),
298 298
 	}
299 299
 	go allocator.runFountain()
300 300
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+Docker packaging
1
+================
2
+
3
+This directory has one subdirectory per packaging distribution.
4
+At minimum, each of these subdirectories should contain a
5
+README.$DISTRIBUTION explaining how to create the native
6
+docker package and how to install it.
7
+
0 8
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+Docker on Arch
1
+==============
2
+
3
+The AUR lxc-docker and lxc-docker-git packages handle building docker on Arch
4
+linux.  The PKGBUILD specifies all dependencies, build, and packaging steps.
5
+
6
+Dependencies
7
+============
8
+
9
+The only buildtime dependencies are git and go which are available via pacman.
10
+The -s flag can be used on makepkg commands below to automatically install
11
+these dependencies.
12
+
13
+Building Package
14
+================
15
+
16
+Download the tarball for either AUR packaged to a local directory.  In that
17
+directory makepkg can be run to build the package.
18
+
19
+# Build the binary package
20
+makepkg
21
+
22
+# Build an updated source tarball
23
+makepkg --source
24
+
0 25
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+PKG_NAME=lxc-docker
1
+DOCKER_VERSION=$(shell head -1 changelog | awk 'match($$0, /\(.+\)/) {print substr($$0, RSTART+1, RLENGTH-4)}')
2
+GITHUB_PATH=github.com/dotcloud/docker
3
+SOURCE_PKG=$(PKG_NAME)_$(DOCKER_VERSION).orig.tar.gz
4
+BUILD_SRC=${CURDIR}/../../build_src
5
+
6
+all:
7
+	# Compile docker. Used by debian dpkg-buildpackage. 
8
+	cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build
9
+
10
+install:
11
+	# Used by debian dpkg-buildpackage 
12
+	mkdir -p $(DESTDIR)/usr/bin
13
+	mkdir -p $(DESTDIR)/etc/init.d
14
+	install -m 0755 src/${GITHUB_PATH}/docker/docker $(DESTDIR)/usr/bin
15
+	install -o root -m 0755 debian/docker.initd $(DESTDIR)/etc/init.d/docker
16
+
17
+debian:
18
+	# This Makefile will compile the github master branch of dotcloud/docker
19
+	# Retrieve docker project and its go structure from internet
20
+	rm -rf ${BUILD_SRC}
21
+	GOPATH=${BUILD_SRC} go get ${GITHUB_PATH}
22
+	# Add debianization
23
+	mkdir ${BUILD_SRC}/debian
24
+	cp Makefile ${BUILD_SRC}
25
+	cp -r * ${BUILD_SRC}/debian
26
+	cp ../../README.md ${BUILD_SRC}
27
+	# Cleanup
28
+	for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done
29
+	rm -rf ${BUILD_SRC}/../${SOURCE_PKG}
30
+	rm -rf ${BUILD_SRC}/pkg
31
+	# Create docker debian files
32
+	cd ${BUILD_SRC}; tar czf ../${SOURCE_PKG} .
33
+	cd ${BUILD_SRC}; dpkg-buildpackage
34
+	rm -rf ${BUILD_SRC}
0 35
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+Docker on Debian
1
+================
2
+
3
+Docker has been built and tested on Wheezy. All docker functionality works
4
+out of the box, except for memory limitation as the stock debian kernel
5
+does not support it yet.
6
+
7
+
8
+Building docker package
9
+~~~~~~~~~~~~~~~~~~~~~~~
10
+
11
+Building Dependencies: debhelper, autotools-dev and golang
12
+
13
+
14
+Assuming you have a wheezy system up and running
15
+
16
+# Download a fresh copy of the docker project
17
+git clone https://github.com/dotcloud/docker.git
18
+cd docker
19
+
20
+# Get building dependencies
21
+sudo apt-get update ; sudo apt-get install -y debhelper autotools-dev golang
22
+
23
+# Make the debian package, with no memory limitation support
24
+(cd packaging/debian; make debian NO_MEMORY_LIMIT=1)
25
+
26
+
27
+Install docker package
28
+~~~~~~~~~~~~~~~~~~~~~~
29
+
30
+sudo dpkg -i lxc-docker_0.1.4-1_amd64.deb; sudo apt-get install -f -y
0 31
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+# -*- mode: ruby -*-
1
+# vi: set ft=ruby :
2
+
3
+$BUILDBOT_IP = '192.168.33.31'
4
+
5
+def v10(config)
6
+  config.vm.box = 'debian'
7
+  config.vm.share_folder 'v-data', '/data/docker', File.dirname(__FILE__) + '/../..'
8
+  config.vm.network :hostonly, $BUILDBOT_IP
9
+
10
+  # Install debian packaging dependencies and create debian packages
11
+  config.vm.provision :shell, :inline => 'apt-get -qq update; apt-get install -y debhelper autotools-dev golang'
12
+  config.vm.provision :shell, :inline => 'cd /data/docker/packaging/debian; make debian'
13
+end
14
+
15
+Vagrant::VERSION < '1.1.0' and Vagrant::Config.run do |config|
16
+  v10(config)
17
+end
18
+
19
+Vagrant::VERSION >= '1.1.0' and Vagrant.configure('1') do |config|
20
+  v10(config)
21
+end
0 22
new file mode 100644
... ...
@@ -0,0 +1,14 @@
0
+lxc-docker (0.1.4-1) unstable; urgency=low
1
+
2
+  Improvements [+], Updates [*], Bug fixes [-]:
3
+  * Changed default bridge interface do 'docker0'
4
+  - Fix a race condition when running the port allocator
5
+
6
+ -- Daniel Mizyrycki <daniel@dotcloud.com>  Wed, 10 Apr 2013 18:06:21 -0700
7
+
8
+
9
+lxc-docker (0.1.0-1) unstable; urgency=low
10
+
11
+  * Initial release
12
+
13
+ -- Daniel Mizyrycki <daniel@dotcloud.com>  Mon, 29 Mar 2013 18:09:55 -0700
0 14
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+9
0 1
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+Source: lxc-docker
1
+Section: admin
2
+Priority: optional
3
+Maintainer: Daniel Mizyrycki <daniel@dotcloud.com>
4
+Build-Depends: debhelper (>= 9),autotools-dev,golang
5
+Standards-Version: 3.9.3
6
+Homepage: http://github.com/dotcloud/docker
7
+
8
+Package: lxc-docker
9
+Architecture: linux-any
10
+Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar
11
+Conflicts: docker
12
+Description: lxc-docker is a Linux container runtime
13
+ Docker complements LXC with a high-level API which operates at the process
14
+ level. It runs unix processes with strong guarantees of isolation and
15
+ repeatability across servers.
16
+ Docker is a great building block for automating distributed systems:
17
+ large-scale web deployments, database clusters, continuous deployment systems,
18
+ private PaaS, service-oriented architectures, etc.
0 19
new file mode 100644
... ...
@@ -0,0 +1,237 @@
0
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1
+Upstream-Name: docker
2
+Upstream-Contact: DotCloud Inc <opensource@dotcloud.com>
3
+Source: http://github.com/dotcloud/docker
4
+
5
+Files: *
6
+Copyright: 2012, DotCloud Inc <opensource@dotcloud.com>
7
+License: Apache-2.0
8
+ Apache License
9
+ Version 2.0, January 2004
10
+ http://www.apache.org/licenses/
11
+ 
12
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
13
+ 
14
+ 1. Definitions.
15
+ 
16
+ "License" shall mean the terms and conditions for use, reproduction,
17
+ and distribution as defined by Sections 1 through 9 of this document.
18
+ 
19
+ "Licensor" shall mean the copyright owner or entity authorized by
20
+ the copyright owner that is granting the License.
21
+ 
22
+ "Legal Entity" shall mean the union of the acting entity and all
23
+ other entities that control, are controlled by, or are under common
24
+ control with that entity. For the purposes of this definition,
25
+ "control" means (i) the power, direct or indirect, to cause the
26
+ direction or management of such entity, whether by contract or
27
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
28
+ outstanding shares, or (iii) beneficial ownership of such entity.
29
+ 
30
+ "You" (or "Your") shall mean an individual or Legal Entity
31
+ exercising permissions granted by this License.
32
+ 
33
+ "Source" form shall mean the preferred form for making modifications,
34
+ including but not limited to software source code, documentation
35
+ source, and configuration files.
36
+ 
37
+ "Object" form shall mean any form resulting from mechanical
38
+ transformation or translation of a Source form, including but
39
+ not limited to compiled object code, generated documentation,
40
+ and conversions to other media types.
41
+ 
42
+ "Work" shall mean the work of authorship, whether in Source or
43
+ Object form, made available under the License, as indicated by a
44
+ copyright notice that is included in or attached to the work
45
+ (an example is provided in the Appendix below).
46
+ 
47
+ "Derivative Works" shall mean any work, whether in Source or Object
48
+ form, that is based on (or derived from) the Work and for which the
49
+ editorial revisions, annotations, elaborations, or other modifications
50
+ represent, as a whole, an original work of authorship. For the purposes
51
+ of this License, Derivative Works shall not include works that remain
52
+ separable from, or merely link (or bind by name) to the interfaces of,
53
+ the Work and Derivative Works thereof.
54
+ 
55
+ "Contribution" shall mean any work of authorship, including
56
+ the original version of the Work and any modifications or additions
57
+ to that Work or Derivative Works thereof, that is intentionally
58
+ submitted to Licensor for inclusion in the Work by the copyright owner
59
+ or by an individual or Legal Entity authorized to submit on behalf of
60
+ the copyright owner. For the purposes of this definition, "submitted"
61
+ means any form of electronic, verbal, or written communication sent
62
+ to the Licensor or its representatives, including but not limited to
63
+ communication on electronic mailing lists, source code control systems,
64
+ and issue tracking systems that are managed by, or on behalf of, the
65
+ Licensor for the purpose of discussing and improving the Work, but
66
+ excluding communication that is conspicuously marked or otherwise
67
+ designated in writing by the copyright owner as "Not a Contribution."
68
+ 
69
+ "Contributor" shall mean Licensor and any individual or Legal Entity
70
+ on behalf of whom a Contribution has been received by Licensor and
71
+ subsequently incorporated within the Work.
72
+ 
73
+ 2. Grant of Copyright License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ copyright license to reproduce, prepare Derivative Works of,
77
+ publicly display, publicly perform, sublicense, and distribute the
78
+ Work and such Derivative Works in Source or Object form.
79
+ 
80
+ 3. Grant of Patent License. Subject to the terms and conditions of
81
+ this License, each Contributor hereby grants to You a perpetual,
82
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
83
+ (except as stated in this section) patent license to make, have made,
84
+ use, offer to sell, sell, import, and otherwise transfer the Work,
85
+ where such license applies only to those patent claims licensable
86
+ by such Contributor that are necessarily infringed by their
87
+ Contribution(s) alone or by combination of their Contribution(s)
88
+ with the Work to which such Contribution(s) was submitted. If You
89
+ institute patent litigation against any entity (including a
90
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
91
+ or a Contribution incorporated within the Work constitutes direct
92
+ or contributory patent infringement, then any patent licenses
93
+ granted to You under this License for that Work shall terminate
94
+ as of the date such litigation is filed.
95
+ 
96
+ 4. Redistribution. You may reproduce and distribute copies of the
97
+ Work or Derivative Works thereof in any medium, with or without
98
+ modifications, and in Source or Object form, provided that You
99
+ meet the following conditions:
100
+ 
101
+ (a) You must give any other recipients of the Work or
102
+ Derivative Works a copy of this License; and
103
+ 
104
+ (b) You must cause any modified files to carry prominent notices
105
+ stating that You changed the files; and
106
+ 
107
+ (c) You must retain, in the Source form of any Derivative Works
108
+ that You distribute, all copyright, patent, trademark, and
109
+ attribution notices from the Source form of the Work,
110
+ excluding those notices that do not pertain to any part of
111
+ the Derivative Works; and
112
+ 
113
+ (d) If the Work includes a "NOTICE" text file as part of its
114
+ distribution, then any Derivative Works that You distribute must
115
+ include a readable copy of the attribution notices contained
116
+ within such NOTICE file, excluding those notices that do not
117
+ pertain to any part of the Derivative Works, in at least one
118
+ of the following places: within a NOTICE text file distributed
119
+ as part of the Derivative Works; within the Source form or
120
+ documentation, if provided along with the Derivative Works; or,
121
+ within a display generated by the Derivative Works, if and
122
+ wherever such third-party notices normally appear. The contents
123
+ of the NOTICE file are for informational purposes only and
124
+ do not modify the License. You may add Your own attribution
125
+ notices within Derivative Works that You distribute, alongside
126
+ or as an addendum to the NOTICE text from the Work, provided
127
+ that such additional attribution notices cannot be construed
128
+ as modifying the License.
129
+ 
130
+ You may add Your own copyright statement to Your modifications and
131
+ may provide additional or different license terms and conditions
132
+ for use, reproduction, or distribution of Your modifications, or
133
+ for any such Derivative Works as a whole, provided Your use,
134
+ reproduction, and distribution of the Work otherwise complies with
135
+ the conditions stated in this License.
136
+ 
137
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
138
+ any Contribution intentionally submitted for inclusion in the Work
139
+ by You to the Licensor shall be under the terms and conditions of
140
+ this License, without any additional terms or conditions.
141
+ Notwithstanding the above, nothing herein shall supersede or modify
142
+ the terms of any separate license agreement you may have executed
143
+ with Licensor regarding such Contributions.
144
+ 
145
+ 6. Trademarks. This License does not grant permission to use the trade
146
+ names, trademarks, service marks, or product names of the Licensor,
147
+ except as required for reasonable and customary use in describing the
148
+ origin of the Work and reproducing the content of the NOTICE file.
149
+ 
150
+ 7. Disclaimer of Warranty. Unless required by applicable law or
151
+ agreed to in writing, Licensor provides the Work (and each
152
+ Contributor provides its Contributions) on an "AS IS" BASIS,
153
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
154
+ implied, including, without limitation, any warranties or conditions
155
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
156
+ PARTICULAR PURPOSE. You are solely responsible for determining the
157
+ appropriateness of using or redistributing the Work and assume any
158
+ risks associated with Your exercise of permissions under this License.
159
+ 
160
+ 8. Limitation of Liability. In no event and under no legal theory,
161
+ whether in tort (including negligence), contract, or otherwise,
162
+ unless required by applicable law (such as deliberate and grossly
163
+ negligent acts) or agreed to in writing, shall any Contributor be
164
+ liable to You for damages, including any direct, indirect, special,
165
+ incidental, or consequential damages of any character arising as a
166
+ result of this License or out of the use or inability to use the
167
+ Work (including but not limited to damages for loss of goodwill,
168
+ work stoppage, computer failure or malfunction, or any and all
169
+ other commercial damages or losses), even if such Contributor
170
+ has been advised of the possibility of such damages.
171
+ 
172
+ 9. Accepting Warranty or Additional Liability. While redistributing
173
+ the Work or Derivative Works thereof, You may choose to offer,
174
+ and charge a fee for, acceptance of support, warranty, indemnity,
175
+ or other liability obligations and/or rights consistent with this
176
+ License. However, in accepting such obligations, You may act only
177
+ on Your own behalf and on Your sole responsibility, not on behalf
178
+ of any other Contributor, and only if You agree to indemnify,
179
+ defend, and hold each Contributor harmless for any liability
180
+ incurred by, or claims asserted against, such Contributor by reason
181
+ of your accepting any such warranty or additional liability.
182
+ 
183
+ END OF TERMS AND CONDITIONS
184
+ 
185
+ APPENDIX: How to apply the Apache License to your work.
186
+ 
187
+ To apply the Apache License to your work, attach the following
188
+ boilerplate notice, with the fields enclosed by brackets "[]"
189
+ replaced with your own identifying information. (Don't include
190
+ the brackets!) The text should be enclosed in the appropriate
191
+ comment syntax for the file format. We also recommend that a
192
+ file or class name and description of purpose be included on the
193
+ same "printed page" as the copyright notice for easier
194
+ identification within third-party archives.
195
+ 
196
+ Copyright 2012 DotCloud Inc
197
+ 
198
+ Licensed under the Apache License, Version 2.0 (the "License");
199
+ you may not use this file except in compliance with the License.
200
+ You may obtain a copy of the License at
201
+ 
202
+ http://www.apache.org/licenses/LICENSE-2.0
203
+ 
204
+ Unless required by applicable law or agreed to in writing, software
205
+ distributed under the License is distributed on an "AS IS" BASIS,
206
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
207
+ See the License for the specific language governing permissions and
208
+ limitations under the License.
209
+ 
210
+ 
211
+Files: src/github.com/kr/pty/*
212
+Copyright: Copyright (c) 2011 Keith Rarick
213
+License: Expat
214
+ Copyright (c) 2011 Keith Rarick
215
+ 
216
+ Permission is hereby granted, free of charge, to any person
217
+ obtaining a copy of this software and associated
218
+ documentation files (the "Software"), to deal in the
219
+ Software without restriction, including without limitation
220
+ the rights to use, copy, modify, merge, publish, distribute,
221
+ sublicense, and/or sell copies of the Software, and to
222
+ permit persons to whom the Software is furnished to do so,
223
+ subject to the following conditions:
224
+ 
225
+ The above copyright notice and this permission notice shall
226
+ be included in all copies or substantial portions of the
227
+ Software.
228
+ 
229
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
230
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
231
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
232
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
233
+ OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
234
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
235
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
236
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0 237
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+#!/bin/sh
1
+
2
+### BEGIN INIT INFO
3
+# Provides:          docker
4
+# Required-Start:    $local_fs
5
+# Required-Stop:     $local_fs
6
+# Default-Start:     2 3 4 5
7
+# Default-Stop:      0 1 6
8
+# Short-Description: docker
9
+# Description:       docker daemon
10
+### END INIT INFO
11
+
12
+DOCKER=/usr/bin/docker
13
+PIDFILE=/var/run/docker.pid
14
+
15
+# Check docker is present
16
+[ -x $DOCKER ] || log_success_msg "Docker not present"
17
+
18
+# Get lsb functions
19
+. /lib/lsb/init-functions
20
+
21
+
22
+case "$1" in
23
+  start)
24
+    log_begin_msg "Starting docker..."
25
+    start-stop-daemon --start --background --exec "$DOCKER" -- -d
26
+    log_end_msg $?
27
+    ;;
28
+  stop)
29
+    log_begin_msg "Stopping docker..."
30
+    docker_pid=`pgrep -f "$DOCKER -d"`
31
+    [ -n "$docker_pid" ] && kill $docker_pid
32
+    log_end_msg $?
33
+    ;;
34
+  status)
35
+    docker_pid=`pgrep -f "$DOCKER -d"`
36
+    if [ -z "$docker_pid" ] ; then
37
+      echo "docker not running"
38
+    else
39
+      echo "docker running (pid $docker_pid)"
40
+    fi
41
+    ;;
42
+  *)
43
+    echo "Usage: /etc/init.d/docker {start|stop|status}"
44
+    exit 1
45
+    ;;
46
+esac
47
+
48
+exit 0
0 49
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+README.md
0 1
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+#!/bin/sh
1
+
2
+# Ensure cgroup is mounted
3
+if [ -z "`/bin/egrep -e '^cgroup' /etc/fstab`" ]; then
4
+    /bin/echo 'cgroup  /sys/fs/cgroup  cgroup  defaults  0   0' >>/etc/fstab
5
+fi
6
+if [ -z "`/bin/mount | /bin/egrep -e '^cgroup'`" ]; then
7
+    /bin/mount /sys/fs/cgroup
8
+fi
9
+
10
+# Start docker
11
+/usr/sbin/update-rc.d docker defaults
12
+/etc/init.d/docker start
0 13
new file mode 100644
... ...
@@ -0,0 +1,16 @@
0
+Maintainer duty
1
+===============
2
+
3
+The Debian project specifies the role of a 'maintainer' which is the person
4
+making the Debian package of the program. This role requires an 'sponsor' to
5
+upload the package. As a maintainer you should follow the guide
6
+http://www.debian.org/doc/manuals/maint-guide . Your sponsor will be there
7
+helping you succeed.
8
+
9
+The most relevant information to update is the changelog file:
10
+Each new release should create a new first paragraph with new release version,
11
+changes, and the maintainer information.
12
+
13
+After this is done, follow README.debian to generate the actual source
14
+packages and talk with your sponsor to upload them into the official Debian
15
+package archive.
0 16
new file mode 100755
... ...
@@ -0,0 +1,13 @@
0
+#!/usr/bin/make -f
1
+# -*- makefile -*-
2
+# Sample debian/rules that uses debhelper.
3
+# This file was originally written by Joey Hess and Craig Small.
4
+# As a special exception, when this file is copied by dh-make into a
5
+# dh-make output file, you may use that output file without restriction.
6
+# This special exception was added by Craig Small in version 0.37 of dh-make.
7
+
8
+# Uncomment this to turn on verbose mode.
9
+#export DH_VERBOSE=1
10
+
11
+%:
12
+	dh ${@} --with autotools_dev
0 13
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+3.0 (quilt)
0 1
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+# Ubuntu package Makefile
1
+#
2
+# Dependencies:  debhelper autotools-dev devscripts golang
3
+# Notes:
4
+# Use 'make ubuntu' to create the ubuntu package
5
+# GPG_KEY environment variable needs to contain a GPG private key for package to be signed
6
+# and uploaded to docker PPA.
7
+# If GPG_KEY is not defined, make ubuntu will create docker package and exit with
8
+# status code 2
9
+
10
+PKG_NAME=lxc-docker
11
+VERSION=$(shell head -1 changelog | sed 's/^.\+(\(.\+\)..).\+$$/\1/')
12
+GITHUB_PATH=github.com/dotcloud/docker
13
+DOCKER_VERSION=${PKG_NAME}_${VERSION}
14
+DOCKER_FVERSION=${PKG_NAME}_$(shell head -1 changelog | sed 's/^.\+(\(.\+\)).\+$$/\1/')
15
+BUILD_SRC=${CURDIR}/../../build_src
16
+VERSION_TAG=v$(shell head -1 changelog | sed 's/^.\+(\(.\+\)-[0-9]\+).\+$$/\1/')
17
+
18
+all:
19
+	# Compile docker. Used by dpkg-buildpackage.
20
+	cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build
21
+
22
+install:
23
+	# Used by dpkg-buildpackage
24
+	mkdir -p ${DESTDIR}/usr/bin
25
+	mkdir -p ${DESTDIR}/etc/init
26
+	mkdir -p ${DESTDIR}/DEBIAN
27
+	install -m 0755 src/${GITHUB_PATH}/docker/docker ${DESTDIR}/usr/bin
28
+	install -o root -m 0755 debian/docker.upstart ${DESTDIR}/etc/init/docker.conf
29
+	install debian/lxc-docker.prerm ${DESTDIR}/DEBIAN/prerm
30
+	install debian/lxc-docker.postinst ${DESTDIR}/DEBIAN/postinst
31
+
32
+ubuntu:
33
+	# This Makefile will compile the github master branch of dotcloud/docker
34
+	# Retrieve docker project and its go structure from internet
35
+	rm -rf ${BUILD_SRC}
36
+	git clone $(shell git rev-parse --show-toplevel) ${BUILD_SRC}/${GITHUB_PATH}
37
+	cd ${BUILD_SRC}/${GITHUB_PATH}; git checkout ${VERSION_TAG} && GOPATH=${BUILD_SRC} go get -d
38
+	# Add debianization
39
+	mkdir ${BUILD_SRC}/debian
40
+	cp Makefile ${BUILD_SRC}
41
+	cp -r * ${BUILD_SRC}/debian
42
+	cp ../../README.md ${BUILD_SRC}
43
+	# Cleanup
44
+	for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done
45
+	rm -rf ${BUILD_SRC}/../${DOCKER_VERSION}.orig.tar.gz
46
+	rm -rf ${BUILD_SRC}/pkg
47
+	# Create docker debian files
48
+	cd ${BUILD_SRC}; tar czf ../${DOCKER_VERSION}.orig.tar.gz .
49
+	cd ${BUILD_SRC}; dpkg-buildpackage -us -uc
50
+	rm -rf ${BUILD_SRC}
51
+	# Sign package and upload it to PPA if GPG_KEY environment variable
52
+	# holds a private GPG KEY
53
+	if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi
54
+	mkdir ${BUILD_SRC}
55
+	# Import gpg signing key
56
+	echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import
57
+	# Sign the package
58
+	cd ${BUILD_SRC}; dpkg-source -x ${BUILD_SRC}/../${DOCKER_FVERSION}.dsc
59
+	cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa
60
+	cd ${BUILD_SRC};dput ppa:dotcloud/lxc-docker ${DOCKER_FVERSION}_source.changes
61
+	rm -rf ${BUILD_SRC}
0 62
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+Docker on Ubuntu
1
+================
2
+
3
+The easiest way to get docker up and running natively on Ubuntu is installing
4
+it from its official PPA::
5
+
6
+  sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >>/etc/apt/sources.list"
7
+  sudo apt-get update
8
+  sudo apt-get install lxc-docker
9
+
10
+
11
+Building docker package
12
+~~~~~~~~~~~~~~~~~~~~~~~
13
+
14
+The building process is shared by both, developers and maintainers. If you are
15
+a developer, the Makefile will stop with exit status 2 right before signing
16
+the built packages.
17
+
18
+Assuming you are working on an Ubuntu 12.04 TLS system ::
19
+
20
+  # Download a fresh copy of the docker project
21
+  git clone https://github.com/dotcloud/docker.git
22
+  cd docker
23
+
24
+  # Get building dependencies
25
+  sudo apt-get update; sudo apt-get install -y debhelper autotools-dev devscripts golang
26
+
27
+  # Make the ubuntu package
28
+  (cd packaging/ubuntu; make ubuntu)
29
+
30
+
31
+Install docker built package
32
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33
+
34
+::
35
+
36
+  sudo dpkg -i lxc-docker_*_amd64.deb; sudo apt-get install -f -y
0 37
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+BUILDBOT_IP = '192.168.33.32'
1
+
2
+Vagrant::Config.run do |config|
3
+  config.vm.box = 'precise64'
4
+  config.vm.box_url = 'http://files.vagrantup.com/precise64.box'
5
+  config.vm.share_folder 'v-data', '/data/docker', "#{File.dirname(__FILE__)}/../.."
6
+  config.vm.network :hostonly,BUILDBOT_IP
7
+
8
+  # Install ubuntu packaging dependencies and create ubuntu packages
9
+  config.vm.provision :shell, :inline => 'export DEBIAN_FRONTEND=noninteractive; apt-get -qq update; apt-get install -qq -y git debhelper autotools-dev devscripts golang'
10
+  config.vm.provision :shell, :inline => "export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/ubuntu; make ubuntu"
11
+end
0 12
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+lxc-docker (0.1.6-1) precise; urgency=low
1
+
2
+  Improvements [+], Updates [*], Bug fixes [-]:
3
+  + Multiple improvements, updates and bug fixes
4
+
5
+ -- dotCloud <ops@dotcloud.com>  Wed, 17 Apr 2013 20:43:43 -0700
6
+
7
+
8
+lxc-docker (0.1.4.1-1) precise; urgency=low
9
+
10
+  Improvements [+], Updates [*], Bug fixes [-]:
11
+  * Test PPA
12
+
13
+ -- dotCloud <ops@dotcloud.com>  Mon, 15 Apr 2013 12:14:50 -0700
14
+
15
+
16
+lxc-docker (0.1.4-1) precise; urgency=low
17
+
18
+  Improvements [+], Updates [*], Bug fixes [-]:
19
+  * Changed default bridge interface do 'docker0'
20
+  - Fix a race condition when running the port allocator
21
+
22
+ -- dotCloud <ops@dotcloud.com>  Fri, 12 Apr 2013 12:20:06 -0700
23
+
24
+
25
+lxc-docker (0.1.0-1) unstable; urgency=low
26
+
27
+  * Initial release
28
+
29
+ -- dotCloud <ops@dotcloud.com>  Mon, 25 Mar 2013 05:51:12 -0700
0 30
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+8
0 1
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+Source: lxc-docker
1
+Section: misc
2
+Priority: extra
3
+Maintainer: Daniel Mizyrycki <daniel@dotcloud.com>
4
+Build-Depends: debhelper,autotools-dev,devscripts,golang
5
+Standards-Version: 3.9.3
6
+Homepage: http://github.com/dotcloud/docker
7
+
8
+Package: lxc-docker
9
+Architecture: linux-any
10
+Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar
11
+Conflicts: docker
12
+Description: lxc-docker is a Linux container runtime
13
+ Docker complements LXC with a high-level API which operates at the process
14
+ level. It runs unix processes with strong guarantees of isolation and
15
+ repeatability across servers.
16
+ Docker is a great building block for automating distributed systems:
17
+ large-scale web deployments, database clusters, continuous deployment systems,
18
+ private PaaS, service-oriented architectures, etc.
0 19
new file mode 100644
... ...
@@ -0,0 +1,237 @@
0
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
1
+Upstream-Name: docker
2
+Upstream-Contact: DotCloud Inc <opensource@dotcloud.com>
3
+Source: http://github.com/dotcloud/docker
4
+
5
+Files: *
6
+Copyright: 2012, DotCloud Inc <opensource@dotcloud.com>
7
+License: Apache-2.0
8
+ Apache License
9
+ Version 2.0, January 2004
10
+ http://www.apache.org/licenses/
11
+ 
12
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
13
+ 
14
+ 1. Definitions.
15
+ 
16
+ "License" shall mean the terms and conditions for use, reproduction,
17
+ and distribution as defined by Sections 1 through 9 of this document.
18
+ 
19
+ "Licensor" shall mean the copyright owner or entity authorized by
20
+ the copyright owner that is granting the License.
21
+ 
22
+ "Legal Entity" shall mean the union of the acting entity and all
23
+ other entities that control, are controlled by, or are under common
24
+ control with that entity. For the purposes of this definition,
25
+ "control" means (i) the power, direct or indirect, to cause the
26
+ direction or management of such entity, whether by contract or
27
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
28
+ outstanding shares, or (iii) beneficial ownership of such entity.
29
+ 
30
+ "You" (or "Your") shall mean an individual or Legal Entity
31
+ exercising permissions granted by this License.
32
+ 
33
+ "Source" form shall mean the preferred form for making modifications,
34
+ including but not limited to software source code, documentation
35
+ source, and configuration files.
36
+ 
37
+ "Object" form shall mean any form resulting from mechanical
38
+ transformation or translation of a Source form, including but
39
+ not limited to compiled object code, generated documentation,
40
+ and conversions to other media types.
41
+ 
42
+ "Work" shall mean the work of authorship, whether in Source or
43
+ Object form, made available under the License, as indicated by a
44
+ copyright notice that is included in or attached to the work
45
+ (an example is provided in the Appendix below).
46
+ 
47
+ "Derivative Works" shall mean any work, whether in Source or Object
48
+ form, that is based on (or derived from) the Work and for which the
49
+ editorial revisions, annotations, elaborations, or other modifications
50
+ represent, as a whole, an original work of authorship. For the purposes
51
+ of this License, Derivative Works shall not include works that remain
52
+ separable from, or merely link (or bind by name) to the interfaces of,
53
+ the Work and Derivative Works thereof.
54
+ 
55
+ "Contribution" shall mean any work of authorship, including
56
+ the original version of the Work and any modifications or additions
57
+ to that Work or Derivative Works thereof, that is intentionally
58
+ submitted to Licensor for inclusion in the Work by the copyright owner
59
+ or by an individual or Legal Entity authorized to submit on behalf of
60
+ the copyright owner. For the purposes of this definition, "submitted"
61
+ means any form of electronic, verbal, or written communication sent
62
+ to the Licensor or its representatives, including but not limited to
63
+ communication on electronic mailing lists, source code control systems,
64
+ and issue tracking systems that are managed by, or on behalf of, the
65
+ Licensor for the purpose of discussing and improving the Work, but
66
+ excluding communication that is conspicuously marked or otherwise
67
+ designated in writing by the copyright owner as "Not a Contribution."
68
+ 
69
+ "Contributor" shall mean Licensor and any individual or Legal Entity
70
+ on behalf of whom a Contribution has been received by Licensor and
71
+ subsequently incorporated within the Work.
72
+ 
73
+ 2. Grant of Copyright License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ copyright license to reproduce, prepare Derivative Works of,
77
+ publicly display, publicly perform, sublicense, and distribute the
78
+ Work and such Derivative Works in Source or Object form.
79
+ 
80
+ 3. Grant of Patent License. Subject to the terms and conditions of
81
+ this License, each Contributor hereby grants to You a perpetual,
82
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
83
+ (except as stated in this section) patent license to make, have made,
84
+ use, offer to sell, sell, import, and otherwise transfer the Work,
85
+ where such license applies only to those patent claims licensable
86
+ by such Contributor that are necessarily infringed by their
87
+ Contribution(s) alone or by combination of their Contribution(s)
88
+ with the Work to which such Contribution(s) was submitted. If You
89
+ institute patent litigation against any entity (including a
90
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
91
+ or a Contribution incorporated within the Work constitutes direct
92
+ or contributory patent infringement, then any patent licenses
93
+ granted to You under this License for that Work shall terminate
94
+ as of the date such litigation is filed.
95
+ 
96
+ 4. Redistribution. You may reproduce and distribute copies of the
97
+ Work or Derivative Works thereof in any medium, with or without
98
+ modifications, and in Source or Object form, provided that You
99
+ meet the following conditions:
100
+ 
101
+ (a) You must give any other recipients of the Work or
102
+ Derivative Works a copy of this License; and
103
+ 
104
+ (b) You must cause any modified files to carry prominent notices
105
+ stating that You changed the files; and
106
+ 
107
+ (c) You must retain, in the Source form of any Derivative Works
108
+ that You distribute, all copyright, patent, trademark, and
109
+ attribution notices from the Source form of the Work,
110
+ excluding those notices that do not pertain to any part of
111
+ the Derivative Works; and
112
+ 
113
+ (d) If the Work includes a "NOTICE" text file as part of its
114
+ distribution, then any Derivative Works that You distribute must
115
+ include a readable copy of the attribution notices contained
116
+ within such NOTICE file, excluding those notices that do not
117
+ pertain to any part of the Derivative Works, in at least one
118
+ of the following places: within a NOTICE text file distributed
119
+ as part of the Derivative Works; within the Source form or
120
+ documentation, if provided along with the Derivative Works; or,
121
+ within a display generated by the Derivative Works, if and
122
+ wherever such third-party notices normally appear. The contents
123
+ of the NOTICE file are for informational purposes only and
124
+ do not modify the License. You may add Your own attribution
125
+ notices within Derivative Works that You distribute, alongside
126
+ or as an addendum to the NOTICE text from the Work, provided
127
+ that such additional attribution notices cannot be construed
128
+ as modifying the License.
129
+ 
130
+ You may add Your own copyright statement to Your modifications and
131
+ may provide additional or different license terms and conditions
132
+ for use, reproduction, or distribution of Your modifications, or
133
+ for any such Derivative Works as a whole, provided Your use,
134
+ reproduction, and distribution of the Work otherwise complies with
135
+ the conditions stated in this License.
136
+ 
137
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
138
+ any Contribution intentionally submitted for inclusion in the Work
139
+ by You to the Licensor shall be under the terms and conditions of
140
+ this License, without any additional terms or conditions.
141
+ Notwithstanding the above, nothing herein shall supersede or modify
142
+ the terms of any separate license agreement you may have executed
143
+ with Licensor regarding such Contributions.
144
+ 
145
+ 6. Trademarks. This License does not grant permission to use the trade
146
+ names, trademarks, service marks, or product names of the Licensor,
147
+ except as required for reasonable and customary use in describing the
148
+ origin of the Work and reproducing the content of the NOTICE file.
149
+ 
150
+ 7. Disclaimer of Warranty. Unless required by applicable law or
151
+ agreed to in writing, Licensor provides the Work (and each
152
+ Contributor provides its Contributions) on an "AS IS" BASIS,
153
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
154
+ implied, including, without limitation, any warranties or conditions
155
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
156
+ PARTICULAR PURPOSE. You are solely responsible for determining the
157
+ appropriateness of using or redistributing the Work and assume any
158
+ risks associated with Your exercise of permissions under this License.
159
+ 
160
+ 8. Limitation of Liability. In no event and under no legal theory,
161
+ whether in tort (including negligence), contract, or otherwise,
162
+ unless required by applicable law (such as deliberate and grossly
163
+ negligent acts) or agreed to in writing, shall any Contributor be
164
+ liable to You for damages, including any direct, indirect, special,
165
+ incidental, or consequential damages of any character arising as a
166
+ result of this License or out of the use or inability to use the
167
+ Work (including but not limited to damages for loss of goodwill,
168
+ work stoppage, computer failure or malfunction, or any and all
169
+ other commercial damages or losses), even if such Contributor
170
+ has been advised of the possibility of such damages.
171
+ 
172
+ 9. Accepting Warranty or Additional Liability. While redistributing
173
+ the Work or Derivative Works thereof, You may choose to offer,
174
+ and charge a fee for, acceptance of support, warranty, indemnity,
175
+ or other liability obligations and/or rights consistent with this
176
+ License. However, in accepting such obligations, You may act only
177
+ on Your own behalf and on Your sole responsibility, not on behalf
178
+ of any other Contributor, and only if You agree to indemnify,
179
+ defend, and hold each Contributor harmless for any liability
180
+ incurred by, or claims asserted against, such Contributor by reason
181
+ of your accepting any such warranty or additional liability.
182
+ 
183
+ END OF TERMS AND CONDITIONS
184
+ 
185
+ APPENDIX: How to apply the Apache License to your work.
186
+ 
187
+ To apply the Apache License to your work, attach the following
188
+ boilerplate notice, with the fields enclosed by brackets "[]"
189
+ replaced with your own identifying information. (Don't include
190
+ the brackets!) The text should be enclosed in the appropriate
191
+ comment syntax for the file format. We also recommend that a
192
+ file or class name and description of purpose be included on the
193
+ same "printed page" as the copyright notice for easier
194
+ identification within third-party archives.
195
+ 
196
+ Copyright 2012 DotCloud Inc
197
+ 
198
+ Licensed under the Apache License, Version 2.0 (the "License");
199
+ you may not use this file except in compliance with the License.
200
+ You may obtain a copy of the License at
201
+ 
202
+ http://www.apache.org/licenses/LICENSE-2.0
203
+ 
204
+ Unless required by applicable law or agreed to in writing, software
205
+ distributed under the License is distributed on an "AS IS" BASIS,
206
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
207
+ See the License for the specific language governing permissions and
208
+ limitations under the License.
209
+ 
210
+ 
211
+Files: src/github.com/kr/pty/*
212
+Copyright: Copyright (c) 2011 Keith Rarick
213
+License: Expat
214
+ Copyright (c) 2011 Keith Rarick
215
+ 
216
+ Permission is hereby granted, free of charge, to any person
217
+ obtaining a copy of this software and associated
218
+ documentation files (the "Software"), to deal in the
219
+ Software without restriction, including without limitation
220
+ the rights to use, copy, modify, merge, publish, distribute,
221
+ sublicense, and/or sell copies of the Software, and to
222
+ permit persons to whom the Software is furnished to do so,
223
+ subject to the following conditions:
224
+ 
225
+ The above copyright notice and this permission notice shall
226
+ be included in all copies or substantial portions of the
227
+ Software.
228
+ 
229
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
230
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
231
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
232
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
233
+ OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
234
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
235
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
236
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0 237
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+description     "Run docker"
1
+
2
+start on runlevel [2345]
3
+stop on starting rc RUNLEVEL=[016]
4
+respawn
5
+
6
+script
7
+    # FIXME: docker should not depend on the system having en_US.UTF-8
8
+    LC_ALL='en_US.UTF-8' /usr/bin/docker -d
9
+end script
0 10
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+README.md
0 1
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+#!/bin/sh
1
+
2
+# Start docker
3
+/sbin/start docker
0 4
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+#!/bin/sh
1
+
2
+# Stop docker
3
+/sbin/stop docker
0 4
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+Maintainer duty
1
+===============
2
+
3
+Ubuntu allows developers to use their PPA (Personal Package Archive)
4
+repository. This is very convenient for the users as they just need to add
5
+the PPA address, update their package database and use the apt-get tool.
6
+
7
+For now, the official lxc-docker package is located on launchpad and can be
8
+accessed adding the following line to /etc/apt/sources.list ::
9
+
10
+
11
+  deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main
12
+
13
+
14
+Releasing a new package
15
+~~~~~~~~~~~~~~~~~~~~~~~
16
+
17
+The most relevant information to update is the changelog file:
18
+Each new release should create a new first paragraph with new release version,
19
+changes, and the maintainer information.
20
+
21
+Assuming your PPA GPG signing key is on /media/usbdrive/docker.key, load it
22
+into the GPG_KEY environment variable with::
23
+
24
+  export GPG_KEY=`cat /media/usbdrive/docker.key`
25
+
26
+
27
+After this is done and you are ready to upload the package to the PPA, you have
28
+a couple of choices:
29
+
30
+* Follow README.debian to generate the actual source packages and upload them
31
+  to the PPA
32
+* Let vagrant do all the work for you::
33
+
34
+  ( cd docker/packaging/ubuntu; vagrant up )
0 35
new file mode 100755
... ...
@@ -0,0 +1,13 @@
0
+#!/usr/bin/make -f
1
+# -*- makefile -*-
2
+# Sample debian/rules that uses debhelper.
3
+# This file was originally written by Joey Hess and Craig Small.
4
+# As a special exception, when this file is copied by dh-make into a
5
+# dh-make output file, you may use that output file without restriction.
6
+# This special exception was added by Craig Small in version 0.37 of dh-make.
7
+
8
+# Uncomment this to turn on verbose mode.
9
+#export DH_VERBOSE=1
10
+
11
+%:
12
+	dh $@
0 13
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+3.0 (quilt)
... ...
@@ -138,7 +138,8 @@ func ListenAndServe(proto, addr string, service Service) error {
138 138
 			if err != nil {
139 139
 				return err
140 140
 			}
141
-			go func() {
141
+			go func(conn DockerConn) {
142
+				defer conn.Close()
142 143
 				if DEBUG_FLAG {
143 144
 					CLIENT_SOCKET = conn
144 145
 				}
... ...
@@ -146,8 +147,7 @@ func ListenAndServe(proto, addr string, service Service) error {
146 146
 					log.Println("Error:", err.Error())
147 147
 					fmt.Fprintln(conn, "Error:", err.Error())
148 148
 				}
149
-				conn.Close()
150
-			}()
149
+			}(conn)
151 150
 		}
152 151
 	}
153 152
 	return nil
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"io"
8 8
 	"io/ioutil"
9 9
 	"net/http"
10
+	"os"
10 11
 	"path"
11 12
 	"strings"
12 13
 )
... ...
@@ -97,7 +98,7 @@ func (graph *Graph) LookupRemoteImage(imgId string, authConfig *auth.AuthConfig)
97 97
 func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) {
98 98
 	client := &http.Client{}
99 99
 
100
-	fmt.Fprintf(stdout, "Pulling %s metadata\n", imgId)
100
+	fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId)
101 101
 	// Get the Json
102 102
 	req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil)
103 103
 	if err != nil {
... ...
@@ -125,7 +126,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a
125 125
 	img.Id = imgId
126 126
 
127 127
 	// Get the layer
128
-	fmt.Fprintf(stdout, "Pulling %s fs layer\n", imgId)
128
+	fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId)
129 129
 	req, err = http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/layer", nil)
130 130
 	if err != nil {
131 131
 		return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
... ...
@@ -135,7 +136,7 @@ func (graph *Graph) getRemoteImage(stdout io.Writer, imgId string, authConfig *a
135 135
 	if err != nil {
136 136
 		return nil, nil, err
137 137
 	}
138
-	return img, ProgressReader(res.Body, int(res.ContentLength), stdout), nil
138
+	return img, ProgressReader(res.Body, int(res.ContentLength), stdout, "Downloading %v/%v (%v)"), nil
139 139
 }
140 140
 
141 141
 func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.AuthConfig) error {
... ...
@@ -164,7 +165,7 @@ func (graph *Graph) PullImage(stdout io.Writer, imgId string, authConfig *auth.A
164 164
 func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error {
165 165
 	client := &http.Client{}
166 166
 
167
-	fmt.Fprintf(stdout, "Pulling repository %s\n", remote)
167
+	fmt.Fprintf(stdout, "Pulling repository %s\r\n", remote)
168 168
 
169 169
 	var repositoryTarget string
170 170
 	// If we are asking for 'root' repository, lookup on the Library's registry
... ...
@@ -196,7 +197,7 @@ func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, re
196 196
 		return err
197 197
 	}
198 198
 	for tag, rev := range t {
199
-		fmt.Fprintf(stdout, "Pulling tag %s:%s\n", remote, tag)
199
+		fmt.Fprintf(stdout, "Pulling tag %s:%s\r\n", remote, tag)
200 200
 		if err = graph.PullImage(stdout, rev, authConfig); err != nil {
201 201
 			return err
202 202
 		}
... ...
@@ -223,7 +224,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
223 223
 			return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err)
224 224
 		}
225 225
 
226
-		fmt.Fprintf(stdout, "Pushing %s metadata\n", img.Id)
226
+		fmt.Fprintf(stdout, "Pushing %s metadata\r\n", img.Id)
227 227
 
228 228
 		// FIXME: try json with UTF8
229 229
 		jsonData := strings.NewReader(string(jsonRaw))
... ...
@@ -253,7 +254,7 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
253 253
 			}
254 254
 		}
255 255
 
256
-		fmt.Fprintf(stdout, "Pushing %s fs layer\n", img.Id)
256
+		fmt.Fprintf(stdout, "Pushing %s fs layer\r\n", img.Id)
257 257
 		req2, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/layer", nil)
258 258
 		req2.SetBasicAuth(authConfig.Username, authConfig.Password)
259 259
 		res2, err := client.Do(req2)
... ...
@@ -269,24 +270,20 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
269 269
 			return fmt.Errorf("Failed to retrieve layer upload location: %s", err)
270 270
 		}
271 271
 
272
-		// FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB
273
-		// FIXME2: I won't stress it enough, DON'T DO THIS! very high priority
274
-		layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz)
275
-		tmp, err := ioutil.ReadAll(layerData2)
272
+		// FIXME: stream the archive directly to the registry instead of buffering it on disk. This requires either:
273
+		//	a) Implementing S3's proprietary streaming logic, or
274
+		//	b) Stream directly to the registry instead of S3.
275
+		// I prefer option b. because it doesn't lock us into a proprietary cloud service.
276
+		tmpLayer, err := graph.TempLayerArchive(img.Id, Xz, stdout)
276 277
 		if err != nil {
277 278
 			return err
278 279
 		}
279
-		layerLength := len(tmp)
280
-
281
-		layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz)
282
-		if err != nil {
283
-			return fmt.Errorf("Failed to generate layer archive: %s", err)
284
-		}
285
-		req3, err := http.NewRequest("PUT", url.String(), ProgressReader(layerData.(io.ReadCloser), layerLength, stdout))
280
+		defer os.Remove(tmpLayer.Name())
281
+		req3, err := http.NewRequest("PUT", url.String(), ProgressReader(tmpLayer, int(tmpLayer.Size), stdout, "Uploading %v/%v (%v)"))
286 282
 		if err != nil {
287 283
 			return err
288 284
 		}
289
-		req3.ContentLength = int64(layerLength)
285
+		req3.ContentLength = int64(tmpLayer.Size)
290 286
 
291 287
 		req3.TransferEncoding = []string{"none"}
292 288
 		res3, err := client.Do(req3)
... ...
@@ -375,15 +372,15 @@ func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId string, a
375 375
 	// Check if the local impage exists
376 376
 	img, err := graph.Get(imgId)
377 377
 	if err != nil {
378
-		fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\n", remote, tag, imgId)
378
+		fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\r\n", remote, tag, imgId)
379 379
 		return nil
380 380
 	}
381
-	fmt.Fprintf(stdout, "Pushing tag %s:%s\n", remote, tag)
381
+	fmt.Fprintf(stdout, "Pushing tag %s:%s\r\n", remote, tag)
382 382
 	// Push the image
383 383
 	if err = graph.PushImage(stdout, img, authConfig); err != nil {
384 384
 		return err
385 385
 	}
386
-	fmt.Fprintf(stdout, "Registering tag %s:%s\n", remote, tag)
386
+	fmt.Fprintf(stdout, "Registering tag %s:%s\r\n", remote, tag)
387 387
 	// And then the tag
388 388
 	if err = graph.pushTag(remote, imgId, tag, authConfig); err != nil {
389 389
 		return err
... ...
@@ -399,7 +396,7 @@ func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Re
399 399
 		return fmt.Errorf("Permission denied on repository %s\n", remote)
400 400
 	}
401 401
 
402
-	fmt.Fprintf(stdout, "Pushing repository %s (%d tags)\n", remote, len(localRepo))
402
+	fmt.Fprintf(stdout, "Pushing repository %s (%d tags)\r\n", remote, len(localRepo))
403 403
 	// For each image within the repo, push them
404 404
 	for tag, imgId := range localRepo {
405 405
 		if err := graph.pushPrimitive(stdout, remote, tag, imgId, authConfig); err != nil {
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"github.com/dotcloud/docker/auth"
7 7
 	"io"
8 8
 	"io/ioutil"
9
+	"log"
9 10
 	"os"
10 11
 	"os/exec"
11 12
 	"path"
... ...
@@ -14,6 +15,11 @@ import (
14 14
 	"time"
15 15
 )
16 16
 
17
+type Capabilities struct {
18
+	MemoryLimit bool
19
+	SwapLimit   bool
20
+}
21
+
17 22
 type Runtime struct {
18 23
 	root           string
19 24
 	repository     string
... ...
@@ -23,6 +29,8 @@ type Runtime struct {
23 23
 	repositories   *TagStore
24 24
 	authConfig     *auth.AuthConfig
25 25
 	idIndex        *TruncIndex
26
+	capabilities   *Capabilities
27
+	kernelVersion  *KernelVersionInfo
26 28
 }
27 29
 
28 30
 var sysInitPath string
... ...
@@ -82,6 +90,7 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
82 82
 	if config.Hostname == "" {
83 83
 		config.Hostname = id[:12]
84 84
 	}
85
+
85 86
 	container := &Container{
86 87
 		// FIXME: we should generate the ID here instead of receiving it as an argument
87 88
 		Id:              id,
... ...
@@ -100,6 +109,24 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) {
100 100
 	if err := os.Mkdir(container.root, 0700); err != nil {
101 101
 		return nil, err
102 102
 	}
103
+
104
+	// If custom dns exists, then create a resolv.conf for the container
105
+	if len(config.Dns) > 0 {
106
+		container.ResolvConfPath = path.Join(container.root, "resolv.conf")
107
+		f, err := os.Create(container.ResolvConfPath)
108
+		if err != nil {
109
+			return nil, err
110
+		}
111
+		defer f.Close()
112
+		for _, dns := range config.Dns {
113
+			if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil {
114
+				return nil, err
115
+			}
116
+		}
117
+	} else {
118
+		container.ResolvConfPath = "/etc/resolv.conf"
119
+	}
120
+
103 121
 	// Step 2: save the container json
104 122
 	if err := container.ToDisk(); err != nil {
105 123
 		return nil, err
... ...
@@ -119,6 +146,9 @@ func (runtime *Runtime) Load(id string) (*Container, error) {
119 119
 	if container.Id != id {
120 120
 		return container, fmt.Errorf("Container %s is stored at %s", container.Id, id)
121 121
 	}
122
+	if container.State.Running {
123
+		container.State.Ghost = true
124
+	}
122 125
 	if err := runtime.Register(container); err != nil {
123 126
 		return nil, err
124 127
 	}
... ...
@@ -134,6 +164,9 @@ func (runtime *Runtime) Register(container *Container) error {
134 134
 		return err
135 135
 	}
136 136
 
137
+	// init the wait lock
138
+	container.waitLock = make(chan struct{})
139
+
137 140
 	// FIXME: if the container is supposed to be running but is not, auto restart it?
138 141
 	//        if so, then we need to restart monitor and init a new lock
139 142
 	// If the container is supposed to be running, make sure of it
... ...
@@ -150,6 +183,14 @@ func (runtime *Runtime) Register(container *Container) error {
150 150
 			}
151 151
 		}
152 152
 	}
153
+
154
+	// If the container is not running or just has been flagged not running
155
+	// then close the wait lock chan (will be reset upon start)
156
+	if !container.State.Running {
157
+		close(container.waitLock)
158
+	}
159
+
160
+	// Even if not running, we init the lock (prevents races in start/stop/kill)
153 161
 	container.State.initLock()
154 162
 
155 163
 	container.runtime = runtime
... ...
@@ -184,7 +225,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
184 184
 		return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.Id)
185 185
 	}
186 186
 
187
-	if err := container.Stop(); err != nil {
187
+	if err := container.Stop(10); err != nil {
188 188
 		return err
189 189
 	}
190 190
 	if mounted, err := container.Mounted(); err != nil {
... ...
@@ -205,7 +246,7 @@ func (runtime *Runtime) Destroy(container *Container) error {
205 205
 
206 206
 // Commit creates a new filesystem image from the current state of a container.
207 207
 // The image can optionally be tagged into a repository
208
-func (runtime *Runtime) Commit(id, repository, tag, comment string) (*Image, error) {
208
+func (runtime *Runtime) Commit(id, repository, tag, comment, author string) (*Image, error) {
209 209
 	container := runtime.Get(id)
210 210
 	if container == nil {
211 211
 		return nil, fmt.Errorf("No such container: %s", id)
... ...
@@ -217,7 +258,7 @@ func (runtime *Runtime) Commit(id, repository, tag, comment string) (*Image, err
217 217
 		return nil, err
218 218
 	}
219 219
 	// Create a new image from the container's base layers + a new layer from container changes
220
-	img, err := runtime.graph.Create(rwTar, container, comment)
220
+	img, err := runtime.graph.Create(rwTar, container, comment, author)
221 221
 	if err != nil {
222 222
 		return nil, err
223 223
 	}
... ...
@@ -249,7 +290,38 @@ func (runtime *Runtime) restore() error {
249 249
 
250 250
 // FIXME: harmonize with NewGraph()
251 251
 func NewRuntime() (*Runtime, error) {
252
-	return NewRuntimeFromDirectory("/var/lib/docker")
252
+	runtime, err := NewRuntimeFromDirectory("/var/lib/docker")
253
+	if err != nil {
254
+		return nil, err
255
+	}
256
+
257
+	k, err := GetKernelVersion()
258
+	if err != nil {
259
+		return nil, err
260
+	}
261
+	runtime.kernelVersion = k
262
+
263
+	if CompareKernelVersion(k, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
264
+		log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
265
+	}
266
+
267
+	if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil {
268
+		log.Printf("WARNING: %s\n", err)
269
+	} else {
270
+		_, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes"))
271
+		_, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes"))
272
+		runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil
273
+		if !runtime.capabilities.MemoryLimit {
274
+		   	log.Printf("WARNING: Your kernel does not support cgroup memory limit.")
275
+		}
276
+
277
+		_, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes"))
278
+		runtime.capabilities.SwapLimit = err == nil
279
+		if !runtime.capabilities.SwapLimit {
280
+		   	log.Printf("WARNING: Your kernel does not support cgroup swap limit.")
281
+		}
282
+	}
283
+	return runtime, nil
253 284
 }
254 285
 
255 286
 func NewRuntimeFromDirectory(root string) (*Runtime, error) {
... ...
@@ -288,6 +360,7 @@ func NewRuntimeFromDirectory(root string) (*Runtime, error) {
288 288
 		repositories:   repositories,
289 289
 		authConfig:     authConfig,
290 290
 		idIndex:        NewTruncIndex(),
291
+		capabilities:   &Capabilities{},
291 292
 	}
292 293
 
293 294
 	if err := runtime.restore(); err != nil {
... ...
@@ -1,9 +1,11 @@
1 1
 package docker
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"github.com/dotcloud/docker/rcli"
5 6
 	"io"
6 7
 	"io/ioutil"
8
+	"net"
7 9
 	"os"
8 10
 	"os/exec"
9 11
 	"os/user"
... ...
@@ -12,12 +14,9 @@ import (
12 12
 	"time"
13 13
 )
14 14
 
15
-// FIXME: this is no longer needed
16
-const testLayerPath string = "/var/lib/docker/docker-ut.tar"
17 15
 const unitTestImageName string = "docker-ut"
18 16
 
19
-var unitTestStoreBase string
20
-var srv *Server
17
+const unitTestStoreBase string = "/var/lib/docker/unit-tests"
21 18
 
22 19
 func nuke(runtime *Runtime) error {
23 20
 	var wg sync.WaitGroup
... ...
@@ -61,15 +60,8 @@ func init() {
61 61
 		panic("docker tests needs to be run as root")
62 62
 	}
63 63
 
64
-	// Create a temp directory
65
-	root, err := ioutil.TempDir("", "docker-test")
66
-	if err != nil {
67
-		panic(err)
68
-	}
69
-	unitTestStoreBase = root
70
-
71 64
 	// Make it our Store root
72
-	runtime, err := NewRuntimeFromDirectory(root)
65
+	runtime, err := NewRuntimeFromDirectory(unitTestStoreBase)
73 66
 	if err != nil {
74 67
 		panic(err)
75 68
 	}
... ...
@@ -262,6 +254,47 @@ func TestGet(t *testing.T) {
262 262
 
263 263
 }
264 264
 
265
+// Run a container with a TCP port allocated, and test that it can receive connections on localhost
266
+func TestAllocatePortLocalhost(t *testing.T) {
267
+	runtime, err := newTestRuntime()
268
+	if err != nil {
269
+		t.Fatal(err)
270
+	}
271
+	container, err := runtime.Create(&Config{
272
+		Image:     GetTestImage(runtime).Id,
273
+		Cmd:       []string{"sh", "-c", "echo well hello there | nc -l -p 5555"},
274
+		PortSpecs: []string{"5555"},
275
+	},
276
+	)
277
+	if err != nil {
278
+		t.Fatal(err)
279
+	}
280
+	if err := container.Start(); err != nil {
281
+		t.Fatal(err)
282
+	}
283
+	defer container.Kill()
284
+	time.Sleep(300 * time.Millisecond) // Wait for the container to run
285
+	conn, err := net.Dial("tcp",
286
+		fmt.Sprintf(
287
+			"localhost:%s", container.NetworkSettings.PortMapping["5555"],
288
+		),
289
+	)
290
+	if err != nil {
291
+		t.Fatal(err)
292
+	}
293
+	defer conn.Close()
294
+	output, err := ioutil.ReadAll(conn)
295
+	if err != nil {
296
+		t.Fatal(err)
297
+	}
298
+	if string(output) != "well hello there\n" {
299
+		t.Fatalf("Received wrong output from network connection: should be '%s', not '%s'",
300
+			"well hello there\n",
301
+			string(output),
302
+		)
303
+	}
304
+}
305
+
265 306
 func TestRestore(t *testing.T) {
266 307
 
267 308
 	root, err := ioutil.TempDir("", "docker-test")
... ...
@@ -12,11 +12,15 @@ type State struct {
12 12
 	ExitCode  int
13 13
 	StartedAt time.Time
14 14
 	l         *sync.Mutex
15
+	Ghost     bool
15 16
 }
16 17
 
17 18
 // String returns a human-readable description of the state
18 19
 func (s *State) String() string {
19 20
 	if s.Running {
21
+		if s.Ghost {
22
+			return fmt.Sprintf("Ghost")
23
+		}
20 24
 		return fmt.Sprintf("Up %s", HumanDuration(time.Now().Sub(s.StartedAt)))
21 25
 	}
22 26
 	return fmt.Sprintf("Exit %d", s.ExitCode)
... ...
@@ -17,8 +17,7 @@ func setupNetworking(gw string) {
17 17
 	if gw == "" {
18 18
 		return
19 19
 	}
20
-	cmd := exec.Command("/sbin/route", "add", "default", "gw", gw)
21
-	if err := cmd.Run(); err != nil {
20
+	if _, err := ip("route", "add", "default", "via", gw); err != nil {
22 21
 		log.Fatalf("Unable to set up networking: %v", err)
23 22
 	}
24 23
 }
... ...
@@ -54,8 +53,7 @@ func changeUser(u string) {
54 54
 }
55 55
 
56 56
 // Clear environment pollution introduced by lxc-start
57
-func cleanupEnv() {
58
-	env := os.Environ()
57
+func cleanupEnv(env ListOpts) {
59 58
 	os.Clearenv()
60 59
 	for _, kv := range env {
61 60
 		parts := strings.SplitN(kv, "=", 2)
... ...
@@ -92,10 +90,13 @@ func SysInit() {
92 92
 	var u = flag.String("u", "", "username or uid")
93 93
 	var gw = flag.String("g", "", "gateway address")
94 94
 
95
+	var flEnv ListOpts
96
+	flag.Var(&flEnv, "e", "Set environment variables")
97
+
95 98
 	flag.Parse()
96 99
 
100
+	cleanupEnv(flEnv)
97 101
 	setupNetworking(*gw)
98
-	cleanupEnv()
99 102
 	changeUser(*u)
100 103
 	executeProgram(flag.Arg(0), flag.Args())
101 104
 }
... ...
@@ -12,9 +12,12 @@ import (
12 12
 	"os"
13 13
 	"os/exec"
14 14
 	"path/filepath"
15
+	"regexp"
15 16
 	"runtime"
17
+	"strconv"
16 18
 	"strings"
17 19
 	"sync"
20
+	"syscall"
18 21
 	"time"
19 22
 )
20 23
 
... ...
@@ -69,23 +72,30 @@ type progressReader struct {
69 69
 	readTotal    int           // Expected stream length (bytes)
70 70
 	readProgress int           // How much has been read so far (bytes)
71 71
 	lastUpdate   int           // How many bytes read at least update
72
+	template     string        // Template to print. Default "%v/%v (%v)"
72 73
 }
73 74
 
74 75
 func (r *progressReader) Read(p []byte) (n int, err error) {
75 76
 	read, err := io.ReadCloser(r.reader).Read(p)
76 77
 	r.readProgress += read
77 78
 
78
-	// Only update progress for every 1% read
79
-	updateEvery := int(0.01 * float64(r.readTotal))
80
-	if r.readProgress-r.lastUpdate > updateEvery || r.readProgress == r.readTotal {
81
-		fmt.Fprintf(r.output, "%d/%d (%.0f%%)\r",
82
-			r.readProgress,
83
-			r.readTotal,
84
-			float64(r.readProgress)/float64(r.readTotal)*100)
79
+	updateEvery := 4096
80
+	if r.readTotal > 0 {
81
+		// Only update progress for every 1% read
82
+		if increment := int(0.01 * float64(r.readTotal)); increment > updateEvery {
83
+			updateEvery = increment
84
+		}
85
+	}
86
+	if r.readProgress-r.lastUpdate > updateEvery || err != nil {
87
+		if r.readTotal > 0 {
88
+			fmt.Fprintf(r.output, r.template+"\r", r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
89
+		} else {
90
+			fmt.Fprintf(r.output, r.template+"\r", r.readProgress, "?", "n/a")
91
+		}
85 92
 		r.lastUpdate = r.readProgress
86 93
 	}
87 94
 	// Send newline when complete
88
-	if err == io.EOF {
95
+	if err != nil {
89 96
 		fmt.Fprintf(r.output, "\n")
90 97
 	}
91 98
 
... ...
@@ -94,8 +104,11 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
94 94
 func (r *progressReader) Close() error {
95 95
 	return io.ReadCloser(r.reader).Close()
96 96
 }
97
-func ProgressReader(r io.ReadCloser, size int, output io.Writer) *progressReader {
98
-	return &progressReader{r, output, size, 0, 0}
97
+func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string) *progressReader {
98
+	if template == "" {
99
+		template = "%v/%v (%v)"
100
+	}
101
+	return &progressReader{r, output, size, 0, 0, template}
99 102
 }
100 103
 
101 104
 // HumanDuration returns a human-readable approximation of a duration
... ...
@@ -384,3 +397,104 @@ func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error)
384 384
 	}
385 385
 	return written, err
386 386
 }
387
+
388
+type KernelVersionInfo struct {
389
+	Kernel int
390
+	Major  int
391
+	Minor  int
392
+	Flavor string
393
+}
394
+
395
+// FIXME: this doens't build on Darwin
396
+func GetKernelVersion() (*KernelVersionInfo, error) {
397
+	var uts syscall.Utsname
398
+
399
+	if err := syscall.Uname(&uts); err != nil {
400
+		return nil, err
401
+	}
402
+
403
+	release := make([]byte, len(uts.Release))
404
+
405
+	i := 0
406
+	for _, c := range uts.Release {
407
+		release[i] = byte(c)
408
+		i++
409
+	}
410
+
411
+	tmp := strings.SplitN(string(release), "-", 2)
412
+	if len(tmp) != 2 {
413
+		return nil, fmt.Errorf("Unrecognized kernel version")
414
+	}
415
+	tmp2 := strings.SplitN(tmp[0], ".", 3)
416
+	if len(tmp2) != 3 {
417
+		return nil, fmt.Errorf("Unrecognized kernel version")
418
+	}
419
+
420
+	kernel, err := strconv.Atoi(tmp2[0])
421
+	if err != nil {
422
+		return nil, err
423
+	}
424
+
425
+	major, err := strconv.Atoi(tmp2[1])
426
+	if err != nil {
427
+		return nil, err
428
+	}
429
+
430
+	minor, err := strconv.Atoi(tmp2[2])
431
+	if err != nil {
432
+		return nil, err
433
+	}
434
+
435
+	flavor := tmp[1]
436
+
437
+	return &KernelVersionInfo{
438
+		Kernel: kernel,
439
+		Major:  major,
440
+		Minor:  minor,
441
+		Flavor: flavor,
442
+	}, nil
443
+}
444
+
445
+func (k *KernelVersionInfo) String() string {
446
+	return fmt.Sprintf("%d.%d.%d-%s", k.Kernel, k.Major, k.Minor, k.Flavor)
447
+}
448
+
449
+// Compare two KernelVersionInfo struct.
450
+// Returns -1 if a < b, = if a == b, 1 it a > b
451
+func CompareKernelVersion(a, b *KernelVersionInfo) int {
452
+	if a.Kernel < b.Kernel {
453
+		return -1
454
+	} else if a.Kernel > b.Kernel {
455
+		return 1
456
+	}
457
+
458
+	if a.Major < b.Major {
459
+		return -1
460
+	} else if a.Major > b.Major {
461
+		return 1
462
+	}
463
+
464
+	if a.Minor < b.Minor {
465
+		return -1
466
+	} else if a.Minor > b.Minor {
467
+		return 1
468
+	}
469
+
470
+	return 0
471
+}
472
+
473
+func FindCgroupMountpoint(cgroupType string) (string, error) {
474
+	output, err := exec.Command("mount").CombinedOutput()
475
+	if err != nil {
476
+		return "", err
477
+	}
478
+
479
+	reg := regexp.MustCompile(`^cgroup on (.*) type cgroup \(.*` + cgroupType + `[,\)]`)
480
+	for _, line := range strings.Split(string(output), "\n") {
481
+		r := reg.FindStringSubmatch(line)
482
+		if len(r) == 2 {
483
+			return r[1], nil
484
+		}
485
+	}
486
+	return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
487
+}
... ...
@@ -228,3 +228,36 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin
228 228
 		t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
229 229
 	}
230 230
 }
231
+
232
+func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) {
233
+	if r := CompareKernelVersion(a, b); r != result {
234
+		t.Fatalf("Unepected kernel version comparaison result. Found %d, expected %d", r, result)
235
+	}
236
+}
237
+
238
+func TestCompareKernelVersion(t *testing.T) {
239
+	assertKernelVersion(t,
240
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
241
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
242
+		0)
243
+	assertKernelVersion(t,
244
+		&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
245
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
246
+		-1)
247
+	assertKernelVersion(t,
248
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
249
+		&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
250
+		1)
251
+	assertKernelVersion(t,
252
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
253
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "16"},
254
+		0)
255
+	assertKernelVersion(t,
256
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5},
257
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
258
+		1)
259
+	assertKernelVersion(t,
260
+		&KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Flavor: "25"},
261
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
262
+		-1)
263
+}