github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/integration-cli/docker_cli_cp_to_container_test.go (about)

     1  package main
     2  
     3  import (
     4  	"os"
     5  
     6  	"github.com/docker/docker/integration-cli/checker"
     7  	"github.com/go-check/check"
     8  )
     9  
    10  // Try all of the test cases from the archive package which implements the
    11  // internals of `docker cp` and ensure that the behavior matches when actually
    12  // copying to and from containers.
    13  
    14  // Basic assumptions about SRC and DST:
    15  // 1. SRC must exist.
    16  // 2. If SRC ends with a trailing separator, it must be a directory.
    17  // 3. DST parent directory must exist.
    18  // 4. If DST exists as a file, it must not end with a trailing separator.
    19  
    20  // Check that copying from a local path to a symlink in a container copies to
    21  // the symlink target and does not overwrite the container symlink itself.
    22  func (s *DockerSuite) TestCpToSymlinkDestination(c *check.C) {
    23  	//  stat /tmp/test-cp-to-symlink-destination-262430901/vol3 gets permission denied for the user
    24  	testRequires(c, NotUserNamespace)
    25  	testRequires(c, DaemonIsLinux)
    26  	testRequires(c, testEnv.IsLocalDaemon) // Requires local volume mount bind.
    27  
    28  	testVol := getTestDir(c, "test-cp-to-symlink-destination-")
    29  	defer os.RemoveAll(testVol)
    30  
    31  	makeTestContentInDir(c, testVol)
    32  
    33  	containerID := makeTestContainer(c, testContainerOptions{
    34  		volumes: defaultVolumes(testVol), // Our bind mount is at /vol2
    35  	})
    36  
    37  	// First, copy a local file to a symlink to a file in the container. This
    38  	// should overwrite the symlink target contents with the source contents.
    39  	srcPath := cpPath(testVol, "file2")
    40  	dstPath := containerCpPath(containerID, "/vol2/symlinkToFile1")
    41  
    42  	c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil)
    43  
    44  	// The symlink should not have been modified.
    45  	c.Assert(symlinkTargetEquals(c, cpPath(testVol, "symlinkToFile1"), "file1"), checker.IsNil)
    46  
    47  	// The file should have the contents of "file2" now.
    48  	c.Assert(fileContentEquals(c, cpPath(testVol, "file1"), "file2\n"), checker.IsNil)
    49  
    50  	// Next, copy a local file to a symlink to a directory in the container.
    51  	// This should copy the file into the symlink target directory.
    52  	dstPath = containerCpPath(containerID, "/vol2/symlinkToDir1")
    53  
    54  	c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil)
    55  
    56  	// The symlink should not have been modified.
    57  	c.Assert(symlinkTargetEquals(c, cpPath(testVol, "symlinkToDir1"), "dir1"), checker.IsNil)
    58  
    59  	// The file should have the contents of "file2" now.
    60  	c.Assert(fileContentEquals(c, cpPath(testVol, "file2"), "file2\n"), checker.IsNil)
    61  
    62  	// Next, copy a file to a symlink to a file that does not exist (a broken
    63  	// symlink) in the container. This should create the target file with the
    64  	// contents of the source file.
    65  	dstPath = containerCpPath(containerID, "/vol2/brokenSymlinkToFileX")
    66  
    67  	c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil)
    68  
    69  	// The symlink should not have been modified.
    70  	c.Assert(symlinkTargetEquals(c, cpPath(testVol, "brokenSymlinkToFileX"), "fileX"), checker.IsNil)
    71  
    72  	// The file should have the contents of "file2" now.
    73  	c.Assert(fileContentEquals(c, cpPath(testVol, "fileX"), "file2\n"), checker.IsNil)
    74  
    75  	// Next, copy a local directory to a symlink to a directory in the
    76  	// container. This should copy the directory into the symlink target
    77  	// directory and not modify the symlink.
    78  	srcPath = cpPath(testVol, "/dir2")
    79  	dstPath = containerCpPath(containerID, "/vol2/symlinkToDir1")
    80  
    81  	c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil)
    82  
    83  	// The symlink should not have been modified.
    84  	c.Assert(symlinkTargetEquals(c, cpPath(testVol, "symlinkToDir1"), "dir1"), checker.IsNil)
    85  
    86  	// The directory should now contain a copy of "dir2".
    87  	c.Assert(fileContentEquals(c, cpPath(testVol, "dir1/dir2/file2-1"), "file2-1\n"), checker.IsNil)
    88  
    89  	// Next, copy a local directory to a symlink to a local directory that does
    90  	// not exist (a broken symlink) in the container. This should create the
    91  	// target as a directory with the contents of the source directory. It
    92  	// should not modify the symlink.
    93  	dstPath = containerCpPath(containerID, "/vol2/brokenSymlinkToDirX")
    94  
    95  	c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil)
    96  
    97  	// The symlink should not have been modified.
    98  	c.Assert(symlinkTargetEquals(c, cpPath(testVol, "brokenSymlinkToDirX"), "dirX"), checker.IsNil)
    99  
   100  	// The "dirX" directory should now be a copy of "dir2".
   101  	c.Assert(fileContentEquals(c, cpPath(testVol, "dirX/file2-1"), "file2-1\n"), checker.IsNil)
   102  }
   103  
   104  // Possibilities are reduced to the remaining 10 cases:
   105  //
   106  //  case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action
   107  // ===================================================================================================
   108  //   A   |  no      |  -              |  no       |  -       |  no      |  create file
   109  //   B   |  no      |  -              |  no       |  -       |  yes     |  error
   110  //   C   |  no      |  -              |  yes      |  no      |  -       |  overwrite file
   111  //   D   |  no      |  -              |  yes      |  yes     |  -       |  create file in dst dir
   112  //   E   |  yes     |  no             |  no       |  -       |  -       |  create dir, copy contents
   113  //   F   |  yes     |  no             |  yes      |  no      |  -       |  error
   114  //   G   |  yes     |  no             |  yes      |  yes     |  -       |  copy dir and contents
   115  //   H   |  yes     |  yes            |  no       |  -       |  -       |  create dir, copy contents
   116  //   I   |  yes     |  yes            |  yes      |  no      |  -       |  error
   117  //   J   |  yes     |  yes            |  yes      |  yes     |  -       |  copy dir contents
   118  //
   119  
   120  // A. SRC specifies a file and DST (no trailing path separator) doesn't
   121  //    exist. This should create a file with the name DST and copy the
   122  //    contents of the source file into it.
   123  func (s *DockerSuite) TestCpToCaseA(c *check.C) {
   124  	containerID := makeTestContainer(c, testContainerOptions{
   125  		workDir: "/root", command: makeCatFileCommand("itWorks.txt"),
   126  	})
   127  
   128  	tmpDir := getTestDir(c, "test-cp-to-case-a")
   129  	defer os.RemoveAll(tmpDir)
   130  
   131  	makeTestContentInDir(c, tmpDir)
   132  
   133  	srcPath := cpPath(tmpDir, "file1")
   134  	dstPath := containerCpPath(containerID, "/root/itWorks.txt")
   135  
   136  	c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil)
   137  
   138  	c.Assert(containerStartOutputEquals(c, containerID, "file1\n"), checker.IsNil)
   139  }
   140  
   141  // B. SRC specifies a file and DST (with trailing path separator) doesn't
   142  //    exist. This should cause an error because the copy operation cannot
   143  //    create a directory when copying a single file.
   144  func (s *DockerSuite) TestCpToCaseB(c *check.C) {
   145  	containerID := makeTestContainer(c, testContainerOptions{
   146  		command: makeCatFileCommand("testDir/file1"),
   147  	})
   148  
   149  	tmpDir := getTestDir(c, "test-cp-to-case-b")
   150  	defer os.RemoveAll(tmpDir)
   151  
   152  	makeTestContentInDir(c, tmpDir)
   153  
   154  	srcPath := cpPath(tmpDir, "file1")
   155  	dstDir := containerCpPathTrailingSep(containerID, "testDir")
   156  
   157  	err := runDockerCp(c, srcPath, dstDir, nil)
   158  	c.Assert(err, checker.NotNil)
   159  
   160  	c.Assert(isCpDirNotExist(err), checker.True, check.Commentf("expected DirNotExists error, but got %T: %s", err, err))
   161  }
   162  
   163  // C. SRC specifies a file and DST exists as a file. This should overwrite
   164  //    the file at DST with the contents of the source file.
   165  func (s *DockerSuite) TestCpToCaseC(c *check.C) {
   166  	testRequires(c, DaemonIsLinux)
   167  	containerID := makeTestContainer(c, testContainerOptions{
   168  		addContent: true, workDir: "/root",
   169  		command: makeCatFileCommand("file2"),
   170  	})
   171  
   172  	tmpDir := getTestDir(c, "test-cp-to-case-c")
   173  	defer os.RemoveAll(tmpDir)
   174  
   175  	makeTestContentInDir(c, tmpDir)
   176  
   177  	srcPath := cpPath(tmpDir, "file1")
   178  	dstPath := containerCpPath(containerID, "/root/file2")
   179  
   180  	// Ensure the container's file starts with the original content.
   181  	c.Assert(containerStartOutputEquals(c, containerID, "file2\n"), checker.IsNil)
   182  
   183  	c.Assert(runDockerCp(c, srcPath, dstPath, nil), checker.IsNil)
   184  
   185  	// Should now contain file1's contents.
   186  	c.Assert(containerStartOutputEquals(c, containerID, "file1\n"), checker.IsNil)
   187  }
   188  
   189  // D. SRC specifies a file and DST exists as a directory. This should place
   190  //    a copy of the source file inside it using the basename from SRC. Ensure
   191  //    this works whether DST has a trailing path separator or not.
   192  func (s *DockerSuite) TestCpToCaseD(c *check.C) {
   193  	testRequires(c, DaemonIsLinux)
   194  	containerID := makeTestContainer(c, testContainerOptions{
   195  		addContent: true,
   196  		command:    makeCatFileCommand("/dir1/file1"),
   197  	})
   198  
   199  	tmpDir := getTestDir(c, "test-cp-to-case-d")
   200  	defer os.RemoveAll(tmpDir)
   201  
   202  	makeTestContentInDir(c, tmpDir)
   203  
   204  	srcPath := cpPath(tmpDir, "file1")
   205  	dstDir := containerCpPath(containerID, "dir1")
   206  
   207  	// Ensure that dstPath doesn't exist.
   208  	c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil)
   209  
   210  	c.Assert(runDockerCp(c, srcPath, dstDir, nil), checker.IsNil)
   211  
   212  	// Should now contain file1's contents.
   213  	c.Assert(containerStartOutputEquals(c, containerID, "file1\n"), checker.IsNil)
   214  
   215  	// Now try again but using a trailing path separator for dstDir.
   216  
   217  	// Make new destination container.
   218  	containerID = makeTestContainer(c, testContainerOptions{
   219  		addContent: true,
   220  		command:    makeCatFileCommand("/dir1/file1"),
   221  	})
   222  
   223  	dstDir = containerCpPathTrailingSep(containerID, "dir1")
   224  
   225  	// Ensure that dstPath doesn't exist.
   226  	c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil)
   227  
   228  	c.Assert(runDockerCp(c, srcPath, dstDir, nil), checker.IsNil)
   229  
   230  	// Should now contain file1's contents.
   231  	c.Assert(containerStartOutputEquals(c, containerID, "file1\n"), checker.IsNil)
   232  }
   233  
   234  // E. SRC specifies a directory and DST does not exist. This should create a
   235  //    directory at DST and copy the contents of the SRC directory into the DST
   236  //    directory. Ensure this works whether DST has a trailing path separator or
   237  //    not.
   238  func (s *DockerSuite) TestCpToCaseE(c *check.C) {
   239  	containerID := makeTestContainer(c, testContainerOptions{
   240  		command: makeCatFileCommand("/testDir/file1-1"),
   241  	})
   242  
   243  	tmpDir := getTestDir(c, "test-cp-to-case-e")
   244  	defer os.RemoveAll(tmpDir)
   245  
   246  	makeTestContentInDir(c, tmpDir)
   247  
   248  	srcDir := cpPath(tmpDir, "dir1")
   249  	dstDir := containerCpPath(containerID, "testDir")
   250  
   251  	c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil)
   252  
   253  	// Should now contain file1-1's contents.
   254  	c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil)
   255  
   256  	// Now try again but using a trailing path separator for dstDir.
   257  
   258  	// Make new destination container.
   259  	containerID = makeTestContainer(c, testContainerOptions{
   260  		command: makeCatFileCommand("/testDir/file1-1"),
   261  	})
   262  
   263  	dstDir = containerCpPathTrailingSep(containerID, "testDir")
   264  
   265  	c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil)
   266  
   267  	// Should now contain file1-1's contents.
   268  	c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil)
   269  }
   270  
   271  // F. SRC specifies a directory and DST exists as a file. This should cause an
   272  //    error as it is not possible to overwrite a file with a directory.
   273  func (s *DockerSuite) TestCpToCaseF(c *check.C) {
   274  	testRequires(c, DaemonIsLinux)
   275  	containerID := makeTestContainer(c, testContainerOptions{
   276  		addContent: true, workDir: "/root",
   277  	})
   278  
   279  	tmpDir := getTestDir(c, "test-cp-to-case-f")
   280  	defer os.RemoveAll(tmpDir)
   281  
   282  	makeTestContentInDir(c, tmpDir)
   283  
   284  	srcDir := cpPath(tmpDir, "dir1")
   285  	dstFile := containerCpPath(containerID, "/root/file1")
   286  
   287  	err := runDockerCp(c, srcDir, dstFile, nil)
   288  	c.Assert(err, checker.NotNil)
   289  
   290  	c.Assert(isCpCannotCopyDir(err), checker.True, check.Commentf("expected ErrCannotCopyDir error, but got %T: %s", err, err))
   291  }
   292  
   293  // G. SRC specifies a directory and DST exists as a directory. This should copy
   294  //    the SRC directory and all its contents to the DST directory. Ensure this
   295  //    works whether DST has a trailing path separator or not.
   296  func (s *DockerSuite) TestCpToCaseG(c *check.C) {
   297  	testRequires(c, DaemonIsLinux)
   298  	containerID := makeTestContainer(c, testContainerOptions{
   299  		addContent: true, workDir: "/root",
   300  		command: makeCatFileCommand("dir2/dir1/file1-1"),
   301  	})
   302  
   303  	tmpDir := getTestDir(c, "test-cp-to-case-g")
   304  	defer os.RemoveAll(tmpDir)
   305  
   306  	makeTestContentInDir(c, tmpDir)
   307  
   308  	srcDir := cpPath(tmpDir, "dir1")
   309  	dstDir := containerCpPath(containerID, "/root/dir2")
   310  
   311  	// Ensure that dstPath doesn't exist.
   312  	c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil)
   313  
   314  	c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil)
   315  
   316  	// Should now contain file1-1's contents.
   317  	c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil)
   318  
   319  	// Now try again but using a trailing path separator for dstDir.
   320  
   321  	// Make new destination container.
   322  	containerID = makeTestContainer(c, testContainerOptions{
   323  		addContent: true,
   324  		command:    makeCatFileCommand("/dir2/dir1/file1-1"),
   325  	})
   326  
   327  	dstDir = containerCpPathTrailingSep(containerID, "/dir2")
   328  
   329  	// Ensure that dstPath doesn't exist.
   330  	c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil)
   331  
   332  	c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil)
   333  
   334  	// Should now contain file1-1's contents.
   335  	c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil)
   336  }
   337  
   338  // H. SRC specifies a directory's contents only and DST does not exist. This
   339  //    should create a directory at DST and copy the contents of the SRC
   340  //    directory (but not the directory itself) into the DST directory. Ensure
   341  //    this works whether DST has a trailing path separator or not.
   342  func (s *DockerSuite) TestCpToCaseH(c *check.C) {
   343  	containerID := makeTestContainer(c, testContainerOptions{
   344  		command: makeCatFileCommand("/testDir/file1-1"),
   345  	})
   346  
   347  	tmpDir := getTestDir(c, "test-cp-to-case-h")
   348  	defer os.RemoveAll(tmpDir)
   349  
   350  	makeTestContentInDir(c, tmpDir)
   351  
   352  	srcDir := cpPathTrailingSep(tmpDir, "dir1") + "."
   353  	dstDir := containerCpPath(containerID, "testDir")
   354  
   355  	c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil)
   356  
   357  	// Should now contain file1-1's contents.
   358  	c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil)
   359  
   360  	// Now try again but using a trailing path separator for dstDir.
   361  
   362  	// Make new destination container.
   363  	containerID = makeTestContainer(c, testContainerOptions{
   364  		command: makeCatFileCommand("/testDir/file1-1"),
   365  	})
   366  
   367  	dstDir = containerCpPathTrailingSep(containerID, "testDir")
   368  
   369  	c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil)
   370  
   371  	// Should now contain file1-1's contents.
   372  	c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil)
   373  }
   374  
   375  // I. SRC specifies a directory's contents only and DST exists as a file. This
   376  //    should cause an error as it is not possible to overwrite a file with a
   377  //    directory.
   378  func (s *DockerSuite) TestCpToCaseI(c *check.C) {
   379  	testRequires(c, DaemonIsLinux)
   380  	containerID := makeTestContainer(c, testContainerOptions{
   381  		addContent: true, workDir: "/root",
   382  	})
   383  
   384  	tmpDir := getTestDir(c, "test-cp-to-case-i")
   385  	defer os.RemoveAll(tmpDir)
   386  
   387  	makeTestContentInDir(c, tmpDir)
   388  
   389  	srcDir := cpPathTrailingSep(tmpDir, "dir1") + "."
   390  	dstFile := containerCpPath(containerID, "/root/file1")
   391  
   392  	err := runDockerCp(c, srcDir, dstFile, nil)
   393  	c.Assert(err, checker.NotNil)
   394  
   395  	c.Assert(isCpCannotCopyDir(err), checker.True, check.Commentf("expected ErrCannotCopyDir error, but got %T: %s", err, err))
   396  }
   397  
   398  // J. SRC specifies a directory's contents only and DST exists as a directory.
   399  //    This should copy the contents of the SRC directory (but not the directory
   400  //    itself) into the DST directory. Ensure this works whether DST has a
   401  //    trailing path separator or not.
   402  func (s *DockerSuite) TestCpToCaseJ(c *check.C) {
   403  	testRequires(c, DaemonIsLinux)
   404  	containerID := makeTestContainer(c, testContainerOptions{
   405  		addContent: true, workDir: "/root",
   406  		command: makeCatFileCommand("/dir2/file1-1"),
   407  	})
   408  
   409  	tmpDir := getTestDir(c, "test-cp-to-case-j")
   410  	defer os.RemoveAll(tmpDir)
   411  
   412  	makeTestContentInDir(c, tmpDir)
   413  
   414  	srcDir := cpPathTrailingSep(tmpDir, "dir1") + "."
   415  	dstDir := containerCpPath(containerID, "/dir2")
   416  
   417  	// Ensure that dstPath doesn't exist.
   418  	c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil)
   419  
   420  	c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil)
   421  
   422  	// Should now contain file1-1's contents.
   423  	c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil)
   424  
   425  	// Now try again but using a trailing path separator for dstDir.
   426  
   427  	// Make new destination container.
   428  	containerID = makeTestContainer(c, testContainerOptions{
   429  		command: makeCatFileCommand("/dir2/file1-1"),
   430  	})
   431  
   432  	dstDir = containerCpPathTrailingSep(containerID, "/dir2")
   433  
   434  	// Ensure that dstPath doesn't exist.
   435  	c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil)
   436  
   437  	c.Assert(runDockerCp(c, srcDir, dstDir, nil), checker.IsNil)
   438  
   439  	// Should now contain file1-1's contents.
   440  	c.Assert(containerStartOutputEquals(c, containerID, "file1-1\n"), checker.IsNil)
   441  }
   442  
   443  // The `docker cp` command should also ensure that you cannot
   444  // write to a container rootfs that is marked as read-only.
   445  func (s *DockerSuite) TestCpToErrReadOnlyRootfs(c *check.C) {
   446  	// --read-only + userns has remount issues
   447  	testRequires(c, DaemonIsLinux, NotUserNamespace)
   448  	tmpDir := getTestDir(c, "test-cp-to-err-read-only-rootfs")
   449  	defer os.RemoveAll(tmpDir)
   450  
   451  	makeTestContentInDir(c, tmpDir)
   452  
   453  	containerID := makeTestContainer(c, testContainerOptions{
   454  		readOnly: true, workDir: "/root",
   455  		command: makeCatFileCommand("shouldNotExist"),
   456  	})
   457  
   458  	srcPath := cpPath(tmpDir, "file1")
   459  	dstPath := containerCpPath(containerID, "/root/shouldNotExist")
   460  
   461  	err := runDockerCp(c, srcPath, dstPath, nil)
   462  	c.Assert(err, checker.NotNil)
   463  
   464  	c.Assert(isCpCannotCopyReadOnly(err), checker.True, check.Commentf("expected ErrContainerRootfsReadonly error, but got %T: %s", err, err))
   465  
   466  	// Ensure that dstPath doesn't exist.
   467  	c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil)
   468  }
   469  
   470  // The `docker cp` command should also ensure that you
   471  // cannot write to a volume that is mounted as read-only.
   472  func (s *DockerSuite) TestCpToErrReadOnlyVolume(c *check.C) {
   473  	// --read-only + userns has remount issues
   474  	testRequires(c, DaemonIsLinux, NotUserNamespace)
   475  	tmpDir := getTestDir(c, "test-cp-to-err-read-only-volume")
   476  	defer os.RemoveAll(tmpDir)
   477  
   478  	makeTestContentInDir(c, tmpDir)
   479  
   480  	containerID := makeTestContainer(c, testContainerOptions{
   481  		volumes: defaultVolumes(tmpDir), workDir: "/root",
   482  		command: makeCatFileCommand("/vol_ro/shouldNotExist"),
   483  	})
   484  
   485  	srcPath := cpPath(tmpDir, "file1")
   486  	dstPath := containerCpPath(containerID, "/vol_ro/shouldNotExist")
   487  
   488  	err := runDockerCp(c, srcPath, dstPath, nil)
   489  	c.Assert(err, checker.NotNil)
   490  
   491  	c.Assert(isCpCannotCopyReadOnly(err), checker.True, check.Commentf("expected ErrVolumeReadonly error, but got %T: %s", err, err))
   492  
   493  	// Ensure that dstPath doesn't exist.
   494  	c.Assert(containerStartOutputEquals(c, containerID, ""), checker.IsNil)
   495  }