Skip to content

Commit aebb027

Browse files
committed
fix(psutils): 修复 WSL Docker 路径转换
1 parent d5e5715 commit aebb027

4 files changed

Lines changed: 95 additions & 2 deletions

File tree

‎.trellis/spec/psutils/package/index.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* 修改配置解析、env 读取、frontmatter、`.psd1` data file 或 CLI 参数合并时,先阅读 [Shared Config Resolver](./shared-config-resolver.md)。
1515
* Pester 运行方式复用 `scripts/pwsh/devops/Invoke-PesterMode.ps1`,包内脚本通过 `PWSH_TEST_PATH` 限定 `./tests`。
1616
* 覆盖率门槛仍由根目录 `PesterConfiguration.ps1` 统一管理;包级 `test:full` 只做断言回归,不单独改 coverage 策略。
17+
* 修改 WSL Docker wrapper 或 docker 参数路径转换时,先阅读 [WSL Docker Wrapper](./wsl-docker-wrapper.md)。
1718

1819
## Package Script Contract
1920

@@ -32,3 +33,4 @@
3233
| Guide | Description | Status |
3334
|-------|-------------|--------|
3435
| [Shared Config Resolver](./shared-config-resolver.md) | `psutils/src/config` 配置来源、合并优先级、导出函数与测试契约 | Active |
36+
| [WSL Docker Wrapper](./wsl-docker-wrapper.md) | `psutils/modules/docker.psm1` WSL Docker wrapper 的路径转换与跨平台测试契约 | Active |
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# WSL Docker Wrapper Spec
2+
3+
> 本规范记录 `psutils/modules/docker.psm1` 中 WSL Docker wrapper 的路径转换与测试契约。
4+
5+
## Scenario: WSL Docker Path Conversion
6+
7+
### 1. Scope / Trigger
8+
9+
- Trigger: 修改 `ConvertTo-WslDockerPath`、`ConvertTo-WslDockerArgument`、`Invoke-WslDocker`、`Enable-WslDockerWrapper` 或 `psutils/tests/docker.Tests.ps1`。
10+
- Scope: Windows PowerShell 会话把 `docker` 命令代理到 WSL 发行版内的 Docker Engine,期间只转换需要进入 WSL 的宿主机路径。
11+
- Design intent: wrapper 必须支持 Windows 调用场景,同时 Pester 测试要能在 Windows、Linux、macOS CI 上稳定验证同一契约。
12+
13+
### 2. Signatures
14+
15+
- `ConvertTo-WslDockerPath -Path <string> [-WslCommand <string>] [-Distro <string>]`
16+
- `ConvertTo-WslDockerVolumeSpec -VolumeSpec <string> [-WslCommand <string>] [-Distro <string>]`
17+
- `ConvertTo-WslDockerMountSpec -MountSpec <string> [-WslCommand <string>] [-Distro <string>]`
18+
- `ConvertTo-WslDockerArgument -Arguments <object[]> -Distro <string> [-WslCommand <string>]`
19+
- `Invoke-WslDocker -Distro <string> -Arguments <object[]> [-WslCommand <string>]`
20+
21+
### 3. Contracts
22+
23+
- Windows drive paths like `C:\data` and UNC paths must be converted through `wslpath -a`.
24+
- Existing relative paths must first resolve against the current PowerShell location.
25+
- If the resolved path is still POSIX style, return that absolute POSIX path directly instead of calling `wslpath`.
26+
- Non-existing relative values remain unchanged so service names, image names and container names are not treated as paths.
27+
- `-f`、`--file`、`--env-file`、`--project-directory` accept path values.
28+
- `-v`、`--volume` convert only the host source side.
29+
- `--mount` and `type=bind,...` convert only `src=` or `source=`.
30+
- `Invoke-WslDocker` passes the converted current location through `wsl.exe -d <distro> --cd <path> -- docker ...`; if conversion leaves a Windows drive path, fallback working directory is `~`.
31+
32+
### 4. Validation & Error Matrix
33+
34+
| Condition | Expected Behavior |
35+
|-----------|-------------------|
36+
| `Path` is null, empty or whitespace | Return original value |
37+
| Existing relative path on Windows | Resolve to absolute Windows path, then call `wslpath -a` |
38+
| Existing relative path on Linux/macOS CI | Resolve to absolute POSIX path and return it directly |
39+
| POSIX absolute path | Return original POSIX path |
40+
| Non-existing relative token | Return original token |
41+
| `wslpath` throws or exits non-zero | Write verbose diagnostic and return original input |
42+
| Working directory stays Windows drive path | Use `~` for `--cd` |
43+
44+
### 5. Good/Base/Bad Cases
45+
46+
- Good: `docker compose -f .\docker-compose.yml --env-file=.env -v .\data:/data:ro` converts the three host paths on Windows and returns absolute POSIX paths unchanged on Linux/macOS CI.
47+
- Base: `docker compose restart api` keeps `api` unchanged even if a file with that name exists.
48+
- Bad: Treating every slash-prefixed path as Windows input creates invalid values like `/mnt//tmp/...` in non-Windows CI.
49+
50+
### 6. Tests Required
51+
52+
- `psutils/tests/docker.Tests.ps1` must cover wrapper enablement, raw `docker run`, compose file/env/volume/mount conversion, env path forwarding, and non-path positional arguments.
53+
- Path conversion tests must derive expected current working directory through `ConvertTo-WslDockerPath` instead of hard-coding one OS-specific path.
54+
- Test doubles for `wsl.exe` must model Windows drive paths and POSIX paths separately.
55+
56+
### 7. Wrong vs Correct
57+
58+
#### Wrong
59+
60+
```powershell
61+
# 在 Linux/macOS CI 中会把 /tmp/demo 错转成 /mnt//mp/demo
62+
& wsl.exe -d Ubuntu-24.04 -- wslpath -a '/tmp/demo'
63+
```
64+
65+
问题:POSIX 路径不是 Windows 宿主机路径,不能交给按盘符模拟的 `wslpath` 测试替身。
66+
67+
#### Correct
68+
69+
```powershell
70+
$resolvedPath = Resolve-Path -LiteralPath '.\data'
71+
if ($resolvedPath.Path -match '^[A-Za-z]:[\\/]' -or $resolvedPath.Path -match '^\\\\') {
72+
& wsl.exe -d Ubuntu-24.04 -- wslpath -a $resolvedPath.Path
73+
}
74+
else {
75+
$resolvedPath.Path
76+
}
77+
```
78+
79+
理由:只转换 Windows 宿主机路径,跨平台 CI 里的 POSIX 路径保持原生形态。

