This moves the platform specific stuff in a separate package and keeps
the `volume` package and the defined interfaces light to import.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -37,6 +37,7 @@ import ( |
| 37 | 37 |
"github.com/docker/docker/restartmanager" |
| 38 | 38 |
"github.com/docker/docker/runconfig" |
| 39 | 39 |
"github.com/docker/docker/volume" |
| 40 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 40 | 41 |
"github.com/docker/go-connections/nat" |
| 41 | 42 |
units "github.com/docker/go-units" |
| 42 | 43 |
"github.com/docker/libnetwork" |
| ... | ... |
@@ -94,7 +95,7 @@ type Container struct {
|
| 94 | 94 |
RestartCount int |
| 95 | 95 |
HasBeenStartedBefore bool |
| 96 | 96 |
HasBeenManuallyStopped bool // used for unless-stopped restart policy |
| 97 |
- MountPoints map[string]*volume.MountPoint |
|
| 97 |
+ MountPoints map[string]*volumemounts.MountPoint |
|
| 98 | 98 |
HostConfig *containertypes.HostConfig `json:"-"` // do not serialize the host config in the json, otherwise we'll make the container unportable |
| 99 | 99 |
ExecCommands *exec.Store `json:"-"` |
| 100 | 100 |
DependencyStore agentexec.DependencyGetter `json:"-"` |
| ... | ... |
@@ -128,7 +129,7 @@ func NewBaseContainer(id, root string) *Container {
|
| 128 | 128 |
State: NewState(), |
| 129 | 129 |
ExecCommands: exec.NewStore(), |
| 130 | 130 |
Root: root, |
| 131 |
- MountPoints: make(map[string]*volume.MountPoint), |
|
| 131 |
+ MountPoints: make(map[string]*volumemounts.MountPoint), |
|
| 132 | 132 |
StreamConfig: stream.NewConfig(), |
| 133 | 133 |
attachContext: &attachContext{},
|
| 134 | 134 |
} |
| ... | ... |
@@ -450,8 +451,8 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu |
| 450 | 450 |
if operatingSystem == "" {
|
| 451 | 451 |
operatingSystem = runtime.GOOS |
| 452 | 452 |
} |
| 453 |
- volumeParser := volume.NewParser(operatingSystem) |
|
| 454 |
- container.MountPoints[destination] = &volume.MountPoint{
|
|
| 453 |
+ volumeParser := volumemounts.NewParser(operatingSystem) |
|
| 454 |
+ container.MountPoints[destination] = &volumemounts.MountPoint{
|
|
| 455 | 455 |
Type: mounttypes.TypeVolume, |
| 456 | 456 |
Name: vol.Name(), |
| 457 | 457 |
Driver: vol.DriverName(), |
| ... | ... |
@@ -15,6 +15,7 @@ import ( |
| 15 | 15 |
"github.com/docker/docker/pkg/mount" |
| 16 | 16 |
"github.com/docker/docker/pkg/stringid" |
| 17 | 17 |
"github.com/docker/docker/volume" |
| 18 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 18 | 19 |
"github.com/opencontainers/selinux/go-selinux/label" |
| 19 | 20 |
"github.com/pkg/errors" |
| 20 | 21 |
"github.com/sirupsen/logrus" |
| ... | ... |
@@ -61,7 +62,7 @@ func (container *Container) BuildHostnameFile() error {
|
| 61 | 61 |
func (container *Container) NetworkMounts() []Mount {
|
| 62 | 62 |
var mounts []Mount |
| 63 | 63 |
shared := container.HostConfig.NetworkMode.IsContainer() |
| 64 |
- parser := volume.NewParser(container.OS) |
|
| 64 |
+ parser := volumemounts.NewParser(container.OS) |
|
| 65 | 65 |
if container.ResolvConfPath != "" {
|
| 66 | 66 |
if _, err := os.Stat(container.ResolvConfPath); err != nil {
|
| 67 | 67 |
logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err)
|
| ... | ... |
@@ -198,7 +199,7 @@ func (container *Container) UnmountIpcMount(unmount func(pth string) error) erro |
| 198 | 198 |
// IpcMounts returns the list of IPC mounts |
| 199 | 199 |
func (container *Container) IpcMounts() []Mount {
|
| 200 | 200 |
var mounts []Mount |
| 201 |
- parser := volume.NewParser(container.OS) |
|
| 201 |
+ parser := volumemounts.NewParser(container.OS) |
|
| 202 | 202 |
|
| 203 | 203 |
if container.HasMountFor("/dev/shm") {
|
| 204 | 204 |
return mounts |
| ... | ... |
@@ -402,7 +403,7 @@ func copyExistingContents(source, destination string) error {
|
| 402 | 402 |
|
| 403 | 403 |
// TmpfsMounts returns the list of tmpfs mounts |
| 404 | 404 |
func (container *Container) TmpfsMounts() ([]Mount, error) {
|
| 405 |
- parser := volume.NewParser(container.OS) |
|
| 405 |
+ parser := volumemounts.NewParser(container.OS) |
|
| 406 | 406 |
var mounts []Mount |
| 407 | 407 |
for dest, data := range container.HostConfig.Tmpfs {
|
| 408 | 408 |
mounts = append(mounts, Mount{
|
| ... | ... |
@@ -4,7 +4,7 @@ package daemon // import "github.com/docker/docker/daemon" |
| 4 | 4 |
|
| 5 | 5 |
import ( |
| 6 | 6 |
"github.com/docker/docker/container" |
| 7 |
- "github.com/docker/docker/volume" |
|
| 7 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 | 10 |
// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it |
| ... | ... |
@@ -12,7 +12,7 @@ import ( |
| 12 | 12 |
// cannot be configured with a read-only rootfs. |
| 13 | 13 |
func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) {
|
| 14 | 14 |
var toVolume bool |
| 15 |
- parser := volume.NewParser(container.OS) |
|
| 15 |
+ parser := volumemounts.NewParser(container.OS) |
|
| 16 | 16 |
for _, mnt := range container.MountPoints {
|
| 17 | 17 |
if toVolume = parser.HasResource(mnt, absPath); toVolume {
|
| 18 | 18 |
if mnt.RW {
|
| ... | ... |
@@ -20,7 +20,7 @@ import ( |
| 20 | 20 |
"github.com/docker/docker/pkg/system" |
| 21 | 21 |
"github.com/docker/docker/pkg/truncindex" |
| 22 | 22 |
"github.com/docker/docker/runconfig" |
| 23 |
- "github.com/docker/docker/volume" |
|
| 23 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 24 | 24 |
"github.com/docker/go-connections/nat" |
| 25 | 25 |
"github.com/opencontainers/selinux/go-selinux/label" |
| 26 | 26 |
"github.com/pkg/errors" |
| ... | ... |
@@ -296,7 +296,7 @@ func (daemon *Daemon) verifyContainerSettings(platform string, hostConfig *conta |
| 296 | 296 |
} |
| 297 | 297 |
|
| 298 | 298 |
// Validate mounts; check if host directories still exist |
| 299 |
- parser := volume.NewParser(platform) |
|
| 299 |
+ parser := volumemounts.NewParser(platform) |
|
| 300 | 300 |
for _, cfg := range hostConfig.Mounts {
|
| 301 | 301 |
if err := parser.ValidateMountConfig(&cfg); err != nil {
|
| 302 | 302 |
return nil, err |
| ... | ... |
@@ -7,7 +7,7 @@ import ( |
| 7 | 7 |
containertypes "github.com/docker/docker/api/types/container" |
| 8 | 8 |
"github.com/docker/docker/container" |
| 9 | 9 |
"github.com/docker/docker/pkg/stringid" |
| 10 |
- "github.com/docker/docker/volume" |
|
| 10 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 11 | 11 |
) |
| 12 | 12 |
|
| 13 | 13 |
// createContainerOSSpecificSettings performs host-OS specific container create functionality |
| ... | ... |
@@ -26,7 +26,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con |
| 26 | 26 |
} |
| 27 | 27 |
hostConfig.Isolation = "hyperv" |
| 28 | 28 |
} |
| 29 |
- parser := volume.NewParser(container.OS) |
|
| 29 |
+ parser := volumemounts.NewParser(container.OS) |
|
| 30 | 30 |
for spec := range config.Volumes {
|
| 31 | 31 |
|
| 32 | 32 |
mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver) |
| ... | ... |
@@ -33,7 +33,7 @@ import ( |
| 33 | 33 |
"github.com/docker/docker/pkg/parsers/kernel" |
| 34 | 34 |
"github.com/docker/docker/pkg/sysinfo" |
| 35 | 35 |
"github.com/docker/docker/runconfig" |
| 36 |
- "github.com/docker/docker/volume" |
|
| 36 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 37 | 37 |
"github.com/docker/libnetwork" |
| 38 | 38 |
nwconfig "github.com/docker/libnetwork/config" |
| 39 | 39 |
"github.com/docker/libnetwork/drivers/bridge" |
| ... | ... |
@@ -626,7 +626,7 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes. |
| 626 | 626 |
return warnings, fmt.Errorf("Unknown runtime specified %s", hostConfig.Runtime)
|
| 627 | 627 |
} |
| 628 | 628 |
|
| 629 |
- parser := volume.NewParser(runtime.GOOS) |
|
| 629 |
+ parser := volumemounts.NewParser(runtime.GOOS) |
|
| 630 | 630 |
for dest := range hostConfig.Tmpfs {
|
| 631 | 631 |
if err := parser.ValidateTmpfsMountDestination(dest); err != nil {
|
| 632 | 632 |
return warnings, err |
| ... | ... |
@@ -18,7 +18,7 @@ import ( |
| 18 | 18 |
"github.com/docker/docker/oci" |
| 19 | 19 |
"github.com/docker/docker/pkg/idtools" |
| 20 | 20 |
"github.com/docker/docker/pkg/mount" |
| 21 |
- "github.com/docker/docker/volume" |
|
| 21 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 22 | 22 |
"github.com/opencontainers/runc/libcontainer/apparmor" |
| 23 | 23 |
"github.com/opencontainers/runc/libcontainer/cgroups" |
| 24 | 24 |
"github.com/opencontainers/runc/libcontainer/devices" |
| ... | ... |
@@ -580,7 +580,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c |
| 580 | 580 |
|
| 581 | 581 |
if m.Source == "tmpfs" {
|
| 582 | 582 |
data := m.Data |
| 583 |
- parser := volume.NewParser("linux")
|
|
| 583 |
+ parser := volumemounts.NewParser("linux")
|
|
| 584 | 584 |
options := []string{"noexec", "nosuid", "nodev", string(parser.DefaultPropagationMode())}
|
| 585 | 585 |
if data != "" {
|
| 586 | 586 |
options = append(options, strings.Split(data, ",")...) |
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
"github.com/docker/docker/container" |
| 15 | 15 |
"github.com/docker/docker/errdefs" |
| 16 | 16 |
"github.com/docker/docker/volume" |
| 17 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 17 | 18 |
"github.com/pkg/errors" |
| 18 | 19 |
"github.com/sirupsen/logrus" |
| 19 | 20 |
) |
| ... | ... |
@@ -74,8 +75,9 @@ func (m mounts) parts(i int) int {
|
| 74 | 74 |
// 4. Cleanup old volumes that are about to be reassigned. |
| 75 | 75 |
func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) {
|
| 76 | 76 |
binds := map[string]bool{}
|
| 77 |
- mountPoints := map[string]*volume.MountPoint{}
|
|
| 78 |
- parser := volume.NewParser(container.OS) |
|
| 77 |
+ mountPoints := map[string]*volumemounts.MountPoint{}
|
|
| 78 |
+ parser := volumemounts.NewParser(container.OS) |
|
| 79 |
+ |
|
| 79 | 80 |
defer func() {
|
| 80 | 81 |
// clean up the container mountpoints once return with error |
| 81 | 82 |
if retErr != nil {
|
| ... | ... |
@@ -115,7 +117,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 115 | 115 |
} |
| 116 | 116 |
|
| 117 | 117 |
for _, m := range c.MountPoints {
|
| 118 |
- cp := &volume.MountPoint{
|
|
| 118 |
+ cp := &volumemounts.MountPoint{
|
|
| 119 | 119 |
Type: m.Type, |
| 120 | 120 |
Name: m.Name, |
| 121 | 121 |
Source: m.Source, |
| ... | ... |
@@ -250,7 +252,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
| 250 | 250 |
|
| 251 | 251 |
// lazyInitializeVolume initializes a mountpoint's volume if needed. |
| 252 | 252 |
// This happens after a daemon restart. |
| 253 |
-func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volume.MountPoint) error {
|
|
| 253 |
+func (daemon *Daemon) lazyInitializeVolume(containerID string, m *volumemounts.MountPoint) error {
|
|
| 254 | 254 |
if len(m.Driver) > 0 && m.Volume == nil {
|
| 255 | 255 |
v, err := daemon.volumes.GetWithRef(m.Name, m.Driver, containerID) |
| 256 | 256 |
if err != nil {
|
| ... | ... |
@@ -270,7 +272,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
|
| 270 | 270 |
container.Lock() |
| 271 | 271 |
defer container.Unlock() |
| 272 | 272 |
|
| 273 |
- parser := volume.NewParser(container.OS) |
|
| 273 |
+ parser := volumemounts.NewParser(container.OS) |
|
| 274 | 274 |
|
| 275 | 275 |
maybeUpdate := make(map[string]bool) |
| 276 | 276 |
for _, mp := range container.MountPoints {
|
| ... | ... |
@@ -288,7 +290,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
|
| 288 | 288 |
mountSpecs[m.Target] = true |
| 289 | 289 |
} |
| 290 | 290 |
|
| 291 |
- binds := make(map[string]*volume.MountPoint, len(container.HostConfig.Binds)) |
|
| 291 |
+ binds := make(map[string]*volumemounts.MountPoint, len(container.HostConfig.Binds)) |
|
| 292 | 292 |
for _, rawSpec := range container.HostConfig.Binds {
|
| 293 | 293 |
mp, err := parser.ParseMountRaw(rawSpec, container.HostConfig.VolumeDriver) |
| 294 | 294 |
if err != nil {
|
| ... | ... |
@@ -298,7 +300,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
|
| 298 | 298 |
binds[mp.Destination] = mp |
| 299 | 299 |
} |
| 300 | 300 |
|
| 301 |
- volumesFrom := make(map[string]volume.MountPoint) |
|
| 301 |
+ volumesFrom := make(map[string]volumemounts.MountPoint) |
|
| 302 | 302 |
for _, fromSpec := range container.HostConfig.VolumesFrom {
|
| 303 | 303 |
from, _, err := parser.ParseVolumesFrom(fromSpec) |
| 304 | 304 |
if err != nil {
|
| ... | ... |
@@ -321,7 +323,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) {
|
| 321 | 321 |
fromC.Unlock() |
| 322 | 322 |
} |
| 323 | 323 |
|
| 324 |
- needsUpdate := func(containerMount, other *volume.MountPoint) bool {
|
|
| 324 |
+ needsUpdate := func(containerMount, other *volumemounts.MountPoint) bool {
|
|
| 325 | 325 |
if containerMount.Type != other.Type || !reflect.DeepEqual(containerMount.Spec, other.Spec) {
|
| 326 | 326 |
return true |
| 327 | 327 |
} |
| ... | ... |
@@ -4,7 +4,7 @@ import ( |
| 4 | 4 |
"runtime" |
| 5 | 5 |
"testing" |
| 6 | 6 |
|
| 7 |
- "github.com/docker/docker/volume" |
|
| 7 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 | 10 |
func TestParseVolumesFrom(t *testing.T) {
|
| ... | ... |
@@ -21,7 +21,7 @@ func TestParseVolumesFrom(t *testing.T) {
|
| 21 | 21 |
{"foobar:baz", "", "", true},
|
| 22 | 22 |
} |
| 23 | 23 |
|
| 24 |
- parser := volume.NewParser(runtime.GOOS) |
|
| 24 |
+ parser := volumemounts.NewParser(runtime.GOOS) |
|
| 25 | 25 |
|
| 26 | 26 |
for _, c := range cases {
|
| 27 | 27 |
id, mode, err := parser.ParseVolumesFrom(c.spec) |
| ... | ... |
@@ -12,7 +12,7 @@ import ( |
| 12 | 12 |
"github.com/docker/docker/container" |
| 13 | 13 |
"github.com/docker/docker/pkg/fileutils" |
| 14 | 14 |
"github.com/docker/docker/pkg/mount" |
| 15 |
- "github.com/docker/docker/volume" |
|
| 15 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 16 | 16 |
) |
| 17 | 17 |
|
| 18 | 18 |
// setupMounts iterates through each of the mount points for a container and |
| ... | ... |
@@ -40,7 +40,7 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er |
| 40 | 40 |
// mount the socket the daemon is listening on. During daemon shutdown, the socket |
| 41 | 41 |
// (/var/run/docker.sock by default) doesn't exist anymore causing the call to m.Setup to |
| 42 | 42 |
// create at directory instead. This in turn will prevent the daemon to restart. |
| 43 |
- checkfunc := func(m *volume.MountPoint) error {
|
|
| 43 |
+ checkfunc := func(m *volumemounts.MountPoint) error {
|
|
| 44 | 44 |
if _, exist := daemon.hosts[m.Source]; exist && daemon.IsShuttingDown() {
|
| 45 | 45 |
return fmt.Errorf("Could not mount %q to container while the daemon is shutting down", m.Source)
|
| 46 | 46 |
} |
| ... | ... |
@@ -102,7 +102,7 @@ func sortMounts(m []container.Mount) []container.Mount {
|
| 102 | 102 |
// setBindModeIfNull is platform specific processing to ensure the |
| 103 | 103 |
// shared mode is set to 'z' if it is null. This is called in the case |
| 104 | 104 |
// of processing a named volume and not a typical bind. |
| 105 |
-func setBindModeIfNull(bind *volume.MountPoint) {
|
|
| 105 |
+func setBindModeIfNull(bind *volumemounts.MountPoint) {
|
|
| 106 | 106 |
if bind.Mode == "" {
|
| 107 | 107 |
bind.Mode = "z" |
| 108 | 108 |
} |
| ... | ... |
@@ -11,7 +11,7 @@ import ( |
| 11 | 11 |
containertypes "github.com/docker/docker/api/types/container" |
| 12 | 12 |
mounttypes "github.com/docker/docker/api/types/mount" |
| 13 | 13 |
"github.com/docker/docker/container" |
| 14 |
- "github.com/docker/docker/volume" |
|
| 14 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 15 | 15 |
) |
| 16 | 16 |
|
| 17 | 17 |
func TestBackportMountSpec(t *testing.T) {
|
| ... | ... |
@@ -19,7 +19,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 19 | 19 |
|
| 20 | 20 |
c := &container.Container{
|
| 21 | 21 |
State: &container.State{},
|
| 22 |
- MountPoints: map[string]*volume.MountPoint{
|
|
| 22 |
+ MountPoints: map[string]*volumemounts.MountPoint{
|
|
| 23 | 23 |
"/apple": {Destination: "/apple", Source: "/var/lib/docker/volumes/12345678", Name: "12345678", RW: true, CopyData: true}, // anonymous volume
|
| 24 | 24 |
"/banana": {Destination: "/banana", Source: "/var/lib/docker/volumes/data", Name: "data", RW: true, CopyData: true}, // named volume
|
| 25 | 25 |
"/cherry": {Destination: "/cherry", Source: "/var/lib/docker/volumes/data", Name: "data", CopyData: true}, // RO named volume
|
| ... | ... |
@@ -73,7 +73,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 73 | 73 |
d.containers.Add("1", &container.Container{
|
| 74 | 74 |
State: &container.State{},
|
| 75 | 75 |
ID: "1", |
| 76 |
- MountPoints: map[string]*volume.MountPoint{
|
|
| 76 |
+ MountPoints: map[string]*volumemounts.MountPoint{
|
|
| 77 | 77 |
"/kumquat": {Destination: "/kumquat", Name: "data", RW: false, CopyData: true},
|
| 78 | 78 |
}, |
| 79 | 79 |
HostConfig: &containertypes.HostConfig{
|
| ... | ... |
@@ -84,11 +84,11 @@ func TestBackportMountSpec(t *testing.T) {
|
| 84 | 84 |
}) |
| 85 | 85 |
|
| 86 | 86 |
type expected struct {
|
| 87 |
- mp *volume.MountPoint |
|
| 87 |
+ mp *volumemounts.MountPoint |
|
| 88 | 88 |
comment string |
| 89 | 89 |
} |
| 90 | 90 |
|
| 91 |
- pretty := func(mp *volume.MountPoint) string {
|
|
| 91 |
+ pretty := func(mp *volumemounts.MountPoint) string {
|
|
| 92 | 92 |
b, err := json.MarshalIndent(mp, "\t", " ") |
| 93 | 93 |
if err != nil {
|
| 94 | 94 |
return fmt.Sprintf("%#v", mp)
|
| ... | ... |
@@ -98,7 +98,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 98 | 98 |
|
| 99 | 99 |
for _, x := range []expected{
|
| 100 | 100 |
{
|
| 101 |
- mp: &volume.MountPoint{
|
|
| 101 |
+ mp: &volumemounts.MountPoint{
|
|
| 102 | 102 |
Type: mounttypes.TypeVolume, |
| 103 | 103 |
Destination: "/apple", |
| 104 | 104 |
RW: true, |
| ... | ... |
@@ -114,7 +114,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 114 | 114 |
comment: "anonymous volume", |
| 115 | 115 |
}, |
| 116 | 116 |
{
|
| 117 |
- mp: &volume.MountPoint{
|
|
| 117 |
+ mp: &volumemounts.MountPoint{
|
|
| 118 | 118 |
Type: mounttypes.TypeVolume, |
| 119 | 119 |
Destination: "/banana", |
| 120 | 120 |
RW: true, |
| ... | ... |
@@ -130,7 +130,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 130 | 130 |
comment: "named volume", |
| 131 | 131 |
}, |
| 132 | 132 |
{
|
| 133 |
- mp: &volume.MountPoint{
|
|
| 133 |
+ mp: &volumemounts.MountPoint{
|
|
| 134 | 134 |
Type: mounttypes.TypeVolume, |
| 135 | 135 |
Destination: "/cherry", |
| 136 | 136 |
Name: "data", |
| ... | ... |
@@ -146,7 +146,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 146 | 146 |
comment: "read-only named volume", |
| 147 | 147 |
}, |
| 148 | 148 |
{
|
| 149 |
- mp: &volume.MountPoint{
|
|
| 149 |
+ mp: &volumemounts.MountPoint{
|
|
| 150 | 150 |
Type: mounttypes.TypeVolume, |
| 151 | 151 |
Destination: "/dates", |
| 152 | 152 |
Name: "data", |
| ... | ... |
@@ -162,7 +162,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 162 | 162 |
comment: "named volume with nocopy", |
| 163 | 163 |
}, |
| 164 | 164 |
{
|
| 165 |
- mp: &volume.MountPoint{
|
|
| 165 |
+ mp: &volumemounts.MountPoint{
|
|
| 166 | 166 |
Type: mounttypes.TypeVolume, |
| 167 | 167 |
Destination: "/elderberry", |
| 168 | 168 |
Name: "data", |
| ... | ... |
@@ -178,7 +178,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 178 | 178 |
comment: "masks an anonymous volume", |
| 179 | 179 |
}, |
| 180 | 180 |
{
|
| 181 |
- mp: &volume.MountPoint{
|
|
| 181 |
+ mp: &volumemounts.MountPoint{
|
|
| 182 | 182 |
Type: mounttypes.TypeBind, |
| 183 | 183 |
Destination: "/fig", |
| 184 | 184 |
Source: "/data", |
| ... | ... |
@@ -192,7 +192,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 192 | 192 |
comment: "bind mount with read/write", |
| 193 | 193 |
}, |
| 194 | 194 |
{
|
| 195 |
- mp: &volume.MountPoint{
|
|
| 195 |
+ mp: &volumemounts.MountPoint{
|
|
| 196 | 196 |
Type: mounttypes.TypeBind, |
| 197 | 197 |
Destination: "/guava", |
| 198 | 198 |
Source: "/data", |
| ... | ... |
@@ -209,7 +209,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 209 | 209 |
comment: "bind mount with read/write + shared propagation", |
| 210 | 210 |
}, |
| 211 | 211 |
{
|
| 212 |
- mp: &volume.MountPoint{
|
|
| 212 |
+ mp: &volumemounts.MountPoint{
|
|
| 213 | 213 |
Type: mounttypes.TypeVolume, |
| 214 | 214 |
Destination: "/honeydew", |
| 215 | 215 |
Source: "/var/lib/docker/volumes/data", |
| ... | ... |
@@ -229,7 +229,7 @@ func TestBackportMountSpec(t *testing.T) {
|
| 229 | 229 |
comment: "volume defined in mounts API", |
| 230 | 230 |
}, |
| 231 | 231 |
{
|
| 232 |
- mp: &volume.MountPoint{
|
|
| 232 |
+ mp: &volumemounts.MountPoint{
|
|
| 233 | 233 |
Type: mounttypes.TypeVolume, |
| 234 | 234 |
Destination: "/kumquat", |
| 235 | 235 |
Source: "/var/lib/docker/volumes/data", |
| ... | ... |
@@ -6,7 +6,7 @@ import ( |
| 6 | 6 |
"github.com/docker/docker/api/types/mount" |
| 7 | 7 |
"github.com/docker/docker/container" |
| 8 | 8 |
"github.com/docker/docker/pkg/idtools" |
| 9 |
- "github.com/docker/docker/volume" |
|
| 9 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 | 12 |
// setupMounts configures the mount points for a container by appending each |
| ... | ... |
@@ -20,7 +20,7 @@ import ( |
| 20 | 20 |
|
| 21 | 21 |
func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) {
|
| 22 | 22 |
var mnts []container.Mount |
| 23 |
- for _, mount := range c.MountPoints { // type is volume.MountPoint
|
|
| 23 |
+ for _, mount := range c.MountPoints { // type is volumemounts.MountPoint
|
|
| 24 | 24 |
if err := daemon.lazyInitializeVolume(c.ID, mount); err != nil {
|
| 25 | 25 |
return nil, err |
| 26 | 26 |
} |
| ... | ... |
@@ -42,7 +42,7 @@ func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, er |
| 42 | 42 |
|
| 43 | 43 |
// setBindModeIfNull is platform specific processing which is a no-op on |
| 44 | 44 |
// Windows. |
| 45 |
-func setBindModeIfNull(bind *volume.MountPoint) {
|
|
| 45 |
+func setBindModeIfNull(bind *volumemounts.MountPoint) {
|
|
| 46 | 46 |
return |
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 | 49 |
deleted file mode 100644 |
| ... | ... |
@@ -1,34 +0,0 @@ |
| 1 |
-package volume // import "github.com/docker/docker/volume" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
- "path" |
|
| 6 |
- |
|
| 7 |
- "github.com/docker/docker/api/types/mount" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-var lcowSpecificValidators mountValidator = func(m *mount.Mount) error {
|
|
| 11 |
- if path.Clean(m.Target) == "/" {
|
|
| 12 |
- return ErrVolumeTargetIsRoot |
|
| 13 |
- } |
|
| 14 |
- if m.Type == mount.TypeNamedPipe {
|
|
| 15 |
- return errors.New("Linux containers on Windows do not support named pipe mounts")
|
|
| 16 |
- } |
|
| 17 |
- return nil |
|
| 18 |
-} |
|
| 19 |
- |
|
| 20 |
-type lcowParser struct {
|
|
| 21 |
- windowsParser |
|
| 22 |
-} |
|
| 23 |
- |
|
| 24 |
-func (p *lcowParser) ValidateMountConfig(mnt *mount.Mount) error {
|
|
| 25 |
- return p.validateMountConfigReg(mnt, rxLCOWDestination, lcowSpecificValidators) |
|
| 26 |
-} |
|
| 27 |
- |
|
| 28 |
-func (p *lcowParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
|
| 29 |
- return p.parseMountRaw(raw, volumeDriver, rxLCOWDestination, false, lcowSpecificValidators) |
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 |
-func (p *lcowParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
|
|
| 33 |
- return p.parseMountSpec(cfg, rxLCOWDestination, false, lcowSpecificValidators) |
|
| 34 |
-} |
| 35 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,416 +0,0 @@ |
| 1 |
-package volume // import "github.com/docker/docker/volume" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
- "fmt" |
|
| 6 |
- "path" |
|
| 7 |
- "path/filepath" |
|
| 8 |
- "strings" |
|
| 9 |
- |
|
| 10 |
- "github.com/docker/docker/api/types/mount" |
|
| 11 |
- "github.com/docker/docker/pkg/stringid" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-type linuxParser struct {
|
|
| 15 |
-} |
|
| 16 |
- |
|
| 17 |
-func linuxSplitRawSpec(raw string) ([]string, error) {
|
|
| 18 |
- if strings.Count(raw, ":") > 2 {
|
|
| 19 |
- return nil, errInvalidSpec(raw) |
|
| 20 |
- } |
|
| 21 |
- |
|
| 22 |
- arr := strings.SplitN(raw, ":", 3) |
|
| 23 |
- if arr[0] == "" {
|
|
| 24 |
- return nil, errInvalidSpec(raw) |
|
| 25 |
- } |
|
| 26 |
- return arr, nil |
|
| 27 |
-} |
|
| 28 |
- |
|
| 29 |
-func linuxValidateNotRoot(p string) error {
|
|
| 30 |
- p = path.Clean(strings.Replace(p, `\`, `/`, -1)) |
|
| 31 |
- if p == "/" {
|
|
| 32 |
- return ErrVolumeTargetIsRoot |
|
| 33 |
- } |
|
| 34 |
- return nil |
|
| 35 |
-} |
|
| 36 |
-func linuxValidateAbsolute(p string) error {
|
|
| 37 |
- p = strings.Replace(p, `\`, `/`, -1) |
|
| 38 |
- if path.IsAbs(p) {
|
|
| 39 |
- return nil |
|
| 40 |
- } |
|
| 41 |
- return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
|
|
| 42 |
-} |
|
| 43 |
-func (p *linuxParser) ValidateMountConfig(mnt *mount.Mount) error {
|
|
| 44 |
- // there was something looking like a bug in existing codebase: |
|
| 45 |
- // - validateMountConfig on linux was called with options skipping bind source existence when calling ParseMountRaw |
|
| 46 |
- // - but not when calling ParseMountSpec directly... nor when the unit test called it directly |
|
| 47 |
- return p.validateMountConfigImpl(mnt, true) |
|
| 48 |
-} |
|
| 49 |
-func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error {
|
|
| 50 |
- if len(mnt.Target) == 0 {
|
|
| 51 |
- return &errMountConfig{mnt, errMissingField("Target")}
|
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- if err := linuxValidateNotRoot(mnt.Target); err != nil {
|
|
| 55 |
- return &errMountConfig{mnt, err}
|
|
| 56 |
- } |
|
| 57 |
- |
|
| 58 |
- if err := linuxValidateAbsolute(mnt.Target); err != nil {
|
|
| 59 |
- return &errMountConfig{mnt, err}
|
|
| 60 |
- } |
|
| 61 |
- |
|
| 62 |
- switch mnt.Type {
|
|
| 63 |
- case mount.TypeBind: |
|
| 64 |
- if len(mnt.Source) == 0 {
|
|
| 65 |
- return &errMountConfig{mnt, errMissingField("Source")}
|
|
| 66 |
- } |
|
| 67 |
- // Don't error out just because the propagation mode is not supported on the platform |
|
| 68 |
- if opts := mnt.BindOptions; opts != nil {
|
|
| 69 |
- if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 {
|
|
| 70 |
- if _, ok := linuxPropagationModes[opts.Propagation]; !ok {
|
|
| 71 |
- return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
|
|
| 72 |
- } |
|
| 73 |
- } |
|
| 74 |
- } |
|
| 75 |
- if mnt.VolumeOptions != nil {
|
|
| 76 |
- return &errMountConfig{mnt, errExtraField("VolumeOptions")}
|
|
| 77 |
- } |
|
| 78 |
- |
|
| 79 |
- if err := linuxValidateAbsolute(mnt.Source); err != nil {
|
|
| 80 |
- return &errMountConfig{mnt, err}
|
|
| 81 |
- } |
|
| 82 |
- |
|
| 83 |
- if validateBindSourceExists {
|
|
| 84 |
- exists, _, _ := currentFileInfoProvider.fileInfo(mnt.Source) |
|
| 85 |
- if !exists {
|
|
| 86 |
- return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
|
|
| 87 |
- } |
|
| 88 |
- } |
|
| 89 |
- |
|
| 90 |
- case mount.TypeVolume: |
|
| 91 |
- if mnt.BindOptions != nil {
|
|
| 92 |
- return &errMountConfig{mnt, errExtraField("BindOptions")}
|
|
| 93 |
- } |
|
| 94 |
- |
|
| 95 |
- if len(mnt.Source) == 0 && mnt.ReadOnly {
|
|
| 96 |
- return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
|
|
| 97 |
- } |
|
| 98 |
- case mount.TypeTmpfs: |
|
| 99 |
- if len(mnt.Source) != 0 {
|
|
| 100 |
- return &errMountConfig{mnt, errExtraField("Source")}
|
|
| 101 |
- } |
|
| 102 |
- if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
|
|
| 103 |
- return &errMountConfig{mnt, err}
|
|
| 104 |
- } |
|
| 105 |
- default: |
|
| 106 |
- return &errMountConfig{mnt, errors.New("mount type unknown")}
|
|
| 107 |
- } |
|
| 108 |
- return nil |
|
| 109 |
-} |
|
| 110 |
- |
|
| 111 |
-// read-write modes |
|
| 112 |
-var rwModes = map[string]bool{
|
|
| 113 |
- "rw": true, |
|
| 114 |
- "ro": true, |
|
| 115 |
-} |
|
| 116 |
- |
|
| 117 |
-// label modes |
|
| 118 |
-var linuxLabelModes = map[string]bool{
|
|
| 119 |
- "Z": true, |
|
| 120 |
- "z": true, |
|
| 121 |
-} |
|
| 122 |
- |
|
| 123 |
-// consistency modes |
|
| 124 |
-var linuxConsistencyModes = map[mount.Consistency]bool{
|
|
| 125 |
- mount.ConsistencyFull: true, |
|
| 126 |
- mount.ConsistencyCached: true, |
|
| 127 |
- mount.ConsistencyDelegated: true, |
|
| 128 |
-} |
|
| 129 |
-var linuxPropagationModes = map[mount.Propagation]bool{
|
|
| 130 |
- mount.PropagationPrivate: true, |
|
| 131 |
- mount.PropagationRPrivate: true, |
|
| 132 |
- mount.PropagationSlave: true, |
|
| 133 |
- mount.PropagationRSlave: true, |
|
| 134 |
- mount.PropagationShared: true, |
|
| 135 |
- mount.PropagationRShared: true, |
|
| 136 |
-} |
|
| 137 |
- |
|
| 138 |
-const linuxDefaultPropagationMode = mount.PropagationRPrivate |
|
| 139 |
- |
|
| 140 |
-func linuxGetPropagation(mode string) mount.Propagation {
|
|
| 141 |
- for _, o := range strings.Split(mode, ",") {
|
|
| 142 |
- prop := mount.Propagation(o) |
|
| 143 |
- if linuxPropagationModes[prop] {
|
|
| 144 |
- return prop |
|
| 145 |
- } |
|
| 146 |
- } |
|
| 147 |
- return linuxDefaultPropagationMode |
|
| 148 |
-} |
|
| 149 |
- |
|
| 150 |
-func linuxHasPropagation(mode string) bool {
|
|
| 151 |
- for _, o := range strings.Split(mode, ",") {
|
|
| 152 |
- if linuxPropagationModes[mount.Propagation(o)] {
|
|
| 153 |
- return true |
|
| 154 |
- } |
|
| 155 |
- } |
|
| 156 |
- return false |
|
| 157 |
-} |
|
| 158 |
- |
|
| 159 |
-func linuxValidMountMode(mode string) bool {
|
|
| 160 |
- if mode == "" {
|
|
| 161 |
- return true |
|
| 162 |
- } |
|
| 163 |
- |
|
| 164 |
- rwModeCount := 0 |
|
| 165 |
- labelModeCount := 0 |
|
| 166 |
- propagationModeCount := 0 |
|
| 167 |
- copyModeCount := 0 |
|
| 168 |
- consistencyModeCount := 0 |
|
| 169 |
- |
|
| 170 |
- for _, o := range strings.Split(mode, ",") {
|
|
| 171 |
- switch {
|
|
| 172 |
- case rwModes[o]: |
|
| 173 |
- rwModeCount++ |
|
| 174 |
- case linuxLabelModes[o]: |
|
| 175 |
- labelModeCount++ |
|
| 176 |
- case linuxPropagationModes[mount.Propagation(o)]: |
|
| 177 |
- propagationModeCount++ |
|
| 178 |
- case copyModeExists(o): |
|
| 179 |
- copyModeCount++ |
|
| 180 |
- case linuxConsistencyModes[mount.Consistency(o)]: |
|
| 181 |
- consistencyModeCount++ |
|
| 182 |
- default: |
|
| 183 |
- return false |
|
| 184 |
- } |
|
| 185 |
- } |
|
| 186 |
- |
|
| 187 |
- // Only one string for each mode is allowed. |
|
| 188 |
- if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
|
|
| 189 |
- return false |
|
| 190 |
- } |
|
| 191 |
- return true |
|
| 192 |
-} |
|
| 193 |
- |
|
| 194 |
-func (p *linuxParser) ReadWrite(mode string) bool {
|
|
| 195 |
- if !linuxValidMountMode(mode) {
|
|
| 196 |
- return false |
|
| 197 |
- } |
|
| 198 |
- |
|
| 199 |
- for _, o := range strings.Split(mode, ",") {
|
|
| 200 |
- if o == "ro" {
|
|
| 201 |
- return false |
|
| 202 |
- } |
|
| 203 |
- } |
|
| 204 |
- return true |
|
| 205 |
-} |
|
| 206 |
- |
|
| 207 |
-func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
|
| 208 |
- arr, err := linuxSplitRawSpec(raw) |
|
| 209 |
- if err != nil {
|
|
| 210 |
- return nil, err |
|
| 211 |
- } |
|
| 212 |
- |
|
| 213 |
- var spec mount.Mount |
|
| 214 |
- var mode string |
|
| 215 |
- switch len(arr) {
|
|
| 216 |
- case 1: |
|
| 217 |
- // Just a destination path in the container |
|
| 218 |
- spec.Target = arr[0] |
|
| 219 |
- case 2: |
|
| 220 |
- if linuxValidMountMode(arr[1]) {
|
|
| 221 |
- // Destination + Mode is not a valid volume - volumes |
|
| 222 |
- // cannot include a mode. e.g. /foo:rw |
|
| 223 |
- return nil, errInvalidSpec(raw) |
|
| 224 |
- } |
|
| 225 |
- // Host Source Path or Name + Destination |
|
| 226 |
- spec.Source = arr[0] |
|
| 227 |
- spec.Target = arr[1] |
|
| 228 |
- case 3: |
|
| 229 |
- // HostSourcePath+DestinationPath+Mode |
|
| 230 |
- spec.Source = arr[0] |
|
| 231 |
- spec.Target = arr[1] |
|
| 232 |
- mode = arr[2] |
|
| 233 |
- default: |
|
| 234 |
- return nil, errInvalidSpec(raw) |
|
| 235 |
- } |
|
| 236 |
- |
|
| 237 |
- if !linuxValidMountMode(mode) {
|
|
| 238 |
- return nil, errInvalidMode(mode) |
|
| 239 |
- } |
|
| 240 |
- |
|
| 241 |
- if path.IsAbs(spec.Source) {
|
|
| 242 |
- spec.Type = mount.TypeBind |
|
| 243 |
- } else {
|
|
| 244 |
- spec.Type = mount.TypeVolume |
|
| 245 |
- } |
|
| 246 |
- |
|
| 247 |
- spec.ReadOnly = !p.ReadWrite(mode) |
|
| 248 |
- |
|
| 249 |
- // cannot assume that if a volume driver is passed in that we should set it |
|
| 250 |
- if volumeDriver != "" && spec.Type == mount.TypeVolume {
|
|
| 251 |
- spec.VolumeOptions = &mount.VolumeOptions{
|
|
| 252 |
- DriverConfig: &mount.Driver{Name: volumeDriver},
|
|
| 253 |
- } |
|
| 254 |
- } |
|
| 255 |
- |
|
| 256 |
- if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
|
| 257 |
- if spec.VolumeOptions == nil {
|
|
| 258 |
- spec.VolumeOptions = &mount.VolumeOptions{}
|
|
| 259 |
- } |
|
| 260 |
- spec.VolumeOptions.NoCopy = !copyData |
|
| 261 |
- } |
|
| 262 |
- if linuxHasPropagation(mode) {
|
|
| 263 |
- spec.BindOptions = &mount.BindOptions{
|
|
| 264 |
- Propagation: linuxGetPropagation(mode), |
|
| 265 |
- } |
|
| 266 |
- } |
|
| 267 |
- |
|
| 268 |
- mp, err := p.parseMountSpec(spec, false) |
|
| 269 |
- if mp != nil {
|
|
| 270 |
- mp.Mode = mode |
|
| 271 |
- } |
|
| 272 |
- if err != nil {
|
|
| 273 |
- err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
|
|
| 274 |
- } |
|
| 275 |
- return mp, err |
|
| 276 |
-} |
|
| 277 |
-func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
|
|
| 278 |
- return p.parseMountSpec(cfg, true) |
|
| 279 |
-} |
|
| 280 |
-func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) {
|
|
| 281 |
- if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil {
|
|
| 282 |
- return nil, err |
|
| 283 |
- } |
|
| 284 |
- mp := &MountPoint{
|
|
| 285 |
- RW: !cfg.ReadOnly, |
|
| 286 |
- Destination: path.Clean(filepath.ToSlash(cfg.Target)), |
|
| 287 |
- Type: cfg.Type, |
|
| 288 |
- Spec: cfg, |
|
| 289 |
- } |
|
| 290 |
- |
|
| 291 |
- switch cfg.Type {
|
|
| 292 |
- case mount.TypeVolume: |
|
| 293 |
- if cfg.Source == "" {
|
|
| 294 |
- mp.Name = stringid.GenerateNonCryptoID() |
|
| 295 |
- } else {
|
|
| 296 |
- mp.Name = cfg.Source |
|
| 297 |
- } |
|
| 298 |
- mp.CopyData = p.DefaultCopyMode() |
|
| 299 |
- |
|
| 300 |
- if cfg.VolumeOptions != nil {
|
|
| 301 |
- if cfg.VolumeOptions.DriverConfig != nil {
|
|
| 302 |
- mp.Driver = cfg.VolumeOptions.DriverConfig.Name |
|
| 303 |
- } |
|
| 304 |
- if cfg.VolumeOptions.NoCopy {
|
|
| 305 |
- mp.CopyData = false |
|
| 306 |
- } |
|
| 307 |
- } |
|
| 308 |
- case mount.TypeBind: |
|
| 309 |
- mp.Source = path.Clean(filepath.ToSlash(cfg.Source)) |
|
| 310 |
- if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
|
|
| 311 |
- mp.Propagation = cfg.BindOptions.Propagation |
|
| 312 |
- } else {
|
|
| 313 |
- // If user did not specify a propagation mode, get |
|
| 314 |
- // default propagation mode. |
|
| 315 |
- mp.Propagation = linuxDefaultPropagationMode |
|
| 316 |
- } |
|
| 317 |
- case mount.TypeTmpfs: |
|
| 318 |
- // NOP |
|
| 319 |
- } |
|
| 320 |
- return mp, nil |
|
| 321 |
-} |
|
| 322 |
- |
|
| 323 |
-func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) {
|
|
| 324 |
- if len(spec) == 0 {
|
|
| 325 |
- return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
|
|
| 326 |
- } |
|
| 327 |
- |
|
| 328 |
- specParts := strings.SplitN(spec, ":", 2) |
|
| 329 |
- id := specParts[0] |
|
| 330 |
- mode := "rw" |
|
| 331 |
- |
|
| 332 |
- if len(specParts) == 2 {
|
|
| 333 |
- mode = specParts[1] |
|
| 334 |
- if !linuxValidMountMode(mode) {
|
|
| 335 |
- return "", "", errInvalidMode(mode) |
|
| 336 |
- } |
|
| 337 |
- // For now don't allow propagation properties while importing |
|
| 338 |
- // volumes from data container. These volumes will inherit |
|
| 339 |
- // the same propagation property as of the original volume |
|
| 340 |
- // in data container. This probably can be relaxed in future. |
|
| 341 |
- if linuxHasPropagation(mode) {
|
|
| 342 |
- return "", "", errInvalidMode(mode) |
|
| 343 |
- } |
|
| 344 |
- // Do not allow copy modes on volumes-from |
|
| 345 |
- if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
|
| 346 |
- return "", "", errInvalidMode(mode) |
|
| 347 |
- } |
|
| 348 |
- } |
|
| 349 |
- return id, mode, nil |
|
| 350 |
-} |
|
| 351 |
- |
|
| 352 |
-func (p *linuxParser) DefaultPropagationMode() mount.Propagation {
|
|
| 353 |
- return linuxDefaultPropagationMode |
|
| 354 |
-} |
|
| 355 |
- |
|
| 356 |
-func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
|
|
| 357 |
- var rawOpts []string |
|
| 358 |
- if readOnly {
|
|
| 359 |
- rawOpts = append(rawOpts, "ro") |
|
| 360 |
- } |
|
| 361 |
- |
|
| 362 |
- if opt != nil && opt.Mode != 0 {
|
|
| 363 |
- rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
|
|
| 364 |
- } |
|
| 365 |
- |
|
| 366 |
- if opt != nil && opt.SizeBytes != 0 {
|
|
| 367 |
- // calculate suffix here, making this linux specific, but that is |
|
| 368 |
- // okay, since API is that way anyways. |
|
| 369 |
- |
|
| 370 |
- // we do this by finding the suffix that divides evenly into the |
|
| 371 |
- // value, returning the value itself, with no suffix, if it fails. |
|
| 372 |
- // |
|
| 373 |
- // For the most part, we don't enforce any semantic to this values. |
|
| 374 |
- // The operating system will usually align this and enforce minimum |
|
| 375 |
- // and maximums. |
|
| 376 |
- var ( |
|
| 377 |
- size = opt.SizeBytes |
|
| 378 |
- suffix string |
|
| 379 |
- ) |
|
| 380 |
- for _, r := range []struct {
|
|
| 381 |
- suffix string |
|
| 382 |
- divisor int64 |
|
| 383 |
- }{
|
|
| 384 |
- {"g", 1 << 30},
|
|
| 385 |
- {"m", 1 << 20},
|
|
| 386 |
- {"k", 1 << 10},
|
|
| 387 |
- } {
|
|
| 388 |
- if size%r.divisor == 0 {
|
|
| 389 |
- size = size / r.divisor |
|
| 390 |
- suffix = r.suffix |
|
| 391 |
- break |
|
| 392 |
- } |
|
| 393 |
- } |
|
| 394 |
- |
|
| 395 |
- rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
|
|
| 396 |
- } |
|
| 397 |
- return strings.Join(rawOpts, ","), nil |
|
| 398 |
-} |
|
| 399 |
- |
|
| 400 |
-func (p *linuxParser) DefaultCopyMode() bool {
|
|
| 401 |
- return true |
|
| 402 |
-} |
|
| 403 |
-func (p *linuxParser) ValidateVolumeName(name string) error {
|
|
| 404 |
- return nil |
|
| 405 |
-} |
|
| 406 |
- |
|
| 407 |
-func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool {
|
|
| 408 |
- return len(m.Source) > 0 || m.Driver == DefaultDriverName |
|
| 409 |
-} |
|
| 410 |
- |
|
| 411 |
-func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error {
|
|
| 412 |
- if err := linuxValidateNotRoot(dest); err != nil {
|
|
| 413 |
- return err |
|
| 414 |
- } |
|
| 415 |
- return linuxValidateAbsolute(dest) |
|
| 416 |
-} |
| 417 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,34 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "path" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/api/types/mount" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+var lcowSpecificValidators mountValidator = func(m *mount.Mount) error {
|
|
| 10 |
+ if path.Clean(m.Target) == "/" {
|
|
| 11 |
+ return ErrVolumeTargetIsRoot |
|
| 12 |
+ } |
|
| 13 |
+ if m.Type == mount.TypeNamedPipe {
|
|
| 14 |
+ return errors.New("Linux containers on Windows do not support named pipe mounts")
|
|
| 15 |
+ } |
|
| 16 |
+ return nil |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+type lcowParser struct {
|
|
| 20 |
+ windowsParser |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func (p *lcowParser) ValidateMountConfig(mnt *mount.Mount) error {
|
|
| 24 |
+ return p.validateMountConfigReg(mnt, rxLCOWDestination, lcowSpecificValidators) |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+func (p *lcowParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
|
| 28 |
+ return p.parseMountRaw(raw, volumeDriver, rxLCOWDestination, false, lcowSpecificValidators) |
|
| 29 |
+} |
|
| 30 |
+ |
|
| 31 |
+func (p *lcowParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
|
|
| 32 |
+ return p.parseMountSpec(cfg, rxLCOWDestination, false, lcowSpecificValidators) |
|
| 33 |
+} |
| 0 | 34 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,417 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "path" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "strings" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/api/types/mount" |
|
| 10 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 11 |
+ "github.com/docker/docker/volume" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+type linuxParser struct {
|
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+func linuxSplitRawSpec(raw string) ([]string, error) {
|
|
| 18 |
+ if strings.Count(raw, ":") > 2 {
|
|
| 19 |
+ return nil, errInvalidSpec(raw) |
|
| 20 |
+ } |
|
| 21 |
+ |
|
| 22 |
+ arr := strings.SplitN(raw, ":", 3) |
|
| 23 |
+ if arr[0] == "" {
|
|
| 24 |
+ return nil, errInvalidSpec(raw) |
|
| 25 |
+ } |
|
| 26 |
+ return arr, nil |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func linuxValidateNotRoot(p string) error {
|
|
| 30 |
+ p = path.Clean(strings.Replace(p, `\`, `/`, -1)) |
|
| 31 |
+ if p == "/" {
|
|
| 32 |
+ return ErrVolumeTargetIsRoot |
|
| 33 |
+ } |
|
| 34 |
+ return nil |
|
| 35 |
+} |
|
| 36 |
+func linuxValidateAbsolute(p string) error {
|
|
| 37 |
+ p = strings.Replace(p, `\`, `/`, -1) |
|
| 38 |
+ if path.IsAbs(p) {
|
|
| 39 |
+ return nil |
|
| 40 |
+ } |
|
| 41 |
+ return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
|
|
| 42 |
+} |
|
| 43 |
+func (p *linuxParser) ValidateMountConfig(mnt *mount.Mount) error {
|
|
| 44 |
+ // there was something looking like a bug in existing codebase: |
|
| 45 |
+ // - validateMountConfig on linux was called with options skipping bind source existence when calling ParseMountRaw |
|
| 46 |
+ // - but not when calling ParseMountSpec directly... nor when the unit test called it directly |
|
| 47 |
+ return p.validateMountConfigImpl(mnt, true) |
|
| 48 |
+} |
|
| 49 |
+func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error {
|
|
| 50 |
+ if len(mnt.Target) == 0 {
|
|
| 51 |
+ return &errMountConfig{mnt, errMissingField("Target")}
|
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ if err := linuxValidateNotRoot(mnt.Target); err != nil {
|
|
| 55 |
+ return &errMountConfig{mnt, err}
|
|
| 56 |
+ } |
|
| 57 |
+ |
|
| 58 |
+ if err := linuxValidateAbsolute(mnt.Target); err != nil {
|
|
| 59 |
+ return &errMountConfig{mnt, err}
|
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ switch mnt.Type {
|
|
| 63 |
+ case mount.TypeBind: |
|
| 64 |
+ if len(mnt.Source) == 0 {
|
|
| 65 |
+ return &errMountConfig{mnt, errMissingField("Source")}
|
|
| 66 |
+ } |
|
| 67 |
+ // Don't error out just because the propagation mode is not supported on the platform |
|
| 68 |
+ if opts := mnt.BindOptions; opts != nil {
|
|
| 69 |
+ if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 {
|
|
| 70 |
+ if _, ok := linuxPropagationModes[opts.Propagation]; !ok {
|
|
| 71 |
+ return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
|
|
| 72 |
+ } |
|
| 73 |
+ } |
|
| 74 |
+ } |
|
| 75 |
+ if mnt.VolumeOptions != nil {
|
|
| 76 |
+ return &errMountConfig{mnt, errExtraField("VolumeOptions")}
|
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 79 |
+ if err := linuxValidateAbsolute(mnt.Source); err != nil {
|
|
| 80 |
+ return &errMountConfig{mnt, err}
|
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ if validateBindSourceExists {
|
|
| 84 |
+ exists, _, _ := currentFileInfoProvider.fileInfo(mnt.Source) |
|
| 85 |
+ if !exists {
|
|
| 86 |
+ return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
|
|
| 87 |
+ } |
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ case mount.TypeVolume: |
|
| 91 |
+ if mnt.BindOptions != nil {
|
|
| 92 |
+ return &errMountConfig{mnt, errExtraField("BindOptions")}
|
|
| 93 |
+ } |
|
| 94 |
+ |
|
| 95 |
+ if len(mnt.Source) == 0 && mnt.ReadOnly {
|
|
| 96 |
+ return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
|
|
| 97 |
+ } |
|
| 98 |
+ case mount.TypeTmpfs: |
|
| 99 |
+ if len(mnt.Source) != 0 {
|
|
| 100 |
+ return &errMountConfig{mnt, errExtraField("Source")}
|
|
| 101 |
+ } |
|
| 102 |
+ if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil {
|
|
| 103 |
+ return &errMountConfig{mnt, err}
|
|
| 104 |
+ } |
|
| 105 |
+ default: |
|
| 106 |
+ return &errMountConfig{mnt, errors.New("mount type unknown")}
|
|
| 107 |
+ } |
|
| 108 |
+ return nil |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 111 |
+// read-write modes |
|
| 112 |
+var rwModes = map[string]bool{
|
|
| 113 |
+ "rw": true, |
|
| 114 |
+ "ro": true, |
|
| 115 |
+} |
|
| 116 |
+ |
|
| 117 |
+// label modes |
|
| 118 |
+var linuxLabelModes = map[string]bool{
|
|
| 119 |
+ "Z": true, |
|
| 120 |
+ "z": true, |
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+// consistency modes |
|
| 124 |
+var linuxConsistencyModes = map[mount.Consistency]bool{
|
|
| 125 |
+ mount.ConsistencyFull: true, |
|
| 126 |
+ mount.ConsistencyCached: true, |
|
| 127 |
+ mount.ConsistencyDelegated: true, |
|
| 128 |
+} |
|
| 129 |
+var linuxPropagationModes = map[mount.Propagation]bool{
|
|
| 130 |
+ mount.PropagationPrivate: true, |
|
| 131 |
+ mount.PropagationRPrivate: true, |
|
| 132 |
+ mount.PropagationSlave: true, |
|
| 133 |
+ mount.PropagationRSlave: true, |
|
| 134 |
+ mount.PropagationShared: true, |
|
| 135 |
+ mount.PropagationRShared: true, |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+const linuxDefaultPropagationMode = mount.PropagationRPrivate |
|
| 139 |
+ |
|
| 140 |
+func linuxGetPropagation(mode string) mount.Propagation {
|
|
| 141 |
+ for _, o := range strings.Split(mode, ",") {
|
|
| 142 |
+ prop := mount.Propagation(o) |
|
| 143 |
+ if linuxPropagationModes[prop] {
|
|
| 144 |
+ return prop |
|
| 145 |
+ } |
|
| 146 |
+ } |
|
| 147 |
+ return linuxDefaultPropagationMode |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+func linuxHasPropagation(mode string) bool {
|
|
| 151 |
+ for _, o := range strings.Split(mode, ",") {
|
|
| 152 |
+ if linuxPropagationModes[mount.Propagation(o)] {
|
|
| 153 |
+ return true |
|
| 154 |
+ } |
|
| 155 |
+ } |
|
| 156 |
+ return false |
|
| 157 |
+} |
|
| 158 |
+ |
|
| 159 |
+func linuxValidMountMode(mode string) bool {
|
|
| 160 |
+ if mode == "" {
|
|
| 161 |
+ return true |
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ rwModeCount := 0 |
|
| 165 |
+ labelModeCount := 0 |
|
| 166 |
+ propagationModeCount := 0 |
|
| 167 |
+ copyModeCount := 0 |
|
| 168 |
+ consistencyModeCount := 0 |
|
| 169 |
+ |
|
| 170 |
+ for _, o := range strings.Split(mode, ",") {
|
|
| 171 |
+ switch {
|
|
| 172 |
+ case rwModes[o]: |
|
| 173 |
+ rwModeCount++ |
|
| 174 |
+ case linuxLabelModes[o]: |
|
| 175 |
+ labelModeCount++ |
|
| 176 |
+ case linuxPropagationModes[mount.Propagation(o)]: |
|
| 177 |
+ propagationModeCount++ |
|
| 178 |
+ case copyModeExists(o): |
|
| 179 |
+ copyModeCount++ |
|
| 180 |
+ case linuxConsistencyModes[mount.Consistency(o)]: |
|
| 181 |
+ consistencyModeCount++ |
|
| 182 |
+ default: |
|
| 183 |
+ return false |
|
| 184 |
+ } |
|
| 185 |
+ } |
|
| 186 |
+ |
|
| 187 |
+ // Only one string for each mode is allowed. |
|
| 188 |
+ if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 {
|
|
| 189 |
+ return false |
|
| 190 |
+ } |
|
| 191 |
+ return true |
|
| 192 |
+} |
|
| 193 |
+ |
|
| 194 |
+func (p *linuxParser) ReadWrite(mode string) bool {
|
|
| 195 |
+ if !linuxValidMountMode(mode) {
|
|
| 196 |
+ return false |
|
| 197 |
+ } |
|
| 198 |
+ |
|
| 199 |
+ for _, o := range strings.Split(mode, ",") {
|
|
| 200 |
+ if o == "ro" {
|
|
| 201 |
+ return false |
|
| 202 |
+ } |
|
| 203 |
+ } |
|
| 204 |
+ return true |
|
| 205 |
+} |
|
| 206 |
+ |
|
| 207 |
+func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
|
| 208 |
+ arr, err := linuxSplitRawSpec(raw) |
|
| 209 |
+ if err != nil {
|
|
| 210 |
+ return nil, err |
|
| 211 |
+ } |
|
| 212 |
+ |
|
| 213 |
+ var spec mount.Mount |
|
| 214 |
+ var mode string |
|
| 215 |
+ switch len(arr) {
|
|
| 216 |
+ case 1: |
|
| 217 |
+ // Just a destination path in the container |
|
| 218 |
+ spec.Target = arr[0] |
|
| 219 |
+ case 2: |
|
| 220 |
+ if linuxValidMountMode(arr[1]) {
|
|
| 221 |
+ // Destination + Mode is not a valid volume - volumes |
|
| 222 |
+ // cannot include a mode. e.g. /foo:rw |
|
| 223 |
+ return nil, errInvalidSpec(raw) |
|
| 224 |
+ } |
|
| 225 |
+ // Host Source Path or Name + Destination |
|
| 226 |
+ spec.Source = arr[0] |
|
| 227 |
+ spec.Target = arr[1] |
|
| 228 |
+ case 3: |
|
| 229 |
+ // HostSourcePath+DestinationPath+Mode |
|
| 230 |
+ spec.Source = arr[0] |
|
| 231 |
+ spec.Target = arr[1] |
|
| 232 |
+ mode = arr[2] |
|
| 233 |
+ default: |
|
| 234 |
+ return nil, errInvalidSpec(raw) |
|
| 235 |
+ } |
|
| 236 |
+ |
|
| 237 |
+ if !linuxValidMountMode(mode) {
|
|
| 238 |
+ return nil, errInvalidMode(mode) |
|
| 239 |
+ } |
|
| 240 |
+ |
|
| 241 |
+ if path.IsAbs(spec.Source) {
|
|
| 242 |
+ spec.Type = mount.TypeBind |
|
| 243 |
+ } else {
|
|
| 244 |
+ spec.Type = mount.TypeVolume |
|
| 245 |
+ } |
|
| 246 |
+ |
|
| 247 |
+ spec.ReadOnly = !p.ReadWrite(mode) |
|
| 248 |
+ |
|
| 249 |
+ // cannot assume that if a volume driver is passed in that we should set it |
|
| 250 |
+ if volumeDriver != "" && spec.Type == mount.TypeVolume {
|
|
| 251 |
+ spec.VolumeOptions = &mount.VolumeOptions{
|
|
| 252 |
+ DriverConfig: &mount.Driver{Name: volumeDriver},
|
|
| 253 |
+ } |
|
| 254 |
+ } |
|
| 255 |
+ |
|
| 256 |
+ if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
|
| 257 |
+ if spec.VolumeOptions == nil {
|
|
| 258 |
+ spec.VolumeOptions = &mount.VolumeOptions{}
|
|
| 259 |
+ } |
|
| 260 |
+ spec.VolumeOptions.NoCopy = !copyData |
|
| 261 |
+ } |
|
| 262 |
+ if linuxHasPropagation(mode) {
|
|
| 263 |
+ spec.BindOptions = &mount.BindOptions{
|
|
| 264 |
+ Propagation: linuxGetPropagation(mode), |
|
| 265 |
+ } |
|
| 266 |
+ } |
|
| 267 |
+ |
|
| 268 |
+ mp, err := p.parseMountSpec(spec, false) |
|
| 269 |
+ if mp != nil {
|
|
| 270 |
+ mp.Mode = mode |
|
| 271 |
+ } |
|
| 272 |
+ if err != nil {
|
|
| 273 |
+ err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
|
|
| 274 |
+ } |
|
| 275 |
+ return mp, err |
|
| 276 |
+} |
|
| 277 |
+func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
|
|
| 278 |
+ return p.parseMountSpec(cfg, true) |
|
| 279 |
+} |
|
| 280 |
+func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) {
|
|
| 281 |
+ if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil {
|
|
| 282 |
+ return nil, err |
|
| 283 |
+ } |
|
| 284 |
+ mp := &MountPoint{
|
|
| 285 |
+ RW: !cfg.ReadOnly, |
|
| 286 |
+ Destination: path.Clean(filepath.ToSlash(cfg.Target)), |
|
| 287 |
+ Type: cfg.Type, |
|
| 288 |
+ Spec: cfg, |
|
| 289 |
+ } |
|
| 290 |
+ |
|
| 291 |
+ switch cfg.Type {
|
|
| 292 |
+ case mount.TypeVolume: |
|
| 293 |
+ if cfg.Source == "" {
|
|
| 294 |
+ mp.Name = stringid.GenerateNonCryptoID() |
|
| 295 |
+ } else {
|
|
| 296 |
+ mp.Name = cfg.Source |
|
| 297 |
+ } |
|
| 298 |
+ mp.CopyData = p.DefaultCopyMode() |
|
| 299 |
+ |
|
| 300 |
+ if cfg.VolumeOptions != nil {
|
|
| 301 |
+ if cfg.VolumeOptions.DriverConfig != nil {
|
|
| 302 |
+ mp.Driver = cfg.VolumeOptions.DriverConfig.Name |
|
| 303 |
+ } |
|
| 304 |
+ if cfg.VolumeOptions.NoCopy {
|
|
| 305 |
+ mp.CopyData = false |
|
| 306 |
+ } |
|
| 307 |
+ } |
|
| 308 |
+ case mount.TypeBind: |
|
| 309 |
+ mp.Source = path.Clean(filepath.ToSlash(cfg.Source)) |
|
| 310 |
+ if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
|
|
| 311 |
+ mp.Propagation = cfg.BindOptions.Propagation |
|
| 312 |
+ } else {
|
|
| 313 |
+ // If user did not specify a propagation mode, get |
|
| 314 |
+ // default propagation mode. |
|
| 315 |
+ mp.Propagation = linuxDefaultPropagationMode |
|
| 316 |
+ } |
|
| 317 |
+ case mount.TypeTmpfs: |
|
| 318 |
+ // NOP |
|
| 319 |
+ } |
|
| 320 |
+ return mp, nil |
|
| 321 |
+} |
|
| 322 |
+ |
|
| 323 |
+func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) {
|
|
| 324 |
+ if len(spec) == 0 {
|
|
| 325 |
+ return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
|
|
| 326 |
+ } |
|
| 327 |
+ |
|
| 328 |
+ specParts := strings.SplitN(spec, ":", 2) |
|
| 329 |
+ id := specParts[0] |
|
| 330 |
+ mode := "rw" |
|
| 331 |
+ |
|
| 332 |
+ if len(specParts) == 2 {
|
|
| 333 |
+ mode = specParts[1] |
|
| 334 |
+ if !linuxValidMountMode(mode) {
|
|
| 335 |
+ return "", "", errInvalidMode(mode) |
|
| 336 |
+ } |
|
| 337 |
+ // For now don't allow propagation properties while importing |
|
| 338 |
+ // volumes from data container. These volumes will inherit |
|
| 339 |
+ // the same propagation property as of the original volume |
|
| 340 |
+ // in data container. This probably can be relaxed in future. |
|
| 341 |
+ if linuxHasPropagation(mode) {
|
|
| 342 |
+ return "", "", errInvalidMode(mode) |
|
| 343 |
+ } |
|
| 344 |
+ // Do not allow copy modes on volumes-from |
|
| 345 |
+ if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
|
| 346 |
+ return "", "", errInvalidMode(mode) |
|
| 347 |
+ } |
|
| 348 |
+ } |
|
| 349 |
+ return id, mode, nil |
|
| 350 |
+} |
|
| 351 |
+ |
|
| 352 |
+func (p *linuxParser) DefaultPropagationMode() mount.Propagation {
|
|
| 353 |
+ return linuxDefaultPropagationMode |
|
| 354 |
+} |
|
| 355 |
+ |
|
| 356 |
+func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
|
|
| 357 |
+ var rawOpts []string |
|
| 358 |
+ if readOnly {
|
|
| 359 |
+ rawOpts = append(rawOpts, "ro") |
|
| 360 |
+ } |
|
| 361 |
+ |
|
| 362 |
+ if opt != nil && opt.Mode != 0 {
|
|
| 363 |
+ rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode))
|
|
| 364 |
+ } |
|
| 365 |
+ |
|
| 366 |
+ if opt != nil && opt.SizeBytes != 0 {
|
|
| 367 |
+ // calculate suffix here, making this linux specific, but that is |
|
| 368 |
+ // okay, since API is that way anyways. |
|
| 369 |
+ |
|
| 370 |
+ // we do this by finding the suffix that divides evenly into the |
|
| 371 |
+ // value, returning the value itself, with no suffix, if it fails. |
|
| 372 |
+ // |
|
| 373 |
+ // For the most part, we don't enforce any semantic to this values. |
|
| 374 |
+ // The operating system will usually align this and enforce minimum |
|
| 375 |
+ // and maximums. |
|
| 376 |
+ var ( |
|
| 377 |
+ size = opt.SizeBytes |
|
| 378 |
+ suffix string |
|
| 379 |
+ ) |
|
| 380 |
+ for _, r := range []struct {
|
|
| 381 |
+ suffix string |
|
| 382 |
+ divisor int64 |
|
| 383 |
+ }{
|
|
| 384 |
+ {"g", 1 << 30},
|
|
| 385 |
+ {"m", 1 << 20},
|
|
| 386 |
+ {"k", 1 << 10},
|
|
| 387 |
+ } {
|
|
| 388 |
+ if size%r.divisor == 0 {
|
|
| 389 |
+ size = size / r.divisor |
|
| 390 |
+ suffix = r.suffix |
|
| 391 |
+ break |
|
| 392 |
+ } |
|
| 393 |
+ } |
|
| 394 |
+ |
|
| 395 |
+ rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix))
|
|
| 396 |
+ } |
|
| 397 |
+ return strings.Join(rawOpts, ","), nil |
|
| 398 |
+} |
|
| 399 |
+ |
|
| 400 |
+func (p *linuxParser) DefaultCopyMode() bool {
|
|
| 401 |
+ return true |
|
| 402 |
+} |
|
| 403 |
+func (p *linuxParser) ValidateVolumeName(name string) error {
|
|
| 404 |
+ return nil |
|
| 405 |
+} |
|
| 406 |
+ |
|
| 407 |
+func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool {
|
|
| 408 |
+ return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName |
|
| 409 |
+} |
|
| 410 |
+ |
|
| 411 |
+func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error {
|
|
| 412 |
+ if err := linuxValidateNotRoot(dest); err != nil {
|
|
| 413 |
+ return err |
|
| 414 |
+ } |
|
| 415 |
+ return linuxValidateAbsolute(dest) |
|
| 416 |
+} |
| 0 | 417 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,170 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "syscall" |
|
| 7 |
+ |
|
| 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/docker/docker/volume" |
|
| 12 |
+ "github.com/opencontainers/selinux/go-selinux/label" |
|
| 13 |
+ "github.com/pkg/errors" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+// MountPoint is the intersection point between a volume and a container. It |
|
| 17 |
+// specifies which volume is to be used and where inside a container it should |
|
| 18 |
+// be mounted. |
|
| 19 |
+// |
|
| 20 |
+// Note that this type is embedded in `container.Container` object and persisted to disk. |
|
| 21 |
+// Changes to this struct need to by synced with on disk state. |
|
| 22 |
+type MountPoint struct {
|
|
| 23 |
+ // Source is the source path of the mount. |
|
| 24 |
+ // E.g. `mount --bind /foo /bar`, `/foo` is the `Source`. |
|
| 25 |
+ Source string |
|
| 26 |
+ // Destination is the path relative to the container root (`/`) to the mount point |
|
| 27 |
+ // It is where the `Source` is mounted to |
|
| 28 |
+ Destination string |
|
| 29 |
+ // RW is set to true when the mountpoint should be mounted as read-write |
|
| 30 |
+ RW bool |
|
| 31 |
+ // Name is the name reference to the underlying data defined by `Source` |
|
| 32 |
+ // e.g., the volume name |
|
| 33 |
+ Name string |
|
| 34 |
+ // Driver is the volume driver used to create the volume (if it is a volume) |
|
| 35 |
+ Driver string |
|
| 36 |
+ // Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount |
|
| 37 |
+ Type mounttypes.Type `json:",omitempty"` |
|
| 38 |
+ // Volume is the volume providing data to this mountpoint. |
|
| 39 |
+ // This is nil unless `Type` is set to `TypeVolume` |
|
| 40 |
+ Volume volume.Volume `json:"-"` |
|
| 41 |
+ |
|
| 42 |
+ // Mode is the comma separated list of options supplied by the user when creating |
|
| 43 |
+ // the bind/volume mount. |
|
| 44 |
+ // Note Mode is not used on Windows |
|
| 45 |
+ Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`" |
|
| 46 |
+ |
|
| 47 |
+ // Propagation describes how the mounts are propagated from the host into the |
|
| 48 |
+ // mount point, and vice-versa. |
|
| 49 |
+ // See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt |
|
| 50 |
+ // Note Propagation is not used on Windows |
|
| 51 |
+ Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string |
|
| 52 |
+ |
|
| 53 |
+ // Specifies if data should be copied from the container before the first mount |
|
| 54 |
+ // Use a pointer here so we can tell if the user set this value explicitly |
|
| 55 |
+ // This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated |
|
| 56 |
+ CopyData bool `json:"-"` |
|
| 57 |
+ // ID is the opaque ID used to pass to the volume driver. |
|
| 58 |
+ // This should be set by calls to `Mount` and unset by calls to `Unmount` |
|
| 59 |
+ ID string `json:",omitempty"` |
|
| 60 |
+ |
|
| 61 |
+ // Sepc is a copy of the API request that created this mount. |
|
| 62 |
+ Spec mounttypes.Mount |
|
| 63 |
+ |
|
| 64 |
+ // Track usage of this mountpoint |
|
| 65 |
+ // Specifically needed for containers which are running and calls to `docker cp` |
|
| 66 |
+ // because both these actions require mounting the volumes. |
|
| 67 |
+ active int |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+// Cleanup frees resources used by the mountpoint |
|
| 71 |
+func (m *MountPoint) Cleanup() error {
|
|
| 72 |
+ if m.Volume == nil || m.ID == "" {
|
|
| 73 |
+ return nil |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ if err := m.Volume.Unmount(m.ID); err != nil {
|
|
| 77 |
+ return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name()) |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ m.active-- |
|
| 81 |
+ if m.active == 0 {
|
|
| 82 |
+ m.ID = "" |
|
| 83 |
+ } |
|
| 84 |
+ return nil |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+// Setup sets up a mount point by either mounting the volume if it is |
|
| 88 |
+// configured, or creating the source directory if supplied. |
|
| 89 |
+// The, optional, checkFun parameter allows doing additional checking |
|
| 90 |
+// before creating the source directory on the host. |
|
| 91 |
+func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) {
|
|
| 92 |
+ defer func() {
|
|
| 93 |
+ if err != nil || !label.RelabelNeeded(m.Mode) {
|
|
| 94 |
+ return |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ var sourcePath string |
|
| 98 |
+ sourcePath, err = filepath.EvalSymlinks(m.Source) |
|
| 99 |
+ if err != nil {
|
|
| 100 |
+ path = "" |
|
| 101 |
+ err = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source) |
|
| 102 |
+ return |
|
| 103 |
+ } |
|
| 104 |
+ err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode)) |
|
| 105 |
+ if err == syscall.ENOTSUP {
|
|
| 106 |
+ err = nil |
|
| 107 |
+ } |
|
| 108 |
+ if err != nil {
|
|
| 109 |
+ path = "" |
|
| 110 |
+ err = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath) |
|
| 111 |
+ } |
|
| 112 |
+ }() |
|
| 113 |
+ |
|
| 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 |
+ |
|
| 124 |
+ m.ID = id |
|
| 125 |
+ m.active++ |
|
| 126 |
+ return path, nil |
|
| 127 |
+ } |
|
| 128 |
+ |
|
| 129 |
+ if len(m.Source) == 0 {
|
|
| 130 |
+ return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
|
| 131 |
+ } |
|
| 132 |
+ |
|
| 133 |
+ if m.Type == mounttypes.TypeBind {
|
|
| 134 |
+ // Before creating the source directory on the host, invoke checkFun if it's not nil. One of |
|
| 135 |
+ // the use case is to forbid creating the daemon socket as a directory if the daemon is in |
|
| 136 |
+ // the process of shutting down. |
|
| 137 |
+ if checkFun != nil {
|
|
| 138 |
+ if err := checkFun(m); err != nil {
|
|
| 139 |
+ return "", err |
|
| 140 |
+ } |
|
| 141 |
+ } |
|
| 142 |
+ // idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory) |
|
| 143 |
+ // also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it |
|
| 144 |
+ if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil {
|
|
| 145 |
+ if perr, ok := err.(*os.PathError); ok {
|
|
| 146 |
+ if perr.Err != syscall.ENOTDIR {
|
|
| 147 |
+ return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source) |
|
| 148 |
+ } |
|
| 149 |
+ } |
|
| 150 |
+ } |
|
| 151 |
+ } |
|
| 152 |
+ return m.Source, nil |
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+// Path returns the path of a volume in a mount point. |
|
| 156 |
+func (m *MountPoint) Path() string {
|
|
| 157 |
+ if m.Volume != nil {
|
|
| 158 |
+ return m.Volume.Path() |
|
| 159 |
+ } |
|
| 160 |
+ return m.Source |
|
| 161 |
+} |
|
| 162 |
+ |
|
| 163 |
+func errInvalidMode(mode string) error {
|
|
| 164 |
+ return errors.Errorf("invalid mode: %v", mode)
|
|
| 165 |
+} |
|
| 166 |
+ |
|
| 167 |
+func errInvalidSpec(spec string) error {
|
|
| 168 |
+ return errors.Errorf("invalid volume specification: '%s'", spec)
|
|
| 169 |
+} |
| 0 | 170 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,47 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "runtime" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/api/types/mount" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+const ( |
|
| 10 |
+ // OSLinux is the same as runtime.GOOS on linux |
|
| 11 |
+ OSLinux = "linux" |
|
| 12 |
+ // OSWindows is the same as runtime.GOOS on windows |
|
| 13 |
+ OSWindows = "windows" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+// ErrVolumeTargetIsRoot is returned when the target destination is root. |
|
| 17 |
+// It's used by both LCOW and Linux parsers. |
|
| 18 |
+var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")
|
|
| 19 |
+ |
|
| 20 |
+// Parser represents a platform specific parser for mount expressions |
|
| 21 |
+type Parser interface {
|
|
| 22 |
+ ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) |
|
| 23 |
+ ParseMountSpec(cfg mount.Mount) (*MountPoint, error) |
|
| 24 |
+ ParseVolumesFrom(spec string) (string, string, error) |
|
| 25 |
+ DefaultPropagationMode() mount.Propagation |
|
| 26 |
+ ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) |
|
| 27 |
+ DefaultCopyMode() bool |
|
| 28 |
+ ValidateVolumeName(name string) error |
|
| 29 |
+ ReadWrite(mode string) bool |
|
| 30 |
+ IsBackwardCompatible(m *MountPoint) bool |
|
| 31 |
+ HasResource(m *MountPoint, absPath string) bool |
|
| 32 |
+ ValidateTmpfsMountDestination(dest string) error |
|
| 33 |
+ ValidateMountConfig(mt *mount.Mount) error |
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+// NewParser creates a parser for a given container OS, depending on the current host OS (linux on a windows host will resolve to an lcowParser) |
|
| 37 |
+func NewParser(containerOS string) Parser {
|
|
| 38 |
+ switch containerOS {
|
|
| 39 |
+ case OSWindows: |
|
| 40 |
+ return &windowsParser{}
|
|
| 41 |
+ } |
|
| 42 |
+ if runtime.GOOS == OSWindows {
|
|
| 43 |
+ return &lcowParser{}
|
|
| 44 |
+ } |
|
| 45 |
+ return &linuxParser{}
|
|
| 46 |
+} |
| 0 | 47 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,480 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "os" |
|
| 5 |
+ "runtime" |
|
| 6 |
+ "strings" |
|
| 7 |
+ "testing" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/api/types/mount" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+type parseMountRawTestSet struct {
|
|
| 13 |
+ valid []string |
|
| 14 |
+ invalid map[string]string |
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+func TestConvertTmpfsOptions(t *testing.T) {
|
|
| 18 |
+ type testCase struct {
|
|
| 19 |
+ opt mount.TmpfsOptions |
|
| 20 |
+ readOnly bool |
|
| 21 |
+ expectedSubstrings []string |
|
| 22 |
+ unexpectedSubstrings []string |
|
| 23 |
+ } |
|
| 24 |
+ cases := []testCase{
|
|
| 25 |
+ {
|
|
| 26 |
+ opt: mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700},
|
|
| 27 |
+ readOnly: false, |
|
| 28 |
+ expectedSubstrings: []string{"size=1m", "mode=700"},
|
|
| 29 |
+ unexpectedSubstrings: []string{"ro"},
|
|
| 30 |
+ }, |
|
| 31 |
+ {
|
|
| 32 |
+ opt: mount.TmpfsOptions{},
|
|
| 33 |
+ readOnly: true, |
|
| 34 |
+ expectedSubstrings: []string{"ro"},
|
|
| 35 |
+ unexpectedSubstrings: []string{},
|
|
| 36 |
+ }, |
|
| 37 |
+ } |
|
| 38 |
+ p := &linuxParser{}
|
|
| 39 |
+ for _, c := range cases {
|
|
| 40 |
+ data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly) |
|
| 41 |
+ if err != nil {
|
|
| 42 |
+ t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
|
|
| 43 |
+ c.opt, c.readOnly, err) |
|
| 44 |
+ } |
|
| 45 |
+ t.Logf("data=%q", data)
|
|
| 46 |
+ for _, s := range c.expectedSubstrings {
|
|
| 47 |
+ if !strings.Contains(data, s) {
|
|
| 48 |
+ t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
|
|
| 49 |
+ } |
|
| 50 |
+ } |
|
| 51 |
+ for _, s := range c.unexpectedSubstrings {
|
|
| 52 |
+ if strings.Contains(data, s) {
|
|
| 53 |
+ t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
|
|
| 54 |
+ } |
|
| 55 |
+ } |
|
| 56 |
+ } |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+type mockFiProvider struct{}
|
|
| 60 |
+ |
|
| 61 |
+func (mockFiProvider) fileInfo(path string) (exists, isDir bool, err error) {
|
|
| 62 |
+ dirs := map[string]struct{}{
|
|
| 63 |
+ `c:\`: {},
|
|
| 64 |
+ `c:\windows\`: {},
|
|
| 65 |
+ `c:\windows`: {},
|
|
| 66 |
+ `c:\program files`: {},
|
|
| 67 |
+ `c:\Windows`: {},
|
|
| 68 |
+ `c:\Program Files (x86)`: {},
|
|
| 69 |
+ `\\?\c:\windows\`: {},
|
|
| 70 |
+ } |
|
| 71 |
+ files := map[string]struct{}{
|
|
| 72 |
+ `c:\windows\system32\ntdll.dll`: {},
|
|
| 73 |
+ } |
|
| 74 |
+ if _, ok := dirs[path]; ok {
|
|
| 75 |
+ return true, true, nil |
|
| 76 |
+ } |
|
| 77 |
+ if _, ok := files[path]; ok {
|
|
| 78 |
+ return true, false, nil |
|
| 79 |
+ } |
|
| 80 |
+ return false, false, nil |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+func TestParseMountRaw(t *testing.T) {
|
|
| 84 |
+ |
|
| 85 |
+ previousProvider := currentFileInfoProvider |
|
| 86 |
+ defer func() { currentFileInfoProvider = previousProvider }()
|
|
| 87 |
+ currentFileInfoProvider = mockFiProvider{}
|
|
| 88 |
+ windowsSet := parseMountRawTestSet{
|
|
| 89 |
+ valid: []string{
|
|
| 90 |
+ `d:\`, |
|
| 91 |
+ `d:`, |
|
| 92 |
+ `d:\path`, |
|
| 93 |
+ `d:\path with space`, |
|
| 94 |
+ `c:\:d:\`, |
|
| 95 |
+ `c:\windows\:d:`, |
|
| 96 |
+ `c:\windows:d:\s p a c e`, |
|
| 97 |
+ `c:\windows:d:\s p a c e:RW`, |
|
| 98 |
+ `c:\program files:d:\s p a c e i n h o s t d i r`, |
|
| 99 |
+ `0123456789name:d:`, |
|
| 100 |
+ `MiXeDcAsEnAmE:d:`, |
|
| 101 |
+ `name:D:`, |
|
| 102 |
+ `name:D::rW`, |
|
| 103 |
+ `name:D::RW`, |
|
| 104 |
+ `name:D::RO`, |
|
| 105 |
+ `c:/:d:/forward/slashes/are/good/too`, |
|
| 106 |
+ `c:/:d:/including with/spaces:ro`, |
|
| 107 |
+ `c:\Windows`, // With capital |
|
| 108 |
+ `c:\Program Files (x86)`, // With capitals and brackets |
|
| 109 |
+ `\\?\c:\windows\:d:`, // Long path handling (source) |
|
| 110 |
+ `c:\windows\:\\?\d:\`, // Long path handling (target) |
|
| 111 |
+ `\\.\pipe\foo:\\.\pipe\foo`, // named pipe |
|
| 112 |
+ `//./pipe/foo://./pipe/foo`, // named pipe forward slashes |
|
| 113 |
+ }, |
|
| 114 |
+ invalid: map[string]string{
|
|
| 115 |
+ ``: "invalid volume specification: ", |
|
| 116 |
+ `.`: "invalid volume specification: ", |
|
| 117 |
+ `..\`: "invalid volume specification: ", |
|
| 118 |
+ `c:\:..\`: "invalid volume specification: ", |
|
| 119 |
+ `c:\:d:\:xyzzy`: "invalid volume specification: ", |
|
| 120 |
+ `c:`: "cannot be `c:`", |
|
| 121 |
+ `c:\`: "cannot be `c:`", |
|
| 122 |
+ `c:\notexist:d:`: `bind mount source path does not exist: c:\notexist`, |
|
| 123 |
+ `c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`, |
|
| 124 |
+ `name<:d:`: `invalid volume specification`, |
|
| 125 |
+ `name>:d:`: `invalid volume specification`, |
|
| 126 |
+ `name::d:`: `invalid volume specification`, |
|
| 127 |
+ `name":d:`: `invalid volume specification`, |
|
| 128 |
+ `name\:d:`: `invalid volume specification`, |
|
| 129 |
+ `name*:d:`: `invalid volume specification`, |
|
| 130 |
+ `name|:d:`: `invalid volume specification`, |
|
| 131 |
+ `name?:d:`: `invalid volume specification`, |
|
| 132 |
+ `name/:d:`: `invalid volume specification`, |
|
| 133 |
+ `d:\pathandmode:rw`: `invalid volume specification`, |
|
| 134 |
+ `d:\pathandmode:ro`: `invalid volume specification`, |
|
| 135 |
+ `con:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 136 |
+ `PRN:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 137 |
+ `aUx:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 138 |
+ `nul:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 139 |
+ `com1:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 140 |
+ `com2:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 141 |
+ `com3:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 142 |
+ `com4:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 143 |
+ `com5:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 144 |
+ `com6:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 145 |
+ `com7:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 146 |
+ `com8:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 147 |
+ `com9:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 148 |
+ `lpt1:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 149 |
+ `lpt2:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 150 |
+ `lpt3:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 151 |
+ `lpt4:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 152 |
+ `lpt5:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 153 |
+ `lpt6:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 154 |
+ `lpt7:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 155 |
+ `lpt8:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 156 |
+ `lpt9:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 157 |
+ `c:\windows\system32\ntdll.dll`: `Only directories can be mapped on this platform`, |
|
| 158 |
+ `\\.\pipe\foo:c:\pipe`: `'c:\pipe' is not a valid pipe path`, |
|
| 159 |
+ }, |
|
| 160 |
+ } |
|
| 161 |
+ lcowSet := parseMountRawTestSet{
|
|
| 162 |
+ valid: []string{
|
|
| 163 |
+ `/foo`, |
|
| 164 |
+ `/foo/`, |
|
| 165 |
+ `/foo bar`, |
|
| 166 |
+ `c:\:/foo`, |
|
| 167 |
+ `c:\windows\:/foo`, |
|
| 168 |
+ `c:\windows:/s p a c e`, |
|
| 169 |
+ `c:\windows:/s p a c e:RW`, |
|
| 170 |
+ `c:\program files:/s p a c e i n h o s t d i r`, |
|
| 171 |
+ `0123456789name:/foo`, |
|
| 172 |
+ `MiXeDcAsEnAmE:/foo`, |
|
| 173 |
+ `name:/foo`, |
|
| 174 |
+ `name:/foo:rW`, |
|
| 175 |
+ `name:/foo:RW`, |
|
| 176 |
+ `name:/foo:RO`, |
|
| 177 |
+ `c:/:/forward/slashes/are/good/too`, |
|
| 178 |
+ `c:/:/including with/spaces:ro`, |
|
| 179 |
+ `/Program Files (x86)`, // With capitals and brackets |
|
| 180 |
+ }, |
|
| 181 |
+ invalid: map[string]string{
|
|
| 182 |
+ ``: "invalid volume specification: ", |
|
| 183 |
+ `.`: "invalid volume specification: ", |
|
| 184 |
+ `c:`: "invalid volume specification: ", |
|
| 185 |
+ `c:\`: "invalid volume specification: ", |
|
| 186 |
+ `../`: "invalid volume specification: ", |
|
| 187 |
+ `c:\:../`: "invalid volume specification: ", |
|
| 188 |
+ `c:\:/foo:xyzzy`: "invalid volume specification: ", |
|
| 189 |
+ `/`: "destination can't be '/'", |
|
| 190 |
+ `/..`: "destination can't be '/'", |
|
| 191 |
+ `c:\notexist:/foo`: `bind mount source path does not exist: c:\notexist`, |
|
| 192 |
+ `c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`, |
|
| 193 |
+ `name<:/foo`: `invalid volume specification`, |
|
| 194 |
+ `name>:/foo`: `invalid volume specification`, |
|
| 195 |
+ `name::/foo`: `invalid volume specification`, |
|
| 196 |
+ `name":/foo`: `invalid volume specification`, |
|
| 197 |
+ `name\:/foo`: `invalid volume specification`, |
|
| 198 |
+ `name*:/foo`: `invalid volume specification`, |
|
| 199 |
+ `name|:/foo`: `invalid volume specification`, |
|
| 200 |
+ `name?:/foo`: `invalid volume specification`, |
|
| 201 |
+ `name/:/foo`: `invalid volume specification`, |
|
| 202 |
+ `/foo:rw`: `invalid volume specification`, |
|
| 203 |
+ `/foo:ro`: `invalid volume specification`, |
|
| 204 |
+ `con:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 205 |
+ `PRN:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 206 |
+ `aUx:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 207 |
+ `nul:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 208 |
+ `com1:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 209 |
+ `com2:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 210 |
+ `com3:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 211 |
+ `com4:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 212 |
+ `com5:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 213 |
+ `com6:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 214 |
+ `com7:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 215 |
+ `com8:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 216 |
+ `com9:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 217 |
+ `lpt1:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 218 |
+ `lpt2:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 219 |
+ `lpt3:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 220 |
+ `lpt4:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 221 |
+ `lpt5:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 222 |
+ `lpt6:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 223 |
+ `lpt7:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 224 |
+ `lpt8:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 225 |
+ `lpt9:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 226 |
+ `\\.\pipe\foo:/foo`: `Linux containers on Windows do not support named pipe mounts`, |
|
| 227 |
+ }, |
|
| 228 |
+ } |
|
| 229 |
+ linuxSet := parseMountRawTestSet{
|
|
| 230 |
+ valid: []string{
|
|
| 231 |
+ "/home", |
|
| 232 |
+ "/home:/home", |
|
| 233 |
+ "/home:/something/else", |
|
| 234 |
+ "/with space", |
|
| 235 |
+ "/home:/with space", |
|
| 236 |
+ "relative:/absolute-path", |
|
| 237 |
+ "hostPath:/containerPath:ro", |
|
| 238 |
+ "/hostPath:/containerPath:rw", |
|
| 239 |
+ "/rw:/ro", |
|
| 240 |
+ "/hostPath:/containerPath:shared", |
|
| 241 |
+ "/hostPath:/containerPath:rshared", |
|
| 242 |
+ "/hostPath:/containerPath:slave", |
|
| 243 |
+ "/hostPath:/containerPath:rslave", |
|
| 244 |
+ "/hostPath:/containerPath:private", |
|
| 245 |
+ "/hostPath:/containerPath:rprivate", |
|
| 246 |
+ "/hostPath:/containerPath:ro,shared", |
|
| 247 |
+ "/hostPath:/containerPath:ro,slave", |
|
| 248 |
+ "/hostPath:/containerPath:ro,private", |
|
| 249 |
+ "/hostPath:/containerPath:ro,z,shared", |
|
| 250 |
+ "/hostPath:/containerPath:ro,Z,slave", |
|
| 251 |
+ "/hostPath:/containerPath:Z,ro,slave", |
|
| 252 |
+ "/hostPath:/containerPath:slave,Z,ro", |
|
| 253 |
+ "/hostPath:/containerPath:Z,slave,ro", |
|
| 254 |
+ "/hostPath:/containerPath:slave,ro,Z", |
|
| 255 |
+ "/hostPath:/containerPath:rslave,ro,Z", |
|
| 256 |
+ "/hostPath:/containerPath:ro,rshared,Z", |
|
| 257 |
+ "/hostPath:/containerPath:ro,Z,rprivate", |
|
| 258 |
+ }, |
|
| 259 |
+ invalid: map[string]string{
|
|
| 260 |
+ "": "invalid volume specification", |
|
| 261 |
+ "./": "mount path must be absolute", |
|
| 262 |
+ "../": "mount path must be absolute", |
|
| 263 |
+ "/:../": "mount path must be absolute", |
|
| 264 |
+ "/:path": "mount path must be absolute", |
|
| 265 |
+ ":": "invalid volume specification", |
|
| 266 |
+ "/tmp:": "invalid volume specification", |
|
| 267 |
+ ":test": "invalid volume specification", |
|
| 268 |
+ ":/test": "invalid volume specification", |
|
| 269 |
+ "tmp:": "invalid volume specification", |
|
| 270 |
+ ":test:": "invalid volume specification", |
|
| 271 |
+ "::": "invalid volume specification", |
|
| 272 |
+ ":::": "invalid volume specification", |
|
| 273 |
+ "/tmp:::": "invalid volume specification", |
|
| 274 |
+ ":/tmp::": "invalid volume specification", |
|
| 275 |
+ "/path:rw": "invalid volume specification", |
|
| 276 |
+ "/path:ro": "invalid volume specification", |
|
| 277 |
+ "/rw:rw": "invalid volume specification", |
|
| 278 |
+ "path:ro": "invalid volume specification", |
|
| 279 |
+ "/path:/path:sw": `invalid mode`, |
|
| 280 |
+ "/path:/path:rwz": `invalid mode`, |
|
| 281 |
+ "/path:/path:ro,rshared,rslave": `invalid mode`, |
|
| 282 |
+ "/path:/path:ro,z,rshared,rslave": `invalid mode`, |
|
| 283 |
+ "/path:shared": "invalid volume specification", |
|
| 284 |
+ "/path:slave": "invalid volume specification", |
|
| 285 |
+ "/path:private": "invalid volume specification", |
|
| 286 |
+ "name:/absolute-path:shared": "invalid volume specification", |
|
| 287 |
+ "name:/absolute-path:rshared": "invalid volume specification", |
|
| 288 |
+ "name:/absolute-path:slave": "invalid volume specification", |
|
| 289 |
+ "name:/absolute-path:rslave": "invalid volume specification", |
|
| 290 |
+ "name:/absolute-path:private": "invalid volume specification", |
|
| 291 |
+ "name:/absolute-path:rprivate": "invalid volume specification", |
|
| 292 |
+ }, |
|
| 293 |
+ } |
|
| 294 |
+ |
|
| 295 |
+ linParser := &linuxParser{}
|
|
| 296 |
+ winParser := &windowsParser{}
|
|
| 297 |
+ lcowParser := &lcowParser{}
|
|
| 298 |
+ tester := func(parser Parser, set parseMountRawTestSet) {
|
|
| 299 |
+ |
|
| 300 |
+ for _, path := range set.valid {
|
|
| 301 |
+ |
|
| 302 |
+ if _, err := parser.ParseMountRaw(path, "local"); err != nil {
|
|
| 303 |
+ t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
|
|
| 304 |
+ } |
|
| 305 |
+ } |
|
| 306 |
+ |
|
| 307 |
+ for path, expectedError := range set.invalid {
|
|
| 308 |
+ if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
|
|
| 309 |
+ t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
|
|
| 310 |
+ } else {
|
|
| 311 |
+ if !strings.Contains(err.Error(), expectedError) {
|
|
| 312 |
+ t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
|
|
| 313 |
+ } |
|
| 314 |
+ } |
|
| 315 |
+ } |
|
| 316 |
+ } |
|
| 317 |
+ tester(linParser, linuxSet) |
|
| 318 |
+ tester(winParser, windowsSet) |
|
| 319 |
+ tester(lcowParser, lcowSet) |
|
| 320 |
+ |
|
| 321 |
+} |
|
| 322 |
+ |
|
| 323 |
+// testParseMountRaw is a structure used by TestParseMountRawSplit for |
|
| 324 |
+// specifying test cases for the ParseMountRaw() function. |
|
| 325 |
+type testParseMountRaw struct {
|
|
| 326 |
+ bind string |
|
| 327 |
+ driver string |
|
| 328 |
+ expType mount.Type |
|
| 329 |
+ expDest string |
|
| 330 |
+ expSource string |
|
| 331 |
+ expName string |
|
| 332 |
+ expDriver string |
|
| 333 |
+ expRW bool |
|
| 334 |
+ fail bool |
|
| 335 |
+} |
|
| 336 |
+ |
|
| 337 |
+func TestParseMountRawSplit(t *testing.T) {
|
|
| 338 |
+ previousProvider := currentFileInfoProvider |
|
| 339 |
+ defer func() { currentFileInfoProvider = previousProvider }()
|
|
| 340 |
+ currentFileInfoProvider = mockFiProvider{}
|
|
| 341 |
+ windowsCases := []testParseMountRaw{
|
|
| 342 |
+ {`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
|
|
| 343 |
+ {`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
|
|
| 344 |
+ {`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
|
|
| 345 |
+ {`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
|
|
| 346 |
+ {`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
|
|
| 347 |
+ {`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
|
|
| 348 |
+ {`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
|
|
| 349 |
+ {`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
|
|
| 350 |
+ {`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
|
| 351 |
+ {`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
|
| 352 |
+ {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
|
|
| 353 |
+ {`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
|
| 354 |
+ {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
|
| 355 |
+ } |
|
| 356 |
+ lcowCases := []testParseMountRaw{
|
|
| 357 |
+ {`c:\:/foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
|
|
| 358 |
+ {`c:\:/foo:ro`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, false},
|
|
| 359 |
+ {`c:\:/foo:rw`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
|
|
| 360 |
+ {`c:\:/foo:foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, true},
|
|
| 361 |
+ {`name:/foo:rw`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
|
|
| 362 |
+ {`name:/foo`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
|
|
| 363 |
+ {`name:/foo:ro`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", false, false},
|
|
| 364 |
+ {`name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
|
| 365 |
+ {`driver/name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
|
| 366 |
+ {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, true},
|
|
| 367 |
+ {`\\.\pipe\foo:/data`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
|
| 368 |
+ {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
|
| 369 |
+ } |
|
| 370 |
+ linuxCases := []testParseMountRaw{
|
|
| 371 |
+ {"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
|
|
| 372 |
+ {"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
|
|
| 373 |
+ {"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
|
|
| 374 |
+ {"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
|
|
| 375 |
+ {"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
|
|
| 376 |
+ {"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
|
|
| 377 |
+ {"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
|
|
| 378 |
+ {"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
|
|
| 379 |
+ {"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
|
|
| 380 |
+ } |
|
| 381 |
+ linParser := &linuxParser{}
|
|
| 382 |
+ winParser := &windowsParser{}
|
|
| 383 |
+ lcowParser := &lcowParser{}
|
|
| 384 |
+ tester := func(parser Parser, cases []testParseMountRaw) {
|
|
| 385 |
+ for i, c := range cases {
|
|
| 386 |
+ t.Logf("case %d", i)
|
|
| 387 |
+ m, err := parser.ParseMountRaw(c.bind, c.driver) |
|
| 388 |
+ if c.fail {
|
|
| 389 |
+ if err == nil {
|
|
| 390 |
+ t.Errorf("Expected error, was nil, for spec %s\n", c.bind)
|
|
| 391 |
+ } |
|
| 392 |
+ continue |
|
| 393 |
+ } |
|
| 394 |
+ |
|
| 395 |
+ if m == nil || err != nil {
|
|
| 396 |
+ t.Errorf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
|
|
| 397 |
+ continue |
|
| 398 |
+ } |
|
| 399 |
+ |
|
| 400 |
+ if m.Destination != c.expDest {
|
|
| 401 |
+ t.Errorf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
|
|
| 402 |
+ } |
|
| 403 |
+ |
|
| 404 |
+ if m.Source != c.expSource {
|
|
| 405 |
+ t.Errorf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
|
|
| 406 |
+ } |
|
| 407 |
+ |
|
| 408 |
+ if m.Name != c.expName {
|
|
| 409 |
+ t.Errorf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
|
|
| 410 |
+ } |
|
| 411 |
+ |
|
| 412 |
+ if m.Driver != c.expDriver {
|
|
| 413 |
+ t.Errorf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
|
|
| 414 |
+ } |
|
| 415 |
+ |
|
| 416 |
+ if m.RW != c.expRW {
|
|
| 417 |
+ t.Errorf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
|
|
| 418 |
+ } |
|
| 419 |
+ if m.Type != c.expType {
|
|
| 420 |
+ t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
|
|
| 421 |
+ } |
|
| 422 |
+ } |
|
| 423 |
+ } |
|
| 424 |
+ |
|
| 425 |
+ tester(linParser, linuxCases) |
|
| 426 |
+ tester(winParser, windowsCases) |
|
| 427 |
+ tester(lcowParser, lcowCases) |
|
| 428 |
+} |
|
| 429 |
+ |
|
| 430 |
+func TestParseMountSpec(t *testing.T) {
|
|
| 431 |
+ type c struct {
|
|
| 432 |
+ input mount.Mount |
|
| 433 |
+ expected MountPoint |
|
| 434 |
+ } |
|
| 435 |
+ testDir, err := ioutil.TempDir("", "test-mount-config")
|
|
| 436 |
+ if err != nil {
|
|
| 437 |
+ t.Fatal(err) |
|
| 438 |
+ } |
|
| 439 |
+ defer os.RemoveAll(testDir) |
|
| 440 |
+ parser := NewParser(runtime.GOOS) |
|
| 441 |
+ cases := []c{
|
|
| 442 |
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
|
|
| 443 |
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()}},
|
|
| 444 |
+ {mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
|
|
| 445 |
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
|
|
| 446 |
+ {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
|
|
| 447 |
+ {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
|
|
| 448 |
+ } |
|
| 449 |
+ |
|
| 450 |
+ for i, c := range cases {
|
|
| 451 |
+ t.Logf("case %d", i)
|
|
| 452 |
+ mp, err := parser.ParseMountSpec(c.input) |
|
| 453 |
+ if err != nil {
|
|
| 454 |
+ t.Error(err) |
|
| 455 |
+ } |
|
| 456 |
+ |
|
| 457 |
+ if c.expected.Type != mp.Type {
|
|
| 458 |
+ t.Errorf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
|
|
| 459 |
+ } |
|
| 460 |
+ if c.expected.Destination != mp.Destination {
|
|
| 461 |
+ t.Errorf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
|
|
| 462 |
+ } |
|
| 463 |
+ if c.expected.Source != mp.Source {
|
|
| 464 |
+ t.Errorf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
|
|
| 465 |
+ } |
|
| 466 |
+ if c.expected.RW != mp.RW {
|
|
| 467 |
+ t.Errorf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
|
|
| 468 |
+ } |
|
| 469 |
+ if c.expected.Propagation != mp.Propagation {
|
|
| 470 |
+ t.Errorf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
|
|
| 471 |
+ } |
|
| 472 |
+ if c.expected.Driver != mp.Driver {
|
|
| 473 |
+ t.Errorf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
|
|
| 474 |
+ } |
|
| 475 |
+ if c.expected.CopyData != mp.CopyData {
|
|
| 476 |
+ t.Errorf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
|
|
| 477 |
+ } |
|
| 478 |
+ } |
|
| 479 |
+} |
| 0 | 480 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,28 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/api/types/mount" |
|
| 6 |
+ "github.com/pkg/errors" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+type errMountConfig struct {
|
|
| 10 |
+ mount *mount.Mount |
|
| 11 |
+ err error |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+func (e *errMountConfig) Error() string {
|
|
| 15 |
+ return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error())
|
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+func errBindSourceDoesNotExist(path string) error {
|
|
| 19 |
+ return errors.Errorf("bind mount source path does not exist: %s", path)
|
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func errExtraField(name string) error {
|
|
| 23 |
+ return errors.Errorf("field %s must not be specified", name)
|
|
| 24 |
+} |
|
| 25 |
+func errMissingField(name string) error {
|
|
| 26 |
+ return errors.Errorf("field %s must not be empty", name)
|
|
| 27 |
+} |
| 0 | 28 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,73 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "os" |
|
| 6 |
+ "runtime" |
|
| 7 |
+ "strings" |
|
| 8 |
+ "testing" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker/api/types/mount" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func TestValidateMount(t *testing.T) {
|
|
| 14 |
+ testDir, err := ioutil.TempDir("", "test-validate-mount")
|
|
| 15 |
+ if err != nil {
|
|
| 16 |
+ t.Fatal(err) |
|
| 17 |
+ } |
|
| 18 |
+ defer os.RemoveAll(testDir) |
|
| 19 |
+ |
|
| 20 |
+ cases := []struct {
|
|
| 21 |
+ input mount.Mount |
|
| 22 |
+ expected error |
|
| 23 |
+ }{
|
|
| 24 |
+ {mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
|
|
| 25 |
+ {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath, Source: "hello"}, nil},
|
|
| 26 |
+ {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, nil},
|
|
| 27 |
+ {mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
|
|
| 28 |
+ {mount.Mount{Type: mount.TypeBind, Target: testDestinationPath}, errMissingField("Source")},
|
|
| 29 |
+ {mount.Mount{Type: mount.TypeBind, Target: testDestinationPath, Source: testSourcePath, VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
|
|
| 30 |
+ |
|
| 31 |
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, nil},
|
|
| 32 |
+ {mount.Mount{Type: "invalid", Target: testDestinationPath}, errors.New("mount type unknown")},
|
|
| 33 |
+ {mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindSourceDoesNotExist(testSourcePath)},
|
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ lcowCases := []struct {
|
|
| 37 |
+ input mount.Mount |
|
| 38 |
+ expected error |
|
| 39 |
+ }{
|
|
| 40 |
+ {mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
|
|
| 41 |
+ {mount.Mount{Type: mount.TypeVolume, Target: "/foo", Source: "hello"}, nil},
|
|
| 42 |
+ {mount.Mount{Type: mount.TypeVolume, Target: "/foo"}, nil},
|
|
| 43 |
+ {mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
|
|
| 44 |
+ {mount.Mount{Type: mount.TypeBind, Target: "/foo"}, errMissingField("Source")},
|
|
| 45 |
+ {mount.Mount{Type: mount.TypeBind, Target: "/foo", Source: "c:\\foo", VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
|
|
| 46 |
+ {mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindSourceDoesNotExist("c:\\foo")},
|
|
| 47 |
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: "/foo"}, nil},
|
|
| 48 |
+ {mount.Mount{Type: "invalid", Target: "/foo"}, errors.New("mount type unknown")},
|
|
| 49 |
+ } |
|
| 50 |
+ parser := NewParser(runtime.GOOS) |
|
| 51 |
+ for i, x := range cases {
|
|
| 52 |
+ err := parser.ValidateMountConfig(&x.input) |
|
| 53 |
+ if err == nil && x.expected == nil {
|
|
| 54 |
+ continue |
|
| 55 |
+ } |
|
| 56 |
+ if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
|
|
| 57 |
+ t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
|
|
| 58 |
+ } |
|
| 59 |
+ } |
|
| 60 |
+ if runtime.GOOS == "windows" {
|
|
| 61 |
+ parser = &lcowParser{}
|
|
| 62 |
+ for i, x := range lcowCases {
|
|
| 63 |
+ err := parser.ValidateMountConfig(&x.input) |
|
| 64 |
+ if err == nil && x.expected == nil {
|
|
| 65 |
+ continue |
|
| 66 |
+ } |
|
| 67 |
+ if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
|
|
| 68 |
+ t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
|
|
| 69 |
+ } |
|
| 70 |
+ } |
|
| 71 |
+ } |
|
| 72 |
+} |
| 0 | 6 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,23 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+import "strings" |
|
| 3 |
+ |
|
| 4 |
+// {<copy mode>=isEnabled}
|
|
| 5 |
+var copyModes = map[string]bool{
|
|
| 6 |
+ "nocopy": false, |
|
| 7 |
+} |
|
| 8 |
+ |
|
| 9 |
+func copyModeExists(mode string) bool {
|
|
| 10 |
+ _, exists := copyModes[mode] |
|
| 11 |
+ return exists |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+// GetCopyMode gets the copy mode from the mode string for mounts |
|
| 15 |
+func getCopyMode(mode string, def bool) (bool, bool) {
|
|
| 16 |
+ for _, o := range strings.Split(mode, ",") {
|
|
| 17 |
+ if isEnabled, exists := copyModes[o]; exists {
|
|
| 18 |
+ return isEnabled, true |
|
| 19 |
+ } |
|
| 20 |
+ } |
|
| 21 |
+ return def, false |
|
| 22 |
+} |
| 0 | 23 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,18 @@ |
| 0 |
+// +build linux freebsd darwin |
|
| 1 |
+ |
|
| 2 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "strings" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
|
|
| 11 |
+ relPath, err := filepath.Rel(m.Destination, absolutePath) |
|
| 12 |
+ return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
|
|
| 13 |
+} |
|
| 14 |
+ |
|
| 15 |
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
|
|
| 16 |
+ return false |
|
| 17 |
+} |
| 0 | 18 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,8 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
|
|
| 3 |
+ return false |
|
| 4 |
+} |
|
| 5 |
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
|
|
| 6 |
+ return false |
|
| 7 |
+} |
| 0 | 8 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,456 @@ |
| 0 |
+package mounts // import "github.com/docker/docker/volume/mounts" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "os" |
|
| 6 |
+ "regexp" |
|
| 7 |
+ "runtime" |
|
| 8 |
+ "strings" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker/api/types/mount" |
|
| 11 |
+ "github.com/docker/docker/pkg/stringid" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+type windowsParser struct {
|
|
| 15 |
+} |
|
| 16 |
+ |
|
| 17 |
+const ( |
|
| 18 |
+ // Spec should be in the format [source:]destination[:mode] |
|
| 19 |
+ // |
|
| 20 |
+ // Examples: c:\foo bar:d:rw |
|
| 21 |
+ // c:\foo:d:\bar |
|
| 22 |
+ // myname:d: |
|
| 23 |
+ // d:\ |
|
| 24 |
+ // |
|
| 25 |
+ // Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See |
|
| 26 |
+ // https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to |
|
| 27 |
+ // test is https://regex-golang.appspot.com/assets/html/index.html |
|
| 28 |
+ // |
|
| 29 |
+ // Useful link for referencing named capturing groups: |
|
| 30 |
+ // http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex |
|
| 31 |
+ // |
|
| 32 |
+ // There are three match groups: source, destination and mode. |
|
| 33 |
+ // |
|
| 34 |
+ |
|
| 35 |
+ // rxHostDir is the first option of a source |
|
| 36 |
+ rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*` |
|
| 37 |
+ // rxName is the second option of a source |
|
| 38 |
+ rxName = `[^\\/:*?"<>|\r\n]+` |
|
| 39 |
+ |
|
| 40 |
+ // RXReservedNames are reserved names not possible on Windows |
|
| 41 |
+ rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])` |
|
| 42 |
+ |
|
| 43 |
+ // rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \) |
|
| 44 |
+ rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
|
|
| 45 |
+ // rxSource is the combined possibilities for a source |
|
| 46 |
+ rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?` |
|
| 47 |
+ |
|
| 48 |
+ // Source. Can be either a host directory, a name, or omitted: |
|
| 49 |
+ // HostDir: |
|
| 50 |
+ // - Essentially using the folder solution from |
|
| 51 |
+ // https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html |
|
| 52 |
+ // but adding case insensitivity. |
|
| 53 |
+ // - Must be an absolute path such as c:\path |
|
| 54 |
+ // - Can include spaces such as `c:\program files` |
|
| 55 |
+ // - And then followed by a colon which is not in the capture group |
|
| 56 |
+ // - And can be optional |
|
| 57 |
+ // Name: |
|
| 58 |
+ // - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx) |
|
| 59 |
+ // - And then followed by a colon which is not in the capture group |
|
| 60 |
+ // - And can be optional |
|
| 61 |
+ |
|
| 62 |
+ // rxDestination is the regex expression for the mount destination |
|
| 63 |
+ rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `))` |
|
| 64 |
+ |
|
| 65 |
+ rxLCOWDestination = `(?P<destination>/(?:[^\\/:*?"<>\r\n]+[/]?)*)` |
|
| 66 |
+ // Destination (aka container path): |
|
| 67 |
+ // - Variation on hostdir but can be a drive followed by colon as well |
|
| 68 |
+ // - If a path, must be absolute. Can include spaces |
|
| 69 |
+ // - Drive cannot be c: (explicitly checked in code, not RegEx) |
|
| 70 |
+ |
|
| 71 |
+ // rxMode is the regex expression for the mode of the mount |
|
| 72 |
+ // Mode (optional): |
|
| 73 |
+ // - Hopefully self explanatory in comparison to above regex's. |
|
| 74 |
+ // - Colon is not in the capture group |
|
| 75 |
+ rxMode = `(:(?P<mode>(?i)ro|rw))?` |
|
| 76 |
+) |
|
| 77 |
+ |
|
| 78 |
+type mountValidator func(mnt *mount.Mount) error |
|
| 79 |
+ |
|
| 80 |
+func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
|
|
| 81 |
+ specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`) |
|
| 82 |
+ match := specExp.FindStringSubmatch(strings.ToLower(raw)) |
|
| 83 |
+ |
|
| 84 |
+ // Must have something back |
|
| 85 |
+ if len(match) == 0 {
|
|
| 86 |
+ return nil, errInvalidSpec(raw) |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ var split []string |
|
| 90 |
+ matchgroups := make(map[string]string) |
|
| 91 |
+ // Pull out the sub expressions from the named capture groups |
|
| 92 |
+ for i, name := range specExp.SubexpNames() {
|
|
| 93 |
+ matchgroups[name] = strings.ToLower(match[i]) |
|
| 94 |
+ } |
|
| 95 |
+ if source, exists := matchgroups["source"]; exists {
|
|
| 96 |
+ if source != "" {
|
|
| 97 |
+ split = append(split, source) |
|
| 98 |
+ } |
|
| 99 |
+ } |
|
| 100 |
+ if destination, exists := matchgroups["destination"]; exists {
|
|
| 101 |
+ if destination != "" {
|
|
| 102 |
+ split = append(split, destination) |
|
| 103 |
+ } |
|
| 104 |
+ } |
|
| 105 |
+ if mode, exists := matchgroups["mode"]; exists {
|
|
| 106 |
+ if mode != "" {
|
|
| 107 |
+ split = append(split, mode) |
|
| 108 |
+ } |
|
| 109 |
+ } |
|
| 110 |
+ // Fix #26329. If the destination appears to be a file, and the source is null, |
|
| 111 |
+ // it may be because we've fallen through the possible naming regex and hit a |
|
| 112 |
+ // situation where the user intention was to map a file into a container through |
|
| 113 |
+ // a local volume, but this is not supported by the platform. |
|
| 114 |
+ if matchgroups["source"] == "" && matchgroups["destination"] != "" {
|
|
| 115 |
+ volExp := regexp.MustCompile(`^` + rxName + `$`) |
|
| 116 |
+ reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`) |
|
| 117 |
+ |
|
| 118 |
+ if volExp.MatchString(matchgroups["destination"]) {
|
|
| 119 |
+ if reservedNameExp.MatchString(matchgroups["destination"]) {
|
|
| 120 |
+ return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
|
|
| 121 |
+ } |
|
| 122 |
+ } else {
|
|
| 123 |
+ |
|
| 124 |
+ exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"]) |
|
| 125 |
+ if exists && !isDir {
|
|
| 126 |
+ return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
|
|
| 127 |
+ |
|
| 128 |
+ } |
|
| 129 |
+ } |
|
| 130 |
+ } |
|
| 131 |
+ return split, nil |
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func windowsValidMountMode(mode string) bool {
|
|
| 135 |
+ if mode == "" {
|
|
| 136 |
+ return true |
|
| 137 |
+ } |
|
| 138 |
+ return rwModes[strings.ToLower(mode)] |
|
| 139 |
+} |
|
| 140 |
+func windowsValidateNotRoot(p string) error {
|
|
| 141 |
+ p = strings.ToLower(strings.Replace(p, `/`, `\`, -1)) |
|
| 142 |
+ if p == "c:" || p == `c:\` {
|
|
| 143 |
+ return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
|
|
| 144 |
+ } |
|
| 145 |
+ return nil |
|
| 146 |
+} |
|
| 147 |
+ |
|
| 148 |
+var windowsSpecificValidators mountValidator = func(mnt *mount.Mount) error {
|
|
| 149 |
+ return windowsValidateNotRoot(mnt.Target) |
|
| 150 |
+} |
|
| 151 |
+ |
|
| 152 |
+func windowsValidateRegex(p, r string) error {
|
|
| 153 |
+ if regexp.MustCompile(`^` + r + `$`).MatchString(strings.ToLower(p)) {
|
|
| 154 |
+ return nil |
|
| 155 |
+ } |
|
| 156 |
+ return fmt.Errorf("invalid mount path: '%s'", p)
|
|
| 157 |
+} |
|
| 158 |
+func windowsValidateAbsolute(p string) error {
|
|
| 159 |
+ if err := windowsValidateRegex(p, rxDestination); err != nil {
|
|
| 160 |
+ return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
|
|
| 161 |
+ } |
|
| 162 |
+ return nil |
|
| 163 |
+} |
|
| 164 |
+ |
|
| 165 |
+func windowsDetectMountType(p string) mount.Type {
|
|
| 166 |
+ if strings.HasPrefix(p, `\\.\pipe\`) {
|
|
| 167 |
+ return mount.TypeNamedPipe |
|
| 168 |
+ } else if regexp.MustCompile(`^` + rxHostDir + `$`).MatchString(p) {
|
|
| 169 |
+ return mount.TypeBind |
|
| 170 |
+ } else {
|
|
| 171 |
+ return mount.TypeVolume |
|
| 172 |
+ } |
|
| 173 |
+} |
|
| 174 |
+ |
|
| 175 |
+func (p *windowsParser) ReadWrite(mode string) bool {
|
|
| 176 |
+ return strings.ToLower(mode) != "ro" |
|
| 177 |
+} |
|
| 178 |
+ |
|
| 179 |
+// IsVolumeNameValid checks a volume name in a platform specific manner. |
|
| 180 |
+func (p *windowsParser) ValidateVolumeName(name string) error {
|
|
| 181 |
+ nameExp := regexp.MustCompile(`^` + rxName + `$`) |
|
| 182 |
+ if !nameExp.MatchString(name) {
|
|
| 183 |
+ return errors.New("invalid volume name")
|
|
| 184 |
+ } |
|
| 185 |
+ nameExp = regexp.MustCompile(`^` + rxReservedNames + `$`) |
|
| 186 |
+ if nameExp.MatchString(name) {
|
|
| 187 |
+ return fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
|
|
| 188 |
+ } |
|
| 189 |
+ return nil |
|
| 190 |
+} |
|
| 191 |
+func (p *windowsParser) ValidateMountConfig(mnt *mount.Mount) error {
|
|
| 192 |
+ return p.validateMountConfigReg(mnt, rxDestination, windowsSpecificValidators) |
|
| 193 |
+} |
|
| 194 |
+ |
|
| 195 |
+type fileInfoProvider interface {
|
|
| 196 |
+ fileInfo(path string) (exist, isDir bool, err error) |
|
| 197 |
+} |
|
| 198 |
+ |
|
| 199 |
+type defaultFileInfoProvider struct {
|
|
| 200 |
+} |
|
| 201 |
+ |
|
| 202 |
+func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
|
|
| 203 |
+ fi, err := os.Stat(path) |
|
| 204 |
+ if err != nil {
|
|
| 205 |
+ if !os.IsNotExist(err) {
|
|
| 206 |
+ return false, false, err |
|
| 207 |
+ } |
|
| 208 |
+ return false, false, nil |
|
| 209 |
+ } |
|
| 210 |
+ return true, fi.IsDir(), nil |
|
| 211 |
+} |
|
| 212 |
+ |
|
| 213 |
+var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}
|
|
| 214 |
+ |
|
| 215 |
+func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, destRegex string, additionalValidators ...mountValidator) error {
|
|
| 216 |
+ |
|
| 217 |
+ for _, v := range additionalValidators {
|
|
| 218 |
+ if err := v(mnt); err != nil {
|
|
| 219 |
+ return &errMountConfig{mnt, err}
|
|
| 220 |
+ } |
|
| 221 |
+ } |
|
| 222 |
+ if len(mnt.Target) == 0 {
|
|
| 223 |
+ return &errMountConfig{mnt, errMissingField("Target")}
|
|
| 224 |
+ } |
|
| 225 |
+ |
|
| 226 |
+ if err := windowsValidateRegex(mnt.Target, destRegex); err != nil {
|
|
| 227 |
+ return &errMountConfig{mnt, err}
|
|
| 228 |
+ } |
|
| 229 |
+ |
|
| 230 |
+ switch mnt.Type {
|
|
| 231 |
+ case mount.TypeBind: |
|
| 232 |
+ if len(mnt.Source) == 0 {
|
|
| 233 |
+ return &errMountConfig{mnt, errMissingField("Source")}
|
|
| 234 |
+ } |
|
| 235 |
+ // Don't error out just because the propagation mode is not supported on the platform |
|
| 236 |
+ if opts := mnt.BindOptions; opts != nil {
|
|
| 237 |
+ if len(opts.Propagation) > 0 {
|
|
| 238 |
+ return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
|
|
| 239 |
+ } |
|
| 240 |
+ } |
|
| 241 |
+ if mnt.VolumeOptions != nil {
|
|
| 242 |
+ return &errMountConfig{mnt, errExtraField("VolumeOptions")}
|
|
| 243 |
+ } |
|
| 244 |
+ |
|
| 245 |
+ if err := windowsValidateAbsolute(mnt.Source); err != nil {
|
|
| 246 |
+ return &errMountConfig{mnt, err}
|
|
| 247 |
+ } |
|
| 248 |
+ |
|
| 249 |
+ exists, isdir, err := currentFileInfoProvider.fileInfo(mnt.Source) |
|
| 250 |
+ if err != nil {
|
|
| 251 |
+ return &errMountConfig{mnt, err}
|
|
| 252 |
+ } |
|
| 253 |
+ if !exists {
|
|
| 254 |
+ return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
|
|
| 255 |
+ } |
|
| 256 |
+ if !isdir {
|
|
| 257 |
+ return &errMountConfig{mnt, fmt.Errorf("source path must be a directory")}
|
|
| 258 |
+ } |
|
| 259 |
+ |
|
| 260 |
+ case mount.TypeVolume: |
|
| 261 |
+ if mnt.BindOptions != nil {
|
|
| 262 |
+ return &errMountConfig{mnt, errExtraField("BindOptions")}
|
|
| 263 |
+ } |
|
| 264 |
+ |
|
| 265 |
+ if len(mnt.Source) == 0 && mnt.ReadOnly {
|
|
| 266 |
+ return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
|
|
| 267 |
+ } |
|
| 268 |
+ |
|
| 269 |
+ if len(mnt.Source) != 0 {
|
|
| 270 |
+ if err := p.ValidateVolumeName(mnt.Source); err != nil {
|
|
| 271 |
+ return &errMountConfig{mnt, err}
|
|
| 272 |
+ } |
|
| 273 |
+ } |
|
| 274 |
+ case mount.TypeNamedPipe: |
|
| 275 |
+ if len(mnt.Source) == 0 {
|
|
| 276 |
+ return &errMountConfig{mnt, errMissingField("Source")}
|
|
| 277 |
+ } |
|
| 278 |
+ |
|
| 279 |
+ if mnt.BindOptions != nil {
|
|
| 280 |
+ return &errMountConfig{mnt, errExtraField("BindOptions")}
|
|
| 281 |
+ } |
|
| 282 |
+ |
|
| 283 |
+ if mnt.ReadOnly {
|
|
| 284 |
+ return &errMountConfig{mnt, errExtraField("ReadOnly")}
|
|
| 285 |
+ } |
|
| 286 |
+ |
|
| 287 |
+ if windowsDetectMountType(mnt.Source) != mount.TypeNamedPipe {
|
|
| 288 |
+ return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
|
|
| 289 |
+ } |
|
| 290 |
+ |
|
| 291 |
+ if windowsDetectMountType(mnt.Target) != mount.TypeNamedPipe {
|
|
| 292 |
+ return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
|
|
| 293 |
+ } |
|
| 294 |
+ default: |
|
| 295 |
+ return &errMountConfig{mnt, errors.New("mount type unknown")}
|
|
| 296 |
+ } |
|
| 297 |
+ return nil |
|
| 298 |
+} |
|
| 299 |
+func (p *windowsParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
|
| 300 |
+ return p.parseMountRaw(raw, volumeDriver, rxDestination, true, windowsSpecificValidators) |
|
| 301 |
+} |
|
| 302 |
+ |
|
| 303 |
+func (p *windowsParser) parseMountRaw(raw, volumeDriver, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
|
|
| 304 |
+ arr, err := windowsSplitRawSpec(raw, destRegex) |
|
| 305 |
+ if err != nil {
|
|
| 306 |
+ return nil, err |
|
| 307 |
+ } |
|
| 308 |
+ |
|
| 309 |
+ var spec mount.Mount |
|
| 310 |
+ var mode string |
|
| 311 |
+ switch len(arr) {
|
|
| 312 |
+ case 1: |
|
| 313 |
+ // Just a destination path in the container |
|
| 314 |
+ spec.Target = arr[0] |
|
| 315 |
+ case 2: |
|
| 316 |
+ if windowsValidMountMode(arr[1]) {
|
|
| 317 |
+ // Destination + Mode is not a valid volume - volumes |
|
| 318 |
+ // cannot include a mode. e.g. /foo:rw |
|
| 319 |
+ return nil, errInvalidSpec(raw) |
|
| 320 |
+ } |
|
| 321 |
+ // Host Source Path or Name + Destination |
|
| 322 |
+ spec.Source = strings.Replace(arr[0], `/`, `\`, -1) |
|
| 323 |
+ spec.Target = arr[1] |
|
| 324 |
+ case 3: |
|
| 325 |
+ // HostSourcePath+DestinationPath+Mode |
|
| 326 |
+ spec.Source = strings.Replace(arr[0], `/`, `\`, -1) |
|
| 327 |
+ spec.Target = arr[1] |
|
| 328 |
+ mode = arr[2] |
|
| 329 |
+ default: |
|
| 330 |
+ return nil, errInvalidSpec(raw) |
|
| 331 |
+ } |
|
| 332 |
+ if convertTargetToBackslash {
|
|
| 333 |
+ spec.Target = strings.Replace(spec.Target, `/`, `\`, -1) |
|
| 334 |
+ } |
|
| 335 |
+ |
|
| 336 |
+ if !windowsValidMountMode(mode) {
|
|
| 337 |
+ return nil, errInvalidMode(mode) |
|
| 338 |
+ } |
|
| 339 |
+ |
|
| 340 |
+ spec.Type = windowsDetectMountType(spec.Source) |
|
| 341 |
+ spec.ReadOnly = !p.ReadWrite(mode) |
|
| 342 |
+ |
|
| 343 |
+ // cannot assume that if a volume driver is passed in that we should set it |
|
| 344 |
+ if volumeDriver != "" && spec.Type == mount.TypeVolume {
|
|
| 345 |
+ spec.VolumeOptions = &mount.VolumeOptions{
|
|
| 346 |
+ DriverConfig: &mount.Driver{Name: volumeDriver},
|
|
| 347 |
+ } |
|
| 348 |
+ } |
|
| 349 |
+ |
|
| 350 |
+ if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
|
| 351 |
+ if spec.VolumeOptions == nil {
|
|
| 352 |
+ spec.VolumeOptions = &mount.VolumeOptions{}
|
|
| 353 |
+ } |
|
| 354 |
+ spec.VolumeOptions.NoCopy = !copyData |
|
| 355 |
+ } |
|
| 356 |
+ |
|
| 357 |
+ mp, err := p.parseMountSpec(spec, destRegex, convertTargetToBackslash, additionalValidators...) |
|
| 358 |
+ if mp != nil {
|
|
| 359 |
+ mp.Mode = mode |
|
| 360 |
+ } |
|
| 361 |
+ if err != nil {
|
|
| 362 |
+ err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
|
|
| 363 |
+ } |
|
| 364 |
+ return mp, err |
|
| 365 |
+} |
|
| 366 |
+ |
|
| 367 |
+func (p *windowsParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
|
|
| 368 |
+ return p.parseMountSpec(cfg, rxDestination, true, windowsSpecificValidators) |
|
| 369 |
+} |
|
| 370 |
+func (p *windowsParser) parseMountSpec(cfg mount.Mount, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
|
|
| 371 |
+ if err := p.validateMountConfigReg(&cfg, destRegex, additionalValidators...); err != nil {
|
|
| 372 |
+ return nil, err |
|
| 373 |
+ } |
|
| 374 |
+ mp := &MountPoint{
|
|
| 375 |
+ RW: !cfg.ReadOnly, |
|
| 376 |
+ Destination: cfg.Target, |
|
| 377 |
+ Type: cfg.Type, |
|
| 378 |
+ Spec: cfg, |
|
| 379 |
+ } |
|
| 380 |
+ if convertTargetToBackslash {
|
|
| 381 |
+ mp.Destination = strings.Replace(cfg.Target, `/`, `\`, -1) |
|
| 382 |
+ } |
|
| 383 |
+ |
|
| 384 |
+ switch cfg.Type {
|
|
| 385 |
+ case mount.TypeVolume: |
|
| 386 |
+ if cfg.Source == "" {
|
|
| 387 |
+ mp.Name = stringid.GenerateNonCryptoID() |
|
| 388 |
+ } else {
|
|
| 389 |
+ mp.Name = cfg.Source |
|
| 390 |
+ } |
|
| 391 |
+ mp.CopyData = p.DefaultCopyMode() |
|
| 392 |
+ |
|
| 393 |
+ if cfg.VolumeOptions != nil {
|
|
| 394 |
+ if cfg.VolumeOptions.DriverConfig != nil {
|
|
| 395 |
+ mp.Driver = cfg.VolumeOptions.DriverConfig.Name |
|
| 396 |
+ } |
|
| 397 |
+ if cfg.VolumeOptions.NoCopy {
|
|
| 398 |
+ mp.CopyData = false |
|
| 399 |
+ } |
|
| 400 |
+ } |
|
| 401 |
+ case mount.TypeBind: |
|
| 402 |
+ mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1) |
|
| 403 |
+ case mount.TypeNamedPipe: |
|
| 404 |
+ mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1) |
|
| 405 |
+ } |
|
| 406 |
+ // cleanup trailing `\` except for paths like `c:\` |
|
| 407 |
+ if len(mp.Source) > 3 && mp.Source[len(mp.Source)-1] == '\\' {
|
|
| 408 |
+ mp.Source = mp.Source[:len(mp.Source)-1] |
|
| 409 |
+ } |
|
| 410 |
+ if len(mp.Destination) > 3 && mp.Destination[len(mp.Destination)-1] == '\\' {
|
|
| 411 |
+ mp.Destination = mp.Destination[:len(mp.Destination)-1] |
|
| 412 |
+ } |
|
| 413 |
+ return mp, nil |
|
| 414 |
+} |
|
| 415 |
+ |
|
| 416 |
+func (p *windowsParser) ParseVolumesFrom(spec string) (string, string, error) {
|
|
| 417 |
+ if len(spec) == 0 {
|
|
| 418 |
+ return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
|
|
| 419 |
+ } |
|
| 420 |
+ |
|
| 421 |
+ specParts := strings.SplitN(spec, ":", 2) |
|
| 422 |
+ id := specParts[0] |
|
| 423 |
+ mode := "rw" |
|
| 424 |
+ |
|
| 425 |
+ if len(specParts) == 2 {
|
|
| 426 |
+ mode = specParts[1] |
|
| 427 |
+ if !windowsValidMountMode(mode) {
|
|
| 428 |
+ return "", "", errInvalidMode(mode) |
|
| 429 |
+ } |
|
| 430 |
+ |
|
| 431 |
+ // Do not allow copy modes on volumes-from |
|
| 432 |
+ if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
|
| 433 |
+ return "", "", errInvalidMode(mode) |
|
| 434 |
+ } |
|
| 435 |
+ } |
|
| 436 |
+ return id, mode, nil |
|
| 437 |
+} |
|
| 438 |
+ |
|
| 439 |
+func (p *windowsParser) DefaultPropagationMode() mount.Propagation {
|
|
| 440 |
+ return mount.Propagation("")
|
|
| 441 |
+} |
|
| 442 |
+ |
|
| 443 |
+func (p *windowsParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
|
|
| 444 |
+ return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
|
|
| 445 |
+} |
|
| 446 |
+func (p *windowsParser) DefaultCopyMode() bool {
|
|
| 447 |
+ return false |
|
| 448 |
+} |
|
| 449 |
+func (p *windowsParser) IsBackwardCompatible(m *MountPoint) bool {
|
|
| 450 |
+ return false |
|
| 451 |
+} |
|
| 452 |
+ |
|
| 453 |
+func (p *windowsParser) ValidateTmpfsMountDestination(dest string) error {
|
|
| 454 |
+ return errors.New("Platform does not support tmpfs")
|
|
| 455 |
+} |
| 0 | 456 |
deleted file mode 100644 |
| ... | ... |
@@ -1,47 +0,0 @@ |
| 1 |
-package volume // import "github.com/docker/docker/volume" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
- "runtime" |
|
| 6 |
- |
|
| 7 |
- "github.com/docker/docker/api/types/mount" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-const ( |
|
| 11 |
- // OSLinux is the same as runtime.GOOS on linux |
|
| 12 |
- OSLinux = "linux" |
|
| 13 |
- // OSWindows is the same as runtime.GOOS on windows |
|
| 14 |
- OSWindows = "windows" |
|
| 15 |
-) |
|
| 16 |
- |
|
| 17 |
-// ErrVolumeTargetIsRoot is returned when the target destination is root. |
|
| 18 |
-// It's used by both LCOW and Linux parsers. |
|
| 19 |
-var ErrVolumeTargetIsRoot = errors.New("invalid specification: destination can't be '/'")
|
|
| 20 |
- |
|
| 21 |
-// Parser represents a platform specific parser for mount expressions |
|
| 22 |
-type Parser interface {
|
|
| 23 |
- ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) |
|
| 24 |
- ParseMountSpec(cfg mount.Mount) (*MountPoint, error) |
|
| 25 |
- ParseVolumesFrom(spec string) (string, string, error) |
|
| 26 |
- DefaultPropagationMode() mount.Propagation |
|
| 27 |
- ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) |
|
| 28 |
- DefaultCopyMode() bool |
|
| 29 |
- ValidateVolumeName(name string) error |
|
| 30 |
- ReadWrite(mode string) bool |
|
| 31 |
- IsBackwardCompatible(m *MountPoint) bool |
|
| 32 |
- HasResource(m *MountPoint, absPath string) bool |
|
| 33 |
- ValidateTmpfsMountDestination(dest string) error |
|
| 34 |
- ValidateMountConfig(mt *mount.Mount) error |
|
| 35 |
-} |
|
| 36 |
- |
|
| 37 |
-// NewParser creates a parser for a given container OS, depending on the current host OS (linux on a windows host will resolve to an lcowParser) |
|
| 38 |
-func NewParser(containerOS string) Parser {
|
|
| 39 |
- switch containerOS {
|
|
| 40 |
- case OSWindows: |
|
| 41 |
- return &windowsParser{}
|
|
| 42 |
- } |
|
| 43 |
- if runtime.GOOS == OSWindows {
|
|
| 44 |
- return &lcowParser{}
|
|
| 45 |
- } |
|
| 46 |
- return &linuxParser{}
|
|
| 47 |
-} |
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
"github.com/docker/docker/pkg/locker" |
| 15 | 15 |
"github.com/docker/docker/volume" |
| 16 | 16 |
"github.com/docker/docker/volume/drivers" |
| 17 |
+ volumemounts "github.com/docker/docker/volume/mounts" |
|
| 17 | 18 |
"github.com/sirupsen/logrus" |
| 18 | 19 |
) |
| 19 | 20 |
|
| ... | ... |
@@ -387,7 +388,7 @@ func (s *VolumeStore) create(name, driverName string, opts, labels map[string]st |
| 387 | 387 |
|
| 388 | 388 |
// volume name validation is specific to the host os and not on container image |
| 389 | 389 |
// windows/lcow should have an equivalent volumename validation logic so we create a parser for current host OS |
| 390 |
- parser := volume.NewParser(runtime.GOOS) |
|
| 390 |
+ parser := volumemounts.NewParser(runtime.GOOS) |
|
| 391 | 391 |
err := parser.ValidateVolumeName(name) |
| 392 | 392 |
if err != nil {
|
| 393 | 393 |
return nil, err |
| 394 | 394 |
deleted file mode 100644 |
| ... | ... |
@@ -1,28 +0,0 @@ |
| 1 |
-package volume // import "github.com/docker/docker/volume" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- |
|
| 6 |
- "github.com/docker/docker/api/types/mount" |
|
| 7 |
- "github.com/pkg/errors" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-type errMountConfig struct {
|
|
| 11 |
- mount *mount.Mount |
|
| 12 |
- err error |
|
| 13 |
-} |
|
| 14 |
- |
|
| 15 |
-func (e *errMountConfig) Error() string {
|
|
| 16 |
- return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error())
|
|
| 17 |
-} |
|
| 18 |
- |
|
| 19 |
-func errBindSourceDoesNotExist(path string) error {
|
|
| 20 |
- return errors.Errorf("bind mount source path does not exist: %s", path)
|
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-func errExtraField(name string) error {
|
|
| 24 |
- return errors.Errorf("field %s must not be specified", name)
|
|
| 25 |
-} |
|
| 26 |
-func errMissingField(name string) error {
|
|
| 27 |
- return errors.Errorf("field %s must not be empty", name)
|
|
| 28 |
-} |
| 29 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,73 +0,0 @@ |
| 1 |
-package volume // import "github.com/docker/docker/volume" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
- "io/ioutil" |
|
| 6 |
- "os" |
|
| 7 |
- "runtime" |
|
| 8 |
- "strings" |
|
| 9 |
- "testing" |
|
| 10 |
- |
|
| 11 |
- "github.com/docker/docker/api/types/mount" |
|
| 12 |
-) |
|
| 13 |
- |
|
| 14 |
-func TestValidateMount(t *testing.T) {
|
|
| 15 |
- testDir, err := ioutil.TempDir("", "test-validate-mount")
|
|
| 16 |
- if err != nil {
|
|
| 17 |
- t.Fatal(err) |
|
| 18 |
- } |
|
| 19 |
- defer os.RemoveAll(testDir) |
|
| 20 |
- |
|
| 21 |
- cases := []struct {
|
|
| 22 |
- input mount.Mount |
|
| 23 |
- expected error |
|
| 24 |
- }{
|
|
| 25 |
- {mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
|
|
| 26 |
- {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath, Source: "hello"}, nil},
|
|
| 27 |
- {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, nil},
|
|
| 28 |
- {mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
|
|
| 29 |
- {mount.Mount{Type: mount.TypeBind, Target: testDestinationPath}, errMissingField("Source")},
|
|
| 30 |
- {mount.Mount{Type: mount.TypeBind, Target: testDestinationPath, Source: testSourcePath, VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
|
|
| 31 |
- |
|
| 32 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, nil},
|
|
| 33 |
- {mount.Mount{Type: "invalid", Target: testDestinationPath}, errors.New("mount type unknown")},
|
|
| 34 |
- {mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindSourceDoesNotExist(testSourcePath)},
|
|
| 35 |
- } |
|
| 36 |
- |
|
| 37 |
- lcowCases := []struct {
|
|
| 38 |
- input mount.Mount |
|
| 39 |
- expected error |
|
| 40 |
- }{
|
|
| 41 |
- {mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")},
|
|
| 42 |
- {mount.Mount{Type: mount.TypeVolume, Target: "/foo", Source: "hello"}, nil},
|
|
| 43 |
- {mount.Mount{Type: mount.TypeVolume, Target: "/foo"}, nil},
|
|
| 44 |
- {mount.Mount{Type: mount.TypeBind}, errMissingField("Target")},
|
|
| 45 |
- {mount.Mount{Type: mount.TypeBind, Target: "/foo"}, errMissingField("Source")},
|
|
| 46 |
- {mount.Mount{Type: mount.TypeBind, Target: "/foo", Source: "c:\\foo", VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")},
|
|
| 47 |
- {mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindSourceDoesNotExist("c:\\foo")},
|
|
| 48 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: "/foo"}, nil},
|
|
| 49 |
- {mount.Mount{Type: "invalid", Target: "/foo"}, errors.New("mount type unknown")},
|
|
| 50 |
- } |
|
| 51 |
- parser := NewParser(runtime.GOOS) |
|
| 52 |
- for i, x := range cases {
|
|
| 53 |
- err := parser.ValidateMountConfig(&x.input) |
|
| 54 |
- if err == nil && x.expected == nil {
|
|
| 55 |
- continue |
|
| 56 |
- } |
|
| 57 |
- if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
|
|
| 58 |
- t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
|
|
| 59 |
- } |
|
| 60 |
- } |
|
| 61 |
- if runtime.GOOS == "windows" {
|
|
| 62 |
- parser = &lcowParser{}
|
|
| 63 |
- for i, x := range lcowCases {
|
|
| 64 |
- err := parser.ValidateMountConfig(&x.input) |
|
| 65 |
- if err == nil && x.expected == nil {
|
|
| 66 |
- continue |
|
| 67 |
- } |
|
| 68 |
- if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) {
|
|
| 69 |
- t.Errorf("expected %q, got %q, case: %d", x.expected, err, i)
|
|
| 70 |
- } |
|
| 71 |
- } |
|
| 72 |
- } |
|
| 73 |
-} |
| ... | ... |
@@ -1,17 +1,7 @@ |
| 1 | 1 |
package volume // import "github.com/docker/docker/volume" |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "fmt" |
|
| 5 |
- "os" |
|
| 6 |
- "path/filepath" |
|
| 7 |
- "syscall" |
|
| 8 | 4 |
"time" |
| 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/selinux/go-selinux/label" |
|
| 14 |
- "github.com/pkg/errors" |
|
| 15 | 5 |
) |
| 16 | 6 |
|
| 17 | 7 |
// DefaultDriverName is the driver name used for the driver |
| ... | ... |
@@ -77,155 +67,3 @@ type DetailedVolume interface {
|
| 77 | 77 |
Scope() string |
| 78 | 78 |
Volume |
| 79 | 79 |
} |
| 80 |
- |
|
| 81 |
-// MountPoint is the intersection point between a volume and a container. It |
|
| 82 |
-// specifies which volume is to be used and where inside a container it should |
|
| 83 |
-// be mounted. |
|
| 84 |
-type MountPoint struct {
|
|
| 85 |
- // Source is the source path of the mount. |
|
| 86 |
- // E.g. `mount --bind /foo /bar`, `/foo` is the `Source`. |
|
| 87 |
- Source string |
|
| 88 |
- // Destination is the path relative to the container root (`/`) to the mount point |
|
| 89 |
- // It is where the `Source` is mounted to |
|
| 90 |
- Destination string |
|
| 91 |
- // RW is set to true when the mountpoint should be mounted as read-write |
|
| 92 |
- RW bool |
|
| 93 |
- // Name is the name reference to the underlying data defined by `Source` |
|
| 94 |
- // e.g., the volume name |
|
| 95 |
- Name string |
|
| 96 |
- // Driver is the volume driver used to create the volume (if it is a volume) |
|
| 97 |
- Driver string |
|
| 98 |
- // Type of mount to use, see `Type<foo>` definitions in github.com/docker/docker/api/types/mount |
|
| 99 |
- Type mounttypes.Type `json:",omitempty"` |
|
| 100 |
- // Volume is the volume providing data to this mountpoint. |
|
| 101 |
- // This is nil unless `Type` is set to `TypeVolume` |
|
| 102 |
- Volume Volume `json:"-"` |
|
| 103 |
- |
|
| 104 |
- // Mode is the comma separated list of options supplied by the user when creating |
|
| 105 |
- // the bind/volume mount. |
|
| 106 |
- // Note Mode is not used on Windows |
|
| 107 |
- Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`" |
|
| 108 |
- |
|
| 109 |
- // Propagation describes how the mounts are propagated from the host into the |
|
| 110 |
- // mount point, and vice-versa. |
|
| 111 |
- // See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt |
|
| 112 |
- // Note Propagation is not used on Windows |
|
| 113 |
- Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string |
|
| 114 |
- |
|
| 115 |
- // Specifies if data should be copied from the container before the first mount |
|
| 116 |
- // Use a pointer here so we can tell if the user set this value explicitly |
|
| 117 |
- // This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated |
|
| 118 |
- CopyData bool `json:"-"` |
|
| 119 |
- // ID is the opaque ID used to pass to the volume driver. |
|
| 120 |
- // This should be set by calls to `Mount` and unset by calls to `Unmount` |
|
| 121 |
- ID string `json:",omitempty"` |
|
| 122 |
- |
|
| 123 |
- // Sepc is a copy of the API request that created this mount. |
|
| 124 |
- Spec mounttypes.Mount |
|
| 125 |
- |
|
| 126 |
- // Track usage of this mountpoint |
|
| 127 |
- // Specifically needed for containers which are running and calls to `docker cp` |
|
| 128 |
- // because both these actions require mounting the volumes. |
|
| 129 |
- active int |
|
| 130 |
-} |
|
| 131 |
- |
|
| 132 |
-// Cleanup frees resources used by the mountpoint |
|
| 133 |
-func (m *MountPoint) Cleanup() error {
|
|
| 134 |
- if m.Volume == nil || m.ID == "" {
|
|
| 135 |
- return nil |
|
| 136 |
- } |
|
| 137 |
- |
|
| 138 |
- if err := m.Volume.Unmount(m.ID); err != nil {
|
|
| 139 |
- return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name()) |
|
| 140 |
- } |
|
| 141 |
- |
|
| 142 |
- m.active-- |
|
| 143 |
- if m.active == 0 {
|
|
| 144 |
- m.ID = "" |
|
| 145 |
- } |
|
| 146 |
- return nil |
|
| 147 |
-} |
|
| 148 |
- |
|
| 149 |
-// Setup sets up a mount point by either mounting the volume if it is |
|
| 150 |
-// configured, or creating the source directory if supplied. |
|
| 151 |
-// The, optional, checkFun parameter allows doing additional checking |
|
| 152 |
-// before creating the source directory on the host. |
|
| 153 |
-func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) {
|
|
| 154 |
- defer func() {
|
|
| 155 |
- if err != nil || !label.RelabelNeeded(m.Mode) {
|
|
| 156 |
- return |
|
| 157 |
- } |
|
| 158 |
- |
|
| 159 |
- var sourcePath string |
|
| 160 |
- sourcePath, err = filepath.EvalSymlinks(m.Source) |
|
| 161 |
- if err != nil {
|
|
| 162 |
- path = "" |
|
| 163 |
- err = errors.Wrapf(err, "error evaluating symlinks from mount source %q", m.Source) |
|
| 164 |
- return |
|
| 165 |
- } |
|
| 166 |
- err = label.Relabel(sourcePath, mountLabel, label.IsShared(m.Mode)) |
|
| 167 |
- if err == syscall.ENOTSUP {
|
|
| 168 |
- err = nil |
|
| 169 |
- } |
|
| 170 |
- if err != nil {
|
|
| 171 |
- path = "" |
|
| 172 |
- err = errors.Wrapf(err, "error setting label on mount source '%s'", sourcePath) |
|
| 173 |
- } |
|
| 174 |
- }() |
|
| 175 |
- |
|
| 176 |
- if m.Volume != nil {
|
|
| 177 |
- id := m.ID |
|
| 178 |
- if id == "" {
|
|
| 179 |
- id = stringid.GenerateNonCryptoID() |
|
| 180 |
- } |
|
| 181 |
- path, err := m.Volume.Mount(id) |
|
| 182 |
- if err != nil {
|
|
| 183 |
- return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source) |
|
| 184 |
- } |
|
| 185 |
- |
|
| 186 |
- m.ID = id |
|
| 187 |
- m.active++ |
|
| 188 |
- return path, nil |
|
| 189 |
- } |
|
| 190 |
- |
|
| 191 |
- if len(m.Source) == 0 {
|
|
| 192 |
- return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined")
|
|
| 193 |
- } |
|
| 194 |
- |
|
| 195 |
- if m.Type == mounttypes.TypeBind {
|
|
| 196 |
- // Before creating the source directory on the host, invoke checkFun if it's not nil. One of |
|
| 197 |
- // the use case is to forbid creating the daemon socket as a directory if the daemon is in |
|
| 198 |
- // the process of shutting down. |
|
| 199 |
- if checkFun != nil {
|
|
| 200 |
- if err := checkFun(m); err != nil {
|
|
| 201 |
- return "", err |
|
| 202 |
- } |
|
| 203 |
- } |
|
| 204 |
- // idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory) |
|
| 205 |
- // also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it |
|
| 206 |
- if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil {
|
|
| 207 |
- if perr, ok := err.(*os.PathError); ok {
|
|
| 208 |
- if perr.Err != syscall.ENOTDIR {
|
|
| 209 |
- return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source) |
|
| 210 |
- } |
|
| 211 |
- } |
|
| 212 |
- } |
|
| 213 |
- } |
|
| 214 |
- return m.Source, nil |
|
| 215 |
-} |
|
| 216 |
- |
|
| 217 |
-// Path returns the path of a volume in a mount point. |
|
| 218 |
-func (m *MountPoint) Path() string {
|
|
| 219 |
- if m.Volume != nil {
|
|
| 220 |
- return m.Volume.Path() |
|
| 221 |
- } |
|
| 222 |
- return m.Source |
|
| 223 |
-} |
|
| 224 |
- |
|
| 225 |
-func errInvalidMode(mode string) error {
|
|
| 226 |
- return errors.Errorf("invalid mode: %v", mode)
|
|
| 227 |
-} |
|
| 228 |
- |
|
| 229 |
-func errInvalidSpec(spec string) error {
|
|
| 230 |
- return errors.Errorf("invalid volume specification: '%s'", spec)
|
|
| 231 |
-} |
| 232 | 80 |
deleted file mode 100644 |
| ... | ... |
@@ -1,23 +0,0 @@ |
| 1 |
-package volume // import "github.com/docker/docker/volume" |
|
| 2 |
- |
|
| 3 |
-import "strings" |
|
| 4 |
- |
|
| 5 |
-// {<copy mode>=isEnabled}
|
|
| 6 |
-var copyModes = map[string]bool{
|
|
| 7 |
- "nocopy": false, |
|
| 8 |
-} |
|
| 9 |
- |
|
| 10 |
-func copyModeExists(mode string) bool {
|
|
| 11 |
- _, exists := copyModes[mode] |
|
| 12 |
- return exists |
|
| 13 |
-} |
|
| 14 |
- |
|
| 15 |
-// GetCopyMode gets the copy mode from the mode string for mounts |
|
| 16 |
-func getCopyMode(mode string, def bool) (bool, bool) {
|
|
| 17 |
- for _, o := range strings.Split(mode, ",") {
|
|
| 18 |
- if isEnabled, exists := copyModes[o]; exists {
|
|
| 19 |
- return isEnabled, true |
|
| 20 |
- } |
|
| 21 |
- } |
|
| 22 |
- return def, false |
|
| 23 |
-} |
| 24 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,480 +0,0 @@ |
| 1 |
-package volume // import "github.com/docker/docker/volume" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "io/ioutil" |
|
| 5 |
- "os" |
|
| 6 |
- "runtime" |
|
| 7 |
- "strings" |
|
| 8 |
- "testing" |
|
| 9 |
- |
|
| 10 |
- "github.com/docker/docker/api/types/mount" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-type parseMountRawTestSet struct {
|
|
| 14 |
- valid []string |
|
| 15 |
- invalid map[string]string |
|
| 16 |
-} |
|
| 17 |
- |
|
| 18 |
-func TestConvertTmpfsOptions(t *testing.T) {
|
|
| 19 |
- type testCase struct {
|
|
| 20 |
- opt mount.TmpfsOptions |
|
| 21 |
- readOnly bool |
|
| 22 |
- expectedSubstrings []string |
|
| 23 |
- unexpectedSubstrings []string |
|
| 24 |
- } |
|
| 25 |
- cases := []testCase{
|
|
| 26 |
- {
|
|
| 27 |
- opt: mount.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700},
|
|
| 28 |
- readOnly: false, |
|
| 29 |
- expectedSubstrings: []string{"size=1m", "mode=700"},
|
|
| 30 |
- unexpectedSubstrings: []string{"ro"},
|
|
| 31 |
- }, |
|
| 32 |
- {
|
|
| 33 |
- opt: mount.TmpfsOptions{},
|
|
| 34 |
- readOnly: true, |
|
| 35 |
- expectedSubstrings: []string{"ro"},
|
|
| 36 |
- unexpectedSubstrings: []string{},
|
|
| 37 |
- }, |
|
| 38 |
- } |
|
| 39 |
- p := &linuxParser{}
|
|
| 40 |
- for _, c := range cases {
|
|
| 41 |
- data, err := p.ConvertTmpfsOptions(&c.opt, c.readOnly) |
|
| 42 |
- if err != nil {
|
|
| 43 |
- t.Fatalf("could not convert %+v (readOnly: %v) to string: %v",
|
|
| 44 |
- c.opt, c.readOnly, err) |
|
| 45 |
- } |
|
| 46 |
- t.Logf("data=%q", data)
|
|
| 47 |
- for _, s := range c.expectedSubstrings {
|
|
| 48 |
- if !strings.Contains(data, s) {
|
|
| 49 |
- t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c)
|
|
| 50 |
- } |
|
| 51 |
- } |
|
| 52 |
- for _, s := range c.unexpectedSubstrings {
|
|
| 53 |
- if strings.Contains(data, s) {
|
|
| 54 |
- t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c)
|
|
| 55 |
- } |
|
| 56 |
- } |
|
| 57 |
- } |
|
| 58 |
-} |
|
| 59 |
- |
|
| 60 |
-type mockFiProvider struct{}
|
|
| 61 |
- |
|
| 62 |
-func (mockFiProvider) fileInfo(path string) (exists, isDir bool, err error) {
|
|
| 63 |
- dirs := map[string]struct{}{
|
|
| 64 |
- `c:\`: {},
|
|
| 65 |
- `c:\windows\`: {},
|
|
| 66 |
- `c:\windows`: {},
|
|
| 67 |
- `c:\program files`: {},
|
|
| 68 |
- `c:\Windows`: {},
|
|
| 69 |
- `c:\Program Files (x86)`: {},
|
|
| 70 |
- `\\?\c:\windows\`: {},
|
|
| 71 |
- } |
|
| 72 |
- files := map[string]struct{}{
|
|
| 73 |
- `c:\windows\system32\ntdll.dll`: {},
|
|
| 74 |
- } |
|
| 75 |
- if _, ok := dirs[path]; ok {
|
|
| 76 |
- return true, true, nil |
|
| 77 |
- } |
|
| 78 |
- if _, ok := files[path]; ok {
|
|
| 79 |
- return true, false, nil |
|
| 80 |
- } |
|
| 81 |
- return false, false, nil |
|
| 82 |
-} |
|
| 83 |
- |
|
| 84 |
-func TestParseMountRaw(t *testing.T) {
|
|
| 85 |
- |
|
| 86 |
- previousProvider := currentFileInfoProvider |
|
| 87 |
- defer func() { currentFileInfoProvider = previousProvider }()
|
|
| 88 |
- currentFileInfoProvider = mockFiProvider{}
|
|
| 89 |
- windowsSet := parseMountRawTestSet{
|
|
| 90 |
- valid: []string{
|
|
| 91 |
- `d:\`, |
|
| 92 |
- `d:`, |
|
| 93 |
- `d:\path`, |
|
| 94 |
- `d:\path with space`, |
|
| 95 |
- `c:\:d:\`, |
|
| 96 |
- `c:\windows\:d:`, |
|
| 97 |
- `c:\windows:d:\s p a c e`, |
|
| 98 |
- `c:\windows:d:\s p a c e:RW`, |
|
| 99 |
- `c:\program files:d:\s p a c e i n h o s t d i r`, |
|
| 100 |
- `0123456789name:d:`, |
|
| 101 |
- `MiXeDcAsEnAmE:d:`, |
|
| 102 |
- `name:D:`, |
|
| 103 |
- `name:D::rW`, |
|
| 104 |
- `name:D::RW`, |
|
| 105 |
- `name:D::RO`, |
|
| 106 |
- `c:/:d:/forward/slashes/are/good/too`, |
|
| 107 |
- `c:/:d:/including with/spaces:ro`, |
|
| 108 |
- `c:\Windows`, // With capital |
|
| 109 |
- `c:\Program Files (x86)`, // With capitals and brackets |
|
| 110 |
- `\\?\c:\windows\:d:`, // Long path handling (source) |
|
| 111 |
- `c:\windows\:\\?\d:\`, // Long path handling (target) |
|
| 112 |
- `\\.\pipe\foo:\\.\pipe\foo`, // named pipe |
|
| 113 |
- `//./pipe/foo://./pipe/foo`, // named pipe forward slashes |
|
| 114 |
- }, |
|
| 115 |
- invalid: map[string]string{
|
|
| 116 |
- ``: "invalid volume specification: ", |
|
| 117 |
- `.`: "invalid volume specification: ", |
|
| 118 |
- `..\`: "invalid volume specification: ", |
|
| 119 |
- `c:\:..\`: "invalid volume specification: ", |
|
| 120 |
- `c:\:d:\:xyzzy`: "invalid volume specification: ", |
|
| 121 |
- `c:`: "cannot be `c:`", |
|
| 122 |
- `c:\`: "cannot be `c:`", |
|
| 123 |
- `c:\notexist:d:`: `bind mount source path does not exist: c:\notexist`, |
|
| 124 |
- `c:\windows\system32\ntdll.dll:d:`: `source path must be a directory`, |
|
| 125 |
- `name<:d:`: `invalid volume specification`, |
|
| 126 |
- `name>:d:`: `invalid volume specification`, |
|
| 127 |
- `name::d:`: `invalid volume specification`, |
|
| 128 |
- `name":d:`: `invalid volume specification`, |
|
| 129 |
- `name\:d:`: `invalid volume specification`, |
|
| 130 |
- `name*:d:`: `invalid volume specification`, |
|
| 131 |
- `name|:d:`: `invalid volume specification`, |
|
| 132 |
- `name?:d:`: `invalid volume specification`, |
|
| 133 |
- `name/:d:`: `invalid volume specification`, |
|
| 134 |
- `d:\pathandmode:rw`: `invalid volume specification`, |
|
| 135 |
- `d:\pathandmode:ro`: `invalid volume specification`, |
|
| 136 |
- `con:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 137 |
- `PRN:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 138 |
- `aUx:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 139 |
- `nul:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 140 |
- `com1:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 141 |
- `com2:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 142 |
- `com3:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 143 |
- `com4:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 144 |
- `com5:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 145 |
- `com6:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 146 |
- `com7:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 147 |
- `com8:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 148 |
- `com9:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 149 |
- `lpt1:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 150 |
- `lpt2:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 151 |
- `lpt3:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 152 |
- `lpt4:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 153 |
- `lpt5:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 154 |
- `lpt6:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 155 |
- `lpt7:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 156 |
- `lpt8:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 157 |
- `lpt9:d:`: `cannot be a reserved word for Windows filenames`, |
|
| 158 |
- `c:\windows\system32\ntdll.dll`: `Only directories can be mapped on this platform`, |
|
| 159 |
- `\\.\pipe\foo:c:\pipe`: `'c:\pipe' is not a valid pipe path`, |
|
| 160 |
- }, |
|
| 161 |
- } |
|
| 162 |
- lcowSet := parseMountRawTestSet{
|
|
| 163 |
- valid: []string{
|
|
| 164 |
- `/foo`, |
|
| 165 |
- `/foo/`, |
|
| 166 |
- `/foo bar`, |
|
| 167 |
- `c:\:/foo`, |
|
| 168 |
- `c:\windows\:/foo`, |
|
| 169 |
- `c:\windows:/s p a c e`, |
|
| 170 |
- `c:\windows:/s p a c e:RW`, |
|
| 171 |
- `c:\program files:/s p a c e i n h o s t d i r`, |
|
| 172 |
- `0123456789name:/foo`, |
|
| 173 |
- `MiXeDcAsEnAmE:/foo`, |
|
| 174 |
- `name:/foo`, |
|
| 175 |
- `name:/foo:rW`, |
|
| 176 |
- `name:/foo:RW`, |
|
| 177 |
- `name:/foo:RO`, |
|
| 178 |
- `c:/:/forward/slashes/are/good/too`, |
|
| 179 |
- `c:/:/including with/spaces:ro`, |
|
| 180 |
- `/Program Files (x86)`, // With capitals and brackets |
|
| 181 |
- }, |
|
| 182 |
- invalid: map[string]string{
|
|
| 183 |
- ``: "invalid volume specification: ", |
|
| 184 |
- `.`: "invalid volume specification: ", |
|
| 185 |
- `c:`: "invalid volume specification: ", |
|
| 186 |
- `c:\`: "invalid volume specification: ", |
|
| 187 |
- `../`: "invalid volume specification: ", |
|
| 188 |
- `c:\:../`: "invalid volume specification: ", |
|
| 189 |
- `c:\:/foo:xyzzy`: "invalid volume specification: ", |
|
| 190 |
- `/`: "destination can't be '/'", |
|
| 191 |
- `/..`: "destination can't be '/'", |
|
| 192 |
- `c:\notexist:/foo`: `bind mount source path does not exist: c:\notexist`, |
|
| 193 |
- `c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`, |
|
| 194 |
- `name<:/foo`: `invalid volume specification`, |
|
| 195 |
- `name>:/foo`: `invalid volume specification`, |
|
| 196 |
- `name::/foo`: `invalid volume specification`, |
|
| 197 |
- `name":/foo`: `invalid volume specification`, |
|
| 198 |
- `name\:/foo`: `invalid volume specification`, |
|
| 199 |
- `name*:/foo`: `invalid volume specification`, |
|
| 200 |
- `name|:/foo`: `invalid volume specification`, |
|
| 201 |
- `name?:/foo`: `invalid volume specification`, |
|
| 202 |
- `name/:/foo`: `invalid volume specification`, |
|
| 203 |
- `/foo:rw`: `invalid volume specification`, |
|
| 204 |
- `/foo:ro`: `invalid volume specification`, |
|
| 205 |
- `con:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 206 |
- `PRN:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 207 |
- `aUx:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 208 |
- `nul:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 209 |
- `com1:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 210 |
- `com2:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 211 |
- `com3:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 212 |
- `com4:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 213 |
- `com5:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 214 |
- `com6:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 215 |
- `com7:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 216 |
- `com8:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 217 |
- `com9:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 218 |
- `lpt1:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 219 |
- `lpt2:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 220 |
- `lpt3:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 221 |
- `lpt4:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 222 |
- `lpt5:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 223 |
- `lpt6:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 224 |
- `lpt7:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 225 |
- `lpt8:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 226 |
- `lpt9:/foo`: `cannot be a reserved word for Windows filenames`, |
|
| 227 |
- `\\.\pipe\foo:/foo`: `Linux containers on Windows do not support named pipe mounts`, |
|
| 228 |
- }, |
|
| 229 |
- } |
|
| 230 |
- linuxSet := parseMountRawTestSet{
|
|
| 231 |
- valid: []string{
|
|
| 232 |
- "/home", |
|
| 233 |
- "/home:/home", |
|
| 234 |
- "/home:/something/else", |
|
| 235 |
- "/with space", |
|
| 236 |
- "/home:/with space", |
|
| 237 |
- "relative:/absolute-path", |
|
| 238 |
- "hostPath:/containerPath:ro", |
|
| 239 |
- "/hostPath:/containerPath:rw", |
|
| 240 |
- "/rw:/ro", |
|
| 241 |
- "/hostPath:/containerPath:shared", |
|
| 242 |
- "/hostPath:/containerPath:rshared", |
|
| 243 |
- "/hostPath:/containerPath:slave", |
|
| 244 |
- "/hostPath:/containerPath:rslave", |
|
| 245 |
- "/hostPath:/containerPath:private", |
|
| 246 |
- "/hostPath:/containerPath:rprivate", |
|
| 247 |
- "/hostPath:/containerPath:ro,shared", |
|
| 248 |
- "/hostPath:/containerPath:ro,slave", |
|
| 249 |
- "/hostPath:/containerPath:ro,private", |
|
| 250 |
- "/hostPath:/containerPath:ro,z,shared", |
|
| 251 |
- "/hostPath:/containerPath:ro,Z,slave", |
|
| 252 |
- "/hostPath:/containerPath:Z,ro,slave", |
|
| 253 |
- "/hostPath:/containerPath:slave,Z,ro", |
|
| 254 |
- "/hostPath:/containerPath:Z,slave,ro", |
|
| 255 |
- "/hostPath:/containerPath:slave,ro,Z", |
|
| 256 |
- "/hostPath:/containerPath:rslave,ro,Z", |
|
| 257 |
- "/hostPath:/containerPath:ro,rshared,Z", |
|
| 258 |
- "/hostPath:/containerPath:ro,Z,rprivate", |
|
| 259 |
- }, |
|
| 260 |
- invalid: map[string]string{
|
|
| 261 |
- "": "invalid volume specification", |
|
| 262 |
- "./": "mount path must be absolute", |
|
| 263 |
- "../": "mount path must be absolute", |
|
| 264 |
- "/:../": "mount path must be absolute", |
|
| 265 |
- "/:path": "mount path must be absolute", |
|
| 266 |
- ":": "invalid volume specification", |
|
| 267 |
- "/tmp:": "invalid volume specification", |
|
| 268 |
- ":test": "invalid volume specification", |
|
| 269 |
- ":/test": "invalid volume specification", |
|
| 270 |
- "tmp:": "invalid volume specification", |
|
| 271 |
- ":test:": "invalid volume specification", |
|
| 272 |
- "::": "invalid volume specification", |
|
| 273 |
- ":::": "invalid volume specification", |
|
| 274 |
- "/tmp:::": "invalid volume specification", |
|
| 275 |
- ":/tmp::": "invalid volume specification", |
|
| 276 |
- "/path:rw": "invalid volume specification", |
|
| 277 |
- "/path:ro": "invalid volume specification", |
|
| 278 |
- "/rw:rw": "invalid volume specification", |
|
| 279 |
- "path:ro": "invalid volume specification", |
|
| 280 |
- "/path:/path:sw": `invalid mode`, |
|
| 281 |
- "/path:/path:rwz": `invalid mode`, |
|
| 282 |
- "/path:/path:ro,rshared,rslave": `invalid mode`, |
|
| 283 |
- "/path:/path:ro,z,rshared,rslave": `invalid mode`, |
|
| 284 |
- "/path:shared": "invalid volume specification", |
|
| 285 |
- "/path:slave": "invalid volume specification", |
|
| 286 |
- "/path:private": "invalid volume specification", |
|
| 287 |
- "name:/absolute-path:shared": "invalid volume specification", |
|
| 288 |
- "name:/absolute-path:rshared": "invalid volume specification", |
|
| 289 |
- "name:/absolute-path:slave": "invalid volume specification", |
|
| 290 |
- "name:/absolute-path:rslave": "invalid volume specification", |
|
| 291 |
- "name:/absolute-path:private": "invalid volume specification", |
|
| 292 |
- "name:/absolute-path:rprivate": "invalid volume specification", |
|
| 293 |
- }, |
|
| 294 |
- } |
|
| 295 |
- |
|
| 296 |
- linParser := &linuxParser{}
|
|
| 297 |
- winParser := &windowsParser{}
|
|
| 298 |
- lcowParser := &lcowParser{}
|
|
| 299 |
- tester := func(parser Parser, set parseMountRawTestSet) {
|
|
| 300 |
- |
|
| 301 |
- for _, path := range set.valid {
|
|
| 302 |
- |
|
| 303 |
- if _, err := parser.ParseMountRaw(path, "local"); err != nil {
|
|
| 304 |
- t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err)
|
|
| 305 |
- } |
|
| 306 |
- } |
|
| 307 |
- |
|
| 308 |
- for path, expectedError := range set.invalid {
|
|
| 309 |
- if mp, err := parser.ParseMountRaw(path, "local"); err == nil {
|
|
| 310 |
- t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp)
|
|
| 311 |
- } else {
|
|
| 312 |
- if !strings.Contains(err.Error(), expectedError) {
|
|
| 313 |
- t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error())
|
|
| 314 |
- } |
|
| 315 |
- } |
|
| 316 |
- } |
|
| 317 |
- } |
|
| 318 |
- tester(linParser, linuxSet) |
|
| 319 |
- tester(winParser, windowsSet) |
|
| 320 |
- tester(lcowParser, lcowSet) |
|
| 321 |
- |
|
| 322 |
-} |
|
| 323 |
- |
|
| 324 |
-// testParseMountRaw is a structure used by TestParseMountRawSplit for |
|
| 325 |
-// specifying test cases for the ParseMountRaw() function. |
|
| 326 |
-type testParseMountRaw struct {
|
|
| 327 |
- bind string |
|
| 328 |
- driver string |
|
| 329 |
- expType mount.Type |
|
| 330 |
- expDest string |
|
| 331 |
- expSource string |
|
| 332 |
- expName string |
|
| 333 |
- expDriver string |
|
| 334 |
- expRW bool |
|
| 335 |
- fail bool |
|
| 336 |
-} |
|
| 337 |
- |
|
| 338 |
-func TestParseMountRawSplit(t *testing.T) {
|
|
| 339 |
- previousProvider := currentFileInfoProvider |
|
| 340 |
- defer func() { currentFileInfoProvider = previousProvider }()
|
|
| 341 |
- currentFileInfoProvider = mockFiProvider{}
|
|
| 342 |
- windowsCases := []testParseMountRaw{
|
|
| 343 |
- {`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false},
|
|
| 344 |
- {`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
|
|
| 345 |
- {`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false},
|
|
| 346 |
- {`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false},
|
|
| 347 |
- {`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true},
|
|
| 348 |
- {`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
|
|
| 349 |
- {`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false},
|
|
| 350 |
- {`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false},
|
|
| 351 |
- {`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
|
| 352 |
- {`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
|
| 353 |
- {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false},
|
|
| 354 |
- {`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
|
| 355 |
- {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
|
| 356 |
- } |
|
| 357 |
- lcowCases := []testParseMountRaw{
|
|
| 358 |
- {`c:\:/foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
|
|
| 359 |
- {`c:\:/foo:ro`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, false},
|
|
| 360 |
- {`c:\:/foo:rw`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false},
|
|
| 361 |
- {`c:\:/foo:foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, true},
|
|
| 362 |
- {`name:/foo:rw`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
|
|
| 363 |
- {`name:/foo`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false},
|
|
| 364 |
- {`name:/foo:ro`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", false, false},
|
|
| 365 |
- {`name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
|
| 366 |
- {`driver/name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true},
|
|
| 367 |
- {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, true},
|
|
| 368 |
- {`\\.\pipe\foo:/data`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
|
| 369 |
- {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true},
|
|
| 370 |
- } |
|
| 371 |
- linuxCases := []testParseMountRaw{
|
|
| 372 |
- {"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false},
|
|
| 373 |
- {"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false},
|
|
| 374 |
- {"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false},
|
|
| 375 |
- {"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true},
|
|
| 376 |
- {"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false},
|
|
| 377 |
- {"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false},
|
|
| 378 |
- {"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false},
|
|
| 379 |
- {"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false},
|
|
| 380 |
- {"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true},
|
|
| 381 |
- } |
|
| 382 |
- linParser := &linuxParser{}
|
|
| 383 |
- winParser := &windowsParser{}
|
|
| 384 |
- lcowParser := &lcowParser{}
|
|
| 385 |
- tester := func(parser Parser, cases []testParseMountRaw) {
|
|
| 386 |
- for i, c := range cases {
|
|
| 387 |
- t.Logf("case %d", i)
|
|
| 388 |
- m, err := parser.ParseMountRaw(c.bind, c.driver) |
|
| 389 |
- if c.fail {
|
|
| 390 |
- if err == nil {
|
|
| 391 |
- t.Errorf("Expected error, was nil, for spec %s\n", c.bind)
|
|
| 392 |
- } |
|
| 393 |
- continue |
|
| 394 |
- } |
|
| 395 |
- |
|
| 396 |
- if m == nil || err != nil {
|
|
| 397 |
- t.Errorf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error())
|
|
| 398 |
- continue |
|
| 399 |
- } |
|
| 400 |
- |
|
| 401 |
- if m.Destination != c.expDest {
|
|
| 402 |
- t.Errorf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind)
|
|
| 403 |
- } |
|
| 404 |
- |
|
| 405 |
- if m.Source != c.expSource {
|
|
| 406 |
- t.Errorf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind)
|
|
| 407 |
- } |
|
| 408 |
- |
|
| 409 |
- if m.Name != c.expName {
|
|
| 410 |
- t.Errorf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind)
|
|
| 411 |
- } |
|
| 412 |
- |
|
| 413 |
- if m.Driver != c.expDriver {
|
|
| 414 |
- t.Errorf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind)
|
|
| 415 |
- } |
|
| 416 |
- |
|
| 417 |
- if m.RW != c.expRW {
|
|
| 418 |
- t.Errorf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind)
|
|
| 419 |
- } |
|
| 420 |
- if m.Type != c.expType {
|
|
| 421 |
- t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind)
|
|
| 422 |
- } |
|
| 423 |
- } |
|
| 424 |
- } |
|
| 425 |
- |
|
| 426 |
- tester(linParser, linuxCases) |
|
| 427 |
- tester(winParser, windowsCases) |
|
| 428 |
- tester(lcowParser, lcowCases) |
|
| 429 |
-} |
|
| 430 |
- |
|
| 431 |
-func TestParseMountSpec(t *testing.T) {
|
|
| 432 |
- type c struct {
|
|
| 433 |
- input mount.Mount |
|
| 434 |
- expected MountPoint |
|
| 435 |
- } |
|
| 436 |
- testDir, err := ioutil.TempDir("", "test-mount-config")
|
|
| 437 |
- if err != nil {
|
|
| 438 |
- t.Fatal(err) |
|
| 439 |
- } |
|
| 440 |
- defer os.RemoveAll(testDir) |
|
| 441 |
- parser := NewParser(runtime.GOOS) |
|
| 442 |
- cases := []c{
|
|
| 443 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
|
|
| 444 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()}},
|
|
| 445 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
|
|
| 446 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}},
|
|
| 447 |
- {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
|
|
| 448 |
- {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}},
|
|
| 449 |
- } |
|
| 450 |
- |
|
| 451 |
- for i, c := range cases {
|
|
| 452 |
- t.Logf("case %d", i)
|
|
| 453 |
- mp, err := parser.ParseMountSpec(c.input) |
|
| 454 |
- if err != nil {
|
|
| 455 |
- t.Error(err) |
|
| 456 |
- } |
|
| 457 |
- |
|
| 458 |
- if c.expected.Type != mp.Type {
|
|
| 459 |
- t.Errorf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type)
|
|
| 460 |
- } |
|
| 461 |
- if c.expected.Destination != mp.Destination {
|
|
| 462 |
- t.Errorf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination)
|
|
| 463 |
- } |
|
| 464 |
- if c.expected.Source != mp.Source {
|
|
| 465 |
- t.Errorf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source)
|
|
| 466 |
- } |
|
| 467 |
- if c.expected.RW != mp.RW {
|
|
| 468 |
- t.Errorf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW)
|
|
| 469 |
- } |
|
| 470 |
- if c.expected.Propagation != mp.Propagation {
|
|
| 471 |
- t.Errorf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation)
|
|
| 472 |
- } |
|
| 473 |
- if c.expected.Driver != mp.Driver {
|
|
| 474 |
- t.Errorf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver)
|
|
| 475 |
- } |
|
| 476 |
- if c.expected.CopyData != mp.CopyData {
|
|
| 477 |
- t.Errorf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData)
|
|
| 478 |
- } |
|
| 479 |
- } |
|
| 480 |
-} |
| 481 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,18 +0,0 @@ |
| 1 |
-// +build linux freebsd darwin |
|
| 2 |
- |
|
| 3 |
-package volume // import "github.com/docker/docker/volume" |
|
| 4 |
- |
|
| 5 |
-import ( |
|
| 6 |
- "fmt" |
|
| 7 |
- "path/filepath" |
|
| 8 |
- "strings" |
|
| 9 |
-) |
|
| 10 |
- |
|
| 11 |
-func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
|
|
| 12 |
- relPath, err := filepath.Rel(m.Destination, absolutePath) |
|
| 13 |
- return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator))
|
|
| 14 |
-} |
|
| 15 |
- |
|
| 16 |
-func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
|
|
| 17 |
- return false |
|
| 18 |
-} |
| 19 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,8 +0,0 @@ |
| 1 |
-package volume // import "github.com/docker/docker/volume" |
|
| 2 |
- |
|
| 3 |
-func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool {
|
|
| 4 |
- return false |
|
| 5 |
-} |
|
| 6 |
-func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool {
|
|
| 7 |
- return false |
|
| 8 |
-} |
| 9 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,456 +0,0 @@ |
| 1 |
-package volume // import "github.com/docker/docker/volume" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "errors" |
|
| 5 |
- "fmt" |
|
| 6 |
- "os" |
|
| 7 |
- "regexp" |
|
| 8 |
- "runtime" |
|
| 9 |
- "strings" |
|
| 10 |
- |
|
| 11 |
- "github.com/docker/docker/api/types/mount" |
|
| 12 |
- "github.com/docker/docker/pkg/stringid" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-type windowsParser struct {
|
|
| 16 |
-} |
|
| 17 |
- |
|
| 18 |
-const ( |
|
| 19 |
- // Spec should be in the format [source:]destination[:mode] |
|
| 20 |
- // |
|
| 21 |
- // Examples: c:\foo bar:d:rw |
|
| 22 |
- // c:\foo:d:\bar |
|
| 23 |
- // myname:d: |
|
| 24 |
- // d:\ |
|
| 25 |
- // |
|
| 26 |
- // Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See |
|
| 27 |
- // https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to |
|
| 28 |
- // test is https://regex-golang.appspot.com/assets/html/index.html |
|
| 29 |
- // |
|
| 30 |
- // Useful link for referencing named capturing groups: |
|
| 31 |
- // http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex |
|
| 32 |
- // |
|
| 33 |
- // There are three match groups: source, destination and mode. |
|
| 34 |
- // |
|
| 35 |
- |
|
| 36 |
- // rxHostDir is the first option of a source |
|
| 37 |
- rxHostDir = `(?:\\\\\?\\)?[a-z]:[\\/](?:[^\\/:*?"<>|\r\n]+[\\/]?)*` |
|
| 38 |
- // rxName is the second option of a source |
|
| 39 |
- rxName = `[^\\/:*?"<>|\r\n]+` |
|
| 40 |
- |
|
| 41 |
- // RXReservedNames are reserved names not possible on Windows |
|
| 42 |
- rxReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])` |
|
| 43 |
- |
|
| 44 |
- // rxPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \) |
|
| 45 |
- rxPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+`
|
|
| 46 |
- // rxSource is the combined possibilities for a source |
|
| 47 |
- rxSource = `((?P<source>((` + rxHostDir + `)|(` + rxName + `)|(` + rxPipe + `))):)?` |
|
| 48 |
- |
|
| 49 |
- // Source. Can be either a host directory, a name, or omitted: |
|
| 50 |
- // HostDir: |
|
| 51 |
- // - Essentially using the folder solution from |
|
| 52 |
- // https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html |
|
| 53 |
- // but adding case insensitivity. |
|
| 54 |
- // - Must be an absolute path such as c:\path |
|
| 55 |
- // - Can include spaces such as `c:\program files` |
|
| 56 |
- // - And then followed by a colon which is not in the capture group |
|
| 57 |
- // - And can be optional |
|
| 58 |
- // Name: |
|
| 59 |
- // - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx) |
|
| 60 |
- // - And then followed by a colon which is not in the capture group |
|
| 61 |
- // - And can be optional |
|
| 62 |
- |
|
| 63 |
- // rxDestination is the regex expression for the mount destination |
|
| 64 |
- rxDestination = `(?P<destination>((?:\\\\\?\\)?([a-z]):((?:[\\/][^\\/:*?"<>\r\n]+)*[\\/]?))|(` + rxPipe + `))` |
|
| 65 |
- |
|
| 66 |
- rxLCOWDestination = `(?P<destination>/(?:[^\\/:*?"<>\r\n]+[/]?)*)` |
|
| 67 |
- // Destination (aka container path): |
|
| 68 |
- // - Variation on hostdir but can be a drive followed by colon as well |
|
| 69 |
- // - If a path, must be absolute. Can include spaces |
|
| 70 |
- // - Drive cannot be c: (explicitly checked in code, not RegEx) |
|
| 71 |
- |
|
| 72 |
- // rxMode is the regex expression for the mode of the mount |
|
| 73 |
- // Mode (optional): |
|
| 74 |
- // - Hopefully self explanatory in comparison to above regex's. |
|
| 75 |
- // - Colon is not in the capture group |
|
| 76 |
- rxMode = `(:(?P<mode>(?i)ro|rw))?` |
|
| 77 |
-) |
|
| 78 |
- |
|
| 79 |
-type mountValidator func(mnt *mount.Mount) error |
|
| 80 |
- |
|
| 81 |
-func windowsSplitRawSpec(raw, destRegex string) ([]string, error) {
|
|
| 82 |
- specExp := regexp.MustCompile(`^` + rxSource + destRegex + rxMode + `$`) |
|
| 83 |
- match := specExp.FindStringSubmatch(strings.ToLower(raw)) |
|
| 84 |
- |
|
| 85 |
- // Must have something back |
|
| 86 |
- if len(match) == 0 {
|
|
| 87 |
- return nil, errInvalidSpec(raw) |
|
| 88 |
- } |
|
| 89 |
- |
|
| 90 |
- var split []string |
|
| 91 |
- matchgroups := make(map[string]string) |
|
| 92 |
- // Pull out the sub expressions from the named capture groups |
|
| 93 |
- for i, name := range specExp.SubexpNames() {
|
|
| 94 |
- matchgroups[name] = strings.ToLower(match[i]) |
|
| 95 |
- } |
|
| 96 |
- if source, exists := matchgroups["source"]; exists {
|
|
| 97 |
- if source != "" {
|
|
| 98 |
- split = append(split, source) |
|
| 99 |
- } |
|
| 100 |
- } |
|
| 101 |
- if destination, exists := matchgroups["destination"]; exists {
|
|
| 102 |
- if destination != "" {
|
|
| 103 |
- split = append(split, destination) |
|
| 104 |
- } |
|
| 105 |
- } |
|
| 106 |
- if mode, exists := matchgroups["mode"]; exists {
|
|
| 107 |
- if mode != "" {
|
|
| 108 |
- split = append(split, mode) |
|
| 109 |
- } |
|
| 110 |
- } |
|
| 111 |
- // Fix #26329. If the destination appears to be a file, and the source is null, |
|
| 112 |
- // it may be because we've fallen through the possible naming regex and hit a |
|
| 113 |
- // situation where the user intention was to map a file into a container through |
|
| 114 |
- // a local volume, but this is not supported by the platform. |
|
| 115 |
- if matchgroups["source"] == "" && matchgroups["destination"] != "" {
|
|
| 116 |
- volExp := regexp.MustCompile(`^` + rxName + `$`) |
|
| 117 |
- reservedNameExp := regexp.MustCompile(`^` + rxReservedNames + `$`) |
|
| 118 |
- |
|
| 119 |
- if volExp.MatchString(matchgroups["destination"]) {
|
|
| 120 |
- if reservedNameExp.MatchString(matchgroups["destination"]) {
|
|
| 121 |
- return nil, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", matchgroups["destination"])
|
|
| 122 |
- } |
|
| 123 |
- } else {
|
|
| 124 |
- |
|
| 125 |
- exists, isDir, _ := currentFileInfoProvider.fileInfo(matchgroups["destination"]) |
|
| 126 |
- if exists && !isDir {
|
|
| 127 |
- return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"])
|
|
| 128 |
- |
|
| 129 |
- } |
|
| 130 |
- } |
|
| 131 |
- } |
|
| 132 |
- return split, nil |
|
| 133 |
-} |
|
| 134 |
- |
|
| 135 |
-func windowsValidMountMode(mode string) bool {
|
|
| 136 |
- if mode == "" {
|
|
| 137 |
- return true |
|
| 138 |
- } |
|
| 139 |
- return rwModes[strings.ToLower(mode)] |
|
| 140 |
-} |
|
| 141 |
-func windowsValidateNotRoot(p string) error {
|
|
| 142 |
- p = strings.ToLower(strings.Replace(p, `/`, `\`, -1)) |
|
| 143 |
- if p == "c:" || p == `c:\` {
|
|
| 144 |
- return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p)
|
|
| 145 |
- } |
|
| 146 |
- return nil |
|
| 147 |
-} |
|
| 148 |
- |
|
| 149 |
-var windowsSpecificValidators mountValidator = func(mnt *mount.Mount) error {
|
|
| 150 |
- return windowsValidateNotRoot(mnt.Target) |
|
| 151 |
-} |
|
| 152 |
- |
|
| 153 |
-func windowsValidateRegex(p, r string) error {
|
|
| 154 |
- if regexp.MustCompile(`^` + r + `$`).MatchString(strings.ToLower(p)) {
|
|
| 155 |
- return nil |
|
| 156 |
- } |
|
| 157 |
- return fmt.Errorf("invalid mount path: '%s'", p)
|
|
| 158 |
-} |
|
| 159 |
-func windowsValidateAbsolute(p string) error {
|
|
| 160 |
- if err := windowsValidateRegex(p, rxDestination); err != nil {
|
|
| 161 |
- return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p)
|
|
| 162 |
- } |
|
| 163 |
- return nil |
|
| 164 |
-} |
|
| 165 |
- |
|
| 166 |
-func windowsDetectMountType(p string) mount.Type {
|
|
| 167 |
- if strings.HasPrefix(p, `\\.\pipe\`) {
|
|
| 168 |
- return mount.TypeNamedPipe |
|
| 169 |
- } else if regexp.MustCompile(`^` + rxHostDir + `$`).MatchString(p) {
|
|
| 170 |
- return mount.TypeBind |
|
| 171 |
- } else {
|
|
| 172 |
- return mount.TypeVolume |
|
| 173 |
- } |
|
| 174 |
-} |
|
| 175 |
- |
|
| 176 |
-func (p *windowsParser) ReadWrite(mode string) bool {
|
|
| 177 |
- return strings.ToLower(mode) != "ro" |
|
| 178 |
-} |
|
| 179 |
- |
|
| 180 |
-// IsVolumeNameValid checks a volume name in a platform specific manner. |
|
| 181 |
-func (p *windowsParser) ValidateVolumeName(name string) error {
|
|
| 182 |
- nameExp := regexp.MustCompile(`^` + rxName + `$`) |
|
| 183 |
- if !nameExp.MatchString(name) {
|
|
| 184 |
- return errors.New("invalid volume name")
|
|
| 185 |
- } |
|
| 186 |
- nameExp = regexp.MustCompile(`^` + rxReservedNames + `$`) |
|
| 187 |
- if nameExp.MatchString(name) {
|
|
| 188 |
- return fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name)
|
|
| 189 |
- } |
|
| 190 |
- return nil |
|
| 191 |
-} |
|
| 192 |
-func (p *windowsParser) ValidateMountConfig(mnt *mount.Mount) error {
|
|
| 193 |
- return p.validateMountConfigReg(mnt, rxDestination, windowsSpecificValidators) |
|
| 194 |
-} |
|
| 195 |
- |
|
| 196 |
-type fileInfoProvider interface {
|
|
| 197 |
- fileInfo(path string) (exist, isDir bool, err error) |
|
| 198 |
-} |
|
| 199 |
- |
|
| 200 |
-type defaultFileInfoProvider struct {
|
|
| 201 |
-} |
|
| 202 |
- |
|
| 203 |
-func (defaultFileInfoProvider) fileInfo(path string) (exist, isDir bool, err error) {
|
|
| 204 |
- fi, err := os.Stat(path) |
|
| 205 |
- if err != nil {
|
|
| 206 |
- if !os.IsNotExist(err) {
|
|
| 207 |
- return false, false, err |
|
| 208 |
- } |
|
| 209 |
- return false, false, nil |
|
| 210 |
- } |
|
| 211 |
- return true, fi.IsDir(), nil |
|
| 212 |
-} |
|
| 213 |
- |
|
| 214 |
-var currentFileInfoProvider fileInfoProvider = defaultFileInfoProvider{}
|
|
| 215 |
- |
|
| 216 |
-func (p *windowsParser) validateMountConfigReg(mnt *mount.Mount, destRegex string, additionalValidators ...mountValidator) error {
|
|
| 217 |
- |
|
| 218 |
- for _, v := range additionalValidators {
|
|
| 219 |
- if err := v(mnt); err != nil {
|
|
| 220 |
- return &errMountConfig{mnt, err}
|
|
| 221 |
- } |
|
| 222 |
- } |
|
| 223 |
- if len(mnt.Target) == 0 {
|
|
| 224 |
- return &errMountConfig{mnt, errMissingField("Target")}
|
|
| 225 |
- } |
|
| 226 |
- |
|
| 227 |
- if err := windowsValidateRegex(mnt.Target, destRegex); err != nil {
|
|
| 228 |
- return &errMountConfig{mnt, err}
|
|
| 229 |
- } |
|
| 230 |
- |
|
| 231 |
- switch mnt.Type {
|
|
| 232 |
- case mount.TypeBind: |
|
| 233 |
- if len(mnt.Source) == 0 {
|
|
| 234 |
- return &errMountConfig{mnt, errMissingField("Source")}
|
|
| 235 |
- } |
|
| 236 |
- // Don't error out just because the propagation mode is not supported on the platform |
|
| 237 |
- if opts := mnt.BindOptions; opts != nil {
|
|
| 238 |
- if len(opts.Propagation) > 0 {
|
|
| 239 |
- return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)}
|
|
| 240 |
- } |
|
| 241 |
- } |
|
| 242 |
- if mnt.VolumeOptions != nil {
|
|
| 243 |
- return &errMountConfig{mnt, errExtraField("VolumeOptions")}
|
|
| 244 |
- } |
|
| 245 |
- |
|
| 246 |
- if err := windowsValidateAbsolute(mnt.Source); err != nil {
|
|
| 247 |
- return &errMountConfig{mnt, err}
|
|
| 248 |
- } |
|
| 249 |
- |
|
| 250 |
- exists, isdir, err := currentFileInfoProvider.fileInfo(mnt.Source) |
|
| 251 |
- if err != nil {
|
|
| 252 |
- return &errMountConfig{mnt, err}
|
|
| 253 |
- } |
|
| 254 |
- if !exists {
|
|
| 255 |
- return &errMountConfig{mnt, errBindSourceDoesNotExist(mnt.Source)}
|
|
| 256 |
- } |
|
| 257 |
- if !isdir {
|
|
| 258 |
- return &errMountConfig{mnt, fmt.Errorf("source path must be a directory")}
|
|
| 259 |
- } |
|
| 260 |
- |
|
| 261 |
- case mount.TypeVolume: |
|
| 262 |
- if mnt.BindOptions != nil {
|
|
| 263 |
- return &errMountConfig{mnt, errExtraField("BindOptions")}
|
|
| 264 |
- } |
|
| 265 |
- |
|
| 266 |
- if len(mnt.Source) == 0 && mnt.ReadOnly {
|
|
| 267 |
- return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")}
|
|
| 268 |
- } |
|
| 269 |
- |
|
| 270 |
- if len(mnt.Source) != 0 {
|
|
| 271 |
- if err := p.ValidateVolumeName(mnt.Source); err != nil {
|
|
| 272 |
- return &errMountConfig{mnt, err}
|
|
| 273 |
- } |
|
| 274 |
- } |
|
| 275 |
- case mount.TypeNamedPipe: |
|
| 276 |
- if len(mnt.Source) == 0 {
|
|
| 277 |
- return &errMountConfig{mnt, errMissingField("Source")}
|
|
| 278 |
- } |
|
| 279 |
- |
|
| 280 |
- if mnt.BindOptions != nil {
|
|
| 281 |
- return &errMountConfig{mnt, errExtraField("BindOptions")}
|
|
| 282 |
- } |
|
| 283 |
- |
|
| 284 |
- if mnt.ReadOnly {
|
|
| 285 |
- return &errMountConfig{mnt, errExtraField("ReadOnly")}
|
|
| 286 |
- } |
|
| 287 |
- |
|
| 288 |
- if windowsDetectMountType(mnt.Source) != mount.TypeNamedPipe {
|
|
| 289 |
- return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)}
|
|
| 290 |
- } |
|
| 291 |
- |
|
| 292 |
- if windowsDetectMountType(mnt.Target) != mount.TypeNamedPipe {
|
|
| 293 |
- return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)}
|
|
| 294 |
- } |
|
| 295 |
- default: |
|
| 296 |
- return &errMountConfig{mnt, errors.New("mount type unknown")}
|
|
| 297 |
- } |
|
| 298 |
- return nil |
|
| 299 |
-} |
|
| 300 |
-func (p *windowsParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) {
|
|
| 301 |
- return p.parseMountRaw(raw, volumeDriver, rxDestination, true, windowsSpecificValidators) |
|
| 302 |
-} |
|
| 303 |
- |
|
| 304 |
-func (p *windowsParser) parseMountRaw(raw, volumeDriver, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
|
|
| 305 |
- arr, err := windowsSplitRawSpec(raw, destRegex) |
|
| 306 |
- if err != nil {
|
|
| 307 |
- return nil, err |
|
| 308 |
- } |
|
| 309 |
- |
|
| 310 |
- var spec mount.Mount |
|
| 311 |
- var mode string |
|
| 312 |
- switch len(arr) {
|
|
| 313 |
- case 1: |
|
| 314 |
- // Just a destination path in the container |
|
| 315 |
- spec.Target = arr[0] |
|
| 316 |
- case 2: |
|
| 317 |
- if windowsValidMountMode(arr[1]) {
|
|
| 318 |
- // Destination + Mode is not a valid volume - volumes |
|
| 319 |
- // cannot include a mode. e.g. /foo:rw |
|
| 320 |
- return nil, errInvalidSpec(raw) |
|
| 321 |
- } |
|
| 322 |
- // Host Source Path or Name + Destination |
|
| 323 |
- spec.Source = strings.Replace(arr[0], `/`, `\`, -1) |
|
| 324 |
- spec.Target = arr[1] |
|
| 325 |
- case 3: |
|
| 326 |
- // HostSourcePath+DestinationPath+Mode |
|
| 327 |
- spec.Source = strings.Replace(arr[0], `/`, `\`, -1) |
|
| 328 |
- spec.Target = arr[1] |
|
| 329 |
- mode = arr[2] |
|
| 330 |
- default: |
|
| 331 |
- return nil, errInvalidSpec(raw) |
|
| 332 |
- } |
|
| 333 |
- if convertTargetToBackslash {
|
|
| 334 |
- spec.Target = strings.Replace(spec.Target, `/`, `\`, -1) |
|
| 335 |
- } |
|
| 336 |
- |
|
| 337 |
- if !windowsValidMountMode(mode) {
|
|
| 338 |
- return nil, errInvalidMode(mode) |
|
| 339 |
- } |
|
| 340 |
- |
|
| 341 |
- spec.Type = windowsDetectMountType(spec.Source) |
|
| 342 |
- spec.ReadOnly = !p.ReadWrite(mode) |
|
| 343 |
- |
|
| 344 |
- // cannot assume that if a volume driver is passed in that we should set it |
|
| 345 |
- if volumeDriver != "" && spec.Type == mount.TypeVolume {
|
|
| 346 |
- spec.VolumeOptions = &mount.VolumeOptions{
|
|
| 347 |
- DriverConfig: &mount.Driver{Name: volumeDriver},
|
|
| 348 |
- } |
|
| 349 |
- } |
|
| 350 |
- |
|
| 351 |
- if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
|
| 352 |
- if spec.VolumeOptions == nil {
|
|
| 353 |
- spec.VolumeOptions = &mount.VolumeOptions{}
|
|
| 354 |
- } |
|
| 355 |
- spec.VolumeOptions.NoCopy = !copyData |
|
| 356 |
- } |
|
| 357 |
- |
|
| 358 |
- mp, err := p.parseMountSpec(spec, destRegex, convertTargetToBackslash, additionalValidators...) |
|
| 359 |
- if mp != nil {
|
|
| 360 |
- mp.Mode = mode |
|
| 361 |
- } |
|
| 362 |
- if err != nil {
|
|
| 363 |
- err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err)
|
|
| 364 |
- } |
|
| 365 |
- return mp, err |
|
| 366 |
-} |
|
| 367 |
- |
|
| 368 |
-func (p *windowsParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) {
|
|
| 369 |
- return p.parseMountSpec(cfg, rxDestination, true, windowsSpecificValidators) |
|
| 370 |
-} |
|
| 371 |
-func (p *windowsParser) parseMountSpec(cfg mount.Mount, destRegex string, convertTargetToBackslash bool, additionalValidators ...mountValidator) (*MountPoint, error) {
|
|
| 372 |
- if err := p.validateMountConfigReg(&cfg, destRegex, additionalValidators...); err != nil {
|
|
| 373 |
- return nil, err |
|
| 374 |
- } |
|
| 375 |
- mp := &MountPoint{
|
|
| 376 |
- RW: !cfg.ReadOnly, |
|
| 377 |
- Destination: cfg.Target, |
|
| 378 |
- Type: cfg.Type, |
|
| 379 |
- Spec: cfg, |
|
| 380 |
- } |
|
| 381 |
- if convertTargetToBackslash {
|
|
| 382 |
- mp.Destination = strings.Replace(cfg.Target, `/`, `\`, -1) |
|
| 383 |
- } |
|
| 384 |
- |
|
| 385 |
- switch cfg.Type {
|
|
| 386 |
- case mount.TypeVolume: |
|
| 387 |
- if cfg.Source == "" {
|
|
| 388 |
- mp.Name = stringid.GenerateNonCryptoID() |
|
| 389 |
- } else {
|
|
| 390 |
- mp.Name = cfg.Source |
|
| 391 |
- } |
|
| 392 |
- mp.CopyData = p.DefaultCopyMode() |
|
| 393 |
- |
|
| 394 |
- if cfg.VolumeOptions != nil {
|
|
| 395 |
- if cfg.VolumeOptions.DriverConfig != nil {
|
|
| 396 |
- mp.Driver = cfg.VolumeOptions.DriverConfig.Name |
|
| 397 |
- } |
|
| 398 |
- if cfg.VolumeOptions.NoCopy {
|
|
| 399 |
- mp.CopyData = false |
|
| 400 |
- } |
|
| 401 |
- } |
|
| 402 |
- case mount.TypeBind: |
|
| 403 |
- mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1) |
|
| 404 |
- case mount.TypeNamedPipe: |
|
| 405 |
- mp.Source = strings.Replace(cfg.Source, `/`, `\`, -1) |
|
| 406 |
- } |
|
| 407 |
- // cleanup trailing `\` except for paths like `c:\` |
|
| 408 |
- if len(mp.Source) > 3 && mp.Source[len(mp.Source)-1] == '\\' {
|
|
| 409 |
- mp.Source = mp.Source[:len(mp.Source)-1] |
|
| 410 |
- } |
|
| 411 |
- if len(mp.Destination) > 3 && mp.Destination[len(mp.Destination)-1] == '\\' {
|
|
| 412 |
- mp.Destination = mp.Destination[:len(mp.Destination)-1] |
|
| 413 |
- } |
|
| 414 |
- return mp, nil |
|
| 415 |
-} |
|
| 416 |
- |
|
| 417 |
-func (p *windowsParser) ParseVolumesFrom(spec string) (string, string, error) {
|
|
| 418 |
- if len(spec) == 0 {
|
|
| 419 |
- return "", "", fmt.Errorf("volumes-from specification cannot be an empty string")
|
|
| 420 |
- } |
|
| 421 |
- |
|
| 422 |
- specParts := strings.SplitN(spec, ":", 2) |
|
| 423 |
- id := specParts[0] |
|
| 424 |
- mode := "rw" |
|
| 425 |
- |
|
| 426 |
- if len(specParts) == 2 {
|
|
| 427 |
- mode = specParts[1] |
|
| 428 |
- if !windowsValidMountMode(mode) {
|
|
| 429 |
- return "", "", errInvalidMode(mode) |
|
| 430 |
- } |
|
| 431 |
- |
|
| 432 |
- // Do not allow copy modes on volumes-from |
|
| 433 |
- if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet {
|
|
| 434 |
- return "", "", errInvalidMode(mode) |
|
| 435 |
- } |
|
| 436 |
- } |
|
| 437 |
- return id, mode, nil |
|
| 438 |
-} |
|
| 439 |
- |
|
| 440 |
-func (p *windowsParser) DefaultPropagationMode() mount.Propagation {
|
|
| 441 |
- return mount.Propagation("")
|
|
| 442 |
-} |
|
| 443 |
- |
|
| 444 |
-func (p *windowsParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) {
|
|
| 445 |
- return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS)
|
|
| 446 |
-} |
|
| 447 |
-func (p *windowsParser) DefaultCopyMode() bool {
|
|
| 448 |
- return false |
|
| 449 |
-} |
|
| 450 |
-func (p *windowsParser) IsBackwardCompatible(m *MountPoint) bool {
|
|
| 451 |
- return false |
|
| 452 |
-} |
|
| 453 |
- |
|
| 454 |
-func (p *windowsParser) ValidateTmpfsMountDestination(dest string) error {
|
|
| 455 |
- return errors.New("Platform does not support tmpfs")
|
|
| 456 |
-} |