Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
Simon Ferquel authored on 2017/08/02 02:32:44... | ... |
@@ -435,6 +435,11 @@ func (container *Container) ShouldRestart() bool { |
435 | 435 |
|
436 | 436 |
// AddMountPointWithVolume adds a new mount point configured with a volume to the container. |
437 | 437 |
func (container *Container) AddMountPointWithVolume(destination string, vol volume.Volume, rw bool) { |
438 |
+ operatingSystem := container.Platform |
|
439 |
+ if operatingSystem == "" { |
|
440 |
+ operatingSystem = runtime.GOOS |
|
441 |
+ } |
|
442 |
+ volumeParser := volume.NewParser(operatingSystem) |
|
438 | 443 |
container.MountPoints[destination] = &volume.MountPoint{ |
439 | 444 |
Type: mounttypes.TypeVolume, |
440 | 445 |
Name: vol.Name(), |
... | ... |
@@ -442,7 +447,7 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu |
442 | 442 |
Destination: destination, |
443 | 443 |
RW: rw, |
444 | 444 |
Volume: vol, |
445 |
- CopyData: volume.DefaultCopyMode, |
|
445 |
+ CopyData: volumeParser.DefaultCopyMode(), |
|
446 | 446 |
} |
447 | 447 |
} |
448 | 448 |
|
... | ... |
@@ -68,6 +68,7 @@ func (container *Container) BuildHostnameFile() error { |
68 | 68 |
func (container *Container) NetworkMounts() []Mount { |
69 | 69 |
var mounts []Mount |
70 | 70 |
shared := container.HostConfig.NetworkMode.IsContainer() |
71 |
+ parser := volume.NewParser(container.Platform) |
|
71 | 72 |
if container.ResolvConfPath != "" { |
72 | 73 |
if _, err := os.Stat(container.ResolvConfPath); err != nil { |
73 | 74 |
logrus.Warnf("ResolvConfPath set to %q, but can't stat this filename (err = %v); skipping", container.ResolvConfPath, err) |
... | ... |
@@ -83,7 +84,7 @@ func (container *Container) NetworkMounts() []Mount { |
83 | 83 |
Source: container.ResolvConfPath, |
84 | 84 |
Destination: "/etc/resolv.conf", |
85 | 85 |
Writable: writable, |
86 |
- Propagation: string(volume.DefaultPropagationMode), |
|
86 |
+ Propagation: string(parser.DefaultPropagationMode()), |
|
87 | 87 |
}) |
88 | 88 |
} |
89 | 89 |
} |
... | ... |
@@ -102,7 +103,7 @@ func (container *Container) NetworkMounts() []Mount { |
102 | 102 |
Source: container.HostnamePath, |
103 | 103 |
Destination: "/etc/hostname", |
104 | 104 |
Writable: writable, |
105 |
- Propagation: string(volume.DefaultPropagationMode), |
|
105 |
+ Propagation: string(parser.DefaultPropagationMode()), |
|
106 | 106 |
}) |
107 | 107 |
} |
108 | 108 |
} |
... | ... |
@@ -121,7 +122,7 @@ func (container *Container) NetworkMounts() []Mount { |
121 | 121 |
Source: container.HostsPath, |
122 | 122 |
Destination: "/etc/hosts", |
123 | 123 |
Writable: writable, |
124 |
- Propagation: string(volume.DefaultPropagationMode), |
|
124 |
+ Propagation: string(parser.DefaultPropagationMode()), |
|
125 | 125 |
}) |
126 | 126 |
} |
127 | 127 |
} |
... | ... |
@@ -196,6 +197,7 @@ func (container *Container) UnmountIpcMount(unmount func(pth string) error) erro |
196 | 196 |
// IpcMounts returns the list of IPC mounts |
197 | 197 |
func (container *Container) IpcMounts() []Mount { |
198 | 198 |
var mounts []Mount |
199 |
+ parser := volume.NewParser(container.Platform) |
|
199 | 200 |
|
200 | 201 |
if container.HasMountFor("/dev/shm") { |
201 | 202 |
return mounts |
... | ... |
@@ -209,7 +211,7 @@ func (container *Container) IpcMounts() []Mount { |
209 | 209 |
Source: container.ShmPath, |
210 | 210 |
Destination: "/dev/shm", |
211 | 211 |
Writable: true, |
212 |
- Propagation: string(volume.DefaultPropagationMode), |
|
212 |
+ Propagation: string(parser.DefaultPropagationMode()), |
|
213 | 213 |
}) |
214 | 214 |
|
215 | 215 |
return mounts |
... | ... |
@@ -429,6 +431,7 @@ func copyOwnership(source, destination string) error { |
429 | 429 |
|
430 | 430 |
// TmpfsMounts returns the list of tmpfs mounts |
431 | 431 |
func (container *Container) TmpfsMounts() ([]Mount, error) { |
432 |
+ parser := volume.NewParser(container.Platform) |
|
432 | 433 |
var mounts []Mount |
433 | 434 |
for dest, data := range container.HostConfig.Tmpfs { |
434 | 435 |
mounts = append(mounts, Mount{ |
... | ... |
@@ -439,7 +442,7 @@ func (container *Container) TmpfsMounts() ([]Mount, error) { |
439 | 439 |
} |
440 | 440 |
for dest, mnt := range container.MountPoints { |
441 | 441 |
if mnt.Type == mounttypes.TypeTmpfs { |
442 |
- data, err := volume.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions, mnt.Spec.ReadOnly) |
|
442 |
+ data, err := parser.ConvertTmpfsOptions(mnt.Spec.TmpfsOptions, mnt.Spec.ReadOnly) |
|
443 | 443 |
if err != nil { |
444 | 444 |
return nil, err |
445 | 445 |
} |
... | ... |
@@ -4,6 +4,7 @@ package daemon |
4 | 4 |
|
5 | 5 |
import ( |
6 | 6 |
"github.com/docker/docker/container" |
7 |
+ "github.com/docker/docker/volume" |
|
7 | 8 |
) |
8 | 9 |
|
9 | 10 |
// checkIfPathIsInAVolume checks if the path is in a volume. If it is, it |
... | ... |
@@ -11,8 +12,9 @@ import ( |
11 | 11 |
// cannot be configured with a read-only rootfs. |
12 | 12 |
func checkIfPathIsInAVolume(container *container.Container, absPath string) (bool, error) { |
13 | 13 |
var toVolume bool |
14 |
+ parser := volume.NewParser(container.Platform) |
|
14 | 15 |
for _, mnt := range container.MountPoints { |
15 |
- if toVolume = mnt.HasResource(absPath); toVolume { |
|
16 |
+ if toVolume = parser.HasResource(mnt, absPath); toVolume { |
|
16 | 17 |
if mnt.RW { |
17 | 18 |
break |
18 | 19 |
} |
... | ... |
@@ -26,10 +26,10 @@ func (daemon *Daemon) createContainerPlatformSpecificSettings(container *contain |
26 | 26 |
} |
27 | 27 |
hostConfig.Isolation = "hyperv" |
28 | 28 |
} |
29 |
- |
|
29 |
+ parser := volume.NewParser(container.Platform) |
|
30 | 30 |
for spec := range config.Volumes { |
31 | 31 |
|
32 |
- mp, err := volume.ParseMountRaw(spec, hostConfig.VolumeDriver) |
|
32 |
+ mp, err := parser.ParseMountRaw(spec, hostConfig.VolumeDriver) |
|
33 | 33 |
if err != nil { |
34 | 34 |
return fmt.Errorf("Unrecognised volume spec: %v", err) |
35 | 35 |
} |
... | ... |
@@ -612,8 +612,9 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes. |
612 | 612 |
return warnings, fmt.Errorf("Unknown runtime specified %s", hostConfig.Runtime) |
613 | 613 |
} |
614 | 614 |
|
615 |
+ parser := volume.NewParser(runtime.GOOS) |
|
615 | 616 |
for dest := range hostConfig.Tmpfs { |
616 |
- if err := volume.ValidateTmpfsMountDestination(dest); err != nil { |
|
617 |
+ if err := parser.ValidateTmpfsMountDestination(dest); err != nil { |
|
617 | 618 |
return warnings, err |
618 | 619 |
} |
619 | 620 |
} |
... | ... |
@@ -498,6 +498,7 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c |
498 | 498 |
|
499 | 499 |
// Filter out mounts from spec |
500 | 500 |
noIpc := c.HostConfig.IpcMode.IsNone() |
501 |
+ // Filter out mounts that are overridden by user supplied mounts |
|
501 | 502 |
var defaultMounts []specs.Mount |
502 | 503 |
_, mountDev := userMounts["/dev"] |
503 | 504 |
for _, m := range s.Mounts { |
... | ... |
@@ -524,7 +525,8 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c |
524 | 524 |
|
525 | 525 |
if m.Source == "tmpfs" { |
526 | 526 |
data := m.Data |
527 |
- options := []string{"noexec", "nosuid", "nodev", string(volume.DefaultPropagationMode)} |
|
527 |
+ parser := volume.NewParser("linux") |
|
528 |
+ options := []string{"noexec", "nosuid", "nodev", string(parser.DefaultPropagationMode())} |
|
528 | 529 |
if data != "" { |
529 | 530 |
options = append(options, strings.Split(data, ",")...) |
530 | 531 |
} |
... | ... |
@@ -4,6 +4,7 @@ import ( |
4 | 4 |
"fmt" |
5 | 5 |
"io/ioutil" |
6 | 6 |
"path/filepath" |
7 |
+ "runtime" |
|
7 | 8 |
"strings" |
8 | 9 |
|
9 | 10 |
containertypes "github.com/docker/docker/api/types/container" |
... | ... |
@@ -108,6 +109,11 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) { |
108 | 108 |
if !mount.Writable { |
109 | 109 |
m.Options = append(m.Options, "ro") |
110 | 110 |
} |
111 |
+ if img.OS != runtime.GOOS { |
|
112 |
+ m.Type = "bind" |
|
113 |
+ m.Options = append(m.Options, "rbind") |
|
114 |
+ m.Options = append(m.Options, fmt.Sprintf("uvmpath=/tmp/gcs/%s/binds", c.ID)) |
|
115 |
+ } |
|
111 | 116 |
s.Mounts = append(s.Mounts, m) |
112 | 117 |
} |
113 | 118 |
|
... | ... |
@@ -75,6 +75,7 @@ func (m mounts) parts(i int) int { |
75 | 75 |
func (daemon *Daemon) registerMountPoints(container *container.Container, hostConfig *containertypes.HostConfig) (retErr error) { |
76 | 76 |
binds := map[string]bool{} |
77 | 77 |
mountPoints := map[string]*volume.MountPoint{} |
78 |
+ parser := volume.NewParser(container.Platform) |
|
78 | 79 |
defer func() { |
79 | 80 |
// clean up the container mountpoints once return with error |
80 | 81 |
if retErr != nil { |
... | ... |
@@ -103,7 +104,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
103 | 103 |
|
104 | 104 |
// 2. Read volumes from other containers. |
105 | 105 |
for _, v := range hostConfig.VolumesFrom { |
106 |
- containerID, mode, err := volume.ParseVolumesFrom(v) |
|
106 |
+ containerID, mode, err := parser.ParseVolumesFrom(v) |
|
107 | 107 |
if err != nil { |
108 | 108 |
return err |
109 | 109 |
} |
... | ... |
@@ -118,7 +119,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
118 | 118 |
Type: m.Type, |
119 | 119 |
Name: m.Name, |
120 | 120 |
Source: m.Source, |
121 |
- RW: m.RW && volume.ReadWrite(mode), |
|
121 |
+ RW: m.RW && parser.ReadWrite(mode), |
|
122 | 122 |
Driver: m.Driver, |
123 | 123 |
Destination: m.Destination, |
124 | 124 |
Propagation: m.Propagation, |
... | ... |
@@ -140,7 +141,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
140 | 140 |
|
141 | 141 |
// 3. Read bind mounts |
142 | 142 |
for _, b := range hostConfig.Binds { |
143 |
- bind, err := volume.ParseMountRaw(b, hostConfig.VolumeDriver) |
|
143 |
+ bind, err := parser.ParseMountRaw(b, hostConfig.VolumeDriver) |
|
144 | 144 |
if err != nil { |
145 | 145 |
return err |
146 | 146 |
} |
... | ... |
@@ -172,7 +173,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
172 | 172 |
} |
173 | 173 |
|
174 | 174 |
for _, cfg := range hostConfig.Mounts { |
175 |
- mp, err := volume.ParseMountSpec(cfg) |
|
175 |
+ mp, err := parser.ParseMountSpec(cfg) |
|
176 | 176 |
if err != nil { |
177 | 177 |
return validationError{err} |
178 | 178 |
} |
... | ... |
@@ -217,7 +218,7 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo |
217 | 217 |
|
218 | 218 |
// 4. Cleanup old volumes that are about to be reassigned. |
219 | 219 |
for _, m := range mountPoints { |
220 |
- if m.BackwardsCompatible() { |
|
220 |
+ if parser.IsBackwardCompatible(m) { |
|
221 | 221 |
if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil { |
222 | 222 |
daemon.volumes.Dereference(mp.Volume, container.ID) |
223 | 223 |
} |
... | ... |
@@ -252,6 +253,8 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) { |
252 | 252 |
container.Lock() |
253 | 253 |
defer container.Unlock() |
254 | 254 |
|
255 |
+ parser := volume.NewParser(container.Platform) |
|
256 |
+ |
|
255 | 257 |
maybeUpdate := make(map[string]bool) |
256 | 258 |
for _, mp := range container.MountPoints { |
257 | 259 |
if mp.Spec.Source != "" && mp.Type != "" { |
... | ... |
@@ -270,7 +273,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) { |
270 | 270 |
|
271 | 271 |
binds := make(map[string]*volume.MountPoint, len(container.HostConfig.Binds)) |
272 | 272 |
for _, rawSpec := range container.HostConfig.Binds { |
273 |
- mp, err := volume.ParseMountRaw(rawSpec, container.HostConfig.VolumeDriver) |
|
273 |
+ mp, err := parser.ParseMountRaw(rawSpec, container.HostConfig.VolumeDriver) |
|
274 | 274 |
if err != nil { |
275 | 275 |
logrus.WithError(err).Error("Got unexpected error while re-parsing raw volume spec during spec backport") |
276 | 276 |
continue |
... | ... |
@@ -280,7 +283,7 @@ func (daemon *Daemon) backportMountSpec(container *container.Container) { |
280 | 280 |
|
281 | 281 |
volumesFrom := make(map[string]volume.MountPoint) |
282 | 282 |
for _, fromSpec := range container.HostConfig.VolumesFrom { |
283 |
- from, _, err := volume.ParseVolumesFrom(fromSpec) |
|
283 |
+ from, _, err := parser.ParseVolumesFrom(fromSpec) |
|
284 | 284 |
if err != nil { |
285 | 285 |
logrus.WithError(err).WithField("id", container.ID).Error("Error reading volumes-from spec during mount spec backport") |
286 | 286 |
continue |
... | ... |
@@ -1,6 +1,7 @@ |
1 | 1 |
package daemon |
2 | 2 |
|
3 | 3 |
import ( |
4 |
+ "runtime" |
|
4 | 5 |
"testing" |
5 | 6 |
|
6 | 7 |
"github.com/docker/docker/volume" |
... | ... |
@@ -20,8 +21,10 @@ func TestParseVolumesFrom(t *testing.T) { |
20 | 20 |
{"foobar:baz", "", "", true}, |
21 | 21 |
} |
22 | 22 |
|
23 |
+ parser := volume.NewParser(runtime.GOOS) |
|
24 |
+ |
|
23 | 25 |
for _, c := range cases { |
24 |
- id, mode, err := volume.ParseVolumesFrom(c.spec) |
|
26 |
+ id, mode, err := parser.ParseVolumesFrom(c.spec) |
|
25 | 27 |
if c.fail { |
26 | 28 |
if err == nil { |
27 | 29 |
t.Fatalf("Expected error, was nil, for spec %s\n", c.spec) |
... | ... |
@@ -7,6 +7,7 @@ import ( |
7 | 7 |
"io" |
8 | 8 |
"io/ioutil" |
9 | 9 |
"os" |
10 |
+ "path" |
|
10 | 11 |
"path/filepath" |
11 | 12 |
"regexp" |
12 | 13 |
"strings" |
... | ... |
@@ -388,11 +389,101 @@ func (clnt *client) createLinux(containerID string, checkpoint string, checkpoin |
388 | 388 |
configuration.NetworkSharedContainerName = spec.Windows.Network.NetworkSharedContainerName |
389 | 389 |
} |
390 | 390 |
|
391 |
+ // Add the mounts (volumes, bind mounts etc) to the structure. We have to do |
|
392 |
+ // some translation for both the mapped directories passed into HCS and in |
|
393 |
+ // the spec. |
|
394 |
+ // |
|
395 |
+ // For HCS, we only pass in the mounts from the spec which are type "bind". |
|
396 |
+ // Further, the "ContainerPath" field (which is a little mis-leadingly |
|
397 |
+ // named when it applies to the utility VM rather than the container in the |
|
398 |
+ // utility VM) is moved to under /tmp/gcs/<ID>/binds, where this is passed |
|
399 |
+ // by the caller through a 'uvmpath' option. |
|
400 |
+ // |
|
401 |
+ // We do similar translation for the mounts in the spec by stripping out |
|
402 |
+ // the uvmpath option, and translating the Source path to the location in the |
|
403 |
+ // utility VM calculated above. |
|
404 |
+ // |
|
405 |
+ // From inside the utility VM, you would see a 9p mount such as in the following |
|
406 |
+ // where a host folder has been mapped to /target. The line with /tmp/gcs/<ID>/binds |
|
407 |
+ // specifically: |
|
408 |
+ // |
|
409 |
+ // / # mount |
|
410 |
+ // rootfs on / type rootfs (rw,size=463736k,nr_inodes=115934) |
|
411 |
+ // proc on /proc type proc (rw,relatime) |
|
412 |
+ // sysfs on /sys type sysfs (rw,relatime) |
|
413 |
+ // udev on /dev type devtmpfs (rw,relatime,size=498100k,nr_inodes=124525,mode=755) |
|
414 |
+ // tmpfs on /run type tmpfs (rw,relatime) |
|
415 |
+ // cgroup on /sys/fs/cgroup type cgroup (rw,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,perf_event,net_prio,hugetlb,pids,rdma) |
|
416 |
+ // mqueue on /dev/mqueue type mqueue (rw,relatime) |
|
417 |
+ // devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000) |
|
418 |
+ // /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target on /binds/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/target type 9p (rw,sync,dirsync,relatime,trans=fd,rfdno=6,wfdno=6) |
|
419 |
+ // /dev/pmem0 on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0 type ext4 (ro,relatime,block_validity,delalloc,norecovery,barrier,dax,user_xattr,acl) |
|
420 |
+ // /dev/sda on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl) |
|
421 |
+ // overlay on /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/rootfs type overlay (rw,relatime,lowerdir=/tmp/base/:/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/layer0,upperdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/upper,workdir=/tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc/scratch/work) |
|
422 |
+ // |
|
423 |
+ // /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l |
|
424 |
+ // total 16 |
|
425 |
+ // drwx------ 3 0 0 60 Sep 7 18:54 binds |
|
426 |
+ // -rw-r--r-- 1 0 0 3345 Sep 7 18:54 config.json |
|
427 |
+ // drwxr-xr-x 10 0 0 4096 Sep 6 17:26 layer0 |
|
428 |
+ // drwxr-xr-x 1 0 0 4096 Sep 7 18:54 rootfs |
|
429 |
+ // drwxr-xr-x 5 0 0 4096 Sep 7 18:54 scratch |
|
430 |
+ // |
|
431 |
+ // /tmp/gcs/b3ea9126d67702173647ece2744f7c11181c0150e9890fc9a431849838033edc # ls -l binds |
|
432 |
+ // total 0 |
|
433 |
+ // drwxrwxrwt 2 0 0 4096 Sep 7 16:51 target |
|
434 |
+ |
|
435 |
+ mds := []hcsshim.MappedDir{} |
|
436 |
+ specMounts := []specs.Mount{} |
|
437 |
+ for _, mount := range spec.Mounts { |
|
438 |
+ specMount := mount |
|
439 |
+ if mount.Type == "bind" { |
|
440 |
+ // Strip out the uvmpath from the options |
|
441 |
+ updatedOptions := []string{} |
|
442 |
+ uvmPath := "" |
|
443 |
+ readonly := false |
|
444 |
+ for _, opt := range mount.Options { |
|
445 |
+ dropOption := false |
|
446 |
+ elements := strings.SplitN(opt, "=", 2) |
|
447 |
+ switch elements[0] { |
|
448 |
+ case "uvmpath": |
|
449 |
+ uvmPath = elements[1] |
|
450 |
+ dropOption = true |
|
451 |
+ case "rw": |
|
452 |
+ case "ro": |
|
453 |
+ readonly = true |
|
454 |
+ case "rbind": |
|
455 |
+ default: |
|
456 |
+ return fmt.Errorf("unsupported option %q", opt) |
|
457 |
+ } |
|
458 |
+ if !dropOption { |
|
459 |
+ updatedOptions = append(updatedOptions, opt) |
|
460 |
+ } |
|
461 |
+ } |
|
462 |
+ mount.Options = updatedOptions |
|
463 |
+ if uvmPath == "" { |
|
464 |
+ return fmt.Errorf("no uvmpath for bind mount %+v", mount) |
|
465 |
+ } |
|
466 |
+ md := hcsshim.MappedDir{ |
|
467 |
+ HostPath: mount.Source, |
|
468 |
+ ContainerPath: path.Join(uvmPath, mount.Destination), |
|
469 |
+ CreateInUtilityVM: true, |
|
470 |
+ ReadOnly: readonly, |
|
471 |
+ } |
|
472 |
+ mds = append(mds, md) |
|
473 |
+ specMount.Source = path.Join(uvmPath, mount.Destination) |
|
474 |
+ } |
|
475 |
+ specMounts = append(specMounts, specMount) |
|
476 |
+ } |
|
477 |
+ configuration.MappedDirectories = mds |
|
478 |
+ |
|
391 | 479 |
hcsContainer, err := hcsshim.CreateContainer(containerID, configuration) |
392 | 480 |
if err != nil { |
393 | 481 |
return err |
394 | 482 |
} |
395 | 483 |
|
484 |
+ spec.Mounts = specMounts |
|
485 |
+ |
|
396 | 486 |
// Construct a container object for calling start on it. |
397 | 487 |
container := &container{ |
398 | 488 |
containerCommon: containerCommon{ |
... | ... |
@@ -7,8 +7,6 @@ import ( |
7 | 7 |
"github.com/docker/docker/api/types/container" |
8 | 8 |
networktypes "github.com/docker/docker/api/types/network" |
9 | 9 |
"github.com/docker/docker/pkg/sysinfo" |
10 |
- "github.com/docker/docker/volume" |
|
11 |
- "github.com/pkg/errors" |
|
12 | 10 |
) |
13 | 11 |
|
14 | 12 |
// ContainerDecoder implements httputils.ContainerDecoder |
... | ... |
@@ -46,11 +44,6 @@ func decodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon |
46 | 46 |
if w.Config.Volumes == nil { |
47 | 47 |
w.Config.Volumes = make(map[string]struct{}) |
48 | 48 |
} |
49 |
- |
|
50 |
- // Now validate all the volumes and binds |
|
51 |
- if err := validateMountSettings(w.Config, hc); err != nil { |
|
52 |
- return nil, nil, nil, err |
|
53 |
- } |
|
54 | 49 |
} |
55 | 50 |
|
56 | 51 |
// Certain parameters need daemon-side validation that cannot be done |
... | ... |
@@ -86,23 +79,3 @@ func decodeContainerConfig(src io.Reader) (*container.Config, *container.HostCon |
86 | 86 |
|
87 | 87 |
return w.Config, hc, w.NetworkingConfig, nil |
88 | 88 |
} |
89 |
- |
|
90 |
-// validateMountSettings validates each of the volumes and bind settings |
|
91 |
-// passed by the caller to ensure they are valid. |
|
92 |
-func validateMountSettings(c *container.Config, hc *container.HostConfig) error { |
|
93 |
- // it is ok to have len(hc.Mounts) > 0 && (len(hc.Binds) > 0 || len (c.Volumes) > 0 || len (hc.Tmpfs) > 0 ) |
|
94 |
- |
|
95 |
- // Ensure all volumes and binds are valid. |
|
96 |
- for spec := range c.Volumes { |
|
97 |
- if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil { |
|
98 |
- return errors.Wrapf(err, "invalid volume spec %q", spec) |
|
99 |
- } |
|
100 |
- } |
|
101 |
- for _, spec := range hc.Binds { |
|
102 |
- if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil { |
|
103 |
- return errors.Wrapf(err, "invalid bind mount spec %q", spec) |
|
104 |
- } |
|
105 |
- } |
|
106 |
- |
|
107 |
- return nil |
|
108 |
-} |
... | ... |
@@ -9,12 +9,9 @@ import ( |
9 | 9 |
"strings" |
10 | 10 |
"testing" |
11 | 11 |
|
12 |
- "os" |
|
13 |
- |
|
14 | 12 |
"github.com/docker/docker/api/types/container" |
15 | 13 |
networktypes "github.com/docker/docker/api/types/network" |
16 | 14 |
"github.com/docker/docker/api/types/strslice" |
17 |
- "github.com/gotestyourself/gotestyourself/skip" |
|
18 | 15 |
"github.com/stretchr/testify/assert" |
19 | 16 |
"github.com/stretchr/testify/require" |
20 | 17 |
) |
... | ... |
@@ -143,103 +140,6 @@ func callDecodeContainerConfigIsolation(isolation string) (*container.Config, *c |
143 | 143 |
return decodeContainerConfig(bytes.NewReader(b)) |
144 | 144 |
} |
145 | 145 |
|
146 |
-func TestDecodeContainerConfigWithVolumes(t *testing.T) { |
|
147 |
- var testcases = []decodeConfigTestcase{ |
|
148 |
- { |
|
149 |
- doc: "no paths volume", |
|
150 |
- wrapper: containerWrapperWithVolume(":"), |
|
151 |
- expectedErr: `invalid volume specification: ':'`, |
|
152 |
- }, |
|
153 |
- { |
|
154 |
- doc: "no paths bind", |
|
155 |
- wrapper: containerWrapperWithBind(":"), |
|
156 |
- expectedErr: `invalid volume specification: ':'`, |
|
157 |
- }, |
|
158 |
- { |
|
159 |
- doc: "no paths or mode volume", |
|
160 |
- wrapper: containerWrapperWithVolume("::"), |
|
161 |
- expectedErr: `invalid volume specification: '::'`, |
|
162 |
- }, |
|
163 |
- { |
|
164 |
- doc: "no paths or mode bind", |
|
165 |
- wrapper: containerWrapperWithBind("::"), |
|
166 |
- expectedErr: `invalid volume specification: '::'`, |
|
167 |
- }, |
|
168 |
- } |
|
169 |
- for _, testcase := range testcases { |
|
170 |
- t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase)) |
|
171 |
- } |
|
172 |
-} |
|
173 |
- |
|
174 |
-func TestDecodeContainerConfigWithVolumesUnix(t *testing.T) { |
|
175 |
- skip.IfCondition(t, runtime.GOOS == "windows") |
|
176 |
- |
|
177 |
- baseErr := `invalid mount config for type "volume": invalid specification: ` |
|
178 |
- var testcases = []decodeConfigTestcase{ |
|
179 |
- { |
|
180 |
- doc: "root to root volume", |
|
181 |
- wrapper: containerWrapperWithVolume("/:/"), |
|
182 |
- expectedErr: `invalid volume specification: '/:/'`, |
|
183 |
- }, |
|
184 |
- { |
|
185 |
- doc: "root to root bind", |
|
186 |
- wrapper: containerWrapperWithBind("/:/"), |
|
187 |
- expectedErr: `invalid volume specification: '/:/'`, |
|
188 |
- }, |
|
189 |
- { |
|
190 |
- doc: "no destination path volume", |
|
191 |
- wrapper: containerWrapperWithVolume(`/tmp:`), |
|
192 |
- expectedErr: ` invalid volume specification: '/tmp:'`, |
|
193 |
- }, |
|
194 |
- { |
|
195 |
- doc: "no destination path bind", |
|
196 |
- wrapper: containerWrapperWithBind(`/tmp:`), |
|
197 |
- expectedErr: ` invalid volume specification: '/tmp:'`, |
|
198 |
- }, |
|
199 |
- { |
|
200 |
- doc: "no destination path or mode volume", |
|
201 |
- wrapper: containerWrapperWithVolume(`/tmp::`), |
|
202 |
- expectedErr: `invalid mount config for type "bind": field Target must not be empty`, |
|
203 |
- }, |
|
204 |
- { |
|
205 |
- doc: "no destination path or mode bind", |
|
206 |
- wrapper: containerWrapperWithBind(`/tmp::`), |
|
207 |
- expectedErr: `invalid mount config for type "bind": field Target must not be empty`, |
|
208 |
- }, |
|
209 |
- { |
|
210 |
- doc: "too many sections volume", |
|
211 |
- wrapper: containerWrapperWithVolume(`/tmp:/tmp:/tmp:/tmp`), |
|
212 |
- expectedErr: `invalid volume specification: '/tmp:/tmp:/tmp:/tmp'`, |
|
213 |
- }, |
|
214 |
- { |
|
215 |
- doc: "too many sections bind", |
|
216 |
- wrapper: containerWrapperWithBind(`/tmp:/tmp:/tmp:/tmp`), |
|
217 |
- expectedErr: `invalid volume specification: '/tmp:/tmp:/tmp:/tmp'`, |
|
218 |
- }, |
|
219 |
- { |
|
220 |
- doc: "just root volume", |
|
221 |
- wrapper: containerWrapperWithVolume("/"), |
|
222 |
- expectedErr: baseErr + `destination can't be '/'`, |
|
223 |
- }, |
|
224 |
- { |
|
225 |
- doc: "just root bind", |
|
226 |
- wrapper: containerWrapperWithBind("/"), |
|
227 |
- expectedErr: baseErr + `destination can't be '/'`, |
|
228 |
- }, |
|
229 |
- { |
|
230 |
- doc: "bind mount passed as a volume", |
|
231 |
- wrapper: containerWrapperWithVolume(`/foo:/bar`), |
|
232 |
- expectedConfig: &container.Config{ |
|
233 |
- Volumes: map[string]struct{}{`/foo:/bar`: {}}, |
|
234 |
- }, |
|
235 |
- expectedHostConfig: &container.HostConfig{NetworkMode: "default"}, |
|
236 |
- }, |
|
237 |
- } |
|
238 |
- for _, testcase := range testcases { |
|
239 |
- t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase)) |
|
240 |
- } |
|
241 |
-} |
|
242 |
- |
|
243 | 146 |
type decodeConfigTestcase struct { |
244 | 147 |
doc string |
245 | 148 |
wrapper ContainerConfigWrapper |
... | ... |
@@ -266,89 +166,6 @@ func runDecodeContainerConfigTestCase(testcase decodeConfigTestcase) func(t *tes |
266 | 266 |
} |
267 | 267 |
} |
268 | 268 |
|
269 |
-func TestDecodeContainerConfigWithVolumesWindows(t *testing.T) { |
|
270 |
- skip.IfCondition(t, runtime.GOOS != "windows") |
|
271 |
- |
|
272 |
- tmpDir := os.Getenv("TEMP") |
|
273 |
- systemDrive := os.Getenv("SystemDrive") |
|
274 |
- var testcases = []decodeConfigTestcase{ |
|
275 |
- { |
|
276 |
- doc: "root to root volume", |
|
277 |
- wrapper: containerWrapperWithVolume(systemDrive + `\:c:\`), |
|
278 |
- expectedErr: `invalid volume specification: `, |
|
279 |
- }, |
|
280 |
- { |
|
281 |
- doc: "root to root bind", |
|
282 |
- wrapper: containerWrapperWithBind(systemDrive + `\:c:\`), |
|
283 |
- expectedErr: `invalid volume specification: `, |
|
284 |
- }, |
|
285 |
- { |
|
286 |
- doc: "no destination path volume", |
|
287 |
- wrapper: containerWrapperWithVolume(tmpDir + `\:`), |
|
288 |
- expectedErr: `invalid volume specification: `, |
|
289 |
- }, |
|
290 |
- { |
|
291 |
- doc: "no destination path bind", |
|
292 |
- wrapper: containerWrapperWithBind(tmpDir + `\:`), |
|
293 |
- expectedErr: `invalid volume specification: `, |
|
294 |
- }, |
|
295 |
- { |
|
296 |
- doc: "no destination path or mode volume", |
|
297 |
- wrapper: containerWrapperWithVolume(tmpDir + `\::`), |
|
298 |
- expectedErr: `invalid volume specification: `, |
|
299 |
- }, |
|
300 |
- { |
|
301 |
- doc: "no destination path or mode bind", |
|
302 |
- wrapper: containerWrapperWithBind(tmpDir + `\::`), |
|
303 |
- expectedErr: `invalid volume specification: `, |
|
304 |
- }, |
|
305 |
- { |
|
306 |
- doc: "too many sections volume", |
|
307 |
- wrapper: containerWrapperWithVolume(tmpDir + ":" + tmpDir + ":" + tmpDir + ":" + tmpDir), |
|
308 |
- expectedErr: `invalid volume specification: `, |
|
309 |
- }, |
|
310 |
- { |
|
311 |
- doc: "too many sections bind", |
|
312 |
- wrapper: containerWrapperWithBind(tmpDir + ":" + tmpDir + ":" + tmpDir + ":" + tmpDir), |
|
313 |
- expectedErr: `invalid volume specification: `, |
|
314 |
- }, |
|
315 |
- { |
|
316 |
- doc: "no drive letter volume", |
|
317 |
- wrapper: containerWrapperWithVolume(`\tmp`), |
|
318 |
- expectedErr: `invalid volume specification: `, |
|
319 |
- }, |
|
320 |
- { |
|
321 |
- doc: "no drive letter bind", |
|
322 |
- wrapper: containerWrapperWithBind(`\tmp`), |
|
323 |
- expectedErr: `invalid volume specification: `, |
|
324 |
- }, |
|
325 |
- { |
|
326 |
- doc: "root to c-drive volume", |
|
327 |
- wrapper: containerWrapperWithVolume(systemDrive + `\:c:`), |
|
328 |
- expectedErr: `invalid volume specification: `, |
|
329 |
- }, |
|
330 |
- { |
|
331 |
- doc: "root to c-drive bind", |
|
332 |
- wrapper: containerWrapperWithBind(systemDrive + `\:c:`), |
|
333 |
- expectedErr: `invalid volume specification: `, |
|
334 |
- }, |
|
335 |
- { |
|
336 |
- doc: "container path without driver letter volume", |
|
337 |
- wrapper: containerWrapperWithVolume(`c:\windows:\somewhere`), |
|
338 |
- expectedErr: `invalid volume specification: `, |
|
339 |
- }, |
|
340 |
- { |
|
341 |
- doc: "container path without driver letter bind", |
|
342 |
- wrapper: containerWrapperWithBind(`c:\windows:\somewhere`), |
|
343 |
- expectedErr: `invalid volume specification: `, |
|
344 |
- }, |
|
345 |
- } |
|
346 |
- |
|
347 |
- for _, testcase := range testcases { |
|
348 |
- t.Run(testcase.doc, runDecodeContainerConfigTestCase(testcase)) |
|
349 |
- } |
|
350 |
-} |
|
351 |
- |
|
352 | 269 |
func marshal(t *testing.T, w ContainerConfigWrapper, doc string) []byte { |
353 | 270 |
b, err := json.Marshal(w) |
354 | 271 |
require.NoError(t, err, "%s: failed to encode config wrapper", doc) |
355 | 272 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,35 @@ |
0 |
+package volume |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "errors" |
|
4 |
+ "fmt" |
|
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 fmt.Errorf("invalid specification: destination can't be '/'") |
|
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 |
+} |
0 | 35 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,416 @@ |
0 |
+package volume |
|
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 |
+) |
|
12 |
+ |
|
13 |
+type linuxParser struct { |
|
14 |
+} |
|
15 |
+ |
|
16 |
+func linuxSplitRawSpec(raw string) ([]string, error) { |
|
17 |
+ if strings.Count(raw, ":") > 2 { |
|
18 |
+ return nil, errInvalidSpec(raw) |
|
19 |
+ } |
|
20 |
+ |
|
21 |
+ arr := strings.SplitN(raw, ":", 3) |
|
22 |
+ if arr[0] == "" { |
|
23 |
+ return nil, errInvalidSpec(raw) |
|
24 |
+ } |
|
25 |
+ return arr, nil |
|
26 |
+} |
|
27 |
+ |
|
28 |
+func linuxValidateNotRoot(p string) error { |
|
29 |
+ p = path.Clean(strings.Replace(p, `\`, `/`, -1)) |
|
30 |
+ if p == "/" { |
|
31 |
+ return fmt.Errorf("invalid specification: destination can't be '/'") |
|
32 |
+ } |
|
33 |
+ return nil |
|
34 |
+} |
|
35 |
+func linuxValidateAbsolute(p string) error { |
|
36 |
+ p = strings.Replace(p, `\`, `/`, -1) |
|
37 |
+ if path.IsAbs(p) { |
|
38 |
+ return nil |
|
39 |
+ } |
|
40 |
+ return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p) |
|
41 |
+} |
|
42 |
+func (p *linuxParser) validateMountConfig(mnt *mount.Mount) error { |
|
43 |
+ // there was something looking like a bug in existing codebase: |
|
44 |
+ // - validateMountConfig on linux was called with options skipping bind source existance when calling ParseMountRaw |
|
45 |
+ // - but not when calling ParseMountSpec directly... nor when the unit test called it directly |
|
46 |
+ return p.validateMountConfigImpl(mnt, true) |
|
47 |
+} |
|
48 |
+func (p *linuxParser) validateMountConfigImpl(mnt *mount.Mount, validateBindSourceExists bool) error { |
|
49 |
+ if len(mnt.Target) == 0 { |
|
50 |
+ return &errMountConfig{mnt, errMissingField("Target")} |
|
51 |
+ } |
|
52 |
+ |
|
53 |
+ if err := linuxValidateNotRoot(mnt.Target); err != nil { |
|
54 |
+ return &errMountConfig{mnt, err} |
|
55 |
+ } |
|
56 |
+ |
|
57 |
+ if err := linuxValidateAbsolute(mnt.Target); err != nil { |
|
58 |
+ return &errMountConfig{mnt, err} |
|
59 |
+ } |
|
60 |
+ |
|
61 |
+ switch mnt.Type { |
|
62 |
+ case mount.TypeBind: |
|
63 |
+ if len(mnt.Source) == 0 { |
|
64 |
+ return &errMountConfig{mnt, errMissingField("Source")} |
|
65 |
+ } |
|
66 |
+ // Don't error out just because the propagation mode is not supported on the platform |
|
67 |
+ if opts := mnt.BindOptions; opts != nil { |
|
68 |
+ if len(opts.Propagation) > 0 && len(linuxPropagationModes) > 0 { |
|
69 |
+ if _, ok := linuxPropagationModes[opts.Propagation]; !ok { |
|
70 |
+ return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)} |
|
71 |
+ } |
|
72 |
+ } |
|
73 |
+ } |
|
74 |
+ if mnt.VolumeOptions != nil { |
|
75 |
+ return &errMountConfig{mnt, errExtraField("VolumeOptions")} |
|
76 |
+ } |
|
77 |
+ |
|
78 |
+ if err := linuxValidateAbsolute(mnt.Source); err != nil { |
|
79 |
+ return &errMountConfig{mnt, err} |
|
80 |
+ } |
|
81 |
+ |
|
82 |
+ if validateBindSourceExists { |
|
83 |
+ exists, _, _ := currentFileInfoProvider.fileInfo(mnt.Source) |
|
84 |
+ if !exists { |
|
85 |
+ return &errMountConfig{mnt, errBindNotExist} |
|
86 |
+ } |
|
87 |
+ } |
|
88 |
+ |
|
89 |
+ case mount.TypeVolume: |
|
90 |
+ if mnt.BindOptions != nil { |
|
91 |
+ return &errMountConfig{mnt, errExtraField("BindOptions")} |
|
92 |
+ } |
|
93 |
+ |
|
94 |
+ if len(mnt.Source) == 0 && mnt.ReadOnly { |
|
95 |
+ return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")} |
|
96 |
+ } |
|
97 |
+ case mount.TypeTmpfs: |
|
98 |
+ if len(mnt.Source) != 0 { |
|
99 |
+ return &errMountConfig{mnt, errExtraField("Source")} |
|
100 |
+ } |
|
101 |
+ if _, err := p.ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil { |
|
102 |
+ return &errMountConfig{mnt, err} |
|
103 |
+ } |
|
104 |
+ default: |
|
105 |
+ return &errMountConfig{mnt, errors.New("mount type unknown")} |
|
106 |
+ } |
|
107 |
+ return nil |
|
108 |
+} |
|
109 |
+ |
|
110 |
+// read-write modes |
|
111 |
+var rwModes = map[string]bool{ |
|
112 |
+ "rw": true, |
|
113 |
+ "ro": true, |
|
114 |
+} |
|
115 |
+ |
|
116 |
+// label modes |
|
117 |
+var linuxLabelModes = map[string]bool{ |
|
118 |
+ "Z": true, |
|
119 |
+ "z": true, |
|
120 |
+} |
|
121 |
+ |
|
122 |
+// consistency modes |
|
123 |
+var linuxConsistencyModes = map[mount.Consistency]bool{ |
|
124 |
+ mount.ConsistencyFull: true, |
|
125 |
+ mount.ConsistencyCached: true, |
|
126 |
+ mount.ConsistencyDelegated: true, |
|
127 |
+} |
|
128 |
+var linuxPropagationModes = map[mount.Propagation]bool{ |
|
129 |
+ mount.PropagationPrivate: true, |
|
130 |
+ mount.PropagationRPrivate: true, |
|
131 |
+ mount.PropagationSlave: true, |
|
132 |
+ mount.PropagationRSlave: true, |
|
133 |
+ mount.PropagationShared: true, |
|
134 |
+ mount.PropagationRShared: true, |
|
135 |
+} |
|
136 |
+ |
|
137 |
+const linuxDefaultPropagationMode = mount.PropagationRPrivate |
|
138 |
+ |
|
139 |
+func linuxGetPropagation(mode string) mount.Propagation { |
|
140 |
+ for _, o := range strings.Split(mode, ",") { |
|
141 |
+ prop := mount.Propagation(o) |
|
142 |
+ if linuxPropagationModes[prop] { |
|
143 |
+ return prop |
|
144 |
+ } |
|
145 |
+ } |
|
146 |
+ return linuxDefaultPropagationMode |
|
147 |
+} |
|
148 |
+ |
|
149 |
+func linuxHasPropagation(mode string) bool { |
|
150 |
+ for _, o := range strings.Split(mode, ",") { |
|
151 |
+ if linuxPropagationModes[mount.Propagation(o)] { |
|
152 |
+ return true |
|
153 |
+ } |
|
154 |
+ } |
|
155 |
+ return false |
|
156 |
+} |
|
157 |
+ |
|
158 |
+func linuxValidMountMode(mode string) bool { |
|
159 |
+ if mode == "" { |
|
160 |
+ return true |
|
161 |
+ } |
|
162 |
+ |
|
163 |
+ rwModeCount := 0 |
|
164 |
+ labelModeCount := 0 |
|
165 |
+ propagationModeCount := 0 |
|
166 |
+ copyModeCount := 0 |
|
167 |
+ consistencyModeCount := 0 |
|
168 |
+ |
|
169 |
+ for _, o := range strings.Split(mode, ",") { |
|
170 |
+ switch { |
|
171 |
+ case rwModes[o]: |
|
172 |
+ rwModeCount++ |
|
173 |
+ case linuxLabelModes[o]: |
|
174 |
+ labelModeCount++ |
|
175 |
+ case linuxPropagationModes[mount.Propagation(o)]: |
|
176 |
+ propagationModeCount++ |
|
177 |
+ case copyModeExists(o): |
|
178 |
+ copyModeCount++ |
|
179 |
+ case linuxConsistencyModes[mount.Consistency(o)]: |
|
180 |
+ consistencyModeCount++ |
|
181 |
+ default: |
|
182 |
+ return false |
|
183 |
+ } |
|
184 |
+ } |
|
185 |
+ |
|
186 |
+ // Only one string for each mode is allowed. |
|
187 |
+ if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 { |
|
188 |
+ return false |
|
189 |
+ } |
|
190 |
+ return true |
|
191 |
+} |
|
192 |
+ |
|
193 |
+func (p *linuxParser) ReadWrite(mode string) bool { |
|
194 |
+ if !linuxValidMountMode(mode) { |
|
195 |
+ return false |
|
196 |
+ } |
|
197 |
+ |
|
198 |
+ for _, o := range strings.Split(mode, ",") { |
|
199 |
+ if o == "ro" { |
|
200 |
+ return false |
|
201 |
+ } |
|
202 |
+ } |
|
203 |
+ return true |
|
204 |
+} |
|
205 |
+ |
|
206 |
+func (p *linuxParser) ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) { |
|
207 |
+ arr, err := linuxSplitRawSpec(raw) |
|
208 |
+ if err != nil { |
|
209 |
+ return nil, err |
|
210 |
+ } |
|
211 |
+ |
|
212 |
+ var spec mount.Mount |
|
213 |
+ var mode string |
|
214 |
+ switch len(arr) { |
|
215 |
+ case 1: |
|
216 |
+ // Just a destination path in the container |
|
217 |
+ spec.Target = arr[0] |
|
218 |
+ case 2: |
|
219 |
+ if linuxValidMountMode(arr[1]) { |
|
220 |
+ // Destination + Mode is not a valid volume - volumes |
|
221 |
+ // cannot include a mode. e.g. /foo:rw |
|
222 |
+ return nil, errInvalidSpec(raw) |
|
223 |
+ } |
|
224 |
+ // Host Source Path or Name + Destination |
|
225 |
+ spec.Source = arr[0] |
|
226 |
+ spec.Target = arr[1] |
|
227 |
+ case 3: |
|
228 |
+ // HostSourcePath+DestinationPath+Mode |
|
229 |
+ spec.Source = arr[0] |
|
230 |
+ spec.Target = arr[1] |
|
231 |
+ mode = arr[2] |
|
232 |
+ default: |
|
233 |
+ return nil, errInvalidSpec(raw) |
|
234 |
+ } |
|
235 |
+ |
|
236 |
+ if !linuxValidMountMode(mode) { |
|
237 |
+ return nil, errInvalidMode(mode) |
|
238 |
+ } |
|
239 |
+ |
|
240 |
+ if path.IsAbs(spec.Source) { |
|
241 |
+ spec.Type = mount.TypeBind |
|
242 |
+ } else { |
|
243 |
+ spec.Type = mount.TypeVolume |
|
244 |
+ } |
|
245 |
+ |
|
246 |
+ spec.ReadOnly = !p.ReadWrite(mode) |
|
247 |
+ |
|
248 |
+ // cannot assume that if a volume driver is passed in that we should set it |
|
249 |
+ if volumeDriver != "" && spec.Type == mount.TypeVolume { |
|
250 |
+ spec.VolumeOptions = &mount.VolumeOptions{ |
|
251 |
+ DriverConfig: &mount.Driver{Name: volumeDriver}, |
|
252 |
+ } |
|
253 |
+ } |
|
254 |
+ |
|
255 |
+ if copyData, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { |
|
256 |
+ if spec.VolumeOptions == nil { |
|
257 |
+ spec.VolumeOptions = &mount.VolumeOptions{} |
|
258 |
+ } |
|
259 |
+ spec.VolumeOptions.NoCopy = !copyData |
|
260 |
+ } |
|
261 |
+ if linuxHasPropagation(mode) { |
|
262 |
+ spec.BindOptions = &mount.BindOptions{ |
|
263 |
+ Propagation: linuxGetPropagation(mode), |
|
264 |
+ } |
|
265 |
+ } |
|
266 |
+ |
|
267 |
+ mp, err := p.parseMountSpec(spec, false) |
|
268 |
+ if mp != nil { |
|
269 |
+ mp.Mode = mode |
|
270 |
+ } |
|
271 |
+ if err != nil { |
|
272 |
+ err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err) |
|
273 |
+ } |
|
274 |
+ return mp, err |
|
275 |
+} |
|
276 |
+func (p *linuxParser) ParseMountSpec(cfg mount.Mount) (*MountPoint, error) { |
|
277 |
+ return p.parseMountSpec(cfg, true) |
|
278 |
+} |
|
279 |
+func (p *linuxParser) parseMountSpec(cfg mount.Mount, validateBindSourceExists bool) (*MountPoint, error) { |
|
280 |
+ if err := p.validateMountConfigImpl(&cfg, validateBindSourceExists); err != nil { |
|
281 |
+ return nil, err |
|
282 |
+ } |
|
283 |
+ mp := &MountPoint{ |
|
284 |
+ RW: !cfg.ReadOnly, |
|
285 |
+ Destination: path.Clean(filepath.ToSlash(cfg.Target)), |
|
286 |
+ Type: cfg.Type, |
|
287 |
+ Spec: cfg, |
|
288 |
+ } |
|
289 |
+ |
|
290 |
+ switch cfg.Type { |
|
291 |
+ case mount.TypeVolume: |
|
292 |
+ if cfg.Source == "" { |
|
293 |
+ mp.Name = stringid.GenerateNonCryptoID() |
|
294 |
+ } else { |
|
295 |
+ mp.Name = cfg.Source |
|
296 |
+ } |
|
297 |
+ mp.CopyData = p.DefaultCopyMode() |
|
298 |
+ |
|
299 |
+ if cfg.VolumeOptions != nil { |
|
300 |
+ if cfg.VolumeOptions.DriverConfig != nil { |
|
301 |
+ mp.Driver = cfg.VolumeOptions.DriverConfig.Name |
|
302 |
+ } |
|
303 |
+ if cfg.VolumeOptions.NoCopy { |
|
304 |
+ mp.CopyData = false |
|
305 |
+ } |
|
306 |
+ } |
|
307 |
+ case mount.TypeBind: |
|
308 |
+ mp.Source = path.Clean(filepath.ToSlash(cfg.Source)) |
|
309 |
+ if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 { |
|
310 |
+ mp.Propagation = cfg.BindOptions.Propagation |
|
311 |
+ } else { |
|
312 |
+ // If user did not specify a propagation mode, get |
|
313 |
+ // default propagation mode. |
|
314 |
+ mp.Propagation = linuxDefaultPropagationMode |
|
315 |
+ } |
|
316 |
+ case mount.TypeTmpfs: |
|
317 |
+ // NOP |
|
318 |
+ } |
|
319 |
+ return mp, nil |
|
320 |
+} |
|
321 |
+ |
|
322 |
+func (p *linuxParser) ParseVolumesFrom(spec string) (string, string, error) { |
|
323 |
+ if len(spec) == 0 { |
|
324 |
+ return "", "", fmt.Errorf("volumes-from specification cannot be an empty string") |
|
325 |
+ } |
|
326 |
+ |
|
327 |
+ specParts := strings.SplitN(spec, ":", 2) |
|
328 |
+ id := specParts[0] |
|
329 |
+ mode := "rw" |
|
330 |
+ |
|
331 |
+ if len(specParts) == 2 { |
|
332 |
+ mode = specParts[1] |
|
333 |
+ if !linuxValidMountMode(mode) { |
|
334 |
+ return "", "", errInvalidMode(mode) |
|
335 |
+ } |
|
336 |
+ // For now don't allow propagation properties while importing |
|
337 |
+ // volumes from data container. These volumes will inherit |
|
338 |
+ // the same propagation property as of the original volume |
|
339 |
+ // in data container. This probably can be relaxed in future. |
|
340 |
+ if linuxHasPropagation(mode) { |
|
341 |
+ return "", "", errInvalidMode(mode) |
|
342 |
+ } |
|
343 |
+ // Do not allow copy modes on volumes-from |
|
344 |
+ if _, isSet := getCopyMode(mode, p.DefaultCopyMode()); isSet { |
|
345 |
+ return "", "", errInvalidMode(mode) |
|
346 |
+ } |
|
347 |
+ } |
|
348 |
+ return id, mode, nil |
|
349 |
+} |
|
350 |
+ |
|
351 |
+func (p *linuxParser) DefaultPropagationMode() mount.Propagation { |
|
352 |
+ return linuxDefaultPropagationMode |
|
353 |
+} |
|
354 |
+ |
|
355 |
+func (p *linuxParser) ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) { |
|
356 |
+ var rawOpts []string |
|
357 |
+ if readOnly { |
|
358 |
+ rawOpts = append(rawOpts, "ro") |
|
359 |
+ } |
|
360 |
+ |
|
361 |
+ if opt != nil && opt.Mode != 0 { |
|
362 |
+ rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode)) |
|
363 |
+ } |
|
364 |
+ |
|
365 |
+ if opt != nil && opt.SizeBytes != 0 { |
|
366 |
+ // calculate suffix here, making this linux specific, but that is |
|
367 |
+ // okay, since API is that way anyways. |
|
368 |
+ |
|
369 |
+ // we do this by finding the suffix that divides evenly into the |
|
370 |
+ // value, returning the value itself, with no suffix, if it fails. |
|
371 |
+ // |
|
372 |
+ // For the most part, we don't enforce any semantic to this values. |
|
373 |
+ // The operating system will usually align this and enforce minimum |
|
374 |
+ // and maximums. |
|
375 |
+ var ( |
|
376 |
+ size = opt.SizeBytes |
|
377 |
+ suffix string |
|
378 |
+ ) |
|
379 |
+ for _, r := range []struct { |
|
380 |
+ suffix string |
|
381 |
+ divisor int64 |
|
382 |
+ }{ |
|
383 |
+ {"g", 1 << 30}, |
|
384 |
+ {"m", 1 << 20}, |
|
385 |
+ {"k", 1 << 10}, |
|
386 |
+ } { |
|
387 |
+ if size%r.divisor == 0 { |
|
388 |
+ size = size / r.divisor |
|
389 |
+ suffix = r.suffix |
|
390 |
+ break |
|
391 |
+ } |
|
392 |
+ } |
|
393 |
+ |
|
394 |
+ rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix)) |
|
395 |
+ } |
|
396 |
+ return strings.Join(rawOpts, ","), nil |
|
397 |
+} |
|
398 |
+ |
|
399 |
+func (p *linuxParser) DefaultCopyMode() bool { |
|
400 |
+ return true |
|
401 |
+} |
|
402 |
+func (p *linuxParser) ValidateVolumeName(name string) error { |
|
403 |
+ return nil |
|
404 |
+} |
|
405 |
+ |
|
406 |
+func (p *linuxParser) IsBackwardCompatible(m *MountPoint) bool { |
|
407 |
+ return len(m.Source) > 0 || m.Driver == DefaultDriverName |
|
408 |
+} |
|
409 |
+ |
|
410 |
+func (p *linuxParser) ValidateTmpfsMountDestination(dest string) error { |
|
411 |
+ if err := linuxValidateNotRoot(dest); err != nil { |
|
412 |
+ return err |
|
413 |
+ } |
|
414 |
+ return linuxValidateAbsolute(dest) |
|
415 |
+} |
0 | 416 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,43 @@ |
0 |
+package volume |
|
1 |
+ |
|
2 |
+import ( |
|
3 |
+ "runtime" |
|
4 |
+ |
|
5 |
+ "github.com/docker/docker/api/types/mount" |
|
6 |
+) |
|
7 |
+ |
|
8 |
+const ( |
|
9 |
+ // OSLinux is the same as runtime.GOOS on linux |
|
10 |
+ OSLinux = "linux" |
|
11 |
+ // OSWindows is the same as runtime.GOOS on windows |
|
12 |
+ OSWindows = "windows" |
|
13 |
+) |
|
14 |
+ |
|
15 |
+// Parser represents a platform specific parser for mount expressions |
|
16 |
+type Parser interface { |
|
17 |
+ ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) |
|
18 |
+ ParseMountSpec(cfg mount.Mount) (*MountPoint, error) |
|
19 |
+ ParseVolumesFrom(spec string) (string, string, error) |
|
20 |
+ DefaultPropagationMode() mount.Propagation |
|
21 |
+ ConvertTmpfsOptions(opt *mount.TmpfsOptions, readOnly bool) (string, error) |
|
22 |
+ DefaultCopyMode() bool |
|
23 |
+ ValidateVolumeName(name string) error |
|
24 |
+ ReadWrite(mode string) bool |
|
25 |
+ IsBackwardCompatible(m *MountPoint) bool |
|
26 |
+ HasResource(m *MountPoint, absPath string) bool |
|
27 |
+ ValidateTmpfsMountDestination(dest string) error |
|
28 |
+ |
|
29 |
+ validateMountConfig(mt *mount.Mount) error |
|
30 |
+} |
|
31 |
+ |
|
32 |
+// 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) |
|
33 |
+func NewParser(containerOS string) Parser { |
|
34 |
+ switch containerOS { |
|
35 |
+ case OSWindows: |
|
36 |
+ return &windowsParser{} |
|
37 |
+ } |
|
38 |
+ if runtime.GOOS == OSWindows { |
|
39 |
+ return &lcowParser{} |
|
40 |
+ } |
|
41 |
+ return &linuxParser{} |
|
42 |
+} |
... | ... |
@@ -9,8 +9,6 @@ const ( |
9 | 9 |
errVolumeInUse conflictError = "volume is in use" |
10 | 10 |
// errNoSuchVolume is a typed error returned if the requested volume doesn't exist in the volume store |
11 | 11 |
errNoSuchVolume notFoundError = "no such volume" |
12 |
- // errInvalidName is a typed error returned when creating a volume with a name that is not valid on the platform |
|
13 |
- errInvalidName invalidName = "volume name is not valid on this platform" |
|
14 | 12 |
// errNameConflict is a typed error returned on create when a volume exists with the given name, but for a different driver |
15 | 13 |
errNameConflict conflictError = "volume name must be unique" |
16 | 14 |
) |
... | ... |
@@ -30,13 +28,6 @@ func (e notFoundError) Error() string { |
30 | 30 |
|
31 | 31 |
func (notFoundError) NotFound() {} |
32 | 32 |
|
33 |
-type invalidName string |
|
34 |
- |
|
35 |
-func (e invalidName) Error() string { |
|
36 |
- return string(e) |
|
37 |
-} |
|
38 |
-func (invalidName) InvalidParameter() {} |
|
39 |
- |
|
40 | 33 |
// OpErr is the error type returned by functions in the store package. It describes |
41 | 34 |
// the operation, volume name, and error. |
42 | 35 |
type OpErr struct { |
... | ... |
@@ -4,6 +4,7 @@ import ( |
4 | 4 |
"net" |
5 | 5 |
"os" |
6 | 6 |
"path/filepath" |
7 |
+ "runtime" |
|
7 | 8 |
"sync" |
8 | 9 |
"time" |
9 | 10 |
|
... | ... |
@@ -369,13 +370,14 @@ func volumeExists(v volume.Volume) (bool, error) { |
369 | 369 |
// It is expected that callers of this function hold any necessary locks. |
370 | 370 |
func (s *VolumeStore) create(name, driverName string, opts, labels map[string]string) (volume.Volume, error) { |
371 | 371 |
// Validate the name in a platform-specific manner |
372 |
- valid, err := volume.IsVolumeNameValid(name) |
|
372 |
+ |
|
373 |
+ // volume name validation is specific to the host os and not on container image |
|
374 |
+ // windows/lcow should have an equivalent volumename validation logic so we create a parser for current host OS |
|
375 |
+ parser := volume.NewParser(runtime.GOOS) |
|
376 |
+ err := parser.ValidateVolumeName(name) |
|
373 | 377 |
if err != nil { |
374 | 378 |
return nil, err |
375 | 379 |
} |
376 |
- if !valid { |
|
377 |
- return nil, &OpErr{Err: errInvalidName, Name: name, Op: "create"} |
|
378 |
- } |
|
379 | 380 |
|
380 | 381 |
v, err := s.checkConflict(name, driverName) |
381 | 382 |
if err != nil { |
... | ... |
@@ -2,8 +2,6 @@ package volume |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"fmt" |
5 |
- "os" |
|
6 |
- "runtime" |
|
7 | 5 |
|
8 | 6 |
"github.com/docker/docker/api/types/mount" |
9 | 7 |
"github.com/pkg/errors" |
... | ... |
@@ -11,120 +9,6 @@ import ( |
11 | 11 |
|
12 | 12 |
var errBindNotExist = errors.New("bind source path does not exist") |
13 | 13 |
|
14 |
-type validateOpts struct { |
|
15 |
- skipBindSourceCheck bool |
|
16 |
-} |
|
17 |
- |
|
18 |
-func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error { |
|
19 |
- opts := validateOpts{} |
|
20 |
- for _, o := range options { |
|
21 |
- o(&opts) |
|
22 |
- } |
|
23 |
- |
|
24 |
- if len(mnt.Target) == 0 { |
|
25 |
- return &errMountConfig{mnt, errMissingField("Target")} |
|
26 |
- } |
|
27 |
- |
|
28 |
- if err := validateNotRoot(mnt.Target); err != nil { |
|
29 |
- return &errMountConfig{mnt, err} |
|
30 |
- } |
|
31 |
- |
|
32 |
- if err := validateAbsolute(mnt.Target); err != nil { |
|
33 |
- return &errMountConfig{mnt, err} |
|
34 |
- } |
|
35 |
- |
|
36 |
- switch mnt.Type { |
|
37 |
- case mount.TypeBind: |
|
38 |
- if len(mnt.Source) == 0 { |
|
39 |
- return &errMountConfig{mnt, errMissingField("Source")} |
|
40 |
- } |
|
41 |
- // Don't error out just because the propagation mode is not supported on the platform |
|
42 |
- if opts := mnt.BindOptions; opts != nil { |
|
43 |
- if len(opts.Propagation) > 0 && len(propagationModes) > 0 { |
|
44 |
- if _, ok := propagationModes[opts.Propagation]; !ok { |
|
45 |
- return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)} |
|
46 |
- } |
|
47 |
- } |
|
48 |
- } |
|
49 |
- if mnt.VolumeOptions != nil { |
|
50 |
- return &errMountConfig{mnt, errExtraField("VolumeOptions")} |
|
51 |
- } |
|
52 |
- |
|
53 |
- if err := validateAbsolute(mnt.Source); err != nil { |
|
54 |
- return &errMountConfig{mnt, err} |
|
55 |
- } |
|
56 |
- |
|
57 |
- // Do not allow binding to non-existent path |
|
58 |
- if !opts.skipBindSourceCheck { |
|
59 |
- fi, err := os.Stat(mnt.Source) |
|
60 |
- if err != nil { |
|
61 |
- if !os.IsNotExist(err) { |
|
62 |
- return &errMountConfig{mnt, err} |
|
63 |
- } |
|
64 |
- return &errMountConfig{mnt, errBindNotExist} |
|
65 |
- } |
|
66 |
- if err := validateStat(fi); err != nil { |
|
67 |
- return &errMountConfig{mnt, err} |
|
68 |
- } |
|
69 |
- } |
|
70 |
- case mount.TypeVolume: |
|
71 |
- if mnt.BindOptions != nil { |
|
72 |
- return &errMountConfig{mnt, errExtraField("BindOptions")} |
|
73 |
- } |
|
74 |
- |
|
75 |
- if len(mnt.Source) == 0 && mnt.ReadOnly { |
|
76 |
- return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")} |
|
77 |
- } |
|
78 |
- |
|
79 |
- if len(mnt.Source) != 0 { |
|
80 |
- if valid, err := IsVolumeNameValid(mnt.Source); !valid { |
|
81 |
- if err == nil { |
|
82 |
- err = errors.New("invalid volume name") |
|
83 |
- } |
|
84 |
- return &errMountConfig{mnt, err} |
|
85 |
- } |
|
86 |
- } |
|
87 |
- case mount.TypeTmpfs: |
|
88 |
- if len(mnt.Source) != 0 { |
|
89 |
- return &errMountConfig{mnt, errExtraField("Source")} |
|
90 |
- } |
|
91 |
- if err := ValidateTmpfsMountDestination(mnt.Target); err != nil { |
|
92 |
- return &errMountConfig{mnt, err} |
|
93 |
- } |
|
94 |
- if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil { |
|
95 |
- return &errMountConfig{mnt, err} |
|
96 |
- } |
|
97 |
- case mount.TypeNamedPipe: |
|
98 |
- if runtime.GOOS != "windows" { |
|
99 |
- return &errMountConfig{mnt, errors.New("named pipe bind mounts are not supported on this OS")} |
|
100 |
- } |
|
101 |
- |
|
102 |
- if len(mnt.Source) == 0 { |
|
103 |
- return &errMountConfig{mnt, errMissingField("Source")} |
|
104 |
- } |
|
105 |
- |
|
106 |
- if mnt.BindOptions != nil { |
|
107 |
- return &errMountConfig{mnt, errExtraField("BindOptions")} |
|
108 |
- } |
|
109 |
- |
|
110 |
- if mnt.ReadOnly { |
|
111 |
- return &errMountConfig{mnt, errExtraField("ReadOnly")} |
|
112 |
- } |
|
113 |
- |
|
114 |
- if detectMountType(mnt.Source) != mount.TypeNamedPipe { |
|
115 |
- return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Source)} |
|
116 |
- } |
|
117 |
- |
|
118 |
- if detectMountType(mnt.Target) != mount.TypeNamedPipe { |
|
119 |
- return &errMountConfig{mnt, fmt.Errorf("'%s' is not a valid pipe path", mnt.Target)} |
|
120 |
- } |
|
121 |
- |
|
122 |
- default: |
|
123 |
- return &errMountConfig{mnt, errors.New("mount type unknown")} |
|
124 |
- } |
|
125 |
- return nil |
|
126 |
-} |
|
127 |
- |
|
128 | 14 |
type errMountConfig struct { |
129 | 15 |
mount *mount.Mount |
130 | 16 |
err error |
... | ... |
@@ -140,37 +24,3 @@ func errExtraField(name string) error { |
140 | 140 |
func errMissingField(name string) error { |
141 | 141 |
return errors.Errorf("field %s must not be empty", name) |
142 | 142 |
} |
143 |
- |
|
144 |
-func validateAbsolute(p string) error { |
|
145 |
- p = convertSlash(p) |
|
146 |
- if isAbsPath(p) { |
|
147 |
- return nil |
|
148 |
- } |
|
149 |
- return errors.Errorf("invalid mount path: '%s' mount path must be absolute", p) |
|
150 |
-} |
|
151 |
- |
|
152 |
-// ValidateTmpfsMountDestination validates the destination of tmpfs mount. |
|
153 |
-// Currently, we have only two obvious rule for validation: |
|
154 |
-// - path must not be "/" |
|
155 |
-// - path must be absolute |
|
156 |
-// We should add more rules carefully (#30166) |
|
157 |
-func ValidateTmpfsMountDestination(dest string) error { |
|
158 |
- if err := validateNotRoot(dest); err != nil { |
|
159 |
- return err |
|
160 |
- } |
|
161 |
- return validateAbsolute(dest) |
|
162 |
-} |
|
163 |
- |
|
164 |
-type validationError struct { |
|
165 |
- err error |
|
166 |
-} |
|
167 |
- |
|
168 |
-func (e validationError) Error() string { |
|
169 |
- return e.err.Error() |
|
170 |
-} |
|
171 |
- |
|
172 |
-func (e validationError) InvalidParameter() {} |
|
173 |
- |
|
174 |
-func (e validationError) Cause() error { |
|
175 |
- return e.err |
|
176 |
-} |
... | ... |
@@ -4,6 +4,7 @@ import ( |
4 | 4 |
"errors" |
5 | 5 |
"io/ioutil" |
6 | 6 |
"os" |
7 |
+ "runtime" |
|
7 | 8 |
"strings" |
8 | 9 |
"testing" |
9 | 10 |
|
... | ... |
@@ -27,17 +28,50 @@ func TestValidateMount(t *testing.T) { |
27 | 27 |
{mount.Mount{Type: mount.TypeBind}, errMissingField("Target")}, |
28 | 28 |
{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath}, errMissingField("Source")}, |
29 | 29 |
{mount.Mount{Type: mount.TypeBind, Target: testDestinationPath, Source: testSourcePath, VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")}, |
30 |
- {mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindNotExist}, |
|
30 |
+ |
|
31 | 31 |
{mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, nil}, |
32 | 32 |
{mount.Mount{Type: "invalid", Target: testDestinationPath}, errors.New("mount type unknown")}, |
33 | 33 |
} |
34 |
+ if runtime.GOOS == "windows" { |
|
35 |
+ cases = append(cases, struct { |
|
36 |
+ input mount.Mount |
|
37 |
+ expected error |
|
38 |
+ }{mount.Mount{Type: mount.TypeBind, Source: testSourcePath, Target: testDestinationPath}, errBindNotExist}) // bind source existance is not checked on linux |
|
39 |
+ } |
|
40 |
+ lcowCases := []struct { |
|
41 |
+ input mount.Mount |
|
42 |
+ expected error |
|
43 |
+ }{ |
|
44 |
+ {mount.Mount{Type: mount.TypeVolume}, errMissingField("Target")}, |
|
45 |
+ {mount.Mount{Type: mount.TypeVolume, Target: "/foo", Source: "hello"}, nil}, |
|
46 |
+ {mount.Mount{Type: mount.TypeVolume, Target: "/foo"}, nil}, |
|
47 |
+ {mount.Mount{Type: mount.TypeBind}, errMissingField("Target")}, |
|
48 |
+ {mount.Mount{Type: mount.TypeBind, Target: "/foo"}, errMissingField("Source")}, |
|
49 |
+ {mount.Mount{Type: mount.TypeBind, Target: "/foo", Source: "c:\\foo", VolumeOptions: &mount.VolumeOptions{}}, errExtraField("VolumeOptions")}, |
|
50 |
+ {mount.Mount{Type: mount.TypeBind, Source: "c:\\foo", Target: "/foo"}, errBindNotExist}, |
|
51 |
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: "/foo"}, nil}, |
|
52 |
+ {mount.Mount{Type: "invalid", Target: "/foo"}, errors.New("mount type unknown")}, |
|
53 |
+ } |
|
54 |
+ parser := NewParser(runtime.GOOS) |
|
34 | 55 |
for i, x := range cases { |
35 |
- err := validateMountConfig(&x.input) |
|
56 |
+ err := parser.validateMountConfig(&x.input) |
|
36 | 57 |
if err == nil && x.expected == nil { |
37 | 58 |
continue |
38 | 59 |
} |
39 | 60 |
if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) { |
40 |
- t.Fatalf("expected %q, got %q, case: %d", x.expected, err, i) |
|
61 |
+ t.Errorf("expected %q, got %q, case: %d", x.expected, err, i) |
|
62 |
+ } |
|
63 |
+ } |
|
64 |
+ if runtime.GOOS == "windows" { |
|
65 |
+ parser = &lcowParser{} |
|
66 |
+ for i, x := range lcowCases { |
|
67 |
+ err := parser.validateMountConfig(&x.input) |
|
68 |
+ if err == nil && x.expected == nil { |
|
69 |
+ continue |
|
70 |
+ } |
|
71 |
+ if (err == nil && x.expected != nil) || (x.expected == nil && err != nil) || !strings.Contains(err.Error(), x.expected.Error()) { |
|
72 |
+ t.Errorf("expected %q, got %q, case: %d", x.expected, err, i) |
|
73 |
+ } |
|
41 | 74 |
} |
42 | 75 |
} |
43 | 76 |
} |
... | ... |
@@ -3,7 +3,6 @@ package volume |
3 | 3 |
import ( |
4 | 4 |
"fmt" |
5 | 5 |
"os" |
6 |
- "strings" |
|
7 | 6 |
"syscall" |
8 | 7 |
"time" |
9 | 8 |
|
... | ... |
@@ -216,153 +215,10 @@ func (m *MountPoint) Path() string { |
216 | 216 |
return m.Source |
217 | 217 |
} |
218 | 218 |
|
219 |
-// ParseVolumesFrom ensures that the supplied volumes-from is valid. |
|
220 |
-func ParseVolumesFrom(spec string) (string, string, error) { |
|
221 |
- if len(spec) == 0 { |
|
222 |
- return "", "", fmt.Errorf("volumes-from specification cannot be an empty string") |
|
223 |
- } |
|
224 |
- |
|
225 |
- specParts := strings.SplitN(spec, ":", 2) |
|
226 |
- id := specParts[0] |
|
227 |
- mode := "rw" |
|
228 |
- |
|
229 |
- if len(specParts) == 2 { |
|
230 |
- mode = specParts[1] |
|
231 |
- if !ValidMountMode(mode) { |
|
232 |
- return "", "", errInvalidMode(mode) |
|
233 |
- } |
|
234 |
- // For now don't allow propagation properties while importing |
|
235 |
- // volumes from data container. These volumes will inherit |
|
236 |
- // the same propagation property as of the original volume |
|
237 |
- // in data container. This probably can be relaxed in future. |
|
238 |
- if HasPropagation(mode) { |
|
239 |
- return "", "", errInvalidMode(mode) |
|
240 |
- } |
|
241 |
- // Do not allow copy modes on volumes-from |
|
242 |
- if _, isSet := getCopyMode(mode); isSet { |
|
243 |
- return "", "", errInvalidMode(mode) |
|
244 |
- } |
|
245 |
- } |
|
246 |
- return id, mode, nil |
|
247 |
-} |
|
248 |
- |
|
249 |
-// ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a |
|
250 |
-// structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to |
|
251 |
-// validate the spec and create a MountPoint |
|
252 |
-func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) { |
|
253 |
- arr, err := splitRawSpec(convertSlash(raw)) |
|
254 |
- if err != nil { |
|
255 |
- return nil, err |
|
256 |
- } |
|
257 |
- |
|
258 |
- var spec mounttypes.Mount |
|
259 |
- var mode string |
|
260 |
- switch len(arr) { |
|
261 |
- case 1: |
|
262 |
- // Just a destination path in the container |
|
263 |
- spec.Target = arr[0] |
|
264 |
- case 2: |
|
265 |
- if ValidMountMode(arr[1]) { |
|
266 |
- // Destination + Mode is not a valid volume - volumes |
|
267 |
- // cannot include a mode. e.g. /foo:rw |
|
268 |
- return nil, errInvalidSpec(raw) |
|
269 |
- } |
|
270 |
- // Host Source Path or Name + Destination |
|
271 |
- spec.Source = arr[0] |
|
272 |
- spec.Target = arr[1] |
|
273 |
- case 3: |
|
274 |
- // HostSourcePath+DestinationPath+Mode |
|
275 |
- spec.Source = arr[0] |
|
276 |
- spec.Target = arr[1] |
|
277 |
- mode = arr[2] |
|
278 |
- default: |
|
279 |
- return nil, errInvalidSpec(raw) |
|
280 |
- } |
|
281 |
- |
|
282 |
- if !ValidMountMode(mode) { |
|
283 |
- return nil, errInvalidMode(mode) |
|
284 |
- } |
|
285 |
- |
|
286 |
- spec.Type = detectMountType(spec.Source) |
|
287 |
- spec.ReadOnly = !ReadWrite(mode) |
|
288 |
- |
|
289 |
- // cannot assume that if a volume driver is passed in that we should set it |
|
290 |
- if volumeDriver != "" && spec.Type == mounttypes.TypeVolume { |
|
291 |
- spec.VolumeOptions = &mounttypes.VolumeOptions{ |
|
292 |
- DriverConfig: &mounttypes.Driver{Name: volumeDriver}, |
|
293 |
- } |
|
294 |
- } |
|
295 |
- |
|
296 |
- if copyData, isSet := getCopyMode(mode); isSet { |
|
297 |
- if spec.VolumeOptions == nil { |
|
298 |
- spec.VolumeOptions = &mounttypes.VolumeOptions{} |
|
299 |
- } |
|
300 |
- spec.VolumeOptions.NoCopy = !copyData |
|
301 |
- } |
|
302 |
- if HasPropagation(mode) { |
|
303 |
- spec.BindOptions = &mounttypes.BindOptions{ |
|
304 |
- Propagation: GetPropagation(mode), |
|
305 |
- } |
|
306 |
- } |
|
307 |
- |
|
308 |
- mp, err := ParseMountSpec(spec, platformRawValidationOpts...) |
|
309 |
- if mp != nil { |
|
310 |
- mp.Mode = mode |
|
311 |
- } |
|
312 |
- if err != nil { |
|
313 |
- err = errors.Wrap(err, errInvalidSpec(raw).Error()) |
|
314 |
- } |
|
315 |
- return mp, err |
|
316 |
-} |
|
317 |
- |
|
318 |
-// ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it. |
|
319 |
-func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) { |
|
320 |
- if err := validateMountConfig(&cfg, options...); err != nil { |
|
321 |
- return nil, validationError{err} |
|
322 |
- } |
|
323 |
- mp := &MountPoint{ |
|
324 |
- RW: !cfg.ReadOnly, |
|
325 |
- Destination: clean(convertSlash(cfg.Target)), |
|
326 |
- Type: cfg.Type, |
|
327 |
- Spec: cfg, |
|
328 |
- } |
|
329 |
- |
|
330 |
- switch cfg.Type { |
|
331 |
- case mounttypes.TypeVolume: |
|
332 |
- if cfg.Source == "" { |
|
333 |
- mp.Name = stringid.GenerateNonCryptoID() |
|
334 |
- } else { |
|
335 |
- mp.Name = cfg.Source |
|
336 |
- } |
|
337 |
- mp.CopyData = DefaultCopyMode |
|
338 |
- |
|
339 |
- if cfg.VolumeOptions != nil { |
|
340 |
- if cfg.VolumeOptions.DriverConfig != nil { |
|
341 |
- mp.Driver = cfg.VolumeOptions.DriverConfig.Name |
|
342 |
- } |
|
343 |
- if cfg.VolumeOptions.NoCopy { |
|
344 |
- mp.CopyData = false |
|
345 |
- } |
|
346 |
- } |
|
347 |
- case mounttypes.TypeBind, mounttypes.TypeNamedPipe: |
|
348 |
- mp.Source = clean(convertSlash(cfg.Source)) |
|
349 |
- if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 { |
|
350 |
- mp.Propagation = cfg.BindOptions.Propagation |
|
351 |
- } else { |
|
352 |
- // If user did not specify a propagation mode, get |
|
353 |
- // default propagation mode. |
|
354 |
- mp.Propagation = DefaultPropagationMode |
|
355 |
- } |
|
356 |
- case mounttypes.TypeTmpfs: |
|
357 |
- // NOP |
|
358 |
- } |
|
359 |
- return mp, nil |
|
360 |
-} |
|
361 |
- |
|
362 | 219 |
func errInvalidMode(mode string) error { |
363 |
- return validationError{errors.Errorf("invalid mode: %v", mode)} |
|
220 |
+ return errors.Errorf("invalid mode: %v", mode) |
|
364 | 221 |
} |
365 | 222 |
|
366 | 223 |
func errInvalidSpec(spec string) error { |
367 |
- return validationError{errors.Errorf("invalid volume specification: '%s'", spec)} |
|
224 |
+ return errors.Errorf("invalid volume specification: '%s'", spec) |
|
368 | 225 |
} |
... | ... |
@@ -13,11 +13,11 @@ func copyModeExists(mode string) bool { |
13 | 13 |
} |
14 | 14 |
|
15 | 15 |
// GetCopyMode gets the copy mode from the mode string for mounts |
16 |
-func getCopyMode(mode string) (bool, bool) { |
|
16 |
+func getCopyMode(mode string, def bool) (bool, bool) { |
|
17 | 17 |
for _, o := range strings.Split(mode, ",") { |
18 | 18 |
if isEnabled, exists := copyModes[o]; exists { |
19 | 19 |
return isEnabled, true |
20 | 20 |
} |
21 | 21 |
} |
22 |
- return DefaultCopyMode, false |
|
22 |
+ return def, false |
|
23 | 23 |
} |
7 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,56 +0,0 @@ |
1 |
-// +build linux |
|
2 |
- |
|
3 |
-package volume |
|
4 |
- |
|
5 |
-import ( |
|
6 |
- "fmt" |
|
7 |
- "strings" |
|
8 |
- |
|
9 |
- mounttypes "github.com/docker/docker/api/types/mount" |
|
10 |
-) |
|
11 |
- |
|
12 |
-// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string |
|
13 |
-// for mount(2). |
|
14 |
-func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) { |
|
15 |
- var rawOpts []string |
|
16 |
- if readOnly { |
|
17 |
- rawOpts = append(rawOpts, "ro") |
|
18 |
- } |
|
19 |
- |
|
20 |
- if opt != nil && opt.Mode != 0 { |
|
21 |
- rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode)) |
|
22 |
- } |
|
23 |
- |
|
24 |
- if opt != nil && opt.SizeBytes != 0 { |
|
25 |
- // calculate suffix here, making this linux specific, but that is |
|
26 |
- // okay, since API is that way anyways. |
|
27 |
- |
|
28 |
- // we do this by finding the suffix that divides evenly into the |
|
29 |
- // value, returning the value itself, with no suffix, if it fails. |
|
30 |
- // |
|
31 |
- // For the most part, we don't enforce any semantic to this values. |
|
32 |
- // The operating system will usually align this and enforce minimum |
|
33 |
- // and maximums. |
|
34 |
- var ( |
|
35 |
- size = opt.SizeBytes |
|
36 |
- suffix string |
|
37 |
- ) |
|
38 |
- for _, r := range []struct { |
|
39 |
- suffix string |
|
40 |
- divisor int64 |
|
41 |
- }{ |
|
42 |
- {"g", 1 << 30}, |
|
43 |
- {"m", 1 << 20}, |
|
44 |
- {"k", 1 << 10}, |
|
45 |
- } { |
|
46 |
- if size%r.divisor == 0 { |
|
47 |
- size = size / r.divisor |
|
48 |
- suffix = r.suffix |
|
49 |
- break |
|
50 |
- } |
|
51 |
- } |
|
52 |
- |
|
53 |
- rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix)) |
|
54 |
- } |
|
55 |
- return strings.Join(rawOpts, ","), nil |
|
56 |
-} |
57 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,51 +0,0 @@ |
1 |
-// +build linux |
|
2 |
- |
|
3 |
-package volume |
|
4 |
- |
|
5 |
-import ( |
|
6 |
- "strings" |
|
7 |
- "testing" |
|
8 |
- |
|
9 |
- mounttypes "github.com/docker/docker/api/types/mount" |
|
10 |
-) |
|
11 |
- |
|
12 |
-func TestConvertTmpfsOptions(t *testing.T) { |
|
13 |
- type testCase struct { |
|
14 |
- opt mounttypes.TmpfsOptions |
|
15 |
- readOnly bool |
|
16 |
- expectedSubstrings []string |
|
17 |
- unexpectedSubstrings []string |
|
18 |
- } |
|
19 |
- cases := []testCase{ |
|
20 |
- { |
|
21 |
- opt: mounttypes.TmpfsOptions{SizeBytes: 1024 * 1024, Mode: 0700}, |
|
22 |
- readOnly: false, |
|
23 |
- expectedSubstrings: []string{"size=1m", "mode=700"}, |
|
24 |
- unexpectedSubstrings: []string{"ro"}, |
|
25 |
- }, |
|
26 |
- { |
|
27 |
- opt: mounttypes.TmpfsOptions{}, |
|
28 |
- readOnly: true, |
|
29 |
- expectedSubstrings: []string{"ro"}, |
|
30 |
- unexpectedSubstrings: []string{}, |
|
31 |
- }, |
|
32 |
- } |
|
33 |
- for _, c := range cases { |
|
34 |
- data, err := ConvertTmpfsOptions(&c.opt, c.readOnly) |
|
35 |
- if err != nil { |
|
36 |
- t.Fatalf("could not convert %+v (readOnly: %v) to string: %v", |
|
37 |
- c.opt, c.readOnly, err) |
|
38 |
- } |
|
39 |
- t.Logf("data=%q", data) |
|
40 |
- for _, s := range c.expectedSubstrings { |
|
41 |
- if !strings.Contains(data, s) { |
|
42 |
- t.Fatalf("expected substring: %s, got %v (case=%+v)", s, data, c) |
|
43 |
- } |
|
44 |
- } |
|
45 |
- for _, s := range c.unexpectedSubstrings { |
|
46 |
- if strings.Contains(data, s) { |
|
47 |
- t.Fatalf("unexpected substring: %s, got %v (case=%+v)", s, data, c) |
|
48 |
- } |
|
49 |
- } |
|
50 |
- } |
|
51 |
-} |
52 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,47 +0,0 @@ |
1 |
-// +build linux |
|
2 |
- |
|
3 |
-package volume |
|
4 |
- |
|
5 |
-import ( |
|
6 |
- "strings" |
|
7 |
- |
|
8 |
- mounttypes "github.com/docker/docker/api/types/mount" |
|
9 |
-) |
|
10 |
- |
|
11 |
-// DefaultPropagationMode defines what propagation mode should be used by |
|
12 |
-// default if user has not specified one explicitly. |
|
13 |
-// propagation modes |
|
14 |
-const DefaultPropagationMode = mounttypes.PropagationRPrivate |
|
15 |
- |
|
16 |
-var propagationModes = map[mounttypes.Propagation]bool{ |
|
17 |
- mounttypes.PropagationPrivate: true, |
|
18 |
- mounttypes.PropagationRPrivate: true, |
|
19 |
- mounttypes.PropagationSlave: true, |
|
20 |
- mounttypes.PropagationRSlave: true, |
|
21 |
- mounttypes.PropagationShared: true, |
|
22 |
- mounttypes.PropagationRShared: true, |
|
23 |
-} |
|
24 |
- |
|
25 |
-// GetPropagation extracts and returns the mount propagation mode. If there |
|
26 |
-// are no specifications, then by default it is "private". |
|
27 |
-func GetPropagation(mode string) mounttypes.Propagation { |
|
28 |
- for _, o := range strings.Split(mode, ",") { |
|
29 |
- prop := mounttypes.Propagation(o) |
|
30 |
- if propagationModes[prop] { |
|
31 |
- return prop |
|
32 |
- } |
|
33 |
- } |
|
34 |
- return DefaultPropagationMode |
|
35 |
-} |
|
36 |
- |
|
37 |
-// HasPropagation checks if there is a valid propagation mode present in |
|
38 |
-// passed string. Returns true if a valid propagation mode specifier is |
|
39 |
-// present, false otherwise. |
|
40 |
-func HasPropagation(mode string) bool { |
|
41 |
- for _, o := range strings.Split(mode, ",") { |
|
42 |
- if propagationModes[mounttypes.Propagation(o)] { |
|
43 |
- return true |
|
44 |
- } |
|
45 |
- } |
|
46 |
- return false |
|
47 |
-} |
48 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,65 +0,0 @@ |
1 |
-// +build linux |
|
2 |
- |
|
3 |
-package volume |
|
4 |
- |
|
5 |
-import ( |
|
6 |
- "strings" |
|
7 |
- "testing" |
|
8 |
-) |
|
9 |
- |
|
10 |
-func TestParseMountRawPropagation(t *testing.T) { |
|
11 |
- var ( |
|
12 |
- valid []string |
|
13 |
- invalid map[string]string |
|
14 |
- ) |
|
15 |
- |
|
16 |
- valid = []string{ |
|
17 |
- "/hostPath:/containerPath:shared", |
|
18 |
- "/hostPath:/containerPath:rshared", |
|
19 |
- "/hostPath:/containerPath:slave", |
|
20 |
- "/hostPath:/containerPath:rslave", |
|
21 |
- "/hostPath:/containerPath:private", |
|
22 |
- "/hostPath:/containerPath:rprivate", |
|
23 |
- "/hostPath:/containerPath:ro,shared", |
|
24 |
- "/hostPath:/containerPath:ro,slave", |
|
25 |
- "/hostPath:/containerPath:ro,private", |
|
26 |
- "/hostPath:/containerPath:ro,z,shared", |
|
27 |
- "/hostPath:/containerPath:ro,Z,slave", |
|
28 |
- "/hostPath:/containerPath:Z,ro,slave", |
|
29 |
- "/hostPath:/containerPath:slave,Z,ro", |
|
30 |
- "/hostPath:/containerPath:Z,slave,ro", |
|
31 |
- "/hostPath:/containerPath:slave,ro,Z", |
|
32 |
- "/hostPath:/containerPath:rslave,ro,Z", |
|
33 |
- "/hostPath:/containerPath:ro,rshared,Z", |
|
34 |
- "/hostPath:/containerPath:ro,Z,rprivate", |
|
35 |
- } |
|
36 |
- invalid = map[string]string{ |
|
37 |
- "/path:/path:ro,rshared,rslave": `invalid mode`, |
|
38 |
- "/path:/path:ro,z,rshared,rslave": `invalid mode`, |
|
39 |
- "/path:shared": "invalid volume specification", |
|
40 |
- "/path:slave": "invalid volume specification", |
|
41 |
- "/path:private": "invalid volume specification", |
|
42 |
- "name:/absolute-path:shared": "invalid volume specification", |
|
43 |
- "name:/absolute-path:rshared": "invalid volume specification", |
|
44 |
- "name:/absolute-path:slave": "invalid volume specification", |
|
45 |
- "name:/absolute-path:rslave": "invalid volume specification", |
|
46 |
- "name:/absolute-path:private": "invalid volume specification", |
|
47 |
- "name:/absolute-path:rprivate": "invalid volume specification", |
|
48 |
- } |
|
49 |
- |
|
50 |
- for _, path := range valid { |
|
51 |
- if _, err := ParseMountRaw(path, "local"); err != nil { |
|
52 |
- t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err) |
|
53 |
- } |
|
54 |
- } |
|
55 |
- |
|
56 |
- for path, expectedError := range invalid { |
|
57 |
- if _, err := ParseMountRaw(path, "local"); err == nil { |
|
58 |
- t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err %v", path, err) |
|
59 |
- } else { |
|
60 |
- if !strings.Contains(err.Error(), expectedError) { |
|
61 |
- t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error()) |
|
62 |
- } |
|
63 |
- } |
|
64 |
- } |
|
65 |
-} |
66 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,24 +0,0 @@ |
1 |
-// +build !linux |
|
2 |
- |
|
3 |
-package volume |
|
4 |
- |
|
5 |
-import mounttypes "github.com/docker/docker/api/types/mount" |
|
6 |
- |
|
7 |
-// DefaultPropagationMode is used only in linux. In other cases it returns |
|
8 |
-// empty string. |
|
9 |
-const DefaultPropagationMode mounttypes.Propagation = "" |
|
10 |
- |
|
11 |
-// propagation modes not supported on this platform. |
|
12 |
-var propagationModes = map[mounttypes.Propagation]bool{} |
|
13 |
- |
|
14 |
-// GetPropagation is not supported. Return empty string. |
|
15 |
-func GetPropagation(mode string) mounttypes.Propagation { |
|
16 |
- return DefaultPropagationMode |
|
17 |
-} |
|
18 |
- |
|
19 |
-// HasPropagation checks if there is a valid propagation mode present in |
|
20 |
-// passed string. Returns true if a valid propagation mode specifier is |
|
21 |
-// present, false otherwise. |
|
22 |
-func HasPropagation(mode string) bool { |
|
23 |
- return false |
|
24 |
-} |
... | ... |
@@ -10,14 +10,84 @@ import ( |
10 | 10 |
"github.com/docker/docker/api/types/mount" |
11 | 11 |
) |
12 | 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 |
+ |
|
13 | 84 |
func TestParseMountRaw(t *testing.T) { |
14 |
- var ( |
|
15 |
- valid []string |
|
16 |
- invalid map[string]string |
|
17 |
- ) |
|
18 | 85 |
|
19 |
- if runtime.GOOS == "windows" { |
|
20 |
- valid = []string{ |
|
86 |
+ previousProvider := currentFileInfoProvider |
|
87 |
+ defer func() { currentFileInfoProvider = previousProvider }() |
|
88 |
+ currentFileInfoProvider = mockFiProvider{} |
|
89 |
+ windowsSet := parseMountRawTestSet{ |
|
90 |
+ valid: []string{ |
|
21 | 91 |
`d:\`, |
22 | 92 |
`d:`, |
23 | 93 |
`d:\path`, |
... | ... |
@@ -35,10 +105,14 @@ func TestParseMountRaw(t *testing.T) { |
35 | 35 |
`name:D::RO`, |
36 | 36 |
`c:/:d:/forward/slashes/are/good/too`, |
37 | 37 |
`c:/:d:/including with/spaces:ro`, |
38 |
- `c:\Windows`, // With capital |
|
39 |
- `c:\Program Files (x86)`, // With capitals and brackets |
|
40 |
- } |
|
41 |
- invalid = map[string]string{ |
|
38 |
+ `c:\Windows`, // With capital |
|
39 |
+ `c:\Program Files (x86)`, // With capitals and brackets |
|
40 |
+ `\\?\c:\windows\:d:`, // Long path handling (source) |
|
41 |
+ `c:\windows\:\\?\d:\`, // Long path handling (target) |
|
42 |
+ `\\.\pipe\foo:\\.\pipe\foo`, // named pipe |
|
43 |
+ `//./pipe/foo://./pipe/foo`, // named pipe forward slashes |
|
44 |
+ }, |
|
45 |
+ invalid: map[string]string{ |
|
42 | 46 |
``: "invalid volume specification: ", |
43 | 47 |
`.`: "invalid volume specification: ", |
44 | 48 |
`..\`: "invalid volume specification: ", |
... | ... |
@@ -82,10 +156,79 @@ func TestParseMountRaw(t *testing.T) { |
82 | 82 |
`lpt8:d:`: `cannot be a reserved word for Windows filenames`, |
83 | 83 |
`lpt9:d:`: `cannot be a reserved word for Windows filenames`, |
84 | 84 |
`c:\windows\system32\ntdll.dll`: `Only directories can be mapped on this platform`, |
85 |
- } |
|
86 |
- |
|
87 |
- } else { |
|
88 |
- valid = []string{ |
|
85 |
+ `\\.\pipe\foo:c:\pipe`: `'c:\pipe' is not a valid pipe path`, |
|
86 |
+ }, |
|
87 |
+ } |
|
88 |
+ lcowSet := parseMountRawTestSet{ |
|
89 |
+ valid: []string{ |
|
90 |
+ `/foo`, |
|
91 |
+ `/foo/`, |
|
92 |
+ `/foo bar`, |
|
93 |
+ `c:\:/foo`, |
|
94 |
+ `c:\windows\:/foo`, |
|
95 |
+ `c:\windows:/s p a c e`, |
|
96 |
+ `c:\windows:/s p a c e:RW`, |
|
97 |
+ `c:\program files:/s p a c e i n h o s t d i r`, |
|
98 |
+ `0123456789name:/foo`, |
|
99 |
+ `MiXeDcAsEnAmE:/foo`, |
|
100 |
+ `name:/foo`, |
|
101 |
+ `name:/foo:rW`, |
|
102 |
+ `name:/foo:RW`, |
|
103 |
+ `name:/foo:RO`, |
|
104 |
+ `c:/:/forward/slashes/are/good/too`, |
|
105 |
+ `c:/:/including with/spaces:ro`, |
|
106 |
+ `/Program Files (x86)`, // With capitals and brackets |
|
107 |
+ }, |
|
108 |
+ invalid: map[string]string{ |
|
109 |
+ ``: "invalid volume specification: ", |
|
110 |
+ `.`: "invalid volume specification: ", |
|
111 |
+ `c:`: "invalid volume specification: ", |
|
112 |
+ `c:\`: "invalid volume specification: ", |
|
113 |
+ `../`: "invalid volume specification: ", |
|
114 |
+ `c:\:../`: "invalid volume specification: ", |
|
115 |
+ `c:\:/foo:xyzzy`: "invalid volume specification: ", |
|
116 |
+ `/`: "destination can't be '/'", |
|
117 |
+ `/..`: "destination can't be '/'", |
|
118 |
+ `c:\notexist:/foo`: `source path does not exist`, |
|
119 |
+ `c:\windows\system32\ntdll.dll:/foo`: `source path must be a directory`, |
|
120 |
+ `name<:/foo`: `invalid volume specification`, |
|
121 |
+ `name>:/foo`: `invalid volume specification`, |
|
122 |
+ `name::/foo`: `invalid volume specification`, |
|
123 |
+ `name":/foo`: `invalid volume specification`, |
|
124 |
+ `name\:/foo`: `invalid volume specification`, |
|
125 |
+ `name*:/foo`: `invalid volume specification`, |
|
126 |
+ `name|:/foo`: `invalid volume specification`, |
|
127 |
+ `name?:/foo`: `invalid volume specification`, |
|
128 |
+ `name/:/foo`: `invalid volume specification`, |
|
129 |
+ `/foo:rw`: `invalid volume specification`, |
|
130 |
+ `/foo:ro`: `invalid volume specification`, |
|
131 |
+ `con:/foo`: `cannot be a reserved word for Windows filenames`, |
|
132 |
+ `PRN:/foo`: `cannot be a reserved word for Windows filenames`, |
|
133 |
+ `aUx:/foo`: `cannot be a reserved word for Windows filenames`, |
|
134 |
+ `nul:/foo`: `cannot be a reserved word for Windows filenames`, |
|
135 |
+ `com1:/foo`: `cannot be a reserved word for Windows filenames`, |
|
136 |
+ `com2:/foo`: `cannot be a reserved word for Windows filenames`, |
|
137 |
+ `com3:/foo`: `cannot be a reserved word for Windows filenames`, |
|
138 |
+ `com4:/foo`: `cannot be a reserved word for Windows filenames`, |
|
139 |
+ `com5:/foo`: `cannot be a reserved word for Windows filenames`, |
|
140 |
+ `com6:/foo`: `cannot be a reserved word for Windows filenames`, |
|
141 |
+ `com7:/foo`: `cannot be a reserved word for Windows filenames`, |
|
142 |
+ `com8:/foo`: `cannot be a reserved word for Windows filenames`, |
|
143 |
+ `com9:/foo`: `cannot be a reserved word for Windows filenames`, |
|
144 |
+ `lpt1:/foo`: `cannot be a reserved word for Windows filenames`, |
|
145 |
+ `lpt2:/foo`: `cannot be a reserved word for Windows filenames`, |
|
146 |
+ `lpt3:/foo`: `cannot be a reserved word for Windows filenames`, |
|
147 |
+ `lpt4:/foo`: `cannot be a reserved word for Windows filenames`, |
|
148 |
+ `lpt5:/foo`: `cannot be a reserved word for Windows filenames`, |
|
149 |
+ `lpt6:/foo`: `cannot be a reserved word for Windows filenames`, |
|
150 |
+ `lpt7:/foo`: `cannot be a reserved word for Windows filenames`, |
|
151 |
+ `lpt8:/foo`: `cannot be a reserved word for Windows filenames`, |
|
152 |
+ `lpt9:/foo`: `cannot be a reserved word for Windows filenames`, |
|
153 |
+ `\\.\pipe\foo:/foo`: `Linux containers on Windows do not support named pipe mounts`, |
|
154 |
+ }, |
|
155 |
+ } |
|
156 |
+ linuxSet := parseMountRawTestSet{ |
|
157 |
+ valid: []string{ |
|
89 | 158 |
"/home", |
90 | 159 |
"/home:/home", |
91 | 160 |
"/home:/something/else", |
... | ... |
@@ -95,47 +238,87 @@ func TestParseMountRaw(t *testing.T) { |
95 | 95 |
"hostPath:/containerPath:ro", |
96 | 96 |
"/hostPath:/containerPath:rw", |
97 | 97 |
"/rw:/ro", |
98 |
- } |
|
99 |
- invalid = map[string]string{ |
|
100 |
- "": "invalid volume specification", |
|
101 |
- "./": "mount path must be absolute", |
|
102 |
- "../": "mount path must be absolute", |
|
103 |
- "/:../": "mount path must be absolute", |
|
104 |
- "/:path": "mount path must be absolute", |
|
105 |
- ":": "invalid volume specification", |
|
106 |
- "/tmp:": "invalid volume specification", |
|
107 |
- ":test": "invalid volume specification", |
|
108 |
- ":/test": "invalid volume specification", |
|
109 |
- "tmp:": "invalid volume specification", |
|
110 |
- ":test:": "invalid volume specification", |
|
111 |
- "::": "invalid volume specification", |
|
112 |
- ":::": "invalid volume specification", |
|
113 |
- "/tmp:::": "invalid volume specification", |
|
114 |
- ":/tmp::": "invalid volume specification", |
|
115 |
- "/path:rw": "invalid volume specification", |
|
116 |
- "/path:ro": "invalid volume specification", |
|
117 |
- "/rw:rw": "invalid volume specification", |
|
118 |
- "path:ro": "invalid volume specification", |
|
119 |
- "/path:/path:sw": `invalid mode`, |
|
120 |
- "/path:/path:rwz": `invalid mode`, |
|
121 |
- } |
|
98 |
+ "/hostPath:/containerPath:shared", |
|
99 |
+ "/hostPath:/containerPath:rshared", |
|
100 |
+ "/hostPath:/containerPath:slave", |
|
101 |
+ "/hostPath:/containerPath:rslave", |
|
102 |
+ "/hostPath:/containerPath:private", |
|
103 |
+ "/hostPath:/containerPath:rprivate", |
|
104 |
+ "/hostPath:/containerPath:ro,shared", |
|
105 |
+ "/hostPath:/containerPath:ro,slave", |
|
106 |
+ "/hostPath:/containerPath:ro,private", |
|
107 |
+ "/hostPath:/containerPath:ro,z,shared", |
|
108 |
+ "/hostPath:/containerPath:ro,Z,slave", |
|
109 |
+ "/hostPath:/containerPath:Z,ro,slave", |
|
110 |
+ "/hostPath:/containerPath:slave,Z,ro", |
|
111 |
+ "/hostPath:/containerPath:Z,slave,ro", |
|
112 |
+ "/hostPath:/containerPath:slave,ro,Z", |
|
113 |
+ "/hostPath:/containerPath:rslave,ro,Z", |
|
114 |
+ "/hostPath:/containerPath:ro,rshared,Z", |
|
115 |
+ "/hostPath:/containerPath:ro,Z,rprivate", |
|
116 |
+ }, |
|
117 |
+ invalid: map[string]string{ |
|
118 |
+ "": "invalid volume specification", |
|
119 |
+ "./": "mount path must be absolute", |
|
120 |
+ "../": "mount path must be absolute", |
|
121 |
+ "/:../": "mount path must be absolute", |
|
122 |
+ "/:path": "mount path must be absolute", |
|
123 |
+ ":": "invalid volume specification", |
|
124 |
+ "/tmp:": "invalid volume specification", |
|
125 |
+ ":test": "invalid volume specification", |
|
126 |
+ ":/test": "invalid volume specification", |
|
127 |
+ "tmp:": "invalid volume specification", |
|
128 |
+ ":test:": "invalid volume specification", |
|
129 |
+ "::": "invalid volume specification", |
|
130 |
+ ":::": "invalid volume specification", |
|
131 |
+ "/tmp:::": "invalid volume specification", |
|
132 |
+ ":/tmp::": "invalid volume specification", |
|
133 |
+ "/path:rw": "invalid volume specification", |
|
134 |
+ "/path:ro": "invalid volume specification", |
|
135 |
+ "/rw:rw": "invalid volume specification", |
|
136 |
+ "path:ro": "invalid volume specification", |
|
137 |
+ "/path:/path:sw": `invalid mode`, |
|
138 |
+ "/path:/path:rwz": `invalid mode`, |
|
139 |
+ "/path:/path:ro,rshared,rslave": `invalid mode`, |
|
140 |
+ "/path:/path:ro,z,rshared,rslave": `invalid mode`, |
|
141 |
+ "/path:shared": "invalid volume specification", |
|
142 |
+ "/path:slave": "invalid volume specification", |
|
143 |
+ "/path:private": "invalid volume specification", |
|
144 |
+ "name:/absolute-path:shared": "invalid volume specification", |
|
145 |
+ "name:/absolute-path:rshared": "invalid volume specification", |
|
146 |
+ "name:/absolute-path:slave": "invalid volume specification", |
|
147 |
+ "name:/absolute-path:rslave": "invalid volume specification", |
|
148 |
+ "name:/absolute-path:private": "invalid volume specification", |
|
149 |
+ "name:/absolute-path:rprivate": "invalid volume specification", |
|
150 |
+ }, |
|
122 | 151 |
} |
123 | 152 |
|
124 |
- for _, path := range valid { |
|
125 |
- if _, err := ParseMountRaw(path, "local"); err != nil { |
|
126 |
- t.Fatalf("ParseMountRaw(`%q`) should succeed: error %q", path, err) |
|
153 |
+ linParser := &linuxParser{} |
|
154 |
+ winParser := &windowsParser{} |
|
155 |
+ lcowParser := &lcowParser{} |
|
156 |
+ tester := func(parser Parser, set parseMountRawTestSet) { |
|
157 |
+ |
|
158 |
+ for _, path := range set.valid { |
|
159 |
+ |
|
160 |
+ if _, err := parser.ParseMountRaw(path, "local"); err != nil { |
|
161 |
+ t.Errorf("ParseMountRaw(`%q`) should succeed: error %q", path, err) |
|
162 |
+ } |
|
127 | 163 |
} |
128 |
- } |
|
129 | 164 |
|
130 |
- for path, expectedError := range invalid { |
|
131 |
- if mp, err := ParseMountRaw(path, "local"); err == nil { |
|
132 |
- t.Fatalf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp) |
|
133 |
- } else { |
|
134 |
- if !strings.Contains(err.Error(), expectedError) { |
|
135 |
- t.Fatalf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error()) |
|
165 |
+ for path, expectedError := range set.invalid { |
|
166 |
+ if mp, err := parser.ParseMountRaw(path, "local"); err == nil { |
|
167 |
+ t.Errorf("ParseMountRaw(`%q`) should have failed validation. Err '%v' - MP: %v", path, err, mp) |
|
168 |
+ } else { |
|
169 |
+ if !strings.Contains(err.Error(), expectedError) { |
|
170 |
+ t.Errorf("ParseMountRaw(`%q`) error should contain %q, got %v", path, expectedError, err.Error()) |
|
171 |
+ } |
|
136 | 172 |
} |
137 | 173 |
} |
138 | 174 |
} |
175 |
+ tester(linParser, linuxSet) |
|
176 |
+ tester(winParser, windowsSet) |
|
177 |
+ tester(lcowParser, lcowSet) |
|
178 |
+ |
|
139 | 179 |
} |
140 | 180 |
|
141 | 181 |
// testParseMountRaw is a structure used by TestParseMountRawSplit for |
... | ... |
@@ -153,76 +336,96 @@ type testParseMountRaw struct { |
153 | 153 |
} |
154 | 154 |
|
155 | 155 |
func TestParseMountRawSplit(t *testing.T) { |
156 |
- var cases []testParseMountRaw |
|
157 |
- if runtime.GOOS == "windows" { |
|
158 |
- cases = []testParseMountRaw{ |
|
159 |
- {`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false}, |
|
160 |
- {`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false}, |
|
161 |
- {`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false}, |
|
162 |
- {`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false}, |
|
163 |
- {`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true}, |
|
164 |
- {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false}, |
|
165 |
- {`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, |
|
166 |
- {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, |
|
167 |
- {`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false}, |
|
168 |
- {`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false}, |
|
169 |
- {`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false}, |
|
170 |
- {`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, |
|
171 |
- {`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, |
|
172 |
- } |
|
173 |
- } else { |
|
174 |
- cases = []testParseMountRaw{ |
|
175 |
- {"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false}, |
|
176 |
- {"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false}, |
|
177 |
- {"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false}, |
|
178 |
- {"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true}, |
|
179 |
- {"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false}, |
|
180 |
- {"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false}, |
|
181 |
- {"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false}, |
|
182 |
- {"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false}, |
|
183 |
- {"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true}, |
|
184 |
- } |
|
156 |
+ previousProvider := currentFileInfoProvider |
|
157 |
+ defer func() { currentFileInfoProvider = previousProvider }() |
|
158 |
+ currentFileInfoProvider = mockFiProvider{} |
|
159 |
+ windowsCases := []testParseMountRaw{ |
|
160 |
+ {`c:\:d:`, "local", mount.TypeBind, `d:`, `c:\`, ``, "", true, false}, |
|
161 |
+ {`c:\:d:\`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false}, |
|
162 |
+ {`c:\:d:\:ro`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, false}, |
|
163 |
+ {`c:\:d:\:rw`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", true, false}, |
|
164 |
+ {`c:\:d:\:foo`, "local", mount.TypeBind, `d:\`, `c:\`, ``, "", false, true}, |
|
165 |
+ {`name:d::rw`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false}, |
|
166 |
+ {`name:d:`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", true, false}, |
|
167 |
+ {`name:d::ro`, "local", mount.TypeVolume, `d:`, ``, `name`, "local", false, false}, |
|
168 |
+ {`name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, |
|
169 |
+ {`driver/name:c:`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, |
|
170 |
+ {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, false}, |
|
171 |
+ {`\\.\pipe\foo:c:\foo\bar`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, |
|
172 |
+ {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, |
|
185 | 173 |
} |
186 |
- |
|
187 |
- for i, c := range cases { |
|
188 |
- t.Logf("case %d", i) |
|
189 |
- m, err := ParseMountRaw(c.bind, c.driver) |
|
190 |
- if c.fail { |
|
191 |
- if err == nil { |
|
192 |
- t.Fatalf("Expected error, was nil, for spec %s\n", c.bind) |
|
174 |
+ lcowCases := []testParseMountRaw{ |
|
175 |
+ {`c:\:/foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false}, |
|
176 |
+ {`c:\:/foo:ro`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, false}, |
|
177 |
+ {`c:\:/foo:rw`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", true, false}, |
|
178 |
+ {`c:\:/foo:foo`, "local", mount.TypeBind, `/foo`, `c:\`, ``, "", false, true}, |
|
179 |
+ {`name:/foo:rw`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false}, |
|
180 |
+ {`name:/foo`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", true, false}, |
|
181 |
+ {`name:/foo:ro`, "local", mount.TypeVolume, `/foo`, ``, `name`, "local", false, false}, |
|
182 |
+ {`name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, |
|
183 |
+ {`driver/name:/`, "", mount.TypeVolume, ``, ``, ``, "", true, true}, |
|
184 |
+ {`\\.\pipe\foo:\\.\pipe\bar`, "local", mount.TypeNamedPipe, `\\.\pipe\bar`, `\\.\pipe\foo`, "", "", true, true}, |
|
185 |
+ {`\\.\pipe\foo:/data`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, |
|
186 |
+ {`c:\foo\bar:\\.\pipe\foo`, "local", mount.TypeNamedPipe, ``, ``, "", "", true, true}, |
|
187 |
+ } |
|
188 |
+ linuxCases := []testParseMountRaw{ |
|
189 |
+ {"/tmp:/tmp1", "", mount.TypeBind, "/tmp1", "/tmp", "", "", true, false}, |
|
190 |
+ {"/tmp:/tmp2:ro", "", mount.TypeBind, "/tmp2", "/tmp", "", "", false, false}, |
|
191 |
+ {"/tmp:/tmp3:rw", "", mount.TypeBind, "/tmp3", "/tmp", "", "", true, false}, |
|
192 |
+ {"/tmp:/tmp4:foo", "", mount.TypeBind, "", "", "", "", false, true}, |
|
193 |
+ {"name:/named1", "", mount.TypeVolume, "/named1", "", "name", "", true, false}, |
|
194 |
+ {"name:/named2", "external", mount.TypeVolume, "/named2", "", "name", "external", true, false}, |
|
195 |
+ {"name:/named3:ro", "local", mount.TypeVolume, "/named3", "", "name", "local", false, false}, |
|
196 |
+ {"local/name:/tmp:rw", "", mount.TypeVolume, "/tmp", "", "local/name", "", true, false}, |
|
197 |
+ {"/tmp:tmp", "", mount.TypeBind, "", "", "", "", true, true}, |
|
198 |
+ } |
|
199 |
+ linParser := &linuxParser{} |
|
200 |
+ winParser := &windowsParser{} |
|
201 |
+ lcowParser := &lcowParser{} |
|
202 |
+ tester := func(parser Parser, cases []testParseMountRaw) { |
|
203 |
+ for i, c := range cases { |
|
204 |
+ t.Logf("case %d", i) |
|
205 |
+ m, err := parser.ParseMountRaw(c.bind, c.driver) |
|
206 |
+ if c.fail { |
|
207 |
+ if err == nil { |
|
208 |
+ t.Errorf("Expected error, was nil, for spec %s\n", c.bind) |
|
209 |
+ } |
|
210 |
+ continue |
|
193 | 211 |
} |
194 |
- continue |
|
195 |
- } |
|
196 |
- |
|
197 |
- if m == nil || err != nil { |
|
198 |
- t.Fatalf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error()) |
|
199 |
- continue |
|
200 |
- } |
|
201 | 212 |
|
202 |
- if m.Type != c.expType { |
|
203 |
- t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind) |
|
204 |
- } |
|
213 |
+ if m == nil || err != nil { |
|
214 |
+ t.Errorf("ParseMountRaw failed for spec '%s', driver '%s', error '%v'", c.bind, c.driver, err.Error()) |
|
215 |
+ continue |
|
216 |
+ } |
|
205 | 217 |
|
206 |
- if m.Destination != c.expDest { |
|
207 |
- t.Fatalf("Expected destination '%s', was '%s', for spec '%s'", c.expDest, m.Destination, c.bind) |
|
208 |
- } |
|
218 |
+ if m.Destination != c.expDest { |
|
219 |
+ t.Errorf("Expected destination '%s, was %s', for spec '%s'", c.expDest, m.Destination, c.bind) |
|
220 |
+ } |
|
209 | 221 |
|
210 |
- if m.Source != c.expSource { |
|
211 |
- t.Fatalf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind) |
|
212 |
- } |
|
222 |
+ if m.Source != c.expSource { |
|
223 |
+ t.Errorf("Expected source '%s', was '%s', for spec '%s'", c.expSource, m.Source, c.bind) |
|
224 |
+ } |
|
213 | 225 |
|
214 |
- if m.Name != c.expName { |
|
215 |
- t.Fatalf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind) |
|
216 |
- } |
|
226 |
+ if m.Name != c.expName { |
|
227 |
+ t.Errorf("Expected name '%s', was '%s' for spec '%s'", c.expName, m.Name, c.bind) |
|
228 |
+ } |
|
217 | 229 |
|
218 |
- if m.Driver != c.expDriver { |
|
219 |
- t.Fatalf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind) |
|
220 |
- } |
|
230 |
+ if m.Driver != c.expDriver { |
|
231 |
+ t.Errorf("Expected driver '%s', was '%s', for spec '%s'", c.expDriver, m.Driver, c.bind) |
|
232 |
+ } |
|
221 | 233 |
|
222 |
- if m.RW != c.expRW { |
|
223 |
- t.Fatalf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind) |
|
234 |
+ if m.RW != c.expRW { |
|
235 |
+ t.Errorf("Expected RW '%v', was '%v' for spec '%s'", c.expRW, m.RW, c.bind) |
|
236 |
+ } |
|
237 |
+ if m.Type != c.expType { |
|
238 |
+ t.Fatalf("Expected type '%s', was '%s', for spec '%s'", c.expType, m.Type, c.bind) |
|
239 |
+ } |
|
224 | 240 |
} |
225 | 241 |
} |
242 |
+ |
|
243 |
+ tester(linParser, linuxCases) |
|
244 |
+ tester(winParser, windowsCases) |
|
245 |
+ tester(lcowParser, lcowCases) |
|
226 | 246 |
} |
227 | 247 |
|
228 | 248 |
func TestParseMountSpec(t *testing.T) { |
... | ... |
@@ -235,43 +438,43 @@ func TestParseMountSpec(t *testing.T) { |
235 | 235 |
t.Fatal(err) |
236 | 236 |
} |
237 | 237 |
defer os.RemoveAll(testDir) |
238 |
- |
|
238 |
+ parser := NewParser(runtime.GOOS) |
|
239 | 239 |
cases := []c{ |
240 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}}, |
|
241 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: DefaultPropagationMode}}, |
|
242 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir + string(os.PathSeparator), Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}}, |
|
243 |
- {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath + string(os.PathSeparator), ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: DefaultPropagationMode}}, |
|
244 |
- {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}}, |
|
245 |
- {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: DefaultCopyMode}}, |
|
240 |
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath, ReadOnly: true}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, Propagation: parser.DefaultPropagationMode()}}, |
|
241 |
+ {mount.Mount{Type: mount.TypeBind, Source: testDir, Target: testDestinationPath}, MountPoint{Type: mount.TypeBind, Source: testDir, Destination: testDestinationPath, RW: true, Propagation: parser.DefaultPropagationMode()}}, |
|
242 |
+ {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()}}, |
|
243 |
+ {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()}}, |
|
244 |
+ {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}}, |
|
245 |
+ {mount.Mount{Type: mount.TypeVolume, Target: testDestinationPath + string(os.PathSeparator)}, MountPoint{Type: mount.TypeVolume, Destination: testDestinationPath, RW: true, CopyData: parser.DefaultCopyMode()}}, |
|
246 | 246 |
} |
247 | 247 |
|
248 | 248 |
for i, c := range cases { |
249 | 249 |
t.Logf("case %d", i) |
250 |
- mp, err := ParseMountSpec(c.input) |
|
250 |
+ mp, err := parser.ParseMountSpec(c.input) |
|
251 | 251 |
if err != nil { |
252 |
- t.Fatal(err) |
|
252 |
+ t.Error(err) |
|
253 | 253 |
} |
254 | 254 |
|
255 | 255 |
if c.expected.Type != mp.Type { |
256 |
- t.Fatalf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type) |
|
256 |
+ t.Errorf("Expected mount types to match. Expected: '%s', Actual: '%s'", c.expected.Type, mp.Type) |
|
257 | 257 |
} |
258 | 258 |
if c.expected.Destination != mp.Destination { |
259 |
- t.Fatalf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination) |
|
259 |
+ t.Errorf("Expected mount destination to match. Expected: '%s', Actual: '%s'", c.expected.Destination, mp.Destination) |
|
260 | 260 |
} |
261 | 261 |
if c.expected.Source != mp.Source { |
262 |
- t.Fatalf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source) |
|
262 |
+ t.Errorf("Expected mount source to match. Expected: '%s', Actual: '%s'", c.expected.Source, mp.Source) |
|
263 | 263 |
} |
264 | 264 |
if c.expected.RW != mp.RW { |
265 |
- t.Fatalf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW) |
|
265 |
+ t.Errorf("Expected mount writable to match. Expected: '%v', Actual: '%v'", c.expected.RW, mp.RW) |
|
266 | 266 |
} |
267 | 267 |
if c.expected.Propagation != mp.Propagation { |
268 |
- t.Fatalf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation) |
|
268 |
+ t.Errorf("Expected mount propagation to match. Expected: '%v', Actual: '%s'", c.expected.Propagation, mp.Propagation) |
|
269 | 269 |
} |
270 | 270 |
if c.expected.Driver != mp.Driver { |
271 |
- t.Fatalf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver) |
|
271 |
+ t.Errorf("Expected mount driver to match. Expected: '%v', Actual: '%s'", c.expected.Driver, mp.Driver) |
|
272 | 272 |
} |
273 | 273 |
if c.expected.CopyData != mp.CopyData { |
274 |
- t.Fatalf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData) |
|
274 |
+ t.Errorf("Expected mount copy data to match. Expected: '%v', Actual: '%v'", c.expected.CopyData, mp.CopyData) |
|
275 | 275 |
} |
276 | 276 |
} |
277 | 277 |
} |
... | ... |
@@ -4,153 +4,15 @@ package volume |
4 | 4 |
|
5 | 5 |
import ( |
6 | 6 |
"fmt" |
7 |
- "os" |
|
8 | 7 |
"path/filepath" |
9 | 8 |
"strings" |
10 |
- |
|
11 |
- mounttypes "github.com/docker/docker/api/types/mount" |
|
12 | 9 |
) |
13 | 10 |
|
14 |
-var platformRawValidationOpts = []func(o *validateOpts){ |
|
15 |
- // need to make sure to not error out if the bind source does not exist on unix |
|
16 |
- // this is supported for historical reasons, the path will be automatically |
|
17 |
- // created later. |
|
18 |
- func(o *validateOpts) { o.skipBindSourceCheck = true }, |
|
19 |
-} |
|
20 |
- |
|
21 |
-// read-write modes |
|
22 |
-var rwModes = map[string]bool{ |
|
23 |
- "rw": true, |
|
24 |
- "ro": true, |
|
25 |
-} |
|
26 |
- |
|
27 |
-// label modes |
|
28 |
-var labelModes = map[string]bool{ |
|
29 |
- "Z": true, |
|
30 |
- "z": true, |
|
31 |
-} |
|
32 |
- |
|
33 |
-// consistency modes |
|
34 |
-var consistencyModes = map[mounttypes.Consistency]bool{ |
|
35 |
- mounttypes.ConsistencyFull: true, |
|
36 |
- mounttypes.ConsistencyCached: true, |
|
37 |
- mounttypes.ConsistencyDelegated: true, |
|
38 |
-} |
|
39 |
- |
|
40 |
-// BackwardsCompatible decides whether this mount point can be |
|
41 |
-// used in old versions of Docker or not. |
|
42 |
-// Only bind mounts and local volumes can be used in old versions of Docker. |
|
43 |
-func (m *MountPoint) BackwardsCompatible() bool { |
|
44 |
- return len(m.Source) > 0 || m.Driver == DefaultDriverName |
|
45 |
-} |
|
46 |
- |
|
47 |
-// HasResource checks whether the given absolute path for a container is in |
|
48 |
-// this mount point. If the relative path starts with `../` then the resource |
|
49 |
-// is outside of this mount point, but we can't simply check for this prefix |
|
50 |
-// because it misses `..` which is also outside of the mount, so check both. |
|
51 |
-func (m *MountPoint) HasResource(absolutePath string) bool { |
|
11 |
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool { |
|
52 | 12 |
relPath, err := filepath.Rel(m.Destination, absolutePath) |
53 | 13 |
return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator)) |
54 | 14 |
} |
55 | 15 |
|
56 |
-// IsVolumeNameValid checks a volume name in a platform specific manner. |
|
57 |
-func IsVolumeNameValid(name string) (bool, error) { |
|
58 |
- return true, nil |
|
59 |
-} |
|
60 |
- |
|
61 |
-// ValidMountMode will make sure the mount mode is valid. |
|
62 |
-// returns if it's a valid mount mode or not. |
|
63 |
-func ValidMountMode(mode string) bool { |
|
64 |
- if mode == "" { |
|
65 |
- return true |
|
66 |
- } |
|
67 |
- |
|
68 |
- rwModeCount := 0 |
|
69 |
- labelModeCount := 0 |
|
70 |
- propagationModeCount := 0 |
|
71 |
- copyModeCount := 0 |
|
72 |
- consistencyModeCount := 0 |
|
73 |
- |
|
74 |
- for _, o := range strings.Split(mode, ",") { |
|
75 |
- switch { |
|
76 |
- case rwModes[o]: |
|
77 |
- rwModeCount++ |
|
78 |
- case labelModes[o]: |
|
79 |
- labelModeCount++ |
|
80 |
- case propagationModes[mounttypes.Propagation(o)]: |
|
81 |
- propagationModeCount++ |
|
82 |
- case copyModeExists(o): |
|
83 |
- copyModeCount++ |
|
84 |
- case consistencyModes[mounttypes.Consistency(o)]: |
|
85 |
- consistencyModeCount++ |
|
86 |
- default: |
|
87 |
- return false |
|
88 |
- } |
|
89 |
- } |
|
90 |
- |
|
91 |
- // Only one string for each mode is allowed. |
|
92 |
- if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 { |
|
93 |
- return false |
|
94 |
- } |
|
95 |
- return true |
|
96 |
-} |
|
97 |
- |
|
98 |
-// ReadWrite tells you if a mode string is a valid read-write mode or not. |
|
99 |
-// If there are no specifications w.r.t read write mode, then by default |
|
100 |
-// it returns true. |
|
101 |
-func ReadWrite(mode string) bool { |
|
102 |
- if !ValidMountMode(mode) { |
|
103 |
- return false |
|
104 |
- } |
|
105 |
- |
|
106 |
- for _, o := range strings.Split(mode, ",") { |
|
107 |
- if o == "ro" { |
|
108 |
- return false |
|
109 |
- } |
|
110 |
- } |
|
111 |
- return true |
|
112 |
-} |
|
113 |
- |
|
114 |
-func validateNotRoot(p string) error { |
|
115 |
- p = filepath.Clean(convertSlash(p)) |
|
116 |
- if p == "/" { |
|
117 |
- return fmt.Errorf("invalid specification: destination can't be '/'") |
|
118 |
- } |
|
119 |
- return nil |
|
120 |
-} |
|
121 |
- |
|
122 |
-func convertSlash(p string) string { |
|
123 |
- return p |
|
124 |
-} |
|
125 |
- |
|
126 |
-// isAbsPath reports whether the path is absolute. |
|
127 |
-func isAbsPath(p string) bool { |
|
128 |
- return filepath.IsAbs(p) |
|
129 |
-} |
|
130 |
- |
|
131 |
-func splitRawSpec(raw string) ([]string, error) { |
|
132 |
- if strings.Count(raw, ":") > 2 { |
|
133 |
- return nil, errInvalidSpec(raw) |
|
134 |
- } |
|
135 |
- |
|
136 |
- arr := strings.SplitN(raw, ":", 3) |
|
137 |
- if arr[0] == "" { |
|
138 |
- return nil, errInvalidSpec(raw) |
|
139 |
- } |
|
140 |
- return arr, nil |
|
141 |
-} |
|
142 |
- |
|
143 |
-func detectMountType(p string) mounttypes.Type { |
|
144 |
- if filepath.IsAbs(p) { |
|
145 |
- return mounttypes.TypeBind |
|
146 |
- } |
|
147 |
- return mounttypes.TypeVolume |
|
148 |
-} |
|
149 |
- |
|
150 |
-func clean(p string) string { |
|
151 |
- return filepath.Clean(p) |
|
152 |
-} |
|
153 |
- |
|
154 |
-func validateStat(fi os.FileInfo) error { |
|
155 |
- return nil |
|
16 |
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool { |
|
17 |
+ return false |
|
156 | 18 |
} |
157 | 19 |
deleted file mode 100644 |
... | ... |
@@ -1,16 +0,0 @@ |
1 |
-// +build !linux |
|
2 |
- |
|
3 |
-package volume |
|
4 |
- |
|
5 |
-import ( |
|
6 |
- "fmt" |
|
7 |
- "runtime" |
|
8 |
- |
|
9 |
- mounttypes "github.com/docker/docker/api/types/mount" |
|
10 |
-) |
|
11 |
- |
|
12 |
-// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string |
|
13 |
-// for mount(2). |
|
14 |
-func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) { |
|
15 |
- return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS) |
|
16 |
-} |
... | ... |
@@ -1,213 +1,8 @@ |
1 | 1 |
package volume |
2 | 2 |
|
3 |
-import ( |
|
4 |
- "fmt" |
|
5 |
- "os" |
|
6 |
- "path/filepath" |
|
7 |
- "regexp" |
|
8 |
- "strings" |
|
9 |
- |
|
10 |
- mounttypes "github.com/docker/docker/api/types/mount" |
|
11 |
-) |
|
12 |
- |
|
13 |
-// read-write modes |
|
14 |
-var rwModes = map[string]bool{ |
|
15 |
- "rw": true, |
|
16 |
-} |
|
17 |
- |
|
18 |
-// read-only modes |
|
19 |
-var roModes = map[string]bool{ |
|
20 |
- "ro": true, |
|
21 |
-} |
|
22 |
- |
|
23 |
-var platformRawValidationOpts = []func(*validateOpts){} |
|
24 |
- |
|
25 |
-const ( |
|
26 |
- // Spec should be in the format [source:]destination[:mode] |
|
27 |
- // |
|
28 |
- // Examples: c:\foo bar:d:rw |
|
29 |
- // c:\foo:d:\bar |
|
30 |
- // myname:d: |
|
31 |
- // d:\ |
|
32 |
- // |
|
33 |
- // Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See |
|
34 |
- // https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to |
|
35 |
- // test is https://regex-golang.appspot.com/assets/html/index.html |
|
36 |
- // |
|
37 |
- // Useful link for referencing named capturing groups: |
|
38 |
- // http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex |
|
39 |
- // |
|
40 |
- // There are three match groups: source, destination and mode. |
|
41 |
- // |
|
42 |
- |
|
43 |
- // RXHostDir is the first option of a source |
|
44 |
- RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*` |
|
45 |
- // RXName is the second option of a source |
|
46 |
- RXName = `[^\\/:*?"<>|\r\n]+` |
|
47 |
- // RXPipe is a named path pipe (starts with `\\.\pipe\`, possibly with / instead of \) |
|
48 |
- RXPipe = `[/\\]{2}.[/\\]pipe[/\\][^:*?"<>|\r\n]+` |
|
49 |
- // RXReservedNames are reserved names not possible on Windows |
|
50 |
- RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])` |
|
51 |
- |
|
52 |
- // RXSource is the combined possibilities for a source |
|
53 |
- RXSource = `((?P<source>((` + RXHostDir + `)|(` + RXName + `)|(` + RXPipe + `))):)?` |
|
54 |
- |
|
55 |
- // Source. Can be either a host directory, a name, or omitted: |
|
56 |
- // HostDir: |
|
57 |
- // - Essentially using the folder solution from |
|
58 |
- // https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html |
|
59 |
- // but adding case insensitivity. |
|
60 |
- // - Must be an absolute path such as c:\path |
|
61 |
- // - Can include spaces such as `c:\program files` |
|
62 |
- // - And then followed by a colon which is not in the capture group |
|
63 |
- // - And can be optional |
|
64 |
- // Name: |
|
65 |
- // - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx) |
|
66 |
- // - And then followed by a colon which is not in the capture group |
|
67 |
- // - And can be optional |
|
68 |
- |
|
69 |
- // RXDestinationDir is the file path option for the mount destination |
|
70 |
- RXDestinationDir = `([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?)` |
|
71 |
- // RXDestination is the regex expression for the mount destination |
|
72 |
- RXDestination = `(?P<destination>(` + RXDestinationDir + `)|(` + RXPipe + `))` |
|
73 |
- // Destination (aka container path): |
|
74 |
- // - Variation on hostdir but can be a drive followed by colon as well |
|
75 |
- // - If a path, must be absolute. Can include spaces |
|
76 |
- // - Drive cannot be c: (explicitly checked in code, not RegEx) |
|
77 |
- |
|
78 |
- // RXMode is the regex expression for the mode of the mount |
|
79 |
- // Mode (optional): |
|
80 |
- // - Hopefully self explanatory in comparison to above regex's. |
|
81 |
- // - Colon is not in the capture group |
|
82 |
- RXMode = `(:(?P<mode>(?i)ro|rw))?` |
|
83 |
-) |
|
84 |
- |
|
85 |
-// BackwardsCompatible decides whether this mount point can be |
|
86 |
-// used in old versions of Docker or not. |
|
87 |
-// Windows volumes are never backwards compatible. |
|
88 |
-func (m *MountPoint) BackwardsCompatible() bool { |
|
3 |
+func (p *windowsParser) HasResource(m *MountPoint, absolutePath string) bool { |
|
89 | 4 |
return false |
90 | 5 |
} |
91 |
- |
|
92 |
-func splitRawSpec(raw string) ([]string, error) { |
|
93 |
- specExp := regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`) |
|
94 |
- match := specExp.FindStringSubmatch(strings.ToLower(raw)) |
|
95 |
- |
|
96 |
- // Must have something back |
|
97 |
- if len(match) == 0 { |
|
98 |
- return nil, errInvalidSpec(raw) |
|
99 |
- } |
|
100 |
- |
|
101 |
- var split []string |
|
102 |
- matchgroups := make(map[string]string) |
|
103 |
- // Pull out the sub expressions from the named capture groups |
|
104 |
- for i, name := range specExp.SubexpNames() { |
|
105 |
- matchgroups[name] = strings.ToLower(match[i]) |
|
106 |
- } |
|
107 |
- if source, exists := matchgroups["source"]; exists { |
|
108 |
- if source != "" { |
|
109 |
- split = append(split, source) |
|
110 |
- } |
|
111 |
- } |
|
112 |
- if destination, exists := matchgroups["destination"]; exists { |
|
113 |
- if destination != "" { |
|
114 |
- split = append(split, destination) |
|
115 |
- } |
|
116 |
- } |
|
117 |
- if mode, exists := matchgroups["mode"]; exists { |
|
118 |
- if mode != "" { |
|
119 |
- split = append(split, mode) |
|
120 |
- } |
|
121 |
- } |
|
122 |
- // Fix #26329. If the destination appears to be a file, and the source is null, |
|
123 |
- // it may be because we've fallen through the possible naming regex and hit a |
|
124 |
- // situation where the user intention was to map a file into a container through |
|
125 |
- // a local volume, but this is not supported by the platform. |
|
126 |
- if matchgroups["source"] == "" && matchgroups["destination"] != "" { |
|
127 |
- validName, err := IsVolumeNameValid(matchgroups["destination"]) |
|
128 |
- if err != nil { |
|
129 |
- return nil, err |
|
130 |
- } |
|
131 |
- if !validName { |
|
132 |
- if fi, err := os.Stat(matchgroups["destination"]); err == nil { |
|
133 |
- if !fi.IsDir() { |
|
134 |
- return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"]) |
|
135 |
- } |
|
136 |
- } |
|
137 |
- } |
|
138 |
- } |
|
139 |
- return split, nil |
|
140 |
-} |
|
141 |
- |
|
142 |
-func detectMountType(p string) mounttypes.Type { |
|
143 |
- if strings.HasPrefix(filepath.FromSlash(p), `\\.\pipe\`) { |
|
144 |
- return mounttypes.TypeNamedPipe |
|
145 |
- } else if filepath.IsAbs(p) { |
|
146 |
- return mounttypes.TypeBind |
|
147 |
- } |
|
148 |
- return mounttypes.TypeVolume |
|
149 |
-} |
|
150 |
- |
|
151 |
-// IsVolumeNameValid checks a volume name in a platform specific manner. |
|
152 |
-func IsVolumeNameValid(name string) (bool, error) { |
|
153 |
- nameExp := regexp.MustCompile(`^` + RXName + `$`) |
|
154 |
- if !nameExp.MatchString(name) { |
|
155 |
- return false, nil |
|
156 |
- } |
|
157 |
- nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`) |
|
158 |
- if nameExp.MatchString(name) { |
|
159 |
- return false, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name) |
|
160 |
- } |
|
161 |
- return true, nil |
|
162 |
-} |
|
163 |
- |
|
164 |
-// ValidMountMode will make sure the mount mode is valid. |
|
165 |
-// returns if it's a valid mount mode or not. |
|
166 |
-func ValidMountMode(mode string) bool { |
|
167 |
- if mode == "" { |
|
168 |
- return true |
|
169 |
- } |
|
170 |
- return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)] |
|
171 |
-} |
|
172 |
- |
|
173 |
-// ReadWrite tells you if a mode string is a valid read-write mode or not. |
|
174 |
-func ReadWrite(mode string) bool { |
|
175 |
- return rwModes[strings.ToLower(mode)] || mode == "" |
|
176 |
-} |
|
177 |
- |
|
178 |
-func validateNotRoot(p string) error { |
|
179 |
- p = strings.ToLower(convertSlash(p)) |
|
180 |
- if p == "c:" || p == `c:\` { |
|
181 |
- return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p) |
|
182 |
- } |
|
183 |
- return nil |
|
184 |
-} |
|
185 |
- |
|
186 |
-func convertSlash(p string) string { |
|
187 |
- return filepath.FromSlash(p) |
|
188 |
-} |
|
189 |
- |
|
190 |
-// isAbsPath returns whether a path is absolute for the purposes of mounting into a container |
|
191 |
-// (absolute paths, drive letter paths such as X:, and paths starting with `\\.\` to support named pipes). |
|
192 |
-func isAbsPath(p string) bool { |
|
193 |
- return filepath.IsAbs(p) || |
|
194 |
- strings.HasPrefix(p, `\\.\`) || |
|
195 |
- (len(p) == 2 && p[1] == ':' && ((p[0] >= 'a' && p[0] <= 'z') || (p[0] >= 'A' && p[0] <= 'Z'))) |
|
196 |
-} |
|
197 |
- |
|
198 |
-// Do not clean plain drive letters or paths starting with `\\.\`. |
|
199 |
-var cleanRegexp = regexp.MustCompile(`^([a-z]:|[/\\]{2}\.[/\\].*)$`) |
|
200 |
- |
|
201 |
-func clean(p string) string { |
|
202 |
- if match := cleanRegexp.MatchString(p); match { |
|
203 |
- return p |
|
204 |
- } |
|
205 |
- return filepath.Clean(p) |
|
206 |
-} |
|
207 |
- |
|
208 |
-func validateStat(fi os.FileInfo) error { |
|
209 |
- if !fi.IsDir() { |
|
210 |
- return fmt.Errorf("source path must be a directory") |
|
211 |
- } |
|
212 |
- return nil |
|
6 |
+func (p *linuxParser) HasResource(m *MountPoint, absolutePath string) bool { |
|
7 |
+ return false |
|
213 | 8 |
} |
214 | 9 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,456 @@ |
0 |
+package volume |
|
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, errBindNotExist} |
|
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 |
+} |