Skip to content

Commit 021d30c

Browse files
committed
fix: mounting issue with single character volume on windows
Signed-off-by: axel7083 <[email protected]>
1 parent 53c9100 commit 021d30c

File tree

4 files changed

+178
-1
lines changed

4 files changed

+178
-1
lines changed

pkg/specgen/volumes.go

+16-1
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,9 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na
203203
return mounts, volumes, overlayVolumes, nil
204204
}
205205

206-
// Splits a volume string, accounting for Win drive paths
206+
// SplitVolumeString Splits a volume string, accounting for Win drive paths
207207
// when running as a WSL linux guest or Windows client
208+
// Format: [[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]
208209
func SplitVolumeString(vol string) []string {
209210
parts := strings.Split(vol, ":")
210211
if !shouldResolveWinPaths() {
@@ -217,6 +218,20 @@ func SplitVolumeString(vol string) []string {
217218
n = 4
218219
}
219220

221+
// Determine if the last part is an option (e.g., "ro", "z")
222+
hasOption := !strings.HasPrefix(parts[len(parts)-1], "/")
223+
224+
// Case: Volume or relative host path (e.g., "vol-name:/container" or "./hello:/container")
225+
if !hasOption && len(parts) == 2 {
226+
return parts
227+
}
228+
229+
// Case: Volume or relative host path with options (e.g., "vol-name:/container:ro" or "./hello:/container:ro")
230+
if hasOption && len(parts) == 3 {
231+
return parts
232+
}
233+
234+
// Case: Windows absolute path (e.g., "C:/Users:/mnt:ro")
220235
if hasWinDriveScheme(vol, n) {
221236
first := parts[0] + ":" + parts[1]
222237
parts = parts[1:]

pkg/specgen/volumes_linux_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package specgen
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestSplitVolumeString(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
volume string
13+
expect []string
14+
}{
15+
// relative host paths
16+
{
17+
name: "relative host path",
18+
volume: "./hello:/container",
19+
expect: []string{"./hello", "/container"},
20+
},
21+
{
22+
name: "relative host path with options",
23+
volume: "./hello:/container:ro",
24+
expect: []string{"./hello", "/container", "ro"},
25+
},
26+
// absolute host path
27+
{
28+
name: "absolute host path",
29+
volume: "/hello:/container",
30+
expect: []string{"/hello", "/container"},
31+
},
32+
{
33+
name: "absolute host path with option",
34+
volume: "/hello:/container:ro",
35+
expect: []string{"/hello", "/container", "ro"},
36+
},
37+
{
38+
name: "absolute host path with option",
39+
volume: "/hello:/container:ro",
40+
expect: []string{"/hello", "/container", "ro"},
41+
},
42+
// volume source
43+
{
44+
name: "volume without option",
45+
volume: "vol-name:/container",
46+
expect: []string{"vol-name", "/container"},
47+
},
48+
{
49+
name: "volume with option",
50+
volume: "vol-name:/container:ro",
51+
expect: []string{"vol-name", "/container", "ro"},
52+
},
53+
{
54+
name: "single letter volume without option",
55+
volume: "a:/container",
56+
expect: []string{"a", "/container"},
57+
},
58+
{
59+
name: "single letter volume with option",
60+
volume: "a:/container:ro",
61+
expect: []string{"a", "/container", "ro"},
62+
},
63+
}
64+
65+
for _, tt := range tests {
66+
t.Run(tt.name, func(t *testing.T) {
67+
parts := SplitVolumeString(tt.volume)
68+
69+
assert.Equal(t, tt.expect, parts, tt.name)
70+
})
71+
}
72+
}

pkg/specgen/volumes_windows_test.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package specgen
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestSplitVolumeString(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
volume string
13+
expect []string
14+
}{
15+
// relative host paths
16+
{
17+
name: "relative host path",
18+
volume: "./hello:/container",
19+
expect: []string{"./hello", "/container"},
20+
},
21+
{
22+
name: "relative host path with options",
23+
volume: "./hello:/container:ro",
24+
expect: []string{"./hello", "/container", "ro"},
25+
},
26+
// absolute host path
27+
{
28+
name: "absolute host path",
29+
volume: "C:\\hello:/container",
30+
expect: []string{"C:\\hello", "/container"},
31+
},
32+
{
33+
name: "absolute host path with option",
34+
volume: "C:\\hello:/container:ro",
35+
expect: []string{"C:\\hello", "/container", "ro"},
36+
},
37+
{
38+
name: "absolute host path with option",
39+
volume: "C:\\hello:/container:ro",
40+
expect: []string{"C:\\hello", "/container", "ro"},
41+
},
42+
{
43+
name: "absolute extended host path",
44+
volume: `\\?\C:\hello:/container`,
45+
expect: []string{`\\?\C:\hello`, "/container"},
46+
},
47+
// volume source
48+
{
49+
name: "volume without option",
50+
volume: "vol-name:/container",
51+
expect: []string{"vol-name", "/container"},
52+
},
53+
{
54+
name: "volume with option",
55+
volume: "vol-name:/container:ro",
56+
expect: []string{"vol-name", "/container", "ro"},
57+
},
58+
{
59+
name: "single letter volume without option",
60+
volume: "a:/container",
61+
expect: []string{"a", "/container"},
62+
},
63+
{
64+
name: "single letter volume with option",
65+
volume: "a:/container:ro",
66+
expect: []string{"a", "/container", "ro"},
67+
},
68+
}
69+
70+
for _, tt := range tests {
71+
t.Run(tt.name, func(t *testing.T) {
72+
parts := SplitVolumeString(tt.volume)
73+
74+
assert.Equal(t, tt.expect, parts, tt.name)
75+
})
76+
}
77+
}

test/e2e/run_volume_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,19 @@ var _ = Describe("Podman run with volumes", func() {
145145
Expect(session).To(ExitWithError(125, fmt.Sprintf("%s: duplicate mount destination", dest)))
146146
})
147147

148+
It("podman run with single character volume", func() {
149+
// 1. create single character volume
150+
session := podmanTest.Podman([]string{"volume", "create", "a"})
151+
session.WaitWithDefaultTimeout()
152+
volName := session.OutputToString()
153+
Expect(session).Should(ExitCleanly())
154+
155+
// 2. create container with volume
156+
session = podmanTest.Podman([]string{"run", "--volume", volName + ":/data", ALPINE, "sh", "-c", "echo hello world"})
157+
session.WaitWithDefaultTimeout()
158+
Expect(session).Should(ExitCleanly())
159+
})
160+
148161
It("podman run with conflict between image volume and user mount succeeds", func() {
149162
err = podmanTest.RestoreArtifact(REDIS_IMAGE)
150163
Expect(err).ToNot(HaveOccurred())

0 commit comments

Comments
 (0)