From 418135e7eac6e664834b8a9d09d8051ec296a48f Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Thu, 14 May 2015 13:44:29 -0700 Subject: [PATCH] integration-cli: New `docker cp` integration tests Adds several integration tests for `docker cp` behavior with over a dozen tests for each of: container -> local local -> container Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- .../docker_cli_cp_from_container_test.go | 503 ++++++++++++++ integration-cli/docker_cli_cp_test.go | 12 + .../docker_cli_cp_to_container_test.go | 634 ++++++++++++++++++ integration-cli/docker_cli_cp_utils.go | 298 ++++++++ integration-cli/docker_cli_events_test.go | 30 +- 5 files changed, 1473 insertions(+), 4 deletions(-) create mode 100644 integration-cli/docker_cli_cp_from_container_test.go create mode 100644 integration-cli/docker_cli_cp_to_container_test.go create mode 100644 integration-cli/docker_cli_cp_utils.go diff --git a/integration-cli/docker_cli_cp_from_container_test.go b/integration-cli/docker_cli_cp_from_container_test.go new file mode 100644 index 0000000000..14536ce859 --- /dev/null +++ b/integration-cli/docker_cli_cp_from_container_test.go @@ -0,0 +1,503 @@ +package main + +import ( + "os" + "path/filepath" + + "github.com/go-check/check" +) + +// docker cp CONTAINER:PATH LOCALPATH + +// Try all of the test cases from the archive package which implements the +// internals of `docker cp` and ensure that the behavior matches when actually +// copying to and from containers. + +// Basic assumptions about SRC and DST: +// 1. SRC must exist. +// 2. If SRC ends with a trailing separator, it must be a directory. +// 3. DST parent directory must exist. +// 4. If DST exists as a file, it must not end with a trailing separator. + +// First get these easy error cases out of the way. + +// Test for error when SRC does not exist. +func (s *DockerSuite) TestCpFromErrSrcNotExists(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-err-src-not-exists") + defer os.RemoveAll(tmpDir) + + err := runDockerCp(c, containerCpPath(cID, "file1"), tmpDir) + if err == nil { + c.Fatal("expected IsNotExist error, but got nil instead") + } + + if !isCpNotExist(err) { + c.Fatalf("expected IsNotExist error, but got %T: %s", err, err) + } +} + +// Test for error when SRC ends in a trailing +// path separator but it exists as a file. +func (s *DockerSuite) TestCpFromErrSrcNotDir(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-err-src-not-dir") + defer os.RemoveAll(tmpDir) + + err := runDockerCp(c, containerCpPathTrailingSep(cID, "file1"), tmpDir) + if err == nil { + c.Fatal("expected IsNotDir error, but got nil instead") + } + + if !isCpNotDir(err) { + c.Fatalf("expected IsNotDir error, but got %T: %s", err, err) + } +} + +// Test for error when SRC is a valid file or directory, +// bu the DST parent directory does not exist. +func (s *DockerSuite) TestCpFromErrDstParentNotExists(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-err-dst-parent-not-exists") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + // Try with a file source. + srcPath := containerCpPath(cID, "/file1") + dstPath := cpPath(tmpDir, "notExists", "file1") + + err := runDockerCp(c, srcPath, dstPath) + if err == nil { + c.Fatal("expected IsNotExist error, but got nil instead") + } + + if !isCpNotExist(err) { + c.Fatalf("expected IsNotExist error, but got %T: %s", err, err) + } + + // Try with a directory source. + srcPath = containerCpPath(cID, "/dir1") + + if err := runDockerCp(c, srcPath, dstPath); err == nil { + c.Fatal("expected IsNotExist error, but got nil instead") + } + + if !isCpNotExist(err) { + c.Fatalf("expected IsNotExist error, but got %T: %s", err, err) + } +} + +// Test for error when DST ends in a trailing +// path separator but exists as a file. +func (s *DockerSuite) TestCpFromErrDstNotDir(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-err-dst-not-dir") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + // Try with a file source. + srcPath := containerCpPath(cID, "/file1") + dstPath := cpPathTrailingSep(tmpDir, "file1") + + err := runDockerCp(c, srcPath, dstPath) + if err == nil { + c.Fatal("expected IsNotDir error, but got nil instead") + } + + if !isCpNotDir(err) { + c.Fatalf("expected IsNotDir error, but got %T: %s", err, err) + } + + // Try with a directory source. + srcPath = containerCpPath(cID, "/dir1") + + if err := runDockerCp(c, srcPath, dstPath); err == nil { + c.Fatal("expected IsNotDir error, but got nil instead") + } + + if !isCpNotDir(err) { + c.Fatalf("expected IsNotDir error, but got %T: %s", err, err) + } +} + +// Possibilities are reduced to the remaining 10 cases: +// +// case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action +// =================================================================================================== +// A | no | - | no | - | no | create file +// B | no | - | no | - | yes | error +// C | no | - | yes | no | - | overwrite file +// D | no | - | yes | yes | - | create file in dst dir +// E | yes | no | no | - | - | create dir, copy contents +// F | yes | no | yes | no | - | error +// G | yes | no | yes | yes | - | copy dir and contents +// H | yes | yes | no | - | - | create dir, copy contents +// I | yes | yes | yes | no | - | error +// J | yes | yes | yes | yes | - | copy dir contents +// + +// A. SRC specifies a file and DST (no trailing path separator) doesn't +// exist. This should create a file with the name DST and copy the +// contents of the source file into it. +func (s *DockerSuite) TestCpFromCaseA(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-a") + defer os.RemoveAll(tmpDir) + + srcPath := containerCpPath(cID, "/root/file1") + dstPath := cpPath(tmpDir, "itWorks.txt") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1\n"); err != nil { + c.Fatal(err) + } +} + +// B. SRC specifies a file and DST (with trailing path separator) doesn't +// exist. This should cause an error because the copy operation cannot +// create a directory when copying a single file. +func (s *DockerSuite) TestCpFromCaseB(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-b") + defer os.RemoveAll(tmpDir) + + srcPath := containerCpPath(cID, "/file1") + dstDir := cpPathTrailingSep(tmpDir, "testDir") + + err := runDockerCp(c, srcPath, dstDir) + if err == nil { + c.Fatal("expected DirNotExists error, but got nil instead") + } + + if !isCpDirNotExist(err) { + c.Fatalf("expected DirNotExists error, but got %T: %s", err, err) + } +} + +// C. SRC specifies a file and DST exists as a file. This should overwrite +// the file at DST with the contents of the source file. +func (s *DockerSuite) TestCpFromCaseC(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-c") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcPath := containerCpPath(cID, "/root/file1") + dstPath := cpPath(tmpDir, "file2") + + // Ensure the local file starts with different content. + if err := fileContentEquals(c, dstPath, "file2\n"); err != nil { + c.Fatal(err) + } + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1\n"); err != nil { + c.Fatal(err) + } +} + +// D. SRC specifies a file and DST exists as a directory. This should place +// a copy of the source file inside it using the basename from SRC. Ensure +// this works whether DST has a trailing path separator or not. +func (s *DockerSuite) TestCpFromCaseD(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-d") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcPath := containerCpPath(cID, "/file1") + dstDir := cpPath(tmpDir, "dir1") + dstPath := filepath.Join(dstDir, "file1") + + // Ensure that dstPath doesn't exist. + if _, err := os.Stat(dstPath); !os.IsNotExist(err) { + c.Fatalf("did not expect dstPath %q to exist", dstPath) + } + + if err := runDockerCp(c, srcPath, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err := os.RemoveAll(dstDir); err != nil { + c.Fatalf("unable to remove dstDir: %s", err) + } + + if err := os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + c.Fatalf("unable to make dstDir: %s", err) + } + + dstDir = cpPathTrailingSep(tmpDir, "dir1") + + if err := runDockerCp(c, srcPath, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1\n"); err != nil { + c.Fatal(err) + } +} + +// E. SRC specifies a directory and DST does not exist. This should create a +// directory at DST and copy the contents of the SRC directory into the DST +// directory. Ensure this works whether DST has a trailing path separator or +// not. +func (s *DockerSuite) TestCpFromCaseE(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-e") + defer os.RemoveAll(tmpDir) + + srcDir := containerCpPath(cID, "dir1") + dstDir := cpPath(tmpDir, "testDir") + dstPath := filepath.Join(dstDir, "file1-1") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1-1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err := os.RemoveAll(dstDir); err != nil { + c.Fatalf("unable to remove dstDir: %s", err) + } + + dstDir = cpPathTrailingSep(tmpDir, "testDir") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1-1\n"); err != nil { + c.Fatal(err) + } +} + +// F. SRC specifies a directory and DST exists as a file. This should cause an +// error as it is not possible to overwrite a file with a directory. +func (s *DockerSuite) TestCpFromCaseF(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-f") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := containerCpPath(cID, "/root/dir1") + dstFile := cpPath(tmpDir, "file1") + + err := runDockerCp(c, srcDir, dstFile) + if err == nil { + c.Fatal("expected ErrCannotCopyDir error, but got nil instead") + } + + if !isCpCannotCopyDir(err) { + c.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) + } +} + +// G. SRC specifies a directory and DST exists as a directory. This should copy +// the SRC directory and all its contents to the DST directory. Ensure this +// works whether DST has a trailing path separator or not. +func (s *DockerSuite) TestCpFromCaseG(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-g") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := containerCpPath(cID, "/root/dir1") + dstDir := cpPath(tmpDir, "dir2") + resultDir := filepath.Join(dstDir, "dir1") + dstPath := filepath.Join(resultDir, "file1-1") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1-1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err := os.RemoveAll(dstDir); err != nil { + c.Fatalf("unable to remove dstDir: %s", err) + } + + if err := os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + c.Fatalf("unable to make dstDir: %s", err) + } + + dstDir = cpPathTrailingSep(tmpDir, "dir2") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1-1\n"); err != nil { + c.Fatal(err) + } +} + +// H. SRC specifies a directory's contents only and DST does not exist. This +// should create a directory at DST and copy the contents of the SRC +// directory (but not the directory itself) into the DST directory. Ensure +// this works whether DST has a trailing path separator or not. +func (s *DockerSuite) TestCpFromCaseH(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-h") + defer os.RemoveAll(tmpDir) + + srcDir := containerCpPathTrailingSep(cID, "dir1") + "." + dstDir := cpPath(tmpDir, "testDir") + dstPath := filepath.Join(dstDir, "file1-1") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1-1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err := os.RemoveAll(dstDir); err != nil { + c.Fatalf("unable to remove resultDir: %s", err) + } + + dstDir = cpPathTrailingSep(tmpDir, "testDir") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1-1\n"); err != nil { + c.Fatal(err) + } +} + +// I. SRC specifies a direcotry's contents only and DST exists as a file. This +// should cause an error as it is not possible to overwrite a file with a +// directory. +func (s *DockerSuite) TestCpFromCaseI(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-i") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := containerCpPathTrailingSep(cID, "/root/dir1") + "." + dstFile := cpPath(tmpDir, "file1") + + err := runDockerCp(c, srcDir, dstFile) + if err == nil { + c.Fatal("expected ErrCannotCopyDir error, but got nil instead") + } + + if !isCpCannotCopyDir(err) { + c.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) + } +} + +// J. SRC specifies a directory's contents only and DST exists as a directory. +// This should copy the contents of the SRC directory (but not the directory +// itself) into the DST directory. Ensure this works whether DST has a +// trailing path separator or not. +func (s *DockerSuite) TestCpFromCaseJ(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-from-case-j") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := containerCpPathTrailingSep(cID, "/root/dir1") + "." + dstDir := cpPath(tmpDir, "dir2") + dstPath := filepath.Join(dstDir, "file1-1") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1-1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + if err := os.RemoveAll(dstDir); err != nil { + c.Fatalf("unable to remove dstDir: %s", err) + } + + if err := os.MkdirAll(dstDir, os.FileMode(0755)); err != nil { + c.Fatalf("unable to make dstDir: %s", err) + } + + dstDir = cpPathTrailingSep(tmpDir, "dir2") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := fileContentEquals(c, dstPath, "file1-1\n"); err != nil { + c.Fatal(err) + } +} diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 45340cf6c1..03c0a4a63e 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -23,6 +23,18 @@ const ( cpHostContents = "hello, i am the host" ) +// Ensure that an all-local path case returns an error. +func (s *DockerSuite) TestCpLocalOnly(c *check.C) { + err := runDockerCp(c, "foo", "bar") + if err == nil { + c.Fatal("expected failure, got success") + } + + if !strings.Contains(err.Error(), "must specify at least one container source") { + c.Fatalf("unexpected output: %s", err.Error()) + } +} + // Test for #5656 // Check that garbage paths don't escape the container's rootfs func (s *DockerSuite) TestCpGarbagePath(c *check.C) { diff --git a/integration-cli/docker_cli_cp_to_container_test.go b/integration-cli/docker_cli_cp_to_container_test.go new file mode 100644 index 0000000000..4179553d18 --- /dev/null +++ b/integration-cli/docker_cli_cp_to_container_test.go @@ -0,0 +1,634 @@ +package main + +import ( + "os" + + "github.com/go-check/check" +) + +// docker cp LOCALPATH CONTAINER:PATH + +// Try all of the test cases from the archive package which implements the +// internals of `docker cp` and ensure that the behavior matches when actually +// copying to and from containers. + +// Basic assumptions about SRC and DST: +// 1. SRC must exist. +// 2. If SRC ends with a trailing separator, it must be a directory. +// 3. DST parent directory must exist. +// 4. If DST exists as a file, it must not end with a trailing separator. + +// First get these easy error cases out of the way. + +// Test for error when SRC does not exist. +func (s *DockerSuite) TestCpToErrSrcNotExists(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-err-src-not-exists") + defer os.RemoveAll(tmpDir) + + srcPath := cpPath(tmpDir, "file1") + dstPath := containerCpPath(cID, "file1") + + err := runDockerCp(c, srcPath, dstPath) + if err == nil { + c.Fatal("expected IsNotExist error, but got nil instead") + } + + if !isCpNotExist(err) { + c.Fatalf("expected IsNotExist error, but got %T: %s", err, err) + } +} + +// Test for error when SRC ends in a trailing +// path separator but it exists as a file. +func (s *DockerSuite) TestCpToErrSrcNotDir(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-err-src-not-dir") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcPath := cpPathTrailingSep(tmpDir, "file1") + dstPath := containerCpPath(cID, "testDir") + + err := runDockerCp(c, srcPath, dstPath) + if err == nil { + c.Fatal("expected IsNotDir error, but got nil instead") + } + + if !isCpNotDir(err) { + c.Fatalf("expected IsNotDir error, but got %T: %s", err, err) + } +} + +// Test for error when SRC is a valid file or directory, +// bu the DST parent directory does not exist. +func (s *DockerSuite) TestCpToErrDstParentNotExists(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-err-dst-parent-not-exists") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + // Try with a file source. + srcPath := cpPath(tmpDir, "file1") + dstPath := containerCpPath(cID, "/notExists", "file1") + + err := runDockerCp(c, srcPath, dstPath) + if err == nil { + c.Fatal("expected IsNotExist error, but got nil instead") + } + + if !isCpNotExist(err) { + c.Fatalf("expected IsNotExist error, but got %T: %s", err, err) + } + + // Try with a directory source. + srcPath = cpPath(tmpDir, "dir1") + + if err := runDockerCp(c, srcPath, dstPath); err == nil { + c.Fatal("expected IsNotExist error, but got nil instead") + } + + if !isCpNotExist(err) { + c.Fatalf("expected IsNotExist error, but got %T: %s", err, err) + } +} + +// Test for error when DST ends in a trailing path separator but exists as a +// file. Also test that we cannot overwirite an existing directory with a +// non-directory and cannot overwrite an existing +func (s *DockerSuite) TestCpToErrDstNotDir(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{addContent: true}) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-err-dst-not-dir") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + // Try with a file source. + srcPath := cpPath(tmpDir, "dir1/file1-1") + dstPath := containerCpPathTrailingSep(cID, "file1") + + // The client should encounter an error trying to stat the destination + // and then be unable to copy since the destination is asserted to be a + // directory but does not exist. + err := runDockerCp(c, srcPath, dstPath) + if err == nil { + c.Fatal("expected DirNotExist error, but got nil instead") + } + + if !isCpDirNotExist(err) { + c.Fatalf("expected DirNotExist error, but got %T: %s", err, err) + } + + // Try with a directory source. + srcPath = cpPath(tmpDir, "dir1") + + // The client should encounter an error trying to stat the destination and + // then decide to extract to the parent directory instead with a rebased + // name in the source archive, but this directory would overwrite the + // existing file with the same name. + err = runDockerCp(c, srcPath, dstPath) + if err == nil { + c.Fatal("expected CannotOverwriteNonDirWithDir error, but got nil instead") + } + + if !isCannotOverwriteNonDirWithDir(err) { + c.Fatalf("expected CannotOverwriteNonDirWithDir error, but got %T: %s", err, err) + } +} + +// Possibilities are reduced to the remaining 10 cases: +// +// case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action +// =================================================================================================== +// A | no | - | no | - | no | create file +// B | no | - | no | - | yes | error +// C | no | - | yes | no | - | overwrite file +// D | no | - | yes | yes | - | create file in dst dir +// E | yes | no | no | - | - | create dir, copy contents +// F | yes | no | yes | no | - | error +// G | yes | no | yes | yes | - | copy dir and contents +// H | yes | yes | no | - | - | create dir, copy contents +// I | yes | yes | yes | no | - | error +// J | yes | yes | yes | yes | - | copy dir contents +// + +// A. SRC specifies a file and DST (no trailing path separator) doesn't +// exist. This should create a file with the name DST and copy the +// contents of the source file into it. +func (s *DockerSuite) TestCpToCaseA(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + workDir: "/root", command: makeCatFileCommand("itWorks.txt"), + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-a") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcPath := cpPath(tmpDir, "file1") + dstPath := containerCpPath(cID, "/root/itWorks.txt") + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + if err := containerStartOutputEquals(c, cID, "file1\n"); err != nil { + c.Fatal(err) + } +} + +// B. SRC specifies a file and DST (with trailing path separator) doesn't +// exist. This should cause an error because the copy operation cannot +// create a directory when copying a single file. +func (s *DockerSuite) TestCpToCaseB(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + command: makeCatFileCommand("testDir/file1"), + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-b") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcPath := cpPath(tmpDir, "file1") + dstDir := containerCpPathTrailingSep(cID, "testDir") + + err := runDockerCp(c, srcPath, dstDir) + if err == nil { + c.Fatal("expected DirNotExists error, but got nil instead") + } + + if !isCpDirNotExist(err) { + c.Fatalf("expected DirNotExists error, but got %T: %s", err, err) + } +} + +// C. SRC specifies a file and DST exists as a file. This should overwrite +// the file at DST with the contents of the source file. +func (s *DockerSuite) TestCpToCaseC(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + command: makeCatFileCommand("file2"), + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-c") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcPath := cpPath(tmpDir, "file1") + dstPath := containerCpPath(cID, "/root/file2") + + // Ensure the container's file starts with the original content. + if err := containerStartOutputEquals(c, cID, "file2\n"); err != nil { + c.Fatal(err) + } + + if err := runDockerCp(c, srcPath, dstPath); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1's contents. + if err := containerStartOutputEquals(c, cID, "file1\n"); err != nil { + c.Fatal(err) + } +} + +// D. SRC specifies a file and DST exists as a directory. This should place +// a copy of the source file inside it using the basename from SRC. Ensure +// this works whether DST has a trailing path separator or not. +func (s *DockerSuite) TestCpToCaseD(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, + command: makeCatFileCommand("/dir1/file1"), + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-d") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcPath := cpPath(tmpDir, "file1") + dstDir := containerCpPath(cID, "dir1") + + // Ensure that dstPath doesn't exist. + if err := containerStartOutputEquals(c, cID, ""); err != nil { + c.Fatal(err) + } + + if err := runDockerCp(c, srcPath, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1's contents. + if err := containerStartOutputEquals(c, cID, "file1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + // Make new destination container. + cID = makeTestContainer(c, testContainerOptions{ + addContent: true, + command: makeCatFileCommand("/dir1/file1"), + }) + defer deleteContainer(cID) + + dstDir = containerCpPathTrailingSep(cID, "dir1") + + // Ensure that dstPath doesn't exist. + if err := containerStartOutputEquals(c, cID, ""); err != nil { + c.Fatal(err) + } + + if err := runDockerCp(c, srcPath, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1's contents. + if err := containerStartOutputEquals(c, cID, "file1\n"); err != nil { + c.Fatal(err) + } +} + +// E. SRC specifies a directory and DST does not exist. This should create a +// directory at DST and copy the contents of the SRC directory into the DST +// directory. Ensure this works whether DST has a trailing path separator or +// not. +func (s *DockerSuite) TestCpToCaseE(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + command: makeCatFileCommand("/testDir/file1-1"), + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-e") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := cpPath(tmpDir, "dir1") + dstDir := containerCpPath(cID, "testDir") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1-1's contents. + if err := containerStartOutputEquals(c, cID, "file1-1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + // Make new destination container. + cID = makeTestContainer(c, testContainerOptions{ + command: makeCatFileCommand("/testDir/file1-1"), + }) + defer deleteContainer(cID) + + dstDir = containerCpPathTrailingSep(cID, "testDir") + + err := runDockerCp(c, srcDir, dstDir) + if err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1-1's contents. + if err := containerStartOutputEquals(c, cID, "file1-1\n"); err != nil { + c.Fatal(err) + } +} + +// F. SRC specifies a directory and DST exists as a file. This should cause an +// error as it is not possible to overwrite a file with a directory. +func (s *DockerSuite) TestCpToCaseF(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-f") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := cpPath(tmpDir, "dir1") + dstFile := containerCpPath(cID, "/root/file1") + + err := runDockerCp(c, srcDir, dstFile) + if err == nil { + c.Fatal("expected ErrCannotCopyDir error, but got nil instead") + } + + if !isCpCannotCopyDir(err) { + c.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) + } +} + +// G. SRC specifies a directory and DST exists as a directory. This should copy +// the SRC directory and all its contents to the DST directory. Ensure this +// works whether DST has a trailing path separator or not. +func (s *DockerSuite) TestCpToCaseG(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + command: makeCatFileCommand("dir2/dir1/file1-1"), + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-g") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := cpPath(tmpDir, "dir1") + dstDir := containerCpPath(cID, "/root/dir2") + + // Ensure that dstPath doesn't exist. + if err := containerStartOutputEquals(c, cID, ""); err != nil { + c.Fatal(err) + } + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1-1's contents. + if err := containerStartOutputEquals(c, cID, "file1-1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + // Make new destination container. + cID = makeTestContainer(c, testContainerOptions{ + addContent: true, + command: makeCatFileCommand("/dir2/dir1/file1-1"), + }) + defer deleteContainer(cID) + + dstDir = containerCpPathTrailingSep(cID, "/dir2") + + // Ensure that dstPath doesn't exist. + if err := containerStartOutputEquals(c, cID, ""); err != nil { + c.Fatal(err) + } + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1-1's contents. + if err := containerStartOutputEquals(c, cID, "file1-1\n"); err != nil { + c.Fatal(err) + } +} + +// H. SRC specifies a directory's contents only and DST does not exist. This +// should create a directory at DST and copy the contents of the SRC +// directory (but not the directory itself) into the DST directory. Ensure +// this works whether DST has a trailing path separator or not. +func (s *DockerSuite) TestCpToCaseH(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + command: makeCatFileCommand("/testDir/file1-1"), + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-h") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := cpPathTrailingSep(tmpDir, "dir1") + "." + dstDir := containerCpPath(cID, "testDir") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1-1's contents. + if err := containerStartOutputEquals(c, cID, "file1-1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + // Make new destination container. + cID = makeTestContainer(c, testContainerOptions{ + command: makeCatFileCommand("/testDir/file1-1"), + }) + defer deleteContainer(cID) + + dstDir = containerCpPathTrailingSep(cID, "testDir") + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1-1's contents. + if err := containerStartOutputEquals(c, cID, "file1-1\n"); err != nil { + c.Fatal(err) + } +} + +// I. SRC specifies a direcotry's contents only and DST exists as a file. This +// should cause an error as it is not possible to overwrite a file with a +// directory. +func (s *DockerSuite) TestCpToCaseI(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-i") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := cpPathTrailingSep(tmpDir, "dir1") + "." + dstFile := containerCpPath(cID, "/root/file1") + + err := runDockerCp(c, srcDir, dstFile) + if err == nil { + c.Fatal("expected ErrCannotCopyDir error, but got nil instead") + } + + if !isCpCannotCopyDir(err) { + c.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err) + } +} + +// J. SRC specifies a directory's contents only and DST exists as a directory. +// This should copy the contents of the SRC directory (but not the directory +// itself) into the DST directory. Ensure this works whether DST has a +// trailing path separator or not. +func (s *DockerSuite) TestCpToCaseJ(c *check.C) { + cID := makeTestContainer(c, testContainerOptions{ + addContent: true, workDir: "/root", + command: makeCatFileCommand("/dir2/file1-1"), + }) + defer deleteContainer(cID) + + tmpDir := getTestDir(c, "test-cp-to-case-j") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + srcDir := cpPathTrailingSep(tmpDir, "dir1") + "." + dstDir := containerCpPath(cID, "/dir2") + + // Ensure that dstPath doesn't exist. + if err := containerStartOutputEquals(c, cID, ""); err != nil { + c.Fatal(err) + } + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1-1's contents. + if err := containerStartOutputEquals(c, cID, "file1-1\n"); err != nil { + c.Fatal(err) + } + + // Now try again but using a trailing path separator for dstDir. + + // Make new destination container. + cID = makeTestContainer(c, testContainerOptions{ + command: makeCatFileCommand("/dir2/file1-1"), + }) + defer deleteContainer(cID) + + dstDir = containerCpPathTrailingSep(cID, "/dir2") + + // Ensure that dstPath doesn't exist. + if err := containerStartOutputEquals(c, cID, ""); err != nil { + c.Fatal(err) + } + + if err := runDockerCp(c, srcDir, dstDir); err != nil { + c.Fatalf("unexpected error %T: %s", err, err) + } + + // Should now contain file1-1's contents. + if err := containerStartOutputEquals(c, cID, "file1-1\n"); err != nil { + c.Fatal(err) + } +} + +// The `docker cp` command should also ensure that you cannot +// write to a container rootfs that is marked as read-only. +func (s *DockerSuite) TestCpToErrReadOnlyRootfs(c *check.C) { + tmpDir := getTestDir(c, "test-cp-to-err-read-only-rootfs") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + cID := makeTestContainer(c, testContainerOptions{ + readOnly: true, workDir: "/root", + command: makeCatFileCommand("shouldNotExist"), + }) + defer deleteContainer(cID) + + srcPath := cpPath(tmpDir, "file1") + dstPath := containerCpPath(cID, "/root/shouldNotExist") + + err := runDockerCp(c, srcPath, dstPath) + if err == nil { + c.Fatal("expected ErrContainerRootfsReadonly error, but got nil instead") + } + + if !isCpCannotCopyReadOnly(err) { + c.Fatalf("expected ErrContainerRootfsReadonly error, but got %T: %s", err, err) + } + + // Ensure that dstPath doesn't exist. + if err := containerStartOutputEquals(c, cID, ""); err != nil { + c.Fatal(err) + } +} + +// The `docker cp` command should also ensure that you +// cannot write to a volume that is mounted as read-only. +func (s *DockerSuite) TestCpToErrReadOnlyVolume(c *check.C) { + tmpDir := getTestDir(c, "test-cp-to-err-read-only-volume") + defer os.RemoveAll(tmpDir) + + makeTestContentInDir(c, tmpDir) + + cID := makeTestContainer(c, testContainerOptions{ + volumes: defaultVolumes(tmpDir), workDir: "/root", + command: makeCatFileCommand("/vol_ro/shouldNotExist"), + }) + defer deleteContainer(cID) + + srcPath := cpPath(tmpDir, "file1") + dstPath := containerCpPath(cID, "/vol_ro/shouldNotExist") + + err := runDockerCp(c, srcPath, dstPath) + if err == nil { + c.Fatal("expected ErrVolumeReadonly error, but got nil instead") + } + + if !isCpCannotCopyReadOnly(err) { + c.Fatalf("expected ErrVolumeReadonly error, but got %T: %s", err, err) + } + + // Ensure that dstPath doesn't exist. + if err := containerStartOutputEquals(c, cID, ""); err != nil { + c.Fatal(err) + } +} diff --git a/integration-cli/docker_cli_cp_utils.go b/integration-cli/docker_cli_cp_utils.go new file mode 100644 index 0000000000..96b7b466a4 --- /dev/null +++ b/integration-cli/docker_cli_cp_utils.go @@ -0,0 +1,298 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/archive" + "github.com/go-check/check" +) + +type FileType uint32 + +const ( + Regular FileType = iota + Dir + Symlink +) + +type FileData struct { + filetype FileType + path string + contents string +} + +func (fd FileData) creationCommand() string { + var command string + + switch fd.filetype { + case Regular: + // Don't overwrite the file if it already exists! + command = fmt.Sprintf("if [ ! -f %s ]; then echo %q > %s; fi", fd.path, fd.contents, fd.path) + case Dir: + command = fmt.Sprintf("mkdir -p %s", fd.path) + case Symlink: + command = fmt.Sprintf("ln -fs %s %s", fd.contents, fd.path) + } + + return command +} + +func mkFilesCommand(fds []FileData) string { + commands := make([]string, len(fds)) + + for i, fd := range fds { + commands[i] = fd.creationCommand() + } + + return strings.Join(commands, " && ") +} + +var defaultFileData = []FileData{ + {Regular, "file1", "file1"}, + {Regular, "file2", "file2"}, + {Regular, "file3", "file3"}, + {Regular, "file4", "file4"}, + {Regular, "file5", "file5"}, + {Regular, "file6", "file6"}, + {Regular, "file7", "file7"}, + {Dir, "dir1", ""}, + {Regular, "dir1/file1-1", "file1-1"}, + {Regular, "dir1/file1-2", "file1-2"}, + {Dir, "dir2", ""}, + {Regular, "dir2/file2-1", "file2-1"}, + {Regular, "dir2/file2-2", "file2-2"}, + {Dir, "dir3", ""}, + {Regular, "dir3/file3-1", "file3-1"}, + {Regular, "dir3/file3-2", "file3-2"}, + {Dir, "dir4", ""}, + {Regular, "dir4/file3-1", "file4-1"}, + {Regular, "dir4/file3-2", "file4-2"}, + {Dir, "dir5", ""}, + {Symlink, "symlink1", "target1"}, + {Symlink, "symlink2", "target2"}, +} + +func defaultMkContentCommand() string { + return mkFilesCommand(defaultFileData) +} + +func makeTestContentInDir(c *check.C, dir string) { + for _, fd := range defaultFileData { + path := filepath.Join(dir, filepath.FromSlash(fd.path)) + switch fd.filetype { + case Regular: + if err := ioutil.WriteFile(path, []byte(fd.contents+"\n"), os.FileMode(0666)); err != nil { + c.Fatal(err) + } + case Dir: + if err := os.Mkdir(path, os.FileMode(0777)); err != nil { + c.Fatal(err) + } + case Symlink: + if err := os.Symlink(fd.contents, path); err != nil { + c.Fatal(err) + } + } + } +} + +type testContainerOptions struct { + addContent bool + readOnly bool + volumes []string + workDir string + command string +} + +func makeTestContainer(c *check.C, options testContainerOptions) (containerID string) { + if options.addContent { + mkContentCmd := defaultMkContentCommand() + if options.command == "" { + options.command = mkContentCmd + } else { + options.command = fmt.Sprintf("%s && %s", defaultMkContentCommand(), options.command) + } + } + + if options.command == "" { + options.command = "#(nop)" + } + + args := []string{"run", "-d"} + + for _, volume := range options.volumes { + args = append(args, "-v", volume) + } + + if options.workDir != "" { + args = append(args, "-w", options.workDir) + } + + if options.readOnly { + args = append(args, "--read-only") + } + + args = append(args, "busybox", "/bin/sh", "-c", options.command) + + out, status := dockerCmd(c, args...) + if status != 0 { + c.Fatalf("failed to run container, status %d: %s", status, out) + } + + containerID = strings.TrimSpace(out) + + out, status = dockerCmd(c, "wait", containerID) + if status != 0 { + c.Fatalf("failed to wait for test container container, status %d: %s", status, out) + } + + if exitCode := strings.TrimSpace(out); exitCode != "0" { + logs, status := dockerCmd(c, "logs", containerID) + if status != 0 { + logs = "UNABLE TO GET LOGS" + } + c.Fatalf("failed to make test container, exit code (%d): %s", exitCode, logs) + } + + return +} + +func makeCatFileCommand(path string) string { + return fmt.Sprintf("if [ -f %s ]; then cat %s; fi", path, path) +} + +func cpPath(pathElements ...string) string { + localizedPathElements := make([]string, len(pathElements)) + for i, path := range pathElements { + localizedPathElements[i] = filepath.FromSlash(path) + } + return strings.Join(localizedPathElements, string(filepath.Separator)) +} + +func cpPathTrailingSep(pathElements ...string) string { + return fmt.Sprintf("%s%c", cpPath(pathElements...), filepath.Separator) +} + +func containerCpPath(containerID string, pathElements ...string) string { + joined := strings.Join(pathElements, "/") + return fmt.Sprintf("%s:%s", containerID, joined) +} + +func containerCpPathTrailingSep(containerID string, pathElements ...string) string { + return fmt.Sprintf("%s/", containerCpPath(containerID, pathElements...)) +} + +func runDockerCp(c *check.C, src, dst string) (err error) { + c.Logf("running `docker cp %s %s`", src, dst) + + args := []string{"cp", src, dst} + + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, args...)) + if err != nil { + err = fmt.Errorf("error executing `docker cp` command: %s: %s", err, out) + } + + return +} + +func startContainerGetOutput(c *check.C, cID string) (out string, err error) { + c.Logf("running `docker start -a %s`", cID) + + args := []string{"start", "-a", cID} + + out, _, err = runCommandWithOutput(exec.Command(dockerBinary, args...)) + if err != nil { + err = fmt.Errorf("error executing `docker start` command: %s: %s", err, out) + } + + return +} + +func getTestDir(c *check.C, label string) (tmpDir string) { + var err error + + if tmpDir, err = ioutil.TempDir("", label); err != nil { + c.Fatalf("unable to make temporary directory: %s", err) + } + + return +} + +func isCpNotExist(err error) bool { + return strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "cannot find the file specified") +} + +func isCpDirNotExist(err error) bool { + return strings.Contains(err.Error(), archive.ErrDirNotExists.Error()) +} + +func isCpNotDir(err error) bool { + return strings.Contains(err.Error(), archive.ErrNotDirectory.Error()) || strings.Contains(err.Error(), "filename, directory name, or volume label syntax is incorrect") +} + +func isCpCannotCopyDir(err error) bool { + return strings.Contains(err.Error(), archive.ErrCannotCopyDir.Error()) +} + +func isCpCannotCopyReadOnly(err error) bool { + return strings.Contains(err.Error(), "marked read-only") +} + +func isCannotOverwriteNonDirWithDir(err error) bool { + return strings.Contains(err.Error(), "cannot overwrite non-directory") +} + +func fileContentEquals(c *check.C, filename, contents string) (err error) { + c.Logf("checking that file %q contains %q\n", filename, contents) + + fileBytes, err := ioutil.ReadFile(filename) + if err != nil { + return + } + + expectedBytes, err := ioutil.ReadAll(strings.NewReader(contents)) + if err != nil { + return + } + + if !bytes.Equal(fileBytes, expectedBytes) { + err = fmt.Errorf("file content not equal - expected %q, got %q", string(expectedBytes), string(fileBytes)) + } + + return +} + +func containerStartOutputEquals(c *check.C, cID, contents string) (err error) { + c.Logf("checking that container %q start output contains %q\n", cID, contents) + + out, err := startContainerGetOutput(c, cID) + if err != nil { + return err + } + + if out != contents { + err = fmt.Errorf("output contents not equal - expected %q, got %q", contents, out) + } + + return +} + +func defaultVolumes(tmpDir string) []string { + if SameHostDaemon.Condition() { + return []string{ + "/vol1", + fmt.Sprintf("%s:/vol2", tmpDir), + fmt.Sprintf("%s:/vol3", filepath.Join(tmpDir, "vol3")), + fmt.Sprintf("%s:/vol_ro:ro", filepath.Join(tmpDir, "vol_ro")), + } + } + + // Can't bind-mount volumes with separate host daemon. + return []string{"/vol1", "/vol2", "/vol3", "/vol_ro:/vol_ro:ro"} +} diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 0873e64ab4..6742ea453e 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -3,7 +3,9 @@ package main import ( "bufio" "fmt" + "io/ioutil" "net/http" + "os" "os/exec" "regexp" "strconv" @@ -519,6 +521,7 @@ func (s *DockerSuite) TestEventsCommit(c *check.C) { func (s *DockerSuite) TestEventsCopy(c *check.C) { since := daemonTime(c).Unix() + // Build a test image. id, err := buildImage("cpimg", ` FROM busybox RUN echo HI > /tmp/file`, true) @@ -526,12 +529,31 @@ func (s *DockerSuite) TestEventsCopy(c *check.C) { c.Fatalf("Couldn't create image: %q", err) } - dockerCmd(c, "run", "--name=cptest", id, "true") - dockerCmd(c, "cp", "cptest:/tmp/file", "-") + // Create an empty test file. + tempFile, err := ioutil.TempFile("", "test-events-copy-") + if err != nil { + c.Fatal(err) + } + defer os.Remove(tempFile.Name()) + + if err := tempFile.Close(); err != nil { + c.Fatal(err) + } + + dockerCmd(c, "create", "--name=cptest", id) + + dockerCmd(c, "cp", "cptest:/tmp/file", tempFile.Name()) out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since))) - if !strings.Contains(out, " copy\n") { - c.Fatalf("Missing 'copy' log event\n%s", out) + if !strings.Contains(out, " archive-path\n") { + c.Fatalf("Missing 'archive-path' log event\n%s", out) + } + + dockerCmd(c, "cp", tempFile.Name(), "cptest:/tmp/filecopy") + + out, _ = dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since))) + if !strings.Contains(out, " extract-to-dir\n") { + c.Fatalf("Missing 'extract-to-dir' log event\n%s", out) } }