Browse code

bump to master

Victor Vieux authored on 2013/05/31 08:38:40
Showing 94 changed files
... ...
@@ -1,3 +1,8 @@
1
+# This file lists all individuals having contributed content to the repository.
2
+# If you're submitting a patch, please add your name here in alphabetical order as part of the patch.
3
+#
4
+# For a list of active project maintainers, see the MAINTAINERS file.
5
+#
1 6
 Al Tobey <al@ooyala.com>
2 7
 Alexey Shamrin <shamrin@gmail.com>
3 8
 Andrea Luzzardi <aluzzardi@gmail.com>
... ...
@@ -1,5 +1,9 @@
1 1
 # Changelog
2 2
 
3
+## 0.3.3 (2013-05-23)
4
+ - Registry: Fix push regression
5
+ - Various bugfixes
6
+
3 7
 ## 0.3.2 (2013-05-09)
4 8
  * Runtime: Store the actual archive on commit
5 9
  * Registry: Improve the checksum process
... ...
@@ -1,9 +1,6 @@
1 1
 # Contributing to Docker
2 2
 
3
-Want to hack on Docker? Awesome! There are instructions to get you
4
-started on the website: http://docker.io/gettingstarted.html
5
-
6
-They are probably not perfect, please let us know if anything feels
3
+Want to hack on Docker? Awesome! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels
7 4
 wrong or incomplete.
8 5
 
9 6
 ## Contribution guidelines
... ...
@@ -91,3 +88,73 @@ Add your name to the AUTHORS file, but make sure the list is sorted and your
91 91
 name and email address match your git configuration. The AUTHORS file is
92 92
 regenerated occasionally from the git commit history, so a mismatch may result
93 93
 in your changes being overwritten.
94
+
95
+
96
+## Decision process
97
+
98
+### How are decisions made?
99
+
100
+Short answer: with pull requests to the docker repository.
101
+
102
+Docker is an open-source project with an open design philosophy. This means that the repository is the source of truth for EVERY aspect of the project,
103
+including its philosophy, design, roadmap and APIs. *If it's part of the project, it's in the repo. It's in the repo, it's part of the project.*
104
+
105
+As a result, all decisions can be expressed as changes to the repository. An implementation change is a change to the source code. An API change is a change to
106
+the API specification. A philosophy change is a change to the philosophy manifesto. And so on.
107
+
108
+All decisions affecting docker, big and small, follow the same 3 steps:
109
+
110
+* Step 1: Open a pull request. Anyone can do this.
111
+
112
+* Step 2: Discuss the pull request. Anyone can do this.
113
+
114
+* Step 3: Accept or refuse a pull request. The relevant maintainer does this (see below "Who decides what?")
115
+
116
+
117
+### Who decides what?
118
+
119
+So all decisions are pull requests, and the relevant maintainer makes the decision by accepting or refusing the pull request.
120
+But how do we identify the relevant maintainer for a given pull request?
121
+
122
+Docker follows the timeless, highly efficient and totally unfair system known as [Benevolent dictator for life](http://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life),
123
+with yours truly, Solomon Hykes, in the role of BDFL.
124
+This means that all decisions are made by default by me. Since making every decision myself would be highly unscalable, in practice decisions are spread across multiple maintainers.
125
+
126
+The relevant maintainer for a pull request is assigned in 3 steps:
127
+
128
+* Step 1: Determine the subdirectory affected by the pull request. This might be src/registry, docs/source/api, or any other part of the repo.
129
+
130
+* Step 2: Find the MAINTAINERS file which affects this directory. If the directory itself does not have a MAINTAINERS file, work your way up the the repo hierarchy until you find one.
131
+
132
+* Step 3: The first maintainer listed is the primary maintainer. The pull request is assigned to him. He may assign it to other listed maintainers, at his discretion.
133
+
134
+
135
+### I'm a maintainer, should I make pull requests too?
136
+
137
+Primary maintainers are not required to create pull requests when changing their own subdirectory, but secondary maintainers are.
138
+
139
+### Who assigns maintainers?
140
+
141
+Solomon.
142
+
143
+### How can I become a maintainer?
144
+
145
+* Step 1: learn the component inside out
146
+* Step 2: make yourself useful by contributing code, bugfixes, support etc.
147
+* Step 3: volunteer on the irc channel (#docker@freenode)
148
+
149
+Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available.
150
+You don't have to be a maintainer to make a difference on the project!
151
+
152
+### What are a maintainer's responsibility?
153
+
154
+It is every maintainer's responsibility to:
155
+
156
+* 1) Expose a clear roadmap for improving their component.
157
+* 2) Deliver prompt feedback and decisions on pull requests.
158
+* 3) Be available to anyone with questions, bug reports, criticism etc. on their component. This includes irc, github requests and the mailing list.
159
+* 4) Make sure their component respects the philosophy, design and roadmap of the project.
160
+
161
+### How is this process changed?
162
+
163
+Just like everything else: by making a pull request :)
94 164
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+Solomon Hykes <solomon@dotcloud.com>
1
+Guillaume Charmes <guillaume@dotcloud.com>
2
+Victor Vieux <victor@dotcloud.com>
3
+api.go: Victor Vieux <victor@dotcloud.com>
4
+Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>
... ...
@@ -12,7 +12,7 @@ Docker is an open-source implementation of the deployment engine which powers [d
12 12
 It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
13 13
 of applications and databases.
14 14
 
15
-![Docker L](docs/sources/static_files/lego_docker.jpg "Docker")
15
+![Docker L](docs/sources/concepts/images/lego_docker.jpg "Docker")
16 16
 
17 17
 ## Better than VMs
18 18
 
19 19
deleted file mode 100644
... ...
@@ -1,71 +0,0 @@
1
-
2
-## Spec for data volumes
3
-
4
-Spec owner: Solomon Hykes <solomon@dotcloud.com>
5
-
6
-Data volumes (issue #111) are a much-requested feature which trigger much discussion and debate. Below is the current authoritative spec for implementing data volumes.
7
-This spec will be deprecated once the feature is fully implemented.
8
-
9
-Discussion, requests, trolls, demands, offerings, threats and other forms of supplications concerning this spec should be addressed to Solomon here: https://github.com/dotcloud/docker/issues/111
10
-
11
-
12
-### 1. Creating data volumes
13
-
14
-At container creation, parts of a container's filesystem can be mounted as separate data volumes. Volumes are defined with the -v flag.
15
-
16
-For example:
17
-
18
-```bash
19
-$ docker run -v /var/lib/postgres -v /var/log postgres /usr/bin/postgres
20
-```
21
-
22
-In this example, a new container is created from the 'postgres' image. At the same time, docker creates 2 new data volumes: one will be mapped to the container at /var/lib/postgres, the other at /var/log.
23
-
24
-2 important notes:
25
-
26
-1) Volumes don't have top-level names. At no point does the user provide a name, or is a name given to him. Volumes are identified by the path at which they are mounted inside their container.
27
-
28
-2) The user doesn't choose the source of the volume. Docker only mounts volumes it created itself, in the same way that it only runs containers that it created itself. That is by design.
29
-
30
-
31
-### 2. Sharing data volumes
32
-
33
-Instead of creating its own volumes, a container can share another container's volumes. For example:
34
-
35
-```bash
36
-$ docker run --volumes-from $OTHER_CONTAINER_ID postgres /usr/local/bin/postgres-backup
37
-```
38
-
39
-In this example, a new container is created from the 'postgres' example. At the same time, docker will *re-use* the 2 data volumes created in the previous example. One volume will be mounted on the /var/lib/postgres of *both* containers, and the other will be mounted on the /var/log of both containers.
40
-
41
-### 3. Under the hood
42
-
43
-Docker stores volumes in /var/lib/docker/volumes. Each volume receives a globally unique ID at creation, and is stored at /var/lib/docker/volumes/ID.
44
-
45
-At creation, volumes are attached to a single container - the source of truth for this mapping will be the container's configuration.
46
-
47
-Mounting a volume consists of calling "mount --bind" from the volume's directory to the appropriate sub-directory of the container mountpoint. This may be done by Docker itself, or farmed out to lxc (which supports mount-binding) if possible.
48
-
49
-
50
-### 4. Backups, transfers and other volume operations
51
-
52
-Volumes sometimes need to be backed up, transfered between hosts, synchronized, etc. These operations typically are application-specific or site-specific, eg. rsync vs. S3 upload vs. replication vs...
53
-
54
-Rather than attempting to implement all these scenarios directly, Docker will allow for custom implementations using an extension mechanism.
55
-
56
-### 5. Custom volume handlers
57
-
58
-Docker allows for arbitrary code to be executed against a container's volumes, to implement any custom action: backup, transfer, synchronization across hosts, etc.
59
-
60
-Here's an example:
61
-
62
-```bash
63
-$ DB=$(docker run -d -v /var/lib/postgres -v /var/log postgres /usr/bin/postgres)
64
-
65
-$ BACKUP_JOB=$(docker run -d --volumes-from $DB shykes/backuper /usr/local/bin/backup-postgres --s3creds=$S3CREDS)
66
-
67
-$ docker wait $BACKUP_JOB
68
-```
69
-
70
-Congratulations, you just implemented a custom volume handler, using Docker's built-in ability to 1) execute arbitrary code and 2) share volumes between containers.
71
-
... ...
@@ -13,6 +13,8 @@ import (
13 13
 	"strings"
14 14
 )
15 15
 
16
+const API_VERSION = 1.1
17
+
16 18
 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
17 19
 	conn, _, err := w.(http.Hijacker).Hijack()
