diff --git a/hack/ci/windows.ps1 b/hack/ci/windows.ps1 index ebb4030890..fe166e9bb7 100644 --- a/hack/ci/windows.ps1 +++ b/hack/ci/windows.ps1 @@ -1012,9 +1012,6 @@ Catch [Exception] { Throw $_ } Finally { - # Preserve the LastExitCode of the tests - $tmpLastExitCode = $LastExitCode - $ErrorActionPreference="SilentlyContinue" $global:ProgressPreference=$origProgressPreference Write-Host -ForegroundColor Green "INFO: Tidying up at end of run" @@ -1053,6 +1050,4 @@ Finally { $Dur=New-TimeSpan -Start $StartTime -End $(Get-Date) Write-Host -ForegroundColor $FinallyColour "`nINFO: executeCI.ps1 exiting at $(date). Duration $dur`n" - - exit $tmpLastExitCode } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 720a909bd0..76ce061e7d 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -30,7 +30,6 @@ import ( "github.com/docker/docker/testutil/request" "github.com/docker/docker/volume" "github.com/docker/go-connections/nat" - "github.com/moby/sys/mount" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/poll" @@ -2056,7 +2055,7 @@ func (s *DockerSuite) TestContainersAPICreateMountsCreate(c *testing.T) { assert.NilError(c, err) defer os.RemoveAll(tmpDir3) - assert.Assert(c, mount.Mount(tmpDir3, tmpDir3, "none", "bind,shared") == nil) + assert.Assert(c, mountWrapper(tmpDir3, tmpDir3, "none", "bind,shared") == nil) cases = append(cases, []testCase{ { diff --git a/integration-cli/docker_api_containers_unix_test.go b/integration-cli/docker_api_containers_unix_test.go new file mode 100644 index 0000000000..05c667fa69 --- /dev/null +++ b/integration-cli/docker_api_containers_unix_test.go @@ -0,0 +1,9 @@ +// +build !windows + +package main + +import "github.com/moby/sys/mount" + +func mountWrapper(device, target, mType, options string) error { + return mount.Mount(device, target, mType, options) +} diff --git a/integration-cli/docker_api_containers_windows_test.go b/integration-cli/docker_api_containers_windows_test.go index 0423d18426..1038506bee 100644 --- a/integration-cli/docker_api_containers_windows_test.go +++ b/integration-cli/docker_api_containers_windows_test.go @@ -15,6 +15,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/mount" + "github.com/pkg/errors" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" ) @@ -75,3 +76,8 @@ func (s *DockerSuite) TestContainersAPICreateMountsBindNamedPipe(c *testing.T) { assert.NilError(c, err) assert.Check(c, is.Equal(text, strings.TrimSpace(string(b)))) } + +func mountWrapper(device, target, mType, options string) error { + // This should never be called. + return errors.Errorf("there is no implementation of Mount on this platform") +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 7c22cfc43a..e0c1bbeb18 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -34,7 +34,6 @@ import ( "github.com/docker/go-connections/nat" "github.com/docker/libnetwork/resolvconf" "github.com/docker/libnetwork/types" - "github.com/moby/sys/mount" "github.com/moby/sys/mountinfo" "gotest.tools/v3/assert" "gotest.tools/v3/icmd" @@ -3764,86 +3763,6 @@ func (s *DockerSuite) TestRunWithOomScoreAdjInvalidRange(c *testing.T) { } } -func (s *DockerSuite) TestRunVolumesMountedAsShared(c *testing.T) { - // Volume propagation is linux only. Also it creates directories for - // bind mounting, so needs to be same host. - testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon, NotUserNamespace) - - // Prepare a source directory to bind mount - tmpDir, err := ioutil.TempDir("", "volume-source") - if err != nil { - c.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - if err := os.Mkdir(path.Join(tmpDir, "mnt1"), 0755); err != nil { - c.Fatal(err) - } - - // Convert this directory into a shared mount point so that we do - // not rely on propagation properties of parent mount. - icmd.RunCommand("mount", "--bind", tmpDir, tmpDir).Assert(c, icmd.Success) - icmd.RunCommand("mount", "--make-private", "--make-shared", tmpDir).Assert(c, icmd.Success) - - dockerCmd(c, "run", "--privileged", "-v", fmt.Sprintf("%s:/volume-dest:shared", tmpDir), "busybox", "mount", "--bind", "/volume-dest/mnt1", "/volume-dest/mnt1") - - // Make sure a bind mount under a shared volume propagated to host. - if mounted, _ := mountinfo.Mounted(path.Join(tmpDir, "mnt1")); !mounted { - c.Fatalf("Bind mount under shared volume did not propagate to host") - } - - mount.Unmount(path.Join(tmpDir, "mnt1")) -} - -func (s *DockerSuite) TestRunVolumesMountedAsSlave(c *testing.T) { - // Volume propagation is linux only. Also it creates directories for - // bind mounting, so needs to be same host. - testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon, NotUserNamespace) - - // Prepare a source directory to bind mount - tmpDir, err := ioutil.TempDir("", "volume-source") - if err != nil { - c.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - if err := os.Mkdir(path.Join(tmpDir, "mnt1"), 0755); err != nil { - c.Fatal(err) - } - - // Prepare a source directory with file in it. We will bind mount this - // directory and see if file shows up. - tmpDir2, err := ioutil.TempDir("", "volume-source2") - if err != nil { - c.Fatal(err) - } - defer os.RemoveAll(tmpDir2) - - if err := ioutil.WriteFile(path.Join(tmpDir2, "slave-testfile"), []byte("Test"), 0644); err != nil { - c.Fatal(err) - } - - // Convert this directory into a shared mount point so that we do - // not rely on propagation properties of parent mount. - icmd.RunCommand("mount", "--bind", tmpDir, tmpDir).Assert(c, icmd.Success) - icmd.RunCommand("mount", "--make-private", "--make-shared", tmpDir).Assert(c, icmd.Success) - - dockerCmd(c, "run", "-i", "-d", "--name", "parent", "-v", fmt.Sprintf("%s:/volume-dest:slave", tmpDir), "busybox", "top") - - // Bind mount tmpDir2/ onto tmpDir/mnt1. If mount propagates inside - // container then contents of tmpDir2/slave-testfile should become - // visible at "/volume-dest/mnt1/slave-testfile" - icmd.RunCommand("mount", "--bind", tmpDir2, path.Join(tmpDir, "mnt1")).Assert(c, icmd.Success) - - out, _ := dockerCmd(c, "exec", "parent", "cat", "/volume-dest/mnt1/slave-testfile") - - mount.Unmount(path.Join(tmpDir, "mnt1")) - - if out != "Test" { - c.Fatalf("Bind mount under slave volume did not propagate to container") - } -} - func (s *DockerSuite) TestRunNamedVolumesMountedAsShared(c *testing.T) { testRequires(c, DaemonIsLinux, NotUserNamespace) out, exitCode, _ := dockerCmdWithError("run", "-v", "foo:/test:shared", "busybox", "touch", "/test/somefile") diff --git a/integration/container/mounts_linux_test.go b/integration/container/mounts_linux_test.go index 1ebdb32fbd..d65bc99f0d 100644 --- a/integration/container/mounts_linux_test.go +++ b/integration/container/mounts_linux_test.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/integration/internal/container" "github.com/docker/docker/pkg/system" "github.com/moby/sys/mount" + "github.com/moby/sys/mountinfo" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" "gotest.tools/v3/fs" @@ -266,3 +267,127 @@ func TestContainerBindMountNonRecursive(t *testing.T) { poll.WaitOn(t, container.IsSuccessful(ctx, client, c), poll.WithDelay(100*time.Millisecond)) } } + +func TestContainerVolumesMountedAsShared(t *testing.T) { + // Volume propagation is linux only. Also it creates directories for + // bind mounting, so needs to be same host. + skip.If(t, testEnv.IsRemoteDaemon) + skip.If(t, testEnv.IsUserNamespace) + skip.If(t, testEnv.IsRootless, "cannot be tested because RootlessKit executes the daemon in private mount namespace (https://github.com/rootless-containers/rootlesskit/issues/97)") + + defer setupTest(t)() + + // Prepare a source directory to bind mount + tmpDir1 := fs.NewDir(t, "volume-source", fs.WithMode(0755), + fs.WithDir("mnt1", fs.WithMode(0755))) + defer tmpDir1.Remove() + tmpDir1Mnt := filepath.Join(tmpDir1.Path(), "mnt1") + + // Convert this directory into a shared mount point so that we do + // not rely on propagation properties of parent mount. + if err := mount.Mount(tmpDir1.Path(), tmpDir1.Path(), "none", "bind,private"); err != nil { + t.Fatal(err) + } + defer func() { + if err := mount.Unmount(tmpDir1.Path()); err != nil { + t.Fatal(err) + } + }() + if err := mount.Mount("none", tmpDir1.Path(), "none", "shared"); err != nil { + t.Fatal(err) + } + + sharedMount := mounttypes.Mount{ + Type: mounttypes.TypeBind, + Source: tmpDir1.Path(), + Target: "/volume-dest", + BindOptions: &mounttypes.BindOptions{ + Propagation: mounttypes.PropagationShared, + }, + } + + bindMountCmd := []string{"mount", "--bind", "/volume-dest/mnt1", "/volume-dest/mnt1"} + + ctx := context.Background() + client := testEnv.APIClient() + containerID := container.Run(ctx, t, client, container.WithPrivileged(true), container.WithMount(sharedMount), container.WithCmd(bindMountCmd...)) + poll.WaitOn(t, container.IsSuccessful(ctx, client, containerID), poll.WithDelay(100*time.Millisecond)) + + // Make sure a bind mount under a shared volume propagated to host. + if mounted, _ := mountinfo.Mounted(tmpDir1Mnt); !mounted { + t.Fatalf("Bind mount under shared volume did not propagate to host") + } + + mount.Unmount(tmpDir1Mnt) +} + +func TestContainerVolumesMountedAsSlave(t *testing.T) { + // Volume propagation is linux only. Also it creates directories for + // bind mounting, so needs to be same host. + skip.If(t, testEnv.IsRemoteDaemon) + skip.If(t, testEnv.IsUserNamespace) + skip.If(t, testEnv.IsRootless, "cannot be tested because RootlessKit executes the daemon in private mount namespace (https://github.com/rootless-containers/rootlesskit/issues/97)") + + // Prepare a source directory to bind mount + tmpDir1 := fs.NewDir(t, "volume-source", fs.WithMode(0755), + fs.WithDir("mnt1", fs.WithMode(0755))) + defer tmpDir1.Remove() + tmpDir1Mnt := filepath.Join(tmpDir1.Path(), "mnt1") + + // Prepare a source directory with file in it. We will bind mount this + // directory and see if file shows up. + tmpDir2 := fs.NewDir(t, "volume-source2", fs.WithMode(0755), + fs.WithFile("slave-testfile", "Test", fs.WithMode(0644))) + defer tmpDir2.Remove() + + // Convert this directory into a shared mount point so that we do + // not rely on propagation properties of parent mount. + if err := mount.Mount(tmpDir1.Path(), tmpDir1.Path(), "none", "bind,private"); err != nil { + t.Fatal(err) + } + defer func() { + if err := mount.Unmount(tmpDir1.Path()); err != nil { + t.Fatal(err) + } + }() + if err := mount.Mount("none", tmpDir1.Path(), "none", "shared"); err != nil { + t.Fatal(err) + } + + slaveMount := mounttypes.Mount{ + Type: mounttypes.TypeBind, + Source: tmpDir1.Path(), + Target: "/volume-dest", + BindOptions: &mounttypes.BindOptions{ + Propagation: mounttypes.PropagationSlave, + }, + } + + topCmd := []string{"top"} + + ctx := context.Background() + client := testEnv.APIClient() + containerID := container.Run(ctx, t, client, container.WithTty(true), container.WithMount(slaveMount), container.WithCmd(topCmd...)) + + // Bind mount tmpDir2/ onto tmpDir1/mnt1. If mount propagates inside + // container then contents of tmpDir2/slave-testfile should become + // visible at "/volume-dest/mnt1/slave-testfile" + if err := mount.Mount(tmpDir2.Path(), tmpDir1Mnt, "none", "bind"); err != nil { + t.Fatal(err) + } + defer func() { + if err := mount.Unmount(tmpDir1Mnt); err != nil { + t.Fatal(err) + } + }() + + mountCmd := []string{"cat", "/volume-dest/mnt1/slave-testfile"} + + if result, err := container.Exec(ctx, client, containerID, mountCmd); err == nil { + if result.Stdout() != "Test" { + t.Fatalf("Bind mount under slave volume did not propagate to container") + } + } else { + t.Fatal(err) + } +} diff --git a/pkg/mount/deprecated.go b/pkg/mount/deprecated.go index 70406e11b0..16c8e5952d 100644 --- a/pkg/mount/deprecated.go +++ b/pkg/mount/deprecated.go @@ -4,52 +4,9 @@ package mount // import "github.com/docker/docker/pkg/mount" // Use github.com/moby/sys/mount and github.com/moby/sys/mountinfo instead. import ( - sysmount "github.com/moby/sys/mount" "github.com/moby/sys/mountinfo" ) -// Deprecated: use github.com/moby/sys/mount instead. -//nolint:golint -var ( - Mount = sysmount.Mount - ForceMount = sysmount.Mount // a deprecated synonym - Unmount = sysmount.Unmount - RecursiveUnmount = sysmount.RecursiveUnmount -) - -// Deprecated: use github.com/moby/sys/mount instead. -//nolint:golint -const ( - RDONLY = sysmount.RDONLY - NOSUID = sysmount.NOSUID - NOEXEC = sysmount.NOEXEC - SYNCHRONOUS = sysmount.SYNCHRONOUS - NOATIME = sysmount.NOATIME - BIND = sysmount.BIND - DIRSYNC = sysmount.DIRSYNC - MANDLOCK = sysmount.MANDLOCK - NODEV = sysmount.NODEV - NODIRATIME = sysmount.NODIRATIME - UNBINDABLE = sysmount.UNBINDABLE - RUNBINDABLE = sysmount.RUNBINDABLE - PRIVATE = sysmount.PRIVATE - RPRIVATE = sysmount.RPRIVATE - SHARED = sysmount.SHARED - RSHARED = sysmount.RSHARED - SLAVE = sysmount.SLAVE - RSLAVE = sysmount.RSLAVE - RBIND = sysmount.RBIND - RELATIME = sysmount.RELATIME - REMOUNT = sysmount.REMOUNT - STRICTATIME = sysmount.STRICTATIME -) - -// Deprecated: use github.com/moby/sys/mount instead. -//nolint:golint -var ( - MergeTmpfsOptions = sysmount.MergeTmpfsOptions -) - //nolint:golint type ( // FilterFunc is a type. diff --git a/pkg/mount/deprecated_unix.go b/pkg/mount/deprecated_unix.go new file mode 100644 index 0000000000..1787f20424 --- /dev/null +++ b/pkg/mount/deprecated_unix.go @@ -0,0 +1,52 @@ +// +build !darwin,!windows + +package mount // import "github.com/docker/docker/pkg/mount" + +// Deprecated: this package is not maintained and will be removed. +// Use github.com/moby/sys/mount and github.com/moby/sys/mountinfo instead. + +import ( + sysmount "github.com/moby/sys/mount" +) + +// Deprecated: use github.com/moby/sys/mount instead. +//nolint:golint +var ( + Mount = sysmount.Mount + ForceMount = sysmount.Mount // a deprecated synonym + Unmount = sysmount.Unmount + RecursiveUnmount = sysmount.RecursiveUnmount +) + +// Deprecated: use github.com/moby/sys/mount instead. +//nolint:golint +const ( + RDONLY = sysmount.RDONLY + NOSUID = sysmount.NOSUID + NOEXEC = sysmount.NOEXEC + SYNCHRONOUS = sysmount.SYNCHRONOUS + NOATIME = sysmount.NOATIME + BIND = sysmount.BIND + DIRSYNC = sysmount.DIRSYNC + MANDLOCK = sysmount.MANDLOCK + NODEV = sysmount.NODEV + NODIRATIME = sysmount.NODIRATIME + UNBINDABLE = sysmount.UNBINDABLE + RUNBINDABLE = sysmount.RUNBINDABLE + PRIVATE = sysmount.PRIVATE + RPRIVATE = sysmount.RPRIVATE + SHARED = sysmount.SHARED + RSHARED = sysmount.RSHARED + SLAVE = sysmount.SLAVE + RSLAVE = sysmount.RSLAVE + RBIND = sysmount.RBIND + RELATIME = sysmount.RELATIME + REMOUNT = sysmount.REMOUNT + STRICTATIME = sysmount.STRICTATIME +) + +// Deprecated: use github.com/moby/sys/mount instead. +//nolint:golint +var ( + MergeTmpfsOptions = sysmount.MergeTmpfsOptions +) diff --git a/pkg/system/rm_test.go b/pkg/system/rm_test.go index 4ba2c8f47d..fcb0ef932b 100644 --- a/pkg/system/rm_test.go +++ b/pkg/system/rm_test.go @@ -2,14 +2,7 @@ package system // import "github.com/docker/docker/pkg/system" import ( "io/ioutil" - "os" - "path/filepath" - "runtime" "testing" - "time" - - "github.com/moby/sys/mount" - "gotest.tools/v3/skip" ) func TestEnsureRemoveAllNotExist(t *testing.T) { @@ -39,46 +32,3 @@ func TestEnsureRemoveAllWithFile(t *testing.T) { t.Fatal(err) } } - -func TestEnsureRemoveAllWithMount(t *testing.T) { - skip.If(t, runtime.GOOS == "windows", "mount not supported on Windows") - skip.If(t, os.Getuid() != 0, "skipping test that requires root") - - dir1, err := ioutil.TempDir("", "test-ensure-removeall-with-dir1") - if err != nil { - t.Fatal(err) - } - dir2, err := ioutil.TempDir("", "test-ensure-removeall-with-dir2") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir2) - - bindDir := filepath.Join(dir1, "bind") - if err := os.MkdirAll(bindDir, 0755); err != nil { - t.Fatal(err) - } - - if err := mount.Mount(dir2, bindDir, "none", "bind"); err != nil { - t.Fatal(err) - } - - done := make(chan struct{}, 1) - go func() { - err = EnsureRemoveAll(dir1) - close(done) - }() - - select { - case <-done: - if err != nil { - t.Fatal(err) - } - case <-time.After(5 * time.Second): - t.Fatal("timeout waiting for EnsureRemoveAll to finish") - } - - if _, err := os.Stat(dir1); !os.IsNotExist(err) { - t.Fatalf("expected %q to not exist", dir1) - } -} diff --git a/pkg/system/rm_unix_test.go b/pkg/system/rm_unix_test.go new file mode 100644 index 0000000000..934a98fd0d --- /dev/null +++ b/pkg/system/rm_unix_test.go @@ -0,0 +1,58 @@ +// +build !windows + +package system // import "github.com/docker/docker/pkg/system" + +import ( + "io/ioutil" + "os" + "path/filepath" + "runtime" + "testing" + "time" + + "github.com/moby/sys/mount" + "gotest.tools/v3/skip" +) + +func TestEnsureRemoveAllWithMount(t *testing.T) { + skip.If(t, runtime.GOOS == "windows", "mount not supported on Windows") + skip.If(t, os.Getuid() != 0, "skipping test that requires root") + + dir1, err := ioutil.TempDir("", "test-ensure-removeall-with-dir1") + if err != nil { + t.Fatal(err) + } + dir2, err := ioutil.TempDir("", "test-ensure-removeall-with-dir2") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir2) + + bindDir := filepath.Join(dir1, "bind") + if err := os.MkdirAll(bindDir, 0755); err != nil { + t.Fatal(err) + } + + if err := mount.Mount(dir2, bindDir, "none", "bind"); err != nil { + t.Fatal(err) + } + + done := make(chan struct{}, 1) + go func() { + err = EnsureRemoveAll(dir1) + close(done) + }() + + select { + case <-done: + if err != nil { + t.Fatal(err) + } + case <-time.After(5 * time.Second): + t.Fatal("timeout waiting for EnsureRemoveAll to finish") + } + + if _, err := os.Stat(dir1); !os.IsNotExist(err) { + t.Fatalf("expected %q to not exist", dir1) + } +}