Files
archy/core/archipelago/src/container/traits.rs
T
archipelagoandClaude Fable 5 ceb319c292 feat(catalog): reload manifest overlay when a refreshed catalog changes
load_manifests() only ran at startup, so a manifest published via the
signed catalog sat dormant until the next service restart (found while
shipping the strfry manifest fixes: package.check-updates refreshed the
cache but the orchestrator kept rendering the old overlay).

- refresh_catalog now reports whether the cached bytes actually changed
  (write_cache skips identical rewrites), so unchanged hourly polls
  don't churn the manifest map.
- New ContainerOrchestrator::reload_manifests (default no-op); prod
  delegates to load_manifests — the rebuild is atomic under the state
  write lock, and the reconciler's durable user-stopped/uninstalled
  marker filters make a mid-session reload equivalent to the restart
  path that already runs on every boot.
- package.check-updates reloads on change and reports catalog_changed +
  manifests_reloaded; the hourly update-scheduler tick (and its startup
  refresh) do the same.

Tests: app_catalog 5/5 (new write_cache changed-detection test),
reconcile 16/16, knows_app 1/1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 18:54:00 -04:00

77 lines
3.5 KiB
Rust

//! Orchestrator trait — the shared surface the RPC layer talks to.
//!
//! Step 4 of the rust-orchestrator migration. Unifies the container lifecycle
//! surface of `DevContainerOrchestrator` and `ProdContainerOrchestrator` so
//! `RpcHandler` can hold `Arc<dyn ContainerOrchestrator>` and stop caring
//! which mode it is in.
//!
//! The trait takes `app_id: &str` everywhere (never a manifest path). Dev and
//! Prod both resolve app_id → manifest internally. The legacy
//! `container-install { manifest_path }` RPC shape is preserved as a concrete
//! `install_container_from_path` method on `DevContainerOrchestrator` only,
//! since that ad-hoc workflow is a dev convenience and has no prod meaning.
//!
//! See `docs/rust-orchestrator-migration.md`.
use anyhow::Result;
use archipelago_container::ContainerStatus;
use async_trait::async_trait;
/// Lifecycle + query operations every orchestrator exposes to the RPC layer.
#[async_trait]
pub trait ContainerOrchestrator: Send + Sync {
/// Build-or-pull the image, create the container, and start it. Returns the
/// podman container name that was created. Assumes the app_id corresponds
/// to a manifest the orchestrator already knows about.
async fn install(&self, app_id: &str) -> Result<String>;
/// True when this orchestrator holds a manifest for `app_id` (disk or
/// signed-catalog overlay) — i.e. `install(app_id)` would not fail with
/// "unknown app_id". Lets the RPC layer route any manifest-driven app
/// through the orchestrator without a per-app allowlist. Defaults to
/// `false` so orchestrators without a manifest registry keep routing
/// through the legacy install flow.
async fn knows_app(&self, _app_id: &str) -> bool {
false
}
/// Rebuild the in-memory manifest map (disk + signed-catalog overlay).
/// Called after a runtime catalog refresh detects changed bytes so catalog
/// manifest changes take effect without a service restart — without this,
/// `load_manifests` only runs at startup and a freshly published manifest
/// sits dormant until the next restart. Returns the merged manifest count.
/// Defaults to a no-op for orchestrators without a manifest registry.
async fn reload_manifests(&self) -> Result<usize> {
Ok(0)
}
/// Start an already-created container.
async fn start(&self, app_id: &str) -> Result<()>;
/// Stop a running container. No-op on Prod if already stopped.
async fn stop(&self, app_id: &str) -> Result<()>;
/// Stop-then-start. Best-effort: ignores stop failure.
async fn restart(&self, app_id: &str) -> Result<()>;
/// Remove the container. `preserve_data = true` keeps the volumes; `false`
/// is honored on a best-effort basis (Dev cleans, Prod leaves the volume
/// management to the data layer).
async fn remove(&self, app_id: &str, preserve_data: bool) -> Result<()>;
/// Pull/rebuild the image and recreate the container from scratch.
async fn upgrade(&self, app_id: &str) -> Result<()>;
/// Current state of a single container.
async fn status(&self, app_id: &str) -> Result<ContainerStatus>;
/// All containers this orchestrator knows about.
async fn list(&self) -> Result<Vec<ContainerStatus>>;
/// Tail the container's stdout+stderr.
async fn logs(&self, app_id: &str, lines: u32) -> Result<Vec<String>>;
/// Coarse health summary: "healthy", "unhealthy", "starting", "paused", "unknown".
async fn health(&self, app_id: &str) -> Result<String>;
}