mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-01 02:04:24 +00:00
Remove the [server.slatedb] settings and the SlateDB prefix probe
No store sits behind `[server.slatedb]` any more: the section leaves the settings layer, the resolved server settings, the defaults, the API schema, the TypeScript client, the install wizard and the docs, and `fabro install` probes the bucket for the `artifacts/` prefix alone. A settings file that still carries the section is rewritten once at startup by a temporary migration that removes it with a backup beside the file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
ae70aa6ccf
commit
76461da780
31 changed files with 286 additions and 359 deletions
|
|
@ -142,7 +142,6 @@ petri_testkit = { git = "https://github.com/lithoscomputer/petri.git", rev = "4d
|
|||
sentry = { version = "0.35", default-features = false, features = ["backtrace", "contexts", "ureq", "rustls"] }
|
||||
fork = "0.2"
|
||||
exec = "0.3"
|
||||
slatedb = { version = "0.11.2", features = ["zstd"] }
|
||||
object_store = { version = "0.12.5", features = ["aws"] }
|
||||
rust-embed = "8"
|
||||
percent-encoding = "2"
|
||||
|
|
|
|||
|
|
@ -555,7 +555,7 @@ describe("InstallApp", () => {
|
|||
expect(text).toContain("fabro-data");
|
||||
expect(text).toContain("us-east-1");
|
||||
expect(text).toContain("Access key");
|
||||
expect(text).toContain("slatedb/, artifacts/");
|
||||
expect(text).toContain("artifacts/");
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
|
|
|
|||
|
|
@ -773,7 +773,7 @@ function ObjectStoreStep({
|
|||
return (
|
||||
<StepPanel
|
||||
title="Choose the shared object store"
|
||||
description="This configures the shared backend for both SlateDB and run artifacts. Fabro still keeps its local storage root on disk."
|
||||
description="This configures the backend for run artifacts. Fabro still keeps its local storage root on disk."
|
||||
error={saveError}
|
||||
submitting={submitting}
|
||||
submittingLabel={
|
||||
|
|
@ -969,7 +969,7 @@ function ObjectStoreStep({
|
|||
<div className="space-y-3">
|
||||
<Field
|
||||
label="Local directory"
|
||||
hint="Shared root for SlateDB and run artifacts."
|
||||
hint="Root for run artifacts."
|
||||
>
|
||||
<input
|
||||
ref={localRootInputRef}
|
||||
|
|
@ -989,7 +989,7 @@ function ObjectStoreStep({
|
|||
/>
|
||||
</Field>
|
||||
<p className="rounded-lg bg-overlay px-4 py-3 text-sm/6 text-fg-3 outline-1 -outline-offset-1 outline-white/10">
|
||||
Fabro will store SlateDB and run artifacts under this directory.
|
||||
Fabro will store run artifacts under this directory.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1556,7 +1556,7 @@ function WelcomeScreen() {
|
|||
["Server URL", "Confirm where operators will reach Fabro."],
|
||||
[
|
||||
"Object store",
|
||||
"Choose local disk or AWS S3 for SlateDB and artifacts.",
|
||||
"Choose local disk or AWS S3 for artifacts.",
|
||||
],
|
||||
["Sandbox", "Choose Docker or Daytona for workflow execution."],
|
||||
["LLMs", "Validate API keys for Anthropic, OpenAI, or Gemini."],
|
||||
|
|
@ -1869,12 +1869,12 @@ const OBJECT_STORE_PROVIDER_OPTIONS: ReadonlyArray<CardOption<ObjectStoreProvide
|
|||
{
|
||||
id: "local",
|
||||
title: "Local disk",
|
||||
body: "Uses the host filesystem for SlateDB and run artifacts.",
|
||||
body: "Uses the host filesystem for run artifacts.",
|
||||
},
|
||||
{
|
||||
id: "s3",
|
||||
title: "AWS S3",
|
||||
body: "Uses one S3 bucket with fixed slatedb/ and artifacts/ prefixes.",
|
||||
body: "Uses one S3 bucket with the fixed artifacts/ prefix.",
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -2394,7 +2394,7 @@ function ObjectStoreSummaryRows({
|
|||
: "Runtime credentials"
|
||||
}
|
||||
/>
|
||||
<SummaryRow label="Prefixes" value="slatedb/, artifacts/" mono />
|
||||
<SummaryRow label="Prefix" value="artifacts/" mono />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,14 +38,12 @@ export default function SettingsStorage() {
|
|||
{settings && resources ? (
|
||||
<>
|
||||
<StorageRootPanel settings={settings} disk={resources.disk} />
|
||||
<SlateDbPanel settings={settings} />
|
||||
<ArtifactsPanel settings={settings} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PanelSkeleton />
|
||||
<PanelSkeleton />
|
||||
<PanelSkeleton />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -75,15 +73,6 @@ function StorageRootPanel({
|
|||
);
|
||||
}
|
||||
|
||||
function SlateDbPanel({ settings }: { settings: ServerSettings }) {
|
||||
const { slatedb } = settings.server;
|
||||
return (
|
||||
<Panel title="SlateDB">
|
||||
<ObjectStoreRows store={slatedb.store} prefix={slatedb.prefix} />
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
function ArtifactsPanel({ settings }: { settings: ServerSettings }) {
|
||||
const { artifacts } = settings.server;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -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.sandbox]`, `[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.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.
|
||||
|
|
@ -73,15 +73,6 @@ prefix = "artifacts"
|
|||
bucket = "my-fabro-data"
|
||||
region = "us-east-1"
|
||||
|
||||
[server.slatedb]
|
||||
provider = "s3"
|
||||
prefix = "slatedb"
|
||||
disk_cache = true
|
||||
|
||||
[server.slatedb.s3]
|
||||
bucket = "my-fabro-data"
|
||||
region = "us-east-1"
|
||||
|
||||
[server.scheduler]
|
||||
max_concurrent_runs = 8
|
||||
|
||||
|
|
@ -226,40 +217,19 @@ E2B_API_URL = "https://api.e2b.example"
|
|||
| `env` | Complete environment for the plugin, apart from `inherit_env` | `{}` |
|
||||
| `inherit_env` | Ambient variables forwarded from the server process | `[]` |
|
||||
|
||||
### `[server.slatedb]` section
|
||||
### Removed: `[server.slatedb]`
|
||||
|
||||
Configure the embedded SlateDB key-value store used for the remaining
|
||||
object-store-backed indexes and as the read-only source for temporary storage
|
||||
migrations. Run history and content-addressed blobs live in SQLite; artifacts
|
||||
use `[server.artifacts]`.
|
||||
Earlier releases kept an embedded SlateDB key-value store beside the
|
||||
artifact store. Run history and content-addressed blobs now live in SQLite,
|
||||
so the section has no store behind it. A `settings.toml` that still carries
|
||||
`[server.slatedb]` is rewritten once at startup: the section is removed, a
|
||||
backup is written beside the file, and a warning names both. Delete the
|
||||
section yourself to avoid the rewrite.
|
||||
|
||||
| Key | Description | Default |
|
||||
|---|---|---|
|
||||
| `provider` | Object store backend: `local` or `s3` | `"local"` |
|
||||
| `prefix` | Key prefix within the object store | `""` |
|
||||
| `flush_interval` | How often to flush the write-ahead log | `"1ms"` |
|
||||
| `disk_cache` | Enable a local disk cache for object store reads | `false` |
|
||||
|
||||
When `disk_cache = true`, Fabro creates a cache directory at `<storage_root>/cache/slatedb` and
|
||||
configures SlateDB to cache object store bytes on local disk (16 GB max, 4 MB parts). This
|
||||
significantly reduces read latency and costs for S3-backed deployments. A warning is emitted if
|
||||
enabled with `provider = "local"` since the disk cache adds overhead when the object store is
|
||||
already local.
|
||||
|
||||
```toml title="settings.toml"
|
||||
[server.slatedb]
|
||||
provider = "s3"
|
||||
disk_cache = true
|
||||
|
||||
[server.slatedb.s3]
|
||||
bucket = "fabro-production"
|
||||
region = "us-east-1"
|
||||
```
|
||||
|
||||
The browser install wizard's `Object store` step manages both `[server.slatedb]` and
|
||||
`[server.artifacts]` together. `Local disk` uses the detected local object-store root, defaulting
|
||||
to `<storage_root>/objects`, with fixed prefixes `slatedb` and `artifacts`. `AWS S3` writes one
|
||||
shared bucket with the same fixed prefixes.
|
||||
The browser install wizard's `Object store` step manages `[server.artifacts]`.
|
||||
`Local disk` uses the detected local object-store root, defaulting to
|
||||
`<storage_root>/objects`, with the fixed prefix `artifacts`. `AWS S3` writes
|
||||
one bucket with the same fixed prefix.
|
||||
|
||||
```toml title="Local disk object store"
|
||||
[server.artifacts]
|
||||
|
|
@ -268,13 +238,6 @@ prefix = "artifacts"
|
|||
|
||||
[server.artifacts.local]
|
||||
root = "/var/lib/fabro/objects"
|
||||
|
||||
[server.slatedb]
|
||||
provider = "local"
|
||||
prefix = "slatedb"
|
||||
|
||||
[server.slatedb.local]
|
||||
root = "/var/lib/fabro/objects"
|
||||
```
|
||||
|
||||
The wizard only covers AWS S3 bucket/region plus one of:
|
||||
|
|
@ -284,16 +247,14 @@ The wizard only covers AWS S3 bucket/region plus one of:
|
|||
|
||||
Advanced S3-compatible settings such as custom `endpoint` or `path_style` remain a manual
|
||||
configuration path. If you need MinIO, R2, or another S3-compatible backend, configure
|
||||
`[server.slatedb]` and `[server.artifacts]` directly in `settings.toml`. The runtime still
|
||||
honors those hand-edited values even though the browser wizard does not manage them.
|
||||
`[server.artifacts]` directly in `settings.toml`. The runtime still honors those hand-edited
|
||||
values even though the browser wizard does not manage them.
|
||||
|
||||
### SQLite state and migration backups
|
||||
|
||||
Shared relational state, including run events and current run rows,
|
||||
Shared relational state, including run records and current run rows,
|
||||
content-addressed blobs, vault entries, server-managed definitions, and CLI
|
||||
auth sessions, lives at `<storage_root>/db/fabro.sqlite3`. The
|
||||
`[server.slatedb]` object store remains configured for compatibility imports
|
||||
and session-to-run reverse indexes during the storage transition.
|
||||
auth sessions, lives at `<storage_root>/db/fabro.sqlite3`.
|
||||
|
||||
CLI auth sessions are stored as an `auth_sessions` row per signed-in CLI, with the rotating refresh tokens for that session in `refresh_tokens`. Pending browser-to-CLI handoffs live briefly in `oauth_authorization_codes`; the table contains a SHA-256 hash of each one-time code, never the raw bearer value. Revoking a session from **Settings → Sessions**, or with `DELETE /api/v1/auth/sessions/{id}`, deletes the session row and its tokens together.
|
||||
|
||||
|
|
@ -476,7 +437,7 @@ Fabro resolves these from `process env -> server.env`.
|
|||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `AWS_ACCESS_KEY_ID` | Static AWS access key ID for S3-backed `[server.slatedb]` / `[server.artifacts]` |
|
||||
| `AWS_ACCESS_KEY_ID` | Static AWS access key ID for an S3-backed `[server.artifacts]` |
|
||||
| `AWS_SECRET_ACCESS_KEY` | Matching static AWS secret access key |
|
||||
| `AWS_SESSION_TOKEN` | Optional matching AWS session token for temporary static credentials |
|
||||
|
||||
|
|
@ -484,8 +445,8 @@ The browser install wizard can write these into `server.env` for the AWS S3 manu
|
|||
path. It does not support manual STS/session-token input; use runtime credentials instead for ECS,
|
||||
EC2 instance profiles, IRSA, or web-identity flows.
|
||||
|
||||
For the narrowest production policy, scope access to one bucket and the `slatedb/` and
|
||||
`artifacts/` prefixes with `s3:ListBucket` plus `s3:GetObject`, `s3:PutObject`, and
|
||||
For the narrowest production policy, scope access to one bucket and the `artifacts/` prefix
|
||||
with `s3:ListBucket` plus `s3:GetObject`, `s3:PutObject`, and
|
||||
`s3:DeleteObject`. Prefer a dedicated IAM user or role for Fabro instead of reusing broad AWS
|
||||
credentials.
|
||||
|
||||
|
|
|
|||
|
|
@ -14890,7 +14890,6 @@ components:
|
|||
- sandbox
|
||||
- storage
|
||||
- artifacts
|
||||
- slatedb
|
||||
- scheduler
|
||||
- logging
|
||||
- integrations
|
||||
|
|
@ -14909,8 +14908,6 @@ components:
|
|||
$ref: "#/components/schemas/ServerStorageSettings"
|
||||
artifacts:
|
||||
$ref: "#/components/schemas/ServerArtifactsSettings"
|
||||
slatedb:
|
||||
$ref: "#/components/schemas/ServerSlateDbSettings"
|
||||
scheduler:
|
||||
$ref: "#/components/schemas/ServerSchedulerSettings"
|
||||
logging:
|
||||
|
|
@ -15048,19 +15045,6 @@ components:
|
|||
store:
|
||||
$ref: "#/components/schemas/ObjectStoreSettings"
|
||||
|
||||
ServerSlateDbSettings:
|
||||
type: object
|
||||
required: [prefix, store, flush_interval, disk_cache]
|
||||
properties:
|
||||
prefix:
|
||||
type: string
|
||||
store:
|
||||
$ref: "#/components/schemas/ObjectStoreSettings"
|
||||
flush_interval:
|
||||
type: string
|
||||
disk_cache:
|
||||
type: boolean
|
||||
|
||||
ObjectStoreSettings:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/ObjectStoreLocalSettings"
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ When Fabro can construct a direct install URL, the token is embedded in the URL
|
|||
The `Object store` step offers two wizard-managed modes:
|
||||
|
||||
- `Local disk` for a host-local object-store root, detected by default and editable before continuing
|
||||
- `AWS S3` for one shared bucket with fixed `slatedb/` and `artifacts/` prefixes
|
||||
- `AWS S3` for one bucket with the fixed `artifacts/` prefix
|
||||
|
||||
The wizard's manual-credential path stores only `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` in `server.env`. It does not collect STS/session tokens or S3-compatible endpoint settings. If you need MinIO, Cloudflare R2, path-style options, or custom endpoints, finish install with local defaults and then edit `[server.slatedb]` / `[server.artifacts]` in `settings.toml` manually.
|
||||
The wizard's manual-credential path stores only `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` in `server.env`. It does not collect STS/session tokens or S3-compatible endpoint settings. If you need MinIO, Cloudflare R2, path-style options, or custom endpoints, finish install with local defaults and then edit `[server.artifacts]` in `settings.toml` manually.
|
||||
|
||||
When you finish the wizard, the server writes `~/.fabro/settings.toml` and exits cleanly. Start it again to boot in configured mode:
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ Files that omit `_version` are treated as version `1`. The legacy top-level `ver
|
|||
| CLI-only | `[cli.target]`, `[cli.auth]`, `[cli.exec]`, `[cli.output]`, `[cli.updates]`, `[cli.logging]` |
|
||||
| Server-side run policy | `[run.model]`, `[run.environment]`, `[environments.<slug>]`, `[run.checkpoint]`, `[run.inputs]`, `[run.prepare]`, `[run.pull_request]`, `[run.integrations.github]`, `[run.hooks]`, `[run.agent.mcps]` |
|
||||
| Shared LLM catalog | `[llm]`, a lithos-llm catalog overlay: `[llm.providers.<id>]`, `[llm.providers.<id>.models.<id>]`, and the agent harness under `metadata.agent` |
|
||||
| Server-only | `[server.listen]`, `[server.api]`, `[server.web]`, `[server.auth]`, `[server.storage]`, `[server.artifacts]`, `[server.slatedb]`, `[server.scheduler]`, `[server.logging]`, `[server.integrations]` |
|
||||
| Server-only | `[server.listen]`, `[server.api]`, `[server.web]`, `[server.auth]`, `[server.storage]`, `[server.artifacts]`, `[server.scheduler]`, `[server.logging]`, `[server.integrations]` |
|
||||
|
||||
`[cli.*]` and `[server.*]` stanzas are owner-specific: they are only consumed from `~/.fabro/settings.toml` (plus process-local flags and env overrides). The same stanzas in `.fabro/project.toml` or `workflow.toml` remain schema-valid but runtime-inert.
|
||||
|
||||
|
|
|
|||
|
|
@ -550,7 +550,7 @@ mod tests {
|
|||
"os": "darwin",
|
||||
"arch": "arm64",
|
||||
"storage_dir": "/tmp/fabro-worker-auth",
|
||||
"storage_engine": "slatedb",
|
||||
"storage_engine": "sqlite",
|
||||
"runs": { "total": 0, "active": 0 },
|
||||
"uptime_secs": 42
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ fn auth_login_refresh_logout_flow() {
|
|||
"os": "darwin",
|
||||
"arch": "arm64",
|
||||
"storage_dir": "/tmp/fabro-auth-flow",
|
||||
"storage_engine": "slatedb",
|
||||
"storage_engine": "sqlite",
|
||||
"runs": { "total": 0, "active": 0 },
|
||||
"uptime_secs": 42
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -849,7 +849,7 @@ pub(crate) async fn get_system_info(
|
|||
"profile": option_env!("FABRO_BUILD_PROFILE"),
|
||||
"os": std::env::consts::OS,
|
||||
"arch": std::env::consts::ARCH,
|
||||
"storage_engine": "slatedb",
|
||||
"storage_engine": "sqlite",
|
||||
"storage_dir": "/demo/fabro/storage",
|
||||
"uptime_secs": 42,
|
||||
"runs": { "total": 3, "active": 1 },
|
||||
|
|
|
|||
|
|
@ -1270,32 +1270,26 @@ async fn validate_install_object_store_selection(
|
|||
Some(&build_options),
|
||||
)?;
|
||||
|
||||
let probe_prefix = |index: usize, prefix: &'static str| {
|
||||
let object_store = &object_store;
|
||||
async move {
|
||||
let path = ObjectStorePath::from(prefix);
|
||||
object_store
|
||||
.list_with_delimiter(Some(&path))
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|err| (index, err))
|
||||
}
|
||||
};
|
||||
// The bucket answers for the one prefix Fabro keeps objects under.
|
||||
let probe = async {
|
||||
tokio::try_join!(probe_prefix(0, "artifacts"), probe_prefix(1, "slatedb")).map(|_| ())
|
||||
let path = ObjectStorePath::from("artifacts");
|
||||
object_store
|
||||
.list_with_delimiter(Some(&path))
|
||||
.await
|
||||
.map(|_| ())
|
||||
};
|
||||
|
||||
match timeout(VALIDATION_TIMEOUT, probe).await {
|
||||
Ok(Ok(())) => Ok(()),
|
||||
Err(_) => bail!(VALIDATION_TIMEOUT_MSG),
|
||||
Ok(Err((index, err))) => bail!(
|
||||
Ok(Err(err)) => bail!(
|
||||
"{}",
|
||||
classify_object_store_validation_error(bucket, region, index, &err)
|
||||
classify_object_store_validation_error(bucket, region, &err)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const PREFIX_ACCESS_ERROR_MSG: &str = "Fabro reached the bucket but could not verify access to slatedb/ and artifacts/. Validation requires bucket list access plus object access under both prefixes.";
|
||||
const PREFIX_ACCESS_ERROR_MSG: &str = "Fabro reached the bucket but could not verify access to artifacts/. Validation requires bucket list access plus object access under that prefix.";
|
||||
const VALIDATION_TIMEOUT_MSG: &str = "Timed out while checking S3 access. Verify the bucket, region, and network path, then try again.";
|
||||
|
||||
fn bucket_credentials_error(bucket: &str, region: &str) -> String {
|
||||
|
|
@ -1305,16 +1299,9 @@ fn bucket_credentials_error(bucket: &str, region: &str) -> String {
|
|||
fn classify_object_store_validation_error(
|
||||
bucket: &str,
|
||||
region: &str,
|
||||
prefix_index: usize,
|
||||
err: &object_store::Error,
|
||||
) -> String {
|
||||
let credentials_or_prefix_error = || {
|
||||
if prefix_index == 0 {
|
||||
bucket_credentials_error(bucket, region)
|
||||
} else {
|
||||
PREFIX_ACCESS_ERROR_MSG.to_string()
|
||||
}
|
||||
};
|
||||
let credentials_or_prefix_error = || bucket_credentials_error(bucket, region);
|
||||
match err {
|
||||
object_store::Error::PermissionDenied { .. }
|
||||
| object_store::Error::Unauthenticated { .. } => credentials_or_prefix_error(),
|
||||
|
|
@ -2745,7 +2732,7 @@ AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/fabro-web-identity-token\n",
|
|||
};
|
||||
|
||||
assert_eq!(
|
||||
classify_object_store_validation_error("fabro-data", "us-east-1", 0, &err),
|
||||
classify_object_store_validation_error("fabro-data", "us-east-1", &err),
|
||||
"Bucket fabro-data is not reachable in region us-east-1. Verify the AWS region and try again."
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1345,12 +1345,6 @@ mod tests {
|
|||
panic!("artifacts store should stay local");
|
||||
};
|
||||
assert_eq!(root, "/srv/fabro-storage/objects/artifacts");
|
||||
let fabro_types::settings::ObjectStoreSettings::Local { root } =
|
||||
&resolved.server_settings.server.slatedb.store
|
||||
else {
|
||||
panic!("slatedb store should stay local");
|
||||
};
|
||||
assert_eq!(root, "/srv/fabro-storage/objects/slatedb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -874,10 +874,6 @@ mod tests {
|
|||
local_store_root(&settings.server.artifacts.store),
|
||||
storage_root.join("objects/artifacts")
|
||||
);
|
||||
assert_eq!(
|
||||
local_store_root(&settings.server.slatedb.store),
|
||||
storage_root.join("objects/slatedb")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -415,7 +415,6 @@ pub fn write_object_store_settings(
|
|||
let root_table = root_table_mut(doc)?;
|
||||
let server = ensure_table(root_table, "server")?;
|
||||
write_local_store_settings(server, "artifacts", "artifacts", root)?;
|
||||
write_local_store_settings(server, "slatedb", "slatedb", root)?;
|
||||
}
|
||||
Ok(InstallObjectStoreEnvPlan {
|
||||
writes: Vec::new(),
|
||||
|
|
@ -437,7 +436,6 @@ pub fn write_object_store_settings(
|
|||
let root = root_table_mut(doc)?;
|
||||
let server = ensure_table(root, "server")?;
|
||||
write_s3_store_settings(server, "artifacts", "artifacts", bucket, region)?;
|
||||
write_s3_store_settings(server, "slatedb", "slatedb", bucket, region)?;
|
||||
|
||||
let removals = object_store_env_removals();
|
||||
let writes = match credential_mode {
|
||||
|
|
@ -1327,32 +1325,7 @@ stale = "remove-me"
|
|||
.and_then(toml::Value::as_str),
|
||||
Some("/srv/fabro/objects")
|
||||
);
|
||||
assert_eq!(
|
||||
server
|
||||
.get("slatedb")
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|slatedb| slatedb.get("provider"))
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("local")
|
||||
);
|
||||
assert_eq!(
|
||||
server
|
||||
.get("slatedb")
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|slatedb| slatedb.get("prefix"))
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("slatedb")
|
||||
);
|
||||
assert_eq!(
|
||||
server
|
||||
.get("slatedb")
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|slatedb| slatedb.get("local"))
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|local| local.get("root"))
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("/srv/fabro/objects")
|
||||
);
|
||||
assert!(server.get("slatedb").is_none());
|
||||
assert!(plan.writes.is_empty());
|
||||
assert_eq!(plan.removals.len(), 2);
|
||||
}
|
||||
|
|
@ -1381,14 +1354,7 @@ stale = "remove-me"
|
|||
.and_then(toml::Value::as_str),
|
||||
Some("artifacts")
|
||||
);
|
||||
assert_eq!(
|
||||
server
|
||||
.get("slatedb")
|
||||
.and_then(toml::Value::as_table)
|
||||
.and_then(|slatedb| slatedb.get("prefix"))
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("slatedb")
|
||||
);
|
||||
assert!(server.get("slatedb").is_none());
|
||||
assert!(plan.writes.is_empty());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -297,11 +297,6 @@ fn main() {
|
|||
"fabro_types::settings::server::ServerArtifactsSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ServerSlateDbSettings",
|
||||
"fabro_types::settings::server::ServerSlateDbSettings",
|
||||
&[],
|
||||
),
|
||||
(
|
||||
"ObjectStoreSettings",
|
||||
"fabro_types::settings::server::ObjectStoreSettings",
|
||||
|
|
|
|||
|
|
@ -29,8 +29,7 @@ pub mod types {
|
|||
ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings, ServerIntegrationsSettings,
|
||||
ServerListenSettings, ServerLoggingSettings, ServerSandboxProviderSettings,
|
||||
ServerSandboxProvidersSettings, ServerSandboxSettings, ServerSchedulerSettings,
|
||||
ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings,
|
||||
WebhookStrategy,
|
||||
ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings, WebhookStrategy,
|
||||
};
|
||||
pub use fabro_types::settings::{McpTransport, ServerNamespace};
|
||||
pub use fabro_types::status::{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,189 @@
|
|||
//! Temporary compatibility migration: drop `[server.slatedb]` from a
|
||||
//! settings file.
|
||||
//!
|
||||
//! Earlier releases kept an embedded SlateDB store beside the artifact
|
||||
//! store and configured it under `[server.slatedb]`. Run history and blobs
|
||||
//! live in SQLite now and the section has no store behind it, so the
|
||||
//! settings layer no longer knows the key and would refuse the file. This
|
||||
//! migration removes the section (and its `local` and `s3` subtables) once,
|
||||
//! with a backup beside the file, and leaves every other key as it was.
|
||||
//!
|
||||
//! Delete this migration after `REMOVAL_DEADLINE`, once supported upgrade
|
||||
//! windows no longer start from a release that wrote the section.
|
||||
|
||||
#![expect(
|
||||
clippy::disallowed_methods,
|
||||
reason = "temporary startup config migration uses synchronous file I/O before config is loaded"
|
||||
)]
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use toml_edit::DocumentMut;
|
||||
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// When this migration can be removed.
|
||||
pub(crate) const REMOVAL_DEADLINE: &str = "2027-03-01";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RemoveServerSlateDbReport {
|
||||
pub(crate) contents: String,
|
||||
pub(crate) warning: String,
|
||||
#[cfg(test)]
|
||||
backup_path: PathBuf,
|
||||
}
|
||||
|
||||
/// Rewrite `path` without its `[server.slatedb]` section when it has one:
|
||||
/// the backup is written first, then the file. `Ok(None)` when the file
|
||||
/// has no such section, or is not TOML the layer could read anyway.
|
||||
pub(crate) fn migrate_settings_path(
|
||||
path: &Path,
|
||||
contents: &str,
|
||||
) -> Result<Option<RemoveServerSlateDbReport>> {
|
||||
let Some(next_contents) = migrate_contents(contents) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let backup_path = write_next_backup(path, contents)?;
|
||||
std::fs::write(path, &next_contents).map_err(|source| {
|
||||
Error::other(format!(
|
||||
"writing migrated settings file {}: {source}",
|
||||
path.display()
|
||||
))
|
||||
})?;
|
||||
let warning = format!(
|
||||
"Removed the [server.slatedb] section from {}: Fabro no longer keeps a SlateDB store. Backup written to {}. This temporary compatibility migration will be removed after {REMOVAL_DEADLINE}.",
|
||||
path.display(),
|
||||
backup_path.display()
|
||||
);
|
||||
Ok(Some(RemoveServerSlateDbReport {
|
||||
contents: next_contents,
|
||||
warning,
|
||||
#[cfg(test)]
|
||||
backup_path,
|
||||
}))
|
||||
}
|
||||
|
||||
/// The contents without `[server.slatedb]`, or `None` when there is
|
||||
/// nothing to remove.
|
||||
pub(crate) fn migrate_contents(contents: &str) -> Option<String> {
|
||||
let mut doc = contents.parse::<DocumentMut>().ok()?;
|
||||
let server = doc.get_mut("server")?.as_table_like_mut()?;
|
||||
server.remove("slatedb")?;
|
||||
Some(doc.to_string())
|
||||
}
|
||||
|
||||
fn write_next_backup(path: &Path, contents: &str) -> Result<PathBuf> {
|
||||
for index in 0u32.. {
|
||||
let backup_path = backup_path_for(path, index);
|
||||
match std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&backup_path)
|
||||
{
|
||||
Ok(mut file) => {
|
||||
file.write_all(contents.as_bytes()).map_err(|source| {
|
||||
Error::other(format!(
|
||||
"writing server.slatedb migration backup {}: {source}",
|
||||
backup_path.display()
|
||||
))
|
||||
})?;
|
||||
return Ok(backup_path);
|
||||
}
|
||||
Err(source) if source.kind() == std::io::ErrorKind::AlreadyExists => {}
|
||||
Err(source) => {
|
||||
return Err(Error::other(format!(
|
||||
"writing server.slatedb migration backup {}: {source}",
|
||||
backup_path.display()
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
unreachable!("unbounded backup suffix search should return")
|
||||
}
|
||||
|
||||
fn backup_path_for(path: &Path, index: u32) -> PathBuf {
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("settings.toml");
|
||||
if index == 0 {
|
||||
path.with_file_name(format!("{file_name}.server-slatedb-migration.bak"))
|
||||
} else {
|
||||
path.with_file_name(format!("{file_name}.server-slatedb-migration.{index}.bak"))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const LEGACY: &str = r#"_version = 1
|
||||
|
||||
[server.artifacts]
|
||||
provider = "local"
|
||||
prefix = "artifacts"
|
||||
|
||||
# The store that is gone.
|
||||
[server.slatedb]
|
||||
provider = "s3"
|
||||
prefix = "slatedb"
|
||||
disk_cache = true
|
||||
|
||||
[server.slatedb.s3]
|
||||
bucket = "fabro-data"
|
||||
region = "us-east-1"
|
||||
|
||||
[server.scheduler]
|
||||
max_concurrent_runs = 3
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn a_file_without_the_section_is_left_alone() {
|
||||
assert_eq!(
|
||||
migrate_contents("_version = 1\n\n[server.web]\nenabled = true\n"),
|
||||
None
|
||||
);
|
||||
assert_eq!(migrate_contents("not toml ["), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_section_and_its_subtables_go_and_the_rest_stays() {
|
||||
let migrated = migrate_contents(LEGACY).expect("the section is removed");
|
||||
assert!(!migrated.contains("slatedb"), "{migrated}");
|
||||
assert!(migrated.contains("[server.artifacts]"), "{migrated}");
|
||||
assert!(migrated.contains("prefix = \"artifacts\""), "{migrated}");
|
||||
assert!(migrated.contains("max_concurrent_runs = 3"), "{migrated}");
|
||||
assert_eq!(
|
||||
migrate_contents(&migrated),
|
||||
None,
|
||||
"a second pass is a no-op"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_file_is_rewritten_with_a_backup_once() {
|
||||
let dir = tempfile::tempdir().expect("a temp dir");
|
||||
let path = dir.path().join("settings.toml");
|
||||
std::fs::write(&path, LEGACY).expect("the legacy file");
|
||||
|
||||
let report = migrate_settings_path(&path, LEGACY)
|
||||
.expect("the migration runs")
|
||||
.expect("the file changed");
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(&report.backup_path).expect("the backup"),
|
||||
LEGACY
|
||||
);
|
||||
let rewritten = std::fs::read_to_string(&path).expect("the file");
|
||||
assert_eq!(rewritten, report.contents);
|
||||
assert!(!rewritten.contains("slatedb"));
|
||||
assert!(report.warning.contains("[server.slatedb]"));
|
||||
|
||||
assert!(
|
||||
migrate_settings_path(&path, &rewritten)
|
||||
.expect("the migration runs")
|
||||
.is_none(),
|
||||
"the second run is a no-op"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -42,9 +42,3 @@ max_concurrent_runs = 5
|
|||
[server.artifacts]
|
||||
provider = "local"
|
||||
prefix = ""
|
||||
|
||||
[server.slatedb]
|
||||
provider = "local"
|
||||
prefix = ""
|
||||
flush_interval = "1ms"
|
||||
disk_cache = false
|
||||
|
|
|
|||
|
|
@ -38,8 +38,7 @@ pub use server::{
|
|||
ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer,
|
||||
ServerIntegrationsLayer, ServerLayer, ServerListenLayer, ServerLoggingLayer,
|
||||
ServerSandboxLayer, ServerSandboxProviderLayer, ServerSandboxProvidersLayer,
|
||||
ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer,
|
||||
SlackIntegrationLayer,
|
||||
ServerSchedulerLayer, ServerStorageLayer, ServerWebLayer, SlackIntegrationLayer,
|
||||
};
|
||||
pub use settings::SettingsLayer;
|
||||
pub use workflow::WorkflowLayer;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use fabro_types::SandboxProviderKind;
|
||||
use fabro_types::settings::InterpString;
|
||||
use fabro_types::settings::server::{
|
||||
GithubIntegrationStrategy, LogDestination, ObjectStoreProvider, ServerAuthMethod,
|
||||
WebhookStrategy,
|
||||
};
|
||||
use fabro_types::settings::{Duration, InterpString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::LogFilter;
|
||||
|
|
@ -31,8 +31,6 @@ pub struct ServerLayer {
|
|||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub artifacts: Option<ServerArtifactsLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub slatedb: Option<ServerSlateDbLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub scheduler: Option<ServerSchedulerLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub logging: Option<ServerLoggingLayer>,
|
||||
|
|
@ -167,24 +165,6 @@ pub struct ServerArtifactsLayer {
|
|||
pub s3: Option<ObjectStoreS3Layer>,
|
||||
}
|
||||
|
||||
/// `[server.slatedb]` — SlateDB bottomless storage plus tunables.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, fabro_macros::Combine)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ServerSlateDbLayer {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub provider: Option<ObjectStoreProvider>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub prefix: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub flush_interval: Option<Duration>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub local: Option<ObjectStoreLocalLayer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub s3: Option<ObjectStoreS3Layer>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub disk_cache: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ObjectStoreLocalLayer {
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@ pub use layers::{
|
|||
ServerApiLayer, ServerArtifactsLayer, ServerAuthGithubLayer, ServerAuthLayer,
|
||||
ServerIntegrationsLayer, ServerLayer, ServerListenLayer, ServerLoggingLayer,
|
||||
ServerSandboxLayer, ServerSandboxProviderLayer, ServerSandboxProvidersLayer,
|
||||
ServerSchedulerLayer, ServerSlateDbLayer, ServerStorageLayer, ServerWebLayer, SettingsLayer,
|
||||
SlackIntegrationLayer, StickyMap, StringOrSplice, WorkflowLayer,
|
||||
ServerSchedulerLayer, ServerStorageLayer, ServerWebLayer, SettingsLayer, SlackIntegrationLayer,
|
||||
StickyMap, StringOrSplice, WorkflowLayer,
|
||||
};
|
||||
pub use logging::{resolve_log_destination, resolve_log_destination_with_env};
|
||||
pub use parse::ParseError;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,54 @@
|
|||
//! The settings-file migrations, in the order they run: the legacy
|
||||
//! `[run.sandbox]` and `[environments]` rewrites first, then the removal of
|
||||
//! `[server.slatedb]`. Each is state-driven and a no-op on a file already
|
||||
//! in the current shape.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::Result;
|
||||
|
||||
#[path = "../migrations/2026050101_legacy_sandbox_to_environments.rs"]
|
||||
mod legacy_sandbox_to_environments;
|
||||
#[path = "../migrations/2026091801_remove_server_slatedb.rs"]
|
||||
mod remove_server_slatedb;
|
||||
#[path = "../migrations/2026052801_settings_environments_to_server_files.rs"]
|
||||
mod settings_environments_to_server_files;
|
||||
|
||||
pub(crate) use settings_environments_to_server_files::SettingsEnvironmentsMigrationReport as MigrationReport;
|
||||
/// What the migrations left: the file's contents now, and the warning that
|
||||
/// names every rewrite.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct MigrationReport {
|
||||
pub(crate) contents: String,
|
||||
pub(crate) warning: String,
|
||||
}
|
||||
|
||||
pub(crate) fn run_migrations(
|
||||
path: &Path,
|
||||
original_contents: &str,
|
||||
) -> Result<Option<MigrationReport>> {
|
||||
settings_environments_to_server_files::migrate_settings_path(path, original_contents)
|
||||
let mut report: Option<MigrationReport> = None;
|
||||
if let Some(environments) =
|
||||
settings_environments_to_server_files::migrate_settings_path(path, original_contents)?
|
||||
{
|
||||
report = Some(MigrationReport {
|
||||
contents: environments.contents,
|
||||
warning: environments.warning,
|
||||
});
|
||||
}
|
||||
let contents = report
|
||||
.as_ref()
|
||||
.map_or(original_contents, |report| report.contents.as_str());
|
||||
if let Some(slatedb) = remove_server_slatedb::migrate_settings_path(path, contents)? {
|
||||
report = Some(match report {
|
||||
Some(earlier) => MigrationReport {
|
||||
contents: slatedb.contents,
|
||||
warning: format!("{} {}", earlier.warning, slatedb.warning),
|
||||
},
|
||||
None => MigrationReport {
|
||||
contents: slatedb.contents,
|
||||
warning: slatedb.warning,
|
||||
},
|
||||
});
|
||||
}
|
||||
Ok(report)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use fabro_types::settings::server::{
|
|||
ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod, ServerAuthSettings,
|
||||
ServerIntegrationsSettings, ServerListenSettings, ServerLoggingSettings, ServerNamespace,
|
||||
ServerSandboxProviderSettings, ServerSandboxProvidersSettings, ServerSandboxSettings,
|
||||
ServerSchedulerSettings, ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings,
|
||||
SlackIntegrationSettings, WebhookStrategy,
|
||||
ServerSchedulerSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings,
|
||||
WebhookStrategy,
|
||||
};
|
||||
use fabro_util::Home;
|
||||
|
||||
|
|
@ -21,8 +21,7 @@ use crate::user::default_storage_dir;
|
|||
use crate::{
|
||||
IntegrationWebhooksLayer, ObjectStoreLocalLayer, ObjectStoreS3Layer, ServerApiLayer,
|
||||
ServerArtifactsLayer, ServerAuthLayer, ServerIntegrationsLayer, ServerLayer, ServerListenLayer,
|
||||
ServerSandboxLayer, ServerSandboxProviderLayer, ServerSlateDbLayer, ServerStorageLayer,
|
||||
ServerWebLayer,
|
||||
ServerSandboxLayer, ServerSandboxProviderLayer, ServerStorageLayer, ServerWebLayer,
|
||||
};
|
||||
|
||||
pub fn resolve_server(layer: &ServerLayer, errors: &mut Vec<ResolveError>) -> ServerNamespace {
|
||||
|
|
@ -44,7 +43,6 @@ pub fn resolve_server(layer: &ServerLayer, errors: &mut Vec<ResolveError>) -> Se
|
|||
sandbox: resolve_sandbox(layer.sandbox.as_ref(), errors),
|
||||
storage: storage.clone(),
|
||||
artifacts: resolve_artifacts(layer.artifacts.as_ref(), &storage.root, errors),
|
||||
slatedb: resolve_slatedb(layer.slatedb.as_ref(), &storage.root, errors),
|
||||
scheduler: ServerSchedulerSettings {
|
||||
max_concurrent_runs: layer
|
||||
.scheduler
|
||||
|
|
@ -294,50 +292,6 @@ fn resolve_artifacts(
|
|||
}
|
||||
}
|
||||
|
||||
fn resolve_slatedb(
|
||||
layer: Option<&ServerSlateDbLayer>,
|
||||
storage_root: &str,
|
||||
errors: &mut Vec<ResolveError>,
|
||||
) -> ServerSlateDbSettings {
|
||||
let provider = layer
|
||||
.and_then(|slatedb| slatedb.provider)
|
||||
.expect("defaults.toml should provide server.slatedb.provider");
|
||||
|
||||
let disk_cache = layer
|
||||
.and_then(|slatedb| slatedb.disk_cache)
|
||||
.expect("defaults.toml should provide server.slatedb.disk_cache");
|
||||
|
||||
if disk_cache && provider == ObjectStoreProvider::Local {
|
||||
tracing::warn!(
|
||||
"disk_cache enabled with local provider; \
|
||||
disk cache is designed for S3-backed deployments \
|
||||
and adds overhead on local filesystems"
|
||||
);
|
||||
}
|
||||
|
||||
let prefix = layer
|
||||
.and_then(|slatedb| slatedb.prefix.clone())
|
||||
.expect("defaults.toml should provide server.slatedb.prefix");
|
||||
warn_if_demoted_template("server.slatedb.prefix", Some(prefix.as_str()));
|
||||
|
||||
ServerSlateDbSettings {
|
||||
prefix,
|
||||
store: resolve_object_store(
|
||||
provider,
|
||||
layer.and_then(|slatedb| slatedb.local.as_ref()),
|
||||
layer.and_then(|slatedb| slatedb.s3.as_ref()),
|
||||
&object_store_default_root(storage_root, "slatedb"),
|
||||
"server.slatedb",
|
||||
errors,
|
||||
),
|
||||
flush_interval: layer
|
||||
.and_then(|slatedb| slatedb.flush_interval)
|
||||
.map(|duration| duration.as_std())
|
||||
.expect("defaults.toml should provide server.slatedb.flush_interval"),
|
||||
disk_cache,
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_object_store(
|
||||
provider: ObjectStoreProvider,
|
||||
local: Option<&ObjectStoreLocalLayer>,
|
||||
|
|
|
|||
|
|
@ -34,11 +34,6 @@ impl Storage {
|
|||
self.root.join("cache")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn slatedb_cache_dir(&self) -> PathBuf {
|
||||
self.cache_dir().join("slatedb")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn secrets_path(&self) -> PathBuf {
|
||||
self.root
|
||||
|
|
@ -77,11 +72,6 @@ impl Storage {
|
|||
self.root.join("objects")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn slatedb_dir(&self) -> PathBuf {
|
||||
self.objects_dir().join("slatedb")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn artifacts_dir(&self) -> PathBuf {
|
||||
self.objects_dir().join("artifacts")
|
||||
|
|
@ -185,10 +175,6 @@ mod tests {
|
|||
storage.cache_dir(),
|
||||
std::path::Path::new("/tmp/fabro-data/cache")
|
||||
);
|
||||
assert_eq!(
|
||||
storage.slatedb_cache_dir(),
|
||||
std::path::Path::new("/tmp/fabro-data/cache/slatedb")
|
||||
);
|
||||
assert_eq!(
|
||||
storage.secrets_path(),
|
||||
std::path::Path::new("/tmp/fabro-data/vaults/default/secrets.json")
|
||||
|
|
@ -205,10 +191,6 @@ mod tests {
|
|||
storage.objects_dir(),
|
||||
std::path::Path::new("/tmp/fabro-data/objects")
|
||||
);
|
||||
assert_eq!(
|
||||
storage.slatedb_dir(),
|
||||
std::path::Path::new("/tmp/fabro-data/objects/slatedb")
|
||||
);
|
||||
assert_eq!(
|
||||
storage.artifacts_dir(),
|
||||
std::path::Path::new("/tmp/fabro-data/objects/artifacts")
|
||||
|
|
|
|||
|
|
@ -89,21 +89,6 @@ fn resolves_server_defaults_from_empty_settings() {
|
|||
ObjectStoreSettings::S3 { .. } => panic!("expected local artifact store by default"),
|
||||
}
|
||||
assert_eq!(settings.artifacts.prefix, "");
|
||||
|
||||
match settings.slatedb.store {
|
||||
ObjectStoreSettings::Local { root } => {
|
||||
assert_eq!(
|
||||
root,
|
||||
default_storage_dir()
|
||||
.join("objects")
|
||||
.join("slatedb")
|
||||
.to_string_lossy()
|
||||
);
|
||||
}
|
||||
ObjectStoreSettings::S3 { .. } => panic!("expected local slatedb store by default"),
|
||||
}
|
||||
|
||||
assert!(!settings.slatedb.disk_cache);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -542,22 +527,6 @@ enabled = true
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_disk_cache_true_from_settings() {
|
||||
let file = parse(
|
||||
r"
|
||||
_version = 1
|
||||
|
||||
[server.slatedb]
|
||||
disk_cache = true
|
||||
",
|
||||
);
|
||||
|
||||
let settings = resolve_server(&file);
|
||||
|
||||
assert!(settings.slatedb.disk_cache);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parsing_rejects_removed_server_ip_allowlist() {
|
||||
let err = r#"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ impl ServerSettings {
|
|||
pub fn with_storage_override(mut self, path: &Path) -> Self {
|
||||
self.server.storage.root = path.display().to_string();
|
||||
override_local_object_store_root(&mut self.server.artifacts.store, path, "artifacts");
|
||||
override_local_object_store_root(&mut self.server.slatedb.store, path, "slatedb");
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,8 @@ pub use server::{
|
|||
GithubIntegrationSettings, IntegrationWebhooksSettings, LogDestination, ObjectStoreSettings,
|
||||
ServerApiSettings, ServerArtifactsSettings, ServerAuthGithubSettings, ServerAuthMethod,
|
||||
ServerAuthSettings, ServerIntegrationsSettings, ServerListenSettings, ServerLoggingSettings,
|
||||
ServerNamespace, ServerSchedulerSettings, ServerSlateDbSettings, ServerStorageSettings,
|
||||
ServerWebSettings, SlackIntegrationSettings,
|
||||
ServerNamespace, ServerSchedulerSettings, ServerStorageSettings, ServerWebSettings,
|
||||
SlackIntegrationSettings,
|
||||
};
|
||||
pub use size::{ParseSizeError, Size};
|
||||
pub use workflow::WorkflowNamespace;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
//! Server domain.
|
||||
//!
|
||||
//! `[server]` is a namespace container; actual settings live in named
|
||||
//! subdomains (listen, api, web, auth, storage, artifacts, slatedb,
|
||||
//! scheduler, logging, integrations). Same-host and split-host
|
||||
//! subdomains (listen, api, web, auth, storage, artifacts, scheduler,
|
||||
//! logging, integrations). Same-host and split-host
|
||||
//! deployments use the same schema.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Duration as StdDuration;
|
||||
|
||||
use serde::de::Error as _;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use super::duration::Duration;
|
||||
use crate::SandboxProviderKind;
|
||||
|
||||
/// A structurally resolved `[server]` view for consumers.
|
||||
|
|
@ -31,7 +29,6 @@ pub struct ServerNamespace {
|
|||
pub sandbox: ServerSandboxSettings,
|
||||
pub storage: ServerStorageSettings,
|
||||
pub artifacts: ServerArtifactsSettings,
|
||||
pub slatedb: ServerSlateDbSettings,
|
||||
pub scheduler: ServerSchedulerSettings,
|
||||
pub logging: ServerLoggingSettings,
|
||||
pub integrations: ServerIntegrationsSettings,
|
||||
|
|
@ -52,7 +49,6 @@ impl ServerNamespace {
|
|||
sandbox: ServerSandboxSettings::default(),
|
||||
storage: ServerStorageSettings::default(),
|
||||
artifacts: ServerArtifactsSettings::default(),
|
||||
slatedb: ServerSlateDbSettings::default(),
|
||||
scheduler: ServerSchedulerSettings::default(),
|
||||
logging: ServerLoggingSettings::default(),
|
||||
integrations: ServerIntegrationsSettings::default(),
|
||||
|
|
@ -220,29 +216,6 @@ pub struct ServerArtifactsSettings {
|
|||
pub store: ObjectStoreSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ServerSlateDbSettings {
|
||||
pub prefix: String,
|
||||
pub store: ObjectStoreSettings,
|
||||
#[serde(
|
||||
serialize_with = "serialize_std_duration",
|
||||
deserialize_with = "deserialize_std_duration"
|
||||
)]
|
||||
pub flush_interval: StdDuration,
|
||||
pub disk_cache: bool,
|
||||
}
|
||||
|
||||
impl Default for ServerSlateDbSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
prefix: String::new(),
|
||||
store: ObjectStoreSettings::default(),
|
||||
flush_interval: StdDuration::ZERO,
|
||||
disk_cache: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ObjectStoreSettings {
|
||||
|
|
@ -348,20 +321,6 @@ where
|
|||
value.parse().map_err(D::Error::custom)
|
||||
}
|
||||
|
||||
fn serialize_std_duration<S>(value: &StdDuration, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&Duration::from_std(*value).to_string())
|
||||
}
|
||||
|
||||
fn deserialize_std_duration<'de, D>(deserializer: D) -> Result<StdDuration, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(Duration::deserialize(deserializer)?.as_std())
|
||||
}
|
||||
|
||||
/// Closed enum of object-store providers. Unknown providers hard-fail
|
||||
/// against the schema rather than passing through as opaque strings.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -457,7 +457,6 @@ export * from './server-sandbox-provider-settings';
|
|||
export * from './server-sandbox-settings';
|
||||
export * from './server-scheduler-settings';
|
||||
export * from './server-settings';
|
||||
export * from './server-slate-db-settings';
|
||||
export * from './server-storage-settings';
|
||||
export * from './server-web-settings';
|
||||
export * from './session-detail';
|
||||
|
|
|
|||
|
|
@ -39,9 +39,6 @@ import type { ServerSandboxSettings } from './server-sandbox-settings';
|
|||
import type { ServerSchedulerSettings } from './server-scheduler-settings';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ServerSlateDbSettings } from './server-slate-db-settings';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { ServerStorageSettings } from './server-storage-settings';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
|
|
@ -55,7 +52,6 @@ export interface ServerNamespace {
|
|||
'sandbox': ServerSandboxSettings;
|
||||
'storage': ServerStorageSettings;
|
||||
'artifacts': ServerArtifactsSettings;
|
||||
'slatedb': ServerSlateDbSettings;
|
||||
'scheduler': ServerSchedulerSettings;
|
||||
'logging': ServerLoggingSettings;
|
||||
'integrations': ServerIntegrationsSettings;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue