Browse code

Merged master branch into fs

shin- authored on 2013/03/11 21:42:36
Showing 30 changed files
... ...
@@ -1,4 +1,5 @@
1
-docker
2
-dockerd
1
+.vagrant
2
+docker/docker
3
+dockerd/dockerd
3 4
 .*.swp
4 5
 a.out
... ...
@@ -23,7 +23,7 @@ Notable features
23 23
 
24 24
 * Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.
25 25
 
26
-* Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own (COMING SOON)
26
+* Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.
27 27
 
28 28
 * Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.
29 29
 
... ...
@@ -34,6 +34,56 @@ Notable features
34 34
 * Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throaway interactive shell.
35 35
 
36 36
 
37
+
38
+Under the hood
39
+--------------
40
+
41
+Under the hood, Docker is built on the following components:
42
+
43
+
44
+* 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;
45
+
46
+* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities;
47
+
48
+* The [Go](http://golang.org) programming language;
49
+
50
+* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
51
+
52
+
53
+Setup instructions
54
+==================
55
+
56
+Requirements
57
+------------
58
+
59
+Right now, the officially supported distributions are:
60
+
61
+* Ubuntu 12.04 (precise LTS)
62
+* Ubuntu 12.10 (quantal)
63
+
64
+Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested.
65
+
66
+
67
+Installation
68
+---------------
69
+
70
+1. Set up your host of choice on a physical / virtual machine
71
+2. Assume root identity on your newly installed environment (`sudo -s`)
72
+3. Type the following commands:
73
+
74
+        apt-get update
75
+        apt-get install lxc wget bsdtar curl
76
+
77
+4. Download the latest docker binaries: `wget http://docker.io.s3.amazonaws.com/builds/$(uname -s)/$(uname -m)/docker-master.tgz` ([Or get the Linux/x86_64 binaries here](http://docker.io.s3.amazonaws.com/builds/Linux/x86_64/docker-master.tgz) )
78
+5. Extract the contents of the tar file `tar -xf docker-master.tar.gz`
79
+6. Launch the docker daemon in the background `./dockerd &`
80
+7. Download a base image `./docker pull base`
81
+8. Run your first container! `./docker run -i -a -t base /bin/bash`
82
+9. Start exploring `./docker --help`
83
+
84
+Consider adding docker and dockerd to your `PATH` for simplicity.
85
+
86
+
37 87
 What is a Standard Container?
38 88
 -----------------------------
39 89
 
... ...
@@ -76,20 +126,6 @@ With Standard Containers we can put an end to that embarrassment, by making INDU
76 76
 
77 77
 
78 78
 
79
-Under the hood
80
-
81
-Under the hood, Docker is built on the following components:
82
-
83
-
84
-* 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;
85
-
86
-* [AUFS](http://aufs.sourceforge.net/aufs.html), a powerful union filesystem with copy-on-write capabilities;
87
-
88
-* The [Go](http://golang.org) programming language;
89
-
90
-* [lxc](http://lxc.sourceforge.net/), a set of convenience scripts to simplify the creation of linux containers.
91
-
92 79
 
93 80
 Standard Container Specification
94 81
 --------------------------------
... ...
@@ -135,121 +171,3 @@ Standard Container Specification
135 135
 #### Security
136 136
 
137 137
 
138
-Setup instructions
139
-==================
140
-
141
-Requirements
142
-
143
-Right now, the officially supported distributions are:
144
-
145
-* Ubuntu 12.04 (precise LTS)
146
-* Ubuntu 12.10 (quantal)
147
-
148
-Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up-to-date lxc. However this has not been tested.
149
-
150
-
151
-Step by step host setup
152
-
153
-1. Set up your host of choice on a physical / virtual machine
154
-2. Assume root identity on your newly installed environment (`sudo -s`)
155
-3. Type the following commands:
156
-
157
-        apt-get update
158
-        apt-get install lxc wget
159
-
160
-4. Download the latest version of the [docker binaries](https://dl.dropbox.com/u/20637798/docker.tar.gz) (`wget https://dl.dropbox.com/u/20637798/docker.tar.gz`) (warning: this may not be the most up-to-date build)
161
-5. Extract the contents of the tar file `tar -xf docker.tar.gz`
162
-6. Launch the docker daemon `./dockerd`
163
-7. Download a base image by running 'docker pull -j base'
164
-
165
-
166
-Client installation
167
-
168
-4. Download the latest version of the [docker binaries](https://dl.dropbox.com/u/20637798/docker.tar.gz) (`wget https://dl.dropbox.com/u/20637798/docker.tar.gz`)
169
-5. Extract the contents of the tar file `tar -xf docker.tar.gz`
170
-6. You can now use the docker client binary `./docker`. Consider adding it to your `PATH` for simplicity.
171
-
172
-Vagrant Usage
173
-
174
-1. Install Vagrant from http://vagrantup.com
175
-2. Run `vagrant up`. This will take a few minutes as it does the following:
176
-    - Download Quantal64 base box
177
-    - Kick off Puppet to do:
178
-        - Download & untar most recent docker binary tarball to vagrant homedir.
179
-        - Debootstrap to /var/lib/docker/images/ubuntu.
180
-        - Install & run dockerd as service.
181
-        - Put docker in /usr/local/bin.
182
-        - Put latest Go toolchain in /usr/local/go.
183
-
184
-Sample run output:
185
-
186
-```bash
187
-$ vagrant up
188
-[default] Importing base box 'quantal64'...
189
-[default] Matching MAC address for NAT networking...
190
-[default] Clearing any previously set forwarded ports...
191
-[default] Forwarding ports...
192
-[default] -- 22 => 2222 (adapter 1)
193
-[default] Creating shared folders metadata...
194
-[default] Clearing any previously set network interfaces...
195
-[default] Booting VM...
196
-[default] Waiting for VM to boot. This can take a few minutes.
197
-[default] VM booted and ready for use!
198
-[default] Mounting shared folders...
199
-[default] -- v-root: /vagrant
200
-[default] -- manifests: /tmp/vagrant-puppet/manifests
201
-[default] -- v-pp-m0: /tmp/vagrant-puppet/modules-0
202
-[default] Running provisioner: Vagrant::Provisioners::Puppet...
203
-[default] Running Puppet with /tmp/vagrant-puppet/manifests/quantal64.pp...
204
-stdin: is not a tty
205
-notice: /Stage[main]//Node[default]/Exec[apt_update]/returns: executed successfully
206
-
207
-notice: /Stage[main]/Docker/Exec[fetch-docker]/returns: executed successfully
208
-notice: /Stage[main]/Docker/Package[lxc]/ensure: ensure changed 'purged' to 'present'
209
-notice: /Stage[main]/Docker/Exec[fetch-go]/returns: executed successfully
210
-
211
-notice: /Stage[main]/Docker/Exec[copy-docker-bin]/returns: executed successfully
212
-notice: /Stage[main]/Docker/Exec[debootstrap]/returns: executed successfully
213
-notice: /Stage[main]/Docker/File[/etc/init/dockerd.conf]/ensure: defined content as '{md5}78a593d38dd9919af14d8f0545ac95e9'
214
-
215
-notice: /Stage[main]/Docker/Service[dockerd]/ensure: ensure changed 'stopped' to 'running'
216
-
217
-notice: Finished catalog run in 329.74 seconds
218
-```
219
-
220
-When this has successfully completed, you should be able to get into your new system with `vagrant ssh` and use `docker`:
221
-
222
-```bash
223
-$ vagrant ssh
224
-Welcome to Ubuntu 12.10 (GNU/Linux 3.5.0-17-generic x86_64)
225
-
226
- * Documentation:  https://help.ubuntu.com/
227
-
228
-Last login: Sun Feb  3 19:37:37 2013
229
-vagrant@vagrant-ubuntu-12:~$ DOCKER=localhost:4242 docker help
230
-Usage: docker COMMAND [arg...]
231
-
232
-A self-sufficient runtime for linux containers.
233
-
234
-Commands:
235
-    run       Run a command in a container
236
-    ps        Display a list of containers
237
-    pull      Download a tarball and create a container from it
238
-    put       Upload a tarball and create a container from it
239
-    rm        Remove containers
240
-    wait      Wait for the state of a container to change
241
-    stop      Stop a running container
242
-    logs      Fetch the logs of a container
243
-    diff      Inspect changes on a container's filesystem
244
-    commit    Save the state of a container
245
-    attach    Attach to the standard inputs and outputs of a running container
246
-    info      Display system-wide information
247
-    tar       Stream the contents of a container as a tar archive
248
-    web       Generate a web UI
249
-    attach    Attach to a running container
250
-```
251
-    
... ...
@@ -1,8 +1,8 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"github.com/dotcloud/docker/rcli"
5 4
 	"github.com/dotcloud/docker/future"
5
+	"github.com/dotcloud/docker/rcli"
6 6
 	"io"
7 7
 	"io/ioutil"
8 8
 	"log"
... ...
@@ -112,7 +112,7 @@ func InteractiveMode(scripts ...string) error {
112 112
 		return err
113 113
 	}
114 114
 	io.WriteString(rcfile, "enable -n help\n")
115
-	os.Setenv("PATH", tmp + ":" + os.Getenv("PATH"))
115
+	os.Setenv("PATH", tmp+":"+os.Getenv("PATH"))
116 116
 	os.Setenv("PS1", "\\h docker> ")
117 117
 	shell := exec.Command("/bin/bash", append([]string{"--rcfile", rcfile.Name()}, scripts...)...)
118 118
 	shell.Stdin = os.Stdin
... ...
@@ -15,7 +15,6 @@ type Termios struct {
15 15
 	Ospeed uintptr
16 16
 }
17 17
 
18
-
19 18
 const (
20 19
 	// Input flags
21 20
 	inpck  = 0x010
... ...
@@ -35,113 +34,110 @@ const (
35 35
 )
36 36
 
37 37
 const (
38
-        HUPCL                             = 0x4000 
39
-        ICANON                            = 0x100 
40
-        ICRNL                             = 0x100 
41
-        IEXTEN                            = 0x400
42
-        BRKINT                            = 0x2 
43
-        CFLUSH                            = 0xf 
44
-        CLOCAL                            = 0x8000 
45
-        CREAD                             = 0x800 
46
-        CS5                               = 0x0 
47
-        CS6                               = 0x100 
48
-        CS7                               = 0x200 
49
-        CS8                               = 0x300 
50
-        CSIZE                             = 0x300 
51
-        CSTART                            = 0x11 
52
-        CSTATUS                           = 0x14 
53
-        CSTOP                             = 0x13 
54
-        CSTOPB                            = 0x400 
55
-        CSUSP                             = 0x1a 
56
-        IGNBRK                            = 0x1 
57
-        IGNCR                             = 0x80 
58
-        IGNPAR                            = 0x4 
59
-        IMAXBEL                           = 0x2000 
60
-        INLCR                             = 0x40 
61
-        INPCK                             = 0x10 
62
-        ISIG                              = 0x80 
63
-        ISTRIP                            = 0x20 
64
-        IUTF8                             = 0x4000 
65
-        IXANY                             = 0x800 
66
-        IXOFF                             = 0x400 
67
-        IXON                              = 0x200 
68
-        NOFLSH                            = 0x80000000 
69
-        OCRNL                             = 0x10 
70
-        OFDEL                             = 0x20000 
71
-        OFILL                             = 0x80 
72
-        ONLCR                             = 0x2 
73
-        ONLRET                            = 0x40 
74
-        ONOCR                             = 0x20 
75
-        ONOEOT                            = 0x8 
76
-        OPOST                             = 0x1 
77
-RENB                            = 0x1000 
78
-        PARMRK                            = 0x8 
79
-        PARODD                            = 0x2000 
80
-
81
-        TOSTOP                            = 0x400000 
82
-        VDISCARD                          = 0xf 
83
-        VDSUSP                            = 0xb 
84
-        VEOF                              = 0x0 
85
-        VEOL                              = 0x1 
86
-        VEOL2                             = 0x2 
87
-        VERASE                            = 0x3 
88
-        VINTR                             = 0x8 
89
-        VKILL                             = 0x5 
90
-        VLNEXT                            = 0xe 
91
-        VMIN                              = 0x10 
92
-        VQUIT                             = 0x9 
93
-        VREPRINT                          = 0x6 
94
-        VSTART                            = 0xc 
95
-        VSTATUS                           = 0x12 
96
-        VSTOP                             = 0xd 
97
-        VSUSP                             = 0xa 
98
-        VT0                               = 0x0 
99
-        VT1                               = 0x10000 
100
-        VTDLY                             = 0x10000 
101
-        VTIME                             = 0x11 
102
-	ECHO				  = 0x00000008
103
-
104
-        PENDIN                            = 0x20000000 
38
+	HUPCL   = 0x4000
39
+	ICANON  = 0x100
40
+	ICRNL   = 0x100
41
+	IEXTEN  = 0x400
42
+	BRKINT  = 0x2
43
+	CFLUSH  = 0xf
44
+	CLOCAL  = 0x8000
45
+	CREAD   = 0x800
46
+	CS5     = 0x0
47
+	CS6     = 0x100
48
+	CS7     = 0x200
49
+	CS8     = 0x300
50
+	CSIZE   = 0x300
51
+	CSTART  = 0x11
52
+	CSTATUS = 0x14
53
+	CSTOP   = 0x13
54
+	CSTOPB  = 0x400
55
+	CSUSP   = 0x1a
56
+	IGNBRK  = 0x1
57
+	IGNCR   = 0x80
58
+	IGNPAR  = 0x4
59
+	IMAXBEL = 0x2000
60
+	INLCR   = 0x40
61
+	INPCK   = 0x10
62
+	ISIG    = 0x80
63
+	ISTRIP  = 0x20
64
+	IUTF8   = 0x4000
65
+	IXANY   = 0x800
66
+	IXOFF   = 0x400
67
+	IXON    = 0x200
68
+	NOFLSH  = 0x80000000
69
+	OCRNL   = 0x10
70
+	OFDEL   = 0x20000
71
+	OFILL   = 0x80
72
+	ONLCR   = 0x2
73
+	ONLRET  = 0x40
74
+	ONOCR   = 0x20
75
+	ONOEOT  = 0x8
76
+	OPOST   = 0x1
77
+	RENB    = 0x1000
78
+	PARMRK  = 0x8
79
+	PARODD  = 0x2000
80
+
81
+	TOSTOP   = 0x400000
82
+	VDISCARD = 0xf
83
+	VDSUSP   = 0xb
84
+	VEOF     = 0x0
85
+	VEOL     = 0x1
86
+	VEOL2    = 0x2
87
+	VERASE   = 0x3
88
+	VINTR    = 0x8
89
+	VKILL    = 0x5
90
+	VLNEXT   = 0xe
91
+	VMIN     = 0x10
92
+	VQUIT    = 0x9
93
+	VREPRINT = 0x6
94
+	VSTART   = 0xc
95
+	VSTATUS  = 0x12
96
+	VSTOP    = 0xd
97
+	VSUSP    = 0xa
98
+	VT0      = 0x0
99
+	VT1      = 0x10000
100
+	VTDLY    = 0x10000
101
+	VTIME    = 0x11
102
+	ECHO     = 0x00000008
103
+
104
+	PENDIN = 0x20000000
105 105
 )
106 106
 
107 107
 type State struct {
108
-       termios Termios
108
+	termios Termios
109 109
 }
110 110
 
111 111
 // IsTerminal returns true if the given file descriptor is a terminal.
112 112
 func IsTerminal(fd int) bool {
113
-        var termios Termios
114
-        _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
115
-        return err == 0
113
+	var termios Termios
114
+	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
115
+	return err == 0
116 116
 }
117 117
 
118 118
 // MakeRaw put the terminal connected to the given file descriptor into raw
119 119
 // mode and returns the previous state of the terminal so that it can be
120 120
 // restored.
121 121
 func MakeRaw(fd int) (*State, error) {
122
-        var oldState State
123
-        if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
124
-                return nil, err
125
-        }
126
-
127
-        newState := oldState.termios
128
-        newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
129
-        newState.Iflag |= ICRNL
130
-        newState.Oflag |= ONLCR
131
-        newState.Lflag &^= ECHO | ICANON | ISIG
132
-        if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
133
-                return nil, err
134
-        }
135
-
136
-        return &oldState, nil
122
+	var oldState State
123
+	if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
124
+		return nil, err
125
+	}
126
+
127
+	newState := oldState.termios
128
+	newState.Iflag &^= ISTRIP | INLCR | IGNCR | IXON | IXOFF
129
+	newState.Iflag |= ICRNL
130
+	newState.Oflag |= ONLCR
131
+	newState.Lflag &^= ECHO | ICANON | ISIG
132
+	if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
133
+		return nil, err
134
+	}
135
+
136
+	return &oldState, nil
137 137
 }
138 138
 
139
-
140 139
 // Restore restores the terminal connected to the given file descriptor to a
141 140
 // previous state.
142 141
 func Restore(fd int, state *State) error {
143
-        _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
144
-        return err
142
+	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
143
+	return err
145 144
 }
146
-
147
-
... ...
@@ -1,7 +1,7 @@
1 1
 package docker
2 2
 
3 3
 import (
4
-	"bytes"
4
+	"./fs"
5 5
 	"encoding/json"
6 6
 	"errors"
7 7
 	"github.com/kr/pty"
... ...
@@ -11,10 +11,9 @@ import (
11 11
 	"os"
12 12
 	"os/exec"
13 13
 	"path"
14
-	"strings"
14
+	"strconv"
15 15
 	"syscall"
16 16
 	"time"
17
-	"./fs"
18 17
 )
19 18
 
20 19
 var sysInitPath string
... ...
@@ -35,7 +34,11 @@ type Container struct {
35 35
 	Config     *Config
36 36
 	Mountpoint *fs.Mountpoint
37 37
 	State      *State
38
-	Image	   string
38
+	Image      string
39
+
40
+	network         *NetworkInterface
41
+	networkManager  *NetworkManager
42
+	NetworkSettings *NetworkSettings
39 43
 
40 44
 	SysInitPath   string
41 45
 	lxcConfigPath string
... ...
@@ -45,40 +48,61 @@ type Container struct {
45 45
 	stdin         io.ReadCloser
46 46
 	stdinPipe     io.WriteCloser
47 47
 
48
-	stdoutLog *bytes.Buffer
49
-	stderrLog *bytes.Buffer
48
+	stdoutLog *os.File
49
+	stderrLog *os.File
50 50
 }
51 51
 
52 52
 type Config struct {
53 53
 	Hostname  string
54 54
 	User      string
55 55
 	Ram       int64
56
+	Ports     []int
56 57
 	Tty       bool // Attach standard streams to a tty, including stdin if it is not closed.
57 58
 	OpenStdin bool // Open stdin
58 59
 }
59 60
 
60
-func createContainer(id string, root string, command string, args []string, image *fs.Image, config *Config) (*Container, error) {
61
+type NetworkSettings struct {
62
+	IpAddress   string
63
+	IpPrefixLen int
64
+	Gateway     string
65
+	PortMapping map[string]string
66
+}
67
+
68
+func createContainer(id string, root string, command string, args []string, image *fs.Image, config *Config, netManager *NetworkManager) (*Container, error) {
61 69
 	mountpoint, err := image.Mountpoint(path.Join(root, "rootfs"), path.Join(root, "rw"))
62 70
 	if err != nil {
63 71
 		return nil, err
64 72
 	}
65 73
 	container := &Container{
66
-		Id:         id,
67
-		Root:       root,
68
-		Created:    time.Now(),
69
-		Path:       command,
70
-		Args:       args,
71
-		Config:     config,
72
-		Image:		image.Id,
73
-		Mountpoint: mountpoint,
74
-		State:      newState(),
75
-
76
-		SysInitPath:   sysInitPath,
77
-		lxcConfigPath: path.Join(root, "config.lxc"),
78
-		stdout:        newWriteBroadcaster(),
79
-		stderr:        newWriteBroadcaster(),
80
-		stdoutLog:     new(bytes.Buffer),
81
-		stderrLog:     new(bytes.Buffer),
74
+		Id:              id,
75
+		Root:            root,
76
+		Created:         time.Now(),
77
+		Path:            command,
78
+		Args:            args,
79
+		Config:          config,
80
+		Image:           image.Id,
81
+		Mountpoint:      mountpoint,
82
+		State:           newState(),
83
+		networkManager:  netManager,
84
+		NetworkSettings: &NetworkSettings{},
85
+		SysInitPath:     sysInitPath,
86
+		lxcConfigPath:   path.Join(root, "config.lxc"),
87
+		stdout:          newWriteBroadcaster(),
88
+		stderr:          newWriteBroadcaster(),
89
+	}
90
+	if err := os.Mkdir(root, 0700); err != nil {
91
+		return nil, err
92
+	}
93
+	// Setup logging of stdout and stderr to disk
94
+	if stdoutLog, err := os.OpenFile(path.Join(container.Root, id+"-stdout.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
95
+		return nil, err
96
+	} else {
97
+		container.stdoutLog = stdoutLog
98
+	}
99
+	if stderrLog, err := os.OpenFile(path.Join(container.Root, id+"-stderr.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
100
+		return nil, err
101
+	} else {
102
+		container.stderrLog = stderrLog
82 103
 	}
83 104
 	if container.Config.OpenStdin {
84 105
 		container.stdin, container.stdinPipe = io.Pipe()
... ...
@@ -88,36 +112,43 @@ func createContainer(id string, root string, command string, args []string, imag
88 88
 	container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
89 89
 	container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
90 90
 
91
-	if err := os.Mkdir(root, 0700); err != nil {
92
-		return nil, err
93
-	}
94
-	/*if err := container.Filesystem.createMountPoints(); err != nil {
95
-		return nil, err
96
-	}*/
97 91
 	if err := container.save(); err != nil {
98 92
 		return nil, err
99 93
 	}
100 94
 	return container, nil
101 95
 }
102 96
 
103
-func loadContainer(containerPath string) (*Container, error) {
97
+func loadContainer(containerPath string, netManager *NetworkManager) (*Container, error) {
104 98
 	data, err := ioutil.ReadFile(path.Join(containerPath, "config.json"))
105 99
 	if err != nil {
106 100
 		return nil, err
107 101
 	}
108 102
 	container := &Container{
109
-		stdout:        newWriteBroadcaster(),
110
-		stderr:        newWriteBroadcaster(),
111
-		stdoutLog:     new(bytes.Buffer),
112
-		stderrLog:     new(bytes.Buffer),
113
-		lxcConfigPath: path.Join(containerPath, "config.lxc"),
103
+		stdout:          newWriteBroadcaster(),
104
+		stderr:          newWriteBroadcaster(),
105
+		lxcConfigPath:   path.Join(containerPath, "config.lxc"),
106
+		networkManager:  netManager,
107
+		NetworkSettings: &NetworkSettings{},
114 108
 	}
109
+	// Load container settings
115 110
 	if err := json.Unmarshal(data, container); err != nil {
116 111
 		return nil, err
117 112
 	}
118
-	// if err := container.Filesystem.createMountPoints(); err != nil {
119
-	// 	return nil, err
120
-	// }
113
+
114
+	// Setup logging of stdout and stderr to disk
115
+	if stdoutLog, err := os.OpenFile(path.Join(container.Root, container.Id+"-stdout.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
116
+		return nil, err
117
+	} else {
118
+		container.stdoutLog = stdoutLog
119
+	}
120
+	if stderrLog, err := os.OpenFile(path.Join(container.Root, container.Id+"-stderr.log"), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600); err != nil {
121
+		return nil, err
122
+	} else {
123
+		container.stderrLog = stderrLog
124
+	}
125
+	container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
126
+	container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
127
+
121 128
 	if container.Config.OpenStdin {
122 129
 		container.stdin, container.stdinPipe = io.Pipe()
123 130
 	} else {
... ...
@@ -270,6 +301,9 @@ func (container *Container) Start() error {
270 270
 	if err := container.Mountpoint.EnsureMounted(); err != nil {
271 271
 		return err
272 272
 	}
273
+	if err := container.allocateNetwork(); err != nil {
274
+		return err
275
+	}
273 276
 	if err := container.generateLXCConfig(); err != nil {
274 277
 		return err
275 278
 	}
... ...
@@ -279,11 +313,19 @@ func (container *Container) Start() error {
279 279
 		"--",
280 280
 		"/sbin/init",
281 281
 	}
282
+
283
+	// Networking
284
+	params = append(params, "-g", container.network.Gateway.String())
285
+
286
+	// User
282 287
 	if container.Config.User != "" {
283 288
 		params = append(params, "-u", container.Config.User)
284 289
 	}
290
+
291
+	// Program
285 292
 	params = append(params, "--", container.Path)
286 293
 	params = append(params, container.Args...)
294
+
287 295
 	container.cmd = exec.Command("/usr/bin/lxc-start", params...)
288 296
 
289 297
 	var err error
... ...
@@ -337,7 +379,11 @@ func (container *Container) StdoutPipe() (io.ReadCloser, error) {
337 337
 }
338 338
 
339 339
 func (container *Container) StdoutLog() io.Reader {
340
-	return strings.NewReader(container.stdoutLog.String())
340
+	r, err := os.Open(container.stdoutLog.Name())
341
+	if err != nil {
342
+		return nil
343
+	}
344
+	return r
341 345
 }
342 346
 
343 347
 func (container *Container) StderrPipe() (io.ReadCloser, error) {
... ...
@@ -347,7 +393,39 @@ func (container *Container) StderrPipe() (io.ReadCloser, error) {
347 347
 }
348 348
 
349 349
 func (container *Container) StderrLog() io.Reader {
350
-	return strings.NewReader(container.stderrLog.String())
350
+	r, err := os.Open(container.stderrLog.Name())
351
+	if err != nil {
352
+		return nil
353
+	}
354
+	return r
355
+}
356
+
357
+func (container *Container) allocateNetwork() error {
358
+	iface, err := container.networkManager.Allocate()
359
+	if err != nil {
360
+		return err
361
+	}
362
+	container.NetworkSettings.PortMapping = make(map[string]string)
363
+	for _, port := range container.Config.Ports {
364
+		if extPort, err := iface.AllocatePort(port); err != nil {
365
+			iface.Release()
366
+			return err
367
+		} else {
368
+			container.NetworkSettings.PortMapping[strconv.Itoa(port)] = strconv.Itoa(extPort)
369
+		}
370
+	}
371
+	container.network = iface
372
+	container.NetworkSettings.IpAddress = iface.IPNet.IP.String()
373
+	container.NetworkSettings.IpPrefixLen, _ = iface.IPNet.Mask.Size()
374
+	container.NetworkSettings.Gateway = iface.Gateway.String()
375
+	return nil
376
+}
377
+
378
+func (container *Container) releaseNetwork() error {
379
+	err := container.network.Release()
380
+	container.network = nil
381
+	container.NetworkSettings = &NetworkSettings{}
382
+	return err
351 383
 }
352 384
 
353 385
 func (container *Container) monitor() {
... ...
@@ -356,6 +434,9 @@ func (container *Container) monitor() {
356 356
 	exitCode := container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()
357 357
 
358 358
 	// Cleanup
359
+	if err := container.releaseNetwork(); err != nil {
360
+		log.Printf("%v: Failed to release network: %v", container.Id, err)
361
+	}
359 362
 	container.stdout.Close()
360 363
 	container.stderr.Close()
361 364
 	if err := container.Mountpoint.Umount(); err != nil {
... ...
@@ -422,11 +503,13 @@ func (container *Container) Restart() error {
422 422
 	return nil
423 423
 }
424 424
 
425
-func (container *Container) Wait() {
425
+// Wait blocks until the container stops running, then returns its exit code.
426
+func (container *Container) Wait() int {
426 427
 
427 428
 	for container.State.Running {
428 429
 		container.State.wait()
429 430
 	}
431
+	return container.State.ExitCode
430 432
 }
431 433
 
432 434
 func (container *Container) WaitTimeout(timeout time.Duration) error {
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"fmt"
5 5
 	"io"
6 6
 	"io/ioutil"
7
+	"sort"
7 8
 	"strings"
8 9
 	"testing"
9 10
 	"time"
... ...
@@ -513,6 +514,55 @@ func TestTty(t *testing.T) {
513 513
 	}
514 514
 }
515 515
 
516
+func TestEnv(t *testing.T) {
517
+	docker, err := newTestDocker()
518
+	if err != nil {
519
+		t.Fatal(err)
520
+	}
521
+	container, err := docker.Create(
522
+		"env_test",
523
+		"/usr/bin/env",
524
+		[]string{},
525
+		GetTestImage(docker),
526
+		&Config{},
527
+	)
528
+	if err != nil {
529
+		t.Fatal(err)
530
+	}
531
+	defer docker.Destroy(container)
532
+	stdout, err := container.StdoutPipe()
533
+	if err != nil {
534
+		t.Fatal(err)
535
+	}
536
+	defer stdout.Close()
537
+	if err := container.Start(); err != nil {
538
+		t.Fatal(err)
539
+	}
540
+	container.Wait()
541
+	output, err := ioutil.ReadAll(stdout)
542
+	if err != nil {
543
+		t.Fatal(err)
544
+	}
545
+	actualEnv := strings.Split(string(output), "\n")
546
+	if actualEnv[len(actualEnv)-1] == "" {
547
+		actualEnv = actualEnv[:len(actualEnv)-1]
548
+	}
549
+	sort.Strings(actualEnv)
550
+	goodEnv := []string{
551
+		"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
552
+		"HOME=/",
553
+	}
554
+	sort.Strings(goodEnv)
555
+	if len(goodEnv) != len(actualEnv) {
556
+		t.Fatalf("Wrong environment: should be %d variables, not: '%s'\n", len(goodEnv), strings.Join(actualEnv, ", "))
557
+	}
558
+	for i := range goodEnv {
559
+		if actualEnv[i] != goodEnv[i] {
560
+			t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i])
561
+		}
562
+	}
563
+}
564
+
516 565
 func BenchmarkRunSequencial(b *testing.B) {
517 566
 	docker, err := newTestDocker()
518 567
 	if err != nil {
... ...
@@ -1,6 +1,7 @@
1 1
 package docker
2 2
 
3 3
 import (
4
+	"./fs"
4 5
 	"container/list"
5 6
 	"fmt"
6 7
 	"io/ioutil"
... ...
@@ -8,14 +9,14 @@ import (
8 8
 	"os"
9 9
 	"path"
10 10
 	"sort"
11
-	"./fs"
12 11
 )
13 12
 
14 13
 type Docker struct {
15
-	root       	string
16
-	repository 	string
17
-	containers 	*list.List
18
-	Store		*fs.Store
14
+	root           string
15
+	repository     string
16
+	containers     *list.List
17
+	networkManager *NetworkManager
18
+	Store          *fs.Store
19 19
 }
20 20
 
21 21
 func (docker *Docker) List() []*Container {
... ...
@@ -53,7 +54,8 @@ func (docker *Docker) Create(id string, command string, args []string, image *fs
53 53
 		return nil, fmt.Errorf("Container %v already exists", id)
54 54
 	}
55 55
 	root := path.Join(docker.repository, id)
56
-	container, err := createContainer(id, root, command, args, image, config)
56
+
57
+	container, err := createContainer(id, root, command, args, image, config, docker.networkManager)
57 58
 	if err != nil {
58 59
 		return nil, err
59 60
 	}
... ...
@@ -92,7 +94,7 @@ func (docker *Docker) restore() error {
92 92
 		return err
93 93
 	}
94 94
 	for _, v := range dir {
95
-		container, err := loadContainer(path.Join(docker.repository, v.Name()))
95
+		container, err := loadContainer(path.Join(docker.repository, v.Name()), docker.networkManager)
96 96
 		if err != nil {
97 97
 			log.Printf("Failed to load container %v: %v", v.Name(), err)
98 98
 			continue
... ...
@@ -112,12 +114,17 @@ func NewFromDirectory(root string) (*Docker, error) {
112 112
 	if err != nil {
113 113
 		return nil, err
114 114
 	}
115
+	netManager, err := newNetworkManager(networkBridgeIface)
116
+	if err != nil {
117
+		return nil, err
118
+	}
115 119
 
116 120
 	docker := &Docker{
117
-		root:       root,
118
-		repository: path.Join(root, "containers"),
119
-		containers: list.New(),
120
-		Store:		store,
121
+		root:           root,
122
+		repository:     path.Join(root, "containers"),
123
+		containers:     list.New(),
124
+		Store:          store,
125
+		networkManager: netManager,
121 126
 	}
122 127
 
123 128
 	if err := os.MkdirAll(docker.repository, 0700); err != nil && !os.IsExist(err) {
... ...
@@ -2,10 +2,10 @@ package main
2 2
 
3 3
 import (
4 4
 	"flag"
5
+	"github.com/dotcloud/docker/client"
5 6
 	"log"
6 7
 	"os"
7 8
 	"path"
8
-	"github.com/dotcloud/docker/client"
9 9
 )
10 10
 
11 11
 func main() {
... ...
@@ -27,4 +27,3 @@ func main() {
27 27
 		}
28 28
 	}
29 29
 }
30
-
... ...
@@ -1,17 +1,17 @@
1 1
 package docker
2 2
 
3 3
 import (
4
+	"./fs"
5
+	"io"
4 6
 	"io/ioutil"
5 7
 	"log"
6 8
 	"os"
7 9
 	"testing"
8
-	"io"
9
-	"./fs"
10 10
 )
11 11
 
12 12
 const testLayerPath string = "/var/lib/docker/docker-ut.tar"
13 13
 
14
-func layerArchive(tarfile string)  (io.Reader, error) {
14
+func layerArchive(tarfile string) (io.Reader, error) {
15 15
 	// FIXME: need to close f somewhere
16 16
 	f, err := os.Open(tarfile)
17 17
 	if err != nil {
... ...
@@ -57,7 +57,7 @@ func newTestDocker() (*Docker, error) {
57 57
 	return docker, nil
58 58
 }
59 59
 
60
-func GetTestImage(docker *Docker) (*fs.Image) {
60
+func GetTestImage(docker *Docker) *fs.Image {
61 61
 	imgs, err := docker.Store.Images()
62 62
 	if err != nil {
63 63
 		panic(err)
64 64
new file mode 100755
... ...
@@ -0,0 +1,73 @@
0
+#!/usr/bin/env docker -i
1
+
2
+# Uncomment to debug:
3
+#set -x
4
+
5
+export NORAW=1
6
+
7
+IMG=shykes/pybuilder:11d4f58638a72935
8
+
9
+if [ $# -lt 3 ]; then
10
+	echo "Usage: $0 build|run USER/REPO REV"
11
+	echo "Example usage:"
12
+	echo ""
13
+	echo "		REV=7d5f035432fe1453eea389b0f1b02a2a93c8009e"
14
+	echo "		$0 build shykes/helloflask \$REV"
15
+	echo "		$0 run shykes/helloflask \$REV"
16
+	echo ""
17
+	exit 1
18
+fi
19
+
20
+CMD=$1
21
+
22
+FORCE=0
23
+if [ "$2" = "-f" ]; then
24
+	FORCE=1
25
+	shift
26
+fi
27
+
28
+REPO=$2
29
+REV=$3
30
+
31
+BUILD_IMAGE=builds/github.com/$REPO/$REV
32
+
33
+
34
+if [ "$CMD" = "build" ]; then
35
+	if [ ! -z "`images -q $BUILD_IMAGE`" ]; then
36
+		if [ "$FORCE" -ne 1 ]; then
37
+			echo "$BUILD_IMAGE already exists"
38
+			exit
39
+		fi
40
+	fi
41
+
42
+	# Allocate a TTY to work around python's aggressive buffering of stdout
43
+	BUILD_JOB=`run -t $IMG /usr/local/bin/buildapp http://github.com/$REPO/archive/$REV.tar.gz`
44
+
45
+	if [ -z "$BUILD_JOB" ]; then
46
+		echo "Build failed"
47
+		exit 1
48
+	fi
49
+
50
+	if attach $BUILD_JOB ; then
51
+		BUILD_STATUS=`docker wait $BUILD_JOB`
52
+		if [ -z "$BUILD_STATUS" -o "$BUILD_STATUS" != 0 ]; then
53
+			echo "Build failed"
54
+			exit 1
55
+		fi
56
+
57
+	else
58
+		echo "Build failed"
59
+		exit 1
60
+	fi
61
+
62
+	commit $BUILD_JOB $BUILD_IMAGE
63
+
64
+	echo "Build saved at $BUILD_IMAGE"
65
+elif [ "$CMD" = "run" ]; then
66
+	RUN_JOB=`run $BUILD_IMAGE /usr/local/bin/runapp`
67
+	if [ -z "$RUN_JOB" ]; then
68
+		echo "Run failed"
69
+		exit 1
70
+	fi
71
+	attach $RUN_JOB
72
+fi
... ...
@@ -1,20 +1,19 @@
1 1
 package fake
2 2
 
3 3
 import (
4
+	"archive/tar"
4 5
 	"bytes"
5
-	"math/rand"
6
+	"github.com/kr/pty"
6 7
 	"io"
7
-	"archive/tar"
8
+	"math/rand"
8 9
 	"os/exec"
9
-	"github.com/kr/pty"
10 10
 )
11 11
 
12
-
13 12
 func FakeTar() (io.Reader, error) {
14 13
 	content := []byte("Hello world!\n")
15 14
 	buf := new(bytes.Buffer)
16 15
 	tw := tar.NewWriter(buf)
17
-	for _, name := range []string {"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
16
+	for _, name := range []string{"hello", "etc/postgres/postgres.conf", "etc/passwd", "var/log/postgres/postgres.conf"} {
18 17
 		hdr := new(tar.Header)
19 18
 		hdr.Size = int64(len(content))
20 19
 		hdr.Name = name
... ...
@@ -27,7 +26,6 @@ func FakeTar() (io.Reader, error) {
27 27
 	return buf, nil
28 28
 }
29 29
 
30
-
31 30
 func WriteFakeTar(dst io.Writer) error {
32 31
 	if data, err := FakeTar(); err != nil {
33 32
 		return err
... ...
@@ -37,7 +35,6 @@ func WriteFakeTar(dst io.Writer) error {
37 37
 	return nil
38 38
 }
39 39
 
40
-
41 40
 func RandomBytesChanged() uint {
42 41
 	return uint(rand.Int31n(24 * 1024 * 1024))
43 42
 }
... ...
@@ -54,7 +51,6 @@ func ContainerRunning() bool {
54 54
 	return false
55 55
 }
56 56
 
57
-
58 57
 func StartCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) {
59 58
 	if interactive {
60 59
 		term, err := pty.Start(cmd)
... ...
@@ -76,5 +72,3 @@ func StartCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadClose
76 76
 	}
77 77
 	return stdin, stdout, nil
78 78
 }
79
-
80
-
81 79
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package fs
1
+
2
+import (
3
+	"errors"
4
+	"io"
5
+	"io/ioutil"
6
+	"os/exec"
7
+)
8
+
9
+type Compression uint32
10
+
11
+const (
12
+	Uncompressed Compression = iota
13
+	Bzip2
14
+	Gzip
15
+)
16
+
17
+func (compression *Compression) Flag() string {
18
+	switch *compression {
19
+	case Bzip2:
20
+		return "j"
21
+	case Gzip:
22
+		return "z"
23
+	}
24
+	return ""
25
+}
26
+
27
+func Tar(path string, compression Compression) (io.Reader, error) {
28
+	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
29
+	return CmdStream(cmd)
30
+}
31
+
32
+func Untar(archive io.Reader, path string) error {
33
+	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
34
+	cmd.Stdin = archive
35
+	output, err := cmd.CombinedOutput()
36
+	if err != nil {
37
+		return errors.New(err.Error() + ": " + string(output))
38
+	}
39
+	return nil
40
+}
41
+
42
+func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
43
+	stdout, err := cmd.StdoutPipe()
44
+	if err != nil {
45
+		return nil, err
46
+	}
47
+	stderr, err := cmd.StderrPipe()
48
+	if err != nil {
49
+		return nil, err
50
+	}
51
+	pipeR, pipeW := io.Pipe()
52
+	go func() {
53
+		_, err := io.Copy(pipeW, stdout)
54
+		if err != nil {
55
+			pipeW.CloseWithError(err)
56
+		}
57
+		errText, e := ioutil.ReadAll(stderr)
58
+		if e != nil {
59
+			errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
60
+		}
61
+		if err := cmd.Wait(); err != nil {
62
+			// FIXME: can this block if stderr outputs more than the size of StderrPipe()'s buffer?
63
+			pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
64
+		} else {
65
+			pipeW.Close()
66
+		}
67
+	}()
68
+	if err := cmd.Start(); err != nil {
69
+		return nil, err
70
+	}
71
+	return pipeR, nil
72
+}
0 73
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package fs
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"os/exec"
6
+	"testing"
7
+)
8
+
9
+func TestCmdStreamBad(t *testing.T) {
10
+	badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
11
+	out, err := CmdStream(badCmd)
12
+	if err != nil {
13
+		t.Fatalf("Failed to start command: " + err.Error())
14
+	}
15
+	if output, err := ioutil.ReadAll(out); err == nil {
16
+		t.Fatalf("Command should have failed")
17
+	} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
18
+		t.Fatalf("Wrong error value (%s)", err.Error())
19
+	} else if s := string(output); s != "hello\n" {
20
+		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
21
+	}
22
+}
23
+
24
+func TestCmdStreamGood(t *testing.T) {
25
+	cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
26
+	out, err := CmdStream(cmd)
27
+	if err != nil {
28
+		t.Fatal(err)
29
+	}
30
+	if output, err := ioutil.ReadAll(out); err != nil {
31
+		t.Fatalf("Command should not have failed (err=%s)", err)
32
+	} else if s := string(output); s != "hello\n" {
33
+		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
34
+	}
35
+}
36
+
37
+func TestTarUntar(t *testing.T) {
38
+	archive, err := Tar(".", Uncompressed)
39
+	if err != nil {
40
+		t.Fatal(err)
41
+	}
42
+	tmp, err := ioutil.TempDir("", "docker-test-untar")
43
+	if err != nil {
44
+		t.Fatal(err)
45
+	}
46
+	defer os.RemoveAll(tmp)
47
+	if err := Untar(archive, tmp); err != nil {
48
+		t.Fatal(err)
49
+	}
50
+	if _, err := os.Stat(tmp); err != nil {
51
+		t.Fatalf("Error stating %s: %s", tmp, err.Error())
52
+	}
53
+}
... ...
@@ -1,29 +1,20 @@
1 1
 package fs
2 2
 
3 3
 import (
4
+	"../future"
4 5
 	"errors"
5
-	"path"
6
-	"path/filepath"
6
+	"fmt"
7 7
 	"io"
8 8
 	"io/ioutil"
9 9
 	"os"
10
-	"os/exec"
11
-	"fmt"
12
-	"../future"
10
+	"path"
11
+	"path/filepath"
13 12
 )
14 13
 
15 14
 type LayerStore struct {
16
-	Root	string
15
+	Root string
17 16
 }
18 17
 
19
-type Compression uint32
20
-
21
-const (
22
-	Uncompressed	Compression = iota
23
-	Bzip2
24
-	Gzip
25
-)
26
-
27 18
 func NewLayerStore(root string) (*LayerStore, error) {
28 19
 	abspath, err := filepath.Abs(root)
29 20
 	if err != nil {
... ...
@@ -80,10 +71,9 @@ func (store *LayerStore) Init() error {
80 80
 	return os.Mkdir(store.Root, 0700)
81 81
 }
82 82
 
83
-
84 83
 func (store *LayerStore) Mktemp() (string, error) {
85 84
 	tmpName := future.RandomId()
86
-	tmpPath := path.Join(store.Root, "tmp-" + tmpName)
85
+	tmpPath := path.Join(store.Root, "tmp-"+tmpName)
87 86
 	if err := os.Mkdir(tmpPath, 0700); err != nil {
88 87
 		return "", err
89 88
 	}
... ...
@@ -94,54 +84,42 @@ func (store *LayerStore) layerPath(id string) string {
94 94
 	return path.Join(store.Root, id)
95 95
 }
96 96
 
97
-
98
-func (store *LayerStore) AddLayer(id string, archive Archive, stderr io.Writer, compression Compression) (string, error) {
97
+func (store *LayerStore) AddLayer(id string, archive Archive) (string, error) {
99 98
 	if _, err := os.Stat(store.layerPath(id)); err == nil {
100
-		return "", errors.New("Layer already exists: " + id)
99
+		return "", fmt.Errorf("Layer already exists: %v", id)
101 100
 	}
101
+	errors := make(chan error)
102
+	// Untar
102 103
 	tmp, err := store.Mktemp()
103 104
 	defer os.RemoveAll(tmp)
104 105
 	if err != nil {
105
-		return "", errors.New(fmt.Sprintf("Mktemp failed: %s", err))
106
-	}
107
-	extractFlags := "-x"
108
-	if compression == Bzip2 {
109
-		extractFlags += "j"
110
-	} else if compression == Gzip {
111
-		extractFlags += "z"
112
-	}
113
-	untarCmd := exec.Command("tar", "-C", tmp, extractFlags)
114
-	untarW, err := untarCmd.StdinPipe()
115
-	if err != nil {
116
-		return "", errors.New(fmt.Sprintf("Could not obtain stdin pipe: %s", err))
117
-	}
118
-	untarStderr, err := untarCmd.StderrPipe()
119
-	if err != nil {
120
-		return "", errors.New(fmt.Sprintf("Could not obtain stderr pipe: %s", err))
106
+		return "", fmt.Errorf("Mktemp failed: %s", err)
121 107
 	}
122
-	go io.Copy(stderr, untarStderr)
123
-	untarStdout, err := untarCmd.StdoutPipe()
124
-	if err != nil {
125
-		return "", errors.New(fmt.Sprintf("Could not obtain stdout pipe: %s", err))
126
-	}
127
-	go io.Copy(stderr, untarStdout)
128
-	untarCmd.Start()
129
-	job_copy := future.Go(func() error {
130
-		_, err := io.Copy(untarW, archive)
131
-		untarW.Close()
132
-		return err
133
-	})
134 108
 
135
-	if err := untarCmd.Wait(); err != nil {
136
-		return "", errors.New(fmt.Sprintf("Error while waiting for untar command to complete: %s", err))
109
+	untarR, untarW := io.Pipe()
110
+	go func() {
111
+		errors <- Untar(untarR, tmp)
112
+	}()
113
+	_, err = io.Copy(untarW, archive)
114
+	untarW.Close()
115
+	if err != nil {
116
+		return "", err
137 117
 	}
138
-	if err := <-job_copy; err != nil {
139
-		return "", errors.New(fmt.Sprintf("Error while copying: %s", err))
118
+	// Wait for goroutines
119
+	for i := 0; i < 1; i += 1 {
120
+		select {
121
+		case err := <-errors:
122
+			{
123
+				if err != nil {
124
+					return "", err
125
+				}
126
+			}
127
+		}
140 128
 	}
141 129
 	layer := store.layerPath(id)
142 130
 	if !store.Exists(id) {
143 131
 		if err := os.Rename(tmp, layer); err != nil {
144
-			return "", errors.New(fmt.Sprintf("Could not rename temp dir to layer %s: %s", layer, err))
132
+			return "", fmt.Errorf("Could not rename temp dir to layer %s: %s", layer, err)
145 133
 		}
146 134
 	}
147 135
 	return layer, nil
... ...
@@ -1,14 +1,12 @@
1 1
 package fs
2 2
 
3 3
 import (
4
+	"github.com/dotcloud/docker/fake"
4 5
 	"io/ioutil"
5
-	"testing"
6 6
 	"os"
7
-	"github.com/dotcloud/docker/fake"
7
+	"testing"
8 8
 )
9 9
 
10
-
11
-
12 10
 func TestLayersInit(t *testing.T) {
13 11
 	store := tempStore(t)
14 12
 	defer os.RemoveAll(store.Root)
... ...
@@ -25,7 +23,7 @@ func TestLayersInit(t *testing.T) {
25 25
 func TestAddLayer(t *testing.T) {
26 26
 	store := tempStore(t)
27 27
 	defer os.RemoveAll(store.Root)
28
-	layer, err := store.AddLayer("foo", testArchive(t), os.Stderr, Uncompressed)
28
+	layer, err := store.AddLayer("foo", testArchive(t))
29 29
 	if err != nil {
30 30
 		t.Fatal(err)
31 31
 	}
... ...
@@ -46,15 +44,14 @@ func TestAddLayer(t *testing.T) {
46 46
 func TestAddLayerDuplicate(t *testing.T) {
47 47
 	store := tempStore(t)
48 48
 	defer os.RemoveAll(store.Root)
49
-	if _, err := store.AddLayer("foobar123", testArchive(t), os.Stderr, Uncompressed); err != nil {
49
+	if _, err := store.AddLayer("foobar123", testArchive(t)); err != nil {
50 50
 		t.Fatal(err)
51 51
 	}
52
-	if _, err := store.AddLayer("foobar123", testArchive(t), os.Stderr, Uncompressed); err == nil {
52
+	if _, err := store.AddLayer("foobar123", testArchive(t)); err == nil {
53 53
 		t.Fatalf("Creating duplicate layer should fail")
54 54
 	}
55 55
 }
56 56
 
57
-
58 57
 /*
59 58
  * HELPER FUNCTIONS
60 59
  */
... ...
@@ -10,9 +10,9 @@ import (
10 10
 	"io"
11 11
 	"os"
12 12
 	"path"
13
+	"path/filepath"
13 14
 	"syscall"
14 15
 	"time"
15
-	"path/filepath"
16 16
 )
17 17
 
18 18
 type Store struct {
... ...
@@ -121,7 +121,7 @@ func (store *Store) Create(layerData Archive, parent *Image, pth, comment string
121 121
 	}
122 122
 	// FIXME: we shouldn't have to pass os.Stderr to AddLayer()...
123 123
 	// FIXME: Archive should contain compression info. For now we only support uncompressed.
124
-	_, err := store.layers.AddLayer(img.Id, layerData, os.Stderr, Uncompressed)
124
+	_, err := store.layers.AddLayer(img.Id, layerData)
125 125
 	if err != nil {
126 126
 		return nil, errors.New(fmt.Sprintf("Could not add layer: %s", err))
127 127
 	}
... ...
@@ -168,7 +168,6 @@ type Image struct {
168 168
 	store   *Store `db:"-"`
169 169
 }
170 170
 
171
-
172 171
 func (image *Image) Copy(pth string) (*Image, error) {
173 172
 	if err := image.store.orm.Insert(&Path{Path: pth, Image: image.Id}); err != nil {
174 173
 		return nil, err
... ...
@@ -198,7 +197,7 @@ func (image *Image) Mountpoint(root, rw string) (*Mountpoint, error) {
198 198
 
199 199
 func (image *Image) layers() ([]string, error) {
200 200
 	var list []string
201
-	var err  error
201
+	var err error
202 202
 	currentImg := image
203 203
 	for currentImg != nil {
204 204
 		if layer := image.store.layers.Get(image.Id); layer != "" {
... ...
@@ -1,12 +1,13 @@
1 1
 package future
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"crypto/sha256"
5
-	"io"
6 6
 	"fmt"
7
-	"time"
8
-	"bytes"
7
+	"io"
9 8
 	"math/rand"
9
+	"os/exec"
10
+	"time"
10 11
 )
11 12
 
12 13
 func Seed() {
... ...
@@ -30,18 +31,18 @@ func HumanDuration(d time.Duration) string {
30 30
 		return "About a minute"
31 31
 	} else if minutes < 60 {
32 32
 		return fmt.Sprintf("%d minutes", minutes)
33
-	} else if hours := int(d.Hours()); hours  == 1{
33
+	} else if hours := int(d.Hours()); hours == 1 {
34 34
 		return "About an hour"
35 35
 	} else if hours < 48 {
36 36
 		return fmt.Sprintf("%d hours", hours)
37
-	} else if hours < 24 * 7 * 2 {
38
-		return fmt.Sprintf("%d days", hours / 24)
39
-	} else if hours < 24 * 30 * 3 {
40
-		return fmt.Sprintf("%d weeks", hours / 24 / 7)
41
-	} else if hours < 24 * 365 * 2 {
42
-		return fmt.Sprintf("%d months", hours / 24 / 30)
37
+	} else if hours < 24*7*2 {
38
+		return fmt.Sprintf("%d days", hours/24)
39
+	} else if hours < 24*30*3 {
40
+		return fmt.Sprintf("%d weeks", hours/24/7)
41
+	} else if hours < 24*365*2 {
42
+		return fmt.Sprintf("%d months", hours/24/30)
43 43
 	}
44
-	return fmt.Sprintf("%d years", d.Hours() / 24 / 365)
44
+	return fmt.Sprintf("%d years", d.Hours()/24/365)
45 45
 }
46 46
 
47 47
 func randomBytes() io.Reader {
... ...
@@ -61,3 +62,41 @@ func Go(f func() error) chan error {
61 61
 	return ch
62 62
 }
63 63
 
64
+// Pv wraps an io.Reader such that it is passed through unchanged,
65
+// but logs the number of bytes copied (comparable to the unix command pv)
66
+func Pv(src io.Reader, info io.Writer) io.Reader {
67
+	var totalBytes int
68
+	data := make([]byte, 2048)
69
+	r, w := io.Pipe()
70
+	go func() {
71
+		for {
72
+			if n, err := src.Read(data); err != nil {
73
+				w.CloseWithError(err)
74
+				return
75
+			} else {
76
+				totalBytes += n
77
+				fmt.Fprintf(info, "--> %d bytes\n", totalBytes)
78
+				if _, err = w.Write(data[:n]); err != nil {
79
+					return
80
+				}
81
+			}
82
+		}
83
+	}()
84
+	return r
85
+}
86
+
87
+// Curl makes an http request by executing the unix command 'curl', and returns
88
+// the body of the response. If `stderr` is not nil, a progress bar will be
89
+// written to it.
90
+func Curl(url string, stderr io.Writer) (io.Reader, error) {
91
+	curl := exec.Command("curl", "-#", "-L", url)
92
+	output, err := curl.StdoutPipe()
93
+	if err != nil {
94
+		return nil, err
95
+	}
96
+	curl.Stderr = stderr
97
+	if err := curl.Start(); err != nil {
98
+		return nil, err
99
+	}
100
+	return output, nil
101
+}
64 102
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package image
1
+
2
+import (
3
+	"errors"
4
+	"io"
5
+	"io/ioutil"
6
+	"os/exec"
7
+)
8
+
9
+type Compression uint32
10
+
11
+const (
12
+	Uncompressed Compression = iota
13
+	Bzip2
14
+	Gzip
15
+)
16
+
17
+func (compression *Compression) Flag() string {
18
+	switch *compression {
19
+	case Bzip2:
20
+		return "j"
21
+	case Gzip:
22
+		return "z"
23
+	}
24
+	return ""
25
+}
26
+
27
+func Tar(path string, compression Compression) (io.Reader, error) {
28
+	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
29
+	return CmdStream(cmd)
30
+}
31
+
32
+func Untar(archive io.Reader, path string) error {
33
+	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
34
+	cmd.Stdin = archive
35
+	output, err := cmd.CombinedOutput()
36
+	if err != nil {
37
+		return errors.New(err.Error() + ": " + string(output))
38
+	}
39
+	return nil
40
+}
41
+
42
+func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
43
+	stdout, err := cmd.StdoutPipe()
44
+	if err != nil {
45
+		return nil, err
46
+	}
47
+	stderr, err := cmd.StderrPipe()
48
+	if err != nil {
49
+		return nil, err
50
+	}
51
+	pipeR, pipeW := io.Pipe()
52
+	go func() {
53
+		_, err := io.Copy(pipeW, stdout)
54
+		if err != nil {
55
+			pipeW.CloseWithError(err)
56
+		}
57
+		errText, e := ioutil.ReadAll(stderr)
58
+		if e != nil {
59
+			errText = []byte("(...couldn't fetch stderr: " + e.Error() + ")")
60
+		}
61
+		if err := cmd.Wait(); err != nil {
62
+			// FIXME: can this block if stderr outputs more than the size of StderrPipe()'s buffer?
63
+			pipeW.CloseWithError(errors.New(err.Error() + ": " + string(errText)))
64
+		} else {
65
+			pipeW.Close()
66
+		}
67
+	}()
68
+	if err := cmd.Start(); err != nil {
69
+		return nil, err
70
+	}
71
+	return pipeR, nil
72
+}
0 73
new file mode 100644
... ...
@@ -0,0 +1,54 @@
0
+package image
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"os/exec"
6
+	"testing"
7
+)
8
+
9
+func TestCmdStreamBad(t *testing.T) {
10
+	badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
11
+	out, err := CmdStream(badCmd)
12
+	if err != nil {
13
+		t.Fatalf("Failed to start command: " + err.Error())
14
+	}
15
+	if output, err := ioutil.ReadAll(out); err == nil {
16
+		t.Fatalf("Command should have failed")
17
+	} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
18
+		t.Fatalf("Wrong error value (%s)", err.Error())
19
+	} else if s := string(output); s != "hello\n" {
20
+		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
21
+	}
22
+}
23
+
24
+func TestCmdStreamGood(t *testing.T) {
25
+	cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
26
+	out, err := CmdStream(cmd)
27
+	if err != nil {
28
+		t.Fatal(err)
29
+	}
30
+	if output, err := ioutil.ReadAll(out); err != nil {
31
+		t.Fatalf("Command should not have failed (err=%s)", err)
32
+	} else if s := string(output); s != "hello\n" {
33
+		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
34
+	}
35
+}
36
+
37
+func TestTarUntar(t *testing.T) {
38
+	archive, err := Tar(".", Uncompressed)
39
+	if err != nil {
40
+		t.Fatal(err)
41
+	}
42
+	tmp, err := ioutil.TempDir("", "docker-test-untar")
43
+	if err != nil {
44
+		t.Fatal(err)
45
+	}
46
+	defer os.RemoveAll(tmp)
47
+	if err := Untar(archive, tmp); err != nil {
48
+		t.Fatal(err)
49
+	}
50
+	if _, err := os.Stat(tmp); err != nil {
51
+		t.Fatalf("Error stating %s: %s", tmp, err.Error())
52
+	}
53
+}
... ...
@@ -1,27 +1,26 @@
1 1
 package image
2 2
 
3 3
 import (
4
+	"encoding/json"
5
+	"errors"
6
+	"github.com/dotcloud/docker/future"
4 7
 	"io"
5 8
 	"io/ioutil"
6
-	"encoding/json"
7
-	"time"
9
+	"os"
8 10
 	"path"
9 11
 	"path/filepath"
10
-	"errors"
12
+	"regexp"
11 13
 	"sort"
12
-	"os"
13
-	"github.com/dotcloud/docker/future"
14 14
 	"strings"
15
+	"time"
15 16
 )
16 17
 
17
-
18 18
 type Store struct {
19 19
 	*Index
20
-	Root	string
21
-	Layers	*LayerStore
20
+	Root   string
21
+	Layers *LayerStore
22 22
 }
23 23
 
24
-
25 24
 func New(root string) (*Store, error) {
26 25
 	abspath, err := filepath.Abs(root)
27 26
 	if err != nil {
... ...
@@ -38,22 +37,16 @@ func New(root string) (*Store, error) {
38 38
 		return nil, err
39 39
 	}
40 40
 	return &Store{
41
-		Root: abspath,
42
-		Index: NewIndex(path.Join(root, "index.json")),
41
+		Root:   abspath,
42
+		Index:  NewIndex(path.Join(root, "index.json")),
43 43
 		Layers: layers,
44 44
 	}, nil
45 45
 }
46 46
 
47
-type Compression uint32
48
-
49
-const (
50
-	Uncompressed	Compression = iota
51
-	Bzip2
52
-	Gzip
53
-)
54
-
55
-func (store *Store) Import(name string, archive io.Reader, stderr io.Writer, parent *Image, compression Compression) (*Image, error) {
56
-	layer, err := store.Layers.AddLayer(archive, stderr, compression)
47
+// Import creates a new image from the contents of `archive` and registers it in the store as `name`.
48
+// If `parent` is not nil, it will registered as the parent of the new image.
49
+func (store *Store) Import(name string, archive io.Reader, parent *Image) (*Image, error) {
50
+	layer, err := store.Layers.AddLayer(archive)
57 51
 	if err != nil {
58 52
 		return nil, err
59 53
 	}
... ...
@@ -79,20 +72,19 @@ func (store *Store) Create(name string, source string, layers ...string) (*Image
79 79
 	return image, nil
80 80
 }
81 81
 
82
-
83 82
 // Index
84 83
 
85 84
 type Index struct {
86
-	Path	string
87
-	ByName	map[string]*History
88
-	ById	map[string]*Image
85
+	Path   string
86
+	ByName map[string]*History
87
+	ById   map[string]*Image
89 88
 }
90 89
 
91 90
 func NewIndex(path string) *Index {
92 91
 	return &Index{
93
-		Path: path,
92
+		Path:   path,
94 93
 		ByName: make(map[string]*History),
95
-		ById: make(map[string]*Image),
94
+		ById:   make(map[string]*Image),
96 95
 	}
97 96
 }
98 97
 
... ...
@@ -218,11 +210,36 @@ func (index *Index) Delete(name string) error {
218 218
 	return nil
219 219
 }
220 220
 
221
+// DeleteMatch deletes all images whose name matches `pattern`
222
+func (index *Index) DeleteMatch(pattern string) error {
223
+	// Load
224
+	if err := index.load(); err != nil {
225
+		return err
226
+	}
227
+	for name, history := range index.ByName {
228
+		if match, err := regexp.MatchString(pattern, name); err != nil {
229
+			return err
230
+		} else if match {
231
+			// Remove from index lookup
232
+			for _, image := range *history {
233
+				delete(index.ById, image.Id)
234
+			}
235
+			// Remove from name lookup
236
+			delete(index.ByName, name)
237
+		}
238
+	}
239
+	// Save
240
+	if err := index.save(); err != nil {
241
+		return err
242
+	}
243
+	return nil
244
+}
245
+
221 246
 func (index *Index) Names() []string {
222 247
 	if err := index.load(); err != nil {
223 248
 		return []string{}
224 249
 	}
225
-	var names[]string
250
+	var names []string
226 251
 	for name := range index.ByName {
227 252
 		names = append(names, name)
228 253
 	}
... ...
@@ -285,23 +302,23 @@ func (history *History) Add(image *Image) {
285 285
 func (history *History) Del(id string) {
286 286
 	for idx, image := range *history {
287 287
 		if image.Id == id {
288
-			*history = append((*history)[:idx], (*history)[idx + 1:]...)
288
+			*history = append((*history)[:idx], (*history)[idx+1:]...)
289 289
 		}
290 290
 	}
291 291
 }
292 292
 
293 293
 type Image struct {
294
-	Id	string		// Globally unique identifier
295
-	Layers	[]string	// Absolute paths
296
-	Created	time.Time
297
-	Parent	string
294
+	Id      string   // Globally unique identifier
295
+	Layers  []string // Absolute paths
296
+	Created time.Time
297
+	Parent  string
298 298
 }
299 299
 
300 300
 func (image *Image) IdParts() (string, string) {
301 301
 	if len(image.Id) < 8 {
302 302
 		return "", image.Id
303 303
 	}
304
-	hash := image.Id[len(image.Id)-8:len(image.Id)]
304
+	hash := image.Id[len(image.Id)-8 : len(image.Id)]
305 305
 	name := image.Id[:len(image.Id)-9]
306 306
 	return name, hash
307 307
 }
... ...
@@ -322,7 +339,7 @@ func generateImageId(name string, layers []string) (string, error) {
322 322
 		for _, layer := range layers {
323 323
 			ids += path.Base(layer)
324 324
 		}
325
-		if h, err := future.ComputeId(strings.NewReader(ids)); err != nil  {
325
+		if h, err := future.ComputeId(strings.NewReader(ids)); err != nil {
326 326
 			return "", err
327 327
 		} else {
328 328
 			hash = h
... ...
@@ -337,9 +354,9 @@ func NewImage(name string, layers []string, parent string) (*Image, error) {
337 337
 		return nil, err
338 338
 	}
339 339
 	return &Image{
340
-		Id:		id,
341
-		Layers:		layers,
342
-		Created:	time.Now(),
343
-		Parent:		parent,
340
+		Id:      id,
341
+		Layers:  layers,
342
+		Created: time.Now(),
343
+		Parent:  parent,
344 344
 	}, nil
345 345
 }
346 346
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+package image
1
+
2
+import (
3
+	"bytes"
4
+	"github.com/dotcloud/docker/fake"
5
+	"github.com/dotcloud/docker/future"
6
+	"io/ioutil"
7
+	"os"
8
+	"testing"
9
+)
10
+
11
+func TestAddLayer(t *testing.T) {
12
+	tmp, err := ioutil.TempDir("", "docker-test-image")
13
+	if err != nil {
14
+		t.Fatal(err)
15
+	}
16
+	defer os.RemoveAll(tmp)
17
+	store, err := NewLayerStore(tmp)
18
+	if err != nil {
19
+		t.Fatal(err)
20
+	}
21
+	archive, err := fake.FakeTar()
22
+	if err != nil {
23
+		t.Fatal(err)
24
+	}
25
+	layer, err := store.AddLayer(archive)
26
+	if err != nil {
27
+		t.Fatal(err)
28
+	}
29
+	if _, err := os.Stat(layer); err != nil {
30
+		t.Fatalf("Error testing for existence of layer: %s\n", err.Error())
31
+	}
32
+}
33
+
34
+func TestComputeId(t *testing.T) {
35
+	id1, err := future.ComputeId(bytes.NewBufferString("hello world\n"))
36
+	if err != nil {
37
+		t.Fatal(err)
38
+	}
39
+	id2, err := future.ComputeId(bytes.NewBufferString("foo bar\n"))
40
+	if err != nil {
41
+		t.Fatal(err)
42
+	}
43
+	if id1 == id2 {
44
+		t.Fatalf("Identical checksums for difference content (%s == %s)", id1, id2)
45
+	}
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1,34 @@
0
+# This script is meant for quick & easy install via 'curl URL-OF-SCRIPPT | bash'
1
+# Courtesy of Jeff Lindsay <progrium@gmail.com>
2
+
3
+cd /tmp
4
+
5
+echo "Ensuring dependencies are installed..."
6
+apt-get --yes install lxc wget bsdtar 2>&1 > /dev/null
7
+
8
+echo "Downloading docker binary..."
9
+wget -q https://dl.dropbox.com/u/20637798/docker.tar.gz 2>&1 > /dev/null
10
+tar -xf docker.tar.gz 2>&1 > /dev/null
11
+
12
+echo "Installing into /usr/local/bin..."
13
+mv docker/docker /usr/local/bin
14
+mv dockerd/dockerd /usr/local/bin
15
+
16
+if [[ -f /etc/init/dockerd.conf ]]
17
+then
18
+  echo "Upstart script already exists."
19
+else
20
+  echo "Creating /etc/init/dockerd.conf..."
21
+  echo "exec /usr/local/bin/dockerd" > /etc/init/dockerd.conf
22
+fi
23
+
24
+echo "Restarting dockerd..."
25
+restart dockerd > /dev/null
26
+
27
+echo "Cleaning up..."
28
+rmdir docker
29
+rmdir dockerd
30
+rm docker.tar.gz
31
+
32
+echo "Finished!"
33
+echo
... ...
@@ -14,12 +14,12 @@ lxc.utsname = {{.Id}}
14 14
 #lxc.aa_profile = unconfined
15 15
 
16 16
 # network configuration
17
-#lxc.network.type = veth
18
-#lxc.network.flags = up
19
-#lxc.network.link = br0
20
-#lxc.network.name = eth0  # Internal container network interface name
21
-#lxc.network.mtu = 1500
22
-#lxc.network.ipv4 = {ip_address}/{ip_prefix_len}
17
+lxc.network.type = veth
18
+lxc.network.flags = up
19
+lxc.network.link = lxcbr0
20
+lxc.network.name = eth0
21
+lxc.network.mtu = 1500
22
+lxc.network.ipv4 = {{.NetworkSettings.IpAddress}}/{{.NetworkSettings.IpPrefixLen}}
23 23
 
24 24
 # root filesystem
25 25
 {{$ROOTFS := .Mountpoint.Root}}
... ...
@@ -2,7 +2,6 @@ package docker
2 2
 
3 3
 import "syscall"
4 4
 
5
-
6 5
 func mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
7 6
 	return syscall.Mount(source, target, fstype, flags, data)
8 7
 }
9 8
new file mode 100644
... ...
@@ -0,0 +1,356 @@
0
+package docker
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/binary"
5
+	"errors"
6
+	"fmt"
7
+	"log"
8
+	"net"
9
+	"os/exec"
10
+	"strconv"
11
+	"strings"
12
+)
13
+
14
+const (
15
+	networkBridgeIface = "lxcbr0"
16
+	portRangeStart     = 49153
17
+	portRangeEnd       = 65535
18
+)
19
+
20
+// Calculates the first and last IP addresses in an IPNet
21
+func networkRange(network *net.IPNet) (net.IP, net.IP) {
22
+	netIP := network.IP.To4()
23
+	firstIP := netIP.Mask(network.Mask)
24
+	lastIP := net.IPv4(0, 0, 0, 0).To4()
25
+	for i := 0; i < len(lastIP); i++ {
26
+		lastIP[i] = netIP[i] | ^network.Mask[i]
27
+	}
28
+	return firstIP, lastIP
29
+}
30
+
31
+// Converts a 4 bytes IP into a 32 bit integer
32
+func ipToInt(ip net.IP) (int32, error) {
33
+	buf := bytes.NewBuffer(ip.To4())
34
+	var n int32
35
+	if err := binary.Read(buf, binary.BigEndian, &n); err != nil {
36
+		return 0, err
37
+	}
38
+	return n, nil
39
+}
40
+
41
+// Converts 32 bit integer into a 4 bytes IP address
42
+func intToIp(n int32) (net.IP, error) {
43
+	var buf bytes.Buffer
44
+	if err := binary.Write(&buf, binary.BigEndian, &n); err != nil {
45
+		return net.IP{}, err
46
+	}
47
+	ip := net.IPv4(0, 0, 0, 0).To4()
48
+	for i := 0; i < net.IPv4len; i++ {
49
+		ip[i] = buf.Bytes()[i]
50
+	}
51
+	return ip, nil
52
+}
53
+
54
+// Given a netmask, calculates the number of available hosts
55
+func networkSize(mask net.IPMask) (int32, error) {
56
+	m := net.IPv4Mask(0, 0, 0, 0)
57
+	for i := 0; i < net.IPv4len; i++ {
58
+		m[i] = ^mask[i]
59
+	}
60
+	buf := bytes.NewBuffer(m)
61
+	var n int32
62
+	if err := binary.Read(buf, binary.BigEndian, &n); err != nil {
63
+		return 0, err
64
+	}
65
+	return n + 1, nil
66
+}
67
+
68
+// Wrapper around the iptables command
69
+func iptables(args ...string) error {
70
+	if err := exec.Command("/sbin/iptables", args...).Run(); err != nil {
71
+		return fmt.Errorf("iptables failed: iptables %v", strings.Join(args, " "))
72
+	}
73
+	return nil
74
+}
75
+
76
+// Return the IPv4 address of a network interface
77
+func getIfaceAddr(name string) (net.Addr, error) {
78
+	iface, err := net.InterfaceByName(name)
79
+	if err != nil {
80
+		return nil, err
81
+	}
82
+	addrs, err := iface.Addrs()
83
+	if err != nil {
84
+		return nil, err
85
+	}
86
+	var addrs4 []net.Addr
87
+	for _, addr := range addrs {
88
+		ip := (addr.(*net.IPNet)).IP
89
+		if ip4 := ip.To4(); len(ip4) == net.IPv4len {
90
+			addrs4 = append(addrs4, addr)
91
+		}
92
+	}
93
+	switch {
94
+	case len(addrs4) == 0:
95
+		return nil, fmt.Errorf("Interface %v has no IP addresses", name)
96
+	case len(addrs4) > 1:
97
+		return nil, fmt.Errorf("Interface %v has more than 1 IPv4 address", name)
98
+	}
99
+	return addrs4[0], nil
100
+}
101
+
102
+// Port mapper takes care of mapping external ports to containers by setting
103
+// up iptables rules.
104
+// It keeps track of all mappings and is able to unmap at will
105
+type PortMapper struct {
106
+	mapping map[int]net.TCPAddr
107
+}
108
+
109
+func (mapper *PortMapper) cleanup() error {
110
+	// Ignore errors - This could mean the chains were never set up
111
+	iptables("-t", "nat", "-D", "PREROUTING", "-j", "DOCKER")
112
+	iptables("-t", "nat", "-F", "DOCKER")
113
+	iptables("-t", "nat", "-X", "DOCKER")
114
+	mapper.mapping = make(map[int]net.TCPAddr)
115
+	return nil
116
+}
117
+
118
+func (mapper *PortMapper) setup() error {
119
+	if err := iptables("-t", "nat", "-N", "DOCKER"); err != nil {
120
+		return errors.New("Unable to setup port networking: Failed to create DOCKER chain")
121
+	}
122
+	if err := iptables("-t", "nat", "-A", "PREROUTING", "-j", "DOCKER"); err != nil {
123
+		return errors.New("Unable to setup port networking: Failed to inject docker in PREROUTING chain")
124
+	}
125
+	return nil
126
+}
127
+
128
+func (mapper *PortMapper) iptablesForward(rule string, port int, dest net.TCPAddr) error {
129
+	return iptables("-t", "nat", rule, "DOCKER", "-p", "tcp", "--dport", strconv.Itoa(port),
130
+		"-j", "DNAT", "--to-destination", net.JoinHostPort(dest.IP.String(), strconv.Itoa(dest.Port)))
131
+}
132
+
133
+func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error {
134
+	if err := mapper.iptablesForward("-A", port, dest); err != nil {
135
+		return err
136
+	}
137
+	mapper.mapping[port] = dest
138
+	return nil
139
+}
140
+
141
+func (mapper *PortMapper) Unmap(port int) error {
142
+	dest, ok := mapper.mapping[port]
143
+	if !ok {
144
+		return errors.New("Port is not mapped")
145
+	}
146
+	if err := mapper.iptablesForward("-D", port, dest); err != nil {
147
+		return err
148
+	}
149
+	delete(mapper.mapping, port)
150
+	return nil
151
+}
152
+
153
+func newPortMapper() (*PortMapper, error) {
154
+	mapper := &PortMapper{}
155
+	if err := mapper.cleanup(); err != nil {
156
+		return nil, err
157
+	}
158
+	if err := mapper.setup(); err != nil {
159
+		return nil, err
160
+	}
161
+	return mapper, nil
162
+}
163
+
164
+// Port allocator: Atomatically allocate and release networking ports
165
+type PortAllocator struct {
166
+	ports chan (int)
167
+}
168
+
169
+func (alloc *PortAllocator) populate(start, end int) {
170
+	alloc.ports = make(chan int, end-start)
171
+	for port := start; port < end; port++ {
172
+		alloc.ports <- port
173
+	}
174
+}
175
+
176
+func (alloc *PortAllocator) Acquire() (int, error) {
177
+	select {
178
+	case port := <-alloc.ports:
179
+		return port, nil
180
+	default:
181
+		return -1, errors.New("No more ports available")
182
+	}
183
+	return -1, nil
184
+}
185
+
186
+func (alloc *PortAllocator) Release(port int) error {
187
+	select {
188
+	case alloc.ports <- port:
189
+		return nil
190
+	default:
191
+		return errors.New("Too many ports have been released")
192
+	}
193
+	return nil
194
+}
195
+
196
+func newPortAllocator(start, end int) (*PortAllocator, error) {
197
+	allocator := &PortAllocator{}
198
+	allocator.populate(start, end)
199
+	return allocator, nil
200
+}
201
+
202
+// IP allocator: Atomatically allocate and release networking ports
203
+type IPAllocator struct {
204
+	network *net.IPNet
205
+	queue   chan (net.IP)
206
+}
207
+
208
+func (alloc *IPAllocator) populate() error {
209
+	firstIP, _ := networkRange(alloc.network)
210
+	size, err := networkSize(alloc.network.Mask)
211
+	if err != nil {
212
+		return err
213
+	}
214
+	// The queue size should be the network size - 3
215
+	// -1 for the network address, -1 for the broadcast address and
216
+	// -1 for the gateway address
217
+	alloc.queue = make(chan net.IP, size-3)
218
+	for i := int32(1); i < size-1; i++ {
219
+		ipNum, err := ipToInt(firstIP)
220
+		if err != nil {
221
+			return err
222
+		}
223
+		ip, err := intToIp(ipNum + int32(i))
224
+		if err != nil {
225
+			return err
226
+		}
227
+		// Discard the network IP (that's the host IP address)
228
+		if ip.Equal(alloc.network.IP) {
229
+			continue
230
+		}
231
+		alloc.queue <- ip
232
+	}
233
+	return nil
234
+}
235
+
236
+func (alloc *IPAllocator) Acquire() (net.IP, error) {
237
+	select {
238
+	case ip := <-alloc.queue:
239
+		return ip, nil
240
+	default:
241
+		return net.IP{}, errors.New("No more IP addresses available")
242
+	}
243
+	return net.IP{}, nil
244
+}
245
+
246
+func (alloc *IPAllocator) Release(ip net.IP) error {
247
+	select {
248
+	case alloc.queue <- ip:
249
+		return nil
250
+	default:
251
+		return errors.New("Too many IP addresses have been released")
252
+	}
253
+	return nil
254
+}
255
+
256
+func newIPAllocator(network *net.IPNet) (*IPAllocator, error) {
257
+	alloc := &IPAllocator{
258
+		network: network,
259
+	}
260
+	if err := alloc.populate(); err != nil {
261
+		return nil, err
262
+	}
263
+	return alloc, nil
264
+}
265
+
266
+// Network interface represents the networking stack of a container
267
+type NetworkInterface struct {
268
+	IPNet   net.IPNet
269
+	Gateway net.IP
270
+
271
+	manager  *NetworkManager
272
+	extPorts []int
273
+}
274
+
275
+// Allocate an external TCP port and map it to the interface
276
+func (iface *NetworkInterface) AllocatePort(port int) (int, error) {
277
+	extPort, err := iface.manager.portAllocator.Acquire()
278
+	if err != nil {
279
+		return -1, err
280
+	}
281
+	if err := iface.manager.portMapper.Map(extPort, net.TCPAddr{iface.IPNet.IP, port}); err != nil {
282
+		iface.manager.portAllocator.Release(extPort)
283
+		return -1, err
284
+	}
285
+	iface.extPorts = append(iface.extPorts, extPort)
286
+	return extPort, nil
287
+}
288
+
289
+// Release: Network cleanup - release all resources
290
+func (iface *NetworkInterface) Release() error {
291
+	for _, port := range iface.extPorts {
292
+		if err := iface.manager.portMapper.Unmap(port); err != nil {
293
+			log.Printf("Unable to unmap port %v: %v", port, err)
294
+		}
295
+		if err := iface.manager.portAllocator.Release(port); err != nil {
296
+			log.Printf("Unable to release port %v: %v", port, err)
297
+		}
298
+
299
+	}
300
+	return iface.manager.ipAllocator.Release(iface.IPNet.IP)
301
+}
302
+
303
+// Network Manager manages a set of network interfaces
304
+// Only *one* manager per host machine should be used
305
+type NetworkManager struct {
306
+	bridgeIface   string
307
+	bridgeNetwork *net.IPNet
308
+
309
+	ipAllocator   *IPAllocator
310
+	portAllocator *PortAllocator
311
+	portMapper    *PortMapper
312
+}
313
+
314
+// Allocate a network interface
315
+func (manager *NetworkManager) Allocate() (*NetworkInterface, error) {
316
+	ip, err := manager.ipAllocator.Acquire()
317
+	if err != nil {
318
+		return nil, err
319
+	}
320
+	iface := &NetworkInterface{
321
+		IPNet:   net.IPNet{ip, manager.bridgeNetwork.Mask},
322
+		Gateway: manager.bridgeNetwork.IP,
323
+		manager: manager,
324
+	}
325
+	return iface, nil
326
+}
327
+
328
+func newNetworkManager(bridgeIface string) (*NetworkManager, error) {
329
+	addr, err := getIfaceAddr(bridgeIface)
330
+	if err != nil {
331
+		return nil, err
332
+	}
333
+	network := addr.(*net.IPNet)
334
+
335
+	ipAllocator, err := newIPAllocator(network)
336
+	if err != nil {
337
+		return nil, err
338
+	}
339
+
340
+	portAllocator, err := newPortAllocator(portRangeStart, portRangeEnd)
341
+	if err != nil {
342
+		return nil, err
343
+	}
344
+
345
+	portMapper, err := newPortMapper()
346
+
347
+	manager := &NetworkManager{
348
+		bridgeIface:   bridgeIface,
349
+		bridgeNetwork: network,
350
+		ipAllocator:   ipAllocator,
351
+		portAllocator: portAllocator,
352
+		portMapper:    portMapper,
353
+	}
354
+	return manager, nil
355
+}
0 356
new file mode 100644
... ...
@@ -0,0 +1,130 @@
0
+package docker
1
+
2
+import (
3
+	"net"
4
+	"testing"
5
+)
6
+
7
+func TestNetworkRange(t *testing.T) {
8
+	// Simple class C test
9
+	_, network, _ := net.ParseCIDR("192.168.0.1/24")
10
+	first, last := networkRange(network)
11
+	if !first.Equal(net.ParseIP("192.168.0.0")) {
12
+		t.Error(first.String())
13
+	}
14
+	if !last.Equal(net.ParseIP("192.168.0.255")) {
15
+		t.Error(last.String())
16
+	}
17
+	if size, err := networkSize(network.Mask); err != nil || size != 256 {
18
+		t.Error(size, err)
19
+	}
20
+
21
+	// Class A test
22
+	_, network, _ = net.ParseCIDR("10.0.0.1/8")
23
+	first, last = networkRange(network)
24
+	if !first.Equal(net.ParseIP("10.0.0.0")) {
25
+		t.Error(first.String())
26
+	}
27
+	if !last.Equal(net.ParseIP("10.255.255.255")) {
28
+		t.Error(last.String())
29
+	}
30
+	if size, err := networkSize(network.Mask); err != nil || size != 16777216 {
31
+		t.Error(size, err)
32
+	}
33
+
34
+	// Class A, random IP address
35
+	_, network, _ = net.ParseCIDR("10.1.2.3/8")
36
+	first, last = networkRange(network)
37
+	if !first.Equal(net.ParseIP("10.0.0.0")) {
38
+		t.Error(first.String())
39
+	}
40
+	if !last.Equal(net.ParseIP("10.255.255.255")) {
41
+		t.Error(last.String())
42
+	}
43
+
44
+	// 32bit mask
45
+	_, network, _ = net.ParseCIDR("10.1.2.3/32")
46
+	first, last = networkRange(network)
47
+	if !first.Equal(net.ParseIP("10.1.2.3")) {
48
+		t.Error(first.String())
49
+	}
50
+	if !last.Equal(net.ParseIP("10.1.2.3")) {
51
+		t.Error(last.String())
52
+	}
53
+	if size, err := networkSize(network.Mask); err != nil || size != 1 {
54
+		t.Error(size, err)
55
+	}
56
+
57
+	// 31bit mask
58
+	_, network, _ = net.ParseCIDR("10.1.2.3/31")
59
+	first, last = networkRange(network)
60
+	if !first.Equal(net.ParseIP("10.1.2.2")) {
61
+		t.Error(first.String())
62
+	}
63
+	if !last.Equal(net.ParseIP("10.1.2.3")) {
64
+		t.Error(last.String())
65
+	}
66
+	if size, err := networkSize(network.Mask); err != nil || size != 2 {
67
+		t.Error(size, err)
68
+	}
69
+
70
+	// 26bit mask
71
+	_, network, _ = net.ParseCIDR("10.1.2.3/26")
72
+	first, last = networkRange(network)
73
+	if !first.Equal(net.ParseIP("10.1.2.0")) {
74
+		t.Error(first.String())
75
+	}
76
+	if !last.Equal(net.ParseIP("10.1.2.63")) {
77
+		t.Error(last.String())
78
+	}
79
+	if size, err := networkSize(network.Mask); err != nil || size != 64 {
80
+		t.Error(size, err)
81
+	}
82
+}
83
+
84
+func TestConversion(t *testing.T) {
85
+	ip := net.ParseIP("127.0.0.1")
86
+	i, err := ipToInt(ip)
87
+	if err != nil {
88
+		t.Fatal(err)
89
+	}
90
+	if i == 0 {
91
+		t.Fatal("converted to zero")
92
+	}
93
+	conv, err := intToIp(i)
94
+	if err != nil {
95
+		t.Fatal(err)
96
+	}
97
+	if !ip.Equal(conv) {
98
+		t.Error(conv.String())
99
+	}
100
+}
101
+
102
+func TestIPAllocator(t *testing.T) {
103
+	gwIP, n, _ := net.ParseCIDR("127.0.0.1/29")
104
+	alloc, err := newIPAllocator(&net.IPNet{gwIP, n.Mask})
105
+	if err != nil {
106
+		t.Fatal(err)
107
+	}
108
+	var lastIP net.IP
109
+	for i := 0; i < 5; i++ {
110
+		ip, err := alloc.Acquire()
111
+		if err != nil {
112
+			t.Fatal(err)
113
+		}
114
+		lastIP = ip
115
+	}
116
+	ip, err := alloc.Acquire()
117
+	if err == nil {
118
+		t.Fatal("There shouldn't be any IP addresses at this point")
119
+	}
120
+	// Release 1 IP
121
+	alloc.Release(lastIP)
122
+	ip, err = alloc.Acquire()
123
+	if err != nil {
124
+		t.Fatal(err)
125
+	}
126
+	if !ip.Equal(lastIP) {
127
+		t.Fatal(ip.String())
128
+	}
129
+}
... ...
@@ -8,7 +8,7 @@ class docker {
8 8
 
9 9
     Package { ensure => "installed" }
10 10
 
11
-    package { ["lxc", "debootstrap", "wget"]: }
11
+    package { ["lxc", "debootstrap", "wget", "bsdtar"]: }
12 12
 
13 13
     exec { "debootstrap" :
14 14
         require => Package["debootstrap"],
... ...
@@ -1,20 +1,21 @@
1 1
 package server
2 2
 
3 3
 import (
4
+	".."
5
+	"../fs"
6
+	"../future"
7
+	"../rcli"
4 8
 	"bufio"
5 9
 	"bytes"
6 10
 	"encoding/json"
7 11
 	"errors"
8 12
 	"fmt"
9
-	".."
10
-	"../future"
11
-	"../fs"
12
-	"../rcli"
13 13
 	"io"
14 14
 	"net/http"
15 15
 	"net/url"
16 16
 	"os"
17 17
 	"path"
18
+	"strconv"
18 19
 	"strings"
19 20
 	"sync"
20 21
 	"text/tabwriter"
... ...
@@ -43,6 +44,7 @@ func (srv *Server) Help() string {
43 43
 		{"ps", "Display a list of containers"},
44 44
 		{"pull", "Download a tarball and create a container from it"},
45 45
 		{"put", "Upload a tarball and create a container from it"},
46
+		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
46 47
 		{"rm", "Remove containers"},
47 48
 		{"kill", "Kill a running container"},
48 49
 		{"wait", "Wait for the state of a container to change"},
... ...
@@ -53,6 +55,7 @@ func (srv *Server) Help() string {
53 53
 		{"diff", "Inspect changes on a container's filesystem"},
54 54
 		{"commit", "Save the state of a container"},
55 55
 		{"attach", "Attach to the standard inputs and outputs of a running container"},
56
+		{"wait", "Block until a container exits, then print its exit code"},
56 57
 		{"info", "Display system-wide information"},
57 58
 		{"tar", "Stream the contents of a container as a tar archive"},
58 59
 		{"web", "Generate a web UI"},
... ...
@@ -63,6 +66,27 @@ func (srv *Server) Help() string {
63 63
 	return help
64 64
 }
65 65
 
66
+// 'docker wait': block until a container stops
67
+func (srv *Server) CmdWait(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
68
+	cmd := rcli.Subcmd(stdout, "wait", "[OPTIONS] NAME", "Block until a container stops, then print its exit code.")
69
+	if err := cmd.Parse(args); err != nil {
70
+		cmd.Usage()
71
+		return nil
72
+	}
73
+	if cmd.NArg() < 1 {
74
+		cmd.Usage()
75
+		return nil
76
+	}
77
+	for _, name := range cmd.Args() {
78
+		if container := srv.containers.Get(name); container != nil {
79
+			fmt.Fprintln(stdout, container.Wait())
80
+		} else {
81
+			return errors.New("No such container: " + name)
82
+		}
83
+	}
84
+	return nil
85
+}
86
+
66 87
 // 'docker info': display system-wide information.
67 88
 func (srv *Server) CmdInfo(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
68 89
 	fmt.Fprintf(stdout, "containers: %d\nversion: %s\nimages: %d\n",
... ...
@@ -269,8 +293,8 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
269 269
 	var obj interface{}
270 270
 	if container := srv.containers.Get(name); container != nil {
271 271
 		obj = container
272
-	//} else if image, err := srv.images.List(name); image != nil {
273
-	//	obj = image
272
+		//} else if image, err := srv.images.List(name); image != nil {
273
+		//	obj = image
274 274
 	} else {
275 275
 		return errors.New("No such container or image: " + name)
276 276
 	}
... ...
@@ -288,9 +312,34 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
288 288
 	return nil
289 289
 }
290 290
 
291
+func (srv *Server) CmdPort(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
292
+	cmd := rcli.Subcmd(stdout, "port", "[OPTIONS] CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
293
+	if err := cmd.Parse(args); err != nil {
294
+		cmd.Usage()
295
+		return nil
296
+	}
297
+	if cmd.NArg() != 2 {
298
+		cmd.Usage()
299
+		return nil
300
+	}
301
+	name := cmd.Arg(0)
302
+	privatePort := cmd.Arg(1)
303
+	if container := srv.containers.Get(name); container == nil {
304
+		return errors.New("No such container: " + name)
305
+	} else {
306
+		if frontend, exists := container.NetworkSettings.PortMapping[privatePort]; !exists {
307
+			return fmt.Errorf("No private port '%s' allocated on %s", privatePort, name)
308
+		} else {
309
+			fmt.Fprintln(stdout, frontend)
310
+		}
311
+	}
312
+	return nil
313
+}
314
+
291 315
 // 'docker rmi NAME' removes all images with the name NAME
292 316
 // func (srv *Server) CmdRmi(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
293 317
 // 	cmd := rcli.Subcmd(stdout, "rmimage", "[OPTIONS] IMAGE", "Remove an image")
318
+// 	fl_regexp := cmd.Bool("r", false, "Use IMAGE as a regular expression instead of an exact name")
294 319
 // 	if err := cmd.Parse(args); err != nil {
295 320
 // 		cmd.Usage()
296 321
 // 		return nil
... ...
@@ -300,11 +349,17 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
300 300
 // 		return nil
301 301
 // 	}
302 302
 // 	for _, name := range cmd.Args() {
303
-// 		image := srv.images.Find(name)
304
-// 		if image == nil {
305
-// 			return errors.New("No such image: " + name)
303
+// 		var err error
304
+// 		if *fl_regexp {
305
+// 			err = srv.images.DeleteMatch(name)
306
+// 		} else {
307
+// 			image := srv.images.Find(name)
308
+// 			if image == nil {
309
+// 				return errors.New("No such image: " + name)
310
+// 			}
311
+// 			err = srv.images.Delete(name)
306 312
 // 		}
307
-// 		if err := srv.images.Delete(name); err != nil {
313
+// 		if err != nil {
308 314
 // 			return err
309 315
 // 		}
310 316
 // 	}
... ...
@@ -367,11 +422,18 @@ func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string
367 367
 		u.Host = "s3.amazonaws.com"
368 368
 		u.Path = path.Join("/docker.io/images", u.Path)
369 369
 	}
370
-	fmt.Fprintf(stdout, "Downloading %s from %s...\n", name, u.String())
371
-	resp, err := http.Get(u.String())
370
+	fmt.Fprintf(stdout, "Downloading from %s\n", u.String())
371
+	// Download with curl (pretty progress bar)
372
+	// If curl is not available, fallback to http.Get()
373
+	archive, err := future.Curl(u.String(), stdout)
372 374
 	if err != nil {
373
-		return err
375
+		if resp, err := http.Get(u.String()); err != nil {
376
+			return err
377
+		} else {
378
+			archive = resp.Body
379
+		}
374 380
 	}
381
+	fmt.Fprintf(stdout, "Unpacking to %s\n", name)
375 382
 	img, err := srv.images.Create(resp.Body, nil, name, "")
376 383
 	if err != nil {
377 384
 		return err
... ...
@@ -668,10 +730,10 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
668 668
 	return errors.New("No such container: " + cmd.Arg(0))
669 669
 }
670 670
 
671
-func (srv *Server) CreateContainer(img *fs.Image, user string, tty bool, openStdin bool, comment string, cmd string, args ...string) (*docker.Container, error) {
671
+func (srv *Server) CreateContainer(img *fs.Image, ports []int, user string, tty bool, openStdin bool, comment string, cmd string, args ...string) (*docker.Container, error) {
672 672
 	id := future.RandomId()[:8]
673 673
 	container, err := srv.containers.Create(id, cmd, args, img,
674
-		&docker.Config{Hostname: id, User: user, Tty: tty, OpenStdin: openStdin})
674
+		&docker.Config{Hostname: id, Ports: ports, User: user, Tty: tty, OpenStdin: openStdin})
675 675
 	if err != nil {
676 676
 		return nil, err
677 677
 	}
... ...
@@ -732,6 +794,22 @@ func (srv *Server) CmdAttach(stdin io.ReadCloser, stdout io.Writer, args ...stri
732 732
 	return nil
733 733
 }
734 734
 
735
+// Ports type - Used to parse multiple -p flags
736
+type ports []int
737
+
738
+func (p *ports) String() string {
739
+	return fmt.Sprint(*p)
740
+}
741
+
742
+func (p *ports) Set(value string) error {
743
+	port, err := strconv.Atoi(value)
744
+	if err != nil {
745
+		return fmt.Errorf("Invalid port: %v", value)
746
+	}
747
+	*p = append(*p, port)
748
+	return nil
749
+}
750
+
735 751
 func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
736 752
 	cmd := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
737 753
 	fl_user := cmd.String("u", "", "Username or UID")
... ...
@@ -739,6 +817,8 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
739 739
 	fl_stdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
740 740
 	fl_tty := cmd.Bool("t", false, "Allocate a pseudo-tty")
741 741
 	fl_comment := cmd.String("c", "", "Comment")
742
+	var fl_ports ports
743
+	cmd.Var(&fl_ports, "p", "Map a network port to the container")
742 744
 	if err := cmd.Parse(args); err != nil {
743 745
 		return nil
744 746
 	}
... ...
@@ -766,7 +846,7 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
766 766
 		return errors.New("No such image: " + name)
767 767
 	}
768 768
 	// Create new container
769
-	container, err := srv.CreateContainer(img, *fl_user, *fl_tty, *fl_stdin, *fl_comment, cmdline[0], cmdline[1:]...)
769
+	container, err := srv.CreateContainer(img, fl_ports, *fl_user, *fl_tty, *fl_stdin, *fl_comment, cmdline[0], cmdline[1:]...)
770 770
 	if err != nil {
771 771
 		return errors.New("Error creating container: " + err.Error())
772 772
 	}
... ...
@@ -11,6 +11,17 @@ import (
11 11
 	"syscall"
12 12
 )
13 13
 
14
+// Setup networking
15
+func setupNetworking(gw string) {
16
+	if gw == "" {
17
+		return
18
+	}
19
+	cmd := exec.Command("/sbin/route", "add", "default", "gw", gw)
20
+	if err := cmd.Run(); err != nil {
21
+		log.Fatalf("Unable to set up networking: %v", err)
22
+	}
23
+}
24
+
14 25
 // Takes care of dropping privileges to the desired user
15 26
 func changeUser(u string) {
16 27
 	if u == "" {
... ...
@@ -41,6 +52,13 @@ func changeUser(u string) {
41 41
 	}
42 42
 }
43 43
 
44
+// Set the environment to a known, repeatable state
45
+func setupEnv() {
46
+	os.Clearenv()
47
+	os.Setenv("HOME", "/")
48
+	os.Setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")
49
+}
50
+
44 51
 func executeProgram(name string, args []string) {
45 52
 	path, err := exec.LookPath(name)
46 53
 	if err != nil {
... ...
@@ -62,8 +80,12 @@ func SysInit() {
62 62
 		os.Exit(1)
63 63
 	}
64 64
 	var u = flag.String("u", "", "username or uid")
65
+	var gw = flag.String("g", "", "gateway address")
65 66
 
66 67
 	flag.Parse()
68
+
69
+	setupNetworking(*gw)
67 70
 	changeUser(*u)
71
+	setupEnv()
68 72
 	executeProgram(flag.Arg(0), flag.Args())
69 73
 }
... ...
@@ -17,25 +17,6 @@ func Trunc(s string, maxlen int) string {
17 17
 	return s[:maxlen]
18 18
 }
19 19
 
20
-// Tar generates a tar archive from a filesystem path, and returns it as a stream.
21
-// Path must point to a directory.
22
-
23
-func Tar(path string) (io.Reader, error) {
24
-	cmd := exec.Command("tar", "-C", path, "-c", ".")
25
-	output, err := cmd.StdoutPipe()
26
-	if err != nil {
27
-		return nil, err
28
-	}
29
-	if err := cmd.Start(); err != nil {
30
-		return nil, err
31
-	}
32
-	// FIXME: errors will not be passed because we don't wait for the command.
33
-	// Instead, consumers will hit EOF right away.
34
-	// This can be fixed by waiting for the process to exit, or for the first write
35
-	// on stdout, whichever comes first.
36
-	return output, nil
37
-}
38
-
39 20
 // Figure out the absolute path of our own binary
40 21
 func SelfPath() string {
41 22
 	path, err := exec.LookPath(os.Args[0])