... | ... |
@@ -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) { |
... | ... |
@@ -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}} |
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 |
+} |
... | ... |
@@ -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]) |