diff --git a/api/client.go b/api/client.go index 8ee61f6c22..1aceed50e7 100644 --- a/api/client.go +++ b/api/client.go @@ -817,8 +817,9 @@ func (cli *DockerCli) CmdPort(args ...string) error { // 'docker rmi IMAGE' removes all images with the name IMAGE func (cli *DockerCli) CmdRmi(args ...string) error { var ( - cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images") - force = cmd.Bool([]string{"f", "-force"}, false, "Force") + cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images") + force = cmd.Bool([]string{"f", "-force"}, false, "Force") + noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") ) if err := cmd.Parse(args); err != nil { return nil @@ -832,6 +833,9 @@ func (cli *DockerCli) CmdRmi(args ...string) error { if *force { v.Set("force", "1") } + if *noprune { + v.Set("noprune", "1") + } var encounteredError error for _, name := range cmd.Args() { diff --git a/api/server.go b/api/server.go index f3c5b0fc2d..5ae640623b 100644 --- a/api/server.go +++ b/api/server.go @@ -623,6 +623,7 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr var job = eng.Job("image_delete", vars["name"]) streamJSON(job, w, false) job.Setenv("force", r.Form.Get("force")) + job.Setenv("noprune", r.Form.Get("noprune")) return job.Run() } diff --git a/docs/sources/reference/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst index 93558fa974..ca7463f351 100644 --- a/docs/sources/reference/api/docker_remote_api.rst +++ b/docs/sources/reference/api/docker_remote_api.rst @@ -50,6 +50,7 @@ What's new **New!** You can now use the force parameter to force delete of an image, even if it's tagged in multiple repositories. + **New!** You can now use the noprune parameter to prevent the deletion of parent images .. http:delete:: /containers/(id) diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.rst b/docs/sources/reference/api/docker_remote_api_v1.10.rst index 20af253f0e..649f58196e 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.10.rst @@ -931,6 +931,7 @@ Remove an image ] :query force: 1/True/true or 0/False/false, default false + :query noprune: 1/True/true or 0/False/false, default false :statuscode 200: no error :statuscode 404: no such image :statuscode 409: conflict diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 6d55a0aedc..f302862b9e 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1092,6 +1092,7 @@ containers will not be deleted. Remove one or more images -f, --force=false: Force + --no-prune=false: Do not delete untagged parents Removing tagged images ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/integration/commands_test.go b/integration/commands_test.go index dba15842c7..5804d9f351 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -1062,7 +1062,7 @@ func TestContainerOrphaning(t *testing.T) { // remove the second image by name resp := engine.NewTable("", 0) - if err := srv.DeleteImage(imageName, resp, true, false); err == nil { + if err := srv.DeleteImage(imageName, resp, true, false, false); err == nil { t.Fatal("Expected error, got none") } diff --git a/integration/server_test.go b/integration/server_test.go index 2455c766e3..a401f1306e 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -36,7 +36,7 @@ func TestImageTagImageDelete(t *testing.T) { t.Errorf("Expected %d images, %d found", nExpected, nActual) } - if err := srv.DeleteImage("utest/docker:tag2", engine.NewTable("", 0), true, false); err != nil { + if err := srv.DeleteImage("utest/docker:tag2", engine.NewTable("", 0), true, false, false); err != nil { t.Fatal(err) } @@ -48,7 +48,7 @@ func TestImageTagImageDelete(t *testing.T) { t.Errorf("Expected %d images, %d found", nExpected, nActual) } - if err := srv.DeleteImage("utest:5000/docker:tag3", engine.NewTable("", 0), true, false); err != nil { + if err := srv.DeleteImage("utest:5000/docker:tag3", engine.NewTable("", 0), true, false, false); err != nil { t.Fatal(err) } @@ -57,7 +57,7 @@ func TestImageTagImageDelete(t *testing.T) { nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1 nActual = len(images.Data[0].GetList("RepoTags")) - if err := srv.DeleteImage("utest:tag1", engine.NewTable("", 0), true, false); err != nil { + if err := srv.DeleteImage("utest:tag1", engine.NewTable("", 0), true, false, false); err != nil { t.Fatal(err) } @@ -579,7 +579,7 @@ func TestRmi(t *testing.T) { t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len()) } - if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false); err != nil { + if err = srv.DeleteImage(imageID, engine.NewTable("", 0), true, false, false); err != nil { t.Fatal(err) } @@ -815,7 +815,7 @@ func TestDeleteTagWithExistingContainers(t *testing.T) { // Try to remove the tag imgs := engine.NewTable("", 0) - if err := srv.DeleteImage("utest:tag1", imgs, true, false); err != nil { + if err := srv.DeleteImage("utest:tag1", imgs, true, false, false); err != nil { t.Fatal(err) } diff --git a/server/server.go b/server/server.go index 69b65ce4a5..93fc7b0bb1 100644 --- a/server/server.go +++ b/server/server.go @@ -1839,7 +1839,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force bool) error { +func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force, noprune bool) error { var ( repoName, tag string tags = []string{} @@ -1920,8 +1920,8 @@ func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force boo out.Set("Deleted", img.ID) imgs.Add(out) srv.LogEvent("delete", img.ID, "") - if img.Parent != "" { - err := srv.DeleteImage(img.Parent, imgs, false, force) + if img.Parent != "" && !noprune { + err := srv.DeleteImage(img.Parent, imgs, false, force, noprune) if first { return err } @@ -1938,7 +1938,7 @@ func (srv *Server) ImageDelete(job *engine.Job) engine.Status { return job.Errorf("Usage: %s IMAGE", job.Name) } imgs := engine.NewTable("", 0) - if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force")); err != nil { + if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil { return job.Error(err) } if len(imgs.Data) == 0 {