github.com/yogeshlonkar/moby@v1.13.2-0.20201203103638-c0b64beaea94/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) (path string, err error) {
   128  	defer func() {
   129  		if err == nil {
   130  			if label.RelabelNeeded(m.Mode) {
   131  				if err = label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode)); err != nil {
   132  					path = ""
   133  					err = errors.Wrapf(err, "error setting label on mount source '%s'", m.Source)
   134  					return
   135  				}
   136  			}
   137  		}
   138  		return
   139  	}()
   140  
   141  	if m.Volume != nil {
   142  		id := m.ID
   143  		if id == "" {
   144  			id = stringid.GenerateNonCryptoID()
   145  		}
   146  		path, err := m.Volume.Mount(id)
   147  		if err != nil {
   148  			return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
   149  		}
   150  		m.ID = id
   151  		return path, nil
   152  	}
   153  	if len(m.Source) == 0 {
   154  		return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
   155  	}
   156  	// system.MkdirAll() produces an error if m.Source exists and is a file (not a directory),
   157  	if m.Type == mounttypes.TypeBind {
   158  		// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
   159  		// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
   160  		if err := idtools.MkdirAllNewAs(m.Source, 0755, rootUID, rootGID); err != nil {
   161  			if perr, ok := err.(*os.PathError); ok {
   162  				if perr.Err != syscall.ENOTDIR {
   163  					return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
   164  				}
   165  			}
   166  		}
   167  	}
   168  	return m.Source, nil
   169  }
   170  
   171  // Path returns the path of a volume in a mount point.
   172  func (m *MountPoint) Path() string {
   173  	if m.Volume != nil {
   174  		return m.Volume.Path()
   175  	}
   176  	return m.Source
   177  }
   178  
   179  // ParseVolumesFrom ensures that the supplied volumes-from is valid.
   180  func ParseVolumesFrom(spec string) (string, string, error) {
   181  	if len(spec) == 0 {
   182  		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
   183  	}
   184  
   185  	specParts := strings.SplitN(spec, ":", 2)
   186  	id := specParts[0]
   187  	mode := "rw"
   188  
   189  	if len(specParts) == 2 {
   190  		mode = specParts[1]
   191  		if !ValidMountMode(mode) {
   192  			return "", "", errInvalidMode(mode)
   193  		}
   194  		// For now don't allow propagation properties while importing
   195  		// volumes from data container. These volumes will inherit
   196  		// the same propagation property as of the original volume
   197  		// in data container. This probably can be relaxed in future.
   198  		if HasPropagation(mode) {
   199  			return "", "", errInvalidMode(mode)
   200  		}
   201  		// Do not allow copy modes on volumes-from
   202  		if _, isSet := getCopyMode(mode); isSet {
   203  			return "", "", errInvalidMode(mode)
   204  		}
   205  	}
   206  	return id, mode, nil
   207  }
   208  
   209  // ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a
   210  // structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to
   211  // validate the spec and create a MountPoint
   212  func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
   213  	arr, err := splitRawSpec(convertSlash(raw))
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  
   218  	var spec mounttypes.Mount
   219  	var mode string
   220  	switch len(arr) {
   221  	case 1:
   222  		// Just a destination path in the container
   223  		spec.Target = arr[0]
   224  	case 2:
   225  		if ValidMountMode(arr[1]) {
   226  			// Destination + Mode is not a valid volume - volumes
   227  			// cannot include a mode. eg /foo:rw
   228  			return nil, errInvalidSpec(raw)
   229  		}
   230  		// Host Source Path or Name + Destination
   231  		spec.Source = arr[0]
   232  		spec.Target = arr[1]
   233  	case 3:
   234  		// HostSourcePath+DestinationPath+Mode
   235  		spec.Source = arr[0]
   236  		spec.Target = arr[1]
   237  		mode = arr[2]
   238  	default:
   239  		return nil, errInvalidSpec(raw)
   240  	}
   241  
   242  	if !ValidMountMode(mode) {
   243  		return nil, errInvalidMode(mode)
   244  	}
   245  
   246  	if filepath.IsAbs(spec.Source) {
   247  		spec.Type = mounttypes.TypeBind
   248  	} else {
   249  		spec.Type = mounttypes.TypeVolume
   250  	}
   251  
   252  	spec.ReadOnly = !ReadWrite(mode)
   253  
   254  	// cannot assume that if a volume driver is passed in that we should set it
   255  	if volumeDriver != "" && spec.Type == mounttypes.TypeVolume {
   256  		spec.VolumeOptions = &mounttypes.VolumeOptions{
   257  			DriverConfig: &mounttypes.Driver{Name: volumeDriver},
   258  		}
   259  	}
   260  
   261  	if copyData, isSet := getCopyMode(mode); isSet {
   262  		if spec.VolumeOptions == nil {
   263  			spec.VolumeOptions = &mounttypes.VolumeOptions{}
   264  		}
   265  		spec.VolumeOptions.NoCopy = !copyData
   266  	}
   267  	if HasPropagation(mode) {
   268  		spec.BindOptions = &mounttypes.BindOptions{
   269  			Propagation: GetPropagation(mode),
   270  		}
   271  	}
   272  
   273  	mp, err := ParseMountSpec(spec, platformRawValidationOpts...)
   274  	if mp != nil {
   275  		mp.Mode = mode
   276  	}
   277  	if err != nil {
   278  		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
   279  	}
   280  	return mp, err
   281  }
   282  
   283  // ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it.
   284  func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) {
   285  	if err := validateMountConfig(&cfg, options...); err != nil {
   286  		return nil, err
   287  	}
   288  	mp := &MountPoint{
   289  		RW:          !cfg.ReadOnly,
   290  		Destination: clean(convertSlash(cfg.Target)),
   291  		Type:        cfg.Type,
   292  		Spec:        cfg,
   293  	}
   294  
   295  	switch cfg.Type {
   296  	case mounttypes.TypeVolume:
   297  		if cfg.Source == "" {
   298  			mp.Name = stringid.GenerateNonCryptoID()
   299  		} else {
   300  			mp.Name = cfg.Source
   301  		}
   302  		mp.CopyData = DefaultCopyMode
   303  
   304  		if cfg.VolumeOptions != nil {
   305  			if cfg.VolumeOptions.DriverConfig != nil {
   306  				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
   307  			}
   308  			if cfg.VolumeOptions.NoCopy {
   309  				mp.CopyData = false
   310  			}
   311  		}
   312  	case mounttypes.TypeBind:
   313  		mp.Source = clean(convertSlash(cfg.Source))
   314  		if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
   315  			mp.Propagation = cfg.BindOptions.Propagation
   316  		} else {
   317  			// If user did not specify a propagation mode, get
   318  			// default propagation mode.
   319  			mp.Propagation = DefaultPropagationMode
   320  		}
   321  	case mounttypes.TypeTmpfs:
   322  		// NOP
   323  	}
   324  	return mp, nil
   325  }
   326  
   327  func errInvalidMode(mode string) error {
   328  	return fmt.Errorf("invalid mode: %v", mode)
   329  }
   330  
   331  func errInvalidSpec(spec string) error {
   332  	return fmt.Errorf("invalid volume specification: '%s'", spec)
   333  }