Cleanup: initializeVolume

- Use a common struct for Volumes
- Split out some functionality in intializeVolume into separate functions
- Removes some duplicate code
- In general much easier to grok the code now

Docker-DCO-1.1-Signed-off-by: Brian Goff <cpuguy83@gmail.com> (github: cpuguy83)
This commit is contained in:
Brian Goff 2014-08-08 11:00:18 -04:00
parent 7dbab337dd
commit e350df5b2c
1 changed files with 128 additions and 126 deletions

View File

@ -13,10 +13,24 @@ import (
"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 {
@ -122,35 +136,40 @@ func applyVolumesFrom(container *Container) error {
return nil
}
func parseBindVolumeSpec(spec string) (BindMap, error) {
func parseBindVolumeSpec(spec string) (Volume, error) {
var (
arr = strings.Split(spec, ":")
err error = nil
vol BindMap
arr = strings.Split(spec, ":")
vol Volume
)
vol.isBindMount = true
switch len(arr) {
case 1:
vol.DstPath = spec
vol.VolPath = spec
vol.Mode = "rw"
case 2:
vol.SrcPath = arr[0]
vol.DstPath = arr[1]
vol.HostPath = arr[0]
vol.VolPath = arr[1]
vol.Mode = "rw"
case 3:
vol.SrcPath = arr[0]
vol.DstPath = arr[1]
vol.HostPath = arr[0]
vol.VolPath = arr[1]
vol.Mode = arr[2]
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 (
// Create the requested bind mounts
binds = make(map[string]BindMap)
volumes = map[string]Volume{}
// Define illegal container destinations
illegalDsts = []string{"/", "."}
)
@ -158,151 +177,134 @@ func getBindMap(container *Container) (map[string]BindMap, error) {
for _, bind := range container.hostConfig.Binds {
vol, err := parseBindVolumeSpec(bind)
if err != nil {
return binds, err
return volumes, err
}
// Bail if trying to mount to an illegal destination
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 {
binds, err := getBindMap(container)
// Get all the bindmounts
volumes, err := getBindMap(container)
if err != nil {
return err
}
// Create the requested volumes if they don't exist
// Get all the rest of the volumes
for volPath := range container.Config.Volumes {
if err := initializeVolume(container, volPath, binds); err != nil {
// 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 _, vol := range volumes {
if err = vol.initialize(container); err != nil {
return err
}
}
return nil
}
func createVolumeHostPath(container *Container) (string, error) {
volumesDriver := container.daemon.volumes.Driver()
// 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 hostPath, nil
}
func (v *Volume) initialize(container *Container) error {
var err error
v.VolPath = filepath.Clean(v.VolPath)
// Do not initialize an existing volume
if _, exists := container.Volumes[v.VolPath]; exists {
return 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 {
return err
}
}
for volPath := range binds {
if err := initializeVolume(container, volPath, binds); err != nil {
return err
}
hostPath, err := filepath.EvalSymlinks(v.HostPath)
if err != nil {
return err
}
// Create the mountpoint
// 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 {
return err
}
container.Volumes[v.VolPath] = hostPath
container.VolumesRW[v.VolPath] = v.isRw()
volIsDir, err := v.isDir()
if err != nil {
return err
}
if err := createIfNotExists(fullVolPath, volIsDir); err != nil {
return err
}
// Do not copy or change permissions if we are mounting from the host
if v.isRw() && !v.isBindMount {
return copyExistingContents(fullVolPath, hostPath)
}
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
}
f, err := os.OpenFile(destination, os.O_CREATE, 0755)
if err != nil {
return err
}
f.Close()
}
}
return nil
}
func initializeVolume(container *Container, volPath string, binds map[string]BindMap) error {
volumesDriver := container.daemon.volumes.Driver()
volPath = filepath.Clean(volPath)
// Skip existing volumes
if _, exists := container.Volumes[volPath]; exists {
if _, err := os.Stat(destination); err == nil || !os.IsNotExist(err) {
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 err != nil {
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 isDir {
return os.MkdirAll(destination, 0755)
}
if p, err := filepath.EvalSymlinks(destination); err != nil {
if err := os.MkdirAll(filepath.Dir(destination), 0755); err != nil {
return err
} else {
destination = p
}
// Create the mountpoint
source, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, volPath), container.basefs)
f, err := os.OpenFile(destination, os.O_CREATE, 0755)
if err != nil {
return err
}
f.Close()
newVolPath, err := filepath.Rel(container.basefs, source)
if err != nil {
return err
}
newVolPath = "/" + newVolPath
if volPath != newVolPath {
delete(container.Volumes, volPath)
delete(container.VolumesRW, volPath)
}
container.Volumes[volPath] = destination
container.VolumesRW[volPath] = srcRW
if err := createIfNotExists(source, volIsDir); err != nil {
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
}
}
return nil
}