18 20
 	if err != nil {
... ...
@@ -31,6 +33,13 @@ func parseForm(r *http.Request) error {
31 31
 	return nil
32 32
 }
33 33
 
34
+func parseMultipartForm(r *http.Request) error {
35
+	if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
36
+		return err
37
+	}
38
+	return nil
39
+}
40
+
34 41
 func httpError(w http.ResponseWriter, err error) {
35 42
 	if strings.HasPrefix(err.Error(), "No such") {
36 43
 		http.Error(w, err.Error(), http.StatusNotFound)
... ...
@@ -46,6 +55,7 @@ func writeJson(w http.ResponseWriter, b []byte) {
46 46
 	w.Write(b)
47 47
 }
48 48
 
49
+// FIXME: Use stvconv.ParseBool() instead?
49 50
 func getBoolParam(value string) (bool, error) {
50 51
 	if value == "1" || strings.ToLower(value) == "true" {
51 52
 		return true, nil
... ...
@@ -56,8 +66,17 @@ func getBoolParam(value string) (bool, error) {
56 56
 	return false, fmt.Errorf("Bad parameter")
57 57
 }
58 58
 
59
-func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
60
-	b, err := json.Marshal(srv.registry.GetAuthConfig())
59
+func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
60
+	// FIXME: Handle multiple login at once
61
+	// FIXME: return specific error code if config file missing?
62
+	authConfig, err := auth.LoadConfig(srv.runtime.root)
63
+	if err != nil {
64
+		if err != auth.ErrConfigFileMissing {
65
+			return err
66
+		}
67
+		authConfig = &auth.AuthConfig{}
68
+	}
69
+	b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email})
61 70
 	if err != nil {
62 71
 		return err
63 72
 	}
... ...
@@ -65,14 +84,22 @@ func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[strin
65 65
 	return nil
66 66
 }
67 67
 
68
-func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
68
+func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
69
+	// FIXME: Handle multiple login at once
69 70
 	config := &auth.AuthConfig{}
70 71
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
71 72
 		return err
72 73
 	}
73 74
 
74
-	if config.Username == srv.registry.GetAuthConfig().Username {
75
-		config.Password = srv.registry.GetAuthConfig().Password
75
+	authConfig, err := auth.LoadConfig(srv.runtime.root)
76
+	if err != nil {
77
+		if err != auth.ErrConfigFileMissing {
78
+			return err
79
+		}
80
+		authConfig = &auth.AuthConfig{}
81
+	}
82
+	if config.Username == authConfig.Username {
83
+		config.Password = authConfig.Password
76 84
 	}
77 85
 
78 86
 	newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root)
... ...
@@ -80,7 +107,6 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[stri
80 80
 	if err != nil {
81 81
 		return err
82 82
 	}
83
-	srv.registry.ResetClient(newAuthConfig)
84 83
 
85 84
 	if status != "" {
86 85
 		b, err := json.Marshal(&ApiAuth{Status: status})
... ...
@@ -94,7 +120,7 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[stri
94 94
 	return nil
95 95
 }
96 96
 
97
-func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
97
+func getVersion(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
98 98
 	m := srv.DockerVersion()
99 99
 	b, err := json.Marshal(m)
100 100
 	if err != nil {
... ...
@@ -104,7 +130,7 @@ func getVersion(srv *Server, w http.ResponseWriter, r *http.Request, vars map[st
104 104
 	return nil
105 105
 }
106 106
 
107
-func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
107
+func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
108 108
 	if vars == nil {
109 109
 		return fmt.Errorf("Missing parameter")
110 110
 	}
... ...
@@ -116,7 +142,7 @@ func postContainersKill(srv *Server, w http.ResponseWriter, r *http.Request, var
116 116
 	return nil
117 117
 }
118 118
 
119
-func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
119
+func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
120 120
 	if vars == nil {
121 121
 		return fmt.Errorf("Missing parameter")
122 122
 	}
... ...
@@ -129,7 +155,7 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, va
129 129
 	return nil
130 130
 }
131 131
 
132
-func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
132
+func getImagesJson(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
133 133
 	if err := parseForm(r); err != nil {
134 134
 		return err
135 135
 	}
... ...
@@ -152,14 +178,14 @@ func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map
152 152
 	return nil
153 153
 }
154 154
 
155
-func getImagesViz(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
155
+func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
156 156
 	if err := srv.ImagesViz(w); err != nil {
157 157
 		return err
158 158
 	}
159 159
 	return nil
160 160
 }
161 161
 
162
-func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
162
+func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
163 163
 	out := srv.DockerInfo()
164 164
 	b, err := json.Marshal(out)
165 165
 	if err != nil {
... ...
@@ -169,7 +195,7 @@ func getInfo(srv *Server, w http.ResponseWriter, r *http.Request, vars map[strin
169 169
 	return nil
170 170
 }
171 171
 
172
-func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
172
+func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
173 173
 	if vars == nil {
174 174
 		return fmt.Errorf("Missing parameter")
175 175
 	}
... ...
@@ -186,7 +212,7 @@ func getImagesHistory(srv *Server, w http.ResponseWriter, r *http.Request, vars
186 186
 	return nil
187 187
 }
188 188
 
189
-func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
189
+func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
190 190
 	if vars == nil {
191 191
 		return fmt.Errorf("Missing parameter")
192 192
 	}
... ...
@@ -203,7 +229,7 @@ func getContainersChanges(srv *Server, w http.ResponseWriter, r *http.Request, v
203 203
 	return nil
204 204
 }
205 205
 
206
-func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
206
+func getContainersJson(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
207 207
 	if err := parseForm(r); err != nil {
208 208
 		return err
209 209
 	}
... ...
@@ -227,7 +253,7 @@ func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars m
227 227
 	return nil
228 228
 }
229 229
 
230
-func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
230
+func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
231 231
 	if err := parseForm(r); err != nil {
232 232
 		return err
233 233
 	}
... ...
@@ -249,7 +275,7 @@ func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map
249 249
 	return nil
250 250
 }
251 251
 
252
-func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
252
+func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
253 253
 	if err := parseForm(r); err != nil {
254 254
 		return err
255 255
 	}
... ...
@@ -276,7 +302,7 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[st
276 276
 }
277 277
 
278 278
 // Creates an image from Pull or from Import
279
-func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
279
+func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
280 280
 	if err := parseForm(r); err != nil {
281 281
 		return err
282 282
 	}
... ...
@@ -288,7 +314,10 @@ func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars
288 288
 
289 289
 	if image != "" { //pull
290 290
 		registry := r.Form.Get("registry")
291
-		if err := srv.ImagePull(image, tag, registry, w); err != nil {
291
+		if version > 1.0 {
292
+			w.Header().Set("Content-Type", "application/json")
293
+		}
294
+		if err := srv.ImagePull(image, tag, registry, w, version > 1.0); err != nil {
292 295
 			return err
293 296
 		}
294 297
 	} else { //import
... ...
@@ -299,7 +328,7 @@ func postImagesCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars
299 299
 	return nil
300 300
 }
301 301
 
302
-func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
302
+func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
303 303
 	if err := parseForm(r); err != nil {
304 304
 		return err
305 305
 	}
... ...
@@ -317,7 +346,7 @@ func getImagesSearch(srv *Server, w http.ResponseWriter, r *http.Request, vars m
317 317
 	return nil
318 318
 }
319 319
 
320
-func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
320
+func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
321 321
 	if err := parseForm(r); err != nil {
322 322
 		return err
323 323
 	}
... ...
@@ -329,13 +358,19 @@ func postImagesInsert(srv *Server, w http.ResponseWriter, r *http.Request, vars
329 329
 	}
330 330
 	name := vars["name"]
331 331
 
332
-	if err := srv.ImageInsert(name, url, path, w); err != nil {
332
+	imgId, err := srv.ImageInsert(name, url, path, w)
333
+	if err != nil {
334
+		return err
335
+	}
336
+	b, err := json.Marshal(&ApiId{Id: imgId})
337
+	if err != nil {
333 338
 		return err
334 339
 	}
340
+	writeJson(w, b)
335 341
 	return nil
336 342
 }
337 343
 
338
-func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
344
+func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
339 345
 	if err := parseForm(r); err != nil {
340 346
 		return err
341 347
 	}
... ...
@@ -352,7 +387,7 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars ma
352 352
 	return nil
353 353
 }
354 354
 
355
-func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
355
+func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
356 356
 	config := &Config{}
357 357
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
358 358
 		return err
... ...
@@ -382,7 +417,7 @@ func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, v
382 382
 	return nil
383 383
 }
384 384
 
385
-func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
385
+func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
386 386
 	if err := parseForm(r); err != nil {
387 387
 		return err
388 388
 	}
... ...
@@ -401,7 +436,7 @@ func postContainersRestart(srv *Server, w http.ResponseWriter, r *http.Request,
401 401
 	return nil
402 402
 }
403 403
 
404
-func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
404
+func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
405 405
 	if err := parseForm(r); err != nil {
406 406
 		return err
407 407
 	}
... ...
@@ -421,7 +456,7 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars
421 421
 	return nil
422 422
 }
423 423
 
424
-func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
424
+func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
425 425
 	if vars == nil {
426 426
 		return fmt.Errorf("Missing parameter")
427 427
 	}
... ...
@@ -433,7 +468,7 @@ func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[
433 433
 	return nil
434 434
 }
435 435
 
436
-func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
436
+func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
437 437
 	if vars == nil {
438 438
 		return fmt.Errorf("Missing parameter")
439 439
 	}
... ...
@@ -445,7 +480,7 @@ func postContainersStart(srv *Server, w http.ResponseWriter, r *http.Request, va
445 445
 	return nil
446 446
 }
447 447
 
448
-func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
448
+func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
449 449
 	if err := parseForm(r); err != nil {
450 450
 		return err
451 451
 	}
... ...
@@ -466,7 +501,7 @@ func postContainersStop(srv *Server, w http.ResponseWriter, r *http.Request, var
466 466
 	return nil
467 467
 }
468 468
 
469
-func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
469
+func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
470 470
 	if vars == nil {
471 471
 		return fmt.Errorf("Missing parameter")
472 472
 	}
... ...
@@ -483,7 +518,29 @@ func postContainersWait(srv *Server, w http.ResponseWriter, r *http.Request, var
483 483
 	return nil
484 484
 }
485 485
 
486
-func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
486
+func postContainersResize(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
487
+	if err := parseForm(r); err != nil {
488
+		return err
489
+	}
490
+	height, err := strconv.Atoi(r.Form.Get("h"))
491
+	if err != nil {
492
+		return err
493
+	}
494
+	width, err := strconv.Atoi(r.Form.Get("w"))
495
+	if err != nil {
496
+		return err
497
+	}
498
+	if vars == nil {
499
+		return fmt.Errorf("Missing parameter")
500
+	}
501
+	name := vars["name"]
502
+	if err := srv.ContainerResize(name, height, width); err != nil {
503
+		return err
504
+	}
505
+	return nil
506
+}
507
+
508
+func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
487 509
 	if err := parseForm(r); err != nil {
488 510
 		return err
489 511
 	}
... ...
@@ -513,6 +570,10 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, v
513 513
 	}
514 514
 	name := vars["name"]
515 515
 
516
+	if _, err := srv.ContainerInspect(name); err != nil {
517
+		return err
518
+	}
519
+
516 520
 	in, out, err := hijackServer(w)
517 521
 	if err != nil {
518 522
 		return err
... ...
@@ -526,7 +587,7 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, v
526 526
 	return nil
527 527
 }
528 528
 
529
-func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
529
+func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
530 530
 	if vars == nil {
531 531
 		return fmt.Errorf("Missing parameter")
532 532
 	}
... ...
@@ -544,7 +605,7 @@ func getContainersByName(srv *Server, w http.ResponseWriter, r *http.Request, va
544 544
 	return nil
545 545
 }
546 546
 
547
-func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
547
+func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
548 548
 	if vars == nil {
549 549
 		return fmt.Errorf("Missing parameter")
550 550
 	}
... ...
@@ -562,7 +623,7 @@ func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars m
562 562
 	return nil
563 563
 }
564 564
 
565
-func postImagesGetCache(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
565
+func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
566 566
 	apiConfig := &ApiImageConfig{}
567 567
 	if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil {
568 568
 		return err
... ...
@@ -585,11 +646,35 @@ func postImagesGetCache(srv *Server, w http.ResponseWriter, r *http.Request, var
585 585
 	return nil
586 586
 }
587 587
 
588
+func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
589
+	if err := r.ParseMultipartForm(4096); err != nil {
590
+		return err
591
+	}
592
+
593
+	dockerfile, _, err := r.FormFile("Dockerfile")
594
+	if err != nil {
595
+		return err
596
+	}
597
+
598
+	context, _, err := r.FormFile("Context")
599
+	if err != nil {
600
+		if err != http.ErrMissingFile {
601
+			return err
602
+		}
603
+	}
604
+
605
+	b := NewBuildFile(srv, utils.NewWriteFlusher(w))
606
+	if _, err := b.Build(dockerfile, context); err != nil {
607
+		fmt.Fprintf(w, "Error build: %s\n", err)
608
+	}
609
+	return nil
610
+}
611
+
588 612
 func ListenAndServe(addr string, srv *Server, logging bool) error {
589 613
 	r := mux.NewRouter()
590 614
 	log.Printf("Listening for HTTP on %s\n", addr)
591 615
 
592
-	m := map[string]map[string]func(*Server, http.ResponseWriter, *http.Request, map[string]string) error{
616
+	m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{
593 617
 		"GET": {
594 618
 			"/auth":                         getAuth,
595 619
 			"/version":                      getVersion,
... ...
@@ -599,7 +684,8 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
599 599
 			"/images/search":                getImagesSearch,
600 600
 			"/images/{name:.*}/history":     getImagesHistory,
601 601
 			"/images/{name:.*}/json":        getImagesByName,
602
-			"/containers/ps":                getContainersPs,
602
+			"/containers/ps":                getContainersJson,
603
+			"/containers/json":              getContainersJson,
603 604
 			"/containers/{name:.*}/export":  getContainersExport,
604 605
 			"/containers/{name:.*}/changes": getContainersChanges,
605 606
 			"/containers/{name:.*}/json":    getContainersByName,
... ...
@@ -607,6 +693,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
607 607
 		"POST": {
608 608
 			"/auth":                         postAuth,
609 609
 			"/commit":                       postCommit,
610
+			"/build":                        postBuild,
610 611
 			"/images/create":                postImagesCreate,
611 612
 			"/images/{name:.*}/insert":      postImagesInsert,
612 613
 			"/images/{name:.*}/push":        postImagesPush,
... ...
@@ -618,6 +705,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
618 618
 			"/containers/{name:.*}/start":   postContainersStart,
619 619
 			"/containers/{name:.*}/stop":    postContainersStop,
620 620
 			"/containers/{name:.*}/wait":    postContainersWait,
621
+			"/containers/{name:.*}/resize":  postContainersResize,
621 622
 			"/containers/{name:.*}/attach":  postContainersAttach,
622 623
 		},
623 624
 		"DELETE": {
... ...
@@ -633,7 +721,7 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
633 633
 			localRoute := route
634 634
 			localMethod := method
635 635
 			localFct := fct
636
-			r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
636
+			f := func(w http.ResponseWriter, r *http.Request) {
637 637
 				utils.Debugf("Calling %s %s", localMethod, localRoute)
638 638
 				if logging {
639 639
 					log.Println(r.Method, r.RequestURI)
... ...
@@ -644,12 +732,21 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
644 644
 						utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
645 645
 					}
646 646
 				}
647
-				if err := localFct(srv, w, r, mux.Vars(r)); err != nil {
647
+				version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
648
+				if err != nil {
649
+					version = API_VERSION
650
+				}
651
+				if version == 0 || version > API_VERSION {
652
+					w.WriteHeader(http.StatusNotFound)
653
+					return
654
+				}
655
+				if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
648 656
 					httpError(w, err)
649 657
 				}
650
-			})
658
+			}
659
+			r.Path("/v{version:[0-9.]+}" + localRoute).Methods(localMethod).HandlerFunc(f)
660
+			r.Path(localRoute).Methods(localMethod).HandlerFunc(f)
651 661
 		}
652 662
 	}
653
-
654 663
 	return http.ListenAndServe(addr, r)
655 664
 }
... ...
@@ -26,8 +26,7 @@ func TestGetAuth(t *testing.T) {
26 26
 	defer nuke(runtime)
27 27
 
28 28
 	srv := &Server{
29
-		runtime:  runtime,
30
-		registry: registry.NewRegistry(runtime.root),
29
+		runtime: runtime,
31 30
 	}
32 31
 
33 32
 	r := httptest.NewRecorder()
... ...
@@ -48,7 +47,7 @@ func TestGetAuth(t *testing.T) {
48 48
 		t.Fatal(err)
49 49
 	}
50 50
 
51
-	if err := postAuth(srv, r, req, nil); err != nil {
51
+	if err := postAuth(srv, API_VERSION, r, req, nil); err != nil {
52 52
 		t.Fatal(err)
53 53
 	}
54 54
 
... ...
@@ -56,7 +55,7 @@ func TestGetAuth(t *testing.T) {
56 56
 		t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code)
57 57
 	}
58 58
 
59
-	newAuthConfig := srv.registry.GetAuthConfig()
59
+	newAuthConfig := registry.NewRegistry(runtime.root).GetAuthConfig(false)
60 60
 	if newAuthConfig.Username != authConfig.Username ||
61 61
 		newAuthConfig.Email != authConfig.Email {
62 62
 		t.Fatalf("The auth configuration hasn't been set correctly")
... ...
@@ -74,7 +73,7 @@ func TestGetVersion(t *testing.T) {
74 74
 
75 75
 	r := httptest.NewRecorder()
76 76
 
77
-	if err := getVersion(srv, r, nil, nil); err != nil {
77
+	if err := getVersion(srv, API_VERSION, r, nil, nil); err != nil {
78 78
 		t.Fatal(err)
79 79
 	}
80 80
 
... ...
@@ -98,7 +97,7 @@ func TestGetInfo(t *testing.T) {
98 98
 
99 99
 	r := httptest.NewRecorder()
100 100
 
101
-	if err := getInfo(srv, r, nil, nil); err != nil {
101
+	if err := getInfo(srv, API_VERSION, r, nil, nil); err != nil {
102 102
 		t.Fatal(err)
103 103
 	}
104 104
 
... ...
@@ -129,7 +128,7 @@ func TestGetImagesJson(t *testing.T) {
129 129
 
130 130
 	r := httptest.NewRecorder()
131 131
 
132
-	if err := getImagesJson(srv, r, req, nil); err != nil {
132
+	if err := getImagesJson(srv, API_VERSION, r, req, nil); err != nil {
133 133
 		t.Fatal(err)
134 134
 	}
135 135
 
... ...
@@ -154,7 +153,7 @@ func TestGetImagesJson(t *testing.T) {
154 154
 		t.Fatal(err)
155 155
 	}
156 156
 
157
-	if err := getImagesJson(srv, r2, req2, nil); err != nil {
157
+	if err := getImagesJson(srv, API_VERSION, r2, req2, nil); err != nil {
158 158
 		t.Fatal(err)
159 159
 	}
160 160
 
... ...
@@ -179,7 +178,7 @@ func TestGetImagesJson(t *testing.T) {
179 179
 		t.Fatal(err)
180 180
 	}
181 181
 
182
-	if err := getImagesJson(srv, r3, req3, nil); err != nil {
182
+	if err := getImagesJson(srv, API_VERSION, r3, req3, nil); err != nil {
183 183
 		t.Fatal(err)
184 184
 	}
185 185
 
... ...
@@ -200,7 +199,7 @@ func TestGetImagesJson(t *testing.T) {
200 200
 		t.Fatal(err)
201 201
 	}
202 202
 
203
-	err = getImagesJson(srv, r4, req4, nil)
203
+	err = getImagesJson(srv, API_VERSION, r4, req4, nil)
204 204
 	if err == nil {
205 205
 		t.Fatalf("Error expected, received none")
206 206
 	}
... ...
@@ -221,7 +220,7 @@ func TestGetImagesViz(t *testing.T) {
221 221
 	srv := &Server{runtime: runtime}
222 222
 
223 223
 	r := httptest.NewRecorder()
224
-	if err := getImagesViz(srv, r, nil, nil); err != nil {
224
+	if err := getImagesViz(srv, API_VERSION, r, nil, nil); err != nil {
225 225
 		t.Fatal(err)
226 226
 	}
227 227
 
... ...
@@ -247,8 +246,7 @@ func TestGetImagesSearch(t *testing.T) {
247 247
 	defer nuke(runtime)
248 248
 
249 249
 	srv := &Server{
250
-		runtime:  runtime,
251
-		registry: registry.NewRegistry(runtime.root),
250
+		runtime: runtime,
252 251
 	}
253 252
 
254 253
 	r := httptest.NewRecorder()
... ...
@@ -258,7 +256,7 @@ func TestGetImagesSearch(t *testing.T) {
258 258
 		t.Fatal(err)
259 259
 	}
260 260
 
261
-	if err := getImagesSearch(srv, r, req, nil); err != nil {
261
+	if err := getImagesSearch(srv, API_VERSION, r, req, nil); err != nil {
262 262
 		t.Fatal(err)
263 263
 	}
264 264
 
... ...
@@ -282,7 +280,7 @@ func TestGetImagesHistory(t *testing.T) {
282 282
 
283 283
 	r := httptest.NewRecorder()
284 284
 
285
-	if err := getImagesHistory(srv, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
285
+	if err := getImagesHistory(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
286 286
 		t.Fatal(err)
287 287
 	}
288 288
 
... ...
@@ -305,7 +303,7 @@ func TestGetImagesByName(t *testing.T) {
305 305
 	srv := &Server{runtime: runtime}
306 306
 
307 307
 	r := httptest.NewRecorder()
308
-	if err := getImagesByName(srv, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
308
+	if err := getImagesByName(srv, API_VERSION, r, nil, map[string]string{"name": unitTestImageName}); err != nil {
309 309
 		t.Fatal(err)
310 310
 	}
311 311
 
... ...
@@ -318,7 +316,7 @@ func TestGetImagesByName(t *testing.T) {
318 318
 	}
319 319
 }
320 320
 
321
-func TestGetContainersPs(t *testing.T) {
321
+func TestGetContainersJson(t *testing.T) {
322 322
 	runtime, err := newTestRuntime()
323 323
 	if err != nil {
324 324
 		t.Fatal(err)
... ...
@@ -336,13 +334,13 @@ func TestGetContainersPs(t *testing.T) {
336 336
 	}
337 337
 	defer runtime.Destroy(container)
338 338
 
339
-	req, err := http.NewRequest("GET", "/containers?quiet=1&all=1", nil)
339
+	req, err := http.NewRequest("GET", "/containers/json?all=1", nil)
340 340
 	if err != nil {
341 341
 		t.Fatal(err)
342 342
 	}
343 343
 
344 344
 	r := httptest.NewRecorder()
345
-	if err := getContainersPs(srv, r, req, nil); err != nil {
345
+	if err := getContainersJson(srv, API_VERSION, r, req, nil); err != nil {
346 346
 		t.Fatal(err)
347 347
 	}
348 348
 	containers := []ApiContainers{}
... ...
@@ -385,7 +383,7 @@ func TestGetContainersExport(t *testing.T) {
385 385
 	}
386 386
 
387 387
 	r := httptest.NewRecorder()
388
-	if err = getContainersExport(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
388
+	if err = getContainersExport(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
389 389
 		t.Fatal(err)
390 390
 	}
391 391
 
... ...
@@ -440,7 +438,7 @@ func TestGetContainersChanges(t *testing.T) {
440 440
 	}
441 441
 
442 442
 	r := httptest.NewRecorder()
443
-	if err := getContainersChanges(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
443
+	if err := getContainersChanges(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
444 444
 		t.Fatal(err)
445 445
 	}
446 446
 	changes := []Change{}
... ...
@@ -484,7 +482,7 @@ func TestGetContainersByName(t *testing.T) {
484 484
 	defer runtime.Destroy(container)
485 485
 
486 486
 	r := httptest.NewRecorder()
487
-	if err := getContainersByName(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
487
+	if err := getContainersByName(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
488 488
 		t.Fatal(err)
489 489
 	}
490 490
 	outContainer := &Container{}
... ...
@@ -504,18 +502,19 @@ func TestPostAuth(t *testing.T) {
504 504
 	defer nuke(runtime)
505 505
 
506 506
 	srv := &Server{
507
-		runtime:  runtime,
508
-		registry: registry.NewRegistry(runtime.root),
507
+		runtime: runtime,
509 508
 	}
510 509
 
511
-	authConfigOrig := &auth.AuthConfig{
510
+	config := &auth.AuthConfig{
512 511
 		Username: "utest",
513 512
 		Email:    "utest@yopmail.com",
514 513
 	}
515
-	srv.registry.ResetClient(authConfigOrig)
514
+
515
+	authStr := auth.EncodeAuth(config)
516
+	auth.SaveConfig(runtime.root, authStr, config.Email)
516 517
 
517 518
 	r := httptest.NewRecorder()
518
-	if err := getAuth(srv, r, nil, nil); err != nil {
519
+	if err := getAuth(srv, API_VERSION, r, nil, nil); err != nil {
519 520
 		t.Fatal(err)
520 521
 	}
521 522
 
... ...
@@ -524,7 +523,7 @@ func TestPostAuth(t *testing.T) {
524 524
 		t.Fatal(err)
525 525
 	}
526 526
 
527
-	if authConfig.Username != authConfigOrig.Username || authConfig.Email != authConfigOrig.Email {
527
+	if authConfig.Username != config.Username || authConfig.Email != config.Email {
528 528
 		t.Errorf("The retrieve auth mismatch with the one set.")
529 529
 	}
530 530
 }
... ...
@@ -562,7 +561,7 @@ func TestPostCommit(t *testing.T) {
562 562
 	}
563 563
 
564 564
 	r := httptest.NewRecorder()
565
-	if err := postCommit(srv, r, req, nil); err != nil {
565
+	if err := postCommit(srv, API_VERSION, r, req, nil); err != nil {
566 566
 		t.Fatal(err)
567 567
 	}
568 568
 	if r.Code != http.StatusCreated {
... ...
@@ -840,7 +839,7 @@ func TestPostContainersCreate(t *testing.T) {
840 840
 	}
841 841
 
842 842
 	r := httptest.NewRecorder()
843
-	if err := postContainersCreate(srv, r, req, nil); err != nil {
843
+	if err := postContainersCreate(srv, API_VERSION, r, req, nil); err != nil {
844 844
 		t.Fatal(err)
845 845
 	}
846 846
 	if r.Code != http.StatusCreated {
... ...
@@ -903,7 +902,7 @@ func TestPostContainersKill(t *testing.T) {
903 903
 	}
904 904
 
905 905
 	r := httptest.NewRecorder()
906
-	if err := postContainersKill(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
906
+	if err := postContainersKill(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
907 907
 		t.Fatal(err)
908 908
 	}
909 909
 	if r.Code != http.StatusNoContent {
... ...
@@ -951,7 +950,7 @@ func TestPostContainersRestart(t *testing.T) {
951 951
 		t.Fatal(err)
952 952
 	}
953 953
 	r := httptest.NewRecorder()
954
-	if err := postContainersRestart(srv, r, req, map[string]string{"name": container.Id}); err != nil {
954
+	if err := postContainersRestart(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
955 955
 		t.Fatal(err)
956 956
 	}
957 957
 	if r.Code != http.StatusNoContent {
... ...
@@ -992,7 +991,7 @@ func TestPostContainersStart(t *testing.T) {
992 992
 	defer runtime.Destroy(container)
993 993
 
994 994
 	r := httptest.NewRecorder()
995
-	if err := postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
995
+	if err := postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
996 996
 		t.Fatal(err)
997 997
 	}
998 998
 	if r.Code != http.StatusNoContent {
... ...
@@ -1007,7 +1006,7 @@ func TestPostContainersStart(t *testing.T) {
1007 1007
 	}
1008 1008
 
1009 1009
 	r = httptest.NewRecorder()
1010
-	if err = postContainersStart(srv, r, nil, map[string]string{"name": container.Id}); err == nil {
1010
+	if err = postContainersStart(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err == nil {
1011 1011
 		t.Fatalf("A running containter should be able to be started")
1012 1012
 	}
1013 1013
 
... ...
@@ -1054,7 +1053,7 @@ func TestPostContainersStop(t *testing.T) {
1054 1054
 		t.Fatal(err)
1055 1055
 	}
1056 1056
 	r := httptest.NewRecorder()
1057
-	if err := postContainersStop(srv, r, req, map[string]string{"name": container.Id}); err != nil {
1057
+	if err := postContainersStop(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
1058 1058
 		t.Fatal(err)
1059 1059
 	}
1060 1060
 	if r.Code != http.StatusNoContent {
... ...
@@ -1092,7 +1091,7 @@ func TestPostContainersWait(t *testing.T) {
1092 1092
 
1093 1093
 	setTimeout(t, "Wait timed out", 3*time.Second, func() {
1094 1094
 		r := httptest.NewRecorder()
1095
-		if err := postContainersWait(srv, r, nil, map[string]string{"name": container.Id}); err != nil {
1095
+		if err := postContainersWait(srv, API_VERSION, r, nil, map[string]string{"name": container.Id}); err != nil {
1096 1096
 			t.Fatal(err)
1097 1097
 		}
1098 1098
 		apiWait := &ApiWait{}
... ...
@@ -1154,7 +1153,7 @@ func TestPostContainersAttach(t *testing.T) {
1154 1154
 			t.Fatal(err)
1155 1155
 		}
1156 1156
 
1157
-		if err := postContainersAttach(srv, r, req, map[string]string{"name": container.Id}); err != nil {
1157
+		if err := postContainersAttach(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
1158 1158
 			t.Fatal(err)
1159 1159
 		}
1160 1160
 	}()
... ...
@@ -1224,7 +1223,7 @@ func TestDeleteContainers(t *testing.T) {
1224 1224
 		t.Fatal(err)
1225 1225
 	}
1226 1226
 	r := httptest.NewRecorder()
1227
-	if err := deleteContainers(srv, r, req, map[string]string{"name": container.Id}); err != nil {
1227
+	if err := deleteContainers(srv, API_VERSION, r, req, map[string]string{"name": container.Id}); err != nil {
1228 1228
 		t.Fatal(err)
1229 1229
 	}
1230 1230
 	if r.Code != http.StatusNoContent {
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"errors"
5
+	"fmt"
5 6
 	"io"
6 7
 	"io/ioutil"
7 8
 	"os"
... ...
@@ -31,6 +32,20 @@ func (compression *Compression) Flag() string {
31 31
 	return ""
32 32
 }
33 33
 
34
+func (compression *Compression) Extension() string {
35
+	switch *compression {
36
+	case Uncompressed:
37
+		return "tar"
38
+	case Bzip2:
39
+		return "tar.bz2"
40
+	case Gzip:
41
+		return "tar.gz"
42
+	case Xz:
43
+		return "tar.xz"
44
+	}
45
+	return ""
46
+}
47
+
34 48
 func Tar(path string, compression Compression) (io.Reader, error) {
35 49
 	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-c"+compression.Flag(), ".")
36 50
 	return CmdStream(cmd)
... ...
@@ -41,7 +56,7 @@ func Untar(archive io.Reader, path string) error {
41 41
 	cmd.Stdin = archive
42 42
 	output, err := cmd.CombinedOutput()
43 43
 	if err != nil {
44
-		return errors.New(err.Error() + ": " + string(output))
44
+		return fmt.Errorf("%s: %s", err, output)
45 45
 	}
46 46
 	return nil
47 47
 }
48 48
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../registry/MAINTAINERS
0 1
\ No newline at end of file
... ...
@@ -3,6 +3,7 @@ package auth
3 3
 import (
4 4
 	"encoding/base64"
5 5
 	"encoding/json"
6
+	"errors"
6 7
 	"fmt"
7 8
 	"io/ioutil"
8 9
 	"net/http"
... ...
@@ -17,6 +18,12 @@ const CONFIGFILE = ".dockercfg"
17 17
 // the registry server we want to login against
18 18
 const INDEX_SERVER = "https://index.docker.io/v1"
19 19
 
20
+//const INDEX_SERVER = "http://indexstaging-docker.dotcloud.com/"
21
+
22
+var (
23
+	ErrConfigFileMissing error = errors.New("The Auth config file is missing")
24
+)
25
+
20 26
 type AuthConfig struct {
21 27
 	Username string `json:"username"`
22 28
 	Password string `json:"password"`
... ...
@@ -75,7 +82,7 @@ func DecodeAuth(authStr string) (*AuthConfig, error) {
75 75
 func LoadConfig(rootPath string) (*AuthConfig, error) {
76 76
 	confFile := path.Join(rootPath, CONFIGFILE)
77 77
 	if _, err := os.Stat(confFile); err != nil {
78
-		return &AuthConfig{}, fmt.Errorf("The Auth config file is missing")
78
+		return nil, ErrConfigFileMissing
79 79
 	}
80 80
 	b, err := ioutil.ReadFile(confFile)
81 81
 	if err != nil {
... ...
@@ -97,7 +104,7 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
97 97
 }
98 98
 
99 99
 // save the auth config
100
-func saveConfig(rootPath, authStr string, email string) error {
100
+func SaveConfig(rootPath, authStr string, email string) error {
101 101
 	confFile := path.Join(rootPath, CONFIGFILE)
102 102
 	if len(email) == 0 {
103 103
 		os.Remove(confFile)
... ...
@@ -161,7 +168,9 @@ func Login(authConfig *AuthConfig) (string, error) {
161 161
 				status = "Login Succeeded\n"
162 162
 				storeConfig = true
163 163
 			} else if resp.StatusCode == 401 {
164
-				saveConfig(authConfig.rootPath, "", "")
164
+				if err := SaveConfig(authConfig.rootPath, "", ""); err != nil {
165
+					return "", err
166
+				}
165 167
 				return "", fmt.Errorf("Wrong login/password, please try again")
166 168
 			} else {
167 169
 				return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
... ...
@@ -175,7 +184,9 @@ func Login(authConfig *AuthConfig) (string, error) {
175 175
 	}
176 176
 	if storeConfig {
177 177
 		authStr := EncodeAuth(authConfig)
178
-		saveConfig(authConfig.rootPath, authStr, authConfig.Email)
178
+		if err := SaveConfig(authConfig.rootPath, authStr, authConfig.Email); err != nil {
179
+			return "", err
180
+		}
179 181
 	}
180 182
 	return status, nil
181 183
 }
... ...
@@ -12,12 +12,6 @@ import (
12 12
 	"strings"
13 13
 )
14 14
 
15
-type BuilderClient interface {
16
-	Build(io.Reader) (string, error)
17
-	CmdFrom(string) error
18
-	CmdRun(string) error
19
-}
20
-
21 15
 type builderClient struct {
22 16
 	cli *DockerCli
23 17
 
... ...
@@ -32,12 +26,6 @@ type builderClient struct {
32 32
 }
33 33
 
34 34
 func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
35
-	for c := range containers {
36
-		if _, _, err := b.cli.call("DELETE", "/containers/"+c, nil); err != nil {
37
-			utils.Debugf("%s", err)
38
-		}
39
-		utils.Debugf("Removing container %s", c)
40
-	}
41 35
 	for i := range images {
42 36
 		if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
43 37
 			utils.Debugf("%s", err)
... ...
@@ -164,8 +152,23 @@ func (b *builderClient) CmdExpose(args string) error {
164 164
 }
165 165
 
166 166
 func (b *builderClient) CmdInsert(args string) error {
167
-	// FIXME: Reimplement this once the remove_hijack branch gets merged.
168
-	// We need to retrieve the resulting Id
167
+	// tmp := strings.SplitN(args, "\t ", 2)
168
+	// sourceUrl, destPath := tmp[0], tmp[1]
169
+
170
+	// v := url.Values{}
171
+	// v.Set("url", sourceUrl)
172
+	// v.Set("path", destPath)
173
+	// body, _, err := b.cli.call("POST", "/images/insert?"+v.Encode(), nil)
174
+	// if err != nil {
175
+	// 	return err
176
+	// }
177
+
178
+	// apiId := &ApiId{}
179
+	// if err := json.Unmarshal(body, apiId); err != nil {
180
+	// 	return err
181
+	// }
182
+
183
+	// FIXME: Reimplement this, we need to retrieve the resulting Id
169 184
 	return fmt.Errorf("INSERT not implemented")
170 185
 }
171 186
 
... ...
@@ -246,7 +249,7 @@ func (b *builderClient) commit(id string) error {
246 246
 	return nil
247 247
 }
248 248
 
249
-func (b *builderClient) Build(dockerfile io.Reader) (string, error) {
249
+func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
250 250
 	defer b.clearTmp(b.tmpContainers, b.tmpImages)
251 251
 	file := bufio.NewReader(dockerfile)
252 252
 	for {
... ...
@@ -269,18 +272,18 @@ func (b *builderClient) Build(dockerfile io.Reader) (string, error) {
269 269
 		instruction := strings.ToLower(strings.Trim(tmp[0], " "))
270 270
 		arguments := strings.Trim(tmp[1], " ")
271 271
 
272
-		fmt.Printf("%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
272
+		fmt.Fprintf(os.Stderr, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
273 273
 
274 274
 		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
275 275
 		if !exists {
276
-			fmt.Printf("Skipping unknown instruction %s\n", strings.ToUpper(instruction))
276
+			fmt.Fprintf(os.Stderr, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
277 277
 		}
278 278
 		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
279 279
 		if ret != nil {
280 280
 			return "", ret.(error)
281 281
 		}
282 282
 
283
-		fmt.Printf("===> %v\n", b.image)
283
+		fmt.Fprintf(os.Stderr, "===> %v\n", b.image)
284 284
 	}
285 285
 	if b.needCommit {
286 286
 		if err := b.commit(""); err != nil {
... ...
@@ -295,13 +298,13 @@ func (b *builderClient) Build(dockerfile io.Reader) (string, error) {
295 295
 		for i := range b.tmpContainers {
296 296
 			delete(b.tmpContainers, i)
297 297
 		}
298
-		fmt.Printf("Build finished. image id: %s\n", b.image)
298
+		fmt.Fprintf(os.Stderr, "Build finished. image id: %s\n", b.image)
299 299
 		return b.image, nil
300 300
 	}
301 301
 	return "", fmt.Errorf("An error occured during the build\n")
302 302
 }
303 303
 
304
-func NewBuilderClient(addr string, port int) BuilderClient {
304
+func NewBuilderClient(addr string, port int) BuildFile {
305 305
 	return &builderClient{
306 306
 		cli:           NewDockerCli(addr, port),
307 307
 		config:        &Config{},
308 308
new file mode 100644
... ...
@@ -0,0 +1,377 @@
0
+package docker
1
+
2
+import (
3
+	"bufio"
4
+	"encoding/json"
5
+	"fmt"
6
+	"github.com/dotcloud/docker/utils"
7
+	"io"
8
+	"io/ioutil"
9
+	"os"
10
+	"path"
11
+	"reflect"
12
+	"strings"
13
+)
14
+
15
+type BuildFile interface {
16
+	Build(io.Reader, io.Reader) (string, error)
17
+	CmdFrom(string) error
18
+	CmdRun(string) error
19
+}
20
+
21
+type buildFile struct {
22
+	runtime *Runtime
23
+	builder *Builder
24
+	srv     *Server
25
+
26
+	image      string
27
+	maintainer string
28
+	config     *Config
29
+	context    string
30
+
31
+	tmpContainers map[string]struct{}
32
+	tmpImages     map[string]struct{}
33
+
34
+	needCommit bool
35
+
36
+	out io.Writer
37
+}
38
+
39
+func (b *buildFile) clearTmp(containers, images map[string]struct{}) {
40
+	for c := range containers {
41
+		tmp := b.runtime.Get(c)
42
+		b.runtime.Destroy(tmp)
43
+		utils.Debugf("Removing container %s", c)
44
+	}
45
+	for i := range images {
46
+		b.runtime.graph.Delete(i)
47
+		utils.Debugf("Removing image %s", i)
48
+	}
49
+}
50
+
51
+func (b *buildFile) CmdFrom(name string) error {
52
+	image, err := b.runtime.repositories.LookupImage(name)
53
+	if err != nil {
54
+		if b.runtime.graph.IsNotExist(err) {
55
+
56
+			var tag, remote string
57
+			if strings.Contains(name, ":") {
58
+				remoteParts := strings.Split(name, ":")
59
+				tag = remoteParts[1]
60
+				remote = remoteParts[0]
61
+			} else {
62
+				remote = name
63
+			}
64
+
65
+			if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil {
66
+				return err
67
+			}
68
+
69
+			image, err = b.runtime.repositories.LookupImage(name)
70
+			if err != nil {
71
+				return err
72
+			}
73
+		} else {
74
+			return err
75
+		}
76
+	}
77
+	b.image = image.Id
78
+	b.config = &Config{}
79
+	return nil
80
+}
81
+
82
+func (b *buildFile) CmdMaintainer(name string) error {
83
+	b.needCommit = true
84
+	b.maintainer = name
85
+	return nil
86
+}
87
+
88
+func (b *buildFile) CmdRun(args string) error {
89
+	if b.image == "" {
90
+		return fmt.Errorf("Please provide a source image with `from` prior to run")
91
+	}
92
+	config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
93
+	if err != nil {
94
+		return err
95
+	}
96
+
97
+	cmd, env := b.config.Cmd, b.config.Env
98
+	b.config.Cmd = nil
99
+	MergeConfig(b.config, config)
100
+
101
+	if cache, err := b.srv.ImageGetCached(b.image, config); err != nil {
102
+		return err
103
+	} else if cache != nil {
104
+		utils.Debugf("Use cached version")
105
+		b.image = cache.Id
106
+		return nil
107
+	}
108
+
109
+	cid, err := b.run()
110
+	if err != nil {
111
+		return err
112
+	}
113
+	b.config.Cmd, b.config.Env = cmd, env
114
+	return b.commit(cid)
115
+}
116
+
117
+func (b *buildFile) CmdEnv(args string) error {
118
+	b.needCommit = true
119
+	tmp := strings.SplitN(args, " ", 2)
120
+	if len(tmp) != 2 {
121
+		return fmt.Errorf("Invalid ENV format")
122
+	}
123
+	key := strings.Trim(tmp[0], " ")
124
+	value := strings.Trim(tmp[1], " ")
125
+
126
+	for i, elem := range b.config.Env {
127
+		if strings.HasPrefix(elem, key+"=") {
128
+			b.config.Env[i] = key + "=" + value
129
+			return nil
130
+		}
131
+	}
132
+	b.config.Env = append(b.config.Env, key+"="+value)
133
+	return nil
134
+}
135
+
136
+func (b *buildFile) CmdCmd(args string) error {
137
+	b.needCommit = true
138
+	var cmd []string
139
+	if err := json.Unmarshal([]byte(args), &cmd); err != nil {
140
+		utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
141
+		b.config.Cmd = []string{"/bin/sh", "-c", args}
142
+	} else {
143
+		b.config.Cmd = cmd
144
+	}
145
+	return nil
146
+}
147
+
148
+func (b *buildFile) CmdExpose(args string) error {
149
+	ports := strings.Split(args, " ")
150
+	b.config.PortSpecs = append(ports, b.config.PortSpecs...)
151
+	return nil
152
+}
153
+
154
+func (b *buildFile) CmdInsert(args string) error {
155
+	if b.image == "" {
156
+		return fmt.Errorf("Please provide a source image with `from` prior to insert")
157
+	}
158
+	tmp := strings.SplitN(args, " ", 2)
159
+	if len(tmp) != 2 {
160
+		return fmt.Errorf("Invalid INSERT format")
161
+	}
162
+	sourceUrl := strings.Trim(tmp[0], " ")
163
+	destPath := strings.Trim(tmp[1], " ")
164
+
165
+	file, err := utils.Download(sourceUrl, b.out)
166
+	if err != nil {
167
+		return err
168
+	}
169
+	defer file.Body.Close()
170
+
171
+	b.config.Cmd = []string{"echo", "INSERT", sourceUrl, "in", destPath}
172
+	cid, err := b.run()
173
+	if err != nil {
174
+		return err
175
+	}
176
+
177
+	container := b.runtime.Get(cid)
178
+	if container == nil {
179
+		return fmt.Errorf("An error occured while creating the container")
180
+	}
181
+
182
+	if err := container.Inject(file.Body, destPath); err != nil {
183
+		return err
184
+	}
185
+
186
+	return b.commit(cid)
187
+}
188
+
189
+func (b *buildFile) CmdAdd(args string) error {
190
+	if b.context == "" {
191
+		return fmt.Errorf("No context given. Impossible to use ADD")
192
+	}
193
+	tmp := strings.SplitN(args, " ", 2)
194
+	if len(tmp) != 2 {
195
+		return fmt.Errorf("Invalid INSERT format")
196
+	}
197
+	orig := strings.Trim(tmp[0], " ")
198
+	dest := strings.Trim(tmp[1], " ")
199
+
200
+	b.config.Cmd = []string{"echo", "PUSH", orig, "in", dest}
201
+	cid, err := b.run()
202
+	if err != nil {
203
+		return err
204
+	}
205
+
206
+	container := b.runtime.Get(cid)
207
+	if container == nil {
208
+		return fmt.Errorf("Error while creating the container (CmdAdd)")
209
+	}
210
+
211
+	if err := os.MkdirAll(path.Join(container.rwPath(), dest), 0700); err != nil {
212
+		return err
213
+	}
214
+
215
+	origPath := path.Join(b.context, orig)
216
+	destPath := path.Join(container.rwPath(), dest)
217
+
218
+	fi, err := os.Stat(origPath)
219
+	if err != nil {
220
+		return err
221
+	}
222
+	if fi.IsDir() {
223
+		files, err := ioutil.ReadDir(path.Join(b.context, orig))
224
+		if err != nil {
225
+			return err
226
+		}
227
+		for _, fi := range files {
228
+			if err := utils.CopyDirectory(path.Join(origPath, fi.Name()), path.Join(destPath, fi.Name())); err != nil {
229
+				return err
230
+			}
231
+		}
232
+	} else {
233
+		if err := utils.CopyDirectory(origPath, destPath); err != nil {
234
+			return err
235
+		}
236
+	}
237
+
238
+	return b.commit(cid)
239
+}
240
+
241
+func (b *buildFile) run() (string, error) {
242
+	if b.image == "" {
243
+		return "", fmt.Errorf("Please provide a source image with `from` prior to run")
244
+	}
245
+	b.config.Image = b.image
246
+
247
+	// Create the container and start it
248
+	c, err := b.builder.Create(b.config)
249
+	if err != nil {
250
+		return "", err
251
+	}
252
+	b.tmpContainers[c.Id] = struct{}{}
253
+
254
+	//start the container
255
+	if err := c.Start(); err != nil {
256
+		return "", err
257
+	}
258
+
259
+	// Wait for it to finish
260
+	if ret := c.Wait(); ret != 0 {
261
+		return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, ret)
262
+	}
263
+
264
+	return c.Id, nil
265
+}
266
+
267
+func (b *buildFile) commit(id string) error {
268
+	if b.image == "" {
269
+		return fmt.Errorf("Please provide a source image with `from` prior to commit")
270
+	}
271
+	b.config.Image = b.image
272
+	if id == "" {
273
+		cmd := b.config.Cmd
274
+		b.config.Cmd = []string{"true"}
275
+		if cid, err := b.run(); err != nil {
276
+			return err
277
+		} else {
278
+			id = cid
279
+		}
280
+		b.config.Cmd = cmd
281
+	}
282
+
283
+	container := b.runtime.Get(id)
284
+	if container == nil {
285
+		return fmt.Errorf("An error occured while creating the container")
286
+	}
287
+
288
+	// Commit the container
289
+	image, err := b.builder.Commit(container, "", "", "", b.maintainer, nil)
290
+	if err != nil {
291
+		return err
292
+	}
293
+	b.tmpImages[image.Id] = struct{}{}
294
+	b.image = image.Id
295
+	b.needCommit = false
296
+	return nil
297
+}
298
+
299
+func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
300
+	defer b.clearTmp(b.tmpContainers, b.tmpImages)
301
+
302
+	if context != nil {
303
+		name, err := ioutil.TempDir("/tmp", "docker-build")
304
+		if err != nil {
305
+			return "", err
306
+		}
307
+		if err := Untar(context, name); err != nil {
308
+			return "", err
309
+		}
310
+		defer os.RemoveAll(name)
311
+		b.context = name
312
+	}
313
+	file := bufio.NewReader(dockerfile)
314
+	for {
315
+		line, err := file.ReadString('\n')
316
+		if err != nil {
317
+			if err == io.EOF {
318
+				break
319
+			}
320
+			return "", err
321
+		}
322
+		line = strings.Replace(strings.TrimSpace(line), "	", " ", 1)
323
+		// Skip comments and empty line
324
+		if len(line) == 0 || line[0] == '#' {
325
+			continue
326
+		}
327
+		tmp := strings.SplitN(line, " ", 2)
328
+		if len(tmp) != 2 {
329
+			return "", fmt.Errorf("Invalid Dockerfile format")
330
+		}
331
+		instruction := strings.ToLower(strings.Trim(tmp[0], " "))
332
+		arguments := strings.Trim(tmp[1], " ")
333
+
334
+		fmt.Fprintf(b.out, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
335
+
336
+		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
337
+		if !exists {
338
+			fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
339
+		}
340
+		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
341
+		if ret != nil {
342
+			return "", ret.(error)
343
+		}
344
+
345
+		fmt.Fprintf(b.out, "===> %v\n", b.image)
346
+	}
347
+	if b.needCommit {
348
+		if err := b.commit(""); err != nil {
349
+			return "", err
350
+		}
351
+	}
352
+	if b.image != "" {
353
+		// The build is successful, keep the temporary containers and images
354
+		for i := range b.tmpImages {
355
+			delete(b.tmpImages, i)
356
+		}
357
+		fmt.Fprintf(b.out, "Build success.\n Image id:\n%s\n", b.image)
358
+		return b.image, nil
359
+	}
360
+	for i := range b.tmpContainers {
361
+		delete(b.tmpContainers, i)
362
+	}
363
+	return "", fmt.Errorf("An error occured during the build\n")
364
+}
365
+
366
+func NewBuildFile(srv *Server, out io.Writer) BuildFile {
367
+	return &buildFile{
368
+		builder:       NewBuilder(srv.runtime),
369
+		runtime:       srv.runtime,
370
+		srv:           srv,
371
+		config:        &Config{},
372
+		out:           out,
373
+		tmpContainers: make(map[string]struct{}),
374
+		tmpImages:     make(map[string]struct{}),
375
+	}
376
+}
0 377
new file mode 100644
... ...
@@ -0,0 +1,72 @@
0
+package docker
1
+
2
+import (
3
+	"github.com/dotcloud/docker/utils"
4
+	"strings"
5
+	"testing"
6
+)
7
+
8
+const Dockerfile = `
9
+# VERSION		0.1
10
+# DOCKER-VERSION	0.2
11
+
12
+from   ` + unitTestImageName + `
13
+run    sh -c 'echo root:testpass > /tmp/passwd'
14
+run    mkdir -p /var/run/sshd
15
+`
16
+
17
+func TestBuild(t *testing.T) {
18
+	runtime, err := newTestRuntime()
19
+	if err != nil {
20
+		t.Fatal(err)
21
+	}
22
+	defer nuke(runtime)
23
+
24
+	srv := &Server{runtime: runtime}
25
+
26
+	buildfile := NewBuildFile(srv, &utils.NopWriter{})
27
+
28
+	imgId, err := buildfile.Build(strings.NewReader(Dockerfile), nil)
29
+	if err != nil {
30
+		t.Fatal(err)
31
+	}
32
+
33
+	builder := NewBuilder(runtime)
34
+	container, err := builder.Create(
35
+		&Config{
36
+			Image: imgId,
37
+			Cmd:   []string{"cat", "/tmp/passwd"},
38
+		},
39
+	)
40
+	if err != nil {
41
+		t.Fatal(err)
42
+	}
43
+	defer runtime.Destroy(container)
44
+
45
+	output, err := container.Output()
46
+	if err != nil {
47
+		t.Fatal(err)
48
+	}
49
+	if string(output) != "root:testpass\n" {
50
+		t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
51
+	}
52
+
53
+	container2, err := builder.Create(
54
+		&Config{
55
+			Image: imgId,
56
+			Cmd:   []string{"ls", "-d", "/var/run/sshd"},
57
+		},
58
+	)
59
+	if err != nil {
60
+		t.Fatal(err)
61
+	}
62
+	defer runtime.Destroy(container2)
63
+
64
+	output, err = container2.Output()
65
+	if err != nil {
66
+		t.Fatal(err)
67
+	}
68
+	if string(output) != "/var/run/sshd\n" {
69
+		t.Fatal("/var/run/sshd has not been created")
70
+	}
71
+}
... ...
@@ -10,35 +10,42 @@ import (
10 10
 	"github.com/dotcloud/docker/utils"
11 11
 	"io"
12 12
 	"io/ioutil"
13
+	"mime/multipart"
13 14
 	"net"
14 15
 	"net/http"
15 16
 	"net/http/httputil"
16 17
 	"net/url"
17 18
 	"os"
19
+	"os/signal"
18 20
 	"path/filepath"
19 21
 	"reflect"
20 22
 	"strconv"
21 23
 	"strings"
24
+	"syscall"
22 25
 	"text/tabwriter"
23 26
 	"time"
24 27
 	"unicode"
25 28
 )
26 29
 
27
-const VERSION = "0.3.2"
30
+const VERSION = "0.3.3"
28 31
 
29 32
 var (
30 33
 	GIT_COMMIT string
31 34
 )
32 35
 
33
-func ParseCommands(args ...string) error {
34
-	cli := NewDockerCli("0.0.0.0", 4243)
36
+func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
37
+	methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:])
38
+	return reflect.TypeOf(cli).MethodByName(methodName)
39
+}
40
+
41
+func ParseCommands(addr string, port int, args ...string) error {
42
+	cli := NewDockerCli(addr, port)
35 43
 
36 44
 	if len(args) > 0 {
37
-		methodName := "Cmd" + strings.ToUpper(args[0][:1]) + strings.ToLower(args[0][1:])
38
-		method, exists := reflect.TypeOf(cli).MethodByName(methodName)
45
+		method, exists := cli.getMethod(args[0])
39 46
 		if !exists {
40 47
 			fmt.Println("Error: Command not found:", args[0])
41
-			return cli.CmdHelp(args...)
48
+			return cli.CmdHelp(args[1:]...)
42 49
 		}
43 50
 		ret := method.Func.CallSlice([]reflect.Value{
44 51
 			reflect.ValueOf(cli),
... ...
@@ -53,38 +60,50 @@ func ParseCommands(args ...string) error {
53 53
 }
54 54
 
55 55
 func (cli *DockerCli) CmdHelp(args ...string) error {
56
-	help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
57
-	for cmd, description := range map[string]string{
58
-		"attach":  "Attach to a running container",
59
-		"build":   "Build a container from Dockerfile or via stdin",
60
-		"commit":  "Create a new image from a container's changes",
61
-		"diff":    "Inspect changes on a container's filesystem",
62
-		"export":  "Stream the contents of a container as a tar archive",
63
-		"history": "Show the history of an image",
64
-		"images":  "List images",
65
-		"import":  "Create a new filesystem image from the contents of a tarball",
66
-		"info":    "Display system-wide information",
67
-		"insert":  "Insert a file in an image",
68
-		"inspect": "Return low-level information on a container",
69
-		"kill":    "Kill a running container",
70
-		"login":   "Register or Login to the docker registry server",
71
-		"logs":    "Fetch the logs of a container",
72
-		"port":    "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT",
73
-		"ps":      "List containers",
74
-		"pull":    "Pull an image or a repository from the docker registry server",
75
-		"push":    "Push an image or a repository to the docker registry server",
76
-		"restart": "Restart a running container",
77
-		"rm":      "Remove a container",
78
-		"rmi":     "Remove an image",
79
-		"run":     "Run a command in a new container",
80
-		"search":  "Search for an image in the docker index",
81
-		"start":   "Start a stopped container",
82
-		"stop":    "Stop a running container",
83
-		"tag":     "Tag an image into a repository",
84
-		"version": "Show the docker version information",
85
-		"wait":    "Block until a container stops, then print its exit code",
56
+	if len(args) > 0 {
57
+		method, exists := cli.getMethod(args[0])
58
+		if !exists {
59
+			fmt.Println("Error: Command not found:", args[0])
60
+		} else {
61
+			method.Func.CallSlice([]reflect.Value{
62
+				reflect.ValueOf(cli),
63
+				reflect.ValueOf([]string{"--help"}),
64
+			})[0].Interface()
65
+			return nil
66
+		}
67
+	}
68
+	help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n  -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
69
+	for _, command := range [][2]string{
70
+		{"attach", "Attach to a running container"},
71
+		{"build", "Build a container from a Dockerfile"},
72
+		{"commit", "Create a new image from a container's changes"},
73
+		{"diff", "Inspect changes on a container's filesystem"},
74
+		{"export", "Stream the contents of a container as a tar archive"},
75
+		{"history", "Show the history of an image"},
76
+		{"images", "List images"},
77
+		{"import", "Create a new filesystem image from the contents of a tarball"},
78
+		{"info", "Display system-wide information"},
79
+		{"insert", "Insert a file in an image"},
80
+		{"inspect", "Return low-level information on a container"},
81
+		{"kill", "Kill a running container"},
82
+		{"login", "Register or Login to the docker registry server"},
83
+		{"logs", "Fetch the logs of a container"},
84
+		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
85
+		{"ps", "List containers"},
86
+		{"pull", "Pull an image or a repository from the docker registry server"},
87
+		{"push", "Push an image or a repository to the docker registry server"},
88
+		{"restart", "Restart a running container"},
89
+		{"rm", "Remove a container"},
90
+		{"rmi", "Remove an image"},
91
+		{"run", "Run a command in a new container"},
92
+		{"search", "Search for an image in the docker index"},
93
+		{"start", "Start a stopped container"},
94
+		{"stop", "Stop a running container"},
95
+		{"tag", "Tag an image into a repository"},
96
+		{"version", "Show the docker version information"},
97
+		{"wait", "Block until a container stops}, then print its exit code"},
86 98
 	} {
87
-		help += fmt.Sprintf("    %-10.10s%s\n", cmd, description)
99
+		help += fmt.Sprintf("    %-10.10s%s\n", command[0], command[1])
88 100
 	}
89 101
 	fmt.Println(help)
90 102
 	return nil
... ...
@@ -104,39 +123,103 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
104 104
 	v.Set("url", cmd.Arg(1))
105 105
 	v.Set("path", cmd.Arg(2))
106 106
 
107
-	err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, os.Stdout)
108
-	if err != nil {
107
+	if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, os.Stdout); err != nil {
109 108
 		return err
110 109
 	}
111 110
 	return nil
112 111
 }
113 112
 
114 113
 func (cli *DockerCli) CmdBuild(args ...string) error {
115
-	cmd := Subcmd("build", "-|Dockerfile", "Build an image from Dockerfile or via stdin")
114
+	cmd := Subcmd("build", "[OPTIONS] [CONTEXT]", "Build an image from a Dockerfile")
115
+	fileName := cmd.String("f", "Dockerfile", "Use `file` as Dockerfile. Can be '-' for stdin")
116 116
 	if err := cmd.Parse(args); err != nil {
117 117
 		return nil
118 118
 	}
119
+
119 120
 	var (
120
-		file io.ReadCloser
121
-		err  error
121
+		file          io.ReadCloser
122
+		multipartBody io.Reader
123
+		err           error
122 124
 	)
123 125
 
124
-	if cmd.NArg() == 0 {
125
-		file, err = os.Open("Dockerfile")
126
+	// Init the needed component for the Multipart
127
+	buff := bytes.NewBuffer([]byte{})
128
+	multipartBody = buff
129
+	w := multipart.NewWriter(buff)
130
+	boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
131
+
132
+	// Create a FormFile multipart for the Dockerfile
133
+	if *fileName == "-" {
134
+		file = os.Stdin
135
+	} else {
136
+		file, err = os.Open(*fileName)
126 137
 		if err != nil {
127 138
 			return err
128 139
 		}
129
-	} else if cmd.Arg(0) == "-" {
130
-		file = os.Stdin
140
+		defer file.Close()
141
+	}
142
+	if wField, err := w.CreateFormFile("Dockerfile", *fileName); err != nil {
143
+		return err
131 144
 	} else {
132
-		file, err = os.Open(cmd.Arg(0))
145
+		io.Copy(wField, file)
146
+	}
147
+	multipartBody = io.MultiReader(multipartBody, boundary)
148
+
149
+	compression := Bzip2
150
+
151
+	// Create a FormFile multipart for the context if needed
152
+	if cmd.Arg(0) != "" {
153
+		// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
154
+		context, err := Tar(cmd.Arg(0), compression)
155
+		if err != nil {
156
+			return err
157
+		}
158
+		// NOTE: Do this in case '.' or '..' is input
159
+		absPath, err := filepath.Abs(cmd.Arg(0))
160
+		if err != nil {
161
+			return err
162
+		}
163
+		if wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension()); err != nil {
164
+			return err
165
+		} else {
166
+			// FIXME: Find a way to have a progressbar for the upload too
167
+			io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Caching Context %v/%v (%v)\r", false))
168
+		}
169
+
170
+		multipartBody = io.MultiReader(multipartBody, boundary)
171
+	}
172
+
173
+	// Send the multipart request with correct content-type
174
+	req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), multipartBody)
175
+	if err != nil {
176
+		return err
177
+	}
178
+	req.Header.Set("Content-Type", w.FormDataContentType())
179
+	if cmd.Arg(0) != "" {
180
+		req.Header.Set("X-Docker-Context-Compression", compression.Flag())
181
+		fmt.Println("Uploading Context...")
182
+	}
183
+
184
+	resp, err := http.DefaultClient.Do(req)
185
+	if err != nil {
186
+		return err
187
+	}
188
+	defer resp.Body.Close()
189
+
190
+	// Check for errors
191
+	if resp.StatusCode < 200 || resp.StatusCode >= 400 {
192
+		body, err := ioutil.ReadAll(resp.Body)
133 193
 		if err != nil {
134 194
 			return err
135 195
 		}
196
+		return fmt.Errorf("error: %s", body)
136 197
 	}
137
-	if _, err := NewBuilderClient("0.0.0.0", 4243).Build(file); err != nil {
198
+
199
+	// Output the result
200
+	if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
138 201
 		return err
139 202
 	}
203
+
140 204
 	return nil
141 205
 }
142 206
 
... ...
@@ -283,12 +366,10 @@ func (cli *DockerCli) CmdWait(args ...string) error {
283 283
 // 'docker version': show version information
284 284
 func (cli *DockerCli) CmdVersion(args ...string) error {
285 285
 	cmd := Subcmd("version", "", "Show the docker version information.")
286
-	fmt.Println(len(args))
287 286
 	if err := cmd.Parse(args); err != nil {
288 287
 		return nil
289 288
 	}
290 289
 
291
-	fmt.Println(cmd.NArg())
292 290
 	if cmd.NArg() > 0 {
293 291
 		cmd.Usage()
294 292
 		return nil
... ...
@@ -607,39 +688,13 @@ func (cli *DockerCli) CmdPush(args ...string) error {
607 607
 		return nil
608 608
 	}
609 609
 
610
-	body, _, err := cli.call("GET", "/auth", nil)
610
+	username, err := cli.checkIfLogged(*registry == "", "push")
611 611
 	if err != nil {
612 612
 		return err
613 613
 	}
614 614
 
615
-	var out auth.AuthConfig
616
-	err = json.Unmarshal(body, &out)
617
-	if err != nil {
618
-		return err
619
-	}
620
-
621
-	// If the login failed AND we're using the index, abort
622
-	if *registry == "" && out.Username == "" {
623
-		if err := cli.CmdLogin(args...); err != nil {
624
-			return err
625
-		}
626
-
627
-		body, _, err = cli.call("GET", "/auth", nil)
628
-		if err != nil {
629
-			return err
630
-		}
631
-		err = json.Unmarshal(body, &out)
632
-		if err != nil {
633
-			return err
634
-		}
635
-
636
-		if out.Username == "" {
637
-			return fmt.Errorf("Please login prior to push. ('docker login')")
638
-		}
639
-	}
640
-
641 615
 	if len(strings.SplitN(name, "/", 2)) == 1 {
642
-		return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", out.Username, name)
616
+		return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name)
643 617
 	}
644 618
 
645 619
 	v := url.Values{}
... ...
@@ -670,6 +725,12 @@ func (cli *DockerCli) CmdPull(args ...string) error {
670 670
 		remote = remoteParts[0]
671 671
 	}
672 672
 
673
+	if strings.Contains(remote, "/") {
674
+		if _, err := cli.checkIfLogged(true, "pull"); err != nil {
675
+			return err
676
+		}
677
+	}
678
+
673 679
 	v := url.Values{}
674 680
 	v.Set("fromImage", remote)
675 681
 	v.Set("tag", *tag)
... ...
@@ -795,7 +856,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
795 795
 		v.Set("before", *before)
796 796
 	}
797 797
 
798
-	body, _, err := cli.call("GET", "/containers/ps?"+v.Encode(), nil)
798
+	body, _, err := cli.call("GET", "/containers/json?"+v.Encode(), nil)
799 799
 	if err != nil {
800 800
 		return err
801 801
 	}
... ...
@@ -813,9 +874,9 @@ func (cli *DockerCli) CmdPs(args ...string) error {
813 813
 	for _, out := range outs {
814 814
 		if !*quiet {
815 815
 			if *noTrunc {
816
-				fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t", out.Id, out.Image, out.Command, out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
816
+				fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.Id, out.Image, out.Command, out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
817 817
 			} else {
818
-				fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\t", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
818
+				fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
819 819
 			}
820 820
 			if out.SizeRootFs > 0 {
821 821
 				fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
... ...
@@ -937,7 +998,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
937 937
 	v.Set("stdout", "1")
938 938
 	v.Set("stderr", "1")
939 939
 
940
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil {
940
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false, nil, os.Stdout); err != nil {
941 941
 		return err
942 942
 	}
943 943
 	return nil
... ...
@@ -964,14 +1025,35 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
964 964
 		return err
965 965
 	}
966 966
 
967
+	splitStderr := container.Config.Tty
968
+
969
+	connections := 1
970
+	if splitStderr {
971
+		connections += 1
972
+	}
973
+	chErrors := make(chan error, connections)
974
+	cli.monitorTtySize(cmd.Arg(0))
975
+	if splitStderr {
976
+		go func() {
977
+			chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
978
+		}()
979
+	}
967 980
 	v := url.Values{}
968 981
 	v.Set("stream", "1")
969
-	v.Set("stdout", "1")
970
-	v.Set("stderr", "1")
971 982
 	v.Set("stdin", "1")
972
-
973
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
974
-		return err
983
+	v.Set("stdout", "1")
984
+	if !splitStderr {
985
+		v.Set("stderr", "1")
986
+	}
987
+	go func() {
988
+		chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
989
+	}()
990
+	for connections > 0 {
991
+		err := <-chErrors
992
+		if err != nil {
993
+			return err
994
+		}
995
+		connections -= 1
975 996
 	}
976 997
 	return nil
977 998
 }
... ...
@@ -1135,19 +1217,14 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1135 1135
 		fmt.Fprintln(os.Stderr, "WARNING: ", warning)
1136 1136
 	}
1137 1137
 
1138
-	v := url.Values{}
1139
-	v.Set("logs", "1")
1140
-	v.Set("stream", "1")
1138
+	splitStderr := !config.Tty
1141 1139
 
1142
-	if config.AttachStdin {
1143
-		v.Set("stdin", "1")
1140
+	connections := 0
1141
+	if config.AttachStdin || config.AttachStdout || (!splitStderr && config.AttachStderr) {
1142
+		connections += 1
1144 1143
 	}
1145
-	if config.AttachStdout {
1146
-		v.Set("stdout", "1")
1147
-	}
1148
-	if config.AttachStderr {
1149
-		v.Set("stderr", "1")
1150
-
1144
+	if splitStderr && config.AttachStderr {
1145
+		connections += 1
1151 1146
 	}
1152 1147
 
1153 1148
 	//start the container
... ...
@@ -1156,9 +1233,38 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1156 1156
 		return err
1157 1157
 	}
1158 1158
 
1159
-	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
1160
-		if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
1161
-			return err
1159
+	if connections > 0 {
1160
+		chErrors := make(chan error, connections)
1161
+		cli.monitorTtySize(out.Id)
1162
+
1163
+		if splitStderr && config.AttachStderr {
1164
+			go func() {
1165
+				chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
1166
+			}()
1167
+		}
1168
+
1169
+		v := url.Values{}
1170
+		v.Set("logs", "1")
1171
+		v.Set("stream", "1")
1172
+
1173
+		if config.AttachStdin {
1174
+			v.Set("stdin", "1")
1175
+		}
1176
+		if config.AttachStdout {
1177
+			v.Set("stdout", "1")
1178
+		}
1179
+		if !splitStderr && config.AttachStderr {
1180
+			v.Set("stderr", "1")
1181
+		}
1182
+		go func() {
1183
+			chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
1184
+		}()
1185
+		for connections > 0 {
1186
+			err := <-chErrors
1187
+			if err != nil {
1188
+				return err
1189
+			}
1190
+			connections -= 1
1162 1191
 		}
1163 1192
 	}
1164 1193
 	if !config.AttachStdout && !config.AttachStderr {
... ...
@@ -1167,6 +1273,40 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1167 1167
 	return nil
1168 1168
 }
1169 1169
 
1170
+func (cli *DockerCli) checkIfLogged(condition bool, action string) (string, error) {
1171
+	body, _, err := cli.call("GET", "/auth", nil)
1172
+	if err != nil {
1173
+		return "", err
1174
+	}
1175
+
1176
+	var out auth.AuthConfig
1177
+	err = json.Unmarshal(body, &out)
1178
+	if err != nil {
1179
+		return "", err
1180
+	}
1181
+
1182
+	// If condition AND the login failed
1183
+	if condition && out.Username == "" {
1184
+		if err := cli.CmdLogin(""); err != nil {
1185
+			return "", err
1186
+		}
1187
+
1188
+		body, _, err = cli.call("GET", "/auth", nil)
1189
+		if err != nil {
1190
+			return "", err
1191
+		}
1192
+		err = json.Unmarshal(body, &out)
1193
+		if err != nil {
1194
+			return "", err
1195
+		}
1196
+
1197
+		if out.Username == "" {
1198
+			return "", fmt.Errorf("Please login prior to %s. ('docker login')", action)
1199
+		}
1200
+	}
1201
+	return out.Username, nil
1202
+}
1203
+
1170 1204
 func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
1171 1205
 	var params io.Reader
1172 1206
 	if data != nil {
... ...
@@ -1177,7 +1317,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
1177 1177
 		params = bytes.NewBuffer(buf)
1178 1178
 	}
1179 1179
 
1180
-	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d", cli.host, cli.port)+path, params)
1180
+	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), params)
1181 1181
 	if err != nil {
1182 1182
 		return nil, -1, err
1183 1183
 	}
... ...
@@ -1209,7 +1349,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1209 1209
 	if (method == "POST" || method == "PUT") && in == nil {
1210 1210
 		in = bytes.NewReader([]byte{})
1211 1211
 	}
1212
-	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, path), in)
1212
+	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, API_VERSION, path), in)
1213 1213
 	if err != nil {
1214 1214
 		return err
1215 1215
 	}
... ...
@@ -1233,14 +1373,35 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1233 1233
 		return fmt.Errorf("error: %s", body)
1234 1234
 	}
1235 1235
 
1236
-	if _, err := io.Copy(out, resp.Body); err != nil {
1237
-		return err
1236
+	if resp.Header.Get("Content-Type") == "application/json" {
1237
+		type Message struct {
1238
+			Status   string `json:"status,omitempty"`
1239
+			Progress string `json:"progress,omitempty"`
1240
+		}
1241
+		dec := json.NewDecoder(resp.Body)
1242
+		for {
1243
+			var m Message
1244
+			if err := dec.Decode(&m); err == io.EOF {
1245
+				break
1246
+			} else if err != nil {
1247
+				return err
1248
+			}
1249
+			if m.Progress != "" {
1250
+				fmt.Fprintf(out, "Downloading %s\r", m.Progress)
1251
+			} else {
1252
+				fmt.Fprintf(out, "%s\n", m.Status)
1253
+			}
1254
+		}
1255
+	} else {
1256
+		if _, err := io.Copy(out, resp.Body); err != nil {
1257
+			return err
1258
+		}
1238 1259
 	}
1239 1260
 	return nil
1240 1261
 }
1241 1262
 
1242
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
1243
-	req, err := http.NewRequest(method, path, nil)
1263
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
1264
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", API_VERSION, path), nil)
1244 1265
 	if err != nil {
1245 1266
 		return err
1246 1267
 	}
... ...
@@ -1257,20 +1418,19 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
1257 1257
 	defer rwc.Close()
1258 1258
 
1259 1259
 	receiveStdout := utils.Go(func() error {
1260
-		_, err := io.Copy(os.Stdout, br)
1260
+		_, err := io.Copy(out, br)
1261 1261
 		return err
1262 1262
 	})
1263 1263
 
1264
-	if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
1264
+	if in != nil && setRawTerminal && term.IsTerminal(int(in.Fd())) && os.Getenv("NORAW") == "" {
1265 1265
 		if oldState, err := term.SetRawTerminal(); err != nil {
1266 1266
 			return err
1267 1267
 		} else {
1268 1268
 			defer term.RestoreTerminal(oldState)
1269 1269
 		}
1270 1270
 	}
1271
-
1272 1271
 	sendStdin := utils.Go(func() error {
1273
-		_, err := io.Copy(rwc, os.Stdin)
1272
+		_, err := io.Copy(rwc, in)
1274 1273
 		if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
1275 1274
 			fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err)
1276 1275
 		}
... ...
@@ -1290,6 +1450,33 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
1290 1290
 
1291 1291
 }
1292 1292
 
1293
+func (cli *DockerCli) resizeTty(id string) {
1294
+	ws, err := term.GetWinsize(os.Stdin.Fd())
1295
+	if err != nil {
1296
+		utils.Debugf("Error getting size: %s", err)
1297
+	}
1298
+	v := url.Values{}
1299
+	v.Set("h", strconv.Itoa(int(ws.Height)))
1300
+	v.Set("w", strconv.Itoa(int(ws.Width)))
1301
+	if _, _, err := cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil); err != nil {
1302
+		utils.Debugf("Error resize: %s", err)
1303
+	}
1304
+}
1305
+
1306
+func (cli *DockerCli) monitorTtySize(id string) {
1307
+	cli.resizeTty(id)
1308
+
1309
+	c := make(chan os.Signal, 1)
1310
+	signal.Notify(c, syscall.SIGWINCH)
1311
+	go func() {
1312
+		for sig := range c {
1313
+			if sig == syscall.SIGWINCH {
1314
+				cli.resizeTty(id)
1315
+			}
1316
+		}
1317
+	}()
1318
+}
1319
+
1293 1320
 func Subcmd(name, signature, description string) *flag.FlagSet {
1294 1321
 	flags := flag.NewFlagSet(name, flag.ContinueOnError)
1295 1322
 	flags.Usage = func() {
... ...
@@ -1299,8 +1486,8 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
1299 1299
 	return flags
1300 1300
 }
1301 1301
 
1302
-func NewDockerCli(host string, port int) *DockerCli {
1303
-	return &DockerCli{host, port}
1302
+func NewDockerCli(addr string, port int) *DockerCli {
1303
+	return &DockerCli{addr, port}
1304 1304
 }
1305 1305
 
1306 1306
 type DockerCli struct {
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"encoding/json"
5 5
 	"flag"
6 6
 	"fmt"
7
+	"github.com/dotcloud/docker/term"
7 8
 	"github.com/dotcloud/docker/utils"
8 9
 	"github.com/kr/pty"
9 10
 	"io"
... ...
@@ -755,6 +756,14 @@ func (container *Container) Wait() int {
755 755
 	return container.State.ExitCode
756 756
 }
757 757
 
758
+func (container *Container) Resize(h, w int) error {
759
+	pty, ok := container.ptyMaster.(*os.File)
760
+	if !ok {
761
+		return fmt.Errorf("ptyMaster does not have Fd() method")
762
+	}
763
+	return term.SetWinsize(pty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
764
+}
765
+
758 766
 func (container *Container) ExportRw() (Archive, error) {
759 767
 	return Tar(container.rwPath(), Uncompressed)
760 768
 }
761 769
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+# Maintainer wanted! Enroll on #docker@freenode
0 1
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Solomon Hykes <solomon@dotcloud.com>
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"os"
11 11
 	"os/signal"
12 12
 	"strconv"
13
+	"strings"
13 14
 	"syscall"
14 15
 )
15 16
 
... ...
@@ -23,18 +24,38 @@ func main() {
23 23
 		docker.SysInit()
24 24
 		return
25 25
 	}
26
+	host := "127.0.0.1"
27
+	port := 4243
26 28
 	// FIXME: Switch d and D ? (to be more sshd like)
27 29
 	flDaemon := flag.Bool("d", false, "Daemon mode")
28 30
 	flDebug := flag.Bool("D", false, "Debug mode")
29 31
 	flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
30 32
 	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
31 33
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
34
+	flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
32 35
 	flag.Parse()
33 36
 	if *bridgeName != "" {
34 37
 		docker.NetworkBridgeIface = *bridgeName
35 38
 	} else {
36 39
 		docker.NetworkBridgeIface = docker.DefaultNetworkBridge
37 40
 	}
41
+
42
+	if strings.Contains(*flHost, ":") {
43
+		hostParts := strings.Split(*flHost, ":")
44
+		if len(hostParts) != 2 {
45
+			log.Fatal("Invalid bind address format.")
46
+			os.Exit(-1)
47
+		}
48
+		if hostParts[0] != "" {
49
+			host = hostParts[0]
50
+		}
51
+		if p, err := strconv.Atoi(hostParts[1]); err == nil {
52
+			port = p
53
+		}
54
+	} else {
55
+		host = *flHost
56
+	}
57
+
38 58
 	if *flDebug {
39 59
 		os.Setenv("DEBUG", "1")
40 60
 	}
... ...
@@ -44,12 +65,12 @@ func main() {
44 44
 			flag.Usage()
45 45
 			return
46 46
 		}
47
-		if err := daemon(*pidfile, *flAutoRestart); err != nil {
47
+		if err := daemon(*pidfile, host, port, *flAutoRestart); err != nil {
48 48
 			log.Fatal(err)
49 49
 			os.Exit(-1)
50 50
 		}
51 51
 	} else {
52
-		if err := docker.ParseCommands(flag.Args()...); err != nil {
52
+		if err := docker.ParseCommands(host, port, flag.Args()...); err != nil {
53 53
 			log.Fatal(err)
54 54
 			os.Exit(-1)
55 55
 		}
... ...
@@ -83,7 +104,10 @@ func removePidFile(pidfile string) {
83 83
 	}
84 84
 }
85 85
 
86
-func daemon(pidfile string, autoRestart bool) error {
86
+func daemon(pidfile, addr string, port int, autoRestart bool) error {
87
+	if addr != "127.0.0.1" {
88
+		log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
89
+	}
87 90
 	if err := createPidFile(pidfile); err != nil {
88 91
 		log.Fatal(err)
89 92
 	}
... ...
@@ -103,5 +127,5 @@ func daemon(pidfile string, autoRestart bool) error {
103 103
 		return err
104 104
 	}
105 105
 
106
-	return docker.ListenAndServe("0.0.0.0:4243", server, true)
106
+	return docker.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), server, true)
107 107
 }
108 108
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+Andy Rothfusz <andy@dotcloud.com>
1
+Ken Cochrane <ken@dotcloud.com>
0 2
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Solomon Hykes <solomon@dotcloud.com>
... ...
@@ -1,3 +1,7 @@
1
+:title: Remote API
2
+:description: API Documentation for Docker
3
+:keywords: API, Docker, rcli, REST, documentation
4
+
1 5
 =================
2 6
 Docker Remote API
3 7
 =================
... ...
@@ -20,7 +24,7 @@ Docker Remote API
20 20
 List containers
21 21
 ***************
22 22
 
23
-.. http:get:: /containers/ps
23
+.. http:get:: /containers/json
24 24
 
25 25
 	List containers
26 26
 
... ...
@@ -28,7 +32,7 @@ List containers
28 28
 
29 29
 	.. sourcecode:: http
30 30
 
31
-	   GET /containers/ps?all=1&before=8dfafdbc3a40 HTTP/1.1
31
+	   GET /containers/json?all=1&before=8dfafdbc3a40 HTTP/1.1
32 32
 	   
33 33
 	**Example response**:
34 34
 
... ...
@@ -118,7 +122,8 @@ Create a container
118 118
 	.. sourcecode:: http
119 119
 
120 120
 	   HTTP/1.1 201 OK
121
-	   
121
+	   Content-Type: application/json
122
+
122 123
 	   {
123 124
 		"Id":"e90e34656806"
124 125
 		"Warnings":[]
... ...
@@ -373,7 +378,7 @@ Attach to a container
373 373
 
374 374
 .. http:post:: /containers/(id)/attach
375 375
 
376
-	Stop the container ``id``
376
+	Attach to the container ``id``
377 377
 
378 378
 	**Example request**:
379 379
 
... ...
@@ -1,6 +1,6 @@
1
-:title: docker documentation
1
+:title: API Documentation
2 2
 :description: docker documentation
3
-:keywords:
3
+:keywords: docker, ipa, documentation
4 4
 
5 5
 API's
6 6
 =============
... ...
@@ -1,4 +1,4 @@
1
-:title: docker Registry documentation
1
+:title: Registry Documentation
2 2
 :description: Documentation for docker Registry and Registry API
3 3
 :keywords: docker, registry, api, index
4 4
 
... ...
@@ -301,7 +301,7 @@ POST /v1/users
301 301
     {"email": "sam@dotcloud.com", "password": "toto42", "username": "foobar"'}
302 302
 
303 303
 **Validation**:
304
-    - **username** : min 4 character, max 30 characters, all lowercase no special characters.
304
+    - **username** : min 4 character, max 30 characters, must match the regular expression [a-z0-9_].
305 305
     - **password**: min 5 characters
306 306
 
307 307
 **Valid**: return HTTP 200
... ...
@@ -345,6 +345,11 @@ GET /v1/users
345 345
 
346 346
 The Registry does not know anything about users. Even though repositories are under usernames, it’s just a namespace for the registry. Allowing us to implement organizations or different namespaces per user later, without modifying the Registry’s API.
347 347
 
348
+The following naming restrictions apply:
349
+
350
+- Namespaces must match the same regular expression as usernames (See 4.2.1.)
351
+- Repository names must match the regular expression [a-zA-Z0-9-_.]
352
+
348 353
 4.3.1 Get all tags
349 354
 ^^^^^^^^^^^^^^^^^^
350 355
 
... ...
@@ -14,7 +14,8 @@ To list available commands, either run ``docker`` with no parameters or execute
14 14
 ``docker help``::
15 15
 
16 16
   $ docker
17
-    Usage: docker COMMAND [arg...]
17
+    Usage: docker [OPTIONS] COMMAND [arg...]
18
+      -H="127.0.0.1:4243": Host:port to bind/connect to
18 19
 
19 20
     A self-sufficient runtime for linux containers.
20 21
 
... ...
@@ -1,3 +1,7 @@
1
+:title: Attach Command
2
+:description: Attach to a running container
3
+:keywords: attach, container, docker, documentation
4
+
1 5
 ===========================================
2 6
 ``attach`` -- Attach to a running container
3 7
 ===========================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Build Command
2
+:description: Build a new image from the Dockerfile passed via stdin
3
+:keywords: build, docker, container, documentation
4
+
1 5
 ========================================================
2 6
 ``build`` -- Build a container from Dockerfile via stdin
3 7
 ========================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Commit Command
2
+:description: Create a new image from a container's changes
3
+:keywords: commit, docker, container, documentation
4
+
1 5
 ===========================================================
2 6
 ``commit`` -- Create a new image from a container's changes
3 7
 ===========================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Diff Command
2
+:description: Inspect changes on a container's filesystem
3
+:keywords: diff, docker, container, documentation
4
+
1 5
 =======================================================
2 6
 ``diff`` -- Inspect changes on a container's filesystem
3 7
 =======================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Export Command
2
+:description: Export the contents of a filesystem as a tar archive
3
+:keywords: export, docker, container, documentation
4
+
1 5
 =================================================================
2 6
 ``export`` -- Stream the contents of a container as a tar archive
3 7
 =================================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: History Command
2
+:description: Show the history of an image
3
+:keywords: history, docker, container, documentation
4
+
1 5
 ===========================================
2 6
 ``history`` -- Show the history of an image
3 7
 ===========================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Images Command
2
+:description: List images
3
+:keywords: images, docker, container, documentation
4
+
1 5
 =========================
2 6
 ``images`` -- List images
3 7
 =========================
... ...
@@ -1,3 +1,7 @@
1
+:title: Import Command
2
+:description: Create a new filesystem image from the contents of a tarball
3
+:keywords: import, tarball, docker, url, documentation
4
+
1 5
 ==========================================================================
2 6
 ``import`` -- Create a new filesystem image from the contents of a tarball
3 7
 ==========================================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Info Command
2
+:description: Display system-wide information.
3
+:keywords: info, docker, information, documentation
4
+
1 5
 ===========================================
2 6
 ``info`` -- Display system-wide information
3 7
 ===========================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Inspect Command
2
+:description: Return low-level information on a container
3
+:keywords: inspect, container, docker, documentation
4
+
1 5
 ==========================================================
2 6
 ``inspect`` -- Return low-level information on a container
3 7
 ==========================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Kill Command
2
+:description: Kill a running container
3
+:keywords: kill, container, docker, documentation
4
+
1 5
 ====================================
2 6
 ``kill`` -- Kill a running container
3 7
 ====================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Login Command
2
+:description: Register or Login to the docker registry server
3
+:keywords: login, docker, documentation
4
+
1 5
 ============================================================
2 6
 ``login`` -- Register or Login to the docker registry server
3 7
 ============================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Logs Command
2
+:description: Fetch the logs of a container
3
+:keywords: logs, container, docker, documentation
4
+
1 5
 =========================================
2 6
 ``logs`` -- Fetch the logs of a container
3 7
 =========================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Port Command
2
+:description: Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
3
+:keywords: port, docker, container, documentation
4
+
1 5
 =========================================================================
2 6
 ``port`` -- Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
3 7
 =========================================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Ps Command
2
+:description: List containers
3
+:keywords: ps, docker, documentation, container
4
+
1 5
 =========================
2 6
 ``ps`` -- List containers
3 7
 =========================
... ...
@@ -1,3 +1,7 @@
1
+:title: Pull Command
2
+:description: Pull an image or a repository from the registry
3
+:keywords: pull, image, repo, repository, documentation, docker
4
+
1 5
 =========================================================================
2 6
 ``pull`` -- Pull an image or a repository from the docker registry server
3 7
 =========================================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Push Command
2
+:description: Push an image or a repository to the registry
3
+:keywords: push, docker, image, repository, documentation, repo
4
+
1 5
 =======================================================================
2 6
 ``push`` -- Push an image or a repository to the docker registry server
3 7
 =======================================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Restart Command
2
+:description: Restart a running container
3
+:keywords: restart, container, docker, documentation
4
+
1 5
 ==========================================
2 6
 ``restart`` -- Restart a running container
3 7
 ==========================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Rm Command
2
+:description: Remove a container
3
+:keywords: remove, container, docker, documentation, rm
4
+
1 5
 ============================
2 6
 ``rm`` -- Remove a container
3 7
 ============================
... ...
@@ -1,3 +1,7 @@
1
+:title: Rmi Command
2
+:description: Remove an image
3
+:keywords: rmi, remove, image, docker, documentation
4
+
1 5
 ==========================
2 6
 ``rmi`` -- Remove an image
3 7
 ==========================
... ...
@@ -1,3 +1,7 @@
1
+:title: Run Command
2
+:description: Run a command in a new container
3
+:keywords: run, container, docker, documentation 
4
+
1 5
 ===========================================
2 6
 ``run`` -- Run a command in a new container
3 7
 ===========================================
... ...
@@ -19,5 +23,5 @@
19 19
       -t=false: Allocate a pseudo-tty
20 20
       -u="": Username or UID
21 21
       -d=[]: Set custom dns servers for the container
22
-      -v=[]: Creates a new volumes and mount it at the specified path.
22
+      -v=[]: Creates a new volume and mounts it at the specified path.
23 23
       -volumes-from="": Mount all volumes from the given container.
... ...
@@ -1,3 +1,7 @@
1
+:title: Search Command
2
+:description: Searches for the TERM parameter on the Docker index and prints out a list of repositories that match.
3
+:keywords: search, docker, image, documentation 
4
+
1 5
 ===================================================================
2 6
 ``search`` -- Search for an image in the docker index
3 7
 ===================================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Start Command
2
+:description: Start a stopped container
3
+:keywords: start, docker, container, documentation
4
+
1 5
 ======================================
2 6
 ``start`` -- Start a stopped container
3 7
 ======================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Stop Command
2
+:description: Stop a running container
3
+:keywords: stop, container, docker, documentation
4
+
1 5
 ====================================
2 6
 ``stop`` -- Stop a running container
3 7
 ====================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Tag Command
2
+:description: Tag an image into a repository
3
+:keywords: tag, docker, image, repository, documentation, repo
4
+
1 5
 =========================================
2 6
 ``tag`` -- Tag an image into a repository
3 7
 =========================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Version Command
2
+:description: 
3
+:keywords: version, docker, documentation
4
+
1 5
 ==================================================
2 6
 ``version`` -- Show the docker version information
3 7
 ==================================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Wait Command
2
+:description: Block until a container stops, then print its exit code.
3
+:keywords: wait, docker, container, documentation
4
+
1 5
 ===================================================================
2 6
 ``wait`` -- Block until a container stops, then print its exit code
3 7
 ===================================================================
... ...
@@ -1,6 +1,6 @@
1
-:title: docker documentation
1
+:title: Commands
2 2
 :description: -- todo: change me
3
-:keywords: todo: change me
3
+:keywords: todo, commands, command line, help, docker, documentation
4 4
 
5 5
 
6 6
 Commands
7 7
deleted file mode 100644
... ...
@@ -1,25 +0,0 @@
1
-:title: Building blocks
2
-:description: An introduction to docker and standard containers?
3
-:keywords: containers, lxc, concepts, explanation
4
-
5
-
6
-Building blocks
7
-===============
8
-
9
-.. _images:
10
-
11
-Images
12
-An original container image. These are stored on disk and are comparable with what you normally expect from a stopped virtual machine image. Images are stored (and retrieved from) repository
13
-
14
-Images are stored on your local file system under /var/lib/docker/graph
15
-
16
-
17
-.. _containers:
18
-
19
-Containers
20
-A container is a local version of an image. It can be running or stopped, The equivalent would be a virtual machine instance.
21
-
22
-Containers are stored on your local file system under /var/lib/docker/containers
23
-
... ...
@@ -1,6 +1,6 @@
1 1
 :title: Introduction
2 2
 :description: An introduction to docker and standard containers?
3
-:keywords: containers, lxc, concepts, explanation
3
+:keywords: containers, lxc, concepts, explanation, docker, documentation
4 4
 
5 5
 
6 6
 :note: This version of the introduction is temporary, just to make sure we don't break the links from the website when the documentation is updated
... ...
@@ -1,6 +1,6 @@
1
-:title: docker documentation
1
+:title: Concepts
2 2
 :description: -- todo: change me
3
-:keywords: todo: change me
3
+:keywords: concepts, documentation, docker, containers
4 4
 
5 5
 
6 6
 
... ...
@@ -13,5 +13,4 @@ Contents:
13 13
    :maxdepth: 1
14 14
 
15 15
    ../index
16
-   buildingblocks
17 16
 
... ...
@@ -1,101 +1,9 @@
1
+:title: Contribution Guidelines
2
+:description: Contribution guidelines: create issues, convetions, pull requests
3
+:keywords: contributing, docker, documentation, help, guideline
4
+
1 5
 Contributing to Docker
2 6
 ======================
3 7
 
4
-Want to hack on Docker? Awesome! There are instructions to get you
5
-started on the website: http://docker.io/gettingstarted.html
6
-
7
-They are probably not perfect, please let us know if anything feels
8
-wrong or incomplete.
9
-
10
-Contribution guidelines
11
-
12
-Pull requests are always welcome
13
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
14
-
15
-We are always thrilled to receive pull requests, and do our best to
16
-process them as fast as possible. Not sure if that typo is worth a pull
17
-request? Do it! We will appreciate it.
18
-
19
-If your pull request is not accepted on the first try, don't be
20
-discouraged! If there's a problem with the implementation, hopefully you
21
-received feedback on what to improve.
22
-
23
-We're trying very hard to keep Docker lean and focused. We don't want it
24
-to do everything for everybody. This means that we might decide against
25
-incorporating a new feature. However, there might be a way to implement
26
-that feature *on top of* docker.
27
-
28
-Discuss your design on the mailing list
29
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30
-
31
-We recommend discussing your plans `on the mailing
32
-list <https://groups.google.com/forum/?fromgroups#!forum/docker-club>`__
33
-before starting to code - especially for more ambitious contributions.
34
-This gives other contributors a chance to point you in the right
35
-direction, give feedback on your design, and maybe point out if someone
36
-else is working on the same thing.
37
-
38
-Create issues...
39
-~~~~~~~~~~~~~~~~
40
-
41
-Any significant improvement should be documented as `a github
42
-issue <https://github.com/dotcloud/docker/issues>`__ before anybody
43
-starts working on it.
44
-
45
-...but check for existing issues first!
46
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
47
-
48
-Please take a moment to check that an issue doesn't already exist
49
-documenting your bug report or improvement proposal. If it does, it
50
-never hurts to add a quick "+1" or "I have this problem too". This will
51
-help prioritize the most common problems and requests.
52
-
53
-Conventions
54
-~~~~~~~~~~~
55
-
56
-Fork the repo and make changes on your fork in a feature branch:
57
-
58
-- If it's a bugfix branch, name it XXX-something where XXX is the number of the
59
-  issue
60
-- If it's a feature branch, create an enhancement issue to announce your
61
-  intentions, and name it XXX-something where XXX is the number of the issue.
62
-
63
-Submit unit tests for your changes.  Go has a great test framework built in; use
64
-it! Take a look at existing tests for inspiration. Run the full test suite on
65
-your branch before submitting a pull request.
66
-
67
-Make sure you include relevant updates or additions to documentation when
68
-creating or modifying features.
69
-
70
-Write clean code. Universally formatted code promotes ease of writing, reading,
71
-and maintenance. Always run ``go fmt`` before committing your changes. Most
72
-editors have plugins that do this automatically, and there's also a git
73
-pre-commit hook:
74
-
75
-.. code-block:: bash
76
-
77
-    curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/master/fmt-check && chmod +x .git/hooks/pre-commit
78
-
79
-
80
-Pull requests descriptions should be as clear as possible and include a
81
-reference to all the issues that they address.
82
-
83
-Code review comments may be added to your pull request. Discuss, then make the
84
-suggested modifications and push additional commits to your feature branch. Be
85
-sure to post a comment after pushing. The new commits will show up in the pull
86
-request automatically, but the reviewers will not be notified unless you
87
-comment.
88
-
89
-Before the pull request is merged, make sure that you squash your commits into
90
-logical units of work using ``git rebase -i`` and ``git push -f``. After every
91
-commit the test suite should be passing. Include documentation changes in the
92
-same commit so that a revert would remove all traces of the feature or fix.
93
-
94
-Commits that fix or close an issue should include a reference like ``Closes #XXX``
95
-or ``Fixes #XXX``, which will automatically close the issue when merged.
8
+Want to hack on Docker? Awesome! The repository includes `all the instructions you need to get started <https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`.
96 9
 
97
-Add your name to the AUTHORS file, but make sure the list is sorted and your
98
-name and email address match your git configuration. The AUTHORS file is
99
-regenerated occasionally from the git commit history, so a mismatch may result
100
-in your changes being overwritten.
... ...
@@ -40,7 +40,7 @@ We attach to the new container to see what is going on. Ctrl-C to disconnect
40 40
 
41 41
 .. code-block:: bash
42 42
 
43
-    BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/hykes/helloflask/master)
43
+    BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
44 44
 
45 45
 Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name.
46 46
 
... ...
@@ -58,7 +58,7 @@ Use the new image we just created and create a new container with network port 5
58 58
 .. code-block:: bash
59 59
 
60 60
     docker logs $WEB_WORKER
61
-     * Running on \http://0.0.0.0:5000/
61
+     * Running on http://0.0.0.0:5000/
62 62
 
63 63
 view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
64 64
 
... ...
@@ -70,7 +70,8 @@ lookup the public-facing port which is NAT-ed store the private port used by the
70 70
 
71 71
 .. code-block:: bash
72 72
 
73
-    curl \http://`hostname`:$WEB_PORT
73
+    # install curl if necessary, then ...
74
+    curl http://127.0.0.1:$WEB_PORT
74 75
       Hello world!
75 76
 
76 77
 access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.
... ...
@@ -1,3 +1,7 @@
1
+:title: FAQ
2
+:description: Most frequently asked questions.
3
+:keywords: faq, questions, documentation, docker
4
+
1 5
 FAQ
2 6
 ===
3 7
 
... ...
@@ -1,3 +1,7 @@
1
+:title: Index Environment Variable
2
+:description: Setting this environment variable on the docker server will change the URL docker index.
3
+:keywords: docker, index environment variable, documentation 
4
+
1 5
 =================================
2 6
 Docker Index Environment Variable
3 7
 =================================
... ...
@@ -1,3 +1,7 @@
1
+:title: Installation on Amazon EC2 
2
+:description: Docker installation on Amazon EC2 with a single vagrant command. Vagrant 1.1 or higher is required.
3
+:keywords: amazon ec2, virtualization, cloud, docker, documentation, installation
4
+
1 5
 Amazon EC2
2 6
 ==========
3 7
 
... ...
@@ -1,3 +1,7 @@
1
+:title: Installation on Arch Linux
2
+:description: Docker installation on Arch Linux. 
3
+:keywords: arch linux, virtualization, docker, documentation, installation
4
+
1 5
 .. _arch_linux:
2 6
 
3 7
 Arch Linux
... ...
@@ -63,3 +67,21 @@ To start on system boot:
63 63
 ::
64 64
 
65 65
     sudo systemctl enable docker
66
+    
67
+Network Configuration
68
+---------------------
69
+
70
+IPv4 packet forwarding is disabled by default on Arch, so internet access from inside
71
+the container may not work.
72
+
73
+To enable the forwarding, run as root on the host system:
74
+
75
+::
76
+
77
+    sysctl net.ipv4.ip_forward=1
78
+    
79
+And, to make it persistent across reboots, enable it on the host's **/etc/sysctl.conf**:
80
+
81
+::
82
+
83
+    net.ipv4.ip_forward=1
... ...
@@ -1,3 +1,7 @@
1
+:title: Installation from Binaries
2
+:description: This instruction set is meant for hackers who want to try out Docker on a variety of environments.
3
+:keywords: binaries, installation, docker, documentation, linux
4
+
1 5
 .. _binaries:
2 6
 
3 7
 Binaries
... ...
@@ -23,7 +27,7 @@ But we know people have had success running it under
23 23
 Dependencies:
24 24
 -------------
25 25
 
26
-* 3.8 Kernel
26
+* 3.8 Kernel (read more about :ref:`kernel`)
27 27
 * AUFS filesystem support
28 28
 * lxc
29 29
 * bsdtar
... ...
@@ -1,6 +1,6 @@
1
-:title: docker documentation
1
+:title: Documentation
2 2
 :description: -- todo: change me
3
-:keywords: todo: change me
3
+:keywords: todo, docker, documentation, installation, OS support
4 4
 
5 5
 
6 6
 
... ...
@@ -1,63 +1,35 @@
1
+:title: Kernel Requirements
2
+:description: Kernel supports
3
+:keywords: kernel requirements, kernel support, docker, installation, cgroups, namespaces
4
+
1 5
 .. _kernel:
2 6
 
3 7
 Kernel Requirements
4 8
 ===================
5 9
 
10
+In short, Docker has the following kernel requirements:
11
+
12
+- Linux version 3.8 or above.
13
+
14
+- `AUFS support <http://aufs.sourceforge.net/>`_.
15
+
16
+- Cgroups and namespaces must be enabled.
17
+
18
+
6 19
   The officially supported kernel is the one recommended by the
7 20
   :ref:`ubuntu_linux` installation path. It is the one that most developers
8 21
   will use, and the one that receives the most attention from the core
9 22
   contributors. If you decide to go with a different kernel and hit a bug,
10 23
   please try to reproduce it with the official kernels first.
11 24
 
12
-If for some reason you cannot or do not want to use the "official" kernels,
25
+If you cannot or do not want to use the "official" kernels,
13 26
 here is some technical background about the features (both optional and
14 27
 mandatory) that docker needs to run successfully.
15 28
 
16
-In short, you need kernel version 3.8 (or above), compiled to include
17
-`AUFS support <http://aufs.sourceforge.net/>`_. Of course, you need to
18
-enable cgroups and namespaces.
19
-
20
-
21
-Namespaces and Cgroups
22
-
23
-You need to enable namespaces and cgroups, to the extend of what is needed
24
-to run LXC containers. Technically, while namespaces have been introduced
25
-in the early 2.6 kernels, we do not advise to try any kernel before 2.6.32
26
-to run LXC containers. Note that 2.6.32 has some documented issues regarding
27
-network namespace setup and teardown; those issues are not a risk if you
28
-run containers in a private environment, but can lead to denial-of-service
29
-attacks if you want to run untrusted code in your containers. For more details,
30
-see `[LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
31
-
32
-Kernels 2.6.38, and every version since 3.2, have been deployed successfully
33
-to run containerized production workloads. Feature-wise, there is no huge
34
-improvement between 2.6.38 and up to 3.6 (as far as docker is concerned!).
35
-
36
-Starting with version 3.7, the kernel has basic support for
37
-`Checkpoint/Restore In Userspace <http://criu.org/>`_, which is not used by
38
-docker at this point, but allows to suspend the state of a container to
39
-disk and resume it later.
40
-
41
-Version 3.8 provides improvements in stability, which are deemed necessary
42
-for the operation of docker. Versions 3.2 to 3.5 have been shown to
43
-exhibit a reproducible bug (for more details, see issue
44
-`#407 <https://github.com/dotcloud/docker/issues/407>`_).
45
-
46
-Version 3.8 also brings better support for the
47
-`setns() syscall <http://lwn.net/Articles/531381/>`_ -- but this should not
48
-be a concern since docker does not leverage on this feature for now.
49
-
50
-If you want a technical overview about those concepts, you might
51
-want to check those articles on dotCloud's blog:
52
-`about namespaces <http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part>`_
53
-and `about cgroups <http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c>`_.
54
-
29
+Linux version 3.8 or above
30
+--------------------------
55 31
 
56
-Important Note About Pre-3.8 Kernels
57
-
58
-As mentioned above, kernels before 3.8 are not stable when used with docker.
32
+Kernel versions 3.2 to 3.5 are not stable when used with docker.
59 33
 In some circumstances, you will experience kernel "oopses", or even crashes.
60 34
 The symptoms include:
61 35
 
... ...
@@ -77,6 +49,36 @@ detects something older than 3.8.
77 77
 See issue `#407 <https://github.com/dotcloud/docker/issues/407>`_ for details.
78 78
 
79 79
 
80
+AUFS support
81
+------------
82
+
83
+Docker currently relies on AUFS, an unioning filesystem.
84
+While AUFS is included in the kernels built by the Debian and Ubuntu
85
+distributions, is not part of the standard kernel. This means that if
86
+you decide to roll your own kernel, you will have to patch your
87
+kernel tree to add AUFS. The process is documented on
88
+`AUFS webpage <http://aufs.sourceforge.net/>`_.
89
+
90
+
91
+Cgroups and namespaces
92
+----------------------
93
+
94
+You need to enable namespaces and cgroups, to the extend of what is needed
95
+to run LXC containers. Technically, while namespaces have been introduced
96
+in the early 2.6 kernels, we do not advise to try any kernel before 2.6.32
97
+to run LXC containers. Note that 2.6.32 has some documented issues regarding
98
+network namespace setup and teardown; those issues are not a risk if you
99
+run containers in a private environment, but can lead to denial-of-service
100
+attacks if you want to run untrusted code in your containers. For more details,
101
+see `[LP#720095 <https://bugs.launchpad.net/ubuntu/+source/linux/+bug/720095>`_.
102
+
103
+Kernels 2.6.38, and every version since 3.2, have been deployed successfully
104
+to run containerized production workloads. Feature-wise, there is no huge
105
+improvement between 2.6.38 and up to 3.6 (as far as docker is concerned!).
106
+
107
+
108
+
109
+
80 110
 Extra Cgroup Controllers
81 111
 ------------------------
82 112
 
... ...
@@ -111,39 +113,3 @@ And replace it by the following one::
111 111
     GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount"
112 112
 
113 113
 Then run ``update-grub``, and reboot.
114
-
115
-
116
-AUFS
117
-
118
-Docker currently relies on AUFS, an unioning filesystem.
119
-While AUFS is included in the kernels built by the Debian and Ubuntu
120
-distributions, is not part of the standard kernel. This means that if
121
-you decide to roll your own kernel, you will have to patch your
122
-kernel tree to add AUFS. The process is documented on
123
-`AUFS webpage <http://aufs.sourceforge.net/>`_.
124
-
125
-Note: the AUFS patch is fairly intrusive, but for the record, people have
126
-successfully applied GRSEC and AUFS together, to obtain hardened production
127
-kernels.
128
-
129
-If you want more information about that topic, there is an
130
-`article about AUFS on dotCloud's blog 
131
-<http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-34-a>`_.
132
-
133
-
134
-BTRFS, ZFS, OverlayFS...
135
-
136
-There is ongoing development on docker, to implement support for
137
-`BTRFS <http://en.wikipedia.org/wiki/Btrfs>`_
138
-(see github issue `#443 <https://github.com/dotcloud/docker/issues/443>`_).
139
-
140
-People have also showed interest for `ZFS <http://en.wikipedia.org/wiki/ZFS>`_
141
-(using e.g. `ZFS-on-Linux <http://zfsonlinux.org/>`_) and OverlayFS.
142
-The latter is functionally close to AUFS, and it might end up being included
143
-in the stock kernel; so it's a strong candidate!
144
-
145
-Would you like to `contribute
146
-<https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md>`_
147
-support for your favorite filesystem?
... ...
@@ -1,3 +1,7 @@
1
+:title: Rackspace Cloud Installation
2
+:description: Installing Docker on Ubuntu proviced by Rackspace
3
+:keywords: Rackspace Cloud, installation, docker, linux, ubuntu
4
+
1 5
 ===============
2 6
 Rackspace Cloud
3 7
 ===============
... ...
@@ -1,3 +1,7 @@
1
+:title: Requirements and Installation on Ubuntu Linux
2
+:description: Please note this project is currently under heavy development. It should not be used in production.
3
+:keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux
4
+
1 5
 .. _ubuntu_linux:
2 6
 
3 7
 Ubuntu Linux
... ...
@@ -12,7 +16,7 @@ Right now, the officially supported distribution are:
12 12
 
13 13
 Docker has the following dependencies
14 14
 
15
-* Linux kernel 3.8
15
+* Linux kernel 3.8 (read more about :ref:`kernel`)
16 16
 * AUFS file system support (we are working on BTRFS support as an alternative)
17 17
 
18 18
 .. _ubuntu_precise:
... ...
@@ -34,7 +38,7 @@ Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.
34 34
 .. code-block:: bash
35 35
 
36 36
    # install the backported kernel
37
-   sudo apt-get update && sudo apt-get install linux-image-3.8.0-19-generic
37
+   sudo apt-get update && sudo apt-get install linux-image-generic-lts-raring
38 38
 
39 39
    # reboot
40 40
    sudo reboot
... ...
@@ -50,9 +54,9 @@ which makes installing Docker on Ubuntu very easy.
50 50
 .. code-block:: bash
51 51
 
52 52
    # Add the PPA sources to your apt sources list.
53
-   sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' > /etc/apt/sources.list.d/lxc-docker.list"
53
+   sudo apt-get install python-software-properties && sudo add-apt-repository ppa:dotcloud/lxc-docker
54 54
 
55
-   # Update your sources, you will see a warning.
55
+   # Update your sources
56 56
    sudo apt-get update
57 57
 
58 58
    # Install, you will see another warning that the package cannot be authenticated. Confirm install.
... ...
@@ -1,3 +1,7 @@
1
+:title: Upgrading
2
+:description: These instructions are for upgrading Docker
3
+:keywords: Docker, Docker documentation, upgrading docker, upgrade
4
+
1 5
 .. _upgrading:
2 6
 
3 7
 Upgrading
... ...
@@ -1,3 +1,6 @@
1
+:title: Using Vagrant (Mac, Linux)
2
+:description: This guide will setup a new virtualbox virtual machine with docker installed on your computer.
3
+:keywords: Docker, Docker documentation, virtualbox, vagrant, git, ssh, putty, cygwin
1 4
 
2 5
 .. _install_using_vagrant:
3 6
 
... ...
@@ -1,6 +1,6 @@
1
-:title: docker documentation
2
-:description: docker documentation
3
-:keywords:
1
+:title: Documentation
2
+:description: -- todo: change me
3
+:keywords: todo, docker, documentation, installation, usage, examples, contributing, faq, command line, concepts
4 4
 
5 5
 Documentation
6 6
 =============
... ...
@@ -1,6 +1,6 @@
1
-:title: Base commands
1
+:title: Basic Commands
2 2
 :description: Common usage and commands
3
-:keywords: Examples, Usage
3
+:keywords: Examples, Usage, basic commands, docker, documentation, examples
4 4
 
5 5
 
6 6
 The basics
... ...
@@ -33,6 +33,19 @@ Running an interactive shell
33 33
   # allocate a tty, attach stdin and stdout
34 34
   docker run -i -t base /bin/bash
35 35
 
36
+Bind Docker to another host/port
37
+--------------------------------
38
+
39
+If you want Docker to listen to another port and bind to another ip
40
+use -host and -port on both deamon and client
41
+
42
+.. code-block:: bash
43
+
44
+   # Run docker in daemon mode
45
+   sudo <path to>/docker -H 0.0.0.0:5555 &
46
+   # Download a base image
47
+   docker -H :5555 pull base
48
+
36 49
 
37 50
 Starting a long-running worker process
38 51
 --------------------------------------
... ...
@@ -1,3 +1,7 @@
1
+:title: Docker Builder
2
+:description: Docker Builder specifes a simple DSL which allows you to automate the steps you would normally manually take to create an image.
3
+:keywords: builder, docker, Docker Builder, automation, image creation
4
+
1 5
 ==============
2 6
 Docker Builder
3 7
 ==============
... ...
@@ -1,6 +1,6 @@
1
-:title: docker documentation
1
+:title: Documentation
2 2
 :description: -- todo: change me
3
-:keywords: todo: change me
3
+:keywords: todo, docker, documentation, basic, builder
4 4
 
5 5
 
6 6
 
... ...
@@ -1,3 +1,6 @@
1
+:title: Puppet Usage
2
+:description: Installating and using Puppet
3
+:keywords: puppet, installation, usage, docker, documentation
1 4
 
2 5
 .. _install_using_puppet:
3 6
 
... ...
@@ -22,9 +25,9 @@ Installation
22 22
 The module is available on the `Puppet Forge <https://forge.puppetlabs.com/garethr/docker/>`_
23 23
 and can be installed using the built-in module tool.
24 24
 
25
-   .. code-block:: bash
25
+.. code-block:: bash
26 26
 
27
-      puppet module install garethr/docker
27
+   puppet module install garethr/docker
28 28
 
29 29
 It can also be found on `GitHub <https://www.github.com/garethr/garethr-docker>`_ 
30 30
 if you would rather download the source.
... ...
@@ -38,9 +41,9 @@ for managing images and containers.
38 38
 Installation
39 39
 ~~~~~~~~~~~~
40 40
 
41
-   .. code-block:: ruby
41
+.. code-block:: ruby
42 42
 
43
-      include 'docker'
43
+  include 'docker'
44 44
 
45 45
 Images
46 46
 ~~~~~~
... ...
@@ -48,26 +51,26 @@ Images
48 48
 The next step is probably to install a docker image, for this we have a
49 49
 defined type which can be used like so:
50 50
 
51
-   .. code-block:: ruby
51
+.. code-block:: ruby
52 52
 
53
-      docker::image { 'base': }
53
+  docker::image { 'base': }
54 54
 
55 55
 This is equivalent to running:
56 56
 
57
-   .. code-block:: bash
57
+.. code-block:: bash
58 58
 
59
-      docker pull base
59
+  docker pull base
60 60
 
61 61
 Note that it will only if the image of that name does not already exist.
62 62
 This is downloading a large binary so on first run can take a while.
63 63
 For that reason this define turns off the default 5 minute timeout
64 64
 for exec. Note that you can also remove images you no longer need with:
65 65
 
66
-   .. code-block:: ruby
66
+.. code-block:: ruby
67 67
 
68
-      docker::image { 'base':
69
-        ensure => 'absent',
70
-      }
68
+  docker::image { 'base':
69
+    ensure => 'absent',
70
+  }
71 71
 
72 72
 Containers
73 73
 ~~~~~~~~~~
... ...
@@ -75,35 +78,35 @@ Containers
75 75
 Now you have an image you can run commands within a container managed by
76 76
 docker.
77 77
 
78
-   .. code-block:: ruby
78
+.. code-block:: ruby
79 79
 
80
-      docker::run { 'helloworld':
81
-        image   => 'base',
82
-        command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"',
83
-      }
80
+  docker::run { 'helloworld':
81
+    image   => 'base',
82
+    command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"',
83
+  }
84 84
 
85 85
 This is equivalent to running the following command, but under upstart:
86 86
 
87
-   .. code-block:: bash
87
+.. code-block:: bash
88 88
 
89
-      docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done"
89
+  docker run -d base /bin/sh -c "while true; do echo hello world; sleep 1; done"
90 90
 
91 91
 Run also contains a number of optional parameters:
92 92
 
93
-   .. code-block:: ruby
94
-
95
-      docker::run { 'helloworld':
96
-        image        => 'base',
97
-        command      => '/bin/sh -c "while true; do echo hello world; sleep 1; done"',
98
-        ports        => ['4444', '4555'],
99
-        volumes      => ['/var/lib/counchdb', '/var/log'],
100
-        volumes_from => '6446ea52fbc9',
101
-        memory_limit => 10485760, # bytes 
102
-        username     => 'example',
103
-        hostname     => 'example.com',
104
-        env          => ['FOO=BAR', 'FOO2=BAR2'],
105
-        dns          => ['8.8.8.8', '8.8.4.4'],
106
-      }
93
+.. code-block:: ruby
94
+
95
+  docker::run { 'helloworld':
96
+    image        => 'base',
97
+    command      => '/bin/sh -c "while true; do echo hello world; sleep 1; done"',
98
+    ports        => ['4444', '4555'],
99
+    volumes      => ['/var/lib/counchdb', '/var/log'],
100
+    volumes_from => '6446ea52fbc9',
101
+    memory_limit => 10485760, # bytes
102
+    username     => 'example',
103
+    hostname     => 'example.com',
104
+    env          => ['FOO=BAR', 'FOO2=BAR2'],
105
+    dns          => ['8.8.8.8', '8.8.4.4'],
106
+  }
107 107
 
108 108
 Note that ports, env, dns and volumes can be set with either a single string
109 109
 or as above with an array of values.
... ...
@@ -1,3 +1,7 @@
1
+:title: Working With Repositories
2
+:description: Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind Docker, and user repositories.
3
+:keywords: repo, repositiores, usage, pull image, push image, image, documentation
4
+
1 5
 .. _working_with_the_repository:
2 6
 
3 7
 Working with the repository
4 8
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Thatcher Penskens <thatcher@dotcloud.com>
... ...
@@ -8,7 +8,7 @@
8 8
     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 9
     <meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
10 10
 
11
-    <title>Docker - {{ meta['title'] if meta and meta['title'] else title }}</title>
11
+    <title>{{ meta['title'] if meta and meta['title'] else title }} - Docker Documentation</title>
12 12
 
13 13
     <meta name="description" content="{{ meta['description'] if meta }}" />
14 14
     <meta name="keywords" content="{{ meta['keywords'] if meta }}" />
15 15
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Thatcher Penskens <thatcher@dotcloud.com>
... ...
@@ -89,9 +89,10 @@
89 89
                     <li>
90 90
                         <p><strong>Install Docker</strong></p>
91 91
                         <p>Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.</p>
92
-                        <p>You may see some warnings that the GPG keys cannot be verified.</p>
92
+                        <p>This may import a new GPG key (key 63561DC6: public key "Launchpad PPA for dotcloud team" imported).</p>
93 93
                         <div class="highlight">
94
-                            <pre>sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"</pre>
94
+                            <pre>sudo apt-get install software-properties-common</pre>
95
+                            <pre>sudo add-apt-repository ppa:dotcloud/lxc-docker</pre>
95 96
                             <pre>sudo apt-get update</pre>
96 97
                             <pre>sudo apt-get install lxc-docker</pre>
97 98
                         </div>
... ...
@@ -198,6 +198,35 @@
198 198
 
199 199
 
200 200
 <div class="container">
201
+
202
+    <div class="row">
203
+        <div class="span6">
204
+            <section class="contentblock twitterblock">
205
+                <img src="https://si0.twimg.com/profile_images/2707460527/252a64411a339184ff375a96fb68dcb0_bigger.png">
206
+                <em>Mitchell Hashimoto‏@mitchellh:</em> Docker launched today. It is incredible. They’re also working RIGHT NOW on a Vagrant provider. LXC is COMING!!
207
+            </section>
208
+        </div>
209
+        <div class="span6">
210
+            <section class="contentblock twitterblock">
211
+                <img src="https://si0.twimg.com/profile_images/1108290260/Adam_Jacob-114x150_original_bigger.jpg">
212
+                <em>Adam Jacob‏@adamhjk:</em> Docker is clearly the right idea. @solomonstre absolutely killed it. Containerized app deployment is the future, I think.
213
+            </section>
214
+        </div>
215
+    </div>
216
+    <div class="row">
217
+        <div class="span6">
218
+            <section class="contentblock twitterblock">
219
+                <img src="https://si0.twimg.com/profile_images/14872832/twitter_pic_bigger.jpg">
220
+                <em>Matt Townsend‏@mtownsend:</em> I have a serious code crush on docker.io - it's Lego for PaaS. Motherfucking awesome Lego.
221
+            </section>
222
+        </div>
223
+        <div class="span6">
224
+            <section class="contentblock twitterblock">
225
+                <img src="https://si0.twimg.com/profile_images/1312352395/rupert-259x300_bigger.jpg">
226
+                <em>Rob Harrop‏@robertharrop:</em> Impressed by @getdocker - it's all kinds of magic. Serious rethink of AWS architecture happening @skillsmatter.
227
+            </section>
228
+        </div>
229
+    </div>
201 230
     <div class="row">
202 231
         <div class="span6">
203 232
             <section class="contentblock twitterblock">
... ...
@@ -174,7 +174,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
174 174
 	if err != nil {
175 175
 		return nil, err
176 176
 	}
177
-	return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)"), tmp.Root)
177
+	return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)", false), tmp.Root)
178 178
 }
179 179
 
180 180
 // Mktemp creates a temporary sub-directory inside the graph's filesystem.
181 181
new file mode 100755
... ...
@@ -0,0 +1,3 @@
0
+#!/bin/sh
1
+
2
+find $1 -name MAINTAINERS -exec cat {} ';' | sed -E -e 's/^[^:]*: *(.*)$/\1/' | grep -E -v -e '^ *$' -e '^ *#.*$' | sort -u
0 3
new file mode 100755
... ...
@@ -0,0 +1,58 @@
0
+#!/bin/sh
1
+
2
+if [ $# -ne 1 ]; then
3
+	echo >&2 "Usage: $0 PATH"
4
+	echo >&2 "Show the primary and secondary maintainers for a given path"
5
+	exit 1
6
+fi
7
+
8
+set -e
9
+
10
+DEST=$1
11
+DESTFILE=""
12
+if [ ! -d $DEST ]; then
13
+	DESTFILE=$(basename $DEST)
14
+	DEST=$(dirname $DEST)
15
+fi
16
+
17
+MAINTAINERS=()
18
+cd $DEST
19
+while true; do
20
+	if [ -e ./MAINTAINERS ]; then
21
+		{
22
+			while read line; do
23
+				re='^([^:]*): *(.*)$'
24
+				file=$(echo $line | sed -E -n "s/$re/\1/p")
25
+				if [ ! -z "$file" ]; then
26
+					if [ "$file" = "$DESTFILE" ]; then
27
+						echo "Override: $line"
28
+						maintainer=$(echo $line | sed -E -n "s/$re/\2/p")
29
+						MAINTAINERS=("$maintainer" "${MAINTAINERS[@]}")
30
+					fi
31
+				else
32
+					MAINTAINERS+=("$line");
33
+				fi
34
+			done;
35
+		} < MAINTAINERS
36
+	fi
37
+	if [ -d .git ]; then
38
+		break
39
+	fi
40
+	if [ "$(pwd)" = "/" ]; then
41
+		break
42
+	fi
43
+	cd ..
44
+done
45
+
46
+PRIMARY="${MAINTAINERS[0]}"
47
+PRIMARY_FIRSTNAME=$(echo $PRIMARY | cut -d' ' -f1)
48
+
49
+firstname() {
50
+	echo $1 | cut -d' ' -f1
51
+}
52
+
53
+echo "--- $PRIMARY is the PRIMARY MAINTAINER of $1. Assign pull requests to him."
54
+echo "$(firstname $PRIMARY) may assign pull requests to the following secondary maintainers:"
55
+for SECONDARY in "${MAINTAINERS[@]:1}"; do
56
+	echo "--- $SECONDARY"
57
+done
0 58
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Daniel Mizyrycki <daniel@dotcloud.com>
... ...
@@ -1,37 +1,44 @@
1
+lxc-docker (0.3.3-1) precise; urgency=low
2
+  - Registry: Fix push regression
3
+  - Various bugfixes
4
+
5
+ -- dotCloud <ops@dotcloud.com>  Thu, 23 May 2013 00:00:00 -0700
6
+
7
+
1 8
 lxc-docker (0.3.2-1) precise; urgency=low
2
- - Runtime: Store the actual archive on commit
3
- - Registry: Improve the checksum process
4
- - Registry: Use the size to have a good progress bar while pushing
5
- - Registry: Use the actual archive if it exists in order to speed up the push
6
- - Registry: Fix error 400 on push
9
+  - Runtime: Store the actual archive on commit
10
+  - Registry: Improve the checksum process
11
+  - Registry: Use the size to have a good progress bar while pushing
12
+  - Registry: Use the actual archive if it exists in order to speed up the push
13
+  - Registry: Fix error 400 on push
7 14
 
8 15
  -- dotCloud <ops@dotcloud.com>  Fri, 9 May 2013 00:00:00 -0700
9 16
 
10 17
 
11 18
 lxc-docker (0.3.1-1) precise; urgency=low
12
- - Builder: Implement the autorun capability within docker builder
13
- - Builder: Add caching to docker builder
14
- - Builder: Add support for docker builder with native API as top level command
15
- - Runtime: Add go version to debug infos
16
- - Builder: Implement ENV within docker builder
17
- - Registry: Add docker search top level command in order to search a repository
18
- - Images: output graph of images to dot (graphviz)
19
- - Documentation: new introduction and high-level overview
20
- - Documentation: Add the documentation for docker builder
21
- - Website: new high-level overview
22
- - Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
23
- - Images: fix ByParent function
24
- - Builder: Check the command existance prior create and add Unit tests for the case
25
- - Registry: Fix pull for official images with specific tag
26
- - Registry: Fix issue when login in with a different user and trying to push
27
- - Documentation: CSS fix for docker documentation to make REST API docs look better.
28
- - Documentation: Fixed CouchDB example page header mistake
29
- - Documentation: fixed README formatting
30
- - Registry: Improve checksum - async calculation
31
- - Runtime: kernel version - don't show the dash if flavor is empty
32
- - Documentation: updated www.docker.io website.
33
- - Builder: use any whitespaces instead of tabs
34
- - Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
19
+  - Builder: Implement the autorun capability within docker builder
20
+  - Builder: Add caching to docker builder
21
+  - Builder: Add support for docker builder with native API as top level command
22
+  - Runtime: Add go version to debug infos
23
+  - Builder: Implement ENV within docker builder
24
+  - Registry: Add docker search top level command in order to search a repository
25
+  - Images: output graph of images to dot (graphviz)
26
+  - Documentation: new introduction and high-level overview
27
+  - Documentation: Add the documentation for docker builder
28
+  - Website: new high-level overview
29
+  - Makefile: Swap "go get" for "go get -d", especially to compile on go1.1rc
30
+  - Images: fix ByParent function
31
+  - Builder: Check the command existance prior create and add Unit tests for the case
32
+  - Registry: Fix pull for official images with specific tag
33
+  - Registry: Fix issue when login in with a different user and trying to push
34
+  - Documentation: CSS fix for docker documentation to make REST API docs look better.
35
+  - Documentation: Fixed CouchDB example page header mistake
36
+  - Documentation: fixed README formatting
37
+  - Registry: Improve checksum - async calculation
38
+  - Runtime: kernel version - don't show the dash if flavor is empty
39
+  - Documentation: updated www.docker.io website.
40
+  - Builder: use any whitespaces instead of tabs
41
+  - Packaging: packaging ubuntu; issue #510: Use goland-stable PPA package to build docker
35 42
 
36 43
  -- dotCloud <ops@dotcloud.com>  Fri, 8 May 2013 00:00:00 -0700
37 44
 
38 45
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+Sam Alba <sam@dotcloud.com>
1
+Joffrey Fuhrer <joffrey@dotcloud.com>
2
+Ken Cochrane <ken@dotcloud.com>
... ...
@@ -330,6 +330,9 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat
330 330
 	if validate {
331 331
 		suffix = "images"
332 332
 	}
333
+
334
+	utils.Debugf("Image list pushed to index:\n%s\n", imgListJson)
335
+
333 336
 	req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJson))
334 337
 	if err != nil {
335 338
 		return nil, err
... ...
@@ -428,9 +431,14 @@ func (r *Registry) ResetClient(authConfig *auth.AuthConfig) {
428 428
 	r.client.Jar = cookiejar.NewCookieJar()
429 429
 }
430 430
 
431
-func (r *Registry) GetAuthConfig() *auth.AuthConfig {
431
+func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
432
+	password := ""
433
+	if withPasswd {
434
+		password = r.authConfig.Password
435
+	}
432 436
 	return &auth.AuthConfig{
433 437
 		Username: r.authConfig.Username,
438
+		Password: password,
434 439
 		Email:    r.authConfig.Email,
435 440
 	}
436 441
 }
... ...
@@ -2,14 +2,15 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"github.com/dotcloud/docker/registry"
6 5
 	"github.com/dotcloud/docker/utils"
7 6
 	"io"
8 7
 	"io/ioutil"
8
+	"log"
9 9
 	"net"
10 10
 	"os"
11
-	"os/exec"
12 11
 	"os/user"
12
+	"strconv"
13
+	"strings"
13 14
 	"sync"
14 15
 	"testing"
15 16
 	"time"
... ...
@@ -32,13 +33,6 @@ func nuke(runtime *Runtime) error {
32 32
 	return os.RemoveAll(runtime.root)
33 33
 }
34 34
 
35
-func CopyDirectory(source, dest string) error {
36
-	if _, err := exec.Command("cp", "-ra", source, dest).Output(); err != nil {
37
-		return err
38
-	}
39
-	return nil
40
-}
41
-
42 35
 func layerArchive(tarfile string) (io.Reader, error) {
43 36
 	// FIXME: need to close f somewhere
44 37
 	f, err := os.Open(tarfile)
... ...
@@ -71,15 +65,16 @@ func init() {
71 71
 
72 72
 	// Create the "Server"
73 73
 	srv := &Server{
74
-		runtime:  runtime,
75
-		registry: registry.NewRegistry(runtime.root),
74
+		runtime: runtime,
76 75
 	}
77 76
 	// Retrieve the Image
78
-	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout); err != nil {
77
+	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, false); err != nil {
79 78
 		panic(err)
80 79
 	}
81 80
 }
82 81
 
82
+// FIXME: test that ImagePull(json=true) send correct json output
83
+
83 84
 func newTestRuntime() (*Runtime, error) {
84 85
 	root, err := ioutil.TempDir("", "docker-test")
85 86
 	if err != nil {
... ...
@@ -88,7 +83,7 @@ func newTestRuntime() (*Runtime, error) {
88 88
 	if err := os.Remove(root); err != nil {
89 89
 		return nil, err
90 90
 	}
91
-	if err := CopyDirectory(unitTestStoreBase, root); err != nil {
91
+	if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
92 92
 		return nil, err
93 93
 	}
94 94
 
... ...
@@ -285,24 +280,50 @@ func TestGet(t *testing.T) {
285 285
 
286 286
 }
287 287
 
288
-// Run a container with a TCP port allocated, and test that it can receive connections on localhost
289
-func TestAllocatePortLocalhost(t *testing.T) {
290
-	runtime, err := newTestRuntime()
291
-	if err != nil {
292
-		t.Fatal(err)
293
-	}
288
+func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
289
+	strPort := strconv.Itoa(port)
294 290
 	container, err := NewBuilder(runtime).Create(&Config{
295 291
 		Image:     GetTestImage(runtime).Id,
296
-		Cmd:       []string{"sh", "-c", "echo well hello there | nc -l -p 5555"},
297
-		PortSpecs: []string{"5555"},
292
+		Cmd:       []string{"sh", "-c", "echo well hello there | nc -l -p " + strPort},
293
+		PortSpecs: []string{strPort},
298 294
 	},
299 295
 	)
300 296
 	if err != nil {
301
-		t.Fatal(err)
297
+		return nil, err
302 298
 	}
303 299
 	if err := container.Start(); err != nil {
300
+		if strings.Contains(err.Error(), "address already in use") {
301
+			return nil, nil
302
+		}
303
+		return nil, err
304
+	}
305
+	return container, nil
306
+}
307
+
308
+// Run a container with a TCP port allocated, and test that it can receive connections on localhost
309
+func TestAllocatePortLocalhost(t *testing.T) {
310
+	runtime, err := newTestRuntime()
311
+	if err != nil {
304 312
 		t.Fatal(err)
305 313
 	}
314
+	port := 5554
315
+
316
+	var container *Container
317
+	for {
318
+		port += 1
319
+		log.Println("Trying port", port)
320
+		t.Log("Trying port", port)
321
+		container, err = findAvailalblePort(runtime, port)
322
+		if container != nil {
323
+			break
324
+		}
325
+		if err != nil {
326
+			t.Fatal(err)
327
+		}
328
+		log.Println("Port", port, "already in use")
329
+		t.Log("Port", port, "already in use")
330
+	}
331
+
306 332
 	defer container.Kill()
307 333
 
308 334
 	setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
... ...
@@ -316,7 +337,7 @@ func TestAllocatePortLocalhost(t *testing.T) {
316 316
 
317 317
 	conn, err := net.Dial("tcp",
318 318
 		fmt.Sprintf(
319
-			"localhost:%s", container.NetworkSettings.PortMapping["5555"],
319
+			"localhost:%s", container.NetworkSettings.PortMapping[strconv.Itoa(port)],
320 320
 		),
321 321
 	)
322 322
 	if err != nil {
... ...
@@ -345,7 +366,7 @@ func TestRestore(t *testing.T) {
345 345
 	if err := os.Remove(root); err != nil {
346 346
 		t.Fatal(err)
347 347
 	}
348
-	if err := CopyDirectory(unitTestStoreBase, root); err != nil {
348
+	if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil {
349 349
 		t.Fatal(err)
350 350
 	}
351 351
 
... ...
@@ -49,7 +49,8 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
49 49
 }
50 50
 
51 51
 func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
52
-	results, err := srv.registry.SearchRepositories(term)
52
+
53
+	results, err := registry.NewRegistry(srv.runtime.root).SearchRepositories(term)
53 54
 	if err != nil {
54 55
 		return nil, err
55 56
 	}
... ...
@@ -67,40 +68,40 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
67 67
 	return outs, nil
68 68
 }
69 69
 
70
-func (srv *Server) ImageInsert(name, url, path string, out io.Writer) error {
70
+func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, error) {
71 71
 	out = utils.NewWriteFlusher(out)
72 72
 	img, err := srv.runtime.repositories.LookupImage(name)
73 73
 	if err != nil {
74
-		return err
74
+		return "", err
75 75
 	}
76 76
 
77 77
 	file, err := utils.Download(url, out)
78 78
 	if err != nil {
79
-		return err
79
+		return "", err
80 80
 	}
81 81
 	defer file.Body.Close()
82 82
 
83 83
 	config, _, err := ParseRun([]string{img.Id, "echo", "insert", url, path}, srv.runtime.capabilities)
84 84
 	if err != nil {
85
-		return err
85
+		return "", err
86 86
 	}
87 87
 
88 88
 	b := NewBuilder(srv.runtime)
89 89
 	c, err := b.Create(config)
90 90
 	if err != nil {
91
-		return err
91
+		return "", err
92 92
 	}
93 93
 
94
-	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)"), path); err != nil {
95
-		return err
94
+	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)\r", false), path); err != nil {
95
+		return "", err
96 96
 	}
97 97
 	// FIXME: Handle custom repo, tag comment, author
98 98
 	img, err = b.Commit(c, "", "", img.Comment, img.Author, nil)
99 99
 	if err != nil {
100
-		return err
100
+		return "", err
101 101
 	}
102 102
 	fmt.Fprintf(out, "%s\n", img.Id)
103
-	return nil
103
+	return img.ShortId(), nil
104 104
 }
105 105
 
106 106
 func (srv *Server) ImagesViz(out io.Writer) error {
... ...
@@ -297,9 +298,8 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
297 297
 	return nil
298 298
 }
299 299
 
300
-func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []string) error {
301
-	out = utils.NewWriteFlusher(out)
302
-	history, err := srv.registry.GetRemoteHistory(imgId, registry, token)
300
+func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, json bool) error {
301
+	history, err := r.GetRemoteHistory(imgId, endpoint, token)
303 302
 	if err != nil {
304 303
 		return err
305 304
 	}
... ...
@@ -308,8 +308,8 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri
308 308
 	// FIXME: Launch the getRemoteImage() in goroutines
309 309
 	for _, id := range history {
310 310
 		if !srv.runtime.graph.Exists(id) {
311
-			fmt.Fprintf(out, "Pulling %s metadata\r\n", id)
312
-			imgJson, err := srv.registry.GetRemoteImageJson(id, registry, token)
311
+			fmt.Fprintf(out, utils.FormatStatus("Pulling %s metadata", json), id)
312
+			imgJson, err := r.GetRemoteImageJson(id, endpoint, token)
313 313
 			if err != nil {
314 314
 				// FIXME: Keep goging in case of error?
315 315
 				return err
... ...
@@ -320,12 +320,12 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri
320 320
 			}
321 321
 
322 322
 			// Get the layer
323
-			fmt.Fprintf(out, "Pulling %s fs layer\r\n", img.Id)
324
-			layer, contentLength, err := srv.registry.GetRemoteImageLayer(img.Id, registry, token)
323
+			fmt.Fprintf(out, utils.FormatStatus("Pulling %s fs layer", json), id)
324
+			layer, contentLength, err := r.GetRemoteImageLayer(img.Id, endpoint, token)
325 325
 			if err != nil {
326 326
 				return err
327 327
 			}
328
-			if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, "Downloading %v/%v (%v)"), false, img); err != nil {
328
+			if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, utils.FormatProgress("%v/%v (%v)", json), json), false, img); err != nil {
329 329
 				return err
330 330
 			}
331 331
 		}
... ...
@@ -333,10 +333,9 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri
333 333
 	return nil
334 334
 }
335 335
 
336
-func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error {
337
-	out = utils.NewWriteFlusher(out)
338
-	fmt.Fprintf(out, "Pulling repository %s from %s\r\n", remote, auth.IndexServerAddress())
339
-	repoData, err := srv.registry.GetRepositoryData(remote)
336
+func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, json bool) error {
337
+	fmt.Fprintf(out, utils.FormatStatus("Pulling repository %s from %s", json), remote, auth.IndexServerAddress())
338
+	repoData, err := r.GetRepositoryData(remote)
340 339
 	if err != nil {
341 340
 		return err
342 341
 	}
... ...
@@ -348,7 +347,7 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error
348 348
 	}
349 349
 
350 350
 	utils.Debugf("Retrieving the tag list")
351
-	tagsList, err := srv.registry.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens)
351
+	tagsList, err := r.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens)
352 352
 	if err != nil {
353 353
 		return err
354 354
 	}
... ...
@@ -372,11 +371,11 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error
372 372
 			utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
373 373
 			continue
374 374
 		}
375
-		fmt.Fprintf(out, "Pulling image %s (%s) from %s\n", img.Id, img.Tag, remote)
375
+		fmt.Fprintf(out, utils.FormatStatus("Pulling image %s (%s) from %s", json), img.Id, img.Tag, remote)
376 376
 		success := false
377 377
 		for _, ep := range repoData.Endpoints {
378
-			if err := srv.pullImage(out, img.Id, "https://"+ep+"/v1", repoData.Tokens); err != nil {
379
-				fmt.Fprintf(out, "Error while retrieving image for tag: %s (%s); checking next endpoint\n", askedTag, err)
378
+			if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, json); err != nil {
379
+				fmt.Fprintf(out, utils.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint\n", json), askedTag, err)
380 380
 				continue
381 381
 			}
382 382
 			success = true
... ...
@@ -401,15 +400,17 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error
401 401
 	return nil
402 402
 }
403 403
 
404
-func (srv *Server) ImagePull(name, tag, registry string, out io.Writer) error {
405
-	if registry != "" {
406
-		if err := srv.pullImage(out, name, registry, nil); err != nil {
404
+func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, json bool) error {
405
+	r := registry.NewRegistry(srv.runtime.root)
406
+	out = utils.NewWriteFlusher(out)
407
+	if endpoint != "" {
408
+		if err := srv.pullImage(r, out, name, endpoint, nil, json); err != nil {
407 409
 			return err
408 410
 		}
409 411
 		return nil
410 412
 	}
411 413
 
412
-	if err := srv.pullRepository(out, name, tag); err != nil {
414
+	if err := srv.pullRepository(r, out, name, tag, json); err != nil {
413 415
 		return err
414 416
 	}
415 417
 
... ...
@@ -482,21 +483,20 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
482 482
 	return imgList, nil
483 483
 }
484 484
 
485
-func (srv *Server) pushRepository(out io.Writer, name string, localRepo map[string]string) error {
485
+func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string) error {
486 486
 	out = utils.NewWriteFlusher(out)
487 487
 	fmt.Fprintf(out, "Processing checksums\n")
488 488
 	imgList, err := srv.getImageList(localRepo)
489 489
 	if err != nil {
490 490
 		return err
491 491
 	}
492
-	fmt.Fprintf(out, "Sending image list\n")
492
+	fmt.Fprintf(out, "Sending images list\n")
493 493
 
494
-	repoData, err := srv.registry.PushImageJsonIndex(name, imgList, false)
494
+	repoData, err := r.PushImageJsonIndex(name, imgList, false)
495 495
 	if err != nil {
496 496
 		return err
497 497
 	}
498 498
 
499
-	// FIXME: Send only needed images
500 499
 	for _, ep := range repoData.Endpoints {
501 500
 		fmt.Fprintf(out, "Pushing repository %s to %s (%d tags)\r\n", name, ep, len(localRepo))
502 501
 		// For each image within the repo, push them
... ...
@@ -505,24 +505,24 @@ func (srv *Server) pushRepository(out io.Writer, name string, localRepo map[stri
505 505
 				fmt.Fprintf(out, "Image %s already on registry, skipping\n", name)
506 506
 				continue
507 507
 			}
508
-			if err := srv.pushImage(out, name, elem.Id, ep, repoData.Tokens); err != nil {
508
+			if err := srv.pushImage(r, out, name, elem.Id, ep, repoData.Tokens); err != nil {
509 509
 				// FIXME: Continue on error?
510 510
 				return err
511 511
 			}
512 512
 			fmt.Fprintf(out, "Pushing tags for rev [%s] on {%s}\n", elem.Id, ep+"/users/"+name+"/"+elem.Tag)
513
-			if err := srv.registry.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
513
+			if err := r.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
514 514
 				return err
515 515
 			}
516 516
 		}
517 517
 	}
518 518
 
519
-	if _, err := srv.registry.PushImageJsonIndex(name, imgList, true); err != nil {
519
+	if _, err := r.PushImageJsonIndex(name, imgList, true); err != nil {
520 520
 		return err
521 521
 	}
522 522
 	return nil
523 523
 }
524 524
 
525
-func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []string) error {
525
+func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string) error {
526 526
 	out = utils.NewWriteFlusher(out)
527 527
 	jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json"))
528 528
 	if err != nil {
... ...
@@ -541,7 +541,7 @@ func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []st
541 541
 	}
542 542
 
543 543
 	// Send the json
544
-	if err := srv.registry.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
544
+	if err := r.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
545 545
 		if err == registry.ErrAlreadyExists {
546 546
 			fmt.Fprintf(out, "Image %s already uploaded ; skipping\n", imgData.Id)
547 547
 			return nil
... ...
@@ -576,20 +576,22 @@ func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []st
576 576
 	}
577 577
 
578 578
 	// Send the layer
579
-	if err := srv.registry.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, ""), ep, token); err != nil {
579
+	if err := r.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, "", false), ep, token); err != nil {
580 580
 		return err
581 581
 	}
582 582
 	return nil
583 583
 }
584 584
 
585
-func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
585
+func (srv *Server) ImagePush(name, endpoint string, out io.Writer) error {
586 586
 	out = utils.NewWriteFlusher(out)
587 587
 	img, err := srv.runtime.graph.Get(name)
588
+	r := registry.NewRegistry(srv.runtime.root)
589
+
588 590
 	if err != nil {
589 591
 		fmt.Fprintf(out, "The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
590 592
 		// If it fails, try to get the repository
591 593
 		if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
592
-			if err := srv.pushRepository(out, name, localRepo); err != nil {
594
+			if err := srv.pushRepository(r, out, name, localRepo); err != nil {
593 595
 				return err
594 596
 			}
595 597
 			return nil
... ...
@@ -598,7 +600,7 @@ func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
598 598
 		return err
599 599
 	}
600 600
 	fmt.Fprintf(out, "The push refers to an image: [%s]\n", name)
601
-	if err := srv.pushImage(out, name, img.Id, registry, nil); err != nil {
601
+	if err := srv.pushImage(r, out, name, img.Id, endpoint, nil); err != nil {
602 602
 		return err
603 603
 	}
604 604
 	return nil
... ...
@@ -627,7 +629,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
627 627
 		if err != nil {
628 628
 			return err
629 629
 		}
630
-		archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)")
630
+		archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)\r", false)
631 631
 	}
632 632
 	img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
633 633
 	if err != nil {
... ...
@@ -782,6 +784,13 @@ func (srv *Server) ContainerWait(name string) (int, error) {
782 782
 	return 0, fmt.Errorf("No such container: %s", name)
783 783
 }
784 784
 
785
+func (srv *Server) ContainerResize(name string, h, w int) error {
786
+	if container := srv.runtime.Get(name); container != nil {
787
+		return container.Resize(h, w)
788
+	}
789
+	return fmt.Errorf("No such container: %s", name)
790
+}
791
+
785 792
 func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, in io.ReadCloser, out io.Writer) error {
786 793
 	container := srv.runtime.Get(name)
787 794
 	if container == nil {
... ...
@@ -871,14 +880,12 @@ func NewServer(autoRestart bool) (*Server, error) {
871 871
 		return nil, err
872 872
 	}
873 873
 	srv := &Server{
874
-		runtime:  runtime,
875
-		registry: registry.NewRegistry(runtime.root),
874
+		runtime: runtime,
876 875
 	}
877 876
 	runtime.srv = srv
878 877
 	return srv, nil
879 878
 }
880 879
 
881 880
 type Server struct {
882
-	runtime  *Runtime
883
-	registry *registry.Registry
881
+	runtime *Runtime
884 882
 }
... ...
@@ -85,6 +85,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
85 85
 		t.Fatal(err)
86 86
 	}
87 87
 
88
+	// FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty")
88 89
 	if err = srv.ContainerDestroy(id, true); err != nil {
89 90
 		t.Fatal(err)
90 91
 	}
... ...
@@ -109,17 +109,35 @@ type State struct {
109 109
 	termios Termios
110 110
 }
111 111
 
112
+type Winsize struct {
113
+	Width  uint16
114
+	Height uint16
115
+	x      uint16
116
+	y      uint16
117
+}
118
+
119
+func GetWinsize(fd uintptr) (*Winsize, error) {
120
+	ws := &Winsize{}
121
+	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
122
+	return ws, err
123
+}
124
+
125
+func SetWinsize(fd uintptr, ws *Winsize) error {
126
+	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
127
+	return err
128
+}
129
+
112 130
 // IsTerminal returns true if the given file descriptor is a terminal.
113 131
 func IsTerminal(fd int) bool {
114 132
 	var termios Termios
115
-	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
133
+	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(getTermios), uintptr(unsafe.Pointer(&termios)))
116 134
 	return err == 0
117 135
 }
118 136
 
119 137
 // Restore restores the terminal connected to the given file descriptor to a
120 138
 // previous state.
121 139
 func Restore(fd int, state *State) error {
122
-	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
140
+	_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
123 141
 	return err
124 142
 }
125 143
 
126 144
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Daniel Mizyrycki <daniel@dotcloud.com>
... ...
@@ -49,10 +49,10 @@ func CompareConfig(a, b *Config) bool {
49 49
 }
50 50
 
51 51
 func MergeConfig(userConf, imageConf *Config) {
52
-	if userConf.Hostname != "" {
52
+	if userConf.Hostname == "" {
53 53
 		userConf.Hostname = imageConf.Hostname
54 54
 	}
55
-	if userConf.User != "" {
55
+	if userConf.User == "" {
56 56
 		userConf.User = imageConf.User
57 57
 	}
58 58
 	if userConf.Memory == 0 {
... ...
@@ -69,6 +69,7 @@ type progressReader struct {
69 69
 	readProgress int           // How much has been read so far (bytes)
70 70
 	lastUpdate   int           // How many bytes read at least update
71 71
 	template     string        // Template to print. Default "%v/%v (%v)"
72
+	json         bool
72 73
 }
73 74
 
74 75
 func (r *progressReader) Read(p []byte) (n int, err error) {
... ...
@@ -84,15 +85,15 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
84 84
 	}
85 85
 	if r.readProgress-r.lastUpdate > updateEvery || err != nil {
86 86
 		if r.readTotal > 0 {
87
-			fmt.Fprintf(r.output, r.template+"\r", r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
87
+			fmt.Fprintf(r.output, r.template, r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
88 88
 		} else {
89
-			fmt.Fprintf(r.output, r.template+"\r", r.readProgress, "?", "n/a")
89
+			fmt.Fprintf(r.output, r.template, r.readProgress, "?", "n/a")
90 90
 		}
91 91
 		r.lastUpdate = r.readProgress
92 92
 	}
93 93
 	// Send newline when complete
94 94
 	if err != nil {
95
-		fmt.Fprintf(r.output, "\n")
95
+		fmt.Fprintf(r.output, FormatStatus("", r.json))
96 96
 	}
97 97
 
98 98
 	return read, err
... ...
@@ -100,11 +101,11 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
100 100
 func (r *progressReader) Close() error {
101 101
 	return io.ReadCloser(r.reader).Close()
102 102
 }
103
-func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string) *progressReader {
103
+func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader {
104 104
 	if template == "" {
105
-		template = "%v/%v (%v)"
105
+		template = "%v/%v (%v)\r"
106 106
 	}
107
-	return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template}
107
+	return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template, json}
108 108
 }
109 109
 
110 110
 // HumanDuration returns a human-readable approximation of a duration
... ...
@@ -545,6 +546,13 @@ func GetKernelVersion() (*KernelVersionInfo, error) {
545 545
 	}, nil
546 546
 }
547 547
 
548
+func CopyDirectory(source, dest string) error {
549
+	if output, err := exec.Command("cp", "-ra", source, dest).CombinedOutput(); err != nil {
550
+		return fmt.Errorf("Error copy: %s (%s)", err, output)
551
+	}
552
+	return nil
553
+}
554
+
548 555
 type NopFlusher struct{}
549 556
 
550 557
 func (f *NopFlusher) Flush() {}
... ...
@@ -569,3 +577,17 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
569 569
 	}
570 570
 	return &WriteFlusher{w: w, flusher: flusher}
571 571
 }
572
+
573
+func FormatStatus(str string, json bool) string {
574
+	if json {
575
+		return "{\"status\" : \"" + str + "\"}"
576
+	}
577
+	return str + "\r\n"
578
+}
579
+
580
+func FormatProgress(str string, json bool) string {
581
+	if json {
582
+		return "{\"progress\" : \"" + str + "\"}"
583
+	}
584
+	return "Downloading " + str + "\r"
585
+}