|
@@ -13,10 +13,24 @@ import (
|
|
"github.com/docker/docker/pkg/symlink"
|
|
"github.com/docker/docker/pkg/symlink"
|
|
)
|
|
)
|
|
|
|
|
|
-type BindMap struct {
|
|
|
|
- SrcPath string
|
|
|
|
- DstPath string
|
|
|
|
- Mode string
|
|
|
|
|
|
+type Volume struct {
|
|
|
|
+ HostPath string
|
|
|
|
+ VolPath string
|
|
|
|
+ Mode string
|
|
|
|
+ isBindMount bool
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (v *Volume) isRw() bool {
|
|
|
|
+ return v.Mode == "" || strings.ToLower(v.Mode) == "rw"
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (v *Volume) isDir() (bool, error) {
|
|
|
|
+ stat, err := os.Stat(v.HostPath)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return false, err
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return stat.IsDir(), nil
|
|
}
|
|
}
|
|
|
|
|
|
func prepareVolumesForContainer(container *Container) error {
|
|
func prepareVolumesForContainer(container *Container) error {
|
|
@@ -122,35 +136,40 @@ func applyVolumesFrom(container *Container) error {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
-func parseBindVolumeSpec(spec string) (BindMap, error) {
|
|
|
|
|
|
+func parseBindVolumeSpec(spec string) (Volume, error) {
|
|
var (
|
|
var (
|
|
- arr = strings.Split(spec, ":")
|
|
|
|
- err error = nil
|
|
|
|
- vol BindMap
|
|
|
|
|
|
+ arr = strings.Split(spec, ":")
|
|
|
|
+ vol Volume
|
|
)
|
|
)
|
|
|
|
|
|
|
|
+ vol.isBindMount = true
|
|
switch len(arr) {
|
|
switch len(arr) {
|
|
case 1:
|
|
case 1:
|
|
- vol.DstPath = spec
|
|
|
|
|
|
+ vol.VolPath = spec
|
|
vol.Mode = "rw"
|
|
vol.Mode = "rw"
|
|
case 2:
|
|
case 2:
|
|
- vol.SrcPath = arr[0]
|
|
|
|
- vol.DstPath = arr[1]
|
|
|
|
|
|
+ vol.HostPath = arr[0]
|
|
|
|
+ vol.VolPath = arr[1]
|
|
vol.Mode = "rw"
|
|
vol.Mode = "rw"
|
|
case 3:
|
|
case 3:
|
|
- vol.SrcPath = arr[0]
|
|
|
|
- vol.DstPath = arr[1]
|
|
|
|
|
|
+ vol.HostPath = arr[0]
|
|
|
|
+ vol.VolPath = arr[1]
|
|
vol.Mode = arr[2]
|
|
vol.Mode = arr[2]
|
|
default:
|
|
default:
|
|
- err = fmt.Errorf("Invalid volume specification: %s", spec)
|
|
|
|
|
|
+ return vol, fmt.Errorf("Invalid volume specification: %s", spec)
|
|
}
|
|
}
|
|
- return vol, err
|
|
|
|
|
|
+
|
|
|
|
+ if !filepath.IsAbs(vol.HostPath) {
|
|
|
|
+ return vol, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", vol.HostPath)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return vol, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func getBindMap(container *Container) (map[string]BindMap, error) {
|
|
|
|
|
|
+func getBindMap(container *Container) (map[string]Volume, error) {
|
|
var (
|
|
var (
|
|
// Create the requested bind mounts
|
|
// Create the requested bind mounts
|
|
- binds = make(map[string]BindMap)
|
|
|
|
|
|
+ volumes = map[string]Volume{}
|
|
// Define illegal container destinations
|
|
// Define illegal container destinations
|
|
illegalDsts = []string{"/", "."}
|
|
illegalDsts = []string{"/", "."}
|
|
)
|
|
)
|
|
@@ -158,151 +177,134 @@ func getBindMap(container *Container) (map[string]BindMap, error) {
|
|
for _, bind := range container.hostConfig.Binds {
|
|
for _, bind := range container.hostConfig.Binds {
|
|
vol, err := parseBindVolumeSpec(bind)
|
|
vol, err := parseBindVolumeSpec(bind)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return binds, err
|
|
|
|
|
|
+ return volumes, err
|
|
}
|
|
}
|
|
// Bail if trying to mount to an illegal destination
|
|
// Bail if trying to mount to an illegal destination
|
|
for _, illegal := range illegalDsts {
|
|
for _, illegal := range illegalDsts {
|
|
- if vol.DstPath == illegal {
|
|
|
|
- return nil, fmt.Errorf("Illegal bind destination: %s", vol.DstPath)
|
|
|
|
|
|
+ if vol.VolPath == illegal {
|
|
|
|
+ return nil, fmt.Errorf("Illegal bind destination: %s", vol.VolPath)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- binds[filepath.Clean(vol.DstPath)] = vol
|
|
|
|
|
|
+ volumes[filepath.Clean(vol.VolPath)] = vol
|
|
}
|
|
}
|
|
- return binds, nil
|
|
|
|
|
|
+ return volumes, nil
|
|
}
|
|
}
|
|
|
|
|
|
func createVolumes(container *Container) error {
|
|
func createVolumes(container *Container) error {
|
|
- binds, err := getBindMap(container)
|
|
|
|
|
|
+ // Get all the bindmounts
|
|
|
|
+ volumes, err := getBindMap(container)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- // Create the requested volumes if they don't exist
|
|
|
|
|
|
+ // Get all the rest of the volumes
|
|
for volPath := range container.Config.Volumes {
|
|
for volPath := range container.Config.Volumes {
|
|
- if err := initializeVolume(container, volPath, binds); err != nil {
|
|
|
|
- return err
|
|
|
|
|
|
+ // Make sure the the volume isn't already specified as a bindmount
|
|
|
|
+ if _, exists := volumes[volPath]; !exists {
|
|
|
|
+ volumes[volPath] = Volume{
|
|
|
|
+ VolPath: volPath,
|
|
|
|
+ Mode: "rw",
|
|
|
|
+ isBindMount: false,
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- for volPath := range binds {
|
|
|
|
- if err := initializeVolume(container, volPath, binds); err != nil {
|
|
|
|
|
|
+ for _, vol := range volumes {
|
|
|
|
+ if err = vol.initialize(container); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
return nil
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
|
|
-func createIfNotExists(destination string, isDir bool) error {
|
|
|
|
- if _, err := os.Stat(destination); err != nil && os.IsNotExist(err) {
|
|
|
|
- if isDir {
|
|
|
|
- if err := os.MkdirAll(destination, 0755); err != nil {
|
|
|
|
- return err
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil {
|
|
|
|
- return err
|
|
|
|
- }
|
|
|
|
|
|
+func createVolumeHostPath(container *Container) (string, error) {
|
|
|
|
+ volumesDriver := container.daemon.volumes.Driver()
|
|
|
|
|
|
- f, err := os.OpenFile(destination, os.O_CREATE, 0755)
|
|
|
|
- if err != nil {
|
|
|
|
- return err
|
|
|
|
- }
|
|
|
|
- f.Close()
|
|
|
|
- }
|
|
|
|
|
|
+ // Do not pass a container as the parameter for the volume creation.
|
|
|
|
+ // The graph driver using the container's information ( Image ) to
|
|
|
|
+ // create the parent.
|
|
|
|
+ c, err := container.daemon.volumes.Create(nil, "", "", "", "", nil, nil)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return "", err
|
|
|
|
+ }
|
|
|
|
+ hostPath, err := volumesDriver.Get(c.ID, "")
|
|
|
|
+ if err != nil {
|
|
|
|
+ return hostPath, fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err)
|
|
}
|
|
}
|
|
|
|
|
|
- return nil
|
|
|
|
|
|
+ return hostPath, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func initializeVolume(container *Container, volPath string, binds map[string]BindMap) error {
|
|
|
|
- volumesDriver := container.daemon.volumes.Driver()
|
|
|
|
- volPath = filepath.Clean(volPath)
|
|
|
|
|
|
+func (v *Volume) initialize(container *Container) error {
|
|
|
|
+ var err error
|
|
|
|
+ v.VolPath = filepath.Clean(v.VolPath)
|
|
|
|
|
|
- // Skip existing volumes
|
|
|
|
- if _, exists := container.Volumes[volPath]; exists {
|
|
|
|
|
|
+ // Do not initialize an existing volume
|
|
|
|
+ if _, exists := container.Volumes[v.VolPath]; exists {
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|
|
- var (
|
|
|
|
- destination string
|
|
|
|
- isBindMount bool
|
|
|
|
- volIsDir = true
|
|
|
|
-
|
|
|
|
- srcRW = false
|
|
|
|
- )
|
|
|
|
-
|
|
|
|
- // If an external bind is defined for this volume, use that as a source
|
|
|
|
- if bindMap, exists := binds[volPath]; exists {
|
|
|
|
- isBindMount = true
|
|
|
|
- destination = bindMap.SrcPath
|
|
|
|
-
|
|
|
|
- if !filepath.IsAbs(destination) {
|
|
|
|
- return fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", destination)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if strings.ToLower(bindMap.Mode) == "rw" {
|
|
|
|
- srcRW = true
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if stat, err := os.Stat(bindMap.SrcPath); err != nil {
|
|
|
|
- return err
|
|
|
|
- } else {
|
|
|
|
- volIsDir = stat.IsDir()
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- // Do not pass a container as the parameter for the volume creation.
|
|
|
|
- // The graph driver using the container's information ( Image ) to
|
|
|
|
- // create the parent.
|
|
|
|
- c, err := container.daemon.volumes.Create(nil, "", "", "", "", nil, nil)
|
|
|
|
|
|
+ // If it's not a bindmount we need to create the dir on the host
|
|
|
|
+ if !v.isBindMount {
|
|
|
|
+ v.HostPath, err = createVolumeHostPath(container)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
-
|
|
|
|
- destination, err = volumesDriver.Get(c.ID, "")
|
|
|
|
- if err != nil {
|
|
|
|
- return fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- srcRW = true
|
|
|
|
}
|
|
}
|
|
|
|
|
|
- if p, err := filepath.EvalSymlinks(destination); err != nil {
|
|
|
|
|
|
+ hostPath, err := filepath.EvalSymlinks(v.HostPath)
|
|
|
|
+ if err != nil {
|
|
return err
|
|
return err
|
|
- } else {
|
|
|
|
- destination = p
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// Create the mountpoint
|
|
// Create the mountpoint
|
|
- source, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, volPath), container.basefs)
|
|
|
|
|
|
+ // This is the path to the volume within the container FS
|
|
|
|
+ // This differs from `hostPath` in that `hostPath` refers to the place where
|
|
|
|
+ // the volume data is actually stored on the host
|
|
|
|
+ fullVolPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, v.VolPath), container.basefs)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- newVolPath, err := filepath.Rel(container.basefs, source)
|
|
|
|
|
|
+ container.Volumes[v.VolPath] = hostPath
|
|
|
|
+ container.VolumesRW[v.VolPath] = v.isRw()
|
|
|
|
+
|
|
|
|
+ volIsDir, err := v.isDir()
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
- newVolPath = "/" + newVolPath
|
|
|
|
|
|
+ if err := createIfNotExists(fullVolPath, volIsDir); err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
|
|
- if volPath != newVolPath {
|
|
|
|
- delete(container.Volumes, volPath)
|
|
|
|
- delete(container.VolumesRW, volPath)
|
|
|
|
|
|
+ // Do not copy or change permissions if we are mounting from the host
|
|
|
|
+ if v.isRw() && !v.isBindMount {
|
|
|
|
+ return copyExistingContents(fullVolPath, hostPath)
|
|
}
|
|
}
|
|
|
|
+ return nil
|
|
|
|
+}
|
|
|
|
|
|
- container.Volumes[volPath] = destination
|
|
|
|
- container.VolumesRW[volPath] = srcRW
|
|
|
|
|
|
+func createIfNotExists(destination string, isDir bool) error {
|
|
|
|
+ if _, err := os.Stat(destination); err == nil || !os.IsNotExist(err) {
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
|
|
- if err := createIfNotExists(source, volIsDir); err != nil {
|
|
|
|
|
|
+ if isDir {
|
|
|
|
+ return os.MkdirAll(destination, 0755)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
|
|
|
|
- // Do not copy or change permissions if we are mounting from the host
|
|
|
|
- if srcRW && !isBindMount {
|
|
|
|
- if err := copyExistingContents(source, destination); err != nil {
|
|
|
|
- return err
|
|
|
|
- }
|
|
|
|
|
|
+ f, err := os.OpenFile(destination, os.O_CREATE, 0755)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
+ f.Close()
|
|
|
|
+
|
|
return nil
|
|
return nil
|
|
}
|
|
}
|
|
|
|
|