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 5096b05212..17075dae7f 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 453574946d..d749ead73c 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 7b90252864..fe1cbecd9b 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 1b9cd19c83..b7aa692477 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 8b97555ba6..03030e120f 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 03030e120f..1ad1d80a4f 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 1ad1d80a4f..d709aba7e2 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 8984d00cd9..53a895918c 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 921aebe4ce..283ebb130d 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 33ba8125de..26cea0189e 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 03f1d4b9cf..9541e27de7 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 d90e7fda08..9a2ab4edf5 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 fe1cbecd9b..f223fbe53d 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 f6b3a93a95..4a54398c5f 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 8f2befb237..1759257e6e 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 fb1cd211ad..844d15cc13 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 704f5ac48d..2af7e548fc 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 d1a089c3a9..2e819728b4 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 743b33d545..541c927b83 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 2e819728b4..64bde12c4d 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 64bde12c4d..69ead3aff6 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 2af7e548fc..0289048b89 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 69ead3aff6..126ac7d6e9 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 126ac7d6e9..2af1e3cd8f 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 4a54398c5f..1148203d3d 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 1759257e6e..586fcf7a64 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 1a24ab2875..6f759f2d80 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 844d15cc13..61d0bcb5ff 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 0289048b89..42dc1b06af 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 b941367632..60285fc792 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 2af1e3cd8f..3d2cfaa500 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 541c927b83..52b3479263 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 1148203d3d..630727aa60 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 5bc2978e09..140182890d 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 c6b7c8a51c..ef449653c4 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 | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/container.go b/container.go index ef449653c4..0b54419122 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() - - cStdout, err := container.StdoutPipe() - if err != nil { - utils.Debugf("Error stdout pipe") - return + if stdinCloser != nil { + defer stdinCloser.Close() + } + + if cStdout, err := container.StdoutPipe(); err != nil { + utils.Debugf("Error stdout pipe") + } 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() - - cStderr, err := container.StdoutPipe() - if err != nil { - utils.Debugf("Error stdout pipe") - return + if stdinCloser != nil { + defer stdinCloser.Close() + } + + if cStderr, err := container.StdoutPipe(); err != nil { + utils.Debugf("Error stdout pipe") + } 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 7a98ef41a1..4bd8f2df31 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 81120b22d2..3e30cbdd58 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 3e30cbdd58..254b0371a9 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 0b54419122..5db98ac382 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 61387e21cf..f335db6953 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 bcad502abc..1c909e5431 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 f55cc6950a..a11ff94049 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 0000000000..e252fb2589 --- /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 5f9e9c35ab..5b2504d378 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 1190510279..eea69aa5dc 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 140182890d..7133649f6e 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 0000000000..5d03275909 --- /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 5d03275909..90bc056406 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")