Browse code

Merge branch 'master' into add-libcontainer

Conflicts:
execdriver/termconsole.go

Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)

Michael Crosby authored on 2014/02/27 05:55:24
Showing 28 changed files
... ...
@@ -1,8 +1,7 @@
1 1
 Solomon Hykes <solomon@dotcloud.com> (@shykes)
2 2
 Guillaume Charmes <guillaume@dotcloud.com> (@creack)
3
-Victor Vieux <victor@dotcloud.com> (@vieux)
3
+Victor Vieux <vieux@docker.com> (@vieux)
4 4
 Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
5 5
 .travis.yml: Tianon Gravi <admwiggin@gmail.com> (@tianon)
6
-api.go: Victor Vieux <victor@dotcloud.com> (@vieux)
7 6
 Dockerfile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
8 7
 Makefile: Tianon Gravi <admwiggin@gmail.com> (@tianon)
... ...
@@ -32,10 +32,10 @@ shell: build
32 32
 	$(DOCKER_RUN_DOCKER) bash
33 33
 
34 34
 build: bundles
35
-	docker build -rm -t "$(DOCKER_IMAGE)" .
35
+	docker build -t "$(DOCKER_IMAGE)" .
36 36
 
37 37
 docs-build:
38
-	docker build -rm -t "$(DOCKER_DOCS_IMAGE)" docs
38
+	docker build -t "$(DOCKER_DOCS_IMAGE)" docs
39 39
 
40 40
 bundles:
41 41
 	mkdir bundles
42 42
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Victor Vieux <vieux@docker.com> (@vieux)
... ...
@@ -138,7 +138,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
138 138
 	tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
139 139
 	suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
140 140
 	noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
141
-	rm := cmd.Bool([]string{"#rm", "-rm"}, false, "Remove intermediate containers after a successful build")
141
+	rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build")
142 142
 	if err := cmd.Parse(args); err != nil {
143 143
 		return nil
144 144
 	}
... ...
@@ -780,7 +780,10 @@ func (cli *DockerCli) CmdPort(args ...string) error {
780 780
 
781 781
 // 'docker rmi IMAGE' removes all images with the name IMAGE
782 782
 func (cli *DockerCli) CmdRmi(args ...string) error {
783
-	cmd := cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
783
+	var (
784
+		cmd   = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
785
+		force = cmd.Bool([]string{"f", "-force"}, false, "Force")
786
+	)
784 787
 	if err := cmd.Parse(args); err != nil {
785 788
 		return nil
786 789
 	}
... ...
@@ -789,9 +792,14 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
789 789
 		return nil
790 790
 	}
791 791
 
792
+	v := url.Values{}
793
+	if *force {
794
+		v.Set("force", "1")
795
+	}
796
+
792 797
 	var encounteredError error
793 798
 	for _, name := range cmd.Args() {
794
-		body, _, err := readBody(cli.call("DELETE", "/images/"+name, nil, false))
799
+		body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false))
795 800
 		if err != nil {
796 801
 			fmt.Fprintf(cli.err, "%s\n", err)
797 802
 			encounteredError = fmt.Errorf("Error: failed to remove one or more images")
... ...
@@ -2032,7 +2040,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b
2032 2032
 	re := regexp.MustCompile("/+")
2033 2033
 	path = re.ReplaceAllString(path, "/")
2034 2034
 
2035
-	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
2035
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), params)
2036 2036
 	if err != nil {
2037 2037
 		return nil, -1, err
2038 2038
 	}
... ...
@@ -2109,7 +2117,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h
2109 2109
 	re := regexp.MustCompile("/+")
2110 2110
 	path = re.ReplaceAllString(path, "/")
2111 2111
 
2112
-	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
2112
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), in)
2113 2113
 	if err != nil {
2114 2114
 		return err
2115 2115
 	}
... ...
@@ -2173,7 +2181,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
2173 2173
 	re := regexp.MustCompile("/+")
2174 2174
 	path = re.ReplaceAllString(path, "/")
2175 2175
 
2176
-	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
2176
+	req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", APIVERSION, path), nil)
2177 2177
 	if err != nil {
2178 2178
 		return err
2179 2179
 	}
... ...
@@ -9,7 +9,7 @@ import (
9 9
 )
10 10
 
11 11
 const (
12
-	APIVERSION        = 1.9
12
+	APIVERSION        = "1.10"
13 13
 	DEFAULTHTTPHOST   = "127.0.0.1"
14 14
 	DEFAULTUNIXSOCKET = "/var/run/docker.sock"
15 15
 )
