Browse code

Experimenting with a UI which differentiates images and containers

Solomon Hykes authored on 2013/01/28 08:42:42
Showing 6 changed files
... ...
@@ -59,16 +59,15 @@ func createContainer(id string, root string, command string, args []string, laye
59 59
 		stdoutLog:	new(bytes.Buffer),
60 60
 		stderrLog:	new(bytes.Buffer),
61 61
 	}
62
-	if err := container.Filesystem.createMountPoints(); err != nil {
63
-		return nil, err
64
-	}
65
-
66 62
 	container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
67 63
 	container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
68 64
 
69 65
 	if err := os.Mkdir(root, 0700); err != nil {
70 66
 		return nil, err
71 67
 	}
68
+	if err := container.Filesystem.createMountPoints(); err != nil {
69
+		return nil, err
70
+	}
72 71
 	if err := container.save(); err != nil {
73 72
 		return nil, err
74 73
 	}
... ...
@@ -3,6 +3,7 @@ package main
3 3
 import (
4 4
 	"github.com/dotcloud/docker"
5 5
 	"github.com/dotcloud/docker/rcli"
6
+	"github.com/dotcloud/docker/image"
6 7
 	"github.com/dotcloud/docker/future"
7 8
 	"bufio"
8 9
 	"errors"
... ...
@@ -12,7 +13,6 @@ import (
12 12
 	"fmt"
13 13
 	"strings"
14 14
 	"text/tabwriter"
15
-	"sort"
16 15
 	"os"
17 16
 	"time"
18 17
 	"net/http"
... ...
@@ -60,7 +60,7 @@ func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string
60 60
 		return nil
61 61
 	}
62 62
 	for _, name := range cmd.Args() {
63
-		if container := srv.docker.Get(name); container != nil {
63
+		if container := srv.containers.Get(name); container != nil {
64 64
 			if err := container.Stop(); err != nil {
65 65
 				return err
66 66
 			}
... ...
@@ -83,7 +83,7 @@ func (srv *Server) CmdUmount(stdin io.ReadCloser, stdout io.Writer, args ...stri
83 83
 		return nil
84 84
 	}
85 85
 	for _, name := range cmd.Args() {
86
-		if container, exists := srv.findContainer(name); exists {
86
+		if container := srv.containers.Get(name); container != nil {
87 87
 			if err := container.Filesystem.Umount(); err != nil {
88 88
 				return err
89 89
 			}
... ...
@@ -106,7 +106,7 @@ func (srv *Server) CmdMount(stdin io.ReadCloser, stdout io.Writer, args ...strin
106 106
 		return nil
107 107
 	}
108 108
 	for _, name := range cmd.Args() {
109
-		if container, exists := srv.findContainer(name); exists {
109
+		if container := srv.containers.Get(name); container != nil {
110 110
 			if err := container.Filesystem.Mount(); err != nil {
111 111
 				return err
112 112
 			}
... ...
@@ -129,7 +129,7 @@ func (srv *Server) CmdCat(stdin io.ReadCloser, stdout io.Writer, args ...string)
129 129
 		return nil
130 130
 	}
131 131
 	name, path := cmd.Arg(0), cmd.Arg(1)
132
-	if container, exists := srv.findContainer(name); exists {
132
+	if container := srv.containers.Get(name); container != nil {
133 133
 		if f, err := container.Filesystem.OpenFile(path, os.O_RDONLY, 0); err != nil {
134 134
 			return err
135 135
 		} else if _, err := io.Copy(stdout, f); err != nil {
... ...
@@ -151,7 +151,7 @@ func (srv *Server) CmdWrite(stdin io.ReadCloser, stdout io.Writer, args ...strin
151 151
 		return nil
152 152
 	}
153 153
 	name, path := cmd.Arg(0), cmd.Arg(1)
154
-	if container, exists := srv.findContainer(name); exists {
154
+	if container := srv.containers.Get(name); container != nil {
155 155
 		if f, err := container.Filesystem.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600); err != nil {
156 156
 			return err
157 157
 		} else if _, err := io.Copy(f, stdin); err != nil {
... ...
@@ -174,7 +174,7 @@ func (srv *Server) CmdLs(stdin io.ReadCloser, stdout io.Writer, args ...string)
174 174
 		return nil
175 175
 	}
176 176
 	name, path := cmd.Arg(0), cmd.Arg(1)
177
-	if container, exists := srv.findContainer(name); exists {
177
+	if container := srv.containers.Get(name); container != nil {
178 178
 		if files, err := container.Filesystem.ReadDir(path); err != nil {
179 179
 			return err
180 180
 		} else {
... ...
@@ -198,7 +198,7 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
198 198
 		return nil
199 199
 	}
200 200
 	name := cmd.Arg(0)
201
-	if container, exists := srv.findContainer(name); exists {
201
+	if container := srv.containers.Get(name); container != nil {
202 202
 		data, err := json.Marshal(container)
203 203
 		if err != nil {
204 204
 			return err
... ...
@@ -215,12 +215,54 @@ func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...str
215 215
 	return errors.New("No such container: " + name)
216 216
 }
217 217
 
218
+func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
219
+	flags := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
220
+	if err := flags.Parse(args); err != nil {
221
+		return nil
222
+	}
223
+	for _, name := range flags.Args() {
224
+		container := srv.containers.Get(name)
225
+		if container == nil {
226
+			return errors.New("No such container: " + name)
227
+		}
228
+		if err := srv.containers.Destroy(container); err != nil {
229
+			fmt.Fprintln(stdout, "Error destroying container " + name + ": " + err.Error())
230
+		}
231
+	}
232
+	return nil
233
+}
218 234
 
235
+func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
236
+	if len(args) < 1 {
237
+		return errors.New("Not enough arguments")
238
+	}
239
+	resp, err := http.Get(args[0])
240
+	if err != nil {
241
+		return err
242
+	}
243
+	img, err := srv.images.Import(args[0], resp.Body, stdout, nil)
244
+	if err != nil {
245
+		return err
246
+	}
247
+	fmt.Fprintln(stdout, img.Id)
248
+	return nil
249
+}
219 250
 
251
+func (srv *Server) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
252
+	if len(args) < 1 {
253
+		return errors.New("Not enough arguments")
254
+	}
255
+	img, err := srv.images.Import(args[0], stdin, stdout, nil)
256
+	if err != nil {
257
+		return err
258
+	}
259
+	fmt.Fprintln(stdout, img.Id)
260
+	return nil
261
+}
220 262
 
221
-func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
222
-	flags := rcli.Subcmd(stdout, "list", "[OPTIONS] [NAME]", "List containers")
223
-	limit := flags.Int("l", 0, "Only show the N most recent versions of each name")
263
+func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
264
+	flags := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images")
265
+	limit := flags.Int("l", 0, "Only show the N most recent versions of each image")
224 266
 	quiet := flags.Bool("q", false, "only show numeric IDs")
225 267
 	flags.Parse(args)
226 268
 	if flags.NArg() > 1 {
... ...
@@ -231,34 +273,28 @@ func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string
231 231
 	if flags.NArg() == 1 {
232 232
 		nameFilter = flags.Arg(0)
233 233
 	}
234
-	var names []string
235
-	for name := range srv.containersByName {
236
-		names = append(names, name)
237
-	}
238
-	sort.Strings(names)
239 234
 	w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
240 235
 	if (!*quiet) {
241
-		fmt.Fprintf(w, "NAME\tID\tCREATED\tSOURCE\tRUNNING\tMOUNTED\tCOMMAND\tPID\tEXIT\n")
236
+		fmt.Fprintf(w, "NAME\tID\tCREATED\tPARENT\n")
242 237
 	}
243
-	for _, name := range names {
238
+	for _, name := range srv.images.Names() {
244 239
 		if nameFilter != "" && nameFilter != name {
245 240
 			continue
246 241
 		}
247
-		for idx, container := range *srv.containersByName[name] {
242
+		for idx, img := range *srv.images.ByName[name] {
248 243
 			if *limit > 0 && idx >= *limit {
249 244
 				break
250 245
 			}
251 246
 			if !*quiet {
247
+				id := img.Id
248
+				if !img.IdIsFinal() {
249
+					id += "..."
250
+				}
252 251
 				for idx, field := range []string{
253
-					/* NAME */	container.GetUserData("name"),
254
-					/* ID */	container.Id,
255
-					/* CREATED */	future.HumanDuration(time.Now().Sub(container.Created)) + " ago",
256
-					/* SOURCE */	container.GetUserData("source"),
257
-					/* RUNNING */	fmt.Sprintf("%v", container.State.Running),
258
-					/* MOUNTED */	fmt.Sprintf("%v", container.Filesystem.IsMounted()),
259
-					/* COMMAND */	fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
260
-					/* PID */	fmt.Sprintf("%v", container.State.Pid),
261
-					/* EXIT CODE */	fmt.Sprintf("%v", container.State.ExitCode),
252
+					/* NAME */	name,
253
+					/* ID */	id,
254
+					/* CREATED */	future.HumanDuration(time.Now().Sub(img.Created)) + " ago",
255
+					/* PARENT */	img.Parent,
262 256
 				} {
263 257
 					if idx == 0 {
264 258
 						w.Write([]byte(field))
... ...
@@ -268,7 +304,7 @@ func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string
268 268
 				}
269 269
 				w.Write([]byte{'\n'})
270 270
 			} else {
271
-				stdout.Write([]byte(container.Id + "\n"))
271
+				stdout.Write([]byte(img.Id + "\n"))
272 272
 			}
273 273
 		}
274 274
 	}
... ...
@@ -276,113 +312,120 @@ func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string
276 276
 		w.Flush()
277 277
 	}
278 278
 	return nil
279
-}
280 279
 
281
-func (srv *Server) findContainer(name string) (*docker.Container, bool) {
282
-	// 1: look for container by ID
283
-	if container := srv.docker.Get(name); container != nil {
284
-		return container, true
285
-	}
286
-	// 2: look for a container by name (and pick the most recent)
287
-	if containers, exists := srv.containersByName[name]; exists {
288
-		return (*containers)[0], true
289
-	}
290
-	return nil, false
291 280
 }
292 281
 
293
-
294
-func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
295
-	flags := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
296
-	if err := flags.Parse(args); err != nil {
282
+func (srv *Server) CmdContainers(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
283
+	cmd := rcli.Subcmd(stdout,
284
+		"containers", "[OPTIONS]",
285
+		"List containers")
286
+	quiet := cmd.Bool("q", false, "Only display numeric IDs")
287
+	if err := cmd.Parse(args); err != nil {
297 288
 		return nil
298 289
 	}
299
-	for _, name := range flags.Args() {
300
-		if _, err := srv.rm(name); err != nil {
301
-			fmt.Fprintln(stdout, "Error: " + err.Error())
290
+	w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
291
+	if (!*quiet) {
292
+		fmt.Fprintf(w, "ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\n")
293
+	}
294
+	for _, container := range srv.containers.List() {
295
+		if !*quiet {
296
+			for idx, field := range[]string {
297
+				/* ID */	container.Id,
298
+				/* IMAGE */	container.GetUserData("image"),
299
+				/* COMMAND */	fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
300
+				/* CREATED */	future.HumanDuration(time.Now().Sub(container.Created)) + " ago",
301
+				/* STATUS */	container.State.String(),
302
+			} {
303
+				if idx == 0 {
304
+					w.Write([]byte(field))
305
+				} else {
306
+					w.Write([]byte("\t" + field))
307
+				}
308
+			}
309
+			w.Write([]byte{'\n'})
310
+		} else {
311
+			stdout.Write([]byte(container.Id + "\n"))
302 312
 		}
303 313
 	}
314
+	if (!*quiet) {
315
+		w.Flush()
316
+	}
304 317
 	return nil
305 318
 }
306 319
 
307
-func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
308
-	if len(args) < 1 {
309
-		return errors.New("Not enough arguments")
310
-	}
311
-	resp, err := http.Get(args[0])
312
-	if err != nil {
313
-		return err
314
-	}
315
-	layer, err := srv.layers.AddLayer(resp.Body, stdout)
316
-	if err != nil {
317
-		return err
320
+func (srv *Server) CmdLayers(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
321
+	flags := rcli.Subcmd(stdout,
322
+		"layers", "[OPTIONS]",
323
+		"List filesystem layers (debug only)")
324
+	if err := flags.Parse(args); err != nil {
325
+		return nil
318 326
 	}
319
-	container, err := srv.addContainer(layer.Id(), []string{layer.Path}, args[0], "download")
320
-	if err != nil {
321
-		return err
327
+	for _, layer := range srv.images.Layers.List() {
328
+		fmt.Fprintln(stdout, layer)
322 329
 	}
323
-	fmt.Fprintln(stdout, container.Id)
324 330
 	return nil
325 331
 }
326 332
 
327
-func (srv *Server) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
328
-	if len(args) < 1 {
329
-		return errors.New("Not enough arguments")
333
+
334
+func (srv *Server) CmdCp(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
335
+	flags := rcli.Subcmd(stdout,
336
+		"cp", "[OPTIONS] IMAGE NAME",
337
+		"Create a copy of IMAGE and call it NAME")
338
+	if err := flags.Parse(args); err != nil {
339
+		return nil
330 340
 	}
331
-	fmt.Printf("Adding layer\n")
332
-	layer, err := srv.layers.AddLayer(stdin, stdout)
333
-	if err != nil {
341
+	if newImage, err := srv.images.Copy(flags.Arg(0), flags.Arg(1)); err != nil {
334 342
 		return err
343
+	} else {
344
+		fmt.Fprintln(stdout, newImage.Id)
335 345
 	}
336
-	id := layer.Id()
337
-	if !srv.docker.Exists(id) {
338
-		log.Println("Creating new container: " + id)
339
-		log.Printf("%v\n", srv.docker.List())
340
-		_, err := srv.addContainer(id, []string{layer.Path}, args[0], "upload")
341
-		if err != nil {
342
-			return err
343
-		}
344
-	}
345
-	fmt.Fprintln(stdout, id)
346 346
 	return nil
347 347
 }
348 348
 
349
-
350 349
 func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
351 350
 	flags := rcli.Subcmd(stdout,
352
-		"fork", "[OPTIONS] CONTAINER [DEST]",
353
-		"Duplicate a container")
354
-	// FIXME "-r" to reset changes in the new container
351
+		"commit", "[OPTIONS] CONTAINER [DEST]",
352
+		"Create a new image from a container's changes")
355 353
 	if err := flags.Parse(args); err != nil {
356 354
 		return nil
357 355
 	}
358
-	srcName, dstName := flags.Arg(0), flags.Arg(1)
359
-	if srcName == "" {
356
+	containerName, imgName := flags.Arg(0), flags.Arg(1)
357
+	if containerName == "" || imgName == "" {
360 358
 		flags.Usage()
361 359
 		return nil
362 360
 	}
363
-	if dstName == "" {
364
-		dstName = srcName
365
-	}
366
-	/*
367
-	if src, exists := srv.findContainer(srcName); exists {
368
-		baseLayer := src.Filesystem.Layers[0]
369
-		//dst := srv.addContainer(dstName, "snapshot:" + src.Id, src.Size)
370
-		//fmt.Fprintln(stdout, dst.Id)
361
+	if container := srv.containers.Get(containerName); container != nil {
362
+		// FIXME: freeze the container before copying it to avoid data corruption?
363
+		rwTar, err := docker.Tar(container.Filesystem.RWPath)
364
+		if err != nil {
365
+			return err
366
+		}
367
+		// Create a new image from the container's base layers + a new layer from container changes
368
+		parentImg := srv.images.Find(container.GetUserData("image"))
369
+		img, err := srv.images.Import(imgName, rwTar, stdout, parentImg)
370
+		if err != nil {
371
+			return err
372
+		}
373
+		fmt.Fprintln(stdout, img.Id)
371 374
 		return nil
372 375
 	}
373
-	*/
374
-	return errors.New("No such container: " + srcName)
376
+	return errors.New("No such container: " + containerName)
375 377
 }
376 378
 
379
+
377 380
 func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
378 381
 	flags := rcli.Subcmd(stdout,
379 382
 		"tar", "CONTAINER",
380 383
 		"Stream the contents of a container as a tar archive")
384
+	fl_sparse := flags.Bool("s", false, "Generate a sparse tar stream (top layer + reference to bottom layers)")
381 385
 	if err := flags.Parse(args); err != nil {
382 386
 		return nil
383 387
 	}
388
+	if *fl_sparse {
389
+		return errors.New("Sparse mode not yet implemented") // FIXME
390
+	}
384 391
 	name := flags.Arg(0)
385
-	if container, exists := srv.findContainer(name); exists {
392
+	if container := srv.containers.Get(name); container != nil {
386 393
 		data, err := container.Filesystem.Tar()
387 394
 		if err != nil {
388 395
 			return err
... ...
@@ -406,7 +449,7 @@ func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string
406 406
 	if flags.NArg() < 1 {
407 407
 		return errors.New("Not enough arguments")
408 408
 	}
409
-	if container, exists := srv.findContainer(flags.Arg(0)); !exists {
409
+	if container := srv.containers.Get(flags.Arg(0)); container == nil {
410 410
 		return errors.New("No such container")
411 411
 	} else {
412 412
 		changes, err := container.Filesystem.Changes()
... ...
@@ -431,7 +474,7 @@ func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...strin
431 431
 		return errors.New("Not enough arguments")
432 432
 	}
433 433
 	for _, name := range flags.Args() {
434
-		if container, exists := srv.findContainer(name); exists {
434
+		if container := srv.containers.Get(name); container != nil {
435 435
 			if err := container.Filesystem.Reset(); err != nil {
436 436
 				return errors.New("Reset " + container.Id + ": " + err.Error())
437 437
 			}
... ...
@@ -440,70 +483,6 @@ func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...strin
440 440
 	return nil
441 441
 }
442 442
 
443
-// ByDate wraps an array of layers so they can be sorted by date (most recent first)
444
-
445
-type ByDate []*docker.Container
446
-
447
-func (c *ByDate) Len() int {
448
-	return len(*c)
449
-}
450
-
451
-func (c *ByDate) Less(i, j int) bool {
452
-	containers := *c
453
-	return containers[j].Created.Before(containers[i].Created)
454
-}
455
-
456
-func (c *ByDate) Swap(i, j int) {
457
-	containers := *c
458
-	tmp := containers[i]
459
-	containers[i] = containers[j]
460
-	containers[j] = tmp
461
-}
462
-
463
-func (c *ByDate) Add(container *docker.Container) {
464
-	*c = append(*c, container)
465
-	sort.Sort(c)
466
-}
467
-
468
-func (c *ByDate) Del(id string) {
469
-	for idx, container := range *c {
470
-		if container.Id == id {
471
-			*c = append((*c)[:idx], (*c)[idx + 1:]...)
472
-		}
473
-	}
474
-}
475
-
476
-
477
-func (srv *Server) addContainer(id string, layers []string, name string, source string) (*docker.Container, error) {
478
-	c, err := srv.docker.Create(id, "", nil, layers, &docker.Config{Hostname: id, Ram: 512 * 1024 * 1024})
479
-	if err != nil {
480
-		return nil, err
481
-	}
482
-	if err := c.SetUserData("name", name); err != nil {
483
-		srv.docker.Destroy(c)
484
-		return nil, err
485
-	}
486
-	if _, exists := srv.containersByName[name]; !exists {
487
-		srv.containersByName[name] = new(ByDate)
488
-	}
489
-	srv.containersByName[name].Add(c)
490
-	return c, nil
491
-}
492
-
493
-
494
-func (srv *Server) rm(id string) (*docker.Container, error) {
495
-	container := srv.docker.Get(id)
496
-	if container == nil {
497
-		return nil, errors.New("No such continer: " + id)
498
-	}
499
-	// Remove from name lookup
500
-	srv.containersByName[container.GetUserData("name")].Del(container.Id)
501
-	if err := srv.docker.Destroy(container); err != nil {
502
-		return container, err
503
-	}
504
-	return container, nil
505
-}
506
-
507 443
 
508 444
 func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
509 445
 	flags := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
... ...
@@ -515,7 +494,7 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
515 515
 		return nil
516 516
 	}
517 517
 	name := flags.Arg(0)
518
-	if container, exists := srv.findContainer(name); exists {
518
+	if container := srv.containers.Get(name); container != nil {
519 519
 		if _, err := io.Copy(stdout, container.StdoutLog()); err != nil {
520 520
 			return err
521 521
 		}
... ...
@@ -528,8 +507,21 @@ func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string
528 528
 }
529 529
 
530 530
 
531
+func (srv *Server) CreateContainer(img *image.Image, cmd string, args ...string) (*docker.Container, error) {
532
+	id := future.RandomId()
533
+	container, err := srv.containers.Create(id, cmd, args, img.Layers, &docker.Config{Hostname: id})
534
+	if err != nil {
535
+		return nil, err
536
+	}
537
+	if err := container.SetUserData("image", img.Id); err != nil {
538
+		srv.containers.Destroy(container)
539
+		return nil, errors.New("Error setting container userdata: " + err.Error())
540
+	}
541
+	return container, nil
542
+}
543
+
531 544
 func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
532
-	flags := rcli.Subcmd(stdout, "run", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in a container")
545
+	flags := rcli.Subcmd(stdout, "run", "[OPTIONS] IMAGE COMMAND [ARG...]", "Run a command in a new container")
533 546
 	fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
534 547
 	//fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
535 548
 	if err := flags.Parse(args); err != nil {
... ...
@@ -540,40 +532,44 @@ func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string)
540 540
 		return nil
541 541
 	}
542 542
 	name, cmd := flags.Arg(0), flags.Args()[1:]
543
-	if container, exists := srv.findContainer(name); exists {
544
-		log.Printf("Running container %#v\n", container)
545
-		container.Path = cmd[0]
546
-		container.Args  = cmd[1:]
547
-		if *fl_attach {
548
-			cmd_stdout, err := container.StdoutPipe()
549
-			if err != nil {
550
-				return err
551
-			}
552
-			cmd_stderr, err := container.StderrPipe()
553
-			if err != nil {
554
-				return err
555
-			}
556
-			if err := container.Start(); err != nil {
557
-				return err
558
-			}
559
-			sending_stdout := future.Go(func() error { _, err := io.Copy(stdout, cmd_stdout); return err })
560
-			sending_stderr := future.Go(func() error { _, err := io.Copy(stdout, cmd_stderr); return err })
561
-			err_sending_stdout := <-sending_stdout
562
-			err_sending_stderr := <-sending_stderr
563
-			if err_sending_stdout != nil {
564
-				return err_sending_stdout
565
-			}
566
-			return err_sending_stderr
567
-		} else {
568
-			if output, err := container.Output(); err != nil {
569
-				return err
570
-			} else {
571
-				fmt.Printf("-->|%s|\n", output)
572
-			}
543
+	// Find the image
544
+	img := srv.images.Find(name)
545
+	if img == nil {
546
+		return errors.New("No such image: " + name)
547
+	}
548
+	// Create new container
549
+	container, err := srv.CreateContainer(img, cmd[0], cmd[1:]...)
550
+	if err != nil {
551
+		return errors.New("Error creating container: " + err.Error())
552
+	}
553
+	// Run the container
554
+	if *fl_attach {
555
+		cmd_stdout, err := container.StdoutPipe()
556
+		if err != nil {
557
+			return err
573 558
 		}
574
-		return nil
559
+		cmd_stderr, err := container.StderrPipe()
560
+		if err != nil {
561
+			return err
562
+		}
563
+		if err := container.Start(); err != nil {
564
+			return err
565
+		}
566
+		sending_stdout := future.Go(func() error { _, err := io.Copy(stdout, cmd_stdout); return err })
567
+		sending_stderr := future.Go(func() error { _, err := io.Copy(stdout, cmd_stderr); return err })
568
+		err_sending_stdout := <-sending_stdout
569
+		err_sending_stderr := <-sending_stderr
570
+		if err_sending_stdout != nil {
571
+			return err_sending_stdout
572
+		}
573
+		return err_sending_stderr
574
+	} else {
575
+		if err := container.Start(); err != nil {
576
+			return err
577
+		}
578
+		fmt.Fprintln(stdout, container.Id)
575 579
 	}
576
-	return errors.New("No such container: " + name)
580
+	return nil
577 581
 }
578 582
 
579 583
 func main() {
... ...
@@ -594,30 +590,18 @@ func main() {
594 594
 }
595 595
 
596 596
 func New() (*Server, error) {
597
-	store, err := future.NewStore("/var/lib/docker/layers")
597
+	images, err := image.New("/var/lib/docker/images")
598 598
 	if err != nil {
599 599
 		return nil, err
600 600
 	}
601
-	if err := store.Init(); err != nil {
602
-		return nil, err
603
-	}
604
-	d, err := docker.New()
601
+	containers, err := docker.New()
605 602
 	if err != nil {
606 603
 		return nil, err
607 604
 	}
608 605
 	srv := &Server{
609
-		containersByName: make(map[string]*ByDate),
610
-		layers: store,
611
-		docker: d,
612
-	}
613
-	for _, container := range srv.docker.List() {
614
-		name := container.GetUserData("name")
615
-		if _, exists := srv.containersByName[name]; !exists {
616
-			srv.containersByName[name] = new(ByDate)
617
-		}
618
-		srv.containersByName[name].Add(container)
606
+		images: images,
607
+		containers: containers,
619 608
 	}
620
-	log.Printf("Done building index\n")
621 609
 	return srv, nil
622 610
 }
623 611
 
... ...
@@ -662,8 +646,7 @@ func (srv *Server) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string)
662 662
 
663 663
 
664 664
 type Server struct {
665
-	containersByName	map[string]*ByDate
666
-	layers			*future.Store
667
-	docker			*docker.Docker
665
+	containers	*docker.Docker
666
+	images		*image.Store
668 667
 }
669 668
 
670 669
deleted file mode 100644
... ...
@@ -1,135 +0,0 @@
1
-package future
2
-
3
-import (
4
-	"errors"
5
-	"path"
6
-	"path/filepath"
7
-	"io"
8
-	"os"
9
-	"os/exec"
10
-)
11
-
12
-type Store struct {
13
-	Root	string
14
-}
15
-
16
-
17
-func NewStore(root string) (*Store, error) {
18
-	abspath, err := filepath.Abs(root)
19
-	if err != nil {
20
-		return nil, err
21
-	}
22
-	return &Store{
23
-		Root: abspath,
24
-	}, nil
25
-}
26
-
27
-func (store *Store) Get(id string) (*Layer, bool) {
28
-	layer := &Layer{Path: store.layerPath(id)}
29
-	if !layer.Exists() {
30
-		return nil, false
31
-	}
32
-	return layer, true
33
-}
34
-
35
-func (store *Store) Exists() (bool, error) {
36
-	if stat, err := os.Stat(store.Root); err != nil {
37
-		if os.IsNotExist(err) {
38
-			return false, nil
39
-		}
40
-		return false, err
41
-	} else if !stat.IsDir() {
42
-		return false, errors.New("Not a directory: " + store.Root)
43
-	}
44
-	return true, nil
45
-}
46
-
47
-func (store *Store) Init() error {
48
-	if exists, err := store.Exists(); err != nil {
49
-		return err
50
-	} else if exists {
51
-		return nil
52
-	}
53
-	return os.Mkdir(store.Root, 0700)
54
-}
55
-
56
-
57
-func (store *Store) Mktemp() (string, error) {
58
-	tmpName := RandomId()
59
-	tmpPath := path.Join(store.Root, "tmp-" + tmpName)
60
-	if err := os.Mkdir(tmpPath, 0700); err != nil {
61
-		return "", err
62
-	}
63
-	return tmpPath, nil
64
-}
65
-
66
-func (store *Store) layerPath(id string) string {
67
-	return path.Join(store.Root, id)
68
-}
69
-
70
-
71
-func (store *Store) AddLayer(archive io.Reader, stderr io.Writer) (*Layer, error) {
72
-	tmp, err := store.Mktemp()
73
-	defer os.RemoveAll(tmp)
74
-	if err != nil {
75
-		return nil, err
76
-	}
77
-	untarCmd := exec.Command("tar", "-C", tmp, "-x")
78
-	untarW, err := untarCmd.StdinPipe()
79
-	if err != nil {
80
-		return nil, err
81
-	}
82
-	untarStderr, err := untarCmd.StderrPipe()
83
-	if err != nil {
84
-		return nil, err
85
-	}
86
-	go io.Copy(stderr, untarStderr)
87
-	untarStdout, err := untarCmd.StdoutPipe()
88
-	if err != nil {
89
-		return nil, err
90
-	}
91
-	go io.Copy(stderr, untarStdout)
92
-	untarCmd.Start()
93
-	hashR, hashW := io.Pipe()
94
-	job_copy := Go(func() error {
95
-		_, err := io.Copy(io.MultiWriter(hashW, untarW), archive)
96
-		hashW.Close()
97
-		untarW.Close()
98
-		return err
99
-	})
100
-	id, err := ComputeId(hashR)
101
-	if err != nil {
102
-		return nil, err
103
-	}
104
-	if err := untarCmd.Wait(); err != nil {
105
-		return nil, err
106
-	}
107
-	if err := <-job_copy; err != nil {
108
-		return nil, err
109
-	}
110
-	layer := &Layer{Path: store.layerPath(id)}
111
-	if !layer.Exists() {
112
-		if err := os.Rename(tmp, layer.Path); err != nil {
113
-			return nil, err
114
-		}
115
-	}
116
-
117
-	return layer, nil
118
-}
119
-
120
-
121
-type Layer struct {
122
-	Path	string
123
-}
124
-
125
-func (layer *Layer) Exists() bool {
126
-	st, err := os.Stat(layer.Path)
127
-	if err != nil {
128
-		return false
129
-	}
130
-	return st.IsDir()
131
-}
132
-
133
-func (layer *Layer) Id() string {
134
-	return path.Base(layer.Path)
135
-}
136 1
new file mode 100644
... ...
@@ -0,0 +1,316 @@
0
+package image
1
+
2
+import (
3
+	"io"
4
+	"io/ioutil"
5
+	"encoding/json"
6
+	"time"
7
+	"path"
8
+	"path/filepath"
9
+	"errors"
10
+	"sort"
11
+	"os"
12
+	"github.com/dotcloud/docker/future"
13
+	"strings"
14
+)
15
+
16
+
17
+type Store struct {
18
+	*Index
19
+	Root	string
20
+	Layers	*LayerStore
21
+}
22
+
23
+
24
+func New(root string) (*Store, error) {
25
+	abspath, err := filepath.Abs(root)
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+	layers, err := NewLayerStore(path.Join(root, "layers"))
30
+	if err != nil {
31
+		return nil, err
32
+	}
33
+	if err := layers.Init(); err != nil {
34
+		return nil, err
35
+	}
36
+	return &Store{
37
+		Root: abspath,
38
+		Index: NewIndex(path.Join(root, "index.json")),
39
+		Layers: layers,
40
+	}, nil
41
+}
42
+
43
+
44
+func (store *Store) Import(name string, archive io.Reader, stderr io.Writer, parent *Image) (*Image, error) {
45
+	layer, err := store.Layers.AddLayer(archive, stderr)
46
+	if err != nil {
47
+		return nil, err
48
+	}
49
+	layers := []string{layer}
50
+	if parent != nil {
51
+		layers = append(parent.Layers, layers...)
52
+	}
53
+	var parentId string
54
+	if parent != nil {
55
+		parentId = parent.Id
56
+	}
57
+	return store.Create(name, parentId, layers...)
58
+}
59
+
60
+func (store *Store) Create(name string, source string, layers ...string) (*Image, error) {
61
+	image, err := NewImage(name, layers, source)
62
+	if err != nil {
63
+		return nil, err
64
+	}
65
+	if err := store.Index.Add(name, image); err != nil {
66
+		return nil, err
67
+	}
68
+	return image, nil
69
+}
70
+
71
+
72
+// Index
73
+
74
+type Index struct {
75
+	Path	string
76
+	ByName	map[string]*History
77
+	ById	map[string]*Image
78
+}
79
+
80
+func NewIndex(path string) *Index {
81
+	return &Index{
82
+		Path: path,
83
+		ByName: make(map[string]*History),
84
+		ById: make(map[string]*Image),
85
+	}
86
+}
87
+
88
+func (index *Index) Exists(id string) bool {
89
+	_, exists := index.ById[id]
90
+	return exists
91
+}
92
+
93
+func (index *Index) Find(idOrName string) *Image {
94
+	// Load
95
+	if err := index.load(); err != nil {
96
+		return nil
97
+	}
98
+	// Lookup by ID
99
+	if image, exists := index.ById[idOrName]; exists {
100
+		return image
101
+	}
102
+	// Lookup by name
103
+	if history, exists := index.ByName[idOrName]; exists && history.Len() > 0 {
104
+		return (*history)[0]
105
+	}
106
+	return nil
107
+}
108
+
109
+func (index *Index) Add(name string, image *Image) error {
110
+	// Load
111
+	if err := index.load(); err != nil {
112
+		return err
113
+	}
114
+	if _, exists := index.ByName[name]; !exists {
115
+		index.ByName[name] = new(History)
116
+	} else {
117
+		// If this image is already the latest version, don't add it.
118
+		if (*index.ByName[name])[0].Id == image.Id {
119
+			return nil
120
+		}
121
+	}
122
+	index.ByName[name].Add(image)
123
+	index.ById[image.Id] = image
124
+	// Save
125
+	if err := index.save(); err != nil {
126
+		return err
127
+	}
128
+	return nil
129
+}
130
+
131
+func (index *Index) Copy(srcNameOrId, dstName string) (*Image, error) {
132
+	if srcNameOrId == "" || dstName == "" {
133
+		return nil, errors.New("Illegal image name")
134
+	}
135
+	// Load
136
+	if err := index.load(); err != nil {
137
+		return nil, err
138
+	}
139
+	src := index.Find(srcNameOrId)
140
+	if src == nil {
141
+		return nil, errors.New("No such image: " + srcNameOrId)
142
+	}
143
+	if index.Find(dstName) != nil {
144
+		return nil, errors.New(dstName + ": image already exists.")
145
+	}
146
+	dst, err := NewImage(dstName, src.Layers, src.Id)
147
+	if err != nil {
148
+		return nil, err
149
+	}
150
+	if err := index.Add(dstName, dst); err != nil {
151
+		return nil, err
152
+	}
153
+	// Save
154
+	if err := index.save(); err != nil {
155
+		return nil, err
156
+	}
157
+	return dst, nil
158
+}
159
+
160
+func (index *Index) Rename(oldName, newName string) error {
161
+	// Load
162
+	if err := index.load(); err != nil {
163
+		return err
164
+	}
165
+	if _, exists := index.ByName[oldName]; !exists {
166
+		return errors.New("Can't rename " + oldName + ": no such image.")
167
+	}
168
+	if _, exists := index.ByName[newName]; exists {
169
+		return errors.New("Can't rename to " + newName + ": name is already in use.")
170
+	}
171
+	index.ByName[newName] = index.ByName[oldName]
172
+	delete(index.ByName, oldName)
173
+	// Change the ID of all images, since they include the name
174
+	for _, image := range *index.ByName[newName] {
175
+		if id, err := generateImageId(newName, image.Layers); err != nil {
176
+			return err
177
+		} else {
178
+			oldId := image.Id
179
+			image.Id = id
180
+			index.ById[id] = image
181
+			delete(index.ById, oldId)
182
+		}
183
+	}
184
+	// Save
185
+	if err := index.save(); err != nil {
186
+		return err
187
+	}
188
+	return nil
189
+}
190
+
191
+func (index *Index) Names() []string {
192
+	if err := index.load(); err != nil {
193
+		return []string{}
194
+	}
195
+	var names[]string
196
+	for name := range index.ByName {
197
+		names = append(names, name)
198
+	}
199
+	sort.Strings(names)
200
+	return names
201
+}
202
+
203
+func (index *Index) load() error {
204
+	jsonData, err := ioutil.ReadFile(index.Path)
205
+	if err != nil {
206
+		if os.IsNotExist(err) {
207
+			return nil
208
+		}
209
+		return err
210
+	}
211
+	path := index.Path
212
+	if err := json.Unmarshal(jsonData, index); err != nil {
213
+		return err
214
+	}
215
+	index.Path = path
216
+	return nil
217
+}
218
+
219
+func (index *Index) save() error {
220
+	jsonData, err := json.Marshal(index)
221
+	if err != nil {
222
+		return err
223
+	}
224
+	if err := ioutil.WriteFile(index.Path, jsonData, 0600); err != nil {
225
+		return err
226
+	}
227
+	return nil
228
+}
229
+
230
+// History wraps an array of images so they can be sorted by date (most recent first)
231
+
232
+type History []*Image
233
+
234
+func (history *History) Len() int {
235
+	return len(*history)
236
+}
237
+
238
+func (history *History) Less(i, j int) bool {
239
+	images := *history
240
+	return images[j].Created.Before(images[i].Created)
241
+}
242
+
243
+func (history *History) Swap(i, j int) {
244
+	images := *history
245
+	tmp := images[i]
246
+	images[i] = images[j]
247
+	images[j] = tmp
248
+}
249
+
250
+func (history *History) Add(image *Image) {
251
+	*history = append(*history, image)
252
+	sort.Sort(history)
253
+}
254
+
255
+func (history *History) Del(id string) {
256
+	for idx, image := range *history {
257
+		if image.Id == id {
258
+			*history = append((*history)[:idx], (*history)[idx + 1:]...)
259
+		}
260
+	}
261
+}
262
+
263
+type Image struct {
264
+	Id	string		// Globally unique identifier
265
+	Layers	[]string	// Absolute paths
266
+	Created	time.Time
267
+	Parent	string
268
+}
269
+
270
+func (image *Image) IdParts() (string, string) {
271
+	if len(image.Id) < 8 {
272
+		return "", image.Id
273
+	}
274
+	hash := image.Id[len(image.Id)-8:len(image.Id)]
275
+	name := image.Id[:len(image.Id)-9]
276
+	return name, hash
277
+}
278
+
279
+func (image *Image) IdIsFinal() bool {
280
+	return len(image.Layers) == 1
281
+}
282
+
283
+func generateImageId(name string, layers []string) (string, error) {
284
+	if len(layers) == 0 {
285
+		return "", errors.New("No layers provided.")
286
+	}
287
+	var hash string
288
+	if len(layers) == 1 {
289
+		hash = path.Base(layers[0])
290
+	} else {
291
+		var ids string
292
+		for _, layer := range layers {
293
+			ids += path.Base(layer)
294
+		}
295
+		if h, err := future.ComputeId(strings.NewReader(ids)); err != nil  {
296
+			return "", err
297
+		} else {
298
+			hash = h
299
+		}
300
+	}
301
+	return name + ":" + hash, nil
302
+}
303
+
304
+func NewImage(name string, layers []string, parent string) (*Image, error) {
305
+	id, err := generateImageId(name, layers)
306
+	if err != nil {
307
+		return nil, err
308
+	}
309
+	return &Image{
310
+		Id:		id,
311
+		Layers:		layers,
312
+		Created:	time.Now(),
313
+		Parent:		parent,
314
+	}, nil
315
+}
0 316
new file mode 100644
... ...
@@ -0,0 +1,139 @@
0
+package image
1
+
2
+import (
3
+	"errors"
4
+	"path"
5
+	"path/filepath"
6
+	"io"
7
+	"io/ioutil"
8
+	"os"
9
+	"os/exec"
10
+	"github.com/dotcloud/docker/future"
11
+)
12
+
13
+type LayerStore struct {
14
+	Root	string
15
+}
16
+
17
+func NewLayerStore(root string) (*LayerStore, error) {
18
+	abspath, err := filepath.Abs(root)
19
+	if err != nil {
20
+		return nil, err
21
+	}
22
+	return &LayerStore{
23
+		Root: abspath,
24
+	}, nil
25
+}
26
+
27
+func (store *LayerStore) List() []string {
28
+	files, err := ioutil.ReadDir(store.Root)
29
+	if err != nil {
30
+		return []string{}
31
+	}
32
+	var layers []string
33
+	for _, st := range files {
34
+		if st.IsDir() {
35
+			layers = append(layers, path.Join(store.Root, st.Name()))
36
+		}
37
+	}
38
+	return layers
39
+}
40
+
41
+func (store *LayerStore) Get(id string) string {
42
+	if !store.Exists(id) {
43
+		return ""
44
+	}
45
+	return store.layerPath(id)
46
+}
47
+
48
+func (store *LayerStore) rootExists() (bool, error) {
49
+	if stat, err := os.Stat(store.Root); err != nil {
50
+		if os.IsNotExist(err) {
51
+			return false, nil
52
+		}
53
+		return false, err
54
+	} else if !stat.IsDir() {
55
+		return false, errors.New("Not a directory: " + store.Root)
56
+	}
57
+	return true, nil
58
+}
59
+
60
+func (store *LayerStore) Init() error {
61
+	if exists, err := store.rootExists(); err != nil {
62
+		return err
63
+	} else if exists {
64
+		return nil
65
+	}
66
+	return os.Mkdir(store.Root, 0700)
67
+}
68
+
69
+
70
+func (store *LayerStore) Mktemp() (string, error) {
71
+	tmpName := future.RandomId()
72
+	tmpPath := path.Join(store.Root, "tmp-" + tmpName)
73
+	if err := os.Mkdir(tmpPath, 0700); err != nil {
74
+		return "", err
75
+	}
76
+	return tmpPath, nil
77
+}
78
+
79
+func (store *LayerStore) layerPath(id string) string {
80
+	return path.Join(store.Root, id)
81
+}
82
+
83
+
84
+func (store *LayerStore) AddLayer(archive io.Reader, stderr io.Writer) (string, error) {
85
+	tmp, err := store.Mktemp()
86
+	defer os.RemoveAll(tmp)
87
+	if err != nil {
88
+		return "", err
89
+	}
90
+	untarCmd := exec.Command("tar", "-C", tmp, "-x")
91
+	untarW, err := untarCmd.StdinPipe()
92
+	if err != nil {
93
+		return "", err
94
+	}
95
+	untarStderr, err := untarCmd.StderrPipe()
96
+	if err != nil {
97
+		return "", err
98
+	}
99
+	go io.Copy(stderr, untarStderr)
100
+	untarStdout, err := untarCmd.StdoutPipe()
101
+	if err != nil {
102
+		return "", err
103
+	}
104
+	go io.Copy(stderr, untarStdout)
105
+	untarCmd.Start()
106
+	hashR, hashW := io.Pipe()
107
+	job_copy := future.Go(func() error {
108
+		_, err := io.Copy(io.MultiWriter(hashW, untarW), archive)
109
+		hashW.Close()
110
+		untarW.Close()
111
+		return err
112
+	})
113
+	id, err := future.ComputeId(hashR)
114
+	if err != nil {
115
+		return "", err
116
+	}
117
+	if err := untarCmd.Wait(); err != nil {
118
+		return "", err
119
+	}
120
+	if err := <-job_copy; err != nil {
121
+		return "", err
122
+	}
123
+	layer := store.layerPath(id)
124
+	if !store.Exists(id) {
125
+		if err := os.Rename(tmp, layer); err != nil {
126
+			return "", err
127
+		}
128
+	}
129
+	return layer, nil
130
+}
131
+
132
+func (store *LayerStore) Exists(id string) bool {
133
+	st, err := os.Stat(store.layerPath(id))
134
+	if err != nil {
135
+		return false
136
+	}
137
+	return st.IsDir()
138
+}
... ...
@@ -3,6 +3,8 @@ package docker
3 3
 import (
4 4
 	"sync"
5 5
 	"time"
6
+	"fmt"
7
+	"github.com/dotcloud/docker/future"
6 8
 )
7 9
 
8 10
 type State struct {
... ...
@@ -15,6 +17,7 @@ type State struct {
15 15
 	stateChangeCond *sync.Cond
16 16
 }
17 17
 
18
+
18 19
 func newState() *State {
19 20
 	lock := new(sync.Mutex)
20 21
 	return &State{
... ...
@@ -23,6 +26,14 @@ func newState() *State {
23 23
 	}
24 24
 }
25 25
 
26
+// String returns a human-readable description of the state
27
+func (s *State) String() string {
28
+	if s.Running {
29
+		return fmt.Sprintf("Running for %s", future.HumanDuration(time.Now().Sub(s.StartedAt)))
30
+	}
31
+	return fmt.Sprintf("Exited with %d", s.ExitCode)
32
+}
33
+
26 34
 func (s *State) setRunning(pid int) {
27 35
 	s.Running = true
28 36
 	s.ExitCode = 0