mirror of
https://github.com/docker/cli.git
synced 2026-08-24 10:05:37 -05:00
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>
This commit is contained in:
+2
-31
@@ -177,37 +177,8 @@ func (m *MountOpt) Set(value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if mount.Type == "" {
|
||||
return errors.New("type is required")
|
||||
}
|
||||
|
||||
if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume {
|
||||
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
if mount.ImageOptions != nil && mount.Type != mounttypes.TypeImage {
|
||||
return fmt.Errorf("cannot mix 'image-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind {
|
||||
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs {
|
||||
return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type)
|
||||
}
|
||||
|
||||
if mount.BindOptions != nil {
|
||||
if mount.BindOptions.ReadOnlyNonRecursive {
|
||||
if !mount.ReadOnly {
|
||||
return errors.New("option 'bind-recursive=writable' requires 'readonly' to be specified in conjunction")
|
||||
}
|
||||
}
|
||||
if mount.BindOptions.ReadOnlyForceRecursive {
|
||||
if !mount.ReadOnly {
|
||||
return errors.New("option 'bind-recursive=readonly' requires 'readonly' to be specified in conjunction")
|
||||
}
|
||||
if mount.BindOptions.Propagation != mounttypes.PropagationRPrivate {
|
||||
return errors.New("option 'bind-recursive=readonly' requires 'bind-propagation=rprivate' to be specified in conjunction")
|
||||
}
|
||||
}
|
||||
if err := validateMountOptions(&mount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.values = append(m.values, mount)
|
||||
|
||||
@@ -1,9 +1,71 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/moby/moby/api/types/mount"
|
||||
)
|
||||
|
||||
// validateMountOptions performs client-side validation of mount options. Similar
|
||||
// validation happens on the daemon side, but this validation allows us to
|
||||
// produce user-friendly errors matching command-line options.
|
||||
func validateMountOptions(m *mount.Mount) error {
|
||||
if err := validateExclusiveOptions(m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m.BindOptions != nil {
|
||||
if m.BindOptions.ReadOnlyNonRecursive && !m.ReadOnly {
|
||||
return errors.New("option 'bind-recursive=writable' requires 'readonly' to be specified in conjunction")
|
||||
}
|
||||
if m.BindOptions.ReadOnlyForceRecursive {
|
||||
if !m.ReadOnly {
|
||||
return errors.New("option 'bind-recursive=readonly' requires 'readonly' to be specified in conjunction")
|
||||
}
|
||||
if m.BindOptions.Propagation != mount.PropagationRPrivate {
|
||||
// FIXME(thaJeztah): this is missing daemon-side validation
|
||||
//
|
||||
// docker run --rm --mount type=bind,src=/var/run,target=/foo,bind-recursive=readonly,readonly alpine
|
||||
// # no error
|
||||
return errors.New("option 'bind-recursive=readonly' requires 'bind-propagation=rprivate' to be specified in conjunction")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateExclusiveOptions checks if the given mount config only contains
|
||||
// options for the given mount-type.
|
||||
//
|
||||
// This is the client-side equivalent of [mounts.validateExclusiveOptions] in
|
||||
// the daemon, but with error-messages matching client-side flags / options.
|
||||
//
|
||||
// [mounts.validateExclusiveOptions]: https://github.com/moby/moby/blob/v2.0.0-beta.6/daemon/volume/mounts/validate.go#L31-L50
|
||||
func validateExclusiveOptions(m *mount.Mount) error {
|
||||
if m.Type == "" {
|
||||
return errors.New("type is required")
|
||||
}
|
||||
|
||||
if m.Type != mount.TypeBind && m.BindOptions != nil {
|
||||
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", m.Type)
|
||||
}
|
||||
if m.Type != mount.TypeVolume && m.VolumeOptions != nil {
|
||||
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", m.Type)
|
||||
}
|
||||
if m.Type != mount.TypeImage && m.ImageOptions != nil {
|
||||
return fmt.Errorf("cannot mix 'image-*' options with mount type '%s'", m.Type)
|
||||
}
|
||||
if m.Type != mount.TypeTmpfs && m.TmpfsOptions != nil {
|
||||
return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", m.Type)
|
||||
}
|
||||
if m.Type != mount.TypeCluster && m.ClusterOptions != nil {
|
||||
return fmt.Errorf("cannot mix 'cluster-*' options with mount type '%s'", m.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseBoolValue returns the boolean value represented by the string. It returns
|
||||
// true if no value is set.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user