... ...
@@ -12,6 +12,8 @@ import (
12 12
 	"github.com/dotcloud/docker/engine"
13 13
 	"github.com/dotcloud/docker/pkg/listenbuffer"
14 14
 	"github.com/dotcloud/docker/pkg/systemd"
15
+	"github.com/dotcloud/docker/pkg/user"
16
+	"github.com/dotcloud/docker/pkg/version"
15 17
 	"github.com/dotcloud/docker/utils"
16 18
 	"github.com/gorilla/mux"
17 19
 	"io"
... ...
@@ -21,7 +23,6 @@ import (
21 21
 	"net/http"
22 22
 	"net/http/pprof"
23 23
 	"os"
24
-	"regexp"
25 24
 	"strconv"
26 25
 	"strings"
27 26
 	"syscall"
... ...
@@ -32,7 +33,7 @@ var (
32 32
 	activationLock chan struct{}
33 33
 )
34 34
 
35
-type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error
35
+type HttpApiFunc func(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error
36 36
 
37 37
 func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
38 38
 	conn, _, err := w.(http.Hijacker).Hijack()
... ...
@@ -113,7 +114,7 @@ func getBoolParam(value string) (bool, error) {
113 113
 	return ret, nil
114 114
 }
115 115
 
116
-func postAuth(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
116
+func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
117 117
 	var (
118 118
 		authConfig, err = ioutil.ReadAll(r.Body)
119 119
 		job             = eng.Job("auth")
... ...
@@ -136,13 +137,13 @@ func postAuth(eng *engine.Engine, version float64, w http.ResponseWriter, r *htt
136 136
 	return nil
137 137
 }
138 138
 
139
-func getVersion(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
139
+func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
140 140
 	w.Header().Set("Content-Type", "application/json")
141 141
 	eng.ServeHTTP(w, r)
142 142
 	return nil
143 143
 }
144 144
 
145
-func postContainersKill(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
145
+func postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
146 146
 	if vars == nil {
147 147
 		return fmt.Errorf("Missing parameter")
148 148
 	}
... ...
@@ -160,7 +161,7 @@ func postContainersKill(eng *engine.Engine, version float64, w http.ResponseWrit
160 160
 	return nil
161 161
 }
162 162
 
163
-func getContainersExport(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
163
+func getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
164 164
 	if vars == nil {
165 165
 		return fmt.Errorf("Missing parameter")
166 166
 	}
... ...
@@ -172,7 +173,7 @@ func getContainersExport(eng *engine.Engine, version float64, w http.ResponseWri
172 172
 	return nil
173 173
 }
174 174
 
175
-func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
175
+func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
176 176
 	if err := parseForm(r); err != nil {
177 177
 		return err
178 178
 	}
... ...
@@ -186,7 +187,7 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r
186 186
 	job.Setenv("filter", r.Form.Get("filter"))
187 187
 	job.Setenv("all", r.Form.Get("all"))
188 188
 
189
-	if version >= 1.7 {
189
+	if version.GreaterThanOrEqualTo("1.7") {
190 190
 		streamJSON(job, w, false)
191 191
 	} else if outs, err = job.Stdout.AddListTable(); err != nil {
192 192
 		return err
... ...
@@ -196,7 +197,7 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r
196 196
 		return err
197 197
 	}
198 198
 
199
-	if version < 1.7 && outs != nil { // Convert to legacy format
199
+	if version.LessThan("1.7") && outs != nil { // Convert to legacy format
200 200
 		outsLegacy := engine.NewTable("Created", 0)
201 201
 		for _, out := range outs.Data {
202 202
 			for _, repoTag := range out.GetList("RepoTags") {
... ...
@@ -219,8 +220,8 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r
219 219
 	return nil
220 220
 }
221 221
 
222
-func getImagesViz(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
223
-	if version > 1.6 {
222
+func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
223
+	if version.GreaterThan("1.6") {
224 224
 		w.WriteHeader(http.StatusNotFound)
225 225
 		return fmt.Errorf("This is now implemented in the client.")
226 226
 	}
... ...
@@ -228,13 +229,13 @@ func getImagesViz(eng *engine.Engine, version float64, w http.ResponseWriter, r
228 228
 	return nil
229 229
 }
230 230
 
231
-func getInfo(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
231
+func getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
232 232
 	w.Header().Set("Content-Type", "application/json")
233 233
 	eng.ServeHTTP(w, r)
234 234
 	return nil
235 235
 }
236 236
 
237
-func getEvents(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
237
+func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
238 238
 	if err := parseForm(r); err != nil {
239 239
 		return err
240 240
 	}
... ...
@@ -245,7 +246,7 @@ func getEvents(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht
245 245
 	return job.Run()
246 246
 }
247 247
 
248
-func getImagesHistory(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
248
+func getImagesHistory(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
249 249
 	if vars == nil {
250 250
 		return fmt.Errorf("Missing parameter")
251 251
 	}
... ...
@@ -259,7 +260,7 @@ func getImagesHistory(eng *engine.Engine, version float64, w http.ResponseWriter
259 259
 	return nil
260 260
 }
261 261
 
262
-func getContainersChanges(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
262
+func getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
263 263
 	if vars == nil {
264 264
 		return fmt.Errorf("Missing parameter")
265 265
 	}
... ...
@@ -269,8 +270,8 @@ func getContainersChanges(eng *engine.Engine, version float64, w http.ResponseWr
269 269
 	return job.Run()
270 270
 }
271 271
 
272
-func getContainersTop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
273
-	if version < 1.4 {
272
+func getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
273
+	if version.LessThan("1.4") {
274 274
 		return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.")
275 275
 	}
276 276
 	if vars == nil {
... ...
@@ -285,7 +286,7 @@ func getContainersTop(eng *engine.Engine, version float64, w http.ResponseWriter
285 285
 	return job.Run()
286 286
 }
287 287
 
288
-func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
288
+func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
289 289
 	if err := parseForm(r); err != nil {
290 290
 		return err
291 291
 	}
... ...
@@ -301,7 +302,7 @@ func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWrite
301 301
 	job.Setenv("before", r.Form.Get("before"))
302 302
 	job.Setenv("limit", r.Form.Get("limit"))
303 303
 
304
-	if version >= 1.5 {
304
+	if version.GreaterThanOrEqualTo("1.5") {
305 305
 		streamJSON(job, w, false)
306 306
 	} else if outs, err = job.Stdout.AddTable(); err != nil {
307 307
 		return err
... ...
@@ -309,7 +310,7 @@ func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWrite
309 309
 	if err = job.Run(); err != nil {
310 310
 		return err
311 311
 	}
312
-	if version < 1.5 { // Convert to legacy format
312
+	if version.LessThan("1.5") { // Convert to legacy format
313 313
 		for _, out := range outs.Data {
314 314
 			ports := engine.NewTable("", 0)
315 315
 			ports.ReadListFrom([]byte(out.Get("Ports")))
... ...
@@ -323,7 +324,7 @@ func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWrite
323 323
 	return nil
324 324
 }
325 325
 
326
-func postImagesTag(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
326
+func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
327 327
 	if err := parseForm(r); err != nil {
328 328
 		return err
329 329
 	}
... ...
@@ -340,7 +341,7 @@ func postImagesTag(eng *engine.Engine, version float64, w http.ResponseWriter, r
340 340
 	return nil
341 341
 }
342 342
 
343
-func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
343
+func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
344 344
 	if err := parseForm(r); err != nil {
345 345
 		return err
346 346
 	}
... ...
@@ -369,7 +370,7 @@ func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *h
369 369
 }
370 370
 
371 371
 // Creates an image from Pull or from Import
372
-func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
372
+func postImagesCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
373 373
 	if err := parseForm(r); err != nil {
374 374
 		return err
375 375
 	}
... ...
@@ -389,9 +390,6 @@ func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter
389 389
 			authConfig = &auth.AuthConfig{}
390 390
 		}
391 391
 	}
392
-	if version > 1.0 {
393
-		w.Header().Set("Content-Type", "application/json")
394
-	}
395 392
 	if image != "" { //pull
396 393
 		metaHeaders := map[string][]string{}
397 394
 		for k, v := range r.Header {
... ...
@@ -400,7 +398,7 @@ func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter
400 400
 			}
401 401
 		}
402 402
 		job = eng.Job("pull", r.Form.Get("fromImage"), tag)
403
-		job.SetenvBool("parallel", version > 1.3)
403
+		job.SetenvBool("parallel", version.GreaterThan("1.3"))
404 404
 		job.SetenvJson("metaHeaders", metaHeaders)
405 405
 		job.SetenvJson("authConfig", authConfig)
406 406
 	} else { //import
... ...
@@ -408,7 +406,7 @@ func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter
408 408
 		job.Stdin.Add(r.Body)
409 409
 	}
410 410
 
411
-	if version > 1.0 {
411
+	if version.GreaterThan("1.0") {
412 412
 		job.SetenvBool("json", true)
413 413
 		streamJSON(job, w, true)
414 414
 	} else {
... ...
@@ -418,14 +416,14 @@ func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter
418 418
 		if !job.Stdout.Used() {
419 419
 			return err
420 420
 		}
421
-		sf := utils.NewStreamFormatter(version > 1.0)
421
+		sf := utils.NewStreamFormatter(version.GreaterThan("1.0"))
422 422
 		w.Write(sf.FormatError(err))
423 423
 	}
424 424
 
425 425
 	return nil
426 426
 }
427 427
 
428
-func getImagesSearch(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
428
+func getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
429 429
 	if err := parseForm(r); err != nil {
430 430
 		return err
431 431
 	}
... ...
@@ -457,19 +455,15 @@ func getImagesSearch(eng *engine.Engine, version float64, w http.ResponseWriter,
457 457
 	return job.Run()
458 458
 }
459 459
 
460
-func postImagesInsert(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
460
+func postImagesInsert(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
461 461
 	if err := parseForm(r); err != nil {
462 462
 		return err
463 463
 	}
464 464
 	if vars == nil {
465 465
 		return fmt.Errorf("Missing parameter")
466 466
 	}
467
-	if version > 1.0 {
468
-		w.Header().Set("Content-Type", "application/json")
469
-	}
470
-
471 467
 	job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path"))
472
-	if version > 1.0 {
468
+	if version.GreaterThan("1.0") {
473 469
 		job.SetenvBool("json", true)
474 470
 		streamJSON(job, w, false)
475 471
 	} else {
... ...
@@ -479,14 +473,14 @@ func postImagesInsert(eng *engine.Engine, version float64, w http.ResponseWriter
479 479
 		if !job.Stdout.Used() {
480 480
 			return err
481 481
 		}
482
-		sf := utils.NewStreamFormatter(version > 1.0)
482
+		sf := utils.NewStreamFormatter(version.GreaterThan("1.0"))
483 483
 		w.Write(sf.FormatError(err))
484 484
 	}
485 485
 
486 486
 	return nil
487 487
 }
488 488
 
489
-func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
489
+func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
490 490
 	if vars == nil {
491 491
 		return fmt.Errorf("Missing parameter")
492 492
 	}
... ...
@@ -517,13 +511,10 @@ func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter,
517 517
 		}
518 518
 	}
519 519
 
520
-	if version > 1.0 {
521
-		w.Header().Set("Content-Type", "application/json")
522
-	}
523 520
 	job := eng.Job("push", vars["name"])
524 521
 	job.SetenvJson("metaHeaders", metaHeaders)
525 522
 	job.SetenvJson("authConfig", authConfig)
526
-	if version > 1.0 {
523
+	if version.GreaterThan("1.0") {
527 524
 		job.SetenvBool("json", true)
528 525
 		streamJSON(job, w, true)
529 526
 	} else {
... ...
@@ -534,17 +525,17 @@ func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter,
534 534
 		if !job.Stdout.Used() {
535 535
 			return err
536 536
 		}
537
-		sf := utils.NewStreamFormatter(version > 1.0)
537
+		sf := utils.NewStreamFormatter(version.GreaterThan("1.0"))
538 538
 		w.Write(sf.FormatError(err))
539 539
 	}
540 540
 	return nil
541 541
 }
542 542
 
543
-func getImagesGet(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
543
+func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
544 544
 	if vars == nil {
545 545
 		return fmt.Errorf("Missing parameter")
546 546
 	}
547
-	if version > 1.0 {
547
+	if version.GreaterThan("1.0") {
548 548
 		w.Header().Set("Content-Type", "application/x-tar")
549 549
 	}
550 550
 	job := eng.Job("image_export", vars["name"])
... ...
@@ -552,13 +543,13 @@ func getImagesGet(eng *engine.Engine, version float64, w http.ResponseWriter, r
552 552
 	return job.Run()
553 553
 }
554 554
 
555
-func postImagesLoad(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
555
+func postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
556 556
 	job := eng.Job("load")
557 557
 	job.Stdin.Add(r.Body)
558 558
 	return job.Run()
559 559
 }
560 560
 
561
-func postContainersCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
561
+func postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
562 562
 	if err := parseForm(r); err != nil {
563 563
 		return nil
564 564
 	}
... ...
@@ -589,7 +580,7 @@ func postContainersCreate(eng *engine.Engine, version float64, w http.ResponseWr
589 589
 	return writeJSON(w, http.StatusCreated, out)
590 590
 }
591 591
 
592
-func postContainersRestart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
592
+func postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
593 593
 	if err := parseForm(r); err != nil {
594 594
 		return err
595 595
 	}
... ...
@@ -605,7 +596,7 @@ func postContainersRestart(eng *engine.Engine, version float64, w http.ResponseW
605 605
 	return nil
606 606
 }
607 607
 
608
-func deleteContainers(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
608
+func deleteContainers(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
609 609
 	if err := parseForm(r); err != nil {
610 610
 		return err
611 611
 	}
... ...
@@ -622,7 +613,7 @@ func deleteContainers(eng *engine.Engine, version float64, w http.ResponseWriter
622 622
 	return nil
623 623
 }
624 624
 
625
-func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
625
+func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
626 626
 	if err := parseForm(r); err != nil {
627 627
 		return err
628 628
 	}
... ...
@@ -631,12 +622,12 @@ func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r
631 631
 	}
632 632
 	var job = eng.Job("image_delete", vars["name"])
633 633
 	streamJSON(job, w, false)
634
-	job.SetenvBool("autoPrune", version > 1.1)
634
+	job.Setenv("force", r.Form.Get("force"))
635 635
 
636 636
 	return job.Run()
637 637
 }
638 638
 
639
-func postContainersStart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
639
+func postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
640 640
 	if vars == nil {
641 641
 		return fmt.Errorf("Missing parameter")
642 642
 	}
... ...
@@ -657,7 +648,7 @@ func postContainersStart(eng *engine.Engine, version float64, w http.ResponseWri
657 657
 	return nil
658 658
 }
659 659
 
660
-func postContainersStop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
660
+func postContainersStop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
661 661
 	if err := parseForm(r); err != nil {
662 662
 		return err
663 663
 	}
... ...
@@ -673,7 +664,7 @@ func postContainersStop(eng *engine.Engine, version float64, w http.ResponseWrit
673 673
 	return nil
674 674
 }
675 675
 
676
-func postContainersWait(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
676
+func postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
677 677
 	if vars == nil {
678 678
 		return fmt.Errorf("Missing parameter")
679 679
 	}
... ...
@@ -695,7 +686,7 @@ func postContainersWait(eng *engine.Engine, version float64, w http.ResponseWrit
695 695
 	return writeJSON(w, http.StatusOK, env)
696 696
 }
697 697
 
698
-func postContainersResize(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
698
+func postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
699 699
 	if err := parseForm(r); err != nil {
700 700
 		return err
701 701
 	}
... ...
@@ -708,7 +699,7 @@ func postContainersResize(eng *engine.Engine, version float64, w http.ResponseWr
708 708
 	return nil
709 709
 }
710 710
 
711
-func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
711
+func postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
712 712
 	if err := parseForm(r); err != nil {
713 713
 		return err
714 714
 	}
... ...
@@ -750,7 +741,7 @@ func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWr
750 750
 
751 751
 	fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
752 752
 
753
-	if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version >= 1.6 {
753
+	if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") {
754 754
 		errStream = utils.NewStdWriter(outStream, utils.Stderr)
755 755
 		outStream = utils.NewStdWriter(outStream, utils.Stdout)
756 756
 	} else {
... ...
@@ -773,7 +764,7 @@ func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWr
773 773
 	return nil
774 774
 }
775 775
 
776
-func wsContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
776
+func wsContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
777 777
 	if err := parseForm(r); err != nil {
778 778
 		return err
779 779
 	}
... ...
@@ -805,7 +796,7 @@ func wsContainersAttach(eng *engine.Engine, version float64, w http.ResponseWrit
805 805
 	return nil
806 806
 }
807 807
 
808
-func getContainersByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
808
+func getContainersByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
809 809
 	if vars == nil {
810 810
 		return fmt.Errorf("Missing parameter")
811 811
 	}
... ...
@@ -815,7 +806,7 @@ func getContainersByName(eng *engine.Engine, version float64, w http.ResponseWri
815 815
 	return job.Run()
816 816
 }
817 817
 
818
-func getImagesByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
818
+func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
819 819
 	if vars == nil {
820 820
 		return fmt.Errorf("Missing parameter")
821 821
 	}
... ...
@@ -825,8 +816,8 @@ func getImagesByName(eng *engine.Engine, version float64, w http.ResponseWriter,
825 825
 	return job.Run()
826 826
 }
827 827
 
828
-func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
829
-	if version < 1.3 {
828
+func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
829
+	if version.LessThan("1.3") {
830 830
 		return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
831 831
 	}
832 832
 	var (
... ...
@@ -841,7 +832,7 @@ func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht
841 841
 	// Both headers will be parsed and sent along to the daemon, but if a non-empty
842 842
 	// ConfigFile is present, any value provided as an AuthConfig directly will
843 843
 	// be overridden. See BuildFile::CmdFrom for details.
844
-	if version < 1.9 && authEncoded != "" {
844
+	if version.LessThan("1.9") && authEncoded != "" {
845 845
 		authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
846 846
 		if err := json.NewDecoder(authJson).Decode(authConfig); err != nil {
847 847
 			// for a pull it is not an error if no auth was given
... ...
@@ -859,7 +850,7 @@ func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht
859 859
 		}
860 860
 	}
861 861
 
862
-	if version >= 1.8 {
862
+	if version.GreaterThanOrEqualTo("1.8") {
863 863
 		job.SetenvBool("json", true)
864 864
 		streamJSON(job, w, true)
865 865
 	} else {
... ...
@@ -878,13 +869,13 @@ func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *ht
878 878
 		if !job.Stdout.Used() {
879 879
 			return err
880 880
 		}
881
-		sf := utils.NewStreamFormatter(version >= 1.8)
881
+		sf := utils.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8"))
882 882
 		w.Write(sf.FormatError(err))
883 883
 	}
884 884
 	return nil
885 885
 }
886 886
 
887
-func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
887
+func postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
888 888
 	if vars == nil {
889 889
 		return fmt.Errorf("Missing parameter")
890 890
 	}
... ...
@@ -907,7 +898,7 @@ func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWrit
907 907
 	}
908 908
 
909 909
 	job := eng.Job("container_copy", vars["name"], copyData.Get("Resource"))
910
-	streamJSON(job, w, false)
910
+	job.Stdout.Add(w)
911 911
 	if err := job.Run(); err != nil {
912 912
 		utils.Errorf("%s", err.Error())
913 913
 		if strings.Contains(err.Error(), "No such container") {
... ...
@@ -917,7 +908,7 @@ func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWrit
917 917
 	return nil
918 918
 }
919 919
 
920
-func optionsHandler(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
920
+func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
921 921
 	w.WriteHeader(http.StatusOK)
922 922
 	return nil
923 923
 }
... ...
@@ -927,7 +918,7 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) {
927 927
 	w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
928 928
 }
929 929
 
930
-func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion string) http.HandlerFunc {
930
+func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion version.Version) http.HandlerFunc {
931 931
 	return func(w http.ResponseWriter, r *http.Request) {
932 932
 		// log the request
933 933
 		utils.Debugf("Calling %s %s", localMethod, localRoute)
... ...
@@ -938,20 +929,20 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local
938 938
 
939 939
 		if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
940 940
 			userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
941
-			if len(userAgent) == 2 && userAgent[1] != dockerVersion {
941
+			if len(userAgent) == 2 && !dockerVersion.Equal(userAgent[1]) {
942 942
 				utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
943 943
 			}
944 944
 		}
945
-		version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64)
946
-		if err != nil {
945
+		version := version.Version(mux.Vars(r)["version"])
946
+		if version == "" {
947 947
 			version = APIVERSION
948 948
 		}
949 949
 		if enableCors {
950 950
 			writeCorsHeaders(w, r)
951 951
 		}
952 952
 
953
-		if version == 0 || version > APIVERSION {
954
-			http.Error(w, fmt.Errorf("client and server don't have same version (client : %g, server: %g)", version, APIVERSION).Error(), http.StatusNotFound)
953
+		if version.GreaterThan(APIVERSION) {
954
+			http.Error(w, fmt.Errorf("client and server don't have same version (client : %s, server: %s)", version, APIVERSION).Error(), http.StatusNotFound)
955 955
 			return
956 956
 		}
957 957
 
... ...
@@ -1049,7 +1040,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
1049 1049
 			localMethod := method
1050 1050
 
1051 1051
 			// build the handler function
1052
-			f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, dockerVersion)
1052
+			f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, version.Version(dockerVersion))
1053 1053
 
1054 1054
 			// add the new route
1055 1055
 			if localRoute == "" {
... ...
@@ -1067,13 +1058,13 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
1067 1067
 // ServeRequest processes a single http request to the docker remote api.
1068 1068
 // FIXME: refactor this to be part of Server and not require re-creating a new
1069 1069
 // router each time. This requires first moving ListenAndServe into Server.
1070
-func ServeRequest(eng *engine.Engine, apiversion float64, w http.ResponseWriter, req *http.Request) error {
1070
+func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.ResponseWriter, req *http.Request) error {
1071 1071
 	router, err := createRouter(eng, false, true, "")
1072 1072
 	if err != nil {
1073 1073
 		return err
1074 1074
 	}
1075 1075
 	// Insert APIVERSION into the request as a convenience
1076
-	req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path)
1076
+	req.URL.Path = fmt.Sprintf("/v%s%s", apiversion, req.URL.Path)
1077 1077
 	router.ServeHTTP(w, req)
1078 1078
 	return nil
1079 1079
 }
... ...
@@ -1142,18 +1133,15 @@ func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors
1142 1142
 			return err
1143 1143
 		}
1144 1144
 
1145
-		groups, err := ioutil.ReadFile("/etc/group")
1145
+		groups, err := user.ParseGroupFilter(func(g *user.Group) bool {
1146
+			return g.Name == "docker"
1147
+		})
1146 1148
 		if err != nil {
1147 1149
 			return err
1148 1150
 		}
1149
-		re := regexp.MustCompile("(^|\n)docker:.*?:([0-9]+)")
1150
-		if gidMatch := re.FindStringSubmatch(string(groups)); gidMatch != nil {
1151
-			gid, err := strconv.Atoi(gidMatch[2])
1152
-			if err != nil {
1153
-				return err
1154
-			}
1155
-			utils.Debugf("docker group found. gid: %d", gid)
1156
-			if err := os.Chown(addr, 0, gid); err != nil {
1151
+		if len(groups) > 0 {
1152
+			utils.Debugf("docker group found. gid: %d", groups[0].Gid)
1153
+			if err := os.Chown(addr, 0, groups[0].Gid); err != nil {
1157 1154
 				return err
1158 1155
 			}
1159 1156
 		}
... ...
@@ -790,6 +790,19 @@ func (container *Container) monitor(callback execdriver.StartCallback) error {
790 790
 		utils.Errorf("Error running container: %s", err)
791 791
 	}
792 792
 
793
+	container.State.SetStopped(exitCode)
794
+
795
+	// FIXME: there is a race condition here which causes this to fail during the unit tests.
796
+	// If another goroutine was waiting for Wait() to return before removing the container's root
797
+	// from the filesystem... At this point it may already have done so.
798
+	// This is because State.setStopped() has already been called, and has caused Wait()
799
+	// to return.
800
+	// FIXME: why are we serializing running state to disk in the first place?
801
+	//log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err)
802
+	if err := container.ToDisk(); err != nil {
803
+		utils.Errorf("Error dumping container state to disk: %s\n", err)
804
+	}
805
+
793 806
 	// Cleanup
794 807
 	container.cleanup()
795 808
 
... ...
@@ -798,23 +811,12 @@ func (container *Container) monitor(callback execdriver.StartCallback) error {
798 798
 		container.stdin, container.stdinPipe = io.Pipe()
799 799
 	}
800 800
 
801
-	container.State.SetStopped(exitCode)
802
-
803 801
 	if container.runtime != nil && container.runtime.srv != nil {
804 802
 		container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image))
805 803
 	}
806 804
 
807 805
 	close(container.waitLock)
808 806
 
809
-	// FIXME: there is a race condition here which causes this to fail during the unit tests.
810
-	// If another goroutine was waiting for Wait() to return before removing the container's root
811
-	// from the filesystem... At this point it may already have done so.
812
-	// This is because State.setStopped() has already been called, and has caused Wait()
813
-	// to return.
814
-	// FIXME: why are we serializing running state to disk in the first place?
815
-	//log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err)
816
-	container.ToDisk()
817
-
818 807
 	return err
819 808
 }
820 809
 
... ...
@@ -76,7 +76,7 @@ rm -rf "$target"/var/cache/ldconfig/*
76 76
 
77 77
 version=
78 78
 if [ -r "$target"/etc/redhat-release ]; then
79
-    version="$(sed 's/^[^0-9\]*\([0-9.]\+\).*$/\1/' /etc/redhat-release)"
79
+    version="$(sed 's/^[^0-9\]*\([0-9.]\+\).*$/\1/' "$target"/etc/redhat-release)"
80 80
 fi
81 81
 
82 82
 if [ -z "$version" ]; then
... ...
@@ -122,7 +122,10 @@ If the test are successful then the tail of the output should look something lik
122 122
 	PASS
123 123
 	ok  	github.com/dotcloud/docker/utils	0.017s
124 124
 
125
+If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'.
126
+You can use this to select certain tests to run, eg.
125 127
 
128
+    TESTFLAGS='-run ^TestBuild$' make test
126 129
 
127 130
 
128 131
 Step 6: Use Docker
... ...
@@ -26,15 +26,31 @@ Docker Remote API
26 26
 2. Versions
27 27
 ===========
28 28
 
29
-The current version of the API is 1.9
29
+The current version of the API is 1.10
30 30
 
31 31
 Calling /images/<name>/insert is the same as calling
32
-/v1.9/images/<name>/insert
32
+/v1.10/images/<name>/insert
33 33
 
34 34
 You can still call an old version of the api using
35 35
 /v1.0/images/<name>/insert
36 36
 
37 37
 
38
+v1.10
39
+*****
40
+
41
+Full Documentation
42
+------------------
43
+
44
+:doc:`docker_remote_api_v1.10`
45
+
46
+What's new
47
+----------
48
+
49
+.. http:delete:: /images/(name)
50
+
51
+   **New!** You can now use the force parameter to force delete of an image, even if it's
52
+   tagged in multiple repositories.
53
+
38 54
 v1.9
39 55
 ****
40 56
 
41 57
new file mode 100644
... ...
@@ -0,0 +1,1282 @@
0
+:title: Remote API v1.10
1
+:description: API Documentation for Docker
2
+:keywords: API, Docker, rcli, REST, documentation
3
+
4
+:orphan:
5
+
6
+=======================
7
+Docker Remote API v1.10
8
+=======================
9
+
10
+.. contents:: Table of Contents
11
+
12
+1. Brief introduction
13
+=====================
14
+
15
+- The Remote API has replaced rcli
16
+- The daemon listens on ``unix:///var/run/docker.sock``, but you can
17
+  :ref:`bind_docker`.
18
+- The API tends to be REST, but for some complex commands, like
19
+  ``attach`` or ``pull``, the HTTP connection is hijacked to transport
20
+  ``stdout, stdin`` and ``stderr``
21
+
22
+2. Endpoints
23
+============
24
+
25
+2.1 Containers
26
+--------------
27
+
28
+List containers
29
+***************
30
+
31
+.. http:get:: /containers/json
32
+
33
+        List containers
34
+
35
+        **Example request**:
36
+
37
+        .. sourcecode:: http
38
+
39
+           GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1
40
+
41
+        **Example response**:
42
+
43
+        .. sourcecode:: http
44
+
45
+           HTTP/1.1 200 OK
46
+           Content-Type: application/json
47
+
48
+           [
49
+                {
50
+                        "Id": "8dfafdbc3a40",
51
+                        "Image": "base:latest",
52
+                        "Command": "echo 1",
53
+                        "Created": 1367854155,
54
+                        "Status": "Exit 0",
55
+                        "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
56
+                        "SizeRw":12288,
57
+                        "SizeRootFs":0
58
+                },
59
+                {
60
+                        "Id": "9cd87474be90",
61
+                        "Image": "base:latest",
62
+                        "Command": "echo 222222",
63
+                        "Created": 1367854155,
64
+                        "Status": "Exit 0",
65
+                        "Ports":[],
66
+                        "SizeRw":12288,
67
+                        "SizeRootFs":0
68
+                },
69
+                {
70
+                        "Id": "3176a2479c92",
71
+                        "Image": "base:latest",
72
+                        "Command": "echo 3333333333333333",
73
+                        "Created": 1367854154,
74
+                        "Status": "Exit 0",
75
+                        "Ports":[],
76
+                        "SizeRw":12288,
77
+                        "SizeRootFs":0
78
+                },
79
+                {
80
+                        "Id": "4cb07b47f9fb",
81
+                        "Image": "base:latest",
82
+                        "Command": "echo 444444444444444444444444444444444",
83
+                        "Created": 1367854152,
84
+                        "Status": "Exit 0",
85
+                        "Ports":[],
86
+                        "SizeRw":12288,
87
+                        "SizeRootFs":0
88
+                }
89
+           ]
90
+
91
+        :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default
92
+        :query limit: Show ``limit`` last created containers, include non-running ones.
93
+        :query since: Show only containers created since Id, include non-running ones.
94
+        :query before: Show only containers created before Id, include non-running ones.
95
+        :query size: 1/True/true or 0/False/false, Show the containers sizes
96
+        :statuscode 200: no error
97
+        :statuscode 400: bad parameter
98
+        :statuscode 500: server error
99
+
100
+
101
+Create a container
102
+******************
103
+
104
+.. http:post:: /containers/create
105
+
106
+        Create a container
107
+
108
+        **Example request**:
109
+
110
+        .. sourcecode:: http
111
+
112
+           POST /containers/create HTTP/1.1
113
+           Content-Type: application/json
114
+
115
+           {
116
+                "Hostname":"",
117
+                "User":"",
118
+                "Memory":0,
119
+                "MemorySwap":0,
120
+                "AttachStdin":false,
121
+                "AttachStdout":true,
122
+                "AttachStderr":true,
123
+                "PortSpecs":null,
124
+                "Tty":false,
125
+                "OpenStdin":false,
126
+                "StdinOnce":false,
127
+                "Env":null,
128
+                "Cmd":[
129
+                        "date"
130
+                ],
131
+                "Dns":null,
132
+                "Image":"base",
133
+                "Volumes":{
134
+                        "/tmp": {}
135
+                },
136
+                "VolumesFrom":"",
137
+                "WorkingDir":"",
138
+                "ExposedPorts":{
139
+                        "22/tcp": {}
140
+                }
141
+           }
142
+
143
+        **Example response**:
144
+
145
+        .. sourcecode:: http
146
+
147
+           HTTP/1.1 201 OK
148
+           Content-Type: application/json
149
+
150
+           {
151
+                "Id":"e90e34656806"
152
+                "Warnings":[]
153
+           }
154
+
155
+        :jsonparam config: the container's configuration
156
+        :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``.
157
+        :statuscode 201: no error
158
+        :statuscode 404: no such container
159
+        :statuscode 406: impossible to attach (container not running)
160
+        :statuscode 500: server error
161
+
162
+
163
+Inspect a container
164
+*******************
165
+
166
+.. http:get:: /containers/(id)/json
167
+
168
+        Return low-level information on the container ``id``
169
+
170
+        **Example request**:
171
+
172
+        .. sourcecode:: http
173
+
174
+           GET /containers/4fa6e0f0c678/json HTTP/1.1
175
+
176
+        **Example response**:
177
+
178
+        .. sourcecode:: http
179
+
180
+           HTTP/1.1 200 OK
181
+           Content-Type: application/json
182
+
183
+           {
184
+                        "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
185
+                        "Created": "2013-05-07T14:51:42.041847+02:00",
186
+                        "Path": "date",
187
+                        "Args": [],
188
+                        "Config": {
189
+                                "Hostname": "4fa6e0f0c678",
190
+                                "User": "",
191
+                                "Memory": 0,
192
+                                "MemorySwap": 0,
193
+                                "AttachStdin": false,
194
+                                "AttachStdout": true,
195
+                                "AttachStderr": true,
196
+                                "PortSpecs": null,
197
+                                "Tty": false,
198
+                                "OpenStdin": false,
199
+                                "StdinOnce": false,
200
+                                "Env": null,
201
+                                "Cmd": [
202
+                                        "date"
203
+                                ],
204
+                                "Dns": null,
205
+                                "Image": "base",
206
+                                "Volumes": {},
207
+                                "VolumesFrom": "",
208
+                                "WorkingDir":""
209
+
210
+                        },
211
+                        "State": {
212
+                                "Running": false,
213
+                                "Pid": 0,
214
+                                "ExitCode": 0,
215
+                                "StartedAt": "2013-05-07T14:51:42.087658+02:01360",
216
+                                "Ghost": false
217
+                        },
218
+                        "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
219
+                        "NetworkSettings": {
220
+                                "IpAddress": "",
221
+                                "IpPrefixLen": 0,
222
+                                "Gateway": "",
223
+                                "Bridge": "",
224
+                                "PortMapping": null
225
+                        },
226
+                        "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker",
227
+                        "ResolvConfPath": "/etc/resolv.conf",
228
+                        "Volumes": {},
229
+                        "HostConfig": {
230
+                            "Binds": null,
231
+                            "ContainerIDFile": "",
232
+                            "LxcConf": [],
233
+                            "Privileged": false,
234
+                            "PortBindings": {
235
+                               "80/tcp": [
236
+                                   {
237
+                                       "HostIp": "0.0.0.0",
238
+                                       "HostPort": "49153"
239
+                                   }
240
+                               ]
241
+                            },
242
+                            "Links": null,
243
+                            "PublishAllPorts": false
244
+                        }
245
+           }
246
+
247
+        :statuscode 200: no error
248
+        :statuscode 404: no such container
249
+        :statuscode 500: server error
250
+
251
+
252
+List processes running inside a container
253
+*****************************************
254
+
255
+.. http:get:: /containers/(id)/top
256
+
257
+        List processes running inside the container ``id``
258
+
259
+        **Example request**:
260
+
261
+        .. sourcecode:: http
262
+
263
+           GET /containers/4fa6e0f0c678/top HTTP/1.1
264
+
265
+        **Example response**:
266
+
267
+        .. sourcecode:: http
268
+
269
+           HTTP/1.1 200 OK
270
+           Content-Type: application/json
271
+
272
+           {
273
+                "Titles":[
274
+                        "USER",
275
+                        "PID",
276
+                        "%CPU",
277
+                        "%MEM",
278
+                        "VSZ",
279
+                        "RSS",
280
+                        "TTY",
281
+                        "STAT",
282
+                        "START",
283
+                        "TIME",
284
+                        "COMMAND"
285
+                        ],
286
+                "Processes":[
287
+                        ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"],
288
+                        ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"]
289
+                ]
290
+           }
291
+
292
+        :query ps_args: ps arguments to use (eg. aux)
293
+        :statuscode 200: no error
294
+        :statuscode 404: no such container
295
+        :statuscode 500: server error
296
+
297
+
298
+Inspect changes on a container's filesystem
299
+*******************************************
300
+
301
+.. http:get:: /containers/(id)/changes
302
+
303
+        Inspect changes on container ``id`` 's filesystem
304
+
305
+        **Example request**:
306
+
307
+        .. sourcecode:: http
308
+
309
+           GET /containers/4fa6e0f0c678/changes HTTP/1.1
310
+
311
+
312
+        **Example response**:
313
+
314
+        .. sourcecode:: http
315
+
316
+           HTTP/1.1 200 OK
317
+           Content-Type: application/json
318
+
319
+           [
320
+                {
321
+                        "Path":"/dev",
322
+                        "Kind":0
323
+                },
324
+                {
325
+                        "Path":"/dev/kmsg",
326
+                        "Kind":1
327
+                },
328
+                {
329
+                        "Path":"/test",
330
+                        "Kind":1
331
+                }
332
+           ]
333
+
334
+        :statuscode 200: no error
335
+        :statuscode 404: no such container
336
+        :statuscode 500: server error
337
+
338
+
339
+Export a container
340
+******************
341
+
342
+.. http:get:: /containers/(id)/export
343
+
344
+        Export the contents of container ``id``
345
+
346
+        **Example request**:
347
+
348
+        .. sourcecode:: http
349
+
350
+           GET /containers/4fa6e0f0c678/export HTTP/1.1
351
+
352
+
353
+        **Example response**:
354
+
355
+        .. sourcecode:: http
356
+
357
+           HTTP/1.1 200 OK
358
+           Content-Type: application/octet-stream
359
+
360
+           {{ STREAM }}
361
+
362
+        :statuscode 200: no error
363
+        :statuscode 404: no such container
364
+        :statuscode 500: server error
365
+
366
+
367
+Start a container
368
+*****************
369
+
370
+.. http:post:: /containers/(id)/start
371
+
372
+        Start the container ``id``
373
+
374
+        **Example request**:
375
+
376
+        .. sourcecode:: http
377
+
378
+           POST /containers/(id)/start HTTP/1.1
379
+           Content-Type: application/json
380
+
381
+           {
382
+                "Binds":["/tmp:/tmp"],
383
+                "LxcConf":{"lxc.utsname":"docker"},
384
+                "PortBindings":{ "22/tcp": [{ "HostPort": "11022" }] },
385
+                "PublishAllPorts":false,
386
+                "Privileged":false
387
+           }
388
+
389
+        **Example response**:
390
+
391
+        .. sourcecode:: http
392
+
393
+           HTTP/1.1 204 No Content
394
+           Content-Type: text/plain
395
+
396
+        :jsonparam hostConfig: the container's host configuration (optional)
397
+        :statuscode 204: no error
398
+        :statuscode 404: no such container
399
+        :statuscode 500: server error
400
+
401
+
402
+Stop a container
403
+****************
404
+
405
+.. http:post:: /containers/(id)/stop
406
+
407
+        Stop the container ``id``
408
+
409
+        **Example request**:
410
+
411
+        .. sourcecode:: http
412
+
413
+           POST /containers/e90e34656806/stop?t=5 HTTP/1.1
414
+
415
+        **Example response**:
416
+
417
+        .. sourcecode:: http
418
+
419
+           HTTP/1.1 204 OK
420
+
421
+        :query t: number of seconds to wait before killing the container
422
+        :statuscode 204: no error
423
+        :statuscode 404: no such container
424
+        :statuscode 500: server error
425
+
426
+
427
+Restart a container
428
+*******************
429
+
430
+.. http:post:: /containers/(id)/restart
431
+
432
+        Restart the container ``id``
433
+
434
+        **Example request**:
435
+
436
+        .. sourcecode:: http
437
+
438
+           POST /containers/e90e34656806/restart?t=5 HTTP/1.1
439
+
440
+        **Example response**:
441
+
442
+        .. sourcecode:: http
443
+
444
+           HTTP/1.1 204 OK
445
+
446
+        :query t: number of seconds to wait before killing the container
447
+        :statuscode 204: no error
448
+        :statuscode 404: no such container
449
+        :statuscode 500: server error
450
+
451
+
452
+Kill a container
453
+****************
454
+
455
+.. http:post:: /containers/(id)/kill
456
+
457
+        Kill the container ``id``
458
+
459
+        **Example request**:
460
+
461
+        .. sourcecode:: http
462
+
463
+           POST /containers/e90e34656806/kill HTTP/1.1
464
+
465
+        **Example response**:
466
+
467
+        .. sourcecode:: http
468
+
469
+           HTTP/1.1 204 OK
470
+
471
+        :statuscode 204: no error
472
+        :statuscode 404: no such container
473
+        :statuscode 500: server error
474
+
475
+
476
+Attach to a container
477
+*********************
478
+
479
+.. http:post:: /containers/(id)/attach
480
+
481
+        Attach to the container ``id``
482
+
483
+        **Example request**:
484
+
485
+        .. sourcecode:: http
486
+
487
+           POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1
488
+
489
+        **Example response**:
490
+
491
+        .. sourcecode:: http
492
+
493
+           HTTP/1.1 200 OK
494
+           Content-Type: application/vnd.docker.raw-stream
495
+
496
+           {{ STREAM }}
497
+
498
+        :query logs: 1/True/true or 0/False/false, return logs. Default false
499
+        :query stream: 1/True/true or 0/False/false, return stream. Default false
500
+        :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false
501
+        :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false
502
+        :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false
503
+        :statuscode 200: no error
504
+        :statuscode 400: bad parameter
505
+        :statuscode 404: no such container
506
+        :statuscode 500: server error
507
+
508
+        **Stream details**:
509
+
510
+        When using the TTY setting is enabled in
511
+        :http:post:`/containers/create`, the stream is the raw data
512
+        from the process PTY and client's stdin.  When the TTY is
513
+        disabled, then the stream is multiplexed to separate stdout
514
+        and stderr.
515
+
516
+        The format is a **Header** and a **Payload** (frame).
517
+
518
+        **HEADER**
519
+
520
+        The header will contain the information on which stream write
521
+        the stream (stdout or stderr). It also contain the size of
522
+        the associated frame encoded on the last 4 bytes (uint32).
523
+
524
+        It is encoded on the first 8 bytes like this::
525
+
526
+            header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
527
+
528
+        ``STREAM_TYPE`` can be:
529
+
530
+        - 0: stdin (will be writen on stdout)
531
+        - 1: stdout
532
+        - 2: stderr
533
+
534
+        ``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian.
535
+
536
+        **PAYLOAD**
537
+
538
+        The payload is the raw stream.
539
+
540
+        **IMPLEMENTATION**
541
+
542
+        The simplest way to implement the Attach protocol is the following:
543
+
544
+        1) Read 8 bytes
545
+        2) chose stdout or stderr depending on the first byte
546
+        3) Extract the frame size from the last 4 byets
547
+        4) Read the extracted size and output it on the correct output
548
+        5) Goto 1)
549
+
550
+
551
+
552
+Wait a container
553
+****************
554
+
555
+.. http:post:: /containers/(id)/wait
556
+
557
+        Block until container ``id`` stops, then returns the exit code
558
+
559
+        **Example request**:
560
+
561
+        .. sourcecode:: http
562
+
563
+           POST /containers/16253994b7c4/wait HTTP/1.1
564
+
565
+        **Example response**:
566
+
567
+        .. sourcecode:: http
568
+
569
+           HTTP/1.1 200 OK
570
+           Content-Type: application/json
571
+
572
+           {"StatusCode":0}
573
+
574
+        :statuscode 200: no error
575
+        :statuscode 404: no such container
576
+        :statuscode 500: server error
577
+
578
+
579
+Remove a container
580
+*******************
581
+
582
+.. http:delete:: /containers/(id)
583
+
584
+        Remove the container ``id`` from the filesystem
585
+
586
+        **Example request**:
587
+
588
+        .. sourcecode:: http
589
+
590
+           DELETE /containers/16253994b7c4?v=1 HTTP/1.1
591
+
592
+        **Example response**:
593
+
594
+        .. sourcecode:: http
595
+
596
+           HTTP/1.1 204 OK
597
+
598
+        :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false
599
+        :statuscode 204: no error
600
+        :statuscode 400: bad parameter
601
+        :statuscode 404: no such container
602
+        :statuscode 500: server error
603
+
604
+
605
+Copy files or folders from a container
606
+**************************************
607
+
608
+.. http:post:: /containers/(id)/copy
609
+
610
+        Copy files or folders of container ``id``
611
+
612
+        **Example request**:
613
+
614
+        .. sourcecode:: http
615
+
616
+           POST /containers/4fa6e0f0c678/copy HTTP/1.1
617
+           Content-Type: application/json
618
+
619
+           {
620
+                "Resource":"test.txt"
621
+           }
622
+
623
+        **Example response**:
624
+
625
+        .. sourcecode:: http
626
+
627
+           HTTP/1.1 200 OK
628
+           Content-Type: application/octet-stream
629
+
630
+           {{ STREAM }}
631
+
632
+        :statuscode 200: no error
633
+        :statuscode 404: no such container
634
+        :statuscode 500: server error
635
+
636
+
637
+2.2 Images
638
+----------
639
+
640
+List Images
641
+***********
642
+
643
+.. http:get:: /images/json
644
+
645
+        **Example request**:
646
+
647
+        .. sourcecode:: http
648
+
649
+           GET /images/json?all=0 HTTP/1.1
650
+
651
+        **Example response**:
652
+
653
+        .. sourcecode:: http
654
+
655
+           HTTP/1.1 200 OK
656
+           Content-Type: application/json
657
+
658
+           [
659
+             {
660
+                "RepoTags": [
661
+                  "ubuntu:12.04",
662
+                  "ubuntu:precise",
663
+                  "ubuntu:latest"
664
+                ],
665
+                "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
666
+                "Created": 1365714795,
667
+                "Size": 131506275,
668
+                "VirtualSize": 131506275
669
+             },
670
+             {
671
+                "RepoTags": [
672
+                  "ubuntu:12.10",
673
+                  "ubuntu:quantal"
674
+                ],
675
+                "ParentId": "27cf784147099545",
676
+                "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
677
+                "Created": 1364102658,
678
+                "Size": 24653,
679
+                "VirtualSize": 180116135
680
+             }
681
+           ]
682
+
683
+
684
+Create an image
685
+***************
686
+
687
+.. http:post:: /images/create
688
+
689
+        Create an image, either by pull it from the registry or by importing it
690
+
691
+        **Example request**:
692
+
693
+        .. sourcecode:: http
694
+
695
+           POST /images/create?fromImage=base HTTP/1.1
696
+
697
+        **Example response**:
698
+
699
+        .. sourcecode:: http
700
+
701
+           HTTP/1.1 200 OK
702
+           Content-Type: application/json
703
+
704
+           {"status":"Pulling..."}
705
+           {"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}}
706
+           {"error":"Invalid..."}
707
+           ...
708
+
709
+        When using this endpoint to pull an image from the registry,
710
+        the ``X-Registry-Auth`` header can be used to include a
711
+        base64-encoded AuthConfig object.
712
+
713
+        :query fromImage: name of the image to pull
714
+        :query fromSrc: source to import, - means stdin
715
+        :query repo: repository
716
+        :query tag: tag
717
+        :query registry: the registry to pull from
718
+        :reqheader X-Registry-Auth: base64-encoded AuthConfig object
719
+        :statuscode 200: no error
720
+        :statuscode 500: server error
721
+
722
+
723
+
724
+Insert a file in an image
725
+*************************
726
+
727
+.. http:post:: /images/(name)/insert
728
+
729
+        Insert a file from ``url`` in the image ``name`` at ``path``
730
+
731
+        **Example request**:
732
+
733
+        .. sourcecode:: http
734
+
735
+           POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
736
+
737
+        **Example response**:
738
+
739
+        .. sourcecode:: http
740
+
741
+           HTTP/1.1 200 OK
742
+           Content-Type: application/json
743
+
744
+           {"status":"Inserting..."}
745
+           {"status":"Inserting", "progress":"1/? (n/a)", "progressDetail":{"current":1}}
746
+           {"error":"Invalid..."}
747
+           ...
748
+
749
+        :statuscode 200: no error
750
+        :statuscode 500: server error
751
+
752
+
753
+Inspect an image
754
+****************
755
+
756
+.. http:get:: /images/(name)/json
757
+
758
+        Return low-level information on the image ``name``
759
+
760
+        **Example request**:
761
+
762
+        .. sourcecode:: http
763
+
764
+           GET /images/base/json HTTP/1.1
765
+
766
+        **Example response**:
767
+
768
+        .. sourcecode:: http
769
+
770
+           HTTP/1.1 200 OK
771
+           Content-Type: application/json
772
+
773
+           {
774
+                "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
775
+                "parent":"27cf784147099545",
776
+                "created":"2013-03-23T22:24:18.818426-07:00",
777
+                "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0",
778
+                "container_config":
779
+                        {
780
+                                "Hostname":"",
781
+                                "User":"",
782
+                                "Memory":0,
783
+                                "MemorySwap":0,
784
+                                "AttachStdin":false,
785
+                                "AttachStdout":false,
786
+                                "AttachStderr":false,
787
+                                "PortSpecs":null,
788
+                                "Tty":true,
789
+                                "OpenStdin":true,
790
+                                "StdinOnce":false,
791
+                                "Env":null,
792
+                                "Cmd": ["/bin/bash"]
793
+                                ,"Dns":null,
794
+                                "Image":"base",
795
+                                "Volumes":null,
796
+                                "VolumesFrom":"",
797
+                                "WorkingDir":""
798
+                        },
799
+                "Size": 6824592
800
+           }
801
+
802
+        :statuscode 200: no error
803
+        :statuscode 404: no such image
804
+        :statuscode 500: server error
805
+
806
+
807
+Get the history of an image
808
+***************************
809
+
810
+.. http:get:: /images/(name)/history
811
+
812
+        Return the history of the image ``name``
813
+
814
+        **Example request**:
815
+
816
+        .. sourcecode:: http
817
+
818
+           GET /images/base/history HTTP/1.1
819
+
820
+        **Example response**:
821
+
822
+        .. sourcecode:: http
823
+
824
+           HTTP/1.1 200 OK
825
+           Content-Type: application/json
826
+
827
+           [
828
+                {
829
+                        "Id":"b750fe79269d",
830
+                        "Created":1364102658,
831
+                        "CreatedBy":"/bin/bash"
832
+                },
833
+                {
834
+                        "Id":"27cf78414709",
835
+                        "Created":1364068391,
836
+                        "CreatedBy":""
837
+                }
838
+           ]
839
+
840
+        :statuscode 200: no error
841
+        :statuscode 404: no such image
842
+        :statuscode 500: server error
843
+
844
+
845
+Push an image on the registry
846
+*****************************
847
+
848
+.. http:post:: /images/(name)/push
849
+
850
+   Push the image ``name`` on the registry
851
+
852
+   **Example request**:
853
+
854
+   .. sourcecode:: http
855
+
856
+      POST /images/test/push HTTP/1.1
857
+
858
+   **Example response**:
859
+
860
+   .. sourcecode:: http
861
+
862
+    HTTP/1.1 200 OK
863
+    Content-Type: application/json
864
+
865
+    {"status":"Pushing..."}
866
+    {"status":"Pushing", "progress":"1/? (n/a)", "progressDetail":{"current":1}}}
867
+    {"error":"Invalid..."}
868
+    ...
869
+
870
+   :query registry: the registry you wan to push, optional
871
+   :reqheader X-Registry-Auth: include a base64-encoded AuthConfig object.
872
+   :statuscode 200: no error
873
+   :statuscode 404: no such image
874
+   :statuscode 500: server error
875
+
876
+
877
+Tag an image into a repository
878
+******************************
879
+
880
+.. http:post:: /images/(name)/tag
881
+
882
+        Tag the image ``name`` into a repository
883
+
884
+        **Example request**:
885
+
886
+        .. sourcecode:: http
887
+
888
+           POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1
889
+
890
+        **Example response**:
891
+
892
+        .. sourcecode:: http
893
+
894
+           HTTP/1.1 201 OK
895
+
896
+        :query repo: The repository to tag in
897
+        :query force: 1/True/true or 0/False/false, default false
898
+        :statuscode 201: no error
899
+        :statuscode 400: bad parameter
900
+        :statuscode 404: no such image
901
+        :statuscode 409: conflict
902
+        :statuscode 500: server error
903
+
904
+
905
+Remove an image
906
+***************
907
+
908
+.. http:delete:: /images/(name)
909
+
910
+        Remove the image ``name`` from the filesystem
911
+
912
+        **Example request**:
913
+
914
+        .. sourcecode:: http
915
+
916
+           DELETE /images/test HTTP/1.1
917
+
918
+        **Example response**:
919
+
920
+        .. sourcecode:: http
921
+
922
+           HTTP/1.1 200 OK
923
+           Content-type: application/json
924
+
925
+           [
926
+            {"Untagged":"3e2f21a89f"},
927
+            {"Deleted":"3e2f21a89f"},
928
+            {"Deleted":"53b4f83ac9"}
929
+           ]
930
+
931
+        :query force: 1/True/true or 0/False/false, default false
932
+        :statuscode 200: no error
933
+        :statuscode 404: no such image
934
+        :statuscode 409: conflict
935
+        :statuscode 500: server error
936
+
937
+
938
+Search images
939
+*************
940
+
941
+.. http:get:: /images/search
942
+
943
+        Search for an image in the docker index.
944
+
945
+        .. note::
946
+
947
+           The response keys have changed from API v1.6 to reflect the JSON
948
+           sent by the registry server to the docker daemon's request.
949
+
950
+        **Example request**:
951
+
952
+        .. sourcecode:: http
953
+
954
+           GET /images/search?term=sshd HTTP/1.1
955
+
956
+        **Example response**:
957
+
958
+        .. sourcecode:: http
959
+
960
+           HTTP/1.1 200 OK
961
+           Content-Type: application/json
962
+
963
+           [
964
+                   {
965
+                       "description": "",
966
+                       "is_official": false,
967
+                       "is_trusted": false,
968
+                       "name": "wma55/u1210sshd",
969
+                       "star_count": 0
970
+                   },
971
+                   {
972
+                       "description": "",
973
+                       "is_official": false,
974
+                       "is_trusted": false,
975
+                       "name": "jdswinbank/sshd",
976
+                       "star_count": 0
977
+                   },
978
+                   {
979
+                       "description": "",
980
+                       "is_official": false,
981
+                       "is_trusted": false,
982
+                       "name": "vgauthier/sshd",
983
+                       "star_count": 0
984
+                   }
985
+           ...
986
+           ]
987
+
988
+        :query term: term to search
989
+        :statuscode 200: no error
990
+        :statuscode 500: server error
991
+
992
+
993
+2.3 Misc
994
+--------
995
+
996
+Build an image from Dockerfile via stdin
997
+****************************************
998
+
999
+.. http:post:: /build
1000
+
1001
+   Build an image from Dockerfile via stdin
1002
+
1003
+   **Example request**:
1004
+
1005
+   .. sourcecode:: http
1006
+
1007
+      POST /build HTTP/1.1
1008
+
1009
+      {{ STREAM }}
1010
+
1011
+   **Example response**:
1012
+
1013
+   .. sourcecode:: http
1014
+
1015
+      HTTP/1.1 200 OK
1016
+      Content-Type: application/json
1017
+
1018
+      {"stream":"Step 1..."}
1019
+      {"stream":"..."}
1020
+      {"error":"Error...", "errorDetail":{"code": 123, "message": "Error..."}}
1021
+
1022
+
1023
+   The stream must be a tar archive compressed with one of the
1024
+   following algorithms: identity (no compression), gzip, bzip2,
1025
+   xz.
1026
+
1027
+   The archive must include a file called ``Dockerfile`` at its
1028
+   root. It may include any number of other files, which will be
1029
+   accessible in the build context (See the :ref:`ADD build command
1030
+   <dockerbuilder>`).
1031
+
1032
+   :query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
1033
+   :query q: suppress verbose build output
1034
+   :query nocache: do not use the cache when building the image
1035
+   :reqheader Content-type: should be set to ``"application/tar"``.
1036
+   :reqheader X-Registry-Config: base64-encoded ConfigFile object
1037
+   :statuscode 200: no error
1038
+   :statuscode 500: server error
1039
+
1040
+
1041
+
1042
+Check auth configuration
1043
+************************
1044
+
1045
+.. http:post:: /auth
1046
+
1047
+        Get the default username and email
1048
+
1049
+        **Example request**:
1050
+
1051
+        .. sourcecode:: http
1052
+
1053
+           POST /auth HTTP/1.1
1054
+           Content-Type: application/json
1055
+
1056
+           {
1057
+                "username":"hannibal",
1058
+                "password:"xxxx",
1059
+                "email":"hannibal@a-team.com",
1060
+                "serveraddress":"https://index.docker.io/v1/"
1061
+           }
1062
+
1063
+        **Example response**:
1064
+
1065
+        .. sourcecode:: http
1066
+
1067
+           HTTP/1.1 200 OK
1068
+
1069
+        :statuscode 200: no error
1070
+        :statuscode 204: no error
1071
+        :statuscode 500: server error
1072
+
1073
+
1074
+Display system-wide information
1075
+*******************************
1076
+
1077
+.. http:get:: /info
1078
+
1079
+        Display system-wide information
1080
+
1081
+        **Example request**:
1082
+
1083
+        .. sourcecode:: http
1084
+
1085
+           GET /info HTTP/1.1
1086
+
1087
+        **Example response**:
1088
+
1089
+        .. sourcecode:: http
1090
+
1091
+           HTTP/1.1 200 OK
1092
+           Content-Type: application/json
1093
+
1094
+           {
1095
+                "Containers":11,
1096
+                "Images":16,
1097
+                "Debug":false,
1098
+                "NFd": 11,
1099
+                "NGoroutines":21,
1100
+                "MemoryLimit":true,
1101
+                "SwapLimit":false,
1102
+                "IPv4Forwarding":true
1103
+           }
1104
+
1105
+        :statuscode 200: no error
1106
+        :statuscode 500: server error
1107
+
1108
+
1109
+Show the docker version information
1110
+***********************************
1111
+
1112
+.. http:get:: /version
1113
+
1114
+        Show the docker version information
1115
+
1116
+        **Example request**:
1117
+
1118
+        .. sourcecode:: http
1119
+
1120
+           GET /version HTTP/1.1
1121
+
1122
+        **Example response**:
1123
+
1124
+        .. sourcecode:: http
1125
+
1126
+           HTTP/1.1 200 OK
1127
+           Content-Type: application/json
1128
+
1129
+           {
1130
+                "Version":"0.2.2",
1131
+                "GitCommit":"5a2a5cc+CHANGES",
1132
+                "GoVersion":"go1.0.3"
1133
+           }
1134
+
1135
+        :statuscode 200: no error
1136
+        :statuscode 500: server error
1137
+
1138
+
1139
+Create a new image from a container's changes
1140
+*********************************************
1141
+
1142
+.. http:post:: /commit
1143
+
1144
+    Create a new image from a container's changes
1145
+
1146
+    **Example request**:
1147
+
1148
+    .. sourcecode:: http
1149
+
1150
+        POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
1151
+
1152
+    **Example response**:
1153
+
1154
+    .. sourcecode:: http
1155
+
1156
+        HTTP/1.1 201 OK
1157
+            Content-Type: application/vnd.docker.raw-stream
1158
+
1159
+        {"Id":"596069db4bf5"}
1160
+
1161
+    :query container: source container
1162
+    :query repo: repository
1163
+    :query tag: tag
1164
+    :query m: commit message
1165
+    :query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
1166
+    :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
1167
+    :statuscode 201: no error
1168
+    :statuscode 404: no such container
1169
+    :statuscode 500: server error
1170
+
1171
+
1172
+Monitor Docker's events
1173
+***********************
1174
+
1175
+.. http:get:: /events
1176
+
1177
+        Get events from docker, either in real time via streaming, or via polling (using `since`)
1178
+
1179
+        **Example request**:
1180
+
1181
+        .. sourcecode:: http
1182
+
1183
+           GET /events?since=1374067924
1184
+
1185
+        **Example response**:
1186
+
1187
+        .. sourcecode:: http
1188
+
1189
+           HTTP/1.1 200 OK
1190
+           Content-Type: application/json
1191
+
1192
+           {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
1193
+           {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
1194
+           {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966}
1195
+           {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970}
1196
+
1197
+        :query since: timestamp used for polling
1198
+        :statuscode 200: no error
1199
+        :statuscode 500: server error
1200
+
1201
+Get a tarball containing all images and tags in a repository
1202
+************************************************************
1203
+
1204
+.. http:get:: /images/(name)/get
1205
+
1206
+        Get a tarball containing all images and metadata for the repository specified by ``name``.
1207
+
1208
+        **Example request**
1209
+
1210
+        .. sourcecode:: http
1211
+
1212
+           GET /images/ubuntu/get
1213
+
1214
+        **Example response**:
1215
+
1216
+        .. sourcecode:: http
1217
+
1218
+           HTTP/1.1 200 OK
1219
+           Content-Type: application/x-tar
1220
+
1221
+           Binary data stream
1222
+
1223
+        :statuscode 200: no error
1224
+        :statuscode 500: server error
1225
+
1226
+Load a tarball with a set of images and tags into docker
1227
+********************************************************
1228
+
1229
+.. http:post:: /images/load
1230
+
1231
+   Load a set of images and tags into the docker repository.
1232
+
1233
+   **Example request**
1234
+
1235
+   .. sourcecode:: http
1236
+
1237
+      POST /images/load
1238
+
1239
+      Tarball in body
1240
+
1241
+   **Example response**:
1242
+
1243
+   .. sourcecode:: http
1244
+
1245
+      HTTP/1.1 200 OK
1246
+
1247
+   :statuscode 200: no error
1248
+   :statuscode 500: server error
1249
+
1250
+3. Going further
1251
+================
1252
+
1253
+3.1 Inside 'docker run'
1254
+-----------------------
1255
+
1256
+Here are the steps of 'docker run' :
1257
+
1258
+* Create the container
1259
+* If the status code is 404, it means the image doesn't exists:
1260
+        * Try to pull it
1261
+        * Then retry to create the container
1262
+* Start the container
1263
+* If you are not in detached mode:
1264
+        * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1
1265
+* If in detached mode or only stdin is attached:
1266
+        * Display the container's id
1267
+
1268
+
1269
+3.2 Hijacking
1270
+-------------
1271
+
1272
+In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future.
1273
+
1274
+3.3 CORS Requests
1275
+-----------------
1276
+
1277
+To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
1278
+
1279
+.. code-block:: bash
1280
+
1281
+   docker -d -H="192.168.1.9:4243" -api-enable-cors
... ...
@@ -185,11 +185,11 @@ Examples:
185 185
 
186 186
     Usage: docker build [OPTIONS] PATH | URL | -
187 187
     Build a new container image from the source code at PATH
188
-      -t, --time="": Repository name (and optionally a tag) to be applied 
188
+      -t, --time="": Repository name (and optionally a tag) to be applied
189 189
              to the resulting image in case of success.
190 190
       -q, --quiet=false: Suppress the verbose output generated by the containers.
191 191
       --no-cache: Do not use the cache when building the image.
192
-      --rm: Remove intermediate containers after a successful build
192
+      --rm=true: Remove intermediate containers after a successful build
193 193
 
194 194
 The files at ``PATH`` or ``URL`` are called the "context" of the build. The
195 195
 build process may refer to any of the files in the context, for example when
... ...
@@ -198,8 +198,6 @@ is given as ``URL``, then no context is set.  When a Git repository is set as
198 198
 ``URL``, then the repository is used as the context. Git repositories are
199 199
 cloned with their submodules (`git clone --recursive`).
200 200
 
201
-.. note:: ``docker build --rm`` does not affect the image cache which is used to accelerate builds, it only removes the duplicate writeable container layers.
202
-
203 201
 .. _cli_build_examples:
204 202
 
205 203
 .. seealso:: :ref:`dockerbuilder`.
... ...
@@ -209,7 +207,7 @@ Examples:
209 209
 
210 210
 .. code-block:: bash
211 211
 
212
-    $ sudo docker build --rm .
212
+    $ sudo docker build .
213 213
     Uploading context 10240 bytes
214 214
     Step 1 : FROM busybox
215 215
     Pulling repository busybox
... ...
@@ -249,9 +247,8 @@ The transfer of context from the local machine to the Docker daemon is
249 249
 what the ``docker`` client means when you see the "Uploading context"
250 250
 message.
251 251
 
252
-The ``--rm`` option tells Docker to remove the intermediate containers and 
253
-layers that were used to create each image layer. Doing so has no impact on
254
-the image build cache.
252
+If you wish to keep the intermediate containers after the build is complete,
253
+you must use ``--rm=false``. This does not affect the build cache.
255 254
 
256 255
 
257 256
 .. code-block:: bash
... ...
@@ -1023,6 +1020,8 @@ containers will not be deleted.
1023 1023
     Usage: docker rmi IMAGE [IMAGE...]
1024 1024
 
1025 1025
     Remove one or more images
1026
+
1027
+      -f, --force=false: Force
1026 1028
     
1027 1029
 Removing tagged images
1028 1030
 ~~~~~~~~~~~~~~~~~~~~~~
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"os"
10 10
 	"path/filepath"
11 11
 	"runtime"
12
+	"sort"
12 13
 	"strings"
13 14
 )
14 15
 
... ...
@@ -29,6 +30,10 @@ func Register(name string, handler Handler) error {
29 29
 	return nil
30 30
 }
31 31
 
32
+func unregister(name string) {
33
+	delete(globalHandlers, name)
34
+}
35
+
32 36
 // The Engine is the core of Docker.
33 37
 // It acts as a store for *containers*, and allows manipulation of these
34 38
 // containers by executing *jobs*.
... ...
@@ -106,6 +111,12 @@ func New(root string) (*Engine, error) {
106 106
 		Stderr:   os.Stderr,
107 107
 		Stdin:    os.Stdin,
108 108
 	}
109
+	eng.Register("commands", func(job *Job) Status {
110
+		for _, name := range eng.commands() {
111
+			job.Printf("%s\n", name)
112
+		}
113
+		return StatusOK
114
+	})
109 115
 	// Copy existing global handlers
110 116
 	for k, v := range globalHandlers {
111 117
 		eng.handlers[k] = v
... ...
@@ -117,6 +128,17 @@ func (eng *Engine) String() string {
117 117
 	return fmt.Sprintf("%s|%s", eng.Root(), eng.id[:8])
118 118
 }
119 119
 
120
+// Commands returns a list of all currently registered commands,
121
+// sorted alphabetically.
122
+func (eng *Engine) commands() []string {
123
+	names := make([]string, 0, len(eng.handlers))
124
+	for name := range eng.handlers {
125
+		names = append(names, name)
126
+	}
127
+	sort.Strings(names)
128
+	return names
129
+}
130
+
120 131
 // Job creates a new job which can later be executed.
121 132
 // This function mimics `Command` from the standard os/exec package.
122 133
 func (eng *Engine) Job(name string, args ...string) *Job {
... ...
@@ -1,6 +1,7 @@
1 1
 package engine
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"io/ioutil"
5 6
 	"os"
6 7
 	"path"
... ...
@@ -17,6 +18,8 @@ func TestRegister(t *testing.T) {
17 17
 	if err := Register("dummy1", nil); err == nil {
18 18
 		t.Fatalf("Expecting error, got none")
19 19
 	}
20
+	// Register is global so let's cleanup to avoid conflicts
21
+	defer unregister("dummy1")
20 22
 
21 23
 	eng := newTestEngine(t)
22 24
 
... ...
@@ -33,6 +36,7 @@ func TestRegister(t *testing.T) {
33 33
 	if err := eng.Register("dummy2", nil); err == nil {
34 34
 		t.Fatalf("Expecting error, got none")
35 35
 	}
36
+	defer unregister("dummy2")
36 37
 }
37 38
 
38 39
 func TestJob(t *testing.T) {
... ...
@@ -49,6 +53,7 @@ func TestJob(t *testing.T) {
49 49
 	}
50 50
 
51 51
 	eng.Register("dummy2", h)
52
+	defer unregister("dummy2")
52 53
 	job2 := eng.Job("dummy2", "--level=awesome")
53 54
 
54 55
 	if job2.handler == nil {
... ...
@@ -60,6 +65,24 @@ func TestJob(t *testing.T) {
60 60
 	}
61 61
 }
62 62
 
63
+func TestEngineCommands(t *testing.T) {
64
+	eng := newTestEngine(t)
65
+	defer os.RemoveAll(eng.Root())
66
+	handler := func(job *Job) Status { return StatusOK }
67
+	eng.Register("foo", handler)
68
+	eng.Register("bar", handler)
69
+	eng.Register("echo", handler)
70
+	eng.Register("die", handler)
71
+	var output bytes.Buffer
72
+	commands := eng.Job("commands")
73
+	commands.Stdout.Add(&output)
74
+	commands.Run()
75
+	expected := "bar\ncommands\ndie\necho\nfoo\n"
76
+	if result := output.String(); result != expected {
77
+		t.Fatalf("Unexpected output:\nExpected = %v\nResult   = %v\n", expected, result)
78
+	}
79
+}
80
+
63 81
 func TestEngineRoot(t *testing.T) {
64 82
 	tmp, err := ioutil.TempDir("", "docker-test-TestEngineCreateDir")
65 83
 	if err != nil {
... ...
@@ -77,7 +77,7 @@ func (d *driver) Name() string {
77 77
 }
78 78
 
79 79
 func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
80
-	if err := SetTerminal(c, pipes); err != nil {
80
+	if err := execdriver.SetTerminal(c, pipes); err != nil {
81 81
 		return -1, err
82 82
 	}
83 83
 	configPath, err := d.generateLXCConfig(c)
... ...
@@ -12,6 +12,7 @@ const LxcTemplate = `
12 12
 lxc.network.type = veth
13 13
 lxc.network.link = {{.Network.Bridge}}
14 14
 lxc.network.name = eth0
15
+lxc.network.mtu = {{.Network.Mtu}}
15 16
 {{else}}
16 17
 # network is disabled (-n=false)
17 18
 lxc.network.type = empty
18 19
deleted file mode 100644
... ...
@@ -1,127 +0,0 @@
1
-package lxc
2
-
3
-import (
4
-	"github.com/dotcloud/docker/execdriver"
5
-	"github.com/dotcloud/docker/pkg/term"
6
-	"github.com/kr/pty"
7
-	"io"
8
-	"os"
9
-	"os/exec"
10
-)
11
-
12
-func SetTerminal(command *execdriver.Command, pipes *execdriver.Pipes) error {
13
-	var (
14
-		term execdriver.Terminal
15
-		err  error
16
-	)
17
-	if command.Tty {
18
-		term, err = NewTtyConsole(command, pipes)
19
-	} else {
20
-		term, err = NewStdConsole(command, pipes)
21
-	}
22
-	if err != nil {
23
-		return err
24
-	}
25
-	command.Terminal = term
26
-	return nil
27
-}
28
-
29
-type TtyConsole struct {
30
-	MasterPty *os.File
31
-	SlavePty  *os.File
32
-}
33
-
34
-func NewTtyConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*TtyConsole, error) {
35
-	ptyMaster, ptySlave, err := pty.Open()
36
-	if err != nil {
37
-		return nil, err
38
-	}
39
-	tty := &TtyConsole{
40
-		MasterPty: ptyMaster,
41
-		SlavePty:  ptySlave,
42
-	}
43
-	if err := tty.AttachPipes(&command.Cmd, pipes); err != nil {
44
-		tty.Close()
45
-		return nil, err
46
-	}
47
-	command.Console = tty.SlavePty.Name()
48
-	return tty, nil
49
-}
50
-
51
-func (t *TtyConsole) Master() *os.File {
52
-	return t.MasterPty
53
-}
54
-
55
-func (t *TtyConsole) Resize(h, w int) error {
56
-	return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
57
-}
58
-
59
-func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error {
60
-	command.Stdout = t.SlavePty
61
-	command.Stderr = t.SlavePty
62
-
63
-	go func() {
64
-		if wb, ok := pipes.Stdout.(interface {
65
-			CloseWriters() error
66
-		}); ok {
67
-			defer wb.CloseWriters()
68
-		}
69
-		io.Copy(pipes.Stdout, t.MasterPty)
70
-	}()
71
-
72
-	if pipes.Stdin != nil {
73
-		command.Stdin = t.SlavePty
74
-		command.SysProcAttr.Setctty = true
75
-
76
-		go func() {
77
-			defer pipes.Stdin.Close()
78
-			io.Copy(t.MasterPty, pipes.Stdin)
79
-		}()
80
-	}
81
-	return nil
82
-}
83
-
84
-func (t *TtyConsole) Close() error {
85
-	t.SlavePty.Close()
86
-	return t.MasterPty.Close()
87
-}
88
-
89
-type StdConsole struct {
90
-}
91
-
92
-func NewStdConsole(command *execdriver.Command, pipes *execdriver.Pipes) (*StdConsole, error) {
93
-	std := &StdConsole{}
94
-
95
-	if err := std.AttachPipes(&command.Cmd, pipes); err != nil {
96
-		return nil, err
97
-	}
98
-	return std, nil
99
-}
100
-
101
-func (s *StdConsole) AttachPipes(command *exec.Cmd, pipes *execdriver.Pipes) error {
102
-	command.Stdout = pipes.Stdout
103
-	command.Stderr = pipes.Stderr
104
-
105
-	if pipes.Stdin != nil {
106
-		stdin, err := command.StdinPipe()
107
-		if err != nil {
108
-			return err
109
-		}
110
-
111
-		go func() {
112
-			defer stdin.Close()
113
-			io.Copy(stdin, pipes.Stdin)
114
-		}()
115
-	}
116
-	return nil
117
-}
118
-
119
-func (s *StdConsole) Resize(h, w int) error {
120
-	// we do not need to reside a non tty
121
-	return nil
122
-}
123
-
124
-func (s *StdConsole) Close() error {
125
-	// nothing to close here
126
-	return nil
127
-}
... ...
@@ -6,14 +6,13 @@ package native
6 6
 
7 7
 import (
8 8
 	"github.com/dotcloud/docker/execdriver"
9
-	"github.com/dotcloud/docker/execdriver/lxc"
10 9
 	"io"
11 10
 	"os"
12 11
 	"os/exec"
13 12
 )
14 13
 
15 14
 type dockerStdTerm struct {
16
-	lxc.StdConsole
15
+	execdriver.StdConsole
17 16
 	pipes *execdriver.Pipes
18 17
 }
19 18
 
... ...
@@ -26,7 +25,7 @@ func (d *dockerStdTerm) SetMaster(master *os.File) {
26 26
 }
27 27
 
28 28
 type dockerTtyTerm struct {
29
-	lxc.TtyConsole
29
+	execdriver.TtyConsole
30 30
 	pipes *execdriver.Pipes
31 31
 }
32 32
 
33 33
new file mode 100644
... ...
@@ -0,0 +1,126 @@
0
+package execdriver
1
+
2
+import (
3
+	"github.com/dotcloud/docker/pkg/term"
4
+	"github.com/kr/pty"
5
+	"io"
6
+	"os"
7
+	"os/exec"
8
+)
9
+
10
+func SetTerminal(command *Command, pipes *Pipes) error {
11
+	var (
12
+		term Terminal
13
+		err  error
14
+	)
15
+	if command.Tty {
16
+		term, err = NewTtyConsole(command, pipes)
17
+	} else {
18
+		term, err = NewStdConsole(command, pipes)
19
+	}
20
+	if err != nil {
21
+		return err
22
+	}
23
+	command.Terminal = term
24
+	return nil
25
+}
26
+
27
+type TtyConsole struct {
28
+	MasterPty *os.File
29
+	SlavePty  *os.File
30
+}
31
+
32
+func NewTtyConsole(command *Command, pipes *Pipes) (*TtyConsole, error) {
33
+	ptyMaster, ptySlave, err := pty.Open()
34
+	if err != nil {
35
+		return nil, err
36
+	}
37
+	tty := &TtyConsole{
38
+		MasterPty: ptyMaster,
39
+		SlavePty:  ptySlave,
40
+	}
41
+	if err := tty.AttachPipes(&command.Cmd, pipes); err != nil {
42
+		tty.Close()
43
+		return nil, err
44
+	}
45
+	command.Console = tty.SlavePty.Name()
46
+	return tty, nil
47
+}
48
+
49
+func (t *TtyConsole) Master() *os.File {
50
+	return t.MasterPty
51
+}
52
+
53
+func (t *TtyConsole) Resize(h, w int) error {
54
+	return term.SetWinsize(t.MasterPty.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
55
+}
56
+
57
+func (t *TtyConsole) AttachPipes(command *exec.Cmd, pipes *Pipes) error {
58
+	command.Stdout = t.SlavePty
59
+	command.Stderr = t.SlavePty
60
+
61
+	go func() {
62
+		if wb, ok := pipes.Stdout.(interface {
63
+			CloseWriters() error
64
+		}); ok {
65
+			defer wb.CloseWriters()
66
+		}
67
+		io.Copy(pipes.Stdout, t.MasterPty)
68
+	}()
69
+
70
+	if pipes.Stdin != nil {
71
+		command.Stdin = t.SlavePty
72
+		command.SysProcAttr.Setctty = true
73
+
74
+		go func() {
75
+			defer pipes.Stdin.Close()
76
+			io.Copy(t.MasterPty, pipes.Stdin)
77
+		}()
78
+	}
79
+	return nil
80
+}
81
+
82
+func (t *TtyConsole) Close() error {
83
+	t.SlavePty.Close()
84
+	return t.MasterPty.Close()
85
+}
86
+
87
+type StdConsole struct {
88
+}
89
+
90
+func NewStdConsole(command *Command, pipes *Pipes) (*StdConsole, error) {
91
+	std := &StdConsole{}
92
+
93
+	if err := std.AttachPipes(&command.Cmd, pipes); err != nil {
94
+		return nil, err
95
+	}
96
+	return std, nil
97
+}
98
+
99
+func (s *StdConsole) AttachPipes(command *exec.Cmd, pipes *Pipes) error {
100
+	command.Stdout = pipes.Stdout
101
+	command.Stderr = pipes.Stderr
102
+
103
+	if pipes.Stdin != nil {
104
+		stdin, err := command.StdinPipe()
105
+		if err != nil {
106
+			return err
107
+		}
108
+
109
+		go func() {
110
+			defer stdin.Close()
111
+			io.Copy(stdin, pipes.Stdin)
112
+		}()
113
+	}
114
+	return nil
115
+}
116
+
117
+func (s *StdConsole) Resize(h, w int) error {
118
+	// we do not need to reside a non tty
119
+	return nil
120
+}
121
+
122
+func (s *StdConsole) Close() error {
123
+	// nothing to close here
124
+	return nil
125
+}
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"github.com/dotcloud/docker/runconfig"
14 14
 	"github.com/dotcloud/docker/utils"
15 15
 	"io"
16
+	"io/ioutil"
16 17
 	"net"
17 18
 	"net/http"
18 19
 	"net/http/httptest"
... ...
@@ -1175,6 +1176,8 @@ func TestGetEnabledCors(t *testing.T) {
1175 1175
 
1176 1176
 func TestDeleteImages(t *testing.T) {
1177 1177
 	eng := NewTestEngine(t)
1178
+	//we expect errors, so we disable stderr
1179
+	eng.Stderr = ioutil.Discard
1178 1180
 	defer mkRuntimeFromEngine(eng, t).Nuke()
1179 1181
 
1180 1182
 	initialImages := getImages(eng, t, true, "")
... ...
@@ -1031,7 +1031,10 @@ func TestContainerOrphaning(t *testing.T) {
1031 1031
 	buildSomething(template2, imageName)
1032 1032
 
1033 1033
 	// remove the second image by name
1034
-	resp, err := srv.DeleteImage(imageName, true)
1034
+	resp := engine.NewTable("", 0)
1035
+	if err := srv.DeleteImage(imageName, resp, true, false); err == nil {
1036
+		t.Fatal("Expected error, got none")
1037
+	}
1035 1038
 
1036 1039
 	// see if we deleted the first image (and orphaned the container)
1037 1040
 	for _, i := range resp.Data {
... ...
@@ -1043,11 +1046,12 @@ func TestContainerOrphaning(t *testing.T) {
1043 1043
 }
1044 1044
 
1045 1045
 func TestCmdKill(t *testing.T) {
1046
-	stdin, stdinPipe := io.Pipe()
1047
-	stdout, stdoutPipe := io.Pipe()
1048
-
1049
-	cli := api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
1050
-	cli2 := api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr)
1046
+	var (
1047
+		stdin, stdinPipe   = io.Pipe()
1048
+		stdout, stdoutPipe = io.Pipe()
1049
+		cli                = api.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
1050
+		cli2               = api.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr)
1051
+	)
1051 1052
 	defer cleanup(globalEngine, t)
1052 1053
 
1053 1054
 	ch := make(chan struct{})
... ...
@@ -1086,6 +1090,7 @@ func TestCmdKill(t *testing.T) {
1086 1086
 		}
1087 1087
 	})
1088 1088
 
1089
+	stdout.Close()
1089 1090
 	time.Sleep(500 * time.Millisecond)
1090 1091
 	if !container.State.IsRunning() {
1091 1092
 		t.Fatal("The container should be still running")
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"github.com/dotcloud/docker"
5
+	"github.com/dotcloud/docker/engine"
5 6
 	"github.com/dotcloud/docker/runconfig"
6 7
 	"strings"
7 8
 	"testing"
... ...
@@ -35,7 +36,7 @@ func TestImageTagImageDelete(t *testing.T) {
35 35
 		t.Errorf("Expected %d images, %d found", nExpected, nActual)
36 36
 	}
37 37
 
38
-	if _, err := srv.DeleteImage("utest/docker:tag2", true); err != nil {
38
+	if err := srv.DeleteImage("utest/docker:tag2", engine.NewTable("", 0), true, false); err != nil {
39 39
 		t.Fatal(err)
40 40
 	}
41 41
 
... ...
@@ -47,7 +48,7 @@ func TestImageTagImageDelete(t *testing.T) {
47 47
 		t.Errorf("Expected %d images, %d found", nExpected, nActual)
48 48
 	}
49 49
 
50
-	if _, err := srv.DeleteImage("utest:5000/docker:tag3", true); err != nil {
50
+	if err := srv.DeleteImage("utest:5000/docker:tag3", engine.NewTable("", 0), true, false); err != nil {
51 51
 		t.Fatal(err)
52 52
 	}
53 53
 
... ...
@@ -56,7 +57,7 @@ func TestImageTagImageDelete(t *testing.T) {
56 56
 	nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1
57 57
 	nActual = len(images.Data[0].GetList("RepoTags"))
58 58
 
59
-	if _, err := srv.DeleteImage("utest:tag1", true); err != nil {
59
+	if err := srv.DeleteImage("utest:tag1", engine.NewTable("", 0), true, false); err != nil {
60 60
 		t.Fatal(err)
61 61
 	}
62 62
 
... ...
@@ -447,8 +448,7 @@ func TestRmi(t *testing.T) {
447 447
 		t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len())
448 448
 	}
449 449
 
450
-	_, err = srv.DeleteImage(imageID, true)
451
-	if err != nil {
450
+	if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false); err != nil {
452 451
 		t.Fatal(err)
453 452
 	}
454 453
 
... ...
@@ -683,8 +683,8 @@ func TestDeleteTagWithExistingContainers(t *testing.T) {
683 683
 	}
684 684
 
685 685
 	// Try to remove the tag
686
-	imgs, err := srv.DeleteImage("utest:tag1", true)
687
-	if err != nil {
686
+	imgs := engine.NewTable("", 0)
687
+	if err := srv.DeleteImage("utest:tag1", imgs, true, false); err != nil {
688 688
 		t.Fatal(err)
689 689
 	}
690 690
 
... ...
@@ -692,7 +692,7 @@ func TestDeleteTagWithExistingContainers(t *testing.T) {
692 692
 		t.Fatalf("Should only have deleted one untag %d", len(imgs.Data))
693 693
 	}
694 694
 
695
-	if untag := imgs.Data[0].Get("Untagged"); untag != unitTestImageID {
695
+	if untag := imgs.Data[0].Get("Untagged"); untag != "utest:tag1" {
696 696
 		t.Fatalf("Expected %s got %s", unitTestImageID, untag)
697 697
 	}
698 698
 }
699 699
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+package version
1
+
2
+import (
3
+	"strconv"
4
+	"strings"
5
+)
6
+
7
+type Version string
8
+
9
+func (me Version) compareTo(other string) int {
10
+	var (
11
+		meTab    = strings.Split(string(me), ".")
12
+		otherTab = strings.Split(other, ".")
13
+	)
14
+	for i, s := range meTab {
15
+		var meInt, otherInt int
16
+		meInt, _ = strconv.Atoi(s)
17
+		if len(otherTab) > i {
18
+			otherInt, _ = strconv.Atoi(otherTab[i])
19
+		}
20
+		if meInt > otherInt {
21
+			return 1
22
+		}
23
+		if otherInt > meInt {
24
+			return -1
25
+		}
26
+	}
27
+	if len(otherTab) > len(meTab) {
28
+		return -1
29
+	}
30
+	return 0
31
+}
32
+
33
+func (me Version) LessThan(other string) bool {
34
+	return me.compareTo(other) == -1
35
+}
36
+
37
+func (me Version) LessThanOrEqualTo(other string) bool {
38
+	return me.compareTo(other) <= 0
39
+}
40
+
41
+func (me Version) GreaterThan(other string) bool {
42
+	return me.compareTo(other) == 1
43
+}
44
+
45
+func (me Version) GreaterThanOrEqualTo(other string) bool {
46
+	return me.compareTo(other) >= 0
47
+}
48
+
49
+func (me Version) Equal(other string) bool {
50
+	return me.compareTo(other) == 0
51
+}
0 52
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+package version
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func assertVersion(t *testing.T, a, b string, result int) {
7
+	if r := Version(a).compareTo(b); r != result {
8
+		t.Fatalf("Unexpected version comparison result. Found %d, expected %d", r, result)
9
+	}
10
+}
11
+
12
+func TestCompareVersion(t *testing.T) {
13
+	assertVersion(t, "1.12", "1.12", 0)
14
+	assertVersion(t, "1.05.00.0156", "1.0.221.9289", 1)
15
+	assertVersion(t, "1", "1.0.1", -1)
16
+	assertVersion(t, "1.0.1", "1", 1)
17
+	assertVersion(t, "1.0.1", "1.0.2", -1)
18
+	assertVersion(t, "1.0.2", "1.0.3", -1)
19
+	assertVersion(t, "1.0.3", "1.1", -1)
20
+	assertVersion(t, "1.1", "1.1.1", -1)
21
+	assertVersion(t, "1.1.1", "1.1.2", -1)
22
+	assertVersion(t, "1.1.2", "1.2", -1)
23
+
24
+}
... ...
@@ -2,7 +2,6 @@ package docker
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
-	"errors"
6 5
 	"fmt"
7 6
 	"github.com/dotcloud/docker/archive"
8 7
 	"github.com/dotcloud/docker/auth"
... ...
@@ -1810,102 +1809,33 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status {
1810 1810
 	return engine.StatusOK
1811 1811
 }
1812 1812
 
1813
-var ErrImageReferenced = errors.New("Image referenced by a repository")
1814
-
1815
-func (srv *Server) deleteImageAndChildren(id string, imgs *engine.Table, byParents map[string][]*Image) error {
1816
-	// If the image is referenced by a repo, do not delete
1817
-	if len(srv.runtime.repositories.ByID()[id]) != 0 {
1818
-		return ErrImageReferenced
1819
-	}
1820
-	// If the image is not referenced but has children, go recursive
1821
-	referenced := false
1822
-	for _, img := range byParents[id] {
1823
-		if err := srv.deleteImageAndChildren(img.ID, imgs, byParents); err != nil {
1824
-			if err != ErrImageReferenced {
1825
-				return err
1826
-			}
1827
-			referenced = true
1828
-		}
1829
-	}
1830
-	if referenced {
1831
-		return ErrImageReferenced
1832
-	}
1833
-
1834
-	// If the image is not referenced and has no children, remove it
1835
-	byParents, err := srv.runtime.graph.ByParent()
1836
-	if err != nil {
1837
-		return err
1838
-	}
1839
-	if len(byParents[id]) == 0 && srv.canDeleteImage(id) == nil {
1840
-		if err := srv.runtime.repositories.DeleteAll(id); err != nil {
1841
-			return err
1842
-		}
1843
-		err := srv.runtime.graph.Delete(id)
1844
-		if err != nil {
1845
-			return err
1846
-		}
1847
-		out := &engine.Env{}
1848
-		out.Set("Deleted", id)
1849
-		imgs.Add(out)
1850
-		srv.LogEvent("delete", id, "")
1851
-		return nil
1852
-	}
1853
-	return nil
1854
-}
1855
-
1856
-func (srv *Server) deleteImageParents(img *Image, imgs *engine.Table) error {
1857
-	if img.Parent != "" {
1858
-		parent, err := srv.runtime.graph.Get(img.Parent)
1859
-		if err != nil {
1860
-			return err
1861
-		}
1862
-		byParents, err := srv.runtime.graph.ByParent()
1863
-		if err != nil {
1864
-			return err
1865
-		}
1866
-		// Remove all children images
1867
-		if err := srv.deleteImageAndChildren(img.Parent, imgs, byParents); err != nil {
1868
-			return err
1869
-		}
1870
-		return srv.deleteImageParents(parent, imgs)
1871
-	}
1872
-	return nil
1873
-}
1874
-
1875
-func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, error) {
1813
+func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force bool) error {
1876 1814
 	var (
1877 1815
 		repoName, tag string
1878
-		img, err      = srv.runtime.repositories.LookupImage(name)
1879
-		imgs          = engine.NewTable("", 0)
1880 1816
 		tags          = []string{}
1881 1817
 	)
1882 1818
 
1883
-	if err != nil {
1884
-		return nil, fmt.Errorf("No such image: %s", name)
1819
+	repoName, tag = utils.ParseRepositoryTag(name)
1820
+	if tag == "" {
1821
+		tag = DEFAULTTAG
1885 1822
 	}
1886 1823
 
1887
-	// FIXME: What does autoPrune mean ?
1888
-	if !autoPrune {
1889
-		if err := srv.runtime.graph.Delete(img.ID); err != nil {
1890
-			return nil, fmt.Errorf("Cannot delete image %s: %s", name, err)
1824
+	img, err := srv.runtime.repositories.LookupImage(name)
1825
+	if err != nil {
1826
+		if r, _ := srv.runtime.repositories.Get(repoName); r != nil {
1827
+			return fmt.Errorf("No such image: %s:%s", repoName, tag)
1891 1828
 		}
1892
-		return nil, nil
1829
+		return fmt.Errorf("No such image: %s", name)
1893 1830
 	}
1894 1831
 
1895
-	if !strings.Contains(img.ID, name) {
1896
-		repoName, tag = utils.ParseRepositoryTag(name)
1832
+	if strings.Contains(img.ID, name) {
1833
+		repoName = ""
1834
+		tag = ""
1897 1835
 	}
1898 1836
 
1899
-	// If we have a repo and the image is not referenced anywhere else
1900
-	// then just perform an untag and do not validate.
1901
-	//
1902
-	// i.e. only validate if we are performing an actual delete and not
1903
-	// an untag op
1904
-	if repoName != "" && len(srv.runtime.repositories.ByID()[img.ID]) == 1 {
1905
-		// Prevent deletion if image is used by a container
1906
-		if err := srv.canDeleteImage(img.ID); err != nil {
1907
-			return nil, err
1908
-		}
1837
+	byParents, err := srv.runtime.graph.ByParent()
1838
+	if err != nil {
1839
+		return err
1909 1840
 	}
1910 1841
 
1911 1842
 	//If delete by id, see if the id belong only to one repository
... ...
@@ -1917,51 +1847,68 @@ func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, erro
1917 1917
 				if parsedTag != "" {
1918 1918
 					tags = append(tags, parsedTag)
1919 1919
 				}
1920
-			} else if repoName != parsedRepo {
1920
+			} else if repoName != parsedRepo && !force {
1921 1921
 				// the id belongs to multiple repos, like base:latest and user:test,
1922 1922
 				// in that case return conflict
1923
-				return nil, fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories", utils.TruncateID(img.ID))
1923
+				return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
1924 1924
 			}
1925 1925
 		}
1926 1926
 	} else {
1927 1927
 		tags = append(tags, tag)
1928 1928
 	}
1929 1929
 
1930
+	if !first && len(tags) > 0 {
1931
+		return nil
1932
+	}
1933
+
1930 1934
 	//Untag the current image
1931 1935
 	for _, tag := range tags {
1932 1936
 		tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag)
1933 1937
 		if err != nil {
1934
-			return nil, err
1938
+			return err
1935 1939
 		}
1936 1940
 		if tagDeleted {
1937 1941
 			out := &engine.Env{}
1938
-			out.Set("Untagged", img.ID)
1942
+			out.Set("Untagged", repoName+":"+tag)
1939 1943
 			imgs.Add(out)
1940 1944
 			srv.LogEvent("untag", img.ID, "")
1941 1945
 		}
1942 1946
 	}
1943
-
1944
-	if len(srv.runtime.repositories.ByID()[img.ID]) == 0 {
1945
-		if err := srv.deleteImageAndChildren(img.ID, imgs, nil); err != nil {
1946
-			if err != ErrImageReferenced {
1947
-				return imgs, err
1947
+	tags = srv.runtime.repositories.ByID()[img.ID]
1948
+	if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
1949
+		if len(byParents[img.ID]) == 0 {
1950
+			if err := srv.canDeleteImage(img.ID); err != nil {
1951
+				return err
1952
+			}
1953
+			if err := srv.runtime.repositories.DeleteAll(img.ID); err != nil {
1954
+				return err
1948 1955
 			}
1949
-		} else if err := srv.deleteImageParents(img, imgs); err != nil {
1950
-			if err != ErrImageReferenced {
1951
-				return imgs, err
1956
+			if err := srv.runtime.graph.Delete(img.ID); err != nil {
1957
+				return err
1952 1958
 			}
1959
+			out := &engine.Env{}
1960
+			out.Set("Deleted", img.ID)
1961
+			imgs.Add(out)
1962
+			srv.LogEvent("delete", img.ID, "")
1963
+			if img.Parent != "" {
1964
+				err := srv.DeleteImage(img.Parent, imgs, false, force)
1965
+				if first {
1966
+					return err
1967
+				}
1968
+
1969
+			}
1970
+
1953 1971
 		}
1954 1972
 	}
1955
-	return imgs, nil
1973
+	return nil
1956 1974
 }
1957 1975
 
1958 1976
 func (srv *Server) ImageDelete(job *engine.Job) engine.Status {
1959 1977
 	if n := len(job.Args); n != 1 {
1960 1978
 		return job.Errorf("Usage: %s IMAGE", job.Name)
1961 1979
 	}
1962
-
1963
-	imgs, err := srv.DeleteImage(job.Args[0], job.GetenvBool("autoPrune"))
1964
-	if err != nil {
1980
+	imgs := engine.NewTable("", 0)
1981
+	if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force")); err != nil {
1965 1982
 		return job.Error(err)
1966 1983
 	}
1967 1984
 	if len(imgs.Data) == 0 {
... ...
@@ -52,7 +52,7 @@ func (p *JSONProgress) String() string {
52 52
 	}
53 53
 	numbersBox = fmt.Sprintf("%8v/%v", current, total)
54 54
 
55
-	if p.Start > 0 && percentage < 50 {
55
+	if p.Current > 0 && p.Start > 0 && percentage < 50 {
56 56
 		fromStart := time.Now().UTC().Sub(time.Unix(int64(p.Start), 0))
57 57
 		perEntry := fromStart / time.Duration(p.Current)
58 58
 		left := time.Duration(p.Total-p.Current) * perEntry
... ...
@@ -606,16 +606,22 @@ func GetKernelVersion() (*KernelVersionInfo, error) {
606 606
 func ParseRelease(release string) (*KernelVersionInfo, error) {
607 607
 	var (
608 608
 		kernel, major, minor, parsed int
609
-		flavor                       string
609
+		flavor, partial              string
610 610
 	)
611 611
 
612 612
 	// Ignore error from Sscanf to allow an empty flavor.  Instead, just
613 613
 	// make sure we got all the version numbers.
614
-	parsed, _ = fmt.Sscanf(release, "%d.%d.%d%s", &kernel, &major, &minor, &flavor)
615
-	if parsed < 3 {
614
+	parsed, _ = fmt.Sscanf(release, "%d.%d%s", &kernel, &major, &partial)
615
+	if parsed < 2 {
616 616
 		return nil, errors.New("Can't parse kernel version " + release)
617 617
 	}
618 618
 
619
+	// sometimes we have 3.12.25-gentoo, but sometimes we just have 3.12-1-amd64
620
+	parsed, _ = fmt.Sscanf(partial, ".%d%s", &minor, &flavor)
621
+	if parsed < 1 {
622
+		flavor = partial
623
+	}
624
+
619 625
 	return &KernelVersionInfo{
620 626
 		Kernel: kernel,
621 627
 		Major:  major,
... ...
@@ -420,6 +420,7 @@ func TestParseRelease(t *testing.T) {
420 420
 	assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: ".longterm-1"}, 0)
421 421
 	assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "-19-generic"}, 0)
422 422
 	assertParseRelease(t, "3.12.8tag", &KernelVersionInfo{Kernel: 3, Major: 12, Minor: 8, Flavor: "tag"}, 0)
423
+	assertParseRelease(t, "3.12-1-amd64", &KernelVersionInfo{Kernel: 3, Major: 12, Minor: 0, Flavor: "-1-amd64"}, 0)
423 424
 }
424 425
 
425 426
 func TestParsePortMapping(t *testing.T) {