Browse code

Merge branch 'master' into 610-improve_rmi-feature

Guillaume J. Charmes authored on 2013/05/17 03:15:16
Showing 81 changed files
... ...
@@ -2,7 +2,7 @@
2 2
 <charles.hooper@dotcloud.com> <chooper@plumata.com> 
3 3
 <daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
4 4
 <daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
5
-Guillaume J. Charmes <guillaume.charmes@dotcloud.com> creack <charmes.guillaume@gmail.com>
5
+Guillaume J. Charmes <guillaume.charmes@dotcloud.com> <charmes.guillaume@gmail.com>
6 6
 <guillaume.charmes@dotcloud.com> <guillaume@dotcloud.com>
7 7
 <kencochrane@gmail.com> <KenCochrane@gmail.com>
8 8
 <sridharr@activestate.com> <github@srid.name>
... ...
@@ -16,4 +16,6 @@ Tim Terhorst <mynamewastaken+git@gmail.com>
16 16
 Andy Smith <github@anarkystic.com>
17 17
 <kalessin@kalessin.fr> <louis@dotcloud.com>
18 18
 <victor.vieux@dotcloud.com> <victor@dotcloud.com>
19
+<victor.vieux@dotcloud.com> <dev@vvieux.com>
19 20
 <dominik@honnef.co> <dominikh@fork-bomb.org>
21
+Thatcher Peskens <thatcher@dotcloud.com>
... ...
@@ -1,24 +1,34 @@
1
+Al Tobey <al@ooyala.com>
2
+Alexey Shamrin <shamrin@gmail.com>
1 3
 Andrea Luzzardi <aluzzardi@gmail.com>
2 4
 Andy Rothfusz <github@metaliveblog.com>
3 5
 Andy Smith <github@anarkystic.com>
4 6
 Antony Messerli <amesserl@rackspace.com>
7
+Barry Allard <barry.allard@gmail.com>
8
+Brandon Liu <bdon@bdon.org>
5 9
 Brian McCallister <brianm@skife.org>
10
+Bruno Bigras <bigras.bruno@gmail.com>
6 11
 Caleb Spare <cespare@gmail.com>
7 12
 Charles Hooper <charles.hooper@dotcloud.com>
8 13
 Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
9 14
 Daniel Robinson <gottagetmac@gmail.com>
15
+Daniel Von Fange <daniel@leancoder.com>
10 16
 Dominik Honnef <dominik@honnef.co>
11 17
 Don Spaulding <donspauldingii@gmail.com>
18
+Dr Nic Williams <drnicwilliams@gmail.com>
19
+Evan Wies <evan@neomantra.net>
12 20
 ezbercih <cem.ezberci@gmail.com>
13 21
 Flavio Castelli <fcastelli@suse.com>
14 22
 Francisco Souza <f@souza.cc>
15 23
 Frederick F. Kautz IV <fkautz@alumni.cmu.edu>
16 24
 Guillaume J. Charmes <guillaume.charmes@dotcloud.com>
25
+Harley Laue <losinggeneration@gmail.com>
17 26
 Hunter Blanks <hunter@twilio.com>
18 27
 Jeff Lindsay <progrium@gmail.com>
19 28
 Jeremy Grosser <jeremy@synack.me>
20 29
 Joffrey F <joffrey@dotcloud.com>
21 30
 John Costa <john.costa@gmail.com>
31
+Jonas Pfenniger <jonas@pfenniger.name>
22 32
 Jonathan Rudenberg <jonathan@titanous.com>
23 33
 Julien Barbier <write0@gmail.com>
24 34
 Jérôme Petazzoni <jerome.petazzoni@dotcloud.com>
... ...
@@ -27,8 +37,11 @@ Kevin J. Lynagh <kevin@keminglabs.com>
27 27
 Louis Opter <kalessin@kalessin.fr>
28 28
 Maxim Treskin <zerthurd@gmail.com>
29 29
 Mikhail Sobolev <mss@mawhrin.net>
30
+Nate Jones <nate@endot.org>
30 31
 Nelson Chen <crazysim@gmail.com>
31 32
 Niall O'Higgins <niallo@unworkable.org>
33
+odk- <github@odkurzacz.org>
34
+Paul Bowsher <pbowsher@globalpersonals.co.uk>
32 35
 Paul Hammond <paul@paulhammond.org>
33 36
 Piotr Bogdan <ppbogdan@gmail.com>
34 37
 Robert Obryk <robryk@gmail.com>
... ...
@@ -38,6 +51,8 @@ Silas Sewell <silas@sewell.org>
38 38
 Solomon Hykes <solomon@dotcloud.com>
39 39
 Sridhar Ratnakumar <sridharr@activestate.com>
40 40
 Thatcher Peskens <thatcher@dotcloud.com>
41
+Thomas Bikeev <thomas.bikeev@mac.com>
42
+Tianon Gravi <admwiggin@gmail.com>
41 43
 Tim Terhorst <mynamewastaken+git@gmail.com>
42 44
 Troy Howard <thoward37@gmail.com>
43 45
 unclejack <unclejacksons@gmail.com>
... ...
@@ -4,8 +4,8 @@ import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6 6
 	"github.com/dotcloud/docker/auth"
7
+	"github.com/dotcloud/docker/utils"
7 8
 	"github.com/gorilla/mux"
8
-	"github.com/shin-/cookiejar"
9 9
 	"io"
10 10
 	"log"
11 11
 	"net/http"
... ...
@@ -34,6 +34,8 @@ func parseForm(r *http.Request) error {
34 34
 func httpError(w http.ResponseWriter, err error) {
35 35
 	if strings.HasPrefix(err.Error(), "No such") {
36 36
 		http.Error(w, err.Error(), http.StatusNotFound)
37
+	} else if strings.HasPrefix(err.Error(), "Bad parameter") {
38
+		http.Error(w, err.Error(), http.StatusBadRequest)
37 39
 	} else {
38 40
 		http.Error(w, err.Error(), http.StatusInternalServerError)
39 41
 	}
... ...
@@ -44,12 +46,18 @@ func writeJson(w http.ResponseWriter, b []byte) {
44 44
 	w.Write(b)
45 45
 }
46 46
 
47
-func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
48
-	config := &auth.AuthConfig{
49
-		Username: srv.runtime.authConfig.Username,
50
-		Email:    srv.runtime.authConfig.Email,
47
+func getBoolParam(value string) (bool, error) {
48
+	if value == "1" || strings.ToLower(value) == "true" {
49
+		return true, nil
50
+	}
51
+	if value == "" || value == "0" || strings.ToLower(value) == "false" {
52
+		return false, nil
51 53
 	}
52
-	b, err := json.Marshal(config)
54
+	return false, fmt.Errorf("Bad parameter")
55
+}
56
+
57
+func getAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
58
+	b, err := json.Marshal(srv.registry.GetAuthConfig())
53 59
 	if err != nil {
54 60
 		return err
55 61
 	}
... ...
@@ -63,18 +71,17 @@ func postAuth(srv *Server, w http.ResponseWriter, r *http.Request, vars map[stri
63 63
 		return err
64 64
 	}
65 65
 
66
-	if config.Username == srv.runtime.authConfig.Username {
67
-		config.Password = srv.runtime.authConfig.Password
66
+	if config.Username == srv.registry.GetAuthConfig().Username {
67
+		config.Password = srv.registry.GetAuthConfig().Password
68 68
 	}
69 69
 
70 70
 	newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root)
71 71
 	status, err := auth.Login(newAuthConfig)
72 72
 	if err != nil {
73 73
 		return err
74
-	} else {
75
-		srv.runtime.graph.getHttpClient().Jar = cookiejar.NewCookieJar()
76
-		srv.runtime.authConfig = newAuthConfig
77 74
 	}
75
+	srv.registry.ResetClient(newAuthConfig)
76
+
78 77
 	if status != "" {
79 78
 		b, err := json.Marshal(&ApiAuth{Status: status})
80 79
 		if err != nil {
... ...
@@ -116,7 +123,7 @@ func getContainersExport(srv *Server, w http.ResponseWriter, r *http.Request, va
116 116
 	name := vars["name"]
117 117
 
118 118
 	if err := srv.ContainerExport(name, w); err != nil {
119
-		Debugf("%s", err.Error())
119
+		utils.Debugf("%s", err.Error())
120 120
 		return err
121 121
 	}
122 122
 	return nil
... ...
@@ -127,7 +134,10 @@ func getImagesJson(srv *Server, w http.ResponseWriter, r *http.Request, vars map
127 127
 		return err
128 128
 	}
129 129
 
130
-	all := r.Form.Get("all") == "1"
130
+	all, err := getBoolParam(r.Form.Get("all"))
131
+	if err != nil {
132
+		return err
133
+	}
131 134
 	filter := r.Form.Get("filter")
132 135
 
133 136
 	outs, err := srv.Images(all, filter)
... ...
@@ -197,7 +207,10 @@ func getContainersPs(srv *Server, w http.ResponseWriter, r *http.Request, vars m
197 197
 	if err := parseForm(r); err != nil {
198 198
 		return err
199 199
 	}
200
-	all := r.Form.Get("all") == "1"
200
+	all, err := getBoolParam(r.Form.Get("all"))
201
+	if err != nil {
202
+		return err
203
+	}
201 204
 	since := r.Form.Get("since")
202 205
 	before := r.Form.Get("before")
203 206
 	n, err := strconv.Atoi(r.Form.Get("limit"))
... ...
@@ -224,7 +237,10 @@ func postImagesTag(srv *Server, w http.ResponseWriter, r *http.Request, vars map
224 224
 		return fmt.Errorf("Missing parameter")
225 225
 	}
226 226
 	name := vars["name"]
227
-	force := r.Form.Get("force") == "1"
227
+	force, err := getBoolParam(r.Form.Get("force"))
228
+	if err != nil {
229
+		return err
230
+	}
228 231
 
229 232
 	if err := srv.ContainerTag(name, repo, tag, force); err != nil {
230 233
 		return err
... ...
@@ -239,7 +255,7 @@ func postCommit(srv *Server, w http.ResponseWriter, r *http.Request, vars map[st
239 239
 	}
240 240
 	config := &Config{}
241 241
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
242
-		Debugf("%s", err.Error())
242
+		utils.Debugf("%s", err.Error())
243 243
 	}
244 244
 	repo := r.Form.Get("repo")
245 245
 	tag := r.Form.Get("tag")
... ...
@@ -335,7 +351,6 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars ma
335 335
 	if err := parseForm(r); err != nil {
336 336
 		return err
337 337
 	}
338
-
339 338
 	registry := r.Form.Get("registry")
340 339
 
341 340
 	if vars == nil {
... ...
@@ -363,7 +378,7 @@ func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[str
363 363
 	defer in.Close()
364 364
 	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
365 365
 	if err := srv.ImageCreateFromFile(in, out); err != nil {
366
-		fmt.Fprintln(out, "Error: %s\n", err)
366
+		fmt.Fprintf(out, "Error: %s\n", err)
367 367
 	}
368 368
 	return nil
369 369
 }
... ...
@@ -425,7 +440,10 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars
425 425
 		return fmt.Errorf("Missing parameter")
426 426
 	}
427 427
 	name := vars["name"]
428
-	removeVolume := r.Form.Get("v") == "1"
428
+	removeVolume, err := getBoolParam(r.Form.Get("v"))
429
+	if err != nil {
430
+		return err
431
+	}
429 432
 
430 433
 	if err := srv.ContainerDestroy(name, removeVolume); err != nil {
431 434
 		return err
... ...
@@ -500,11 +518,27 @@ func postContainersAttach(srv *Server, w http.ResponseWriter, r *http.Request, v
500 500
 	if err := parseForm(r); err != nil {
501 501
 		return err
502 502
 	}
503
-	logs := r.Form.Get("logs") == "1"
504
-	stream := r.Form.Get("stream") == "1"
505
-	stdin := r.Form.Get("stdin") == "1"
506
-	stdout := r.Form.Get("stdout") == "1"
507
-	stderr := r.Form.Get("stderr") == "1"
503
+	logs, err := getBoolParam(r.Form.Get("logs"))
504
+	if err != nil {
505
+		return err
506
+	}
507
+	stream, err := getBoolParam(r.Form.Get("stream"))
508
+	if err != nil {
509
+		return err
510
+	}
511
+	stdin, err := getBoolParam(r.Form.Get("stdin"))
512
+	if err != nil {
513
+		return err
514
+	}
515
+	stdout, err := getBoolParam(r.Form.Get("stdout"))
516
+	if err != nil {
517
+		return err
518
+	}
519
+	stderr, err := getBoolParam(r.Form.Get("stderr"))
520
+	if err != nil {
521
+		return err
522
+	}
523
+
508 524
 	if vars == nil {
509 525
 		return fmt.Errorf("Missing parameter")
510 526
 	}
... ...
@@ -602,20 +636,20 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
602 602
 
603 603
 	for method, routes := range m {
604 604
 		for route, fct := range routes {
605
-			Debugf("Registering %s, %s", method, route)
605
+			utils.Debugf("Registering %s, %s", method, route)
606 606
 			// NOTE: scope issue, make sure the variables are local and won't be changed
607 607
 			localRoute := route
608 608
 			localMethod := method
609 609
 			localFct := fct
610 610
 			r.Path(localRoute).Methods(localMethod).HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
611
-				Debugf("Calling %s %s", localMethod, localRoute)
611
+				utils.Debugf("Calling %s %s", localMethod, localRoute)
612 612
 				if logging {
613 613
 					log.Println(r.Method, r.RequestURI)
614 614
 				}
615 615
 				if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
616 616
 					userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
617 617
 					if len(userAgent) == 2 && userAgent[1] != VERSION {
618
-						Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
618
+						utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION)
619 619
 					}
620 620
 				}
621 621
 				if err := localFct(srv, w, r, mux.Vars(r)); err != nil {
... ...
@@ -6,6 +6,8 @@ import (
6 6
 	"bytes"
7 7
 	"encoding/json"
8 8
 	"github.com/dotcloud/docker/auth"
9
+	"github.com/dotcloud/docker/registry"
10
+	"github.com/dotcloud/docker/utils"
9 11
 	"io"
10 12
 	"net"
11 13
 	"net/http"
... ...
@@ -23,7 +25,10 @@ func TestGetAuth(t *testing.T) {
23 23
 	}
24 24
 	defer nuke(runtime)
25 25
 
26
-	srv := &Server{runtime: runtime}
26
+	srv := &Server{
27
+		runtime:  runtime,
28
+		registry: registry.NewRegistry(runtime.root),
29
+	}
27 30
 
28 31
 	r := httptest.NewRecorder()
29 32
 
... ...
@@ -46,13 +51,14 @@ func TestGetAuth(t *testing.T) {
46 46
 	if err := postAuth(srv, r, req, nil); err != nil {
47 47
 		t.Fatal(err)
48 48
 	}
49
+
49 50
 	if r.Code != http.StatusOK && r.Code != 0 {
50 51
 		t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code)
51 52
 	}
52 53
 
53
-	if runtime.authConfig.Username != authConfig.Username ||
54
-		runtime.authConfig.Password != authConfig.Password ||
55
-		runtime.authConfig.Email != authConfig.Email {
54
+	newAuthConfig := srv.registry.GetAuthConfig()
55
+	if newAuthConfig.Username != authConfig.Username ||
56
+		newAuthConfig.Email != authConfig.Email {
56 57
 		t.Fatalf("The auth configuration hasn't been set correctly")
57 58
 	}
58 59
 }
... ...
@@ -143,7 +149,7 @@ func TestGetImagesJson(t *testing.T) {
143 143
 	r2 := httptest.NewRecorder()
144 144
 
145 145
 	// all=1
146
-	req2, err := http.NewRequest("GET", "/images/json?all=1", nil)
146
+	req2, err := http.NewRequest("GET", "/images/json?all=true", nil)
147 147
 	if err != nil {
148 148
 		t.Fatal(err)
149 149
 	}
... ...
@@ -185,6 +191,24 @@ func TestGetImagesJson(t *testing.T) {
185 185
 	if len(images3) != 0 {
186 186
 		t.Errorf("Excepted 1 image, %d found", len(images3))
187 187
 	}
188
+
189
+	r4 := httptest.NewRecorder()
190
+
191
+	// all=foobar
192
+	req4, err := http.NewRequest("GET", "/images/json?all=foobar", nil)
193
+	if err != nil {
194
+		t.Fatal(err)
195
+	}
196
+
197
+	err = getImagesJson(srv, r4, req4, nil)
198
+	if err == nil {
199
+		t.Fatalf("Error expected, received none")
200
+	}
201
+
202
+	httpError(r4, err)
203
+	if r4.Code != http.StatusBadRequest {
204
+		t.Fatalf("%d Bad Request expected, received %d\n", http.StatusBadRequest, r4.Code)
205
+	}
188 206
 }
189 207
 
190 208
 func TestGetImagesViz(t *testing.T) {
... ...
@@ -222,7 +246,10 @@ func TestGetImagesSearch(t *testing.T) {
222 222
 	}
223 223
 	defer nuke(runtime)
224 224
 
225
-	srv := &Server{runtime: runtime}
225
+	srv := &Server{
226
+		runtime:  runtime,
227
+		registry: registry.NewRegistry(runtime.root),
228
+	}
226 229
 
227 230
 	r := httptest.NewRecorder()
228 231
 
... ...
@@ -476,13 +503,16 @@ func TestPostAuth(t *testing.T) {
476 476
 	}
477 477
 	defer nuke(runtime)
478 478
 
479
-	srv := &Server{runtime: runtime}
479
+	srv := &Server{
480
+		runtime:  runtime,
481
+		registry: registry.NewRegistry(runtime.root),
482
+	}
480 483
 
481 484
 	authConfigOrig := &auth.AuthConfig{
482 485
 		Username: "utest",
483 486
 		Email:    "utest@yopmail.com",
484 487
 	}
485
-	runtime.authConfig = authConfigOrig
488
+	srv.registry.ResetClient(authConfigOrig)
486 489
 
487 490
 	r := httptest.NewRecorder()
488 491
 	if err := getAuth(srv, r, nil, nil); err != nil {
... ...
@@ -811,7 +841,7 @@ func TestPostContainersCreate(t *testing.T) {
811 811
 
812 812
 	if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil {
813 813
 		if os.IsNotExist(err) {
814
-			Debugf("Err: %s", err)
814
+			utils.Debugf("Err: %s", err)
815 815
 			t.Fatalf("The test file has not been created")
816 816
 		}
817 817
 		t.Fatal(err)
... ...
@@ -15,13 +15,13 @@ import (
15 15
 const CONFIGFILE = ".dockercfg"
16 16
 
17 17
 // the registry server we want to login against
18
-const INDEX_SERVER = "https://index.docker.io"
18
+const INDEX_SERVER = "https://index.docker.io/v1"
19 19
 
20 20
 type AuthConfig struct {
21 21
 	Username string `json:"username"`
22 22
 	Password string `json:"password"`
23 23
 	Email    string `json:"email"`
24
-	rootPath string `json:-`
24
+	rootPath string
25 25
 }
26 26
 
27 27
 func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
... ...
@@ -33,6 +33,13 @@ func NewAuthConfig(username, password, email, rootPath string) *AuthConfig {
33 33
 	}
34 34
 }
35 35
 
36
+func IndexServerAddress() string {
37
+	if os.Getenv("DOCKER_INDEX_URL") != "" {
38
+		return os.Getenv("DOCKER_INDEX_URL") + "/v1"
39
+	}
40
+	return INDEX_SERVER
41
+}
42
+
36 43
 // create a base64 encoded auth string to store in config
37 44
 func EncodeAuth(authConfig *AuthConfig) string {
38 45
 	authStr := authConfig.Username + ":" + authConfig.Password
... ...
@@ -119,7 +126,7 @@ func Login(authConfig *AuthConfig) (string, error) {
119 119
 
120 120
 	// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
121 121
 	b := strings.NewReader(string(jsonBody))
122
-	req1, err := http.Post(INDEX_SERVER+"/v1/users/", "application/json; charset=utf-8", b)
122
+	req1, err := http.Post(IndexServerAddress()+"/users/", "application/json; charset=utf-8", b)
123 123
 	if err != nil {
124 124
 		return "", fmt.Errorf("Server Error: %s", err)
125 125
 	}
... ...
@@ -139,7 +146,7 @@ func Login(authConfig *AuthConfig) (string, error) {
139 139
 			"Please check your e-mail for a confirmation link.")
140 140
 	} else if reqStatusCode == 400 {
141 141
 		if string(reqBody) == "\"Username or email already exists\"" {
142
-			req, err := http.NewRequest("GET", INDEX_SERVER+"/v1/users/", nil)
142
+			req, err := http.NewRequest("GET", IndexServerAddress()+"/users/", nil)
143 143
 			req.SetBasicAuth(authConfig.Username, authConfig.Password)
144 144
 			resp, err := client.Do(req)
145 145
 			if err != nil {
... ...
@@ -1,6 +1,10 @@
1 1
 package auth
2 2
 
3 3
 import (
4
+	"crypto/rand"
5
+	"encoding/hex"
6
+	"os"
7
+	"strings"
4 8
 	"testing"
5 9
 )
6 10
 
... ...
@@ -21,3 +25,49 @@ func TestEncodeAuth(t *testing.T) {
21 21
 		t.Fatal("AuthString encoding isn't correct.")
22 22
 	}
23 23
 }
24
+
25
+func TestLogin(t *testing.T) {
26
+	os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
27
+	defer os.Setenv("DOCKER_INDEX_URL", "")
28
+	authConfig := NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", "/tmp")
29
+	status, err := Login(authConfig)
30
+	if err != nil {
31
+		t.Fatal(err)
32
+	}
33
+	if status != "Login Succeeded\n" {
34
+		t.Fatalf("Expected status \"Login Succeeded\", found \"%s\" instead", status)
35
+	}
36
+}
37
+
38
+func TestCreateAccount(t *testing.T) {
39
+	os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
40
+	defer os.Setenv("DOCKER_INDEX_URL", "")
41
+	tokenBuffer := make([]byte, 16)
42
+	_, err := rand.Read(tokenBuffer)
43
+	if err != nil {
44
+		t.Fatal(err)
45
+	}
46
+	token := hex.EncodeToString(tokenBuffer)[:12]
47
+	username := "ut" + token
48
+	authConfig := NewAuthConfig(username, "test42", "docker-ut+"+token+"@example.com", "/tmp")
49
+	status, err := Login(authConfig)
50
+	if err != nil {
51
+		t.Fatal(err)
52
+	}
53
+	expectedStatus := "Account created. Please use the confirmation link we sent" +
54
+		" to your e-mail to activate it.\n"
55
+	if status != expectedStatus {
56
+		t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status)
57
+	}
58
+
59
+	status, err = Login(authConfig)
60
+	if err == nil {
61
+		t.Fatalf("Expected error but found nil instead")
62
+	}
63
+
64
+	expectedError := "Login: Account is not Active"
65
+
66
+	if !strings.Contains(err.Error(), expectedError) {
67
+		t.Fatalf("Expected message \"%s\" but found \"%s\" instead", expectedError, err.Error())
68
+	}
69
+}
24 70
deleted file mode 100644
... ...
@@ -1,20 +0,0 @@
1
-Buildbot
2
-========
3
-
4
-Buildbot is a continuous integration system designed to automate the
5
-build/test cycle. By automatically rebuilding and testing the tree each time
6
-something has changed, build problems are pinpointed quickly, before other
7
-developers are inconvenienced by the failure.
8
-
9
-When running 'make hack' at the docker root directory, it spawns a virtual
10
-machine in the background running a buildbot instance and adds a git
11
-post-commit hook that automatically run docker tests for you.
12
-
13
-You can check your buildbot instance at http://192.168.33.21:8010/waterfall
14
-
15
-
16
-Buildbot dependencies
17
-
18
-vagrant, virtualbox packages and python package requests
19
-
20 1
deleted file mode 100644
... ...
@@ -1,28 +0,0 @@
1
-# -*- mode: ruby -*-
2
-# vi: set ft=ruby :
3
-
4
-$BUILDBOT_IP = '192.168.33.21'
5
-
6
-def v10(config)
7
-  config.vm.box = "quantal64_3.5.0-25"
8
-  config.vm.box_url = "http://get.docker.io/vbox/ubuntu/12.10/quantal64_3.5.0-25.box"
9
-  config.vm.share_folder 'v-data', '/data/docker', File.dirname(__FILE__) + '/..'
10
-  config.vm.network :hostonly, $BUILDBOT_IP
11
-
12
-  # Ensure puppet is installed on the instance
13
-  config.vm.provision :shell, :inline => 'apt-get -qq update; apt-get install -y puppet'
14
-
15
-  config.vm.provision :puppet do |puppet|
16
-    puppet.manifests_path = '.'
17
-    puppet.manifest_file  = 'buildbot.pp'
18
-    puppet.options = ['--templatedir','.']
19
-  end
20
-end
21
-
22
-Vagrant::VERSION < '1.1.0' and Vagrant::Config.run do |config|
23
-  v10(config)
24
-end
25
-
26
-Vagrant::VERSION >= '1.1.0' and Vagrant.configure('1') do |config|
27
-  v10(config)
28
-end
29 1
deleted file mode 100755
... ...
@@ -1,43 +0,0 @@
1
-#!/bin/bash
2
-
3
-# Auto setup of buildbot configuration. Package installation is being done
4
-# on buildbot.pp
5
-# Dependencies: buildbot, buildbot-slave, supervisor
6
-
7
-SLAVE_NAME='buildworker'
8
-SLAVE_SOCKET='localhost:9989'
9
-BUILDBOT_PWD='pass-docker'
10
-USER='vagrant'
11
-ROOT_PATH='/data/buildbot'
12
-DOCKER_PATH='/data/docker'
13
-BUILDBOT_CFG="$DOCKER_PATH/buildbot/buildbot-cfg"
14
-IP=$(grep BUILDBOT_IP /data/docker/buildbot/Vagrantfile | awk -F "'" '{ print $2; }')
15
-
16
-function run { su $USER -c "$1"; }
17
-
18
-export PATH=/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin
19
-
20
-# Exit if buildbot has already been installed
21
-[ -d "$ROOT_PATH" ] && exit 0
22
-
23
-# Setup buildbot
24
-run "mkdir -p ${ROOT_PATH}"
25
-cd ${ROOT_PATH}
26
-run "buildbot create-master master"
27
-run "cp $BUILDBOT_CFG/master.cfg master"
28
-run "sed -i 's/localhost/$IP/' master/master.cfg"
29
-run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD"
30
-
31
-# Allow buildbot subprocesses (docker tests) to properly run in containers,
32
-# in particular with docker -u
33
-run "sed -i 's/^umask = None/umask = 000/' ${ROOT_PATH}/slave/buildbot.tac"
34
-
35
-# Setup supervisor
36
-cp $BUILDBOT_CFG/buildbot.conf /etc/supervisor/conf.d/buildbot.conf
37
-sed -i "s/^chmod=0700.*0700./chmod=0770\nchown=root:$USER/" /etc/supervisor/supervisord.conf
38
-kill -HUP `pgrep -f "/usr/bin/python /usr/bin/supervisord"`
39
-
40
-# Add git hook
41
-cp $BUILDBOT_CFG/post-commit $DOCKER_PATH/.git/hooks
42
-sed -i "s/localhost/$IP/" $DOCKER_PATH/.git/hooks/post-commit
43
-
44 1
deleted file mode 100644
... ...
@@ -1,32 +0,0 @@
1
-node default {
2
-    $USER = 'vagrant'
3
-    $ROOT_PATH = '/data/buildbot'
4
-    $DOCKER_PATH = '/data/docker'
5
-
6
-    exec {'apt_update': command => '/usr/bin/apt-get update' }
7
-    Package { require => Exec['apt_update'] }
8
-    group {'puppet': ensure => 'present'}
9
-
10
-    # Install dependencies
11
-    Package { ensure => 'installed' }
12
-    package { ['python-dev','python-pip','supervisor','lxc','bsdtar','git','golang']: }
13
-
14
-    file{[ '/data' ]:
15
-        owner => $USER, group => $USER, ensure => 'directory' }
16
-
17
-    file {'/var/tmp/requirements.txt':
18
-        content => template('requirements.txt') }
19
-
20
-    exec {'requirements':
21
-        require => [ Package['python-dev'], Package['python-pip'],
22
-            File['/var/tmp/requirements.txt'] ],
23
-        cwd     => '/var/tmp',
24
-        command => "/bin/sh -c '(/usr/bin/pip install -r requirements.txt;
25
-            rm /var/tmp/requirements.txt)'" }
26
-
27
-    exec {'buildbot-cfg-sh':
28
-        require => [ Package['supervisor'], Exec['requirements']],
29
-        path    => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin',
30
-        cwd     => '/data',
31
-        command => "$DOCKER_PATH/buildbot/buildbot-cfg/buildbot-cfg.sh" }
32
-}
33 1
deleted file mode 100644
... ...
@@ -1,6 +0,0 @@
1
-sqlalchemy<=0.7.9
2
-sqlalchemy-migrate>=0.7.2
3
-buildbot==0.8.7p1
4
-buildbot_slave==0.8.7p1
5
-nose==1.2.1
6
-requests==1.1.0
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"bufio"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"github.com/dotcloud/docker/utils"
7 8
 	"io"
8 9
 	"os"
9 10
 	"path"
... ...
@@ -45,7 +46,7 @@ func (builder *Builder) mergeConfig(userConf, imageConf *Config) {
45 45
 		userConf.PortSpecs = imageConf.PortSpecs
46 46
 	}
47 47
 	if !userConf.Tty {
48
-		userConf.Tty = userConf.Tty
48
+		userConf.Tty = imageConf.Tty
49 49
 	}
50 50
 	if !userConf.OpenStdin {
51 51
 		userConf.OpenStdin = imageConf.OpenStdin
... ...
@@ -161,11 +162,11 @@ func (builder *Builder) clearTmp(containers, images map[string]struct{}) {
161 161
 	for c := range containers {
162 162
 		tmp := builder.runtime.Get(c)
163 163
 		builder.runtime.Destroy(tmp)
164
-		Debugf("Removing container %s", c)
164
+		utils.Debugf("Removing container %s", c)
165 165
 	}
166 166
 	for i := range images {
167 167
 		builder.runtime.graph.Delete(i)
168
-		Debugf("Removing image %s", i)
168
+		utils.Debugf("Removing image %s", i)
169 169
 	}
170 170
 }
171 171
 
... ...
@@ -234,28 +235,29 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
234 234
 			fmt.Fprintf(stdout, "FROM %s\n", arguments)
235 235
 			image, err = builder.runtime.repositories.LookupImage(arguments)
236 236
 			if err != nil {
237
-				if builder.runtime.graph.IsNotExist(err) {
238
-
239
-					var tag, remote string
240
-					if strings.Contains(arguments, ":") {
241
-						remoteParts := strings.Split(arguments, ":")
242
-						tag = remoteParts[1]
243
-						remote = remoteParts[0]
244
-					} else {
245
-						remote = arguments
246
-					}
247
-
248
-					if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
249
-						return nil, err
250
-					}
251
-
252
-					image, err = builder.runtime.repositories.LookupImage(arguments)
253
-					if err != nil {
254
-						return nil, err
255
-					}
256
-				} else {
257
-					return nil, err
258
-				}
237
+				// if builder.runtime.graph.IsNotExist(err) {
238
+
239
+				// 	var tag, remote string
240
+				// 	if strings.Contains(arguments, ":") {
241
+				// 		remoteParts := strings.Split(arguments, ":")
242
+				// 		tag = remoteParts[1]
243
+				// 		remote = remoteParts[0]
244
+				// 	} else {
245
+				// 		remote = arguments
246
+				// 	}
247
+
248
+				// 	panic("TODO: reimplement this")
249
+				// 	// if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
250
+				// 	// 	return nil, err
251
+				// 	// }
252
+
253
+				// 	image, err = builder.runtime.repositories.LookupImage(arguments)
254
+				// 	if err != nil {
255
+				// 		return nil, err
256
+				// 	}
257
+				// } else {
258
+				return nil, err
259
+				// }
259 260
 			}
260 261
 			config = &Config{}
261 262
 
... ...
@@ -286,7 +288,7 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
286 286
 				break
287 287
 			}
288 288
 
289
-			Debugf("Env -----> %v ------ %v\n", config.Env, env)
289
+			utils.Debugf("Env -----> %v ------ %v\n", config.Env, env)
290 290
 
291 291
 			// Create the container and start it
292 292
 			c, err := builder.Create(config)
... ...
@@ -410,7 +412,7 @@ func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, e
410 410
 			destPath := strings.Trim(tmp[1], " ")
411 411
 			fmt.Fprintf(stdout, "COPY %s to %s in %s\n", sourceUrl, destPath, base.ShortId())
412 412
 
413
-			file, err := Download(sourceUrl, stdout)
413
+			file, err := utils.Download(sourceUrl, stdout)
414 414
 			if err != nil {
415 415
 				return nil, err
416 416
 			}
... ...
@@ -1,6 +1,7 @@
1 1
 package docker
2 2
 
3 3
 import (
4
+	"github.com/dotcloud/docker/utils"
4 5
 	"strings"
5 6
 	"testing"
6 7
 )
... ...
@@ -24,7 +25,7 @@ func TestBuild(t *testing.T) {
24 24
 
25 25
 	builder := NewBuilder(runtime)
26 26
 
27
-	img, err := builder.Build(strings.NewReader(Dockerfile), &nopWriter{})
27
+	img, err := builder.Build(strings.NewReader(Dockerfile), &utils.NopWriter{})
28 28
 	if err != nil {
29 29
 		t.Fatal(err)
30 30
 	}
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"fmt"
8 8
 	"github.com/dotcloud/docker/auth"
9 9
 	"github.com/dotcloud/docker/term"
10
+	"github.com/dotcloud/docker/utils"
10 11
 	"io"
11 12
 	"io/ioutil"
12 13
 	"net"
... ...
@@ -15,6 +16,7 @@ import (
15 15
 	"net/url"
16 16
 	"os"
17 17
 	"path/filepath"
18
+	"reflect"
18 19
 	"strconv"
19 20
 	"strings"
20 21
 	"text/tabwriter"
... ...
@@ -29,50 +31,28 @@ var (
29 29
 )
30 30
 
31 31
 func ParseCommands(args ...string) error {
32
-
33
-	cmds := map[string]func(args ...string) error{
34
-		"attach":  CmdAttach,
35
-		"build":   CmdBuild,
36
-		"commit":  CmdCommit,
37
-		"diff":    CmdDiff,
38
-		"export":  CmdExport,
39
-		"images":  CmdImages,
40
-		"info":    CmdInfo,
41
-		"insert":  CmdInsert,
42
-		"inspect": CmdInspect,
43
-		"import":  CmdImport,
44
-		"history": CmdHistory,
45
-		"kill":    CmdKill,
46
-		"login":   CmdLogin,
47
-		"logs":    CmdLogs,
48
-		"port":    CmdPort,
49
-		"ps":      CmdPs,
50
-		"pull":    CmdPull,
51
-		"push":    CmdPush,
52
-		"restart": CmdRestart,
53
-		"rm":      CmdRm,
54
-		"rmi":     CmdRmi,
55
-		"run":     CmdRun,
56
-		"tag":     CmdTag,
57
-		"search":  CmdSearch,
58
-		"start":   CmdStart,
59
-		"stop":    CmdStop,
60
-		"version": CmdVersion,
61
-		"wait":    CmdWait,
62
-	}
32
+	cli := NewDockerCli("0.0.0.0", 4243)
63 33
 
64 34
 	if len(args) > 0 {
65
-		cmd, exists := cmds[args[0]]
35
+		methodName := "Cmd" + strings.ToUpper(args[0][:1]) + strings.ToLower(args[0][1:])
36
+		method, exists := reflect.TypeOf(cli).MethodByName(methodName)
66 37
 		if !exists {
67 38
 			fmt.Println("Error: Command not found:", args[0])
68
-			return cmdHelp(args...)
39
+			return cli.CmdHelp(args...)
69 40
 		}
70
-		return cmd(args[1:]...)
41
+		ret := method.Func.CallSlice([]reflect.Value{
42
+			reflect.ValueOf(cli),
43
+			reflect.ValueOf(args[1:]),
44
+		})[0].Interface()
45
+		if ret == nil {
46
+			return nil
47
+		}
48
+		return ret.(error)
71 49
 	}
72
-	return cmdHelp(args...)
50
+	return cli.CmdHelp(args...)
73 51
 }
74 52
 
75
-func cmdHelp(args ...string) error {
53
+func (cli *DockerCli) CmdHelp(args ...string) error {
76 54
 	help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
77 55
 	for _, cmd := range [][]string{
78 56
 		{"attach", "Attach to a running container"},
... ...
@@ -110,7 +90,7 @@ func cmdHelp(args ...string) error {
110 110
 	return nil
111 111
 }
112 112
 
113
-func CmdInsert(args ...string) error {
113
+func (cli *DockerCli) CmdInsert(args ...string) error {
114 114
 	cmd := Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
115 115
 	if err := cmd.Parse(args); err != nil {
116 116
 		return nil
... ...
@@ -124,20 +104,20 @@ func CmdInsert(args ...string) error {
124 124
 	v.Set("url", cmd.Arg(1))
125 125
 	v.Set("path", cmd.Arg(2))
126 126
 
127
-	err := hijack("POST", "/images/"+cmd.Arg(0)+"?"+v.Encode(), false)
127
+	err := cli.hijack("POST", "/images/"+cmd.Arg(0)+"?"+v.Encode(), false)
128 128
 	if err != nil {
129 129
 		return err
130 130
 	}
131 131
 	return nil
132 132
 }
133 133
 
134
-func CmdBuild(args ...string) error {
134
+func (cli *DockerCli) CmdBuild(args ...string) error {
135 135
 	cmd := Subcmd("build", "-", "Build an image from Dockerfile via stdin")
136 136
 	if err := cmd.Parse(args); err != nil {
137 137
 		return nil
138 138
 	}
139 139
 
140
-	err := hijack("POST", "/build", false)
140
+	err := cli.hijack("POST", "/build", false)
141 141
 	if err != nil {
142 142
 		return err
143 143
 	}
... ...
@@ -145,7 +125,7 @@ func CmdBuild(args ...string) error {
145 145
 }
146 146
 
147 147
 // 'docker login': login / register a user to registry service.
148
-func CmdLogin(args ...string) error {
148
+func (cli *DockerCli) CmdLogin(args ...string) error {
149 149
 	var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string {
150 150
 		char := make([]byte, 1)
151 151
 		buffer := make([]byte, 64)
... ...
@@ -188,11 +168,11 @@ func CmdLogin(args ...string) error {
188 188
 		return readStringOnRawTerminal(stdin, stdout, false)
189 189
 	}
190 190
 
191
-	oldState, err := SetRawTerminal()
191
+	oldState, err := term.SetRawTerminal()
192 192
 	if err != nil {
193 193
 		return err
194 194
 	} else {
195
-		defer RestoreTerminal(oldState)
195
+		defer term.RestoreTerminal(oldState)
196 196
 	}
197 197
 
198 198
 	cmd := Subcmd("login", "", "Register or Login to the docker registry server")
... ...
@@ -200,7 +180,7 @@ func CmdLogin(args ...string) error {
200 200
 		return nil
201 201
 	}
202 202
 
203
-	body, _, err := call("GET", "/auth", nil)
203
+	body, _, err := cli.call("GET", "/auth", nil)
204 204
 	if err != nil {
205 205
 		return err
206 206
 	}
... ...
@@ -241,7 +221,7 @@ func CmdLogin(args ...string) error {
241 241
 	out.Password = password
242 242
 	out.Email = email
243 243
 
244
-	body, _, err = call("POST", "/auth", out)
244
+	body, _, err = cli.call("POST", "/auth", out)
245 245
 	if err != nil {
246 246
 		return err
247 247
 	}
... ...
@@ -252,14 +232,14 @@ func CmdLogin(args ...string) error {
252 252
 		return err
253 253
 	}
254 254
 	if out2.Status != "" {
255
-		RestoreTerminal(oldState)
255
+		term.RestoreTerminal(oldState)
256 256
 		fmt.Print(out2.Status)
257 257
 	}
258 258
 	return nil
259 259
 }
260 260
 
261 261
 // 'docker wait': block until a container stops
262
-func CmdWait(args ...string) error {
262
+func (cli *DockerCli) CmdWait(args ...string) error {
263 263
 	cmd := Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
264 264
 	if err := cmd.Parse(args); err != nil {
265 265
 		return nil
... ...
@@ -269,7 +249,7 @@ func CmdWait(args ...string) error {
269 269
 		return nil
270 270
 	}
271 271
 	for _, name := range cmd.Args() {
272
-		body, _, err := call("POST", "/containers/"+name+"/wait", nil)
272
+		body, _, err := cli.call("POST", "/containers/"+name+"/wait", nil)
273 273
 		if err != nil {
274 274
 			fmt.Printf("%s", err)
275 275
 		} else {
... ...
@@ -285,17 +265,20 @@ func CmdWait(args ...string) error {
285 285
 }
286 286
 
287 287
 // 'docker version': show version information
288
-func CmdVersion(args ...string) error {
288
+func (cli *DockerCli) CmdVersion(args ...string) error {
289 289
 	cmd := Subcmd("version", "", "Show the docker version information.")
290
+	fmt.Println(len(args))
290 291
 	if err := cmd.Parse(args); err != nil {
291 292
 		return nil
292 293
 	}
294
+
295
+	fmt.Println(cmd.NArg())
293 296
 	if cmd.NArg() > 0 {
294 297
 		cmd.Usage()
295 298
 		return nil
296 299
 	}
297 300
 
298
-	body, _, err := call("GET", "/version", nil)
301
+	body, _, err := cli.call("GET", "/version", nil)
299 302
 	if err != nil {
300 303
 		return err
301 304
 	}
... ...
@@ -303,7 +286,7 @@ func CmdVersion(args ...string) error {
303 303
 	var out ApiVersion
304 304
 	err = json.Unmarshal(body, &out)
305 305
 	if err != nil {
306
-		Debugf("Error unmarshal: body: %s, err: %s\n", body, err)
306
+		utils.Debugf("Error unmarshal: body: %s, err: %s\n", body, err)
307 307
 		return err
308 308
 	}
309 309
 	fmt.Println("Version:", out.Version)
... ...
@@ -319,7 +302,7 @@ func CmdVersion(args ...string) error {
319 319
 }
320 320
 
321 321
 // 'docker info': display system-wide information.
322
-func CmdInfo(args ...string) error {
322
+func (cli *DockerCli) CmdInfo(args ...string) error {
323 323
 	cmd := Subcmd("info", "", "Display system-wide information")
324 324
 	if err := cmd.Parse(args); err != nil {
325 325
 		return nil
... ...
@@ -329,7 +312,7 @@ func CmdInfo(args ...string) error {
329 329
 		return nil
330 330
 	}
331 331
 
332
-	body, _, err := call("GET", "/info", nil)
332
+	body, _, err := cli.call("GET", "/info", nil)
333 333
 	if err != nil {
334 334
 		return err
335 335
 	}
... ...
@@ -347,7 +330,7 @@ func CmdInfo(args ...string) error {
347 347
 	return nil
348 348
 }
349 349
 
350
-func CmdStop(args ...string) error {
350
+func (cli *DockerCli) CmdStop(args ...string) error {
351 351
 	cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container")
352 352
 	nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
353 353
 	if err := cmd.Parse(args); err != nil {
... ...
@@ -362,7 +345,7 @@ func CmdStop(args ...string) error {
362 362
 	v.Set("t", strconv.Itoa(*nSeconds))
363 363
 
364 364
 	for _, name := range cmd.Args() {
365
-		_, _, err := call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil)
365
+		_, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil)
366 366
 		if err != nil {
367 367
 			fmt.Printf("%s", err)
368 368
 		} else {
... ...
@@ -372,7 +355,7 @@ func CmdStop(args ...string) error {
372 372
 	return nil
373 373
 }
374 374
 
375
-func CmdRestart(args ...string) error {
375
+func (cli *DockerCli) CmdRestart(args ...string) error {
376 376
 	cmd := Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
377 377
 	nSeconds := cmd.Int("t", 10, "wait t seconds before killing the container")
378 378
 	if err := cmd.Parse(args); err != nil {
... ...
@@ -387,7 +370,7 @@ func CmdRestart(args ...string) error {
387 387
 	v.Set("t", strconv.Itoa(*nSeconds))
388 388
 
389 389
 	for _, name := range cmd.Args() {
390
-		_, _, err := call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil)
390
+		_, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil)
391 391
 		if err != nil {
392 392
 			fmt.Printf("%s", err)
393 393
 		} else {
... ...
@@ -397,7 +380,7 @@ func CmdRestart(args ...string) error {
397 397
 	return nil
398 398
 }
399 399
 
400
-func CmdStart(args ...string) error {
400
+func (cli *DockerCli) CmdStart(args ...string) error {
401 401
 	cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
402 402
 	if err := cmd.Parse(args); err != nil {
403 403
 		return nil
... ...
@@ -408,7 +391,7 @@ func CmdStart(args ...string) error {
408 408
 	}
409 409
 
410 410
 	for _, name := range args {
411
-		_, _, err := call("POST", "/containers/"+name+"/start", nil)
411
+		_, _, err := cli.call("POST", "/containers/"+name+"/start", nil)
412 412
 		if err != nil {
413 413
 			fmt.Printf("%s", err)
414 414
 		} else {
... ...
@@ -418,7 +401,7 @@ func CmdStart(args ...string) error {
418 418
 	return nil
419 419
 }
420 420
 
421
-func CmdInspect(args ...string) error {
421
+func (cli *DockerCli) CmdInspect(args ...string) error {
422 422
 	cmd := Subcmd("inspect", "CONTAINER|IMAGE", "Return low-level information on a container/image")
423 423
 	if err := cmd.Parse(args); err != nil {
424 424
 		return nil
... ...
@@ -427,9 +410,9 @@ func CmdInspect(args ...string) error {
427 427
 		cmd.Usage()
428 428
 		return nil
429 429
 	}
430
-	obj, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
430
+	obj, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
431 431
 	if err != nil {
432
-		obj, _, err = call("GET", "/images/"+cmd.Arg(0)+"/json", nil)
432
+		obj, _, err = cli.call("GET", "/images/"+cmd.Arg(0)+"/json", nil)
433 433
 		if err != nil {
434 434
 			return err
435 435
 		}
... ...
@@ -445,7 +428,7 @@ func CmdInspect(args ...string) error {
445 445
 	return nil
446 446
 }
447 447
 
448
-func CmdPort(args ...string) error {
448
+func (cli *DockerCli) CmdPort(args ...string) error {
449 449
 	cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
450 450
 	if err := cmd.Parse(args); err != nil {
451 451
 		return nil
... ...
@@ -455,7 +438,7 @@ func CmdPort(args ...string) error {
455 455
 		return nil
456 456
 	}
457 457
 
458
-	body, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
458
+	body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
459 459
 	if err != nil {
460 460
 		return err
461 461
 	}
... ...
@@ -474,7 +457,7 @@ func CmdPort(args ...string) error {
474 474
 }
475 475
 
476 476
 // 'docker rmi IMAGE' removes all images with the name IMAGE
477
-func CmdRmi(args ...string) error {
477
+func (cli *DockerCli) CmdRmi(args ...string) error {
478 478
 	cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image")
479 479
 	if err := cmd.Parse(args); err != nil {
480 480
 		return nil
... ...
@@ -485,7 +468,7 @@ func CmdRmi(args ...string) error {
485 485
 	}
486 486
 
487 487
 	for _, name := range cmd.Args() {
488
-		_, _, err := call("DELETE", "/images/"+name, nil)
488
+		_, _, err := cli.call("DELETE", "/images/"+name, nil)
489 489
 		if err != nil {
490 490
 			fmt.Printf("%s", err)
491 491
 		} else {
... ...
@@ -495,7 +478,7 @@ func CmdRmi(args ...string) error {
495 495
 	return nil
496 496
 }
497 497
 
498
-func CmdHistory(args ...string) error {
498
+func (cli *DockerCli) CmdHistory(args ...string) error {
499 499
 	cmd := Subcmd("history", "IMAGE", "Show the history of an image")
500 500
 	if err := cmd.Parse(args); err != nil {
501 501
 		return nil
... ...
@@ -505,7 +488,7 @@ func CmdHistory(args ...string) error {
505 505
 		return nil
506 506
 	}
507 507
 
508
-	body, _, err := call("GET", "/images/"+cmd.Arg(0)+"/history", nil)
508
+	body, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil)
509 509
 	if err != nil {
510 510
 		return err
511 511
 	}
... ...
@@ -519,13 +502,13 @@ func CmdHistory(args ...string) error {
519 519
 	fmt.Fprintln(w, "ID\tCREATED\tCREATED BY")
520 520
 
521 521
 	for _, out := range outs {
522
-		fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.Id, HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
522
+		fmt.Fprintf(w, "%s\t%s ago\t%s\n", out.Id, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.CreatedBy)
523 523
 	}
524 524
 	w.Flush()
525 525
 	return nil
526 526
 }
527 527
 
528
-func CmdRm(args ...string) error {
528
+func (cli *DockerCli) CmdRm(args ...string) error {
529 529
 	cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove a container")
530 530
 	v := cmd.Bool("v", false, "Remove the volumes associated to the container")
531 531
 	if err := cmd.Parse(args); err != nil {
... ...
@@ -540,7 +523,7 @@ func CmdRm(args ...string) error {
540 540
 		val.Set("v", "1")
541 541
 	}
542 542
 	for _, name := range cmd.Args() {
543
-		_, _, err := call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
543
+		_, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)
544 544
 		if err != nil {
545 545
 			fmt.Printf("%s", err)
546 546
 		} else {
... ...
@@ -551,7 +534,7 @@ func CmdRm(args ...string) error {
551 551
 }
552 552
 
553 553
 // 'docker kill NAME' kills a running container
554
-func CmdKill(args ...string) error {
554
+func (cli *DockerCli) CmdKill(args ...string) error {
555 555
 	cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container")
556 556
 	if err := cmd.Parse(args); err != nil {
557 557
 		return nil
... ...
@@ -562,7 +545,7 @@ func CmdKill(args ...string) error {
562 562
 	}
563 563
 
564 564
 	for _, name := range args {
565
-		_, _, err := call("POST", "/containers/"+name+"/kill", nil)
565
+		_, _, err := cli.call("POST", "/containers/"+name+"/kill", nil)
566 566
 		if err != nil {
567 567
 			fmt.Printf("%s", err)
568 568
 		} else {
... ...
@@ -572,7 +555,7 @@ func CmdKill(args ...string) error {
572 572
 	return nil
573 573
 }
574 574
 
575
-func CmdImport(args ...string) error {
575
+func (cli *DockerCli) CmdImport(args ...string) error {
576 576
 	cmd := Subcmd("import", "URL|- [REPOSITORY [TAG]]", "Create a new filesystem image from the contents of a tarball")
577 577
 
578 578
 	if err := cmd.Parse(args); err != nil {
... ...
@@ -588,14 +571,14 @@ func CmdImport(args ...string) error {
588 588
 	v.Set("tag", tag)
589 589
 	v.Set("fromSrc", src)
590 590
 
591
-	err := hijack("POST", "/images/create?"+v.Encode(), false)
591
+	err := cli.hijack("POST", "/images/create?"+v.Encode(), false)
592 592
 	if err != nil {
593 593
 		return err
594 594
 	}
595 595
 	return nil
596 596
 }
597 597
 
598
-func CmdPush(args ...string) error {
598
+func (cli *DockerCli) CmdPush(args ...string) error {
599 599
 	cmd := Subcmd("push", "[OPTION] NAME", "Push an image or a repository to the registry")
600 600
 	registry := cmd.String("registry", "", "Registry host to push the image to")
601 601
 	if err := cmd.Parse(args); err != nil {
... ...
@@ -608,7 +591,7 @@ func CmdPush(args ...string) error {
608 608
 		return nil
609 609
 	}
610 610
 
611
-	body, _, err := call("GET", "/auth", nil)
611
+	body, _, err := cli.call("GET", "/auth", nil)
612 612
 	if err != nil {
613 613
 		return err
614 614
 	}
... ...
@@ -621,11 +604,11 @@ func CmdPush(args ...string) error {
621 621
 
622 622
 	// If the login failed AND we're using the index, abort
623 623
 	if *registry == "" && out.Username == "" {
624
-		if err := CmdLogin(args...); err != nil {
624
+		if err := cli.CmdLogin(args...); err != nil {
625 625
 			return err
626 626
 		}
627 627
 
628
-		body, _, err = call("GET", "/auth", nil)
628
+		body, _, err = cli.call("GET", "/auth", nil)
629 629
 		if err != nil {
630 630
 			return err
631 631
 		}
... ...
@@ -645,13 +628,13 @@ func CmdPush(args ...string) error {
645 645
 
646 646
 	v := url.Values{}
647 647
 	v.Set("registry", *registry)
648
-	if err := hijack("POST", "/images/"+name+"/push?"+v.Encode(), false); err != nil {
648
+	if err := cli.hijack("POST", "/images/"+name+"/push?"+v.Encode(), false); err != nil {
649 649
 		return err
650 650
 	}
651 651
 	return nil
652 652
 }
653 653
 
654
-func CmdPull(args ...string) error {
654
+func (cli *DockerCli) CmdPull(args ...string) error {
655 655
 	cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
656 656
 	tag := cmd.String("t", "", "Download tagged image in repository")
657 657
 	registry := cmd.String("registry", "", "Registry to download from. Necessary if image is pulled by ID")
... ...
@@ -676,14 +659,14 @@ func CmdPull(args ...string) error {
676 676
 	v.Set("tag", *tag)
677 677
 	v.Set("registry", *registry)
678 678
 
679
-	if err := hijack("POST", "/images/create?"+v.Encode(), false); err != nil {
679
+	if err := cli.hijack("POST", "/images/create?"+v.Encode(), false); err != nil {
680 680
 		return err
681 681
 	}
682 682
 
683 683
 	return nil
684 684
 }
685 685
 
686
-func CmdImages(args ...string) error {
686
+func (cli *DockerCli) CmdImages(args ...string) error {
687 687
 	cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images")
688 688
 	quiet := cmd.Bool("q", false, "only show numeric IDs")
689 689
 	all := cmd.Bool("a", false, "show all images")
... ...
@@ -699,7 +682,7 @@ func CmdImages(args ...string) error {
699 699
 	}
700 700
 
701 701
 	if *flViz {
702
-		body, _, err := call("GET", "/images/viz", false)
702
+		body, _, err := cli.call("GET", "/images/viz", false)
703 703
 		if err != nil {
704 704
 			return err
705 705
 		}
... ...
@@ -713,7 +696,7 @@ func CmdImages(args ...string) error {
713 713
 			v.Set("all", "1")
714 714
 		}
715 715
 
716
-		body, _, err := call("GET", "/images/json?"+v.Encode(), nil)
716
+		body, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil)
717 717
 		if err != nil {
718 718
 			return err
719 719
 		}
... ...
@@ -742,14 +725,14 @@ func CmdImages(args ...string) error {
742 742
 				if *noTrunc {
743 743
 					fmt.Fprintf(w, "%s\t", out.Id)
744 744
 				} else {
745
-					fmt.Fprintf(w, "%s\t", TruncateId(out.Id))
745
+					fmt.Fprintf(w, "%s\t", utils.TruncateId(out.Id))
746 746
 				}
747
-				fmt.Fprintf(w, "%s ago\n", HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
747
+				fmt.Fprintf(w, "%s ago\n", utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
748 748
 			} else {
749 749
 				if *noTrunc {
750 750
 					fmt.Fprintln(w, out.Id)
751 751
 				} else {
752
-					fmt.Fprintln(w, TruncateId(out.Id))
752
+					fmt.Fprintln(w, utils.TruncateId(out.Id))
753 753
 				}
754 754
 			}
755 755
 		}
... ...
@@ -761,7 +744,7 @@ func CmdImages(args ...string) error {
761 761
 	return nil
762 762
 }
763 763
 
764
-func CmdPs(args ...string) error {
764
+func (cli *DockerCli) CmdPs(args ...string) error {
765 765
 	cmd := Subcmd("ps", "[OPTIONS]", "List containers")
766 766
 	quiet := cmd.Bool("q", false, "Only display numeric IDs")
767 767
 	all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.")
... ...
@@ -791,7 +774,7 @@ func CmdPs(args ...string) error {
791 791
 		v.Set("before", *before)
792 792
 	}
793 793
 
794
-	body, _, err := call("GET", "/containers/ps?"+v.Encode(), nil)
794
+	body, _, err := cli.call("GET", "/containers/ps?"+v.Encode(), nil)
795 795
 	if err != nil {
796 796
 		return err
797 797
 	}
... ...
@@ -809,15 +792,15 @@ func CmdPs(args ...string) error {
809 809
 	for _, out := range outs {
810 810
 		if !*quiet {
811 811
 			if *noTrunc {
812
-				fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", out.Id, out.Image, out.Command, out.Status, HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
812
+				fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", out.Id, out.Image, out.Command, out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
813 813
 			} else {
814
-				fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", TruncateId(out.Id), out.Image, Trunc(out.Command, 20), out.Status, HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
814
+				fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), out.Status, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Ports)
815 815
 			}
816 816
 		} else {
817 817
 			if *noTrunc {
818 818
 				fmt.Fprintln(w, out.Id)
819 819
 			} else {
820
-				fmt.Fprintln(w, TruncateId(out.Id))
820
+				fmt.Fprintln(w, utils.TruncateId(out.Id))
821 821
 			}
822 822
 		}
823 823
 	}
... ...
@@ -828,7 +811,7 @@ func CmdPs(args ...string) error {
828 828
 	return nil
829 829
 }
830 830
 
831
-func CmdCommit(args ...string) error {
831
+func (cli *DockerCli) CmdCommit(args ...string) error {
832 832
 	cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", "Create a new image from a container's changes")
833 833
 	flComment := cmd.String("m", "", "Commit message")
834 834
 	flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
... ...
@@ -855,7 +838,7 @@ func CmdCommit(args ...string) error {
855 855
 			return err
856 856
 		}
857 857
 	}
858
-	body, _, err := call("POST", "/commit?"+v.Encode(), config)
858
+	body, _, err := cli.call("POST", "/commit?"+v.Encode(), config)
859 859
 	if err != nil {
860 860
 		return err
861 861
 	}
... ...
@@ -870,7 +853,7 @@ func CmdCommit(args ...string) error {
870 870
 	return nil
871 871
 }
872 872
 
873
-func CmdExport(args ...string) error {
873
+func (cli *DockerCli) CmdExport(args ...string) error {
874 874
 	cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
875 875
 	if err := cmd.Parse(args); err != nil {
876 876
 		return nil
... ...
@@ -881,13 +864,13 @@ func CmdExport(args ...string) error {
881 881
 		return nil
882 882
 	}
883 883
 
884
-	if err := stream("GET", "/containers/"+cmd.Arg(0)+"/export"); err != nil {
884
+	if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export"); err != nil {
885 885
 		return err
886 886
 	}
887 887
 	return nil
888 888
 }
889 889
 
890
-func CmdDiff(args ...string) error {
890
+func (cli *DockerCli) CmdDiff(args ...string) error {
891 891
 	cmd := Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
892 892
 	if err := cmd.Parse(args); err != nil {
893 893
 		return nil
... ...
@@ -897,7 +880,7 @@ func CmdDiff(args ...string) error {
897 897
 		return nil
898 898
 	}
899 899
 
900
-	body, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil)
900
+	body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil)
901 901
 	if err != nil {
902 902
 		return err
903 903
 	}
... ...
@@ -913,7 +896,7 @@ func CmdDiff(args ...string) error {
913 913
 	return nil
914 914
 }
915 915
 
916
-func CmdLogs(args ...string) error {
916
+func (cli *DockerCli) CmdLogs(args ...string) error {
917 917
 	cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
918 918
 	if err := cmd.Parse(args); err != nil {
919 919
 		return nil
... ...
@@ -928,13 +911,13 @@ func CmdLogs(args ...string) error {
928 928
 	v.Set("stdout", "1")
929 929
 	v.Set("stderr", "1")
930 930
 
931
-	if err := hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil {
931
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil {
932 932
 		return err
933 933
 	}
934 934
 	return nil
935 935
 }
936 936
 
937
-func CmdAttach(args ...string) error {
937
+func (cli *DockerCli) CmdAttach(args ...string) error {
938 938
 	cmd := Subcmd("attach", "CONTAINER", "Attach to a running container")
939 939
 	if err := cmd.Parse(args); err != nil {
940 940
 		return nil
... ...
@@ -944,7 +927,7 @@ func CmdAttach(args ...string) error {
944 944
 		return nil
945 945
 	}
946 946
 
947
-	body, _, err := call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
947
+	body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)
948 948
 	if err != nil {
949 949
 		return err
950 950
 	}
... ...
@@ -961,13 +944,13 @@ func CmdAttach(args ...string) error {
961 961
 	v.Set("stderr", "1")
962 962
 	v.Set("stdin", "1")
963 963
 
964
-	if err := hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
964
+	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
965 965
 		return err
966 966
 	}
967 967
 	return nil
968 968
 }
969 969
 
970
-func CmdSearch(args ...string) error {
970
+func (cli *DockerCli) CmdSearch(args ...string) error {
971 971
 	cmd := Subcmd("search", "NAME", "Search the docker index for images")
972 972
 	if err := cmd.Parse(args); err != nil {
973 973
 		return nil
... ...
@@ -979,7 +962,7 @@ func CmdSearch(args ...string) error {
979 979
 
980 980
 	v := url.Values{}
981 981
 	v.Set("term", cmd.Arg(0))
982
-	body, _, err := call("GET", "/images/search?"+v.Encode(), nil)
982
+	body, _, err := cli.call("GET", "/images/search?"+v.Encode(), nil)
983 983
 	if err != nil {
984 984
 		return err
985 985
 	}
... ...
@@ -1060,7 +1043,7 @@ func (opts PathOpts) Set(val string) error {
1060 1060
 	return nil
1061 1061
 }
1062 1062
 
1063
-func CmdTag(args ...string) error {
1063
+func (cli *DockerCli) CmdTag(args ...string) error {
1064 1064
 	cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY [TAG]", "Tag an image into a repository")
1065 1065
 	force := cmd.Bool("f", false, "Force")
1066 1066
 	if err := cmd.Parse(args); err != nil {
... ...
@@ -1081,13 +1064,13 @@ func CmdTag(args ...string) error {
1081 1081
 		v.Set("force", "1")
1082 1082
 	}
1083 1083
 
1084
-	if _, _, err := call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil); err != nil {
1084
+	if _, _, err := cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil); err != nil {
1085 1085
 		return err
1086 1086
 	}
1087 1087
 	return nil
1088 1088
 }
1089 1089
 
1090
-func CmdRun(args ...string) error {
1090
+func (cli *DockerCli) CmdRun(args ...string) error {
1091 1091
 	config, cmd, err := ParseRun(args, nil)
1092 1092
 	if err != nil {
1093 1093
 		return err
... ...
@@ -1098,16 +1081,16 @@ func CmdRun(args ...string) error {
1098 1098
 	}
1099 1099
 
1100 1100
 	//create the container
1101
-	body, statusCode, err := call("POST", "/containers/create", config)
1101
+	body, statusCode, err := cli.call("POST", "/containers/create", config)
1102 1102
 	//if image not found try to pull it
1103 1103
 	if statusCode == 404 {
1104 1104
 		v := url.Values{}
1105 1105
 		v.Set("fromImage", config.Image)
1106
-		err = hijack("POST", "/images/create?"+v.Encode(), false)
1106
+		err = cli.hijack("POST", "/images/create?"+v.Encode(), false)
1107 1107
 		if err != nil {
1108 1108
 			return err
1109 1109
 		}
1110
-		body, _, err = call("POST", "/containers/create", config)
1110
+		body, _, err = cli.call("POST", "/containers/create", config)
1111 1111
 		if err != nil {
1112 1112
 			return err
1113 1113
 		}
... ...
@@ -1142,13 +1125,13 @@ func CmdRun(args ...string) error {
1142 1142
 	}
1143 1143
 
1144 1144
 	//start the container
1145
-	_, _, err = call("POST", "/containers/"+out.Id+"/start", nil)
1145
+	_, _, err = cli.call("POST", "/containers/"+out.Id+"/start", nil)
1146 1146
 	if err != nil {
1147 1147
 		return err
1148 1148
 	}
1149 1149
 
1150 1150
 	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
1151
-		if err := hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
1151
+		if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
1152 1152
 			return err
1153 1153
 		}
1154 1154
 	}
... ...
@@ -1158,7 +1141,7 @@ func CmdRun(args ...string) error {
1158 1158
 	return nil
1159 1159
 }
1160 1160
 
1161
-func call(method, path string, data interface{}) ([]byte, int, error) {
1161
+func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
1162 1162
 	var params io.Reader
1163 1163
 	if data != nil {
1164 1164
 		buf, err := json.Marshal(data)
... ...
@@ -1168,7 +1151,7 @@ func call(method, path string, data interface{}) ([]byte, int, error) {
1168 1168
 		params = bytes.NewBuffer(buf)
1169 1169
 	}
1170 1170
 
1171
-	req, err := http.NewRequest(method, "http://0.0.0.0:4243"+path, params)
1171
+	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d", cli.host, cli.port)+path, params)
1172 1172
 	if err != nil {
1173 1173
 		return nil, -1, err
1174 1174
 	}
... ...
@@ -1196,8 +1179,8 @@ func call(method, path string, data interface{}) ([]byte, int, error) {
1196 1196
 	return body, resp.StatusCode, nil
1197 1197
 }
1198 1198
 
1199
-func stream(method, path string) error {
1200
-	req, err := http.NewRequest(method, "http://0.0.0.0:4243"+path, nil)
1199
+func (cli *DockerCli) stream(method, path string) error {
1200
+	req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, path), nil)
1201 1201
 	if err != nil {
1202 1202
 		return err
1203 1203
 	}
... ...
@@ -1227,13 +1210,13 @@ func stream(method, path string) error {
1227 1227
 	return nil
1228 1228
 }
1229 1229
 
1230
-func hijack(method, path string, setRawTerminal bool) error {
1230
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
1231 1231
 	req, err := http.NewRequest(method, path, nil)
1232 1232
 	if err != nil {
1233 1233
 		return err
1234 1234
 	}
1235 1235
 	req.Header.Set("Content-Type", "plain/text")
1236
-	dial, err := net.Dial("tcp", "0.0.0.0:4243")
1236
+	dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
1237 1237
 	if err != nil {
1238 1238
 		return err
1239 1239
 	}
... ...
@@ -1244,20 +1227,20 @@ func hijack(method, path string, setRawTerminal bool) error {
1244 1244
 	rwc, br := clientconn.Hijack()
1245 1245
 	defer rwc.Close()
1246 1246
 
1247
-	receiveStdout := Go(func() error {
1247
+	receiveStdout := utils.Go(func() error {
1248 1248
 		_, err := io.Copy(os.Stdout, br)
1249 1249
 		return err
1250 1250
 	})
1251 1251
 
1252 1252
 	if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
1253
-		if oldState, err := SetRawTerminal(); err != nil {
1253
+		if oldState, err := term.SetRawTerminal(); err != nil {
1254 1254
 			return err
1255 1255
 		} else {
1256
-			defer RestoreTerminal(oldState)
1256
+			defer term.RestoreTerminal(oldState)
1257 1257
 		}
1258 1258
 	}
1259 1259
 
1260
-	sendStdin := Go(func() error {
1260
+	sendStdin := utils.Go(func() error {
1261 1261
 		_, err := io.Copy(rwc, os.Stdin)
1262 1262
 		if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
1263 1263
 			fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err)
... ...
@@ -1286,3 +1269,12 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
1286 1286
 	}
1287 1287
 	return flags
1288 1288
 }
1289
+
1290
+func NewDockerCli(host string, port int) *DockerCli {
1291
+	return &DockerCli{host, port}
1292
+}
1293
+
1294
+type DockerCli struct {
1295
+	host string
1296
+	port int
1297
+}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"encoding/json"
5 5
 	"flag"
6 6
 	"fmt"
7
+	"github.com/dotcloud/docker/utils"
7 8
 	"github.com/kr/pty"
8 9
 	"io"
9 10
 	"io/ioutil"
... ...
@@ -39,8 +40,8 @@ type Container struct {
39 39
 	ResolvConfPath string
40 40
 
41 41
 	cmd       *exec.Cmd
42
-	stdout    *writeBroadcaster
43
-	stderr    *writeBroadcaster
42
+	stdout    *utils.WriteBroadcaster
43
+	stderr    *utils.WriteBroadcaster
44 44
 	stdin     io.ReadCloser
45 45
 	stdinPipe io.WriteCloser
46 46
 	ptyMaster io.Closer
... ...
@@ -251,9 +252,9 @@ func (container *Container) startPty() error {
251 251
 	// Copy the PTYs to our broadcasters
252 252
 	go func() {
253 253
 		defer container.stdout.CloseWriters()
254
-		Debugf("[startPty] Begin of stdout pipe")
254
+		utils.Debugf("[startPty] Begin of stdout pipe")
255 255
 		io.Copy(container.stdout, ptyMaster)
256
-		Debugf("[startPty] End of stdout pipe")
256
+		utils.Debugf("[startPty] End of stdout pipe")
257 257
 	}()
258 258
 
259 259
 	// stdin
... ...
@@ -262,9 +263,9 @@ func (container *Container) startPty() error {
262 262
 		container.cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
263 263
 		go func() {
264 264
 			defer container.stdin.Close()
265
-			Debugf("[startPty] Begin of stdin pipe")
265
+			utils.Debugf("[startPty] Begin of stdin pipe")
266 266
 			io.Copy(ptyMaster, container.stdin)
267
-			Debugf("[startPty] End of stdin pipe")
267
+			utils.Debugf("[startPty] End of stdin pipe")
268 268
 		}()
269 269
 	}
270 270
 	if err := container.cmd.Start(); err != nil {
... ...
@@ -284,9 +285,9 @@ func (container *Container) start() error {
284 284
 		}
285 285
 		go func() {
286 286
 			defer stdin.Close()
287
-			Debugf("Begin of stdin pipe [start]")
287
+			utils.Debugf("Begin of stdin pipe [start]")
288 288
 			io.Copy(stdin, container.stdin)
289
-			Debugf("End of stdin pipe [start]")
289
+			utils.Debugf("End of stdin pipe [start]")
290 290
 		}()
291 291
 	}
292 292
 	return container.cmd.Start()
... ...
@@ -303,8 +304,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
303 303
 			errors <- err
304 304
 		} else {
305 305
 			go func() {
306
-				Debugf("[start] attach stdin\n")
307
-				defer Debugf("[end] attach stdin\n")
306
+				utils.Debugf("[start] attach stdin\n")
307
+				defer utils.Debugf("[end] attach stdin\n")
308 308
 				// No matter what, when stdin is closed (io.Copy unblock), close stdout and stderr
309 309
 				if cStdout != nil {
310 310
 					defer cStdout.Close()
... ...
@@ -316,12 +317,12 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
316 316
 					defer cStdin.Close()
317 317
 				}
318 318
 				if container.Config.Tty {
319
-					_, err = CopyEscapable(cStdin, stdin)
319
+					_, err = utils.CopyEscapable(cStdin, stdin)
320 320
 				} else {
321 321
 					_, err = io.Copy(cStdin, stdin)
322 322
 				}
323 323
 				if err != nil {
324
-					Debugf("[error] attach stdin: %s\n", err)
324
+					utils.Debugf("[error] attach stdin: %s\n", err)
325 325
 				}
326 326
 				// Discard error, expecting pipe error
327 327
 				errors <- nil
... ...
@@ -335,8 +336,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
335 335
 		} else {
336 336
 			cStdout = p
337 337
 			go func() {
338
-				Debugf("[start] attach stdout\n")
339
-				defer Debugf("[end]  attach stdout\n")
338
+				utils.Debugf("[start] attach stdout\n")
339
+				defer utils.Debugf("[end]  attach stdout\n")
340 340
 				// If we are in StdinOnce mode, then close stdin
341 341
 				if container.Config.StdinOnce {
342 342
 					if stdin != nil {
... ...
@@ -348,7 +349,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
348 348
 				}
349 349
 				_, err := io.Copy(stdout, cStdout)
350 350
 				if err != nil {
351
-					Debugf("[error] attach stdout: %s\n", err)
351
+					utils.Debugf("[error] attach stdout: %s\n", err)
352 352
 				}
353 353
 				errors <- err
354 354
 			}()
... ...
@@ -361,8 +362,8 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
361 361
 		} else {
362 362
 			cStderr = p
363 363
 			go func() {
364
-				Debugf("[start] attach stderr\n")
365
-				defer Debugf("[end]  attach stderr\n")
364
+				utils.Debugf("[start] attach stderr\n")
365
+				defer utils.Debugf("[end]  attach stderr\n")
366 366
 				// If we are in StdinOnce mode, then close stdin
367 367
 				if container.Config.StdinOnce {
368 368
 					if stdin != nil {
... ...
@@ -374,13 +375,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
374 374
 				}
375 375
 				_, err := io.Copy(stderr, cStderr)
376 376
 				if err != nil {
377
-					Debugf("[error] attach stderr: %s\n", err)
377
+					utils.Debugf("[error] attach stderr: %s\n", err)
378 378
 				}
379 379
 				errors <- err
380 380
 			}()
381 381
 		}
382 382
 	}
383
-	return Go(func() error {
383
+	return utils.Go(func() error {
384 384
 		if cStdout != nil {
385 385
 			defer cStdout.Close()
386 386
 		}
... ...
@@ -390,14 +391,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
390 390
 		// FIXME: how do clean up the stdin goroutine without the unwanted side effect
391 391
 		// of closing the passed stdin? Add an intermediary io.Pipe?
392 392
 		for i := 0; i < nJobs; i += 1 {
393
-			Debugf("Waiting for job %d/%d\n", i+1, nJobs)
393
+			utils.Debugf("Waiting for job %d/%d\n", i+1, nJobs)
394 394
 			if err := <-errors; err != nil {
395
-				Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err)
395
+				utils.Debugf("Job %d returned error %s. Aborting all jobs\n", i+1, err)
396 396
 				return err
397 397
 			}
398
-			Debugf("Job %d completed successfully\n", i+1)
398
+			utils.Debugf("Job %d completed successfully\n", i+1)
399 399
 		}
400
-		Debugf("All jobs completed successfully\n")
400
+		utils.Debugf("All jobs completed successfully\n")
401 401
 		return nil
402 402
 	})
403 403
 }
... ...
@@ -555,13 +556,13 @@ func (container *Container) StdinPipe() (io.WriteCloser, error) {
555 555
 func (container *Container) StdoutPipe() (io.ReadCloser, error) {
556 556
 	reader, writer := io.Pipe()
557 557
 	container.stdout.AddWriter(writer)
558
-	return newBufReader(reader), nil
558
+	return utils.NewBufReader(reader), nil
559 559
 }
560 560
 
561 561
 func (container *Container) StderrPipe() (io.ReadCloser, error) {
562 562
 	reader, writer := io.Pipe()
563 563
 	container.stderr.AddWriter(writer)
564
-	return newBufReader(reader), nil
564
+	return utils.NewBufReader(reader), nil
565 565
 }
566 566
 
567 567
 func (container *Container) allocateNetwork() error {
... ...
@@ -609,20 +610,20 @@ func (container *Container) waitLxc() error {
609 609
 
610 610
 func (container *Container) monitor() {
611 611
 	// Wait for the program to exit
612
-	Debugf("Waiting for process")
612
+	utils.Debugf("Waiting for process")
613 613
 
614 614
 	// If the command does not exists, try to wait via lxc
615 615
 	if container.cmd == nil {
616 616
 		if err := container.waitLxc(); err != nil {
617
-			Debugf("%s: Process: %s", container.Id, err)
617
+			utils.Debugf("%s: Process: %s", container.Id, err)
618 618
 		}
619 619
 	} else {
620 620
 		if err := container.cmd.Wait(); err != nil {
621 621
 			// Discard the error as any signals or non 0 returns will generate an error
622
-			Debugf("%s: Process: %s", container.Id, err)
622
+			utils.Debugf("%s: Process: %s", container.Id, err)
623 623
 		}
624 624
 	}
625
-	Debugf("Process finished")
625
+	utils.Debugf("Process finished")
626 626
 
627 627
 	var exitCode int = -1
628 628
 	if container.cmd != nil {
... ...
@@ -633,19 +634,19 @@ func (container *Container) monitor() {
633 633
 	container.releaseNetwork()
634 634
 	if container.Config.OpenStdin {
635 635
 		if err := container.stdin.Close(); err != nil {
636
-			Debugf("%s: Error close stdin: %s", container.Id, err)
636
+			utils.Debugf("%s: Error close stdin: %s", container.Id, err)
637 637
 		}
638 638
 	}
639 639
 	if err := container.stdout.CloseWriters(); err != nil {
640
-		Debugf("%s: Error close stdout: %s", container.Id, err)
640
+		utils.Debugf("%s: Error close stdout: %s", container.Id, err)
641 641
 	}
642 642
 	if err := container.stderr.CloseWriters(); err != nil {
643
-		Debugf("%s: Error close stderr: %s", container.Id, err)
643
+		utils.Debugf("%s: Error close stderr: %s", container.Id, err)
644 644
 	}
645 645
 
646 646
 	if container.ptyMaster != nil {
647 647
 		if err := container.ptyMaster.Close(); err != nil {
648
-			Debugf("%s: Error closing Pty master: %s", container.Id, err)
648
+			utils.Debugf("%s: Error closing Pty master: %s", container.Id, err)
649 649
 		}
650 650
 	}
651 651
 
... ...
@@ -762,7 +763,7 @@ func (container *Container) RwChecksum() (string, error) {
762 762
 	if err != nil {
763 763
 		return "", err
764 764
 	}
765
-	return HashData(rwData)
765
+	return utils.HashData(rwData)
766 766
 }
767 767
 
768 768
 func (container *Container) Export() (Archive, error) {
... ...
@@ -833,7 +834,7 @@ func (container *Container) Unmount() error {
833 833
 // In case of a collision a lookup with Runtime.Get() will fail, and the caller
834 834
 // will need to use a langer prefix, or the full-length container Id.
835 835
 func (container *Container) ShortId() string {
836
-	return TruncateId(container.Id)
836
+	return utils.TruncateId(container.Id)
837 837
 }
838 838
 
839 839
 func (container *Container) logPath(name string) string {
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"flag"
5 5
 	"fmt"
6 6
 	"github.com/dotcloud/docker"
7
+	"github.com/dotcloud/docker/utils"
7 8
 	"io/ioutil"
8 9
 	"log"
9 10
 	"os"
... ...
@@ -17,7 +18,7 @@ var (
17 17
 )
18 18
 
19 19
 func main() {
20
-	if docker.SelfPath() == "/sbin/init" {
20
+	if utils.SelfPath() == "/sbin/init" {
21 21
 		// Running in init mode
22 22
 		docker.SysInit()
23 23
 		return
24 24
deleted file mode 100644
25 25
deleted file mode 100644
... ...
@@ -1 +0,0 @@
1
-docker.io
... ...
@@ -68,11 +68,12 @@ List containers
68 68
 		}
69 69
 	   ]
70 70
  
71
-	:query all: 1 or 0, Show all containers. Only running containers are shown by default
71
+	:query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default
72 72
 	:query limit: Show ``limit`` last created containers, include non-running ones.
73 73
 	:query since: Show only containers created since Id, include non-running ones.
74 74
 	:query before: Show only containers created before Id, include non-running ones.
75 75
 	:statuscode 200: no error
76
+	:statuscode 400: bad parameter
76 77
 	:statuscode 500: server error
77 78
 
78 79
 
... ...
@@ -389,12 +390,13 @@ Attach to a container
389 389
 
390 390
 	   {{ STREAM }}
391 391
 	   	
392
-	:query logs: 1 or 0, return logs. Default 0
393
-	:query stream: 1 or 0, return stream. Default 0
394
-	:query stdin: 1 or 0, if stream=1, attach to stdin. Default 0
395
-	:query stdout: 1 or 0, if logs=1, return stdout log, if stream=1, attach to stdout. Default 0
396
-	:query stderr: 1 or 0, if logs=1, return stderr log, if stream=1, attach to stderr. Default 0
392
+	:query logs: 1/True/true or 0/False/false, return logs. Default false
393
+	:query stream: 1/True/true or 0/False/false, return stream. Default false
394
+	:query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false
395
+	:query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false
396
+	:query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false
397 397
 	:statuscode 200: no error
398
+	:statuscode 400: bad parameter
398 399
 	:statuscode 404: no such container
399 400
 	:statuscode 500: server error
400 401
 
... ...
@@ -445,8 +447,9 @@ Remove a container
445 445
 
446 446
 	   HTTP/1.1 204 OK
447 447
 
448
-	:query v: 1 or 0, Remove the volumes associated to the container. Default 0
448
+	:query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false
449 449
         :statuscode 204: no error
450
+	:statuscode 400: bad parameter
450 451
         :statuscode 404: no such container
451 452
         :statuscode 500: server error
452 453
 
... ...
@@ -521,8 +524,9 @@ List Images
521 521
 	   base [style=invisible]
522 522
 	   }
523 523
  
524
-	:query all: 1 or 0, Show all containers. Only running containers are shown by default
524
+	:query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default
525 525
 	:statuscode 200: no error
526
+	:statuscode 400: bad parameter
526 527
 	:statuscode 500: server error
527 528
 
528 529
 
... ...
@@ -720,8 +724,9 @@ Tag an image into a repository
720 720
            HTTP/1.1 200 OK
721 721
 
722 722
 	:query repo: The repository to tag in
723
-	:query force: 1 or 0, default 0
723
+	:query force: 1/True/true or 0/False/false, default false
724 724
 	:statuscode 200: no error
725
+	:statuscode 400: bad parameter
725 726
 	:statuscode 404: no such image
726 727
         :statuscode 500: server error
727 728
 
... ...
@@ -9,7 +9,7 @@ Commands
9 9
 Contents:
10 10
 
11 11
 .. toctree::
12
-  :maxdepth: 3
12
+  :maxdepth: 1
13 13
 
14 14
   cli
15 15
   attach  <command/attach>
... ...
@@ -12,6 +12,6 @@ Contents:
12 12
 .. toctree::
13 13
    :maxdepth: 1
14 14
 
15
-   introduction
15
+   ../index
16 16
    buildingblocks
17 17
 
... ...
@@ -2,8 +2,6 @@
2 2
 :description: An introduction to docker and standard containers?
3 3
 :keywords: containers, lxc, concepts, explanation
4 4
 
5
-.. _introduction:
6
-
7 5
 Introduction
8 6
 ============
9 7
 
... ...
@@ -41,7 +41,7 @@ html_add_permalinks = None
41 41
 
42 42
 
43 43
 # The master toctree document.
44
-master_doc = 'index'
44
+master_doc = 'toctree'
45 45
 
46 46
 # General information about the project.
47 47
 project = u'Docker'
48 48
deleted file mode 100644
... ...
@@ -1,2 +0,0 @@
1
-www:
2
-  type: static
3 1
\ No newline at end of file
4 2
deleted file mode 100644
... ...
@@ -1,210 +0,0 @@
1
-<!DOCTYPE html>
2
-<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3
-<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4
-<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
5
-<!--[if gt IE 8]><!-->
6
-<html class="no-js" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
7
-<head>
8
-    <meta charset="utf-8">
9
-    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
10
-    <title>Docker - the Linux container runtime</title>
11
-
12
-    <meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
13
-    <meta name="viewport" content="width=device-width">
14
-
15
-    <!-- twitter bootstrap -->
16
-    <link rel="stylesheet" href="../_static/css/bootstrap.min.css">
17
-    <link rel="stylesheet" href="../_static/css/bootstrap-responsive.min.css">
18
-
19
-    <!-- main style file -->
20
-    <link rel="stylesheet" href="../_static/css/main.css">
21
-
22
-    <!-- vendor scripts -->
23
-    <script src="../_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
24
-    <script src="../_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
25
-
26
-</head>
27
-
28
-
29
-<body>
30
-
31
-<div class="navbar navbar-fixed-top">
32
-    <div class="navbar-dotcloud">
33
-        <div class="container" style="text-align: center;">
34
-
35
-            <div style="float: right" class="pull-right">
36
-                <ul class="nav">
37
-                    <li><a href="../">Introduction</a></li>
38
-                    <li class="active"><a href="./">Getting started</a></li>
39
-                    <li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
40
-                </ul>
41
-
42
-                <div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
43
-                    <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
44
-                    <a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
45
-                </div>
46
-            </div>
47
-
48
-            <div style="margin-left: -12px; float: left;">
49
-                <a href="../index.html"><img style="margin-top: 12px; height: 38px" src="../_static/img/docker-letters-logo.gif"></a>
50
-            </div>
51
-        </div>
52
-    </div>
53
-</div>
54
-
55
-
56
-<div class="container">
57
-    <div class="row">
58
-        <div class="span12 titlebar"><h1 class="pageheader">GETTING STARTED</h1>
59
-        </div>
60
-    </div>
61
-
62
-</div>
63
-
64
-<div class="container">
65
-    <div class="alert alert-info">
66
-        <strong>Docker is still under heavy development.</strong> It should not yet be used in production. Check <a href="http://github.com/dotcloud/docker">the repo</a> for recent progress.
67
-    </div>
68
-    <div class="row">
69
-        <div class="span6">
70
-            <section class="contentblock">
71
-                <h2>
72
-                    <a name="installing-on-ubuntu-1204-and-1210" class="anchor" href="#installing-on-ubuntu-1204-and-1210"><span class="mini-icon mini-icon-link"></span>
73
-                    </a>Installing on Ubuntu</h2>
74
-
75
-                    <p><strong>Requirements</strong></p>
76
-                    <ul>
77
-                        <li>Ubuntu 12.04 (LTS) (64-bit)</li>
78
-                        <li> or Ubuntu 12.10 (quantal) (64-bit)</li>
79
-                    </ul>
80
-                <ol>
81
-                    <li>
82
-                    <p><strong>Install dependencies</strong></p>
83
-                    The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
84
-                    <pre>sudo apt-get install linux-image-extra-`uname -r`</pre>
85
-
86
-
87
-                    </li>
88
-                    <li>
89
-                        <p><strong>Install Docker</strong></p>
90
-                        <p>Add the Ubuntu PPA (Personal Package Archive) sources to your apt sources list, update and install.</p>
91
-                        <p>You may see some warnings that the GPG keys cannot be verified.</p>
92
-                        <div class="highlight">
93
-                            <pre>sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"</pre>
94
-                            <pre>sudo apt-get update</pre>
95
-                            <pre>sudo apt-get install lxc-docker</pre>
96
-                        </div>
97
-
98
-
99
-                    </li>
100
-
101
-                    <li>
102
-                        <p><strong>Run!</strong></p>
103
-
104
-                        <div class="highlight">
105
-                            <pre>docker run -i -t ubuntu /bin/bash</pre>
106
-                        </div>
107
-                    </li>
108
-                    Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.
109
-                </ol>
110
-            </section>
111
-
112
-            <section class="contentblock">
113
-                <h2>Contributing to Docker</h2>
114
-
115
-                <p>Want to hack on Docker? Awesome! We have some <a href="http://docs.docker.io/en/latest/contributing/contributing/">instructions to get you started</a>. They are probably not perfect, please let us know if anything feels wrong or incomplete.</p>
116
-            </section>
117
-
118
-        </div>
119
-        <div class="span6">
120
-            <section class="contentblock">
121
-                <h2>Quick install on other operating systems</h2>
122
-                <p><strong>For other operating systems we recommend and provide a streamlined install with virtualbox,
123
-                    vagrant and an Ubuntu virtual machine.</strong></p>
124
-
125
-                <ul>
126
-                    <li><a href="http://docs.docker.io/en/latest/installation/vagrant/">Mac OS X and other linuxes</a></li>
127
-                    <li><a href="http://docs.docker.io/en/latest/installation/windows/">Windows</a></li>
128
-                </ul>
129
-
130
-            </section>
131
-
132
-            <section class="contentblock">
133
-                <h2>More resources</h2>
134
-                <ul>
135
-                    <li><a href="irc://chat.freenode.net#docker">IRC: docker on freenode</a></li>
136
-                    <li><a href="http://www.github.com/dotcloud/docker">Github</a></li>
137
-                    <li><a href="http://stackoverflow.com/tags/docker/">Ask questions on Stackoverflow</a></li>
138
-                    <li><a href="http://twitter.com/getdocker/">Join the conversation on Twitter</a></li>
139
-                </ul>
140
-            </section>
141
-
142
-
143
-            <section class="contentblock">
144
-                <div id="wufoo-z7x3p3">
145
-                    Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
146
-                </div>
147
-                <script type="text/javascript">var z7x3p3;(function(d, t) {
148
-                    var s = d.createElement(t), options = {
149
-                        'userName':'dotclouddocker',
150
-                        'formHash':'z7x3p3',
151
-                        'autoResize':true,
152
-                        'height':'577',
153
-                        'async':true,
154
-                        'header':'show'};
155
-                    s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
156
-                    s.onload = s.onreadystatechange = function() {
157
-                        var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
158
-                        try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
159
-                    var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
160
-                })(document, 'script');</script>
161
-            </section>
162
-
163
-        </div>
164
-    </div>
165
-</div>
166
-
167
-
168
-<div class="container">
169
-    <footer id="footer" class="footer">
170
-        <div class="row">
171
-            <div class="span12 social">
172
-
173
-                Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
174
-
175
-            </div>
176
-        </div>
177
-
178
-        <div class="row">
179
-            <div class="emptyspace" style="height: 40px">
180
-
181
-            </div>
182
-        </div>
183
-
184
-    </footer>
185
-</div>
186
-
187
-
188
-<!-- bootstrap javascipts -->
189
-<script src="../_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
190
-
191
-<!-- Google analytics -->
192
-<script type="text/javascript">
193
-
194
-    var _gaq = _gaq || [];
195
-    _gaq.push(['_setAccount', 'UA-6096819-11']);
196
-    _gaq.push(['_setDomainName', 'docker.io']);
197
-    _gaq.push(['_setAllowLinker', true]);
198
-    _gaq.push(['_trackPageview']);
199
-
200
-    (function() {
201
-        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
202
-        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
203
-        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
204
-    })();
205
-
206
-</script>
207
-
208
-
209
-</body>
210
-</html>
211 1
deleted file mode 100644
... ...
@@ -1,314 +0,0 @@
1
-<!DOCTYPE html>
2
-<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3
-<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4
-<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
5
-<!--[if gt IE 8]><!-->
6
-<html class="no-js" xmlns="http://www.w3.org/1999/html"> <!--<![endif]-->
7
-<head>
8
-    <meta charset="utf-8">
9
-    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
10
-    <meta name="google-site-verification" content="UxV66EKuPe87dgnH1sbrldrx6VsoWMrx5NjwkgUFxXI" />
11
-    <title>Docker - the Linux container engine</title>
12
-
13
-    <meta name="description" content="Docker encapsulates heterogeneous payloads in standard containers">
14
-    <meta name="viewport" content="width=device-width">
15
-
16
-    <!-- twitter bootstrap -->
17
-    <link rel="stylesheet" href="_static/css/bootstrap.min.css">
18
-    <link rel="stylesheet" href="_static/css/bootstrap-responsive.min.css">
19
-
20
-    <!-- main style file -->
21
-    <link rel="stylesheet" href="_static/css/main.css">
22
-
23
-    <!-- vendor scripts -->
24
-    <script src="_static/js/vendor/jquery-1.9.1.min.js" type="text/javascript" ></script>
25
-    <script src="_static/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js" type="text/javascript" ></script>
26
-
27
-    <style>
28
-        .indexlabel {
29
-            float: left;
30
-            width: 150px;
31
-            display: block;
32
-            padding: 10px 20px 10px;
33
-            font-size: 20px;
34
-            font-weight: 200;
35
-            background-color: #a30000;
36
-            color: white;
37
-            height: 22px;
38
-        }
39
-        .searchbutton {
40
-            font-size: 20px;
41
-            height: 40px;
42
-        }
43
-
44
-        .debug {
45
-            border: 1px red dotted;
46
-        }
47
-
48
-    </style>
49
-
50
-</head>
51
-
52
-
53
-<body>
54
-
55
-<div class="navbar navbar-fixed-top">
56
-    <div class="navbar-dotcloud">
57
-        <div class="container" style="text-align: center;">
58
-
59
-            <div class="pull-right" >
60
-                <ul class="nav">
61
-                    <li class="active"><a href="./">Introduction</a></li>
62
-                    <li ><a href="gettingstarted/">Getting started</a></li>
63
-                    <li class=""><a href="http://docs.docker.io/en/latest/concepts/containers/">Documentation</a></li>
64
-                </ul>
65
-
66
-                <div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
67
-                    <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
68
-                    <a class="github" href="https://github.com/dotcloud/docker/">GitHub</a>
69
-                </div>
70
-            </div>
71
-        </div>
72
-    </div>
73
-</div>
74
-
75
-
76
-<div class="container" style="margin-top: 30px;">
77
-    <div class="row">
78
-
79
-        <div class="span12">
80
-            <section class="contentblock header">
81
-
82
-                <div class="span5" style="margin-bottom: 15px;">
83
-                    <div style="text-align: center;" >
84
-                        <img src="_static/img/docker_letters_500px.png">
85
-
86
-                        <h2>The Linux container engine</h2>
87
-                    </div>
88
-
89
-                    <div style="display: block; text-align: center; margin-top: 20px;">
90
-
91
-                        <h5>
92
-                            Docker is an open-source engine which automates the deployment of applications as highly portable, self-sufficient containers which are independent of hardware, language, framework, packaging system and hosting provider.
93
-                        </h5>
94
-
95
-                    </div>
96
-
97
-
98
-                    <div style="display: block; text-align: center; margin-top: 30px;">
99
-                        <a class="btn btn-custom btn-large" href="gettingstarted/">Let's get started</a>
100
-                    </div>
101
-
102
-                </div>
103
-
104
-                <div class="span6" >
105
-                    <div class="js-video" >
106
-                        <iframe width="600" height="360" src="http://www.youtube.com/embed/wW9CAH9nSLs?feature=player_detailpage&rel=0&modestbranding=1&start=11" frameborder="0" allowfullscreen></iframe>
107
-                    </div>
108
-                </div>
109
-
110
-                <br style="clear: both"/>
111
-            </section>
112
-        </div>
113
-    </div>
114
-</div>
115
-
116
-<div class="container">
117
-    <div class="row">
118
-
119
-        <div class="span6">
120
-            <section class="contentblock">
121
-                <h4>Heterogeneous payloads</h4>
122
-                <p>Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.</p>
123
-                <h4>Any server</h4>
124
-                <p>Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.</p>
125
-                <h4>Isolation</h4>
126
-                <p>Docker isolates processes from each other and from the underlying host, using lightweight containers.</p>
127
-                <h4>Repeatability</h4>
128
-                <p>Because each container is isolated in its own filesystem, they behave the same regardless of where, when, and alongside what they run.</p>
129
-            </section>
130
-        </div>
131
-        <div class="span6">
132
-            <section class="contentblock">
133
-                <h1>New! Docker Index</h1>
134
-                On the Docker Index you can find and explore pre-made container images. It allows you to share your images and download them.
135
-
136
-                <br><br>
137
-                <a href="https://index.docker.io" target="_blank">
138
-                    <div class="indexlabel">
139
-                        DOCKER index
140
-                    </div>
141
-                </a>
142
-                &nbsp;
143
-                <input type="button" class="searchbutton" type="submit" value="Search images"
144
-                       onClick="window.open('https://index.docker.io')" />
145
-
146
-            </section>
147
-            <section class="contentblock">
148
-                <div id="wufoo-z7x3p3">
149
-                    Fill out my <a href="http://dotclouddocker.wufoo.com/forms/z7x3p3">online form</a>.
150
-                </div>
151
-                <script type="text/javascript">var z7x3p3;(function(d, t) {
152
-                    var s = d.createElement(t), options = {
153
-                        'userName':'dotclouddocker',
154
-                        'formHash':'z7x3p3',
155
-                        'autoResize':true,
156
-                        'height':'577',
157
-                        'async':true,
158
-                        'header':'show'};
159
-                    s.src = ('https:' == d.location.protocol ? 'https://' : 'http://') + 'wufoo.com/scripts/embed/form.js';
160
-                    s.onload = s.onreadystatechange = function() {
161
-                        var rs = this.readyState; if (rs) if (rs != 'complete') if (rs != 'loaded') return;
162
-                        try { z7x3p3 = new WufooForm();z7x3p3.initialize(options);z7x3p3.display(); } catch (e) {}};
163
-                    var scr = d.getElementsByTagName(t)[0], par = scr.parentNode; par.insertBefore(s, scr);
164
-                })(document, 'script');</script>
165
-            </section>
166
-        </div>
167
-    </div>
168
-
169
-</div>
170
-
171
-<style>
172
-    .twitterblock {
173
-        min-height: 75px;
174
-    }
175
-
176
-    .twitterblock img {
177
-        float: left;
178
-        margin-right: 10px;
179
-    }
180
-
181
-</style>
182
-
183
-
184
-<div class="container">
185
-    <div class="row">
186
-        <div class="span6">
187
-            <section class="contentblock twitterblock">
188
-                <img src="https://twimg0-a.akamaihd.net/profile_images/2491994496/rbevyyq6ykp6bnoby2je_bigger.jpeg">
189
-                <em>John Willis @botchagalupe:</em> IMHO docker is to paas what chef was to Iaas 4 years ago
190
-            </section>
191
-        </div>
192
-        <div class="span6">
193
-            <section class="contentblock twitterblock">
194
-                <img src="https://twimg0-a.akamaihd.net/profile_images/3348427561/9d7f08f1e103a16c8debd169301b9944_bigger.jpeg">
195
-                <em>John Feminella ‏@superninjarobot:</em> So, @getdocker is pure excellence. If you've ever wished for arbitrary, PaaS-agnostic, lxc/aufs Linux containers, this is your jam!
196
-            </section>
197
-        </div>
198
-    </div>
199
-    <div class="row">
200
-        <div class="span6">
201
-            <section class="contentblock twitterblock">
202
-                <img src="https://si0.twimg.com/profile_images/3408403010/4496ccdd14e9b7285eca04c31a740207_bigger.jpeg">
203
-                <em>David Romulan ‏@destructuring:</em> I haven't had this much fun since AWS
204
-            </section>
205
-        </div>
206
-        <div class="span6">
207
-            <section class="contentblock twitterblock">
208
-                <img src="https://si0.twimg.com/profile_images/780893320/My_Avatar_bigger.jpg">
209
-                <em>Ricardo Gladwell ‏@rgladwell:</em> wow @getdocker is either amazing or totally stupid
210
-            </section>
211
-        </div>
212
-
213
-    </div>
214
-</div>
215
-
216
-<div class="container">
217
-    <div class="row">
218
-        <div class="span6">
219
-
220
-            <section class="contentblock">
221
-
222
-                <h2>Notable features</h2>
223
-
224
-                <ul>
225
-                    <li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
226
-                    <li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
227
-                    <li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
228
-                    <li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.</li>
229
-                    <li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
230
-                    <li>Change management: changes to a container's filesystem can be committed into a new image and re-used to create more containers. No templating or manual configuration required.</li>
231
-                    <li>Interactive shell: docker can allocate a pseudo-tty and attach to the standard input of any container, for example to run a throwaway interactive shell.</li>
232
-                </ul>
233
-
234
-                <h2>Under the hood</h2>
235
-
236
-                <p>Under the hood, Docker is built on the following components:</p>
237
-
238
-                <ul>
239
-                    <li>The <a href="http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c">cgroup</a> and <a href="http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part">namespacing</a> capabilities of the Linux kernel;</li>
240
-                    <li><a href="http://aufs.sourceforge.net/aufs.html">AUFS</a>, a powerful union filesystem with copy-on-write capabilities;</li>
241
-                    <li>The <a href="http://golang.org">Go</a> programming language;</li>
242
-                    <li><a href="http://lxc.sourceforge.net/">lxc</a>, a set of convenience scripts to simplify the creation of linux containers.</li>
243
-                </ul>
244
-
245
-                <h2>Who started it</h2>
246
-                <p>
247
-                    Docker is an open-source implementation of the deployment engine which powers <a href="http://dotcloud.com">dotCloud</a>, a popular Platform-as-a-Service.</p>
248
-
249
-                <p>It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands
250
-                    of applications and databases.
251
-                </p>
252
-
253
-            </section>
254
-        </div>
255
-
256
-        <div class="span6">
257
-
258
-
259
-            <section class="contentblock">
260
-                <h3 id="twitter">Twitter</h3>
261
-                <a class="twitter-timeline" href="https://twitter.com/getdocker" data-widget-id="312730839718957056">Tweets by @getdocker</a>
262
-                <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="//platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
263
-            </section>
264
-
265
-        </div>
266
-    </div>
267
-
268
-</div> <!-- end container -->
269
-
270
-
271
-<div class="container">
272
-    <footer id="footer" class="footer">
273
-        <div class="row">
274
-            <div class="span12">
275
-
276
-                Docker is a project by <a href="http://www.dotcloud.com">dotCloud</a>
277
-
278
-            </div>
279
-        </div>
280
-
281
-        <div class="row">
282
-            <div class="emptyspace" style="height: 40px">
283
-
284
-            </div>
285
-        </div>
286
-
287
-    </footer>
288
-</div>
289
-
290
-
291
-
292
-<!-- bootstrap javascipts -->
293
-<script src="_static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
294
-
295
-<!-- Google analytics -->
296
-<script type="text/javascript">
297
-
298
-    var _gaq = _gaq || [];
299
-    _gaq.push(['_setAccount', 'UA-6096819-11']);
300
-    _gaq.push(['_setDomainName', 'docker.io']);
301
-    _gaq.push(['_setAllowLinker', true]);
302
-    _gaq.push(['_trackPageview']);
303
-
304
-    (function() {
305
-        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
306
-        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
307
-        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
308
-    })();
309
-
310
-</script>
311
-
312
-
313
-</body>
314
-</html>
... ...
@@ -1,22 +1,127 @@
1
-:title: docker documentation
2
-:description: docker documentation
3
-:keywords:
1
+:title: Introduction
2
+:description: An introduction to docker and standard containers?
3
+:keywords: containers, lxc, concepts, explanation
4 4
 
5
-Documentation
6
-=============
5
+.. _introduction:
7 6
 
8
-This documentation has the following resources:
7
+Introduction
8
+============
9 9
 
10
-.. toctree::
11
-   :maxdepth: 1
10
+Docker - The Linux container runtime
11
+------------------------------------
12 12
 
13
-   concepts/index
14
-   installation/index
15
-   use/index
16
-   examples/index
17
-   commandline/index
18
-   contributing/index
19
-   api/index
20
-   faq
13
+Docker complements LXC with a high-level API which operates at the process level. It runs unix processes with strong guarantees of isolation and repeatability across servers.
14
+
15
+Docker is a great building block for automating distributed systems: large-scale web deployments, database clusters, continuous deployment systems, private PaaS, service-oriented architectures, etc.
16
+
17
+
18
+- **Heterogeneous payloads** Any combination of binaries, libraries, configuration files, scripts, virtualenvs, jars, gems, tarballs, you name it. No more juggling between domain-specific tools. Docker can deploy and run them all.
19
+- **Any server** Docker can run on any x64 machine with a modern linux kernel - whether it's a laptop, a bare metal server or a VM. This makes it perfect for multi-cloud deployments.
20
+- **Isolation** docker isolates processes from each other and from the underlying host, using lightweight containers.
21
+- **Repeatability** Because containers are isolated in their own filesystem, they behave the same regardless of where, when, and alongside what they run.
21 22
 
22 23
 .. image:: concepts/images/lego_docker.jpg
24
+
25
+
26
+What is a Standard Container?
27
+-----------------------------
28
+
29
+Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
30
+a format that is self-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
31
+
32
+The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
33
+
34
+A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (http://bricks.argz.com/ins/7823-1/12) are a fundamental unit of physical delivery.
35
+
36
+Standard operations
37
+~~~~~~~~~~~~~~~~~~~
38
+
39
+Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
40
+
41
+
42
+Content-agnostic
43
+~~~~~~~~~~~~~~~~~~~
44
+
45
+Just like shipping containers, Standard Containers are CONTENT-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
46
+
47
+
48
+Infrastructure-agnostic
49
+~~~~~~~~~~~~~~~~~~~~~~~~~~
50
+
51
+Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
52
+
53
+
54
+Designed for automation
55
+~~~~~~~~~~~~~~~~~~~~~~~~~~
56
+
57
+Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well-suited for automation. In fact, you could say automation is their secret weapon.
58
+
59
+Many things that once required time-consuming and error-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune - and was entirely different depending on the facility and the type of goods.
60
+
61
+Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post-it notes were lost, logs were misplaced, cluster updates were half-broken. The process was slow, inefficient and cost a fortune - and was entirely different depending on the language and infrastructure provider.
62
+
63
+
64
+Industrial-grade delivery
65
+~~~~~~~~~~~~~~~~~~~~~~~~~~
66
+
67
+There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half-way across the World in *less time* than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
68
+
69
+With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL-GRADE DELIVERY of software a reality.
70
+
71
+
72
+Standard Container Specification
73
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
74
+
75
+(TODO)
76
+
77
+Image format
78
+~~~~~~~~~~~~
79
+
80
+Standard operations
81
+~~~~~~~~~~~~~~~~~~~
82
+
83
+-  Copy
84
+-  Run
85
+-  Stop
86
+-  Wait
87
+-  Commit
88
+-  Attach standard streams
89
+-  List filesystem changes
90
+-  ...
91
+
92
+Execution environment
93
+~~~~~~~~~~~~~~~~~~~~~
94
+
95
+Root filesystem
96
+^^^^^^^^^^^^^^^
97
+
98
+Environment variables
99
+^^^^^^^^^^^^^^^^^^^^^
100
+
101
+Process arguments
102
+^^^^^^^^^^^^^^^^^
103
+
104
+Networking
105
+^^^^^^^^^^
106
+
107
+Process namespacing
108
+^^^^^^^^^^^^^^^^^^^
109
+
110
+Resource limits
111
+^^^^^^^^^^^^^^^
112
+
113
+Process monitoring
114
+^^^^^^^^^^^^^^^^^^
115
+
116
+Logging
117
+^^^^^^^
118
+
119
+Signals
120
+^^^^^^^
121
+
122
+Pseudo-terminal allocation
123
+^^^^^^^^^^^^^^^^^^^^^^^^^^
124
+
125
+Security
126
+^^^^^^^^
127
+
23 128
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+=================================
1
+Docker Index Environment Variable
2
+=================================
3
+
4
+Variable
5
+--------
6
+
7
+.. code-block:: sh
8
+
9
+    DOCKER_INDEX_URL
10
+
11
+Setting this environment variable on the docker server will change the URL docker index.
12
+This address is used in commands such as ``docker login``, ``docker push`` and ``docker pull``.
13
+The docker daemon doesn't need to be restarted for this parameter to take effect.
14
+
15
+Example
16
+-------
17
+
18
+.. code-block:: sh
19
+
20
+    docker -d &
21
+    export DOCKER_INDEX_URL="https://index.docker.io"
22
+
... ...
@@ -68,7 +68,7 @@ Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant
68 68
    If it stalls indefinitely on ``[default] Waiting for SSH to become available...``, Double check your default security
69 69
    zone on AWS includes rights to SSH (port 22) to your container.
70 70
 
71
-   If you have an advanced AWS setup, you might want to have a look at the https://github.com/mitchellh/vagrant-aws
71
+   If you have an advanced AWS setup, you might want to have a look at https://github.com/mitchellh/vagrant-aws
72 72
 
73 73
 7. Connect to your machine
74 74
 
... ...
@@ -5,48 +5,58 @@ Binaries
5 5
 
6 6
   **Please note this project is currently under heavy development. It should not be used in production.**
7 7
 
8
+**This instruction set is meant for hackers who want to try out Docker on a variety of environments.**
8 9
 
9 10
 Right now, the officially supported distributions are:
10 11
 
11
-- Ubuntu 12.04 (precise LTS) (64-bit)
12
-- Ubuntu 12.10 (quantal) (64-bit)
12
+- :ref:`ubuntu_precise`
13
+- :ref:`ubuntu_raring`
13 14
 
14 15
 
15
-Install dependencies:
16
+But we know people have had success running it under
17
+
18
+- Debian
19
+- Suse
20
+- :ref:`arch_linux`
21
+
16 22
 
17
-::
23
+Dependencies:
24
+-------------
18 25
 
19
-    sudo apt-get install lxc bsdtar
20
-    sudo apt-get install linux-image-extra-`uname -r`
26
+* 3.8 Kernel
27
+* AUFS filesystem support
28
+* lxc
29
+* bsdtar
21 30
 
22
-The linux-image-extra package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
23 31
 
24
-Install the docker binary:
32
+Get the docker binary:
33
+----------------------
25 34
 
26
-::
35
+.. code-block:: bash
27 36
 
28 37
     wget http://get.docker.io/builds/Linux/x86_64/docker-latest.tgz
29 38
     tar -xf docker-latest.tgz
30
-    sudo cp ./docker-latest/docker /usr/local/bin
31
-
32
-Note: docker currently only supports 64-bit Linux hosts.
33 39
 
34 40
 
35 41
 Run the docker daemon
36 42
 ---------------------
37 43
 
38
-::
44
+.. code-block:: bash
39 45
 
40
-    sudo docker -d &
46
+    # start the docker in daemon mode from the directory you unpacked
47
+    sudo ./docker -d &
41 48
 
42 49
 
43 50
 Run your first container!
44 51
 -------------------------
45 52
 
46
-::
53
+.. code-block:: bash
54
+
55
+    # check your docker version
56
+    ./docker version
47 57
 
48
-    docker run -i -t ubuntu /bin/bash
58
+    # run a container and open an interactive shell in the container
59
+    ./docker run -i -t ubuntu /bin/bash
49 60
 
50 61
 
51 62
 
... ...
@@ -14,9 +14,9 @@ Contents:
14 14
 
15 15
    ubuntulinux
16 16
    binaries
17
-   archlinux
18 17
    vagrant
19 18
    windows
20 19
    amazon
21 20
    rackspace
21
+   archlinux
22 22
    upgrading
... ...
@@ -2,220 +2,90 @@
2 2
 Rackspace Cloud
3 3
 ===============
4 4
 
5
-.. contents:: Table of Contents
5
+  Please note this is a community contributed installation path. The only 'official' installation is using the
6
+  :ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
6 7
 
7
-Ubuntu 12.04
8 8
 
9
-1. Build an Ubuntu 12.04 server using the "Next generation cloud servers", with your desired size. It will give you the password, keep that you will need it later.
10
-2. When the server is up and running ssh into the server.
9
+Installing Docker on Ubuntu proviced by Rackspace is pretty straightforward, and you should mostly be able to follow the
10
+:ref:`ubuntu_linux` installation guide.
11 11
 
12
-    .. code-block:: bash
12
+**However, there is one caveat:**
13 13
 
14
-        $ ssh root@<server-ip>
14
+If you are using any linux not already shipping with the 3.8 kernel you will need to install it. And this is a little
15
+more difficult on Rackspace.
15 16
 
16
-3. Once you are logged in you should check what kernel version you are running.
17
+Rackspace boots their servers using grub's menu.lst and does not like non 'virtual' packages (e.g. xen compatible)
18
+kernels there, although they do work. This makes ``update-grub`` to not have the expected result, and you need to
19
+set the kernel manually.
17 20
 
18
-    .. code-block:: bash
19
-
20
-        $ uname -a
21
-        Linux docker-12-04 3.2.0-38-virtual #61-Ubuntu SMP Tue Feb 19 12:37:47 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
22
-
23
-4. Let's update the server package list
24
-
25
-    .. code-block:: bash
26
-
27
-        $ apt-get update
28
-
29
-5. Now lets install Docker and it's dependencies. To keep things simple, we will use the Docker install script. It will take a couple of minutes.
30
-
31
-    .. code-block:: bash
32
-
33
-        $ curl get.docker.io | sudo sh -x
34
-
35
-6. Docker runs best with a new kernel, so lets use 3.8.x
36
-
37
-    .. code-block:: bash
38
-        
39
-        # install the new kernel
40
-        $ apt-get install linux-generic-lts-raring
41
-        
42
-        # update grub so it will use the new kernel after we reboot
43
-        $ update-grub
44
-        
45
-        # update-grub doesn't always work so lets make sure. ``/boot/grub/menu.lst`` was updated.
46
-        $ grep 3.8.0- /boot/grub/menu.lst
47
-        
48
-        # nope it wasn't lets manually update ``/boot/grub/menu.lst``  (make sure you are searching for correct kernel version, look at initial uname -a results.)
49
-        $ sed -i s/3.2.0-38-virtual/3.8.0-19-generic/ /boot/grub/menu.lst
50
-        
51
-        # once again lets make sure it worked.
52
-        $ grep 3.8.0- /boot/grub/menu.lst
53
-        title          Ubuntu 12.04.2 LTS, kernel 3.8.0-19-generic
54
-        kernel          /boot/vmlinuz-3.8.0-19-generic root=/dev/xvda1 ro quiet splash console=hvc0
55
-        initrd          /boot/initrd.img-3.8.0-19-generic
56
-        title          Ubuntu 12.04.2 LTS, kernel 3.8.0-19-generic (recovery mode)
57
-        kernel          /boot/vmlinuz-3.8.0-19-generic root=/dev/xvda1 ro quiet splash  single
58
-        initrd          /boot/initrd.img-3.8.0-19-generic
59
-        
60
-        # much better.
61
-
62
-7. Reboot server (either via command line or console)
63
-8. login again and check to make sure the kernel was updated
64
-
65
-    .. code-block:: bash
66
-        
67
-        $ ssh root@<server_ip>
68
-        $ uname -a
69
-        Linux docker-12-04 3.8.0-19-generic #30~precise1-Ubuntu SMP Wed May 1 22:26:36 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
70
-        
71
-        # nice 3.8.
72
-
73
-9. Make sure docker is running and test it out.
74
-
75
-    .. code-block:: bash
76
-        
77
-        $ start dockerd
78
-        $ docker pull busybox
79
-        $ docker run busybox /bin/echo hello world
80
-        hello world
81
-
82
-Alternate install
83
-^^^^^^^^^^^^^^^^^
84
-If you don't want to run the get.docker.io script and want to use packages instead, you can use the docker PPA. Here is how you use it. Replace step 5 with the following 3 steps.
85
-
86
-1. Add the custom package sources to your apt sources list. Copy and paste the following lines at once.
21
+**Do not attempt this on a production machine!**
87 22
 
88 23
 .. code-block:: bash
89 24
 
90
-   $ sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"
91
-
92
-
93
-2. Update your sources. You will see a warning that GPG signatures cannot be verified.
94
-
95
-.. code-block:: bash
25
+    # update apt
26
+    apt-get update
96 27
 
97
-   $ sudo apt-get update
28
+    # install the new kernel
29
+    apt-get install linux-generic-lts-raring
98 30
 
99 31
 
100
-3. Now install it, you will see another warning that the package cannot be authenticated. Confirm install.
32
+Great, now you have kernel installed in /boot/, next is to make it boot next time.
101 33
 
102 34
 .. code-block:: bash
103 35
 
104
-    $ apt-get install lxc-docker
105
-
106
-
107
-Ubuntu 12.10
108
-
109
-1. Build an Ubuntu 12.10 server using the "Next generation cloud servers", with your desired size. It will give you the password, keep that you will need it later.
110
-2. When the server is up and running ssh into the server.
111
-
112
-    .. code-block:: bash
113
-
114
-        $ ssh root@<server-ip>
115
-
116
-3. Once you are logged in you should check what kernel version you are running.
117
-
118
-    .. code-block:: bash
119
-
120
-        $ uname -a
121
-        Linux docker-12-10 3.5.0-25-generic #39-Ubuntu SMP Mon Feb 25 18:26:58 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
36
+    # find the exact names
37
+    find /boot/ -name '*3.8*'
122 38
 
123
-4. Let's update the server package list
39
+    # this should return some results
124 40
 
125
-    .. code-block:: bash
126 41
 
127
-        $ apt-get update
42
+Now you need to manually edit /boot/grub/menu.lst, you will find a section at the bottom with the existing options.
43
+Copy the top one and substitute the new kernel into that. Make sure the new kernel is on top, and double check kernel
44
+and initrd point to the right files.
128 45
 
129
-5. Now lets install Docker and it's dependencies. To keep things simple, we will use the Docker install script. It will take a couple of minutes.
46
+Make special care to double check the kernel and initrd entries.
130 47
 
131
-    .. code-block:: bash
132
-
133
-        $ curl get.docker.io | sudo sh -x
134
-
135
-6. Docker runs best with a new kernel, so lets use 3.8.x
136
-
137
-    .. code-block:: bash
138
-        
139
-        # add the ppa to get the right kernel package
140
-        $ echo deb http://ppa.launchpad.net/ubuntu-x-swat/q-lts-backport/ubuntu quantal main > /etc/apt/sources.list.d/xswat.list
141
-        
142
-        # add the key for the ppa
143
-        $ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B22AB97AF1CDFA9
144
-        
145
-        # update packages again
146
-        $ apt-get update
147
-        
148
-        # install the new kernel
149
-        $ apt-get install linux-image-3.8.0-19-generic
150
-
151
-        # make sure grub has been updated.
152
-        $ grep 3.8.0- /boot/grub/menu.lst
153
-        title   Ubuntu 12.10, kernel 3.8.0-19-generic
154
-        kernel  /boot/vmlinuz-3.8.0-19-generic root=/dev/xvda1 ro quiet splash console=hvc0
155
-        initrd  /boot/initrd.img-3.8.0-19-generic
156
-        title   Ubuntu 12.10, kernel 3.8.0-19-generic (recovery mode)
157
-        kernel  /boot/vmlinuz-3.8.0-19-generic root=/dev/xvda1 ro quiet splash  single
158
-        initrd  /boot/initrd.img-3.8.0-19-generic
159
-        
160
-        # looks good. If it doesn't work for you, look at the notes for 12.04 to fix.
161
-
162
-7. Reboot server (either via command line or console)
163
-8. login again and check to make sure the kernel was updated
164
-
165
-    .. code-block:: bash
166
-        
167
-        $ ssh root@<server_ip>
168
-        $ uname -a
169
-        Linux docker-12-10 3.8.0-19-generic #29~precise2-Ubuntu SMP Fri Apr 19 16:15:35 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
170
-        
171
-        # nice 3.8.
48
+.. code-block:: bash
172 49
 
173
-9. Make sure docker is running and test it out.
50
+    # now edit /boot/grub/menu.lst
51
+    vi /boot/grub/menu.lst
174 52
 
175
-    .. code-block:: bash
176
-        
177
-        $ start dockerd
178
-        $ docker pull busybox
179
-        $ docker run busybox /bin/echo hello world
180
-        hello world
53
+It will probably look something like this:
181 54
 
182
-Ubuntu 13.04
55
+::
183 56
 
184
-1. Build an Ubuntu 13.04 server using the "Next generation cloud servers", with your desired size. It will give you the password, keep that you will need it later.
185
-2. When the server is up and running ssh into the server.
57
+     ## ## End Default Options ##
186 58
 
187
-    .. code-block:: bash
59
+     title		Ubuntu 12.04.2 LTS, kernel 3.8.x generic
60
+     root		(hd0)
61
+     kernel		/boot/vmlinuz-3.8.0-19-generic root=/dev/xvda1 ro quiet splash console=hvc0
62
+     initrd		/boot/initrd.img-3.8.0-19-generic
188 63
 
189
-        $ ssh root@<server-ip>
64
+     title		Ubuntu 12.04.2 LTS, kernel 3.2.0-38-virtual
65
+     root		(hd0)
66
+     kernel		/boot/vmlinuz-3.2.0-38-virtual root=/dev/xvda1 ro quiet splash console=hvc0
67
+     initrd		/boot/initrd.img-3.2.0-38-virtual
190 68
 
191
-3. Once you are logged in you should check what kernel version you are running.
69
+     title		Ubuntu 12.04.2 LTS, kernel 3.2.0-38-virtual (recovery mode)
70
+     root		(hd0)
71
+     kernel		/boot/vmlinuz-3.2.0-38-virtual root=/dev/xvda1 ro quiet splash  single
72
+     initrd		/boot/initrd.img-3.2.0-38-virtual
192 73
 
193
-    .. code-block:: bash
194 74
 
195
-        $ uname -a
196
-        Linux docker-1304 3.8.0-19-generic #29-Ubuntu SMP Wed Apr 17 18:16:28 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
75
+Reboot server (either via command line or console)
197 76
 
198
-4. Let's update the server package list
77
+.. code-block:: bash
199 78
 
200
-    .. code-block:: bash
79
+   # reboot
201 80
 
202
-        $ apt-get update
81
+Verify the kernel was updated
203 82
 
204
-5. Now lets install Docker and it's dependencies. To keep things simple, we will use the Docker install script. It will take a couple of minutes.
83
+.. code-block:: bash
205 84
 
206
-    .. code-block:: bash
85
+    uname -a
86
+    # Linux docker-12-04 3.8.0-19-generic #30~precise1-Ubuntu SMP Wed May 1 22:26:36 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
207 87
 
208
-        $ curl get.docker.io | sudo sh -x
88
+    # nice! 3.8.
209 89
 
210
-6. Make sure docker is running and test it out.
211 90
 
212
-    .. code-block:: bash
213
-        
214
-        $ start dockerd
215
-        $ docker pull busybox
216
-        $ docker run busybox /bin/echo hello world
217
-        hello world
218
- 
219 91
\ No newline at end of file
92
+Now you can finish with the :ref:`ubuntu_linux` instructions.
220 93
\ No newline at end of file
... ...
@@ -5,20 +5,39 @@ Ubuntu Linux
5 5
 
6 6
   **Please note this project is currently under heavy development. It should not be used in production.**
7 7
 
8
+Right now, the officially supported distribution are:
8 9
 
9
-Right now, the officially supported distributions are:
10
+- :ref:`ubuntu_precise`
11
+- :ref:`ubuntu_raring`
12
+
13
+Docker has the following dependencies
14
+
15
+* Linux kernel 3.8
16
+* AUFS file system support (we are working on BTRFS support as an alternative)
17
+
18
+.. _ubuntu_precise:
19
+
20
+Ubuntu Precise 12.04 (LTS) (64-bit)
21
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
22
+
23
+This installation path should work at all times.
10 24
 
11
-- Ubuntu 12.04 (precise LTS) (64-bit)
12
-- Ubuntu 12.10 (quantal) (64-bit)
13 25
 
14 26
 Dependencies
15 27
 ------------
16 28
 
17
-The linux-image-extra package is only needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
29
+**Linux kernel 3.8**
30
+
31
+Due to a bug in LXC docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel we install comes with AUFS built in.
32
+
18 33
 
19 34
 .. code-block:: bash
20 35
 
21
-   sudo apt-get install linux-image-extra-`uname -r` lxc bsdtar
36
+   # install the backported kernel
37
+   sudo apt-get update && sudo apt-get install linux-image-3.8.0-19-generic
38
+
39
+   # reboot
40
+   sudo reboot
22 41
 
23 42
 
24 43
 Installation
... ...
@@ -28,33 +47,77 @@ Docker is available as a Ubuntu PPA (Personal Package Archive),
28 28
 `hosted on launchpad  <https://launchpad.net/~dotcloud/+archive/lxc-docker>`_
29 29
 which makes installing Docker on Ubuntu very easy.
30 30
 
31
+.. code-block:: bash
31 32
 
33
+   # Add the PPA sources to your apt sources list.
34
+   sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' > /etc/apt/sources.list.d/lxc-docker.list"
32 35
 
33
-Add the custom package sources to your apt sources list. Copy and paste the following lines at once.
36
+   # Update your sources, you will see a warning.
37
+   sudo apt-get update
38
+
39
+   # Install, you will see another warning that the package cannot be authenticated. Confirm install.
40
+   sudo apt-get install lxc-docker
41
+
42
+Verify it worked
34 43
 
35 44
 .. code-block:: bash
36 45
 
37
-   sudo sh -c "echo 'deb http://ppa.launchpad.net/dotcloud/lxc-docker/ubuntu precise main' >> /etc/apt/sources.list"
46
+   # download the base 'ubuntu' container and run bash inside it while setting up an interactive shell
47
+   docker run -i -t ubuntu /bin/bash
48
+
49
+   # type 'exit' to exit
50
+
51
+
52
+**Done!**, now continue with the :ref:`hello_world` example.
53
+
54
+.. _ubuntu_raring:
55
+
56
+Ubuntu Raring 13.04 (64 bit)
57
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58
+
59
+Dependencies
60
+------------
38 61
 
62
+**AUFS filesystem support**
39 63
 
40
-Update your sources. You will see a warning that GPG signatures cannot be verified.
64
+Ubuntu Raring already comes with the 3.8 kernel, so we don't need to install it. However, not all systems
65
+have AUFS filesystem support enabled, so we need to install it.
41 66
 
42 67
 .. code-block:: bash
43 68
 
44 69
    sudo apt-get update
70
+   sudo apt-get install linux-image-extra-`uname -r`
45 71
 
72
+Installation
73
+------------
74
+
75
+Docker is available as a Ubuntu PPA (Personal Package Archive),
76
+`hosted on launchpad  <https://launchpad.net/~dotcloud/+archive/lxc-docker>`_
77
+which makes installing Docker on Ubuntu very easy.
46 78
 
47
-Now install it, you will see another warning that the package cannot be authenticated. Confirm install.
79
+
80
+Add the custom package sources to your apt sources list.
48 81
 
49 82
 .. code-block:: bash
50 83
 
51
-    apt-get install lxc-docker
84
+   # add the sources to your apt
85
+   sudo add-apt-repository ppa:dotcloud/lxc-docker
86
+
87
+   # update
88
+   sudo apt-get update
89
+
90
+   # install
91
+   sudo apt-get install lxc-docker
92
+
52 93
 
53 94
 Verify it worked
54 95
 
55 96
 .. code-block:: bash
56 97
 
57
-   docker
98
+   # download the base 'ubuntu' container and run bash inside it while setting up an interactive shell
99
+   docker run -i -t ubuntu /bin/bash
100
+
101
+   # type exit to exit
58 102
 
59 103
 
60 104
 **Done!**, now continue with the :ref:`hello_world` example.
... ...
@@ -3,38 +3,53 @@
3 3
 Upgrading
4 4
 ============
5 5
 
6
-These instructions are for upgrading your Docker binary for when you had a custom (non package manager) installation.
7
-If you istalled docker using apt-get, use that to upgrade.
6
+**These instructions are for upgrading Docker**
8 7
 
9 8
 
10
-Get the latest docker binary:
9
+After normal installation
10
+-------------------------
11 11
 
12
-::
12
+If you installed Docker normally using apt-get or used Vagrant, use apt-get to upgrade.
13 13
 
14
-  wget http://get.docker.io/builds/$(uname -s)/$(uname -m)/docker-latest.tgz
14
+.. code-block:: bash
15 15
 
16
+   # update your sources list
17
+   sudo apt-get update
16 18
 
19
+   # install the latest
20
+   sudo apt-get install lxc-docker
17 21
 
18
-Unpack it to your current dir
19 22
 
20
-::
23
+After manual installation
24
+-------------------------
21 25
 
22
-   tar -xf docker-latest.tgz
26
+If you installed the Docker binary
27
+
28
+
29
+.. code-block:: bash
30
+
31
+   # kill the running docker daemon
32
+   killall docker
23 33
 
24 34
 
25
-Stop your current daemon. How you stop your daemon depends on how you started it.
35
+.. code-block:: bash
26 36
 
27
-- If you started the daemon manually (``sudo docker -d``), you can just kill the process: ``killall docker``
28
-- If the process was started using upstart (the ubuntu startup daemon), you may need to use that to stop it
37
+   # get the latest binary
38
+   wget http://get.docker.io/builds/Linux/x86_64/docker-latest.tgz
29 39
 
30 40
 
31
-Start docker in daemon mode (-d) and disconnect (&) starting ./docker will start the version in your current dir rather
32
-than the one in your PATH.
41
+.. code-block:: bash
42
+
43
+   # Unpack it to your current dir
44
+   tar -xf docker-latest.tgz
45
+
33 46
 
34
-Now start the daemon
47
+Start docker in daemon mode (-d) and disconnect (&) starting ./docker will start the version in your current dir rather than a version which
48
+might reside in your path.
35 49
 
36
-::
50
+.. code-block:: bash
37 51
 
52
+   # start the new version
38 53
    sudo ./docker -d &
39 54
 
40 55
 
... ...
@@ -1,14 +1,10 @@
1 1
 
2 2
 .. _install_using_vagrant:
3 3
 
4
-Using Vagrant
5
-=============
4
+Using Vagrant (Mac, Linux)
5
+==========================
6 6
 
7
-  Please note this is a community contributed installation path. The only 'official' installation is using the
8
-  :ref:`ubuntu_linux` installation path. This version may sometimes be out of date.
9
-
10
-**Requirements:**
11
-This guide will setup a new virtual machine with docker installed on your computer. This works on most operating
7
+This guide will setup a new virtualbox virtual machine with docker installed on your computer. This works on most operating
12 8
 systems, including MacOX, Windows, Linux, FreeBSD and others. If you can install these and have at least 400Mb RAM
13 9
 to spare you should be good.
14 10
 
... ...
@@ -3,8 +3,8 @@
3 3
 :keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin
4 4
 
5 5
 
6
-Windows (with Vagrant)
7
-======================
6
+Using Vagrant (Windows)
7
+=======================
8 8
 
9 9
   Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version
10 10
   may be out of date because it depends on some binaries to be updated and published
11 11
deleted file mode 100644
... ...
@@ -1,6 +0,0 @@
1
-
2
-# rule to redirect original links created when hosted on github pages
3
-rewrite ^/documentation/(.*).html http://docs.docker.io/en/latest/$1/ permanent;
4
-
5
-# rewrite the stuff which was on the current page
6
-rewrite ^/gettingstarted.html$ /gettingstarted/ permanent;
7 1
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+:title: docker documentation
1
+:description: docker documentation
2
+:keywords:
3
+
4
+Documentation
5
+=============
6
+
7
+This documentation has the following resources:
8
+
9
+.. toctree::
10
+   :titlesonly:
11
+
12
+   concepts/index
13
+   installation/index
14
+   use/index
15
+   examples/index
16
+   commandline/index
17
+   contributing/index
18
+   api/index
19
+   faq
20
+
21
+.. image:: concepts/images/lego_docker.jpg
... ...
@@ -66,7 +66,7 @@
66 66
                 <ul class="nav">
67 67
                     <li><a href="http://www.docker.io/">Introduction</a></li>
68 68
                     <li><a href="http://www.docker.io/gettingstarted/">Getting started</a></li>
69
-                    <li class="active"><a href="{{ pathto('concepts/containers/', 1) }}">Documentation</a></li>
69
+                    <li class="active"><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
70 70
                 </ul>
71 71
                 <div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
72 72
                     <a class="twitter" href="http://twitter.com/getdocker">Twitter</a>
... ...
@@ -155,70 +155,87 @@
155 155
 
156 156
 
157 157
   <!-- script which should be loaded after everything else -->
158
-  <script type="text/javascript">
158
+<script type="text/javascript">
159 159
 
160 160
 
161
-      var shiftWindow = function() {
162
-          scrollBy(0, -70);
163
-          console.log("window shifted")
164
-      };
165
-      window.addEventListener("hashchange", shiftWindow);
161
+    var shiftWindow = function() {
162
+        scrollBy(0, -70);
163
+        console.log("window shifted")
164
+    };
165
+    window.addEventListener("hashchange", shiftWindow);
166 166
 
167
-      function loadShift() {
168
-          if (window.location.hash) {
169
-              console.log("window has hash");
167
+    function loadShift() {
168
+        if (window.location.hash) {
169
+            console.log("window has hash");
170 170
             shiftWindow();
171
-          }
172
-      }
173
-
174
-      $(window).load(function() {
175
-          loadShift();
176
-          console.log("late loadshift");
177
-      });
178
-
179
-      $(function(){
180
-
181
-          // sidebar accordian-ing
182
-          // don't apply on last object (it should be the FAQ)
183
-
184
-          var elements = $('.toctree-l2');
185
-          for (var i = 0; i < elements.length; i += 1) { var current = $(elements[i]); console.log(current); current.children('ul').hide();}
186
-
187
-
188
-          // set initial collapsed state
189
-          var elements = $('.toctree-l1');
190
-          for (var i = 0; i < elements.length; i += 1) {
191
-              var current = $(elements[i]);
192
-              if (current.hasClass('current')) {
193
-                  // do nothing
194
-              } else {
195
-                  // collapse children
196
-                  current.children('ul').hide();
197
-              }
198
-          }
199
-
200
-          // attached handler on click
201
-          $('.sidebar > ul > li > a').not(':last').click(function(){
202
-              if ($(this).parent().hasClass('current')) {
203
-                  $(this).parent().children('ul').slideUp(200, function() {
204
-                      $(this).parent().removeClass('current'); // toggle after effect
205
-                  });
206
-              } else {
207
-                  //$('.sidebar > ul > li > ul').slideUp(100);
208
-                  var current = $(this);
209
-
210
-                  setTimeout(function() {
211
-                      $('.sidebar > ul > li').removeClass('current');
212
-                      current.parent().addClass('current'); // toggle before effect
213
-                      current.parent().children('ul').hide();
214
-                      current.parent().children('ul').slideDown(200);
215
-                  }, 100);
216
-              }
217
-              return false;
218
-          });
219
-
220
-      });
221
-  </script>
171
+        }
172
+    }
173
+
174
+    $(window).load(function() {
175
+        loadShift();
176
+        console.log("late loadshift");
177
+    });
178
+
179
+    $(function(){
180
+
181
+        // sidebar accordian-ing
182
+        // don't apply on last object (it should be the FAQ)
183
+
184
+        // define an array to which all opened items should be added
185
+        var openmenus = [];
186
+
187
+        var elements = $('.toctree-l2');
188
+        for (var i = 0; i < elements.length; i += 1) { var current = $(elements[i]); current.children('ul').hide();}
189
+
190
+
191
+        // set initial collapsed state
192
+        var elements = $('.toctree-l1');
193
+        for (var i = 0; i < elements.length; i += 1) {
194
+            var current = $(elements[i]);
195
+            if (current.hasClass('current')) {
196
+
197
+                currentlink = current.children('a')[0].href;
198
+                openmenus.push(currentlink);
199
+
200
+                // do nothing
201
+            } else {
202
+                // collapse children
203
+                current.children('ul').hide();
204
+            }
205
+        }
206
+
207
+        // attached handler on click
208
+        $('.sidebar > ul > li > a').not(':last').click(function(){
209
+
210
+            var index = $.inArray(this.href, openmenus)
211
+
212
+            if (index > -1) {
213
+                console.log(index);
214
+                openmenus.splice(index, 1);
215
+
216
+
217
+                $(this).parent().children('ul').slideUp(200, function() {
218
+                    // $(this).parent().removeClass('current'); // toggle after effect
219
+                });
220
+            }
221
+            else {
222
+                openmenus.push(this.href);
223
+                console.log(this);
224
+
225
+                var current = $(this);
226
+
227
+                setTimeout(function() {
228
+                    $('.sidebar > ul > li').removeClass('current');
229
+                    current.parent().addClass('current'); // toggle before effect
230
+                    current.parent().children('ul').hide();
231
+                    current.parent().children('ul').slideDown(200);
232
+                }, 100);
233
+            }
234
+            return false;
235
+        });
236
+
237
+    });
238
+</script>
222 239
 
223 240
     <!-- Google analytics -->
224 241
     <script type="text/javascript">
... ...
@@ -36,7 +36,7 @@
36 36
                 <ul class="nav">
37 37
                     <li><a href="../">Introduction</a></li>
38 38
                     <li class="active"><a href="">Getting started</a></li>
39
-                    <li class=""><a href="http://docs.docker.io/en/latest/concepts/introduction/">Documentation</a></li>
39
+                    <li class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
40 40
                 </ul>
41 41
 
42 42
                 <div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
... ...
@@ -76,6 +76,7 @@
76 76
                     <ul>
77 77
                         <li>Ubuntu 12.04 (LTS) (64-bit)</li>
78 78
                         <li> or Ubuntu 12.10 (quantal) (64-bit)</li>
79
+                        <li>The 3.8 Linux Kernel</li>
79 80
                     </ul>
80 81
                 <ol>
81 82
                     <li>
... ...
@@ -105,7 +106,8 @@
105 105
                             <pre>docker run -i -t ubuntu /bin/bash</pre>
106 106
                         </div>
107 107
                     </li>
108
-                    Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.
108
+                    Continue with the <a href="http://docs.docker.io/en/latest/examples/hello_world/">Hello world</a> example.<br>
109
+                    Or check <a href="http://docs.docker.io/en/latest/installation/ubuntulinux/">more detailed installation instructions</a>
109 110
                 </ol>
110 111
             </section>
111 112
 
... ...
@@ -58,9 +58,9 @@
58 58
 
59 59
             <div class="pull-right" >
60 60
                 <ul class="nav">
61
-                    <li class="active"><a href="../sources">Introduction</a></li>
61
+                    <li class="active"><a href="/">Introduction</a></li>
62 62
                     <li ><a href="gettingstarted">Getting started</a></li>
63
-                    <li class=""><a href="http://docs.docker.io/en/latest/concepts/introduction/">Documentation</a></li>
63
+                    <li class=""><a href="http://docs.docker.io/en/latest/">Documentation</a></li>
64 64
                 </ul>
65 65
 
66 66
                 <div class="social links" style="float: right; margin-top: 14px; margin-left: 12px">
... ...
@@ -2,8 +2,9 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"github.com/dotcloud/docker/utils"
5 6
 )
6 7
 
7
-func getKernelVersion() (*KernelVersionInfo, error) {
8
+func getKernelVersion() (*utils.KernelVersionInfo, error) {
8 9
 	return nil, fmt.Errorf("Kernel version detection is not available on darwin")
9 10
 }
... ...
@@ -2,12 +2,14 @@ package docker
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"github.com/dotcloud/docker/utils"
5 6
 	"strconv"
6 7
 	"strings"
7 8
 	"syscall"
8 9
 )
9 10
 
10
-func getKernelVersion() (*KernelVersionInfo, error) {
11
+// FIXME: Move this to utils package
12
+func getKernelVersion() (*utils.KernelVersionInfo, error) {
11 13
 	var (
12 14
 		uts                  syscall.Utsname
13 15
 		flavor               string
... ...
@@ -60,7 +62,7 @@ func getKernelVersion() (*KernelVersionInfo, error) {
60 60
 		flavor = ""
61 61
 	}
62 62
 
63
-	return &KernelVersionInfo{
63
+	return &utils.KernelVersionInfo{
64 64
 		Kernel: kernel,
65 65
 		Major:  major,
66 66
 		Minor:  minor,
... ...
@@ -3,9 +3,10 @@ package docker
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
+	"github.com/dotcloud/docker/registry"
7
+	"github.com/dotcloud/docker/utils"
6 8
 	"io"
7 9
 	"io/ioutil"
8
-	"net/http"
9 10
 	"os"
10 11
 	"path"
11 12
 	"path/filepath"
... ...
@@ -17,8 +18,7 @@ import (
17 17
 // A Graph is a store for versioned filesystem images and the relationship between them.
18 18
 type Graph struct {
19 19
 	Root         string
20
-	idIndex      *TruncIndex
21
-	httpClient   *http.Client
20
+	idIndex      *utils.TruncIndex
22 21
 	checksumLock map[string]*sync.Mutex
23 22
 	lockSumFile  *sync.Mutex
24 23
 	lockSumMap   *sync.Mutex
... ...
@@ -37,7 +37,7 @@ func NewGraph(root string) (*Graph, error) {
37 37
 	}
38 38
 	graph := &Graph{
39 39
 		Root:         abspath,
40
-		idIndex:      NewTruncIndex(),
40
+		idIndex:      utils.NewTruncIndex(),
41 41
 		checksumLock: make(map[string]*sync.Mutex),
42 42
 		lockSumFile:  &sync.Mutex{},
43 43
 		lockSumMap:   &sync.Mutex{},
... ...
@@ -165,7 +165,7 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
165 165
 	if err != nil {
166 166
 		return nil, err
167 167
 	}
168
-	return NewTempArchive(ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)"), tmp.Root)
168
+	return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)"), tmp.Root)
169 169
 }
170 170
 
171 171
 // Mktemp creates a temporary sub-directory inside the graph's filesystem.
... ...
@@ -324,3 +324,17 @@ func (graph *Graph) storeChecksums(checksums map[string]string) error {
324 324
 	}
325 325
 	return nil
326 326
 }
327
+
328
+func (graph *Graph) UpdateChecksums(newChecksums map[string]*registry.ImgData) error {
329
+	graph.lockSumFile.Lock()
330
+	defer graph.lockSumFile.Unlock()
331
+
332
+	localChecksums, err := graph.getStoredChecksums()
333
+	if err != nil {
334
+		return err
335
+	}
336
+	for id, elem := range newChecksums {
337
+		localChecksums[id] = elem.Checksum
338
+	}
339
+	return graph.storeChecksums(localChecksums)
340
+}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"archive/tar"
5 5
 	"bytes"
6 6
 	"errors"
7
+	"github.com/dotcloud/docker/utils"
7 8
 	"io"
8 9
 	"io/ioutil"
9 10
 	"os"
... ...
@@ -155,7 +156,7 @@ func TestDeletePrefix(t *testing.T) {
155 155
 	graph := tempGraph(t)
156 156
 	defer os.RemoveAll(graph.Root)
157 157
 	img := createTestImage(graph, t)
158
-	if err := graph.Delete(TruncateId(img.Id)); err != nil {
158
+	if err := graph.Delete(utils.TruncateId(img.Id)); err != nil {
159 159
 		t.Fatal(err)
160 160
 	}
161 161
 	assertNImages(graph, t, 0)
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"encoding/hex"
7 7
 	"encoding/json"
8 8
 	"fmt"
9
+	"github.com/dotcloud/docker/utils"
9 10
 	"io"
10 11
 	"io/ioutil"
11 12
 	"log"
... ...
@@ -180,7 +181,7 @@ func (image *Image) Changes(rw string) ([]Change, error) {
180 180
 }
181 181
 
182 182
 func (image *Image) ShortId() string {
183
-	return TruncateId(image.Id)
183
+	return utils.TruncateId(image.Id)
184 184
 }
185 185
 
186 186
 func ValidateId(id string) error {
... ...
@@ -359,3 +360,15 @@ func (img *Image) Checksum() (string, error) {
359 359
 
360 360
 	return hash, nil
361 361
 }
362
+
363
+// Build an Image object from raw json data
364
+func NewImgJson(src []byte) (*Image, error) {
365
+	ret := &Image{}
366
+
367
+	utils.Debugf("Json string: {%s}\n", src)
368
+	// FIXME: Is there a cleaner way to "purify" the input json?
369
+	if err := json.Unmarshal(src, ret); err != nil {
370
+		return nil, err
371
+	}
372
+	return ret, nil
373
+}
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"encoding/binary"
5 5
 	"errors"
6 6
 	"fmt"
7
+	"github.com/dotcloud/docker/utils"
7 8
 	"io"
8 9
 	"log"
9 10
 	"net"
... ...
@@ -97,7 +98,7 @@ func checkRouteOverlaps(dockerNetwork *net.IPNet) error {
97 97
 	if err != nil {
98 98
 		return err
99 99
 	}
100
-	Debugf("Routes:\n\n%s", output)
100
+	utils.Debugf("Routes:\n\n%s", output)
101 101
 	for _, line := range strings.Split(output, "\n") {
102 102
 		if strings.Trim(line, "\r\n\t ") == "" || strings.Contains(line, "default") {
103 103
 			continue
... ...
@@ -126,13 +127,13 @@ func CreateBridgeIface(ifaceName string) error {
126 126
 			ifaceAddr = addr
127 127
 			break
128 128
 		} else {
129
-			Debugf("%s: %s", addr, err)
129
+			utils.Debugf("%s: %s", addr, err)
130 130
 		}
131 131
 	}
132 132
 	if ifaceAddr == "" {
133 133
 		return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", ifaceName, ifaceName)
134 134
 	} else {
135
-		Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
135
+		utils.Debugf("Creating bridge %s with network %s", ifaceName, ifaceAddr)
136 136
 	}
137 137
 
138 138
 	if output, err := ip("link", "add", ifaceName, "type", "bridge"); err != nil {
... ...
@@ -239,22 +240,22 @@ func (mapper *PortMapper) Map(port int, dest net.TCPAddr) error {
239 239
 // proxy listens for socket connections on `listener`, and forwards them unmodified
240 240
 // to `proto:address`
241 241
 func proxy(listener net.Listener, proto, address string) error {
242
-	Debugf("proxying to %s:%s", proto, address)
243
-	defer Debugf("Done proxying to %s:%s", proto, address)
242
+	utils.Debugf("proxying to %s:%s", proto, address)
243
+	defer utils.Debugf("Done proxying to %s:%s", proto, address)
244 244
 	for {
245
-		Debugf("Listening on %s", listener)
245
+		utils.Debugf("Listening on %s", listener)
246 246
 		src, err := listener.Accept()
247 247
 		if err != nil {
248 248
 			return err
249 249
 		}
250
-		Debugf("Connecting to %s:%s", proto, address)
250
+		utils.Debugf("Connecting to %s:%s", proto, address)
251 251
 		dst, err := net.Dial(proto, address)
252 252
 		if err != nil {
253 253
 			log.Printf("Error connecting to %s:%s: %s", proto, address, err)
254 254
 			src.Close()
255 255
 			continue
256 256
 		}
257
-		Debugf("Connected to backend, splicing")
257
+		utils.Debugf("Connected to backend, splicing")
258 258
 		splice(src, dst)
259 259
 	}
260 260
 	return nil
... ...
@@ -317,7 +318,7 @@ func (alloc *PortAllocator) runFountain() {
317 317
 
318 318
 // FIXME: Release can no longer fail, change its prototype to reflect that.
319 319
 func (alloc *PortAllocator) Release(port int) error {
320
-	Debugf("Releasing %d", port)
320
+	utils.Debugf("Releasing %d", port)
321 321
 	alloc.lock.Lock()
322 322
 	delete(alloc.inUse, port)
323 323
 	alloc.lock.Unlock()
... ...
@@ -325,7 +326,7 @@ func (alloc *PortAllocator) Release(port int) error {
325 325
 }
326 326
 
327 327
 func (alloc *PortAllocator) Acquire(port int) (int, error) {
328
-	Debugf("Acquiring %d", port)
328
+	utils.Debugf("Acquiring %d", port)
329 329
 	if port == 0 {
330 330
 		// Allocate a port from the fountain
331 331
 		for port := range alloc.fountain {
... ...
@@ -1,35 +1,59 @@
1
+# Debian package Makefile
2
+#
3
+# Dependencies:  git debhelper build-essential autotools-dev devscripts golang
4
+# Notes:
5
+# Use 'make debian' to create the debian package
6
+# To create a specific version, use 'VERSION_TAG=v0.2.0 make debian'
7
+# GPG_KEY environment variable needs to contain a GPG private key for package
8
+# to be signed and uploaded to debian.
9
+# If GPG_KEY is not defined, make debian will create docker package and exit
10
+# with status code 2
11
+
1 12
 PKG_NAME=lxc-docker
2
-DOCKER_VERSION=$(shell head -1 changelog | awk 'match($$0, /\(.+\)/) {print substr($$0, RSTART+1, RLENGTH-4)}')
13
+ROOT_PATH=$(shell git rev-parse --show-toplevel)
3 14
 GITHUB_PATH=github.com/dotcloud/docker
4
-SOURCE_PKG=$(PKG_NAME)_$(DOCKER_VERSION).orig.tar.gz
5
-BUILD_SRC=${CURDIR}/../../build_src
15
+BUILD_SRC=build_src
16
+VERSION_TAG?=v$(shell sed -E 's/.+\((.+)-.+\).+/\1/;q' changelog)
17
+VERSION=$(shell echo ${VERSION_TAG} | cut -c2-)
18
+DOCKER_VERSION=${PKG_NAME}_${VERSION}
6 19
 
7 20
 all:
8
-	# Compile docker. Used by debian dpkg-buildpackage. 
21
+	# Compile docker. Used by debian dpkg-buildpackage.
9 22
 	cd src/${GITHUB_PATH}/docker; GOPATH=${CURDIR} go build
10 23
 
11 24
 install:
12
-	# Used by debian dpkg-buildpackage 
25
+	# Used by debian dpkg-buildpackage
13 26
 	mkdir -p $(DESTDIR)/usr/bin
14
-	mkdir -p $(DESTDIR)/etc/init.d
15
-	install -m 0755 src/${GITHUB_PATH}/docker/docker $(DESTDIR)/usr/bin
16
-	install -o root -m 0755 debian/docker.initd $(DESTDIR)/etc/init.d/docker
27
+	mkdir -p $(DESTDIR)/usr/share/man/man1
28
+	mkdir -p $(DESTDIR)/usr/share/doc/lxc-docker
29
+	install -m 0755 src/${GITHUB_PATH}/docker/docker $(DESTDIR)/usr/bin/lxc-docker
30
+	cp debian/lxc-docker.1 $(DESTDIR)/usr/share/man/man1
31
+	cp debian/CHANGELOG.md $(DESTDIR)/usr/share/doc/lxc-docker/changelog
17 32
 
18 33
 debian:
19
-	# This Makefile will compile the github master branch of dotcloud/docker
20
-	# Retrieve docker project and its go structure from internet
21
-	rm -rf ${BUILD_SRC}
22
-	GOPATH=${BUILD_SRC} go get ${GITHUB_PATH}
34
+	# Prepare docker source from revision ${VERSION_TAG}
35
+	rm -rf ${BUILD_SRC} ${PKG_NAME}_[0-9]*
36
+	git clone file://$(ROOT_PATH) ${BUILD_SRC}/src/${GITHUB_PATH} --branch ${VERSION_TAG} --depth 1
37
+	GOPATH=${CURDIR}/${BUILD_SRC} go get -d ${GITHUB_PATH}
23 38
 	# Add debianization
24 39
 	mkdir ${BUILD_SRC}/debian
25 40
 	cp Makefile ${BUILD_SRC}
26
-	cp -r * ${BUILD_SRC}/debian
27
-	cp ../../README.md ${BUILD_SRC}
41
+	cp -r `ls | grep -v ${BUILD_SRC}` ${BUILD_SRC}/debian
42
+	cp ${ROOT_PATH}/README.md ${BUILD_SRC}
43
+	cp ${ROOT_PATH}/CHANGELOG.md ${BUILD_SRC}/debian
28 44
 	# Cleanup
29
-	for d in `find ${BUILD_SRC} -name '.git*'`; do rm -rf $$d; done
30
-	rm -rf ${BUILD_SRC}/../${SOURCE_PKG}
31
-	rm -rf ${BUILD_SRC}/pkg
45
+	rm -rf `find . -name '.git*'`
46
+	rm -f ${DOCKER_VERSION}*
32 47
 	# Create docker debian files
33
-	cd ${BUILD_SRC}; tar czf ../${SOURCE_PKG} .
34
-	cd ${BUILD_SRC}; dpkg-buildpackage
48
+	cd ${BUILD_SRC}; tar czf ../${DOCKER_VERSION}.orig.tar.gz .
49
+	cd ${BUILD_SRC}; dpkg-buildpackage -us -uc
35 50
 	rm -rf ${BUILD_SRC}
51
+	# Sign package and upload it to PPA if GPG_KEY environment variable
52
+	# holds a private GPG KEY
53
+	if /usr/bin/test "$${GPG_KEY}" == ""; then exit 2; fi
54
+	mkdir ${BUILD_SRC}
55
+	# Import gpg signing key
56
+	echo "$${GPG_KEY}" | gpg --allow-secret-key-import --import
57
+	# Sign the package
58
+	cd ${BUILD_SRC}; dpkg-source -x ${CURDIR}/${DOCKER_VERSION}-1.dsc
59
+	cd ${BUILD_SRC}/${PKG_NAME}-${VERSION}; debuild -S -sa
36 60
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+Docker on Debian
1
+================
2
+
3
+Docker has been built and tested on Wheezy. All docker functionality works
4
+out of the box, except for memory limitation as the stock debian kernel
5
+disables it by default. To enable docker memory limitation, the kernel needs
6
+to be loaded with boot parameters: cgroup_enable=memory swapaccount=1
7
+
8
+
9
+Building docker package
10
+~~~~~~~~~~~~~~~~~~~~~~~
11
+
12
+Assuming you have a wheezy system up and running
13
+
14
+# Get building dependencies
15
+sudo apt-get update
16
+sudo apt-get install -y debhelper build-essential autotools-dev golang
17
+
18
+# Make the debian package
19
+git clone https://github.com/dotcloud/docker.git
20
+cd docker/packaging/debian
21
+make debian
22
+
23
+
24
+Install docker package
25
+~~~~~~~~~~~~~~~~~~~~~~
26
+
27
+sudo dpkg -i lxc-docker_*-1_amd64.deb; sudo apt-get install -f -y
0 28
deleted file mode 100644
... ...
@@ -1,31 +0,0 @@
1
-Docker on Debian
2
-================
3
-
4
-Docker has been built and tested on Wheezy. All docker functionality works
5
-out of the box, except for memory limitation as the stock debian kernel
6
-does not support it yet.
7
-
8
-
9
-Building docker package
10
-~~~~~~~~~~~~~~~~~~~~~~~
11
-
12
-Building Dependencies: debhelper, autotools-dev and golang
13
-
14
-
15
-Assuming you have a wheezy system up and running
16
-
17
-# Download a fresh copy of the docker project
18
-git clone https://github.com/dotcloud/docker.git
19
-cd docker
20
-
21
-# Get building dependencies
22
-sudo apt-get update ; sudo apt-get install -y debhelper autotools-dev golang
23
-
24
-# Make the debian package, with no memory limitation support
25
-(cd packaging/debian; make debian NO_MEMORY_LIMIT=1)
26
-
27
-
28
-Install docker package
29
-~~~~~~~~~~~~~~~~~~~~~~
30
-
31
-sudo dpkg -i lxc-docker_0.1.4-1_amd64.deb; sudo apt-get install -f -y
... ...
@@ -1,22 +1,18 @@
1
-# -*- mode: ruby -*-
2
-# vi: set ft=ruby :
1
+VM_IP = "192.168.33.31"
2
+PKG_DEP = "git debhelper build-essential autotools-dev devscripts golang"
3 3
 
4
-$BUILDBOT_IP = '192.168.33.31'
4
+Vagrant::Config.run do |config|
5
+  config.vm.box = 'debian-7.0.rc1.64'
6
+  config.vm.box_url = 'http://puppet-vagrant-boxes.puppetlabs.com/debian-70rc1-x64-vbox4210-nocm.box'
7
+  config.vm.share_folder 'v-data', '/data/docker', "#{File.dirname(__FILE__)}/../.."
8
+  config.vm.network :hostonly,VM_IP
5 9
 
6
-def v10(config)
7
-  config.vm.box = 'debian'
8
-  config.vm.share_folder 'v-data', '/data/docker', File.dirname(__FILE__) + '/../..'
9
-  config.vm.network :hostonly, $BUILDBOT_IP
10
+  # Add kernel cgroup memory limitation boot parameters
11
+  grub_cmd="sed -i 's#DEFAULT=\"quiet\"#DEFAULT=\"cgroup_enable=memory swapaccount=1 quiet\"#' /etc/default/grub"
12
+  config.vm.provision :shell, :inline => "#{grub_cmd};update-grub"
10 13
 
11 14
   # Install debian packaging dependencies and create debian packages
12
-  config.vm.provision :shell, :inline => 'apt-get -qq update; apt-get install -y debhelper autotools-dev golang'
13
-  config.vm.provision :shell, :inline => 'cd /data/docker/packaging/debian; make debian'
14
-end
15
-
16
-Vagrant::VERSION < '1.1.0' and Vagrant::Config.run do |config|
17
-  v10(config)
18
-end
19
-
20
-Vagrant::VERSION >= '1.1.0' and Vagrant.configure('1') do |config|
21
-  v10(config)
15
+  pkg_cmd = "apt-get -qq update; DEBIAN_FRONTEND=noninteractive apt-get install -qq -y #{PKG_DEP}; " \
16
+      "export GPG_KEY='#{ENV['GPG_KEY']}'; cd /data/docker/packaging/debian; make debian"
17
+  config.vm.provision :shell, :inline => pkg_cmd
22 18
 end
... ...
@@ -1,14 +1,16 @@
1
-lxc-docker (0.1.4-1) unstable; urgency=low
1
+lxc-docker (0.3.2-1) UNRELEASED; urgency=low
2
+  - Runtime: Store the actual archive on commit
3
+  - Registry: Improve the checksum process
4
+  - Registry: Use the size to have a good progress bar while pushing
5
+  - Registry: Use the actual archive if it exists in order to speed up the push
6
+  - Registry: Fix error 400 on push
2 7
 
3
-  Improvements [+], Updates [*], Bug fixes [-]:
4
-  * Changed default bridge interface do 'docker0'
5
-  - Fix a race condition when running the port allocator
8
+ -- Daniel Mizyrycki <daniel@dotcloud.com>  Sun, 12 May 2013 00:00:00 -0700
6 9
 
7
- -- Daniel Mizyrycki <daniel@dotcloud.com>  Wed, 10 Apr 2013 18:06:21 -0700
8 10
 
11
+lxc-docker (0.2.0-1) UNRELEASED; urgency=low
12
+ 
13
+  - Pre-release (Closes: #706060)
9 14
 
10
-lxc-docker (0.1.0-1) unstable; urgency=low
15
+ -- Daniel Mizyrycki <daniel@dotcloud.com>  Fri, 26 Apr 2013 23:41:29 -0700
11 16
 
12
-  * Initial release
13
-
14
- -- Daniel Mizyrycki <daniel@dotcloud.com>  Mon, 29 Mar 2013 18:09:55 -0700
... ...
@@ -2,15 +2,16 @@ Source: lxc-docker
2 2
 Section: admin
3 3
 Priority: optional
4 4
 Maintainer: Daniel Mizyrycki <daniel@dotcloud.com>
5
-Build-Depends: debhelper (>= 9),autotools-dev,golang
6
-Standards-Version: 3.9.3
5
+Build-Depends: debhelper (>=9), autotools-dev, golang
6
+Standards-Version: 3.9.4
7
+Vcs-Git: git://git.debian.org/git/collab-maint/lxc-docker.git
8
+Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/lxc-docker.git;a=summary
7 9
 Homepage: http://github.com/dotcloud/docker
8 10
 
9 11
 Package: lxc-docker
10 12
 Architecture: linux-any
11
-Depends: ${misc:Depends},${shlibs:Depends},lxc,bsdtar
12
-Conflicts: docker
13
-Description: lxc-docker is a Linux container runtime
13
+Depends: ${shlibs:Depends}, ${misc:Depends}, lxc, bsdtar
14
+Description: Linux container runtime
14 15
  Docker complements LXC with a high-level API which operates at the process
15 16
  level. It runs unix processes with strong guarantees of isolation and
16 17
  repeatability across servers.
... ...
@@ -1,237 +1,22 @@
1 1
 Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 2
 Upstream-Name: docker
3
-Upstream-Contact: DotCloud Inc <opensource@dotcloud.com>
3
+Upstream-Contact: dotCloud Inc <opensource@dotcloud.com>
4 4
 Source: http://github.com/dotcloud/docker
5 5
 
6 6
 Files: *
7
-Copyright: 2012, DotCloud Inc <opensource@dotcloud.com>
7
+Copyright: 2012, dotCloud Inc <opensource@dotcloud.com>
8 8
 License: Apache-2.0
9
- Apache License
10
- Version 2.0, January 2004
11
- http://www.apache.org/licenses/
12
- 
13
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
14
- 
15
- 1. Definitions.
16
- 
17
- "License" shall mean the terms and conditions for use, reproduction,
18
- and distribution as defined by Sections 1 through 9 of this document.
19
- 
20
- "Licensor" shall mean the copyright owner or entity authorized by
21
- the copyright owner that is granting the License.
22
- 
23
- "Legal Entity" shall mean the union of the acting entity and all
24
- other entities that control, are controlled by, or are under common
25
- control with that entity. For the purposes of this definition,
26
- "control" means (i) the power, direct or indirect, to cause the
27
- direction or management of such entity, whether by contract or
28
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
29
- outstanding shares, or (iii) beneficial ownership of such entity.
30
- 
31
- "You" (or "Your") shall mean an individual or Legal Entity
32
- exercising permissions granted by this License.
33
- 
34
- "Source" form shall mean the preferred form for making modifications,
35
- including but not limited to software source code, documentation
36
- source, and configuration files.
37
- 
38
- "Object" form shall mean any form resulting from mechanical
39
- transformation or translation of a Source form, including but
40
- not limited to compiled object code, generated documentation,
41
- and conversions to other media types.
42
- 
43
- "Work" shall mean the work of authorship, whether in Source or
44
- Object form, made available under the License, as indicated by a
45
- copyright notice that is included in or attached to the work
46
- (an example is provided in the Appendix below).
47
- 
48
- "Derivative Works" shall mean any work, whether in Source or Object
49
- form, that is based on (or derived from) the Work and for which the
50
- editorial revisions, annotations, elaborations, or other modifications
51
- represent, as a whole, an original work of authorship. For the purposes
52
- of this License, Derivative Works shall not include works that remain
53
- separable from, or merely link (or bind by name) to the interfaces of,
54
- the Work and Derivative Works thereof.
55
- 
56
- "Contribution" shall mean any work of authorship, including
57
- the original version of the Work and any modifications or additions
58
- to that Work or Derivative Works thereof, that is intentionally
59
- submitted to Licensor for inclusion in the Work by the copyright owner
60
- or by an individual or Legal Entity authorized to submit on behalf of
61
- the copyright owner. For the purposes of this definition, "submitted"
62
- means any form of electronic, verbal, or written communication sent
63
- to the Licensor or its representatives, including but not limited to
64
- communication on electronic mailing lists, source code control systems,
65
- and issue tracking systems that are managed by, or on behalf of, the
66
- Licensor for the purpose of discussing and improving the Work, but
67
- excluding communication that is conspicuously marked or otherwise
68
- designated in writing by the copyright owner as "Not a Contribution."
69
- 
70
- "Contributor" shall mean Licensor and any individual or Legal Entity
71
- on behalf of whom a Contribution has been received by Licensor and
72
- subsequently incorporated within the Work.
73
- 
74
- 2. Grant of Copyright License. Subject to the terms and conditions of
75
- this License, each Contributor hereby grants to You a perpetual,
76
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
- copyright license to reproduce, prepare Derivative Works of,
78
- publicly display, publicly perform, sublicense, and distribute the
79
- Work and such Derivative Works in Source or Object form.
80
- 
81
- 3. Grant of Patent License. Subject to the terms and conditions of
82
- this License, each Contributor hereby grants to You a perpetual,
83
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
84
- (except as stated in this section) patent license to make, have made,
85
- use, offer to sell, sell, import, and otherwise transfer the Work,
86
- where such license applies only to those patent claims licensable
87
- by such Contributor that are necessarily infringed by their
88
- Contribution(s) alone or by combination of their Contribution(s)
89
- with the Work to which such Contribution(s) was submitted. If You
90
- institute patent litigation against any entity (including a
91
- cross-claim or counterclaim in a lawsuit) alleging that the Work
92
- or a Contribution incorporated within the Work constitutes direct
93
- or contributory patent infringement, then any patent licenses
94
- granted to You under this License for that Work shall terminate
95
- as of the date such litigation is filed.
96
- 
97
- 4. Redistribution. You may reproduce and distribute copies of the
98
- Work or Derivative Works thereof in any medium, with or without
99
- modifications, and in Source or Object form, provided that You
100
- meet the following conditions:
101
- 
102
- (a) You must give any other recipients of the Work or
103
- Derivative Works a copy of this License; and
104
- 
105
- (b) You must cause any modified files to carry prominent notices
106
- stating that You changed the files; and
107
- 
108
- (c) You must retain, in the Source form of any Derivative Works
109
- that You distribute, all copyright, patent, trademark, and
110
- attribution notices from the Source form of the Work,
111
- excluding those notices that do not pertain to any part of
112
- the Derivative Works; and
113
- 
114
- (d) If the Work includes a "NOTICE" text file as part of its
115
- distribution, then any Derivative Works that You distribute must
116
- include a readable copy of the attribution notices contained
117
- within such NOTICE file, excluding those notices that do not
118
- pertain to any part of the Derivative Works, in at least one
119
- of the following places: within a NOTICE text file distributed
120
- as part of the Derivative Works; within the Source form or
121
- documentation, if provided along with the Derivative Works; or,
122
- within a display generated by the Derivative Works, if and
123
- wherever such third-party notices normally appear. The contents
124
- of the NOTICE file are for informational purposes only and
125
- do not modify the License. You may add Your own attribution
126
- notices within Derivative Works that You distribute, alongside
127
- or as an addendum to the NOTICE text from the Work, provided
128
- that such additional attribution notices cannot be construed
129
- as modifying the License.
130
- 
131
- You may add Your own copyright statement to Your modifications and
132
- may provide additional or different license terms and conditions
133
- for use, reproduction, or distribution of Your modifications, or
134
- for any such Derivative Works as a whole, provided Your use,
135
- reproduction, and distribution of the Work otherwise complies with
136
- the conditions stated in this License.
137
- 
138
- 5. Submission of Contributions. Unless You explicitly state otherwise,
139
- any Contribution intentionally submitted for inclusion in the Work
140
- by You to the Licensor shall be under the terms and conditions of
141
- this License, without any additional terms or conditions.
142
- Notwithstanding the above, nothing herein shall supersede or modify
143
- the terms of any separate license agreement you may have executed
144
- with Licensor regarding such Contributions.
145
- 
146
- 6. Trademarks. This License does not grant permission to use the trade
147
- names, trademarks, service marks, or product names of the Licensor,
148
- except as required for reasonable and customary use in describing the
149
- origin of the Work and reproducing the content of the NOTICE file.
150
- 
151
- 7. Disclaimer of Warranty. Unless required by applicable law or
152
- agreed to in writing, Licensor provides the Work (and each
153
- Contributor provides its Contributions) on an "AS IS" BASIS,
154
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
155
- implied, including, without limitation, any warranties or conditions
156
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
157
- PARTICULAR PURPOSE. You are solely responsible for determining the
158
- appropriateness of using or redistributing the Work and assume any
159
- risks associated with Your exercise of permissions under this License.
160
- 
161
- 8. Limitation of Liability. In no event and under no legal theory,
162
- whether in tort (including negligence), contract, or otherwise,
163
- unless required by applicable law (such as deliberate and grossly
164
- negligent acts) or agreed to in writing, shall any Contributor be
165
- liable to You for damages, including any direct, indirect, special,
166
- incidental, or consequential damages of any character arising as a
167
- result of this License or out of the use or inability to use the
168
- Work (including but not limited to damages for loss of goodwill,
169
- work stoppage, computer failure or malfunction, or any and all
170
- other commercial damages or losses), even if such Contributor
171
- has been advised of the possibility of such damages.
172
- 
173
- 9. Accepting Warranty or Additional Liability. While redistributing
174
- the Work or Derivative Works thereof, You may choose to offer,
175
- and charge a fee for, acceptance of support, warranty, indemnity,
176
- or other liability obligations and/or rights consistent with this
177
- License. However, in accepting such obligations, You may act only
178
- on Your own behalf and on Your sole responsibility, not on behalf
179
- of any other Contributor, and only if You agree to indemnify,
180
- defend, and hold each Contributor harmless for any liability
181
- incurred by, or claims asserted against, such Contributor by reason
182
- of your accepting any such warranty or additional liability.
183
- 
184
- END OF TERMS AND CONDITIONS
185
- 
186
- APPENDIX: How to apply the Apache License to your work.
187
- 
188
- To apply the Apache License to your work, attach the following
189
- boilerplate notice, with the fields enclosed by brackets "[]"
190
- replaced with your own identifying information. (Don't include
191
- the brackets!) The text should be enclosed in the appropriate
192
- comment syntax for the file format. We also recommend that a
193
- file or class name and description of purpose be included on the
194
- same "printed page" as the copyright notice for easier
195
- identification within third-party archives.
196
- 
197
- Copyright 2012 DotCloud Inc
198
- 
199 9
  Licensed under the Apache License, Version 2.0 (the "License");
200 10
  you may not use this file except in compliance with the License.
201 11
  You may obtain a copy of the License at
202
- 
12
+ .
203 13
  http://www.apache.org/licenses/LICENSE-2.0
204
- 
14
+ .
205 15
  Unless required by applicable law or agreed to in writing, software
206 16
  distributed under the License is distributed on an "AS IS" BASIS,
207 17
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
208 18
  See the License for the specific language governing permissions and
209 19
  limitations under the License.
210
- 
211
- 
212
-Files: src/github.com/kr/pty/*
213
-Copyright: Copyright (c) 2011 Keith Rarick
214
-License: Expat
215
- Copyright (c) 2011 Keith Rarick
216
- 
217
- Permission is hereby granted, free of charge, to any person
218
- obtaining a copy of this software and associated
219
- documentation files (the "Software"), to deal in the
220
- Software without restriction, including without limitation
221
- the rights to use, copy, modify, merge, publish, distribute,
222
- sublicense, and/or sell copies of the Software, and to
223
- permit persons to whom the Software is furnished to do so,
224
- subject to the following conditions:
225
- 
226
- The above copyright notice and this permission notice shall
227
- be included in all copies or substantial portions of the
228
- Software.
229
- 
230
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
231
- KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
232
- WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
233
- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
234
- OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
235
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
236
- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
237
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ .
21
+ On Debian systems, the complete text of the Apache version 2.0 license
22
+ can be found in "/usr/share/common-licenses/Apache-2.0".
238 23
deleted file mode 100644
... ...
@@ -1,49 +0,0 @@
1
-#!/bin/sh
2
-
3
-### BEGIN INIT INFO
4
-# Provides:          docker
5
-# Required-Start:    $local_fs
6
-# Required-Stop:     $local_fs
7
-# Default-Start:     2 3 4 5
8
-# Default-Stop:      0 1 6
9
-# Short-Description: docker
10
-# Description:       docker daemon
11
-### END INIT INFO
12
-
13
-DOCKER=/usr/bin/docker
14
-PIDFILE=/var/run/docker.pid
15
-
16
-# Check docker is present
17
-[ -x $DOCKER ] || log_success_msg "Docker not present"
18
-
19
-# Get lsb functions
20
-. /lib/lsb/init-functions
21
-
22
-
23
-case "$1" in
24
-  start)
25
-    log_begin_msg "Starting docker..."
26
-    start-stop-daemon --start --background --exec "$DOCKER" -- -d
27
-    log_end_msg $?
28
-    ;;
29
-  stop)
30
-    log_begin_msg "Stopping docker..."
31
-    docker_pid=`pgrep -f "$DOCKER -d"`
32
-    [ -n "$docker_pid" ] && kill $docker_pid
33
-    log_end_msg $?
34
-    ;;
35
-  status)
36
-    docker_pid=`pgrep -f "$DOCKER -d"`
37
-    if [ -z "$docker_pid" ] ; then
38
-      echo "docker not running"
39
-    else
40
-      echo "docker running (pid $docker_pid)"
41
-    fi
42
-    ;;
43
-  *)
44
-    echo "Usage: /etc/init.d/docker {start|stop|status}"
45
-    exit 1
46
-    ;;
47
-esac
48
-
49
-exit 0
50 1
new file mode 100644
... ...
@@ -0,0 +1,1149 @@
0
+.TH "DOCKER" "1" "May 07, 2013" "0.1" "Docker"
1
+.SH NAME
2
+docker \- Docker Documentation
3
+.
4
+.nr rst2man-indent-level 0
5
+.
6
+.de1 rstReportMargin
7
+\\$1 \\n[an-margin]
8
+level \\n[rst2man-indent-level]
9
+level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
10
+-
11
+\\n[rst2man-indent0]
12
+\\n[rst2man-indent1]
13
+\\n[rst2man-indent2]
14
+..
15
+.de1 INDENT
16
+.\" .rstReportMargin pre:
17
+. RS \\$1
18
+. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
19
+. nr rst2man-indent-level +1
20
+.\" .rstReportMargin post:
21
+..
22
+.de UNINDENT
23
+. RE
24
+.\" indent \\n[an-margin]
25
+.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
26
+.nr rst2man-indent-level -1
27
+.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
28
+.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
29
+..
30
+.\" Man page generated from reStructeredText.
31
+.
32
+.sp
33
+This documentation has the following resources:
34
+.SH CONCEPTS
35
+.sp
36
+Contents:
37
+.SS Standard Containers
38
+.SS What is a Standard Container?
39
+.sp
40
+Docker defines a unit of software delivery called a Standard Container. The goal of a Standard Container is to encapsulate a software component and all its dependencies in
41
+a format that is self\-describing and portable, so that any compliant runtime can run it without extra dependency, regardless of the underlying machine and the contents of the container.
42
+.sp
43
+The spec for Standard Containers is currently work in progress, but it is very straightforward. It mostly defines 1) an image format, 2) a set of standard operations, and 3) an execution environment.
44
+.sp
45
+A great analogy for this is the shipping container. Just like Standard Containers are a fundamental unit of software delivery, shipping containers (\fI\%http://bricks.argz.com/ins/7823-1/12\fP) are a fundamental unit of physical delivery.
46
+.SS Standard operations
47
+.sp
48
+Just like shipping containers, Standard Containers define a set of STANDARD OPERATIONS. Shipping containers can be lifted, stacked, locked, loaded, unloaded and labelled. Similarly, standard containers can be started, stopped, copied, snapshotted, downloaded, uploaded and tagged.
49
+.SS Content\-agnostic
50
+.sp
51
+Just like shipping containers, Standard Containers are CONTENT\-AGNOSTIC: all standard operations have the same effect regardless of the contents. A shipping container will be stacked in exactly the same way whether it contains Vietnamese powder coffee or spare Maserati parts. Similarly, Standard Containers are started or uploaded in the same way whether they contain a postgres database, a php application with its dependencies and application server, or Java build artifacts.
52
+.SS Infrastructure\-agnostic
53
+.sp
54
+Both types of containers are INFRASTRUCTURE\-AGNOSTIC: they can be transported to thousands of facilities around the world, and manipulated by a wide variety of equipment. A shipping container can be packed in a factory in Ukraine, transported by truck to the nearest routing center, stacked onto a train, loaded into a German boat by an Australian\-built crane, stored in a warehouse at a US facility, etc. Similarly, a standard container can be bundled on my laptop, uploaded to S3, downloaded, run and snapshotted by a build server at Equinix in Virginia, uploaded to 10 staging servers in a home\-made Openstack cluster, then sent to 30 production instances across 3 EC2 regions.
55
+.SS Designed for automation
56
+.sp
57
+Because they offer the same standard operations regardless of content and infrastructure, Standard Containers, just like their physical counterpart, are extremely well\-suited for automation. In fact, you could say automation is their secret weapon.
58
+.sp
59
+Many things that once required time\-consuming and error\-prone human effort can now be programmed. Before shipping containers, a bag of powder coffee was hauled, dragged, dropped, rolled and stacked by 10 different people in 10 different locations by the time it reached its destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The process was slow, inefficient and cost a fortune \- and was entirely different depending on the facility and the type of goods.
60
+.sp
61
+Similarly, before Standard Containers, by the time a software component ran in production, it had been individually built, configured, bundled, documented, patched, vendored, templated, tweaked and instrumented by 10 different people on 10 different computers. Builds failed, libraries conflicted, mirrors crashed, post\-it notes were lost, logs were misplaced, cluster updates were half\-broken. The process was slow, inefficient and cost a fortune \- and was entirely different depending on the language and infrastructure provider.
62
+.SS Industrial\-grade delivery
63
+.sp
64
+There are 17 million shipping containers in existence, packed with every physical good imaginable. Every single one of them can be loaded on the same boats, by the same cranes, in the same facilities, and sent anywhere in the World with incredible efficiency. It is embarrassing to think that a 30 ton shipment of coffee can safely travel half\-way across the World in \fIless time\fP than it takes a software team to deliver its code from one datacenter to another sitting 10 miles away.
65
+.sp
66
+With Standard Containers we can put an end to that embarrassment, by making INDUSTRIAL\-GRADE DELIVERY of software a reality.
67
+.SS Standard Container Specification
68
+.sp
69
+(TODO)
70
+.SS Image format
71
+.SS Standard operations
72
+.INDENT 0.0
73
+.IP \(bu 2
74
+Copy
75
+.IP \(bu 2
76
+Run
77
+.IP \(bu 2
78
+Stop
79
+.IP \(bu 2
80
+Wait
81
+.IP \(bu 2
82
+Commit
83
+.IP \(bu 2
84
+Attach standard streams
85
+.IP \(bu 2
86
+List filesystem changes
87
+.IP \(bu 2
88
+.UNINDENT
89
+.SS Execution environment
90
+.SS Root filesystem
91
+.SS Environment variables
92
+.SS Process arguments
93
+.SS Networking
94
+.SS Process namespacing
95
+.SS Resource limits
96
+.SS Process monitoring
97
+.SS Logging
98
+.SS Signals
99
+.SS Pseudo\-terminal allocation
100
+.SS Security
101
+.SH INSTALLATION
102
+.sp
103
+Contents:
104
+.SS Ubuntu Linux
105
+.INDENT 0.0
106
+.INDENT 3.5
107
+\fBPlease note this project is currently under heavy development. It should not be used in production.\fP
108
+.UNINDENT
109
+.UNINDENT
110
+.sp
111
+Installing on Ubuntu 12.04 and 12.10
112
+.sp
113
+Right now, the officially supported distributions are:
114
+.sp
115
+Ubuntu 12.04 (precise LTS)
116
+Ubuntu 12.10 (quantal)
117
+Docker probably works on other distributions featuring a recent kernel, the AUFS patch, and up\-to\-date lxc. However this has not been tested.
118
+.SS Install dependencies:
119
+.sp
120
+.nf
121
+.ft C
122
+sudo apt\-get install lxc wget bsdtar curl
123
+sudo apt\-get install linux\-image\-extra\-\(gauname \-r\(ga
124
+.ft P
125
+.fi
126
+.sp
127
+The linux\-image\-extra package is needed on standard Ubuntu EC2 AMIs in order to install the aufs kernel module.
128
+.sp
129
+Install the latest docker binary:
130
+.sp
131
+.nf
132
+.ft C
133
+wget http://get.docker.io/builds/$(uname \-s)/$(uname \-m)/docker\-master.tgz
134
+tar \-xf docker\-master.tgz
135
+.ft P
136
+.fi
137
+.sp
138
+Run your first container!
139
+.sp
140
+.nf
141
+.ft C
142
+cd docker\-master
143
+.ft P
144
+.fi
145
+.sp
146
+.nf
147
+.ft C
148
+sudo ./docker run \-i \-t base /bin/bash
149
+.ft P
150
+.fi
151
+.sp
152
+Consider adding docker to your PATH for simplicity.
153
+.sp
154
+Continue with the \fIhello_world\fP example.
155
+.SS Mac OS X and other linux
156
+.INDENT 0.0
157
+.INDENT 3.5
158
+Please note this is a community contributed installation path. The only \(aqofficial\(aq installation is using the \fIubuntu_linux\fP installation path. This version
159
+may be out of date because it depends on some binaries to be updated and published
160
+.UNINDENT
161
+.UNINDENT
162
+.SS Requirements
163
+.sp
164
+We currently rely on some Ubuntu\-linux specific packages, this will change in the future, but for now we provide a
165
+streamlined path to install Virtualbox with a Ubuntu 12.10 image using Vagrant.
166
+.INDENT 0.0
167
+.IP 1. 3
168
+Install virtualbox from \fI\%https://www.virtualbox.org/\fP (or use your package manager)
169
+.IP 2. 3
170
+Install vagrant from \fI\%http://www.vagrantup.com/\fP (or use your package manager)
171
+.IP 3. 3
172
+Install git if you had not installed it before, check if it is installed by running
173
+\fBgit\fP in a terminal window
174
+.UNINDENT
175
+.sp
176
+We recommend having at least about 2Gb of free disk space and 2Gb RAM (or more).
177
+.SS Installation
178
+.INDENT 0.0
179
+.IP 1. 3
180
+Fetch the docker sources
181
+.UNINDENT
182
+.sp
183
+.nf
184
+.ft C
185
+git clone https://github.com/dotcloud/docker.git
186
+.ft P
187
+.fi
188
+.INDENT 0.0
189
+.IP 2. 3
190
+Run vagrant from the sources directory
191
+.UNINDENT
192
+.sp
193
+.nf
194
+.ft C
195
+vagrant up
196
+.ft P
197
+.fi
198
+.sp
199
+Vagrant will:
200
+.INDENT 0.0
201
+.IP \(bu 2
202
+Download the Quantal64 base ubuntu virtual machine image from get.docker.io/
203
+.IP \(bu 2
204
+Boot this image in virtualbox
205
+.UNINDENT
206
+.sp
207
+Then it will use Puppet to perform an initial setup in this machine:
208
+.INDENT 0.0
209
+.IP \(bu 2
210
+Download & untar the most recent docker binary tarball to vagrant homedir.
211
+.IP \(bu 2
212
+Debootstrap to /var/lib/docker/images/ubuntu.
213
+.IP \(bu 2
214
+Install & run dockerd as service.
215
+.IP \(bu 2
216
+Put docker in /usr/local/bin.
217
+.IP \(bu 2
218
+Put latest Go toolchain in /usr/local/go.
219
+.UNINDENT
220
+.sp
221
+You now have a Ubuntu Virtual Machine running with docker pre\-installed.
222
+.sp
223
+To access the VM and use Docker, Run \fBvagrant ssh\fP from the same directory as where you ran
224
+\fBvagrant up\fP. Vagrant will make sure to connect you to the correct VM.
225
+.sp
226
+.nf
227
+.ft C
228
+vagrant ssh
229
+.ft P
230
+.fi
231
+.sp
232
+Now you are in the VM, run docker
233
+.sp
234
+.nf
235
+.ft C
236
+docker
237
+.ft P
238
+.fi
239
+.sp
240
+Continue with the \fIhello_world\fP example.
241
+.SS Windows
242
+.INDENT 0.0
243
+.INDENT 3.5
244
+Please note this is a community contributed installation path. The only \(aqofficial\(aq installation is using the \fIubuntu_linux\fP installation path. This version
245
+may be out of date because it depends on some binaries to be updated and published
246
+.UNINDENT
247
+.UNINDENT
248
+.SS Requirements
249
+.INDENT 0.0
250
+.IP 1. 3
251
+Install virtualbox from \fI\%https://www.virtualbox.org\fP \- or follow this \fI\%tutorial\fP
252
+.UNINDENT
253
+.INDENT 0.0
254
+.IP 2. 3
255
+Install vagrant from \fI\%http://www.vagrantup.com\fP \- or follow this \fI\%tutorial\fP
256
+.UNINDENT
257
+.INDENT 0.0
258
+.IP 3. 3
259
+Install git with ssh from \fI\%http://git-scm.com/downloads\fP \- or follow this \fI\%tutorial\fP
260
+.UNINDENT
261
+.sp
262
+We recommend having at least 2Gb of free disk space and 2Gb of RAM (or more).
263
+.SS Opening a command prompt
264
+.sp
265
+First open a cmd prompt. Press Windows key and then press “R” key. This will open the RUN dialog box for you. Type “cmd” and press Enter. Or you can click on Start, type “cmd” in the “Search programs and files” field, and click on cmd.exe.
266
+[image: Git install]
267
+[image]
268
+.sp
269
+This should open a cmd prompt window.
270
+[image: run docker]
271
+[image]
272
+.sp
273
+Alternatively, you can also use a Cygwin terminal, or Git Bash (or any other command line program you are usually using). The next steps would be the same.
274
+.SS Launch an Ubuntu virtual server
275
+.sp
276
+Let’s download and run an Ubuntu image with docker binaries already installed.
277
+.sp
278
+.nf
279
+.ft C
280
+git clone https://github.com/dotcloud/docker.git
281
+cd docker
282
+vagrant up
283
+.ft P
284
+.fi
285
+[image: run docker]
286
+[image]
287
+.sp
288
+Congratulations! You are running an Ubuntu server with docker installed on it. You do not see it though, because it is running in the background.
289
+.SS Log onto your Ubuntu server
290
+.sp
291
+Let’s log into your Ubuntu server now. To do so you have two choices:
292
+.INDENT 0.0
293
+.IP \(bu 2
294
+Use Vagrant on Windows command prompt OR
295
+.IP \(bu 2
296
+Use SSH
297
+.UNINDENT
298
+.SS Using Vagrant on Windows Command Prompt
299
+.sp
300
+Run the following command
301
+.sp
302
+.nf
303
+.ft C
304
+vagrant ssh
305
+.ft P
306
+.fi
307
+.sp
308
+You may see an error message starting with “\fIssh\fP executable not found”. In this case it means that you do not have SSH in your PATH. If you do not have SSH in your PATH you can set it up with the “set” command. For instance, if your ssh.exe is in the folder named “C:Program Files (x86)Gitbin”, then you can run the following command:
309
+.sp
310
+.nf
311
+.ft C
312
+set PATH=%PATH%;C:\eProgram Files (x86)\eGit\ebin
313
+.ft P
314
+.fi
315
+[image: run docker]
316
+[image]
317
+.SS Using SSH
318
+.sp
319
+First step is to get the IP and port of your Ubuntu server. Simply run:
320
+.sp
321
+.nf
322
+.ft C
323
+vagrant ssh\-config
324
+.ft P
325
+.fi
326
+.sp
327
+You should see an output with HostName and Port information. In this example, HostName is 127.0.0.1 and port is 2222. And the User is “vagrant”. The password is not shown, but it is also “vagrant”.
328
+[image: run docker]
329
+[image]
330
+.sp
331
+You can now use this information for connecting via SSH to your server. To do so you can:
332
+.INDENT 0.0
333
+.IP \(bu 2
334
+Use putty.exe OR
335
+.IP \(bu 2
336
+Use SSH from a terminal
337
+.UNINDENT
338
+.SS Use putty.exe
339
+.sp
340
+You can download putty.exe from this page \fI\%http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html\fP
341
+Launch putty.exe and simply enter the information you got from last step.
342
+[image: run docker]
343
+[image]
344
+.sp
345
+Open, and enter user = vagrant and password = vagrant.
346
+[image: run docker]
347
+[image]
348
+.SS SSH from a terminal
349
+.sp
350
+You can also run this command on your favorite terminal (windows prompt, cygwin, git\-bash, …). Make sure to adapt the IP and port from what you got from the vagrant ssh\-config command.
351
+.sp
352
+.nf
353
+.ft C
354
+ssh vagrant@127.0.0.1 –p 2222
355
+.ft P
356
+.fi
357
+.sp
358
+Enter user = vagrant and password = vagrant.
359
+[image: run docker]
360
+[image]
361
+.sp
362
+Congratulations, you are now logged onto your Ubuntu Server, running on top of your Windows machine !
363
+.SS Running Docker
364
+.sp
365
+First you have to be root in order to run docker. Simply run the following command:
366
+.sp
367
+.nf
368
+.ft C
369
+sudo su
370
+.ft P
371
+.fi
372
+.sp
373
+You are now ready for the docker’s “hello world” example. Run
374
+.sp
375
+.nf
376
+.ft C
377
+docker run \-a busybox echo hello world
378
+.ft P
379
+.fi
380
+[image: run docker]
381
+[image]
382
+.sp
383
+All done!
384
+.sp
385
+Now you can continue with the \fIhello_world\fP example.
386
+.SS Amazon EC2
387
+.INDENT 0.0
388
+.INDENT 3.5
389
+Please note this is a community contributed installation path. The only \(aqofficial\(aq installation is using the \fIubuntu_linux\fP installation path. This version
390
+may be out of date because it depends on some binaries to be updated and published
391
+.UNINDENT
392
+.UNINDENT
393
+.SS Installation
394
+.sp
395
+Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant 1.1 or higher is required.
396
+.INDENT 0.0
397
+.IP 1. 3
398
+Install vagrant from \fI\%http://www.vagrantup.com/\fP (or use your package manager)
399
+.IP 2. 3
400
+Install the vagrant aws plugin
401
+.sp
402
+.nf
403
+.ft C
404
+vagrant plugin install vagrant\-aws
405
+.ft P
406
+.fi
407
+.IP 3. 3
408
+Get the docker sources, this will give you the latest Vagrantfile and puppet manifests.
409
+.sp
410
+.nf
411
+.ft C
412
+git clone https://github.com/dotcloud/docker.git
413
+.ft P
414
+.fi
415
+.IP 4. 3
416
+Check your AWS environment.
417
+.sp
418
+Create a keypair specifically for EC2, give it a name and save it to your disk. \fII usually store these in my ~/.ssh/ folder\fP.
419
+.sp
420
+Check that your default security group has an inbound rule to accept SSH (port 22) connections.
421
+.IP 5. 3
422
+Inform Vagrant of your settings
423
+.sp
424
+Vagrant will read your access credentials from your environment, so we need to set them there first. Make sure
425
+you have everything on amazon aws setup so you can (manually) deploy a new image to EC2.
426
+.sp
427
+.nf
428
+.ft C
429
+export AWS_ACCESS_KEY_ID=xxx
430
+export AWS_SECRET_ACCESS_KEY=xxx
431
+export AWS_KEYPAIR_NAME=xxx
432
+export AWS_SSH_PRIVKEY=xxx
433
+.ft P
434
+.fi
435
+.sp
436
+The environment variables are:
437
+.INDENT 3.0
438
+.IP \(bu 2
439
+\fBAWS_ACCESS_KEY_ID\fP \- The API key used to make requests to AWS
440
+.IP \(bu 2
441
+\fBAWS_SECRET_ACCESS_KEY\fP \- The secret key to make AWS API requests
442
+.IP \(bu 2
443
+\fBAWS_KEYPAIR_NAME\fP \- The name of the keypair used for this EC2 instance
444
+.IP \(bu 2
445
+\fBAWS_SSH_PRIVKEY\fP \- The path to the private key for the named keypair, for example \fB~/.ssh/docker.pem\fP
446
+.UNINDENT
447
+.sp
448
+You can check if they are set correctly by doing something like
449
+.sp
450
+.nf
451
+.ft C
452
+echo $AWS_ACCESS_KEY_ID
453
+.ft P
454
+.fi
455
+.IP 6. 3
456
+Do the magic!
457
+.sp
458
+.nf
459
+.ft C
460
+vagrant up \-\-provider=aws
461
+.ft P
462
+.fi
463
+.sp
464
+If it stalls indefinitely on \fB[default] Waiting for SSH to become available...\fP, Double check your default security
465
+zone on AWS includes rights to SSH (port 22) to your container.
466
+.sp
467
+If you have an advanced AWS setup, you might want to have a look at the \fI\%https://github.com/mitchellh/vagrant-aws\fP
468
+.IP 7. 3
469
+Connect to your machine
470
+.sp
471
+.nf
472
+.ft C
473
+vagrant ssh
474
+.ft P
475
+.fi
476
+.IP 8. 3
477
+Your first command
478
+.sp
479
+Now you are in the VM, run docker
480
+.sp
481
+.nf
482
+.ft C
483
+docker
484
+.ft P
485
+.fi
486
+.UNINDENT
487
+.sp
488
+Continue with the \fIhello_world\fP example.
489
+.SH EXAMPLES
490
+.sp
491
+Contents:
492
+.SS Hello World
493
+.sp
494
+This is the most basic example available for using docker
495
+.sp
496
+This example assumes you have Docker installed.
497
+.sp
498
+Download the base container
499
+.sp
500
+.nf
501
+.ft C
502
+# Download a base image
503
+docker pull base
504
+.ft P
505
+.fi
506
+.sp
507
+The \fIbase\fP image is a minimal \fIubuntu\fP based container, alternatively you can select \fIbusybox\fP, a bare
508
+minimal linux system. The images are retrieved from the docker repository.
509
+.sp
510
+.nf
511
+.ft C
512
+#run a simple echo command, that will echo hello world back to the console over standard out.
513
+docker run base /bin/echo hello world
514
+.ft P
515
+.fi
516
+.sp
517
+\fBExplanation:\fP
518
+.INDENT 0.0
519
+.IP \(bu 2
520
+\fB"docker run"\fP run a command in a new container
521
+.IP \(bu 2
522
+\fB"base"\fP is the image we want to run the command inside of.
523
+.IP \(bu 2
524
+\fB"/bin/echo"\fP is the command we want to run in the container
525
+.IP \(bu 2
526
+\fB"hello world"\fP is the input for the echo command
527
+.UNINDENT
528
+.sp
529
+\fBVideo:\fP
530
+.sp
531
+See the example in action
532
+.sp
533
+Continue to the \fIhello_world_daemon\fP example.
534
+.SS Hello World Daemon
535
+.sp
536
+The most boring daemon ever written.
537
+.sp
538
+This example assumes you have Docker installed and with the base image already imported \fBdocker pull base\fP.
539
+We will use the base image to run a simple hello world daemon that will just print hello world to standard
540
+out every second. It will continue to do this until we stop it.
541
+.sp
542
+\fBSteps:\fP
543
+.sp
544
+.nf
545
+.ft C
546
+$ CONTAINER_ID=$(docker run \-d base /bin/sh \-c "while true; do echo hello world; sleep 1; done")
547
+.ft P
548
+.fi
549
+.sp
550
+We are going to run a simple hello world daemon in a new container made from the busybox daemon.
551
+.INDENT 0.0
552
+.IP \(bu 2
553
+\fB"docker run \-d "\fP run a command in a new container. We pass "\-d" so it runs as a daemon.
554
+.IP \(bu 2
555
+\fB"base"\fP is the image we want to run the command inside of.
556
+.IP \(bu 2
557
+\fB"/bin/sh \-c"\fP is the command we want to run in the container
558
+.IP \(bu 2
559
+\fB"while true; do echo hello world; sleep 1; done"\fP is the mini script we want to run, that will just print hello world once a second until we stop it.
560
+.IP \(bu 2
561
+\fB$CONTAINER_ID\fP the output of the run command will return a container id, we can use in future commands to see what is going on with this process.
562
+.UNINDENT
563
+.sp
564
+.nf
565
+.ft C
566
+$ docker logs $CONTAINER_ID
567
+.ft P
568
+.fi
569
+.sp
570
+Check the logs make sure it is working correctly.
571
+.INDENT 0.0
572
+.IP \(bu 2
573
+\fB"docker logs\fP" This will return the logs for a container
574
+.IP \(bu 2
575
+\fB$CONTAINER_ID\fP The Id of the container we want the logs for.
576
+.UNINDENT
577
+.sp
578
+.nf
579
+.ft C
580
+docker attach $CONTAINER_ID
581
+.ft P
582
+.fi
583
+.sp
584
+Attach to the container to see the results in realtime.
585
+.INDENT 0.0
586
+.IP \(bu 2
587
+\fB"docker attach\fP" This will allow us to attach to a background process to see what is going on.
588
+.IP \(bu 2
589
+\fB$CONTAINER_ID\fP The Id of the container we want to attach too.
590
+.UNINDENT
591
+.sp
592
+.nf
593
+.ft C
594
+docker ps
595
+.ft P
596
+.fi
597
+.sp
598
+Check the process list to make sure it is running.
599
+.INDENT 0.0
600
+.IP \(bu 2
601
+\fB"docker ps"\fP this shows all running process managed by docker
602
+.UNINDENT
603
+.sp
604
+.nf
605
+.ft C
606
+$ docker stop $CONTAINER_ID
607
+.ft P
608
+.fi
609
+.sp
610
+Stop the container, since we don\(aqt need it anymore.
611
+.INDENT 0.0
612
+.IP \(bu 2
613
+\fB"docker stop"\fP This stops a container
614
+.IP \(bu 2
615
+\fB$CONTAINER_ID\fP The Id of the container we want to stop.
616
+.UNINDENT
617
+.sp
618
+.nf
619
+.ft C
620
+docker ps
621
+.ft P
622
+.fi
623
+.sp
624
+Make sure it is really stopped.
625
+.sp
626
+\fBVideo:\fP
627
+.sp
628
+See the example in action
629
+.sp
630
+Continue to the \fIpython_web_app\fP example.
631
+.SS Notes:
632
+.INDENT 0.0
633
+.IP \(bu 2
634
+\fBDocker daemon\fP The docker daemon is started by \fBsudo docker \-d\fP, Vagrant may have started
635
+the Docker daemon for you, but you will need to restart it this way if it was terminated. Otherwise
636
+it may give you \fBCouldn\(aqt create Tag store: open /var/lib/docker/repositories: permission denied\fP
637
+.UNINDENT
638
+.SS Building a python web app
639
+.sp
640
+The goal of this example is to show you how you can author your own docker images using a parent image, making changes to it, and then saving the results as a new image. We will do that by making a simple hello flask web application image.
641
+.sp
642
+\fBSteps:\fP
643
+.sp
644
+.nf
645
+.ft C
646
+$ docker import shykes/pybuilder
647
+.ft P
648
+.fi
649
+.sp
650
+We are importing the "shykes/pybuilder" docker image
651
+.sp
652
+.nf
653
+.ft C
654
+$ URL=http://github.com/shykes/helloflask/archive/master.tar.gz
655
+.ft P
656
+.fi
657
+.sp
658
+We set a URL variable that points to a tarball of a simple helloflask web app
659
+.sp
660
+.nf
661
+.ft C
662
+$ BUILD_JOB=$(docker run \-t shykes/pybuilder:1d9aab3737242c65 /usr/local/bin/buildapp $URL)
663
+.ft P
664
+.fi
665
+.sp
666
+Inside of the "shykes/pybuilder" image there is a command called buildapp, we are running that command and passing the $URL variable from step 2 to it, and running the whole thing inside of a new container. BUILD_JOB will be set with the new container_id. "1d9aab3737242c65" came from the output of step 1 when importing image. also available from \(aqdocker images\(aq.
667
+.sp
668
+.nf
669
+.ft C
670
+$ docker attach $BUILD_JOB
671
+[...]
672
+.ft P
673
+.fi
674
+.sp
675
+We attach to the new container to see what is going on. Ctrl\-C to disconnect
676
+.sp
677
+.nf
678
+.ft C
679
+$ BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/hykes/helloflask/master)
680
+.ft P
681
+.fi
682
+.sp
683
+Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name.
684
+.sp
685
+.nf
686
+.ft C
687
+$ WEB_WORKER=$(docker run \-p 5000 $BUILD_IMG /usr/local/bin/runapp)
688
+.ft P
689
+.fi
690
+.sp
691
+Use the new image we just created and create a new container with network port 5000, and return the container id and store in the WEB_WORKER variable.
692
+.sp
693
+.nf
694
+.ft C
695
+$ docker logs $WEB_WORKER
696
+ * Running on http://0.0.0.0:5000/
697
+.ft P
698
+.fi
699
+.sp
700
+view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on \fI\%http://0.0.0.0:5000/\fP" in the log output.
701
+.sp
702
+\fBVideo:\fP
703
+.sp
704
+See the example in action
705
+.sp
706
+Continue to the \fI\%base commands\fP
707
+.SH CONTRIBUTING
708
+.SS Contributing to Docker
709
+.sp
710
+Want to hack on Docker? Awesome! There are instructions to get you
711
+started on the website: \fI\%http://docker.io/gettingstarted.html\fP
712
+.sp
713
+They are probably not perfect, please let us know if anything feels
714
+wrong or incomplete.
715
+.SS Contribution guidelines
716
+.SS Pull requests are always welcome
717
+.sp
718
+We are always thrilled to receive pull requests, and do our best to
719
+process them as fast as possible. Not sure if that typo is worth a pull
720
+request? Do it! We will appreciate it.
721
+.sp
722
+If your pull request is not accepted on the first try, don\(aqt be
723
+discouraged! If there\(aqs a problem with the implementation, hopefully you
724
+received feedback on what to improve.
725
+.sp
726
+We\(aqre trying very hard to keep Docker lean and focused. We don\(aqt want it
727
+to do everything for everybody. This means that we might decide against
728
+incorporating a new feature. However, there might be a way to implement
729
+that feature \fIon top of\fP docker.
730
+.SS Discuss your design on the mailing list
731
+.sp
732
+We recommend discussing your plans \fI\%on the mailing
733
+list\fP
734
+before starting to code \- especially for more ambitious contributions.
735
+This gives other contributors a chance to point you in the right
736
+direction, give feedback on your design, and maybe point out if someone
737
+else is working on the same thing.
738
+.SS Create issues...
739
+.sp
740
+Any significant improvement should be documented as \fI\%a github
741
+issue\fP before anybody
742
+starts working on it.
743
+.SS ...but check for existing issues first!
744
+.sp
745
+Please take a moment to check that an issue doesn\(aqt already exist
746
+documenting your bug report or improvement proposal. If it does, it
747
+never hurts to add a quick "+1" or "I have this problem too". This will
748
+help prioritize the most common problems and requests.
749
+.SS Write tests
750
+.sp
751
+Golang has a great testing suite built in: use it! Take a look at
752
+existing tests for inspiration.
753
+.SS Setting up a dev environment
754
+.sp
755
+Instructions that have been verified to work on Ubuntu 12.10,
756
+.sp
757
+Then run the docker daemon,
758
+.sp
759
+Run the \fBgo install\fP command (above) to recompile docker.
760
+.SH COMMANDS
761
+.sp
762
+Contents:
763
+.SS Base commands
764
+.SS Running an interactive shell
765
+.sp
766
+.nf
767
+.ft C
768
+# Download a base image
769
+docker import base
770
+
771
+# Run an interactive shell in the base image,
772
+# allocate a tty, attach stdin and stdout
773
+docker run \-a \-i \-t base /bin/bash
774
+.ft P
775
+.fi
776
+.SS Starting a long\-running worker process
777
+.sp
778
+.nf
779
+.ft C
780
+# Run docker in daemon mode
781
+(docker \-d || echo "Docker daemon already running") &
782
+
783
+# Start a very useful long\-running process
784
+JOB=$(docker run base /bin/sh \-c "while true; do echo Hello world!; sleep 1; done")
785
+
786
+# Collect the output of the job so far
787
+docker logs $JOB
788
+
789
+# Kill the job
790
+docker kill $JOB
791
+.ft P
792
+.fi
793
+.SS Listing all running containers
794
+.sp
795
+.nf
796
+.ft C
797
+docker ps
798
+.ft P
799
+.fi
800
+.SS Expose a service on a TCP port
801
+.sp
802
+.nf
803
+.ft C
804
+# Expose port 4444 of this container, and tell netcat to listen on it
805
+JOB=$(docker run \-p 4444 base /bin/nc \-l \-p 4444)
806
+
807
+# Which public port is NATed to my container?
808
+PORT=$(docker port $JOB 4444)
809
+
810
+# Connect to the public port via the host\(aqs public address
811
+echo hello world | nc $(hostname) $PORT
812
+
813
+# Verify that the network connection worked
814
+echo "Daemon received: $(docker logs $JOB)"
815
+.ft P
816
+.fi
817
+.sp
818
+Continue to the complete \fI\%Command Line Interface\fP
819
+.SS Command Line Interface
820
+.SS Docker Usage
821
+.sp
822
+.nf
823
+.ft C
824
+$ docker
825
+  Usage: docker COMMAND [arg...]
826
+
827
+  A self\-sufficient runtime for linux containers.
828
+
829
+  Commands:
830
+      attach    Attach to a running container
831
+      commit    Create a new image from a container\(aqs changes
832
+      diff      Inspect changes on a container\(aqs filesystem
833
+      export    Stream the contents of a container as a tar archive
834
+      history   Show the history of an image
835
+      images    List images
836
+      import    Create a new filesystem image from the contents of a tarball
837
+      info      Display system\-wide information
838
+      inspect   Return low\-level information on a container
839
+      kill      Kill a running container
840
+      login     Register or Login to the docker registry server
841
+      logs      Fetch the logs of a container
842
+      port      Lookup the public\-facing port which is NAT\-ed to PRIVATE_PORT
843
+      ps        List containers
844
+      pull      Pull an image or a repository to the docker registry server
845
+      push      Push an image or a repository to the docker registry server
846
+      restart   Restart a running container
847
+      rm        Remove a container
848
+      rmi       Remove an image
849
+      run       Run a command in a new container
850
+      start     Start a stopped container
851
+      stop      Stop a running container
852
+      tag       Tag an image into a repository
853
+      version   Show the docker version information
854
+      wait      Block until a container stops, then print its exit code
855
+.ft P
856
+.fi
857
+.SS attach
858
+.sp
859
+.nf
860
+.ft C
861
+Usage: docker attach [OPTIONS]
862
+
863
+Attach to a running container
864
+
865
+  \-e=true: Attach to stderr
866
+  \-i=false: Attach to stdin
867
+  \-o=true: Attach to stdout
868
+.ft P
869
+.fi
870
+.SS commit
871
+.sp
872
+.nf
873
+.ft C
874
+Usage: docker commit [OPTIONS] CONTAINER [DEST]
875
+
876
+Create a new image from a container\(aqs changes
877
+
878
+\-m="": Commit message
879
+.ft P
880
+.fi
881
+.SS diff
882
+.sp
883
+.nf
884
+.ft C
885
+Usage: docker diff CONTAINER [OPTIONS]
886
+
887
+Inspect changes on a container\(aqs filesystem
888
+.ft P
889
+.fi
890
+.SS export
891
+.sp
892
+.nf
893
+.ft C
894
+Usage: docker export CONTAINER
895
+
896
+Export the contents of a filesystem as a tar archive
897
+.ft P
898
+.fi
899
+.SS history
900
+.sp
901
+.nf
902
+.ft C
903
+Usage: docker history [OPTIONS] IMAGE
904
+
905
+Show the history of an image
906
+.ft P
907
+.fi
908
+.SS images
909
+.sp
910
+.nf
911
+.ft C
912
+Usage: docker images [OPTIONS] [NAME]
913
+
914
+List images
915
+
916
+  \-a=false: show all images
917
+  \-q=false: only show numeric IDs
918
+.ft P
919
+.fi
920
+.SS import
921
+.sp
922
+Usage: docker import [OPTIONS] URL|\- [REPOSITORY [TAG]]
923
+.sp
924
+Create a new filesystem image from the contents of a tarball
925
+.SS info
926
+.sp
927
+.nf
928
+.ft C
929
+Usage: docker info
930
+
931
+Display system\-wide information.
932
+.ft P
933
+.fi
934
+.SS inspect
935
+.sp
936
+.nf
937
+.ft C
938
+Usage: docker inspect [OPTIONS] CONTAINER
939
+
940
+Return low\-level information on a container
941
+.ft P
942
+.fi
943
+.SS kill
944
+.sp
945
+.nf
946
+.ft C
947
+Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
948
+
949
+Kill a running container
950
+.ft P
951
+.fi
952
+.SS login
953
+.sp
954
+.nf
955
+.ft C
956
+Usage: docker login
957
+
958
+Register or Login to the docker registry server
959
+.ft P
960
+.fi
961
+.SS logs
962
+.sp
963
+.nf
964
+.ft C
965
+Usage: docker logs [OPTIONS] CONTAINER
966
+
967
+Fetch the logs of a container
968
+.ft P
969
+.fi
970
+.SS port
971
+.sp
972
+.nf
973
+.ft C
974
+Usage: docker port [OPTIONS] CONTAINER PRIVATE_PORT
975
+
976
+Lookup the public\-facing port which is NAT\-ed to PRIVATE_PORT
977
+.ft P
978
+.fi
979
+.SS ps
980
+.sp
981
+.nf
982
+.ft C
983
+Usage: docker ps [OPTIONS]
984
+
985
+List containers
986
+
987
+  \-a=false: Show all containers. Only running containers are shown by default.
988
+  \-notrunc=false: Don\(aqt truncate output
989
+  \-q=false: Only display numeric IDs
990
+.ft P
991
+.fi
992
+.SS pull
993
+.sp
994
+.nf
995
+.ft C
996
+Usage: docker pull NAME
997
+
998
+Pull an image or a repository from the registry
999
+.ft P
1000
+.fi
1001
+.SS push
1002
+.sp
1003
+.nf
1004
+.ft C
1005
+Usage: docker push NAME
1006
+
1007
+Push an image or a repository to the registry
1008
+.ft P
1009
+.fi
1010
+.SS restart
1011
+.sp
1012
+.nf
1013
+.ft C
1014
+Usage: docker restart [OPTIONS] NAME
1015
+
1016
+Restart a running container
1017
+.ft P
1018
+.fi
1019
+.SS rm
1020
+.sp
1021
+.nf
1022
+.ft C
1023
+Usage: docker rm [OPTIONS] CONTAINER
1024
+
1025
+Remove a container
1026
+.ft P
1027
+.fi
1028
+.SS rmi
1029
+.sp
1030
+.nf
1031
+.ft C
1032
+Usage: docker rmi [OPTIONS] IMAGE
1033
+
1034
+Remove an image
1035
+
1036
+  \-a=false: Use IMAGE as a path and remove ALL images in this path
1037
+  \-r=false: Use IMAGE as a regular expression instead of an exact name
1038
+.ft P
1039
+.fi
1040
+.SS run
1041
+.sp
1042
+.nf
1043
+.ft C
1044
+Usage: docker run [OPTIONS] IMAGE COMMAND [ARG...]
1045
+
1046
+Run a command in a new container
1047
+
1048
+  \-a=false: Attach stdin and stdout
1049
+  \-c="": Comment
1050
+  \-i=false: Keep stdin open even if not attached
1051
+  \-m=0: Memory limit (in bytes)
1052
+  \-p=[]: Map a network port to the container
1053
+  \-t=false: Allocate a pseudo\-tty
1054
+  \-u="": Username or UID
1055
+.ft P
1056
+.fi
1057
+.SS start
1058
+.sp
1059
+.nf
1060
+.ft C
1061
+Usage: docker start [OPTIONS] NAME
1062
+
1063
+Start a stopped container
1064
+.ft P
1065
+.fi
1066
+.SS stop
1067
+.sp
1068
+.nf
1069
+.ft C
1070
+Usage: docker stop [OPTIONS] NAME
1071
+
1072
+Stop a running container
1073
+.ft P
1074
+.fi
1075
+.SS tag
1076
+.sp
1077
+.nf
1078
+.ft C
1079
+Usage: docker tag [OPTIONS] IMAGE REPOSITORY [TAG]
1080
+
1081
+Tag an image into a repository
1082
+
1083
+  \-f=false: Force
1084
+.ft P
1085
+.fi
1086
+.SS version
1087
+.sp
1088
+.nf
1089
+.ft C
1090
+Usage: docker version
1091
+
1092
+Show the docker version information
1093
+.ft P
1094
+.fi
1095
+.SS wait
1096
+.sp
1097
+.nf
1098
+.ft C
1099
+Usage: docker wait [OPTIONS] NAME
1100
+
1101
+Block until a container stops, then print its exit code.
1102
+.ft P
1103
+.fi
1104
+.SH FAQ
1105
+.SS Most frequently asked questions.
1106
+.sp
1107
+\fB1. How much does Docker cost?\fP
1108
+.sp
1109
+Docker is 100% free, it is open source, so you can use it without paying.
1110
+.sp
1111
+\fB2. What open source license are you using?\fP
1112
+.sp
1113
+We are using the Apache License Version 2.0, see it here: \fI\%https://github.com/dotcloud/docker/blob/master/LICENSE\fP
1114
+.sp
1115
+\fB3. Does Docker run on Mac OS X or Windows?\fP
1116
+.sp
1117
+Not at this time, Docker currently only runs on Linux, but you can use VirtualBox to run Docker in a virtual machine on your box, and get the best of both worlds. Check out the \fI\%MacOSX\fP and \fI\%Windows\fP intallation guides.
1118
+.sp
1119
+\fB4. How do containers compare to virtual machines?\fP
1120
+.sp
1121
+They are complementary. VMs are best used to allocate chunks of hardware resources. Containers operate at the process level, which makes them very lightweight and perfect as a unit of software delivery.
1122
+.sp
1123
+\fB5. Can I help by adding some questions and answers?\fP
1124
+.sp
1125
+Definitely! You can fork \fI\%the repo\fP and edit the documentation sources.
1126
+.sp
1127
+\fB42. Where can I find more answers?\fP
1128
+.sp
1129
+You can find more answers on:
1130
+.INDENT 0.0
1131
+.IP \(bu 2
1132
+\fI\%IRC: docker on freenode\fP
1133
+.IP \(bu 2
1134
+\fI\%Github\fP
1135
+.IP \(bu 2
1136
+\fI\%Ask questions on Stackoverflow\fP
1137
+.IP \(bu 2
1138
+\fI\%Join the conversation on Twitter\fP
1139
+.UNINDENT
1140
+.sp
1141
+Looking for something else to read? Checkout the \fIhello_world\fP example.
1142
+.SH AUTHOR
1143
+Team Docker
1144
+.SH COPYRIGHT
1145
+2013, Team Docker
1146
+.\" Generated by docutils manpage writer.
1147
+.\" 
1148
+.
0 1149
new file mode 100755
... ...
@@ -0,0 +1,74 @@
0
+#!/bin/sh
1
+
2
+### BEGIN INIT INFO
3
+# Provides:             lxc-docker
4
+# Required-Start:       $syslog $remote_fs
5
+# Required-Stop:        $syslog $remote_fs
6
+# Default-Start:        2 3 4 5
7
+# Default-Stop:         0 1 6
8
+# Short-Description:    Linux container runtime
9
+# Description:          Linux container runtime
10
+### END INIT INFO
11
+
12
+DOCKER=/usr/bin/lxc-docker
13
+
14
+# Check lxc-docker is present
15
+[ -x $DOCKER ] || (log_failure_msg "lxc-docker not present"; exit 1)
16
+
17
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
18
+
19
+# Get lsb functions
20
+. /lib/lsb/init-functions
21
+
22
+check_root_id ()
23
+{
24
+  if [ "$(id -u)" != "0" ]; then
25
+    log_failure_msg "LXC Docker must be run as root"; exit 1
26
+  fi
27
+}
28
+
29
+case "$1" in
30
+  start)
31
+    check_root_id || exit 1
32
+    log_begin_msg "Starting LXC Docker"
33
+    mount | grep cgroup >/dev/null || mount -t cgroup none /sys/fs/cgroup
34
+    start-stop-daemon --start --background --exec "$DOCKER" -- -d
35
+    log_end_msg $?
36
+    ;;
37
+
38
+  stop)
39
+    check_root_id || exit 1
40
+    log_begin_msg "Stopping LXC Docker"
41
+    docker_pid=`pgrep -f "$DOCKER -d"`
42
+    [ -n "$docker_pid" ] && kill $docker_pid
43
+    log_end_msg $?
44
+    ;;
45
+
46
+  restart)
47
+    check_root_id || exit 1
48
+    docker_pid=`pgrep -f "$DOCKER -d"`
49
+    [ -n "$docker_pid" ] && /etc/init.d/lxc-docker stop
50
+    /etc/init.d/lxc-docker start
51
+    ;;
52
+
53
+  force-reload)
54
+    check_root_id || exit 1
55
+    /etc/init.d/lxc-docker restart
56
+    ;;
57
+
58
+  status)
59
+    docker_pid=`pgrep -f "$DOCKER -d"`
60
+    if [ -z "$docker_pid" ] ; then
61
+      echo "lxc-docker not running"
62
+    else
63
+      echo "lxc-docker running (pid $docker_pid)"
64
+    fi
65
+    ;;
66
+
67
+  *)
68
+    echo "Usage: /etc/init.d/lxc-docker {start|stop|restart|status}"
69
+    exit 1
70
+    ;;
71
+esac
72
+
73
+exit 0
0 74
deleted file mode 100644
... ...
@@ -1,13 +0,0 @@
1
-#!/bin/sh
2
-
3
-# Ensure cgroup is mounted
4
-if [ -z "`/bin/egrep -e '^cgroup' /etc/fstab`" ]; then
5
-    /bin/echo 'cgroup  /sys/fs/cgroup  cgroup  defaults  0   0' >>/etc/fstab
6
-fi
7
-if [ -z "`/bin/mount | /bin/egrep -e '^cgroup'`" ]; then
8
-    /bin/mount /sys/fs/cgroup
9
-fi
10
-
11
-# Start docker
12
-/usr/sbin/update-rc.d docker defaults
13
-/etc/init.d/docker start
... ...
@@ -1,12 +1,6 @@
1 1
 #!/usr/bin/make -f
2 2
 # -*- makefile -*-
3
-# Sample debian/rules that uses debhelper.
4
-# This file was originally written by Joey Hess and Craig Small.
5
-# As a special exception, when this file is copied by dh-make into a
6
-# dh-make output file, you may use that output file without restriction.
7
-# This special exception was added by Craig Small in version 0.37 of dh-make.
8 3
 
9
-# Uncomment this to turn on verbose mode.
10 4
 #export DH_VERBOSE=1
11 5
 
12 6
 %:
13 7
deleted file mode 100644
... ...
@@ -1,748 +0,0 @@
1
-package docker
2
-
3
-import (
4
-	"bytes"
5
-	"encoding/json"
6
-	"fmt"
7
-	"github.com/dotcloud/docker/auth"
8
-	"github.com/shin-/cookiejar"
9
-	"io"
10
-	"io/ioutil"
11
-	"net/http"
12
-	"net/url"
13
-	"os"
14
-	"path"
15
-	"strings"
16
-)
17
-
18
-//FIXME: Set the endpoint in a conf file or via commandline
19
-const INDEX_ENDPOINT = auth.INDEX_SERVER + "/v1"
20
-
21
-// Build an Image object from raw json data
22
-func NewImgJson(src []byte) (*Image, error) {
23
-	ret := &Image{}
24
-
25
-	Debugf("Json string: {%s}\n", src)
26
-	// FIXME: Is there a cleaner way to "purify" the input json?
27
-	if err := json.Unmarshal(src, ret); err != nil {
28
-		return nil, err
29
-	}
30
-	return ret, nil
31
-}
32
-
33
-func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
34
-	for _, cookie := range c.Jar.Cookies(req.URL) {
35
-		req.AddCookie(cookie)
36
-	}
37
-	return c.Do(req)
38
-}
39
-
40
-// Retrieve the history of a given image from the Registry.
41
-// Return a list of the parent's json (requested image included)
42
-func (graph *Graph) getRemoteHistory(imgId, registry string, token []string) ([]string, error) {
43
-	client := graph.getHttpClient()
44
-
45
-	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/ancestry", nil)
46
-	if err != nil {
47
-		return nil, err
48
-	}
49
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
50
-	res, err := client.Do(req)
51
-	if err != nil || res.StatusCode != 200 {
52
-		if res != nil {
53
-			return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgId)
54
-		}
55
-		return nil, err
56
-	}
57
-	defer res.Body.Close()
58
-
59
-	jsonString, err := ioutil.ReadAll(res.Body)
60
-	if err != nil {
61
-		return nil, fmt.Errorf("Error while reading the http response: %s\n", err)
62
-	}
63
-
64
-	Debugf("Ancestry: %s", jsonString)
65
-	history := new([]string)
66
-	if err := json.Unmarshal(jsonString, history); err != nil {
67
-		return nil, err
68
-	}
69
-	return *history, nil
70
-}
71
-
72
-func (graph *Graph) getHttpClient() *http.Client {
73
-	if graph.httpClient == nil {
74
-		graph.httpClient = &http.Client{}
75
-		graph.httpClient.Jar = cookiejar.NewCookieJar()
76
-	}
77
-	return graph.httpClient
78
-}
79
-
80
-// Check if an image exists in the Registry
81
-func (graph *Graph) LookupRemoteImage(imgId, registry string, authConfig *auth.AuthConfig) bool {
82
-	rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
83
-
84
-	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
85
-	if err != nil {
86
-		return false
87
-	}
88
-	req.SetBasicAuth(authConfig.Username, authConfig.Password)
89
-	res, err := rt.RoundTrip(req)
90
-	return err == nil && res.StatusCode == 307
91
-}
92
-
93
-func (graph *Graph) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) {
94
-	u := INDEX_ENDPOINT + "/repositories/" + repository + "/images"
95
-	req, err := http.NewRequest("GET", u, nil)
96
-	if err != nil {
97
-		return nil, err
98
-	}
99
-	if authConfig != nil && len(authConfig.Username) > 0 {
100
-		req.SetBasicAuth(authConfig.Username, authConfig.Password)
101
-	}
102
-	res, err := graph.getHttpClient().Do(req)
103
-	if err != nil {
104
-		return nil, err
105
-	}
106
-	defer res.Body.Close()
107
-
108
-	// Repository doesn't exist yet
109
-	if res.StatusCode == 404 {
110
-		return nil, nil
111
-	}
112
-
113
-	jsonData, err := ioutil.ReadAll(res.Body)
114
-	if err != nil {
115
-		return nil, err
116
-	}
117
-
118
-	imageList := []map[string]string{}
119
-
120
-	err = json.Unmarshal(jsonData, &imageList)
121
-	if err != nil {
122
-		Debugf("Body: %s (%s)\n", res.Body, u)
123
-		return nil, err
124
-	}
125
-
126
-	return imageList, nil
127
-}
128
-
129
-// Retrieve an image from the Registry.
130
-// Returns the Image object as well as the layer as an Archive (io.Reader)
131
-func (graph *Graph) getRemoteImage(stdout io.Writer, imgId, registry string, token []string) (*Image, Archive, error) {
132
-	client := graph.getHttpClient()
133
-
134
-	fmt.Fprintf(stdout, "Pulling %s metadata\r\n", imgId)
135
-	// Get the Json
136
-	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
137
-	if err != nil {
138
-		return nil, nil, fmt.Errorf("Failed to download json: %s", err)
139
-	}
140
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
141
-	res, err := client.Do(req)
142
-	if err != nil {
143
-		return nil, nil, fmt.Errorf("Failed to download json: %s", err)
144
-	}
145
-	if res.StatusCode != 200 {
146
-		return nil, nil, fmt.Errorf("HTTP code %d", res.StatusCode)
147
-	}
148
-	defer res.Body.Close()
149
-
150
-	jsonString, err := ioutil.ReadAll(res.Body)
151
-	if err != nil {
152
-		return nil, nil, fmt.Errorf("Failed to download json: %s", err)
153
-	}
154
-
155
-	img, err := NewImgJson(jsonString)
156
-	if err != nil {
157
-		return nil, nil, fmt.Errorf("Failed to parse json: %s", err)
158
-	}
159
-	img.Id = imgId
160
-
161
-	// Get the layer
162
-	fmt.Fprintf(stdout, "Pulling %s fs layer\r\n", imgId)
163
-	req, err = http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
164
-	if err != nil {
165
-		return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
166
-	}
167
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
168
-	res, err = client.Do(req)
169
-	if err != nil {
170
-		return nil, nil, err
171
-	}
172
-	return img, ProgressReader(res.Body, int(res.ContentLength), stdout, "Downloading %v/%v (%v)"), nil
173
-}
174
-
175
-func (graph *Graph) getRemoteTags(stdout io.Writer, registries []string, repository string, token []string) (map[string]string, error) {
176
-	client := graph.getHttpClient()
177
-	if strings.Count(repository, "/") == 0 {
178
-		// This will be removed once the Registry supports auto-resolution on
179
-		// the "library" namespace
180
-		repository = "library/" + repository
181
-	}
182
-	for _, host := range registries {
183
-		endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
184
-		req, err := http.NewRequest("GET", endpoint, nil)
185
-		if err != nil {
186
-			return nil, err
187
-		}
188
-		req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
189
-		res, err := client.Do(req)
190
-		defer res.Body.Close()
191
-		Debugf("Got status code %d from %s", res.StatusCode, endpoint)
192
-		if err != nil || (res.StatusCode != 200 && res.StatusCode != 404) {
193
-			continue
194
-		} else if res.StatusCode == 404 {
195
-			return nil, fmt.Errorf("Repository not found")
196
-		}
197
-
198
-		result := make(map[string]string)
199
-
200
-		rawJson, err := ioutil.ReadAll(res.Body)
201
-		if err != nil {
202
-			return nil, err
203
-		}
204
-		if err = json.Unmarshal(rawJson, &result); err != nil {
205
-			return nil, err
206
-		}
207
-		return result, nil
208
-	}
209
-	return nil, fmt.Errorf("Could not reach any registry endpoint")
210
-}
211
-
212
-func (graph *Graph) getImageForTag(stdout io.Writer, tag, remote, registry string, token []string) (string, error) {
213
-	client := graph.getHttpClient()
214
-
215
-	if !strings.Contains(remote, "/") {
216
-		remote = "library/" + remote
217
-	}
218
-
219
-	registryEndpoint := "https://" + registry + "/v1"
220
-	repositoryTarget := registryEndpoint + "/repositories/" + remote + "/tags/" + tag
221
-
222
-	req, err := http.NewRequest("GET", repositoryTarget, nil)
223
-	if err != nil {
224
-		return "", err
225
-	}
226
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
227
-	res, err := client.Do(req)
228
-	if err != nil {
229
-		return "", fmt.Errorf("Error while retrieving repository info: %v", err)
230
-	}
231
-	defer res.Body.Close()
232
-	if res.StatusCode == 403 {
233
-		return "", fmt.Errorf("You aren't authorized to access this resource")
234
-	} else if res.StatusCode != 200 {
235
-		return "", fmt.Errorf("HTTP code: %d", res.StatusCode)
236
-	}
237
-
238
-	var imgId string
239
-	rawJson, err := ioutil.ReadAll(res.Body)
240
-	if err != nil {
241
-		return "", err
242
-	}
243
-	if err = json.Unmarshal(rawJson, &imgId); err != nil {
244
-		return "", err
245
-	}
246
-	return imgId, nil
247
-}
248
-
249
-func (graph *Graph) PullImage(stdout io.Writer, imgId, registry string, token []string) error {
250
-	history, err := graph.getRemoteHistory(imgId, registry, token)
251
-	if err != nil {
252
-		return err
253
-	}
254
-	// FIXME: Try to stream the images?
255
-	// FIXME: Launch the getRemoteImage() in goroutines
256
-	for _, id := range history {
257
-		if !graph.Exists(id) {
258
-			img, layer, err := graph.getRemoteImage(stdout, id, registry, token)
259
-			if err != nil {
260
-				// FIXME: Keep goging in case of error?
261
-				return err
262
-			}
263
-			if err = graph.Register(layer, false, img); err != nil {
264
-				return err
265
-			}
266
-		}
267
-	}
268
-	return nil
269
-}
270
-
271
-func (graph *Graph) PullRepository(stdout io.Writer, remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error {
272
-	client := graph.getHttpClient()
273
-
274
-	fmt.Fprintf(stdout, "Pulling repository %s from %s\r\n", remote, INDEX_ENDPOINT)
275
-	repositoryTarget := INDEX_ENDPOINT + "/repositories/" + remote + "/images"
276
-
277
-	req, err := http.NewRequest("GET", repositoryTarget, nil)
278
-	if err != nil {
279
-		return err
280
-	}
281
-	if authConfig != nil && len(authConfig.Username) > 0 {
282
-		req.SetBasicAuth(authConfig.Username, authConfig.Password)
283
-	}
284
-	req.Header.Set("X-Docker-Token", "true")
285
-
286
-	res, err := client.Do(req)
287
-	if err != nil {
288
-		return err
289
-	}
290
-	defer res.Body.Close()
291
-	if res.StatusCode == 401 {
292
-		return fmt.Errorf("Please login first (HTTP code %d)", res.StatusCode)
293
-	}
294
-	// TODO: Right now we're ignoring checksums in the response body.
295
-	// In the future, we need to use them to check image validity.
296
-	if res.StatusCode != 200 {
297
-		return fmt.Errorf("HTTP code: %d", res.StatusCode)
298
-	}
299
-
300
-	var token, endpoints []string
301
-	if res.Header.Get("X-Docker-Token") != "" {
302
-		token = res.Header["X-Docker-Token"]
303
-	}
304
-	if res.Header.Get("X-Docker-Endpoints") != "" {
305
-		endpoints = res.Header["X-Docker-Endpoints"]
306
-	} else {
307
-		return fmt.Errorf("Index response didn't contain any endpoints")
308
-	}
309
-
310
-	checksumsJson, err := ioutil.ReadAll(res.Body)
311
-	if err != nil {
312
-		return err
313
-	}
314
-
315
-	// Reload the json file to make sure not to overwrite faster sums
316
-	err = func() error {
317
-		localChecksums := make(map[string]string)
318
-		remoteChecksums := []ImgListJson{}
319
-		checksumDictPth := path.Join(graph.Root, "checksums")
320
-
321
-		if err := json.Unmarshal(checksumsJson, &remoteChecksums); err != nil {
322
-			return err
323
-		}
324
-
325
-		graph.lockSumFile.Lock()
326
-		defer graph.lockSumFile.Unlock()
327
-
328
-		if checksumDict, err := ioutil.ReadFile(checksumDictPth); err == nil {
329
-			if err := json.Unmarshal(checksumDict, &localChecksums); err != nil {
330
-				return err
331
-			}
332
-		}
333
-
334
-		for _, elem := range remoteChecksums {
335
-			localChecksums[elem.Id] = elem.Checksum
336
-		}
337
-
338
-		checksumsJson, err = json.Marshal(localChecksums)
339
-		if err != nil {
340
-			return err
341
-		}
342
-		if err := ioutil.WriteFile(checksumDictPth, checksumsJson, 0600); err != nil {
343
-			return err
344
-		}
345
-		return nil
346
-	}()
347
-	if err != nil {
348
-		return err
349
-	}
350
-
351
-	var tagsList map[string]string
352
-	if askedTag == "" {
353
-		tagsList, err = graph.getRemoteTags(stdout, endpoints, remote, token)
354
-		if err != nil {
355
-			return err
356
-		}
357
-	} else {
358
-		tagsList = map[string]string{askedTag: ""}
359
-	}
360
-
361
-	for askedTag, imgId := range tagsList {
362
-		fmt.Fprintf(stdout, "Resolving tag \"%s:%s\" from %s\n", remote, askedTag, endpoints)
363
-		success := false
364
-		for _, registry := range endpoints {
365
-			if imgId == "" {
366
-				imgId, err = graph.getImageForTag(stdout, askedTag, remote, registry, token)
367
-				if err != nil {
368
-					fmt.Fprintf(stdout, "Error while retrieving image for tag: %v (%v) ; "+
369
-						"checking next endpoint", askedTag, err)
370
-					continue
371
-				}
372
-			}
373
-
374
-			if err := graph.PullImage(stdout, imgId, "https://"+registry+"/v1", token); err != nil {
375
-				return err
376
-			}
377
-
378
-			if err = repositories.Set(remote, askedTag, imgId, true); err != nil {
379
-				return err
380
-			}
381
-			success = true
382
-		}
383
-
384
-		if !success {
385
-			return fmt.Errorf("Could not find repository on any of the indexed registries.")
386
-		}
387
-	}
388
-
389
-	if err = repositories.Save(); err != nil {
390
-		return err
391
-	}
392
-
393
-	return nil
394
-}
395
-
396
-// Push a local image to the registry
397
-func (graph *Graph) PushImage(stdout io.Writer, img *Image, registry string, token []string) error {
398
-	registry = "https://" + registry + "/v1"
399
-
400
-	client := graph.getHttpClient()
401
-	jsonRaw, err := ioutil.ReadFile(path.Join(graph.Root, img.Id, "json"))
402
-	if err != nil {
403
-		return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err)
404
-	}
405
-
406
-	fmt.Fprintf(stdout, "Pushing %s metadata\r\n", img.Id)
407
-
408
-	// FIXME: try json with UTF8
409
-	jsonData := strings.NewReader(string(jsonRaw))
410
-	req, err := http.NewRequest("PUT", registry+"/images/"+img.Id+"/json", jsonData)
411
-	if err != nil {
412
-		return err
413
-	}
414
-	req.Header.Add("Content-type", "application/json")
415
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
416
-
417
-	checksum, err := img.Checksum()
418
-	if err != nil {
419
-		return fmt.Errorf("Error while retrieving checksum for %s: %v", img.Id, err)
420
-	}
421
-	req.Header.Set("X-Docker-Checksum", checksum)
422
-	Debugf("Setting checksum for %s: %s", img.ShortId(), checksum)
423
-	res, err := doWithCookies(client, req)
424
-	if err != nil {
425
-		return fmt.Errorf("Failed to upload metadata: %s", err)
426
-	}
427
-	defer res.Body.Close()
428
-	if len(res.Cookies()) > 0 {
429
-		client.Jar.SetCookies(req.URL, res.Cookies())
430
-	}
431
-	if res.StatusCode != 200 {
432
-		errBody, err := ioutil.ReadAll(res.Body)
433
-		if err != nil {
434
-			return fmt.Errorf("HTTP code %d while uploading metadata and error when"+
435
-				" trying to parse response body: %v", res.StatusCode, err)
436
-		}
437
-		var jsonBody map[string]string
438
-		if err := json.Unmarshal(errBody, &jsonBody); err != nil {
439
-			errBody = []byte(err.Error())
440
-		} else if jsonBody["error"] == "Image already exists" {
441
-			fmt.Fprintf(stdout, "Image %v already uploaded ; skipping\n", img.Id)
442
-			return nil
443
-		}
444
-		return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
445
-	}
446
-
447
-	fmt.Fprintf(stdout, "Pushing %s fs layer\r\n", img.Id)
448
-	root, err := img.root()
449
-	if err != nil {
450
-		return err
451
-	}
452
-
453
-	var layerData *TempArchive
454
-	// If the archive exists, use it
455
-	file, err := os.Open(layerArchivePath(root))
456
-	if err != nil {
457
-		if os.IsNotExist(err) {
458
-			// If the archive does not exist, create one from the layer
459
-			layerData, err = graph.TempLayerArchive(img.Id, Xz, stdout)
460
-			if err != nil {
461
-				return fmt.Errorf("Failed to generate layer archive: %s", err)
462
-			}
463
-		} else {
464
-			return err
465
-		}
466
-	} else {
467
-		defer file.Close()
468
-		st, err := file.Stat()
469
-		if err != nil {
470
-			return err
471
-		}
472
-		layerData = &TempArchive{file, st.Size()}
473
-	}
474
-
475
-	req3, err := http.NewRequest("PUT", registry+"/images/"+img.Id+"/layer",
476
-		ProgressReader(layerData, int(layerData.Size), stdout, ""))
477
-	if err != nil {
478
-		return err
479
-	}
480
-
481
-	req3.ContentLength = -1
482
-	req3.TransferEncoding = []string{"chunked"}
483
-	req3.Header.Set("Authorization", "Token "+strings.Join(token, ","))
484
-	res3, err := doWithCookies(client, req3)
485
-	if err != nil {
486
-		return fmt.Errorf("Failed to upload layer: %s", err)
487
-	}
488
-	defer res3.Body.Close()
489
-
490
-	if res3.StatusCode != 200 {
491
-		errBody, err := ioutil.ReadAll(res3.Body)
492
-		if err != nil {
493
-			return fmt.Errorf("HTTP code %d while uploading metadata and error when"+
494
-				" trying to parse response body: %v", res.StatusCode, err)
495
-		}
496
-		return fmt.Errorf("Received HTTP code %d while uploading layer: %s", res3.StatusCode, errBody)
497
-	}
498
-	return nil
499
-}
500
-
501
-// push a tag on the registry.
502
-// Remote has the format '<user>/<repo>
503
-func (graph *Graph) pushTag(remote, revision, tag, registry string, token []string) error {
504
-	// "jsonify" the string
505
-	revision = "\"" + revision + "\""
506
-	registry = "https://" + registry + "/v1"
507
-
508
-	Debugf("Pushing tags for rev [%s] on {%s}\n", revision, registry+"/users/"+remote+"/"+tag)
509
-
510
-	client := graph.getHttpClient()
511
-	req, err := http.NewRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
512
-	if err != nil {
513
-		return err
514
-	}
515
-	req.Header.Add("Content-type", "application/json")
516
-	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
517
-	req.ContentLength = int64(len(revision))
518
-	res, err := doWithCookies(client, req)
519
-	if err != nil {
520
-		return err
521
-	}
522
-	res.Body.Close()
523
-	if res.StatusCode != 200 && res.StatusCode != 201 {
524
-		return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
525
-	}
526
-	return nil
527
-}
528
-
529
-// FIXME: this should really be PushTag
530
-func (graph *Graph) pushPrimitive(stdout io.Writer, remote, tag, imgId, registry string, token []string) error {
531
-	// Check if the local impage exists
532
-	img, err := graph.Get(imgId)
533
-	if err != nil {
534
-		fmt.Fprintf(stdout, "Skipping tag %s:%s: %s does not exist\r\n", remote, tag, imgId)
535
-		return nil
536
-	}
537
-	fmt.Fprintf(stdout, "Pushing image %s:%s\r\n", remote, tag)
538
-	// Push the image
539
-	if err = graph.PushImage(stdout, img, registry, token); err != nil {
540
-		return err
541
-	}
542
-	fmt.Fprintf(stdout, "Registering tag %s:%s\r\n", remote, tag)
543
-	// And then the tag
544
-	if err = graph.pushTag(remote, imgId, tag, registry, token); err != nil {
545
-		return err
546
-	}
547
-	return nil
548
-}
549
-
550
-// Retrieve the checksum of an image
551
-// Priority:
552
-// - Check on the stored checksums
553
-// - Check if the archive exists, if it does not, ask the registry
554
-// - If the archive does exists, process the checksum from it
555
-// - If the archive does not exists and not found on registry, process checksum from layer
556
-func (graph *Graph) getChecksum(imageId string) (string, error) {
557
-	// FIXME: Use in-memory map instead of reading the file each time
558
-	if sums, err := graph.getStoredChecksums(); err != nil {
559
-		return "", err
560
-	} else if checksum, exists := sums[imageId]; exists {
561
-		return checksum, nil
562
-	}
563
-
564
-	img, err := graph.Get(imageId)
565
-	if err != nil {
566
-		return "", err
567
-	}
568
-
569
-	if _, err := os.Stat(layerArchivePath(graph.imageRoot(imageId))); err != nil {
570
-		if os.IsNotExist(err) {
571
-			// TODO: Ask the registry for the checksum
572
-			//       As the archive is not there, it is supposed to come from a pull.
573
-		} else {
574
-			return "", err
575
-		}
576
-	}
577
-
578
-	checksum, err := img.Checksum()
579
-	if err != nil {
580
-		return "", err
581
-	}
582
-	return checksum, nil
583
-}
584
-
585
-type ImgListJson struct {
586
-	Id       string `json:"id"`
587
-	Checksum string `json:"checksum,omitempty"`
588
-	tag      string
589
-}
590
-
591
-// Push a repository to the registry.
592
-// Remote has the format '<user>/<repo>
593
-func (graph *Graph) PushRepository(stdout io.Writer, remote string, localRepo Repository, authConfig *auth.AuthConfig) error {
594
-	client := graph.getHttpClient()
595
-	// FIXME: Do not reset the cookie each time? (need to reset it in case updating latest of a repo and repushing)
596
-	client.Jar = cookiejar.NewCookieJar()
597
-	var imgList []*ImgListJson
598
-
599
-	fmt.Fprintf(stdout, "Processing checksums\n")
600
-	imageSet := make(map[string]struct{})
601
-
602
-	for tag, id := range localRepo {
603
-		img, err := graph.Get(id)
604
-		if err != nil {
605
-			return err
606
-		}
607
-		img.WalkHistory(func(img *Image) error {
608
-			if _, exists := imageSet[img.Id]; exists {
609
-				return nil
610
-			}
611
-			imageSet[img.Id] = struct{}{}
612
-			checksum, err := graph.getChecksum(img.Id)
613
-			if err != nil {
614
-				return err
615
-			}
616
-			imgList = append([]*ImgListJson{{
617
-				Id:       img.Id,
618
-				Checksum: checksum,
619
-				tag:      tag,
620
-			}}, imgList...)
621
-			return nil
622
-		})
623
-	}
624
-
625
-	imgListJson, err := json.Marshal(imgList)
626
-	if err != nil {
627
-		return err
628
-	}
629
-
630
-	Debugf("json sent: %s\n", imgListJson)
631
-
632
-	fmt.Fprintf(stdout, "Sending image list\n")
633
-	req, err := http.NewRequest("PUT", INDEX_ENDPOINT+"/repositories/"+remote+"/", bytes.NewReader(imgListJson))
634
-	if err != nil {
635
-		return err
636
-	}
637
-	req.SetBasicAuth(authConfig.Username, authConfig.Password)
638
-	req.ContentLength = int64(len(imgListJson))
639
-	req.Header.Set("X-Docker-Token", "true")
640
-
641
-	res, err := client.Do(req)
642
-	if err != nil {
643
-		return err
644
-	}
645
-	defer res.Body.Close()
646
-
647
-	for res.StatusCode >= 300 && res.StatusCode < 400 {
648
-		Debugf("Redirected to %s\n", res.Header.Get("Location"))
649
-		req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJson))
650
-		if err != nil {
651
-			return err
652
-		}
653
-		req.SetBasicAuth(authConfig.Username, authConfig.Password)
654
-		req.ContentLength = int64(len(imgListJson))
655
-		req.Header.Set("X-Docker-Token", "true")
656
-
657
-		res, err = client.Do(req)
658
-		if err != nil {
659
-			return err
660
-		}
661
-		defer res.Body.Close()
662
-	}
663
-
664
-	if res.StatusCode != 200 && res.StatusCode != 201 {
665
-		errBody, err := ioutil.ReadAll(res.Body)
666
-		if err != nil {
667
-			return err
668
-		}
669
-		return fmt.Errorf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody)
670
-	}
671
-
672
-	var token, endpoints []string
673
-	if res.Header.Get("X-Docker-Token") != "" {
674
-		token = res.Header["X-Docker-Token"]
675
-		Debugf("Auth token: %v", token)
676
-	} else {
677
-		return fmt.Errorf("Index response didn't contain an access token")
678
-	}
679
-	if res.Header.Get("X-Docker-Endpoints") != "" {
680
-		endpoints = res.Header["X-Docker-Endpoints"]
681
-	} else {
682
-		return fmt.Errorf("Index response didn't contain any endpoints")
683
-	}
684
-
685
-	// FIXME: Send only needed images
686
-	for _, registry := range endpoints {
687
-		fmt.Fprintf(stdout, "Pushing repository %s to %s (%d tags)\r\n", remote, registry, len(localRepo))
688
-		// For each image within the repo, push them
689
-		for _, elem := range imgList {
690
-			if err := graph.pushPrimitive(stdout, remote, elem.tag, elem.Id, registry, token); err != nil {
691
-				// FIXME: Continue on error?
692
-				return err
693
-			}
694
-		}
695
-	}
696
-
697
-	req2, err := http.NewRequest("PUT", INDEX_ENDPOINT+"/repositories/"+remote+"/images", bytes.NewReader(imgListJson))
698
-	if err != nil {
699
-		return err
700
-	}
701
-	req2.SetBasicAuth(authConfig.Username, authConfig.Password)
702
-	req2.Header["X-Docker-Endpoints"] = endpoints
703
-	req2.ContentLength = int64(len(imgListJson))
704
-	res2, err := client.Do(req2)
705
-	if err != nil {
706
-		return err
707
-	}
708
-	defer res2.Body.Close()
709
-	if res2.StatusCode != 204 {
710
-		if errBody, err := ioutil.ReadAll(res2.Body); err != nil {
711
-			return err
712
-		} else {
713
-			return fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res2.StatusCode, remote, errBody)
714
-		}
715
-	}
716
-
717
-	return nil
718
-}
719
-
720
-type SearchResults struct {
721
-	Query      string              `json:"query"`
722
-	NumResults int                 `json:"num_results"`
723
-	Results    []map[string]string `json:"results"`
724
-}
725
-
726
-func (graph *Graph) SearchRepositories(stdout io.Writer, term string) (*SearchResults, error) {
727
-	client := graph.getHttpClient()
728
-	u := INDEX_ENDPOINT + "/search?q=" + url.QueryEscape(term)
729
-	req, err := http.NewRequest("GET", u, nil)
730
-	if err != nil {
731
-		return nil, err
732
-	}
733
-	res, err := client.Do(req)
734
-	if err != nil {
735
-		return nil, err
736
-	}
737
-	defer res.Body.Close()
738
-	if res.StatusCode != 200 {
739
-		return nil, fmt.Errorf("Unexepected status code %d", res.StatusCode)
740
-	}
741
-	rawData, err := ioutil.ReadAll(res.Body)
742
-	if err != nil {
743
-		return nil, err
744
-	}
745
-	result := new(SearchResults)
746
-	err = json.Unmarshal(rawData, result)
747
-	return result, err
748
-}
749 1
new file mode 100644
... ...
@@ -0,0 +1,472 @@
0
+package registry
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"errors"
6
+	"fmt"
7
+	"github.com/dotcloud/docker/auth"
8
+	"github.com/dotcloud/docker/utils"
9
+	"github.com/shin-/cookiejar"
10
+	"io"
11
+	"io/ioutil"
12
+	"net/http"
13
+	"net/url"
14
+	"strings"
15
+)
16
+
17
+var ErrAlreadyExists error = errors.New("Image already exists")
18
+
19
+func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) {
20
+	for _, cookie := range c.Jar.Cookies(req.URL) {
21
+		req.AddCookie(cookie)
22
+	}
23
+	return c.Do(req)
24
+}
25
+
26
+// Retrieve the history of a given image from the Registry.
27
+// Return a list of the parent's json (requested image included)
28
+func (r *Registry) GetRemoteHistory(imgId, registry string, token []string) ([]string, error) {
29
+	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/ancestry", nil)
30
+	if err != nil {
31
+		return nil, err
32
+	}
33
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
34
+	res, err := r.client.Do(req)
35
+	if err != nil || res.StatusCode != 200 {
36
+		if res != nil {
37
+			return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgId)
38
+		}
39
+		return nil, err
40
+	}
41
+	defer res.Body.Close()
42
+
43
+	jsonString, err := ioutil.ReadAll(res.Body)
44
+	if err != nil {
45
+		return nil, fmt.Errorf("Error while reading the http response: %s", err)
46
+	}
47
+
48
+	utils.Debugf("Ancestry: %s", jsonString)
49
+	history := new([]string)
50
+	if err := json.Unmarshal(jsonString, history); err != nil {
51
+		return nil, err
52
+	}
53
+	return *history, nil
54
+}
55
+
56
+// Check if an image exists in the Registry
57
+func (r *Registry) LookupRemoteImage(imgId, registry string, authConfig *auth.AuthConfig) bool {
58
+	rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
59
+
60
+	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
61
+	if err != nil {
62
+		return false
63
+	}
64
+	req.SetBasicAuth(authConfig.Username, authConfig.Password)
65
+	res, err := rt.RoundTrip(req)
66
+	return err == nil && res.StatusCode == 307
67
+}
68
+
69
+func (r *Registry) getImagesInRepository(repository string, authConfig *auth.AuthConfig) ([]map[string]string, error) {
70
+	u := auth.IndexServerAddress() + "/repositories/" + repository + "/images"
71
+	req, err := http.NewRequest("GET", u, nil)
72
+	if err != nil {
73
+		return nil, err
74
+	}
75
+	if authConfig != nil && len(authConfig.Username) > 0 {
76
+		req.SetBasicAuth(authConfig.Username, authConfig.Password)
77
+	}
78
+	res, err := r.client.Do(req)
79
+	if err != nil {
80
+		return nil, err
81
+	}
82
+	defer res.Body.Close()
83
+
84
+	// Repository doesn't exist yet
85
+	if res.StatusCode == 404 {
86
+		return nil, nil
87
+	}
88
+
89
+	jsonData, err := ioutil.ReadAll(res.Body)
90
+	if err != nil {
91
+		return nil, err
92
+	}
93
+
94
+	imageList := []map[string]string{}
95
+	if err := json.Unmarshal(jsonData, &imageList); err != nil {
96
+		utils.Debugf("Body: %s (%s)\n", res.Body, u)
97
+		return nil, err
98
+	}
99
+
100
+	return imageList, nil
101
+}
102
+
103
+// Retrieve an image from the Registry.
104
+// Returns the Image object as well as the layer as an Archive (io.Reader)
105
+func (r *Registry) GetRemoteImageJson(imgId, registry string, token []string) ([]byte, error) {
106
+	// Get the Json
107
+	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/json", nil)
108
+	if err != nil {
109
+		return nil, fmt.Errorf("Failed to download json: %s", err)
110
+	}
111
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
112
+	res, err := r.client.Do(req)
113
+	if err != nil {
114
+		return nil, fmt.Errorf("Failed to download json: %s", err)
115
+	}
116
+	defer res.Body.Close()
117
+	if res.StatusCode != 200 {
118
+		return nil, fmt.Errorf("HTTP code %d", res.StatusCode)
119
+	}
120
+	jsonString, err := ioutil.ReadAll(res.Body)
121
+	if err != nil {
122
+		return nil, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
123
+	}
124
+	return jsonString, nil
125
+}
126
+
127
+func (r *Registry) GetRemoteImageLayer(imgId, registry string, token []string) (io.ReadCloser, int, error) {
128
+	req, err := http.NewRequest("GET", registry+"/images/"+imgId+"/layer", nil)
129
+	if err != nil {
130
+		return nil, -1, fmt.Errorf("Error while getting from the server: %s\n", err)
131
+	}
132
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
133
+	res, err := r.client.Do(req)
134
+	if err != nil {
135
+		return nil, -1, err
136
+	}
137
+	return res.Body, int(res.ContentLength), nil
138
+}
139
+
140
+func (r *Registry) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
141
+	if strings.Count(repository, "/") == 0 {
142
+		// This will be removed once the Registry supports auto-resolution on
143
+		// the "library" namespace
144
+		repository = "library/" + repository
145
+	}
146
+	for _, host := range registries {
147
+		endpoint := fmt.Sprintf("https://%s/v1/repositories/%s/tags", host, repository)
148
+		req, err := http.NewRequest("GET", endpoint, nil)
149
+		if err != nil {
150
+			return nil, err
151
+		}
152
+		req.Header.Set("Authorization", "Token "+strings.Join(token, ", "))
153
+		res, err := r.client.Do(req)
154
+		defer res.Body.Close()
155
+		utils.Debugf("Got status code %d from %s", res.StatusCode, endpoint)
156
+		if err != nil || (res.StatusCode != 200 && res.StatusCode != 404) {
157
+			continue
158
+		} else if res.StatusCode == 404 {
159
+			return nil, fmt.Errorf("Repository not found")
160
+		}
161
+
162
+		result := make(map[string]string)
163
+
164
+		rawJson, err := ioutil.ReadAll(res.Body)
165
+		if err != nil {
166
+			return nil, err
167
+		}
168
+		if err := json.Unmarshal(rawJson, &result); err != nil {
169
+			return nil, err
170
+		}
171
+		return result, nil
172
+	}
173
+	return nil, fmt.Errorf("Could not reach any registry endpoint")
174
+}
175
+
176
+func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) {
177
+	utils.Debugf("Pulling repository %s from %s\r\n", remote, auth.IndexServerAddress())
178
+	repositoryTarget := auth.IndexServerAddress() + "/repositories/" + remote + "/images"
179
+
180
+	req, err := http.NewRequest("GET", repositoryTarget, nil)
181
+	if err != nil {
182
+		return nil, err
183
+	}
184
+	if r.authConfig != nil && len(r.authConfig.Username) > 0 {
185
+		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
186
+	}
187
+	req.Header.Set("X-Docker-Token", "true")
188
+
189
+	res, err := r.client.Do(req)
190
+	if err != nil {
191
+		return nil, err
192
+	}
193
+	defer res.Body.Close()
194
+	if res.StatusCode == 401 {
195
+		return nil, fmt.Errorf("Please login first (HTTP code %d)", res.StatusCode)
196
+	}
197
+	// TODO: Right now we're ignoring checksums in the response body.
198
+	// In the future, we need to use them to check image validity.
199
+	if res.StatusCode != 200 {
200
+		return nil, fmt.Errorf("HTTP code: %d", res.StatusCode)
201
+	}
202
+
203
+	var tokens []string
204
+	if res.Header.Get("X-Docker-Token") != "" {
205
+		tokens = res.Header["X-Docker-Token"]
206
+	}
207
+
208
+	var endpoints []string
209
+	if res.Header.Get("X-Docker-Endpoints") != "" {
210
+		endpoints = res.Header["X-Docker-Endpoints"]
211
+	} else {
212
+		return nil, fmt.Errorf("Index response didn't contain any endpoints")
213
+	}
214
+
215
+	checksumsJson, err := ioutil.ReadAll(res.Body)
216
+	if err != nil {
217
+		return nil, err
218
+	}
219
+	remoteChecksums := []*ImgData{}
220
+	if err := json.Unmarshal(checksumsJson, &remoteChecksums); err != nil {
221
+		return nil, err
222
+	}
223
+
224
+	// Forge a better object from the retrieved data
225
+	imgsData := make(map[string]*ImgData)
226
+	for _, elem := range remoteChecksums {
227
+		imgsData[elem.Id] = elem
228
+	}
229
+
230
+	return &RepositoryData{
231
+		ImgList:   imgsData,
232
+		Endpoints: endpoints,
233
+		Tokens:    tokens,
234
+	}, nil
235
+}
236
+
237
+// Push a local image to the registry
238
+func (r *Registry) PushImageJsonRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error {
239
+	registry = "https://" + registry + "/v1"
240
+	// FIXME: try json with UTF8
241
+	req, err := http.NewRequest("PUT", registry+"/images/"+imgData.Id+"/json", strings.NewReader(string(jsonRaw)))
242
+	if err != nil {
243
+		return err
244
+	}
245
+	req.Header.Add("Content-type", "application/json")
246
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
247
+	req.Header.Set("X-Docker-Checksum", imgData.Checksum)
248
+
249
+	utils.Debugf("Setting checksum for %s: %s", imgData.Id, imgData.Checksum)
250
+	res, err := doWithCookies(r.client, req)
251
+	if err != nil {
252
+		return fmt.Errorf("Failed to upload metadata: %s", err)
253
+	}
254
+	defer res.Body.Close()
255
+	if len(res.Cookies()) > 0 {
256
+		r.client.Jar.SetCookies(req.URL, res.Cookies())
257
+	}
258
+	if res.StatusCode != 200 {
259
+		errBody, err := ioutil.ReadAll(res.Body)
260
+		if err != nil {
261
+			return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err)
262
+		}
263
+		var jsonBody map[string]string
264
+		if err := json.Unmarshal(errBody, &jsonBody); err != nil {
265
+			errBody = []byte(err.Error())
266
+		} else if jsonBody["error"] == "Image already exists" {
267
+			return ErrAlreadyExists
268
+		}
269
+		return fmt.Errorf("HTTP code %d while uploading metadata: %s", res.StatusCode, errBody)
270
+	}
271
+	return nil
272
+}
273
+
274
+func (r *Registry) PushImageLayerRegistry(imgId string, layer io.Reader, registry string, token []string) error {
275
+	registry = "https://" + registry + "/v1"
276
+	req, err := http.NewRequest("PUT", registry+"/images/"+imgId+"/layer", layer)
277
+	if err != nil {
278
+		return err
279
+	}
280
+	req.ContentLength = -1
281
+	req.TransferEncoding = []string{"chunked"}
282
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
283
+	res, err := doWithCookies(r.client, req)
284
+	if err != nil {
285
+		return fmt.Errorf("Failed to upload layer: %s", err)
286
+	}
287
+	defer res.Body.Close()
288
+
289
+	if res.StatusCode != 200 {
290
+		errBody, err := ioutil.ReadAll(res.Body)
291
+		if err != nil {
292
+			return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err)
293
+		}
294
+		return fmt.Errorf("Received HTTP code %d while uploading layer: %s", res.StatusCode, errBody)
295
+	}
296
+	return nil
297
+}
298
+
299
+// push a tag on the registry.
300
+// Remote has the format '<user>/<repo>
301
+func (r *Registry) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
302
+	// "jsonify" the string
303
+	revision = "\"" + revision + "\""
304
+	registry = "https://" + registry + "/v1"
305
+
306
+	req, err := http.NewRequest("PUT", registry+"/repositories/"+remote+"/tags/"+tag, strings.NewReader(revision))
307
+	if err != nil {
308
+		return err
309
+	}
310
+	req.Header.Add("Content-type", "application/json")
311
+	req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
312
+	req.ContentLength = int64(len(revision))
313
+	res, err := doWithCookies(r.client, req)
314
+	if err != nil {
315
+		return err
316
+	}
317
+	res.Body.Close()
318
+	if res.StatusCode != 200 && res.StatusCode != 201 {
319
+		return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
320
+	}
321
+	return nil
322
+}
323
+
324
+func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validate bool) (*RepositoryData, error) {
325
+	imgListJson, err := json.Marshal(imgList)
326
+	if err != nil {
327
+		return nil, err
328
+	}
329
+
330
+	utils.Debugf("json sent: %s\n", imgListJson)
331
+
332
+	req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/", bytes.NewReader(imgListJson))
333
+	if err != nil {
334
+		return nil, err
335
+	}
336
+	req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
337
+	req.ContentLength = int64(len(imgListJson))
338
+	req.Header.Set("X-Docker-Token", "true")
339
+
340
+	res, err := r.client.Do(req)
341
+	if err != nil {
342
+		return nil, err
343
+	}
344
+	defer res.Body.Close()
345
+
346
+	// Redirect if necessary
347
+	for res.StatusCode >= 300 && res.StatusCode < 400 {
348
+		utils.Debugf("Redirected to %s\n", res.Header.Get("Location"))
349
+		req, err = http.NewRequest("PUT", res.Header.Get("Location"), bytes.NewReader(imgListJson))
350
+		if err != nil {
351
+			return nil, err
352
+		}
353
+		req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
354
+		req.ContentLength = int64(len(imgListJson))
355
+		req.Header.Set("X-Docker-Token", "true")
356
+
357
+		res, err = r.client.Do(req)
358
+		if err != nil {
359
+			return nil, err
360
+		}
361
+		defer res.Body.Close()
362
+	}
363
+
364
+	if res.StatusCode != 200 && res.StatusCode != 201 {
365
+		errBody, err := ioutil.ReadAll(res.Body)
366
+		if err != nil {
367
+			return nil, err
368
+		}
369
+		return nil, fmt.Errorf("Error: Status %d trying to push repository %s: %s", res.StatusCode, remote, errBody)
370
+	}
371
+
372
+	var tokens []string
373
+	if res.Header.Get("X-Docker-Token") != "" {
374
+		tokens = res.Header["X-Docker-Token"]
375
+		utils.Debugf("Auth token: %v", tokens)
376
+	} else {
377
+		return nil, fmt.Errorf("Index response didn't contain an access token")
378
+	}
379
+
380
+	var endpoints []string
381
+	if res.Header.Get("X-Docker-Endpoints") != "" {
382
+		endpoints = res.Header["X-Docker-Endpoints"]
383
+	} else {
384
+		return nil, fmt.Errorf("Index response didn't contain any endpoints")
385
+	}
386
+
387
+	if validate {
388
+		if res.StatusCode != 204 {
389
+			if errBody, err := ioutil.ReadAll(res.Body); err != nil {
390
+				return nil, err
391
+			} else {
392
+				return nil, fmt.Errorf("Error: Status %d trying to push checksums %s: %s", res.StatusCode, remote, errBody)
393
+			}
394
+		}
395
+	}
396
+
397
+	return &RepositoryData{
398
+		Tokens:    tokens,
399
+		Endpoints: endpoints,
400
+	}, nil
401
+}
402
+
403
+func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
404
+	u := auth.IndexServerAddress() + "/search?q=" + url.QueryEscape(term)
405
+	req, err := http.NewRequest("GET", u, nil)
406
+	if err != nil {
407
+		return nil, err
408
+	}
409
+	res, err := r.client.Do(req)
410
+	if err != nil {
411
+		return nil, err
412
+	}
413
+	defer res.Body.Close()
414
+	if res.StatusCode != 200 {
415
+		return nil, fmt.Errorf("Unexepected status code %d", res.StatusCode)
416
+	}
417
+	rawData, err := ioutil.ReadAll(res.Body)
418
+	if err != nil {
419
+		return nil, err
420
+	}
421
+	result := new(SearchResults)
422
+	err = json.Unmarshal(rawData, result)
423
+	return result, err
424
+}
425
+
426
+func (r *Registry) ResetClient(authConfig *auth.AuthConfig) {
427
+	r.authConfig = authConfig
428
+	r.client.Jar = cookiejar.NewCookieJar()
429
+}
430
+
431
+func (r *Registry) GetAuthConfig() *auth.AuthConfig {
432
+	return &auth.AuthConfig{
433
+		Username: r.authConfig.Username,
434
+		Email:    r.authConfig.Email,
435
+	}
436
+}
437
+
438
+type SearchResults struct {
439
+	Query      string              `json:"query"`
440
+	NumResults int                 `json:"num_results"`
441
+	Results    []map[string]string `json:"results"`
442
+}
443
+
444
+type RepositoryData struct {
445
+	ImgList   map[string]*ImgData
446
+	Endpoints []string
447
+	Tokens    []string
448
+}
449
+
450
+type ImgData struct {
451
+	Id       string `json:"id"`
452
+	Checksum string `json:"checksum,omitempty"`
453
+	Tag      string `json:",omitempty"`
454
+}
455
+
456
+type Registry struct {
457
+	client     *http.Client
458
+	authConfig *auth.AuthConfig
459
+}
460
+
461
+func NewRegistry(root string) *Registry {
462
+	// If the auth file does not exist, keep going
463
+	authConfig, _ := auth.LoadConfig(root)
464
+
465
+	r := &Registry{
466
+		authConfig: authConfig,
467
+		client:     &http.Client{},
468
+	}
469
+	r.client.Jar = cookiejar.NewCookieJar()
470
+	return r
471
+}
0 472
new file mode 100644
... ...
@@ -0,0 +1,168 @@
0
+package registry
1
+
2
+// import (
3
+// 	"crypto/rand"
4
+// 	"encoding/hex"
5
+// 	"github.com/dotcloud/docker"
6
+// 	"github.com/dotcloud/docker/auth"
7
+// 	"io/ioutil"
8
+// 	"os"
9
+// 	"path"
10
+// 	"testing"
11
+// )
12
+
13
+// func newTestRuntime() (*Runtime, error) {
14
+// 	root, err := ioutil.TempDir("", "docker-test")
15
+// 	if err != nil {
16
+// 		return nil, err
17
+// 	}
18
+// 	if err := os.Remove(root); err != nil {
19
+// 		return nil, err
20
+// 	}
21
+
22
+// 	if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
23
+// 		return nil, err
24
+// 	}
25
+
26
+// 	return runtime, nil
27
+// }
28
+
29
+// func TestPull(t *testing.T) {
30
+// 	os.Setenv("DOCKER_INDEX_URL", "")
31
+// 	runtime, err := newTestRuntime()
32
+// 	if err != nil {
33
+// 		t.Fatal(err)
34
+// 	}
35
+// 	defer nuke(runtime)
36
+
37
+// 	err = runtime.graph.PullRepository(ioutil.Discard, "busybox", "", runtime.repositories, nil)
38
+// 	if err != nil {
39
+// 		t.Fatal(err)
40
+// 	}
41
+// 	img, err := runtime.repositories.LookupImage("busybox")
42
+// 	if err != nil {
43
+// 		t.Fatal(err)
44
+// 	}
45
+
46
+// 	// Try to run something on this image to make sure the layer's been downloaded properly.
47
+// 	config, _, err := docker.ParseRun([]string{img.Id, "echo", "Hello World"}, runtime.capabilities)
48
+// 	if err != nil {
49
+// 		t.Fatal(err)
50
+// 	}
51
+
52
+// 	b := NewBuilder(runtime)
53
+// 	container, err := b.Create(config)
54
+// 	if err != nil {
55
+// 		t.Fatal(err)
56
+// 	}
57
+// 	if err := container.Start(); err != nil {
58
+// 		t.Fatal(err)
59
+// 	}
60
+
61
+// 	if status := container.Wait(); status != 0 {
62
+// 		t.Fatalf("Expected status code 0, found %d instead", status)
63
+// 	}
64
+// }
65
+
66
+// func TestPullTag(t *testing.T) {
67
+// 	os.Setenv("DOCKER_INDEX_URL", "")
68
+// 	runtime, err := newTestRuntime()
69
+// 	if err != nil {
70
+// 		t.Fatal(err)
71
+// 	}
72
+// 	defer nuke(runtime)
73
+
74
+// 	err = runtime.graph.PullRepository(ioutil.Discard, "ubuntu", "12.04", runtime.repositories, nil)
75
+// 	if err != nil {
76
+// 		t.Fatal(err)
77
+// 	}
78
+// 	_, err = runtime.repositories.LookupImage("ubuntu:12.04")
79
+// 	if err != nil {
80
+// 		t.Fatal(err)
81
+// 	}
82
+
83
+// 	img2, err := runtime.repositories.LookupImage("ubuntu:12.10")
84
+// 	if img2 != nil {
85
+// 		t.Fatalf("Expected nil image but found %v instead", img2.Id)
86
+// 	}
87
+// }
88
+
89
+// func login(runtime *Runtime) error {
90
+// 	authConfig := auth.NewAuthConfig("unittester", "surlautrerivejetattendrai", "noise+unittester@dotcloud.com", runtime.root)
91
+// 	runtime.authConfig = authConfig
92
+// 	_, err := auth.Login(authConfig)
93
+// 	return err
94
+// }
95
+
96
+// func TestPush(t *testing.T) {
97
+// 	os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com")
98
+// 	defer os.Setenv("DOCKER_INDEX_URL", "")
99
+// 	runtime, err := newTestRuntime()
100
+// 	if err != nil {
101
+// 		t.Fatal(err)
102
+// 	}
103
+// 	defer nuke(runtime)
104
+
105
+// 	err = login(runtime)
106
+// 	if err != nil {
107
+// 		t.Fatal(err)
108
+// 	}
109
+
110
+// 	err = runtime.graph.PullRepository(ioutil.Discard, "joffrey/busybox", "", runtime.repositories, nil)
111
+// 	if err != nil {
112
+// 		t.Fatal(err)
113
+// 	}
114
+// 	tokenBuffer := make([]byte, 16)
115
+// 	_, err = rand.Read(tokenBuffer)
116
+// 	if err != nil {
117
+// 		t.Fatal(err)
118
+// 	}
119
+// 	token := hex.EncodeToString(tokenBuffer)[:29]
120
+// 	config, _, err := ParseRun([]string{"joffrey/busybox", "touch", "/" + token}, runtime.capabilities)
121
+// 	if err != nil {
122
+// 		t.Fatal(err)
123
+// 	}
124
+
125
+// 	b := NewBuilder(runtime)
126
+// 	container, err := b.Create(config)
127
+// 	if err != nil {
128
+// 		t.Fatal(err)
129
+// 	}
130
+// 	if err := container.Start(); err != nil {
131
+// 		t.Fatal(err)
132
+// 	}
133
+
134
+// 	if status := container.Wait(); status != 0 {
135
+// 		t.Fatalf("Expected status code 0, found %d instead", status)
136
+// 	}
137
+
138
+// 	img, err := b.Commit(container, "unittester/"+token, "", "", "", nil)
139
+// 	if err != nil {
140
+// 		t.Fatal(err)
141
+// 	}
142
+
143
+// 	repo := runtime.repositories.Repositories["unittester/"+token]
144
+// 	err = runtime.graph.PushRepository(ioutil.Discard, "unittester/"+token, repo, runtime.authConfig)
145
+// 	if err != nil {
146
+// 		t.Fatal(err)
147
+// 	}
148
+
149
+// 	// Remove image so we can pull it again
150
+// 	if err := runtime.graph.Delete(img.Id); err != nil {
151
+// 		t.Fatal(err)
152
+// 	}
153
+
154
+// 	err = runtime.graph.PullRepository(ioutil.Discard, "unittester/"+token, "", runtime.repositories, runtime.authConfig)
155
+// 	if err != nil {
156
+// 		t.Fatal(err)
157
+// 	}
158
+
159
+// 	layerPath, err := img.layer()
160
+// 	if err != nil {
161
+// 		t.Fatal(err)
162
+// 	}
163
+
164
+// 	if _, err := os.Stat(path.Join(layerPath, token)); err != nil {
165
+// 		t.Fatalf("Error while trying to retrieve token file: %v", err)
166
+// 	}
167
+// }
... ...
@@ -3,7 +3,7 @@ package docker
3 3
 import (
4 4
 	"container/list"
5 5
 	"fmt"
6
-	"github.com/dotcloud/docker/auth"
6
+	"github.com/dotcloud/docker/utils"
7 7
 	"io"
8 8
 	"io/ioutil"
9 9
 	"log"
... ...
@@ -26,18 +26,18 @@ type Runtime struct {
26 26
 	networkManager *NetworkManager
27 27
 	graph          *Graph
28 28
 	repositories   *TagStore
29
-	authConfig     *auth.AuthConfig
30
-	idIndex        *TruncIndex
29
+	idIndex        *utils.TruncIndex
31 30
 	capabilities   *Capabilities
32
-	kernelVersion  *KernelVersionInfo
31
+	kernelVersion  *utils.KernelVersionInfo
33 32
 	autoRestart    bool
34 33
 	volumes        *Graph
34
+	srv            *Server
35 35
 }
36 36
 
37 37
 var sysInitPath string
38 38
 
39 39
 func init() {
40
-	sysInitPath = SelfPath()
40
+	sysInitPath = utils.SelfPath()
41 41
 }
42 42
 
43 43
 func (runtime *Runtime) List() []*Container {
... ...
@@ -113,13 +113,13 @@ func (runtime *Runtime) Register(container *Container) error {
113 113
 	container.runtime = runtime
114 114
 
115 115
 	// Attach to stdout and stderr
116
-	container.stderr = newWriteBroadcaster()
117
-	container.stdout = newWriteBroadcaster()
116
+	container.stderr = utils.NewWriteBroadcaster()
117
+	container.stdout = utils.NewWriteBroadcaster()
118 118
 	// Attach to stdin
119 119
 	if container.Config.OpenStdin {
120 120
 		container.stdin, container.stdinPipe = io.Pipe()
121 121
 	} else {
122
-		container.stdinPipe = NopWriteCloser(ioutil.Discard) // Silently drop stdin
122
+		container.stdinPipe = utils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
123 123
 	}
124 124
 	// done
125 125
 	runtime.containers.PushBack(container)
... ...
@@ -137,9 +137,9 @@ func (runtime *Runtime) Register(container *Container) error {
137 137
 			return err
138 138
 		} else {
139 139
 			if !strings.Contains(string(output), "RUNNING") {
140
-				Debugf("Container %s was supposed to be running be is not.", container.Id)
140
+				utils.Debugf("Container %s was supposed to be running be is not.", container.Id)
141 141
 				if runtime.autoRestart {
142
-					Debugf("Restarting")
142
+					utils.Debugf("Restarting")
143 143
 					container.State.Ghost = false
144 144
 					container.State.setStopped(0)
145 145
 					if err := container.Start(); err != nil {
... ...
@@ -147,7 +147,7 @@ func (runtime *Runtime) Register(container *Container) error {
147 147
 					}
148 148
 					nomonitor = true
149 149
 				} else {
150
-					Debugf("Marking as stopped")
150
+					utils.Debugf("Marking as stopped")
151 151
 					container.State.setStopped(-127)
152 152
 					if err := container.ToDisk(); err != nil {
153 153
 						return err
... ...
@@ -168,7 +168,7 @@ func (runtime *Runtime) Register(container *Container) error {
168 168
 	return nil
169 169
 }
170 170
 
171
-func (runtime *Runtime) LogToDisk(src *writeBroadcaster, dst string) error {
171
+func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst string) error {
172 172
 	log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
173 173
 	if err != nil {
174 174
 		return err
... ...
@@ -215,16 +215,16 @@ func (runtime *Runtime) restore() error {
215 215
 		id := v.Name()
216 216
 		container, err := runtime.Load(id)
217 217
 		if err != nil {
218
-			Debugf("Failed to load container %v: %v", id, err)
218
+			utils.Debugf("Failed to load container %v: %v", id, err)
219 219
 			continue
220 220
 		}
221
-		Debugf("Loaded container %v", container.Id)
221
+		utils.Debugf("Loaded container %v", container.Id)
222 222
 	}
223 223
 	return nil
224 224
 }
225 225
 
226 226
 func (runtime *Runtime) UpdateCapabilities(quiet bool) {
227
-	if cgroupMemoryMountpoint, err := FindCgroupMountpoint("memory"); err != nil {
227
+	if cgroupMemoryMountpoint, err := utils.FindCgroupMountpoint("memory"); err != nil {
228 228
 		if !quiet {
229 229
 			log.Printf("WARNING: %s\n", err)
230 230
 		}
... ...
@@ -251,11 +251,11 @@ func NewRuntime(autoRestart bool) (*Runtime, error) {
251 251
 		return nil, err
252 252
 	}
253 253
 
254
-	if k, err := GetKernelVersion(); err != nil {
254
+	if k, err := utils.GetKernelVersion(); err != nil {
255 255
 		log.Printf("WARNING: %s\n", err)
256 256
 	} else {
257 257
 		runtime.kernelVersion = k
258
-		if CompareKernelVersion(k, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
258
+		if utils.CompareKernelVersion(k, &utils.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
259 259
 			log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
260 260
 		}
261 261
 	}
... ...
@@ -289,11 +289,6 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
289 289
 	if err != nil {
290 290
 		return nil, err
291 291
 	}
292
-	authConfig, err := auth.LoadConfig(root)
293
-	if err != nil && authConfig == nil {
294
-		// If the auth file does not exist, keep going
295
-		return nil, err
296
-	}
297 292
 	runtime := &Runtime{
298 293
 		root:           root,
299 294
 		repository:     runtimeRepo,
... ...
@@ -301,8 +296,7 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) {
301 301
 		networkManager: netManager,
302 302
 		graph:          g,
303 303
 		repositories:   repositories,
304
-		authConfig:     authConfig,
305
-		idIndex:        NewTruncIndex(),
304
+		idIndex:        utils.NewTruncIndex(),
306 305
 		capabilities:   &Capabilities{},
307 306
 		autoRestart:    autoRestart,
308 307
 		volumes:        volumes,
... ...
@@ -2,6 +2,8 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"github.com/dotcloud/docker/registry"
6
+	"github.com/dotcloud/docker/utils"
5 7
 	"io"
6 8
 	"io/ioutil"
7 9
 	"net"
... ...
@@ -48,7 +50,7 @@ func layerArchive(tarfile string) (io.Reader, error) {
48 48
 
49 49
 func init() {
50 50
 	// Hack to run sys init during unit testing
51
-	if SelfPath() == "/sbin/init" {
51
+	if utils.SelfPath() == "/sbin/init" {
52 52
 		SysInit()
53 53
 		return
54 54
 	}
... ...
@@ -69,7 +71,8 @@ func init() {
69 69
 
70 70
 	// Create the "Server"
71 71
 	srv := &Server{
72
-		runtime: runtime,
72
+		runtime:  runtime,
73
+		registry: registry.NewRegistry(runtime.root),
73 74
 	}
74 75
 	// Retrieve the Image
75 76
 	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout); err != nil {
... ...
@@ -2,11 +2,15 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"github.com/dotcloud/docker/registry"
6
+	"github.com/dotcloud/docker/utils"
5 7
 	"io"
8
+	"io/ioutil"
6 9
 	"log"
7 10
 	"net/http"
8 11
 	"net/url"
9 12
 	"os"
13
+	"path"
10 14
 	"runtime"
11 15
 	"strings"
12 16
 )
... ...
@@ -44,7 +48,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
44 44
 }
45 45
 
46 46
 func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
47
-	results, err := srv.runtime.graph.SearchRepositories(nil, term)
47
+	results, err := srv.registry.SearchRepositories(term)
48 48
 	if err != nil {
49 49
 		return nil, err
50 50
 	}
... ...
@@ -54,7 +58,7 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
54 54
 		var out ApiSearch
55 55
 		out.Description = repo["description"]
56 56
 		if len(out.Description) > 45 {
57
-			out.Description = Trunc(out.Description, 42) + "..."
57
+			out.Description = utils.Trunc(out.Description, 42) + "..."
58 58
 		}
59 59
 		out.Name = repo["name"]
60 60
 		outs = append(outs, out)
... ...
@@ -68,7 +72,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) error {
68 68
 		return err
69 69
 	}
70 70
 
71
-	file, err := Download(url, out)
71
+	file, err := utils.Download(url, out)
72 72
 	if err != nil {
73 73
 		return err
74 74
 	}
... ...
@@ -85,7 +89,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) error {
85 85
 		return err
86 86
 	}
87 87
 
88
-	if err := c.Inject(ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)"), path); err != nil {
88
+	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)"), path); err != nil {
89 89
 		return err
90 90
 	}
91 91
 	// FIXME: Handle custom repo, tag comment, author
... ...
@@ -124,7 +128,7 @@ func (srv *Server) ImagesViz(out io.Writer) error {
124 124
 
125 125
 	for name, repository := range srv.runtime.repositories.Repositories {
126 126
 		for tag, id := range repository {
127
-			reporefs[TruncateId(id)] = append(reporefs[TruncateId(id)], fmt.Sprintf("%s:%s", name, tag))
127
+			reporefs[utils.TruncateId(id)] = append(reporefs[utils.TruncateId(id)], fmt.Sprintf("%s:%s", name, tag))
128 128
 		}
129 129
 	}
130 130
 
... ...
@@ -193,7 +197,7 @@ func (srv *Server) DockerInfo() ApiInfo {
193 193
 	out.GoVersion = runtime.Version()
194 194
 	if os.Getenv("DEBUG") != "" {
195 195
 		out.Debug = true
196
-		out.NFd = getTotalUsedFds()
196
+		out.NFd = utils.GetTotalUsedFds()
197 197
 		out.NGoroutines = runtime.NumGoroutine()
198 198
 	}
199 199
 	return out
... ...
@@ -283,14 +287,272 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
283 283
 	return nil
284 284
 }
285 285
 
286
+func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []string) error {
287
+	history, err := srv.registry.GetRemoteHistory(imgId, registry, token)
288
+	if err != nil {
289
+		return err
290
+	}
291
+
292
+	// FIXME: Try to stream the images?
293
+	// FIXME: Launch the getRemoteImage() in goroutines
294
+	for _, id := range history {
295
+		if !srv.runtime.graph.Exists(id) {
296
+			fmt.Fprintf(out, "Pulling %s metadata\r\n", id)
297
+			imgJson, err := srv.registry.GetRemoteImageJson(id, registry, token)
298
+			if err != nil {
299
+				// FIXME: Keep goging in case of error?
300
+				return err
301
+			}
302
+			img, err := NewImgJson(imgJson)
303
+			if err != nil {
304
+				return fmt.Errorf("Failed to parse json: %s", err)
305
+			}
306
+
307
+			// Get the layer
308
+			fmt.Fprintf(out, "Pulling %s fs layer\r\n", img.Id)
309
+			layer, contentLength, err := srv.registry.GetRemoteImageLayer(img.Id, registry, token)
310
+			if err != nil {
311
+				return err
312
+			}
313
+			if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, "Downloading %v/%v (%v)"), false, img); err != nil {
314
+				return err
315
+			}
316
+		}
317
+	}
318
+	return nil
319
+}
320
+
321
+func (srv *Server) pullRepository(stdout io.Writer, remote, askedTag string) error {
322
+	utils.Debugf("Retrieving repository data")
323
+	repoData, err := srv.registry.GetRepositoryData(remote)
324
+	if err != nil {
325
+		return err
326
+	}
327
+
328
+	utils.Debugf("Updating checksums")
329
+	// Reload the json file to make sure not to overwrite faster sums
330
+	if err := srv.runtime.graph.UpdateChecksums(repoData.ImgList); err != nil {
331
+		return err
332
+	}
333
+
334
+	utils.Debugf("Retrieving the tag list")
335
+	tagsList, err := srv.registry.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens)
336
+	if err != nil {
337
+		return err
338
+	}
339
+	for tag, id := range tagsList {
340
+		repoData.ImgList[id].Tag = tag
341
+	}
342
+
343
+	for _, img := range repoData.ImgList {
344
+		// If we asked for a specific tag, skip all tags expect the wanted one
345
+		if askedTag != "" && askedTag != img.Tag {
346
+			continue
347
+		}
348
+		fmt.Fprintf(stdout, "Pulling image %s (%s) from %s\n", img.Id, img.Tag, remote)
349
+		success := false
350
+		for _, ep := range repoData.Endpoints {
351
+			if err := srv.pullImage(stdout, img.Id, "https://"+ep+"/v1", repoData.Tokens); err != nil {
352
+				fmt.Fprintf(stdout, "Error while retrieving image for tag: %s (%s); checking next endpoint\n", askedTag, err)
353
+				continue
354
+			}
355
+			if err := srv.runtime.repositories.Set(remote, img.Tag, img.Id, true); err != nil {
356
+				return err
357
+			}
358
+			success = true
359
+			delete(tagsList, img.Tag)
360
+			break
361
+		}
362
+		if !success {
363
+			return fmt.Errorf("Could not find repository on any of the indexed registries.")
364
+		}
365
+	}
366
+	for tag, id := range tagsList {
367
+		if err := srv.runtime.repositories.Set(remote, tag, id, true); err != nil {
368
+			return err
369
+		}
370
+	}
371
+	if err := srv.runtime.repositories.Save(); err != nil {
372
+		return err
373
+	}
374
+
375
+	return nil
376
+}
377
+
286 378
 func (srv *Server) ImagePull(name, tag, registry string, out io.Writer) error {
287 379
 	if registry != "" {
288
-		if err := srv.runtime.graph.PullImage(out, name, registry, nil); err != nil {
380
+		if err := srv.pullImage(out, name, registry, nil); err != nil {
289 381
 			return err
290 382
 		}
291 383
 		return nil
292 384
 	}
293
-	if err := srv.runtime.graph.PullRepository(out, name, tag, srv.runtime.repositories, srv.runtime.authConfig); err != nil {
385
+
386
+	if err := srv.pullRepository(out, name, tag); err != nil {
387
+		return err
388
+	}
389
+
390
+	return nil
391
+}
392
+
393
+// Retrieve the checksum of an image
394
+// Priority:
395
+// - Check on the stored checksums
396
+// - Check if the archive exists, if it does not, ask the registry
397
+// - If the archive does exists, process the checksum from it
398
+// - If the archive does not exists and not found on registry, process checksum from layer
399
+func (srv *Server) getChecksum(imageId string) (string, error) {
400
+	// FIXME: Use in-memory map instead of reading the file each time
401
+	if sums, err := srv.runtime.graph.getStoredChecksums(); err != nil {
402
+		return "", err
403
+	} else if checksum, exists := sums[imageId]; exists {
404
+		return checksum, nil
405
+	}
406
+
407
+	img, err := srv.runtime.graph.Get(imageId)
408
+	if err != nil {
409
+		return "", err
410
+	}
411
+
412
+	if _, err := os.Stat(layerArchivePath(srv.runtime.graph.imageRoot(imageId))); err != nil {
413
+		if os.IsNotExist(err) {
414
+			// TODO: Ask the registry for the checksum
415
+			//       As the archive is not there, it is supposed to come from a pull.
416
+		} else {
417
+			return "", err
418
+		}
419
+	}
420
+
421
+	checksum, err := img.Checksum()
422
+	if err != nil {
423
+		return "", err
424
+	}
425
+	return checksum, nil
426
+}
427
+
428
+// Retrieve the all the images to be uploaded in the correct order
429
+// Note: we can't use a map as it is not ordered
430
+func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgData, error) {
431
+	var imgList []*registry.ImgData
432
+
433
+	imageSet := make(map[string]struct{})
434
+	for tag, id := range localRepo {
435
+		img, err := srv.runtime.graph.Get(id)
436
+		if err != nil {
437
+			return nil, err
438
+		}
439
+		img.WalkHistory(func(img *Image) error {
440
+			if _, exists := imageSet[img.Id]; exists {
441
+				return nil
442
+			}
443
+			imageSet[img.Id] = struct{}{}
444
+			checksum, err := srv.getChecksum(img.Id)
445
+			if err != nil {
446
+				return err
447
+			}
448
+			imgList = append([]*registry.ImgData{{
449
+				Id:       img.Id,
450
+				Checksum: checksum,
451
+				Tag:      tag,
452
+			}}, imgList...)
453
+			return nil
454
+		})
455
+	}
456
+	return imgList, nil
457
+}
458
+
459
+func (srv *Server) pushRepository(out io.Writer, name string, localRepo map[string]string) error {
460
+	fmt.Fprintf(out, "Processing checksums\n")
461
+	imgList, err := srv.getImageList(localRepo)
462
+	if err != nil {
463
+		return err
464
+	}
465
+	fmt.Fprintf(out, "Sending image list\n")
466
+
467
+	repoData, err := srv.registry.PushImageJsonIndex(name, imgList, false)
468
+	if err != nil {
469
+		return err
470
+	}
471
+
472
+	// FIXME: Send only needed images
473
+	for _, ep := range repoData.Endpoints {
474
+		fmt.Fprintf(out, "Pushing repository %s to %s (%d tags)\r\n", name, ep, len(localRepo))
475
+		// For each image within the repo, push them
476
+		for _, elem := range imgList {
477
+			if _, exists := repoData.ImgList[elem.Id]; exists {
478
+				fmt.Fprintf(out, "Image %s already on registry, skipping\n", name)
479
+				continue
480
+			}
481
+			if err := srv.pushImage(out, name, elem.Id, ep, repoData.Tokens); err != nil {
482
+				// FIXME: Continue on error?
483
+				return err
484
+			}
485
+			fmt.Fprintf(out, "Pushing tags for rev [%s] on {%s}\n", elem.Id, ep+"/users/"+name+"/"+elem.Tag)
486
+			if err := srv.registry.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
487
+				return err
488
+			}
489
+		}
490
+	}
491
+
492
+	if _, err := srv.registry.PushImageJsonIndex(name, imgList, true); err != nil {
493
+		return err
494
+	}
495
+	return nil
496
+}
497
+
498
+func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []string) error {
499
+	jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json"))
500
+	if err != nil {
501
+		return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgId, err)
502
+	}
503
+	fmt.Fprintf(out, "Pushing %s\r\n", imgId)
504
+
505
+	// Make sure we have the image's checksum
506
+	checksum, err := srv.getChecksum(imgId)
507
+	if err != nil {
508
+		return err
509
+	}
510
+	imgData := &registry.ImgData{
511
+		Id:       imgId,
512
+		Checksum: checksum,
513
+	}
514
+
515
+	// Send the json
516
+	if err := srv.registry.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
517
+		if err == registry.ErrAlreadyExists {
518
+			fmt.Fprintf(out, "Image %s already uploaded ; skipping\n", imgData.Id)
519
+			return nil
520
+		}
521
+		return err
522
+	}
523
+
524
+	// Retrieve the tarball to be sent
525
+	var layerData *TempArchive
526
+	// If the archive exists, use it
527
+	file, err := os.Open(layerArchivePath(srv.runtime.graph.imageRoot(imgId)))
528
+	if err != nil {
529
+		if os.IsNotExist(err) {
530
+			// If the archive does not exist, create one from the layer
531
+			layerData, err = srv.runtime.graph.TempLayerArchive(imgId, Xz, out)
532
+			if err != nil {
533
+				return fmt.Errorf("Failed to generate layer archive: %s", err)
534
+			}
535
+		} else {
536
+			return err
537
+		}
538
+	} else {
539
+		defer file.Close()
540
+		st, err := file.Stat()
541
+		if err != nil {
542
+			return err
543
+		}
544
+		layerData = &TempArchive{
545
+			File: file,
546
+			Size: st.Size(),
547
+		}
548
+	}
549
+
550
+	// Send the layer
551
+	if err := srv.registry.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, ""), ep, token); err != nil {
294 552
 		return err
295 553
 	}
296 554
 	return nil
... ...
@@ -299,10 +561,10 @@ func (srv *Server) ImagePull(name, tag, registry string, out io.Writer) error {
299 299
 func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
300 300
 	img, err := srv.runtime.graph.Get(name)
301 301
 	if err != nil {
302
-		Debugf("The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
302
+		fmt.Fprintf(out, "The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
303 303
 		// If it fails, try to get the repository
304 304
 		if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
305
-			if err := srv.runtime.graph.PushRepository(out, name, localRepo, srv.runtime.authConfig); err != nil {
305
+			if err := srv.pushRepository(out, name, localRepo); err != nil {
306 306
 				return err
307 307
 			}
308 308
 			return nil
... ...
@@ -310,8 +572,8 @@ func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
310 310
 
311 311
 		return err
312 312
 	}
313
-	err = srv.runtime.graph.PushImage(out, img, registry, nil)
314
-	if err != nil {
313
+	fmt.Fprintf(out, "The push refers to an image: [%s]\n", name)
314
+	if err := srv.pushImage(out, name, img.Id, registry, nil); err != nil {
315 315
 		return err
316 316
 	}
317 317
 	return nil
... ...
@@ -336,11 +598,11 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
336 336
 		fmt.Fprintln(out, "Downloading from", u)
337 337
 		// Download with curl (pretty progress bar)
338 338
 		// If curl is not available, fallback to http.Get()
339
-		resp, err = Download(u.String(), out)
339
+		resp, err = utils.Download(u.String(), out)
340 340
 		if err != nil {
341 341
 			return err
342 342
 		}
343
-		archive = ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)")
343
+		archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)")
344 344
 	}
345 345
 	img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
346 346
 	if err != nil {
... ...
@@ -397,7 +659,6 @@ func (srv *Server) ContainerRestart(name string, t int) error {
397 397
 }
398 398
 
399 399
 func (srv *Server) ContainerDestroy(name string, removeVolume bool) error {
400
-
401 400
 	if container := srv.runtime.Get(name); container != nil {
402 401
 		volumes := make(map[string]struct{})
403 402
 		// Store all the deleted containers volumes
... ...
@@ -522,17 +783,17 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
522 522
 		if stdout {
523 523
 			cLog, err := container.ReadLog("stdout")
524 524
 			if err != nil {
525
-				Debugf(err.Error())
525
+				utils.Debugf(err.Error())
526 526
 			} else if _, err := io.Copy(out, cLog); err != nil {
527
-				Debugf(err.Error())
527
+				utils.Debugf(err.Error())
528 528
 			}
529 529
 		}
530 530
 		if stderr {
531 531
 			cLog, err := container.ReadLog("stderr")
532 532
 			if err != nil {
533
-				Debugf(err.Error())
533
+				utils.Debugf(err.Error())
534 534
 			} else if _, err := io.Copy(out, cLog); err != nil {
535
-				Debugf(err.Error())
535
+				utils.Debugf(err.Error())
536 536
 			}
537 537
 		}
538 538
 	}
... ...
@@ -553,7 +814,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
553 553
 			r, w := io.Pipe()
554 554
 			go func() {
555 555
 				defer w.Close()
556
-				defer Debugf("Closing buffered stdin pipe")
556
+				defer utils.Debugf("Closing buffered stdin pipe")
557 557
 				io.Copy(w, in)
558 558
 			}()
559 559
 			cStdin = r
... ...
@@ -600,11 +861,14 @@ func NewServer(autoRestart bool) (*Server, error) {
600 600
 		return nil, err
601 601
 	}
602 602
 	srv := &Server{
603
-		runtime: runtime,
603
+		runtime:  runtime,
604
+		registry: registry.NewRegistry(runtime.root),
604 605
 	}
606
+	runtime.srv = srv
605 607
 	return srv, nil
606 608
 }
607 609
 
608 610
 type Server struct {
609
-	runtime *Runtime
611
+	runtime  *Runtime
612
+	registry *registry.Registry
610 613
 }
... ...
@@ -2,6 +2,7 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"github.com/dotcloud/docker/utils"
5 6
 	"sync"
6 7
 	"time"
7 8
 )
... ...
@@ -21,7 +22,7 @@ func (s *State) String() string {
21 21
 		if s.Ghost {
22 22
 			return fmt.Sprintf("Ghost")
23 23
 		}
24
-		return fmt.Sprintf("Up %s", HumanDuration(time.Now().Sub(s.StartedAt)))
24
+		return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().Sub(s.StartedAt)))
25 25
 	}
26 26
 	return fmt.Sprintf("Exit %d", s.ExitCode)
27 27
 }
... ...
@@ -3,6 +3,7 @@ package docker
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
+	"github.com/dotcloud/docker/utils"
6 7
 	"io/ioutil"
7 8
 	"os"
8 9
 	"path/filepath"
... ...
@@ -106,7 +107,7 @@ func (store *TagStore) ImageName(id string) string {
106 106
 	if names, exists := store.ById()[id]; exists && len(names) > 0 {
107 107
 		return names[0]
108 108
 	}
109
-	return TruncateId(id)
109
+	return utils.TruncateId(id)
110 110
 }
111 111
 
112 112
 func (store *TagStore) Delete(repoName, tag, imageName string) error {
... ...
@@ -1,6 +1,8 @@
1 1
 package term
2 2
 
3 3
 import (
4
+	"os"
5
+	"os/signal"
4 6
 	"syscall"
5 7
 	"unsafe"
6 8
 )
... ...
@@ -120,3 +122,22 @@ func Restore(fd int, state *State) error {
120 120
 	_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
121 121
 	return err
122 122
 }
123
+
124
+func SetRawTerminal() (*State, error) {
125
+	oldState, err := MakeRaw(int(os.Stdin.Fd()))
126
+	if err != nil {
127
+		return nil, err
128
+	}
129
+	c := make(chan os.Signal, 1)
130
+	signal.Notify(c, os.Interrupt)
131
+	go func() {
132
+		_ = <-c
133
+		Restore(int(os.Stdin.Fd()), oldState)
134
+		os.Exit(0)
135
+	}()
136
+	return oldState, err
137
+}
138
+
139
+func RestoreTerminal(state *State) {
140
+	Restore(int(os.Stdin.Fd()), state)
141
+}
123 142
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+=======
1
+testing
2
+=======
3
+
4
+This directory contains testing related files.
5
+
6
+
7
+Buildbot
8
+========
9
+
10
+Buildbot is a continuous integration system designed to automate the
11
+build/test cycle. By automatically rebuilding and testing the tree each time
12
+something has changed, build problems are pinpointed quickly, before other
13
+developers are inconvenienced by the failure.
14
+
15
+We are running buildbot in an AWS instance to verify docker passes all tests
16
+when commits get pushed to the master branch.
17
+
18
+You can check docker's buildbot instance at http://docker-ci.dotcloud.com/waterfall
19
+
20
+
21
+Deployment
22
+~~~~~~~~~~
23
+
24
+::
25
+
26
+  # Define AWS credential environment variables
27
+  export AWS_ACCESS_KEY_ID=xxxxxxxxxxxx
28
+  export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxx
29
+  export AWS_KEYPAIR_NAME=xxxxxxxxxxxx
30
+  export AWS_SSH_PRIVKEY=xxxxxxxxxxxx
31
+
32
+  # Checkout docker
33
+  git clone git://github.com/dotcloud/docker.git
34
+
35
+  # Deploy docker on AWS
36
+  cd docker/testing
37
+  vagrant up --provider=aws
38
+
39
+
40
+Buildbot AWS dependencies
41
+-------------------------
42
+
43
+vagrant, virtualbox packages and vagrant aws plugin
0 44
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+# -*- mode: ruby -*-
1
+# vi: set ft=ruby :
2
+
3
+BOX_NAME = "docker-ci"
4
+BOX_URI = "http://files.vagrantup.com/precise64.box"
5
+AWS_AMI = "ami-d0f89fb9"
6
+DOCKER_PATH = "/data/docker"
7
+CFG_PATH = "#{DOCKER_PATH}/testing/buildbot"
8
+BUILDBOT_IP = "192.168.33.41"
9
+on_vbox = File.file?("#{File.dirname(__FILE__)}/.vagrant/machines/default/virtualbox/id") | \
10
+  Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty? & \
11
+  (on_vbox=true; ARGV.each do |arg| on_vbox &&= !arg.downcase.start_with?("--provider") end; on_vbox)
12
+USER = on_vbox ? "vagrant": "ubuntu"
13
+
14
+Vagrant::Config.run do |config|
15
+  # Setup virtual machine box. This VM configuration code is always executed.
16
+  config.vm.box = BOX_NAME
17
+  config.vm.box_url = BOX_URI
18
+  config.vm.share_folder "v-data", DOCKER_PATH, "#{File.dirname(__FILE__)}/.."
19
+  config.vm.network :hostonly, BUILDBOT_IP
20
+
21
+  # Deploy buildbot and its dependencies if it was not done
22
+  if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty?
23
+    pkg_cmd = "apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; "
24
+    # Deploy buildbot CI
25
+    pkg_cmd << "apt-get install -q -y python-dev python-pip supervisor; " \
26
+      "pip install -r #{CFG_PATH}/requirements.txt; " \
27
+      "chown #{USER}.#{USER} /data; cd /data; " \
28
+      "#{CFG_PATH}/setup.sh #{USER} #{CFG_PATH}; "
29
+    # Install docker dependencies
30
+    pkg_cmd << "apt-get install -q -y python-software-properties; " \
31
+      "add-apt-repository -y ppa:gophers/go/ubuntu; apt-get update -qq; " \
32
+      "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc bsdtar git golang-stable make; "
33
+    # Activate new kernel
34
+    pkg_cmd << "shutdown -r +1; "
35
+    config.vm.provision :shell, :inline => pkg_cmd
36
+  end
37
+end
38
+
39
+# Providers were added on Vagrant >= 1.1.0
40
+Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config|
41
+  config.vm.provider :aws do |aws, override|
42
+    aws.access_key_id = ENV["AWS_ACCESS_KEY_ID"]
43
+    aws.secret_access_key = ENV["AWS_SECRET_ACCESS_KEY"]
44
+    aws.keypair_name = ENV["AWS_KEYPAIR_NAME"]
45
+    override.ssh.private_key_path = ENV["AWS_SSH_PRIVKEY"]
46
+    override.ssh.username = USER
47
+    aws.ami = AWS_AMI
48
+    aws.region = "us-east-1"
49
+    aws.instance_type = "m1.small"
50
+    aws.security_groups = "gateway"
51
+  end
52
+
53
+  config.vm.provider :virtualbox do |vb|
54
+  end
55
+end
0 56
new file mode 100644
... ...
@@ -0,0 +1 @@
0
+Buildbot configuration and setup files (except Vagrantfile located on ..)
0 1
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+[program:buildmaster]
1
+command=twistd --nodaemon --no_save -y buildbot.tac
2
+directory=/data/buildbot/master
3
+chown= root:root
4
+redirect_stderr=true
5
+stdout_logfile=/var/log/supervisor/buildbot-master.log
6
+stderr_logfile=/var/log/supervisor/buildbot-master.log
7
+
8
+[program:buildworker]
9
+command=twistd --nodaemon --no_save -y buildbot.tac
10
+directory=/data/buildbot/slave
11
+chown= root:root
12
+redirect_stderr=true
13
+stdout_logfile=/var/log/supervisor/buildbot-slave.log
14
+stderr_logfile=/var/log/supervisor/buildbot-slave.log
15
+
16
+[group:buildbot]
17
+programs=buildmaster,buildworker
0 18
new file mode 100644
... ...
@@ -0,0 +1,55 @@
0
+import os
1
+from buildbot.buildslave import BuildSlave
2
+from buildbot.schedulers.forcesched import ForceScheduler
3
+from buildbot.schedulers.basic import SingleBranchScheduler
4
+from buildbot.changes import filter
5
+from buildbot.config import BuilderConfig
6
+from buildbot.process.factory import BuildFactory
7
+from buildbot.steps.shell import ShellCommand
8
+from buildbot.status import html
9
+from buildbot.status.web import authz, auth
10
+
11
+PORT_WEB = 80           # Buildbot webserver port
12
+PORT_GITHUB = 8011      # Buildbot github hook port
13
+PORT_MASTER = 9989      # Port where buildbot master listen buildworkers
14
+TEST_USER = 'buildbot'  # Credential to authenticate build triggers
15
+TEST_PWD = 'docker'     # Credential to authenticate build triggers
16
+BUILDER_NAME = 'docker'
17
+BUILDPASSWORD = 'pass-docker'  # Credential to authenticate buildworkers
18
+GITHUB_DOCKER = "github.com/dotcloud/docker"
19
+DOCKER_PATH = "/data/docker"
20
+BUILDER_PATH = "/data/buildbot/slave/{0}/build".format(BUILDER_NAME)
21
+DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker'
22
+
23
+
24
+c = BuildmasterConfig = {}
25
+
26
+c['title'] = "Docker"
27
+c['titleURL'] = "waterfall"
28
+c['buildbotURL'] = "http://0.0.0.0:{0}/".format(PORT_WEB)
29
+c['db'] = {'db_url':"sqlite:///state.sqlite"}
30
+c['slaves'] = [BuildSlave('buildworker', BUILDPASSWORD)]
31
+c['slavePortnum'] = PORT_MASTER
32
+
33
+c['schedulers'] = [ForceScheduler(name='trigger',builderNames=[BUILDER_NAME])]
34
+c['schedulers'].append(SingleBranchScheduler(name="all",
35
+    change_filter=filter.ChangeFilter(branch='master'),treeStableTimer=None,
36
+    builderNames=[BUILDER_NAME]))
37
+
38
+# Docker test command
39
+test_cmd = ("cd /tmp; rm -rf {0}; export GOPATH={0}; go get -d {1}; cd {2}; "
40
+    "go test").format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH)
41
+
42
+# Builder
43
+factory = BuildFactory()
44
+factory.addStep(ShellCommand(description='Docker',logEnviron=False,
45
+    usePTY=True,command=test_cmd))
46
+c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'],
47
+    factory=factory)]
48
+
49
+# Status
50
+authz_cfg=authz.Authz(auth=auth.BasicAuth([(TEST_USER,TEST_PWD)]),
51
+    forceBuild='auth')
52
+c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)]
53
+c['status'].append(html.WebStatus(http_port=PORT_GITHUB,allowForce=True,
54
+    change_hook_dialects={ 'github' : True }))
0 55
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+sqlalchemy<=0.7.9
1
+sqlalchemy-migrate>=0.7.2
2
+buildbot==0.8.7p1
3
+buildbot_slave==0.8.7p1
4
+nose==1.2.1
5
+requests==1.1.0
0 6
new file mode 100755
... ...
@@ -0,0 +1,36 @@
0
+#!/bin/bash
1
+
2
+# Setup of buildbot configuration. Package installation is being done by
3
+# Vagrantfile
4
+# Dependencies: buildbot, buildbot-slave, supervisor
5
+
6
+USER=$1
7
+CFG_PATH=$2
8
+BUILDBOT_PATH="/data/buildbot"
9
+DOCKER_PATH="/data/docker"
10
+SLAVE_NAME="buildworker"
11
+SLAVE_SOCKET="localhost:9989"
12
+BUILDBOT_PWD="pass-docker"
13
+export PATH="/bin:sbin:/usr/bin:/usr/sbin:/usr/local/bin"
14
+
15
+function run { su $USER -c "$1"; }
16
+
17
+# Exit if buildbot has already been installed
18
+[ -d "$BUILDBOT_PATH" ] && exit 0
19
+
20
+# Setup buildbot
21
+run "mkdir -p $BUILDBOT_PATH"
22
+cd $BUILDBOT_PATH
23
+run "buildbot create-master master"
24
+run "cp $CFG_PATH/master.cfg master"
25
+run "sed -i -E 's#(DOCKER_PATH = ).+#\1\"$DOCKER_PATH\"#' master/master.cfg"
26
+run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD"
27
+
28
+# Allow buildbot subprocesses (docker tests) to properly run in containers,
29
+# in particular with docker -u
30
+run "sed -i 's/^umask = None/umask = 000/' slave/buildbot.tac"
31
+
32
+# Setup supervisor
33
+cp $CFG_PATH/buildbot.conf /etc/supervisor/conf.d/buildbot.conf
34
+sed -i -E "s/^chmod=0700.+/chmod=0770\nchown=root:$USER/" /etc/supervisor/supervisord.conf
35
+kill -HUP $(pgrep -f "/usr/bin/python /usr/bin/supervisord")
... ...
@@ -1,500 +1,5 @@
1 1
 package docker
2 2
 
3
-import (
4
-	"bytes"
5
-	"crypto/sha256"
6
-	"encoding/hex"
7
-	"errors"
8
-	"fmt"
9
-	"github.com/dotcloud/docker/term"
10
-	"index/suffixarray"
11
-	"io"
12
-	"io/ioutil"
13
-	"net/http"
14
-	"os"
15
-	"os/exec"
16
-	"os/signal"
17
-	"path/filepath"
18
-	"runtime"
19
-	"strings"
20
-	"sync"
21
-	"time"
22
-)
23
-
24
-// Go is a basic promise implementation: it wraps calls a function in a goroutine,
25
-// and returns a channel which will later return the function's return value.
26
-func Go(f func() error) chan error {
27
-	ch := make(chan error)
28
-	go func() {
29
-		ch <- f()
30
-	}()
31
-	return ch
32
-}
33
-
34
-// Request a given URL and return an io.Reader
35
-func Download(url string, stderr io.Writer) (*http.Response, error) {
36
-	var resp *http.Response
37
-	var err error = nil
38
-	if resp, err = http.Get(url); err != nil {
39
-		return nil, err
40
-	}
41
-	if resp.StatusCode >= 400 {
42
-		return nil, errors.New("Got HTTP status code >= 400: " + resp.Status)
43
-	}
44
-	return resp, nil
45
-}
46
-
47
-// Debug function, if the debug flag is set, then display. Do nothing otherwise
48
-// If Docker is in damon mode, also send the debug info on the socket
49
-func Debugf(format string, a ...interface{}) {
50
-	if os.Getenv("DEBUG") != "" {
51
-
52
-		// Retrieve the stack infos
53
-		_, file, line, ok := runtime.Caller(1)
54
-		if !ok {
55
-			file = "<unknown>"
56
-			line = -1
57
-		} else {
58
-			file = file[strings.LastIndex(file, "/")+1:]
59
-		}
60
-
61
-		fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...)
62
-	}
63
-}
64
-
65
-// Reader with progress bar
66
-type progressReader struct {
67
-	reader       io.ReadCloser // Stream to read from
68
-	output       io.Writer     // Where to send progress bar to
69
-	readTotal    int           // Expected stream length (bytes)
70
-	readProgress int           // How much has been read so far (bytes)
71
-	lastUpdate   int           // How many bytes read at least update
72
-	template     string        // Template to print. Default "%v/%v (%v)"
73
-}
74
-
75
-func (r *progressReader) Read(p []byte) (n int, err error) {
76
-	read, err := io.ReadCloser(r.reader).Read(p)
77
-	r.readProgress += read
78
-
79
-	updateEvery := 4096
80
-	if r.readTotal > 0 {
81
-		// Only update progress for every 1% read
82
-		if increment := int(0.01 * float64(r.readTotal)); increment > updateEvery {
83
-			updateEvery = increment
84
-		}
85
-	}
86
-	if r.readProgress-r.lastUpdate > updateEvery || err != nil {
87
-		if r.readTotal > 0 {
88
-			fmt.Fprintf(r.output, r.template+"\r", r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
89
-		} else {
90
-			fmt.Fprintf(r.output, r.template+"\r", r.readProgress, "?", "n/a")
91
-		}
92
-		r.lastUpdate = r.readProgress
93
-	}
94
-	// Send newline when complete
95
-	if err != nil {
96
-		fmt.Fprintf(r.output, "\n")
97
-	}
98
-
99
-	return read, err
100
-}
101
-func (r *progressReader) Close() error {
102
-	return io.ReadCloser(r.reader).Close()
103
-}
104
-func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string) *progressReader {
105
-	if template == "" {
106
-		template = "%v/%v (%v)"
107
-	}
108
-	return &progressReader{r, output, size, 0, 0, template}
109
-}
110
-
111
-// HumanDuration returns a human-readable approximation of a duration
112
-// (eg. "About a minute", "4 hours ago", etc.)
113
-func HumanDuration(d time.Duration) string {
114
-	if seconds := int(d.Seconds()); seconds < 1 {
115
-		return "Less than a second"
116
-	} else if seconds < 60 {
117
-		return fmt.Sprintf("%d seconds", seconds)
118
-	} else if minutes := int(d.Minutes()); minutes == 1 {
119
-		return "About a minute"
120
-	} else if minutes < 60 {
121
-		return fmt.Sprintf("%d minutes", minutes)
122
-	} else if hours := int(d.Hours()); hours == 1 {
123
-		return "About an hour"
124
-	} else if hours < 48 {
125
-		return fmt.Sprintf("%d hours", hours)
126
-	} else if hours < 24*7*2 {
127
-		return fmt.Sprintf("%d days", hours/24)
128
-	} else if hours < 24*30*3 {
129
-		return fmt.Sprintf("%d weeks", hours/24/7)
130
-	} else if hours < 24*365*2 {
131
-		return fmt.Sprintf("%d months", hours/24/30)
132
-	}
133
-	return fmt.Sprintf("%d years", d.Hours()/24/365)
134
-}
135
-
136
-func Trunc(s string, maxlen int) string {
137
-	if len(s) <= maxlen {
138
-		return s
139
-	}
140
-	return s[:maxlen]
141
-}
142
-
143
-// Figure out the absolute path of our own binary
144
-func SelfPath() string {
145
-	path, err := exec.LookPath(os.Args[0])
146
-	if err != nil {
147
-		panic(err)
148
-	}
149
-	path, err = filepath.Abs(path)
150
-	if err != nil {
151
-		panic(err)
152
-	}
153
-	return path
154
-}
155
-
156
-type nopWriter struct {
157
-}
158
-
159
-func (w *nopWriter) Write(buf []byte) (int, error) {
160
-	return len(buf), nil
161
-}
162
-
163
-type nopWriteCloser struct {
164
-	io.Writer
165
-}
166
-
167
-func (w *nopWriteCloser) Close() error { return nil }
168
-
169
-func NopWriteCloser(w io.Writer) io.WriteCloser {
170
-	return &nopWriteCloser{w}
171
-}
172
-
173
-type bufReader struct {
174
-	buf    *bytes.Buffer
175
-	reader io.Reader
176
-	err    error
177
-	l      sync.Mutex
178
-	wait   sync.Cond
179
-}
180
-
181
-func newBufReader(r io.Reader) *bufReader {
182
-	reader := &bufReader{
183
-		buf:    &bytes.Buffer{},
184
-		reader: r,
185
-	}
186
-	reader.wait.L = &reader.l
187
-	go reader.drain()
188
-	return reader
189
-}
190
-
191
-func (r *bufReader) drain() {
192
-	buf := make([]byte, 1024)
193
-	for {
194
-		n, err := r.reader.Read(buf)
195
-		r.l.Lock()
196
-		if err != nil {
197
-			r.err = err
198
-		} else {
199
-			r.buf.Write(buf[0:n])
200
-		}
201
-		r.wait.Signal()
202
-		r.l.Unlock()
203
-		if err != nil {
204
-			break
205
-		}
206
-	}
207
-}
208
-
209
-func (r *bufReader) Read(p []byte) (n int, err error) {
210
-	r.l.Lock()
211
-	defer r.l.Unlock()
212
-	for {
213
-		n, err = r.buf.Read(p)
214
-		if n > 0 {
215
-			return n, err
216
-		}
217
-		if r.err != nil {
218
-			return 0, r.err
219
-		}
220
-		r.wait.Wait()
221
-	}
222
-	panic("unreachable")
223
-}
224
-
225
-func (r *bufReader) Close() error {
226
-	closer, ok := r.reader.(io.ReadCloser)
227
-	if !ok {
228
-		return nil
229
-	}
230
-	return closer.Close()
231
-}
232
-
233
-type writeBroadcaster struct {
234
-	mu      sync.Mutex
235
-	writers map[io.WriteCloser]struct{}
236
-}
237
-
238
-func (w *writeBroadcaster) AddWriter(writer io.WriteCloser) {
239
-	w.mu.Lock()
240
-	w.writers[writer] = struct{}{}
241
-	w.mu.Unlock()
242
-}
243
-
244
-// FIXME: Is that function used?
245
-// FIXME: This relies on the concrete writer type used having equality operator
246
-func (w *writeBroadcaster) RemoveWriter(writer io.WriteCloser) {
247
-	w.mu.Lock()
248
-	delete(w.writers, writer)
249
-	w.mu.Unlock()
250
-}
251
-
252
-func (w *writeBroadcaster) Write(p []byte) (n int, err error) {
253
-	w.mu.Lock()
254
-	defer w.mu.Unlock()
255
-	for writer := range w.writers {
256
-		if n, err := writer.Write(p); err != nil || n != len(p) {
257
-			// On error, evict the writer
258
-			delete(w.writers, writer)
259
-		}
260
-	}
261
-	return len(p), nil
262
-}
263
-
264
-func (w *writeBroadcaster) CloseWriters() error {
265
-	w.mu.Lock()
266
-	defer w.mu.Unlock()
267
-	for writer := range w.writers {
268
-		writer.Close()
269
-	}
270
-	w.writers = make(map[io.WriteCloser]struct{})
271
-	return nil
272
-}
273
-
274
-func newWriteBroadcaster() *writeBroadcaster {
275
-	return &writeBroadcaster{writers: make(map[io.WriteCloser]struct{})}
276
-}
277
-
278
-func getTotalUsedFds() int {
279
-	if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
280
-		Debugf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
281
-	} else {
282
-		return len(fds)
283
-	}
284
-	return -1
285
-}
286
-
287
-// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
288
-// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
289
-type TruncIndex struct {
290
-	index *suffixarray.Index
291
-	ids   map[string]bool
292
-	bytes []byte
293
-}
294
-
295
-func NewTruncIndex() *TruncIndex {
296
-	return &TruncIndex{
297
-		index: suffixarray.New([]byte{' '}),
298
-		ids:   make(map[string]bool),
299
-		bytes: []byte{' '},
300
-	}
301
-}
302
-
303
-func (idx *TruncIndex) Add(id string) error {
304
-	if strings.Contains(id, " ") {
305
-		return fmt.Errorf("Illegal character: ' '")
306
-	}
307
-	if _, exists := idx.ids[id]; exists {
308
-		return fmt.Errorf("Id already exists: %s", id)
309
-	}
310
-	idx.ids[id] = true
311
-	idx.bytes = append(idx.bytes, []byte(id+" ")...)
312
-	idx.index = suffixarray.New(idx.bytes)
313
-	return nil
314
-}
315
-
316
-func (idx *TruncIndex) Delete(id string) error {
317
-	if _, exists := idx.ids[id]; !exists {
318
-		return fmt.Errorf("No such id: %s", id)
319
-	}
320
-	before, after, err := idx.lookup(id)
321
-	if err != nil {
322
-		return err
323
-	}
324
-	delete(idx.ids, id)
325
-	idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...)
326
-	idx.index = suffixarray.New(idx.bytes)
327
-	return nil
328
-}
329
-
330
-func (idx *TruncIndex) lookup(s string) (int, int, error) {
331
-	offsets := idx.index.Lookup([]byte(" "+s), -1)
332
-	//log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes())
333
-	if offsets == nil || len(offsets) == 0 || len(offsets) > 1 {
334
-		return -1, -1, fmt.Errorf("No such id: %s", s)
335
-	}
336
-	offsetBefore := offsets[0] + 1
337
-	offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ")
338
-	return offsetBefore, offsetAfter, nil
339
-}
340
-
341
-func (idx *TruncIndex) Get(s string) (string, error) {
342
-	before, after, err := idx.lookup(s)
343
-	//log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after)
344
-	if err != nil {
345
-		return "", err
346
-	}
347
-	return string(idx.bytes[before:after]), err
348
-}
349
-
350
-// TruncateId returns a shorthand version of a string identifier for convenience.
351
-// A collision with other shorthands is very unlikely, but possible.
352
-// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
353
-// will need to use a langer prefix, or the full-length Id.
354
-func TruncateId(id string) string {
355
-	shortLen := 12
356
-	if len(id) < shortLen {
357
-		shortLen = len(id)
358
-	}
359
-	return id[:shortLen]
360
-}
361
-
362
-// Code c/c from io.Copy() modified to handle escape sequence
363
-func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
364
-	buf := make([]byte, 32*1024)
365
-	for {
366
-		nr, er := src.Read(buf)
367
-		if nr > 0 {
368
-			// ---- Docker addition
369
-			// char 16 is C-p
370
-			if nr == 1 && buf[0] == 16 {
371
-				nr, er = src.Read(buf)
372
-				// char 17 is C-q
373
-				if nr == 1 && buf[0] == 17 {
374
-					if err := src.Close(); err != nil {
375
-						return 0, err
376
-					}
377
-					return 0, io.EOF
378
-				}
379
-			}
380
-			// ---- End of docker
381
-			nw, ew := dst.Write(buf[0:nr])
382
-			if nw > 0 {
383
-				written += int64(nw)
384
-			}
385
-			if ew != nil {
386
-				err = ew
387
-				break
388
-			}
389
-			if nr != nw {
390
-				err = io.ErrShortWrite
391
-				break
392
-			}
393
-		}
394
-		if er == io.EOF {
395
-			break
396
-		}
397
-		if er != nil {
398
-			err = er
399
-			break
400
-		}
401
-	}
402
-	return written, err
403
-}
404
-
405
-func SetRawTerminal() (*term.State, error) {
406
-	oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
407
-	if err != nil {
408
-		return nil, err
409
-	}
410
-	c := make(chan os.Signal, 1)
411
-	signal.Notify(c, os.Interrupt)
412
-	go func() {
413
-		_ = <-c
414
-		term.Restore(int(os.Stdin.Fd()), oldState)
415
-		os.Exit(0)
416
-	}()
417
-	return oldState, err
418
-}
419
-
420
-func RestoreTerminal(state *term.State) {
421
-	term.Restore(int(os.Stdin.Fd()), state)
422
-}
423
-
424
-func HashData(src io.Reader) (string, error) {
425
-	h := sha256.New()
426
-	if _, err := io.Copy(h, src); err != nil {
427
-		return "", err
428
-	}
429
-	return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
430
-}
431
-
432
-type KernelVersionInfo struct {
433
-	Kernel int
434
-	Major  int
435
-	Minor  int
436
-	Flavor string
437
-}
438
-
439
-// FIXME: this doens't build on Darwin
440
-func GetKernelVersion() (*KernelVersionInfo, error) {
441
-	return getKernelVersion()
442
-}
443
-
444
-func (k *KernelVersionInfo) String() string {
445
-	flavor := ""
446
-	if len(k.Flavor) > 0 {
447
-		flavor = fmt.Sprintf("-%s", k.Flavor)
448
-	}
449
-	return fmt.Sprintf("%d.%d.%d%s", k.Kernel, k.Major, k.Minor, flavor)
450
-}
451
-
452
-// Compare two KernelVersionInfo struct.
453
-// Returns -1 if a < b, = if a == b, 1 it a > b
454
-func CompareKernelVersion(a, b *KernelVersionInfo) int {
455
-	if a.Kernel < b.Kernel {
456
-		return -1
457
-	} else if a.Kernel > b.Kernel {
458
-		return 1
459
-	}
460
-
461
-	if a.Major < b.Major {
462
-		return -1
463
-	} else if a.Major > b.Major {
464
-		return 1
465
-	}
466
-
467
-	if a.Minor < b.Minor {
468
-		return -1
469
-	} else if a.Minor > b.Minor {
470
-		return 1
471
-	}
472
-
473
-	return 0
474
-}
475
-
476
-func FindCgroupMountpoint(cgroupType string) (string, error) {
477
-	output, err := ioutil.ReadFile("/proc/mounts")
478
-	if err != nil {
479
-		return "", err
480
-	}
481
-
482
-	// /proc/mounts has 6 fields per line, one mount per line, e.g.
483
-	// cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0
484
-	for _, line := range strings.Split(string(output), "\n") {
485
-		parts := strings.Split(line, " ")
486
-		if len(parts) == 6 && parts[2] == "cgroup" {
487
-			for _, opt := range strings.Split(parts[3], ",") {
488
-				if opt == cgroupType {
489
-					return parts[1], nil
490
-				}
491
-			}
492
-		}
493
-	}
494
-
495
-	return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
496
-}
497
-
498 3
 // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
499 4
 // If OpenStdin is set, then it differs
500 5
 func CompareConfig(a, b *Config) bool {
501 6
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+package utils
1
+
2
+import (
3
+	"errors"
4
+	"syscall"
5
+)
6
+
7
+func uname() (*syscall.Utsname, error) {
8
+	return nil, errors.New("Kernel version detection is not available on darwin")
9
+}
0 10
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+package utils
1
+
2
+import (
3
+	"syscall"
4
+)
5
+
6
+// FIXME: Move this to utils package
7
+func uname() (*syscall.Utsname, error) {
8
+	uts := &syscall.Utsname{}
9
+
10
+	if err := syscall.Uname(uts); err != nil {
11
+		return nil, err
12
+	}
13
+	return uts, nil
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,532 @@
0
+package utils
1
+
2
+import (
3
+	"bytes"
4
+	"crypto/sha256"
5
+	"encoding/hex"
6
+	"errors"
7
+	"fmt"
8
+	"index/suffixarray"
9
+	"io"
10
+	"io/ioutil"
11
+	"net/http"
12
+	"os"
13
+	"os/exec"
14
+	"path/filepath"
15
+	"runtime"
16
+	"strconv"
17
+	"strings"
18
+	"sync"
19
+	"time"
20
+)
21
+
22
+// Go is a basic promise implementation: it wraps calls a function in a goroutine,
23
+// and returns a channel which will later return the function's return value.
24
+func Go(f func() error) chan error {
25
+	ch := make(chan error)
26
+	go func() {
27
+		ch <- f()
28
+	}()
29
+	return ch
30
+}
31
+
32
+// Request a given URL and return an io.Reader
33
+func Download(url string, stderr io.Writer) (*http.Response, error) {
34
+	var resp *http.Response
35
+	var err error = nil
36
+	if resp, err = http.Get(url); err != nil {
37
+		return nil, err
38
+	}
39
+	if resp.StatusCode >= 400 {
40
+		return nil, errors.New("Got HTTP status code >= 400: " + resp.Status)
41
+	}
42
+	return resp, nil
43
+}
44
+
45
+// Debug function, if the debug flag is set, then display. Do nothing otherwise
46
+// If Docker is in damon mode, also send the debug info on the socket
47
+func Debugf(format string, a ...interface{}) {
48
+	if os.Getenv("DEBUG") != "" {
49
+
50
+		// Retrieve the stack infos
51
+		_, file, line, ok := runtime.Caller(1)
52
+		if !ok {
53
+			file = "<unknown>"
54
+			line = -1
55
+		} else {
56
+			file = file[strings.LastIndex(file, "/")+1:]
57
+		}
58
+
59
+		fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...)
60
+	}
61
+}
62
+
63
+// Reader with progress bar
64
+type progressReader struct {
65
+	reader       io.ReadCloser // Stream to read from
66
+	output       io.Writer     // Where to send progress bar to
67
+	readTotal    int           // Expected stream length (bytes)
68
+	readProgress int           // How much has been read so far (bytes)
69
+	lastUpdate   int           // How many bytes read at least update
70
+	template     string        // Template to print. Default "%v/%v (%v)"
71
+}
72
+
73
+func (r *progressReader) Read(p []byte) (n int, err error) {
74
+	read, err := io.ReadCloser(r.reader).Read(p)
75
+	r.readProgress += read
76
+
77
+	updateEvery := 4096
78
+	if r.readTotal > 0 {
79
+		// Only update progress for every 1% read
80
+		if increment := int(0.01 * float64(r.readTotal)); increment > updateEvery {
81
+			updateEvery = increment
82
+		}
83
+	}
84
+	if r.readProgress-r.lastUpdate > updateEvery || err != nil {
85
+		if r.readTotal > 0 {
86
+			fmt.Fprintf(r.output, r.template+"\r", r.readProgress, r.readTotal, fmt.Sprintf("%.0f%%", float64(r.readProgress)/float64(r.readTotal)*100))
87
+		} else {
88
+			fmt.Fprintf(r.output, r.template+"\r", r.readProgress, "?", "n/a")
89
+		}
90
+		r.lastUpdate = r.readProgress
91
+	}
92
+	// Send newline when complete
93
+	if err != nil {
94
+		fmt.Fprintf(r.output, "\n")
95
+	}
96
+
97
+	return read, err
98
+}
99
+func (r *progressReader) Close() error {
100
+	return io.ReadCloser(r.reader).Close()
101
+}
102
+func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string) *progressReader {
103
+	if template == "" {
104
+		template = "%v/%v (%v)"
105
+	}
106
+	return &progressReader{r, output, size, 0, 0, template}
107
+}
108
+
109
+// HumanDuration returns a human-readable approximation of a duration
110
+// (eg. "About a minute", "4 hours ago", etc.)
111
+func HumanDuration(d time.Duration) string {
112
+	if seconds := int(d.Seconds()); seconds < 1 {
113
+		return "Less than a second"
114
+	} else if seconds < 60 {
115
+		return fmt.Sprintf("%d seconds", seconds)
116
+	} else if minutes := int(d.Minutes()); minutes == 1 {
117
+		return "About a minute"
118
+	} else if minutes < 60 {
119
+		return fmt.Sprintf("%d minutes", minutes)
120
+	} else if hours := int(d.Hours()); hours == 1 {
121
+		return "About an hour"
122
+	} else if hours < 48 {
123
+		return fmt.Sprintf("%d hours", hours)
124
+	} else if hours < 24*7*2 {
125
+		return fmt.Sprintf("%d days", hours/24)
126
+	} else if hours < 24*30*3 {
127
+		return fmt.Sprintf("%d weeks", hours/24/7)
128
+	} else if hours < 24*365*2 {
129
+		return fmt.Sprintf("%d months", hours/24/30)
130
+	}
131
+	return fmt.Sprintf("%d years", d.Hours()/24/365)
132
+}
133
+
134
+func Trunc(s string, maxlen int) string {
135
+	if len(s) <= maxlen {
136
+		return s
137
+	}
138
+	return s[:maxlen]
139
+}
140
+
141
+// Figure out the absolute path of our own binary
142
+func SelfPath() string {
143
+	path, err := exec.LookPath(os.Args[0])
144
+	if err != nil {
145
+		panic(err)
146
+	}
147
+	path, err = filepath.Abs(path)
148
+	if err != nil {
149
+		panic(err)
150
+	}
151
+	return path
152
+}
153
+
154
+type NopWriter struct {
155
+}
156
+
157
+func (w *NopWriter) Write(buf []byte) (int, error) {
158
+	return len(buf), nil
159
+}
160
+
161
+type nopWriteCloser struct {
162
+	io.Writer
163
+}
164
+
165
+func (w *nopWriteCloser) Close() error { return nil }
166
+
167
+func NopWriteCloser(w io.Writer) io.WriteCloser {
168
+	return &nopWriteCloser{w}
169
+}
170
+
171
+type bufReader struct {
172
+	buf    *bytes.Buffer
173
+	reader io.Reader
174
+	err    error
175
+	l      sync.Mutex
176
+	wait   sync.Cond
177
+}
178
+
179
+func NewBufReader(r io.Reader) *bufReader {
180
+	reader := &bufReader{
181
+		buf:    &bytes.Buffer{},
182
+		reader: r,
183
+	}
184
+	reader.wait.L = &reader.l
185
+	go reader.drain()
186
+	return reader
187
+}
188
+
189
+func (r *bufReader) drain() {
190
+	buf := make([]byte, 1024)
191
+	for {
192
+		n, err := r.reader.Read(buf)
193
+		r.l.Lock()
194
+		if err != nil {
195
+			r.err = err
196
+		} else {
197
+			r.buf.Write(buf[0:n])
198
+		}
199
+		r.wait.Signal()
200
+		r.l.Unlock()
201
+		if err != nil {
202
+			break
203
+		}
204
+	}
205
+}
206
+
207
+func (r *bufReader) Read(p []byte) (n int, err error) {
208
+	r.l.Lock()
209
+	defer r.l.Unlock()
210
+	for {
211
+		n, err = r.buf.Read(p)
212
+		if n > 0 {
213
+			return n, err
214
+		}
215
+		if r.err != nil {
216
+			return 0, r.err
217
+		}
218
+		r.wait.Wait()
219
+	}
220
+	panic("unreachable")
221
+}
222
+
223
+func (r *bufReader) Close() error {
224
+	closer, ok := r.reader.(io.ReadCloser)
225
+	if !ok {
226
+		return nil
227
+	}
228
+	return closer.Close()
229
+}
230
+
231
+type WriteBroadcaster struct {
232
+	mu      sync.Mutex
233
+	writers map[io.WriteCloser]struct{}
234
+}
235
+
236
+func (w *WriteBroadcaster) AddWriter(writer io.WriteCloser) {
237
+	w.mu.Lock()
238
+	w.writers[writer] = struct{}{}
239
+	w.mu.Unlock()
240
+}
241
+
242
+// FIXME: Is that function used?
243
+// FIXME: This relies on the concrete writer type used having equality operator
244
+func (w *WriteBroadcaster) RemoveWriter(writer io.WriteCloser) {
245
+	w.mu.Lock()
246
+	delete(w.writers, writer)
247
+	w.mu.Unlock()
248
+}
249
+
250
+func (w *WriteBroadcaster) Write(p []byte) (n int, err error) {
251
+	w.mu.Lock()
252
+	defer w.mu.Unlock()
253
+	for writer := range w.writers {
254
+		if n, err := writer.Write(p); err != nil || n != len(p) {
255
+			// On error, evict the writer
256
+			delete(w.writers, writer)
257
+		}
258
+	}
259
+	return len(p), nil
260
+}
261
+
262
+func (w *WriteBroadcaster) CloseWriters() error {
263
+	w.mu.Lock()
264
+	defer w.mu.Unlock()
265
+	for writer := range w.writers {
266
+		writer.Close()
267
+	}
268
+	w.writers = make(map[io.WriteCloser]struct{})
269
+	return nil
270
+}
271
+
272
+func NewWriteBroadcaster() *WriteBroadcaster {
273
+	return &WriteBroadcaster{writers: make(map[io.WriteCloser]struct{})}
274
+}
275
+
276
+func GetTotalUsedFds() int {
277
+	if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
278
+		Debugf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
279
+	} else {
280
+		return len(fds)
281
+	}
282
+	return -1
283
+}
284
+
285
+// TruncIndex allows the retrieval of string identifiers by any of their unique prefixes.
286
+// This is used to retrieve image and container IDs by more convenient shorthand prefixes.
287
+type TruncIndex struct {
288
+	index *suffixarray.Index
289
+	ids   map[string]bool
290
+	bytes []byte
291
+}
292
+
293
+func NewTruncIndex() *TruncIndex {
294
+	return &TruncIndex{
295
+		index: suffixarray.New([]byte{' '}),
296
+		ids:   make(map[string]bool),
297
+		bytes: []byte{' '},
298
+	}
299
+}
300
+
301
+func (idx *TruncIndex) Add(id string) error {
302
+	if strings.Contains(id, " ") {
303
+		return fmt.Errorf("Illegal character: ' '")
304
+	}
305
+	if _, exists := idx.ids[id]; exists {
306
+		return fmt.Errorf("Id already exists: %s", id)
307
+	}
308
+	idx.ids[id] = true
309
+	idx.bytes = append(idx.bytes, []byte(id+" ")...)
310
+	idx.index = suffixarray.New(idx.bytes)
311
+	return nil
312
+}
313
+
314
+func (idx *TruncIndex) Delete(id string) error {
315
+	if _, exists := idx.ids[id]; !exists {
316
+		return fmt.Errorf("No such id: %s", id)
317
+	}
318
+	before, after, err := idx.lookup(id)
319
+	if err != nil {
320
+		return err
321
+	}
322
+	delete(idx.ids, id)
323
+	idx.bytes = append(idx.bytes[:before], idx.bytes[after:]...)
324
+	idx.index = suffixarray.New(idx.bytes)
325
+	return nil
326
+}
327
+
328
+func (idx *TruncIndex) lookup(s string) (int, int, error) {
329
+	offsets := idx.index.Lookup([]byte(" "+s), -1)
330
+	//log.Printf("lookup(%s): %v (index bytes: '%s')\n", s, offsets, idx.index.Bytes())
331
+	if offsets == nil || len(offsets) == 0 || len(offsets) > 1 {
332
+		return -1, -1, fmt.Errorf("No such id: %s", s)
333
+	}
334
+	offsetBefore := offsets[0] + 1
335
+	offsetAfter := offsetBefore + strings.Index(string(idx.bytes[offsetBefore:]), " ")
336
+	return offsetBefore, offsetAfter, nil
337
+}
338
+
339
+func (idx *TruncIndex) Get(s string) (string, error) {
340
+	before, after, err := idx.lookup(s)
341
+	//log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after)
342
+	if err != nil {
343
+		return "", err
344
+	}
345
+	return string(idx.bytes[before:after]), err
346
+}
347
+
348
+// TruncateId returns a shorthand version of a string identifier for convenience.
349
+// A collision with other shorthands is very unlikely, but possible.
350
+// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
351
+// will need to use a langer prefix, or the full-length Id.
352
+func TruncateId(id string) string {
353
+	shortLen := 12
354
+	if len(id) < shortLen {
355
+		shortLen = len(id)
356
+	}
357
+	return id[:shortLen]
358
+}
359
+
360
+// Code c/c from io.Copy() modified to handle escape sequence
361
+func CopyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) {
362
+	buf := make([]byte, 32*1024)
363
+	for {
364
+		nr, er := src.Read(buf)
365
+		if nr > 0 {
366
+			// ---- Docker addition
367
+			// char 16 is C-p
368
+			if nr == 1 && buf[0] == 16 {
369
+				nr, er = src.Read(buf)
370
+				// char 17 is C-q
371
+				if nr == 1 && buf[0] == 17 {
372
+					if err := src.Close(); err != nil {
373
+						return 0, err
374
+					}
375
+					return 0, io.EOF
376
+				}
377
+			}
378
+			// ---- End of docker
379
+			nw, ew := dst.Write(buf[0:nr])
380
+			if nw > 0 {
381
+				written += int64(nw)
382
+			}
383
+			if ew != nil {
384
+				err = ew
385
+				break
386
+			}
387
+			if nr != nw {
388
+				err = io.ErrShortWrite
389
+				break
390
+			}
391
+		}
392
+		if er == io.EOF {
393
+			break
394
+		}
395
+		if er != nil {
396
+			err = er
397
+			break
398
+		}
399
+	}
400
+	return written, err
401
+}
402
+
403
+func HashData(src io.Reader) (string, error) {
404
+	h := sha256.New()
405
+	if _, err := io.Copy(h, src); err != nil {
406
+		return "", err
407
+	}
408
+	return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
409
+}
410
+
411
+type KernelVersionInfo struct {
412
+	Kernel int
413
+	Major  int
414
+	Minor  int
415
+	Flavor string
416
+}
417
+
418
+func (k *KernelVersionInfo) String() string {
419
+	flavor := ""
420
+	if len(k.Flavor) > 0 {
421
+		flavor = fmt.Sprintf("-%s", k.Flavor)
422
+	}
423
+	return fmt.Sprintf("%d.%d.%d%s", k.Kernel, k.Major, k.Minor, flavor)
424
+}
425
+
426
+// Compare two KernelVersionInfo struct.
427
+// Returns -1 if a < b, = if a == b, 1 it a > b
428
+func CompareKernelVersion(a, b *KernelVersionInfo) int {
429
+	if a.Kernel < b.Kernel {
430
+		return -1
431
+	} else if a.Kernel > b.Kernel {
432
+		return 1
433
+	}
434
+
435
+	if a.Major < b.Major {
436
+		return -1
437
+	} else if a.Major > b.Major {
438
+		return 1
439
+	}
440
+
441
+	if a.Minor < b.Minor {
442
+		return -1
443
+	} else if a.Minor > b.Minor {
444
+		return 1
445
+	}
446
+
447
+	return 0
448
+}
449
+
450
+func FindCgroupMountpoint(cgroupType string) (string, error) {
451
+	output, err := ioutil.ReadFile("/proc/mounts")
452
+	if err != nil {
453
+		return "", err
454
+	}
455
+
456
+	// /proc/mounts has 6 fields per line, one mount per line, e.g.
457
+	// cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0
458
+	for _, line := range strings.Split(string(output), "\n") {
459
+		parts := strings.Split(line, " ")
460
+		if len(parts) == 6 && parts[2] == "cgroup" {
461
+			for _, opt := range strings.Split(parts[3], ",") {
462
+				if opt == cgroupType {
463
+					return parts[1], nil
464
+				}
465
+			}
466
+		}
467
+	}
468
+
469
+	return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
470
+}
471
+
472
+func GetKernelVersion() (*KernelVersionInfo, error) {
473
+	var (
474
+		flavor               string
475
+		kernel, major, minor int
476
+		err                  error
477
+	)
478
+
479
+	uts, err := uname()
480
+	if err != nil {
481
+		return nil, err
482
+	}
483
+
484
+	release := make([]byte, len(uts.Release))
485
+
486
+	i := 0
487
+	for _, c := range uts.Release {
488
+		release[i] = byte(c)
489
+		i++
490
+	}
491
+
492
+	// Remove the \x00 from the release for Atoi to parse correctly
493
+	release = release[:bytes.IndexByte(release, 0)]
494
+
495
+	tmp := strings.SplitN(string(release), "-", 2)
496
+	tmp2 := strings.SplitN(tmp[0], ".", 3)
497
+
498
+	if len(tmp2) > 0 {
499
+		kernel, err = strconv.Atoi(tmp2[0])
500
+		if err != nil {
501
+			return nil, err
502
+		}
503
+	}
504
+
505
+	if len(tmp2) > 1 {
506
+		major, err = strconv.Atoi(tmp2[1])
507
+		if err != nil {
508
+			return nil, err
509
+		}
510
+	}
511
+
512
+	if len(tmp2) > 2 {
513
+		minor, err = strconv.Atoi(tmp2[2])
514
+		if err != nil {
515
+			return nil, err
516
+		}
517
+	}
518
+
519
+	if len(tmp) == 2 {
520
+		flavor = tmp[1]
521
+	} else {
522
+		flavor = ""
523
+	}
524
+
525
+	return &KernelVersionInfo{
526
+		Kernel: kernel,
527
+		Major:  major,
528
+		Minor:  minor,
529
+		Flavor: flavor,
530
+	}, nil
531
+}
0 532
new file mode 100644
... ...
@@ -0,0 +1,263 @@
0
+package utils
1
+
2
+import (
3
+	"bytes"
4
+	"errors"
5
+	"io"
6
+	"io/ioutil"
7
+	"testing"
8
+)
9
+
10
+func TestBufReader(t *testing.T) {
11
+	reader, writer := io.Pipe()
12
+	bufreader := NewBufReader(reader)
13
+
14
+	// Write everything down to a Pipe
15
+	// Usually, a pipe should block but because of the buffered reader,
16
+	// the writes will go through
17
+	done := make(chan bool)
18
+	go func() {
19
+		writer.Write([]byte("hello world"))
20
+		writer.Close()
21
+		done <- true
22
+	}()
23
+
24
+	// Drain the reader *after* everything has been written, just to verify
25
+	// it is indeed buffering
26
+	<-done
27
+	output, err := ioutil.ReadAll(bufreader)
28
+	if err != nil {
29
+		t.Fatal(err)
30
+	}
31
+	if !bytes.Equal(output, []byte("hello world")) {
32
+		t.Error(string(output))
33
+	}
34
+}
35
+
36
+type dummyWriter struct {
37
+	buffer      bytes.Buffer
38
+	failOnWrite bool
39
+}
40
+
41
+func (dw *dummyWriter) Write(p []byte) (n int, err error) {
42
+	if dw.failOnWrite {
43
+		return 0, errors.New("Fake fail")
44
+	}
45
+	return dw.buffer.Write(p)
46
+}
47
+
48
+func (dw *dummyWriter) String() string {
49
+	return dw.buffer.String()
50
+}
51
+
52
+func (dw *dummyWriter) Close() error {
53
+	return nil
54
+}
55
+
56
+func TestWriteBroadcaster(t *testing.T) {
57
+	writer := NewWriteBroadcaster()
58
+
59
+	// Test 1: Both bufferA and bufferB should contain "foo"
60
+	bufferA := &dummyWriter{}
61
+	writer.AddWriter(bufferA)
62
+	bufferB := &dummyWriter{}
63
+	writer.AddWriter(bufferB)
64
+	writer.Write([]byte("foo"))
65
+
66
+	if bufferA.String() != "foo" {
67
+		t.Errorf("Buffer contains %v", bufferA.String())
68
+	}
69
+
70
+	if bufferB.String() != "foo" {
71
+		t.Errorf("Buffer contains %v", bufferB.String())
72
+	}
73
+
74
+	// Test2: bufferA and bufferB should contain "foobar",
75
+	// while bufferC should only contain "bar"
76
+	bufferC := &dummyWriter{}
77
+	writer.AddWriter(bufferC)
78
+	writer.Write([]byte("bar"))
79
+
80
+	if bufferA.String() != "foobar" {
81
+		t.Errorf("Buffer contains %v", bufferA.String())
82
+	}
83
+
84
+	if bufferB.String() != "foobar" {
85
+		t.Errorf("Buffer contains %v", bufferB.String())
86
+	}
87
+
88
+	if bufferC.String() != "bar" {
89
+		t.Errorf("Buffer contains %v", bufferC.String())
90
+	}
91
+
92
+	// Test3: Test removal
93
+	writer.RemoveWriter(bufferB)
94
+	writer.Write([]byte("42"))
95
+	if bufferA.String() != "foobar42" {
96
+		t.Errorf("Buffer contains %v", bufferA.String())
97
+	}
98
+	if bufferB.String() != "foobar" {
99
+		t.Errorf("Buffer contains %v", bufferB.String())
100
+	}
101
+	if bufferC.String() != "bar42" {
102
+		t.Errorf("Buffer contains %v", bufferC.String())
103
+	}
104
+
105
+	// Test4: Test eviction on failure
106
+	bufferA.failOnWrite = true
107
+	writer.Write([]byte("fail"))
108
+	if bufferA.String() != "foobar42" {
109
+		t.Errorf("Buffer contains %v", bufferA.String())
110
+	}
111
+	if bufferC.String() != "bar42fail" {
112
+		t.Errorf("Buffer contains %v", bufferC.String())
113
+	}
114
+	// Even though we reset the flag, no more writes should go in there
115
+	bufferA.failOnWrite = false
116
+	writer.Write([]byte("test"))
117
+	if bufferA.String() != "foobar42" {
118
+		t.Errorf("Buffer contains %v", bufferA.String())
119
+	}
120
+	if bufferC.String() != "bar42failtest" {
121
+		t.Errorf("Buffer contains %v", bufferC.String())
122
+	}
123
+
124
+	writer.CloseWriters()
125
+}
126
+
127
+type devNullCloser int
128
+
129
+func (d devNullCloser) Close() error {
130
+	return nil
131
+}
132
+
133
+func (d devNullCloser) Write(buf []byte) (int, error) {
134
+	return len(buf), nil
135
+}
136
+
137
+// This test checks for races. It is only useful when run with the race detector.
138
+func TestRaceWriteBroadcaster(t *testing.T) {
139
+	writer := NewWriteBroadcaster()
140
+	c := make(chan bool)
141
+	go func() {
142
+		writer.AddWriter(devNullCloser(0))
143
+		c <- true
144
+	}()
145
+	writer.Write([]byte("hello"))
146
+	<-c
147
+}
148
+
149
+// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix.
150
+func TestTruncIndex(t *testing.T) {
151
+	index := NewTruncIndex()
152
+	// Get on an empty index
153
+	if _, err := index.Get("foobar"); err == nil {
154
+		t.Fatal("Get on an empty index should return an error")
155
+	}
156
+
157
+	// Spaces should be illegal in an id
158
+	if err := index.Add("I have a space"); err == nil {
159
+		t.Fatalf("Adding an id with ' ' should return an error")
160
+	}
161
+
162
+	id := "99b36c2c326ccc11e726eee6ee78a0baf166ef96"
163
+	// Add an id
164
+	if err := index.Add(id); err != nil {
165
+		t.Fatal(err)
166
+	}
167
+	// Get a non-existing id
168
+	assertIndexGet(t, index, "abracadabra", "", true)
169
+	// Get the exact id
170
+	assertIndexGet(t, index, id, id, false)
171
+	// The first letter should match
172
+	assertIndexGet(t, index, id[:1], id, false)
173
+	// The first half should match
174
+	assertIndexGet(t, index, id[:len(id)/2], id, false)
175
+	// The second half should NOT match
176
+	assertIndexGet(t, index, id[len(id)/2:], "", true)
177
+
178
+	id2 := id[:6] + "blabla"
179
+	// Add an id
180
+	if err := index.Add(id2); err != nil {
181
+		t.Fatal(err)
182
+	}
183
+	// Both exact IDs should work
184
+	assertIndexGet(t, index, id, id, false)
185
+	assertIndexGet(t, index, id2, id2, false)
186
+
187
+	// 6 characters or less should conflict
188
+	assertIndexGet(t, index, id[:6], "", true)
189
+	assertIndexGet(t, index, id[:4], "", true)
190
+	assertIndexGet(t, index, id[:1], "", true)
191
+
192
+	// 7 characters should NOT conflict
193
+	assertIndexGet(t, index, id[:7], id, false)
194
+	assertIndexGet(t, index, id2[:7], id2, false)
195
+
196
+	// Deleting a non-existing id should return an error
197
+	if err := index.Delete("non-existing"); err == nil {
198
+		t.Fatalf("Deleting a non-existing id should return an error")
199
+	}
200
+
201
+	// Deleting id2 should remove conflicts
202
+	if err := index.Delete(id2); err != nil {
203
+		t.Fatal(err)
204
+	}
205
+	// id2 should no longer work
206
+	assertIndexGet(t, index, id2, "", true)
207
+	assertIndexGet(t, index, id2[:7], "", true)
208
+	assertIndexGet(t, index, id2[:11], "", true)
209
+
210
+	// conflicts between id and id2 should be gone
211
+	assertIndexGet(t, index, id[:6], id, false)
212
+	assertIndexGet(t, index, id[:4], id, false)
213
+	assertIndexGet(t, index, id[:1], id, false)
214
+
215
+	// non-conflicting substrings should still not conflict
216
+	assertIndexGet(t, index, id[:7], id, false)
217
+	assertIndexGet(t, index, id[:15], id, false)
218
+	assertIndexGet(t, index, id, id, false)
219
+}
220
+
221
+func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult string, expectError bool) {
222
+	if result, err := index.Get(input); err != nil && !expectError {
223
+		t.Fatalf("Unexpected error getting '%s': %s", input, err)
224
+	} else if err == nil && expectError {
225
+		t.Fatalf("Getting '%s' should return an error", input)
226
+	} else if result != expectedResult {
227
+		t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
228
+	}
229
+}
230
+
231
+func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) {
232
+	if r := CompareKernelVersion(a, b); r != result {
233
+		t.Fatalf("Unepected kernel version comparaison result. Found %d, expected %d", r, result)
234
+	}
235
+}
236
+
237
+func TestCompareKernelVersion(t *testing.T) {
238
+	assertKernelVersion(t,
239
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
240
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
241
+		0)
242
+	assertKernelVersion(t,
243
+		&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
244
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
245
+		-1)
246
+	assertKernelVersion(t,
247
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
248
+		&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
249
+		1)
250
+	assertKernelVersion(t,
251
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
252
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "16"},
253
+		0)
254
+	assertKernelVersion(t,
255
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5},
256
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
257
+		1)
258
+	assertKernelVersion(t,
259
+		&KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Flavor: "25"},
260
+		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
261
+		-1)
262
+}
0 263
deleted file mode 100644
... ...
@@ -1,263 +0,0 @@
1
-package docker
2
-
3
-import (
4
-	"bytes"
5
-	"errors"
6
-	"io"
7
-	"io/ioutil"
8
-	"testing"
9
-)
10
-
11
-func TestBufReader(t *testing.T) {
12
-	reader, writer := io.Pipe()
13
-	bufreader := newBufReader(reader)
14
-
15
-	// Write everything down to a Pipe
16
-	// Usually, a pipe should block but because of the buffered reader,
17
-	// the writes will go through
18
-	done := make(chan bool)
19
-	go func() {
20
-		writer.Write([]byte("hello world"))
21
-		writer.Close()
22
-		done <- true
23
-	}()
24
-
25
-	// Drain the reader *after* everything has been written, just to verify
26
-	// it is indeed buffering
27
-	<-done
28
-	output, err := ioutil.ReadAll(bufreader)
29
-	if err != nil {
30
-		t.Fatal(err)
31
-	}
32
-	if !bytes.Equal(output, []byte("hello world")) {
33
-		t.Error(string(output))
34
-	}
35
-}
36
-
37
-type dummyWriter struct {
38
-	buffer      bytes.Buffer
39
-	failOnWrite bool
40
-}
41
-
42
-func (dw *dummyWriter) Write(p []byte) (n int, err error) {
43
-	if dw.failOnWrite {
44
-		return 0, errors.New("Fake fail")
45
-	}
46
-	return dw.buffer.Write(p)
47
-}
48
-
49
-func (dw *dummyWriter) String() string {
50
-	return dw.buffer.String()
51
-}
52
-
53
-func (dw *dummyWriter) Close() error {
54
-	return nil
55
-}
56
-
57
-func TestWriteBroadcaster(t *testing.T) {
58
-	writer := newWriteBroadcaster()
59
-
60
-	// Test 1: Both bufferA and bufferB should contain "foo"
61
-	bufferA := &dummyWriter{}
62
-	writer.AddWriter(bufferA)
63
-	bufferB := &dummyWriter{}
64
-	writer.AddWriter(bufferB)
65
-	writer.Write([]byte("foo"))
66
-
67
-	if bufferA.String() != "foo" {
68
-		t.Errorf("Buffer contains %v", bufferA.String())
69
-	}
70
-
71
-	if bufferB.String() != "foo" {
72
-		t.Errorf("Buffer contains %v", bufferB.String())
73
-	}
74
-
75
-	// Test2: bufferA and bufferB should contain "foobar",
76
-	// while bufferC should only contain "bar"
77
-	bufferC := &dummyWriter{}
78
-	writer.AddWriter(bufferC)
79
-	writer.Write([]byte("bar"))
80
-
81
-	if bufferA.String() != "foobar" {
82
-		t.Errorf("Buffer contains %v", bufferA.String())
83
-	}
84
-
85
-	if bufferB.String() != "foobar" {
86
-		t.Errorf("Buffer contains %v", bufferB.String())
87
-	}
88
-
89
-	if bufferC.String() != "bar" {
90
-		t.Errorf("Buffer contains %v", bufferC.String())
91
-	}
92
-
93
-	// Test3: Test removal
94
-	writer.RemoveWriter(bufferB)
95
-	writer.Write([]byte("42"))
96
-	if bufferA.String() != "foobar42" {
97
-		t.Errorf("Buffer contains %v", bufferA.String())
98
-	}
99
-	if bufferB.String() != "foobar" {
100
-		t.Errorf("Buffer contains %v", bufferB.String())
101
-	}
102
-	if bufferC.String() != "bar42" {
103
-		t.Errorf("Buffer contains %v", bufferC.String())
104
-	}
105
-
106
-	// Test4: Test eviction on failure
107
-	bufferA.failOnWrite = true
108
-	writer.Write([]byte("fail"))
109
-	if bufferA.String() != "foobar42" {
110
-		t.Errorf("Buffer contains %v", bufferA.String())
111
-	}
112
-	if bufferC.String() != "bar42fail" {
113
-		t.Errorf("Buffer contains %v", bufferC.String())
114
-	}
115
-	// Even though we reset the flag, no more writes should go in there
116
-	bufferA.failOnWrite = false
117
-	writer.Write([]byte("test"))
118
-	if bufferA.String() != "foobar42" {
119
-		t.Errorf("Buffer contains %v", bufferA.String())
120
-	}
121
-	if bufferC.String() != "bar42failtest" {
122
-		t.Errorf("Buffer contains %v", bufferC.String())
123
-	}
124
-
125
-	writer.CloseWriters()
126
-}
127
-
128
-type devNullCloser int
129
-
130
-func (d devNullCloser) Close() error {
131
-	return nil
132
-}
133
-
134
-func (d devNullCloser) Write(buf []byte) (int, error) {
135
-	return len(buf), nil
136
-}
137
-
138
-// This test checks for races. It is only useful when run with the race detector.
139
-func TestRaceWriteBroadcaster(t *testing.T) {
140
-	writer := newWriteBroadcaster()
141
-	c := make(chan bool)
142
-	go func() {
143
-		writer.AddWriter(devNullCloser(0))
144
-		c <- true
145
-	}()
146
-	writer.Write([]byte("hello"))
147
-	<-c
148
-}
149
-
150
-// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix.
151
-func TestTruncIndex(t *testing.T) {
152
-	index := NewTruncIndex()
153
-	// Get on an empty index
154
-	if _, err := index.Get("foobar"); err == nil {
155
-		t.Fatal("Get on an empty index should return an error")
156
-	}
157
-
158
-	// Spaces should be illegal in an id
159
-	if err := index.Add("I have a space"); err == nil {
160
-		t.Fatalf("Adding an id with ' ' should return an error")
161
-	}
162
-
163
-	id := "99b36c2c326ccc11e726eee6ee78a0baf166ef96"
164
-	// Add an id
165
-	if err := index.Add(id); err != nil {
166
-		t.Fatal(err)
167
-	}
168
-	// Get a non-existing id
169
-	assertIndexGet(t, index, "abracadabra", "", true)
170
-	// Get the exact id
171
-	assertIndexGet(t, index, id, id, false)
172
-	// The first letter should match
173
-	assertIndexGet(t, index, id[:1], id, false)
174
-	// The first half should match
175
-	assertIndexGet(t, index, id[:len(id)/2], id, false)
176
-	// The second half should NOT match
177
-	assertIndexGet(t, index, id[len(id)/2:], "", true)
178
-
179
-	id2 := id[:6] + "blabla"
180
-	// Add an id
181
-	if err := index.Add(id2); err != nil {
182
-		t.Fatal(err)
183
-	}
184
-	// Both exact IDs should work
185
-	assertIndexGet(t, index, id, id, false)
186
-	assertIndexGet(t, index, id2, id2, false)
187
-
188
-	// 6 characters or less should conflict
189
-	assertIndexGet(t, index, id[:6], "", true)
190
-	assertIndexGet(t, index, id[:4], "", true)
191
-	assertIndexGet(t, index, id[:1], "", true)
192
-
193
-	// 7 characters should NOT conflict
194
-	assertIndexGet(t, index, id[:7], id, false)
195
-	assertIndexGet(t, index, id2[:7], id2, false)
196
-
197
-	// Deleting a non-existing id should return an error
198
-	if err := index.Delete("non-existing"); err == nil {
199
-		t.Fatalf("Deleting a non-existing id should return an error")
200
-	}
201
-
202
-	// Deleting id2 should remove conflicts
203
-	if err := index.Delete(id2); err != nil {
204
-		t.Fatal(err)
205
-	}
206
-	// id2 should no longer work
207
-	assertIndexGet(t, index, id2, "", true)
208
-	assertIndexGet(t, index, id2[:7], "", true)
209
-	assertIndexGet(t, index, id2[:11], "", true)
210
-
211
-	// conflicts between id and id2 should be gone
212
-	assertIndexGet(t, index, id[:6], id, false)
213
-	assertIndexGet(t, index, id[:4], id, false)
214
-	assertIndexGet(t, index, id[:1], id, false)
215
-
216
-	// non-conflicting substrings should still not conflict
217
-	assertIndexGet(t, index, id[:7], id, false)
218
-	assertIndexGet(t, index, id[:15], id, false)
219
-	assertIndexGet(t, index, id, id, false)
220
-}
221
-
222
-func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult string, expectError bool) {
223
-	if result, err := index.Get(input); err != nil && !expectError {
224
-		t.Fatalf("Unexpected error getting '%s': %s", input, err)
225
-	} else if err == nil && expectError {
226
-		t.Fatalf("Getting '%s' should return an error", input)
227
-	} else if result != expectedResult {
228
-		t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
229
-	}
230
-}
231
-
232
-func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) {
233
-	if r := CompareKernelVersion(a, b); r != result {
234
-		t.Fatalf("Unepected kernel version comparaison result. Found %d, expected %d", r, result)
235
-	}
236
-}
237
-
238
-func TestCompareKernelVersion(t *testing.T) {
239
-	assertKernelVersion(t,
240
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
241
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
242
-		0)
243
-	assertKernelVersion(t,
244
-		&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
245
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
246
-		-1)
247
-	assertKernelVersion(t,
248
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
249
-		&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
250
-		1)
251
-	assertKernelVersion(t,
252
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
253
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "16"},
254
-		0)
255
-	assertKernelVersion(t,
256
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5},
257
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
258
-		1)
259
-	assertKernelVersion(t,
260
-		&KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Flavor: "25"},
261
-		&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
262
-		-1)
263
-}