81fa9feb |
package volume
|
a7e686a7 |
import ( |
a793564b |
"fmt" |
a7e686a7 |
"os" |
fc7b904d |
"path/filepath" |
a7e686a7 |
"strings" |
322cc99c |
"syscall" |
2b6bc294 |
|
91e197d6 |
mounttypes "github.com/docker/docker/api/types/mount" |
72d8a77d |
"github.com/docker/docker/pkg/idtools" |
2b6bc294 |
"github.com/docker/docker/pkg/stringid" |
322cc99c |
"github.com/opencontainers/runc/libcontainer/label" |
2a5e85e2 |
"github.com/pkg/errors" |
a7e686a7 |
)
|
9af963ab |
// DefaultDriverName is the driver name used for the driver
// implemented in the local package. |
2f40b1b2 |
const DefaultDriverName = "local"
// Scopes define if a volume has is cluster-wide (global) or local only.
// Scopes are returned by the volume driver when it is queried for capabilities and then set on a volume
const (
LocalScope = "local"
GlobalScope = "global"
) |
81fa9feb |
|
9af963ab |
// Driver is for creating and removing volumes. |
81fa9feb |
type Driver interface {
// Name returns the name of the volume driver.
Name() string |
c2246f28 |
// Create makes a new volume with the given name. |
b3b7eb27 |
Create(name string, opts map[string]string) (Volume, error) |
81fa9feb |
// Remove deletes the volume. |
d3eca445 |
Remove(vol Volume) (err error)
// List lists all the volumes the driver has
List() ([]Volume, error) |
99a39690 |
// Get retrieves the volume with the requested name |
d3eca445 |
Get(name string) (Volume, error) |
29fea0fd |
// Scope returns the scope of the driver (e.g. `global` or `local`). |
2f40b1b2 |
// Scope determines how the driver is handled at a cluster level
Scope() string
}
// Capability defines a set of capabilities that a driver is able to handle.
type Capability struct {
// Scope is the scope of the driver, `global` or `local`
// A `global` scope indicates that the driver manages volumes across the cluster
// A `local` scope indicates that the driver only manages volumes resources local to the host
// Scope is declared by the driver
Scope string |
81fa9feb |
}
|
9af963ab |
// Volume is a place to store data. It is backed by a specific driver, and can be mounted. |
81fa9feb |
type Volume interface {
// Name returns the name of the volume
Name() string
// DriverName returns the name of the driver which owns this volume.
DriverName() string
// Path returns the absolute path to the volume.
Path() string
// Mount mounts the volume and returns the absolute path to
// where it can be consumed. |
2b6bc294 |
Mount(id string) (string, error) |
81fa9feb |
// Unmount unmounts the volume when it is no longer in use. |
2b6bc294 |
Unmount(id string) error |
36a1c56c |
// Status returns low-level status information about a volume
Status() map[string]interface{} |
81fa9feb |
} |
dfc6c04f |
|
9ce8aac5 |
// DetailedVolume wraps a Volume with user-defined labels, options, and cluster scope (e.g., `local` or `global`)
type DetailedVolume interface { |
2f40b1b2 |
Labels() map[string]string |
9ce8aac5 |
Options() map[string]string |
2f40b1b2 |
Scope() string
Volume
}
|
a7e686a7 |
// MountPoint is the intersection point between a volume and a container. It
// specifies which volume is to be used and where inside a container it should
// be mounted.
type MountPoint struct { |
9a2d0bc3 |
// Source is the source path of the mount.
// E.g. `mount --bind /foo /bar`, `/foo` is the `Source`.
Source string
// Destination is the path relative to the container root (`/`) to the mount point
// It is where the `Source` is mounted to
Destination string
// RW is set to true when the mountpoint should be mounted as read-write
RW bool
// Name is the name reference to the underlying data defined by `Source`
// e.g., the volume name
Name string
// Driver is the volume driver used to create the volume (if it is a volume)
Driver string
// Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount
Type mounttypes.Type `json:",omitempty"`
// Volume is the volume providing data to this mountpoint.
// This is nil unless `Type` is set to `TypeVolume`
Volume Volume `json:"-"` |
a7e686a7 |
|
9a2d0bc3 |
// Mode is the comma separated list of options supplied by the user when creating
// the bind/volume mount. |
a7e686a7 |
// Note Mode is not used on Windows |
fc7b904d |
Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`" |
a2dc4f79 |
|
9a2d0bc3 |
// Propagation describes how the mounts are propagated from the host into the
// mount point, and vice-versa.
// See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt |
a2dc4f79 |
// Note Propagation is not used on Windows |
fc7b904d |
Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string |
b0ac69b6 |
// Specifies if data should be copied from the container before the first mount
// Use a pointer here so we can tell if the user set this value explicitly
// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
CopyData bool `json:"-"` |
2b6bc294 |
// ID is the opaque ID used to pass to the volume driver.
// This should be set by calls to `Mount` and unset by calls to `Unmount` |
9a2d0bc3 |
ID string `json:",omitempty"`
// Sepc is a copy of the API request that created this mount. |
fc7b904d |
Spec mounttypes.Mount |
a7e686a7 |
}
// Setup sets up a mount point by either mounting the volume if it is
// configured, or creating the source directory if supplied. |
0c791c8e |
func (m *MountPoint) Setup(mountLabel string, rootUID, rootGID int) (path string, err error) {
defer func() {
if err == nil {
if label.RelabelNeeded(m.Mode) {
if err = label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode)); err != nil {
path = ""
err = errors.Wrapf(err, "error setting label on mount source '%s'", m.Source)
return
}
}
}
return
}()
|
a7e686a7 |
if m.Volume != nil { |
9a2d0bc3 |
id := m.ID
if id == "" {
id = stringid.GenerateNonCryptoID()
}
path, err := m.Volume.Mount(id)
if err != nil {
return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source) |
2b6bc294 |
} |
9a2d0bc3 |
m.ID = id
return path, nil |
a7e686a7 |
} |
4e080347 |
if len(m.Source) == 0 {
return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
} |
fc7b904d |
// system.MkdirAll() produces an error if m.Source exists and is a file (not a directory),
if m.Type == mounttypes.TypeBind {
// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
if err := idtools.MkdirAllNewAs(m.Source, 0755, rootUID, rootGID); err != nil {
if perr, ok := err.(*os.PathError); ok {
if perr.Err != syscall.ENOTDIR { |
2a5e85e2 |
return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source) |
fc7b904d |
} |
322cc99c |
} |
4e080347 |
} |
322cc99c |
} |
4e080347 |
return m.Source, nil |
dfc6c04f |
}
|
a7e686a7 |
// Path returns the path of a volume in a mount point.
func (m *MountPoint) Path() string {
if m.Volume != nil {
return m.Volume.Path()
}
return m.Source |
dfc6c04f |
}
|
34b82a69 |
// ParseVolumesFrom ensures that the supplied volumes-from is valid. |
a7e686a7 |
func ParseVolumesFrom(spec string) (string, string, error) {
if len(spec) == 0 { |
a503f3ac |
return "", "", fmt.Errorf("volumes-from specification cannot be an empty string") |
a7e686a7 |
}
specParts := strings.SplitN(spec, ":", 2)
id := specParts[0]
mode := "rw"
if len(specParts) == 2 {
mode = specParts[1]
if !ValidMountMode(mode) { |
a793564b |
return "", "", errInvalidMode(mode) |
a7e686a7 |
} |
a2dc4f79 |
// For now don't allow propagation properties while importing
// volumes from data container. These volumes will inherit
// the same propagation property as of the original volume
// in data container. This probably can be relaxed in future.
if HasPropagation(mode) { |
a793564b |
return "", "", errInvalidMode(mode) |
a2dc4f79 |
} |
b0ac69b6 |
// Do not allow copy modes on volumes-from
if _, isSet := getCopyMode(mode); isSet {
return "", "", errInvalidMode(mode)
} |
a7e686a7 |
}
return id, mode, nil
} |
a793564b |
|
fc7b904d |
// ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a
// structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to
// validate the spec and create a MountPoint
func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
arr, err := splitRawSpec(convertSlash(raw))
if err != nil {
return nil, err
}
var spec mounttypes.Mount
var mode string
switch len(arr) {
case 1:
// Just a destination path in the container
spec.Target = arr[0]
case 2:
if ValidMountMode(arr[1]) {
// Destination + Mode is not a valid volume - volumes |
11454e1c |
// cannot include a mode. e.g. /foo:rw |
fc7b904d |
return nil, errInvalidSpec(raw)
}
// Host Source Path or Name + Destination
spec.Source = arr[0]
spec.Target = arr[1]
case 3:
// HostSourcePath+DestinationPath+Mode
spec.Source = arr[0]
spec.Target = arr[1]
mode = arr[2]
default:
return nil, errInvalidSpec(raw)
}
if !ValidMountMode(mode) {
return nil, errInvalidMode(mode)
}
if filepath.IsAbs(spec.Source) {
spec.Type = mounttypes.TypeBind
} else {
spec.Type = mounttypes.TypeVolume
}
spec.ReadOnly = !ReadWrite(mode)
// cannot assume that if a volume driver is passed in that we should set it
if volumeDriver != "" && spec.Type == mounttypes.TypeVolume {
spec.VolumeOptions = &mounttypes.VolumeOptions{
DriverConfig: &mounttypes.Driver{Name: volumeDriver},
}
}
if copyData, isSet := getCopyMode(mode); isSet {
if spec.VolumeOptions == nil {
spec.VolumeOptions = &mounttypes.VolumeOptions{}
}
spec.VolumeOptions.NoCopy = !copyData
}
if HasPropagation(mode) {
spec.BindOptions = &mounttypes.BindOptions{
Propagation: GetPropagation(mode),
}
}
mp, err := ParseMountSpec(spec, platformRawValidationOpts...)
if mp != nil {
mp.Mode = mode
}
if err != nil {
err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
}
return mp, err
}
// ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it.
func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) {
if err := validateMountConfig(&cfg, options...); err != nil {
return nil, err
}
mp := &MountPoint{
RW: !cfg.ReadOnly,
Destination: clean(convertSlash(cfg.Target)),
Type: cfg.Type,
Spec: cfg,
}
switch cfg.Type {
case mounttypes.TypeVolume:
if cfg.Source == "" {
mp.Name = stringid.GenerateNonCryptoID()
} else {
mp.Name = cfg.Source
}
mp.CopyData = DefaultCopyMode
if cfg.VolumeOptions != nil {
if cfg.VolumeOptions.DriverConfig != nil {
mp.Driver = cfg.VolumeOptions.DriverConfig.Name
}
if cfg.VolumeOptions.NoCopy {
mp.CopyData = false
}
}
case mounttypes.TypeBind:
mp.Source = clean(convertSlash(cfg.Source))
if cfg.BindOptions != nil {
if len(cfg.BindOptions.Propagation) > 0 {
mp.Propagation = cfg.BindOptions.Propagation
}
} |
18768fdc |
case mounttypes.TypeTmpfs:
// NOP |
fc7b904d |
}
return mp, nil
}
|
a793564b |
func errInvalidMode(mode string) error {
return fmt.Errorf("invalid mode: %v", mode)
}
func errInvalidSpec(spec string) error { |
fc7b904d |
return fmt.Errorf("invalid volume specification: '%s'", spec) |
a793564b |
} |