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 <shane@axds.co>
This commit is contained in:
Shane St Savage
2025-12-10 19:27:58 -08:00
parent ebf1c0d405
commit beeacde4b2
4 changed files with 159 additions and 7 deletions

View File

@@ -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")}

View File

@@ -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.
//

View File

@@ -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}

View File

@@ -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.
//