github.com/rumpl/bof@v23.0.0-rc.2+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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 *DockerCLICpSuite) 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 }