ソースを参照

Merge branch 'master' into 0.6.5-dm-plugin

Conflicts:
	server.go
Michael Crosby 11 年 前
コミット
2382a0f920

+ 2 - 3
Dockerfile

@@ -46,10 +46,9 @@ run	apt-get install -y -q ruby1.9.3 rubygems libffi-dev
 run	gem install --no-rdoc --no-ri fpm
 run	apt-get install -y -q reprepro dpkg-sig
 
-# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
 run	apt-get install -y -q python-pip
-run	pip install s3cmd
-run	pip install python-magic
+run	pip install s3cmd==1.1.0-beta3
+run	pip install python-magic==0.4.6
 run	/bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
 
 # Runtime dependencies

+ 22 - 0
api.go

@@ -534,6 +534,26 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
 	return nil
 }
 
+func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	name := vars["name"]
+	if version > 1.0 {
+		w.Header().Set("Content-Type", "application/x-tar")
+	}
+	err := srv.ImageExport(name, w)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	err := srv.ImageLoad(r.Body)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	if err := parseForm(r); err != nil {
 		return nil
@@ -1036,6 +1056,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 			"/images/json":                    getImagesJSON,
 			"/images/viz":                     getImagesViz,
 			"/images/search":                  getImagesSearch,
+			"/images/{name:.*}/get":           getImagesGet,
 			"/images/{name:.*}/history":       getImagesHistory,
 			"/images/{name:.*}/json":          getImagesByName,
 			"/containers/ps":                  getContainersJSON,
@@ -1052,6 +1073,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
 			"/build":                        postBuild,
 			"/images/create":                postImagesCreate,
 			"/images/{name:.*}/insert":      postImagesInsert,
+			"/images/load":                  postImagesLoad,
 			"/images/{name:.*}/push":        postImagesPush,
 			"/images/{name:.*}/tag":         postImagesTag,
 			"/containers/create":            postContainersCreate,

+ 0 - 0
api_unit_tests.go → api_unit_test.go


+ 47 - 6
commands.go

@@ -92,6 +92,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
 		{"insert", "Insert a file in an image"},
 		{"inspect", "Return low-level information on a container"},
 		{"kill", "Kill a running container"},
+		{"load", "Load an image from a tar archive"},
 		{"login", "Register or Login to the docker registry server"},
 		{"logs", "Fetch the logs of a container"},
 		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
@@ -102,6 +103,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
 		{"rm", "Remove one or more containers"},
 		{"rmi", "Remove one or more images"},
 		{"run", "Run a command in a new container"},
+		{"save", "Save an image to a tar archive"},
 		{"search", "Search for an image in the docker index"},
 		{"start", "Start a stopped container"},
 		{"stop", "Stop a running container"},
@@ -577,6 +579,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 	}
 
 	var cErr chan error
+	var tty bool
 	if *attach || *openStdin {
 		if cmd.NArg() > 1 {
 			return fmt.Errorf("Impossible to start and attach multiple containers at once.")
@@ -593,17 +596,13 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 			return err
 		}
 
+		tty = container.Config.Tty
+
 		if !container.Config.Tty {
 			sigc := cli.forwardAllSignals(cmd.Arg(0))
 			defer utils.StopCatch(sigc)
 		}
 
-		if container.Config.Tty && cli.isTerminal {
-			if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
-				return err
-			}
-		}
-
 		var in io.ReadCloser
 
 		v := url.Values{}
@@ -641,7 +640,13 @@ func (cli *DockerCli) CmdStart(args ...string) error {
 		}
 		return encounteredError
 	}
+
 	if *openStdin || *attach {
+		if tty && cli.isTerminal {
+			if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
+				utils.Errorf("Error monitoring TTY size: %s\n", err)
+			}
+		}
 		return <-cErr
 	}
 	return nil
@@ -1967,6 +1972,42 @@ func (cli *DockerCli) CmdCp(args ...string) error {
 	return nil
 }
 
