Commit af58790
authored
feat(sdk): add sparse sandbox configuration patches (#1303)
## TL;DR
Adds SDK-owned, sparse sandbox configuration patches and explicit YAML
adapters for `msb run`, `msb create`, and `msb install`. Root and scoped
config flags are repeatable, compose in exact command-line order, and
remain lower precedence than explicit CLI arguments. Configuration is
never auto-discovered. Closes #1292.
## Description
- Add public Rust SDK patch types for images, resources, runtime, init,
filesystem, network, secrets, and scripts.
- Add `SandboxBuilder::configure(SandboxConfigPatch)` and generic
`SandboxConfigPatch::overlay`, with direct conversions from every scoped
patch.
- Keep patches sparse and defer required-field validation until the
final sandbox build, allowing separate sources to complete nested
values.
- Use right-hand replacement for scalars and lists and recursive merge
for maps, including secret entries and other named objects.
- Add repeatable `--conf`, `--net-conf`, `--resource-conf`,
`--runtime-conf`, `--fs-conf`, `--secret-conf`, and `--script-conf` CLI
file adapters over the SDK patch model.
- Preserve one exact left-to-right overlay stream across repeated and
interleaved config flag kinds, including in generated `msb install`
aliases.
- Apply built-in defaults, ordered config sources, and explicit CLI
inputs in documented precedence order while preserving CLI
network-profile behavior.
- Parse YAML strictly, resolve contributing relative paths against their
source files, and support only `${ENV}` interpolation.
- Defer config-provided scripts until the final shell is known so a
later CLI shell override selects the generated shebang.
- Document the complete schema, SDK API, scoped workflow, precedence
rules, and the requirement to pass every config path explicitly.
- Exclude sharing, templates, loops or fleets, cross-layer subtree
copying, and expression syntax beyond `${ENV}`.
## Examples
### Root configuration
```yaml
# sandbox.yaml
image: "python:3.12"
cpus: 2
memory: "1G"
workdir: "/app"
env:
MODE: "production"
mounts:
- "./src:/app"
network:
policy: public
allow:
- "api.openai.com"
scripts:
start: "python app.py"
```
```bash
msb run --conf sandbox.yaml -- start
msb create --conf sandbox.yaml --name agent
msb install --conf sandbox.yaml --name agent
```
### Repeated and interleaved config sources
```yaml
# base.yaml
image: "python:3.12"
memory: "1G"
env:
MODE: "base"
```
```yaml
# standard.yaml (--resource-conf)
cpus: 2
memory: "2G"
```
```yaml
# project.yaml
env:
MODE: "project"
REGION: "us-east-1"
```
```yaml
# large.yaml (--resource-conf)
cpus: 4
memory: "4G"
```
```bash
# Files overlay left to right. The explicit --memory value wins last.
msb run \
--conf base.yaml \
--resource-conf standard.yaml \
--conf project.yaml \
--resource-conf large.yaml \
--memory 8G
```
### Scoped network configuration
```yaml
# net-policy.yaml
policy: public
allow:
- "api.openai.com"
deny:
- "169.254.169.254"
```
```bash
msb run python --net-conf net-policy.yaml
```
### Rust SDK
```rust
use std::collections::BTreeMap;
use microsandbox::{
ResourceConfigPatch, RuntimeConfigPatch, Sandbox, SandboxConfigPatch, SandboxImagePatch,
};
let patch = SandboxConfigPatch::new()
.image(SandboxImagePatch::Image("python:3.12".into()))
.overlay(ResourceConfigPatch::new().cpus(2).memory_mib(1024))
.overlay(RuntimeConfigPatch::new().env(BTreeMap::from([
("MODE".into(), "production".into()),
])));
let sandbox = Sandbox::builder("agent")
.configure(patch)
.memory(2048)
.create()
.await?;
```
## Test Plan
- [x] `cargo fmt --all -- --check`
- [x] `git diff --check`
- [x] `cargo test -p microsandbox -p microsandbox-cli --lib` (848
passed, 3 ignored after rebasing onto `e0c0f9ba`)
- [x] `cargo test -p microsandbox-cli --lib` (299 passed after adding
ordered repeated-config coverage)
- [x] `cargo check -p microsandbox-cli`
- [x] `cargo check -p microsandbox --lib --no-default-features
--features prebuilt`
- [x] `cargo check -p microsandbox-cli --no-default-features --features
prebuilt` (passes with five pre-existing no-network runtime warnings)
- [x] `cargo clippy -p microsandbox -p microsandbox-network -p
microsandbox-cli --lib --bins -- -D warnings -A
clippy::items-after-test-module -A clippy::type-complexity -A
clippy::needless-borrows-for-generic-args -A clippy::cmp-owned`
- [x] `cargo clippy -p microsandbox-cli --lib -- -D warnings`
- [x] `cargo doc -p microsandbox --no-deps`
- [x] Build and codesign `msb` on macOS, verify the hypervisor
entitlement, and run real Apple Hypervisor VMs.
- [x] On macOS, verify root config, all scoped config files,
CLI-over-file precedence, deferred script shell selection, network
allow/deny enforcement, missing-image validation, and rejection of a
wrapped `--net-conf`.
- [x] Run the actual updated `msb install` CLI with `--resource-conf
first --conf base --resource-conf second`; verify validation succeeds
and the generated alias preserves that exact order with absolute paths.
- [x] Clone this PR commit on Ubuntu 26.04 x86_64 with usable KVM, run
`cargo build -p microsandbox-cli`, and execute the root, scoped,
network, and validation scenarios in real Linux/KVM VMs.
- [x] On Ubuntu 26.04/KVM, run the new repeated/interleaved scenario
from commit `86aa3c66`: `root → resources → root → resources` resolved
the final file value to 1 GiB (`MemTotal: 1045056 kB`); moving the
second root last resolved 768 MiB (`MemTotal: 787008 kB`); adding
explicit `--memory 896M` won over the complete file stream (`MemTotal:
916032 kB`). All guests also observed the later root map value
`ORDER_TEST=project`, and the isolated home was empty after all three
ephemeral runs.
- [ ] Unmodified workspace-wide strict Clippy remains blocked by
pre-existing warnings outside this diff; the touched SDK, network, and
CLI targets pass the focused strict command above.1 parent e0c0f9b commit af58790
20 files changed
Lines changed: 4843 additions & 103 deletions
File tree
- crates
- cli
- lib
- commands
- network/lib
- docs
- cli
- sdk/rust/lib
- sandbox
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
121 | 121 | | |
122 | 122 | | |
123 | 123 | | |
| 124 | + | |
124 | 125 | | |
125 | 126 | | |
126 | 127 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | 62 | | |
67 | 63 | | |
68 | 64 | | |
| |||
325 | 321 | | |
326 | 322 | | |
327 | 323 | | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
328 | 349 | | |
329 | 350 | | |
330 | 351 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| 61 | + | |
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
| |||
0 commit comments