gopkg.in/docker/docker.v20@v20.10.27/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	exist.
    97  //
    98  // This should create a file with the name DST and copy the contents of the
    99  // source file into it.
   100  func (s *DockerSuite) TestCpFromCaseA(c *testing.T) {
   101  	testRequires(c, DaemonIsLinux)
   102  	containerID := makeTestContainer(c, testContainerOptions{
   103  		addContent: true, workDir: "/root",
   104  	})
   105  
   106  	tmpDir := getTestDir(c, "test-cp-from-case-a")
   107  	defer os.RemoveAll(tmpDir)
   108  
   109  	srcPath := containerCpPath(containerID, "/root/file1")
   110  	dstPath := cpPath(tmpDir, "itWorks.txt")
   111  
   112  	assert.NilError(c, runDockerCp(c, srcPath, dstPath))
   113  	assert.NilError(c, fileContentEquals(c, dstPath, "file1\n"))
   114  }
   115  
   116  // B. SRC specifies a file and DST (with trailing path separator) doesn't exist.
   117  //
   118  // This should cause an error because the copy operation cannot	create a directory
   119  // when copying a single file.
   120  func (s *DockerSuite) TestCpFromCaseB(c *testing.T) {
   121  	testRequires(c, DaemonIsLinux)
   122  	containerID := makeTestContainer(c, testContainerOptions{addContent: true})
   123  
   124  	tmpDir := getTestDir(c, "test-cp-from-case-b")
   125  	defer os.RemoveAll(tmpDir)
   126  
   127  	srcPath := containerCpPath(containerID, "/file1")
   128  	dstDir := cpPathTrailingSep(tmpDir, "testDir")
   129  
   130  	err := runDockerCp(c, srcPath, dstDir)
   131  	assert.ErrorContains(c, err, "")
   132  	assert.Assert(c, isCpDirNotExist(err), "expected DirNotExists error, but got %T: %s", err, err)
   133  }
   134  
   135  // C. SRC specifies a file and DST exists as a file.
   136  //
   137  // This should overwrite the file at DST with the contents of the source file.
   138  func (s *DockerSuite) TestCpFromCaseC(c *testing.T) {
   139  	testRequires(c, DaemonIsLinux)
   140  	containerID := makeTestContainer(c, testContainerOptions{
   141  		addContent: true, workDir: "/root",
   142  	})
   143  
   144  	tmpDir := getTestDir(c, "test-cp-from-case-c")
   145  	defer os.RemoveAll(tmpDir)
   146  
   147  	makeTestContentInDir(c, tmpDir)
   148  
   149  	srcPath := containerCpPath(containerID, "/root/file1")
   150  	dstPath := cpPath(tmpDir, "file2")
   151  
   152  	// Ensure the local file starts with different content.
   153  	assert.NilError(c, fileContentEquals(c, dstPath, "file2\n"))
   154  	assert.NilError(c, runDockerCp(c, srcPath, dstPath))
   155  	assert.NilError(c, fileContentEquals(c, dstPath, "file1\n"))
   156  }
   157  
   158  // D. SRC specifies a file and DST exists as a directory.
   159  //
   160  // This should place a copy of the source file inside it using the basename from
   161  // SRC. Ensure this works whether DST has a trailing path separator or not.
   162  func (s *DockerSuite) TestCpFromCaseD(c *testing.T) {
   163  	testRequires(c, DaemonIsLinux)
   164  	containerID := makeTestContainer(c, testContainerOptions{addContent: true})
   165  
   166  	tmpDir := getTestDir(c, "test-cp-from-case-d")
   167  	defer os.RemoveAll(tmpDir)
   168  
   169  	makeTestContentInDir(c, tmpDir)
   170  
   171  	srcPath := containerCpPath(containerID, "/file1")
   172  	dstDir := cpPath(tmpDir, "dir1")
   173  	dstPath := filepath.Join(dstDir, "file1")
   174  
   175  	// Ensure that dstPath doesn't exist.
   176  	_, err := os.Stat(dstPath)
   177  	assert.Assert(c, os.IsNotExist(err), "did not expect dstPath %q to exist", dstPath)
   178  
   179  	assert.NilError(c, runDockerCp(c, srcPath, dstDir))
   180  	assert.NilError(c, fileContentEquals(c, dstPath, "file1\n"))
   181  
   182  	// Now try again but using a trailing path separator for dstDir.
   183  
   184  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove dstDir")
   185  	assert.NilError(c, os.MkdirAll(dstDir, os.FileMode(0755)), "unable to make dstDir")
   186  
   187  	dstDir = cpPathTrailingSep(tmpDir, "dir1")
   188  
   189  	assert.NilError(c, runDockerCp(c, srcPath, dstDir))
   190  	assert.NilError(c, fileContentEquals(c, dstPath, "file1\n"))
   191  }
   192  
   193  // E. SRC specifies a directory and DST does not exist.
   194  //
   195  // This should create a directory at DST and copy the contents of the SRC directory
   196  // into the DST directory. Ensure this works whether DST has a trailing path
   197  // separator or	not.
   198  func (s *DockerSuite) TestCpFromCaseE(c *testing.T) {
   199  	testRequires(c, DaemonIsLinux)
   200  	containerID := makeTestContainer(c, testContainerOptions{addContent: true})
   201  
   202  	tmpDir := getTestDir(c, "test-cp-from-case-e")
   203  	defer os.RemoveAll(tmpDir)
   204  
   205  	srcDir := containerCpPath(containerID, "dir1")
   206  	dstDir := cpPath(tmpDir, "testDir")
   207  	dstPath := filepath.Join(dstDir, "file1-1")
   208  
   209  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   210  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   211  
   212  	// Now try again but using a trailing path separator for dstDir.
   213  
   214  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove dstDir")
   215  
   216  	dstDir = cpPathTrailingSep(tmpDir, "testDir")
   217  
   218  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   219  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   220  }
   221  
   222  // F. SRC specifies a directory and DST exists as a file.
   223  //
   224  // This should cause an	error as it is not possible to overwrite a file with a
   225  // directory.
   226  func (s *DockerSuite) TestCpFromCaseF(c *testing.T) {
   227  	testRequires(c, DaemonIsLinux)
   228  	containerID := makeTestContainer(c, testContainerOptions{
   229  		addContent: true, workDir: "/root",
   230  	})
   231  
   232  	tmpDir := getTestDir(c, "test-cp-from-case-f")
   233  	defer os.RemoveAll(tmpDir)
   234  
   235  	makeTestContentInDir(c, tmpDir)
   236  
   237  	srcDir := containerCpPath(containerID, "/root/dir1")
   238  	dstFile := cpPath(tmpDir, "file1")
   239  
   240  	err := runDockerCp(c, srcDir, dstFile)
   241  	assert.ErrorContains(c, err, "")
   242  	assert.Assert(c, isCpCannotCopyDir(err), "expected ErrCannotCopyDir error, but got %T: %s", err, err)
   243  }
   244  
   245  // G. SRC specifies a directory and DST exists as a directory.
   246  //
   247  // This should copy the SRC directory and all its contents to the DST directory.
   248  // Ensure this works whether DST has a trailing path separator or not.
   249  func (s *DockerSuite) TestCpFromCaseG(c *testing.T) {
   250  	testRequires(c, DaemonIsLinux)
   251  	containerID := makeTestContainer(c, testContainerOptions{
   252  		addContent: true, workDir: "/root",
   253  	})
   254  
   255  	tmpDir := getTestDir(c, "test-cp-from-case-g")
   256  	defer os.RemoveAll(tmpDir)
   257  
   258  	makeTestContentInDir(c, tmpDir)
   259  
   260  	srcDir := containerCpPath(containerID, "/root/dir1")
   261  	dstDir := cpPath(tmpDir, "dir2")
   262  	resultDir := filepath.Join(dstDir, "dir1")
   263  	dstPath := filepath.Join(resultDir, "file1-1")
   264  
   265  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   266  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   267  
   268  	// Now try again but using a trailing path separator for dstDir.
   269  
   270  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove dstDir")
   271  	assert.NilError(c, os.MkdirAll(dstDir, os.FileMode(0755)), "unable to make dstDir")
   272  
   273  	dstDir = cpPathTrailingSep(tmpDir, "dir2")
   274  
   275  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   276  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   277  }
   278  
   279  // H. SRC specifies a directory's contents only and DST does not exist.
   280  //
   281  // This should create a directory at DST and copy the contents of the SRC
   282  // directory (but not the directory itself) into the DST directory. Ensure
   283  // this works whether DST has a trailing path separator or not.
   284  func (s *DockerSuite) TestCpFromCaseH(c *testing.T) {
   285  	testRequires(c, DaemonIsLinux)
   286  	containerID := makeTestContainer(c, testContainerOptions{addContent: true})
   287  
   288  	tmpDir := getTestDir(c, "test-cp-from-case-h")
   289  	defer os.RemoveAll(tmpDir)
   290  
   291  	srcDir := containerCpPathTrailingSep(containerID, "dir1") + "."
   292  	dstDir := cpPath(tmpDir, "testDir")
   293  	dstPath := filepath.Join(dstDir, "file1-1")
   294  
   295  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   296  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   297  
   298  	// Now try again but using a trailing path separator for dstDir.
   299  
   300  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove resultDir")
   301  
   302  	dstDir = cpPathTrailingSep(tmpDir, "testDir")
   303  
   304  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   305  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   306  }
   307  
   308  // I. SRC specifies a directory's contents only and DST exists as a file.
   309  //
   310  // This	should cause an error as it is not possible to overwrite a file with a
   311  // directory.
   312  func (s *DockerSuite) TestCpFromCaseI(c *testing.T) {
   313  	testRequires(c, DaemonIsLinux)
   314  	containerID := makeTestContainer(c, testContainerOptions{
   315  		addContent: true, workDir: "/root",
   316  	})
   317  
   318  	tmpDir := getTestDir(c, "test-cp-from-case-i")
   319  	defer os.RemoveAll(tmpDir)
   320  
   321  	makeTestContentInDir(c, tmpDir)
   322  
   323  	srcDir := containerCpPathTrailingSep(containerID, "/root/dir1") + "."
   324  	dstFile := cpPath(tmpDir, "file1")
   325  
   326  	err := runDockerCp(c, srcDir, dstFile)
   327  	assert.ErrorContains(c, err, "")
   328  	assert.Assert(c, isCpCannotCopyDir(err), "expected ErrCannotCopyDir error, but got %T: %s", err, err)
   329  }
   330  
   331  // J. SRC specifies a directory's contents only and DST exists as a directory.
   332  //
   333  // This should copy the contents of the SRC directory (but not the directory
   334  // itself) into the DST directory. Ensure this works whether DST has a
   335  // trailing path separator or not.
   336  func (s *DockerSuite) TestCpFromCaseJ(c *testing.T) {
   337  	testRequires(c, DaemonIsLinux)
   338  	containerID := makeTestContainer(c, testContainerOptions{
   339  		addContent: true, workDir: "/root",
   340  	})
   341  
   342  	tmpDir := getTestDir(c, "test-cp-from-case-j")
   343  	defer os.RemoveAll(tmpDir)
   344  
   345  	makeTestContentInDir(c, tmpDir)
   346  
   347  	srcDir := containerCpPathTrailingSep(containerID, "/root/dir1") + "."
   348  	dstDir := cpPath(tmpDir, "dir2")
   349  	dstPath := filepath.Join(dstDir, "file1-1")
   350  
   351  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   352  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   353  
   354  	// Now try again but using a trailing path separator for dstDir.
   355  
   356  	assert.NilError(c, os.RemoveAll(dstDir), "unable to remove dstDir")
   357  	assert.NilError(c, os.MkdirAll(dstDir, os.FileMode(0755)), "unable to make dstDir")
   358  
   359  	dstDir = cpPathTrailingSep(tmpDir, "dir2")
   360  
   361  	assert.NilError(c, runDockerCp(c, srcDir, dstDir))
   362  	assert.NilError(c, fileContentEquals(c, dstPath, "file1-1\n"))
   363  }