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 }