mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-03 02:24:33 +00:00
The `daytona` provider kind now maps onto the sandbox-driver Daytona provider instead of fabro's own SDK client. Fabro keeps what is its own: the HMAC-named snapshot built from the environment's image or Dockerfile, the explicit 120 minute auto-stop default, the managed labels that gate destructive operations, the clone decision and layout, and push credentials. The driver creates the sandbox, clones natively, and serves exec, files, search, terminal, SSH, preview, and VNC through its facets. - `daytona.rs` builds the driver `SandboxSpec` (snapshot source, `/home/daytona/workspace`, labels, timers, network policy, run name), ensures the snapshot through the driver `SnapshotProvider`, attaches by persisted id with fabro's label guard, and probes credentials through the provider health check under fabro's 20 second budget. - `DriverSandbox` gains a create plan that settles the spec right before the provider call, records the snapshot a sandbox came from, and reports the provider console URL on `Ready`. - Terminals use the driver `Pty` facet; the server's SSH, preview, and VNC endpoints use the `SshAccess`, `PreviewUrls`, and `Vnc` facets through the driver-typed reconnect. The preview endpoint now answers for every provider with a preview facet, so the local sandbox returns its loopback URL. - Daytona credentials travel as `DaytonaCredentials` built from the vault key plus configured URL and organization; nothing reads the process environment implicitly. The inventory registry uses the shared `DriverInventoryProvider`. - The SDK-based `daytona/mod.rs`, `provider/daytona.rs`, the Daytona terminal, the `daytona` cargo feature, and the direct daytona-sdk, git2 (in fabro-sandbox), tungstenite, and rustls dependencies are gone. The live Daytona tests run against the driver-backed sandbox. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
122 lines
4 KiB
Rust
122 lines
4 KiB
Rust
use fabro_test::{fabro_snapshot, test_context};
|
|
use httpmock::MockServer;
|
|
use serde_json::{Value, json};
|
|
|
|
use super::support::{remote_run_summary_json, setup_local_sandbox_run};
|
|
use crate::support::unique_run_id;
|
|
|
|
fn remote_run_summary(run_id: &str) -> serde_json::Value {
|
|
remote_run_summary_json(
|
|
run_id,
|
|
"Preview Test",
|
|
"preview-test",
|
|
"Preview test",
|
|
&json!({
|
|
"kind": "running"
|
|
}),
|
|
"2026-04-19T12:00:00Z",
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn help() {
|
|
let context = test_context!();
|
|
let mut cmd = context.command();
|
|
cmd.args(["sandbox", "preview", "--help"]);
|
|
fabro_snapshot!(context.filters(), cmd, @"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
Get a preview URL for a port on a run's sandbox
|
|
|
|
Usage: fabro sandbox preview [OPTIONS] <RUN> <PORT>
|
|
|
|
Arguments:
|
|
<RUN> Run ID or prefix
|
|
<PORT> Port number
|
|
|
|
Options:
|
|
--json Output as JSON [env: FABRO_JSON=]
|
|
--server <SERVER> Fabro server target: http(s) URL or absolute Unix socket path [env: FABRO_SERVER=]
|
|
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
|
|
--signed Generate a signed URL (embeds auth token, no headers needed)
|
|
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
|
|
--ttl <TTL> Signed URL expiry in seconds (default 3600, requires --signed) [default: 3600]
|
|
--open Open URL in browser (implies --signed)
|
|
--quiet Suppress non-essential output [env: FABRO_QUIET=]
|
|
--verbose Enable verbose output [env: FABRO_VERBOSE=]
|
|
-h, --help Print help
|
|
----- stderr -----
|
|
");
|
|
}
|
|
|
|
/// Preview URLs come from whichever provider facet the run's sandbox
|
|
/// exposes. The local provider runs on the server host, so its preview is
|
|
/// the loopback address for the port.
|
|
#[test]
|
|
fn sandbox_preview_uses_the_local_provider_loopback_url() {
|
|
let context = test_context!();
|
|
let setup = setup_local_sandbox_run(&context);
|
|
let mut cmd = context.preview();
|
|
cmd.args([&setup.run.run_id, "3000"]);
|
|
|
|
fabro_snapshot!(context.filters(), cmd, @"
|
|
success: true
|
|
exit_code: 0
|
|
----- stdout -----
|
|
http://127.0.0.1:3000
|
|
----- stderr -----
|
|
");
|
|
}
|
|
|
|
#[test]
|
|
fn sandbox_preview_open_is_suppressed_by_json_output_format_from_home_config() {
|
|
let context = test_context!();
|
|
context.write_home(
|
|
".fabro/settings.toml",
|
|
"_version = 1\n\n[cli.output]\nformat = \"json\"\n",
|
|
);
|
|
let server = MockServer::start();
|
|
let run_id = unique_run_id();
|
|
let resolve_run = server.mock(|when, then| {
|
|
when.method("GET")
|
|
.path("/api/v1/runs/resolve")
|
|
.query_param("selector", run_id.as_str());
|
|
then.status(200)
|
|
.header("content-type", "application/json")
|
|
.body(remote_run_summary(&run_id).to_string());
|
|
});
|
|
let preview = server.mock(|when, then| {
|
|
when.method("POST")
|
|
.path(format!("/api/v1/runs/{run_id}/preview"))
|
|
.json_body(json!({
|
|
"port": 3000,
|
|
"expires_in_secs": 3600,
|
|
"signed": true,
|
|
}));
|
|
then.status(201)
|
|
.header("content-type", "application/json")
|
|
.body(json!({ "url": "https://preview.example.test/app" }).to_string());
|
|
});
|
|
|
|
let mut cmd = context.preview();
|
|
cmd.args([
|
|
"--server",
|
|
&format!("{}/api/v1", server.base_url()),
|
|
"--open",
|
|
run_id.as_str(),
|
|
"3000",
|
|
]);
|
|
let output = cmd.output().expect("command should run");
|
|
|
|
assert!(output.status.success(), "sandbox preview should succeed");
|
|
let value: Value = serde_json::from_slice(&output.stdout).expect("preview JSON should parse");
|
|
assert_eq!(
|
|
value,
|
|
json!({
|
|
"url": "https://preview.example.test/app"
|
|
})
|
|
);
|
|
resolve_run.assert();
|
|
preview.assert();
|
|
}
|