volume.go 10 KB

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