Browse code

bump master

Victor Vieux authored on 2013/06/03 21:37:51
Showing 48 changed files
... ...
@@ -15,6 +15,7 @@ Brian McCallister <brianm@skife.org>
15 15
 Bruno Bigras <bigras.bruno@gmail.com>
16 16
 Caleb Spare <cespare@gmail.com>
17 17
 Charles Hooper <charles.hooper@dotcloud.com>
18
+Daniel Gasienica <daniel@gasienica.ch>
18 19
 Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com>
19 20
 Daniel Robinson <gottagetmac@gmail.com>
20 21
 Daniel Von Fange <daniel@leancoder.com>
... ...
@@ -1,5 +1,14 @@
1 1
 # Changelog
2 2
 
3
+## 0.3.4 (2013-05-30)
4
+ + Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
5
+ + Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
6
+ + Runtime: interactive TTYs correctly handle window resize
7
+ * Runtime: fix how configuration is merged between layers
8
+ + Remote API: split stdout and stderr on 'docker run'
9
+ + Remote API: optionally listen on a different IP and port (use at your own risk)
10
+ * Documentation: improved install instructions.
11
+
3 12
 ## 0.3.3 (2013-05-23)
4 13
  - Registry: Fix push regression
5 14
  - Various bugfixes
... ...
@@ -1,4 +1,5 @@
1 1
 Solomon Hykes <solomon@dotcloud.com>
2 2
 Guillaume Charmes <guillaume@dotcloud.com>
3
+Victor Vieux <victor@dotcloud.com>
3 4
 api.go: Victor Vieux <victor@dotcloud.com>
4 5
 Vagrantfile: Daniel Mizyrycki <daniel@dotcloud.com>
... ...
@@ -47,6 +47,8 @@ func httpError(w http.ResponseWriter, err error) {
47 47
 		http.Error(w, err.Error(), http.StatusBadRequest)
48 48
 	} else if strings.HasPrefix(err.Error(), "Conflict") {
49 49
 		http.Error(w, err.Error(), http.StatusConflict)
50
+	} else if strings.HasPrefix(err.Error(), "Impossible") {
51
+		http.Error(w, err.Error(), http.StatusNotAcceptable)
50 52
 	} else {
51 53
 		http.Error(w, err.Error(), http.StatusInternalServerError)
52 54
 	}
... ...
@@ -69,7 +71,16 @@ func getBoolParam(value string) (bool, error) {
69 69
 }
70 70
 
71 71
 func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
72
-	b, err := json.Marshal(srv.registry.GetAuthConfig(false))
72
+	// FIXME: Handle multiple login at once
73
+	// FIXME: return specific error code if config file missing?
74
+	authConfig, err := auth.LoadConfig(srv.runtime.root)
75
+	if err != nil {
76
+		if err != auth.ErrConfigFileMissing {
77
+			return err
78
+		}
79
+		authConfig = &auth.AuthConfig{}
80
+	}
81
+	b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email})
73 82
 	if err != nil {
74 83
 		return err
75 84
 	}
... ...
@@ -78,11 +89,19 @@ func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
78 78
 }
79 79
 
80 80
 func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
81
+	// FIXME: Handle multiple login at once
81 82
 	config := &auth.AuthConfig{}
82 83
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
83 84
 		return err
84 85
 	}
85
-	authConfig := srv.registry.GetAuthConfig(true)
86
+
87
+	authConfig, err := auth.LoadConfig(srv.runtime.root)
88
+	if err != nil {
89
+		if err != auth.ErrConfigFileMissing {
90
+			return err
91
+		}
92
+		authConfig = &auth.AuthConfig{}
93
+	}
86 94
 	if config.Username == authConfig.Username {
87 95
 		config.Password = authConfig.Password
88 96
 	}
... ...
@@ -92,7 +111,6 @@ func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reque
92 92
 	if err != nil {
93 93
 		return err
94 94
 	}
95
-	srv.registry.ResetClient(newAuthConfig)
96 95
 
