github.com/kim0/docker@v0.6.2-0.20161130212042-4addda3f07e7/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      string          // Container host directory
    84  	Destination string          // Inside the container
    85  	RW          bool            // True if writable
    86  	Name        string          // Name set by user
    87  	Driver      string          // Volume driver to use
    88  	Type        mounttypes.Type `json:",omitempty"` // Type of mount to use, see `Type<foo>` definitions
    89  	Volume      Volume          `json:"-"`
    90  
    91  	// Note Mode is not used on Windows
    92  	Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`"
    93  
    94  	// Note Propagation is not used on Windows
    95  	Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string
    96  
    97  	// Specifies if data should be copied from the container before the first mount
    98  	// Use a pointer here so we can tell if the user set this value explicitly
    99  	// This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated
   100  	CopyData bool `json:"-"`
   101  	// ID is the opaque ID used to pass to the volume driver.
   102  	// This should be set by calls to `Mount` and unset by calls to `Unmount`
   103  	ID   string `json:",omitempty"`
   104  	Spec mounttypes.Mount
   105  }
   106  
   107  // Setup sets up a mount point by either mounting the volume if it is
   108  // configured, or creating the source directory if supplied.
   109  func (m *MountPoint) Setup(mountLabel string, rootUID, rootGID int) (string, error) {
   110  	if m.Volume != nil {
   111  		if m.ID == "" {
   112  			m.ID = stringid.GenerateNonCryptoID()
   113  		}
   114  		path, err := m.Volume.Mount(m.ID)
   115  		return path, errors.Wrapf(err, "error while mounting volume '%s'", m.Source)
   116  	}
   117  	if len(m.Source) == 0 {
   118  		return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
   119  	}
   120  	// system.MkdirAll() produces an error if m.Source exists and is a file (not a directory),
   121  	if m.Type == mounttypes.TypeBind {
   122  		// idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory)
   123  		// also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it
   124  		if err := idtools.MkdirAllNewAs(m.Source, 0755, rootUID, rootGID); err != nil {
   125  			if perr, ok := err.(*os.PathError); ok {
   126  				if perr.Err != syscall.ENOTDIR {
   127  					return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source)
   128  				}
   129  			}
   130  		}
   131  	}
   132  	if label.RelabelNeeded(m.Mode) {
   133  		if err := label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode)); err != nil {
   134  			return "", errors.Wrapf(err, "error setting label on mount source '%s'", m.Source)
   135  		}
   136  	}
   137  	return m.Source, nil
   138  }
   139  
   140  // Path returns the path of a volume in a mount point.
   141  func (m *MountPoint) Path() string {
   142  	if m.Volume != nil {
   143  		return m.Volume.Path()
   144  	}
   145  	return m.Source
   146  }
   147  
   148  // ParseVolumesFrom ensures that the supplied volumes-from is valid.
   149  func ParseVolumesFrom(spec string) (string, string, error) {
   150  	if len(spec) == 0 {
   151  		return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
   152  	}
   153  
   154  	specParts := strings.SplitN(spec, ":", 2)
   155  	id := specParts[0]
   156  	mode := "rw"
   157  
   158  	if len(specParts) == 2 {
   159  		mode = specParts[1]
   160  		if !ValidMountMode(mode) {
   161  			return "", "", errInvalidMode(mode)
   162  		}
   163  		// For now don't allow propagation properties while importing
   164  		// volumes from data container. These volumes will inherit
   165  		// the same propagation property as of the original volume
   166  		// in data container. This probably can be relaxed in future.
   167  		if HasPropagation(mode) {
   168  			return "", "", errInvalidMode(mode)
   169  		}
   170  		// Do not allow copy modes on volumes-from
   171  		if _, isSet := getCopyMode(mode); isSet {
   172  			return "", "", errInvalidMode(mode)
   173  		}
   174  	}
   175  	return id, mode, nil
   176  }
   177  
   178  // ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a
   179  // structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to
   180  // validate the spec and create a MountPoint
   181  func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
   182  	arr, err := splitRawSpec(convertSlash(raw))
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	var spec mounttypes.Mount
   188  	var mode string
   189  	switch len(arr) {
   190  	case 1:
   191  		// Just a destination path in the container
   192  		spec.Target = arr[0]
   193  	case 2:
   194  		if ValidMountMode(arr[1]) {
   195  			// Destination + Mode is not a valid volume - volumes
   196  			// cannot include a mode. eg /foo:rw
   197  			return nil, errInvalidSpec(raw)
   198  		}
   199  		// Host Source Path or Name + Destination
   200  		spec.Source = arr[0]
   201  		spec.Target = arr[1]
   202  	case 3:
   203  		// HostSourcePath+DestinationPath+Mode
   204  		spec.Source = arr[0]
   205  		spec.Target = arr[1]
   206  		mode = arr[2]
   207  	default:
   208  		return nil, errInvalidSpec(raw)
   209  	}
   210  
   211  	if !ValidMountMode(mode) {
   212  		return nil, errInvalidMode(mode)
   213  	}
   214  
   215  	if filepath.IsAbs(spec.Source) {
   216  		spec.Type = mounttypes.TypeBind
   217  	} else {
   218  		spec.Type = mounttypes.TypeVolume
   219  	}
   220  
   221  	spec.ReadOnly = !ReadWrite(mode)
   222  
   223  	// cannot assume that if a volume driver is passed in that we should set it
   224  	if volumeDriver != "" && spec.Type == mounttypes.TypeVolume {
   225  		spec.VolumeOptions = &mounttypes.VolumeOptions{
   226  			DriverConfig: &mounttypes.Driver{Name: volumeDriver},
   227  		}
   228  	}
   229  
   230  	if copyData, isSet := getCopyMode(mode); isSet {
   231  		if spec.VolumeOptions == nil {
   232  			spec.VolumeOptions = &mounttypes.VolumeOptions{}
   233  		}
   234  		spec.VolumeOptions.NoCopy = !copyData
   235  	}
   236  	if HasPropagation(mode) {
   237  		spec.BindOptions = &mounttypes.BindOptions{
   238  			Propagation: GetPropagation(mode),
   239  		}
   240  	}
   241  
   242  	mp, err := ParseMountSpec(spec, platformRawValidationOpts...)
   243  	if mp != nil {
   244  		mp.Mode = mode
   245  	}
   246  	if err != nil {
   247  		err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
   248  	}
   249  	return mp, err
   250  }
   251  
   252  // ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it.
   253  func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) {
   254  	if err := validateMountConfig(&cfg, options...); err != nil {
   255  		return nil, err
   256  	}
   257  	mp := &MountPoint{
   258  		RW:          !cfg.ReadOnly,
   259  		Destination: clean(convertSlash(cfg.Target)),
   260  		Type:        cfg.Type,
   261  		Spec:        cfg,
   262  	}
   263  
   264  	switch cfg.Type {
   265  	case mounttypes.TypeVolume:
   266  		if cfg.Source == "" {
   267  			mp.Name = stringid.GenerateNonCryptoID()
   268  		} else {
   269  			mp.Name = cfg.Source
   270  		}
   271  		mp.CopyData = DefaultCopyMode
   272  
   273  		mp.Driver = DefaultDriverName
   274  		if cfg.VolumeOptions != nil {
   275  			if cfg.VolumeOptions.DriverConfig != nil {
   276  				mp.Driver = cfg.VolumeOptions.DriverConfig.Name
   277  			}
   278  			if cfg.VolumeOptions.NoCopy {
   279  				mp.CopyData = false
   280  			}
   281  		}
   282  	case mounttypes.TypeBind:
   283  		mp.Source = clean(convertSlash(cfg.Source))
   284  		if cfg.BindOptions != nil {
   285  			if len(cfg.BindOptions.Propagation) > 0 {
   286  				mp.Propagation = cfg.BindOptions.Propagation
   287  			}
   288  		}
   289  	case mounttypes.TypeTmpfs:
   290  		// NOP
   291  	}
   292  	return mp, nil
   293  }
   294  
   295  func errInvalidMode(mode string) error {
   296  	return fmt.Errorf("invalid mode: %v", mode)
   297  }
   298  
   299  func errInvalidSpec(spec string) error {
   300  	return fmt.Errorf("invalid volume specification: '%s'", spec)
   301  }