github.com/olljanat/moby@v1.13.1/volume/volume.go (about)

     1  package volume
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"syscall"
     9  
    10  	mounttypes "github.com/docker/docker/api/types/mount"
    11  	"github.com/docker/docker/pkg/idtools"
    12  	"github.com/docker/docker/pkg/stringid"
    13  	"github.com/opencontainers/runc/libcontainer/label"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // DefaultDriverName is the driver name used for the driver
    18  // implemented in the local package.
    19  const DefaultDriverName = "local"
    20  
    21  // Scopes define if a volume has is cluster-wide (global) or local only.
    22  // Scopes are returned by the volume driver when it is queried for capabilities and then set on a volume
    23  const (
    24  	LocalScope  = "local"
    25  	GlobalScope = "global"
    26  )
    27  
    28  // Driver is for creating and removing volumes.
    29  type Driver interface {
    30  	// Name returns the name of the volume driver.
    31  	Name() string
    32  	// Create makes a new volume with the given id.
    33  	Create(name string, opts map[string]string) (Volume, error)
    34  	// Remove deletes the volume.
    35  	Remove(vol Volume) (err error)
    36  	// List lists all the volumes the driver has
    37  	List() ([]Volume, error)
    38  	// Get retrieves the volume with the requested name
    39  	Get(name string) (Volume, error)
    40  	// Scope returns the scope of the driver (e.g. `global` or `local`).
    41  	// Scope determines how the driver is handled at a cluster level
    42  	Scope() string
    43  }
    44  
    45  // Capability defines a set of capabilities that a driver is able to handle.
    46  type Capability struct {
    47  	// Scope is the scope of the driver, `global` or `local`
    48  	// A `global` scope indicates that the driver manages volumes across the cluster
    49  	// A `local` scope indicates that the driver only manages volumes resources local to the host
    50  	// Scope is declared by the driver
    51  	Scope string
    52  }
    53  
    54  // Volume is a place to store data. It is backed by a specific driver, and can be mounted.
    55  type Volume interface {
    56  	// Name returns the name of the volume
    57  	Name() string
    58  	// DriverName returns the name of the driver which owns this volume.
    59  	DriverName() string
    60  	// Path returns the absolute path to the volume.
    61  	Path() string
    62  	// Mount mounts the volume and returns the absolute path to
    63  	// where it can be consumed.
    64  	Mount(id string) (string, error)
    65  	// Unmount unmounts the volume when it is no longer in use.
    66  	Unmount(id string) error
    67  	// Status returns low-level status information about a volume
    68  	Status() map[string]interface{}
    69  }
    70  
    71  // DetailedVolume wraps a Volume with user-defined labels, options, and cluster scope (e.g., `local` or `global`)
    72  type DetailedVolume interface {
    73  	Labels() map[string]string
    74  	Options() map[string]string
    75  	Scope() string
    76  	Volume
    77  }
    78  
    79  // MountPoint is the intersection point between a volume and a container. It
    80  // specifies which volume is to be used and where inside a container it should
    81  // be mounted.
    82  type MountPoint struct {
    83  	// Source is the source path of the mount.
    84  	// E.g. `mount --bind /foo /bar`, `/foo` is the `Source`.
    85  	Source string
    86  	// Destination is the path relative to the container root (`/`) to the mount point
    87  	// It is where the `Source` is mounted to
    88  	Destination string
    89  	// RW is set to true when the mountpoint should be mounted as read-write
    90  	RW bool
    91  	// Name is the name reference to the underlying data defined by `Source`
    92  	// e.g., the volume name
    93  	Name string
    94  	// Driver is the volume driver used to create the volume (if it is a volume)
    95  	Driver string
    96  	// Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount
    97  	Type mounttypes.Type `json:",omitempty"`
    98  	// Volume is the volume providing data to this mountpoint.
    99  	// This is nil unless `Type` is set to `TypeVolume`
   100  	Volume Volume `json:"-"`
   101  
   102  	// Mode is the comma separated list of options supplied by the user when creating
   103  	// the bind/volume mount.
   104  	// Note Mode is not used on Windows
   105  	Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`"
   106  
   107  	// Propagation describes how the mounts are propagated from the host into the
   108  	// mount point, and vice-versa.
   109  	// See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
   110  	// Note Propagation is not used on Windows
   111  	Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string
   112  
   113  	// Specifies if data should be copied from the container before the first mount
   114  	// Use a pointer here so we can tell if the user set this value explicitly
   115  	// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
   116  	CopyData bool `json:"-"`
   117  	// ID is the opaque ID used to pass to the volume driver.
   118  	// This should be set by calls to `Mount` and unset by calls to `Unmount`
   119  	ID string `json:",omitempty"`
   120  
   121  	// Sepc is a copy of the API request that created this mount.
   122  	Spec mounttypes.Mount
   123  }
   124  
   125  // Setup sets up a mount point by either mounting the volume if it is
   126  // configured, or creating the source directory if supplied.
   127  func (m *MountPoint) Setup(mountLabel string, rootUID, rootGID int) (string, error) {
   128  	if m.Volume != nil {
   129  		id := m.ID
   130  		if id == "" {
   131  			id = stringid.GenerateNonCryptoID()
   132  		}
   133  		path, err := m.Volume.Mount(id)
   134  		if err != nil {
   135  			return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
   136  		}
   137  		m.ID = id
   138  		return path, nil
   139  	}
   140  	if len(m.Source) == 0 {
   141  		return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
   142  	}
   143  	// system.MkdirAll() produces an error if m.Source exists and is a file (not a directory),
   144  	if m.Type == mounttypes.TypeBind {
   145  		// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
   146  		// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
   147  		if err := idtools.MkdirAllNewAs(m.Source, 0755, rootUID, rootGID); err != nil {
   148  			if perr, ok := err.(*os.PathError); ok {
   149  				if perr.Err != syscall.ENOTDIR {
   150  					return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
   151  				}
   152  			}
   153  		}
   154  	}
   155  	if label.RelabelNeeded(m.Mode) {
   156  		if err := label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode)); err != nil {
   157  			return "", errors.Wrapf(err, "error setting label on mount source '%s'", m.Source)
   158  		}
   159  	}
   160  	return m.Source, nil
   161  }
   162  
   163  // Path returns the path of a volume in a mount point.
   164  func (m *MountPoint) Path() string {
   165  	if m.Volume != nil {
   166  		return m.Volume.Path()
   167  	}
   168  	return m.Source
   169  }
   170  
   171  // ParseVolumesFrom ensures that the supplied volumes-from is valid.
   172  func ParseVolumesFrom(spec string) (string, string, error) {
   173  	if len(spec) == 0 {
   174  		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
   175  	}
   176  
   177  	specParts := strings.SplitN(spec, ":", 2)
   178  	id := specParts[0]
   179  	mode := "rw"
   180  
   181  	if len(specParts) == 2 {
   182  		mode = specParts[1]
   183  		if !ValidMountMode(mode) {
   184  			return "", "", errInvalidMode(mode)
   185  		}
   186  		// For now don't allow propagation properties while importing
   187  		// volumes from data container. These volumes will inherit
   188  		// the same propagation property as of the original volume
   189  		// in data container. This probably can be relaxed in future.
   190  		if HasPropagation(mode) {
   191  			return "", "", errInvalidMode(mode)
   192  		}
   193  		// Do not allow copy modes on volumes-from
   194  		if _, isSet := getCopyMode(mode); isSet {
   195  			return "", "", errInvalidMode(mode)
   196  		}
   197  	}
   198  	return id, mode, nil
   199  }
   200  
   201  // ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a
   202  // structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to
   203  // validate the spec and create a MountPoint
   204  func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
   205  	arr, err := splitRawSpec(convertSlash(raw))
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	var spec mounttypes.Mount
   211  	var mode string
   212  	switch len(arr) {
   213  	case 1:
   214  		// Just a destination path in the container
   215  		spec.Target = arr[0]
   216  	case 2:
   217  		if ValidMountMode(arr[1]) {
   218  			// Destination + Mode is not a valid volume - volumes
   219  			// cannot include a mode. eg /foo:rw
   220  			return nil, errInvalidSpec(raw)
   221  		}
   222  		// Host Source Path or Name + Destination
   223  		spec.Source = arr[0]
   224  		spec.Target = arr[1]
   225  	case 3:
   226  		// HostSourcePath+DestinationPath+Mode
   227  		spec.Source = arr[0]
   228  		spec.Target = arr[1]
   229  		mode = arr[2]
   230  	default:
   231  		return nil, errInvalidSpec(raw)
   232  	}
   233  
   234  	if !ValidMountMode(mode) {
   235  		return nil, errInvalidMode(mode)
   236  	}
   237  
   238  	if filepath.IsAbs(spec.Source) {
   239  		spec.Type = mounttypes.TypeBind
   240  	} else {
   241  		spec.Type = mounttypes.TypeVolume
   242  	}
   243  
   244  	spec.ReadOnly = !ReadWrite(mode)
   245  
   246  	// cannot assume that if a volume driver is passed in that we should set it
   247  	if volumeDriver != "" && spec.Type == mounttypes.TypeVolume {
   248  		spec.VolumeOptions = &mounttypes.VolumeOptions{
   249  			DriverConfig: &mounttypes.Driver{Name: volumeDriver},
   250  		}
   251  	}
   252  
   253  	if copyData, isSet := getCopyMode(mode); isSet {
   254  		if spec.VolumeOptions == nil {
   255  			spec.VolumeOptions = &mounttypes.VolumeOptions{}
   256  		}
   257  		spec.VolumeOptions.NoCopy = !copyData
   258  	}
   259  	if HasPropagation(mode) {
   260  		spec.BindOptions = &mounttypes.BindOptions{
   261  			Propagation: GetPropagation(mode),
   262  		}
   263  	}
   264  
   265  	mp, err := ParseMountSpec(spec, platformRawValidationOpts...)
   266  	if mp != nil {
   267  		mp.Mode = mode
   268  	}
   269  	if err != nil {
   270  		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
   271  	}
   272  	return mp, err
   273  }
   274  
   275  // ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it.
   276  func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) {
   277  	if err := validateMountConfig(&cfg, options...); err != nil {
   278  		return nil, err
   279  	}
   280  	mp := &MountPoint{
   281  		RW:          !cfg.ReadOnly,
   282  		Destination: clean(convertSlash(cfg.Target)),
   283  		Type:        cfg.Type,
   284  		Spec:        cfg,
   285  	}
   286  
   287  	switch cfg.Type {
   288  	case mounttypes.TypeVolume:
   289  		if cfg.Source == "" {
   290  			mp.Name = stringid.GenerateNonCryptoID()
   291  		} else {
   292  			mp.Name = cfg.Source
   293  		}
   294  		mp.CopyData = DefaultCopyMode
   295  
   296  		if cfg.VolumeOptions != nil {
   297  			if cfg.VolumeOptions.DriverConfig != nil {
   298  				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
   299  			}
   300  			if cfg.VolumeOptions.NoCopy {
   301  				mp.CopyData = false
   302  			}
   303  		}
   304  	case mounttypes.TypeBind:
   305  		mp.Source = clean(convertSlash(cfg.Source))
   306  		if cfg.BindOptions != nil {
   307  			if len(cfg.BindOptions.Propagation) > 0 {
   308  				mp.Propagation = cfg.BindOptions.Propagation
   309  			}
   310  		}
   311  	case mounttypes.TypeTmpfs:
   312  		// NOP
   313  	}
   314  	return mp, nil
   315  }
   316  
   317  func errInvalidMode(mode string) error {
   318  	return fmt.Errorf("invalid mode: %v", mode)
   319  }
   320  
   321  func errInvalidSpec(spec string) error {
   322  	return fmt.Errorf("invalid volume specification: '%s'", spec)
   323  }