github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/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 }