| package opts |
| |
| import ( |
| "errors" |
| "fmt" |
| "strings" |
| |
| "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. |
| // |
| // It is similar to [strconv.ParseBool], but only accepts 1, true, 0, false. |
| // Any other value returns an error. |
| func parseBoolValue(key string, val string, hasValue bool) (bool, error) { |
| if !hasValue { |
| return true, nil |
| } |
| switch val { |
| case "1", "true": |
| return true, nil |
| case "0", "false": |
| return false, nil |
| default: |
| return false, fmt.Errorf(`invalid value for '%s': invalid boolean value (%q): must be one of "true", "1", "false", or "0" (default "true")`, key, val) |
| } |
| } |
| |
| func ensureVolumeOptions(m *mount.Mount) *mount.VolumeOptions { |
| if m.VolumeOptions == nil { |
| m.VolumeOptions = &mount.VolumeOptions{} |
| } |
| return m.VolumeOptions |
| } |
| |
| func ensureVolumeDriver(m *mount.Mount) *mount.Driver { |
| ensureVolumeOptions(m) |
| if m.VolumeOptions.DriverConfig == nil { |
| m.VolumeOptions.DriverConfig = &mount.Driver{} |
| } |
| return m.VolumeOptions.DriverConfig |
| } |
| |
| func ensureImageOptions(m *mount.Mount) *mount.ImageOptions { |
| if m.ImageOptions == nil { |
| m.ImageOptions = &mount.ImageOptions{} |
| } |
| return m.ImageOptions |
| } |
| |
| func ensureBindOptions(m *mount.Mount) *mount.BindOptions { |
| if m.BindOptions == nil { |
| m.BindOptions = &mount.BindOptions{} |
| } |
| return m.BindOptions |
| } |
| |
| func ensureTmpfsOptions(m *mount.Mount) *mount.TmpfsOptions { |
| if m.TmpfsOptions == nil { |
| m.TmpfsOptions = &mount.TmpfsOptions{} |
| } |
| return m.TmpfsOptions |
| } |
| |
| func setValueOnMap(target map[string]string, keyValue string) map[string]string { |
| k, v, _ := strings.Cut(keyValue, "=") |
| if k == "" { |
| return target |
| } |
| if target == nil { |
| target = map[string]string{} |
| } |
| target[k] = v |
| return target |
| } |