Skip to content
This repository was archived by the owner on Oct 13, 2023. It is now read-only.

Commit ec46e07

Browse files
authored
Merge pull request #1995 from thaJeztah/19.03_backport_cross_platform_bind
[19.03 backport] Detect Windows absolute paths on non-Windows CLI Upstream-commit: d473c60571eeedee67e0625f138a92fe45580caf Component: cli
2 parents 99c11b4 + 1294cf7 commit ec46e07

4 files changed

Lines changed: 212 additions & 6 deletions

File tree

components/cli/cli/compose/loader/loader.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -479,12 +479,13 @@ func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string,
479479
}
480480

481481
filePath := expandUser(volume.Source, lookupEnv)
482-
// Check for a Unix absolute path first, to handle a Windows client
483-
// with a Unix daemon. This handles a Windows client connecting to a
484-
// Unix daemon. Note that this is not required for Docker for Windows
485-
// when specifying a local Windows path, because Docker for Windows
486-
// translates the Windows path into a valid path within the VM.
487-
if !path.IsAbs(filePath) {
482+
// Check if source is an absolute path (either Unix or Windows), to
483+
// handle a Windows client with a Unix daemon or vice-versa.
484+
//
485+
// Note that this is not required for Docker for Windows when specifying
486+
// a local Windows path, because Docker for Windows translates the Windows
487+
// path into a valid path within the VM.
488+
if !path.IsAbs(filePath) && !isAbs(filePath) {
488489
filePath = absPath(workingDir, filePath)
489490
}
490491
volume.Source = filePath

components/cli/cli/compose/loader/loader_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,84 @@ services:
985985
assert.Error(t, err, `invalid mount config for type "bind": field Source must not be empty`)
986986
}
987987

