github.com/wozhu6104/docker@v20.10.10+incompatible/integration-cli/docker_cli_cp_from_container_test.go (about)

     1  package main
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"testing"
     7  
     8  	"gotest.tools/v3/assert"
     9  )
    10  
    11  // Try all of the test cases from the archive package which implements the
    12  // internals of `docker cp` and ensure that the behavior matches when actually
    13  // copying to and from containers.
    14  
    15  // Basic assumptions about SRC and DST:
    16  // 1. SRC must exist.
    17  // 2. If SRC ends with a trailing separator, it must be a directory.
    18  // 3. DST parent directory must exist.
    19  // 4. If DST exists as a file, it must not end with a trailing separator.
    20  
    21  // Check that copying from a container to a local symlink copies to the symlink
    22  // target and does not overwrite the local symlink itself.
    23  // TODO: move to docker/cli and/or integration/container/copy_test.go
    24  func (s *DockerSuite) TestCpFromSymlinkDestination(c *testing.T) {
    25  	testRequires(c, DaemonIsLinux)
    26  	containerID := makeTestContainer(c, testContainerOptions{addContent: true})
    27  
    28  	tmpDir := getTestDir(c, "test-cp-from-err-dst-not-dir")
    29  	defer os.RemoveAll(tmpDir)
    30  
    31  	makeTestContentInDir(c, tmpDir)
    32  
    33  	// First, copy a file from the container to a symlink to a file. This
    34  	// should overwrite the symlink target contents with the source contents.
    35  	srcPath := containerCpPath(containerID, "/file2")
    36  	dstPath := cpPath(tmpDir, "symlinkToFile1")
    37  
    38  	assert.NilError(c, runDockerCp(c, srcPath, dstPath))
    39  	assert.NilError(c, symlinkTargetEquals(c, dstPath, "file1"), "The symlink should not have been modified")
    40  	assert.NilError(c, fileContentEquals(c, cpPath(tmpDir, "file1"), "file2\n"), `The file should have the contents of "file2" now`)
    41  
    42  	// Next, copy a file from the container to a symlink to a directory. This
    43  	// should copy the file into the symlink target directory.
    44  	dstPath = cpPath(tmpDir, "symlinkToDir1")
    45  
    46  	assert.NilError(c, runDockerCp(c, srcPath, dstPath))
    47  	assert.NilError(c, symlinkTargetEquals(c, dstPath, "dir1"), "The symlink should not have been modified")
    48  	assert.NilError(c, fileContentEquals(c, cpPath(tmpDir, "file2"), "file2\n"), `The file should have the contents of "file2" now`)
    49  
    50  	// Next, copy a file from the container to a symlink to a file that does
    51  	// not exist (a broken symlink). This should create the target file with
    52  	// the contents of the source file.
    53  	dstPath = cpPath(tmpDir, "brokenSymlinkToFileX")
    54  
    55  	assert.NilError(c, runDockerCp(c, srcPath, dstPath))
    56  	assert.NilError(c, symlinkTargetEquals(c, dstPath, "fileX"), "The symlink should not have been modified")
    57  	assert.NilError(c, fileContentEquals(c, cpPath(tmpDir, "fileX"), "file2\n"), `The file should have the contents of "file2" now`)
    58  
    59  	// Next, copy a directory from the container to a symlink to a local
    60  	// directory. This should copy the directory into the symlink target
    61  	// directory and not modify the symlink.
    62  	srcPath = containerCpPath(containerID, "/dir2")
    63  	dstPath = cpPath(tmpDir, "symlinkToDir1")
    64  
    65  	assert.NilError(c, runDockerCp(c, srcPath, dstPath))
    66  	assert.NilError(c, symlinkTargetEquals(c, dstPath, "dir1"), "The symlink should not have been modified")
    67  	assert.NilError(c, fileContentEquals(c, cpPath(tmpDir, "dir1/dir2/file2-1"), "file2-1\n"), `The directory should now contain a copy of "dir2"`)
    68  
    69  	// Next, copy a directory from the container to a symlink to a local
    70  	// directory that does not exist (a broken symlink). This should create
    71  	// the target as a directory with the contents of the source directory. It
    72  	// should not modify the symlink.
    73  	dstPath = cpPath(tmpDir, "brokenSymlinkToDirX")
    74  
    75  	assert.NilError(c, runDockerCp(c, srcPath, dstPath))
    76  	assert.NilError(c, symlinkTargetEquals(c, dstPath, "dirX"), "The symlink should not have been modified")
    77  	assert.NilError(c, fileContentEquals(c, cpPath(tmpDir, "dirX/file2-1"), "file2-1\n"), `The "dirX" directory should now be a copy of "dir2"`)
    78  }
    79  
    80  // Possibilities are reduced to the remaining 10 cases:
    81  //
    82  //  case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action
    83  // ===================================================================================================
    84  //   A   |  no      |  -              |  no       |  -       |  no      |  create file
    85  //   B   |  no      |  -              |  no       |  -       |  yes     |  error
    86  //   C   |  no      |  -              |  yes      |  no      |  -       |  overwrite file
    87  //   D   |  no      |  -              |  yes      |  yes     |  -       |  create file in dst dir
    88  //   E   |  yes     |  no             |  no       |  -       |  -       |  create dir, copy contents
    89  //   F   |  yes     |  no             |  yes      |  no      |  -       |  error
    90  //   G   |  yes     |  no             |  yes      |  yes     |  -       |  copy dir and contents
    91  //   H   |  yes     |  yes            |  no       |  -       |  -       |  create dir, copy contents
    92  //   I   |  yes     |  yes            |  yes      |  no      |  -       |  error
    93  //   J   |  yes     |  yes            |  yes      |  yes     |  -       |  copy dir contents
    94  //
    95  
    96  // A. SRC specifies a file and DST (no trailing path separator) doesn't
    97  //    exist. This should create a file with the name DST and copy the
    98  //    contents of the source file into it.
    99  func (s *DockerSuite) TestCpFromCaseA(c *testing.T) {
   100  	testRequires(c, DaemonIsLinux)
   101  	containerID := makeTestContainer(c, testContainerOptions{
   102  		addContent: true, workDir: "/root",
   103  	})
   104  
   105  	tmpDir := getTestDir(c, "test-cp-from-case-a")
   106  	defer os.RemoveAll(tmpDir)
   107  
   108  	srcPath := containerCpPath(containerID, "/root/file1")
   109  	dstPath := cpPath(tmpDir, "itWorks.txt")
   110  
   111  	assert.NilError(c, runDockerCp(c, srcPath, dstPath))
   112  	assert.NilError(c, fileContentEquals(c, dstPath, "file1\n"))
   113  }
   114  
   115  // B. SRC specifies a file and DST (with trailing path separator) doesn't
   116  //    exist. This should cause an error because the copy operation cannot
   117  //    create a directory when copying a single file.
   118  func (s *DockerSuite) TestCpFromCaseB(c *testing.T) {
   119  	testRequires(c, DaemonIsLinux)
   120  	containerID := makeTestContainer(c, testContainerOptions{addContent: true})
   121  
   122  	tmpDir := getTestDir(c, "test-cp-from-case-b")
   123  	defer os.RemoveAll(tmpDir)
   124  
   125  	srcPath := containerCpPath(containerID, "/file1")
   126  	dstDir := cpPathTrailingSep(tmpDir, "testDir")
   127  
   128  	err := runDockerCp(c, srcPath, dstDir)
   129  	assert.ErrorContains(c, err, "")
   130  	assert.Assert(c, isCpDirNotExist(err), "expected DirNotExists error, but got %T: %s", err, err)
   131  }
   132  
   133  // C. SRC specifies a file and DST exists as a file. This should overwrite
   134  //    the file at DST with the contents of the source file.
   135  func (s *DockerSuite) TestCpFromCaseC(c *testing.T) {
   136  	testRequires(c, DaemonIsLinux)
   137  	containerID := makeTestContainer(c, testContainerOptions{
   138  		addContent: true, workDir: "/root",
   139  	})
   140  
   141  	tmpDir := getTestDir(c, "test-cp-from-case-c")
   142  	defer os.RemoveAll(tmpDir)
   143  
   144  	makeTestContentInDir(c, tmpDir)
   145  
   146  	srcPath := containerCpPath(containerID, "/root/file1")
   147  	dstPath := cpPath(tmpDir, "file2")
   148  
   149  	// Ensure the local file starts with different content.
   150  	assert.NilError(c, fileContentEquals(c, dstPath, "file2\n"))
   151  	assert.NilError(c, runDockerCp(c, srcPath, dstPath))
   152  	assert.NilError(c, fileContentEquals(c, dstPath, "file1\n"))
   153  }
   154  
   155  // D. SRC specifies a file and DST exists as a directory. This should place
   156  //    a copy of the source file inside it using the basename from SRC. Ensure
   157  //    this works whether DST has a trailing path separator or not.
   158  func (s *DockerSuite) TestCpFromCaseD(c *testing.T) {
   159  	testRequires(c, DaemonIsLinux)
   160  	containerID := makeTestContainer(c, testContainerOptions{addContent: true})
   161  
   162  	tmpDir := getTestDir(c, "test-cp-from-case-d")
   163  	defer os.RemoveAll(tmpDir)
   164  
   165  	makeTestContentInDir(c, tmpDir)
   166  
   167  	srcPath := containerCpPath(containerID, "/file1")
   168  	dstDir := cpPath(tmpDir, "dir1")
   169  	dstPath := filepath.Join(dstDir, "file1")
   170  
   171  	// Ensure that dstPath doesn't exist.
   172  	_, err := os.Stat(dstPath)
   173  	assert.Assert(c, os.IsNotExist(err), "did not expect dstPath %q to exist", dstPath)
   174  
   175  	assert.NilError(c, runDockerCp(c, srcPath, dstDir))
   176  	assert.NilError(c, fileContentEquals(c, dstPath, "file1\n"))
   177  
   178  	// Now try again but using a trailing path separator for dstDir.
   179  
   180  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove dstDir")
   181  	assert.NilError(c, os.MkdirAll(dstDir, os.FileMode(0755)), "unable to make dstDir")
   182  
   183  	dstDir = cpPathTrailingSep(tmpDir, "dir1")
   184  
   185  	assert.NilError(c, runDockerCp(c, srcPath, dstDir))
   186  	assert.NilError(c, fileContentEquals(c, dstPath, "file1\n"))
   187  }
   188  
   189  // E. SRC specifies a directory and DST does not exist. This should create a
   190  //    directory at DST and copy the contents of the SRC directory into the DST
   191  //    directory. Ensure this works whether DST has a trailing path separator or
   192  //    not.
   193  func (s *DockerSuite) TestCpFromCaseE(c *testing.T) {
   194  	testRequires(c, DaemonIsLinux)
   195  	containerID := makeTestContainer(c, testContainerOptions{addContent: true})
   196  
   197  	tmpDir := getTestDir(c, "test-cp-from-case-e")
   198  	defer os.RemoveAll(tmpDir)
   199  
   200  	srcDir := containerCpPath(containerID, "dir1")
   201  	dstDir := cpPath(tmpDir, "testDir")
   202  	dstPath := filepath.Join(dstDir, "file1-1")
   203  
   204  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   205  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   206  
   207  	// Now try again but using a trailing path separator for dstDir.
   208  
   209  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove dstDir")
   210  
   211  	dstDir = cpPathTrailingSep(tmpDir, "testDir")
   212  
   213  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   214  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   215  }
   216  
   217  // F. SRC specifies a directory and DST exists as a file. This should cause an
   218  //    error as it is not possible to overwrite a file with a directory.
   219  func (s *DockerSuite) TestCpFromCaseF(c *testing.T) {
   220  	testRequires(c, DaemonIsLinux)
   221  	containerID := makeTestContainer(c, testContainerOptions{
   222  		addContent: true, workDir: "/root",
   223  	})
   224  
   225  	tmpDir := getTestDir(c, "test-cp-from-case-f")
   226  	defer os.RemoveAll(tmpDir)
   227  
   228  	makeTestContentInDir(c, tmpDir)
   229  
   230  	srcDir := containerCpPath(containerID, "/root/dir1")
   231  	dstFile := cpPath(tmpDir, "file1")
   232  
   233  	err := runDockerCp(c, srcDir, dstFile)
   234  	assert.ErrorContains(c, err, "")
   235  	assert.Assert(c, isCpCannotCopyDir(err), "expected ErrCannotCopyDir error, but got %T: %s", err, err)
   236  }
   237  
   238  // G. SRC specifies a directory and DST exists as a directory. This should copy
   239  //    the SRC directory and all its contents to the DST directory. Ensure this
   240  //    works whether DST has a trailing path separator or not.
   241  func (s *DockerSuite) TestCpFromCaseG(c *testing.T) {
   242  	testRequires(c, DaemonIsLinux)
   243  	containerID := makeTestContainer(c, testContainerOptions{
   244  		addContent: true, workDir: "/root",
   245  	})
   246  
   247  	tmpDir := getTestDir(c, "test-cp-from-case-g")
   248  	defer os.RemoveAll(tmpDir)
   249  
   250  	makeTestContentInDir(c, tmpDir)
   251  
   252  	srcDir := containerCpPath(containerID, "/root/dir1")
   253  	dstDir := cpPath(tmpDir, "dir2")
   254  	resultDir := filepath.Join(dstDir, "dir1")
   255  	dstPath := filepath.Join(resultDir, "file1-1")
   256  
   257  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   258  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   259  
   260  	// Now try again but using a trailing path separator for dstDir.
   261  
   262  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove dstDir")
   263  	assert.NilError(c, os.MkdirAll(dstDir, os.FileMode(0755)), "unable to make dstDir")
   264  
   265  	dstDir = cpPathTrailingSep(tmpDir, "dir2")
   266  
   267  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   268  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   269  }
   270  
   271  // H. SRC specifies a directory's contents only and DST does not exist. This
   272  //    should create a directory at DST and copy the contents of the SRC
   273  //    directory (but not the directory itself) into the DST directory. Ensure
   274  //    this works whether DST has a trailing path separator or not.
   275  func (s *DockerSuite) TestCpFromCaseH(c *testing.T) {
   276  	testRequires(c, DaemonIsLinux)
   277  	containerID := makeTestContainer(c, testContainerOptions{addContent: true})
   278  
   279  	tmpDir := getTestDir(c, "test-cp-from-case-h")
   280  	defer os.RemoveAll(tmpDir)
   281  
   282  	srcDir := containerCpPathTrailingSep(containerID, "dir1") + "."
   283  	dstDir := cpPath(tmpDir, "testDir")
   284  	dstPath := filepath.Join(dstDir, "file1-1")
   285  
   286  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   287  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   288  
   289  	// Now try again but using a trailing path separator for dstDir.
   290  
   291  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove resultDir")
   292  
   293  	dstDir = cpPathTrailingSep(tmpDir, "testDir")
   294  
   295  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   296  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   297  }
   298  
   299  // I. SRC specifies a directory's contents only and DST exists as a file. This
   300  //    should cause an error as it is not possible to overwrite a file with a
   301  //    directory.
   302  func (s *DockerSuite) TestCpFromCaseI(c *testing.T) {
   303  	testRequires(c, DaemonIsLinux)
   304  	containerID := makeTestContainer(c, testContainerOptions{
   305  		addContent: true, workDir: "/root",
   306  	})
   307  
   308  	tmpDir := getTestDir(c, "test-cp-from-case-i")
   309  	defer os.RemoveAll(tmpDir)
   310  
   311  	makeTestContentInDir(c, tmpDir)
   312  
   313  	srcDir := containerCpPathTrailingSep(containerID, "/root/dir1") + "."
   314  	dstFile := cpPath(tmpDir, "file1")
   315  
   316  	err := runDockerCp(c, srcDir, dstFile)
   317  	assert.ErrorContains(c, err, "")
   318  	assert.Assert(c, isCpCannotCopyDir(err), "expected ErrCannotCopyDir error, but got %T: %s", err, err)
   319  }
   320  
   321  // J. SRC specifies a directory's contents only and DST exists as a directory.
   322  //    This should copy the contents of the SRC directory (but not the directory
   323  //    itself) into the DST directory. Ensure this works whether DST has a
   324  //    trailing path separator or not.
   325  func (s *DockerSuite) TestCpFromCaseJ(c *testing.T) {
   326  	testRequires(c, DaemonIsLinux)
   327  	containerID := makeTestContainer(c, testContainerOptions{
   328  		addContent: true, workDir: "/root",
   329  	})
   330  
   331  	tmpDir := getTestDir(c, "test-cp-from-case-j")
   332  	defer os.RemoveAll(tmpDir)
   333  
   334  	makeTestContentInDir(c, tmpDir)
   335  
   336  	srcDir := containerCpPathTrailingSep(containerID, "/root/dir1") + "."
   337  	dstDir := cpPath(tmpDir, "dir2")
   338  	dstPath := filepath.Join(dstDir, "file1-1")
   339  
   340  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   341  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   342  
   343  	// Now try again but using a trailing path separator for dstDir.
   344  
   345  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove dstDir")
   346  	assert.NilError(c, os.MkdirAll(dstDir, os.FileMode(0755)), "unable to make dstDir")
   347  
   348  	dstDir = cpPathTrailingSep(tmpDir, "dir2")
   349  
   350  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   351  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   352  }