Skip to content

Commit d781df8

Browse files
committed
opts: MountOpt: extract validation to a separate function
This splits the validation code from parsing code, potentially allowing us to either fully deferring it to the daemon, or to perform validation separately. For reference; daemon-side validation currently (docker 29.2.0) produces; docker run --rm --mount type=bind,src=/var/run,target=/foo,bind-recursive=writable alpine docker: Error response from daemon: mount options conflict: !ReadOnly && BindOptions.ReadOnlyNonRecursive docker run --rm --mount type=bind,src=/var/run,target=/foo,bind-recursive=readonly alpine docker: Error response from daemon: mount options conflict: !ReadOnly && BindOptions.ReadOnlyForceRecursive Validation for BindOptions.Propagation is currently missing on the daemon; docker run --rm --mount type=bind,src=/var/run,target=/foo,bind-recursive=readonly,readonly alpine # no error Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent fe1af92 commit d781df8

2 files changed

Lines changed: 64 additions & 31 deletions

File tree

opts/mount.go

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -177,37 +177,8 @@ func (m *MountOpt) Set(value string) error {
177177
}
178178
}
179179

180-
if mount.Type == "" {
181-
return errors.New("type is required")
182-
}
183-
184-
if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume {
185-
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type)
186-
}
187-
if mount.ImageOptions != nil && mount.Type != mounttypes.TypeImage {
188-
return fmt.Errorf("cannot mix 'image-*' options with mount type '%s'", mount.Type)
189-
}
190-
if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind {
191-
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type)
192-
}
193-
if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs {
194-
return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type)
195-
}
196-
197-
if mount.BindOptions != nil {
198-
if mount.BindOptions.ReadOnlyNonRecursive {
199-
if !mount.ReadOnly {
200-
return errors.New("option 'bind-recursive=writable' requires 'readonly' to be specified in conjunction")
201-
}
202-
}
203-
if mount.BindOptions.ReadOnlyForceRecursive {
204-
if !mount.ReadOnly {
205-
return errors.New("option 'bind-recursive=readonly' requires 'readonly' to be specified in conjunction")
206-
}
207-
if mount.BindOptions.Propagation != mounttypes.PropagationRPrivate {
208-
return errors.New("option 'bind-recursive=readonly' requires 'bind-propagation=rprivate' to be specified in conjunction")
209-
}
210-
}
180+
if err := validateMountOptions(&mount); err != nil {
181+
return err
211182
}
212183

213184
m.values = append(m.values, mount)

opts/mount_utils.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,71 @@
11
package opts
22

33
import (
4+
"errors"
45
"fmt"
6+
7+
"github.com/moby/moby/api/types/mount"
58
)
69

10+
// validateMountOptions performs client-side validation of mount options. Similar
11+
// validation happens on the daemon side, but this validation allows us to
12+
// produce user-friendly errors matching command-line options.
13+
func validateMountOptions(m *mount.Mount) error {
14+
if err := validateExclusiveOptions(m); err != nil {
15+
return err
16+
}
17+
18+
if m.BindOptions != nil {
19+
if m.BindOptions.ReadOnlyNonRecursive && !m.ReadOnly {
20+
return errors.New("option 'bind-recursive=writable' requires 'readonly' to be specified in conjunction")
21+
}
22+
if m.BindOptions.ReadOnlyForceRecursive {
23+
if !m.ReadOnly {
24+
return errors.New("option 'bind-recursive=readonly' requires 'readonly' to be specified in conjunction")
25+
}
26+
if m.BindOptions.Propagation != mount.PropagationRPrivate {
27+
// FIXME(thaJeztah): this is missing daemon-side validation
28+
//
29+
// docker run --rm --mount type=bind,src=/var/run,target=/foo,bind-recursive=readonly,readonly alpine
30+
// # no error
31+
return errors.New("option 'bind-recursive=readonly' requires 'bind-propagation=rprivate' to be specified in conjunction")
32+
}
33+
}
34+
}
35+
36+
return nil
37+
}
38+
39+
// validateExclusiveOptions checks if the given mount config only contains
40+
// options for the given mount-type.
41+
//
42+
// This is the client-side equivalent of [mounts.validateExclusiveOptions] in
43+
// the daemon, but with error-messages matching client-side flags / options.
44+
//
45+
// [mounts.validateExclusiveOptions]: https://github.com/moby/moby/blob/v2.0.0-beta.6/daemon/volume/mounts/validate.go#L31-L50
46+
func validateExclusiveOptions(m *mount.Mount) error {
47+
if m.Type == "" {
48+
return errors.New("type is required")
49+
}
50+
51+
if m.Type != mount.TypeBind && m.BindOptions != nil {
52+
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", m.Type)
53+
}
54+
if m.Type != mount.TypeVolume && m.VolumeOptions != nil {
55+
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", m.Type)
56+
}
57+
if m.Type != mount.TypeImage && m.ImageOptions != nil {
58+
return fmt.Errorf("cannot mix 'image-*' options with mount type '%s'", m.Type)
59+
}
60+
if m.Type != mount.TypeTmpfs && m.TmpfsOptions != nil {
61+
return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", m.Type)
62+
}
63+
if m.Type != mount.TypeCluster && m.ClusterOptions != nil {
64+
return fmt.Errorf("cannot mix 'cluster-*' options with mount type '%s'", m.Type)
65+
}
66+
return nil
67+
}
68+
769
// parseBoolValue returns the boolean value represented by the string. It returns
870
// true if no value is set.
971
//

0 commit comments

Comments
 (0)