988+
func TestLoadBindMountSourceIsWindowsAbsolute(t *testing.T) {
989+
tests := []struct {
990+
doc string
991+
yaml string
992+
expected types.ServiceVolumeConfig
993+
}{
994+
{
995+
doc: "Z-drive lowercase",
996+
yaml: `
997+
version: '3.3'
998+
999+
services:
1000+
windows:
1001+
image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
1002+
volumes:
1003+
- type: bind
1004+
source: z:\
1005+
target: c:\data
1006+
`,
1007+
expected: types.ServiceVolumeConfig{Type: "bind", Source: `z:\`, Target: `c:\data`},
1008+
},
1009+
{
1010+
doc: "Z-drive uppercase",
1011+
yaml: `
1012+
version: '3.3'
1013+
1014+
services:
1015+
windows:
1016+
image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
1017+
volumes:
1018+
- type: bind
1019+
source: Z:\
1020+
target: C:\data
1021+
`,
1022+
expected: types.ServiceVolumeConfig{Type: "bind", Source: `Z:\`, Target: `C:\data`},
1023+
},
1024+
{
1025+
doc: "Z-drive subdirectory",
1026+
yaml: `
1027+
version: '3.3'
1028+
1029+
services:
1030+
windows:
1031+
image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
1032+
volumes:
1033+
- type: bind
1034+
source: Z:\some-dir
1035+
target: C:\data
1036+
`,
1037+
expected: types.ServiceVolumeConfig{Type: "bind", Source: `Z:\some-dir`, Target: `C:\data`},
1038+
},
1039+
{
1040+
doc: "forward-slashes",
1041+
yaml: `
1042+
version: '3.3'
1043+
1044+
services:
1045+
app:
1046+
image: app:latest
1047+
volumes:
1048+
- type: bind
1049+
source: /z/some-dir
1050+
target: /c/data
1051+
`,
1052+
expected: types.ServiceVolumeConfig{Type: "bind", Source: `/z/some-dir`, Target: `/c/data`},
1053+
},
1054+
}
1055+
1056+
for _, tc := range tests {
1057+
t.Run(tc.doc, func(t *testing.T) {
1058+
config, err := loadYAML(tc.yaml)
1059+
assert.NilError(t, err)
1060+
assert.Check(t, is.Len(config.Services[0].Volumes, 1))
1061+
assert.Check(t, is.DeepEqual(tc.expected, config.Services[0].Volumes[0]))
1062+
})
1063+
}
1064+
}
1065+
9881066
func TestLoadBindMountWithSource(t *testing.T) {
9891067
config, err := loadYAML(`
9901068
version: "3.5"
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package loader
2+
3+
// Copyright 2010 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
// https://github.com/golang/go/blob/master/LICENSE
7+
8+
// This file contains utilities to check for Windows absolute paths on Linux.
9+
// The code in this file was largely copied from the Golang filepath package
10+
// https://github.com/golang/go/blob/1d0e94b1e13d5e8a323a63cd1cc1ef95290c9c36/src/path/filepath/path_windows.go#L12-L65
11+
12+
func isSlash(c uint8) bool {
13+
return c == '\\' || c == '/'
14+
}
15+
16+
// isAbs reports whether the path is a Windows absolute path.
17+
func isAbs(path string) (b bool) {
18+
l := volumeNameLen(path)
19+
if l == 0 {
20+
return false
21+
}
22+
path = path[l:]
23+
if path == "" {
24+
return false
25+
}
26+
return isSlash(path[0])
27+
}
28+
29+
// volumeNameLen returns length of the leading volume name on Windows.
30+
// It returns 0 elsewhere.
31+
// nolint: gocyclo
32+
func volumeNameLen(path string) int {
33+
if len(path) < 2 {
34+
return 0
35+
}
36+
// with drive letter
37+
c := path[0]
38+
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
39+
return 2
40+
}
41+
// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
42+
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
43+
!isSlash(path[2]) && path[2] != '.' {
44+
// first, leading `\\` and next shouldn't be `\`. its server name.
45+
for n := 3; n < l-1; n++ {
46+
// second, next '\' shouldn't be repeated.
47+
if isSlash(path[n]) {
48+
n++
49+
// third, following something characters. its share name.
50+
if !isSlash(path[n]) {
51+
if path[n] == '.' {
52+
break
53+
}
54+
for ; n < l; n++ {
55+
if isSlash(path[n]) {
56+
break
57+
}
58+
}
59+
return n
60+
}
61+
break
62+
}
63+
}
64+
}
65+
return 0
66+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package loader
2+
3+
// Copyright 2010 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
// https://github.com/golang/go/blob/master/LICENSE
7+
8+
// The code in this file was copied from the Golang filepath package with some
9+
// small modifications to run it on non-Windows platforms.
10+
// https://github.com/golang/go/blob/1d0e94b1e13d5e8a323a63cd1cc1ef95290c9c36/src/path/filepath/path_test.go#L711-L763
11+
12+
import "testing"
13+
14+
type IsAbsTest struct {
15+
path string
16+
isAbs bool
17+
}
18+
19+
var isabstests = []IsAbsTest{
20+
{"", false},
21+
{"/", true},
22+
{"/usr/bin/gcc", true},
23+
{"..", false},
24+
{"/a/../bb", true},
25+
{".", false},
26+
{"./", false},
27+
{"lala", false},
28+
}
29+
30+
var winisabstests = []IsAbsTest{
31+
{`C:\`, true},
32+
{`c\`, false},
33+
{`c::`, false},
34+
{`c:`, false},
35+
{`/`, false},
36+
{`\`, false},
37+
{`\Windows`, false},
38+
{`c:a\b`, false},
39+
{`c:\a\b`, true},
40+
{`c:/a/b`, true},
41+
{`\\host\share\foo`, true},
42+
{`//host/share/foo/bar`, true},
43+
}
44+
45+
func TestIsAbs(t *testing.T) {
46+
tests := append(isabstests, winisabstests...)
47+
// All non-windows tests should fail, because they have no volume letter.
48+
for _, test := range isabstests {
49+
tests = append(tests, IsAbsTest{test.path, false})
50+
}
51+
// All non-windows test should work as intended if prefixed with volume letter.
52+
for _, test := range isabstests {
53+
tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
54+
}
55+
56+
for _, test := range winisabstests {
57+
if r := isAbs(test.path); r != test.isAbs {
58+
t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)