diff --git a/api/client.go b/api/client.go index 338a5b0de1..10075ae613 100644 --- a/api/client.go +++ b/api/client.go @@ -890,6 +890,7 @@ func (cli *DockerCli) CmdRm(args ...string) error { cmd := cli.Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers") v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated to the container") link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link and not the underlying container") + force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of running container") if err := cmd.Parse(args); err != nil { return nil @@ -905,6 +906,9 @@ func (cli *DockerCli) CmdRm(args ...string) error { if *link { val.Set("link", "1") } + if *force { + val.Set("force", "1") + } var encounteredError error for _, name := range cmd.Args() { diff --git a/api/server.go b/api/server.go index 8fc6b4f68b..6fafe60f9f 100644 --- a/api/server.go +++ b/api/server.go @@ -606,6 +606,7 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon job := eng.Job("container_delete", vars["name"]) job.Setenv("removeVolume", r.Form.Get("v")) job.Setenv("removeLink", r.Form.Get("link")) + job.Setenv("forceRemove", r.Form.Get("force")) if err := job.Run(); err != nil { return err } diff --git a/docs/sources/reference/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst index 3733effaf8..e1071bf085 100644 --- a/docs/sources/reference/api/docker_remote_api.rst +++ b/docs/sources/reference/api/docker_remote_api.rst @@ -51,6 +51,11 @@ 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. +.. http:delete:: /containers/(id) + + **New!** You can now use the force paramter to force delete a container, even if + it is currently running + v1.9 **** 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 a6eec3551f..ed63525e7e 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.10.rst @@ -597,6 +597,7 @@ Remove a container HTTP/1.1 204 OK :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :query force: 1/True/true or 0/False/false, Removes the container even if it was running. Default false :statuscode 204: no error :statuscode 400: bad parameter :statuscode 404: no such container diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index c7ce421d88..2e49cd5ca5 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -995,7 +995,8 @@ The last container is marked as a ``Ghost`` container. It is a container that wa Usage: docker rm [OPTIONS] CONTAINER Remove one or more containers - --link="": Remove the link instead of the actual container + -l, --link="": Remove the link instead of the actual container + -f, --force=false: Force removal of running container Known Issues (rm) ~~~~~~~~~~~~~~~~~ diff --git a/integration/server_test.go b/integration/server_test.go index 1247e8d2d8..69a90527bf 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -199,6 +199,68 @@ func TestCreateRmVolumes(t *testing.T) { } } +func TestCreateRmRunning(t *testing.T) { + eng := NewTestEngine(t) + defer mkRuntimeFromEngine(eng, t).Nuke() + + config, hostConfig, _, err := runconfig.Parse([]string{"-name", "foo", unitTestImageID, "sleep 300"}, nil) + if err != nil { + t.Fatal(err) + } + + id := createTestContainer(eng, config, t) + + job := eng.Job("containers") + job.SetenvBool("all", true) + outs, err := job.Stdout.AddListTable() + if err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { + t.Fatal(err) + } + + if len(outs.Data) != 1 { + t.Errorf("Expected 1 container, %v found", len(outs.Data)) + } + + job = eng.Job("start", id) + if err := job.ImportEnv(hostConfig); err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { + t.Fatal(err) + } + + // Test cannot remove running container + job = eng.Job("container_delete", id) + job.SetenvBool("forceRemove", false) + if err := job.Run(); err == nil { + t.Fatal("Expected container delete to fail") + } + + // Test can force removal of running container + job = eng.Job("container_delete", id) + job.SetenvBool("forceRemove", true) + if err := job.Run(); err != nil { + t.Fatal(err) + } + + job = eng.Job("containers") + job.SetenvBool("all", true) + outs, err = job.Stdout.AddListTable() + if err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { + t.Fatal(err) + } + + if len(outs.Data) != 0 { + t.Errorf("Expected 0 container, %v found", len(outs.Data)) + } +} + func TestCommit(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() diff --git a/server.go b/server.go index dafa856abf..024b8aa3c4 100644 --- a/server.go +++ b/server.go @@ -1713,6 +1713,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { name := job.Args[0] removeVolume := job.GetenvBool("removeVolume") removeLink := job.GetenvBool("removeLink") + forceRemove := job.GetenvBool("forceRemove") container := srv.runtime.Get(name) @@ -1750,7 +1751,13 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { if container != nil { if container.State.IsRunning() { - return job.Errorf("Impossible to remove a running container, please stop it first") + if forceRemove { + if err := container.Stop(5); err != nil { + return job.Errorf("Could not stop running container, cannot remove - %v", err) + } + } else { + return job.Errorf("Impossible to remove a running container, please stop it first or use -f") + } } if err := srv.runtime.Destroy(container); err != nil { return job.Errorf("Cannot destroy container %s: %s", name, err)