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