Browse code

Merge pull request #3959 from cpuguy83/3958_add_ability_to_remove_running_container_in_single_command

Add ability to force removal of running container via docker rm -f

unclejack authored on 2014/03/07 07:05:57
Showing 7 changed files
... ...
@@ -890,6 +890,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
890 890
 	cmd := cli.Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
891 891
 	v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated to the container")
892 892
 	link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link and not the underlying container")
893
+	force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of running container")
893 894
 
894 895
 	if err := cmd.Parse(args); err != nil {
895 896
 		return nil
... ...
@@ -905,6 +906,9 @@ func (cli *DockerCli) CmdRm(args ...string) error {
905 905
 	if *link {
906 906
 		val.Set("link", "1")
907 907
 	}
908
+	if *force {
909
+		val.Set("force", "1")
910
+	}
908 911
 
909 912
 	var encounteredError error
910 913
 	for _, name := range cmd.Args() {
... ...
@@ -606,6 +606,7 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon
606 606
 	job := eng.Job("container_delete", vars["name"])
607 607
 	job.Setenv("removeVolume", r.Form.Get("v"))
608 608
 	job.Setenv("removeLink", r.Form.Get("link"))
609
+	job.Setenv("forceRemove", r.Form.Get("force"))
609 610
 	if err := job.Run(); err != nil {
610 611
 		return err
611 612
 	}
... ...
@@ -51,6 +51,11 @@ What's new
51 51
    **New!** You can now use the force parameter to force delete of an image, even if it's
52 52
    tagged in multiple repositories.
53 53
 
54
+.. http:delete:: /containers/(id)
55
+
56
+  **New!** You can now use the force paramter to force delete a container, even if
57
+  it is currently running
58
+
54 59
 v1.9
55 60
 ****
56 61
 
... ...
@@ -597,6 +597,7 @@ Remove a container
597 597
            HTTP/1.1 204 OK
598 598
 
599 599
         :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false
600
+        :query force: 1/True/true or 0/False/false, Removes the container even if it was running. Default false
600 601
         :statuscode 204: no error
601 602
         :statuscode 400: bad parameter
602 603
         :statuscode 404: no such container
... ...
@@ -995,7 +995,8 @@ The last container is marked as a ``Ghost`` container. It is a container that wa
995 995
     Usage: docker rm [OPTIONS] CONTAINER
996 996
 
997 997
     Remove one or more containers
998
-        --link="": Remove the link instead of the actual container
998
+        -l, --link="": Remove the link instead of the actual container
999
+        -f, --force=false: Force removal of running container
999 1000
 
1000 1001
 Known Issues (rm)
1001 1002
 ~~~~~~~~~~~~~~~~~
... ...
@@ -199,6 +199,68 @@ func TestCreateRmVolumes(t *testing.T) {
199 199
 	}
200 200
 }
201 201
 
202
+func TestCreateRmRunning(t *testing.T) {
203
+	eng := NewTestEngine(t)
204
+	defer mkRuntimeFromEngine(eng, t).Nuke()
205
+
206
+	config, hostConfig, _, err := runconfig.Parse([]string{"-name", "foo", unitTestImageID, "sleep 300"}, nil)
207
+	if err != nil {
208
+		t.Fatal(err)
209
+	}
210
+
211
+	id := createTestContainer(eng, config, t)
212
+
213
+	job := eng.Job("containers")
214
+	job.SetenvBool("all", true)
215
+	outs, err := job.Stdout.AddListTable()
216
+	if err != nil {
217
+		t.Fatal(err)
218
+	}
219
+	if err := job.Run(); err != nil {
220
+		t.Fatal(err)
221
+	}
222
+
223
+	if len(outs.Data) != 1 {
224
+		t.Errorf("Expected 1 container, %v found", len(outs.Data))
225
+	}
226
+
227
+	job = eng.Job("start", id)
228
+	if err := job.ImportEnv(hostConfig); err != nil {
229
+		t.Fatal(err)
230
+	}
231
+	if err := job.Run(); err != nil {
232
+		t.Fatal(err)
233
+	}
234
+
235
+	// Test cannot remove running container
236
+	job = eng.Job("container_delete", id)
237
+	job.SetenvBool("forceRemove", false)
238
+	if err := job.Run(); err == nil {
239
+		t.Fatal("Expected container delete to fail")
240
+	}
241
+
242
+	// Test can force removal of running container
243
+	job = eng.Job("container_delete", id)
244
+	job.SetenvBool("forceRemove", true)
245
+	if err := job.Run(); err != nil {
246
+		t.Fatal(err)
247
+	}
248
+
249
+	job = eng.Job("containers")
250
+	job.SetenvBool("all", true)
251
+	outs, err = job.Stdout.AddListTable()
252
+	if err != nil {
253
+		t.Fatal(err)
254
+	}
255
+	if err := job.Run(); err != nil {
256
+		t.Fatal(err)
257
+	}
258
+
259
+	if len(outs.Data) != 0 {
260
+		t.Errorf("Expected 0 container, %v found", len(outs.Data))
261
+	}
262
+}
263
+
202 264
 func TestCommit(t *testing.T) {
203 265
 	eng := NewTestEngine(t)
204 266
 	defer mkRuntimeFromEngine(eng, t).Nuke()
... ...
@@ -1713,6 +1713,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status {
1713 1713
 	name := job.Args[0]
1714 1714
 	removeVolume := job.GetenvBool("removeVolume")
1715 1715
 	removeLink := job.GetenvBool("removeLink")
1716
+	forceRemove := job.GetenvBool("forceRemove")
1716 1717
 
1717 1718
 	container := srv.runtime.Get(name)
1718 1719
 
... ...
@@ -1750,7 +1751,13 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status {
1750 1750
 
1751 1751
 	if container != nil {
1752 1752
 		if container.State.IsRunning() {
1753
-			return job.Errorf("Impossible to remove a running container, please stop it first")
1753
+			if forceRemove {
1754
+				if err := container.Stop(5); err != nil {
1755
+					return job.Errorf("Could not stop running container, cannot remove - %v", err)
1756
+				}
1757
+			} else {
1758
+				return job.Errorf("Impossible to remove a running container, please stop it first or use -f")
1759
+			}
1754 1760
 		}
1755 1761
 		if err := srv.runtime.Destroy(container); err != nil {
1756 1762
 			return job.Errorf("Cannot destroy container %s: %s", name, err)