Selaa lähdekoodia

Merge pull request #1794 from justone/add-images-tree

add -tree option to images
Andy Rothfusz 11 vuotta sitten
vanhempi
commit
807a305f36
13 muutettua tiedostoa jossa 1696 lisäystä ja 80 poistoa
  1. 16 2
      api.go
  2. 31 0
      api_params.go
  3. 3 28
      api_test.go
  4. 129 18
      commands.go
  5. 126 0
      commands_test.go
  6. 106 2
      docs/sources/api/docker_remote_api.rst
  7. 1185 0
      docs/sources/api/docker_remote_api_v1.7.rst
  8. 33 1
      docs/sources/commandline/cli.rst
  9. 1 0
      runtime_test.go
  10. 31 12
      server.go
  11. 9 9
      server_test.go
  12. 1 1
      sorter.go
  13. 25 7
      sorter_test.go

+ 16 - 2
api.go

@@ -23,7 +23,7 @@ import (
 )
 
 const (
-	APIVERSION        = 1.6
+	APIVERSION        = 1.7
 	DEFAULTHTTPHOST   = "127.0.0.1"
 	DEFAULTHTTPPORT   = 4243
 	DEFAULTUNIXSOCKET = "/var/run/docker.sock"
@@ -191,10 +191,24 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.
 		return err
 	}
 
-	return writeJSON(w, http.StatusOK, outs)
+	if version < 1.7 {
+		outs2 := []APIImagesOld{}
+		for _, ctnr := range outs {
+			outs2 = append(outs2, ctnr.ToLegacy()...)
+		}
+
+		return writeJSON(w, http.StatusOK, outs2)
+	} else {
+		return writeJSON(w, http.StatusOK, outs)
+	}
 }
 
 func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	if version > 1.6 {
+		w.WriteHeader(http.StatusNotFound)
+		return fmt.Errorf("This is now implemented in the client.")
+	}
+
 	if err := srv.ImagesViz(w); err != nil {
 		return err
 	}

+ 31 - 0
api_params.go

@@ -1,5 +1,7 @@
 package docker
 
