mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
feat: add server sandbox provider enablement policy (#389)
Operators can now disable individual sandbox providers at the server
level via `[server.sandbox.providers.<provider>]` in `settings.toml`,
without breaking existing deployments that omit the section entirely.
## What changed
**Config layer & resolution** (`fabro-config`, `fabro-types`): new
sparse `ServerSandboxLayer` / `ServerSandboxProvidersLayer` /
`ServerSandboxProviderLayer` structs with `deny_unknown_fields` parse
validation. Resolution defaults every missing level to `enabled = true`.
The resolved `ServerSandboxSettings` / `ServerSandboxProvidersSettings`
/ `ServerSandboxProviderSettings` types live in `fabro-types` and are
shared by all consumers.
**Policy enforcement** (`fabro-server`): three check points enforce the
effective provider (after dry-run Local coercion):
1. `POST /api/v1/runs` — 400 at admission.
2. `POST /api/v1/runs/preflight` — `ok: false` with a `Sandbox Provider
Policy` error check.
3. Launch (`execute_run_in_process` / `execute_run_subprocess`) —
fail-before-execution with a `LaunchFailed` reason.
The dry-run coercion logic was extracted into
`SandboxProvider::effective_for(mode)` on the type itself and reused
across `fabro-server` and `fabro-workflow`.
**Installer** (`fabro-install`): `write_sandbox_settings` now always
writes all three provider policy tables with `enabled = true`, so
generated `settings.toml` files are self-documenting.
**API schema & clients**: `ServerNamespace` gains a required `sandbox`
field in the OpenAPI spec; three new TypeScript model files were
regenerated accordingly.
### Plan Summary
- Task 1: config layer structs → resolved types → resolver helpers →
tests
- Task 2: `effective_sandbox_provider` + `sandbox_provider_policy_error`
helpers; admission, preflight, and launch checks + integration tests
- Task 3: installer writes all three provider entries; install finish
tests updated
- Task 4: OpenAPI schema, `fabro-api` build mappings, TS client
regeneration, docs
### Fabro Details
<details>
<summary>Ran 8 stages in 59m 15s for $45.70</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 5s | – | 0 |
| preflight_lint | 2m 21s | – | 0 |
| implement | 27m 55s | $38.88 | 0 |
| simplify_opus | 14m 20s | $4.93 | 0 |
| simplify_gpt | 3m 14s | $1.88 | 0 |
| verify | 8m 49s | – | 0 |
| **Total** | **59m 15s** | **$45.70** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
edges)</summary>
```dot
digraph ImplementPlan {
graph [
goal="Implement and simplify",
model_stylesheet="
* { model: claude-opus-4-7; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.", model="gpt-55", reasoning_effort="xhigh"]
simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
simplify_gpt [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
verify [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && { command -v rg >/dev/null 2>&1 || { echo 'rg is required for verify'; exit 127; }; } && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=succeeded"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=succeeded"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=succeeded"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify_opus -> simplify_gpt -> verify
verify -> exit [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
This commit is contained in:
parent
7d655d7c95
commit
2c0416e1c5
27 changed files with 593 additions and 26 deletions
|
|
@ -7,6 +7,11 @@ Sandboxes isolate agent execution from the host machine. When an agent runs a sh
|
|||
|
||||
Fabro supports three sandbox providers: `local` (no isolation), `docker` (container-level), and `daytona` (cloud VM). See [Environments](/execution/environments) for full provider-specific configuration.
|
||||
|
||||
Operators can enable or disable which providers the server may launch with
|
||||
`[server.sandbox.providers.<provider>]` in `settings.toml`. Missing entries default to
|
||||
`enabled = true`; setting `enabled = false` rejects new runs whose effective provider is disabled.
|
||||
Dry-run Docker/Daytona runs execute locally, so they are governed by the `local` provider policy.
|
||||
|
||||
## Network access control
|
||||
|
||||
For cloud sandboxes (Daytona), you can control outbound network access with `[environments.<slug>.network]`. Three modes are available: `"allow_all"` (default), `"block"`, and `"cidr_allow_list"` with an `allow = ["..."]` CIDR list.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Fabro only reads `settings.toml`. Older `server.toml`, `user.toml`, and `cli.tom
|
|||
|
||||
| Scope | Examples |
|
||||
|---|---|
|
||||
| Server-owned (runtime-only from local `settings.toml`) | `[server.listen]`, `[server.api]`, `[server.web]`, `[server.auth]`, `[server.storage]`, `[server.artifacts]`, `[server.slatedb]`, `[server.scheduler]`, `[server.logging]`, `[server.integrations]` |
|
||||
| Server-owned (runtime-only from local `settings.toml`) | `[server.listen]`, `[server.api]`, `[server.web]`, `[server.auth]`, `[server.sandbox]`, `[server.storage]`, `[server.artifacts]`, `[server.slatedb]`, `[server.scheduler]`, `[server.logging]`, `[server.integrations]` |
|
||||
| Shared run defaults (layered through `.fabro/project.toml`/`workflow.toml`) | `[run.model]`, `[run.prepare]`, `[run.environment]`, `[environments.<slug>]`, `[run.checkpoint]`, `[run.inputs]`, `[run.pull_request]`, `[run.git]`, `[run.hooks]`, `[run.agent]` |
|
||||
|
||||
The CLI-only `[cli.*]` sections (including `[cli.target]`) belong in the client machine's `settings.toml`. They tell CLI commands how to reach a server. The server process does not read `[cli.*]` for its own binding or routing.
|
||||
|
|
@ -46,6 +46,15 @@ methods = ["dev-token", "github"]
|
|||
[server.auth.github]
|
||||
allowed_usernames = ["alice", "bob"]
|
||||
|
||||
[server.sandbox.providers.local]
|
||||
enabled = true
|
||||
|
||||
[server.sandbox.providers.docker]
|
||||
enabled = true
|
||||
|
||||
[server.sandbox.providers.daytona]
|
||||
enabled = true
|
||||
|
||||
[server.integrations.github]
|
||||
app_id = "123456"
|
||||
client_id = "Iv1.abc123"
|
||||
|
|
@ -165,6 +174,24 @@ GitHub-specific auth policy.
|
|||
|
||||
The GitHub OAuth client ID still lives under `[server.integrations.github].client_id`.
|
||||
|
||||
### `[server.sandbox.providers]` section
|
||||
|
||||
Controls which sandbox providers the server may launch. Missing provider entries default to
|
||||
`enabled = true` for backward compatibility. Disabling a provider rejects new runs whose effective
|
||||
provider is disabled; dry-run Docker/Daytona runs use the local provider and are governed by
|
||||
`server.sandbox.providers.local.enabled`.
|
||||
|
||||
```toml title="settings.toml"
|
||||
[server.sandbox.providers.local]
|
||||
enabled = true
|
||||
|
||||
[server.sandbox.providers.docker]
|
||||
enabled = true
|
||||
|
||||
[server.sandbox.providers.daytona]
|
||||
enabled = true
|
||||
```
|
||||
|
||||
### `[server.slatedb]` section
|
||||
|
||||
Configure the embedded SlateDB key-value store used for run event storage.
|
||||
|
|
|
|||
|
|
@ -11326,6 +11326,7 @@ components:
|
|||
- web
|
||||
- auth
|
||||
- ip_allowlist
|
||||
- sandbox
|
||||
- storage
|
||||
- artifacts
|
||||
- slatedb
|
||||
|
|
@ -11343,6 +11344,8 @@ components:
|
|||
$ref: "#/components/schemas/ServerAuthSettings"
|
||||
ip_allowlist:
|
||||
$ref: "#/components/schemas/ServerIpAllowlistSettings"
|
||||
sandbox:
|
||||
$ref: "#/components/schemas/ServerSandboxSettings"
|
||||
storage:
|
||||
$ref: "#/components/schemas/ServerStorageSettings"
|
||||
artifacts:
|
||||
|
|
@ -11459,6 +11462,31 @@ components:
|
|||
type: string
|
||||
enum: [GitHubMetaHooks]
|
||||
|
||||
ServerSandboxSettings:
|
||||
type: object
|
||||
required: [providers]
|
||||
properties:
|
||||
providers:
|
||||
$ref: "#/components/schemas/ServerSandboxProvidersSettings"
|
||||
|
||||
ServerSandboxProvidersSettings:
|
||||
type: object
|
||||
required: [local, docker, daytona]
|
||||
properties:
|
||||
local:
|
||||
$ref: "#/components/schemas/ServerSandboxProviderSettings"
|
||||
docker:
|
||||
$ref: "#/components/schemas/ServerSandboxProviderSettings"
|
||||
daytona:
|
||||
$ref: "#/components/schemas/ServerSandboxProviderSettings"
|
||||
|
||||
ServerSandboxProviderSettings:
|
||||
type: object
|
||||
required: [enabled]
|
||||
properties:
|
||||
enabled:
|
||||
type: boolean
|
||||
|
||||
ServerStorageSettings:
|
||||
type: object
|
||||
required: [root]
|
||||
|
|
|
|||
|
|
@ -259,6 +259,21 @@ fn main() {
|
|||
"fabro_types::settings::server::IpAllowEntry",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerSandboxSettings",
|
||||
"fabro_types::settings::server::ServerSandboxSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerSandboxProvidersSettings",
|
||||
"fabro_types::settings::server::ServerSandboxProvidersSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerSandboxProviderSettings",
|
||||
"fabro_types::settings::server::ServerSandboxProviderSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerStorageSettings",
|
||||
"fabro_types::settings::server::ServerStorageSettings",
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ pub mod types {
|
|||
IpAllowEntry, LogDestination, ObjectStoreSettings, ServerApiSettings,
|
||||
ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings,
|
||||
ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, ServerIpAllowlistSettings,
|
||||
ServerListenSettings, ServerLoggingSettings, ServerSchedulerSettings,
|
||||
ServerListenSettings, ServerLoggingSettings, ServerSandboxProviderSettings,
|
||||
ServerSandboxProvidersSettings, ServerSandboxSettings, ServerSchedulerSettings,
|
||||
ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings,
|
||||
WebhookStrategy,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,12 +2,18 @@ use std::any::{TypeId, type_name};
|
|||
|
||||
use fabro_api::types::{
|
||||
LogDestination as ApiLogDestination, ObjectStoreSettings as ApiObjectStoreSettings,
|
||||
ServerNamespace as ApiServerNamespace, ServerSettings as ApiServerSettings,
|
||||
ServerNamespace as ApiServerNamespace,
|
||||
ServerSandboxProviderSettings as ApiServerSandboxProviderSettings,
|
||||
ServerSandboxProvidersSettings as ApiServerSandboxProvidersSettings,
|
||||
ServerSandboxSettings as ApiServerSandboxSettings, ServerSettings as ApiServerSettings,
|
||||
};
|
||||
use fabro_config::ServerSettingsBuilder;
|
||||
use fabro_types::ServerSettings;
|
||||
use fabro_types::settings::ServerNamespace;
|
||||
use fabro_types::settings::server::{LogDestination, ObjectStoreSettings};
|
||||
use fabro_types::settings::server::{
|
||||
LogDestination, ObjectStoreSettings, ServerSandboxProviderSettings,
|
||||
ServerSandboxProvidersSettings, ServerSandboxSettings,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn server_settings_family_reuses_domain_types() {
|
||||
|
|
@ -15,6 +21,9 @@ fn server_settings_family_reuses_domain_types() {
|
|||
assert_same_type::<ApiServerNamespace, ServerNamespace>();
|
||||
assert_same_type::<ApiObjectStoreSettings, ObjectStoreSettings>();
|
||||
assert_same_type::<ApiLogDestination, LogDestination>();
|
||||
assert_same_type::<ApiServerSandboxSettings, ServerSandboxSettings>();
|
||||
assert_same_type::<ApiServerSandboxProvidersSettings, ServerSandboxProvidersSettings>();
|
||||
assert_same_type::<ApiServerSandboxProviderSettings, ServerSandboxProviderSettings>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -40,6 +49,9 @@ methods = ["dev-token", "github"]
|
|||
[server.auth.github]
|
||||
allowed_usernames = ["alice"]
|
||||
|
||||
[server.sandbox.providers.daytona]
|
||||
enabled = false
|
||||
|
||||
[server.storage]
|
||||
root = "/srv/fabro"
|
||||
|
||||
|
|
@ -61,6 +73,18 @@ slug = "fabro-dev"
|
|||
assert_eq!(json["server"]["listen"]["address"], "127.0.0.1:32276");
|
||||
assert_eq!(json["server"]["storage"]["root"], "/srv/fabro");
|
||||
assert_eq!(json["server"]["logging"]["destination"], "stdout");
|
||||
assert_eq!(
|
||||
json["server"]["sandbox"]["providers"]["local"]["enabled"],
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
json["server"]["sandbox"]["providers"]["docker"]["enabled"],
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
json["server"]["sandbox"]["providers"]["daytona"]["enabled"],
|
||||
false
|
||||
);
|
||||
assert!(json.get("features").is_none());
|
||||
|
||||
let round_trip: ApiServerSettings =
|
||||
|
|
|
|||
|
|
@ -42,8 +42,9 @@ pub use server::{
|
|||
GithubIntegrationLayer, IntegrationWebhooksLayer, ObjectStoreLocalLayer, ObjectStoreS3Layer,
|
||||
ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer,
|
||||
ServerIntegrationsLayer, ServerIpAllowlistLayer, ServerIpAllowlistOverrideLayer, ServerLayer,
|
||||
ServerListenLayer, ServerLoggingLayer, ServerSchedulerLayer, ServerSlateDbLayer,
|
||||
ServerStorageLayer, ServerWebLayer, SlackIntegrationLayer,
|
||||
ServerListenLayer, ServerLoggingLayer, ServerSandboxLayer, ServerSandboxProviderLayer,
|
||||
ServerSandboxProvidersLayer, ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer,
|
||||
ServerWebLayer, SlackIntegrationLayer,
|
||||
};
|
||||
pub use settings::SettingsLayer;
|
||||
pub use workflow::WorkflowLayer;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ pub struct ServerLayer {
|
|||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub ip_allowlist: Option<ServerIpAllowlistLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub sandbox: Option<ServerSandboxLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub storage: Option<ServerStorageLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub artifacts: Option<ServerArtifactsLayer>,
|
||||
|
|
@ -109,6 +111,32 @@ pub struct ServerIpAllowlistOverrideLayer {
|
|||
pub trusted_proxy_count: Option<u32>,
|
||||
}
|
||||
|
||||
/// `[server.sandbox]` — server-owned sandbox provider policy.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ServerSandboxLayer {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub providers: Option<ServerSandboxProvidersLayer>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ServerSandboxProvidersLayer {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub local: Option<ServerSandboxProviderLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub docker: Option<ServerSandboxProviderLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub daytona: Option<ServerSandboxProviderLayer>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ServerSandboxProviderLayer {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
/// `[server.storage]` — single managed local disk root.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ pub use layers::{
|
|||
RunRunBranchLayer, RunScmLayer, ScmGitHubLayer, ServerApiLayer, ServerArtifactsLayer,
|
||||
ServerAuthGithubLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerIpAllowlistLayer,
|
||||
ServerIpAllowlistOverrideLayer, ServerLayer, ServerListenLayer, ServerLoggingLayer,
|
||||
ServerSandboxLayer, ServerSandboxProviderLayer, ServerSandboxProvidersLayer,
|
||||
ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer, SettingsLayer,
|
||||
SlackIntegrationLayer, StickyMap, StringOrSplice, WorkflowLayer,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ use fabro_types::settings::server::{
|
|||
IpAllowEntry, ObjectStoreProvider, ObjectStoreSettings, ServerApiSettings,
|
||||
ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings,
|
||||
ServerIntegrationsSettings, ServerIpAllowlistOverrideSettings, ServerIpAllowlistSettings,
|
||||
ServerListenSettings, ServerLoggingSettings, ServerNamespace, ServerSchedulerSettings,
|
||||
ServerListenSettings, ServerLoggingSettings, ServerNamespace, ServerSandboxProviderSettings,
|
||||
ServerSandboxProvidersSettings, ServerSandboxSettings, ServerSchedulerSettings,
|
||||
ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings,
|
||||
WebhookStrategy,
|
||||
};
|
||||
|
|
@ -15,8 +16,8 @@ use crate::user::default_storage_dir;
|
|||
use crate::{
|
||||
IntegrationWebhooksLayer, ObjectStoreLocalLayer, ObjectStoreS3Layer, ServerApiLayer,
|
||||
ServerArtifactsLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerIpAllowlistLayer,
|
||||
ServerIpAllowlistOverrideLayer, ServerLayer, ServerListenLayer, ServerSlateDbLayer,
|
||||
ServerStorageLayer, ServerWebLayer,
|
||||
ServerIpAllowlistOverrideLayer, ServerLayer, ServerListenLayer, ServerSandboxLayer,
|
||||
ServerSandboxProviderLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer,
|
||||
};
|
||||
|
||||
pub fn resolve_server(layer: &ServerLayer, errors: &mut Vec<ResolveError>) -> ServerNamespace {
|
||||
|
|
@ -38,6 +39,7 @@ pub fn resolve_server(layer: &ServerLayer, errors: &mut Vec<ResolveError>) -> Se
|
|||
web,
|
||||
auth,
|
||||
ip_allowlist,
|
||||
sandbox: resolve_sandbox(layer.sandbox.as_ref()),
|
||||
storage: storage.clone(),
|
||||
artifacts: resolve_artifacts(layer.artifacts.as_ref(), &storage.root, errors),
|
||||
slatedb: resolve_slatedb(layer.slatedb.as_ref(), &storage.root, errors),
|
||||
|
|
@ -64,6 +66,31 @@ pub fn resolve_server(layer: &ServerLayer, errors: &mut Vec<ResolveError>) -> Se
|
|||
}
|
||||
}
|
||||
|
||||
fn resolve_sandbox(layer: Option<&ServerSandboxLayer>) -> ServerSandboxSettings {
|
||||
let providers = layer.and_then(|sandbox| sandbox.providers.as_ref());
|
||||
ServerSandboxSettings {
|
||||
providers: ServerSandboxProvidersSettings {
|
||||
local: resolve_sandbox_provider(
|
||||
providers.and_then(|providers| providers.local.as_ref()),
|
||||
),
|
||||
docker: resolve_sandbox_provider(
|
||||
providers.and_then(|providers| providers.docker.as_ref()),
|
||||
),
|
||||
daytona: resolve_sandbox_provider(
|
||||
providers.and_then(|providers| providers.daytona.as_ref()),
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_sandbox_provider(
|
||||
layer: Option<&ServerSandboxProviderLayer>,
|
||||
) -> ServerSandboxProviderSettings {
|
||||
ServerSandboxProviderSettings {
|
||||
enabled: layer.and_then(|provider| provider.enabled).unwrap_or(true),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_storage(layer: Option<&ServerStorageLayer>) -> ServerStorageSettings {
|
||||
ServerStorageSettings {
|
||||
root: layer
|
||||
|
|
|
|||
|
|
@ -135,6 +135,66 @@ fn resolved_server_integrations_are_slack_only_for_chat() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_sandbox_defaults_all_providers_enabled() {
|
||||
let settings = ServerSettingsBuilder::from_toml(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[server.auth]
|
||||
methods = ["dev-token"]
|
||||
"#,
|
||||
)
|
||||
.expect("server settings should resolve");
|
||||
|
||||
let sandbox = settings.server.sandbox;
|
||||
assert!(sandbox.providers.local.enabled);
|
||||
assert!(sandbox.providers.docker.enabled);
|
||||
assert!(sandbox.providers.daytona.enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_sandbox_allows_partial_provider_overrides() {
|
||||
let settings = ServerSettingsBuilder::from_toml(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[server.auth]
|
||||
methods = ["dev-token"]
|
||||
|
||||
[server.sandbox.providers.daytona]
|
||||
enabled = false
|
||||
"#,
|
||||
)
|
||||
.expect("server settings should resolve");
|
||||
|
||||
let sandbox = settings.server.sandbox;
|
||||
assert!(sandbox.providers.local.enabled);
|
||||
assert!(sandbox.providers.docker.enabled);
|
||||
assert!(!sandbox.providers.daytona.enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_rejects_unknown_server_sandbox_provider() {
|
||||
let err = ServerSettingsBuilder::from_toml(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[server.auth]
|
||||
methods = ["dev-token"]
|
||||
|
||||
[server.sandbox.providers.exe]
|
||||
enabled = true
|
||||
"#,
|
||||
)
|
||||
.expect_err("unknown sandbox provider should be rejected");
|
||||
|
||||
assert!(
|
||||
err.to_string().contains("unknown field `exe`"),
|
||||
"unexpected error: {err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_rejects_unknown_server_integrations() {
|
||||
let source = r"
|
||||
|
|
|
|||
|
|
@ -442,6 +442,21 @@ pub fn write_object_store_settings(
|
|||
}
|
||||
}
|
||||
|
||||
fn write_sandbox_provider_policy(server: &mut toml::Table) -> Result<()> {
|
||||
use fabro_types::SandboxProvider;
|
||||
let sandbox = ensure_table(server, "sandbox")?;
|
||||
let providers = ensure_table(sandbox, "providers")?;
|
||||
for provider in [
|
||||
SandboxProvider::Local,
|
||||
SandboxProvider::Docker,
|
||||
SandboxProvider::Daytona,
|
||||
] {
|
||||
let entry = ensure_table(providers, &provider.to_string())?;
|
||||
entry.insert("enabled".to_string(), toml::Value::Boolean(true));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_sandbox_settings(
|
||||
doc: &mut toml::Value,
|
||||
selection: InstallSandboxSelection,
|
||||
|
|
@ -461,6 +476,8 @@ pub fn write_sandbox_settings(
|
|||
"provider".to_string(),
|
||||
toml::Value::String(provider.to_string()),
|
||||
);
|
||||
let server = ensure_table(root, "server")?;
|
||||
write_sandbox_provider_policy(server)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -1407,6 +1424,9 @@ stale = "remove-me"
|
|||
.and_then(toml::Value::as_str),
|
||||
Some("docker")
|
||||
);
|
||||
assert_eq!(sandbox_provider_enabled(&doc, "local"), Some(true));
|
||||
assert_eq!(sandbox_provider_enabled(&doc, "docker"), Some(true));
|
||||
assert_eq!(sandbox_provider_enabled(&doc, "daytona"), Some(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1433,6 +1453,22 @@ stale = "remove-me"
|
|||
.and_then(toml::Value::as_str),
|
||||
Some("daytona")
|
||||
);
|
||||
assert_eq!(sandbox_provider_enabled(&doc, "local"), Some(true));
|
||||
assert_eq!(sandbox_provider_enabled(&doc, "docker"), Some(true));
|
||||
assert_eq!(sandbox_provider_enabled(&doc, "daytona"), Some(true));
|
||||
}
|
||||
|
||||
fn sandbox_provider_enabled(doc: &toml::Value, provider: &str) -> Option<bool> {
|
||||
doc.get("server")
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|server| server.get("sandbox"))
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|sandbox| sandbox.get("providers"))
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|providers| providers.get(provider))
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|provider| provider.get("enabled"))
|
||||
.and_then(toml::Value::as_bool)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ use fabro_sandbox::{DockerSandboxOptions, Sandbox, SandboxProvider, SandboxSpec}
|
|||
use fabro_static::EnvVars;
|
||||
use fabro_types::settings::cli::OutputVerbosity;
|
||||
use fabro_types::settings::interp::InterpString;
|
||||
use fabro_types::settings::run::{EnvironmentProvider, RunGoal, RunMode, RunNamespace};
|
||||
use fabro_types::{ManifestPath, RunId, WorkflowSettings};
|
||||
use fabro_types::settings::run::{EnvironmentProvider, RunGoal, RunNamespace};
|
||||
use fabro_types::{ManifestPath, RunId, ServerSettings, WorkflowSettings};
|
||||
use fabro_util::check_report::{CheckDetail, CheckReport, CheckResult, CheckSection, CheckStatus};
|
||||
use fabro_validate::Severity;
|
||||
use fabro_workflow::Error as WorkflowError;
|
||||
|
|
@ -502,13 +502,26 @@ async fn build_preflight_report(
|
|||
let resolved_run = materialized.run;
|
||||
let server_settings = state.server_settings();
|
||||
let github_integration = &server_settings.server.integrations.github;
|
||||
let sandbox_provider = resolve_sandbox_provider(&resolved_run);
|
||||
let sandbox_provider =
|
||||
if resolved_run.execution.mode == RunMode::DryRun && !sandbox_provider.is_local() {
|
||||
SandboxProvider::Local
|
||||
} else {
|
||||
sandbox_provider
|
||||
};
|
||||
let sandbox_provider = effective_sandbox_provider(&resolved_run);
|
||||
if let Some(error) = sandbox_provider_policy_error(&server_settings, sandbox_provider) {
|
||||
checks.push(CheckResult {
|
||||
name: "Sandbox Provider Policy".into(),
|
||||
status: CheckStatus::Error,
|
||||
summary: error,
|
||||
details: Vec::new(),
|
||||
remediation: None,
|
||||
});
|
||||
return Ok((
|
||||
CheckReport {
|
||||
title: "Run Preflight".into(),
|
||||
sections: vec![CheckSection {
|
||||
title: String::new(),
|
||||
checks,
|
||||
}],
|
||||
},
|
||||
false,
|
||||
));
|
||||
}
|
||||
run_environment_capability_check(&mut checks, &resolved_run);
|
||||
let needs_github_credentials =
|
||||
sandbox_provider.is_clone_based() || resolved_run.integrations.github.is_token_requested();
|
||||
|
|
@ -617,8 +630,25 @@ fn base_preflight_checks(prepared: &PreparedManifest, graph: &Graph) -> Vec<Chec
|
|||
]
|
||||
}
|
||||
|
||||
fn resolve_sandbox_provider(settings: &RunNamespace) -> SandboxProvider {
|
||||
SandboxProvider::from(settings.environment.provider)
|
||||
pub(crate) fn sandbox_provider_policy_error(
|
||||
server_settings: &ServerSettings,
|
||||
provider: SandboxProvider,
|
||||
) -> Option<String> {
|
||||
let enabled = server_settings
|
||||
.server
|
||||
.sandbox
|
||||
.providers
|
||||
.for_provider(provider)
|
||||
.enabled;
|
||||
(!enabled).then(|| {
|
||||
format!(
|
||||
"sandbox provider \"{provider}\" is disabled by server.sandbox.providers.{provider}.enabled"
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn effective_sandbox_provider(settings: &RunNamespace) -> SandboxProvider {
|
||||
SandboxProvider::from(settings.environment.provider).effective_for(settings.execution.mode)
|
||||
}
|
||||
|
||||
fn resolve_daytona_config(settings: &RunNamespace) -> DaytonaConfig {
|
||||
|
|
|
|||
|
|
@ -2847,6 +2847,23 @@ async fn finish_cancelled_run_before_execution(state: &Arc<AppState>, run_id: Ru
|
|||
state.scheduler_notify.notify_one();
|
||||
}
|
||||
|
||||
/// Reject the run before execution if its effective sandbox provider is
|
||||
/// disabled by server policy. Returns `true` when the run was rejected.
|
||||
async fn reject_run_if_sandbox_provider_disabled(
|
||||
state: &Arc<AppState>,
|
||||
server_settings: &ServerSettings,
|
||||
run_id: RunId,
|
||||
settings: &RunNamespace,
|
||||
) -> bool {
|
||||
let provider = run_manifest::effective_sandbox_provider(settings);
|
||||
let Some(error) = run_manifest::sandbox_provider_policy_error(server_settings, provider) else {
|
||||
return false;
|
||||
};
|
||||
tracing::warn!(run_id = %run_id, error = %error, "Sandbox provider disabled by server policy");
|
||||
fail_run_before_execution(state, run_id, FailureReason::LaunchFailed, error).await;
|
||||
true
|
||||
}
|
||||
|
||||
async fn fail_run_before_execution(
|
||||
state: &Arc<AppState>,
|
||||
run_id: RunId,
|
||||
|
|
@ -3596,6 +3613,16 @@ async fn execute_run_in_process(state: Arc<AppState>, run_id: RunId) {
|
|||
finish_cancelled_run_before_execution(&state, run_id).await;
|
||||
return;
|
||||
}
|
||||
if reject_run_if_sandbox_provider_disabled(
|
||||
&state,
|
||||
&server_settings,
|
||||
run_id,
|
||||
&persisted.run_spec().settings.run,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return;
|
||||
}
|
||||
let github_app_result = {
|
||||
let run_spec = persisted.run_spec();
|
||||
let settings = &run_spec.settings.run;
|
||||
|
|
@ -3820,6 +3847,16 @@ async fn execute_run_subprocess(state: Arc<AppState>, run_id: RunId) {
|
|||
}
|
||||
};
|
||||
let agent_fabro_tools_enabled = run_state.spec.settings.run.agent.fabro_tools;
|
||||
if reject_run_if_sandbox_provider_disabled(
|
||||
&state,
|
||||
&state.server_settings(),
|
||||
run_id,
|
||||
&run_state.spec.settings.run,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let state_for_build = Arc::clone(&state);
|
||||
let run_dir_for_build = run_dir.clone();
|
||||
|
|
|
|||
|
|
@ -606,6 +606,12 @@ async fn create_run(
|
|||
Err(err) => return ApiError::bad_request(err.to_string()).into_response(),
|
||||
};
|
||||
let run_id = prepared.run_id.unwrap_or_else(RunId::new);
|
||||
let provider = run_manifest::effective_sandbox_provider(&prepared.settings.run);
|
||||
if let Some(error) =
|
||||
run_manifest::sandbox_provider_policy_error(&state.server_settings(), provider)
|
||||
{
|
||||
return ApiError::bad_request(error).into_response();
|
||||
}
|
||||
if let Some(parent_id) = prepared.parent_id {
|
||||
if parent_id == run_id {
|
||||
return ApiError::bad_request("A run cannot be its own parent.").into_response();
|
||||
|
|
|
|||
|
|
@ -959,6 +959,29 @@ id = "missing"
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sandbox_provider_policy_error_reports_disabled_provider() {
|
||||
let settings = server_settings_from_toml(
|
||||
r#"
|
||||
_version = 1
|
||||
|
||||
[server.auth]
|
||||
methods = ["dev-token"]
|
||||
|
||||
[server.sandbox.providers.daytona]
|
||||
enabled = false
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
crate::run_manifest::sandbox_provider_policy_error(&settings, SandboxProvider::Daytona)
|
||||
.as_deref(),
|
||||
Some(
|
||||
"sandbox provider \"daytona\" is disabled by server.sandbox.providers.daytona.enabled"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clone_sandbox_credentials_are_available_for_clone_based_providers() {
|
||||
use fabro_types::settings::run::EnvironmentProvider;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,22 @@ fn spa_fixture_root() -> PathBuf {
|
|||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/spa")
|
||||
}
|
||||
|
||||
fn assert_sandbox_provider_policy_enabled(settings: &str) {
|
||||
assert!(settings.contains("[server.sandbox.providers.local]"));
|
||||
assert!(settings.contains("[server.sandbox.providers.docker]"));
|
||||
assert!(settings.contains("[server.sandbox.providers.daytona]"));
|
||||
assert!(settings.contains("enabled = true"));
|
||||
|
||||
let resolved = ServerSettingsBuilder::from_toml(settings)
|
||||
.expect("settings should resolve")
|
||||
.server
|
||||
.sandbox
|
||||
.providers;
|
||||
assert!(resolved.local.enabled);
|
||||
assert!(resolved.docker.enabled);
|
||||
assert!(resolved.daytona.enabled);
|
||||
}
|
||||
|
||||
async fn mock_daytona_auth_probe(server: &MockServer) -> httpmock::Mock<'_> {
|
||||
server
|
||||
.mock_async(|when, then| {
|
||||
|
|
@ -918,6 +934,7 @@ async fn token_install_finish_persists_settings_env_and_vault() {
|
|||
settings.contains("provider = \"docker\""),
|
||||
"settings.toml should record explicit docker sandbox provider"
|
||||
);
|
||||
assert_sandbox_provider_policy_enabled(&settings);
|
||||
let resolved = ServerSettingsBuilder::from_toml(&settings)
|
||||
.expect("settings should resolve")
|
||||
.server;
|
||||
|
|
@ -2677,6 +2694,7 @@ async fn daytona_install_finish_writes_settings_and_vault_secret() {
|
|||
settings.contains("provider = \"daytona\""),
|
||||
"settings.toml should record daytona sandbox provider"
|
||||
);
|
||||
assert_sandbox_provider_policy_enabled(&settings);
|
||||
|
||||
let vault = Vault::load(Storage::new(temp_dir.path()).secrets_path()).unwrap();
|
||||
assert_eq!(vault.get("DAYTONA_API_KEY"), Some(api_key));
|
||||
|
|
|
|||
|
|
@ -52,6 +52,84 @@ async fn request_json(
|
|||
.await
|
||||
}
|
||||
|
||||
fn daytona_manifest() -> serde_json::Value {
|
||||
let mut manifest = minimal_manifest_json(MINIMAL_DOT);
|
||||
manifest["args"] = serde_json::json!({ "environment": "daytona" });
|
||||
manifest
|
||||
}
|
||||
|
||||
fn daytona_disabled_settings() -> crate::helpers::TestAppSettings {
|
||||
settings_from_toml(
|
||||
r"
|
||||
_version = 1
|
||||
|
||||
[server.sandbox.providers.daytona]
|
||||
enabled = false
|
||||
",
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_run_rejects_disabled_sandbox_provider() {
|
||||
let app = fabro_server::test_support::build_test_router(test_app_state_with_options(
|
||||
daytona_disabled_settings(),
|
||||
5,
|
||||
));
|
||||
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri(api("/runs"))
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(daytona_manifest().to_string()))
|
||||
.expect("create run request should build");
|
||||
let body = response_json(
|
||||
app.clone().oneshot(request).await.unwrap(),
|
||||
StatusCode::BAD_REQUEST,
|
||||
"POST /api/v1/runs",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(
|
||||
body["errors"][0]["detail"],
|
||||
"sandbox provider \"daytona\" is disabled by server.sandbox.providers.daytona.enabled"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn preflight_reports_disabled_sandbox_provider() {
|
||||
let app = fabro_server::test_support::build_test_router(test_app_state_with_options(
|
||||
daytona_disabled_settings(),
|
||||
5,
|
||||
));
|
||||
|
||||
let request = Request::builder()
|
||||
.method("POST")
|
||||
.uri(api("/preflight"))
|
||||
.header("content-type", "application/json")
|
||||
.body(Body::from(daytona_manifest().to_string()))
|
||||
.expect("preflight request should build");
|
||||
let body = response_json(
|
||||
app.clone().oneshot(request).await.unwrap(),
|
||||
StatusCode::OK,
|
||||
"POST /api/v1/preflight",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(body["ok"], false);
|
||||
let checks = body["checks"]["sections"][0]["checks"]
|
||||
.as_array()
|
||||
.expect("preflight checks should be an array");
|
||||
let policy_check = checks
|
||||
.iter()
|
||||
.find(|check| check["name"] == "Sandbox Provider Policy")
|
||||
.expect("policy check should be present");
|
||||
assert_eq!(policy_check["status"], "error");
|
||||
assert_eq!(
|
||||
policy_check["summary"],
|
||||
"sandbox provider \"daytona\" is disabled by server.sandbox.providers.daytona.enabled"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn run_responses_include_ask_fabro_affordance() {
|
||||
let settings = settings_from_toml(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use crate::settings::run::RunMode;
|
||||
|
||||
/// Sandbox provider for agent tool operations.
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, Display, EnumString,
|
||||
|
|
@ -31,6 +33,17 @@ impl SandboxProvider {
|
|||
pub fn is_clone_based(&self) -> bool {
|
||||
matches!(self, Self::Docker | Self::Daytona)
|
||||
}
|
||||
|
||||
/// Coerce non-local providers to `Local` under dry-run; otherwise
|
||||
/// unchanged.
|
||||
#[must_use]
|
||||
pub fn effective_for(self, mode: RunMode) -> Self {
|
||||
if mode == RunMode::DryRun && !self.is_local() {
|
||||
Self::Local
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ pub struct ServerNamespace {
|
|||
pub web: ServerWebSettings,
|
||||
pub auth: ServerAuthSettings,
|
||||
pub ip_allowlist: ServerIpAllowlistSettings,
|
||||
pub sandbox: ServerSandboxSettings,
|
||||
pub storage: ServerStorageSettings,
|
||||
pub artifacts: ServerArtifactsSettings,
|
||||
pub slatedb: ServerSlateDbSettings,
|
||||
|
|
@ -50,6 +51,7 @@ impl ServerNamespace {
|
|||
web: ServerWebSettings::default(),
|
||||
auth: ServerAuthSettings::default(),
|
||||
ip_allowlist: ServerIpAllowlistSettings::default(),
|
||||
sandbox: ServerSandboxSettings::default(),
|
||||
storage: ServerStorageSettings::default(),
|
||||
artifacts: ServerArtifactsSettings::default(),
|
||||
slatedb: ServerSlateDbSettings::default(),
|
||||
|
|
@ -133,6 +135,43 @@ pub struct ServerIpAllowlistOverrideSettings {
|
|||
pub trusted_proxy_count: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ServerSandboxSettings {
|
||||
pub providers: ServerSandboxProvidersSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ServerSandboxProvidersSettings {
|
||||
pub local: ServerSandboxProviderSettings,
|
||||
pub docker: ServerSandboxProviderSettings,
|
||||
pub daytona: ServerSandboxProviderSettings,
|
||||
}
|
||||
|
||||
impl ServerSandboxProvidersSettings {
|
||||
/// Per-provider policy entry.
|
||||
#[must_use]
|
||||
pub fn for_provider(&self, provider: crate::SandboxProvider) -> &ServerSandboxProviderSettings {
|
||||
match provider {
|
||||
crate::SandboxProvider::Local => &self.local,
|
||||
crate::SandboxProvider::Docker => &self.docker,
|
||||
crate::SandboxProvider::Daytona => &self.daytona,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ServerSandboxProviderSettings {
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for ServerSandboxProviderSettings {
|
||||
// The resolver defaults each provider to enabled; keep the struct default
|
||||
// aligned with that so callers that bypass the resolver behave identically.
|
||||
fn default() -> Self {
|
||||
Self { enabled: true }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum IpAllowEntry {
|
||||
Literal(IpNet),
|
||||
|
|
|
|||
|
|
@ -336,13 +336,8 @@ impl RunSession {
|
|||
|
||||
let resolved = &settings.run;
|
||||
|
||||
let sandbox_provider = resolve_sandbox_provider(resolved);
|
||||
let sandbox_provider =
|
||||
if resolved.execution.mode == RunMode::DryRun && !sandbox_provider.is_local() {
|
||||
SandboxProvider::Local
|
||||
} else {
|
||||
sandbox_provider
|
||||
};
|
||||
resolve_sandbox_provider(resolved).effective_for(resolved.execution.mode);
|
||||
let catalog = Arc::clone(&services.catalog);
|
||||
let configured =
|
||||
configured_providers_for_start(services.vault.as_ref(), Arc::clone(&catalog)).await;
|
||||
|
|
|
|||
|
|
@ -397,6 +397,9 @@ models/server-listen-tcp-settings.ts
|
|||
models/server-listen-unix-settings.ts
|
||||
models/server-logging-settings.ts
|
||||
models/server-namespace.ts
|
||||
models/server-sandbox-provider-settings.ts
|
||||
models/server-sandbox-providers-settings.ts
|
||||
models/server-sandbox-settings.ts
|
||||
models/server-scheduler-settings.ts
|
||||
models/server-settings.ts
|
||||
models/server-slate-db-settings.ts
|
||||
|
|
|
|||
|
|
@ -373,6 +373,9 @@ export * from './server-listen-tcp-settings';
|
|||
export * from './server-listen-unix-settings';
|
||||
export * from './server-logging-settings';
|
||||
export * from './server-namespace';
|
||||
export * from './server-sandbox-provider-settings';
|
||||
export * from './server-sandbox-providers-settings';
|
||||
export * from './server-sandbox-settings';
|
||||
export * from './server-scheduler-settings';
|
||||
export * from './server-settings';
|
||||
export * from './server-slate-db-settings';
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ import type { ServerListenSettings } from './server-listen-settings';
|
|||
import type { ServerLoggingSettings } from './server-logging-settings';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ServerSandboxSettings } from './server-sandbox-settings';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ServerSchedulerSettings } from './server-scheduler-settings';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
|
|
@ -53,6 +56,7 @@ export interface ServerNamespace {
|
|||
'web': ServerWebSettings;
|
||||
'auth': ServerAuthSettings;
|
||||
'ip_allowlist': ServerIpAllowlistSettings;
|
||||
'sandbox': ServerSandboxSettings;
|
||||
'storage': ServerStorageSettings;
|
||||
'artifacts': ServerArtifactsSettings;
|
||||
'slatedb': ServerSlateDbSettings;
|
||||
|
|
|
|||
19
lib/packages/fabro-api-client/src/models/server-sandbox-provider-settings.ts
generated
Normal file
19
lib/packages/fabro-api-client/src/models/server-sandbox-provider-settings.ts
generated
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export interface ServerSandboxProviderSettings {
|
||||
'enabled': boolean;
|
||||
}
|
||||
24
lib/packages/fabro-api-client/src/models/server-sandbox-providers-settings.ts
generated
Normal file
24
lib/packages/fabro-api-client/src/models/server-sandbox-providers-settings.ts
generated
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ServerSandboxProviderSettings } from './server-sandbox-provider-settings';
|
||||
|
||||
export interface ServerSandboxProvidersSettings {
|
||||
'local': ServerSandboxProviderSettings;
|
||||
'docker': ServerSandboxProviderSettings;
|
||||
'daytona': ServerSandboxProviderSettings;
|
||||
}
|
||||
22
lib/packages/fabro-api-client/src/models/server-sandbox-settings.ts
generated
Normal file
22
lib/packages/fabro-api-client/src/models/server-sandbox-settings.ts
generated
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ServerSandboxProvidersSettings } from './server-sandbox-providers-settings';
|
||||
|
||||
export interface ServerSandboxSettings {
|
||||
'providers': ServerSandboxProvidersSettings;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue