From 9f52b2fa63d7905f4fe2f21273f38dd5c1ad2359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Wed, 24 Jan 2024 14:15:49 +0100 Subject: [PATCH] mount: Add `volume-subpath` option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Gronowski --- e2e/container/run_test.go | 53 ++++++++++++++++++++++++++++ internal/test/environment/testenv.go | 13 +++++++ opts/mount.go | 2 ++ 3 files changed, 68 insertions(+) diff --git a/e2e/container/run_test.go b/e2e/container/run_test.go index 3f110156ed70..0ebc1ad426f0 100644 --- a/e2e/container/run_test.go +++ b/e2e/container/run_test.go @@ -2,6 +2,7 @@ package container import ( "fmt" + "strings" "testing" "github.com/docker/cli/e2e/internal/fixtures" @@ -149,3 +150,55 @@ func TestRunWithCgroupNamespace(t *testing.T) { "/bin/grep", "-q", "':memory:/$'", "/proc/1/cgroup") result.Assert(t, icmd.Success) } + +func TestMountSubvolume(t *testing.T) { + environment.SkipIfAPIOlder(t, "1.45") + volName := "test-volume-" + t.Name() + icmd.RunCommand("docker", "volume", "create", volName).Assert(t, icmd.Success) + + t.Cleanup(func() { + icmd.RunCommand("docker", "volume", "remove", "-f", volName).Assert(t, icmd.Success) + }) + + defaultMountOpts := []string{ + "type=volume", + "src=" + volName, + "dst=/volume", + } + + // Populate the volume with test data. + icmd.RunCommand("docker", "run", "--rm", "--mount", strings.Join(defaultMountOpts, ","), fixtures.AlpineImage, "sh", "-c", + "echo foo > /volume/bar.txt && "+ + "mkdir /volume/etc && echo root > /volume/etc/passwd && "+ + "mkdir /volume/subdir && echo world > /volume/subdir/hello.txt;", + ).Assert(t, icmd.Success) + + runMount := func(cmd string, mountOpts ...string) *icmd.Result { + mountArg := strings.Join(append(defaultMountOpts, mountOpts...), ",") + return icmd.RunCommand("docker", "run", "--rm", "--mount", mountArg, fixtures.AlpineImage, cmd, "/volume") + } + + for _, tc := range []struct { + name string + cmd string + subpath string + + expectedOut string + expectedErr string + expectedCode int + }{ + {name: "absolute", cmd: "cat", subpath: "/etc/passwd", expectedErr: "subpath must be a relative path within the volume", expectedCode: 125}, + {name: "subpath not exists", cmd: "ls", subpath: "some-path/that/doesnt-exist", expectedErr: "cannot access path ", expectedCode: 127}, + {name: "subdirectory mount", cmd: "ls", subpath: "subdir", expectedOut: "hello.txt"}, + {name: "file mount", cmd: "cat", subpath: "bar.txt", expectedOut: "foo"}, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + runMount(tc.cmd, "volume-subpath="+tc.subpath).Assert(t, icmd.Expected{ + Err: tc.expectedErr, + ExitCode: tc.expectedCode, + Out: tc.expectedOut, + }) + }) + } +} diff --git a/internal/test/environment/testenv.go b/internal/test/environment/testenv.go index 8b035fca1b8a..912c6162a4a2 100644 --- a/internal/test/environment/testenv.go +++ b/internal/test/environment/testenv.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/client" "github.com/pkg/errors" "gotest.tools/v3/icmd" @@ -108,3 +109,15 @@ func SkipIfNotPlatform(t *testing.T, platform string) { daemonPlatform := strings.TrimSpace(result.Stdout()) skip.If(t, daemonPlatform != platform, "running against a non %s daemon", platform) } + +func SkipIfAPIOlder(t *testing.T, minimumVersion string) { + t.Helper() + // Use Client.APIVersion instead of Server.APIVersion. + // The latter is the maximum version that the server supports + // while the Client.APIVersion contains the negotiated version. + result := icmd.RunCmd(icmd.Command("docker", "version", "--format", "{{.Client.APIVersion}}")) + result.Assert(t, icmd.Expected{Err: icmd.None}) + negotiatedVersion := strings.TrimSpace(result.Stdout()) + + skip.If(t, versions.LessThan(negotiatedVersion, minimumVersion), "API too old (%s < %s)", negotiatedVersion, minimumVersion) +} diff --git a/opts/mount.go b/opts/mount.go index 3ab9b36d64cc..430b858e8fae 100644 --- a/opts/mount.go +++ b/opts/mount.go @@ -131,6 +131,8 @@ func (m *MountOpt) Set(value string) error { return fmt.Errorf("invalid value for %s: %s (must be \"enabled\", \"disabled\", \"writable\", or \"readonly\")", key, val) } + case "volume-subpath": + volumeOptions().Subpath = val case "volume-nocopy": volumeOptions().NoCopy, err = strconv.ParseBool(val) if err != nil {