Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/backends/wslc/wsl-container-getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,15 @@ WSLC-specific settings go under `wslc` in the JSON config:
| `imageTarPath` | string | β€” | Path to a local tar file to import as the image |
| `portMappings` | array | `[]` | Host→container TCP forwards; each entry takes `windowsPort` and `containerPort` |

### `containerId` names the WSL session

A create-and-run request names its WSL session after the top-level
`containerId`. The field is optional: omit it and the run gets a generated
name of the form `mxc-wslc-<uuid>`, fresh on every run.

Supply your own only when you need a name you can recognise β€” two runs sharing
one `containerId` are not isolated from each other by the id alone.

### Port mappings

```json
Expand Down
3 changes: 2 additions & 1 deletion docs/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ that can be executed independently.
```json
{
"version": "1.0.0", // Exact schema version. Minimum supported: "0.9.0-alpha"; current stable: "1.0.0".
"containerId": "my-container", // Externally assigned container ID
"containerId": "my-container", // Optional externally assigned container ID.
// Omit it to use backend-defined internal naming.
"containment": "processcontainer", // Backend (see table below)

"lifecycle": {
Expand Down
45 changes: 43 additions & 2 deletions src/mxc-sdk/src/backends/wslc/common/wsl_container_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,21 @@ fn reject_unsupported_lifecycle(request: &ExecutionRequest) -> Result<(), Script
Ok(())
}

const GENERATED_SESSION_NAME_PREFIX: &str = "mxc-wslc-";

/// Name the WSLc session a run gets, minting one when `containerId` is absent:
/// WSLc refuses an empty name with `WSLC_E_INVALID_SESSION_NAME` (0x80040608).
fn resolve_session_name(container_id: &str) -> String {
if container_id.is_empty() {
format!(
"{GENERATED_SESSION_NAME_PREFIX}{}",
uuid::Uuid::new_v4().simple()
)
} else {
container_id.to_string()
}
}

impl WSLContainerRunner {
/// Configure session settings and create the session.
/// Returns the session guard (RAII).
Expand All @@ -506,7 +521,8 @@ impl WSLContainerRunner {
request: &ExecutionRequest,
logger: &mut Logger,
) -> Result<WslcSessionGuard, ScriptResponse> {
let session_name: Vec<u16> = to_wide(&request.container_id);
let session_name_str = resolve_session_name(&request.container_id);
let session_name: Vec<u16> = to_wide(&session_name_str);
let storage_path_str = self.config.storage_path.clone().unwrap_or_else(|| {
std::env::temp_dir()
.join("mxc-wslc-sessions")
Expand Down Expand Up @@ -574,7 +590,7 @@ impl WSLContainerRunner {
let msg = err_msg.to_string_lossy();
return Err(sdk_error("WslcCreateSession failed", hr, &msg));
}
let _ = writeln!(logger, "[WSLC] Session created");
let _ = writeln!(logger, "[WSLC] Session created: {session_name_str}");

Ok(WslcSessionGuard::from_raw(
session,
Expand Down Expand Up @@ -2270,4 +2286,29 @@ mod tests {

assert!(message.contains("ensure WSL2 and the WSLC SDK are installed"));
}

#[test]
fn session_name_is_the_container_id_when_the_request_names_one() {
assert_eq!(resolve_session_name("pitest"), "pitest");
assert_eq!(resolve_session_name("wslc-hello-world"), "wslc-hello-world");
}

#[test]
fn absent_container_id_mints_a_session_name() {
let name = resolve_session_name("");

assert!(
name.starts_with(GENERATED_SESSION_NAME_PREFIX),
"expected a minted name, got: {name}"
);
assert!(
name.len() > GENERATED_SESSION_NAME_PREFIX.len(),
"an empty name is what WSLc refuses with WSLC_E_INVALID_SESSION_NAME, got: {name}"
);
}

#[test]
fn minted_session_names_do_not_repeat() {
assert_ne!(resolve_session_name(""), resolve_session_name(""));
}
}
2 changes: 1 addition & 1 deletion src/tools/wxc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ fn log_request(request: &ExecutionRequest, logger: &mut Logger) {
logger,
"Container name: {}",
if request.container_id.is_empty() {
"CLI"
"(assigned by the backend)"
} else {
&request.container_id
}
Expand Down
14 changes: 14 additions & 0 deletions tests/configs/wslc_no_container_id.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "../../schemas/stable/mxc-config.schema.1.0.0.json",
"version": "1.0.0",
"containment": "wslc",
"process": {
"commandLine": "sh -c 'echo NO_CONTAINER_ID_OK'"
},
"network": {
"egress": { "default": "deny" }
},
"wslc": {
"image": "alpine:latest"
}
}
5 changes: 5 additions & 0 deletions tests/scripts/run_wslc_all_tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ $null = $results.Add((Run-WslcTest "wslc_exit_code.json" -ExpectedExit 42 -Outpu
$null = $results.Add((Run-WslcTest "wslc_stderr.json" -OutputContains "stdout message"))
$null = $results.Add((Run-WslcTest "wslc_large_output.json"))

# `containerId` is optional, and WSLc refuses the empty session name that an
# omitted one used to produce (WSLC_E_INVALID_SESSION_NAME, 0x80040608). Every
# other config here sets the field, so this is the only guard on that path.
$null = $results.Add((Run-WslcTest "wslc_no_container_id.json" -OutputContains "NO_CONTAINER_ID_OK"))

Write-Host "`n--- Environment Tests ---" -ForegroundColor Cyan
# PYTHON_VERSION is baked into the image by ENV. Unlike PATH and TERM, no shell
# fabricates it when it starts without one, so its absence is real evidence that
Expand Down
Loading