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-04-26 18:25:35 +00:00
|
|
|
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}
|
|
|
|
}
|
|
|
|
}
|
2016-09-22 20:14:15 +00:00
|
|
|
case mount.TypeTmpfs:
|
|
|
|
if len(mnt.Source) != 0 {
|
|
|
|
return &errMountConfig{mnt, errExtraField("Source")}
|
|
|
|
}
|
2017-01-16 09:52:43 +00:00
|
|
|
if err := ValidateTmpfsMountDestination(mnt.Target); err != nil {
|
|
|
|
return &errMountConfig{mnt, err}
|
|
|
|
}
|
2016-11-08 06:46:23 +00:00
|
|
|
if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
|
2016-09-22 20:14:15 +00:00
|
|
|
return &errMountConfig{mnt, err}
|
|
|
|
}
|
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-04-26 18:25:35 +00:00
|
|
|
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)
|
|
|
|
}
|
2017-01-16 09:52:43 +00:00
|
|
|
|
|
|
|
// ValidateTmpfsMountDestination validates the destination of tmpfs mount.
|
|
|
|
// Currently, we have only two obvious rule for validation:
|
|
|
|
// - path must not be "/"
|
|
|
|
// - path must be absolute
|
|
|
|
// We should add more rules carefully (#30166)
|
|
|
|
func ValidateTmpfsMountDestination(dest string) error {
|
|
|
|
if err := validateNotRoot(dest); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return validateAbsolute(dest)
|
|
|
|
}
|