From c80448c4d1836dd0e23953fa923f7a270d32df05 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 15 May 2013 13:11:39 +0000 Subject: [PATCH 01/26] improve rmi --- api_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++-- server.go | 36 ++++++++++++++++++++++++++++++++++ server_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ tags.go | 23 ++++++++++++++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) diff --git a/api_test.go b/api_test.go index 5096b0521..17075dae7 100644 --- a/api_test.go +++ b/api_test.go @@ -1189,8 +1189,51 @@ func TestDeleteContainers(t *testing.T) { } func TestDeleteImages(t *testing.T) { - //FIXME: Implement this test - t.Log("Test not implemented") + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + if err := srv.runtime.repositories.Set("test", "test", unitTestImageName, true); err != nil { + t.Fatal(err) + } + + images, err := srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + if len(images) != 2 { + t.Errorf("Excepted 2 images, %d found", len(images)) + } + + r := httptest.NewRecorder() + if err := deleteImages(srv, r, nil, map[string]string{"name": "test:test"}); err != nil { + t.Fatal(err) + } + if r.Code != http.StatusNoContent { + t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + } + + images, err = srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + if len(images) != 1 { + t.Errorf("Excepted 1 image, %d found", len(images)) + } + + /* if c := runtime.Get(container.Id); c != nil { + t.Fatalf("The container as not been deleted") + } + + if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil { + t.Fatalf("The test file has not been deleted") + } */ } // Mocked types for tests diff --git a/server.go b/server.go index 453574946..d749ead73 100644 --- a/server.go +++ b/server.go @@ -439,9 +439,45 @@ func (srv *Server) ImageDelete(name string) error { if err != nil { return fmt.Errorf("No such image: %s", name) } else { + tag := "" + if strings.Contains(name, ":") { + nameParts := strings.Split(name, ":") + name = nameParts[0] + tag = nameParts[1] + } + // if the images is referenced several times + Debugf("Image %s referenced %d times", img.Id, len(srv.runtime.repositories.ById()[img.Id])) + if len(srv.runtime.repositories.ById()[img.Id]) > 1 { + // if it's repo:tag, try to delete the tag (docker rmi base:latest) + if tag != "" { + if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { + return err + } + return nil + } else { + // check if the image is referenced in another repo (base and user/base are the same, docker rmi user/base) + var other bool + for _, repoTag := range srv.runtime.repositories.ById()[img.Id] { + if !strings.Contains(repoTag, name+":") { + other = true + break + } + } + // if found in another repo, delete the repo, other delete the whole image (docker rmi base) + if other { + if err := srv.runtime.repositories.Delete(name, "", img.Id); err != nil { + return err + } + return nil + } + } + } if err := srv.runtime.graph.Delete(img.Id); err != nil { return fmt.Errorf("Error deleting image %s: %s", name, err.Error()) } + if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { + return err + } } return nil } diff --git a/server_test.go b/server_test.go index 7b9025286..fe1cbecd9 100644 --- a/server_test.go +++ b/server_test.go @@ -4,6 +4,58 @@ import ( "testing" ) +func TestContainerTagImageDelete(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil { + t.Fatal(err) + } + if err := srv.runtime.repositories.Set("utest/docker", "tag2", unitTestImageName, false); err != nil { + t.Fatal(err) + } + + images, err := srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + if len(images) != 3 { + t.Errorf("Excepted 3 images, %d found", len(images)) + } + + if err := srv.ImageDelete("utest/docker:tag2"); err != nil { + t.Fatal(err) + } + + images, err = srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + if len(images) != 2 { + t.Errorf("Excepted 2 images, %d found", len(images)) + } + + if err := srv.ImageDelete("utest:tag1"); err != nil { + t.Fatal(err) + } + + images, err = srv.Images(false, "") + if err != nil { + t.Fatal(err) + } + + if len(images) != 1 { + t.Errorf("Excepted 1 image, %d found", len(images)) + } +} + func TestCreateRm(t *testing.T) { runtime, err := newTestRuntime() if err != nil { diff --git a/tags.go b/tags.go index 1b9cd19c8..b7aa69247 100644 --- a/tags.go +++ b/tags.go @@ -109,6 +109,29 @@ func (store *TagStore) ImageName(id string) string { return TruncateId(id) } +func (store *TagStore) Delete(repoName, tag, imageName string) error { + if err := store.Reload(); err != nil { + return err + } + if r, exists := store.Repositories[repoName]; exists { + if tag != "" { + if _, exists2 := r[tag]; exists2 { + delete(r, tag) + if len(r) == 0 { + delete(store.Repositories, repoName) + } + } else { + return fmt.Errorf("No such tag: %s:%s", repoName, tag) + } + } else { + delete(store.Repositories, repoName) + } + } else { + fmt.Errorf("No such repository: %s", repoName) + } + return store.Save() +} + func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { img, err := store.LookupImage(imageName) if err != nil { From db1e965b657e7e184b9f4e136e9c0860769d8f68 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 16 May 2013 11:27:50 -0700 Subject: [PATCH 02/26] Merge fixes + cleanup --- server.go | 62 +++++++++++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/server.go b/server.go index 8b97555ba..03030e120 100644 --- a/server.go +++ b/server.go @@ -699,46 +699,40 @@ func (srv *Server) ImageDelete(name string) error { img, err := srv.runtime.repositories.LookupImage(name) if err != nil { return fmt.Errorf("No such image: %s", name) - } else { - tag := "" - if strings.Contains(name, ":") { - nameParts := strings.Split(name, ":") - name = nameParts[0] - tag = nameParts[1] + } + var tag string + if strings.Contains(name, ":") { + nameParts := strings.Split(name, ":") + name = nameParts[0] + tag = nameParts[1] + } + + // if the images is referenced several times + utils.Debugf("Image %s referenced %d times", img.Id, len(srv.runtime.repositories.ById()[img.Id])) + if len(srv.runtime.repositories.ById()[img.Id]) > 1 { + // if it's repo:tag, try to delete the tag (docker rmi base:latest) + if tag != "" { + if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { + return err + } + return nil } - // if the images is referenced several times - Debugf("Image %s referenced %d times", img.Id, len(srv.runtime.repositories.ById()[img.Id])) - if len(srv.runtime.repositories.ById()[img.Id]) > 1 { - // if it's repo:tag, try to delete the tag (docker rmi base:latest) - if tag != "" { - if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { + // check if the image is referenced in another repo (base and user/base are the same, docker rmi user/base) + for _, repoTag := range srv.runtime.repositories.ById()[img.Id] { + if !strings.Contains(repoTag, name+":") { + // if found in another repo, delete the repo, otherwie delete the whole image (docker rmi base) + if err := srv.runtime.repositories.Delete(name, "", img.Id); err != nil { return err } return nil - } else { - // check if the image is referenced in another repo (base and user/base are the same, docker rmi user/base) - var other bool - for _, repoTag := range srv.runtime.repositories.ById()[img.Id] { - if !strings.Contains(repoTag, name+":") { - other = true - break - } - } - // if found in another repo, delete the repo, other delete the whole image (docker rmi base) - if other { - if err := srv.runtime.repositories.Delete(name, "", img.Id); err != nil { - return err - } - return nil - } } } - if err := srv.runtime.graph.Delete(img.Id); err != nil { - return fmt.Errorf("Error deleting image %s: %s", name, err.Error()) - } - if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { - return err - } + } + if err := srv.runtime.graph.Delete(img.Id); err != nil { + return fmt.Errorf("Error deleting image %s: %s", name, err.Error()) + } + if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { + return err } return nil } From f01990aad2200690618cd64d2378e7e610433a71 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 17 May 2013 17:57:44 +0000 Subject: [PATCH 03/26] fix --- server.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server.go b/server.go index 03030e120..1ad1d80a4 100644 --- a/server.go +++ b/server.go @@ -717,10 +717,11 @@ func (srv *Server) ImageDelete(name string) error { } return nil } - // check if the image is referenced in another repo (base and user/base are the same, docker rmi user/base) + // Let's say you have the same image referenced by base and base2 + // check if the image is referenced in another repo (docker rmi base) for _, repoTag := range srv.runtime.repositories.ById()[img.Id] { - if !strings.Contains(repoTag, name+":") { - // if found in another repo, delete the repo, otherwie delete the whole image (docker rmi base) + if !strings.HasPrefix(repoTag, name+":") { + // if found in another repo (base2) delete the repo base, otherwise delete the whole image if err := srv.runtime.repositories.Delete(name, "", img.Id); err != nil { return err } From d7673274d22140dc6dfa288351f1d4d2e2fa4b63 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 18 May 2013 14:29:32 +0000 Subject: [PATCH 04/26] check if the image to delete isn't parent of another --- server.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server.go b/server.go index 1ad1d80a4..d709aba7e 100644 --- a/server.go +++ b/server.go @@ -729,6 +729,15 @@ func (srv *Server) ImageDelete(name string) error { } } } + // check is the image to delete isn't parent of another image + images, _ := srv.runtime.graph.All() + for _, image := range images { + if imgParent, err := image.GetParent(); err == nil && imgParent != nil { + if imgParent.Id == img.Id { + return fmt.Errorf("Can't delete %s, otherwise %s will be broken", name, image.ShortId()) + } + } + } if err := srv.runtime.graph.Delete(img.Id); err != nil { return fmt.Errorf("Error deleting image %s: %s", name, err.Error()) } From 67b20f2c8cc8d85ab09de95970e4d28c80f0aeb6 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 20 May 2013 18:31:45 +0000 Subject: [PATCH 05/26] add check to see if the image isn't parent of another and add -f to force --- api.go | 11 ++++++++++- api_test.go | 7 ++++++- commands.go | 8 +++++++- docs/sources/api/docker_remote_api.rst | 3 +++ server.go | 14 ++++++++------ server_test.go | 4 ++-- tags.go | 2 +- 7 files changed, 37 insertions(+), 12 deletions(-) diff --git a/api.go b/api.go index 8984d00cd..53a895918 100644 --- a/api.go +++ b/api.go @@ -36,6 +36,8 @@ func httpError(w http.ResponseWriter, err error) { http.Error(w, err.Error(), http.StatusNotFound) } else if strings.HasPrefix(err.Error(), "Bad parameter") { http.Error(w, err.Error(), http.StatusBadRequest) + } else if strings.HasPrefix(err.Error(), "Conflict") { + http.Error(w, err.Error(), http.StatusConflict) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } @@ -453,11 +455,18 @@ func deleteContainers(srv *Server, w http.ResponseWriter, r *http.Request, vars } func deleteImages(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if err := parseForm(r); err != nil { + return err + } if vars == nil { return fmt.Errorf("Missing parameter") } name := vars["name"] - if err := srv.ImageDelete(name); err != nil { + force, err := getBoolParam(r.Form.Get("force")) + if err != nil { + return err + } + if err := srv.ImageDelete(name, force); err != nil { return err } w.WriteHeader(http.StatusNoContent) diff --git a/api_test.go b/api_test.go index 921aebe4c..283ebb130 100644 --- a/api_test.go +++ b/api_test.go @@ -1312,8 +1312,13 @@ func TestDeleteImages(t *testing.T) { t.Errorf("Excepted 2 images, %d found", len(images)) } + req, err := http.NewRequest("DELETE", "/images/test:test", nil) + if err != nil { + t.Fatal(err) + } + r := httptest.NewRecorder() - if err := deleteImages(srv, r, nil, map[string]string{"name": "test:test"}); err != nil { + if err := deleteImages(srv, r, req, map[string]string{"name": "test:test"}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { diff --git a/commands.go b/commands.go index 33ba8125d..26cea0189 100644 --- a/commands.go +++ b/commands.go @@ -459,6 +459,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { // 'docker rmi IMAGE' removes all images with the name IMAGE func (cli *DockerCli) CmdRmi(args ...string) error { cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image") + force := cmd.Bool("f", false, "Force") if err := cmd.Parse(args); err != nil { return nil } @@ -467,8 +468,13 @@ func (cli *DockerCli) CmdRmi(args ...string) error { return nil } + v := url.Values{} + if *force { + v.Set("force", "1") + } + for _, name := range cmd.Args() { - _, _, err := cli.call("DELETE", "/images/"+name, nil) + _, _, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil) if err != nil { fmt.Printf("%s", err) } else { diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 03f1d4b9c..9541e27de 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -728,6 +728,7 @@ Tag an image into a repository :statuscode 200: no error :statuscode 400: bad parameter :statuscode 404: no such image + :statuscode 409: conflict :statuscode 500: server error @@ -750,8 +751,10 @@ Remove an image HTTP/1.1 204 OK + :query force: 1/True/true or 0/False/false, default false :statuscode 204: no error :statuscode 404: no such image + :statuscode 409: conflict :statuscode 500: server error diff --git a/server.go b/server.go index d90e7fda0..9a2ab4edf 100644 --- a/server.go +++ b/server.go @@ -710,7 +710,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { return nil } -func (srv *Server) ImageDelete(name string) error { +func (srv *Server) ImageDelete(name string, force bool) error { img, err := srv.runtime.repositories.LookupImage(name) if err != nil { return fmt.Errorf("No such image: %s", name) @@ -745,11 +745,13 @@ func (srv *Server) ImageDelete(name string) error { } } // check is the image to delete isn't parent of another image - images, _ := srv.runtime.graph.All() - for _, image := range images { - if imgParent, err := image.GetParent(); err == nil && imgParent != nil { - if imgParent.Id == img.Id { - return fmt.Errorf("Can't delete %s, otherwise %s will be broken", name, image.ShortId()) + if !force { + images, _ := srv.runtime.graph.All() + for _, image := range images { + if imgParent, err := image.GetParent(); err == nil && imgParent != nil { + if imgParent.Id == img.Id { + return fmt.Errorf("Conflict: Can't delete %s otherwise %s will be broken", name, image.ShortId()) + } } } } diff --git a/server_test.go b/server_test.go index fe1cbecd9..f223fbe53 100644 --- a/server_test.go +++ b/server_test.go @@ -29,7 +29,7 @@ func TestContainerTagImageDelete(t *testing.T) { t.Errorf("Excepted 3 images, %d found", len(images)) } - if err := srv.ImageDelete("utest/docker:tag2"); err != nil { + if err := srv.ImageDelete("utest/docker:tag2", true); err != nil { t.Fatal(err) } @@ -42,7 +42,7 @@ func TestContainerTagImageDelete(t *testing.T) { t.Errorf("Excepted 2 images, %d found", len(images)) } - if err := srv.ImageDelete("utest:tag1"); err != nil { + if err := srv.ImageDelete("utest:tag1", true); err != nil { t.Fatal(err) } diff --git a/tags.go b/tags.go index f6b3a93a9..4a54398c5 100644 --- a/tags.go +++ b/tags.go @@ -156,7 +156,7 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { } else { repo = make(map[string]string) if old, exists := store.Repositories[repoName]; exists && !force { - return fmt.Errorf("Tag %s:%s is already set to %s", repoName, tag, old) + return fmt.Errorf("Conflict: Tag %s:%s is already set to %s", repoName, tag, old) } store.Repositories[repoName] = repo } From 2eb4e2a0b86f8d244f0b6cbeb0422f8499d834f8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 29 May 2013 16:31:47 +0000 Subject: [PATCH 06/26] removed the -f --- api.go | 6 +----- api_test.go | 2 +- commands.go | 8 +------- server.go | 18 +++++++++++------- server_test.go | 4 ++-- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/api.go b/api.go index 8f2befb23..1759257e6 100644 --- a/api.go +++ b/api.go @@ -450,11 +450,7 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R return fmt.Errorf("Missing parameter") } name := vars["name"] - force, err := getBoolParam(r.Form.Get("force")) - if err != nil { - return err - } - if err := srv.ImageDelete(name, force); err != nil { + if err := srv.ImageDelete(name); err != nil { return err } w.WriteHeader(http.StatusNoContent) diff --git a/api_test.go b/api_test.go index fb1cd211a..844d15cc1 100644 --- a/api_test.go +++ b/api_test.go @@ -1268,7 +1268,7 @@ func TestDeleteImages(t *testing.T) { } r := httptest.NewRecorder() - if err := deleteImages(srv, r, req, map[string]string{"name": "test:test"}); err != nil { + if err := deleteImages(srv, API_VERSION, r, req, map[string]string{"name": "test:test"}); err != nil { t.Fatal(err) } if r.Code != http.StatusNoContent { diff --git a/commands.go b/commands.go index 704f5ac48..2af7e548f 100644 --- a/commands.go +++ b/commands.go @@ -558,7 +558,6 @@ func (cli *DockerCli) CmdPort(args ...string) error { // 'docker rmi IMAGE' removes all images with the name IMAGE func (cli *DockerCli) CmdRmi(args ...string) error { cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove an image") - force := cmd.Bool("f", false, "Force") if err := cmd.Parse(args); err != nil { return nil } @@ -567,13 +566,8 @@ func (cli *DockerCli) CmdRmi(args ...string) error { return nil } - v := url.Values{} - if *force { - v.Set("force", "1") - } - for _, name := range cmd.Args() { - _, _, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil) + _, _, err := cli.call("DELETE", "/images/"+name, nil) if err != nil { fmt.Printf("%s", err) } else { diff --git a/server.go b/server.go index d1a089c3a..2e819728b 100644 --- a/server.go +++ b/server.go @@ -704,7 +704,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { return nil } -func (srv *Server) ImageDelete(name string, force bool) error { +func (srv *Server) ImageDelete(name string) error { img, err := srv.runtime.repositories.LookupImage(name) if err != nil { return fmt.Errorf("No such image: %s", name) @@ -739,13 +739,17 @@ func (srv *Server) ImageDelete(name string, force bool) error { } } // check is the image to delete isn't parent of another image - if !force { - images, _ := srv.runtime.graph.All() - for _, image := range images { - if imgParent, err := image.GetParent(); err == nil && imgParent != nil { - if imgParent.Id == img.Id { - return fmt.Errorf("Conflict: Can't delete %s otherwise %s will be broken", name, image.ShortId()) + images, _ := srv.runtime.graph.All() + for _, image := range images { + if imgParent, err := image.GetParent(); err == nil && imgParent != nil { + if imgParent.Id == img.Id { + if strings.Contains(img.Id, name) { + return fmt.Errorf("Conflict with %s, %s was not removed", image.ShortId(), name) } + if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { + return err + } + return nil } } } diff --git a/server_test.go b/server_test.go index 743b33d54..541c927b8 100644 --- a/server_test.go +++ b/server_test.go @@ -29,7 +29,7 @@ func TestContainerTagImageDelete(t *testing.T) { t.Errorf("Excepted 3 images, %d found", len(images)) } - if err := srv.ImageDelete("utest/docker:tag2", true); err != nil { + if err := srv.ImageDelete("utest/docker:tag2"); err != nil { t.Fatal(err) } @@ -42,7 +42,7 @@ func TestContainerTagImageDelete(t *testing.T) { t.Errorf("Excepted 2 images, %d found", len(images)) } - if err := srv.ImageDelete("utest:tag1", true); err != nil { + if err := srv.ImageDelete("utest:tag1"); err != nil { t.Fatal(err) } From 94f0d478de6e50e8b26e9779da22dbd74cc5952f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 29 May 2013 17:01:54 +0000 Subject: [PATCH 07/26] refacto --- server.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/server.go b/server.go index 2e819728b..64bde12c4 100644 --- a/server.go +++ b/server.go @@ -739,19 +739,15 @@ func (srv *Server) ImageDelete(name string) error { } } // check is the image to delete isn't parent of another image - images, _ := srv.runtime.graph.All() - for _, image := range images { - if imgParent, err := image.GetParent(); err == nil && imgParent != nil { - if imgParent.Id == img.Id { - if strings.Contains(img.Id, name) { - return fmt.Errorf("Conflict with %s, %s was not removed", image.ShortId(), name) - } - if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { - return err - } - return nil - } + byParent, _ := srv.runtime.graph.ByParent() + if childs, exists := byParent[img.Id]; exists { + if strings.Contains(img.Id, name) { + return fmt.Errorf("Conflict with %s, %s was not removed", childs[0].ShortId(), name) } + if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { + return err + } + return nil } if err := srv.runtime.graph.Delete(img.Id); err != nil { return fmt.Errorf("Error deleting image %s: %s", name, err.Error()) From 7e92302c4fba259ac1128db548cf01dad9ad087c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 29 May 2013 17:27:32 +0000 Subject: [PATCH 08/26] auto prune WIP --- server.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/server.go b/server.go index 64bde12c4..69ead3aff 100644 --- a/server.go +++ b/server.go @@ -749,11 +749,23 @@ func (srv *Server) ImageDelete(name string) error { } return nil } - if err := srv.runtime.graph.Delete(img.Id); err != nil { - return fmt.Errorf("Error deleting image %s: %s", name, err.Error()) - } - if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { - return err + parents, _ := img.History() + for _, parent := range parents { + byParent, _ = srv.runtime.graph.ByParent() + //stop if image has children + if _, exists := byParent[parent.Id]; exists { + break + } + //stop if image is tagged and it is not the first image we delete + if _, hasTags := srv.runtime.repositories.ById()[parent.Id]; hasTags && img.Id != parent.Id { + break + } + if err := srv.runtime.graph.Delete(parent.Id); err != nil { + return fmt.Errorf("Error deleting image %s: %s", name, err.Error()) + } + if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { + return err + } } return nil } From c05e9f856d5f12a1244924b02bad66ba5bacb550 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 29 May 2013 11:11:19 -0700 Subject: [PATCH 09/26] Error output fix for docker rmi --- commands.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 2af7e548f..0289048b8 100644 --- a/commands.go +++ b/commands.go @@ -567,9 +567,8 @@ func (cli *DockerCli) CmdRmi(args ...string) error { } for _, name := range cmd.Args() { - _, _, err := cli.call("DELETE", "/images/"+name, nil) - if err != nil { - fmt.Printf("%s", err) + if _, _, err := cli.call("DELETE", "/images/"+name, nil); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) } else { fmt.Println(name) } From 054451fd190f195e06d8a8aa65e83882f60b1f14 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 30 May 2013 12:30:21 -0700 Subject: [PATCH 10/26] NON-WORKING: Beginning of rmi refactor --- server.go | 108 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 83 insertions(+), 25 deletions(-) diff --git a/server.go b/server.go index 69ead3aff..126ac7d6e 100644 --- a/server.go +++ b/server.go @@ -1,6 +1,7 @@ package docker import ( + "errors" "fmt" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/registry" @@ -704,11 +705,86 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { return nil } -func (srv *Server) ImageDelete(name string) error { - img, err := srv.runtime.repositories.LookupImage(name) - if err != nil { - return fmt.Errorf("No such image: %s", name) +func (srv *Server) pruneImage(img *Image, repo, tag string) error { + return nil +} + +var ErrImageReferenced = errors.New("Image referenced by a repository") + +func (srv *Server) deleteImageChildren(id string, byId map[string][]string, byParents map[string][]*Image) error { + // If the image is referenced by a repo, do not delete + if len(byId[id]) != 0 { + return ErrImageReferenced } + // If the image is not referenced and has no children, remove it + if len(byParents[id]) == 0 { + return srv.runtime.graph.Delete(id) + } + + // If the image is not referenced but has children, go recursive + referenced := false + for _, img := range byParents[id] { + if err := srv.deleteImageChildren(img.Id, byId, byParents); err != nil { + if err != ErrImageReferenced { + return err + } else { + referenced = true + } + } + } + if referenced { + return ErrImageReferenced + } + return nil +} + +func (srv *Server) deleteImageParents(img *Image, byId map[string][]string, byParents map[string][]*Image) error { + if img.Parent != "" { + parent, err := srv.runtime.graph.Get(img.Parent) + if err != nil { + return err + } + // Remove all children images + if err := srv.deleteImageChildren(img.Id, byId, byParents); err != nil { + return err + } + // If no error (no referenced children), then remove the parent + if err := srv.runtime.graph.Delete(img.Parent); err != nil { + return err + } + return srv.deleteImageParents(parent, byId, byParents) + } + return nil +} + +func (srv *Server) deleteImage(repoName, tag string) error { + img, err := srv.runtime.repositories.LookupImage(repoName + ":" + tag) + if err != nil { + return fmt.Errorf("No such image: %s:%s", repoName, tag) + } + utils.Debugf("Image %s referenced %d times", img.Id, len(srv.runtime.repositories.ById()[img.Id])) + if err := srv.runtime.repositories.Delete(repoName, tag, img.Id); err != nil { + return err + } + byId := srv.runtime.repositories.ById() + if len(byId[img.Id]) == 0 { + byParents, err := srv.runtime.graph.ByParent() + if err != nil { + return err + } + if err := srv.deleteImageChildren(img.Id, byId, byParents); err != nil { + if err != ErrImageReferenced { + return err + } + } else { + srv.deleteImageParents(img) + } + + } + return nil +} + +func (srv *Server) ImageDelete(name string) error { var tag string if strings.Contains(name, ":") { nameParts := strings.Split(name, ":") @@ -716,28 +792,10 @@ func (srv *Server) ImageDelete(name string) error { tag = nameParts[1] } + srv.deleteImage(name, tag) + // if the images is referenced several times - utils.Debugf("Image %s referenced %d times", img.Id, len(srv.runtime.repositories.ById()[img.Id])) - if len(srv.runtime.repositories.ById()[img.Id]) > 1 { - // if it's repo:tag, try to delete the tag (docker rmi base:latest) - if tag != "" { - if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { - return err - } - return nil - } - // Let's say you have the same image referenced by base and base2 - // check if the image is referenced in another repo (docker rmi base) - for _, repoTag := range srv.runtime.repositories.ById()[img.Id] { - if !strings.HasPrefix(repoTag, name+":") { - // if found in another repo (base2) delete the repo base, otherwise delete the whole image - if err := srv.runtime.repositories.Delete(name, "", img.Id); err != nil { - return err - } - return nil - } - } - } + // check is the image to delete isn't parent of another image byParent, _ := srv.runtime.graph.ByParent() if childs, exists := byParent[img.Id]; exists { From 5aa95b667c5986e3cea45b93bb2370cd46deeea3 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 30 May 2013 22:53:45 +0000 Subject: [PATCH 11/26] WIP needs to fix HTTP error codes --- server.go | 106 +++++++++++++++++++----------------------------------- tags.go | 22 +++++++++++- 2 files changed, 58 insertions(+), 70 deletions(-) diff --git a/server.go b/server.go index 126ac7d6e..2af1e3cd8 100644 --- a/server.go +++ b/server.go @@ -705,26 +705,22 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { return nil } -func (srv *Server) pruneImage(img *Image, repo, tag string) error { - return nil -} - var ErrImageReferenced = errors.New("Image referenced by a repository") -func (srv *Server) deleteImageChildren(id string, byId map[string][]string, byParents map[string][]*Image) error { +func (srv *Server) deleteImageAndChildren(id string) error { // If the image is referenced by a repo, do not delete - if len(byId[id]) != 0 { + if len(srv.runtime.repositories.ById()[id]) != 0 { return ErrImageReferenced } - // If the image is not referenced and has no children, remove it - if len(byParents[id]) == 0 { - return srv.runtime.graph.Delete(id) - } // If the image is not referenced but has children, go recursive referenced := false + byParents, err := srv.runtime.graph.ByParent() + if err != nil { + return err + } for _, img := range byParents[id] { - if err := srv.deleteImageChildren(img.Id, byId, byParents); err != nil { + if err := srv.deleteImageAndChildren(img.Id); err != nil { if err != ErrImageReferenced { return err } else { @@ -735,56 +731,61 @@ func (srv *Server) deleteImageChildren(id string, byId map[string][]string, byPa if referenced { return ErrImageReferenced } + + // If the image is not referenced and has no children, remove it + byParents, err = srv.runtime.graph.ByParent() + if err != nil { + return err + } + if len(byParents[id]) == 0 { + if err := srv.runtime.repositories.DeleteAll(id); err != nil { + return err + } + return srv.runtime.graph.Delete(id) + } return nil } -func (srv *Server) deleteImageParents(img *Image, byId map[string][]string, byParents map[string][]*Image) error { +func (srv *Server) deleteImageParents(img *Image) error { if img.Parent != "" { parent, err := srv.runtime.graph.Get(img.Parent) if err != nil { return err } // Remove all children images - if err := srv.deleteImageChildren(img.Id, byId, byParents); err != nil { + if err := srv.deleteImageAndChildren(img.Parent); err != nil { return err } - // If no error (no referenced children), then remove the parent - if err := srv.runtime.graph.Delete(img.Parent); err != nil { - return err - } - return srv.deleteImageParents(parent, byId, byParents) + return srv.deleteImageParents(parent) } return nil } -func (srv *Server) deleteImage(repoName, tag string) error { - img, err := srv.runtime.repositories.LookupImage(repoName + ":" + tag) - if err != nil { - return fmt.Errorf("No such image: %s:%s", repoName, tag) - } - utils.Debugf("Image %s referenced %d times", img.Id, len(srv.runtime.repositories.ById()[img.Id])) - if err := srv.runtime.repositories.Delete(repoName, tag, img.Id); err != nil { +func (srv *Server) deleteImage(img *Image, repoName, tag string) error { + //Untag the current image + if err := srv.runtime.repositories.Delete(repoName, tag); err != nil { return err } - byId := srv.runtime.repositories.ById() - if len(byId[img.Id]) == 0 { - byParents, err := srv.runtime.graph.ByParent() - if err != nil { - return err - } - if err := srv.deleteImageChildren(img.Id, byId, byParents); err != nil { + if len(srv.runtime.repositories.ById()[img.Id]) == 0 { + if err := srv.deleteImageAndChildren(img.Id); err != nil { + if err != ErrImageReferenced { + return err + } + } else if err := srv.deleteImageParents(img); err != nil { if err != ErrImageReferenced { return err } - } else { - srv.deleteImageParents(img) } - } return nil } func (srv *Server) ImageDelete(name string) error { + img, err := srv.runtime.repositories.LookupImage(name) + if err != nil { + return fmt.Errorf("No such image: %s", name) + } + var tag string if strings.Contains(name, ":") { nameParts := strings.Split(name, ":") @@ -792,40 +793,7 @@ func (srv *Server) ImageDelete(name string) error { tag = nameParts[1] } - srv.deleteImage(name, tag) - - // if the images is referenced several times - - // check is the image to delete isn't parent of another image - byParent, _ := srv.runtime.graph.ByParent() - if childs, exists := byParent[img.Id]; exists { - if strings.Contains(img.Id, name) { - return fmt.Errorf("Conflict with %s, %s was not removed", childs[0].ShortId(), name) - } - if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { - return err - } - return nil - } - parents, _ := img.History() - for _, parent := range parents { - byParent, _ = srv.runtime.graph.ByParent() - //stop if image has children - if _, exists := byParent[parent.Id]; exists { - break - } - //stop if image is tagged and it is not the first image we delete - if _, hasTags := srv.runtime.repositories.ById()[parent.Id]; hasTags && img.Id != parent.Id { - break - } - if err := srv.runtime.graph.Delete(parent.Id); err != nil { - return fmt.Errorf("Error deleting image %s: %s", name, err.Error()) - } - if err := srv.runtime.repositories.Delete(name, tag, img.Id); err != nil { - return err - } - } - return nil + return srv.deleteImage(img, name, tag) } func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) { diff --git a/tags.go b/tags.go index 4a54398c5..1148203d3 100644 --- a/tags.go +++ b/tags.go @@ -110,7 +110,27 @@ func (store *TagStore) ImageName(id string) string { return utils.TruncateId(id) } -func (store *TagStore) Delete(repoName, tag, imageName string) error { +func (store *TagStore) DeleteAll(id string) error { + names, exists := store.ById()[id] + if !exists || len(names) == 0 { + return nil + } + for _, name := range names { + if strings.Contains(name, ":") { + nameParts := strings.Split(name, ":") + if err := store.Delete(nameParts[0], nameParts[1]); err != nil { + return err + } + } else { + if err := store.Delete(name, ""); err != nil { + return err + } + } + } + return nil +} + +func (store *TagStore) Delete(repoName, tag string) error { if err := store.Reload(); err != nil { return err } From 9060b5c2f52d29dec1920eb89ee0d48341540e3b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 31 May 2013 14:37:02 +0000 Subject: [PATCH 12/26] added proper returns type, move the auto-prune in v1.1 api --- api.go | 17 +++++++-- api_params.go | 5 +++ api_test.go | 11 ++++-- commands.go | 18 ++++++++-- docs/sources/api/docker_remote_api.rst | 16 ++++++++- server.go | 48 +++++++++++++++++--------- server_test.go | 4 +-- tags.go | 15 ++++---- 8 files changed, 102 insertions(+), 32 deletions(-) diff --git a/api.go b/api.go index 1759257e6..586fcf7a6 100644 --- a/api.go +++ b/api.go @@ -450,10 +450,23 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R return fmt.Errorf("Missing parameter") } name := vars["name"] - if err := srv.ImageDelete(name); err != nil { + imgs, err := srv.ImageDelete(name, version > 1.0) + if err != nil { return err } - w.WriteHeader(http.StatusNoContent) + if imgs != nil { + if len(*imgs) != 0 { + b, err := json.Marshal(imgs) + if err != nil { + return err + } + writeJson(w, b) + } else { + return fmt.Errorf("Conflict, %s wasn't deleted", name) + } + } else { + w.WriteHeader(http.StatusNoContent) + } return nil } diff --git a/api_params.go b/api_params.go index 1a24ab287..6f759f2d8 100644 --- a/api_params.go +++ b/api_params.go @@ -23,6 +23,11 @@ type ApiInfo struct { NGoroutines int `json:",omitempty"` } +type ApiRmi struct { + Deleted string `json:",omitempty"` + Untagged string `json:",omitempty"` +} + type ApiContainers struct { Id string Image string diff --git a/api_test.go b/api_test.go index 844d15cc1..61d0bcb5f 100644 --- a/api_test.go +++ b/api_test.go @@ -1271,10 +1271,17 @@ func TestDeleteImages(t *testing.T) { if err := deleteImages(srv, API_VERSION, r, req, map[string]string{"name": "test:test"}); err != nil { t.Fatal(err) } - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) + if r.Code != http.StatusOK { + t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } + var outs []ApiRmi + if err := json.Unmarshal(r.Body.Bytes(), &outs); err != nil { + t.Fatal(err) + } + if len(outs) != 1 { + t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs)) + } images, err = srv.Images(false, "") if err != nil { t.Fatal(err) diff --git a/commands.go b/commands.go index 0289048b8..42dc1b06a 100644 --- a/commands.go +++ b/commands.go @@ -567,10 +567,22 @@ func (cli *DockerCli) CmdRmi(args ...string) error { } for _, name := range cmd.Args() { - if _, _, err := cli.call("DELETE", "/images/"+name, nil); err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) + body, _, err := cli.call("DELETE", "/images/"+name, nil) + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err) } else { - fmt.Println(name) + var outs []ApiRmi + err = json.Unmarshal(body, &outs) + if err != nil { + return err + } + for _, out := range outs { + if out.Deleted != "" { + fmt.Println("Deleted:", out.Deleted) + } else { + fmt.Println("Untagged:", out.Untagged) + } + } } } return nil diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index b94136763..60285fc79 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -750,13 +750,27 @@ Remove an image DELETE /images/test HTTP/1.1 - **Example response**: + **Example response v1.0**: .. sourcecode:: http HTTP/1.1 204 OK + **Example response v1.1**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error :statuscode 204: no error :statuscode 404: no such image :statuscode 409: conflict diff --git a/server.go b/server.go index 2af1e3cd8..3d2cfaa50 100644 --- a/server.go +++ b/server.go @@ -707,7 +707,7 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { var ErrImageReferenced = errors.New("Image referenced by a repository") -func (srv *Server) deleteImageAndChildren(id string) error { +func (srv *Server) deleteImageAndChildren(id string, imgs *[]ApiRmi) error { // If the image is referenced by a repo, do not delete if len(srv.runtime.repositories.ById()[id]) != 0 { return ErrImageReferenced @@ -720,7 +720,7 @@ func (srv *Server) deleteImageAndChildren(id string) error { return err } for _, img := range byParents[id] { - if err := srv.deleteImageAndChildren(img.Id); err != nil { + if err := srv.deleteImageAndChildren(img.Id, imgs); err != nil { if err != ErrImageReferenced { return err } else { @@ -741,49 +741,65 @@ func (srv *Server) deleteImageAndChildren(id string) error { if err := srv.runtime.repositories.DeleteAll(id); err != nil { return err } - return srv.runtime.graph.Delete(id) + err := srv.runtime.graph.Delete(id) + if err != nil { + return err + } + *imgs = append(*imgs, ApiRmi{Deleted: utils.TruncateId(id)}) + return nil } return nil } -func (srv *Server) deleteImageParents(img *Image) error { +func (srv *Server) deleteImageParents(img *Image, imgs *[]ApiRmi) error { if img.Parent != "" { parent, err := srv.runtime.graph.Get(img.Parent) if err != nil { return err } // Remove all children images - if err := srv.deleteImageAndChildren(img.Parent); err != nil { + if err := srv.deleteImageAndChildren(img.Parent, imgs); err != nil { return err } - return srv.deleteImageParents(parent) + return srv.deleteImageParents(parent, imgs) } return nil } -func (srv *Server) deleteImage(img *Image, repoName, tag string) error { +func (srv *Server) deleteImage(img *Image, repoName, tag string) (*[]ApiRmi, error) { //Untag the current image - if err := srv.runtime.repositories.Delete(repoName, tag); err != nil { - return err + var imgs []ApiRmi + tagDeleted, err := srv.runtime.repositories.Delete(repoName, tag) + if err != nil { + return nil, err + } + if tagDeleted { + imgs = append(imgs, ApiRmi{Untagged: img.ShortId()}) } if len(srv.runtime.repositories.ById()[img.Id]) == 0 { - if err := srv.deleteImageAndChildren(img.Id); err != nil { + if err := srv.deleteImageAndChildren(img.Id, &imgs); err != nil { if err != ErrImageReferenced { - return err + return &imgs, err } - } else if err := srv.deleteImageParents(img); err != nil { + } else if err := srv.deleteImageParents(img, &imgs); err != nil { if err != ErrImageReferenced { - return err + return &imgs, err } } } - return nil + return &imgs, nil } -func (srv *Server) ImageDelete(name string) error { +func (srv *Server) ImageDelete(name string, autoPrune bool) (*[]ApiRmi, error) { img, err := srv.runtime.repositories.LookupImage(name) if err != nil { - return fmt.Errorf("No such image: %s", name) + return nil, fmt.Errorf("No such image: %s", name) + } + if !autoPrune { + if err := srv.runtime.graph.Delete(img.Id); err != nil { + return nil, fmt.Errorf("Error deleting image %s: %s", name, err.Error()) + } + return nil, nil } var tag string diff --git a/server_test.go b/server_test.go index 541c927b8..52b347926 100644 --- a/server_test.go +++ b/server_test.go @@ -29,7 +29,7 @@ func TestContainerTagImageDelete(t *testing.T) { t.Errorf("Excepted 3 images, %d found", len(images)) } - if err := srv.ImageDelete("utest/docker:tag2"); err != nil { + if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil { t.Fatal(err) } @@ -42,7 +42,7 @@ func TestContainerTagImageDelete(t *testing.T) { t.Errorf("Excepted 2 images, %d found", len(images)) } - if err := srv.ImageDelete("utest:tag1"); err != nil { + if _, err := srv.ImageDelete("utest:tag1", true); err != nil { t.Fatal(err) } diff --git a/tags.go b/tags.go index 1148203d3..630727aa6 100644 --- a/tags.go +++ b/tags.go @@ -118,11 +118,11 @@ func (store *TagStore) DeleteAll(id string) error { for _, name := range names { if strings.Contains(name, ":") { nameParts := strings.Split(name, ":") - if err := store.Delete(nameParts[0], nameParts[1]); err != nil { + if _, err := store.Delete(nameParts[0], nameParts[1]); err != nil { return err } } else { - if err := store.Delete(name, ""); err != nil { + if _, err := store.Delete(name, ""); err != nil { return err } } @@ -130,9 +130,10 @@ func (store *TagStore) DeleteAll(id string) error { return nil } -func (store *TagStore) Delete(repoName, tag string) error { +func (store *TagStore) Delete(repoName, tag string) (bool, error) { + deleted := false if err := store.Reload(); err != nil { - return err + return false, err } if r, exists := store.Repositories[repoName]; exists { if tag != "" { @@ -141,16 +142,18 @@ func (store *TagStore) Delete(repoName, tag string) error { if len(r) == 0 { delete(store.Repositories, repoName) } + deleted = true } else { - return fmt.Errorf("No such tag: %s:%s", repoName, tag) + return false, fmt.Errorf("No such tag: %s:%s", repoName, tag) } } else { delete(store.Repositories, repoName) + deleted = true } } else { fmt.Errorf("No such repository: %s", repoName) } - return store.Save() + return deleted, store.Save() } func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { From d26a3b37a6a8d42b9e7cb7486b928170c43e052e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 3 Jun 2013 20:00:15 +0000 Subject: [PATCH 13/26] allow docker run : --- tags.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tags.go b/tags.go index 5bc2978e0..140182890 100644 --- a/tags.go +++ b/tags.go @@ -151,14 +151,20 @@ func (store *TagStore) Get(repoName string) (Repository, error) { return nil, nil } -func (store *TagStore) GetImage(repoName, tag string) (*Image, error) { +func (store *TagStore) GetImage(repoName, tagOrId string) (*Image, error) { repo, err := store.Get(repoName) if err != nil { return nil, err } else if repo == nil { return nil, nil } - if revision, exists := repo[tag]; exists { + //go through all the tags, to see if tag is in fact an ID + for _, revision := range repo { + if utils.TruncateId(revision) == tagOrId { + return store.graph.Get(revision) + } + } + if revision, exists := repo[tagOrId]; exists { return store.graph.Get(revision) } return nil, nil From 0ca88443985e7a944106ed4ceaf877a97f1ca2ec Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 3 Jun 2013 17:39:29 -0700 Subject: [PATCH 14/26] Fix stale command with stdout is not allocated --- container.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/container.go b/container.go index c6b7c8a51..ef449653c 100644 --- a/container.go +++ b/container.go @@ -355,6 +355,17 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s errors <- err }() } + } else { + go func() { + defer stdinCloser.Close() + + cStdout, err := container.StdoutPipe() + if err != nil { + utils.Debugf("Error stdout pipe") + return + } + io.Copy(&utils.NopWriter{}, cStdout) + }() } if stderr != nil { nJobs += 1 @@ -381,7 +392,19 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s errors <- err }() } + } else { + go func() { + defer stdinCloser.Close() + + cStderr, err := container.StdoutPipe() + if err != nil { + utils.Debugf("Error stdout pipe") + return + } + io.Copy(&utils.NopWriter{}, cStderr) + }() } + return utils.Go(func() error { if cStdout != nil { defer cStdout.Close() From 63e80384ea753c74046c2a4c3f64229c359f466f Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 4 Jun 2013 14:35:32 -0700 Subject: [PATCH 15/26] Fix nil pointer on some situatuion --- container.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/container.go b/container.go index ef449653c..0b5441912 100644 --- a/container.go +++ b/container.go @@ -357,14 +357,15 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s } } else { go func() { - defer stdinCloser.Close() + if stdinCloser != nil { + defer stdinCloser.Close() + } - cStdout, err := container.StdoutPipe() - if err != nil { + if cStdout, err := container.StdoutPipe(); err != nil { utils.Debugf("Error stdout pipe") - return + } else { + io.Copy(&utils.NopWriter{}, cStdout) } - io.Copy(&utils.NopWriter{}, cStdout) }() } if stderr != nil { @@ -394,14 +395,15 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s } } else { go func() { - defer stdinCloser.Close() + if stdinCloser != nil { + defer stdinCloser.Close() + } - cStderr, err := container.StdoutPipe() - if err != nil { + if cStderr, err := container.StdoutPipe(); err != nil { utils.Debugf("Error stdout pipe") - return + } else { + io.Copy(&utils.NopWriter{}, cStderr) } - io.Copy(&utils.NopWriter{}, cStderr) }() } From f67ea78cce83114998390c16305a6869c72f5100 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 5 Jun 2013 12:59:05 +0000 Subject: [PATCH 16/26] move xino stuff to /dev/shm --- image.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/image.go b/image.go index 7a98ef41a..4bd8f2df3 100644 --- a/image.go +++ b/image.go @@ -126,6 +126,8 @@ func MountAUFS(ro []string, rw string, target string) error { } branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) + branches += ",xino=/dev/shm/aufs.xino" + //if error, try to load aufs kernel module if err := mount("none", target, "aufs", 0, branches); err != nil { log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...") From efa7ea592c1819f006acf9480c10e50ec63b3001 Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 6 Jun 2013 22:06:12 +0300 Subject: [PATCH 17/26] docs: warn about build data tx & ADD w/o context This updates the documentation to mention that: 1. a lot of data may get sent to the docker daemon if there is a lot of data in the directory passed to docker build 2. ADD doesn't work in the absence of the context 3. running without a context doesn't send file data to the docker daemon 4. explain that the data sent to the docker daemon will be used by ADD commands --- docs/sources/commandline/command/build.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst index 81120b22d..3e30cbdd5 100644 --- a/docs/sources/commandline/command/build.rst +++ b/docs/sources/commandline/command/build.rst @@ -19,10 +19,14 @@ Examples docker build . -This will take the local Dockerfile +| This will read the Dockerfile from the current directory. It will also send any other files and directories found in the current directory to the docker daemon. +| The contents of this directory would be used by ADD commands found within the Dockerfile. +| This will send a lot of data to the docker daemon if the current directory contains a lot of data. +| .. code-block:: bash docker build - -This will read a Dockerfile form Stdin without context +| This will read a Dockerfile from Stdin without context. Due to the lack of a context, no contents of any local directory will be sent to the docker daemon. +| ADD doesn't work when running in this mode due to the absence of the context, thus having no source files to copy to the container. From 4b3a381f39bc90b36877f4832fe5c53de65a1ec3 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 7 Jun 2013 00:39:43 +0300 Subject: [PATCH 18/26] docs: build: ADD copies just needed data w/ full path --- docs/sources/commandline/command/build.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/commandline/command/build.rst b/docs/sources/commandline/command/build.rst index 3e30cbdd5..254b0371a 100644 --- a/docs/sources/commandline/command/build.rst +++ b/docs/sources/commandline/command/build.rst @@ -22,6 +22,7 @@ Examples | This will read the Dockerfile from the current directory. It will also send any other files and directories found in the current directory to the docker daemon. | The contents of this directory would be used by ADD commands found within the Dockerfile. | This will send a lot of data to the docker daemon if the current directory contains a lot of data. +| If the absolute path is provided instead of '.', only the files and directories required by the ADD commands from the Dockerfile will be added to the context and transferred to the docker daemon. | .. code-block:: bash From 7169212683ba02e2da4c80792702c5210f1c16ea Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 10 Jun 2013 11:08:40 -0700 Subject: [PATCH 19/26] Fix typo --- container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container.go b/container.go index 0b5441912..5db98ac38 100644 --- a/container.go +++ b/container.go @@ -399,7 +399,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s defer stdinCloser.Close() } - if cStderr, err := container.StdoutPipe(); err != nil { + if cStderr, err := container.StderrPipe(); err != nil { utils.Debugf("Error stdout pipe") } else { io.Copy(&utils.NopWriter{}, cStderr) From ecae342434aeccb05b3cffe30c34051d84afdb74 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 12 Jun 2013 10:50:47 -0700 Subject: [PATCH 20/26] New roadmap item: advanced port redirections --- hack/ROADMAP.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/hack/ROADMAP.md b/hack/ROADMAP.md index 61387e21c..f335db695 100644 --- a/hack/ROADMAP.md +++ b/hack/ROADMAP.md @@ -86,3 +86,20 @@ Production-ready Docker is still alpha software, and not suited for production. We are working hard to get there, and we are confident that it will be possible within a few months. + +Advanced port redirections +-------------------------- + +Docker currently supports 2 flavors of port redirection: STATIC->STATIC (eg. "redirect public port 80 to private port 80") +and RANDOM->STATIC (eg. "redirect any public port to private port 80"). + +With these 2 flavors, docker can support the majority of backend programs out there. But some applications have more exotic +requirements, generally to implement custom clustering techniques. These applications include Hadoop, MongoDB, Riak, RabbitMQ, +Disco, and all programs relying on Erlang's OTP. + +To support these applications, Docker needs to support more advanced redirection flavors, including: + +* RANDOM->RANDOM +* STATIC1->STATIC2 + +These flavors should be implemented without breaking existing semantics, if at all possible. From 04cca097ae9f7a8f64dbea1e12f3b871f6a67d65 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Wed, 12 Jun 2013 15:50:09 -0600 Subject: [PATCH 21/26] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bcad502ab..1c909e543 100644 --- a/README.md +++ b/README.md @@ -373,5 +373,8 @@ Standard Container Specification ### Legal -Transfers Docker shall be in accordance with any applicable export control or other legal requirements. +Transfers of Docker shall be in accordance with applicable export controls of any country and all other applicable +legal requirements. Docker shall not be distributed or downloaded to or in Cuba, Iran, North Korea, Sudan or Syria +and shall not be distributed or downloaded to any person on the Denied Persons List administered by the U.S. +Department of Commerce. From 81a11a3c30d01040451898f0e88a19363568edc3 Mon Sep 17 00:00:00 2001 From: Sam Alba Date: Wed, 12 Jun 2013 15:50:30 -0600 Subject: [PATCH 22/26] Update NOTICE --- NOTICE | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/NOTICE b/NOTICE index f55cc6950..a11ff9404 100644 --- a/NOTICE +++ b/NOTICE @@ -3,4 +3,9 @@ Copyright 2012-2013 dotCloud, inc. This product includes software developed at dotCloud, inc. (http://www.dotcloud.com). -This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License. \ No newline at end of file +This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License. + +Transfers of Docker shall be in accordance with applicable export controls of any country and all other applicable +legal requirements. Docker shall not be distributed or downloaded to or in Cuba, Iran, North Korea, Sudan or Syria +and shall not be distributed or downloaded to any person on the Denied Persons List administered by the U.S. +Department of Commerce. From f57175cbadbe7d8f48bfd5e3594116913de6e3ab Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 12 Jun 2013 15:21:28 -0700 Subject: [PATCH 23/26] FIXME: a loose collection of FIXMEs for internal use by the maintainers --- FIXME | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 FIXME diff --git a/FIXME b/FIXME new file mode 100644 index 000000000..e252fb258 --- /dev/null +++ b/FIXME @@ -0,0 +1,18 @@ + +## FIXME + +This file is a loose collection of things to improve in the codebase, for the internal +use of the maintainers. + +They are not big enough to be in the roadmap, not user-facing enough to be github issues, +and not important enough to be discussed in the mailing list. + +They are just like FIXME comments in the source code, except we're not sure where in the source +to put them - so we put them here :) + + +* Merge Runtime, Server and Builder into Runtime +* Run linter on codebase +* Unify build commands and regular commands +* Move source code into src/ subdir for clarity +* Clean up the Makefile, it's a mess From 78a76ad50eaf9d5a99336a47b2b78f547fa8a972 Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 13 Jun 2013 08:59:41 +0300 Subject: [PATCH 24/26] use Go 1.1.1 to build docker --- hack/dockerbuilder/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/dockerbuilder/Dockerfile b/hack/dockerbuilder/Dockerfile index 5f9e9c35a..5b2504d37 100644 --- a/hack/dockerbuilder/Dockerfile +++ b/hack/dockerbuilder/Dockerfile @@ -13,7 +13,7 @@ run apt-get update # Packages required to checkout, build and upload docker run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl -run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.linux-amd64.tar.gz +run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz run tar -C /usr/local -xzf /go.tar.gz run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile From 45a8945746a205ea513efc17bb242c63362c79a9 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 13 Jun 2013 13:17:56 +0000 Subject: [PATCH 25/26] added test --- runtime_test.go | 2 +- tags.go | 2 +- tags_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tags_test.go diff --git a/runtime_test.go b/runtime_test.go index 119051027..eea69aa5d 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -17,7 +17,7 @@ import ( ) const unitTestImageName string = "docker-ut" - +const unitTestImageId string = "e9aa60c60128cad1" const unitTestStoreBase string = "/var/lib/docker/unit-tests" func nuke(runtime *Runtime) error { diff --git a/tags.go b/tags.go index 140182890..7133649f6 100644 --- a/tags.go +++ b/tags.go @@ -160,7 +160,7 @@ func (store *TagStore) GetImage(repoName, tagOrId string) (*Image, error) { } //go through all the tags, to see if tag is in fact an ID for _, revision := range repo { - if utils.TruncateId(revision) == tagOrId { + if strings.HasPrefix(revision, tagOrId) { return store.graph.Get(revision) } } diff --git a/tags_test.go b/tags_test.go new file mode 100644 index 000000000..5d0327590 --- /dev/null +++ b/tags_test.go @@ -0,0 +1,49 @@ +package docker + +import ( + "testing" +) + +func TestLookupImage(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil { + t.Fatal(err) + } else if img == nil { + t.Errorf("Expected 1 image, none found") + } + + if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + DEFAULT_TAG); err != nil { + t.Fatal(err) + } else if img == nil { + t.Errorf("Expected 1 image, none found") + } + + if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + "fail"); err == nil { + t.Errorf("Expected error, none found") + } else if img != nil { + t.Errorf("Expected 0 image, 1 found") + } + + if img, err := runtime.repositories.LookupImage("fail:fail"); err == nil { + t.Errorf("Expected error, none found") + } else if img != nil { + t.Errorf("Expected 0 image, 1 found") + } + + if img, err := runtime.repositories.LookupImage(unitTestImageId); err != nil { + t.Fatal(err) + } else if img == nil { + t.Errorf("Expected 1 image, none found") + } + + if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + unitTestImageId); err != nil { + t.Fatal(err) + } else if img == nil { + t.Errorf("Expected 1 image, none found") + } +} From 42d1c36a5c775a37a0173cf315086fde1c1b7dd8 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 13 Jun 2013 10:25:43 -0700 Subject: [PATCH 26/26] Fix merge issue --- tags_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tags_test.go b/tags_test.go index 5d0327590..90bc05640 100644 --- a/tags_test.go +++ b/tags_test.go @@ -17,7 +17,7 @@ func TestLookupImage(t *testing.T) { t.Errorf("Expected 1 image, none found") } - if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + DEFAULT_TAG); err != nil { + if img, err := runtime.repositories.LookupImage(unitTestImageName + ":" + DEFAULTTAG); err != nil { t.Fatal(err) } else if img == nil { t.Errorf("Expected 1 image, none found")