github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_cp_linux_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "strings" 24 "syscall" 25 "testing" 26 27 "github.com/containerd/nerdctl/pkg/rootlessutil" 28 "github.com/containerd/nerdctl/pkg/testutil" 29 "gotest.tools/v3/assert" 30 ) 31 32 func TestCopyToContainer(t *testing.T) { 33 t.Parallel() 34 base := testutil.NewBase(t) 35 testContainer := testutil.Identifier(t) 36 testStoppedContainer := "stopped-container-" + testutil.Identifier(t) 37 38 base.Cmd("run", "-d", "--name", testContainer, testutil.CommonImage, "sleep", "1h").AssertOK() 39 defer base.Cmd("rm", "-f", testContainer).Run() 40 41 base.Cmd("run", "-d", "--name", testStoppedContainer, testutil.CommonImage, "sleep", "1h").AssertOK() 42 defer base.Cmd("rm", "-f", testStoppedContainer).Run() 43 // Stop container immediately after starting for testing copying into stopped container 44 base.Cmd("stop", testStoppedContainer).AssertOK() 45 srcUID := os.Geteuid() 46 srcDir := t.TempDir() 47 srcFile := filepath.Join(srcDir, "test-file") 48 srcFileContent := []byte("test-file-content") 49 err := os.WriteFile(srcFile, srcFileContent, 0o644) 50 assert.NilError(t, err) 51 52 assertCat := func(catPath string, testContainer string, stopped bool) { 53 if stopped { 54 base.Cmd("start", testContainer).AssertOK() 55 defer base.Cmd("stop", testContainer).AssertOK() 56 } 57 t.Logf("catPath=%q", catPath) 58 base.Cmd("exec", testContainer, "cat", catPath).AssertOutExactly(string(srcFileContent)) 59 base.Cmd("exec", testContainer, "stat", "-c", "%u", catPath).AssertOutExactly(fmt.Sprintf("%d\n", srcUID)) 60 } 61 62 // For the test matrix, see https://docs.docker.com/engine/reference/commandline/cp/ 63 t.Run("SRC_PATH specifies a file", func(t *testing.T) { 64 srcPath := srcFile 65 t.Run("DEST_PATH does not exist", func(t *testing.T) { 66 destPath := "/dest-no-exist-no-slash" 67 base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() 68 catPath := destPath 69 assertCat(catPath, testContainer, false) 70 if rootlessutil.IsRootless() { 71 t.Skip("Test skipped in rootless mode for testStoppedContainer") 72 } 73 base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() 74 assertCat(catPath, testStoppedContainer, true) 75 }) 76 t.Run("DEST_PATH does not exist and ends with /", func(t *testing.T) { 77 destPath := "/dest-no-exist-with-slash/" 78 base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertFail() 79 if rootlessutil.IsRootless() { 80 t.Skip("Test skipped in rootless mode for testStoppedContainer") 81 } 82 base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertFail() 83 }) 84 t.Run("DEST_PATH exists and is a file", func(t *testing.T) { 85 destPath := "/dest-file-exists" 86 base.Cmd("exec", testContainer, "touch", destPath).AssertOK() 87 base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() 88 catPath := destPath 89 assertCat(catPath, testContainer, false) 90 if rootlessutil.IsRootless() { 91 t.Skip("Test skipped in rootless mode for testStoppedContainer") 92 } 93 base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() 94 assertCat(catPath, testStoppedContainer, true) 95 }) 96 t.Run("DEST_PATH exists and is a directory", func(t *testing.T) { 97 destPath := "/dest-dir-exists" 98 base.Cmd("exec", testContainer, "mkdir", "-p", destPath).AssertOK() 99 base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() 100 catPath := filepath.Join(destPath, filepath.Base(srcFile)) 101 assertCat(catPath, testContainer, false) 102 if rootlessutil.IsRootless() { 103 t.Skip("Test skipped in rootless mode for testStoppedContainer") 104 } 105 base.Cmd("start", testStoppedContainer).AssertOK() 106 base.Cmd("exec", testStoppedContainer, "mkdir", "-p", destPath).AssertOK() 107 base.Cmd("stop", testStoppedContainer).AssertOK() 108 base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() 109 assertCat(catPath, testStoppedContainer, true) 110 }) 111 t.Run("DEST_PATH is in a volume", func(t *testing.T) { 112 // Create a volume 113 vol := "somevol" 114 base.Cmd("volume", "create", vol).AssertOK() 115 defer base.Cmd("volume", "rm", vol).Run() 116 con := fmt.Sprintf("%s-with-volume", testContainer) 117 mountDir := "/some_dir" 118 base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK() 119 defer base.Cmd("rm", "-f", con).Run() 120 catPath := filepath.Join(mountDir, filepath.Base(srcFile)) 121 // Running container test 122 base.Cmd("cp", srcPath, con+":"+mountDir).AssertOK() 123 assertCat(catPath, con, false) 124 125 // Skip for rootless 126 if rootlessutil.IsRootless() { 127 t.Skip("Test skipped in rootless mode for testStoppedContainer") 128 } 129 // Stopped container test 130 // Delete previously copied file 131 base.Cmd("exec", con, "rm", catPath).AssertOK() 132 base.Cmd("stop", con).AssertOK() 133 base.Cmd("cp", srcPath, con+":"+mountDir).AssertOK() 134 assertCat(catPath, con, true) 135 }) 136 t.Run("Destination path is a read-only", func(t *testing.T) { 137 vol := "somevol" 138 base.Cmd("volume", "create", vol).AssertOK() 139 defer base.Cmd("volume", "rm", vol).Run() 140 con := fmt.Sprintf("%s-with-read-only-volume", testContainer) 141 mountDir := "/some_dir" 142 // Create container with read-only volume mounted 143 base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s:ro", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK() 144 defer base.Cmd("rm", "-f", con).Run() 145 base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail() 146 147 // Skip for rootless 148 if rootlessutil.IsRootless() { 149 t.Skip("Test skipped in rootless mode for testStoppedContainer") 150 } 151 152 // Stopped container test 153 // Delete previously copied file 154 base.Cmd("stop", con).AssertOK() 155 base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail() 156 }) 157 t.Run("Destination path is a read-only and default tmpfs mount point", func(t *testing.T) { 158 vol := "somevol" 159 base.Cmd("volume", "create", vol).AssertOK() 160 defer base.Cmd("volume", "rm", vol).Run() 161 con := fmt.Sprintf("%s-with-read-only-volume", testContainer) 162 163 // /tmp is from rootfs of alpine 164 mountDir := "/tmp" 165 // Create container with read-only mounted volume mounted at /tmp 166 base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s:ro", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK() 167 defer base.Cmd("rm", "-f", con).Run() 168 base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail() 169 170 // Skip for rootless 171 if rootlessutil.IsRootless() { 172 t.Skip("Test skipped in rootless mode for testStoppedContainer") 173 } 174 175 // Stopped container test 176 // Delete previously copied file 177 base.Cmd("stop", con).AssertOK() 178 base.Cmd("cp", srcPath, con+":"+mountDir).AssertFail() 179 }) 180 }) 181 t.Run("SRC_PATH specifies a directory", func(t *testing.T) { 182 srcPath := srcDir 183 t.Run("DEST_PATH does not exist", func(t *testing.T) { 184 destPath := "/dest2-no-exist" 185 base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() 186 catPath := filepath.Join(destPath, filepath.Base(srcFile)) 187 assertCat(catPath, testContainer, false) 188 if rootlessutil.IsRootless() { 189 t.Skip("Test skipped in rootless mode for testStoppedContainer") 190 } 191 base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() 192 assertCat(catPath, testStoppedContainer, true) 193 }) 194 t.Run("DEST_PATH exists and is a file", func(t *testing.T) { 195 destPath := "/dest2-file-exists" 196 base.Cmd("exec", testContainer, "touch", destPath).AssertOK() 197 base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertFail() 198 if rootlessutil.IsRootless() { 199 t.Skip("Test skipped in rootless mode for testStoppedContainer") 200 } 201 base.Cmd("start", testStoppedContainer).AssertOK() 202 base.Cmd("exec", testStoppedContainer, "touch", destPath).AssertOK() 203 base.Cmd("stop", testStoppedContainer).AssertOK() 204 base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertFail() 205 }) 206 t.Run("DEST_PATH exists and is a directory", func(t *testing.T) { 207 t.Run("SRC_PATH does not end with `/.`", func(t *testing.T) { 208 destPath := "/dest2-dir-exists" 209 base.Cmd("exec", testContainer, "mkdir", "-p", destPath).AssertOK() 210 base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() 211 catPath := filepath.Join(destPath, strings.TrimPrefix(srcFile, filepath.Dir(srcDir)+"/")) 212 assertCat(catPath, testContainer, false) 213 if rootlessutil.IsRootless() { 214 t.Skip("Test skipped in rootless mode for testStoppedContainer") 215 } 216 base.Cmd("start", testStoppedContainer).AssertOK() 217 base.Cmd("exec", testStoppedContainer, "mkdir", "-p", destPath).AssertOK() 218 base.Cmd("stop", testStoppedContainer).AssertOK() 219 base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() 220 assertCat(catPath, testStoppedContainer, true) 221 }) 222 t.Run("SRC_PATH does end with `/.`", func(t *testing.T) { 223 srcPath += "/." 224 destPath := "/dest2-dir2-exists" 225 base.Cmd("exec", testContainer, "mkdir", "-p", destPath).AssertOK() 226 base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertOK() 227 catPath := filepath.Join(destPath, filepath.Base(srcFile)) 228 t.Logf("catPath=%q", catPath) 229 assertCat(catPath, testContainer, false) 230 if rootlessutil.IsRootless() { 231 t.Skip("Test skipped in rootless mode for testStoppedContainer") 232 } 233 base.Cmd("start", testStoppedContainer).AssertOK() 234 base.Cmd("exec", testStoppedContainer, "mkdir", "-p", destPath).AssertOK() 235 base.Cmd("stop", testStoppedContainer).AssertOK() 236 base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertOK() 237 assertCat(catPath, testStoppedContainer, true) 238 }) 239 }) 240 }) 241 } 242 243 func TestCopyFromContainer(t *testing.T) { 244 t.Parallel() 245 base := testutil.NewBase(t) 246 testContainer := testutil.Identifier(t) 247 testStoppedContainer := "stopped-container-" + testutil.Identifier(t) 248 base.Cmd("run", "-d", "--name", testContainer, testutil.CommonImage, "sleep", "1h").AssertOK() 249 defer base.Cmd("rm", "-f", testContainer).Run() 250 251 base.Cmd("run", "-d", "--name", testStoppedContainer, testutil.CommonImage, "sleep", "1h").AssertOK() 252 defer base.Cmd("rm", "-f", testStoppedContainer).Run() 253 254 euid := os.Geteuid() 255 srcUID := 42 256 srcDir := "/test-dir" 257 srcFile := filepath.Join(srcDir, "test-file") 258 srcFileContent := []byte("test-file-content") 259 mkSrcScript := fmt.Sprintf("mkdir -p %q && echo -n %q >%q && chown %d %q", srcDir, srcFileContent, srcFile, srcUID, srcFile) 260 base.Cmd("exec", testContainer, "sh", "-euc", mkSrcScript).AssertOK() 261 base.Cmd("exec", testStoppedContainer, "sh", "-euc", mkSrcScript).AssertOK() 262 // Stop container for testing copying out of stopped container 263 base.Cmd("stop", testStoppedContainer) 264 265 assertCat := func(catPath string) { 266 t.Logf("catPath=%q", catPath) 267 got, err := os.ReadFile(catPath) 268 assert.NilError(t, err) 269 assert.DeepEqual(t, srcFileContent, got) 270 st, err := os.Stat(catPath) 271 assert.NilError(t, err) 272 stSys := st.Sys().(*syscall.Stat_t) 273 // stSys.Uid matches euid, not srcUID 274 assert.DeepEqual(t, uint32(euid), stSys.Uid) 275 } 276 277 td := t.TempDir() 278 // For the test matrix, see https://docs.docker.com/engine/reference/commandline/cp/ 279 t.Run("SRC_PATH specifies a file", func(t *testing.T) { 280 srcPath := srcFile 281 t.Run("DEST_PATH does not exist", func(t *testing.T) { 282 destPath := filepath.Join(td, "dest-no-exist-no-slash") 283 base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK() 284 catPath := destPath 285 assertCat(catPath) 286 if rootlessutil.IsRootless() { 287 t.Skip("Test skipped in rootless mode for testStoppedContainer") 288 } 289 base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK() 290 assertCat(catPath) 291 }) 292 t.Run("DEST_PATH does not exist and ends with /", func(t *testing.T) { 293 destPath := td + "/dest-no-exist-with-slash/" // Avoid filepath.Join, to forcibly append "/" 294 base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertFail() 295 if rootlessutil.IsRootless() { 296 t.Skip("Test skipped in rootless mode for testStoppedContainer") 297 } 298 base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertFail() 299 }) 300 t.Run("DEST_PATH exists and is a file", func(t *testing.T) { 301 destPath := filepath.Join(td, "dest-file-exists") 302 err := os.WriteFile(destPath, []byte(""), 0o644) 303 assert.NilError(t, err) 304 base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK() 305 catPath := destPath 306 assertCat(catPath) 307 if rootlessutil.IsRootless() { 308 t.Skip("Test skipped in rootless mode for testStoppedContainer") 309 } 310 base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK() 311 assertCat(catPath) 312 }) 313 t.Run("DEST_PATH exists and is a directory", func(t *testing.T) { 314 destPath := filepath.Join(td, "dest-dir-exists") 315 err := os.Mkdir(destPath, 0o755) 316 assert.NilError(t, err) 317 base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK() 318 catPath := filepath.Join(destPath, filepath.Base(srcFile)) 319 assertCat(catPath) 320 if rootlessutil.IsRootless() { 321 t.Skip("Test skipped in rootless mode for testStoppedContainer") 322 } 323 base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK() 324 assertCat(catPath) 325 }) 326 t.Run("SRC_PATH is in a volume", func(t *testing.T) { 327 // Setup 328 // Create a volume 329 vol := "somevol" 330 base.Cmd("volume", "create", vol).AssertOK() 331 defer base.Cmd("volume", "rm", "-f", vol).Run() 332 333 // Create container for test 334 con := fmt.Sprintf("%s-with-volume", testContainer) 335 336 mountDir := "/some_dir" 337 base.Cmd("run", "-d", "--name", con, "-v", fmt.Sprintf("%s:%s", vol, mountDir), testutil.CommonImage, "sleep", "1h").AssertOK() 338 defer base.Cmd("rm", "-f", con).Run() 339 340 // Create a file to mounted volume 341 mountedVolFile := filepath.Join(mountDir, "test-file") 342 mkSrcScript = fmt.Sprintf("echo -n %q >%q && chown %d %q", srcFileContent, mountedVolFile, srcUID, mountedVolFile) 343 base.Cmd("exec", con, "sh", "-euc", mkSrcScript).AssertOK() 344 345 // Create destination directory on host for copy 346 destPath := filepath.Join(td, "dest-dir") 347 err := os.Mkdir(destPath, 0o700) 348 assert.NilError(t, err) 349 350 catPath := filepath.Join(destPath, filepath.Base(mountedVolFile)) 351 352 // Running container test 353 base.Cmd("cp", con+":"+mountedVolFile, destPath).AssertOK() 354 assertCat(catPath) 355 356 // Skip for rootless 357 if rootlessutil.IsRootless() { 358 t.Skip("Test skipped in rootless mode for testStoppedContainer") 359 } 360 // Stopped container test 361 base.Cmd("stop", con).AssertOK() 362 base.Cmd("cp", con+":"+mountedVolFile, destPath).AssertOK() 363 assertCat(catPath) 364 }) 365 }) 366 t.Run("SRC_PATH specifies a directory", func(t *testing.T) { 367 srcPath := srcDir 368 t.Run("DEST_PATH does not exist", func(t *testing.T) { 369 destPath := filepath.Join(td, "dest2-no-exist") 370 base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK() 371 catPath := filepath.Join(destPath, filepath.Base(srcFile)) 372 assertCat(catPath) 373 if rootlessutil.IsRootless() { 374 t.Skip("Test skipped in rootless mode for testStoppedContainer") 375 } 376 base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK() 377 assertCat(catPath) 378 }) 379 t.Run("DEST_PATH exists and is a file", func(t *testing.T) { 380 destPath := filepath.Join(td, "dest2-file-exists") 381 err := os.WriteFile(destPath, []byte(""), 0o644) 382 assert.NilError(t, err) 383 base.Cmd("cp", srcPath, testContainer+":"+destPath).AssertFail() 384 if rootlessutil.IsRootless() { 385 t.Skip("Test skipped in rootless mode for testStoppedContainer") 386 } 387 base.Cmd("cp", srcPath, testStoppedContainer+":"+destPath).AssertFail() 388 }) 389 t.Run("DEST_PATH exists and is a directory", func(t *testing.T) { 390 t.Run("SRC_PATH does not end with `/.`", func(t *testing.T) { 391 destPath := filepath.Join(td, "dest2-dir-exists") 392 err := os.Mkdir(destPath, 0o755) 393 assert.NilError(t, err) 394 base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK() 395 catPath := filepath.Join(destPath, strings.TrimPrefix(srcFile, filepath.Dir(srcDir)+"/")) 396 assertCat(catPath) 397 if rootlessutil.IsRootless() { 398 t.Skip("Test skipped in rootless mode for testStoppedContainer") 399 } 400 base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK() 401 assertCat(catPath) 402 }) 403 t.Run("SRC_PATH does end with `/.`", func(t *testing.T) { 404 srcPath += "/." 405 destPath := filepath.Join(td, "dest2-dir2-exists") 406 err := os.Mkdir(destPath, 0o755) 407 assert.NilError(t, err) 408 base.Cmd("cp", testContainer+":"+srcPath, destPath).AssertOK() 409 catPath := filepath.Join(destPath, filepath.Base(srcFile)) 410 assertCat(catPath) 411 if rootlessutil.IsRootless() { 412 t.Skip("Test skipped in rootless mode for testStoppedContainer") 413 } 414 base.Cmd("cp", testStoppedContainer+":"+srcPath, destPath).AssertOK() 415 assertCat(catPath) 416 }) 417 }) 418 }) 419 }