Browse code

Pause/freeze containers during commit

Initiates a pause before committing a container,
adds a pause option to the commit command, defaulting to 'true'.

Fixes bug: #6267
Fixes bug: #3675

Docker-DCO-1.1-Signed-off-by: Eric Windisch <ewindisch@docker.com> (github: ewindisch)

Eric Windisch authored on 2014/06/08 15:37:31
Showing 10 changed files
... ...
@@ -1538,6 +1538,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
1538 1538
 
1539 1539
 func (cli *DockerCli) CmdCommit(args ...string) error {
1540 1540
 	cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
1541
+	flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
1541 1542
 	flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
1542 1543
 	flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\")")
1543 1544
 	// FIXME: --run is deprecated, it will be replaced with inline Dockerfile commands.
... ...
@@ -1569,6 +1570,11 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
1569 1569
 	v.Set("tag", tag)
1570 1570
 	v.Set("comment", *flComment)
1571 1571
 	v.Set("author", *flAuthor)
1572
+
1573
+	if *flPause != true {
1574
+		v.Set("pause", "0")
1575
+	}
1576
+
1572 1577
 	var (
1573 1578
 		config *runconfig.Config
1574 1579
 		env    engine.Env
... ...
@@ -11,7 +11,7 @@ import (
11 11
 )
12 12
 
13 13
 const (
14
-	APIVERSION        version.Version = "1.12"
14
+	APIVERSION        version.Version = "1.13"
15 15
 	DEFAULTHTTPHOST                   = "127.0.0.1"
16 16
 	DEFAULTUNIXSOCKET                 = "/var/run/docker.sock"
17 17
 )
... ...
@@ -439,6 +439,12 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit
439 439
 		utils.Errorf("%s", err)
440 440
 	}
441 441
 
442
+	if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
443
+		job.Setenv("pause", "1")
444
+	} else {
445
+		job.Setenv("pause", r.FormValue("pause"))
446
+	}
447
+
442 448
 	job.Setenv("repo", r.Form.Get("repo"))
443 449
 	job.Setenv("tag", r.Form.Get("tag"))
444 450
 	job.Setenv("author", r.Form.Get("author"))
... ...
@@ -620,8 +620,12 @@ func (daemon *Daemon) createRootfs(container *Container, img *image.Image) error
620 620
 
621 621
 // Commit creates a new filesystem image from the current state of a container.
622 622
 // The image can optionally be tagged into a repository
