github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_run_mount_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 "testing" 25 26 "github.com/containerd/containerd/mount" 27 "github.com/containerd/nerdctl/pkg/rootlessutil" 28 "github.com/containerd/nerdctl/pkg/testutil" 29 mobymount "github.com/moby/sys/mount" 30 "gotest.tools/v3/assert" 31 ) 32 33 func TestRunVolume(t *testing.T) { 34 t.Parallel() 35 base := testutil.NewBase(t) 36 tID := testutil.Identifier(t) 37 rwDir, err := os.MkdirTemp(t.TempDir(), "rw") 38 if err != nil { 39 t.Fatal(err) 40 } 41 roDir, err := os.MkdirTemp(t.TempDir(), "ro") 42 if err != nil { 43 t.Fatal(err) 44 } 45 rwVolName := tID + "-rw" 46 roVolName := tID + "-ro" 47 for _, v := range []string{rwVolName, roVolName} { 48 defer base.Cmd("volume", "rm", "-f", v).Run() 49 base.Cmd("volume", "create", v).AssertOK() 50 } 51 52 containerName := tID 53 defer base.Cmd("rm", "-f", containerName).AssertOK() 54 base.Cmd("run", 55 "-d", 56 "--name", containerName, 57 "-v", fmt.Sprintf("%s:/mnt1", rwDir), 58 "-v", fmt.Sprintf("%s:/mnt2:ro", roDir), 59 "-v", fmt.Sprintf("%s:/mnt3", rwVolName), 60 "-v", fmt.Sprintf("%s:/mnt4:ro", roVolName), 61 testutil.AlpineImage, 62 "top", 63 ).AssertOK() 64 base.Cmd("exec", containerName, "sh", "-exc", "echo -n str1 > /mnt1/file1").AssertOK() 65 base.Cmd("exec", containerName, "sh", "-exc", "echo -n str2 > /mnt2/file2").AssertFail() 66 base.Cmd("exec", containerName, "sh", "-exc", "echo -n str3 > /mnt3/file3").AssertOK() 67 base.Cmd("exec", containerName, "sh", "-exc", "echo -n str4 > /mnt4/file4").AssertFail() 68 base.Cmd("rm", "-f", containerName).AssertOK() 69 base.Cmd("run", 70 "--rm", 71 "-v", fmt.Sprintf("%s:/mnt1", rwDir), 72 "-v", fmt.Sprintf("%s:/mnt3", rwVolName), 73 testutil.AlpineImage, 74 "cat", "/mnt1/file1", "/mnt3/file3", 75 ).AssertOutExactly("str1str3") 76 base.Cmd("run", 77 "--rm", 78 "-v", fmt.Sprintf("%s:/mnt3/mnt1", rwDir), 79 "-v", fmt.Sprintf("%s:/mnt3", rwVolName), 80 testutil.AlpineImage, 81 "cat", "/mnt3/mnt1/file1", "/mnt3/file3", 82 ).AssertOutExactly("str1str3") 83 } 84 85 func TestRunAnonymousVolume(t *testing.T) { 86 t.Parallel() 87 base := testutil.NewBase(t) 88 base.Cmd("run", "--rm", "-v", "/foo", testutil.AlpineImage).AssertOK() 89 base.Cmd("run", "--rm", "-v", "TestVolume2:/foo", testutil.AlpineImage).AssertOK() 90 base.Cmd("run", "--rm", "-v", "TestVolume", testutil.AlpineImage).AssertOK() 91 92 // Destination must be an absolute path not named volume 93 base.Cmd("run", "--rm", "-v", "TestVolume2:TestVolumes", testutil.AlpineImage).AssertFail() 94 } 95 96 func TestRunVolumeRelativePath(t *testing.T) { 97 t.Parallel() 98 base := testutil.NewBase(t) 99 base.Cmd("run", "--rm", "-v", "./foo:/mnt/foo", testutil.AlpineImage).AssertOK() 100 base.Cmd("run", "--rm", "-v", "./foo", testutil.AlpineImage).AssertOK() 101 102 // Destination must be an absolute path not a relative path 103 base.Cmd("run", "--rm", "-v", "./foo:./foo", testutil.AlpineImage).AssertFail() 104 } 105 106 func TestRunAnonymousVolumeWithTypeMountFlag(t *testing.T) { 107 t.Parallel() 108 base := testutil.NewBase(t) 109 base.Cmd("run", "--rm", "--mount", "type=volume,dst=/foo", testutil.AlpineImage, 110 "mountpoint", "-q", "/foo").AssertOK() 111 } 112 113 func TestRunAnonymousVolumeWithBuild(t *testing.T) { 114 t.Parallel() 115 testutil.RequiresBuild(t) 116 base := testutil.NewBase(t) 117 defer base.Cmd("builder", "prune").Run() 118 imageName := testutil.Identifier(t) 119 defer base.Cmd("rmi", imageName).Run() 120 121 dockerfile := fmt.Sprintf(`FROM %s 122 VOLUME /foo 123 `, testutil.AlpineImage) 124 125 buildCtx, err := createBuildContext(dockerfile) 126 assert.NilError(t, err) 127 defer os.RemoveAll(buildCtx) 128 129 base.Cmd("build", "-t", imageName, buildCtx).AssertOK() 130 base.Cmd("run", "--rm", "-v", "/foo", testutil.AlpineImage, 131 "mountpoint", "-q", "/foo").AssertOK() 132 } 133 134 func TestRunCopyingUpInitialContentsOnVolume(t *testing.T) { 135 t.Parallel() 136 testutil.RequiresBuild(t) 137 base := testutil.NewBase(t) 138 defer base.Cmd("builder", "prune").Run() 139 imageName := testutil.Identifier(t) 140 defer base.Cmd("rmi", imageName).Run() 141 volName := testutil.Identifier(t) + "-vol" 142 defer base.Cmd("volume", "rm", volName).Run() 143 144 dockerfile := fmt.Sprintf(`FROM %s 145 RUN mkdir -p /mnt && echo hi > /mnt/initial_file 146 CMD ["cat", "/mnt/initial_file"] 147 `, testutil.AlpineImage) 148 149 buildCtx, err := createBuildContext(dockerfile) 150 assert.NilError(t, err) 151 defer os.RemoveAll(buildCtx) 152 153 base.Cmd("build", "-t", imageName, buildCtx).AssertOK() 154 155 //AnonymousVolume 156 base.Cmd("run", "--rm", imageName).AssertOutExactly("hi\n") 157 base.Cmd("run", "-v", "/mnt", "--rm", imageName).AssertOutExactly("hi\n") 158 159 //NamedVolume should be automatically created 160 base.Cmd("run", "-v", volName+":/mnt", "--rm", imageName).AssertOutExactly("hi\n") 161 } 162 163 func TestRunCopyingUpInitialContentsOnDockerfileVolume(t *testing.T) { 164 t.Parallel() 165 testutil.RequiresBuild(t) 166 base := testutil.NewBase(t) 167 defer base.Cmd("builder", "prune").Run() 168 imageName := testutil.Identifier(t) 169 defer base.Cmd("rmi", imageName).Run() 170 volName := testutil.Identifier(t) + "-vol" 171 defer base.Cmd("volume", "rm", volName).Run() 172 173 dockerfile := fmt.Sprintf(`FROM %s 174 RUN mkdir -p /mnt && echo hi > /mnt/initial_file 175 VOLUME /mnt 176 CMD ["cat", "/mnt/initial_file"] 177 `, testutil.AlpineImage) 178 179 buildCtx, err := createBuildContext(dockerfile) 180 assert.NilError(t, err) 181 defer os.RemoveAll(buildCtx) 182 183 base.Cmd("build", "-t", imageName, buildCtx).AssertOK() 184 //AnonymousVolume 185 base.Cmd("run", "--rm", imageName).AssertOutExactly("hi\n") 186 base.Cmd("run", "-v", "/mnt", "--rm", imageName).AssertOutExactly("hi\n") 187 188 //NamedVolume 189 base.Cmd("volume", "create", volName).AssertOK() 190 base.Cmd("run", "-v", volName+":/mnt", "--rm", imageName).AssertOutExactly("hi\n") 191 192 //mount bind 193 tmpDir, err := os.MkdirTemp(t.TempDir(), "hostDir") 194 assert.NilError(t, err) 195 196 base.Cmd("run", "-v", fmt.Sprintf("%s:/mnt", tmpDir), "--rm", imageName).AssertFail() 197 } 198 199 func TestRunCopyingUpInitialContentsOnVolumeShouldRetainSymlink(t *testing.T) { 200 t.Parallel() 201 testutil.RequiresBuild(t) 202 base := testutil.NewBase(t) 203 defer base.Cmd("builder", "prune").Run() 204 imageName := testutil.Identifier(t) 205 defer base.Cmd("rmi", imageName).Run() 206 207 dockerfile := fmt.Sprintf(`FROM %s 208 RUN ln -s ../../../../../../../../../../../../../../../../../../etc/passwd /mnt/passwd 209 VOLUME /mnt 210 CMD ["readlink", "/mnt/passwd"] 211 `, testutil.AlpineImage) 212 const expected = "../../../../../../../../../../../../../../../../../../etc/passwd\n" 213 214 buildCtx, err := createBuildContext(dockerfile) 215 assert.NilError(t, err) 216 defer os.RemoveAll(buildCtx) 217 218 base.Cmd("build", "-t", imageName, buildCtx).AssertOK() 219 220 base.Cmd("run", "--rm", imageName).AssertOutExactly(expected) 221 base.Cmd("run", "-v", "/mnt", "--rm", imageName).AssertOutExactly(expected) 222 } 223 224 func TestRunCopyingUpInitialContentsShouldNotResetTheCopiedContents(t *testing.T) { 225 t.Parallel() 226 testutil.RequiresBuild(t) 227 base := testutil.NewBase(t) 228 defer base.Cmd("builder", "prune").Run() 229 tID := testutil.Identifier(t) 230 imageName := tID + "-img" 231 volumeName := tID + "-vol" 232 containerName := tID 233 defer func() { 234 base.Cmd("rm", "-f", containerName).Run() 235 base.Cmd("volume", "rm", volumeName).Run() 236 base.Cmd("rmi", imageName).Run() 237 }() 238 239 dockerfile := fmt.Sprintf(`FROM %s 240 RUN echo -n "rev0" > /mnt/file 241 `, testutil.AlpineImage) 242 243 buildCtx, err := createBuildContext(dockerfile) 244 assert.NilError(t, err) 245 defer os.RemoveAll(buildCtx) 246 247 base.Cmd("build", "-t", imageName, buildCtx).AssertOK() 248 249 base.Cmd("volume", "create", volumeName) 250 runContainer := func() { 251 base.Cmd("run", "-d", "--name", containerName, "-v", volumeName+":/mnt", imageName, "sleep", "infinity").AssertOK() 252 } 253 runContainer() 254 base.EnsureContainerStarted(containerName) 255 base.Cmd("exec", containerName, "cat", "/mnt/file").AssertOutExactly("rev0") 256 base.Cmd("exec", containerName, "sh", "-euc", "echo -n \"rev1\" >/mnt/file").AssertOK() 257 base.Cmd("rm", "-f", containerName).AssertOK() 258 runContainer() 259 base.EnsureContainerStarted(containerName) 260 base.Cmd("exec", containerName, "cat", "/mnt/file").AssertOutExactly("rev1") 261 } 262 263 func TestRunTmpfs(t *testing.T) { 264 t.Parallel() 265 base := testutil.NewBase(t) 266 f := func(allow, deny []string) func(stdout string) error { 267 return func(stdout string) error { 268 lines := strings.Split(strings.TrimSpace(stdout), "\n") 269 if len(lines) != 1 { 270 return fmt.Errorf("expected 1 lines, got %q", stdout) 271 } 272 for _, s := range allow { 273 if !strings.Contains(stdout, s) { 274 return fmt.Errorf("expected stdout to contain %q, got %q", s, stdout) 275 } 276 } 277 for _, s := range deny { 278 if strings.Contains(stdout, s) { 279 return fmt.Errorf("expected stdout not to contain %q, got %q", s, stdout) 280 } 281 } 282 return nil 283 } 284 } 285 base.Cmd("run", "--rm", "--tmpfs", "/tmp", testutil.AlpineImage, "grep", "/tmp", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "noexec"}, nil)) 286 base.Cmd("run", "--rm", "--tmpfs", "/tmp:size=64m,exec", testutil.AlpineImage, "grep", "/tmp", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "size=65536k"}, []string{"noexec"})) 287 // for https://github.com/containerd/nerdctl/issues/594 288 base.Cmd("run", "--rm", "--tmpfs", "/dev/shm:rw,exec,size=1g", testutil.AlpineImage, "grep", "/dev/shm", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "size=1048576k"}, []string{"noexec"})) 289 } 290 291 func TestRunBindMountTmpfs(t *testing.T) { 292 t.Parallel() 293 base := testutil.NewBase(t) 294 f := func(allow []string) func(stdout string) error { 295 return func(stdout string) error { 296 lines := strings.Split(strings.TrimSpace(stdout), "\n") 297 if len(lines) != 1 { 298 return fmt.Errorf("expected 1 lines, got %q", stdout) 299 } 300 for _, s := range allow { 301 if !strings.Contains(stdout, s) { 302 return fmt.Errorf("expected stdout to contain %q, got %q", s, stdout) 303 } 304 } 305 return nil 306 } 307 } 308 base.Cmd("run", "--rm", "--mount", "type=tmpfs,target=/tmp", testutil.AlpineImage, "grep", "/tmp", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "noexec"})) 309 base.Cmd("run", "--rm", "--mount", "type=tmpfs,target=/tmp,tmpfs-size=64m", testutil.AlpineImage, "grep", "/tmp", "/proc/mounts").AssertOutWithFunc(f([]string{"rw", "nosuid", "nodev", "size=65536k"})) 310 } 311 312 func TestRunBindMountBind(t *testing.T) { 313 t.Parallel() 314 base := testutil.NewBase(t) 315 tID := testutil.Identifier(t) 316 rwDir, err := os.MkdirTemp(t.TempDir(), "rw") 317 if err != nil { 318 t.Fatal(err) 319 } 320 roDir, err := os.MkdirTemp(t.TempDir(), "ro") 321 if err != nil { 322 t.Fatal(err) 323 } 324 325 containerName := tID 326 defer base.Cmd("rm", "-f", containerName).AssertOK() 327 base.Cmd("run", 328 "-d", 329 "--name", containerName, 330 "--mount", fmt.Sprintf("type=bind,src=%s,target=/mnt1", rwDir), 331 "--mount", fmt.Sprintf("type=bind,src=%s,target=/mnt2,ro", roDir), 332 testutil.AlpineImage, 333 "top", 334 ).AssertOK() 335 base.Cmd("exec", containerName, "sh", "-exc", "echo -n str1 > /mnt1/file1").AssertOK() 336 base.Cmd("exec", containerName, "sh", "-exc", "echo -n str2 > /mnt2/file2").AssertFail() 337 338 base.Cmd("run", 339 "--rm", 340 "--mount", fmt.Sprintf("type=bind,src=%s,target=/mnt1", rwDir), 341 testutil.AlpineImage, 342 "cat", "/mnt1/file1", 343 ).AssertOutExactly("str1") 344 345 // check `bind-propagation` 346 f := func(allow string) func(stdout string) error { 347 return func(stdout string) error { 348 lines := strings.Split(strings.TrimSpace(stdout), "\n") 349 if len(lines) != 1 { 350 return fmt.Errorf("expected 1 lines, got %q", stdout) 351 } 352 fields := strings.Split(lines[0], " ") 353 if len(fields) < 4 { 354 return fmt.Errorf("invalid /proc/mounts format %q", stdout) 355 } 356 357 options := strings.Split(fields[3], ",") 358 359 found := false 360 for _, s := range options { 361 if allow == s { 362 found = true 363 break 364 } 365 } 366 if !found { 367 return fmt.Errorf("expected stdout to contain %q, got %+v", allow, options) 368 } 369 return nil 370 } 371 } 372 base.Cmd("exec", containerName, "grep", "/mnt1", "/proc/mounts").AssertOutWithFunc(f("rw")) 373 base.Cmd("exec", containerName, "grep", "/mnt2", "/proc/mounts").AssertOutWithFunc(f("ro")) 374 } 375 376 func TestRunMountBindMode(t *testing.T) { 377 if rootlessutil.IsRootless() { 378 t.Skip("must be superuser to use mount") 379 } 380 t.Parallel() 381 base := testutil.NewBase(t) 382 383 tmpDir1, err := os.MkdirTemp(t.TempDir(), "rw") 384 if err != nil { 385 t.Fatal(err) 386 } 387 defer os.RemoveAll(tmpDir1) 388 tmpDir1Mnt := filepath.Join(tmpDir1, "mnt") 389 if err := os.MkdirAll(tmpDir1Mnt, 0700); err != nil { 390 t.Fatal(err) 391 } 392 393 tmpDir2, err := os.MkdirTemp(t.TempDir(), "ro") 394 if err != nil { 395 t.Fatal(err) 396 } 397 defer os.RemoveAll(tmpDir2) 398 399 if err := mobymount.Mount(tmpDir2, tmpDir1Mnt, "none", "bind,ro"); err != nil { 400 t.Fatal(err) 401 } 402 defer func() { 403 if err := mobymount.Unmount(tmpDir1Mnt); err != nil { 404 t.Fatal(err) 405 } 406 }() 407 408 base.Cmd("run", 409 "--rm", 410 "--mount", fmt.Sprintf("type=bind,bind-nonrecursive,src=%s,target=/mnt1", tmpDir1), 411 testutil.AlpineImage, 412 "sh", "-euxc", "apk add findmnt -q && findmnt -nR /mnt1", 413 ).AssertOutWithFunc(func(stdout string) error { 414 lines := strings.Split(strings.TrimSpace(stdout), "\n") 415 if len(lines) != 1 { 416 return fmt.Errorf("expected 1 line, got %q", stdout) 417 } 418 if !strings.HasPrefix(lines[0], "/mnt1") { 419 return fmt.Errorf("expected mount /mnt1, got %q", lines[0]) 420 } 421 return nil 422 }) 423 424 base.Cmd("run", 425 "--rm", 426 "--mount", fmt.Sprintf("type=bind,bind-nonrecursive=false,src=%s,target=/mnt1", tmpDir1), 427 testutil.AlpineImage, 428 "sh", "-euxc", "apk add findmnt -q && findmnt -nR /mnt1", 429 ).AssertOutWithFunc(func(stdout string) error { 430 lines := strings.Split(strings.TrimSpace(stdout), "\n") 431 if len(lines) != 2 { 432 return fmt.Errorf("expected 2 line, got %q", stdout) 433 } 434 if !strings.HasPrefix(lines[0], "/mnt1") { 435 return fmt.Errorf("expected mount /mnt1, got %q", lines[0]) 436 } 437 return nil 438 }) 439 } 440 441 func TestRunVolumeBindMode(t *testing.T) { 442 if rootlessutil.IsRootless() { 443 t.Skip("must be superuser to use mount") 444 } 445 testutil.DockerIncompatible(t) 446 t.Parallel() 447 base := testutil.NewBase(t) 448 449 tmpDir1, err := os.MkdirTemp(t.TempDir(), "rw") 450 if err != nil { 451 t.Fatal(err) 452 } 453 defer os.RemoveAll(tmpDir1) 454 tmpDir1Mnt := filepath.Join(tmpDir1, "mnt") 455 if err := os.MkdirAll(tmpDir1Mnt, 0700); err != nil { 456 t.Fatal(err) 457 } 458 459 tmpDir2, err := os.MkdirTemp(t.TempDir(), "ro") 460 if err != nil { 461 t.Fatal(err) 462 } 463 defer os.RemoveAll(tmpDir2) 464 465 if err := mobymount.Mount(tmpDir2, tmpDir1Mnt, "none", "bind,ro"); err != nil { 466 t.Fatal(err) 467 } 468 defer func() { 469 if err := mobymount.Unmount(tmpDir1Mnt); err != nil { 470 t.Fatal(err) 471 } 472 }() 473 474 base.Cmd("run", 475 "--rm", 476 "-v", fmt.Sprintf("%s:/mnt1:bind", tmpDir1), 477 testutil.AlpineImage, 478 "sh", "-euxc", "apk add findmnt -q && findmnt -nR /mnt1", 479 ).AssertOutWithFunc(func(stdout string) error { 480 lines := strings.Split(strings.TrimSpace(stdout), "\n") 481 if len(lines) != 1 { 482 return fmt.Errorf("expected 1 line, got %q", stdout) 483 } 484 if !strings.HasPrefix(lines[0], "/mnt1") { 485 return fmt.Errorf("expected mount /mnt1, got %q", lines[0]) 486 } 487 return nil 488 }) 489 490 base.Cmd("run", 491 "--rm", 492 "-v", fmt.Sprintf("%s:/mnt1:rbind", tmpDir1), 493 testutil.AlpineImage, 494 "sh", "-euxc", "apk add findmnt -q && findmnt -nR /mnt1", 495 ).AssertOutWithFunc(func(stdout string) error { 496 lines := strings.Split(strings.TrimSpace(stdout), "\n") 497 if len(lines) != 2 { 498 return fmt.Errorf("expected 2 line, got %q", stdout) 499 } 500 if !strings.HasPrefix(lines[0], "/mnt1") { 501 return fmt.Errorf("expected mount /mnt1, got %q", lines[0]) 502 } 503 return nil 504 }) 505 } 506 507 func TestRunBindMountPropagation(t *testing.T) { 508 tID := testutil.Identifier(t) 509 510 if !isRootfsShareableMount() { 511 t.Skipf("rootfs doesn't support shared mount, skip test %s", tID) 512 } 513 514 t.Parallel() 515 base := testutil.NewBase(t) 516 517 testCases := []struct { 518 propagation string 519 assertFunc func(containerName, containerNameReplica string) 520 }{ 521 { 522 propagation: "rshared", 523 assertFunc: func(containerName, containerNameReplica string) { 524 // replica can get sub-mounts from original 525 base.Cmd("exec", containerNameReplica, "cat", "/mnt1/replica/foo.txt").AssertOutExactly("toreplica") 526 527 // and sub-mounts from replica will be propagated to the original too 528 base.Cmd("exec", containerName, "cat", "/mnt1/bar/bar.txt").AssertOutExactly("fromreplica") 529 }, 530 }, 531 { 532 propagation: "rslave", 533 assertFunc: func(containerName, containerNameReplica string) { 534 // replica can get sub-mounts from original 535 base.Cmd("exec", containerNameReplica, "cat", "/mnt1/replica/foo.txt").AssertOutExactly("toreplica") 536 537 // but sub-mounts from replica will not be propagated to the original 538 base.Cmd("exec", containerName, "cat", "/mnt1/bar/bar.txt").AssertFail() 539 }, 540 }, 541 { 542 propagation: "rprivate", 543 assertFunc: func(containerName, containerNameReplica string) { 544 // replica can't get sub-mounts from original 545 base.Cmd("exec", containerNameReplica, "cat", "/mnt1/replica/foo.txt").AssertFail() 546 // and sub-mounts from replica will not be propagated to the original too 547 base.Cmd("exec", containerName, "cat", "/mnt1/bar/bar.txt").AssertFail() 548 }, 549 }, 550 { 551 propagation: "", 552 assertFunc: func(containerName, containerNameReplica string) { 553 // replica can't get sub-mounts from original 554 base.Cmd("exec", containerNameReplica, "cat", "/mnt1/replica/foo.txt").AssertFail() 555 // and sub-mounts from replica will not be propagated to the original too 556 base.Cmd("exec", containerName, "cat", "/mnt1/bar/bar.txt").AssertFail() 557 }, 558 }, 559 } 560 561 for _, tc := range testCases { 562 propagationName := tc.propagation 563 if propagationName == "" { 564 propagationName = "default" 565 } 566 567 t.Logf("Running test propagation case %s", propagationName) 568 569 rwDir, err := os.MkdirTemp(t.TempDir(), "rw") 570 if err != nil { 571 t.Fatal(err) 572 } 573 574 containerName := tID + "-" + propagationName 575 containerNameReplica := containerName + "-replica" 576 577 mountOption := fmt.Sprintf("type=bind,src=%s,target=/mnt1,bind-propagation=%s", rwDir, tc.propagation) 578 if tc.propagation == "" { 579 mountOption = fmt.Sprintf("type=bind,src=%s,target=/mnt1", rwDir) 580 } 581 582 containers := []struct { 583 name string 584 mountOption string 585 }{ 586 { 587 name: containerName, 588 mountOption: fmt.Sprintf("type=bind,src=%s,target=/mnt1,bind-propagation=rshared", rwDir), 589 }, 590 { 591 name: containerNameReplica, 592 mountOption: mountOption, 593 }, 594 } 595 for _, c := range containers { 596 base.Cmd("run", "-d", 597 "--privileged", 598 "--name", c.name, 599 "--mount", c.mountOption, 600 testutil.AlpineImage, 601 "top").AssertOK() 602 defer base.Cmd("rm", "-f", c.name).Run() 603 } 604 605 // mount in the first container 606 base.Cmd("exec", containerName, "sh", "-exc", "mkdir /app && mkdir /mnt1/replica && mount --bind /app /mnt1/replica && echo -n toreplica > /app/foo.txt").AssertOK() 607 base.Cmd("exec", containerName, "cat", "/mnt1/replica/foo.txt").AssertOutExactly("toreplica") 608 609 // mount in the second container 610 base.Cmd("exec", containerNameReplica, "sh", "-exc", "mkdir /bar && mkdir /mnt1/bar").AssertOK() 611 base.Cmd("exec", containerNameReplica, "sh", "-exc", "mount --bind /bar /mnt1/bar").AssertOK() 612 613 base.Cmd("exec", containerNameReplica, "sh", "-exc", "echo -n fromreplica > /bar/bar.txt").AssertOK() 614 base.Cmd("exec", containerNameReplica, "cat", "/mnt1/bar/bar.txt").AssertOutExactly("fromreplica") 615 616 // call case specific assert function 617 tc.assertFunc(containerName, containerNameReplica) 618 619 // umount mount point in the first privileged container 620 base.Cmd("exec", containerNameReplica, "sh", "-exc", "umount /mnt1/bar").AssertOK() 621 base.Cmd("exec", containerName, "sh", "-exc", "umount /mnt1/replica").AssertOK() 622 } 623 } 624 625 // isRootfsShareableMount will check if /tmp or / support shareable mount 626 func isRootfsShareableMount() bool { 627 existFunc := func(mi mount.Info) bool { 628 for _, opt := range strings.Split(mi.Optional, " ") { 629 if strings.HasPrefix(opt, "shared:") { 630 return true 631 } 632 } 633 return false 634 } 635 636 mi, err := mount.Lookup("/tmp") 637 if err == nil { 638 return existFunc(mi) 639 } 640 641 mi, err = mount.Lookup("/") 642 if err == nil { 643 return existFunc(mi) 644 } 645 646 return false 647 } 648 649 func TestRunVolumesFrom(t *testing.T) { 650 t.Parallel() 651 base := testutil.NewBase(t) 652 tID := testutil.Identifier(t) 653 rwDir, err := os.MkdirTemp(t.TempDir(), "rw") 654 if err != nil { 655 t.Fatal(err) 656 } 657 roDir, err := os.MkdirTemp(t.TempDir(), "ro") 658 if err != nil { 659 t.Fatal(err) 660 } 661 rwVolName := tID + "-rw" 662 roVolName := tID + "-ro" 663 for _, v := range []string{rwVolName, roVolName} { 664 defer base.Cmd("volume", "rm", "-f", v).Run() 665 base.Cmd("volume", "create", v).AssertOK() 666 } 667 668 fromContainerName := tID + "-from" 669 toContainerName := tID + "-to" 670 defer base.Cmd("rm", "-f", fromContainerName).AssertOK() 671 defer base.Cmd("rm", "-f", toContainerName).AssertOK() 672 base.Cmd("run", 673 "-d", 674 "--name", fromContainerName, 675 "-v", fmt.Sprintf("%s:/mnt1", rwDir), 676 "-v", fmt.Sprintf("%s:/mnt2:ro", roDir), 677 "-v", fmt.Sprintf("%s:/mnt3", rwVolName), 678 "-v", fmt.Sprintf("%s:/mnt4:ro", roVolName), 679 testutil.AlpineImage, 680 "top", 681 ).AssertOK() 682 base.Cmd("run", 683 "-d", 684 "--name", toContainerName, 685 "--volumes-from", fromContainerName, 686 testutil.AlpineImage, 687 "top", 688 ).AssertOK() 689 base.Cmd("exec", toContainerName, "sh", "-exc", "echo -n str1 > /mnt1/file1").AssertOK() 690 base.Cmd("exec", toContainerName, "sh", "-exc", "echo -n str2 > /mnt2/file2").AssertFail() 691 base.Cmd("exec", toContainerName, "sh", "-exc", "echo -n str3 > /mnt3/file3").AssertOK() 692 base.Cmd("exec", toContainerName, "sh", "-exc", "echo -n str4 > /mnt4/file4").AssertFail() 693 base.Cmd("rm", "-f", toContainerName).AssertOK() 694 base.Cmd("run", 695 "--rm", 696 "--volumes-from", fromContainerName, 697 testutil.AlpineImage, 698 "cat", "/mnt1/file1", "/mnt3/file3", 699 ).AssertOutExactly("str1str3") 700 } 701 702 func TestBindMountWhenHostFolderDoesNotExist(t *testing.T) { 703 t.Parallel() 704 base := testutil.NewBase(t) 705 containerName := testutil.Identifier(t) + "-host-dir-not-found" 706 hostDir, err := os.MkdirTemp(t.TempDir(), "rw") 707 if err != nil { 708 t.Fatal(err) 709 } 710 defer os.RemoveAll(hostDir) 711 hp := filepath.Join(hostDir, "does-not-exist") 712 base.Cmd("run", "--name", containerName, "-d", "-v", fmt.Sprintf("%s:/tmp", 713 hp), testutil.AlpineImage).AssertOK() 714 base.Cmd("rm", "-f", containerName).AssertOK() 715 716 // Host directory should get created 717 _, err = os.Stat(hp) 718 assert.NilError(t, err) 719 720 // Test for --mount 721 os.RemoveAll(hp) 722 base.Cmd("run", "--name", containerName, "-d", "--mount", fmt.Sprintf("type=bind, source=%s, target=/tmp", 723 hp), testutil.AlpineImage).AssertFail() 724 _, err = os.Stat(hp) 725 assert.ErrorIs(t, err, os.ErrNotExist) 726 }