From beeacde4b234f66aafb3ba7562f2be32bccaace4 Mon Sep 17 00:00:00 2001 From: Shane St Savage Date: Wed, 10 Dec 2025 19:27:58 -0800 Subject: [PATCH] Remove restriction on anonymous read-only volumes Restriction on anonymouse read-only volumes is currently preventing the use of pre-populated volumes that should be accessed in a read-only manner in a container (e.g. an NFS volume containing data to be processed or served). According to @neersighted the restriction may have originally been put in place with the assumption that pre-populated volumes would be exposed as a named volume by the volume driver. In practice, NFS volumes are mounted using the docker `local` driver by supplying driver opts. Example that fails when `readonly` is specified but works without: ``` docker run --rm -it \ --mount 'readonly,type=volume,dst=/data/dest,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/export/some-share,"volume-opt=o=nfsvers=4,addr=some.server"' \ debian ``` Fixes #45297 Signed-off-by: Shane St Savage --- daemon/volume/mounts/linux_parser.go | 3 - daemon/volume/mounts/linux_parser_test.go | 84 +++++++++++++++++++++ daemon/volume/mounts/windows_parser.go | 4 - daemon/volume/mounts/windows_parser_test.go | 75 ++++++++++++++++++ 4 files changed, 159 insertions(+), 7 deletions(-) diff --git a/daemon/volume/mounts/linux_parser.go b/daemon/volume/mounts/linux_parser.go index c80b456863..0a5208a7c4 100644 --- a/daemon/volume/mounts/linux_parser.go +++ b/daemon/volume/mounts/linux_parser.go @@ -112,9 +112,6 @@ func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSour return &errMountConfig{mnt, errInvalidSubpath} } } - if mnt.ReadOnly && anonymousVolume { - return &errMountConfig{mnt, errors.New("must not set ReadOnly mode when using anonymous volumes")} - } case mount.TypeTmpfs: if mnt.BindOptions != nil { return &errMountConfig{mnt, errExtraField("BindOptions")} diff --git a/daemon/volume/mounts/linux_parser_test.go b/daemon/volume/mounts/linux_parser_test.go index 4b0c9aaa15..2f9edce565 100644 --- a/daemon/volume/mounts/linux_parser_test.go +++ b/daemon/volume/mounts/linux_parser_test.go @@ -2,6 +2,7 @@ package mounts import ( "errors" + "path/filepath" "strings" "testing" @@ -264,6 +265,89 @@ func TestLinuxParseMountRawSplit(t *testing.T) { } } +func TestLinuxValidateMounts(t *testing.T) { + tmpDir := t.TempDir() + + tests := []struct { + doc string + mount mount.Mount + expected *MountPoint + expErr string + }{ + { + doc: "read-write", + mount: mount.Mount{ + Source: tmpDir, + Target: "/tmp1", + Type: mount.TypeBind, + }, + expected: &MountPoint{ + Source: tmpDir, + Destination: "/tmp1", + RW: true, + Type: mount.TypeBind, + Propagation: "rprivate", + Spec: mount.Mount{ + Source: tmpDir, + Target: "/tmp1", + Type: mount.TypeBind, + }, + }, + }, + { + doc: "read-only", + mount: mount.Mount{ + Target: "/data/anonymous-read-only-volume", + ReadOnly: true, + Type: mount.TypeVolume, + }, + expected: &MountPoint{ + Destination: "/data/anonymous-read-only-volume", + Type: mount.TypeVolume, + CopyData: true, + Spec: mount.Mount{ + Target: "/data/anonymous-read-only-volume", + ReadOnly: true, + Type: mount.TypeVolume, + }, + }, + }, + { + doc: "invalid source path", + mount: mount.Mount{ + Source: filepath.Join(tmpDir, "no-such-path"), + Target: "/data/anonymous-read-only-volume", + Type: mount.TypeBind, + }, + expErr: `invalid mount config for type "bind": bind source path does not exist: ` + filepath.Join(tmpDir, "no-such-path"), + }, + { + doc: "invalid mount type", + mount: mount.Mount{ + Target: "/data/invalid-type", + Type: "invalid", + }, + expErr: `invalid mount config for type "invalid": mount type unknown`, + }, + } + + parser := NewLinuxParser() + + for _, tc := range tests { + t.Run(tc.doc, func(t *testing.T) { + m, err := parser.ParseMountSpec(tc.mount) + if tc.expErr != "" { + assert.Check(t, is.Nil(m)) + assert.Check(t, is.Error(err, tc.expErr)) + return + } + + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(*m, *tc.expected, cmpopts.IgnoreUnexported(MountPoint{}))) + }) + } +} + // TestLinuxParseMountSpecBindWithFileinfoError makes sure that the parser returns // the error produced by the fileinfo provider. // diff --git a/daemon/volume/mounts/windows_parser.go b/daemon/volume/mounts/windows_parser.go index 4ddc88452a..4c78a3846c 100644 --- a/daemon/volume/mounts/windows_parser.go +++ b/daemon/volume/mounts/windows_parser.go @@ -270,10 +270,6 @@ func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, additionalValid } } - if anonymousVolume && mnt.ReadOnly { - return &errMountConfig{mnt, errors.New("must not set ReadOnly mode when using anonymous volumes")} - } - if mnt.Source != "" { if err := p.ValidateVolumeName(mnt.Source); err != nil { return &errMountConfig{mnt, err} diff --git a/daemon/volume/mounts/windows_parser_test.go b/daemon/volume/mounts/windows_parser_test.go index 43e4a83991..95c50cf5cc 100644 --- a/daemon/volume/mounts/windows_parser_test.go +++ b/daemon/volume/mounts/windows_parser_test.go @@ -294,6 +294,81 @@ func TestWindowsParseMountRawSplit(t *testing.T) { } } +func TestWindowsValidateMounts(t *testing.T) { + tests := []struct { + mount mount.Mount + expected *MountPoint + expErr string + }{ + { + mount: mount.Mount{ + Source: `c:\`, + Target: `d:\mount`, + Type: mount.TypeBind, + }, + expected: &MountPoint{ + Source: `c:\`, + Destination: `d:\mount`, + RW: true, + Type: mount.TypeBind, + Spec: mount.Mount{ + Source: `c:\`, + Target: `d:\mount`, + Type: mount.TypeBind, + }, + }, + }, + { + mount: mount.Mount{ + Target: `c:/data/anonymous-read-only-volume`, + ReadOnly: true, + Type: mount.TypeVolume, + }, + expected: &MountPoint{ + Destination: `c:\data\anonymous-read-only-volume`, + Type: mount.TypeVolume, + Spec: mount.Mount{ + Target: `c:/data/anonymous-read-only-volume`, + ReadOnly: true, + Type: mount.TypeVolume, + }, + }, + }, + { + mount: mount.Mount{ + Source: "c:/bad/path", + Target: "d:/data/anonymous-read-only-volume", + Type: mount.TypeBind, + }, + expErr: `invalid mount config for type "bind": bind source path does not exist: c:/bad/path`, + }, + { + mount: mount.Mount{ + Target: "d:/data/invalid-type", + Type: "invalid", + }, + expErr: `invalid mount config for type "invalid": mount type unknown`, + }, + } + + parser := NewWindowsParser() + if p, ok := parser.(*windowsParser); ok { + p.fi = mockFiProvider{} + } + + for _, tc := range tests { + m, err := parser.ParseMountSpec(tc.mount) + if tc.expErr != "" { + assert.Check(t, is.Nil(m)) + assert.Check(t, is.Error(err, tc.expErr)) + continue + } + + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(*m, *tc.expected, cmpopts.IgnoreUnexported(MountPoint{}))) + } +} + // TestWindowsParseMountSpecBindWithFileinfoError makes sure that the parser returns // the error produced by the fileinfo provider. //