‎psutils/modules/docker.psm1‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,11 @@ function ConvertTo-WslDockerPath {
466466
else {
467467
return $Path
468468
}
469+
470+
# 非 Windows 路径已经可直接在 WSL/Linux 侧使用,避免 CI 中的 POSIX 路径被误转成 /mnt//path。
471+
if (-not ($pathToConvert -match '^[A-Za-z]:[\\/]' -or $pathToConvert -match '^\\\\')) {
472+
return $pathToConvert
473+
}
469474
}
470475

471476
try {

‎psutils/tests/docker.Tests.ps1‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,13 @@ Describe 'WSL Docker wrapper helper' {
138138
return
139139
}
140140
if ($joined -like '-d Ubuntu-24.04 -- wslpath -a *') {
141-
'/mnt/c/workspace'
141+
$inputPath = $Args[-1]
142+
if ($inputPath -match '^[A-Za-z]:[\\/]') {
143+
'/mnt/c/workspace'
144+
}
145+
else {
146+
$inputPath
147+
}
142148
$global:LASTEXITCODE = 0
143149
return
144150
}
@@ -150,12 +156,13 @@ Describe 'WSL Docker wrapper helper' {
150156
Enable-WslDockerWrapper -DockerCommand 'docker' -WslCommand 'wsl.exe' -Force | Should -BeTrue
151157

152158
docker run --rm alpine:3.20 echo ok
159+
$expectedWorkingDirectory = ConvertTo-WslDockerPath -Path (Get-Location).Path -Distro 'Ubuntu-24.04'
153160

154161
$script:LastWslDockerArgs | Should -Be @(
155162
'-d'
156163
'Ubuntu-24.04'
157164
'--cd'
158-
'/mnt/c/workspace'
165+
$expectedWorkingDirectory
159166
'--'
160167
'docker'
161168
'run'

0 commit comments

Comments
 (0)