623
-func (daemon *Daemon) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*image.Image, error) {
624
-	// FIXME: freeze the container before copying it to avoid data corruption?
623
+func (daemon *Daemon) Commit(container *Container, repository, tag, comment, author string, pause bool, config *runconfig.Config) (*image.Image, error) {
624
+	if pause {
625
+		container.Pause()
626
+		defer container.Unpause()
627
+	}
628
+
625 629
 	if err := container.Mount(); err != nil {
626 630
 		return nil, err
627 631
 	}
... ...
@@ -337,6 +337,7 @@ schema.
337 337
 
338 338
       -a, --author=""     Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
339 339
       -m, --message=""    Commit message
340
+      -p, --pause=true    Pause container during commit
340 341
 
341 342
 It can be useful to commit a container's file changes or settings into a
342 343
 new image. This allows you debug a container by running an interactive
... ...
@@ -344,6 +345,11 @@ shell, or to export a working dataset to another server. Generally, it
344 344
 is better to use Dockerfiles to manage your images in a documented and
345 345
 maintainable way.
346 346
 
347
+By default, the container being committed and its processes will be paused
348
+during the process of committing the image. This reduces the likelihood of
349
+encountering data corruption during the process of creating the commit.
350
+If this behavior is undesired, set the 'p' option to false.
351
+
347 352
 ### Commit an existing container
348 353
 
349 354
     $ sudo docker ps
... ...
@@ -34,6 +34,33 @@ func TestCommitAfterContainerIsDone(t *testing.T) {
34 34
 	logDone("commit - echo foo and commit the image")
35 35
 }
36 36
 
37
+func TestCommitWithoutPause(t *testing.T) {
38
+	runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo")
39
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
40
+	errorOut(err, t, fmt.Sprintf("failed to run container: %v %v", out, err))
41
+
42
+	cleanedContainerID := stripTrailingCharacters(out)
43
+
44
+	waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID)
45
+	_, _, err = runCommandWithOutput(waitCmd)
46
+	errorOut(err, t, fmt.Sprintf("error thrown while waiting for container: %s", out))
47
+
48
+	commitCmd := exec.Command(dockerBinary, "commit", "-p", "false", cleanedContainerID)
49
+	out, _, err = runCommandWithOutput(commitCmd)
50
+	errorOut(err, t, fmt.Sprintf("failed to commit container to image: %v %v", out, err))
51
+
52
+	cleanedImageID := stripTrailingCharacters(out)
53
+
54
+	inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID)
55
+	out, _, err = runCommandWithOutput(inspectCmd)
56
+	errorOut(err, t, fmt.Sprintf("failed to inspect image: %v %v", out, err))
57
+
58
+	deleteContainer(cleanedContainerID)
59
+	deleteImages(cleanedImageID)
60
+
61
+	logDone("commit - echo foo and commit the image")
62
+}
63
+
37 64
 func TestCommitNewFile(t *testing.T) {
38 65
 	cmd := exec.Command(dockerBinary, "run", "--name", "commiter", "busybox", "/bin/sh", "-c", "echo koye > /foo")
39 66
 	if _, err := runCommand(cmd); err != nil {
... ...
@@ -421,7 +421,7 @@ func TestCopyVolumeUidGid(t *testing.T) {
421 421
 		t.Errorf("Container shouldn't be running")
422 422
 	}
423 423
 
424
-	img, err := r.Commit(container1, "", "", "unit test commited image", "", nil)
424
+	img, err := r.Commit(container1, "", "", "unit test commited image", "", true, nil)
425 425
 	if err != nil {
426 426
 		t.Error(err)
427 427
 	}
... ...
@@ -447,7 +447,7 @@ func TestCopyVolumeUidGid(t *testing.T) {
447 447
 		t.Errorf("Container shouldn't be running")
448 448
 	}
449 449
 
450
-	img2, err := r.Commit(container2, "", "", "unit test commited image", "", nil)
450
+	img2, err := r.Commit(container2, "", "", "unit test commited image", "", true, nil)
451 451
 	if err != nil {
452 452
 		t.Error(err)
453 453
 	}
... ...
@@ -481,7 +481,7 @@ func TestCopyVolumeContent(t *testing.T) {
481 481
 		t.Errorf("Container shouldn't be running")
482 482
 	}
483 483
 
484
-	img, err := r.Commit(container1, "", "", "unit test commited image", "", nil)
484
+	img, err := r.Commit(container1, "", "", "unit test commited image", "", true, nil)
485 485
 	if err != nil {
486 486
 		t.Error(err)
487 487
 	}
... ...
@@ -334,7 +334,7 @@ func TestDaemonCreate(t *testing.T) {
334 334
 	}
335 335
 	container, _, err = daemon.Create(config, "")
336 336
 
337
-	_, err = daemon.Commit(container, "testrepo", "testtag", "", "", config)
337
+	_, err = daemon.Commit(container, "testrepo", "testtag", "", "", true, config)
338 338
 	if err != nil {
339 339
 		t.Error(err)
340 340
 	}
... ...
@@ -752,7 +752,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
752 752
 	autoConfig := *b.config
753 753
 	autoConfig.Cmd = autoCmd
754 754
 	// Commit the container
755
-	image, err := b.daemon.Commit(container, "", "", "", b.maintainer, &autoConfig)
755
+	image, err := b.daemon.Commit(container, "", "", "", b.maintainer, true, &autoConfig)
756 756
 	if err != nil {
757 757
 		return err
758 758
 	}
... ...
@@ -1038,7 +1038,7 @@ func (srv *Server) ContainerCommit(job *engine.Job) engine.Status {
1038 1038
 		return job.Error(err)
1039 1039
 	}
1040 1040
 
1041
-	img, err := srv.daemon.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), &newConfig)
1041
+	img, err := srv.daemon.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), job.GetenvBool("pause"), &newConfig)
1042 1042
 	if err != nil {
1043 1043
 		return job.Error(err)
1044 1044
 	}