Skip to main content

ferro_deployments/
lib.rs

1//! # ferro-deployments
2//!
3//! Immutable deployment rows and atomic pointer promotion for the Ferro framework.
4//!
5//! ## Overview
6//!
7//! Each deployment is recorded as an append-only row in the `deployments` table.
8//! A per-owner `deployment_pointers` row tracks which deployment is currently active
9//! and which was the previous one, enabling atomic promotion and rollback.
10//!
11//! Artifact storage is abstracted through [`ferro_storage`] so the same crate works
12//! with local filesystem, S3, or any other configured driver. The artifact shape is
13//! opaque — static site bundles, JSON-UI spec bundles, SSR manifests, and any other
14//! byte sequence all fit without crate-level assumptions.
15//!
16//! ## Quick Start
17//!
18//! Register both migration helpers in your `Migrator`, then use
19//! [`DeploymentConfig::from_env`] to read operator configuration.
20//!
21//! ```rust,ignore
22//! use ferro_deployments::{CreateDeploymentsTable, CreateDeploymentPointersTable};
23//!
24//! impl MigratorTrait for Migrator {
25//!     fn migrations() -> Vec<Box<dyn MigrationTrait>> {
26//!         vec![
27//!             Box::new(CreateDeploymentsTable),
28//!             Box::new(CreateDeploymentPointersTable),
29//!         ]
30//!     }
31//! }
32//! ```
33//!
34//! ## Full lifecycle example (criterion 5 — JSON spec artifact)
35//!
36//! This example stores a JSON spec bundle through the full lifecycle API:
37//! `create` → `store` → `mark_ready` → `promote` → `retrieve`. The artifact
38//! is opaque bytes — the crate makes no assumption about content type.
39//!
40//! ```rust
41//! # use ferro_deployments::{
42//! #     CreateDeploymentPointersTable, CreateDeploymentsTable, DeploymentStorage,
43//! #     Deployments, DeploymentStatus, StorageDeploymentStorage,
44//! # };
45//! # use ferro_storage::{DiskConfig, Storage, StorageConfig};
46//! # use bytes::Bytes;
47//! # use sea_orm::Database;
48//! # use sea_orm_migration::MigratorTrait;
49//! #
50//! # struct DocMigrator;
51//! # #[async_trait::async_trait]
52//! # impl MigratorTrait for DocMigrator {
53//! #     fn migrations() -> Vec<Box<dyn sea_orm_migration::MigrationTrait>> {
54//! #         vec![
55//! #             Box::new(CreateDeploymentsTable),
56//! #             Box::new(CreateDeploymentPointersTable),
57//! #         ]
58//! #     }
59//! # }
60//! #
61//! # tokio::runtime::Runtime::new().unwrap().block_on(async {
62//! // Connect to an in-memory SQLite database and run migrations.
63//! let conn = Database::connect("sqlite::memory:").await.unwrap();
64//! DocMigrator::up(&conn, None).await.unwrap();
65//!
66//! // Create a deployment handle and register a new build.
67//! let deps = Deployments::new(conn);
68//! let d = deps.create("project:demo", Some("sha-abcdef")).await.unwrap();
69//! assert_eq!(d.status, DeploymentStatus::Building);
70//!
71//! // Build a memory-backed storage adapter and store a JSON spec bundle.
72//! let storage_config = StorageConfig::new("mem").disk("mem", DiskConfig::memory());
73//! let disk = Storage::with_storage_config(storage_config).disk("mem").unwrap();
74//! let storage = StorageDeploymentStorage::new(disk);
75//!
76//! let spec_json = Bytes::from_static(br#"{"intent":"browse","fields":[]}"#);
77//! storage.store(d.id, "spec.json", spec_json.clone()).await.unwrap();
78//!
79//! // Transition the deployment to ready, recording artifact location and size.
80//! let artifact_location = format!("deployments/{}/", d.id);
81//! deps.mark_ready(d.id, &artifact_location, spec_json.len() as i64)
82//!     .await
83//!     .unwrap();
84//!
85//! // Atomically promote the deployment.
86//! let previous = deps.promote("project:demo", d.id).await.unwrap();
87//! assert!(previous.is_none(), "first promotion has no previous deployment");
88//!
89//! // Retrieve the stored artifact and verify round-trip fidelity.
90//! let retrieved = storage.retrieve(d.id, "spec.json").await.unwrap();
91//! assert_eq!(retrieved, spec_json);
92//! # });
93//! ```
94
95mod config;
96pub(crate) mod deployment;
97mod error;
98mod migration;
99pub(crate) mod promote;
100mod storage;
101
102pub use config::DeploymentConfig;
103pub use deployment::{Deployment, DeploymentStatus, Deployments};
104pub use error::Error;
105pub use migration::{CreateDeploymentPointersTable, CreateDeploymentsTable};
106pub use storage::{preview_url, DeploymentStorage, StorageDeploymentStorage};