97 96
 	if status != "" {
98 97
 		b, err := json.Marshal(&ApiAuth{Status: status})
... ...
@@ -298,16 +316,25 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
298 298
 	tag := r.Form.Get("tag")
299 299
 	repo := r.Form.Get("repo")
300 300
 
301
+	if version > 1.0 {
302
+		w.Header().Set("Content-Type", "application/json")
303
+	}
304
+	sf := utils.NewStreamFormatter(version > 1.0)
301 305
 	if image != "" { //pull
302 306
 		registry := r.Form.Get("registry")
303
-		if version > 1.0 {
304
-			w.Header().Set("Content-Type", "application/json")
305
-		}
306
-		if err := srv.ImagePull(image, tag, registry, w, version > 1.0); err != nil {
307
+		if err := srv.ImagePull(image, tag, registry, w, sf); err != nil {
308
+			if sf.Used() {
309
+				w.Write(sf.FormatError(err))
310
+				return nil
311
+			}
307 312
 			return err
308 313
 		}
309 314
 	} else { //import
310
-		if err := srv.ImageImport(src, repo, tag, r.Body, w); err != nil {
315
+		if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil {
316
+			if sf.Used() {
317
+				w.Write(sf.FormatError(err))
318
+				return nil
319
+			}
311 320
 			return err
312 321
 		}
313 322
 	}
... ...
@@ -343,10 +370,16 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
343 343
 		return fmt.Errorf("Missing parameter")
344 344
 	}
345 345
 	name := vars["name"]
346
-
347
-	imgId, err := srv.ImageInsert(name, url, path, w)
346
+	if version > 1.0 {
347
+		w.Header().Set("Content-Type", "application/json")
348
+	}
349
+	sf := utils.NewStreamFormatter(version > 1.0)
350
+	imgId, err := srv.ImageInsert(name, url, path, w, sf)
348 351
 	if err != nil {
349
-		return err
352
+		if sf.Used() {
353
+			w.Write(sf.FormatError(err))
354
+			return nil
355
+		}
350 356
 	}
351 357
 	b, err := json.Marshal(&ApiId{Id: imgId})
352 358
 	if err != nil {
... ...
@@ -366,8 +399,15 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
366 366
 		return fmt.Errorf("Missing parameter")
367 367
 	}
368 368
 	name := vars["name"]
369
-
370
-	if err := srv.ImagePush(name, registry, w); err != nil {
369
+	if version > 1.0 {
370
+		w.Header().Set("Content-Type", "application/json")
371
+	}
372
+	sf := utils.NewStreamFormatter(version > 1.0)
373
+	if err := srv.ImagePush(name, registry, w, sf); err != nil {
374
+		if sf.Used() {
375
+			w.Write(sf.FormatError(err))
376
+			return nil
377
+		}
371 378
 		return err
372 379
 	}
373 380
 	return nil
... ...
@@ -652,6 +692,13 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
652 652
 	if err := r.ParseMultipartForm(4096); err != nil {
653 653
 		return err
654 654
 	}
655
+	remote := r.FormValue("t")
656
+	tag := ""
657
+	if strings.Contains(remote, ":") {
658
+		remoteParts := strings.Split(remote, ":")
659
+		tag = remoteParts[1]
660
+		remote = remoteParts[0]
661
+	}
655 662
 
656 663
 	dockerfile, _, err := r.FormFile("Dockerfile")
657 664
 	if err != nil {
... ...
@@ -666,8 +713,10 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
666 666
 	}
667 667
 
668 668
 	b := NewBuildFile(srv, utils.NewWriteFlusher(w))
669
-	if _, err := b.Build(dockerfile, context); err != nil {
669
+	if id, err := b.Build(dockerfile, context); err != nil {
670 670
 		fmt.Fprintf(w, "Error build: %s\n", err)
671
+	} else if remote != "" {
672
+		srv.runtime.repositories.Set(remote, tag, id, false)
671 673
 	}
672 674
 	return nil
673 675
 }
... ...
@@ -26,8 +26,7 @@ func TestGetAuth(t *testing.T) {
26 26
 	defer nuke(runtime)
27 27
 
28 28
 	srv := &Server{
29
-		runtime:  runtime,
30
-		registry: registry.NewRegistry(runtime.root),
29
+		runtime: runtime,
31 30
 	}
32 31
 
33 32
 	r := httptest.NewRecorder()
... ...
@@ -56,7 +55,7 @@ func TestGetAuth(t *testing.T) {
56 56
 		t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code)
57 57
 	}
58 58
 
59
-	newAuthConfig := srv.registry.GetAuthConfig(false)
59
+	newAuthConfig := registry.NewRegistry(runtime.root).GetAuthConfig(false)
60 60
 	if newAuthConfig.Username != authConfig.Username ||
61 61
 		newAuthConfig.Email != authConfig.Email {
62 62
 		t.Fatalf("The auth configuration hasn't been set correctly")
... ...
@@ -247,8 +246,7 @@ func TestGetImagesSearch(t *testing.T) {
247 247
 	defer nuke(runtime)
248 248
 
249 249
 	srv := &Server{
250
-		runtime:  runtime,
251
-		registry: registry.NewRegistry(runtime.root),
250
+		runtime: runtime,
252 251
 	}
253 252
 
254 253
 	r := httptest.NewRecorder()
... ...
@@ -504,15 +502,16 @@ func TestPostAuth(t *testing.T) {
504 504
 	defer nuke(runtime)
505 505
 
506 506
 	srv := &Server{
507
-		runtime:  runtime,
508
-		registry: registry.NewRegistry(runtime.root),
507
+		runtime: runtime,
509 508
 	}
510 509
 
511
-	authConfigOrig := &auth.AuthConfig{
510
+	config := &auth.AuthConfig{
512 511
 		Username: "utest",
513 512
 		Email:    "utest@yopmail.com",
514 513
 	}
515
-	srv.registry.ResetClient(authConfigOrig)
514
+
515
+	authStr := auth.EncodeAuth(config)
516
+	auth.SaveConfig(runtime.root, authStr, config.Email)
516 517
 
517 518
 	r := httptest.NewRecorder()
518 519
 	if err := getAuth(srv, API_VERSION, r, nil, nil); err != nil {
... ...
@@ -524,7 +523,7 @@ func TestPostAuth(t *testing.T) {
524 524
 		t.Fatal(err)
525 525
 	}
526 526
 
527
-	if authConfig.Username != authConfigOrig.Username || authConfig.Email != authConfigOrig.Email {
527
+	if authConfig.Username != config.Username || authConfig.Email != config.Email {
528 528
 		t.Errorf("The retrieve auth mismatch with the one set.")
529 529
 	}
530 530
 }
... ...
@@ -54,6 +54,9 @@ func Tar(path string, compression Compression) (io.Reader, error) {
54 54
 func Untar(archive io.Reader, path string) error {
55 55
 	cmd := exec.Command("bsdtar", "-f", "-", "-C", path, "-x")
56 56
 	cmd.Stdin = archive
57
+	// Hardcode locale environment for predictable outcome regardless of host configuration.
58
+	//   (see https://github.com/dotcloud/docker/issues/355)
59
+	cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"}
57 60
 	output, err := cmd.CombinedOutput()
58 61
 	if err != nil {
59 62
 		return fmt.Errorf("%s: %s", err, output)
... ...
@@ -3,6 +3,7 @@ package auth
3 3
 import (
4 4
 	"encoding/base64"
5 5
 	"encoding/json"
6
+	"errors"
6 7
 	"fmt"
7 8
 	"io/ioutil"
8 9
 	"net/http"
... ...
@@ -17,6 +18,12 @@ const CONFIGFILE = ".dockercfg"
17 17
 // the registry server we want to login against
18 18
 const INDEX_SERVER = "https://index.docker.io/v1"
19 19
 
20
+//const INDEX_SERVER = "http://indexstaging-docker.dotcloud.com/"
21
+
22
+var (
23
+	ErrConfigFileMissing error = errors.New("The Auth config file is missing")
24
+)
25
+
20 26
 type AuthConfig struct {
21 27
 	Username string `json:"username"`
22 28
 	Password string `json:"password"`
... ...
@@ -75,7 +82,7 @@ func DecodeAuth(authStr string) (*AuthConfig, error) {
75 75
 func LoadConfig(rootPath string) (*AuthConfig, error) {
76 76
 	confFile := path.Join(rootPath, CONFIGFILE)
77 77
 	if _, err := os.Stat(confFile); err != nil {
78
-		return &AuthConfig{}, fmt.Errorf("The Auth config file is missing")
78
+		return nil, ErrConfigFileMissing
79 79
 	}
80 80
 	b, err := ioutil.ReadFile(confFile)
81 81
 	if err != nil {
... ...
@@ -97,7 +104,7 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
97 97
 }
98 98
 
99 99
 // save the auth config
100
-func saveConfig(rootPath, authStr string, email string) error {
100
+func SaveConfig(rootPath, authStr string, email string) error {
101 101
 	confFile := path.Join(rootPath, CONFIGFILE)
102 102
 	if len(email) == 0 {
103 103
 		os.Remove(confFile)
... ...
@@ -161,7 +168,9 @@ func Login(authConfig *AuthConfig) (string, error) {
161 161
 				status = "Login Succeeded\n"
162 162
 				storeConfig = true
163 163
 			} else if resp.StatusCode == 401 {
164
-				saveConfig(authConfig.rootPath, "", "")
164
+				if err := SaveConfig(authConfig.rootPath, "", ""); err != nil {
165
+					return "", err
166
+				}
165 167
 				return "", fmt.Errorf("Wrong login/password, please try again")
166 168
 			} else {
167 169
 				return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
... ...
@@ -175,7 +184,9 @@ func Login(authConfig *AuthConfig) (string, error) {
175 175
 	}
176 176
 	if storeConfig {
177 177
 		authStr := EncodeAuth(authConfig)
178
-		saveConfig(authConfig.rootPath, authStr, authConfig.Email)
178
+		if err := SaveConfig(authConfig.rootPath, authStr, authConfig.Email); err != nil {
179
+			return "", err
180
+		}
179 181
 	}
180 182
 	return status, nil
181 183
 }
... ...
@@ -32,8 +32,6 @@ type buildFile struct {
32 32
 	tmpContainers map[string]struct{}
33 33
 	tmpImages     map[string]struct{}
34 34
 
35
-	needCommit bool
36
-
37 35
 	out io.Writer
38 36
 }
39 37
 
... ...
@@ -63,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
63 63
 				remote = name
64 64
 			}
65 65
 
66
-			if err := b.srv.ImagePull(remote, tag, "", b.out, false); err != nil {
66
+			if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false)); err != nil {
67 67
 				return err
68 68
 			}
69 69
 
... ...
@@ -81,9 +79,8 @@ func (b *buildFile) CmdFrom(name string) error {
81 81
 }
82 82
 
83 83
 func (b *buildFile) CmdMaintainer(name string) error {
84
-	b.needCommit = true
85 84
 	b.maintainer = name
86
-	return nil
85
+	return b.commit("", b.config.Cmd, fmt.Sprintf("MAINTAINER %s", name))
87 86
 }
88 87
 
89 88
 func (b *buildFile) CmdRun(args string) error {
... ...
@@ -95,28 +92,34 @@ func (b *buildFile) CmdRun(args string) error {
95 95
 		return err
96 96
 	}
97 97
 
98
-	cmd, env := b.config.Cmd, b.config.Env
98
+	cmd := b.config.Cmd
99 99
 	b.config.Cmd = nil
100 100
 	MergeConfig(b.config, config)
101 101
 
102
-	if cache, err := b.srv.ImageGetCached(b.image, config); err != nil {
102
+	utils.Debugf("Command to be executed: %v", b.config.Cmd)
103
+
104
+	if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
103 105
 		return err
104 106
 	} else if cache != nil {
105
-		utils.Debugf("Use cached version")
107
+		utils.Debugf("[BUILDER] Use cached version")
106 108
 		b.image = cache.Id
107 109
 		return nil
110
+	} else {
111
+		utils.Debugf("[BUILDER] Cache miss")
108 112
 	}
109 113
 
110 114
 	cid, err := b.run()
111 115
 	if err != nil {
112 116
 		return err
113 117
 	}
114
-	b.config.Cmd, b.config.Env = cmd, env
115
-	return b.commit(cid)
118
+	if err := b.commit(cid, cmd, "run"); err != nil {
119
+		return err
120
+	}
121
+	b.config.Cmd = cmd
122
+	return nil
116 123
 }
117 124
 
118 125
 func (b *buildFile) CmdEnv(args string) error {
119
-	b.needCommit = true
120 126
 	tmp := strings.SplitN(args, " ", 2)
121 127
 	if len(tmp) != 2 {
122 128
 		return fmt.Errorf("Invalid ENV format")
... ...
@@ -131,60 +134,34 @@ func (b *buildFile) CmdEnv(args string) error {
131 131
 		}
132 132
 	}
133 133
 	b.config.Env = append(b.config.Env, key+"="+value)
134
-	return nil
134
+	return b.commit("", b.config.Cmd, fmt.Sprintf("ENV %s=%s", key, value))
135 135
 }
136 136
 
137 137
 func (b *buildFile) CmdCmd(args string) error {
138
-	b.needCommit = true
139 138
 	var cmd []string
140 139
 	if err := json.Unmarshal([]byte(args), &cmd); err != nil {
141 140
 		utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
142
-		b.config.Cmd = []string{"/bin/sh", "-c", args}
143
-	} else {
144
-		b.config.Cmd = cmd
141
+		cmd = []string{"/bin/sh", "-c", args}
142
+	}
143
+	if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil {
144
+		return err
145 145
 	}
146
+	b.config.Cmd = cmd
146 147
 	return nil
147 148
 }
148 149
 
149 150
 func (b *buildFile) CmdExpose(args string) error {
150 151
 	ports := strings.Split(args, " ")
151 152
 	b.config.PortSpecs = append(ports, b.config.PortSpecs...)
152
-	return nil
153
+	return b.commit("", b.config.Cmd, fmt.Sprintf("EXPOSE %v", ports))
153 154
 }
154 155
 
155 156
 func (b *buildFile) CmdInsert(args string) error {
156
-	if b.image == "" {
157
-		return fmt.Errorf("Please provide a source image with `from` prior to insert")
158
-	}
159
-	tmp := strings.SplitN(args, " ", 2)
160
-	if len(tmp) != 2 {
161
-		return fmt.Errorf("Invalid INSERT format")
162
-	}
163
-	sourceUrl := strings.Trim(tmp[0], " ")
164
-	destPath := strings.Trim(tmp[1], " ")
165
-
166
-	file, err := utils.Download(sourceUrl, b.out)
167
-	if err != nil {
168
-		return err
169
-	}
170
-	defer file.Body.Close()
171
-
172
-	b.config.Cmd = []string{"echo", "INSERT", sourceUrl, "in", destPath}
173
-	cid, err := b.run()
174
-	if err != nil {
175
-		return err
176
-	}
177
-
178
-	container := b.runtime.Get(cid)
179
-	if container == nil {
180
-		return fmt.Errorf("An error occured while creating the container")
181
-	}
182
-
183
-	if err := container.Inject(file.Body, destPath); err != nil {
184
-		return err
185
-	}
157
+	return fmt.Errorf("INSERT has been deprecated. Please use ADD instead")
158
+}
186 159
 
187
-	return b.commit(cid)
160
+func (b *buildFile) CmdCopy(args string) error {
161
+	return fmt.Errorf("COPY has been deprecated. Please use ADD instead")
188 162
 }
189 163
 
190 164
 func (b *buildFile) CmdAdd(args string) error {
... ...
@@ -193,12 +170,13 @@ func (b *buildFile) CmdAdd(args string) error {
193 193
 	}
194 194
 	tmp := strings.SplitN(args, " ", 2)
195 195
 	if len(tmp) != 2 {
196
-		return fmt.Errorf("Invalid INSERT format")
196
+		return fmt.Errorf("Invalid ADD format")
197 197
 	}
198 198
 	orig := strings.Trim(tmp[0], " ")
199 199
 	dest := strings.Trim(tmp[1], " ")
200 200
 
201
-	b.config.Cmd = []string{"echo", "PUSH", orig, "in", dest}
201
+	cmd := b.config.Cmd
202
+	b.config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) ADD %s in %s", orig, dest)}
202 203
 	cid, err := b.run()
203 204
 	if err != nil {
204 205
 		return err
... ...
@@ -208,19 +186,23 @@ func (b *buildFile) CmdAdd(args string) error {
208 208
 	if container == nil {
209 209
 		return fmt.Errorf("Error while creating the container (CmdAdd)")
210 210
 	}
211
-
212
-	if err := os.MkdirAll(path.Join(container.rwPath(), dest), 0700); err != nil {
211
+	if err := container.EnsureMounted(); err != nil {
213 212
 		return err
214 213
 	}
214
+	defer container.Unmount()
215 215
 
216 216
 	origPath := path.Join(b.context, orig)
217
-	destPath := path.Join(container.rwPath(), dest)
217
+	destPath := path.Join(container.RootfsPath(), dest)
218 218
 
219 219
 	fi, err := os.Stat(origPath)
220 220
 	if err != nil {
221 221
 		return err
222 222
 	}
223 223
 	if fi.IsDir() {
224
+		if err := os.MkdirAll(destPath, 0700); err != nil {
225
+			return err
226
+		}
227
+
224 228
 		files, err := ioutil.ReadDir(path.Join(b.context, orig))
225 229
 		if err != nil {
226 230
 			return err
... ...
@@ -231,12 +213,18 @@ func (b *buildFile) CmdAdd(args string) error {
231 231
 			}
232 232
 		}
233 233
 	} else {
234
+		if err := os.MkdirAll(path.Dir(destPath), 0700); err != nil {
235
+			return err
236
+		}
234 237
 		if err := utils.CopyDirectory(origPath, destPath); err != nil {
235 238
 			return err
236 239
 		}
237 240
 	}
238
-
239
-	return b.commit(cid)
241
+	if err := b.commit(cid, cmd, fmt.Sprintf("ADD %s in %s", orig, dest)); err != nil {
242
+		return err
243
+	}
244
+	b.config.Cmd = cmd
245
+	return nil
240 246
 }
241 247
 
242 248
 func (b *buildFile) run() (string, error) {
... ...
@@ -265,20 +253,30 @@ func (b *buildFile) run() (string, error) {
265 265
 	return c.Id, nil
266 266
 }
267 267
 
268
-func (b *buildFile) commit(id string) error {
268
+// Commit the container <id> with the autorun command <autoCmd>
269
+func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
269 270
 	if b.image == "" {
270 271
 		return fmt.Errorf("Please provide a source image with `from` prior to commit")
271 272
 	}
272 273
 	b.config.Image = b.image
273 274
 	if id == "" {
274
-		cmd := b.config.Cmd
275
-		b.config.Cmd = []string{"true"}
275
+		b.config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment}
276
+
277
+		if cache, err := b.srv.ImageGetCached(b.image, b.config); err != nil {
278
+			return err
279
+		} else if cache != nil {
280
+			utils.Debugf("[BUILDER] Use cached version")
281
+			b.image = cache.Id
282
+			return nil
283
+		} else {
284
+			utils.Debugf("[BUILDER] Cache miss")
285
+		}
286
+
276 287
 		if cid, err := b.run(); err != nil {
277 288
 			return err
278 289
 		} else {
279 290
 			id = cid
280 291
 		}
281
-		b.config.Cmd = cmd
282 292
 	}
283 293
 
284 294
 	container := b.runtime.Get(id)
... ...
@@ -286,20 +284,20 @@ func (b *buildFile) commit(id string) error {
286 286
 		return fmt.Errorf("An error occured while creating the container")
287 287
 	}
288 288
 
289
+	// Note: Actually copy the struct
290
+	autoConfig := *b.config
291
+	autoConfig.Cmd = autoCmd
289 292
 	// Commit the container
290
-	image, err := b.builder.Commit(container, "", "", "", b.maintainer, nil)
293
+	image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig)
291 294
 	if err != nil {
292 295
 		return err
293 296
 	}
294 297
 	b.tmpImages[image.Id] = struct{}{}
295 298
 	b.image = image.Id
296
-	b.needCommit = false
297 299
 	return nil
298 300
 }
299 301
 
300 302
 func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
301
-	defer b.clearTmp(b.tmpContainers, b.tmpImages)
302
-
303 303
 	if context != nil {
304 304
 		name, err := ioutil.TempDir("/tmp", "docker-build")
305 305
 		if err != nil {
... ...
@@ -337,6 +335,7 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
337 337
 		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
338 338
 		if !exists {
339 339
 			fmt.Fprintf(b.out, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
340
+			continue
340 341
 		}
341 342
 		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
342 343
 		if ret != nil {
... ...
@@ -345,22 +344,10 @@ func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
345 345
 
346 346
 		fmt.Fprintf(b.out, "===> %v\n", b.image)
347 347
 	}
348
-	if b.needCommit {
349
-		if err := b.commit(""); err != nil {
350
-			return "", err
351
-		}
352
-	}
353 348
 	if b.image != "" {
354
-		// The build is successful, keep the temporary containers and images
355
-		for i := range b.tmpImages {
356
-			delete(b.tmpImages, i)
357
-		}
358
-		fmt.Fprintf(b.out, "Build success.\n Image id:\n%s\n", b.image)
349
+		fmt.Fprintf(b.out, "Build successful.\n===> %s\n", b.image)
359 350
 		return b.image, nil
360 351
 	}
361
-	for i := range b.tmpContainers {
362
-		delete(b.tmpContainers, i)
363
-	}
364 352
 	return "", fmt.Errorf("An error occured during the build\n")
365 353
 }
366 354
 
... ...
@@ -17,6 +17,7 @@ import (
17 17
 	"net/url"
18 18
 	"os"
19 19
 	"os/signal"
20
+	"path"
20 21
 	"path/filepath"
21 22
 	"reflect"
22 23
 	"strconv"
... ...
@@ -27,7 +28,7 @@ import (
27 27
 	"unicode"
28 28
 )
29 29
 
30
-const VERSION = "0.3.3"
30
+const VERSION = "0.3.4"
31 31
 
32 32
 var (
33 33
 	GIT_COMMIT string
... ...
@@ -73,37 +74,37 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
73 73
 		}
74 74
 	}
75 75
 	help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n  -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
76
-	for cmd, description := range map[string]string{
77
-		"attach":  "Attach to a running container",
78
-		"build":   "Build a container from a Dockerfile",
79
-		"commit":  "Create a new image from a container's changes",
80
-		"diff":    "Inspect changes on a container's filesystem",
81
-		"export":  "Stream the contents of a container as a tar archive",
82
-		"history": "Show the history of an image",
83
-		"images":  "List images",
84
-		"import":  "Create a new filesystem image from the contents of a tarball",
85
-		"info":    "Display system-wide information",
86
-		"insert":  "Insert a file in an image",
87
-		"inspect": "Return low-level information on a container",
88
-		"kill":    "Kill a running container",
89
-		"login":   "Register or Login to the docker registry server",
90
-		"logs":    "Fetch the logs of a container",
91
-		"port":    "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT",
92
-		"ps":      "List containers",
93
-		"pull":    "Pull an image or a repository from the docker registry server",
94
-		"push":    "Push an image or a repository to the docker registry server",
95
-		"restart": "Restart a running container",
96
-		"rm":      "Remove a container",
97
-		"rmi":     "Remove an image",
98
-		"run":     "Run a command in a new container",
99
-		"search":  "Search for an image in the docker index",
100
-		"start":   "Start a stopped container",
101
-		"stop":    "Stop a running container",
102
-		"tag":     "Tag an image into a repository",
103
-		"version": "Show the docker version information",
104
-		"wait":    "Block until a container stops, then print its exit code",
76
+	for _, command := range [][2]string{
77
+		{"attach", "Attach to a running container"},
78
+		{"build", "Build a container from a Dockerfile"},
79
+		{"commit", "Create a new image from a container's changes"},
80
+		{"diff", "Inspect changes on a container's filesystem"},
81
+		{"export", "Stream the contents of a container as a tar archive"},
82
+		{"history", "Show the history of an image"},
83
+		{"images", "List images"},
84
+		{"import", "Create a new filesystem image from the contents of a tarball"},
85
+		{"info", "Display system-wide information"},
86
+		{"insert", "Insert a file in an image"},
87
+		{"inspect", "Return low-level information on a container"},
88
+		{"kill", "Kill a running container"},
89
+		{"login", "Register or Login to the docker registry server"},
90
+		{"logs", "Fetch the logs of a container"},
91
+		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
92
+		{"ps", "List containers"},
93
+		{"pull", "Pull an image or a repository from the docker registry server"},
94
+		{"push", "Push an image or a repository to the docker registry server"},
95
+		{"restart", "Restart a running container"},
96
+		{"rm", "Remove a container"},
97
+		{"rmi", "Remove an image"},
98
+		{"run", "Run a command in a new container"},
99
+		{"search", "Search for an image in the docker index"},
100
+		{"start", "Start a stopped container"},
101
+		{"stop", "Stop a running container"},
102
+		{"tag", "Tag an image into a repository"},
103
+		{"version", "Show the docker version information"},
104
+		{"wait", "Block until a container stops, then print its exit code"},
105 105
 	} {
106
-		help += fmt.Sprintf("    %-10.10s%s\n", cmd, description)
106
+		help += fmt.Sprintf("    %-10.10s%s\n", command[0], command[1])
107 107
 	}
108 108
 	fmt.Println(help)
109 109
 	return nil
... ...
@@ -130,16 +131,20 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
130 130
 }
131 131
 
132 132
 func (cli *DockerCli) CmdBuild(args ...string) error {
133
-	cmd := Subcmd("build", "[OPTIONS] [CONTEXT]", "Build an image from a Dockerfile")
134
-	fileName := cmd.String("f", "Dockerfile", "Use `file` as Dockerfile. Can be '-' for stdin")
133
+	cmd := Subcmd("build", "[OPTIONS] PATH | -", "Build a new container image from the source code at PATH")
134
+	tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
135 135
 	if err := cmd.Parse(args); err != nil {
136 136
 		return nil
137 137
 	}
138
+	if cmd.NArg() != 1 {
139
+		cmd.Usage()
140
+		return nil
141
+	}
138 142
 
139 143
 	var (
140
-		file          io.ReadCloser
141 144
 		multipartBody io.Reader
142
-		err           error
145
+		file          io.ReadCloser
146
+		contextPath   string
143 147
 	)
144 148
 
145 149
 	// Init the needed component for the Multipart
... ...
@@ -148,27 +153,19 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
148 148
 	w := multipart.NewWriter(buff)
149 149
 	boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
150 150
 
151
-	// Create a FormFile multipart for the Dockerfile
152
-	if *fileName == "-" {
151
+	compression := Bzip2
152
+
153
+	if cmd.Arg(0) == "-" {
153 154
 		file = os.Stdin
154 155
 	} else {
155
-		file, err = os.Open(*fileName)
156
-		if err != nil {
156
+		// Send Dockerfile from arg/Dockerfile (deprecate later)
157
+		if f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile")); err != nil {
157 158
 			return err
159
+		} else {
160
+			file = f
158 161
 		}
159
-		defer file.Close()
160
-	}
161
-	if wField, err := w.CreateFormFile("Dockerfile", *fileName); err != nil {
162
-		return err
163
-	} else {
164
-		io.Copy(wField, file)
165
-	}
166
-	multipartBody = io.MultiReader(multipartBody, boundary)
167
-
168
-	compression := Bzip2
169
-
170
-	// Create a FormFile multipart for the context if needed
171
-	if cmd.Arg(0) != "" {
162
+		// Send context from arg
163
+		// Create a FormFile multipart for the context if needed
172 164
 		// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
173 165
 		context, err := Tar(cmd.Arg(0), compression)
174 166
 		if err != nil {
... ...
@@ -183,19 +180,28 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
183 183
 			return err
184 184
 		} else {
185 185
 			// FIXME: Find a way to have a progressbar for the upload too
186
-			io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, "Caching Context %v/%v (%v)\r", false))
186
+			sf := utils.NewStreamFormatter(false)
187
+			io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf))
187 188
 		}
188
-
189 189
 		multipartBody = io.MultiReader(multipartBody, boundary)
190 190
 	}
191
+	// Create a FormFile multipart for the Dockerfile
192
+	if wField, err := w.CreateFormFile("Dockerfile", "Dockerfile"); err != nil {
193
+		return err
194
+	} else {
195
+		io.Copy(wField, file)
196
+	}
197
+	multipartBody = io.MultiReader(multipartBody, boundary)
191 198
 
199
+	v := &url.Values{}
200
+	v.Set("t", *tag)
192 201
 	// Send the multipart request with correct content-type
193
-	req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s", cli.host, cli.port, "/build"), multipartBody)
202
+	req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody)
194 203
 	if err != nil {
195 204
 		return err
196 205
 	}
197 206
 	req.Header.Set("Content-Type", w.FormDataContentType())
198
-	if cmd.Arg(0) != "" {
207
+	if contextPath != "" {
199 208
 		req.Header.Set("X-Docker-Context-Compression", compression.Flag())
200 209
 		fmt.Println("Uploading Context...")
201 210
 	}
... ...
@@ -366,12 +372,10 @@ func (cli *DockerCli) CmdWait(args ...string) error {
366 366
 // 'docker version': show version information
367 367
 func (cli *DockerCli) CmdVersion(args ...string) error {
368 368
 	cmd := Subcmd("version", "", "Show the docker version information.")
369
-	fmt.Println(len(args))
370 369
 	if err := cmd.Parse(args); err != nil {
371 370
 		return nil
372 371
 	}
373 372
 
374
-	fmt.Println(cmd.NArg())
375 373
 	if cmd.NArg() > 0 {
376 374
 		cmd.Usage()
377 375
 		return nil
... ...
@@ -882,9 +886,9 @@ func (cli *DockerCli) CmdPs(args ...string) error {
882 882
 	for _, out := range outs {
883 883
 		if !*quiet {
884 884
 			if *noTrunc {
885
-				fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", out.Id, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
885
+				fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", out.Id, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
886 886
 			} else {
887
-				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), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
887
+				fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\n", utils.TruncateId(out.Id), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports)
888 888
 			}
889 889
 		} else {
890 890
 			if *noTrunc {
... ...
@@ -996,12 +1000,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
996 996
 		return nil
997 997
 	}
998 998
 
999
-	v := url.Values{}
1000
-	v.Set("logs", "1")
1001
-	v.Set("stdout", "1")
1002
-	v.Set("stderr", "1")
1003
-
1004
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), false); err != nil {
999
+	if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil {
1000
+		return err
1001
+	}
1002
+	if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil {
1005 1003
 		return err
1006 1004
 	}
1007 1005
 	return nil
... ...
@@ -1028,15 +1030,35 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
1028 1028
 		return err
1029 1029
 	}
1030 1030
 
1031
+	splitStderr := container.Config.Tty
1032
+
1033
+	connections := 1
1034
+	if splitStderr {
1035
+		connections += 1
1036
+	}
1037
+	chErrors := make(chan error, connections)
1038
+	cli.monitorTtySize(cmd.Arg(0))
1039
+	if splitStderr {
1040
+		go func() {
1041
+			chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?stream=1&stderr=1", false, nil, os.Stderr)
1042
+		}()
1043
+	}
1031 1044
 	v := url.Values{}
1032 1045
 	v.Set("stream", "1")
1033
-	v.Set("stdout", "1")
1034
-	v.Set("stderr", "1")
1035 1046
 	v.Set("stdin", "1")
1036
-
1037
-	cli.monitorTtySize(cmd.Arg(0))
1038
-	if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty); err != nil {
1039
-		return err
1047
+	v.Set("stdout", "1")
1048
+	if !splitStderr {
1049
+		v.Set("stderr", "1")
1050
+	}
1051
+	go func() {
1052
+		chErrors <- cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), container.Config.Tty, os.Stdin, os.Stdout)
1053
+	}()
1054
+	for connections > 0 {
1055
+		err := <-chErrors
1056
+		if err != nil {
1057
+			return err
1058
+		}
1059
+		connections -= 1
1040 1060
 	}
1041 1061
 	return nil
1042 1062
 }
... ...
@@ -1200,19 +1222,14 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1200 1200
 		fmt.Fprintln(os.Stderr, "WARNING: ", warning)
1201 1201
 	}
1202 1202
 
1203
-	v := url.Values{}
1204
-	v.Set("logs", "1")
1205
-	v.Set("stream", "1")
1203
+	splitStderr := !config.Tty
1206 1204
 
1207
-	if config.AttachStdin {
1208
-		v.Set("stdin", "1")
1205
+	connections := 0
1206
+	if config.AttachStdin || config.AttachStdout || (!splitStderr && config.AttachStderr) {
1207
+		connections += 1
1209 1208
 	}
1210
-	if config.AttachStdout {
1211
-		v.Set("stdout", "1")
1212
-	}
1213
-	if config.AttachStderr {
1214
-		v.Set("stderr", "1")
1215
-
1209
+	if splitStderr && config.AttachStderr {
1210
+		connections += 1
1216 1211
 	}
1217 1212
 
1218 1213
 	//start the container
... ...
@@ -1221,10 +1238,38 @@ func (cli *DockerCli) CmdRun(args ...string) error {
1221 1221
 		return err
1222 1222
 	}
1223 1223
 
1224
-	if config.AttachStdin || config.AttachStdout || config.AttachStderr {
1224
+	if connections > 0 {
1225
+		chErrors := make(chan error, connections)
1225 1226
 		cli.monitorTtySize(out.Id)
1226
-		if err := cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty); err != nil {
1227
-			return err
1227
+
1228
+		if splitStderr && config.AttachStderr {
1229
+			go func() {
1230
+				chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?logs=1&stream=1&stderr=1", config.Tty, nil, os.Stderr)
1231
+			}()
1232
+		}
1233
+
1234
+		v := url.Values{}
1235
+		v.Set("logs", "1")
1236
+		v.Set("stream", "1")
1237
+
1238
+		if config.AttachStdin {
1239
+			v.Set("stdin", "1")
1240
+		}
1241
+		if config.AttachStdout {
1242
+			v.Set("stdout", "1")
1243
+		}
1244
+		if !splitStderr && config.AttachStderr {
1245
+			v.Set("stderr", "1")
1246
+		}
1247
+		go func() {
1248
+			chErrors <- cli.hijack("POST", "/containers/"+out.Id+"/attach?"+v.Encode(), config.Tty, os.Stdin, os.Stdout)
1249
+		}()
1250
+		for connections > 0 {
1251
+			err := <-chErrors
1252
+			if err != nil {
1253
+				return err
1254
+			}
1255
+			connections -= 1
1228 1256
 		}
1229 1257
 	}
1230 1258
 	if !config.AttachStdout && !config.AttachStderr {
... ...
@@ -1334,13 +1379,9 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1334 1334
 	}
1335 1335
 
1336 1336
 	if resp.Header.Get("Content-Type") == "application/json" {
1337
-		type Message struct {
1338
-			Status   string `json:"status,omitempty"`
1339
-			Progress string `json:"progress,omitempty"`
1340
-		}
1341 1337
 		dec := json.NewDecoder(resp.Body)
1342 1338
 		for {
1343
-			var m Message
1339
+			var m utils.JsonMessage
1344 1340
 			if err := dec.Decode(&m); err == io.EOF {
1345 1341
 				break
1346 1342
 			} else if err != nil {
... ...
@@ -1348,6 +1389,8 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1348 1348
 			}
1349 1349
 			if m.Progress != "" {
1350 1350
 				fmt.Fprintf(out, "Downloading %s\r", m.Progress)
1351
+			} else if m.Error != "" {
1352
+				return fmt.Errorf(m.Error)
1351 1353
 			} else {
1352 1354
 				fmt.Fprintf(out, "%s\n", m.Status)
1353 1355
 			}
... ...
@@ -1360,7 +1403,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
1360 1360
 	return nil
1361 1361
 }
1362 1362
 
1363
-func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
1363
+func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
1364 1364
 	req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", API_VERSION, path), nil)
1365 1365
 	if err != nil {
1366 1366
 		return err
... ...
@@ -1378,20 +1421,19 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool) error {
1378 1378
 	defer rwc.Close()
1379 1379
 
1380 1380
 	receiveStdout := utils.Go(func() error {
1381
-		_, err := io.Copy(os.Stdout, br)
1381
+		_, err := io.Copy(out, br)
1382 1382
 		return err
1383 1383
 	})
1384 1384
 
1385
-	if setRawTerminal && term.IsTerminal(int(os.Stdin.Fd())) && os.Getenv("NORAW") == "" {
1385
+	if in != nil && setRawTerminal && term.IsTerminal(int(in.Fd())) && os.Getenv("NORAW") == "" {
1386 1386
 		if oldState, err := term.SetRawTerminal(); err != nil {
1387 1387
 			return err
1388 1388
 		} else {
1389 1389
 			defer term.RestoreTerminal(oldState)
1390 1390
 		}
1391 1391
 	}
1392
-
1393 1392
 	sendStdin := utils.Go(func() error {
1394
-		_, err := io.Copy(rwc, os.Stdin)
1393
+		_, err := io.Copy(rwc, in)
1395 1394
 		if err := rwc.(*net.TCPConn).CloseWrite(); err != nil {
1396 1395
 			fmt.Fprintf(os.Stderr, "Couldn't send EOF: %s\n", err)
1397 1396
 		}
... ...
@@ -2,18 +2,15 @@
2 2
 set -e
3 3
 
4 4
 # these should match the names found at http://www.debian.org/releases/
5
-stableSuite='squeeze'
6
-testingSuite='wheezy'
5
+stableSuite='wheezy'
6
+testingSuite='jessie'
7 7
 unstableSuite='sid'
8 8
 
9
-# if suite is equal to this, it gets the "latest" tag
10
-latestSuite="$testingSuite"
11
-
12 9
 variant='minbase'
13 10
 include='iproute,iputils-ping'
14 11
 
15 12
 repo="$1"
16
-suite="${2:-$latestSuite}"
13
+suite="${2:-$stableSuite}"
17 14
 mirror="${3:-}" # stick to the default debootstrap mirror if one is not provided
18 15
 
19 16
 if [ ! "$repo" ]; then
... ...
@@ -41,17 +38,14 @@ img=$(sudo tar -c . | docker import -)
41 41
 # tag suite
42 42
 docker tag $img $repo $suite
43 43
 
44
-if [ "$suite" = "$latestSuite" ]; then
45
-	# tag latest
46
-	docker tag $img $repo latest
47
-fi
48
-
49 44
 # test the image
50 45
 docker run -i -t $repo:$suite echo success
51 46
 
52
-# unstable's version numbers match testing (since it's mostly just a sandbox for testing), so it doesn't get a version number tag
53
-if [ "$suite" != "$unstableSuite" -a "$suite" != 'unstable' ]; then
54
-	# tag the specific version
47
+if [ "$suite" = "$stableSuite" -o "$suite" = 'stable' ]; then
48
+	# tag latest
49
+	docker tag $img $repo latest
50
+	
51
+	# tag the specific debian release version
55 52
 	ver=$(docker run $repo:$suite cat /etc/debian_version)
56 53
 	docker tag $img $repo $ver
57 54
 fi
... ...
@@ -6,6 +6,7 @@ SPHINXOPTS    =
6 6
 SPHINXBUILD   = sphinx-build
7 7
 PAPER         =
8 8
 BUILDDIR      = _build
9
+PYTHON        = python
9 10
 
10 11
 # Internal variables.
11 12
 PAPEROPT_a4     = -D latex_paper_size=a4
... ...
@@ -38,6 +39,7 @@ help:
38 38
 #	@echo "  linkcheck  to check all external links for integrity"
39 39
 #	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
40 40
 	@echo "  docs       to build the docs and copy the static files to the outputdir"
41
+	@echo "  server     to serve the docs in your browser under \`http://localhost:8000\`"
41 42
 	@echo "  publish    to publish the app to dotcloud"
42 43
 
43 44
 clean:
... ...
@@ -49,6 +51,8 @@ docs:
49 49
 	@echo
50 50
 	@echo "Build finished. The documentation pages are now in $(BUILDDIR)/html."
51 51
 
52
+server:
53
+	@cd $(BUILDDIR)/html; $(PYTHON) -m SimpleHTTPServer 8000
52 54
 
53 55
 site:
54 56
 	cp -r website $(BUILDDIR)/
... ...
@@ -14,20 +14,22 @@ Installation
14 14
 ------------
15 15
 
16 16
 * Work in your own fork of the code, we accept pull requests.
17
-* Install sphinx: ``pip install sphinx``
18
-* Install sphinx httpdomain contrib package ``sphinxcontrib-httpdomain``
17
+* Install sphinx: `pip install sphinx`
18
+    * Mac OS X: `[sudo] pip-2.7 install sphinx`)
19
+* Install sphinx httpdomain contrib package: `pip install sphinxcontrib-httpdomain`
20
+    * Mac OS X: `[sudo] pip-2.7 install sphinxcontrib-httpdomain`
19 21
 * If pip is not available you can probably install it using your favorite package manager as **python-pip**
20 22
 
21 23
 Usage
22 24
 -----
23
-* change the .rst files with your favorite editor to your liking
24
-* run *make docs* to clean up old files and generate new ones
25
-* your static website can now be found in the _build dir
26
-* to preview what you have generated, cd into _build/html and then run 'python -m SimpleHTTPServer 8000'
25
+* Change the `.rst` files with your favorite editor to your liking.
26
+* Run `make docs` to clean up old files and generate new ones.
27
+* Your static website can now be found in the `_build` directory.
28
+* To preview what you have generated run `make server` and open <http://localhost:8000/> in your favorite browser.
27 29
 
28
-Working using github's file editor
30
+Working using GitHub's file editor
29 31
 ----------------------------------
30
-Alternatively, for small changes and typo's you might want to use github's built in file editor. It allows
32
+Alternatively, for small changes and typo's you might want to use GitHub's built in file editor. It allows
31 33
 you to preview your changes right online. Just be carefull not to create many commits.
32 34
 
33 35
 Images
... ...
@@ -72,4 +74,4 @@ Guides on using sphinx
72 72
 
73 73
 * Code examples
74 74
 
75
-  Start without $, so it's easy to copy and paste.
76 75
\ No newline at end of file
76
+  Start without $, so it's easy to copy and paste.
... ...
@@ -15,10 +15,17 @@ Docker Remote API
15 15
 - Default port in the docker deamon is 4243 
16 16
 - The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr
17 17
 
18
-2. Endpoints
18
+2. Version
19
+==========
20
+
21
+The current verson of the API is 1.1
22
+Calling /images/<name>/insert is the same as calling /v1.1/images/<name>/insert
23
+You can still call an old version of the api using /v1.0/images/<name>/insert
24
+
25
+3. Endpoints
19 26
 ============
20 27
 
21
-2.1 Containers
28
+3.1 Containers
22 29
 --------------
23 30
 
24 31
 List containers
... ...
@@ -132,6 +139,7 @@ Create a container
132 132
 	:jsonparam config: the container's configuration
133 133
 	:statuscode 201: no error
134 134
 	:statuscode 404: no such container
135
+	:statuscode 406: impossible to attach (container not running)
135 136
 	:statuscode 500: server error
136 137
 
137 138
 
... ...
@@ -459,7 +467,7 @@ Remove a container
459 459
         :statuscode 500: server error
460 460
 
461 461
 
462
-2.2 Images
462
+3.2 Images
463 463
 ----------
464 464
 
465 465
 List Images
... ...
@@ -548,7 +556,19 @@ Create an image
548 548
 
549 549
            POST /images/create?fromImage=base HTTP/1.1
550 550
 
551
-        **Example response**:
551
+        **Example response v1.1**:
552
+
553
+        .. sourcecode:: http
554
+
555
+           HTTP/1.1 200 OK
556
+	   Content-Type: application/json
557
+
558
+	   {"status":"Pulling..."}
559
+	   {"progress":"1/? (n/a)"}
560
+	   {"error":"Invalid..."}
561
+	   ...
562
+
563
+        **Example response v1.0**:
552 564
 
553 565
         .. sourcecode:: http
554 566
 
... ...
@@ -579,7 +599,19 @@ Insert a file in a image
579 579
 
580 580
            POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
581 581
 
582
-	**Example response**:
582
+	**Example response v1.1**:
583
+
584
+        .. sourcecode:: http
585
+
586
+           HTTP/1.1 200 OK
587
+	   Content-Type: application/json
588
+
589
+	   {"status":"Inserting..."}
590
+	   {"progress":"1/? (n/a)"}
591
+	   {"error":"Invalid..."}
592
+	   ...
593
+
594
+	**Example response v1.0**:
583 595
 
584 596
         .. sourcecode:: http
585 597
 
... ...
@@ -694,7 +726,19 @@ Push an image on the registry
694 694
 
695 695
 	    POST /images/test/push HTTP/1.1
696 696
 
697
-	 **Example response**:
697
+	 **Example response v1.1**:
698
+
699
+        .. sourcecode:: http
700
+
701
+           HTTP/1.1 200 OK
702
+	   Content-Type: application/json
703
+
704
+	   {"status":"Pushing..."}
705
+	   {"progress":"1/? (n/a)"}
706
+	   {"error":"Invalid..."}
707
+	   ...
708
+
709
+	 **Example response v1.0**:
698 710
 
699 711
         .. sourcecode:: http
700 712
 
... ...
@@ -817,7 +861,7 @@ Search images
817 817
 	   :statuscode 500: server error
818 818
 
819 819
 
820
-2.3 Misc
820
+3.3 Misc
821 821
 --------
822 822
 
823 823
 Build an image from Dockerfile via stdin
... ...
@@ -843,6 +887,7 @@ Build an image from Dockerfile via stdin
843 843
 	   
844 844
 	   {{ STREAM }}
845 845
 
846
+	:query t: tag to be applied to the resulting image in case of success
846 847
 	:statuscode 200: no error
847 848
         :statuscode 500: server error
848 849
 
... ...
@@ -2,8 +2,8 @@
2 2
 :description: docker documentation
3 3
 :keywords: docker, ipa, documentation
4 4
 
5
-API's
6
-=============
5
+APIs
6
+====
7 7
 
8 8
 This following :
9 9
 
... ...
@@ -246,7 +246,6 @@ The Index has two main purposes (along with its fancy social features):
246 246
 
247 247
 - Resolve short names (to avoid passing absolute URLs all the time)
248 248
    - username/projectname -> \https://registry.docker.io/users/<username>/repositories/<projectname>/
249
-   - team/projectname -> \https://registry.docker.io/team/<team>/repositories/<projectname>/
250 249
 - Authenticate a user as a repos owner (for a central referenced repository)
251 250
 
252 251
 3.1 Without an Index
... ...
@@ -2,12 +2,27 @@
2 2
 :description: Build a new image from the Dockerfile passed via stdin
3 3
 :keywords: build, docker, container, documentation
4 4
 
5
-========================================================
6
-``build`` -- Build a container from Dockerfile via stdin
7
-========================================================
5
+================================================
6
+``build`` -- Build a container from a Dockerfile
7
+================================================
8 8
 
9 9
 ::
10 10
 
11
-    Usage: docker build -
12
-    Example: cat Dockerfile | docker build -
13
-    Build a new image from the Dockerfile passed via stdin
11
+    Usage: docker build [OPTIONS] PATH | -
12
+    Build a new container image from the source code at PATH
13
+      -t="": Tag to be applied to the resulting image in case of success.
14
+
15
+Examples
16
+--------
17
+
18
+.. code-block:: bash
19
+
20
+    docker build .
21
+
22
+This will take the local Dockerfile
23
+
24
+.. code-block:: bash
25
+
26
+    docker build -
27
+
28
+This will read a Dockerfile form Stdin without context
14 29
deleted file mode 100644
... ...
@@ -1,25 +0,0 @@
1
-:title: Building Blocks
2
-:description: An introduction to docker and standard containers?
3
-:keywords: containers, lxc, concepts, explanation
4
-
5
-
6
-Building blocks
7
-===============
8
-
9
-.. _images:
10
-
11
-Images
12
-An original container image. These are stored on disk and are comparable with what you normally expect from a stopped virtual machine image. Images are stored (and retrieved from) repository
13
-
14
-Images are stored on your local file system under /var/lib/docker/graph
15
-
16
-
17
-.. _containers:
18
-
19
-Containers
20
-A container is a local version of an image. It can be running or stopped, The equivalent would be a virtual machine instance.
21
-
22
-Containers are stored on your local file system under /var/lib/docker/containers
23
-
... ...
@@ -13,5 +13,4 @@ Contents:
13 13
    :maxdepth: 1
14 14
 
15 15
    ../index
16
-   buildingblocks
17 16
 
... ...
@@ -5,8 +5,8 @@
5 5
 Introduction
6 6
 ============
7 7
 
8
-Docker - The Linux container runtime
8
+Docker -- The Linux container runtime
9
+-------------------------------------
9 10
 
10 11
 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.
11 12
 
... ...
@@ -1,8 +1,8 @@
1
-:title: Setting up a dev environment
1
+:title: Setting Up a Dev Environment
2 2
 :description: Guides on how to contribute to docker
3 3
 :keywords: Docker, documentation, developers, contributing, dev environment
4 4
 
5
-Setting up a dev environment
5
+Setting Up a Dev Environment
6 6
 ============================
7 7
 
8 8
 Instructions that have been verified to work on Ubuntu 12.10,
... ...
@@ -4,8 +4,8 @@
4 4
 
5 5
 .. _running_couchdb_service:
6 6
 
7
-Create a CouchDB service
8
-========================
7
+CouchDB Service
8
+===============
9 9
 
10 10
 .. include:: example_header.inc
11 11
 
... ...
@@ -1,6 +1,6 @@
1 1
 :title: Docker Examples
2 2
 :description: Examples on how to use Docker
3
-:keywords: docker, hello world, examples
3
+:keywords: docker, hello world, node, nodejs, python, couch, couchdb, redis, ssh, sshd, examples
4 4
 
5 5
 
6 6
 
... ...
@@ -16,6 +16,7 @@ Contents:
16 16
    hello_world
17 17
    hello_world_daemon
18 18
    python_web_app
19
+   nodejs_web_app
19 20
    running_redis_service
20 21
    running_ssh_service
21 22
    couchdb_data_volumes
22 23
new file mode 100644
... ...
@@ -0,0 +1,236 @@
0
+:title: Running a Node.js app on CentOS
1
+:description: Installing and running a Node.js app on CentOS
2
+:keywords: docker, example, package installation, node, centos
3
+
4
+.. _nodejs_web_app:
5
+
6
+Node.js Web App
7
+===============
8
+
9
+.. include:: example_header.inc
10
+
11
+The goal of this example is to show you how you can build your own docker images
12
+from a parent image using a ``Dockerfile`` . We will do that by making a simple
13
+Node.js hello world web application running on CentOS. You can get the full
14
+source code at https://github.com/gasi/docker-node-hello.
15
+
16
+Create Node.js app
17
+
18
+First, create a ``package.json`` file that describes your app and its
19
+dependencies:
20
+
21
+.. code-block:: json
22
+
23
+    {
24
+      "name": "docker-centos-hello",
25
+      "private": true,
26
+      "version": "0.0.1",
27
+      "description": "Node.js Hello World app on CentOS using docker",
28
+      "author": "Daniel Gasienica <daniel@gasienica.ch>",
29
+      "dependencies": {
30
+        "express": "3.2.4"
31
+      }
32
+    }
33
+
34
+Then, create an ``index.js`` file that defines a web app using the
35
+`Express.js <http://expressjs.com/>`_ framework:
36
+
37
+.. code-block:: javascript
38
+
39
+    var express = require('express');
40
+
41
+    // Constants
42
+    var PORT = 8080;
43
+
44
+    // App
45
+    var app = express();
46
+    app.get('/', function (req, res) {
47
+      res.send('Hello World\n');
48
+    });
49
+
50
+    app.listen(PORT)
51
+    console.log('Running on http://localhost:' + PORT);
52
+
53
+
54
+In the next steps, we’ll look at how you can run this app inside a CentOS
55
+container using docker. First, you’ll need to build a docker image of your app.
56
+
57
+Creating a ``Dockerfile``
58
+
59
+Create an empty file called ``Dockerfile``:
60
+
61
+.. code-block:: bash
62
+
63
+    touch Dockerfile
64
+
65
+Open the ``Dockerfile`` in your favorite text editor and add the following line
66
+that defines the version of docker the image requires to build
67
+(this example uses docker 0.3.4):
68
+
69
+.. code-block:: bash
70
+
71
+    # DOCKER-VERSION 0.3.4
72
+
73
+Next, define the parent image you want to use to build your own image on top of.
74
+Here, we’ll use `CentOS <https://index.docker.io/_/centos/>`_ (tag: ``6.4``)
75
+available on the `docker index`_:
76
+
77
+.. code-block:: bash
78
+
79
+    FROM    centos:6.4
80
+
81
+Since we’re building a Node.js app, you’ll have to install Node.js as well as
82
+npm on your CentOS image. Node.js is required to run your app and npm to install
83
+your app’s dependencies defined in ``package.json``.
84
+To install the right package for CentOS, we’ll use the instructions from the
85
+`Node.js wiki`_:
86
+
87
+.. code-block:: bash
88
+
89
+    # Enable EPEL for Node.js
90
+    RUN     rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
91
+    # Install Node.js and npm
92
+    RUN     yum install -y npm-1.2.17-5.el6
93
+
94
+To bundle your app’s source code inside the docker image, use the ``ADD``
95
+command:
96
+
97
+.. code-block:: bash
98
+
99
+    # Bundle app source
100
+    ADD . /src
101
+
102
+Install your app dependencies using npm:
103
+
104
+.. code-block:: bash
105
+
106
+    # Install app dependencies
107
+    RUN cd /src; npm install
108
+
109
+Your app binds to port ``8080`` so you’ll use the ``EXPOSE`` command to have it
110
+mapped by the docker daemon:
111
+
112
+.. code-block:: bash
113
+
114
+    EXPOSE  8080
115
+
116
+Last but not least, define the command to run your app using ``CMD`` which
117
+defines your runtime, i.e. ``node``, and the path to our app, i.e.
118
+``src/index.js`` (see the step where we added the source to the container):
119
+
120
+.. code-block:: bash
121
+
122
+    CMD ["node", "/src/index.js"]
123
+
124
+Your ``Dockerfile`` should now look like this:
125
+
126
+.. code-block:: bash
127
+
128
+
129
+    # DOCKER-VERSION 0.3.4
130
+    FROM    centos:6.4
131
+
132
+    # Enable EPEL for Node.js
133
+    RUN     rpm -Uvh http://download.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
134
+    # Install Node.js and npm
135
+    RUN     yum install -y npm-1.2.17-5.el6
136
+
137
+    # Bundle app source
138
+    ADD . /src
139
+    # Install app dependencies
140
+    RUN cd /src; npm install
141
+
142
+    EXPOSE  8080
143
+    CMD ["node", "/src/index.js"]
144
+
145
+
146
+Building your image
147
+
148
+Go to the directory that has your ``Dockerfile`` and run the following command
149
+to build a docker image. The ``-t`` flag let’s you tag your image so it’s easier
150
+to find later using the ``docker images`` command:
151
+
152
+.. code-block:: bash
153
+
154
+    docker build -t <your username>/centos-node-hello .
155
+
156
+Your image will now be listed by docker:
157
+
158
+.. code-block:: bash
159
+
160
+    docker images
161
+
162
+    > # Example
163
+    > REPOSITORY                 TAG       ID              CREATED
164
+    > centos                     6.4       539c0211cd76    8 weeks ago
165
+    > gasi/centos-node-hello     latest    d64d3505b0d2    2 hours ago
166
+
167
+
168
+Run the image
169
+
170
+Running your image with ``-d`` runs the container in detached mode, leaving the
171
+container running in the background. Run the image you previously built:
172
+
173
+.. code-block:: bash
174
+
175
+    docker run -d <your username>/centos-node-hello
176
+
177
+Print the output of your app:
178
+
179
+.. code-block:: bash
180
+
181
+    # Get container ID
182
+    docker ps
183
+
184
+    # Print app output
185
+    docker logs <container id>
186
+
187
+    > # Example
188
+    > Running on http://localhost:8080
189
+
190
+
191
+Test
192
+
193
+To test your app, get the the port of your app that docker mapped:
194
+
195
+.. code-block:: bash
196
+
197
+    docker ps
198
+
199
+    > # Example
200
+    > ID            IMAGE                          COMMAND              ...   PORTS
201
+    > ecce33b30ebf  gasi/centos-node-hello:latest  node /src/index.js         49160->8080
202
+
203
+In the example above, docker mapped the ``8080`` port of the container to
204
+``49160``.
205
+
206
+Now you can call your app using ``curl`` (install if needed via:
207
+``sudo apt-get install curl``):
208
+
209
+.. code-block:: bash
210
+
211
+    curl -i localhost:49160
212
+
213
+    > HTTP/1.1 200 OK
214
+    > X-Powered-By: Express
215
+    > Content-Type: text/html; charset=utf-8
216
+    > Content-Length: 12
217
+    > Date: Sun, 02 Jun 2013 03:53:22 GMT
218
+    > Connection: keep-alive
219
+    >
220
+    > Hello World
221
+
222
+We hope this tutorial helped you get up and running with Node.js and CentOS on
223
+docker. You can get the full source code at
224
+https://github.com/gasi/docker-node-hello.
225
+
226
+Continue to :ref:`running_redis_service`.
227
+
228
+
229
+.. _Node.js wiki: https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager#rhelcentosscientific-linux-6
230
+.. _docker index: https://index.docker.io/
... ...
@@ -4,8 +4,8 @@
4 4
 
5 5
 .. _python_web_app:
6 6
 
7
-Building a python web app
8
-=========================
7
+Python Web App
8
+==============
9 9
 
10 10
 .. include:: example_header.inc
11 11
 
... ...
@@ -4,7 +4,7 @@
4 4
 
5 5
 .. _running_examples:
6 6
 
7
-Running The Examples
7
+Running the Examples
8 8
 --------------------
9 9
 
10 10
 All the examples assume your machine is running the docker daemon. To run the docker daemon in the background, simply type:
... ...
@@ -4,8 +4,8 @@
4 4
 
5 5
 .. _running_redis_service:
6 6
 
7
-Create a redis service
8
-======================
7
+Redis Service
8
+=============
9 9
 
10 10
 .. include:: example_header.inc
11 11
 
... ...
@@ -34,7 +34,7 @@ Snapshot the installation
34 34
 
35 35
 .. code-block:: bash
36 36
 
37
-    docker ps -a  # grab the container id (this will be the last one in the list)
37
+    docker ps -a  # grab the container id (this will be the first one in the list)
38 38
     docker commit <container_id> <your username>/redis
39 39
 
40 40
 Run the service
... ...
@@ -4,8 +4,8 @@
4 4
 
5 5
 .. _running_ssh_service:
6 6
 
7
-Create an ssh daemon service
8
-============================
7
+SSH Daemon Service
8
+==================
9 9
 
10 10
 .. include:: example_header.inc
11 11
 
... ...
@@ -20,8 +20,7 @@ minutes and not entirely smooth, but gives you a good idea.
20 20
     <div style="margin-top:10px;">
21 21
       <iframe width="800" height="400" src="http://ascii.io/a/2637/raw" frameborder="0"></iframe>
22 22
     </div>
23
-
24
-
23
+	
25 24
 You can also get this sshd container by using
26 25
 ::
27 26
 
... ...
@@ -30,3 +29,49 @@ You can also get this sshd container by using
30 30
 
31 31
 The password is 'screencast'
32 32
 
33
+**Video's Transcription:**
34
+
35
+.. code-block:: bash
36
+
37
+	 # Hello! We are going to try and install openssh on a container and run it as a servic 
38
+	 # let's pull base to get a base ubuntu image. 
39
+	 $ docker pull base
40
+	 # I had it so it was quick
41
+	 # now let's connect using -i for interactive and with -t for terminal 
42
+	 # we execute /bin/bash to get a prompt.
43
+	 $ docker run -i -t base /bin/bash
44
+	 # now let's commit it 
45
+	 # which container was it?
46
+	 $ docker ps -a |more
47
+	 $ docker commit a30a3a2f2b130749995f5902f079dc6ad31ea0621fac595128ec59c6da07feea dhrp/sshd 
48
+	 # I gave the name dhrp/sshd for the container
49
+	 # now we can run it again 
50
+	 $ docker run -d dhrp/sshd /usr/sbin/sshd -D # D for daemon mode 
51
+	 # is it running?
52
+	 $ docker ps
53
+	 # yes!
54
+	 # let's stop it 
55
+	 $ docker stop 0ebf7cec294755399d063f4b1627980d4cbff7d999f0bc82b59c300f8536a562
56
+	 $ docker ps
57
+	 # and reconnect, but now open a port to it
58
+	 $ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D 
59
+	 $ docker port b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 22
60
+	 # it has now given us a port to connect to
61
+	 # we have to connect using a public ip of our host
62
+	 $ hostname
63
+	 $ ifconfig
64
+	 $ ssh root@192.168.33.10 -p 49153
65
+	 # Ah! forgot to set root passwd
66
+	 $ docker commit b2b407cf22cf8e7fa3736fa8852713571074536b1d31def3fdfcd9fa4fd8c8c5 dhrp/sshd 
67
+	 $ docker ps -a
68
+	 $ docker run -i -t dhrp/sshd /bin/bash
69
+	 $ passwd
70
+	 $ exit
71
+	 $ docker commit 9e863f0ca0af31c8b951048ba87641d67c382d08d655c2e4879c51410e0fedc1 dhrp/sshd
72
+	 $ docker run -d -p 22 dhrp/sshd /usr/sbin/sshd -D
73
+	 $ docker port a0aaa9558c90cf5c7782648df904a82365ebacce523e4acc085ac1213bfe2206 22
74
+	 $ ifconfig
75
+	 $ ssh root@192.168.33.10 -p 49154
76
+	 # Thanks for watching, Thatcher thatcher@dotcloud.com
77
+
78
+
... ...
@@ -7,8 +7,8 @@
7 7
 Introduction
8 8
 ============
9 9
 
10
-Docker - The Linux container runtime
10
+Docker -- The Linux container runtime
11
+-------------------------------------
11 12
 
12 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.
13 14
 
... ...
@@ -67,3 +67,21 @@ To start on system boot:
67 67
 ::
68 68
 
69 69
     sudo systemctl enable docker
70
+    
71
+Network Configuration
72
+---------------------
73
+
74
+IPv4 packet forwarding is disabled by default on Arch, so internet access from inside
75
+the container may not work.
76
+
77
+To enable the forwarding, run as root on the host system:
78
+
79
+::
80
+
81
+    sysctl net.ipv4.ip_forward=1
82
+    
83
+And, to make it persistent across reboots, enable it on the host's **/etc/sysctl.conf**:
84
+
85
+::
86
+
87
+    net.ipv4.ip_forward=1
... ...
@@ -3,8 +3,8 @@
3 3
 :keywords: Examples, Usage, basic commands, docker, documentation, examples
4 4
 
5 5
 
6
-The basics
7
-=============
6
+The Basics
7
+==========
8 8
 
9 9
 Starting Docker
10 10
 ---------------
... ...
@@ -125,8 +125,14 @@ curl was installed within the image.
125 125
 .. note::
126 126
     The path must include the file name.
127 127
 
128
-.. note::
129
-    This instruction has temporarily disabled
128
+2.8 ADD
129
+-------
130
+
131
+    ``ADD <src> <dest>``
132
+
133
+The `ADD` instruction will insert the files from the `<src>` path of the context into `<dest>` path 
134
+of the container.
135
+The context must be set in order to use this instruction. (see examples)
130 136
 
131 137
 3. Dockerfile Examples
132 138
 ======================
... ...
@@ -4,8 +4,8 @@
4 4
 
5 5
 .. _working_with_the_repository:
6 6
 
7
-Working with the repository
8
-============================
7
+Working with the Repository
8
+===========================
9 9
 
10 10
 
11 11
 Top-level repositories and user repositories
... ...
@@ -14,9 +14,9 @@ Top-level repositories and user repositories
14 14
 Generally, there are two types of repositories: Top-level repositories which are controlled by the people behind
15 15
 Docker, and user repositories.
16 16
 
17
-* Top-level repositories can easily be recognized by not having a / (slash) in their name. These repositories can
17
+* Top-level repositories can easily be recognized by not having a ``/`` (slash) in their name. These repositories can
18 18
   generally be trusted.
19
-* User repositories always come in the form of <username>/<repo_name>. This is what your published images will look like.
19
+* User repositories always come in the form of ``<username>/<repo_name>``. This is what your published images will look like.
20 20
 * User images are not checked, it is therefore up to you whether or not you trust the creator of this image.
21 21
 
22 22
 
... ...
@@ -270,7 +270,7 @@
270 270
                     <li>Filesystem isolation: each process container runs in a completely separate root filesystem.</li>
271 271
                     <li>Resource isolation: system resources like cpu and memory can be allocated differently to each process container, using cgroups.</li>
272 272
                     <li>Network isolation: each process container runs in its own network namespace, with a virtual interface and IP address of its own.</li>
273
-                    <li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremeley fast, memory-cheap and disk-cheap.</li>
273
+                    <li>Copy-on-write: root filesystems are created using copy-on-write, which makes deployment extremely fast, memory-cheap and disk-cheap.</li>
274 274
                     <li>Logging: the standard streams (stdout/stderr/stdin) of each process container is collected and logged for real-time or batch retrieval.</li>
275 275
                     <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>
276 276
                     <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>
... ...
@@ -107,6 +107,7 @@ func (graph *Graph) Create(layerData Archive, container *Container, comment, aut
107 107
 		DockerVersion: VERSION,
108 108
 		Author:        author,
109 109
 		Config:        config,
110
+		Architecture:  "x86_64",
110 111
 	}
111 112
 	if container != nil {
112 113
 		img.Parent = container.Image
... ...
@@ -165,7 +166,8 @@ func (graph *Graph) TempLayerArchive(id string, compression Compression, output
165 165
 	if err != nil {
166 166
 		return nil, err
167 167
 	}
168
-	return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, "Buffering to disk %v/%v (%v)", false), tmp.Root)
168
+	sf := utils.NewStreamFormatter(false)
169
+	return NewTempArchive(utils.ProgressReader(ioutil.NopCloser(archive), 0, output, sf.FormatProgress("Buffering to disk", "%v/%v (%v)"), sf), tmp.Root)
169 170
 }
170 171
 
171 172
 // Mktemp creates a temporary sub-directory inside the graph's filesystem.
... ...
@@ -1,23 +1,31 @@
1 1
 # This will build a container capable of producing an official binary build of docker and
2 2
 # uploading it to S3
3
+from	ubuntu:12.04
3 4
 maintainer	Solomon Hykes <solomon@dotcloud.com>
4
-from	ubuntu:12.10
5
+# Workaround the upstart issue
6
+run dpkg-divert --local --rename --add /sbin/initctl
7
+run ln -s /bin/true /sbin/initctl
8
+# Enable universe and gophers PPA
9
+run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q python-software-properties
10
+run	add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
11
+run	add-apt-repository -y ppa:gophers/go/ubuntu
5 12
 run	apt-get update
13
+# Packages required to checkout, build and upload docker
6 14
 run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
7 15
 run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
8
-# Packages required to checkout and build docker
9 16
 run	curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.linux-amd64.tar.gz
10 17
 run	tar -C /usr/local -xzf /go.tar.gz
11
-run	echo "export PATH=$PATH:/usr/local/go/bin" > /.bashrc
12
-run	echo "export PATH=$PATH:/usr/local/go/bin" > /.bash_profile
18
+run	echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
19
+run	echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
13 20
 run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
14 21
 run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
15 22
 # Packages required to build an ubuntu package
23
+run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable
16 24
 run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
17 25
 run	DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
18
-copy	fake_initctl	/usr/local/bin/initctl
19 26
 run	apt-get install -y -q devscripts
20
-add	.	/src
27
+# Copy dockerbuilder files into the container
28
+add	.       /src
21 29
 run	cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
22 30
 run	cp /src/s3cfg /.s3cfg
23 31
 cmd	["dockerbuilder"]
... ...
@@ -2,7 +2,7 @@
2 2
 set -x
3 3
 set -e
4 4
 
5
-export PATH=$PATH:/usr/local/go/bin
5
+export PATH=/usr/local/go/bin:$PATH
6 6
 
7 7
 PACKAGE=github.com/dotcloud/docker
8 8
 
... ...
@@ -36,5 +36,6 @@ else
36 36
 fi
37 37
 
38 38
 if [ -z "$NO_UBUNTU" ]; then
39
+	export PATH=`echo $PATH | sed 's#/usr/local/go/bin:##g'`
39 40
 	(cd packaging/ubuntu && make ubuntu)
40 41
 fi
41 42
deleted file mode 100755
... ...
@@ -1,3 +0,0 @@
1
-#!/bin/sh
2
-
3
-echo Whatever you say, man
4 1
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+Ken Cochrane <ken@dotcloud.com>
1
+Jerome Petazzoni <jerome@dotcloud.com>
0 2
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+# Docker project infrastructure
1
+
2
+This directory holds all information about the technical infrastructure of the docker project; servers, dns, email, and all the corresponding tools and configuration.
3
+
4
+Obviously credentials should not be stored in this repo, but how to obtain and use them should be documented here.
... ...
@@ -27,6 +27,7 @@ type Image struct {
27 27
 	DockerVersion   string    `json:"docker_version,omitempty"`
28 28
 	Author          string    `json:"author,omitempty"`
29 29
 	Config          *Config   `json:"config,omitempty"`
30
+	Architecture    string    `json:"architecture,omitempty"`
30 31
 	graph           *Graph
31 32
 }
32 33
 
... ...
@@ -1,3 +1,14 @@
1
+lxc-docker (0.3.4-1) UNRELEASED; urgency=low
2
+  - Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
3
+  - Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
4
+  - Runtime: interactive TTYs correctly handle window resize
5
+  - Runtime: fix how configuration is merged between layers
6
+  - Remote API: split stdout and stderr on 'docker run'
7
+  - Remote API: optionally listen on a different IP and port (use at your own risk)
8
+  - Documentation: improved install instructions.
9
+
10
+ -- dotCloud <ops@dotcloud.com>  Thu, 30 May 2013 00:00:00 -0700
11
+
1 12
 lxc-docker (0.3.2-1) UNRELEASED; urgency=low
2 13
   - Runtime: Store the actual archive on commit
3 14
   - Registry: Improve the checksum process
... ...
@@ -1,3 +1,15 @@
1
+lxc-docker (0.3.4-1) precise; urgency=low
2
+  - Builder: 'docker build' builds a container, layer by layer, from a source repository containing a Dockerfile
3
+  - Builder: 'docker build -t FOO' applies the tag FOO to the newly built container.
4
+  - Runtime: interactive TTYs correctly handle window resize
5
+  - Runtime: fix how configuration is merged between layers
6
+  - Remote API: split stdout and stderr on 'docker run'
7
+  - Remote API: optionally listen on a different IP and port (use at your own risk)
8
+  - Documentation: improved install instructions.
9
+
10
+ -- dotCloud <ops@dotcloud.com>  Thu, 30 May 2013 00:00:00 -0700
11
+
12
+
1 13
 lxc-docker (0.3.3-1) precise; urgency=low
2 14
   - Registry: Fix push regression
3 15
   - Various bugfixes
... ...
@@ -5,6 +5,5 @@ stop on starting rc RUNLEVEL=[016]
5 5
 respawn
6 6
 
7 7
 script
8
-    # FIXME: docker should not depend on the system having en_US.UTF-8
9
-    LC_ALL='en_US.UTF-8' /usr/bin/docker -d
8
+    /usr/bin/docker -d
10 9
 end script
... ...
@@ -330,6 +330,9 @@ func (r *Registry) PushImageJsonIndex(remote string, imgList []*ImgData, validat
330 330
 	if validate {
331 331
 		suffix = "images"
332 332
 	}
333
+
334
+	utils.Debugf("Image list pushed to index:\n%s\n", imgListJson)
335
+
333 336
 	req, err := http.NewRequest("PUT", auth.IndexServerAddress()+"/repositories/"+remote+"/"+suffix, bytes.NewReader(imgListJson))
334 337
 	if err != nil {
335 338
 		return nil, err
... ...
@@ -2,13 +2,15 @@ package docker
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"github.com/dotcloud/docker/registry"
6 5
 	"github.com/dotcloud/docker/utils"
7 6
 	"io"
8 7
 	"io/ioutil"
8
+	"log"
9 9
 	"net"
10 10
 	"os"
11 11
 	"os/user"
12
+	"strconv"
13
+	"strings"
12 14
 	"sync"
13 15
 	"testing"
14 16
 	"time"
... ...
@@ -63,11 +65,10 @@ func init() {
63 63
 
64 64
 	// Create the "Server"
65 65
 	srv := &Server{
66
-		runtime:  runtime,
67
-		registry: registry.NewRegistry(runtime.root),
66
+		runtime: runtime,
68 67
 	}
69 68
 	// Retrieve the Image
70
-	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, false); err != nil {
69
+	if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false)); err != nil {
71 70
 		panic(err)
72 71
 	}
73 72
 }
... ...
@@ -279,24 +280,50 @@ func TestGet(t *testing.T) {
279 279
 
280 280
 }
281 281
 
282
-// Run a container with a TCP port allocated, and test that it can receive connections on localhost
283
-func TestAllocatePortLocalhost(t *testing.T) {
284
-	runtime, err := newTestRuntime()
285
-	if err != nil {
286
-		t.Fatal(err)
287
-	}
282
+func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
283
+	strPort := strconv.Itoa(port)
288 284
 	container, err := NewBuilder(runtime).Create(&Config{
289 285
 		Image:     GetTestImage(runtime).Id,
290
-		Cmd:       []string{"sh", "-c", "echo well hello there | nc -l -p 5555"},
291
-		PortSpecs: []string{"5555"},
286
+		Cmd:       []string{"sh", "-c", "echo well hello there | nc -l -p " + strPort},
287
+		PortSpecs: []string{strPort},
292 288
 	},
293 289
 	)
294 290
 	if err != nil {
295
-		t.Fatal(err)
291
+		return nil, err
296 292
 	}
297 293
 	if err := container.Start(); err != nil {
294
+		if strings.Contains(err.Error(), "address already in use") {
295
+			return nil, nil
296
+		}
297
+		return nil, err
298
+	}
299
+	return container, nil
300
+}
301
+
302
+// Run a container with a TCP port allocated, and test that it can receive connections on localhost
303
+func TestAllocatePortLocalhost(t *testing.T) {
304
+	runtime, err := newTestRuntime()
305
+	if err != nil {
298 306
 		t.Fatal(err)
299 307
 	}
308
+	port := 5554
309
+
310
+	var container *Container
311
+	for {
312
+		port += 1
313
+		log.Println("Trying port", port)
314
+		t.Log("Trying port", port)
315
+		container, err = findAvailalblePort(runtime, port)
316
+		if container != nil {
317
+			break
318
+		}
319
+		if err != nil {
320
+			t.Fatal(err)
321
+		}
322
+		log.Println("Port", port, "already in use")
323
+		t.Log("Port", port, "already in use")
324
+	}
325
+
300 326
 	defer container.Kill()
301 327
 
302 328
 	setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
... ...
@@ -310,7 +337,7 @@ func TestAllocatePortLocalhost(t *testing.T) {
310 310
 
311 311
 	conn, err := net.Dial("tcp",
312 312
 		fmt.Sprintf(
313
-			"localhost:%s", container.NetworkSettings.PortMapping["5555"],
313
+			"localhost:%s", container.NetworkSettings.PortMapping[strconv.Itoa(port)],
314 314
 		),
315 315
 	)
316 316
 	if err != nil {
... ...
@@ -50,7 +50,8 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
50 50
 }
51 51
 
52 52
 func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
53
-	results, err := srv.registry.SearchRepositories(term)
53
+
54
+	results, err := registry.NewRegistry(srv.runtime.root).SearchRepositories(term)
54 55
 	if err != nil {
55 56
 		return nil, err
56 57
 	}
... ...
@@ -68,7 +69,7 @@ func (srv *Server) ImagesSearch(term string) ([]ApiSearch, error) {
68 68
 	return outs, nil
69 69
 }
70 70
 
71
-func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, error) {
71
+func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) (string, error) {
72 72
 	out = utils.NewWriteFlusher(out)
73 73
 	img, err := srv.runtime.repositories.LookupImage(name)
74 74
 	if err != nil {
... ...
@@ -92,7 +93,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
92 92
 		return "", err
93 93
 	}
94 94
 
95
-	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, "Downloading %v/%v (%v)\r", false), path); err != nil {
95
+	if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), path); err != nil {
96 96
 		return "", err
97 97
 	}
98 98
 	// FIXME: Handle custom repo, tag comment, author
... ...
@@ -100,7 +101,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer) (string, e
100 100
 	if err != nil {
101 101
 		return "", err
102 102
 	}
103
-	fmt.Fprintf(out, "%s\n", img.Id)
103
+	out.Write(sf.FormatStatus(img.Id))
104 104
 	return img.ShortId(), nil
105 105
 }
106 106
 
... ...
@@ -292,8 +293,8 @@ func (srv *Server) ContainerTag(name, repo, tag string, force bool) error {
292 292
 	return nil
293 293
 }
294 294
 
295
-func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []string, json bool) error {
296
-	history, err := srv.registry.GetRemoteHistory(imgId, registry, token)
295
+func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgId, endpoint string, token []string, sf *utils.StreamFormatter) error {
296
+	history, err := r.GetRemoteHistory(imgId, endpoint, token)
297 297
 	if err != nil {
298 298
 		return err
299 299
 	}
... ...
@@ -302,8 +303,8 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri
302 302
 	// FIXME: Launch the getRemoteImage() in goroutines
303 303
 	for _, id := range history {
304 304
 		if !srv.runtime.graph.Exists(id) {
305
-			fmt.Fprintf(out, utils.FormatStatus("Pulling %s metadata", json), id)
306
-			imgJson, err := srv.registry.GetRemoteImageJson(id, registry, token)
305
+			out.Write(sf.FormatStatus("Pulling %s metadata", id))
306
+			imgJson, err := r.GetRemoteImageJson(id, endpoint, token)
307 307
 			if err != nil {
308 308
 				// FIXME: Keep goging in case of error?
309 309
 				return err
... ...
@@ -314,12 +315,12 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri
314 314
 			}
315 315
 
316 316
 			// Get the layer
317
-			fmt.Fprintf(out, utils.FormatStatus("Pulling %s fs layer", json), id)
318
-			layer, contentLength, err := srv.registry.GetRemoteImageLayer(img.Id, registry, token)
317
+			out.Write(sf.FormatStatus("Pulling %s fs layer", id))
318
+			layer, contentLength, err := r.GetRemoteImageLayer(img.Id, endpoint, token)
319 319
 			if err != nil {
320 320
 				return err
321 321
 			}
322
-			if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, utils.FormatProgress("%v/%v (%v)", json), json), false, img); err != nil {
322
+			if err := srv.runtime.graph.Register(utils.ProgressReader(layer, contentLength, out, sf.FormatProgress("Downloading", "%v/%v (%v)"), sf), false, img); err != nil {
323 323
 				return err
324 324
 			}
325 325
 		}
... ...
@@ -327,9 +328,9 @@ func (srv *Server) pullImage(out io.Writer, imgId, registry string, token []stri
327 327
 	return nil
328 328
 }
329 329
 
330
-func (srv *Server) pullRepository(out io.Writer, remote, askedTag string, json bool) error {
331
-	fmt.Fprintf(out, utils.FormatStatus("Pulling repository %s from %s", json), remote, auth.IndexServerAddress())
332
-	repoData, err := srv.registry.GetRepositoryData(remote)
330
+func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, remote, askedTag string, sf *utils.StreamFormatter) error {
331
+	out.Write(sf.FormatStatus("Pulling repository %s from %s", remote, auth.IndexServerAddress()))
332
+	repoData, err := r.GetRepositoryData(remote)
333 333
 	if err != nil {
334 334
 		return err
335 335
 	}
... ...
@@ -341,7 +342,7 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string, json b
341 341
 	}
342 342
 
343 343
 	utils.Debugf("Retrieving the tag list")
344
-	tagsList, err := srv.registry.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens)
344
+	tagsList, err := r.GetRemoteTags(repoData.Endpoints, remote, repoData.Tokens)
345 345
 	if err != nil {
346 346
 		return err
347 347
 	}
... ...
@@ -365,11 +366,11 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string, json b
365 365
 			utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
366 366
 			continue
367 367
 		}
368
-		fmt.Fprintf(out, utils.FormatStatus("Pulling image %s (%s) from %s", json), img.Id, img.Tag, remote)
368
+		out.Write(sf.FormatStatus("Pulling image %s (%s) from %s", img.Id, img.Tag, remote))
369 369
 		success := false
370 370
 		for _, ep := range repoData.Endpoints {
371
-			if err := srv.pullImage(out, img.Id, "https://"+ep+"/v1", repoData.Tokens, json); err != nil {
372
-				fmt.Fprintf(out, utils.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint\n", json), askedTag, err)
371
+			if err := srv.pullImage(r, out, img.Id, "https://"+ep+"/v1", repoData.Tokens, sf); err != nil {
372
+				out.Write(sf.FormatStatus("Error while retrieving image for tag: %s (%s); checking next endpoint", askedTag, err))
373 373
 				continue
374 374
 			}
375 375
 			success = true
... ...
@@ -394,16 +395,17 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string, json b
394 394
 	return nil
395 395
 }
396 396
 
397
-func (srv *Server) ImagePull(name, tag, registry string, out io.Writer, json bool) error {
397
+func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter) error {
398
+	r := registry.NewRegistry(srv.runtime.root)
398 399
 	out = utils.NewWriteFlusher(out)
399
-	if registry != "" {
400
-		if err := srv.pullImage(out, name, registry, nil, json); err != nil {
400
+	if endpoint != "" {
401
+		if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
401 402
 			return err
402 403
 		}
403 404
 		return nil
404 405
 	}
405 406
 
406
-	if err := srv.pullRepository(out, name, tag, json); err != nil {
407
+	if err := srv.pullRepository(r, out, name, tag, sf); err != nil {
407 408
 		return err
408 409
 	}
409 410
 
... ...
@@ -476,53 +478,52 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgDat
476 476
 	return imgList, nil
477 477
 }
478 478
 
479
-func (srv *Server) pushRepository(out io.Writer, name string, localRepo map[string]string) error {
479
+func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, name string, localRepo map[string]string, sf *utils.StreamFormatter) error {
480 480
 	out = utils.NewWriteFlusher(out)
481
-	fmt.Fprintf(out, "Processing checksums\n")
481
+	out.Write(sf.FormatStatus("Processing checksums"))
482 482
 	imgList, err := srv.getImageList(localRepo)
483 483
 	if err != nil {
484 484
 		return err
485 485
 	}
486
-	fmt.Fprintf(out, "Sending image list\n")
486
+	out.Write(sf.FormatStatus("Sending image list"))
487 487
 
488
-	repoData, err := srv.registry.PushImageJsonIndex(name, imgList, false)
488
+	repoData, err := r.PushImageJsonIndex(name, imgList, false)
489 489
 	if err != nil {
490 490
 		return err
491 491
 	}
492 492
 
493
-	// FIXME: Send only needed images
494 493
 	for _, ep := range repoData.Endpoints {
495
-		fmt.Fprintf(out, "Pushing repository %s to %s (%d tags)\r\n", name, ep, len(localRepo))
494
+		out.Write(sf.FormatStatus("Pushing repository %s to %s (%d tags)", name, ep, len(localRepo)))
496 495
 		// For each image within the repo, push them
497 496
 		for _, elem := range imgList {
498 497
 			if _, exists := repoData.ImgList[elem.Id]; exists {
499
-				fmt.Fprintf(out, "Image %s already on registry, skipping\n", name)
498
+				out.Write(sf.FormatStatus("Image %s already on registry, skipping", name))
500 499
 				continue
501 500
 			}
502
-			if err := srv.pushImage(out, name, elem.Id, ep, repoData.Tokens); err != nil {
501
+			if err := srv.pushImage(r, out, name, elem.Id, ep, repoData.Tokens, sf); err != nil {
503 502
 				// FIXME: Continue on error?
504 503
 				return err
505 504
 			}
506
-			fmt.Fprintf(out, "Pushing tags for rev [%s] on {%s}\n", elem.Id, ep+"/users/"+name+"/"+elem.Tag)
507
-			if err := srv.registry.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
505
+			out.Write(sf.FormatStatus("Pushing tags for rev [%s] on {%s}", elem.Id, ep+"/users/"+name+"/"+elem.Tag))
506
+			if err := r.PushRegistryTag(name, elem.Id, elem.Tag, ep, repoData.Tokens); err != nil {
508 507
 				return err
509 508
 			}
510 509
 		}
511 510
 	}
512 511
 
513
-	if _, err := srv.registry.PushImageJsonIndex(name, imgList, true); err != nil {
512
+	if _, err := r.PushImageJsonIndex(name, imgList, true); err != nil {
514 513
 		return err
515 514
 	}
516 515
 	return nil
517 516
 }
518 517
 
519
-func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []string) error {
518
+func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId, ep string, token []string, sf *utils.StreamFormatter) error {
520 519
 	out = utils.NewWriteFlusher(out)
521 520
 	jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, imgId, "json"))
522 521
 	if err != nil {
523 522
 		return fmt.Errorf("Error while retreiving the path for {%s}: %s", imgId, err)
524 523
 	}
525
-	fmt.Fprintf(out, "Pushing %s\r\n", imgId)
524
+	out.Write(sf.FormatStatus("Pushing %s", imgId))
526 525
 
527 526
 	// Make sure we have the image's checksum
528 527
 	checksum, err := srv.getChecksum(imgId)
... ...
@@ -535,9 +536,9 @@ func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []st
535 535
 	}
536 536
 
537 537
 	// Send the json
538
-	if err := srv.registry.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
538
+	if err := r.PushImageJsonRegistry(imgData, jsonRaw, ep, token); err != nil {
539 539
 		if err == registry.ErrAlreadyExists {
540
-			fmt.Fprintf(out, "Image %s already uploaded ; skipping\n", imgData.Id)
540
+			out.Write(sf.FormatStatus("Image %s already uploaded ; skipping", imgData.Id))
541 541
 			return nil
542 542
 		}
543 543
 		return err
... ...
@@ -570,20 +571,22 @@ func (srv *Server) pushImage(out io.Writer, remote, imgId, ep string, token []st
570 570
 	}
571 571
 
572 572
 	// Send the layer
573
-	if err := srv.registry.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, "", false), ep, token); err != nil {
573
+	if err := r.PushImageLayerRegistry(imgData.Id, utils.ProgressReader(layerData, int(layerData.Size), out, sf.FormatProgress("", "%v/%v (%v)"), sf), ep, token); err != nil {
574 574
 		return err
575 575
 	}
576 576
 	return nil
577 577
 }
578 578
 
579
-func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
579
+func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter) error {
580 580
 	out = utils.NewWriteFlusher(out)
581 581
 	img, err := srv.runtime.graph.Get(name)
582
+	r := registry.NewRegistry(srv.runtime.root)
583
+
582 584
 	if err != nil {
583
-		fmt.Fprintf(out, "The push refers to a repository [%s] (len: %d)\n", name, len(srv.runtime.repositories.Repositories[name]))
585
+		out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
584 586
 		// If it fails, try to get the repository
585 587
 		if localRepo, exists := srv.runtime.repositories.Repositories[name]; exists {
586
-			if err := srv.pushRepository(out, name, localRepo); err != nil {
588
+			if err := srv.pushRepository(r, out, name, localRepo, sf); err != nil {
587 589
 				return err
588 590
 			}
589 591
 			return nil
... ...
@@ -591,14 +594,14 @@ func (srv *Server) ImagePush(name, registry string, out io.Writer) error {
591 591
 
592 592
 		return err
593 593
 	}
594
-	fmt.Fprintf(out, "The push refers to an image: [%s]\n", name)
595
-	if err := srv.pushImage(out, name, img.Id, registry, nil); err != nil {
594
+	out.Write(sf.FormatStatus("The push refers to an image: [%s]", name))
595
+	if err := srv.pushImage(r, out, name, img.Id, endpoint, nil, sf); err != nil {
596 596
 		return err
597 597
 	}
598 598
 	return nil
599 599
 }
600 600
 
601
-func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer) error {
601
+func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer, sf *utils.StreamFormatter) error {
602 602
 	var archive io.Reader
603 603
 	var resp *http.Response
604 604
 
... ...
@@ -607,21 +610,21 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
607 607
 	} else {
608 608
 		u, err := url.Parse(src)
609 609
 		if err != nil {
610
-			fmt.Fprintf(out, "Error: %s\n", err)
610
+			return err
611 611
 		}
612 612
 		if u.Scheme == "" {
613 613
 			u.Scheme = "http"
614 614
 			u.Host = src
615 615
 			u.Path = ""
616 616
 		}
617
-		fmt.Fprintf(out, "Downloading from %s\n", u)
617
+		out.Write(sf.FormatStatus("Downloading from %s", u))
618 618
 		// Download with curl (pretty progress bar)
619 619
 		// If curl is not available, fallback to http.Get()
620 620
 		resp, err = utils.Download(u.String(), out)
621 621
 		if err != nil {
622 622
 			return err
623 623
 		}
624
-		archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, "Importing %v/%v (%v)\r", false)
624
+		archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf.FormatProgress("Importing", "%v/%v (%v)"), sf)
625 625
 	}
626 626
 	img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil)
627 627
 	if err != nil {
... ...
@@ -633,7 +636,7 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write
633 633
 			return err
634 634
 		}
635 635
 	}
636
-	fmt.Fprintf(out, "%s\n", img.ShortId())
636
+	out.Write(sf.FormatStatus(img.ShortId()))
637 637
 	return nil
638 638
 }
639 639
 
... ...
@@ -883,7 +886,6 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
883 883
 	if container == nil {
884 884
 		return fmt.Errorf("No such container: %s", name)
885 885
 	}
886
-
887 886
 	//logs
888 887
 	if logs {
889 888
 		if stdout {
... ...
@@ -909,6 +911,9 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
909 909
 		if container.State.Ghost {
910 910
 			return fmt.Errorf("Impossible to attach to a ghost container")
911 911
 		}
912
+		if !container.State.Running {
913
+			return fmt.Errorf("Impossible to attach to a stopped container, start it first")
914
+		}
912 915
 
913 916
 		var (
914 917
 			cStdin           io.ReadCloser
... ...
@@ -967,14 +972,12 @@ func NewServer(autoRestart bool) (*Server, error) {
967 967
 		return nil, err
968 968
 	}
969 969
 	srv := &Server{
970
-		runtime:  runtime,
971
-		registry: registry.NewRegistry(runtime.root),
970
+		runtime: runtime,
972 971
 	}
973 972
 	runtime.srv = srv
974 973
 	return srv, nil
975 974
 }
976 975
 
977 976
 type Server struct {
978
-	runtime  *Runtime
979
-	registry *registry.Registry
977
+	runtime *Runtime
980 978
 }
981 979
new file mode 100644
... ...
@@ -0,0 +1,2 @@
0
+Guillaume Charmes <guillaume@dotcloud.com>
1
+Solomon Hykes <solomon@dotcloud.com>
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"bytes"
5 5
 	"crypto/sha256"
6 6
 	"encoding/hex"
7
+	"encoding/json"
7 8
 	"errors"
8 9
 	"fmt"
9 10
 	"index/suffixarray"
... ...
@@ -69,7 +70,7 @@ type progressReader struct {
69 69
 	readProgress int           // How much has been read so far (bytes)
70 70
 	lastUpdate   int           // How many bytes read at least update
71 71
 	template     string        // Template to print. Default "%v/%v (%v)"
72
-	json         bool
72
+	sf *StreamFormatter
73 73
 }
74 74
 
75 75
 func (r *progressReader) Read(p []byte) (n int, err error) {
... ...
@@ -93,7 +94,7 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
93 93
 	}
94 94
 	// Send newline when complete
95 95
 	if err != nil {
96
-		fmt.Fprintf(r.output, FormatStatus("", r.json))
96
+		r.output.Write(r.sf.FormatStatus(""))
97 97
 	}
98 98
 
99 99
 	return read, err
... ...
@@ -101,11 +102,12 @@ func (r *progressReader) Read(p []byte) (n int, err error) {
101 101
 func (r *progressReader) Close() error {
102 102
 	return io.ReadCloser(r.reader).Close()
103 103
 }
104
-func ProgressReader(r io.ReadCloser, size int, output io.Writer, template string, json bool) *progressReader {
105
-	if template == "" {
106
-		template = "%v/%v (%v)\r"
104
+func ProgressReader(r io.ReadCloser, size int, output io.Writer, template []byte, sf *StreamFormatter) *progressReader {
105
+      	tpl := string(template)
106
+	if tpl == "" {
107
+		tpl = string(sf.FormatProgress("", "%v/%v (%v)"))
107 108
 	}
108
-	return &progressReader{r, NewWriteFlusher(output), size, 0, 0, template, json}
109
+	return &progressReader{r, NewWriteFlusher(output), size, 0, 0, tpl, sf}
109 110
 }
110 111
 
111 112
 // HumanDuration returns a human-readable approximation of a duration
... ...
@@ -564,16 +566,57 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher {
564 564
 	return &WriteFlusher{w: w, flusher: flusher}
565 565
 }
566 566
 
567
-func FormatStatus(str string, json bool) string {
568
-	if json {
569
-		return "{\"status\" : \"" + str + "\"}"
567
+type JsonMessage struct {
568
+	Status   string `json:"status,omitempty"`
569
+	Progress string `json:"progress,omitempty"`
570
+	Error    string `json:"error,omitempty"`
571
+}
572
+
573
+type StreamFormatter struct {
574
+	json bool
575
+	used bool
576
+}
577
+
578
+func NewStreamFormatter(json bool) *StreamFormatter {
579
+	return &StreamFormatter{json, false}
580
+}
581
+
582
+func (sf *StreamFormatter) FormatStatus(format string, a ...interface{}) []byte {
583
+	sf.used = true
584
+	str := fmt.Sprintf(format, a...)
585
+	if sf.json {
586
+		b, err := json.Marshal(&JsonMessage{Status:str});
587
+		if err != nil {
588
+			return sf.FormatError(err)
589
+		}
590
+		return b
570 591
 	}
571
-	return str + "\r\n"
592
+	return []byte(str + "\r\n")
572 593
 }
573 594
 
574
-func FormatProgress(str string, json bool) string {
575
-	if json {
576
-		return "{\"progress\" : \"" + str + "\"}"
595
+func (sf *StreamFormatter) FormatError(err error) []byte {
596
+	sf.used = true
597
+	if sf.json {
598
+		if b, err := json.Marshal(&JsonMessage{Error:err.Error()}); err == nil {
599
+			return b
600
+		}
601
+		return []byte("{\"error\":\"format error\"}")
577 602
 	}
578
-	return "Downloading " + str + "\r"
603
+	return []byte("Error: " + err.Error() + "\r\n")
604
+}
605
+
606
+func (sf *StreamFormatter) FormatProgress(action, str string) []byte {
607
+	sf.used = true
608
+	if sf.json {
609
+		b, err := json.Marshal(&JsonMessage{Progress:str})
610
+		if err != nil {
611
+                        return nil
612
+                }
613
+		return b
614
+	}
615
+	return []byte(action + " " + str + "\r")
616
+}
617
+
618
+func (sf *StreamFormatter) Used() bool {
619
+	return sf.used
579 620
 }