moby/volume/validate.go
Brian Goff fc7b904dce Add new HostConfig field, Mounts.
`Mounts` allows users to specify in a much safer way the volumes they
want to use in the container.
This replaces `Binds` and `Volumes`, which both still exist, but
`Mounts` and `Binds`/`Volumes` are exclussive.
The CLI will continue to use `Binds` and `Volumes` due to concerns with
parsing the volume specs on the client side and cross-platform support
(for now).

The new API follows exactly the services mount API.

Example usage of `Mounts`:

```
$ curl -XPOST localhost:2375/containers/create -d '{
  "Image": "alpine:latest",
  "HostConfig": {
    "Mounts": [{
      "Type": "Volume",
      "Target": "/foo"
      },{
      "Type": "bind",
      "Source": "/var/run/docker.sock",
      "Target": "/var/run/docker.sock",
      },{
      "Type": "volume",
      "Name": "important_data",
      "Target": "/var/data",
      "ReadOnly": true,
      "VolumeOptions": {
	"DriverConfig": {
	  Name: "awesomeStorage",
	  Options: {"size": "10m"},
	  Labels: {"some":"label"}
	}
      }]
    }
}'
```

There are currently 2 types of mounts:

  - **bind**: Paths on the host that get mounted into the
    container. Paths must exist prior to creating the container.
  - **volume**: Volumes that persist after the
    container is removed.

Not all fields are available in each type, and validation is done to
ensure these fields aren't mixed up between types.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2016-09-13 09:55:35 -04:00

118 lines
2.9 KiB
Go

package volume
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/docker/docker/api/types/mount"
)
var errBindNotExist = errors.New("bind source path does not exist")
type validateOpts struct {
skipBindSourceCheck bool
skipAbsolutePathCheck bool
}
func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error {
opts := validateOpts{}
for _, o := range options {
o(&opts)
}
if len(mnt.Target) == 0 {
return &errMountConfig{mnt, errMissingField("Target")}
}
if err := validateNotRoot(mnt.Target); err != nil {
return &errMountConfig{mnt, err}
}
if !opts.skipAbsolutePathCheck {
if err := validateAbsolute(mnt.Target); err != nil {
return &errMountConfig{mnt, err}
}
}
switch mnt.Type {
case mount.TypeBind:
if len(mnt.Source) == 0 {
return &errMountConfig{mnt, errMissingField("Source")}
}
// Don't error out just because the propagation mode is not supported on the platform
if opts := mnt.BindOptions; opts != nil {
if len(opts.Propagation) > 0 && len(propagationModes) > 0 {
if _, ok := propagationModes[opts.Propagation]; !ok {
return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
}
}
}
if mnt.VolumeOptions != nil {
return &errMountConfig{mnt, errExtraField("VolumeOptions")}
}
if err := validateAbsolute(mnt.Source); err != nil {
return &errMountConfig{mnt, err}
}
// Do not allow binding to non-existent path
if !opts.skipBindSourceCheck {
fi, err := os.Stat(mnt.Source)
if err != nil {
if !os.IsNotExist(err) {
return &errMountConfig{mnt, err}
}
return &errMountConfig{mnt, errBindNotExist}
}
if err := validateStat(fi); err != nil {
return &errMountConfig{mnt, err}
}
}
case mount.TypeVolume:
if mnt.BindOptions != nil {
return &errMountConfig{mnt, errExtraField("BindOptions")}
}
if len(mnt.Source) == 0 && mnt.ReadOnly {
return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
}
if len(mnt.Source) != 0 {
if valid, err := IsVolumeNameValid(mnt.Source); !valid {
if err == nil {
err = errors.New("invalid volume name")
}
return &errMountConfig{mnt, err}
}
}
default:
return &errMountConfig{mnt, errors.New("mount type unknown")}
}
return nil
}
type errMountConfig struct {
mount *mount.Mount
err error
}
func (e *errMountConfig) Error() string {
return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error())
}
func errExtraField(name string) error {
return fmt.Errorf("field %s must not be specified", name)
}
func errMissingField(name string) error {
return fmt.Errorf("field %s must not be empty", name)
}
func validateAbsolute(p string) error {
p = convertSlash(p)
if filepath.IsAbs(p) {
return nil
}
return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
}