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 {