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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,55 @@ jobs:
EXTENDDB_TEST_PG_ADMIN_CONNECTION_STRING: postgresql://postgres:devpass@127.0.0.1:5432
run: devtools/run-tests --extenddb --pytest --comprehensive --parallel --filter "not import_export"

run-integration-sqlite:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

- name: Build release (SQLite backend)
run: cargo build --release -p extenddb --no-default-features --features sqlite

- name: Initialize ExtendDB
id: init
run: |
output=$(./target/release/extenddb init --backend sqlite --config extenddb.toml 2>&1)
echo "$output"
password=$(echo "$output" | grep -oP 'Password: \K\S+')
echo "admin_password=$password" >> "$GITHUB_OUTPUT"

- name: Start ExtendDB
run: |
./target/release/extenddb serve --config extenddb.toml --foreground &
for i in $(seq 1 30); do
if curl -sk https://127.0.0.1:18443/health | grep -q healthy; then
echo "Server ready"
exit 0
fi
sleep 1
done
echo "Server failed to start"
exit 1

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install Python dependencies
run: pip install -r requirements.txt

- name: Run integration tests
env:
EXTENDDB_TEST_ENDPOINT: https://127.0.0.1:18443
EXTENDDB_ADMIN_USER: admin
EXTENDDB_ADMIN_PASSWORD: ${{ steps.init.outputs.admin_password }}
run: devtools/run-tests --extenddb --pytest --comprehensive --parallel --filter "not import_export"

run-rust-integration:
runs-on: ubuntu-latest
services:
Expand Down Expand Up @@ -158,11 +207,12 @@ jobs:

integration:
runs-on: ubuntu-latest
needs: [run-integration, run-rust-integration]
needs: [run-integration, run-integration-sqlite, run-rust-integration]
if: always()
steps:
- run: |
if [ "${{ needs.run-integration.result }}" != "success" ] || \
[ "${{ needs.run-integration-sqlite.result }}" != "success" ] || \
[ "${{ needs.run-rust-integration.result }}" != "success" ]; then
exit 1
fi
29 changes: 29 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"crates/engine",
"crates/storage",
"crates/storage-postgres",
"crates/storage-sqlite",
"crates/auth",
"crates/server",
"crates/bin",
Expand All @@ -26,6 +27,7 @@ extenddb-cache = { path = "crates/cache" }
extenddb-engine = { path = "crates/engine" }
extenddb-storage = { path = "crates/storage" }
extenddb-storage-postgres = { path = "crates/storage-postgres" }
extenddb-storage-sqlite = { path = "crates/storage-sqlite" }
extenddb-auth = { path = "crates/auth" }
extenddb-server = { path = "crates/server" }

Expand Down
9 changes: 9 additions & 0 deletions crates/bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ path = "src/main.rs"
[features]
default = ["postgres"]
postgres = ["extenddb-storage-postgres"]
sqlite = ["extenddb-storage-sqlite"]
# Build the SQLite backend in its zero-config, ephemeral in-memory mode.
sqlite-memory = ["sqlite", "extenddb-storage-sqlite/memory"]
# Developer mode: plain HTTP on loopback, open authorization (SigV4 still
# enforced), seeded dev credential. A build-time profile for local/CI only; it
# requires a dev backend (`sqlite`/`sqlite-memory`) and fails to build with a
# production backend like `postgres` (enforced at compile time in `backends.rs`).
dev-mode = []

[dependencies]
extenddb-auth = { workspace = true }
Expand All @@ -22,6 +30,7 @@ extenddb-core = { workspace = true }
extenddb-engine = { workspace = true }
extenddb-storage = { workspace = true }
extenddb-storage-postgres = { workspace = true, optional = true }
extenddb-storage-sqlite = { workspace = true, optional = true }
extenddb-server = { workspace = true }
tokio = { workspace = true }
anyhow = { workspace = true }
Expand Down
47 changes: 47 additions & 0 deletions crates/bin/src/backends.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2026 ExtendDB contributors
// SPDX-License-Identifier: Apache-2.0

//! Backend linking.
//!
//! Storage backends register themselves at load time via `inventory::submit!`
//! (bootstrapper, operations engine, config deserializer, server components,
//! and the settings/diagnostics store factories). Those registrations are
//! retained only if the backend crate is linked into the binary, and the linker
//! links a crate only when it is referenced. An enabled-but-unreferenced
//! optional dependency therefore has its registrations silently dead-stripped.
//!
//! [`link_all`] provides exactly one reference per enabled backend, so every
//! backend selected by a Cargo feature is linked and registered. This is the
//! single, explicit place that owns the backend-to-binary linking contract.
//!
//! To add a backend:
//! 1. add it as an optional dependency and a feature in `Cargo.toml`,
//! 2. implement the storage traits and `inventory::submit!` registrations and
//! expose a `pub fn link()` in the backend crate,
//! 3. add a feature-gated `link()` call below.

// Developer mode relaxes the security posture (plain HTTP on loopback, open
// authorization). It is a dev/CI-only profile and must be built only with a
// dev/CI-suitable backend. Rather than denying each production backend by name
// (every backend is a production backend unless proven otherwise, so a deny-list
// would have to grow with each new one), require a known dev backend: dev-mode
// compiles only when `sqlite` is enabled. `sqlite-memory` enables `sqlite`, so it
// is covered too; postgres — or any future production backend — fails the build,
// so there is no path by which a production deployment can serve in dev mode.
#[cfg(all(feature = "dev-mode", not(feature = "sqlite")))]
compile_error!(
"the `dev-mode` feature requires a dev/CI backend such as `sqlite`; it must \
not be built with a production backend like `postgres` (build with \
`--no-default-features --features sqlite-memory,dev-mode`)"
);

/// Reference every backend enabled at build time so its registrations link.
///
/// Call once, before any code that resolves a backend by name (which is all
/// commands, since even `version` enumerates registered backends).
pub fn link_all() {
#[cfg(feature = "postgres")]
extenddb_storage_postgres::link();
#[cfg(feature = "sqlite")]
extenddb_storage_sqlite::link();
}
Loading
Loading