Skip to content

Commit 4268b85

Browse files
convert windows paths to wsl2 paths
Signed-off-by: Swapnanil-Gupta <swpnlg@amazon.com>
1 parent fb60659 commit 4268b85

4 files changed

Lines changed: 424 additions & 1 deletion

File tree

option/modifier.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,16 @@ func WithNerdctlVersion(version string) Modifier {
4949
o.features[nerdctlVersion] = version
5050
})
5151
}
52+
53+
// WithWindowsHostPathTranslation makes the option rewrite Windows drive-letter
54+
// paths (e.g. `C:\Users\foo`) in command arguments to their WSL2 equivalents
55+
// (e.g. `/mnt/c/Users/foo`) before executing.
56+
//
57+
// This is done in the Finch CLI too.
58+
// See (https://github.com/runfinch/finch/blob/ff1346b1d76f083ba86433e4501cbb5e5ce29634/cmd/finch/nerdctl_windows.go#L72),
59+
// But since common-tests is meant to be executed outside of Finch CLI (e.g Finch Core), we need this as an option.
60+
func WithWindowsHostPathTranslation() Modifier {
61+
return newFuncModifier(func(o *Option) {
62+
o.features[windowsHostPathTranslation] = true
63+
})
64+
}

option/option.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type feature int
1919
const (
2020
environmentVariablePassthrough feature = iota
2121
nerdctlVersion feature = iota
22+
windowsHostPathTranslation feature = iota
2223
)
2324