+func (cli *DockerCli) CmdSave(args ...string) error {
+	cmd := Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive")
+	if err := cmd.Parse(args); err != nil {
+		cmd.Usage()
+		return nil
+	}
+
+	if cmd.NArg() != 1 {
+		cmd.Usage()
+		return nil
+	}
+
+	image := cmd.Arg(0)
+
+	if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (cli *DockerCli) CmdLoad(args ...string) error {
+	cmd := Subcmd("load", "SOURCE", "Load an image from a tar archive")
+
+	if cmd.NArg() != 0 {
+		cmd.Usage()
+		return nil
+	}
+
+	err := cli.stream("POST", "/images/load", cli.in, cli.out, nil)
+	if err != nil {
+		fmt.Println("Send failed", err)
+	}
+
+	return nil
+}
+
 func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
 	var params io.Reader
 	if data != nil {

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

@@ -1171,6 +1171,53 @@ Monitor Docker's events
         :statuscode 200: no error
         :statuscode 500: server error
 
+Get a tarball containing all images and tags in a repository
+************************************************************
+
+.. http:get:: /images/(name)/get
+
+  Get a tarball containing all images and metadata for the repository specified by ``name``.
+
+  **Example request**
+
+  .. sourcecode:: http
+  
+           GET /images/ubuntu/get
+
+       **Example response**:
+
+       .. sourcecode:: http
+
+          HTTP/1.1 200 OK
+    Content-Type: application/x-tar
+
+    Binary data stream
+        :statuscode 200: no error
+        :statuscode 500: server error
+
+Load a tarball with a set of images and tags into docker
+********************************************************
+
+.. http:post:: /images/load
+
+  Load a set of images and tags into the docker repository.
+
+  **Example request**
+
+  .. sourcecode:: http
+
+           POST /images/load
+
+         Tarball in body
+
+       **Example response**:
+
+       .. sourcecode:: http
+
+          HTTP/1.1 200 OK
+
+        :statuscode 200: no error
+        :statuscode 500: server error
 
 3. Going further
 ================

+ 23 - 0
docs/sources/commandline/cli.rst

@@ -559,6 +559,18 @@ Known Issues (kill)
 * :issue:`197` indicates that ``docker kill`` may leave directories
   behind and make it difficult to remove the container.
 
+.. _cli_load:
+
+``load``
+--------
+
+::
+
+    Usage: docker load < repository.tar
+
+    Loads a tarred repository from the standard input stream.
+    Restores both images and tags.
+
 .. _cli_login:
 
 ``login``
@@ -852,6 +864,17 @@ Known Issues (run -volumes-from)
   could indicate a permissions problem with AppArmor. Please see the
   issue for a workaround.
 
+.. _cli_save:
+
+``save``
+
+::
+
+    Usage: docker save image > repository.tar
+
+    Streams a tarred repository to the standard output stream.
+    Contains all parent layers, and all tags + versions.
+
 .. _cli_search:
 
 ``search``

+ 55 - 0
engine/engine_test.go

@@ -0,0 +1,55 @@
+package engine
+
+import (
+	"testing"
+)
+
+func TestRegister(t *testing.T) {
+	if err := Register("dummy1", nil); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := Register("dummy1", nil); err == nil {
+		t.Fatalf("Expecting error, got none")
+	}
+
+	eng := newTestEngine(t)
+
+	//Should fail because globan handlers are copied
+	//at the engine creation
+	if err := eng.Register("dummy1", nil); err == nil {
+		t.Fatalf("Expecting error, got none")
+	}
+
+	if err := eng.Register("dummy2", nil); err != nil {
+		t.Fatal(err)
+	}
+
+	if err := eng.Register("dummy2", nil); err == nil {
+		t.Fatalf("Expecting error, got none")
+	}
+}
+
+func TestJob(t *testing.T) {
+	eng := newTestEngine(t)
+	job1 := eng.Job("dummy1", "--level=awesome")
+
+	if job1.handler != nil {
+		t.Fatalf("job1.handler should be empty")
+	}
+
+	h := func(j *Job) string {
+		return j.Name
+	}
+
+	eng.Register("dummy2", h)
+	job2 := eng.Job("dummy2", "--level=awesome")
+
+	if job2.handler == nil {
+		t.Fatalf("job2.handler shouldn't be nil")
+	}
+
+	if job2.handler(job2) != job2.Name {
+		t.Fatalf("handler dummy2 was not found in job2")
+	}
+}

+ 94 - 0
engine/env_test.go

@@ -23,7 +23,101 @@ func TestSetenv(t *testing.T) {
 	if val := job.Getenv("foo"); val != "bar" {
 		t.Fatalf("Getenv returns incorrect value: %s", val)
 	}
+
+	job.Setenv("bar", "")
+	if val := job.Getenv("bar"); val != "" {
+		t.Fatalf("Getenv returns incorrect value: %s", val)
+	}
 	if val := job.Getenv("nonexistent"); val != "" {
 		t.Fatalf("Getenv returns incorrect value: %s", val)
 	}
 }
+
+func TestSetenvBool(t *testing.T) {
+	job := mkJob(t, "dummy")
+	job.SetenvBool("foo", true)
+	if val := job.GetenvBool("foo"); !val {
+		t.Fatalf("GetenvBool returns incorrect value: %b", val)
+	}
+
+	job.SetenvBool("bar", false)
+	if val := job.GetenvBool("bar"); val {
+		t.Fatalf("GetenvBool returns incorrect value: %b", val)
+	}
+
+	if val := job.GetenvBool("nonexistent"); val {
+		t.Fatalf("GetenvBool returns incorrect value: %b", val)
+	}
+}
+
+func TestSetenvInt(t *testing.T) {
+	job := mkJob(t, "dummy")
+
+	job.SetenvInt("foo", -42)
+	if val := job.GetenvInt("foo"); val != -42 {
+		t.Fatalf("GetenvInt returns incorrect value: %d", val)
+	}
+
+	job.SetenvInt("bar", 42)
+	if val := job.GetenvInt("bar"); val != 42 {
+		t.Fatalf("GetenvInt returns incorrect value: %d", val)
+	}
+	if val := job.GetenvInt("nonexistent"); val != -1 {
+		t.Fatalf("GetenvInt returns incorrect value: %d", val)
+	}
+}
+
+func TestSetenvList(t *testing.T) {
+	job := mkJob(t, "dummy")
+
+	job.SetenvList("foo", []string{"bar"})
+	if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" {
+		t.Fatalf("GetenvList returns incorrect value: %v", val)
+	}
+
+	job.SetenvList("bar", nil)
+	if val := job.GetenvList("bar"); val != nil {
+		t.Fatalf("GetenvList returns incorrect value: %v", val)
+	}
+	if val := job.GetenvList("nonexistent"); val != nil {
+		t.Fatalf("GetenvList returns incorrect value: %v", val)
+	}
+}
+
+func TestImportEnv(t *testing.T) {
+	type dummy struct {
+		DummyInt         int
+		DummyStringArray []string
+	}
+
+	job := mkJob(t, "dummy")
+	if err := job.ImportEnv(&dummy{42, []string{"foo", "bar"}}); err != nil {
+		t.Fatal(err)
+	}
+
+	dmy := dummy{}
+	if err := job.ExportEnv(&dmy); err != nil {
+		t.Fatal(err)
+	}
+
+	if dmy.DummyInt != 42 {
+		t.Fatalf("Expected 42, got %d", dmy.DummyInt)
+	}
+
+	if len(dmy.DummyStringArray) != 2 || dmy.DummyStringArray[0] != "foo" || dmy.DummyStringArray[1] != "bar" {
+		t.Fatalf("Expected {foo, bar}, got %v", dmy.DummyStringArray)
+	}
+
+}
+
+func TestEnviron(t *testing.T) {
+	job := mkJob(t, "dummy")
+	job.Setenv("foo", "bar")
+	val, exists := job.Environ()["foo"]
+	if !exists {
+		t.Fatalf("foo not found in the environ")
+	}
+	if val != "bar" {
+		t.Fatalf("bar not found in the environ")
+	}
+}

+ 0 - 4
engine/utils.go → engine/helpers_test.go

@@ -11,10 +11,6 @@ import (
 
 var globalTestID string
 
-func init() {
-	Register("dummy", func(job *Job) string { return "" })
-}
-
 func newTestEngine(t *testing.T) *Engine {
 	// Use the caller function name as a prefix.
 	// This helps trace temp directories back to their test.

+ 5 - 1
engine/job.go

@@ -205,8 +205,12 @@ func (job *Job) SetenvInt(key string, value int64) {
 	job.Setenv(key, fmt.Sprintf("%d", value))
 }
 
+// Returns nil if key not found
 func (job *Job) GetenvList(key string) []string {
 	sval := job.Getenv(key)
+	if sval == "" {
+		return nil
+	}
 	l := make([]string, 0, 1)
 	if err := json.Unmarshal([]byte(sval), &l); err != nil {
 		l = append(l, sval)
@@ -234,7 +238,7 @@ func (job *Job) Setenv(key, value string) {
 // DecodeEnv decodes `src` as a json dictionary, and adds
 // each decoded key-value pair to the environment.
 //
-// If `text` cannot be decoded as a json dictionary, an error
+// If `src` cannot be decoded as a json dictionary, an error
 // is returned.
 func (job *Job) DecodeEnv(src io.Reader) error {
 	m := make(map[string]interface{})

+ 1 - 1
integration/commands_test.go

@@ -329,7 +329,7 @@ func TestRunDisconnectTty(t *testing.T) {
 	// Client disconnect after run -i should keep stdin out in TTY mode
 	container := globalRuntime.List()[0]
 
-	setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
+	setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
 		if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
 			t.Fatal(err)
 		}

+ 7 - 2
integration/container_test.go

@@ -330,6 +330,11 @@ func TestCommitRun(t *testing.T) {
 }
 
 func TestStart(t *testing.T) {
+	_, err1 := os.Stat("/sys/fs/cgroup/cpuacct,cpu")
+	_, err2 := os.Stat("/sys/fs/cgroup/cpu,cpuacct")
+	if err1 == nil || err2 == nil {
+		t.Skip("Fixme. Setting cpu cgroup shares doesn't work in dind on a Fedora host.  The lxc utils are confused by the cpu,cpuacct mount.")
+	}
 	runtime := mkRuntime(t)
 	defer nuke(runtime)
 	container, _, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
@@ -563,7 +568,7 @@ func TestExitCode(t *testing.T) {
 
 	trueContainer, _, err := runtime.Create(&docker.Config{
 		Image: GetTestImage(runtime).ID,
-		Cmd:   []string{"/bin/true", ""},
+		Cmd:   []string{"/bin/true"},
 	}, "")
 	if err != nil {
 		t.Fatal(err)
@@ -578,7 +583,7 @@ func TestExitCode(t *testing.T) {
 
 	falseContainer, _, err := runtime.Create(&docker.Config{
 		Image: GetTestImage(runtime).ID,
-		Cmd:   []string{"/bin/false", ""},
+		Cmd:   []string{"/bin/false"},
 	}, "")
 	if err != nil {
 		t.Fatal(err)

+ 11 - 3
integration/server_test.go

@@ -109,7 +109,7 @@ func TestCreateRmVolumes(t *testing.T) {
 	srv := mkServerFromEngine(eng, t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo test"}, nil)
+	config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -164,7 +164,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 	srv := mkServerFromEngine(eng, t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
-	config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "/bin/cat"}, nil)
+	config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -240,7 +240,7 @@ func TestRmi(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
+	config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -256,6 +256,10 @@ func TestRmi(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	if _, err := srv.ContainerWait(containerID); err != nil {
+		t.Fatal(err)
+	}
+
 	imageID, err := srv.ContainerCommit(containerID, "test", "", "", "", nil)
 	if err != nil {
 		t.Fatal(err)
@@ -277,6 +281,10 @@ func TestRmi(t *testing.T) {
 		t.Fatal(err)
 	}
 
+	if _, err := srv.ContainerWait(containerID); err != nil {
+		t.Fatal(err)
+	}
+
 	_, err = srv.ContainerCommit(containerID, "test", "", "", "", nil)
 	if err != nil {
 		t.Fatal(err)

+ 187 - 10
server.go

@@ -197,6 +197,185 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
 	return fmt.Errorf("No such container: %s", name)
 }
 
+// ImageExport exports all images with the given tag. All versions
+// containing the same tag are exported. The resulting output is an
+// uncompressed tar ball.
+// name is the set of tags to export.
+// out is the writer where the images are written to.
+func (srv *Server) ImageExport(name string, out io.Writer) error {
+	// get image json
+	tempdir, err := ioutil.TempDir("", "docker-export-")
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(tempdir)
+
+	utils.Debugf("Serializing %s", name)
+
+	rootRepo := srv.runtime.repositories.Repositories[name]
+	for _, rootImage := range rootRepo {
+		image, _ := srv.ImageInspect(rootImage)
+		for i := image; i != nil; {
+			// temporary directory
+			tmpImageDir := path.Join(tempdir, i.ID)
+			if err := os.Mkdir(tmpImageDir, os.ModeDir); err != nil {
+				return err
+			}
+			defer os.RemoveAll(tmpImageDir)
+
+			var version = "1.0"
+			var versionBuf = []byte(version)
+
+			if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.ModeAppend); err != nil {
+				return err
+			}
+
+			// serialize json
+			b, err := json.Marshal(i)
+			if err != nil {
+				return err
+			}
+			if err := ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend); err != nil {
+				return err
+			}
+
+			// serialize filesystem
+			fs, err := archive.Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), archive.Uncompressed)
+			if err != nil {
+				return err
+			}
+
+			fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
+			if err != nil {
+				return err
+			}
+			if _, err = io.Copy(fsTar, fs); err != nil {
+				return err
+			}
+			fsTar.Close()
+
+			// find parent
+			if i.Parent != "" {
+				i, err = srv.ImageInspect(i.Parent)
+				if err != nil {
+					return err
+				}
+			} else {
+				i = nil
+			}
+		}
+	}
+
+	// write repositories
+	rootRepoMap := map[string]Repository{}
+	rootRepoMap[name] = rootRepo
+	rootRepoJson, _ := json.Marshal(rootRepoMap)
+
+	if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend); err != nil {
+		return err
+	}
+
+	fs, err := archive.Tar(tempdir, archive.Uncompressed)
+	if err != nil {
+		return err
+	}
+
+	if _, err := io.Copy(out, fs); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Loads a set of images into the repository. This is the complementary of ImageExport.
+// The input stream is an uncompressed tar ball containing images and metadata.
+func (srv *Server) ImageLoad(in io.Reader) error {
+	tmpImageDir, err := ioutil.TempDir("", "docker-import-")
+	if err != nil {
+		return err
+	}
+	defer os.RemoveAll(tmpImageDir)
+
+	var (
+		repoTarFile = path.Join(tmpImageDir, "repo.tar")
+		repoDir     = path.Join(tmpImageDir, "repo")
+	)
+
+	tarFile, err := os.Create(repoTarFile)
+	if err != nil {
+		return err
+	}
+	if _, err := io.Copy(tarFile, in); err != nil {
+		return err
+	}
+	tarFile.Close()
+
+	repoFile, err := os.Open(repoTarFile)
+	if err != nil {
+		return err
+	}
+	if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
+		return err
+	}
+	if err := archive.Untar(repoFile, repoDir, nil); err != nil {
+		return err
+	}
+	repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
+	if err != nil {
+		return err
+	}
+	repositories := map[string]Repository{}
+	if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
+		return err
+	}
+
+	for imageName, tagMap := range repositories {
+		for tag, address := range tagMap {
+			if err := srv.recursiveLoad(address, tmpImageDir); err != nil {
+				return err
+			}
+			if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func (srv *Server) recursiveLoad(address, tmpImageDir string) error {
+	if _, err := srv.ImageInspect(address); err != nil {
+		utils.Debugf("Loading %s", address)
+
+		imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
+		if err != nil {
+			return err
+			utils.Debugf("Error reading json", err)
+		}
+
+		layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
+		if err != nil {
+			utils.Debugf("Error reading embedded tar", err)
+			return err
+		}
+		img, err := NewImgJSON(imageJson)
+		if err != nil {
+			utils.Debugf("Error unmarshalling json", err)
+			return err
+		}
+		if img.Parent != "" {
+			if !srv.runtime.graph.Exists(img.Parent) {
+				if err := srv.recursiveLoad(img.Parent, tmpImageDir); err != nil {
+					return err
+				}
+			}
+		}
+		if err := srv.runtime.graph.Register(imageJson, layer, img); err != nil {
+			return err
+		}
+	}
+	utils.Debugf("Completed processing %s", address)
+	return nil
+}
+
 func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
 	r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
 	if err != nil {
@@ -473,6 +652,12 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
 	var displayed int
 	out := []APIContainers{}
 
+	names := map[string][]string{}
+	srv.runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
+		names[e.ID()] = append(names[e.ID()], p)
+		return nil
+	}, -1)
+
 	for _, container := range srv.runtime.List() {
 		if !container.State.Running && !all && n == -1 && since == "" && before == "" {
 			continue
@@ -493,25 +678,17 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
 			break
 		}
 		displayed++
-		c := createAPIContainer(container, size, srv.runtime)
+		c := createAPIContainer(names[container.ID], container, size, srv.runtime)
 		out = append(out, c)
 	}
 	return out
 }
 
-func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers {
+func createAPIContainer(names []string, container *Container, size bool, runtime *Runtime) APIContainers {
 	c := APIContainers{
 		ID: container.ID,
 	}
-	names := []string{}
-	runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
-		if e.ID() == container.ID {
-			names = append(names, p)
-		}
-		return nil
-	}, -1)
 	c.Names = names
-
 	c.Image = runtime.repositories.ImageName(container.Image)
 	c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
 	c.Created = container.Created.Unix()