Browse Source

merge conflicts

Andrea Luzzardi 12 years ago
parent
commit
2cb444248d
5 changed files with 558 additions and 231 deletions
  1. 77 1
      container.go
  2. 340 230
      dockerd/dockerd.go
  3. 52 0
      fake/fake.go
  4. 59 0
      filesystem.go
  5. 30 0
      utils.go

+ 77 - 1
container.go

@@ -11,6 +11,8 @@ import (
 	"path"
 	"syscall"
 	"time"
+	"strings"
+	"bytes"
 )
 
 type Container struct {
@@ -30,6 +32,9 @@ type Container struct {
 	cmd           *exec.Cmd
 	stdout        *writeBroadcaster
 	stderr        *writeBroadcaster
+
+	stdoutLog	*bytes.Buffer
+	stderrLog	*bytes.Buffer
 }
 
 type Config struct {
@@ -51,8 +56,16 @@ func createContainer(id string, root string, command string, args []string, laye
 		lxcConfigPath: path.Join(root, "config.lxc"),
 		stdout:        newWriteBroadcaster(),
 		stderr:        newWriteBroadcaster(),
+		stdoutLog:	new(bytes.Buffer),
+		stderrLog:	new(bytes.Buffer),
+	}
+	if err := container.Filesystem.createMountPoints(); err != nil {
+		return nil, err
 	}
 
+	container.stdout.AddWriter(NopWriteCloser(container.stdoutLog))
+	container.stderr.AddWriter(NopWriteCloser(container.stderrLog))
+
 	if err := os.Mkdir(root, 0700); err != nil {
 		return nil, err
 	}
@@ -73,14 +86,68 @@ func loadContainer(containerPath string) (*Container, error) {
 	container := &Container{
 		stdout: newWriteBroadcaster(),
 		stderr: newWriteBroadcaster(),
+		stdoutLog: new(bytes.Buffer),
+		stderrLog: new(bytes.Buffer),
 	}
 	if err := json.Unmarshal(data, container); err != nil {
 		return nil, err
 	}
+	if err := container.Filesystem.createMountPoints(); err != nil {
+		return nil, err
+	}
 	container.State = newState()
 	return container, nil
 }
 
+
+func (container *Container) Cmd() *exec.Cmd {
+	return container.cmd
+}
+
+func (container *Container) loadUserData() (map[string]string, error) {
+	jsonData, err := ioutil.ReadFile(path.Join(container.Root, "userdata.json"))
+	if err != nil {
+		if os.IsNotExist(err) {
+			return make(map[string]string), nil
+		}
+		return nil, err
+	}
+	data := make(map[string]string)
+	if err := json.Unmarshal(jsonData, &data); err != nil {
+		return nil, err
+	}
+	return data, nil
+}
+
+func (container *Container) saveUserData(data map[string]string) error {
+	jsonData, err := json.Marshal(data)
+	if err != nil {
+		return err
+	}
+	return ioutil.WriteFile(path.Join(container.Root, "userdata.json"), jsonData, 0700)
+}
+
+func (container *Container) SetUserData(key, value string) error {
+	data, err := container.loadUserData()
+	if err != nil {
+		return err
+	}
+	data[key] = value
+	return container.saveUserData(data)
+}
+
+func (container *Container) GetUserData(key string) (string) {
+	data, err := container.loadUserData()
+	if err != nil {
+		return ""
+	}
+	if value, exists := data[key]; exists {
+		return value
+	}
+	return ""
+}
+
+
 func (container *Container) save() (err error) {
 	data, err := json.Marshal(container)
 	if err != nil {
@@ -103,7 +170,7 @@ func (container *Container) generateLXCConfig() error {
 }
 
 func (container *Container) Start() error {
-	if err := container.Filesystem.Mount(); err != nil {
+	if err := container.Filesystem.EnsureMounted(); err != nil {
 		return err
 	}
 
@@ -156,12 +223,21 @@ func (container *Container) StdoutPipe() (io.ReadCloser, error) {
 	return newBufReader(reader), nil
 }
 
+func (container *Container) StdoutLog() io.Reader {
+	return strings.NewReader(container.stdoutLog.String())
+}
+
+
 func (container *Container) StderrPipe() (io.ReadCloser, error) {
 	reader, writer := io.Pipe()
 	container.stderr.AddWriter(writer)
 	return newBufReader(reader), nil
 }
 
+func (container *Container) StderrLog() io.Reader {
+	return strings.NewReader(container.stderrLog.String())
+}
+
 func (container *Container) monitor() {
 	// Wait for the program to exit
 	container.cmd.Wait()

+ 340 - 230
dockerd/dockerd.go

@@ -1,33 +1,31 @@
 package main
 
 import (
+	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/rcli"
-	"github.com/dotcloud/docker/fake"
 	"github.com/dotcloud/docker/future"
 	"bufio"
 	"errors"
 	"log"
 	"io"
-	"io/ioutil"
-	"os/exec"
 	"flag"
 	"fmt"
-	"github.com/kr/pty"
 	"strings"
-	"bytes"
 	"text/tabwriter"
 	"sort"
 	"os"
 	"time"
 	"net/http"
+	"encoding/json"
+	"bytes"
 )
 
 
-func (docker *Docker) Name() string {
+func (srv *Server) Name() string {
 	return "docker"
 }
 
-func (docker *Docker) Help() string {
+func (srv *Server) Help() string {
 	help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
 	for _, cmd := range [][]interface{}{
 		{"run", "Run a command in a container"},
@@ -42,6 +40,7 @@ func (docker *Docker) Help() string {
 		{"commit", "Save the state of a container"},
 		{"attach", "Attach to the standard inputs and outputs of a running container"},
 		{"info", "Display system-wide information"},
+		{"tar", "Stream the contents of a container as a tar archive"},
 		{"web", "Generate a web UI"},
 	} {
 		help += fmt.Sprintf("    %-10.10s%s\n", cmd...)
@@ -50,7 +49,176 @@ func (docker *Docker) Help() string {
 }
 
 
-func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdStop(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	cmd := rcli.Subcmd(stdout, "stop", "[OPTIONS] NAME", "Stop a running container")
+	if err := cmd.Parse(args); err != nil {
+		cmd.Usage()
+		return nil
+	}
+	if cmd.NArg() < 1 {
+		cmd.Usage()
+		return nil
+	}
+	for _, name := range cmd.Args() {
+		if container := srv.docker.Get(name); container != nil {
+			if err := container.Stop(); err != nil {
+				return err
+			}
+			fmt.Fprintln(stdout, container.Id)
+		} else {
+			return errors.New("No such container: " + name)
+		}
+	}
+	return nil
+}
+
+func (srv *Server) CmdUmount(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	cmd := rcli.Subcmd(stdout, "umount", "[OPTIONS] NAME", "umount a container's filesystem (debug only)")
+	if err := cmd.Parse(args); err != nil {
+		cmd.Usage()
+		return nil
+	}
+	if cmd.NArg() < 1 {
+		cmd.Usage()
+		return nil
+	}
+	for _, name := range cmd.Args() {
+		if container, exists := srv.findContainer(name); exists {
+			if err := container.Filesystem.Umount(); err != nil {
+				return err
+			}
+			fmt.Fprintln(stdout, container.Id)
+		} else {
+			return errors.New("No such container: " + name)
+		}
+	}
+	return nil
+}
+
+func (srv *Server) CmdMount(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	cmd := rcli.Subcmd(stdout, "umount", "[OPTIONS] NAME", "mount a container's filesystem (debug only)")
+	if err := cmd.Parse(args); err != nil {
+		cmd.Usage()
+		return nil
+	}
+	if cmd.NArg() < 1 {
+		cmd.Usage()
+		return nil
+	}
+	for _, name := range cmd.Args() {
+		if container, exists := srv.findContainer(name); exists {
+			if err := container.Filesystem.Mount(); err != nil {
+				return err
+			}
+			fmt.Fprintln(stdout, container.Id)
+		} else {
+			return errors.New("No such container: " + name)
+		}
+	}
+	return nil
+}
+
+func (srv *Server) CmdCat(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	cmd := rcli.Subcmd(stdout, "cat", "[OPTIONS] CONTAINER PATH", "write the contents of a container's file to standard output")
+	if err := cmd.Parse(args); err != nil {
+		cmd.Usage()
+		return nil
+	}
+	if cmd.NArg() < 2 {
+		cmd.Usage()
+		return nil
+	}
+	name, path := cmd.Arg(0), cmd.Arg(1)
+	if container, exists := srv.findContainer(name); exists {
+		if f, err := container.Filesystem.OpenFile(path, os.O_RDONLY, 0); err != nil {
+			return err
+		} else if _, err := io.Copy(stdout, f); err != nil {
+			return err
+		}
+		return nil
+	}
+	return errors.New("No such container: " + name)
+}
+
+func (srv *Server) CmdWrite(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	cmd := rcli.Subcmd(stdout, "write", "[OPTIONS] CONTAINER PATH", "write the contents of standard input to a container's file")
+	if err := cmd.Parse(args); err != nil {
+		cmd.Usage()
+		return nil
+	}
+	if cmd.NArg() < 2 {
+		cmd.Usage()
+		return nil
+	}
+	name, path := cmd.Arg(0), cmd.Arg(1)
+	if container, exists := srv.findContainer(name); exists {
+		if f, err := container.Filesystem.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600); err != nil {
+			return err
+		} else if _, err := io.Copy(f, stdin); err != nil {
+			return err
+		}
+		return nil
+	}
+	return errors.New("No such container: " + name)
+}
+
+
+func (srv *Server) CmdLs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	cmd := rcli.Subcmd(stdout, "ls", "[OPTIONS] CONTAINER PATH", "List the contents of a container's directory")
+	if err := cmd.Parse(args); err != nil {
+		cmd.Usage()
+		return nil
+	}
+	if cmd.NArg() < 2 {
+		cmd.Usage()
+		return nil
+	}
+	name, path := cmd.Arg(0), cmd.Arg(1)
+	if container, exists := srv.findContainer(name); exists {
+		if files, err := container.Filesystem.ReadDir(path); err != nil {
+			return err
+		} else {
+			for _, f := range files {
+				fmt.Fprintln(stdout, f.Name())
+			}
+		}
+		return nil
+	}
+	return errors.New("No such container: " + name)
+}
+
+func (srv *Server) CmdInspect(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	cmd := rcli.Subcmd(stdout, "inspect", "[OPTIONS] CONTAINER", "Return low-level information on a container")
+	if err := cmd.Parse(args); err != nil {
+		cmd.Usage()
+		return nil
+	}
+	if cmd.NArg() < 1 {
+		cmd.Usage()
+		return nil
+	}
+	name := cmd.Arg(0)
+	if container, exists := srv.findContainer(name); exists {
+		data, err := json.Marshal(container)
+		if err != nil {
+			return err
+		}
+		indented := new(bytes.Buffer)
+		if err = json.Indent(indented, data, "", "    "); err != nil {
+			return err
+		}
+		if _, err := io.Copy(stdout, indented); err != nil {
+			return err
+		}
+		return nil
+	}
+	return errors.New("No such container: " + name)
+}
+
+
+
+
+func (srv *Server) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	flags := rcli.Subcmd(stdout, "list", "[OPTIONS] [NAME]", "List containers")
 	limit := flags.Int("l", 0, "Only show the N most recent versions of each name")
 	quiet := flags.Bool("q", false, "only show numeric IDs")
@@ -64,32 +232,33 @@ func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...str
 		nameFilter = flags.Arg(0)
 	}
 	var names []string
-	for name := range docker.containersByName {
+	for name := range srv.containersByName {
 		names = append(names, name)
 	}
 	sort.Strings(names)
 	w := tabwriter.NewWriter(stdout, 20, 1, 3, ' ', 0)
 	if (!*quiet) {
-		fmt.Fprintf(w, "NAME\tID\tCREATED\tSOURCE\tSIZE\tCHANGES\tRUNNING\tCOMMAND\n")
+		fmt.Fprintf(w, "NAME\tID\tCREATED\tSOURCE\tRUNNING\tMOUNTED\tCOMMAND\tPID\tEXIT\n")
 	}
 	for _, name := range names {
 		if nameFilter != "" && nameFilter != name {
 			continue
 		}
-		for idx, container := range *docker.containersByName[name] {
+		for idx, container := range *srv.containersByName[name] {
 			if *limit > 0 && idx >= *limit {
 				break
 			}
 			if !*quiet {
 				for idx, field := range []string{
-					/* NAME */	container.Name,
+					/* NAME */	container.GetUserData("name"),
 					/* ID */	container.Id,
 					/* CREATED */	future.HumanDuration(time.Now().Sub(container.Created)) + " ago",
-					/* SOURCE */	container.Source,
-					/* SIZE */	fmt.Sprintf("%.1fM", float32(container.Size) / 1024 / 1024),
-					/* CHANGES */	fmt.Sprintf("%.1fM", float32(container.BytesChanged) / 1024 / 1024),
-					/* RUNNING */	fmt.Sprintf("%v", container.Running),
-					/* COMMAND */	container.CmdString(),
+					/* SOURCE */	container.GetUserData("source"),
+					/* RUNNING */	fmt.Sprintf("%v", container.State.Running),
+					/* MOUNTED */	fmt.Sprintf("%v", container.Filesystem.IsMounted()),
+					/* COMMAND */	fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
+					/* PID */	fmt.Sprintf("%v", container.State.Pid),
+					/* EXIT CODE */	fmt.Sprintf("%v", container.State.ExitCode),
 				} {
 					if idx == 0 {
 						w.Write([]byte(field))
@@ -109,33 +278,33 @@ func (docker *Docker) CmdList(stdin io.ReadCloser, stdout io.Writer, args ...str
 	return nil
 }
 
-func (docker *Docker) findContainer(name string) (*Container, bool) {
+func (srv *Server) findContainer(name string) (*docker.Container, bool) {
 	// 1: look for container by ID
-	if container, exists := docker.containers[name]; exists {
+	if container := srv.docker.Get(name); container != nil {
 		return container, true
 	}
 	// 2: look for a container by name (and pick the most recent)
-	if containers, exists := docker.containersByName[name]; exists {
+	if containers, exists := srv.containersByName[name]; exists {
 		return (*containers)[0], true
 	}
 	return nil, false
 }
 
 
-func (docker *Docker) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdRm(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	flags := rcli.Subcmd(stdout, "rm", "[OPTIONS] CONTAINER", "Remove a container")
 	if err := flags.Parse(args); err != nil {
 		return nil
 	}
 	for _, name := range flags.Args() {
-		if _, err := docker.rm(name); err != nil {
+		if _, err := srv.rm(name); err != nil {
 			fmt.Fprintln(stdout, "Error: " + err.Error())
 		}
 	}
 	return nil
 }
 
-func (docker *Docker) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	if len(args) < 1 {
 		return errors.New("Not enough arguments")
 	}
@@ -143,31 +312,42 @@ func (docker *Docker) CmdPull(stdin io.ReadCloser, stdout io.Writer, args ...str
 	if err != nil {
 		return err
 	}
-	layer, err := docker.layers.AddLayer(resp.Body, stdout)
+	layer, err := srv.layers.AddLayer(resp.Body, stdout)
+	if err != nil {
+		return err
+	}
+	container, err := srv.addContainer(layer.Id(), []string{layer.Path}, args[0], "download")
 	if err != nil {
 		return err
 	}
-	docker.addContainer(args[0], "download", 0)
-	fmt.Fprintln(stdout, layer.Id())
+	fmt.Fprintln(stdout, container.Id)
 	return nil
 }
 
-func (docker *Docker) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdPut(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	if len(args) < 1 {
 		return errors.New("Not enough arguments")
 	}
 	fmt.Printf("Adding layer\n")
-	layer, err := docker.layers.AddLayer(stdin, stdout)
+	layer, err := srv.layers.AddLayer(stdin, stdout)
 	if err != nil {
 		return err
 	}
-	docker.addContainer(args[0], "upload", 0)
-	fmt.Fprintln(stdout, layer.Id())
+	id := layer.Id()
+	if !srv.docker.Exists(id) {
+		log.Println("Creating new container: " + id)
+		log.Printf("%v\n", srv.docker.List())
+		_, err := srv.addContainer(id, []string{layer.Path}, args[0], "upload")
+		if err != nil {
+			return err
+		}
+	}
+	fmt.Fprintln(stdout, id)
 	return nil
 }
 
 
-func (docker *Docker) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	flags := rcli.Subcmd(stdout,
 		"fork", "[OPTIONS] CONTAINER [DEST]",
 		"Duplicate a container")
@@ -183,15 +363,18 @@ func (docker *Docker) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...s
 	if dstName == "" {
 		dstName = srcName
 	}
-	if src, exists := docker.findContainer(srcName); exists {
-		dst := docker.addContainer(dstName, "snapshot:" + src.Id, src.Size)
-		fmt.Fprintln(stdout, dst.Id)
+	/*
+	if src, exists := srv.findContainer(srcName); exists {
+		baseLayer := src.Filesystem.Layers[0]
+		//dst := srv.addContainer(dstName, "snapshot:" + src.Id, src.Size)
+		//fmt.Fprintln(stdout, dst.Id)
 		return nil
 	}
+	*/
 	return errors.New("No such container: " + srcName)
 }
 
-func (docker *Docker) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	flags := rcli.Subcmd(stdout,
 		"tar", "CONTAINER",
 		"Stream the contents of a container as a tar archive")
@@ -199,70 +382,67 @@ func (docker *Docker) CmdTar(stdin io.ReadCloser, stdout io.Writer, args ...stri
 		return nil
 	}
 	name := flags.Arg(0)
-	if _, exists := docker.findContainer(name); exists {
+	if container, exists := srv.findContainer(name); exists {
+		data, err := container.Filesystem.Tar()
+		if err != nil {
+			return err
+		}
 		// Stream the entire contents of the container (basically a volatile snapshot)
-		return fake.WriteFakeTar(stdout)
+		if _, err := io.Copy(stdout, data); err != nil {
+			return err
+		}
+		return nil
 	}
 	return errors.New("No such container: " + name)
 }
 
-func (docker *Docker) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	flags := rcli.Subcmd(stdout,
 		"diff", "CONTAINER [OPTIONS]",
 		"Inspect changes on a container's filesystem")
-	fl_diff := flags.Bool("d", true, "Show changes in diff format")
-	fl_bytes := flags.Bool("b", false, "Show how many bytes have been changed")
-	fl_list := flags.Bool("l", false, "Show a list of changed files")
 	if err := flags.Parse(args); err != nil {
 		return nil
 	}
 	if flags.NArg() < 1 {
 		return errors.New("Not enough arguments")
 	}
-	if container, exists := docker.findContainer(flags.Arg(0)); !exists {
+	if container, exists := srv.findContainer(flags.Arg(0)); !exists {
 		return errors.New("No such container")
-	} else if *fl_bytes {
-		fmt.Fprintf(stdout, "%d\n", container.BytesChanged)
-	} else if *fl_list {
-		// FAKE
-		fmt.Fprintf(stdout, strings.Join([]string{
-			"/etc/postgres/pg.conf",
-			"/etc/passwd",
-			"/var/lib/postgres",
-			"/usr/bin/postgres",
-			"/usr/bin/psql",
-			"/var/log/postgres",
-			"/var/log/postgres/postgres.log",
-			"/var/log/postgres/postgres.log.0",
-			"/var/log/postgres/postgres.log.1.gz"}, "\n"))
-	} else if *fl_diff {
-		// Achievement unlocked: embed a diff of your code as a string in your code
-		fmt.Fprintf(stdout, `
-diff --git a/dockerd/dockerd.go b/dockerd/dockerd.go
-index 2dae694..e43caca 100644
---- a/dockerd/dockerd.go
-+++ b/dockerd/dockerd.go
-@@ -158,6 +158,7 @@ func (docker *Docker) CmdDiff(stdin io.ReadCloser, stdout io.Writer, args ...str
-        flags := rcli.Subcmd(stdout,
-                "diff", "CONTAINER [OPTIONS]",
-                "Inspect changes on a container's filesystem")
-+       fl_diff := flags.Bool("d", true, "Show changes in diff format")
-        fl_bytes := flags.Bool("b", false, "Show how many bytes have been changes")
-        fl_list := flags.Bool("l", false, "Show a list of changed files")
-        fl_download := flags.Bool("d", false, "Download the changes as gzipped tar stream")
-`)
-		return nil
 	} else {
-		flags.Usage()
-		return nil
+		changes, err := container.Filesystem.Changes()
+		if err != nil {
+			return err
+		}
+		for _, change := range changes {
+			fmt.Fprintln(stdout, change.String())
+		}
 	}
 	return nil
 }
 
+func (srv *Server) CmdReset(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+	flags := rcli.Subcmd(stdout,
+		"reset", "CONTAINER [OPTIONS]",
+		"Reset changes to a container's filesystem")
+	if err := flags.Parse(args); err != nil {
+		return nil
+	}
+	if flags.NArg() < 1 {
+		return errors.New("Not enough arguments")
+	}
+	for _, name := range flags.Args() {
+		if container, exists := srv.findContainer(name); exists {
+			if err := container.Filesystem.Reset(); err != nil {
+				return errors.New("Reset " + container.Id + ": " + err.Error())
+			}
+		}
+	}
+	return nil
+}
 
 // ByDate wraps an array of layers so they can be sorted by date (most recent first)
 
-type ByDate []*Container
+type ByDate []*docker.Container
 
 func (c *ByDate) Len() int {
 	return len(*c)
@@ -280,7 +460,7 @@ func (c *ByDate) Swap(i, j int) {
 	containers[j] = tmp
 }
 
-func (c *ByDate) Add(container *Container) {
+func (c *ByDate) Add(container *docker.Container) {
 	*c = append(*c, container)
 	sort.Sort(c)
 }
@@ -294,46 +474,38 @@ func (c *ByDate) Del(id string) {
 }
 
 
-func (docker *Docker) addContainer(name string, source string, size uint) *Container {
-	if size == 0 {
-		size = fake.RandomContainerSize()
+func (srv *Server) addContainer(id string, layers []string, name string, source string) (*docker.Container, error) {
+	c, err := srv.docker.Create(id, "", nil, layers, &docker.Config{Hostname: id, Ram: 512 * 1024 * 1024})
+	if err != nil {
+		return nil, err
 	}
-	c := &Container{
-		Id:		future.RandomId(),
-		Name:		name,
-		Created:	time.Now(),
-		Source:		source,
-		Size:		size,
-		stdinLog: new(bytes.Buffer),
-		stdoutLog: new(bytes.Buffer),
+	if err := c.SetUserData("name", name); err != nil {
+		srv.docker.Destroy(c)
+		return nil, err
 	}
-	docker.containers[c.Id] = c
-	if _, exists := docker.containersByName[c.Name]; !exists {
-		docker.containersByName[c.Name] = new(ByDate)
+	if _, exists := srv.containersByName[name]; !exists {
+		srv.containersByName[name] = new(ByDate)
 	}
-	docker.containersByName[c.Name].Add(c)
-	return c
-
+	srv.containersByName[name].Add(c)
+	return c, nil
 }
 
 
-func (docker *Docker) rm(id string) (*Container, error) {
-	if container, exists := docker.containers[id]; exists {
-		if container.Running {
-			return nil, errors.New("Container is running: " + id)
-		} else {
-			// Remove from name lookup
-			docker.containersByName[container.Name].Del(container.Id)
-			// Remove from id lookup
-			delete(docker.containers, container.Id)
-			return container, nil
-		}
+func (srv *Server) rm(id string) (*docker.Container, error) {
+	container := srv.docker.Get(id)
+	if container == nil {
+		return nil, errors.New("No such continer: " + id)
+	}
+	// Remove from name lookup
+	srv.containersByName[container.GetUserData("name")].Del(container.Id)
+	if err := srv.docker.Destroy(container); err != nil {
+		return container, err
 	}
-	return nil, errors.New(fmt.Sprintf("No such container: %s", id))
+	return container, nil
 }
 
 
-func (docker *Docker) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	flags := rcli.Subcmd(stdout, "logs", "[OPTIONS] CONTAINER", "Fetch the logs of a container")
 	if err := flags.Parse(args); err != nil {
 		return nil
@@ -343,19 +515,23 @@ func (docker *Docker) CmdLogs(stdin io.ReadCloser, stdout io.Writer, args ...str
 		return nil
 	}
 	name := flags.Arg(0)
-	if container, exists := docker.findContainer(name); exists {
+	if container, exists := srv.findContainer(name); exists {
 		if _, err := io.Copy(stdout, container.StdoutLog()); err != nil {
 			return err
 		}
+		if _, err := io.Copy(stdout, container.StderrLog()); err != nil {
+			return err
+		}
 		return nil
 	}
 	return errors.New("No such container: " + flags.Arg(0))
 }
 
-func (docker *Docker) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+
+func (srv *Server) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	flags := rcli.Subcmd(stdout, "run", "[OPTIONS] CONTAINER COMMAND [ARG...]", "Run a command in a container")
 	fl_attach := flags.Bool("a", false, "Attach stdin and stdout")
-	fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
+	//fl_tty := flags.Bool("t", false, "Allocate a pseudo-tty")
 	if err := flags.Parse(args); err != nil {
 		return nil
 	}
@@ -364,62 +540,60 @@ func (docker *Docker) CmdRun(stdin io.ReadCloser, stdout io.Writer, args ...stri
 		return nil
 	}
 	name, cmd := flags.Arg(0), flags.Args()[1:]
-	if container, exists := docker.findContainer(name); exists {
-		if container.Running {
-			return errors.New("Already running: " + name)
-		}
+	if container, exists := srv.findContainer(name); exists {
+		log.Printf("Running container %#v\n", container)
+		container.Path = cmd[0]
+		container.Args  = cmd[1:]
 		if *fl_attach {
-			return container.Run(cmd[0], cmd[1:], stdin, stdout, *fl_tty)
+			cmd_stdout, err := container.StdoutPipe()
+			if err != nil {
+				return err
+			}
+			cmd_stderr, err := container.StderrPipe()
+			if err != nil {
+				return err
+			}
+			if err := container.Start(); err != nil {
+				return err
+			}
+			sending_stdout := future.Go(func() error { _, err := io.Copy(stdout, cmd_stdout); return err })
+			sending_stderr := future.Go(func() error { _, err := io.Copy(stdout, cmd_stderr); return err })
+			err_sending_stdout := <-sending_stdout
+			err_sending_stderr := <-sending_stderr
+			if err_sending_stdout != nil {
+				return err_sending_stdout
+			}
+			return err_sending_stderr
 		} else {
-			go container.Run(cmd[0], cmd[1:], ioutil.NopCloser(new(bytes.Buffer)), ioutil.Discard, *fl_tty)
-			fmt.Fprintln(stdout, container.Id)
-			return nil
+			if output, err := container.Output(); err != nil {
+				return err
+			} else {
+				fmt.Printf("-->|%s|\n", output)
+			}
 		}
+		return nil
 	}
 	return errors.New("No such container: " + name)
 }
 
-func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) {
-	if interactive {
-		term, err := pty.Start(cmd)
-		if err != nil {
-			return nil, nil, err
-		}
-		return term, term, nil
-	}
-	stdin, err := cmd.StdinPipe()
-	if err != nil {
-		return nil, nil, err
-	}
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		return nil, nil, err
-	}
-	if err := cmd.Start(); err != nil {
-		return nil, nil, err
-	}
-	return stdin, stdout, nil
-}
-
-
 func main() {
 	future.Seed()
 	flag.Parse()
-	docker, err := New()
+	d, err := New()
 	if err != nil {
 		log.Fatal(err)
 	}
 	go func() {
-		if err := rcli.ListenAndServeHTTP(":8080", docker); err != nil {
+		if err := rcli.ListenAndServeHTTP(":8080", d); err != nil {
 			log.Fatal(err)
 		}
 	}()
-	if err := rcli.ListenAndServeTCP(":4242", docker); err != nil {
+	if err := rcli.ListenAndServeTCP(":4242", d); err != nil {
 		log.Fatal(err)
 	}
 }
 
-func New() (*Docker, error) {
+func New() (*Server, error) {
 	store, err := future.NewStore("/var/lib/docker/layers")
 	if err != nil {
 		return nil, err
@@ -427,20 +601,32 @@ func New() (*Docker, error) {
 	if err := store.Init(); err != nil {
 		return nil, err
 	}
-	return &Docker{
+	d, err := docker.New()
+	if err != nil {
+		return nil, err
+	}
+	srv := &Server{
 		containersByName: make(map[string]*ByDate),
-		containers: make(map[string]*Container),
 		layers: store,
-	}, nil
+		docker: d,
+	}
+	for _, container := range srv.docker.List() {
+		name := container.GetUserData("name")
+		if _, exists := srv.containersByName[name]; !exists {
+			srv.containersByName[name] = new(ByDate)
+		}
+		srv.containersByName[name].Add(container)
+	}
+	log.Printf("Done building index\n")
+	return srv, nil
 }
 
-
-func (docker *Docker) CmdMirror(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdMirror(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	_, err := io.Copy(stdout, stdin)
 	return err
 }
 
-func (docker *Docker) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	for {
 		if line, err := bufio.NewReader(stdin).ReadString('\n'); err == nil {
 			fmt.Printf("--- %s", line)
@@ -456,7 +642,7 @@ func (docker *Docker) CmdDebug(stdin io.ReadCloser, stdout io.Writer, args ...st
 	return nil
 }
 
-func (docker *Docker) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
+func (srv *Server) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...string) error {
 	flags := rcli.Subcmd(stdout, "web", "[OPTIONS]", "A web UI for docker")
 	showurl := flags.Bool("u", false, "Return the URL of the web UI")
 	if err := flags.Parse(args); err != nil {
@@ -475,85 +661,9 @@ func (docker *Docker) CmdWeb(stdin io.ReadCloser, stdout io.Writer, args ...stri
 }
 
 
-type Docker struct {
-	containers		map[string]*Container
+type Server struct {
 	containersByName	map[string]*ByDate
 	layers			*future.Store
-}
-
-type Container struct {
-	Id	string
-	Name	string
-	Created	time.Time
-	Source	string
-	Size	uint
-	FilesChanged uint
-	BytesChanged uint
-	Running	bool
-	Cmd	string
-	Args	[]string
-	stdoutLog *bytes.Buffer
-	stdinLog *bytes.Buffer
-}
-
-func (c *Container) Run(command string, args []string, stdin io.ReadCloser, stdout io.Writer, tty bool) error {
-	// Not thread-safe
-	if c.Running {
-		return errors.New("Already running")
-	}
-	c.Cmd = command
-	c.Args = args
-	// Reset logs
-	c.stdoutLog.Reset()
-	c.stdinLog.Reset()
-	cmd := exec.Command(c.Cmd, c.Args...)
-	cmd_stdin, cmd_stdout, err := startCommand(cmd, tty)
-	if err != nil {
-		return err
-	}
-	c.Running = true
-	// ADD FAKE RANDOM CHANGES
-	c.FilesChanged = fake.RandomFilesChanged()
-	c.BytesChanged = fake.RandomBytesChanged()
-	copy_out := future.Go(func() error {
-		_, err := io.Copy(io.MultiWriter(stdout, c.stdoutLog), cmd_stdout)
-		return err
-	})
-	future.Go(func() error {
-		_, err := io.Copy(io.MultiWriter(cmd_stdin, c.stdinLog), stdin)
-		cmd_stdin.Close()
-		stdin.Close()
-		return err
-	})
-	wait := future.Go(func() error {
-		err := cmd.Wait()
-		c.Running = false
-		return err
-	})
-	if err := <-copy_out; err != nil {
-		if c.Running {
-			return err
-		}
-	}
-	if err := <-wait; err != nil {
-		if status, ok := err.(*exec.ExitError); ok {
-			fmt.Fprintln(stdout, status)
-			return nil
-		}
-		return err
-	}
-	return nil
-}
-
-func (c *Container) StdoutLog() io.Reader {
-	return strings.NewReader(c.stdoutLog.String())
-}
-
-func (c *Container) StdinLog() io.Reader {
-	return strings.NewReader(c.stdinLog.String())
-}
-
-func (c *Container) CmdString() string {
-	return strings.Join(append([]string{c.Cmd}, c.Args...), " ")
+	docker			*docker.Docker
 }
 

+ 52 - 0
fake/fake.go

@@ -5,6 +5,10 @@ import (
 	"math/rand"
 	"io"
 	"archive/tar"
+	"github.com/dotcloud/docker"
+	"os/exec"
+	"strings"
+	"github.com/kr/pty"
 )
 
 
@@ -47,3 +51,51 @@ func RandomFilesChanged() uint {
 func RandomContainerSize() uint {
 	return uint(rand.Int31n(142 * 1024 * 1024))
 }
+
+func ContainerRunning() bool {
+	return false
+}
+
+type Container struct {
+	*docker.Container
+	Name	string
+	Source	string
+	Size	uint
+	FilesChanged uint
+	BytesChanged uint
+}
+
+func NewContainer(c *docker.Container) *Container {
+	return &Container{
+		Container:	c,
+		Name:		c.GetUserData("name"),
+	}
+}
+func (c *Container) CmdString() string {
+	return strings.Join(append([]string{c.Path}, c.Args...), " ")
+}
+
+
+func startCommand(cmd *exec.Cmd, interactive bool) (io.WriteCloser, io.ReadCloser, error) {
+	if interactive {
+		term, err := pty.Start(cmd)
+		if err != nil {
+			return nil, nil, err
+		}
+		return term, term, nil
+	}
+	stdin, err := cmd.StdinPipe()
+	if err != nil {
+		return nil, nil, err
+	}
+	stdout, err := cmd.StdoutPipe()
+	if err != nil {
+		return nil, nil, err
+	}
+	if err := cmd.Start(); err != nil {
+		return nil, nil, err
+	}
+	return stdin, stdout, nil
+}
+
+

+ 59 - 0
filesystem.go

@@ -8,6 +8,8 @@ import (
 	"strings"
 	"syscall"
 	"time"
+	"io"
+	"io/ioutil"
 )
 
 type Filesystem struct {
@@ -94,6 +96,24 @@ func (fs *Filesystem) IsMounted() bool {
 	return mntpointSt.Dev != parentSt.Dev
 }
 
+// Tar returns the contents of the filesystem as an uncompressed tar stream
+func (fs *Filesystem) Tar() (io.Reader, error) {
+	if err := fs.EnsureMounted(); err != nil {
+		return nil, err
+	}
+	return Tar(fs.RootFS)
+}
+
+
+func (fs *Filesystem) EnsureMounted() error {
+	if !fs.IsMounted() {
+		if err := fs.Mount(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 type ChangeType int
 
 const (
@@ -107,6 +127,16 @@ type Change struct {
 	Kind ChangeType
 }
 
+func (change *Change) String() string {
+	var kind string
+	switch change.Kind {
+		case ChangeModify:	kind = "C"
+		case ChangeAdd:		kind = "A"
+		case ChangeDelete:	kind = "D"
+	}
+	return fmt.Sprintf("%s %s", kind, change.Path)
+}
+
 func (fs *Filesystem) Changes() ([]Change, error) {
 	var changes []Change
 	err := filepath.Walk(fs.RWPath, func(path string, f os.FileInfo, err error) error {
@@ -179,6 +209,35 @@ func (fs *Filesystem) Changes() ([]Change, error) {
 	return changes, nil
 }
 
+// Reset removes all changes to the filesystem, reverting it to its initial state.
+func (fs *Filesystem) Reset() error {
+	if err := os.RemoveAll(fs.RWPath); err != nil {
+		return err
+	}
+	// We removed the RW directory itself along with its content: let's re-create an empty one.
+	if err := fs.createMountPoints(); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Open opens the named file for reading.
+func (fs *Filesystem) OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
+	if err := fs.EnsureMounted(); err != nil {
+		return nil, err
+	}
+	return os.OpenFile(filepath.Join(fs.RootFS, path), flag, perm)
+}
+
+// ReadDir reads the directory named by dirname, relative to the Filesystem's root,
+// and returns a list of sorted directory entries
+func (fs *Filesystem) ReadDir(dirname string) ([]os.FileInfo, error) {
+	if err := fs.EnsureMounted(); err != nil {
+		return nil, err
+	}
+	return ioutil.ReadDir(filepath.Join(fs.RootFS, dirname))
+}
+
 func newFilesystem(rootfs string, rwpath string, layers []string) *Filesystem {
 	return &Filesystem{
 		RootFS: rootfs,

+ 30 - 0
utils.go

@@ -5,8 +5,38 @@ import (
 	"container/list"
 	"io"
 	"sync"
+	"os/exec"
 )
 
+// Tar generates a tar archive from a filesystem path, and returns it as a stream.
+// Path must point to a directory.
+
+func Tar(path string) (io.Reader, error) {
+	cmd := exec.Command("tar", "-C", path, "-c", ".")
+	output, err := cmd.StdoutPipe()
+	if err != nil {
+		return nil, err
+	}
+	if err := cmd.Start(); err != nil {
+		return nil, err
+	}
+	// FIXME: errors will not be passed because we don't wait for the command.
+	// Instead, consumers will hit EOF right away.
+	// This can be fixed by waiting for the process to exit, or for the first write
+	// on stdout, whichever comes first.
+	return output, nil
+}
+
+type nopWriteCloser struct {
+	io.Writer
+}
+
+func (w *nopWriteCloser) Close() error { return nil }
+
+func NopWriteCloser(w io.Writer) io.WriteCloser {
+	return &nopWriteCloser{w}
+}
+
 type bufReader struct {
 	buf    *bytes.Buffer
 	reader io.Reader