+import "strings"
+
 type APIHistory struct {
 	ID        string   `json:"Id"`
 	Tags      []string `json:",omitempty"`
@@ -9,6 +11,15 @@ type APIHistory struct {
 }
 
 type APIImages struct {
+	ID          string   `json:"Id"`
+	RepoTags    []string `json:",omitempty"`
+	Created     int64
+	Size        int64
+	VirtualSize int64
+	ParentId    string `json:",omitempty"`
+}
+
+type APIImagesOld struct {
 	Repository  string `json:",omitempty"`
 	Tag         string `json:",omitempty"`
 	ID          string `json:"Id"`
@@ -17,6 +28,26 @@ type APIImages struct {
 	VirtualSize int64
 }
 
+func (self *APIImages) ToLegacy() []APIImagesOld {
+
+	outs := []APIImagesOld{}
+	for _, repotag := range self.RepoTags {
+
+		components := strings.SplitN(repotag, ":", 2)
+
+		outs = append(outs, APIImagesOld{
+			ID:          self.ID,
+			Repository:  components[0],
+			Tag:         components[1],
+			Created:     self.Created,
+			Size:        self.Size,
+			VirtualSize: self.VirtualSize,
+		})
+	}
+
+	return outs
+}
+
 type APIInfo struct {
 	Debug              bool
 	Containers         int

+ 3 - 28
api_test.go

@@ -184,7 +184,7 @@ func TestGetImagesJSON(t *testing.T) {
 
 	found := false
 	for _, img := range images {
-		if img.Repository == unitTestImageName {
+		if strings.Contains(img.RepoTags[0], unitTestImageName) {
 			found = true
 			break
 		}
@@ -275,31 +275,6 @@ func TestGetImagesJSON(t *testing.T) {
 	}
 }
 
-func TestGetImagesViz(t *testing.T) {
-	runtime := mkRuntime(t)
-	defer nuke(runtime)
-
-	srv := &Server{runtime: runtime}
-
-	r := httptest.NewRecorder()
-	if err := getImagesViz(srv, APIVERSION, r, nil, nil); err != nil {
-		t.Fatal(err)
-	}
-
-	if r.Code != http.StatusOK {
-		t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code)
-	}
-
-	reader := bufio.NewReader(r.Body)
-	line, err := reader.ReadString('\n')
-	if err != nil {
-		t.Fatal(err)
-	}
-	if line != "digraph docker {\n" {
-		t.Errorf("Expected digraph docker {\n, %s found", line)
-	}
-}
-
 func TestGetImagesHistory(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
@@ -1226,7 +1201,7 @@ func TestDeleteImages(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(images) != len(initialImages)+1 {
+	if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
 		t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
 	}
 
@@ -1265,7 +1240,7 @@ func TestDeleteImages(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(images) != len(initialImages) {
+	if len(images[0].RepoTags) != len(initialImages[0].RepoTags) {
 		t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
 	}
 

+ 129 - 18
commands.go

@@ -1057,6 +1057,7 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 	all := cmd.Bool("a", false, "show all images")
 	noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
 	flViz := cmd.Bool("viz", false, "output graph in graphviz format")
+	flTree := cmd.Bool("tree", false, "output graph in tree format")
 
 	if err := cmd.Parse(args); err != nil {
 		return nil
@@ -1067,11 +1068,77 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 	}
 
 	if *flViz {
-		body, _, err := cli.call("GET", "/images/viz", false)
+		body, _, err := cli.call("GET", "/images/json?all=1", nil)
 		if err != nil {
 			return err
 		}
-		fmt.Fprintf(cli.out, "%s", body)
+
+		var outs []APIImages
+		err = json.Unmarshal(body, &outs)
+		if err != nil {
+			return err
+		}
+
+		fmt.Fprintf(cli.out, "digraph docker {\n")
+
+		for _, image := range outs {
+			if image.ParentId == "" {
+				fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", utils.TruncateID(image.ID))
+			} else {
+				fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", utils.TruncateID(image.ParentId), utils.TruncateID(image.ID))
+			}
+			if image.RepoTags[0] != "<none>:<none>" {
+				fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", utils.TruncateID(image.ID), utils.TruncateID(image.ID), strings.Join(image.RepoTags, "\\n"))
+			}
+		}
+
+		fmt.Fprintf(cli.out, " base [style=invisible]\n}\n")
+	} else if *flTree {
+		body, _, err := cli.call("GET", "/images/json?all=1", nil)
+		if err != nil {
+			return err
+		}
+
+		var outs []APIImages
+		err = json.Unmarshal(body, &outs)
+		if err != nil {
+			return err
+		}
+
+		var startImageArg = cmd.Arg(0)
+		var startImage APIImages
+
+		var roots []APIImages
+		var byParent = make(map[string][]APIImages)
+		for _, image := range outs {
+			if image.ParentId == "" {
+				roots = append(roots, image)
+			} else {
+				if children, exists := byParent[image.ParentId]; exists {
+					byParent[image.ParentId] = append(children, image)
+				} else {
+					byParent[image.ParentId] = []APIImages{image}
+				}
+			}
+
+			if startImageArg != "" {
+				if startImageArg == image.ID || startImageArg == utils.TruncateID(image.ID) {
+					startImage = image
+				}
+
+				for _, repotag := range image.RepoTags {
+					if repotag == startImageArg {
+						startImage = image
+					}
+				}
+			}
+		}
+
+		if startImageArg != "" {
+			WalkTree(cli, noTrunc, []APIImages{startImage}, byParent, "")
+		} else {
+			WalkTree(cli, noTrunc, roots, byParent, "")
+		}
 	} else {
 		v := url.Values{}
 		if cmd.NArg() == 1 {
@@ -1097,27 +1164,29 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 			fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE")
 		}
 
+		var repo string
+		var tag string
 		for _, out := range outs {
-			if out.Repository == "" {
-				out.Repository = "<none>"
-			}
-			if out.Tag == "" {
-				out.Tag = "<none>"
-			}
+			for _, repotag := range out.RepoTags {
 
-			if !*noTrunc {
-				out.ID = utils.TruncateID(out.ID)
-			}
+				components := strings.SplitN(repotag, ":", 2)
+				repo = components[0]
+				tag = components[1]
+
+				if !*noTrunc {
+					out.ID = utils.TruncateID(out.ID)
+				}
 
-			if !*quiet {
-				fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", out.Repository, out.Tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
-				if out.VirtualSize > 0 {
-					fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
+				if !*quiet {
+					fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t", repo, tag, out.ID, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))))
+					if out.VirtualSize > 0 {
+						fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.Size), utils.HumanSize(out.VirtualSize))
+					} else {
+						fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
+					}
 				} else {
-					fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size))
+					fmt.Fprintln(w, out.ID)
 				}
-			} else {
-				fmt.Fprintln(w, out.ID)
 			}
 		}
 
@@ -1128,6 +1197,48 @@ func (cli *DockerCli) CmdImages(args ...string) error {
 	return nil
 }
 
+func WalkTree(cli *DockerCli, noTrunc *bool, images []APIImages, byParent map[string][]APIImages, prefix string) {
+	if len(images) > 1 {
+		length := len(images)
+		for index, image := range images {
+			if index+1 == length {
+				PrintTreeNode(cli, noTrunc, image, prefix+"└─")
+				if subimages, exists := byParent[image.ID]; exists {
+					WalkTree(cli, noTrunc, subimages, byParent, prefix+"  ")
+				}
+			} else {
+				PrintTreeNode(cli, noTrunc, image, prefix+"|─")
+				if subimages, exists := byParent[image.ID]; exists {
+					WalkTree(cli, noTrunc, subimages, byParent, prefix+"| ")
+				}
+			}
+		}
+	} else {
+		for _, image := range images {
+			PrintTreeNode(cli, noTrunc, image, prefix+"└─")
+			if subimages, exists := byParent[image.ID]; exists {
+				WalkTree(cli, noTrunc, subimages, byParent, prefix+"  ")
+			}
+		}
+	}
+}
+
+func PrintTreeNode(cli *DockerCli, noTrunc *bool, image APIImages, prefix string) {
+	var imageID string
+	if *noTrunc {
+		imageID = image.ID
+	} else {
+		imageID = utils.TruncateID(image.ID)
+	}
+
+	fmt.Fprintf(cli.out, "%s%s Size: %s (virtual %s)", prefix, imageID, utils.HumanSize(image.Size), utils.HumanSize(image.VirtualSize))
+	if image.RepoTags[0] != "<none>:<none>" {
+		fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ","))
+	} else {
+		fmt.Fprint(cli.out, "\n")
+	}
+}
+
 func displayablePorts(ports []APIPort) string {
 	result := []string{}
 	for _, port := range ports {

+ 126 - 0
commands_test.go

@@ -6,6 +6,7 @@ import (
 	"github.com/dotcloud/docker/utils"
 	"io"
 	"io/ioutil"
+	"regexp"
 	"strings"
 	"testing"
 	"time"
@@ -699,3 +700,128 @@ func TestRunErrorBindNonExistingSource(t *testing.T) {
 		<-c
 	})
 }
+
+func TestImagesViz(t *testing.T) {
+	stdout, stdoutPipe := io.Pipe()
+
+	cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
+	defer cleanup(globalRuntime)
+
+	srv := &Server{runtime: globalRuntime}
+	image := buildTestImages(t, srv)
+
+	c := make(chan struct{})
+	go func() {
+		defer close(c)
+		if err := cli.CmdImages("-viz"); err != nil {
+			t.Fatal(err)
+		}
+		stdoutPipe.Close()
+	}()
+
+	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
+		cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout))
+		if err != nil {
+			t.Fatal(err)
+		}
+		cmdOutput := string(cmdOutputBytes)
+
+		regexpStrings := []string{
+			"digraph docker {",
+			fmt.Sprintf("base -> \"%s\" \\[style=invis]", unitTestImageIDShort),
+			fmt.Sprintf("label=\"%s\\\\n%s:latest\"", unitTestImageIDShort, unitTestImageName),
+			fmt.Sprintf("label=\"%s\\\\n%s:%s\"", utils.TruncateID(image.ID), "test", "latest"),
+			"base \\[style=invisible]",
+		}
+
+		compiledRegexps := []*regexp.Regexp{}
+		for _, regexpString := range regexpStrings {
+			regexp, err := regexp.Compile(regexpString)
+			if err != nil {
+				fmt.Println("Error in regex string: ", err)
+				return
+			}
+			compiledRegexps = append(compiledRegexps, regexp)
+		}
+
+		for _, regexp := range compiledRegexps {
+			if !regexp.MatchString(cmdOutput) {
+				t.Fatalf("images -viz content '%s' did not match regexp '%s'", cmdOutput, regexp)
+			}
+		}
+	})
+}
+
+func TestImagesTree(t *testing.T) {
+	stdout, stdoutPipe := io.Pipe()
+
+	cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
+	defer cleanup(globalRuntime)
+
+	srv := &Server{runtime: globalRuntime}
+	image := buildTestImages(t, srv)
+
+	c := make(chan struct{})
+	go func() {
+		defer close(c)
+		if err := cli.CmdImages("-tree"); err != nil {
+			t.Fatal(err)
+		}
+		stdoutPipe.Close()
+	}()
+
+	setTimeout(t, "Reading command output time out", 2*time.Second, func() {
+		cmdOutputBytes, err := ioutil.ReadAll(bufio.NewReader(stdout))
+		if err != nil {
+			t.Fatal(err)
+		}
+		cmdOutput := string(cmdOutputBytes)
+
+		regexpStrings := []string{
+			fmt.Sprintf("└─%s Size: (\\d+.\\d+ MB) \\(virtual \\d+.\\d+ MB\\) Tags: %s:latest", unitTestImageIDShort, unitTestImageName),
+			"(?m)^  └─[0-9a-f]+",
+			"(?m)^    └─[0-9a-f]+",
+			"(?m)^      └─[0-9a-f]+",
+			fmt.Sprintf("        └─%s Size: \\d+ B \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)),
+		}
+
+		compiledRegexps := []*regexp.Regexp{}
+		for _, regexpString := range regexpStrings {
+			regexp, err := regexp.Compile(regexpString)
+			if err != nil {
+				fmt.Println("Error in regex string: ", err)
+				return
+			}
+			compiledRegexps = append(compiledRegexps, regexp)
+		}
+
+		for _, regexp := range compiledRegexps {
+			if !regexp.MatchString(cmdOutput) {
+				t.Fatalf("images -tree content '%s' did not match regexp '%s'", cmdOutput, regexp)
+			}
+		}
+	})
+}
+
+func buildTestImages(t *testing.T, srv *Server) *Image {
+
+	var testBuilder = testContextTemplate{
+		`
+from   {IMAGE}
+run    sh -c 'echo root:testpass > /tmp/passwd'
+run    mkdir -p /var/run/sshd
+run    [ "$(cat /tmp/passwd)" = "root:testpass" ]
+run    [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
+`,
+		nil,
+		nil,
+	}
+	image := buildImage(testBuilder, t, srv, true)
+
+	err := srv.ContainerTag(image.ID, "test", "latest", false)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return image
+}

+ 106 - 2
docs/sources/api/docker_remote_api.rst

@@ -26,14 +26,118 @@ Docker Remote API
 2. Versions
 ===========
 
-The current version of the API is 1.6
+The current version of the API is 1.7
 
 Calling /images/<name>/insert is the same as calling
-/v1.6/images/<name>/insert
+/v1.7/images/<name>/insert
 
 You can still call an old version of the api using
 /v1.0/images/<name>/insert
 
+v1.7
+****
+
+Full Documentation
+------------------
+
+:doc:`docker_remote_api_v1.7`
+
+What's new
+----------
+
+.. http:get:: /images/json
+
+   The format of the json returned from this uri changed.  Instead of an entry
+   for each repo/tag on an image, each image is only represented once, with a
+   nested attribute indicating the repo/tags that apply to that image.
+
+   Instead of:
+
+   .. sourcecode:: http
+
+      HTTP/1.1 200 OK
+      Content-Type: application/json
+
+      [
+        {
+          "VirtualSize": 131506275,
+          "Size": 131506275,
+          "Created": 1365714795,
+          "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+          "Tag": "12.04",
+          "Repository": "ubuntu"
+        },
+        {
+          "VirtualSize": 131506275,
+          "Size": 131506275,
+          "Created": 1365714795,
+          "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+          "Tag": "latest",
+          "Repository": "ubuntu"
+        },
+        {
+          "VirtualSize": 131506275,
+          "Size": 131506275,
+          "Created": 1365714795,
+          "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+          "Tag": "precise",
+          "Repository": "ubuntu"
+        },
+        {
+          "VirtualSize": 180116135,
+          "Size": 24653,
+          "Created": 1364102658,
+          "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+          "Tag": "12.10",
+          "Repository": "ubuntu"
+        },
+        {
+          "VirtualSize": 180116135,
+          "Size": 24653,
+          "Created": 1364102658,
+          "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+          "Tag": "quantal",
+          "Repository": "ubuntu"
+        }
+      ]
+
+   The returned json looks like this:
+
+   .. sourcecode:: http
+
+      HTTP/1.1 200 OK
+      Content-Type: application/json
+      
+      [
+        {
+           "RepoTag": [
+             "ubuntu:12.04",
+             "ubuntu:precise",
+             "ubuntu:latest"
+           ],
+           "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+           "Created": 1365714795,
+           "Size": 131506275,
+           "VirtualSize": 131506275
+        },
+        {
+           "RepoTag": [
+             "ubuntu:12.10",
+             "ubuntu:quantal"
+           ],
+           "ParentId": "27cf784147099545",
+           "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+           "Created": 1364102658,
+           "Size": 24653,
+           "VirtualSize": 180116135
+        }
+      ]
+
+.. http:get:: /images/viz
+
+   This URI no longer exists.  The ``images -viz`` output is now generated in
+   the client, using the ``/images/json`` data.
+
 v1.6
 ****
 

+ 1185 - 0
docs/sources/api/docker_remote_api_v1.7.rst

@@ -0,0 +1,1185 @@
+:title: Remote API v1.7
+:description: API Documentation for Docker
+:keywords: API, Docker, rcli, REST, documentation
+
+:orphan:
+
+======================
+Docker Remote API v1.7
+======================
+
+.. contents:: Table of Contents
+
+1. Brief introduction
+=====================
+
+- The Remote API is replacing rcli
+- Default port in the docker daemon is 4243
+- 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
+
+2. Endpoints
+============
+
+2.1 Containers
+--------------
+
+List containers
+***************
+
+.. http:get:: /containers/json
+
+	List containers
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Id": "8dfafdbc3a40",
+			"Image": "base:latest",
+			"Command": "echo 1",
+			"Created": 1367854155,
+			"Status": "Exit 0",
+			"Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
+			"SizeRw":12288,
+			"SizeRootFs":0
+		},
+		{
+			"Id": "9cd87474be90",
+			"Image": "base:latest",
+			"Command": "echo 222222",
+			"Created": 1367854155,
+			"Status": "Exit 0",
+			"Ports":[],
+			"SizeRw":12288,
+			"SizeRootFs":0
+		},
+		{
+			"Id": "3176a2479c92",
+			"Image": "base:latest",
+			"Command": "echo 3333333333333333",
+			"Created": 1367854154,
+			"Status": "Exit 0",
+			"Ports":[],
+			"SizeRw":12288,
+			"SizeRootFs":0
+		},
+		{
+			"Id": "4cb07b47f9fb",
+			"Image": "base:latest",
+			"Command": "echo 444444444444444444444444444444444",
+			"Created": 1367854152,
+			"Status": "Exit 0",
+			"Ports":[],
+			"SizeRw":12288,
+			"SizeRootFs":0
+		}
+	   ]
+ 
+	:query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default
+	:query limit: Show ``limit`` last created containers, include non-running ones.
+	:query since: Show only containers created since Id, include non-running ones.
+	:query before: Show only containers created before Id, include non-running ones.
+	:query size: 1/True/true or 0/False/false, Show the containers sizes
+	:statuscode 200: no error
+	:statuscode 400: bad parameter
+	:statuscode 500: server error
+
+
+Create a container
+******************
+
+.. http:post:: /containers/create
+
+	Create a container
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/create HTTP/1.1
+	   Content-Type: application/json
+
+	   {
+		"Hostname":"",
+		"User":"",
+		"Memory":0,
+		"MemorySwap":0,
+		"AttachStdin":false,
+		"AttachStdout":true,
+		"AttachStderr":true,
+		"PortSpecs":null,
+		"Privileged": false,
+		"Tty":false,
+		"OpenStdin":false,
+		"StdinOnce":false,
+		"Env":null,
+		"Cmd":[
+			"date"
+		],
+		"Dns":null,
+		"Image":"base",
+		"Volumes":{},
+		"VolumesFrom":"",
+		"WorkingDir":""
+
+	   }
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 201 OK
+	   Content-Type: application/json
+
+	   {
+		"Id":"e90e34656806"
+		"Warnings":[]
+	   }
+	
+	:jsonparam config: the container's configuration
+	:statuscode 201: no error
+	:statuscode 404: no such container
+	:statuscode 406: impossible to attach (container not running)
+	:statuscode 500: server error
+
+
+Inspect a container
+*******************
+
+.. http:get:: /containers/(id)/json
+
+	Return low-level information on the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/json HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+			"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
+			"Created": "2013-05-07T14:51:42.041847+02:00",
+			"Path": "date",
+			"Args": [],
+			"Config": {
+				"Hostname": "4fa6e0f0c678",
+				"User": "",
+				"Memory": 0,
+				"MemorySwap": 0,
+				"AttachStdin": false,
+				"AttachStdout": true,
+				"AttachStderr": true,
+				"PortSpecs": null,
+				"Tty": false,
+				"OpenStdin": false,
+				"StdinOnce": false,
+				"Env": null,
+				"Cmd": [
+					"date"
+				],
+				"Dns": null,
+				"Image": "base",
+				"Volumes": {},
+				"VolumesFrom": "",
+				"WorkingDir":""
+
+			},
+			"State": {
+				"Running": false,
+				"Pid": 0,
+				"ExitCode": 0,
+				"StartedAt": "2013-05-07T14:51:42.087658+02:01360",
+				"Ghost": false
+			},
+			"Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+			"NetworkSettings": {
+				"IpAddress": "",
+				"IpPrefixLen": 0,
+				"Gateway": "",
+				"Bridge": "",
+				"PortMapping": null
+			},
+			"SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker",
+			"ResolvConfPath": "/etc/resolv.conf",
+			"Volumes": {}
+	   }
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+List processes running inside a container
+*****************************************
+
+.. http:get:: /containers/(id)/top
+
+	List processes running inside the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/top HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"Titles":[
+			"USER",
+			"PID",
+			"%CPU",
+			"%MEM",
+			"VSZ",
+			"RSS",
+			"TTY",
+			"STAT",
+			"START",
+			"TIME",
+			"COMMAND"
+			],
+		"Processes":[
+			["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"],
+			["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"]
+		]
+	   }
+
+	:query ps_args: ps arguments to use (eg. aux)
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Inspect changes on a container's filesystem
+*******************************************
+
+.. http:get:: /containers/(id)/changes
+
+	Inspect changes on container ``id`` 's filesystem
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/changes HTTP/1.1
+
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Path":"/dev",
+			"Kind":0
+		},
+		{
+			"Path":"/dev/kmsg",
+			"Kind":1
+		},
+		{
+			"Path":"/test",
+			"Kind":1
+		}
+	   ]
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Export a container
+******************
+
+.. http:get:: /containers/(id)/export
+
+	Export the contents of container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /containers/4fa6e0f0c678/export HTTP/1.1
+
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/octet-stream
+	   
+	   {{ STREAM }}
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Start a container
+*****************
+
+.. http:post:: /containers/(id)/start
+
+        Start the container ``id``
+
+        **Example request**:
+
+        .. sourcecode:: http
+
+           POST /containers/(id)/start HTTP/1.1
+           Content-Type: application/json
+
+           {
+                "Binds":["/tmp:/tmp"],
+                "LxcConf":{"lxc.utsname":"docker"}
+           }
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 204 No Content
+           Content-Type: text/plain
+
+        :jsonparam hostConfig: the container's host configuration (optional)
+        :statuscode 204: no error
+        :statuscode 404: no such container
+        :statuscode 500: server error
+
+
+Stop a container
+****************
+
+.. http:post:: /containers/(id)/stop
+
+	Stop the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/stop?t=5 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+	   	
+	:query t: number of seconds to wait before killing the container
+	:statuscode 204: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Restart a container
+*******************
+
+.. http:post:: /containers/(id)/restart
+
+	Restart the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/restart?t=5 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+	   	
+	:query t: number of seconds to wait before killing the container
+	:statuscode 204: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Kill a container
+****************
+
+.. http:post:: /containers/(id)/kill
+
+	Kill the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/e90e34656806/kill HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+	   	
+	:statuscode 204: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Attach to a container
+*********************
+
+.. http:post:: /containers/(id)/attach
+
+	Attach to the container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/vnd.docker.raw-stream
+
+	   {{ STREAM }}
+	   	
+	:query logs: 1/True/true or 0/False/false, return logs. Default false
+	:query stream: 1/True/true or 0/False/false, return stream. Default false
+	:query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false
+	:query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false
+	:query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false
+	:statuscode 200: no error
+	:statuscode 400: bad parameter
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+	**Stream details**:
+
+	When using the TTY setting is enabled in
+	:http:post:`/containers/create`, the stream is the raw data
+	from the process PTY and client's stdin.  When the TTY is
+	disabled, then the stream is multiplexed to separate stdout
+	and stderr.
+
+	The format is a **Header** and a **Payload** (frame).
+
+	**HEADER**
+
+	The header will contain the information on which stream write
+	the stream (stdout or stderr). It also contain the size of
+	the associated frame encoded on the last 4 bytes (uint32).
+
+	It is encoded on the first 8 bytes like this::
+
+	    header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
+
+	``STREAM_TYPE`` can be:
+
+	- 0: stdin (will be writen on stdout)
+	- 1: stdout
+	- 2: stderr
+
+	``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian.
+
+	**PAYLOAD**
+
+	The payload is the raw stream.
+
+	**IMPLEMENTATION**
+
+	The simplest way to implement the Attach protocol is the following:
+
+	1) Read 8 bytes
+	2) chose stdout or stderr depending on the first byte
+	3) Extract the frame size from the last 4 byets
+	4) Read the extracted size and output it on the correct output
+	5) Goto 1)
+
+
+
+Wait a container
+****************
+
+.. http:post:: /containers/(id)/wait
+
+	Block until container ``id`` stops, then returns the exit code
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/16253994b7c4/wait HTTP/1.1
+	   
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {"StatusCode":0}
+	   	
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+Remove a container
+*******************
+
+.. http:delete:: /containers/(id)
+
+	Remove the container ``id`` from the filesystem
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           DELETE /containers/16253994b7c4?v=1 HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+	   HTTP/1.1 204 OK
+
+	:query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false
+        :statuscode 204: no error
+	:statuscode 400: bad parameter
+        :statuscode 404: no such container
+        :statuscode 500: server error
+
+
+Copy files or folders from a container
+**************************************
+
+.. http:post:: /containers/(id)/copy
+
+	Copy files or folders of container ``id``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   POST /containers/4fa6e0f0c678/copy HTTP/1.1
+	   Content-Type: application/json
+
+	   {
+		"Resource":"test.txt"
+	   }
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/octet-stream
+	   
+	   {{ STREAM }}
+
+	:statuscode 200: no error
+	:statuscode 404: no such container
+	:statuscode 500: server error
+
+
+2.2 Images
+----------
+
+List Images
+***********
+
+.. http:get:: /images/json
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /images/json?all=0 HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+	     {
+	   	"RepoTag": [
+	   	  "ubuntu:12.04",
+	   	  "ubuntu:precise",
+	   	  "ubuntu:latest"
+	   	],
+	   	"Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c",
+	   	"Created": 1365714795,
+	   	"Size": 131506275,
+	   	"VirtualSize": 131506275
+	     },
+	     {
+	   	"RepoTag": [
+	   	  "ubuntu:12.10",
+	   	  "ubuntu:quantal"
+	   	],
+	   	"ParentId": "27cf784147099545",
+	   	"Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+	   	"Created": 1364102658,
+	   	"Size": 24653,
+	   	"VirtualSize": 180116135
+	     }
+	   ]
+
+
+Create an image
+***************
+
+.. http:post:: /images/create
+
+	Create an image, either by pull it from the registry or by importing it
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /images/create?fromImage=base HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {"status":"Pulling..."}
+	   {"status":"Pulling", "progress":"1/? (n/a)"}
+	   {"error":"Invalid..."}
+	   ...
+
+	When using this endpoint to pull an image from the registry,
+	the ``X-Registry-Auth`` header can be used to include a
+	base64-encoded AuthConfig object.
+
+        :query fromImage: name of the image to pull
+	:query fromSrc: source to import, - means stdin
+        :query repo: repository
+	:query tag: tag
+	:query registry: the registry to pull from
+        :statuscode 200: no error
+        :statuscode 500: server error
+
+
+Insert a file in an image
+*************************
+
+.. http:post:: /images/(name)/insert
+
+	Insert a file from ``url`` in the image ``name`` at ``path``
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           POST /images/test/insert?path=/usr&url=myurl HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {"status":"Inserting..."}
+	   {"status":"Inserting", "progress":"1/? (n/a)"}
+	   {"error":"Invalid..."}
+	   ...
+
+	:statuscode 200: no error
+        :statuscode 500: server error
+
+
+Inspect an image
+****************
+
+.. http:get:: /images/(name)/json
+
+	Return low-level information on the image ``name``
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   GET /images/base/json HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
+		"parent":"27cf784147099545",
+		"created":"2013-03-23T22:24:18.818426-07:00",
+		"container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0",
+		"container_config":
+			{
+				"Hostname":"",
+				"User":"",
+				"Memory":0,
+				"MemorySwap":0,
+				"AttachStdin":false,
+				"AttachStdout":false,
+				"AttachStderr":false,
+				"PortSpecs":null,
+				"Tty":true,
+				"OpenStdin":true,
+				"StdinOnce":false,
+				"Env":null,
+				"Cmd": ["/bin/bash"]
+				,"Dns":null,
+				"Image":"base",
+				"Volumes":null,
+				"VolumesFrom":"",
+				"WorkingDir":""
+			},
+		"Size": 6824592
+	   }
+
+	:statuscode 200: no error
+	:statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Get the history of an image
+***************************
+
+.. http:get:: /images/(name)/history
+
+        Return the history of the image ``name``
+
+        **Example request**:
+
+        .. sourcecode:: http
+
+           GET /images/base/history HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   [
+		{
+			"Id":"b750fe79269d",
+			"Created":1364102658,
+			"CreatedBy":"/bin/bash"
+		},
+		{
+			"Id":"27cf78414709",
+			"Created":1364068391,
+			"CreatedBy":""
+		}
+	   ]
+
+        :statuscode 200: no error
+        :statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Push an image on the registry
+*****************************
+
+.. http:post:: /images/(name)/push
+
+   Push the image ``name`` on the registry
+
+   **Example request**:
+
+   .. sourcecode:: http
+
+      POST /images/test/push HTTP/1.1
+
+   **Example response**:
+
+   .. sourcecode:: http
+
+    HTTP/1.1 200 OK
+    Content-Type: application/json
+
+   {"status":"Pushing..."}
+   {"status":"Pushing", "progress":"1/? (n/a)"}
+   {"error":"Invalid..."}
+   ...
+
+	The ``X-Registry-Auth`` header can be used to include a
+	base64-encoded AuthConfig object.
+
+   :query registry: the registry you wan to push, optional
+   :statuscode 200: no error
+        :statuscode 404: no such image
+        :statuscode 500: server error
+
+
+Tag an image into a repository
+******************************
+
+.. http:post:: /images/(name)/tag
+
+	Tag the image ``name`` into a repository
+
+        **Example request**:
+
+        .. sourcecode:: http
+			
+	   POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+
+	:query repo: The repository to tag in
+	:query force: 1/True/true or 0/False/false, default false
+	:statuscode 200: no error
+	:statuscode 400: bad parameter
+	:statuscode 404: no such image
+	:statuscode 409: conflict
+        :statuscode 500: server error
+
+
+Remove an image
+***************
+
+.. http:delete:: /images/(name)
+
+	Remove the image ``name`` from the filesystem 
+	
+	**Example request**:
+
+	.. sourcecode:: http
+
+	   DELETE /images/test HTTP/1.1
+
+	**Example response**:
+
+        .. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-type: application/json
+
+	   [
+	    {"Untagged":"3e2f21a89f"},
+	    {"Deleted":"3e2f21a89f"},
+	    {"Deleted":"53b4f83ac9"}
+	   ]
+
+	:statuscode 200: no error
+        :statuscode 404: no such image
+	:statuscode 409: conflict
+        :statuscode 500: server error
+
+
+Search images
+*************
+
+.. http:get:: /images/search
+
+	Search for an image in the docker index
+	
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /images/search?term=sshd HTTP/1.1
+
+	**Example response**:
+
+	.. sourcecode:: http
+
+	   HTTP/1.1 200 OK
+	   Content-Type: application/json
+	   
+	   [
+		{
+			"Name":"cespare/sshd",
+			"Description":""
+		},
+		{
+			"Name":"johnfuller/sshd",
+			"Description":""
+		},
+		{
+			"Name":"dhrp/mongodb-sshd",
+			"Description":""
+		}
+	   ]
+
+	   :query term: term to search
+	   :statuscode 200: no error
+	   :statuscode 500: server error
+
+
+2.3 Misc
+--------
+
+Build an image from Dockerfile via stdin
+****************************************
+
+.. http:post:: /build
+
+   Build an image from Dockerfile via stdin
+
+   **Example request**:
+
+   .. sourcecode:: http
+
+      POST /build HTTP/1.1
+
+      {{ STREAM }}
+
+   **Example response**:
+
+   .. sourcecode:: http
+
+      HTTP/1.1 200 OK
+
+      {{ STREAM }}
+
+
+       The stream must be a tar archive compressed with one of the following algorithms:
+       identity (no compression), gzip, bzip2, xz. The archive must include a file called
+       `Dockerfile` at its root. It may include any number of other files, which will be
+       accessible in the build context (See the ADD build command).
+
+       The Content-type header should be set to "application/tar".
+
+	:query t: repository name (and optionally a tag) to be applied to the resulting image in case of success
+	:query q: suppress verbose build output
+    :query nocache: do not use the cache when building the image
+	:statuscode 200: no error
+    :statuscode 500: server error
+
+
+Check auth configuration
+************************
+
+.. http:post:: /auth
+
+        Get the default username and email
+
+        **Example request**:
+
+        .. sourcecode:: http
+
+           POST /auth HTTP/1.1
+	   Content-Type: application/json
+
+	   {
+		"username":"hannibal",
+		"password:"xxxx",
+		"email":"hannibal@a-team.com",
+		"serveraddress":"https://index.docker.io/v1/"
+	   }
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+
+        :statuscode 200: no error
+        :statuscode 204: no error
+        :statuscode 500: server error
+
+
+Display system-wide information
+*******************************
+
+.. http:get:: /info
+
+	Display system-wide information
+	
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /info HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"Containers":11,
+		"Images":16,
+		"Debug":false,
+		"NFd": 11,
+		"NGoroutines":21,
+		"MemoryLimit":true,
+		"SwapLimit":false,
+		"IPv4Forwarding":true
+	   }
+
+        :statuscode 200: no error
+        :statuscode 500: server error
+
+
+Show the docker version information
+***********************************
+
+.. http:get:: /version
+
+	Show the docker version information
+
+	**Example request**:
+
+        .. sourcecode:: http
+
+           GET /version HTTP/1.1
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {
+		"Version":"0.2.2",
+		"GitCommit":"5a2a5cc+CHANGES",
+		"GoVersion":"go1.0.3"
+	   }
+
+        :statuscode 200: no error
+	:statuscode 500: server error
+
+
+Create a new image from a container's changes
+*********************************************
+
+.. http:post:: /commit
+
+    Create a new image from a container's changes
+
+    **Example request**:
+
+    .. sourcecode:: http
+
+        POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1
+
+    **Example response**:
+
+    .. sourcecode:: http
+
+        HTTP/1.1 201 OK
+	    Content-Type: application/vnd.docker.raw-stream
+
+        {"Id":"596069db4bf5"}
+
+    :query container: source container
+    :query repo: repository
+    :query tag: tag
+    :query m: commit message
+    :query author: author (eg. "John Hannibal Smith <hannibal@a-team.com>")
+    :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]})
+    :statuscode 201: no error
+    :statuscode 404: no such container
+    :statuscode 500: server error
+
+
+Monitor Docker's events
+***********************
+
+.. http:get:: /events
+
+	Get events from docker, either in real time via streaming, or via polling (using `since`)
+
+	**Example request**:
+
+	.. sourcecode:: http
+
+           POST /events?since=1374067924
+
+        **Example response**:
+
+        .. sourcecode:: http
+
+           HTTP/1.1 200 OK
+	   Content-Type: application/json
+
+	   {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
+	   {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
+	   {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966}
+	   {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970}
+
+	:query since: timestamp used for polling
+        :statuscode 200: no error
+        :statuscode 500: server error
+
+
+3. Going further
+================
+
+3.1 Inside 'docker run'
+-----------------------
+
+Here are the steps of 'docker run' :
+
+* Create the container
+* If the status code is 404, it means the image doesn't exists:
+        * Try to pull it
+        * Then retry to create the container
+* Start the container
+* If you are not in detached mode:
+        * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1
+* If in detached mode or only stdin is attached:
+	* Display the container's id
+
+
+3.2 Hijacking
+-------------
+
+In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future.
+
+3.3 CORS Requests
+-----------------
+
+To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
+
+.. code-block:: bash
+
+   docker -d -H="192.168.1.9:4243" -api-enable-cors
+

+ 33 - 1
docs/sources/commandline/cli.rst

@@ -315,8 +315,10 @@ Shell 1: (Again .. now showing events)
     List images
 
       -a=false: show all images
+      -notrunc=false: Don't truncate output
       -q=false: only show numeric IDs
-      -viz=false: output in graphviz format
+      -tree=false: output graph in tree format
+      -viz=false: output graph in graphviz format
 
 Displaying images visually
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -328,6 +330,36 @@ Displaying images visually
 .. image:: docker_images.gif
    :alt: Example inheritance graph of Docker images.
 
+
+Displaying image hierarchy
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+    sudo docker images -tree
+
+    |─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise
+    └─27cf78414709 Size: 180.1 MB (virtual 180.1 MB)
+      └─b750fe79269d Size: 24.65 kB (virtual 180.1 MB) Tags: ubuntu:12.10,ubuntu:quantal
+        |─f98de3b610d5 Size: 12.29 kB (virtual 180.1 MB)
+        | └─7da80deb7dbf Size: 16.38 kB (virtual 180.1 MB)
+        |   └─65ed2fee0a34 Size: 20.66 kB (virtual 180.2 MB)
+        |     └─a2b9ea53dddc Size: 819.7 MB (virtual 999.8 MB)
+        |       └─a29b932eaba8 Size: 28.67 kB (virtual 999.9 MB)
+        |         └─e270a44f124d Size: 12.29 kB (virtual 999.9 MB) Tags: progrium/buildstep:latest
+        └─17e74ac162d8 Size: 53.93 kB (virtual 180.2 MB)
+          └─339a3f56b760 Size: 24.65 kB (virtual 180.2 MB)
+            └─904fcc40e34d Size: 96.7 MB (virtual 276.9 MB)
+              └─b1b0235328dd Size: 363.3 MB (virtual 640.2 MB)
+                └─7cb05d1acb3b Size: 20.48 kB (virtual 640.2 MB)
+                  └─47bf6f34832d Size: 20.48 kB (virtual 640.2 MB)
+                    └─f165104e82ed Size: 12.29 kB (virtual 640.2 MB)
+                      └─d9cf85a47b7e Size: 1.911 MB (virtual 642.2 MB)
+                        └─3ee562df86ca Size: 17.07 kB (virtual 642.2 MB)
+                          └─b05fc2d00e4a Size: 24.96 kB (virtual 642.2 MB)
+                            └─c96a99614930 Size: 12.29 kB (virtual 642.2 MB)
+                              └─a6a357a48c49 Size: 12.29 kB (virtual 642.2 MB) Tags: ndj/mongodb:latest
+
 .. _cli_import:
 
 ``import``

+ 1 - 0
runtime_test.go

@@ -22,6 +22,7 @@ import (
 const (
 	unitTestImageName     = "docker-test-image"
 	unitTestImageID       = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0
+	unitTestImageIDShort  = "83599e29c455"
 	unitTestNetworkBridge = "testdockbr0"
 	unitTestStoreBase     = "/var/lib/docker/unit-tests"
 	testDaemonAddr        = "127.0.0.1:4270"

+ 31 - 12
server.go

@@ -247,7 +247,7 @@ func (srv *Server) ImagesViz(out io.Writer) error {
 	for _, image := range images {
 		parentImage, err = image.GetParent()
 		if err != nil {
-			return err
+			return fmt.Errorf("Error while getting parent image: %v", err)
 		}
 		if parentImage != nil {
 			out.Write([]byte(" \"" + parentImage.ShortID() + "\" -> \"" + image.ShortID() + "\"\n"))
@@ -284,7 +284,7 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
 	if err != nil {
 		return nil, err
 	}
-	outs := []APIImages{} //produce [] when empty instead of 'null'
+	lookup := make(map[string]APIImages)
 	for name, repository := range srv.runtime.repositories.Repositories {
 		if filter != "" {
 			if match, _ := path.Match(filter, name); !match {
@@ -292,27 +292,46 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
 			}
 		}
 		for tag, id := range repository {
-			var out APIImages
 			image, err := srv.runtime.graph.Get(id)
 			if err != nil {
 				log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
 				continue
 			}
-			delete(allImages, id)
-			out.Repository = name
-			out.Tag = tag
-			out.ID = image.ID
-			out.Created = image.Created.Unix()
-			out.Size = image.Size
-			out.VirtualSize = image.getParentsSize(0) + image.Size
-			outs = append(outs, out)
+
+			if out, exists := lookup[id]; exists {
+				out.RepoTags = append(out.RepoTags, fmt.Sprintf("%s:%s", name, tag))
+
+				lookup[id] = out
+			} else {
+				var out APIImages
+
+				delete(allImages, id)
+
+				out.ParentId = image.Parent
+				out.RepoTags = []string{fmt.Sprintf("%s:%s", name, tag)}
+				out.ID = image.ID
+				out.Created = image.Created.Unix()
+				out.Size = image.Size
+				out.VirtualSize = image.getParentsSize(0) + image.Size
+
+				lookup[id] = out
+			}
+
 		}
 	}
-	// Display images which aren't part of a
+
+	outs := make([]APIImages, 0, len(lookup))
+	for _, value := range lookup {
+		outs = append(outs, value)
+	}
+
+	// Display images which aren't part of a repository/tag
 	if filter == "" {
 		for _, image := range allImages {
 			var out APIImages
 			out.ID = image.ID
+			out.ParentId = image.Parent
+			out.RepoTags = []string{"<none>:<none>"}
 			out.Created = image.Created.Unix()
 			out.Size = image.Size
 			out.VirtualSize = image.getParentsSize(0) + image.Size

+ 9 - 9
server_test.go

@@ -34,8 +34,8 @@ func TestContainerTagImageDelete(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(images) != len(initialImages)+3 {
-		t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
+	if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+3 {
+		t.Errorf("Expected %d images, %d found", len(initialImages)+3, len(images))
 	}
 
 	if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil {
@@ -47,7 +47,7 @@ func TestContainerTagImageDelete(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(images) != len(initialImages)+2 {
+	if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+2 {
 		t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
 	}
 
@@ -60,7 +60,7 @@ func TestContainerTagImageDelete(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(images) != len(initialImages)+1 {
+	if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
 		t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
 	}
 
@@ -462,7 +462,7 @@ func TestRmi(t *testing.T) {
 		if strings.Contains(unitTestImageID, image.ID) {
 			continue
 		}
-		if image.Repository == "" {
+		if image.RepoTags[0] == "<none>:<none>" {
 			t.Fatalf("Expected tagged image, got untagged one.")
 		}
 	}
@@ -490,7 +490,7 @@ func TestImagesFilter(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(images) != 2 {
+	if len(images[0].RepoTags) != 2 {
 		t.Fatal("incorrect number of matches returned")
 	}
 
@@ -499,7 +499,7 @@ func TestImagesFilter(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(images) != 1 {
+	if len(images[0].RepoTags) != 1 {
 		t.Fatal("incorrect number of matches returned")
 	}
 
@@ -508,7 +508,7 @@ func TestImagesFilter(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(images) != 1 {
+	if len(images[0].RepoTags) != 1 {
 		t.Fatal("incorrect number of matches returned")
 	}
 
@@ -517,7 +517,7 @@ func TestImagesFilter(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	if len(images) != 1 {
+	if len(images[0].RepoTags) != 1 {
 		t.Fatal("incorrect number of matches returned")
 	}
 }

+ 1 - 1
sorter.go

@@ -25,7 +25,7 @@ func (s *imageSorter) Less(i, j int) bool {
 // Sort []ApiImages by most recent creation date and tag name.
 func sortImagesByCreationAndTag(images []APIImages) {
 	creationAndTag := func(i1, i2 *APIImages) bool {
-		return i1.Created > i2.Created || (i1.Created == i2.Created && i2.Tag > i1.Tag)
+		return i1.Created > i2.Created
 	}
 
 	sorter := &imageSorter{

+ 25 - 7
sorter_test.go

@@ -3,6 +3,7 @@ package docker
 import (
 	"fmt"
 	"testing"
+	"time"
 )
 
 func TestServerListOrderedImagesByCreationDate(t *testing.T) {
@@ -34,29 +35,46 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) {
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 
-	archive, err := fakeTar()
+	err := generateImage("bar", runtime)
 	if err != nil {
 		t.Fatal(err)
 	}
-	image, err := runtime.graph.Create(archive, nil, "Testing", "", nil)
+
+	time.Sleep(time.Second)
+
+	err = generateImage("zed", runtime)
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	srv := &Server{runtime: runtime}
-	srv.ContainerTag(image.ID, "repo", "foo", false)
-	srv.ContainerTag(image.ID, "repo", "bar", false)
-
 	images, err := srv.Images(true, "")
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	if images[0].Created != images[1].Created || images[0].Tag >= images[1].Tag {
-		t.Error("Expected []APIImges to be ordered by most recent creation date and tag name.")
+	if images[0].RepoTags[0] != "repo:zed" && images[0].RepoTags[0] != "repo:bar" {
+		t.Errorf("Expected []APIImges to be ordered by most recent creation date. %s", images)
 	}
 }
 
+func generateImage(name string, runtime *Runtime) error {
+
+	archive, err := fakeTar()
+	if err != nil {
+		return err
+	}
+	image, err := runtime.graph.Create(archive, nil, "Testing", "", nil)
+	if err != nil {
+		return err
+	}
+
+	srv := &Server{runtime: runtime}
+	srv.ContainerTag(image.ID, "repo", name, false)
+
+	return nil
+}
+
 func TestSortUniquePorts(t *testing.T) {
 	ports := []Port{
 		Port("6379/tcp"),