2425
var (
@@ -68,6 +69,9 @@ func New(subject []string, modifiers ...Modifier) (*Option, error) {
6869
// NewCmd creates a command using the stored option and the provided args.
6970
func (o *Option) NewCmd(args ...string) *exec.Cmd {
7071
cmdName := o.subject[0]
72+
if o.supportsWindowsHostPathTranslation() {
73+
args = translateWindowsHostPaths(args)
74+
}
7175
cmdArgs := append(o.subject[1:], args...) //nolint:gocritic // appendAssign does not apply to our case.
7276
cmd := exec.Command(cmdName, cmdArgs...) //nolint:gosec // G204 is not an issue because cmdName is fully controlled by the user.
7377
cmd.Env = append(os.Environ(), o.env...)
@@ -113,6 +117,17 @@ func (o *Option) SupportsEnvVarPassthrough() bool {
113117
return false
114118
}
115119

120+
// supportsWindowsHostPathTranslation reports whether command arguments should
121+
// have Windows host paths rewritten to their WSL2 equivalents before execution.
122+
func (o *Option) supportsWindowsHostPathTranslation() bool {
123+
if value, exists := o.features[windowsHostPathTranslation]; exists {
124+
if boolValue, ok := value.(bool); ok {
125+
return boolValue
126+
}
127+
}
128+
return false
129+
}
130+
116131
// IsNerdctlV1 is used by tests to check if the option supports [feature.nerdctlVersion] == nerdctl1xx.
117132
func (o *Option) IsNerdctlV1() bool {
118133
return o.isNerdctlVersion(isNerdctl1xx)
@@ -159,13 +174,25 @@ func (o *Option) GetNerdctlVersion() (string, error) {
159174
//nolint:gosec // G204 is not an issue because subject is fully controlled by the user.
160175
versionBytes, err := exec.Command(o.subject[0], "version").Output()
161176
if err != nil {
162-
return "", fmt.Errorf("failed to run nerdctl --version: %w", err)
177+
return "", fmt.Errorf("failed to run finch version: %w", err)
163178
}
164179
version, err := getNerdctlVersionMatch(finchNerdctlVersionRegex, string(versionBytes))
165180
if err != nil {
166181
return "", err
167182
}
168183
return version, nil
184+
case "limactl":
185+
// Assumes that "finch" is the vm name
186+
//nolint:gosec // G204 is not an issue because subject is fully controlled by the user.
187+
versionBytes, err := exec.Command(o.subject[0], "shell", "finch", "nerdctl", "--version").Output()
188+
if err != nil {
189+
return "", fmt.Errorf("failed to run nerdctl --version with limactl: %w", err)
190+
}
191+
version, err := getNerdctlVersionMatch(nerdctlVersionRegex, string(versionBytes))
192+
if err != nil {
193+
return "", err
194+
}
195+
return version, nil
169196
default:
170197
return "", fmt.Errorf("unsupported subject %s", execName)
171198
}

option/option_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,116 @@ func TestSupportsEnvVarPassthrough(t *testing.T) {
4949
}
5050
}
5151

52+
func TestTranslateWindowsHostPaths(t *testing.T) {
53+
t.Parallel()
54+
55+
tests := []struct {
56+
name string
57+
in []string
58+
want []string
59+
}{
60+
{
61+
name: "BuildContextPositional",
62+
in: []string{"build", "-t", "foo", `C:\Users\foo\finch-test123`},
63+
want: []string{"build", "-t", "foo", "/mnt/c/Users/foo/finch-test123"},
64+
},
65+
{
66+
name: "BuildDockerfileAndContext",
67+
in: []string{"build", "-f", `D:\a\Dockerfile`, "--no-cache", `D:\a`},
68+
want: []string{"build", "-f", "/mnt/d/a/Dockerfile", "--no-cache", "/mnt/d/a"},
69+
},
70+
{
71+
name: "BuildOutputDest",
72+
in: []string{"build", "-t", "output:tag", "--output", `type=tar,dest=C:\Users\foo\out.tar`, `C:\ctx`},
73+
want: []string{"build", "-t", "output:tag", "--output", "type=tar,dest=/mnt/c/Users/foo/out.tar", "/mnt/c/ctx"},
74+
},
75+
{
76+
name: "BuildOutputEqualsForm",
77+
in: []string{"build", `--output=type=docker`, `C:\ctx`},
78+
want: []string{"build", "--output=type=docker", "/mnt/c/ctx"},
79+
},
80+
{
81+
name: "BuildSecretSrc",
82+
in: []string{"build", "--secret", `id=mysecret,src=C:\Users\foo\secret.txt`, "-f", `C:\Users\foo\Dockerfile`, `C:\Users\foo`},
83+
want: []string{
84+
"build", "--secret", "id=mysecret,src=/mnt/c/Users/foo/secret.txt",
85+
"-f", "/mnt/c/Users/foo/Dockerfile", "/mnt/c/Users/foo",
86+
},
87+
},
88+
{
89+
name: "SaveOutputFlag",
90+
in: []string{"save", "-o", `C:\Users\foo\test.tar`, "alpine:latest"},
91+
want: []string{"save", "-o", "/mnt/c/Users/foo/test.tar", "alpine:latest"},
92+
},
93+
{
94+
name: "SaveOutputFlagBeforeImages",
95+
in: []string{"save", "--output", `C:\Users\foo\test.tar`, "alpine:latest", "alpine:3.13"},
96+
want: []string{"save", "--output", "/mnt/c/Users/foo/test.tar", "alpine:latest", "alpine:3.13"},
97+
},
98+
{
99+
name: "LoadInputFlag",
100+
in: []string{"load", "-i", `C:\Users\foo\test.tar`},
101+
want: []string{"load", "-i", "/mnt/c/Users/foo/test.tar"},
102+
},
103+
{
104+
name: "ComposeFileFlag",
105+
in: []string{"compose", "up", "--file", `C:\Users\foo\docker-compose.yml`},
106+
want: []string{"compose", "up", "--file", "/mnt/c/Users/foo/docker-compose.yml"},
107+
},
108+
{
109+
name: "CpHostToContainer",
110+
in: []string{"cp", `C:\Users\foo\test-file`, "finch-test-ctr:/tmp/test-file"},
111+
want: []string{"cp", "/mnt/c/Users/foo/test-file", "finch-test-ctr:/tmp/test-file"},
112+
},
113+
{
114+
name: "CpContainerToHost",
115+
in: []string{"cp", "finch-test-ctr:/tmp/test-file", `C:\Users\foo\test-file`},
116+
want: []string{"cp", "finch-test-ctr:/tmp/test-file", "/mnt/c/Users/foo/test-file"},
117+
},
118+
{
119+
name: "CpWithFollowLinkFlagAndContainerSpec",
120+
in: []string{"cp", "-L", `C:\Users\foo\symlink`, "finch-test-ctr:/tmp/test-file"},
121+
want: []string{"cp", "-L", "/mnt/c/Users/foo/symlink", "finch-test-ctr:/tmp/test-file"},
122+
},
123+
{
124+
name: "RunBindMountHostPathOnly",
125+
in: []string{"run", "-v", `C:\host:/container`, "alpine:3.13"},
126+
want: []string{"run", "-v", "/mnt/c/host:/container", "alpine:3.13"},
127+
},
128+
{
129+
name: "RunNamedVolumeUntouched",
130+
in: []string{"run", "-v", "foo:/usr/share", "--name", "ctr", "alpine:3.13"},
131+
want: []string{"run", "-v", "foo:/usr/share", "--name", "ctr", "alpine:3.13"},
132+
},
133+
{
134+
name: "RunAnonymousVolumeUntouched",
135+
in: []string{"run", "-v", "/usr/share", "--name", "ctr", "alpine:3.13"},
136+
want: []string{"run", "-v", "/usr/share", "--name", "ctr", "alpine:3.13"},
137+
},
138+
{
139+
name: "PullImageTagAndFlagsUntouched",
140+
in: []string{"pull", "alpine:3.13", "--platform", "linux/amd64"},
141+
want: []string{"pull", "alpine:3.13", "--platform", "linux/amd64"},
142+
},
143+
}
144+
145+
for _, test := range tests {
146+
t.Run(test.name, func(t *testing.T) {
147+
t.Parallel()
148+
149+
got := translateWindowsHostPaths(test.in)
150+
if len(got) != len(test.want) {
151+
t.Fatalf("length mismatch: got %v, want %v", got, test.want)
152+
}
153+
for i := range got {
154+
if got[i] != test.want[i] {
155+
t.Errorf("arg %d: got %q, want %q", i, got[i], test.want[i])
156+
}
157+
}
158+
})
159+
}
160+
}
161+
52162
func TestNerdctlVersion(t *testing.T) {
53163
t.Parallel()
54164

0 commit comments

Comments
 (0)