From 864555bcf04a068d36ae5dee722205fc20b6baa4 Mon Sep 17 00:00:00 2001 From: J M <2364004+Blu-J@users.noreply.github.com> Date: Mon, 9 May 2022 14:53:39 -0600 Subject: [PATCH] Feat bulk locking (#1422) * Feat: Multi-lock capabilities add to config * wip: RPC.rs fixes, new combinatoric * wip: changes * chore: More things that are bulk * fix: Saving * chore: Remove a dyn object * chore: Add tests + remove unused * Fix/feat bulk locking (#1427) * fix: health check * fix: start/stop service * fix: install/uninstall services * chore: Fix the notifications * fix: Version * fix: Version as serde * chore: Update to latest patch db * chore: Change the htLock to something that makes more sense * chore: Fix the rest of the ht * "chore: More ht_lock": --- backend/Cargo.toml | 2 +- backend/src/backup/mod.rs | 3 +- backend/src/bin/embassyd.rs | 6 +- backend/src/config/mod.rs | 521 ++++++++++++++++++++-------- backend/src/config/spec.rs | 317 ++++++++++++----- backend/src/context/rpc.rs | 145 +++++--- backend/src/control.rs | 145 +++++--- backend/src/db/model.rs | 1 + backend/src/db/package.rs | 76 ++++- backend/src/dependencies.rs | 582 +++++++++++++++++++++----------- backend/src/disk/fsck.rs | 2 +- backend/src/init.rs | 72 +++- backend/src/install/cleanup.rs | 288 ++++++++++------ backend/src/install/mod.rs | 238 ++++++++++--- backend/src/install/update.rs | 3 + backend/src/manager/health.rs | 9 +- backend/src/s9pk/mod.rs | 1 - backend/src/util/logger.rs | 1 - backend/src/util/mod.rs | 3 + backend/src/version/mod.rs | 128 +++++-- backend/src/version/v0_3_0.rs | 1 + backend/src/version/v0_3_0_1.rs | 1 + backend/src/version/v0_3_0_2.rs | 1 + backend/src/version/v0_3_0_3.rs | 1 + frontend/package-lock.json | 274 +++++++++++++++ patch-db | 2 +- 26 files changed, 2080 insertions(+), 743 deletions(-) diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 3b0b9447c..f8089b050 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -85,7 +85,7 @@ num_enum = "0.5.4" openssh-keys = "0.5.0" openssl = { version = "0.10.36", features = ["vendored"] } patch-db = { version = "*", path = "../patch-db/patch-db", features = [ - "trace", + "trace" ] } pbkdf2 = "0.9.0" pin-project = "1.0.8" diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index e09f1cdf2..23446b7ce 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -240,7 +240,8 @@ impl BackupActions { .get(db, true) .await?; - reconfigure_dependents_with_live_pointers(ctx, db, &entry).await?; + let receipts = crate::config::ConfigReceipts::new(db).await?; + reconfigure_dependents_with_live_pointers(ctx, db, &receipts, &entry).await?; Ok(()) } diff --git a/backend/src/bin/embassyd.rs b/backend/src/bin/embassyd.rs index b9d693c8f..156c66097 100644 --- a/backend/src/bin/embassyd.rs +++ b/backend/src/bin/embassyd.rs @@ -81,7 +81,11 @@ async fn inner_main(cfg_path: Option<&str>) -> Result, Error> { .expect("send shutdown signal"); }); - rpc_ctx.set_nginx_conf(&mut rpc_ctx.db.handle()).await?; + let mut db = rpc_ctx.db.handle(); + let receipts = embassy::context::rpc::RpcSetNginxReceipts::new(&mut db).await?; + + rpc_ctx.set_nginx_conf(&mut db, receipts).await?; + drop(db); let auth = auth(rpc_ctx.clone()); let ctx = rpc_ctx.clone(); let server = rpc_server!({ diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index 2bcd052a0..14f55c464 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -6,7 +6,7 @@ use color_eyre::eyre::eyre; use futures::future::{BoxFuture, FutureExt}; use indexmap::IndexSet; use itertools::Itertools; -use patch_db::{DbHandle, LockType}; +use patch_db::{DbHandle, LockReceipt, LockTarget, LockTargetId, LockType, Verifier}; use rand::SeedableRng; use regex::Regex; use rpc_toolkit::command; @@ -14,17 +14,20 @@ use serde_json::Value; use tracing::instrument; use crate::context::RpcContext; -use crate::db::model::CurrentDependencyInfo; -use crate::db::util::WithRevision; use crate::dependencies::{ add_dependent_to_current_dependents_lists, break_transitive, heal_all_dependents_transitive, - BreakageRes, DependencyError, DependencyErrors, TaggedDependencyError, + BreakageRes, DependencyConfig, DependencyError, DependencyErrors, TaggedDependencyError, }; -use crate::install::cleanup::remove_from_current_dependents_lists; use crate::s9pk::manifest::{Manifest, PackageId}; -use crate::util::display_none; use crate::util::serde::{display_serializable, parse_stdin_deserializable, IoFormat}; -use crate::{Error, ResultExt as _}; +use crate::Error; +use crate::{db::model::CurrentDependencyInfo, dependencies::DependencyReceipt}; +use crate::{db::util::WithRevision, dependencies::Dependencies}; +use crate::{ + dependencies::BreakTransitiveReceipts, + install::cleanup::{remove_from_current_dependents_lists, UpdateDependencyReceipts}, +}; +use crate::{dependencies::TryHealReceipts, util::display_none}; pub mod action; pub mod spec; @@ -33,8 +36,11 @@ pub mod util; pub use spec::{ConfigSpec, Defaultable}; use util::NumRange; -use self::action::ConfigRes; use self::spec::{PackagePointerSpec, ValueSpecPointer}; +use self::{ + action::{ConfigActions, ConfigRes}, + spec::ConfigPointerReceipts, +}; pub type Config = serde_json::Map; pub trait TypeOf { @@ -163,6 +169,56 @@ pub fn config(#[arg] id: PackageId) -> Result { Ok(id) } +pub struct ConfigGetReceipts { + manifest_volumes: LockReceipt, + manifest_version: LockReceipt, + manifest_config: LockReceipt, ()>, +} + +impl ConfigGetReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks, id); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + id: &PackageId, + ) -> impl FnOnce(&Verifier) -> Result { + + let manifest_version = crate::db::DatabaseModel::new() + .package_data() + .idx_model(id) + .and_then(|x| x.installed()) + .map(|x| x.manifest().version()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let manifest_volumes = crate::db::DatabaseModel::new() + .package_data() + .idx_model(id) + .and_then(|x| x.installed()) + .map(|x| x.manifest().volumes()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let manifest_config = crate::db::DatabaseModel::new() + .package_data() + .idx_model(id) + .and_then(|x| x.installed()) + .map(|x| x.manifest().config()) + .make_locker(LockType::Write) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + manifest_volumes: manifest_volumes.verify(skeleton_key)?, + manifest_version: manifest_version.verify(skeleton_key)?, + manifest_config: manifest_config.verify(skeleton_key)?, + }) + } + } +} + #[command(display(display_serializable))] #[instrument(skip(ctx))] pub async fn get( @@ -173,29 +229,16 @@ pub async fn get( format: Option, ) -> Result { let mut db = ctx.db.handle(); - let pkg_model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .and_then(|m| m.installed()) - .expect(&mut db) - .await - .with_kind(crate::ErrorKind::NotFound)?; - let action = pkg_model - .clone() - .manifest() - .config() - .get(&mut db, true) + let receipts = ConfigGetReceipts::new(&mut db, &id).await?; + let action = receipts + .manifest_config + .get(&mut db) .await? - .to_owned() .ok_or_else(|| Error::new(eyre!("{} has no config", id), crate::ErrorKind::NotFound))?; - let version = pkg_model - .clone() - .manifest() - .version() - .get(&mut db, true) - .await?; - let volumes = pkg_model.manifest().volumes().get(&mut db, true).await?; - action.get(&ctx, &id, &*version, &*volumes).await + + let volumes = receipts.manifest_volumes.get(&mut db).await?; + let version = receipts.manifest_version.get(&mut db).await?; + action.get(&ctx, &id, &version, &volumes).await } #[command( @@ -215,6 +258,147 @@ pub fn set( Ok((id, config, timeout.map(|d| *d), expire_id)) } +/// So, the new locking finds all the possible locks and lifts them up into a bundle of locks. +/// Then this bundle will be passed down into the functions that will need to touch the db, and +/// instead of doing the locks down in the system, we have already done the locks and can +/// do the operation on the db. +/// An UnlockedLock has two types, the type of setting and getting from the db, and the second type +/// is the keys that we need to insert on getting/setting because we have included wild cards into the paths. +pub struct ConfigReceipts { + pub dependency_receipt: DependencyReceipt, + pub config_receipts: ConfigPointerReceipts, + pub update_dependency_receipts: UpdateDependencyReceipts, + pub try_heal_receipts: TryHealReceipts, + pub break_transitive_receipts: BreakTransitiveReceipts, + configured: LockReceipt, + config_actions: LockReceipt, + dependencies: LockReceipt, + volumes: LockReceipt, + version: LockReceipt, + manifest: LockReceipt, + system_pointers: LockReceipt, String>, + pub current_dependents: LockReceipt, String>, + dependency_errors: LockReceipt, + manifest_dependencies_config: LockReceipt, +} + +impl ConfigReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { + let dependency_receipt = DependencyReceipt::setup(locks); + let config_receipts = ConfigPointerReceipts::setup(locks); + let update_dependency_receipts = UpdateDependencyReceipts::setup(locks); + let break_transitive_receipts = BreakTransitiveReceipts::setup(locks); + let try_heal_receipts = TryHealReceipts::setup(locks); + + let configured: LockTarget = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.status().configured()) + .make_locker(LockType::Write) + .add_to_keys(locks); + + let config_actions = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .and_then(|x| x.manifest().config()) + .make_locker(LockType::Read) + .add_to_keys(locks); + + let dependencies = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest().dependencies()) + .make_locker(LockType::Read) + .add_to_keys(locks); + + let volumes = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest().volumes()) + .make_locker(LockType::Read) + .add_to_keys(locks); + + let version = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest().version()) + .make_locker(LockType::Read) + .add_to_keys(locks); + + let manifest = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest()) + .make_locker(LockType::Read) + .add_to_keys(locks); + + let system_pointers = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.system_pointers()) + .make_locker(LockType::Write) + .add_to_keys(locks); + + let current_dependents = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.current_dependents()) + .make_locker(LockType::Write) + .add_to_keys(locks); + + let dependency_errors = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.status().dependency_errors()) + .make_locker(LockType::Write) + .add_to_keys(locks); + + let manifest_dependencies_config = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .and_then(|x| x.manifest().dependencies().star().config()) + .make_locker(LockType::Write) + .add_to_keys(locks); + + move |skeleton_key| { + Ok(Self { + dependency_receipt: dependency_receipt(skeleton_key)?, + config_receipts: config_receipts(skeleton_key)?, + try_heal_receipts: try_heal_receipts(skeleton_key)?, + break_transitive_receipts: break_transitive_receipts(skeleton_key)?, + update_dependency_receipts: update_dependency_receipts(skeleton_key)?, + configured: configured.verify(skeleton_key)?, + config_actions: config_actions.verify(skeleton_key)?, + dependencies: dependencies.verify(skeleton_key)?, + volumes: volumes.verify(skeleton_key)?, + version: version.verify(skeleton_key)?, + manifest: manifest.verify(skeleton_key)?, + system_pointers: system_pointers.verify(skeleton_key)?, + current_dependents: current_dependents.verify(skeleton_key)?, + dependency_errors: dependency_errors.verify(skeleton_key)?, + manifest_dependencies_config: manifest_dependencies_config.verify(skeleton_key)?, + }) + } + } +} + #[command(rename = "dry", display(display_serializable))] #[instrument(skip(ctx))] pub async fn set_dry( @@ -229,6 +413,7 @@ pub async fn set_dry( let mut db = ctx.db.handle(); let mut tx = db.begin().await?; let mut breakages = BTreeMap::new(); + let locks = ConfigReceipts::new(&mut tx).await?; configure( &ctx, &mut tx, @@ -238,20 +423,11 @@ pub async fn set_dry( true, &mut BTreeMap::new(), &mut breakages, + &locks, ) .await?; - crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .expect(&mut tx) - .await? - .installed() - .expect(&mut tx) - .await? - .status() - .configured() - .put(&mut tx, &true) - .await?; + + locks.configured.set(&mut tx, true, &id).await?; tx.abort().await?; Ok(BreakageRes(breakages)) } @@ -264,6 +440,7 @@ pub async fn set_impl( let mut db = ctx.db.handle(); let mut tx = db.begin().await?; let mut breakages = BTreeMap::new(); + let locks = ConfigReceipts::new(&mut tx).await?; configure( &ctx, &mut tx, @@ -273,6 +450,7 @@ pub async fn set_impl( false, &mut BTreeMap::new(), &mut breakages, + &locks, ) .await?; Ok(WithRevision { @@ -281,34 +459,27 @@ pub async fn set_impl( }) } -#[instrument(skip(ctx, db))] -pub async fn configure( +#[instrument(skip(ctx, db, receipts))] +pub async fn configure<'a, Db: DbHandle>( ctx: &RpcContext, - db: &mut Db, + db: &'a mut Db, id: &PackageId, config: Option, timeout: &Option, dry_run: bool, overrides: &mut BTreeMap, breakages: &mut BTreeMap, + receipts: &ConfigReceipts, ) -> Result<(), Error> { - configure_rec(ctx, db, id, config, timeout, dry_run, overrides, breakages).await?; - crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .expect(db) - .await? - .installed() - .expect(db) - .await? - .status() - .configured() - .put(db, &true) - .await?; + configure_rec( + ctx, db, id, config, timeout, dry_run, overrides, breakages, receipts, + ) + .await?; + receipts.configured.set(db, true, &id).await?; Ok(()) } -#[instrument(skip(ctx, db))] +#[instrument(skip(ctx, db, receipts))] pub fn configure_rec<'a, Db: DbHandle>( ctx: &'a RpcContext, db: &'a mut Db, @@ -318,48 +489,33 @@ pub fn configure_rec<'a, Db: DbHandle>( dry_run: bool, overrides: &'a mut BTreeMap, breakages: &'a mut BTreeMap, + receipts: &'a ConfigReceipts, ) -> BoxFuture<'a, Result<(), Error>> { async move { - crate::db::DatabaseModel::new() - .package_data() - .lock(db, LockType::Write) - .await?; // fetch data from db - let pkg_model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|m| m.installed()) - .expect(db) - .await - .with_kind(crate::ErrorKind::NotFound)?; - let action = pkg_model - .clone() - .manifest() - .config() - .get(db, true) + let action = receipts + .config_actions + .get(db, id) .await? - .to_owned() - .ok_or_else(|| Error::new(eyre!("{} has no config", id), crate::ErrorKind::NotFound))?; - let version = pkg_model.clone().manifest().version().get(db, true).await?; - let dependencies = pkg_model - .clone() - .manifest() - .dependencies() - .get(db, true) - .await?; - let volumes = pkg_model.clone().manifest().volumes().get(db, true).await?; - let is_needs_config = !*pkg_model - .clone() - .status() - .configured() - .get(db, true) - .await?; + .ok_or_else(not_found)?; + let dependencies = receipts + .dependencies + .get(db, id) + .await? + .ok_or_else(not_found)?; + let volumes = receipts.volumes.get(db, id).await?.ok_or_else(not_found)?; + let is_needs_config = !receipts + .configured + .get(db, id) + .await? + .ok_or_else(not_found)?; + let version = receipts.version.get(db, id).await?.ok_or_else(not_found)?; // get current config and current spec let ConfigRes { config: old_config, spec, - } = action.get(ctx, id, &*version, &*volumes).await?; + } = action.get(ctx, id, &version, &volumes).await?; // determine new config to use let mut config = if let Some(config) = config.or_else(|| old_config.clone()) { @@ -368,24 +524,26 @@ pub fn configure_rec<'a, Db: DbHandle>( spec.gen(&mut rand::rngs::StdRng::from_entropy(), timeout)? }; - let manifest = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|m| m.installed()) - .map::<_, Manifest>(|i| i.manifest()) - .expect(db) - .await? - .get(db, true) - .await - .with_kind(crate::ErrorKind::NotFound)?; + let manifest = receipts.manifest.get(db, id).await?.ok_or_else(not_found)?; - spec.validate(&*manifest)?; + spec.validate(&manifest)?; spec.matches(&config)?; // check that new config matches spec - spec.update(ctx, db, &*manifest, &*overrides, &mut config) - .await?; // dereference pointers in the new config + spec.update( + ctx, + db, + &manifest, + &*overrides, + &mut config, + &receipts.config_receipts, + ) + .await?; // dereference pointers in the new config // create backreferences to pointers - let mut sys = pkg_model.clone().system_pointers().get_mut(db).await?; + let mut sys = receipts + .system_pointers + .get(db, &id) + .await? + .ok_or_else(not_found)?; sys.truncate(0); let mut current_dependencies: BTreeMap = dependencies .0 @@ -418,12 +576,12 @@ pub fn configure_rec<'a, Db: DbHandle>( ValueSpecPointer::System(s) => sys.push(s), } } - sys.save(db).await?; + receipts.system_pointers.set(db, sys, &id).await?; let signal = if !dry_run { // run config action let res = action - .set(ctx, id, &*version, &*dependencies, &*volumes, &config) + .set(ctx, id, &version, &dependencies, &volumes, &config) .await?; // track dependencies with no pointers @@ -459,50 +617,63 @@ pub fn configure_rec<'a, Db: DbHandle>( }; // update dependencies - let mut deps = pkg_model.clone().current_dependencies().get_mut(db).await?; - remove_from_current_dependents_lists(db, id, deps.keys()).await?; // remove previous - add_dependent_to_current_dependents_lists(db, id, ¤t_dependencies).await?; // add new + remove_from_current_dependents_lists( + db, + id, + current_dependencies.keys(), + &receipts.current_dependents, + ) + .await?; // remove previous + add_dependent_to_current_dependents_lists( + db, + id, + ¤t_dependencies, + &receipts.current_dependents, + ) + .await?; // add new current_dependencies.remove(id); - *deps = current_dependencies.clone(); - deps.save(db).await?; - let mut errs = pkg_model - .clone() - .status() - .dependency_errors() - .get_mut(db) + receipts + .current_dependents + .set(db, current_dependencies.clone(), &id) .await?; - *errs = DependencyErrors::init(ctx, db, &*manifest, ¤t_dependencies).await?; - errs.save(db).await?; + + let errs = receipts + .dependency_errors + .get(db, &id) + .await? + .ok_or_else(not_found)?; + tracing::warn!("Dependency Errors: {:?}", errs); + let errs = DependencyErrors::init( + ctx, + db, + &manifest, + ¤t_dependencies, + &receipts.dependency_receipt.try_heal, + ) + .await?; + receipts.dependency_errors.set(db, errs, &id).await?; // cache current config for dependents overrides.insert(id.clone(), config.clone()); // handle dependents - let dependents = pkg_model.clone().current_dependents().get(db, true).await?; + let dependents = current_dependencies; let prev = if is_needs_config { None } else { old_config } .map(Value::Object) .unwrap_or_default(); let next = Value::Object(config.clone()); for (dependent, dep_info) in dependents.iter().filter(|(dep_id, _)| dep_id != &id) { // check if config passes dependent check - let dependent_model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependent) - .and_then(|pkg| pkg.installed()) - .expect(db) - .await?; - if let Some(cfg) = &*dependent_model - .clone() - .manifest() - .dependencies() - .idx_model(id) - .expect(db) - .await? - .config() - .get(db, true) + if let Some(cfg) = receipts + .manifest_dependencies_config + .get(db, (&dependent, &id)) .await? { - let manifest = dependent_model.clone().manifest().get(db, true).await?; + let manifest = receipts + .manifest + .get(db, &dependent) + .await? + .ok_or_else(not_found)?; if let Err(error) = cfg .check( ctx, @@ -514,7 +685,15 @@ pub fn configure_rec<'a, Db: DbHandle>( .await? { let dep_err = DependencyError::ConfigUnsatisfied { error }; - break_transitive(db, dependent, id, dep_err, breakages).await?; + break_transitive( + db, + dependent, + id, + dep_err, + breakages, + &receipts.break_transitive_receipts, + ) + .await?; } // handle backreferences @@ -523,6 +702,7 @@ pub fn configure_rec<'a, Db: DbHandle>( if cfg_ptr.select(&next) != cfg_ptr.select(&prev) { if let Err(e) = configure_rec( ctx, db, dependent, None, timeout, dry_run, overrides, breakages, + receipts, ) .await { @@ -535,6 +715,7 @@ pub fn configure_rec<'a, Db: DbHandle>( error: format!("{}", e), }, breakages, + &receipts.break_transitive_receipts, ) .await?; } else { @@ -544,7 +725,7 @@ pub fn configure_rec<'a, Db: DbHandle>( } } } - heal_all_dependents_transitive(ctx, db, id).await?; + heal_all_dependents_transitive(ctx, db, id, &receipts.dependency_receipt).await?; } } @@ -568,3 +749,67 @@ pub fn configure_rec<'a, Db: DbHandle>( } .boxed() } +#[instrument] +pub fn not_found() -> Error { + Error::new(eyre!("Could not find"), crate::ErrorKind::Incoherent) +} + +/// We want to have a double check that the paths are what we expect them to be. +/// Found that earlier the paths where not what we expected them to be. +#[tokio::test] +async fn ensure_creation_of_config_paths_makes_sense() { + let mut fake = patch_db::test_utils::NoOpDb(); + let config_locks = ConfigReceipts::new(&mut fake).await.unwrap(); + assert_eq!( + &format!("{}", config_locks.configured.lock.glob), + "/package-data/*/installed/status/configured" + ); + assert_eq!( + &format!("{}", config_locks.config_actions.lock.glob), + "/package-data/*/installed/manifest/config" + ); + assert_eq!( + &format!("{}", config_locks.dependencies.lock.glob), + "/package-data/*/installed/manifest/dependencies" + ); + assert_eq!( + &format!("{}", config_locks.volumes.lock.glob), + "/package-data/*/installed/manifest/volumes" + ); + assert_eq!( + &format!("{}", config_locks.version.lock.glob), + "/package-data/*/installed/manifest/version" + ); + assert_eq!( + &format!("{}", config_locks.volumes.lock.glob), + "/package-data/*/installed/manifest/volumes" + ); + assert_eq!( + &format!("{}", config_locks.manifest.lock.glob), + "/package-data/*/installed/manifest" + ); + assert_eq!( + &format!("{}", config_locks.manifest.lock.glob), + "/package-data/*/installed/manifest" + ); + assert_eq!( + &format!("{}", config_locks.system_pointers.lock.glob), + "/package-data/*/installed/system-pointers" + ); + assert_eq!( + &format!("{}", config_locks.current_dependents.lock.glob), + "/package-data/*/installed/current-dependents" + ); + assert_eq!( + &format!("{}", config_locks.dependency_errors.lock.glob), + "/package-data/*/installed/status/dependency-errors" + ); + assert_eq!( + &format!("{}", config_locks.manifest_dependencies_config.lock.glob), + "/package-data/*/installed/manifest/dependencies/*/config" + ); + assert_eq!( + &format!("{}", config_locks.system_pointers.lock.glob), + "/package-data/*/installed/system-pointers" + ); +} diff --git a/backend/src/config/spec.rs b/backend/src/config/spec.rs index 22f763463..1a01f3af2 100644 --- a/backend/src/config/spec.rs +++ b/backend/src/config/spec.rs @@ -12,7 +12,7 @@ use async_trait::async_trait; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use jsonpath_lib::Compiled as CompiledJsonPath; -use patch_db::{DbHandle, OptionModel}; +use patch_db::{DbHandle, LockReceipt, LockType}; use rand::{CryptoRng, Rng}; use regex::Regex; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -44,6 +44,7 @@ pub trait ValueSpec { manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError>; // returns all pointers that are live in the provided config fn pointers(&self, value: &Value) -> Result, NoMatchWithPath>; @@ -160,9 +161,10 @@ where manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { self.inner - .update(ctx, db, manifest, config_overrides, value) + .update(ctx, db, manifest, config_overrides, value, receipts) .await } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -204,9 +206,10 @@ where manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { self.inner - .update(ctx, db, manifest, config_overrides, value) + .update(ctx, db, manifest, config_overrides, value, receipts) .await } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -281,9 +284,10 @@ where manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { self.inner - .update(ctx, db, manifest, config_overrides, value) + .update(ctx, db, manifest, config_overrides, value, receipts) .await } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -343,7 +347,7 @@ pub enum ValueSpecAny { Pointer(WithDescription), } impl ValueSpecAny { - pub fn name<'a>(&'a self) -> &'a str { + pub fn name(&self) -> &'_ str { match self { ValueSpecAny::Boolean(b) => b.name.as_str(), ValueSpecAny::Enum(e) => e.name.as_str(), @@ -395,16 +399,41 @@ impl ValueSpec for ValueSpecAny { manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { match self { - ValueSpecAny::Boolean(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecAny::Enum(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecAny::List(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecAny::Number(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecAny::Object(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecAny::String(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecAny::Union(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecAny::Pointer(a) => a.update(ctx, db, manifest, config_overrides, value).await, + ValueSpecAny::Boolean(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecAny::Enum(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecAny::List(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecAny::Number(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecAny::Object(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecAny::String(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecAny::Union(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecAny::Pointer(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } } } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -489,6 +518,7 @@ impl ValueSpec for ValueSpecBoolean { _manifest: &Manifest, _config_overrides: &BTreeMap, _value: &mut Value, + _receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { Ok(()) } @@ -578,6 +608,7 @@ impl ValueSpec for ValueSpecEnum { _manifest: &Manifest, _config_overrides: &BTreeMap, _value: &mut Value, + _receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { Ok(()) } @@ -664,12 +695,13 @@ where manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { if let Value::Array(ref mut ls) = value { for (i, val) in ls.into_iter().enumerate() { match self .spec - .update(ctx, db, manifest, config_overrides, val) + .update(ctx, db, manifest, config_overrides, val, receipts) .await { Err(ConfigurationError::NoMatch(e)) => { @@ -771,13 +803,29 @@ impl ValueSpec for ValueSpecList { manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { match self { - ValueSpecList::Enum(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecList::Number(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecList::Object(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecList::String(a) => a.update(ctx, db, manifest, config_overrides, value).await, - ValueSpecList::Union(a) => a.update(ctx, db, manifest, config_overrides, value).await, + ValueSpecList::Enum(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecList::Number(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecList::Object(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecList::String(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } + ValueSpecList::Union(a) => { + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await + } } } fn pointers(&self, value: &Value) -> Result, NoMatchWithPath> { @@ -898,6 +946,7 @@ impl ValueSpec for ValueSpecNumber { _manifest: &Manifest, _config_overrides: &BTreeMap, _value: &mut Value, + _receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { Ok(()) } @@ -961,10 +1010,11 @@ impl ValueSpec for ValueSpecObject { manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { if let Value::Object(o) = value { self.spec - .update(ctx, db, manifest, config_overrides, o) + .update(ctx, db, manifest, config_overrides, o, receipts) .await } else { Err(ConfigurationError::NoMatch(NoMatchWithPath::new( @@ -1063,16 +1113,20 @@ impl ConfigSpec { manifest: &Manifest, config_overrides: &BTreeMap, cfg: &mut Config, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { for (k, vs) in self.0.iter() { match cfg.get_mut(k) { None => { let mut v = Value::Null; - vs.update(ctx, db, manifest, config_overrides, &mut v) + vs.update(ctx, db, manifest, config_overrides, &mut v, receipts) .await?; cfg.insert(k.clone(), v); } - Some(v) => match vs.update(ctx, db, manifest, config_overrides, v).await { + Some(v) => match vs + .update(ctx, db, manifest, config_overrides, v, receipts) + .await + { Err(ConfigurationError::NoMatch(e)) => { Err(ConfigurationError::NoMatch(e.prepend(k.clone()))) } @@ -1160,6 +1214,7 @@ impl ValueSpec for ValueSpecString { _manifest: &Manifest, _config_overrides: &BTreeMap, _value: &mut Value, + _receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { Ok(()) } @@ -1192,10 +1247,7 @@ impl DefaultableWith for ValueSpecString { let candidate = spec.gen(rng); match (spec, &self.pattern) { (DefaultString::Entropy(_), Some(pattern)) - if !pattern.pattern.is_match(&candidate) => - { - () - } + if !pattern.pattern.is_match(&candidate) => {} _ => { return Ok(Value::String(candidate)); } @@ -1371,6 +1423,7 @@ impl ValueSpec for ValueSpecUnion { manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { if let Value::Object(o) = value { match o.get(&self.tag.id) { @@ -1381,7 +1434,10 @@ impl ValueSpec for ValueSpecUnion { None => Err(ConfigurationError::NoMatch(NoMatchWithPath::new( MatchError::Union(tag.clone(), self.variants.keys().cloned().collect()), ))), - Some(spec) => spec.update(ctx, db, manifest, config_overrides, o).await, + Some(spec) => { + spec.update(ctx, db, manifest, config_overrides, o, receipts) + .await + } }, Some(other) => Err(ConfigurationError::NoMatch( NoMatchWithPath::new(MatchError::InvalidType("string", other.type_of())) @@ -1513,13 +1569,16 @@ impl ValueSpec for ValueSpecPointer { manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { match self { ValueSpecPointer::Package(a) => { - a.update(ctx, db, manifest, config_overrides, value).await + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await } ValueSpecPointer::System(a) => { - a.update(ctx, db, manifest, config_overrides, value).await + a.update(ctx, db, manifest, config_overrides, value, receipts) + .await } } } @@ -1563,12 +1622,17 @@ impl PackagePointerSpec { db: &mut Db, manifest: &Manifest, config_overrides: &BTreeMap, + receipts: &ConfigPointerReceipts, ) -> Result { match &self { PackagePointerSpec::TorKey(key) => key.deref(&manifest.id, &ctx.secret_store).await, - PackagePointerSpec::TorAddress(tor) => tor.deref(db).await, - PackagePointerSpec::LanAddress(lan) => lan.deref(db).await, - PackagePointerSpec::Config(cfg) => cfg.deref(ctx, db, config_overrides).await, + PackagePointerSpec::TorAddress(tor) => { + tor.deref(db, &receipts.interface_addresses_receipt).await + } + PackagePointerSpec::LanAddress(lan) => { + lan.deref(db, &receipts.interface_addresses_receipt).await + } + PackagePointerSpec::Config(cfg) => cfg.deref(ctx, db, config_overrides, receipts).await, } } } @@ -1616,8 +1680,11 @@ impl ValueSpec for PackagePointerSpec { manifest: &Manifest, config_overrides: &BTreeMap, value: &mut Value, + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { - *value = self.deref(ctx, db, manifest, config_overrides).await?; + *value = self + .deref(ctx, db, manifest, config_overrides, receipts) + .await?; Ok(()) } fn pointers(&self, _value: &Value) -> Result, NoMatchWithPath> { @@ -1640,16 +1707,17 @@ pub struct TorAddressPointer { interface: InterfaceId, } impl TorAddressPointer { - async fn deref(&self, db: &mut Db) -> Result { - let addr = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&self.package_id) - .and_then(|pde| pde.installed()) - .and_then(|installed| installed.interface_addresses().idx_model(&self.interface)) - .and_then(|addresses| addresses.tor_address()) - .get(db, true) + async fn deref( + &self, + db: &mut Db, + receipt: &InterfaceAddressesReceipt, + ) -> Result { + let addr = receipt + .interface_addresses + .get(db, (&self.package_id, &self.interface)) .await - .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; + .map_err(|e| ConfigurationError::SystemError(Error::from(e)))? + .and_then(|addresses| addresses.tor_address); Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null)) } } @@ -1664,6 +1732,39 @@ impl fmt::Display for TorAddressPointer { } } +pub struct InterfaceAddressesReceipt { + interface_addresses: LockReceipt, +} + +impl InterfaceAddressesReceipt { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + ) -> impl FnOnce(&patch_db::Verifier) -> Result { + // let cleanup_receipts = CleanupFailedReceipts::setup(locks); + + let interface_addresses = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.interface_addresses().star()) + .make_locker(LockType::Read) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + // cleanup_receipts: cleanup_receipts(skeleton_key)?, + interface_addresses: interface_addresses.verify(skeleton_key)?, + }) + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct LanAddressPointer { @@ -1672,28 +1773,81 @@ pub struct LanAddressPointer { } impl fmt::Display for LanAddressPointer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LanAddressPointer { - package_id, - interface, - } => write!(f, "{}: lan-address: {}", package_id, interface), - } + let LanAddressPointer { + package_id, + interface, + } = self; + write!(f, "{}: lan-address: {}", package_id, interface) } } impl LanAddressPointer { - async fn deref(&self, db: &mut Db) -> Result { - let addr = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&self.package_id) - .and_then(|pde| pde.installed()) - .and_then(|installed| installed.interface_addresses().idx_model(&self.interface)) - .and_then(|addresses| addresses.lan_address()) - .get(db, true) + async fn deref( + &self, + db: &mut Db, + receipts: &InterfaceAddressesReceipt, + ) -> Result { + let addr = receipts + .interface_addresses + .get(db, (&self.package_id, &self.interface)) .await - .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; + .ok() + .flatten() + .and_then(|x| x.lan_address); Ok(addr.to_owned().map(Value::String).unwrap_or(Value::Null)) } } + +pub struct ConfigPointerReceipts { + interface_addresses_receipt: InterfaceAddressesReceipt, + manifest_volumes: LockReceipt, + manifest_version: LockReceipt, + config_actions: LockReceipt, +} + +impl ConfigPointerReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + ) -> impl FnOnce(&patch_db::Verifier) -> Result { + let interface_addresses_receipt = InterfaceAddressesReceipt::setup(locks); + + let manifest_volumes = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest().volumes()) + .make_locker(LockType::Read) + .add_to_keys(locks); + let manifest_version = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest().version()) + .make_locker(LockType::Read) + .add_to_keys(locks); + let config_actions = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .and_then(|x| x.manifest().config()) + .make_locker(LockType::Read) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + interface_addresses_receipt: interface_addresses_receipt(skeleton_key)?, + manifest_volumes: manifest_volumes.verify(skeleton_key)?, + config_actions: config_actions.verify(skeleton_key)?, + manifest_version: manifest_version.verify(skeleton_key)?, + }) + } + } +} #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct ConfigPointer { @@ -1710,40 +1864,22 @@ impl ConfigPointer { ctx: &RpcContext, db: &mut Db, config_overrides: &BTreeMap, + receipts: &ConfigPointerReceipts, ) -> Result { if let Some(cfg) = config_overrides.get(&self.package_id) { Ok(self.select(&Value::Object(cfg.clone()))) } else { - let manifest_model: OptionModel = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&self.package_id) - .and_then(|pde| pde.installed()) - .map(|installed| installed.manifest()) - .into(); - let version = manifest_model - .clone() - .map(|manifest| manifest.version()) - .get(db, true) - .await - .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; - let cfg_actions = manifest_model - .clone() - .and_then(|manifest| manifest.config()) - .get(db, true) - .await - .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; - let volumes = manifest_model - .map(|manifest| manifest.volumes()) - .get(db, true) - .await - .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; + let id = &self.package_id; + let version = receipts.manifest_version.get(db, id).await.ok().flatten(); + let cfg_actions = receipts.config_actions.get(db, id).await.ok().flatten(); + let volumes = receipts.manifest_volumes.get(db, id).await.ok().flatten(); if let (Some(version), Some(cfg_actions), Some(volumes)) = - (&*version, &*cfg_actions, &*volumes) + (&version, &cfg_actions, &volumes) { let cfg_res = cfg_actions - .get(&ctx, &self.package_id, version, volumes) + .get(ctx, &self.package_id, version, volumes) .await - .map_err(|e| ConfigurationError::SystemError(Error::from(e)))?; + .map_err(|e| ConfigurationError::SystemError(e))?; if let Some(cfg) = cfg_res.config { Ok(self.select(&Value::Object(cfg))) } else { @@ -1757,13 +1893,12 @@ impl ConfigPointer { } impl fmt::Display for ConfigPointer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ConfigPointer { - package_id, - selector, - .. - } => write!(f, "{}: config: {}", package_id, selector), - } + let ConfigPointer { + package_id, + selector, + .. + } = self; + write!(f, "{}: config: {}", package_id, selector) } } @@ -1909,6 +2044,8 @@ impl ValueSpec for SystemPointerSpec { _manifest: &Manifest, _config_overrides: &BTreeMap, value: &mut Value, + + receipts: &ConfigPointerReceipts, ) -> Result<(), ConfigurationError> { *value = self.deref(db).await?; Ok(()) diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index 87c0c0571..c98218b5f 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -7,8 +7,7 @@ use std::sync::Arc; use std::time::Duration; use bollard::Docker; -use color_eyre::eyre::eyre; -use patch_db::json_ptr::JsonPointer; +use patch_db::{json_ptr::JsonPointer, LockReceipt}; use patch_db::{DbHandle, LockType, PatchDb, Revision}; use reqwest::Url; use rpc_toolkit::url::Host; @@ -21,7 +20,6 @@ use tokio::process::Command; use tokio::sync::{broadcast, oneshot, Mutex, RwLock}; use tracing::instrument; -use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::db::model::{Database, InstalledPackageDataEntry, PackageDataEntry}; use crate::hostname::{derive_hostname, derive_id, get_product_key}; use crate::install::cleanup::{cleanup_failed, uninstall}; @@ -36,6 +34,10 @@ use crate::shutdown::Shutdown; use crate::status::{MainStatus, Status}; use crate::util::io::from_yaml_async_reader; use crate::util::{AsyncFileExt, Invoke}; +use crate::{ + core::rpc_continuations::{RequestGuid, RpcContinuation}, + install::cleanup::CleanupFailedReceipts, +}; use crate::{Error, ResultExt}; #[derive(Debug, Default, Deserialize)] @@ -132,6 +134,71 @@ pub struct RpcContextSeed { pub wifi_manager: Arc>, } +pub struct RpcCleanReceipts { + cleanup_receipts: CleanupFailedReceipts, + packages: LockReceipt, + package: LockReceipt, +} + +impl RpcCleanReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + ) -> impl FnOnce(&patch_db::Verifier) -> Result { + let cleanup_receipts = CleanupFailedReceipts::setup(locks); + + let packages = crate::db::DatabaseModel::new() + .package_data() + .make_locker(LockType::Write) + .add_to_keys(locks); + let package = crate::db::DatabaseModel::new() + .package_data() + .star() + .make_locker(LockType::Write) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + cleanup_receipts: cleanup_receipts(skeleton_key)?, + packages: packages.verify(skeleton_key)?, + package: package.verify(skeleton_key)?, + }) + } + } +} + +pub struct RpcSetNginxReceipts { + server_info: LockReceipt, +} + +impl RpcSetNginxReceipts { + pub async fn new(db: &'_ mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + ) -> impl FnOnce(&patch_db::Verifier) -> Result { + let server_info = crate::db::DatabaseModel::new() + .server_info() + .make_locker(LockType::Read) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + server_info: server_info.verify(skeleton_key)?, + }) + } + } +} + #[derive(Clone)] pub struct RpcContext(Arc); impl RpcContext { @@ -203,13 +270,14 @@ impl RpcContext { tracing::info!("Initialized Package Managers"); Ok(res) } - #[instrument(skip(self, db))] - pub async fn set_nginx_conf(&self, db: &mut Db) -> Result<(), Error> { + #[instrument(skip(self, db, receipts))] + pub async fn set_nginx_conf( + &self, + db: &mut Db, + receipts: RpcSetNginxReceipts, + ) -> Result<(), Error> { tokio::fs::write("/etc/nginx/sites-available/default", { - let info = crate::db::DatabaseModel::new() - .server_info() - .get(db, true) - .await?; + let info = receipts.server_info.get(db).await?; format!( include_str!("../nginx/main-ui.conf.template"), lan_hostname = info.lan_address.host_str().unwrap(), @@ -237,34 +305,19 @@ impl RpcContext { self.is_closed.store(true, Ordering::SeqCst); Ok(()) } + #[instrument(skip(self))] pub async fn cleanup(&self) -> Result<(), Error> { let mut db = self.db.handle(); - crate::db::DatabaseModel::new() - .package_data() - .lock(&mut db, LockType::Write) - .await?; - for package_id in crate::db::DatabaseModel::new() - .package_data() - .keys(&mut db, true) - .await? - { + let receipts = RpcCleanReceipts::new(&mut db).await?; + for (package_id, package) in receipts.packages.get(&mut db).await?.0 { if let Err(e) = async { - let mut pde = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&package_id) - .get_mut(&mut db) - .await?; - match pde.as_mut().ok_or_else(|| { - Error::new( - eyre!("Node does not exist: /package-data/{}", package_id), - crate::ErrorKind::Database, - ) - })? { + match package { PackageDataEntry::Installing { .. } | PackageDataEntry::Restoring { .. } | PackageDataEntry::Updating { .. } => { - cleanup_failed(self, &mut db, &package_id).await?; + cleanup_failed(self, &mut db, &package_id, &receipts.cleanup_receipts) + .await?; } PackageDataEntry::Removing { .. } => { uninstall( @@ -276,17 +329,12 @@ impl RpcContext { .await?; } PackageDataEntry::Installed { - installed: - InstalledPackageDataEntry { - status: Status { main, .. }, - .. - }, - .. + installed, + static_files, + manifest, } => { - let new_main = match std::mem::replace( - main, - MainStatus::Stopped, /* placeholder */ - ) { + let status = installed.status; + let main = match status.main { MainStatus::BackingUp { started, .. } => { if let Some(_) = started { MainStatus::Starting @@ -295,11 +343,20 @@ impl RpcContext { } } MainStatus::Running { .. } => MainStatus::Starting, - a => a, + a => a.clone(), }; - *main = new_main; - - pde.save(&mut db).await?; + let new_package = PackageDataEntry::Installed { + installed: InstalledPackageDataEntry { + status: Status { main, ..status }, + ..installed + }, + static_files, + manifest, + }; + receipts + .package + .set(&mut db, new_package, &package_id) + .await?; } } Ok::<_, Error>(()) diff --git a/backend/src/control.rs b/backend/src/control.rs index 6f31d9025..947bc4fba 100644 --- a/backend/src/control.rs +++ b/backend/src/control.rs @@ -1,11 +1,10 @@ use std::collections::BTreeMap; use color_eyre::eyre::eyre; -use patch_db::{DbHandle, LockType}; +use patch_db::{DbHandle, LockReceipt, LockType}; use rpc_toolkit::command; use tracing::instrument; -use crate::context::RpcContext; use crate::db::util::WithRevision; use crate::dependencies::{ break_all_dependents_transitive, heal_all_dependents_transitive, BreakageRes, DependencyError, @@ -15,8 +14,53 @@ use crate::s9pk::manifest::PackageId; use crate::status::MainStatus; use crate::util::display_none; use crate::util::serde::display_serializable; +use crate::{context::RpcContext, dependencies::DependencyReceipt}; use crate::{Error, ResultExt}; +#[derive(Clone)] +pub struct StartReceipts { + dependency_receipt: DependencyReceipt, + status: LockReceipt, + version: LockReceipt, +} + +impl StartReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks, id); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + id: &PackageId, + ) -> impl FnOnce(&patch_db::Verifier) -> Result { + let dependency_receipt = DependencyReceipt::setup(locks); + let status = crate::db::DatabaseModel::new() + .package_data() + .idx_model(id) + .and_then(|x| x.installed()) + .map(|x| x.status().main()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let version = crate::db::DatabaseModel::new() + .package_data() + .idx_model(id) + .and_then(|x| x.installed()) + .map(|x| x.manifest().version()) + .make_locker(LockType::Read) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + dependency_receipt: dependency_receipt(skeleton_key)?, + status: status.verify(skeleton_key)?, + version: version.verify(skeleton_key)?, + }) + } + } +} + #[command(display(display_none))] #[instrument(skip(ctx))] pub async fn start( @@ -25,37 +69,13 @@ pub async fn start( ) -> Result, Error> { let mut db = ctx.db.handle(); let mut tx = db.begin().await?; - crate::db::DatabaseModel::new() - .package_data() - .lock(&mut tx, LockType::Write) - .await?; - let installed = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .and_then(|pkg| pkg.installed()) - .expect(&mut tx) - .await - .with_ctx(|_| { - ( - crate::ErrorKind::NotFound, - format!("{} is not installed", id), - ) - })?; - installed.lock(&mut tx, LockType::Read).await?; - let version = installed - .clone() - .manifest() - .version() - .get(&mut tx, true) - .await? - .to_owned(); - let mut status = installed.status().main().get_mut(&mut tx).await?; - - *status = MainStatus::Starting; - status.save(&mut tx).await?; - heal_all_dependents_transitive(&ctx, &mut tx, &id).await?; + let receipts = StartReceipts::new(&mut tx, &id).await?; + let version = receipts.version.get(&mut tx).await?; + receipts.status.set(&mut tx, MainStatus::Starting).await?; + heal_all_dependents_transitive(&ctx, &mut tx, &id, &receipts.dependency_receipt).await?; let revision = tx.commit(None).await?; + drop(receipts); ctx.managers .get(&(id, version)) @@ -69,6 +89,40 @@ pub async fn start( response: (), }) } +#[derive(Clone)] +pub struct StopReceipts { + breaks: crate::dependencies::BreakTransitiveReceipts, + status: LockReceipt, +} + +impl StopReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks, id); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + id: &PackageId, + ) -> impl FnOnce(&patch_db::Verifier) -> Result { + let breaks = crate::dependencies::BreakTransitiveReceipts::setup(locks); + let status = crate::db::DatabaseModel::new() + .package_data() + .idx_model(id) + .and_then(|x| x.installed()) + .map(|x| x.status().main()) + .make_locker(LockType::Write) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + breaks: breaks(skeleton_key)?, + status: status.verify(skeleton_key)?, + }) + } + } +} #[instrument(skip(db))] async fn stop_common( @@ -77,27 +131,18 @@ async fn stop_common( breakages: &mut BTreeMap, ) -> Result<(), Error> { let mut tx = db.begin().await?; - let mut status = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&id) - .and_then(|pkg| pkg.installed()) - .expect(&mut tx) - .await - .with_ctx(|_| { - ( - crate::ErrorKind::NotFound, - format!("{} is not installed", id), - ) - })? - .status() - .main() - .get_mut(&mut tx) - .await?; + let receipts = StopReceipts::new(&mut tx, id).await?; + receipts.status.set(&mut tx, MainStatus::Stopping).await?; - *status = MainStatus::Stopping; - status.save(&mut tx).await?; tx.save().await?; - break_all_dependents_transitive(db, &id, DependencyError::NotRunning, breakages).await?; + break_all_dependents_transitive( + db, + id, + DependencyError::NotRunning, + breakages, + &receipts.breaks, + ) + .await?; Ok(()) } diff --git a/backend/src/db/model.rs b/backend/src/db/model.rs index 1a64b31e2..7d34091e7 100644 --- a/backend/src/db/model.rs +++ b/backend/src/db/model.rs @@ -260,6 +260,7 @@ pub struct InstalledPackageDataEntry { #[model] pub manifest: Manifest, pub last_backup: Option>, + #[model] pub system_pointers: Vec, #[model] pub dependency_info: BTreeMap, diff --git a/backend/src/db/package.rs b/backend/src/db/package.rs index 89ce8a543..ea4fb9428 100644 --- a/backend/src/db/package.rs +++ b/backend/src/db/package.rs @@ -1,25 +1,75 @@ -use patch_db::DbHandle; +use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, Verifier}; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::Error; -pub async fn get_packages(db: &mut Db) -> Result, Error> { - let packages = crate::db::DatabaseModel::new() - .package_data() - .get(db, false) - .await?; +pub struct PackageReceipts { + package_data: LockReceipt, +} + +impl PackageReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { + let package_data = crate::db::DatabaseModel::new() + .package_data() + .make_locker(LockType::Read) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + package_data: package_data.verify(&skeleton_key)?, + }) + } + } +} + +pub async fn get_packages( + db: &mut Db, + receipts: &PackageReceipts, +) -> Result, Error> { + let packages = receipts.package_data.get(db).await?; Ok(packages.0.keys().cloned().collect()) } +pub struct ManifestReceipts { + manifest: LockReceipt, +} + +impl ManifestReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks, id); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + id: &PackageId, + ) -> impl FnOnce(&Verifier) -> Result { + let manifest = crate::db::DatabaseModel::new() + .package_data() + .star() + .manifest() + .make_locker(LockType::Read) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + manifest: manifest.verify(&skeleton_key)?, + }) + } + } +} + pub async fn get_manifest( db: &mut Db, pkg: &PackageId, + receipts: &ManifestReceipts, ) -> Result, Error> { - let mpde = crate::db::DatabaseModel::new() - .package_data() - .idx_model(pkg) - .get(db, false) - .await? - .into_owned(); - Ok(mpde.map(|pde| pde.manifest())) + Ok(receipts.manifest.get(db, pkg).await?) } diff --git a/backend/src/dependencies.rs b/backend/src/dependencies.rs index ba3ccabf5..3184a2156 100644 --- a/backend/src/dependencies.rs +++ b/backend/src/dependencies.rs @@ -6,19 +6,20 @@ use color_eyre::eyre::eyre; use emver::VersionRange; use futures::future::BoxFuture; use futures::FutureExt; -use patch_db::{DbHandle, HasModel, LockType, Map, MapModel, PatchDbHandle}; +use patch_db::{ + DbHandle, HasModel, LockReceipt, LockTargetId, LockType, Map, MapModel, PatchDbHandle, Verifier, +}; use rand::SeedableRng; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use tracing::instrument; use crate::action::{ActionImplementation, NoOutput}; -use crate::config::action::ConfigRes; use crate::config::spec::PackagePointerSpec; -use crate::config::{Config, ConfigSpec}; +use crate::config::{action::ConfigActions, Config, ConfigSpec}; +use crate::config::{action::ConfigRes, not_found, ConfigReceipts}; use crate::context::RpcContext; use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntry}; -use crate::error::ResultExt; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::status::health_check::{HealthCheckId, HealthCheckResult}; use crate::status::{MainStatus, Status}; @@ -55,6 +56,72 @@ pub enum DependencyError { Transitive, // { "type": "transitive" } } +#[derive(Clone)] +pub struct TryHealReceipts { + status: LockReceipt, + manifest: LockReceipt, + manifest_version: LockReceipt, + current_dependencies: LockReceipt, String>, + dependency_errors: LockReceipt, +} + +impl TryHealReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { + let manifest_version = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest().version()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let status = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.status()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let manifest = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest()) + .make_locker(LockType::Write) + .add_to_keys(locks); + + let current_dependencies = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.current_dependencies()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let dependency_errors = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.status().dependency_errors()) + .make_locker(LockType::Write) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + status: status.verify(skeleton_key)?, + manifest_version: manifest_version.verify(skeleton_key)?, + current_dependencies: current_dependencies.verify(skeleton_key)?, + manifest: manifest.verify(skeleton_key)?, + dependency_errors: dependency_errors.verify(skeleton_key)?, + }) + } + } +} + impl DependencyError { pub fn cmp_priority(&self, other: &DependencyError) -> std::cmp::Ordering { use std::cmp::Ordering::*; @@ -114,7 +181,7 @@ impl DependencyError { (DependencyError::Transitive, _) => DependencyError::Transitive, } } - #[instrument(skip(ctx, db))] + #[instrument(skip(ctx, db, receipts))] pub fn try_heal<'a, Db: DbHandle>( self, ctx: &'a RpcContext, @@ -123,42 +190,33 @@ impl DependencyError { dependency: &'a PackageId, mut dependency_config: Option, info: &'a DepInfo, + receipts: &'a TryHealReceipts, ) -> BoxFuture<'a, Result, Error>> { async move { Ok(match self { DependencyError::NotInstalled => { - if crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependency) - .and_then(|m| m.installed()) - .exists(db, true) - .await? - { + if receipts.status.get(db, dependency).await?.is_some() { DependencyError::IncorrectVersion { expected: info.version.clone(), received: Default::default(), } - .try_heal(ctx, db, id, dependency, dependency_config, info) + .try_heal(ctx, db, id, dependency, dependency_config, info, receipts) .await? } else { Some(DependencyError::NotInstalled) } } DependencyError::IncorrectVersion { expected, .. } => { - let version: Version = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependency) - .and_then(|m| m.installed()) - .map(|m| m.manifest().version()) - .get(db, true) + let version: Version = receipts + .manifest_version + .get(db, dependency) .await? - .into_owned() .unwrap_or_default(); if version.satisfies(&expected) { DependencyError::ConfigUnsatisfied { error: String::new(), } - .try_heal(ctx, db, id, dependency, dependency_config, info) + .try_heal(ctx, db, id, dependency, dependency_config, info, receipts) .await? } else { Some(DependencyError::IncorrectVersion { @@ -168,24 +226,14 @@ impl DependencyError { } } DependencyError::ConfigUnsatisfied { .. } => { - let dependent_manifest = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|m| m.installed()) - .map::<_, Manifest>(|m| m.manifest()) - .expect(db) + let dependent_manifest = + receipts.manifest.get(db, id).await?.ok_or_else(not_found)?; + let dependency_manifest = receipts + .manifest + .get(db, dependency) .await? - .get(db, true) - .await?; - let dependency_manifest = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependency) - .and_then(|m| m.installed()) - .map::<_, Manifest>(|m| m.manifest()) - .expect(db) - .await? - .get(db, true) - .await?; + .ok_or_else(not_found)?; + let dependency_config = if let Some(cfg) = dependency_config.take() { cfg } else if let Some(cfg_info) = &dependency_manifest.config { @@ -217,40 +265,39 @@ impl DependencyError { } } DependencyError::NotRunning - .try_heal(ctx, db, id, dependency, Some(dependency_config), info) + .try_heal( + ctx, + db, + id, + dependency, + Some(dependency_config), + info, + receipts, + ) .await? } DependencyError::NotRunning => { - let status = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependency) - .and_then(|m| m.installed()) - .map::<_, Status>(|m| m.status()) - .expect(db) + let status = receipts + .status + .get(db, dependency) .await? - .get(db, true) - .await?; + .ok_or_else(not_found)?; if status.main.running() { DependencyError::HealthChecksFailed { failures: BTreeMap::new(), } - .try_heal(ctx, db, id, dependency, dependency_config, info) + .try_heal(ctx, db, id, dependency, dependency_config, info, receipts) .await? } else { Some(DependencyError::NotRunning) } } DependencyError::HealthChecksFailed { .. } => { - let status = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependency) - .and_then(|m| m.installed()) - .map::<_, Status>(|m| m.status()) - .expect(db) + let status = receipts + .status + .get(db, dependency) .await? - .get(db, true) - .await? - .into_owned(); + .ok_or_else(not_found)?; match status.main { MainStatus::BackingUp { started: Some(_), @@ -260,19 +307,14 @@ impl DependencyError { let mut failures = BTreeMap::new(); for (check, res) in health { if !matches!(res, HealthCheckResult::Success) - && crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|m| m.installed()) - .and_then::<_, CurrentDependencyInfo>(|m| { - m.current_dependencies().idx_model(dependency) - }) - .get(db, true) + && receipts + .current_dependencies + .get(db, id) .await? - .into_owned() - .map(|i| i.health_checks) - .unwrap_or_default() - .contains(&check) + .ok_or_else(not_found)? + .get(dependency) + .map(|x| x.health_checks.contains(&check)) + .unwrap_or(false) { failures.insert(check.clone(), res.clone()); } @@ -281,27 +323,39 @@ impl DependencyError { Some(DependencyError::HealthChecksFailed { failures }) } else { DependencyError::Transitive - .try_heal(ctx, db, id, dependency, dependency_config, info) + .try_heal( + ctx, + db, + id, + dependency, + dependency_config, + info, + receipts, + ) .await? } } MainStatus::Starting => { DependencyError::Transitive - .try_heal(ctx, db, id, dependency, dependency_config, info) + .try_heal( + ctx, + db, + id, + dependency, + dependency_config, + info, + receipts, + ) .await? } _ => return Ok(Some(DependencyError::NotRunning)), } } DependencyError::Transitive => { - if crate::db::DatabaseModel::new() - .package_data() - .idx_model(dependency) - .and_then(|m| m.installed()) - .map::<_, DependencyErrors>(|m| m.status().dependency_errors()) - .get(db, true) + if receipts + .dependency_errors + .get(db, dependency) .await? - .into_owned() .unwrap_or_default() .0 .is_empty() @@ -406,6 +460,7 @@ impl DepInfo { dependency_id: &PackageId, dependency_config: Option, // fetch if none dependent_id: &PackageId, + receipts: &TryHealReceipts, ) -> Result, Error> { Ok( if let Some(err) = DependencyError::NotInstalled @@ -416,6 +471,7 @@ impl DepInfo { dependency_id, dependency_config, self, + receipts, ) .await? { @@ -477,6 +533,78 @@ impl DependencyConfig { } } +pub struct DependencyConfigReceipts { + dependencies: LockReceipt, + dependency_volumes: LockReceipt, + dependency_version: LockReceipt, + dependency_config_action: LockReceipt, + package_volumes: LockReceipt, + package_version: LockReceipt, +} + +impl DependencyConfigReceipts { + pub async fn new( + db: &'_ mut impl DbHandle, + package_id: &PackageId, + dependency_id: &PackageId, + ) -> Result { + let mut locks = Vec::new(); + + let dependencies = crate::db::DatabaseModel::new() + .package_data() + .idx_model(package_id) + .and_then(|x| x.installed()) + .map(|x| x.manifest().dependencies()) + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + let dependency_volumes = crate::db::DatabaseModel::new() + .package_data() + .idx_model(dependency_id) + .and_then(|x| x.installed()) + .map(|x| x.manifest().volumes()) + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + let dependency_version = crate::db::DatabaseModel::new() + .package_data() + .idx_model(dependency_id) + .and_then(|x| x.installed()) + .map(|x| x.manifest().version()) + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + let dependency_config_action = crate::db::DatabaseModel::new() + .package_data() + .idx_model(dependency_id) + .and_then(|x| x.installed()) + .and_then(|x| x.manifest().config()) + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + let package_volumes = crate::db::DatabaseModel::new() + .package_data() + .idx_model(package_id) + .and_then(|x| x.installed()) + .map(|x| x.manifest().volumes()) + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + let package_version = crate::db::DatabaseModel::new() + .package_data() + .idx_model(package_id) + .and_then(|x| x.installed()) + .map(|x| x.manifest().version()) + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + + let skeleton_key = db.lock_all(locks).await?; + Ok(Self { + dependencies: dependencies.verify(&skeleton_key)?, + dependency_volumes: dependency_volumes.verify(&skeleton_key)?, + dependency_version: dependency_version.verify(&skeleton_key)?, + dependency_config_action: dependency_config_action.verify(&skeleton_key)?, + package_volumes: package_volumes.verify(&skeleton_key)?, + package_version: package_version.verify(&skeleton_key)?, + }) + } +} + #[command( subcommands(self(configure_impl(async)), configure_dry), display(display_none) @@ -493,11 +621,14 @@ pub async fn configure_impl( (pkg_id, dep_id): (PackageId, PackageId), ) -> Result<(), Error> { let mut db = ctx.db.handle(); + let receipts = DependencyConfigReceipts::new(&mut db, &pkg_id, &dep_id).await?; let ConfigDryRes { old_config: _, new_config, spec: _, - } = configure_logic(ctx.clone(), &mut db, (pkg_id, dep_id.clone())).await?; + } = configure_logic(ctx.clone(), &mut db, (pkg_id, dep_id.clone()), &receipts).await?; + + let locks = ConfigReceipts::new(&mut db).await?; Ok(crate::config::configure( &ctx, &mut db, @@ -507,6 +638,7 @@ pub async fn configure_impl( false, &mut BTreeMap::new(), &mut BTreeMap::new(), + &locks, ) .await?) } @@ -526,67 +658,25 @@ pub async fn configure_dry( #[parent_data] (pkg_id, dependency_id): (PackageId, PackageId), ) -> Result { let mut db = ctx.db.handle(); - configure_logic(ctx, &mut db, (pkg_id, dependency_id)).await + let receipts = DependencyConfigReceipts::new(&mut db, &pkg_id, &dependency_id).await?; + configure_logic(ctx, &mut db, (pkg_id, dependency_id), &receipts).await } pub async fn configure_logic( ctx: RpcContext, db: &mut PatchDbHandle, (pkg_id, dependency_id): (PackageId, PackageId), + receipts: &DependencyConfigReceipts, ) -> Result { - crate::db::DatabaseModel::new() - .package_data() - .lock(db, LockType::Read) - .await?; - let pkg_model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&pkg_id) - .and_then(|m| m.installed()) - .expect(db) - .await - .with_kind(crate::ErrorKind::NotFound)?; - let pkg_version = pkg_model.clone().manifest().version().get(db, true).await?; - let pkg_volumes = pkg_model.clone().manifest().volumes().get(db, true).await?; - let dependency_model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&dependency_id) - .and_then(|m| m.installed()) - .expect(db) - .await - .with_kind(crate::ErrorKind::NotFound)?; - let dependency_config_action = dependency_model - .clone() - .manifest() - .config() - .get(db, true) - .await? - .to_owned() - .ok_or_else(|| { - Error::new( - eyre!("{} has no config", dependency_id), - crate::ErrorKind::NotFound, - ) - })?; - let dependency_version = dependency_model - .clone() - .manifest() - .version() - .get(db, true) - .await?; - let dependency_volumes = dependency_model - .clone() - .manifest() - .volumes() - .get(db, true) - .await?; - let dependencies = pkg_model - .clone() - .manifest() - .dependencies() - .get(db, true) - .await?; + let pkg_version = receipts.package_version.get(db).await?; + let pkg_volumes = receipts.package_volumes.get(db).await?; + let dependency_config_action = receipts.dependency_config_action.get(db).await?; + let dependency_version = receipts.dependency_version.get(db).await?; + let dependency_volumes = receipts.dependency_volumes.get(db).await?; + let dependencies = receipts.dependencies.get(db).await?; let dependency = dependencies + .0 .get(&dependency_id) .ok_or_else(|| { Error::new( @@ -617,8 +707,8 @@ pub async fn configure_logic( .get( &ctx, &dependency_id, - &*dependency_version, - &*dependency_volumes, + &dependency_version, + &dependency_volumes, ) .await?; @@ -650,8 +740,7 @@ pub async fn configure_logic( spec, }) } - -#[instrument(skip(db, current_dependencies))] +#[instrument(skip(db, current_dependencies, current_dependent_receipt))] pub async fn add_dependent_to_current_dependents_lists< 'a, Db: DbHandle, @@ -660,19 +749,15 @@ pub async fn add_dependent_to_current_dependents_lists< db: &mut Db, dependent_id: &PackageId, current_dependencies: I, + current_dependent_receipt: &LockReceipt, String>, ) -> Result<(), Error> { for (dependency, dep_info) in current_dependencies { - if let Some(dependency_model) = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&dependency) - .and_then(|pkg| pkg.installed()) - .check(db) - .await? + if let Some(mut dependency_dependents) = + current_dependent_receipt.get(db, dependency).await? { - dependency_model - .current_dependents() - .idx_model(dependent_id) - .put(db, &dep_info) + dependency_dependents.insert(dependent_id.clone(), dep_info.clone()); + current_dependent_receipt + .set(db, dependency_dependents, dependency) .await?; } } @@ -697,6 +782,7 @@ impl DependencyErrors { db: &mut Db, manifest: &Manifest, current_dependencies: &BTreeMap, + receipts: &TryHealReceipts, ) -> Result { let mut res = BTreeMap::new(); for (dependency_id, info) in current_dependencies.keys().filter_map(|dependency_id| { @@ -707,7 +793,7 @@ impl DependencyErrors { .map(|info| (dependency_id, info)) }) { if let Err(e) = info - .satisfied(ctx, db, dependency_id, None, &manifest.id) + .satisfied(ctx, db, dependency_id, None, &manifest.id, receipts) .await? { res.insert(dependency_id.clone(), e); @@ -735,49 +821,86 @@ pub async fn break_all_dependents_transitive<'a, Db: DbHandle>( id: &'a PackageId, error: DependencyError, breakages: &'a mut BTreeMap, + receipts: &'a BreakTransitiveReceipts, ) -> Result<(), Error> { - for dependent in crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|m| m.installed()) - .expect(db) + for dependent in receipts + .current_dependents + .get(db, id) .await? - .current_dependents() - .keys(db, true) - .await? - .into_iter() - .filter(|dependent| id != dependent) + .iter() + .flat_map(|x| x.keys()) + .filter(|dependent| id != *dependent) { - break_transitive(db, &dependent, id, error.clone(), breakages).await?; + break_transitive(db, dependent, id, error.clone(), breakages, receipts).await?; } Ok(()) } -#[instrument(skip(db))] +#[derive(Clone)] +pub struct BreakTransitiveReceipts { + pub dependency_receipt: DependencyReceipt, + dependency_errors: LockReceipt, + current_dependents: LockReceipt, String>, +} + +impl BreakTransitiveReceipts { + pub async fn new(db: &'_ mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { + let dependency_receipt = DependencyReceipt::setup(locks); + let dependency_errors = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.status().dependency_errors()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let current_dependents = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.current_dependents()) + .make_locker(LockType::Exist) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + dependency_receipt: dependency_receipt(skeleton_key)?, + dependency_errors: dependency_errors.verify(skeleton_key)?, + current_dependents: current_dependents.verify(skeleton_key)?, + }) + } + } +} + +#[instrument(skip(db, receipts))] pub fn break_transitive<'a, Db: DbHandle>( db: &'a mut Db, id: &'a PackageId, dependency: &'a PackageId, error: DependencyError, breakages: &'a mut BTreeMap, + receipts: &'a BreakTransitiveReceipts, ) -> BoxFuture<'a, Result<(), Error>> { async move { let mut tx = db.begin().await?; - let model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|m| m.installed()) - .expect(&mut tx) - .await?; - let mut status = model.clone().status().get_mut(&mut tx).await?; + let mut dependency_errors = receipts + .dependency_errors + .get(&mut tx, id) + .await? + .ok_or_else(not_found)?; - let old = status.dependency_errors.0.remove(dependency); + let old = dependency_errors.0.remove(dependency); let newly_broken = if let Some(e) = &old { error.cmp_priority(&e) == Ordering::Greater } else { true }; - status.dependency_errors.0.insert( + dependency_errors.0.insert( dependency.clone(), if let Some(old) = old { old.merge_with(error.clone()) @@ -793,12 +916,25 @@ pub fn break_transitive<'a, Db: DbHandle>( error: error.clone(), }, ); - status.save(&mut tx).await?; + receipts + .dependency_errors + .set(&mut tx, dependency_errors, id) + .await?; tx.save().await?; - break_all_dependents_transitive(db, id, DependencyError::Transitive, breakages).await?; + break_all_dependents_transitive( + db, + id, + DependencyError::Transitive, + breakages, + receipts, + ) + .await?; } else { - status.save(&mut tx).await?; + receipts + .dependency_errors + .set(&mut tx, dependency_errors, id) + .await?; tx.save().await?; } @@ -808,68 +944,52 @@ pub fn break_transitive<'a, Db: DbHandle>( .boxed() } -#[instrument(skip(ctx, db))] +#[instrument(skip(ctx, db, locks))] pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>( ctx: &'a RpcContext, db: &'a mut Db, id: &'a PackageId, + locks: &'a DependencyReceipt, ) -> Result<(), Error> { - for dependent in crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|m| m.installed()) - .expect(db) + let dependents = locks + .current_dependents + .get(db, id) .await? - .current_dependents() - .keys(db, true) - .await? - .into_iter() - .filter(|dependent| id != dependent) - { - heal_transitive(ctx, db, &dependent, id).await?; + .ok_or_else(not_found)?; + for dependent in dependents.keys().filter(|dependent| id != *dependent) { + heal_transitive(ctx, db, dependent, id, locks).await?; } Ok(()) } -#[instrument(skip(ctx, db))] +#[instrument(skip(ctx, db, receipts))] pub fn heal_transitive<'a, Db: DbHandle>( ctx: &'a RpcContext, db: &'a mut Db, id: &'a PackageId, dependency: &'a PackageId, + receipts: &'a DependencyReceipt, ) -> BoxFuture<'a, Result<(), Error>> { async move { - let mut tx = db.begin().await?; - let model = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|m| m.installed()) - .expect(&mut tx) - .await?; - let mut status = model.clone().status().get_mut(&mut tx).await?; + let mut status = receipts.status.get(db, id).await?.ok_or_else(not_found)?; let old = status.dependency_errors.0.remove(dependency); if let Some(old) = old { - let info = model - .manifest() - .dependencies() - .idx_model(dependency) - .expect(&mut tx) + let info = receipts + .dependency + .get(db, (id, dependency)) .await? - .get(&mut tx, true) - .await?; + .ok_or_else(not_found)?; if let Some(new) = old - .try_heal(ctx, &mut tx, id, dependency, None, &*info) + .try_heal(ctx, db, id, dependency, None, &info, &receipts.try_heal) .await? { status.dependency_errors.0.insert(dependency.clone(), new); - status.save(&mut tx).await?; - tx.save().await?; + receipts.status.set(db, status, id).await?; } else { - status.save(&mut tx).await?; - tx.save().await?; - heal_all_dependents_transitive(ctx, db, id).await?; + receipts.status.set(db, status, id).await?; + heal_all_dependents_transitive(ctx, db, id, receipts).await?; } } @@ -881,6 +1001,7 @@ pub fn heal_transitive<'a, Db: DbHandle>( pub async fn reconfigure_dependents_with_live_pointers( ctx: &RpcContext, mut tx: impl DbHandle, + receipts: &ConfigReceipts, pde: &InstalledPackageDataEntry, ) -> Result<(), Error> { let dependents = &pde.current_dependents; @@ -903,9 +1024,60 @@ pub async fn reconfigure_dependents_with_live_pointers( false, &mut BTreeMap::new(), &mut BTreeMap::new(), + receipts, ) .await?; } } Ok(()) } + +#[derive(Clone)] +pub struct DependencyReceipt { + pub try_heal: TryHealReceipts, + current_dependents: LockReceipt, String>, + status: LockReceipt, + dependency: LockReceipt, +} + +impl DependencyReceipt { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { + let try_heal = TryHealReceipts::setup(locks); + let dependency = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest().dependencies().star()) + .make_locker(LockType::Read) + .add_to_keys(locks); + let current_dependents = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.current_dependents()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let status = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.status()) + .make_locker(LockType::Write) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + try_heal: try_heal(skeleton_key)?, + current_dependents: current_dependents.verify(skeleton_key)?, + status: status.verify(skeleton_key)?, + dependency: dependency.verify(skeleton_key)?, + }) + } + } +} diff --git a/backend/src/disk/fsck.rs b/backend/src/disk/fsck.rs index 0e2f8e0a5..492b9ea83 100644 --- a/backend/src/disk/fsck.rs +++ b/backend/src/disk/fsck.rs @@ -7,7 +7,7 @@ use futures::FutureExt; use tokio::process::Command; use tracing::instrument; -use crate::{Error, ResultExt}; +use crate::Error; #[derive(Debug, Clone, Copy)] #[must_use] diff --git a/backend/src/init.rs b/backend/src/init.rs index f5452a574..0cb4b9e67 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -1,5 +1,6 @@ use std::time::Duration; +use patch_db::{DbHandle, LockReceipt, LockType}; use tokio::process::Command; use crate::context::rpc::RpcContextConfig; @@ -23,6 +24,48 @@ pub async fn check_time_is_synchronized() -> Result { == "NTPSynchronized=yes") } +pub struct InitReceipts { + pub server_version: LockReceipt, + pub version_range: LockReceipt, + pub last_wifi_region: LockReceipt, ()>, + pub status_info: LockReceipt, +} +impl InitReceipts { + pub async fn new(db: &mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let server_version = crate::db::DatabaseModel::new() + .server_info() + .version() + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + let version_range = crate::db::DatabaseModel::new() + .server_info() + .eos_version_compat() + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + let last_wifi_region = crate::db::DatabaseModel::new() + .server_info() + .last_wifi_region() + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + let status_info = crate::db::DatabaseModel::new() + .server_info() + .status_info() + .into_model() + .make_locker(LockType::Write) + .add_to_keys(&mut locks); + + let skeleton_key = db.lock_all(locks).await?; + Ok(Self { + server_version: server_version.verify(&skeleton_key)?, + version_range: version_range.verify(&skeleton_key)?, + status_info: status_info.verify(&skeleton_key)?, + last_wifi_region: last_wifi_region.verify(&skeleton_key)?, + }) + } +} + pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error> { let should_rebuild = tokio::fs::metadata(SYSTEM_REBUILD_PATH).await.is_ok(); let secret_store = cfg.secret_store().await?; @@ -87,13 +130,13 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error let db = cfg.db(&secret_store, product_key).await?; let mut handle = db.handle(); + let receipts = InitReceipts::new(&mut handle).await?; crate::net::wifi::synchronize_wpa_supplicant_conf( &cfg.datadir().join("main"), - &*crate::db::DatabaseModel::new() - .server_info() - .last_wifi_region() - .get(&mut handle, false) + &receipts + .last_wifi_region + .get(&mut handle) .await .map_err(|_e| { Error::new( @@ -104,16 +147,17 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error ) .await?; tracing::info!("Synchronized wpa_supplicant.conf"); - let mut info = crate::db::DatabaseModel::new() - .server_info() - .get_mut(&mut handle) + receipts + .status_info + .set( + &mut handle, + ServerStatus { + backing_up: false, + updated: false, + update_progress: None, + }, + ) .await?; - info.status_info = ServerStatus { - backing_up: false, - updated: false, - update_progress: None, - }; - info.save(&mut handle).await?; let mut warn_time_not_synced = true; for _ in 0..60 { @@ -127,7 +171,7 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error tracing::warn!("Timed out waiting for system time to synchronize"); } - crate::version::init(&mut handle).await?; + crate::version::init(&mut handle, &receipts).await?; if should_rebuild { tokio::fs::remove_file(SYSTEM_REBUILD_PATH).await?; diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index aba81165e..5635dcfd4 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -1,21 +1,65 @@ use std::collections::{BTreeMap, HashMap}; use bollard::image::ListImagesOptions; -use color_eyre::eyre::eyre; -use patch_db::{DbHandle, LockType, PatchDbHandle}; +use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, PatchDbHandle, Verifier}; use sqlx::{Executor, Sqlite}; use tracing::instrument; use super::{PKG_ARCHIVE_DIR, PKG_DOCKER_DIR}; -use crate::context::RpcContext; -use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry}; -use crate::dependencies::reconfigure_dependents_with_live_pointers; -use crate::error::ErrorCollection; -use crate::s9pk::manifest::{Manifest, PackageId}; -use crate::util::{Apply, Version}; -use crate::Error; +use crate::db::model::{InstalledPackageDataEntry, PackageDataEntry}; +use crate::{config::not_found, dependencies::reconfigure_dependents_with_live_pointers}; +use crate::{config::ConfigReceipts, context::RpcContext}; +use crate::{ + db::model::AllPackageData, + s9pk::manifest::{Manifest, PackageId}, +}; +use crate::{db::model::CurrentDependencyInfo, error::ErrorCollection}; +use crate::{ + dependencies::DependencyErrors, + util::{Apply, Version}, +}; +use crate::{dependencies::TryHealReceipts, Error}; -#[instrument(skip(ctx, db, deps))] +pub struct UpdateDependencyReceipts { + try_heal: TryHealReceipts, + dependency_errors: LockReceipt, + manifest: LockReceipt, +} +impl UpdateDependencyReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { + let dependency_errors = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.status().dependency_errors()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let manifest = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.manifest()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let try_heal = TryHealReceipts::setup(locks); + move |skeleton_key| { + Ok(Self { + dependency_errors: dependency_errors.verify(skeleton_key)?, + manifest: manifest.verify(skeleton_key)?, + try_heal: try_heal(skeleton_key)?, + }) + } + } +} + +#[instrument(skip(ctx, db, deps, receipts))] pub async fn update_dependency_errors_of_dependents< 'a, Db: DbHandle, @@ -25,50 +69,31 @@ pub async fn update_dependency_errors_of_dependents< db: &mut Db, id: &PackageId, deps: I, + receipts: &UpdateDependencyReceipts, ) -> Result<(), Error> { for dep in deps { - if let Some(man) = &*crate::db::DatabaseModel::new() - .package_data() - .idx_model(&dep) - .and_then(|m| m.installed()) - .map::<_, Manifest>(|m| m.manifest()) - .get(db, true) - .await? - { + if let Some(man) = receipts.manifest.get(db, dep).await? { if let Err(e) = if let Some(info) = man.dependencies.0.get(id) { - info.satisfied(ctx, db, id, None, dep).await? + info.satisfied(ctx, db, id, None, dep, &receipts.try_heal) + .await? } else { Ok(()) } { - let mut errs = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&dep) - .expect(db) + let mut errs = receipts + .dependency_errors + .get(db, dep) .await? - .installed() - .expect(db) - .await? - .status() - .dependency_errors() - .get_mut(db) - .await?; + .ok_or_else(not_found)?; errs.0.insert(id.clone(), e); - errs.save(db).await?; + receipts.dependency_errors.set(db, errs, dep).await? } else { - let mut errs = crate::db::DatabaseModel::new() - .package_data() - .idx_model(&dep) - .expect(db) + let mut errs = receipts + .dependency_errors + .get(db, dep) .await? - .installed() - .expect(db) - .await? - .status() - .dependency_errors() - .get_mut(db) - .await?; + .ok_or_else(not_found)?; errs.0.remove(id); - errs.save(db).await?; + receipts.dependency_errors.set(db, errs, dep).await? } } } @@ -127,24 +152,50 @@ pub async fn cleanup(ctx: &RpcContext, id: &PackageId, version: &Version) -> Res errors.into_result() } -#[instrument(skip(ctx, db))] +pub struct CleanupFailedReceipts { + package_data_entry: LockReceipt, + package_entries: LockReceipt, +} + +impl CleanupFailedReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup(locks: &mut Vec) -> impl FnOnce(&Verifier) -> Result { + let package_data_entry = crate::db::DatabaseModel::new() + .package_data() + .star() + .make_locker(LockType::Write) + .add_to_keys(locks); + let package_entries = crate::db::DatabaseModel::new() + .package_data() + .make_locker(LockType::Write) + .add_to_keys(locks); + move |skeleton_key| { + Ok(Self { + package_data_entry: package_data_entry.verify(skeleton_key).unwrap(), + package_entries: package_entries.verify(skeleton_key).unwrap(), + }) + } + } +} + +#[instrument(skip(ctx, db, receipts))] pub async fn cleanup_failed( ctx: &RpcContext, db: &mut Db, id: &PackageId, + receipts: &CleanupFailedReceipts, ) -> Result<(), Error> { - crate::db::DatabaseModel::new() - .package_data() - .lock(db, LockType::Write) - .await?; - let pde = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .expect(db) + let pde = receipts + .package_data_entry + .get(db, id) .await? - .get(db, true) - .await? - .into_owned(); + .ok_or_else(not_found)?; if let Some(manifest) = match &pde { PackageDataEntry::Installing { manifest, .. } | PackageDataEntry::Restoring { manifest, .. } => Some(manifest), @@ -173,26 +224,29 @@ pub async fn cleanup_failed( match pde { PackageDataEntry::Installing { .. } | PackageDataEntry::Restoring { .. } => { - crate::db::DatabaseModel::new() - .package_data() - .remove(db, id) - .await?; + let mut entries = receipts + .package_entries + .get(db, id) + .await? + .ok_or_else(not_found)?; + entries.0.remove(id); + receipts.package_entries.set(db, entries, id).await?; } PackageDataEntry::Updating { installed, static_files, .. } => { - crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .put( + receipts + .package_data_entry + .set( db, - &PackageDataEntry::Installed { + PackageDataEntry::Installed { manifest: installed.manifest.clone(), installed, static_files, }, + id, ) .await?; } @@ -202,7 +256,7 @@ pub async fn cleanup_failed( Ok(()) } -#[instrument(skip(db, current_dependencies))] +#[instrument(skip(db, current_dependencies, current_dependent_receipt))] pub async fn remove_from_current_dependents_lists< 'a, Db: DbHandle, @@ -211,29 +265,69 @@ pub async fn remove_from_current_dependents_lists< db: &mut Db, id: &'a PackageId, current_dependencies: I, + current_dependent_receipt: &LockReceipt, String>, ) -> Result<(), Error> { for dep in current_dependencies.into_iter().chain(std::iter::once(id)) { - if let Some(current_dependents) = crate::db::DatabaseModel::new() - .package_data() - .idx_model(dep) - .and_then(|m| m.installed()) - .map::<_, BTreeMap>(|m| m.current_dependents()) - .check(db) - .await? - { - if current_dependents - .clone() - .idx_model(id) - .exists(db, true) - .await? - { - current_dependents.remove(db, id).await? + if let Some(mut current_dependents) = current_dependent_receipt.get(db, dep).await? { + if current_dependents.remove(id).is_some() { + current_dependent_receipt + .set(db, current_dependents, dep) + .await?; } } } Ok(()) } +pub struct UninstallReceipts { + config: ConfigReceipts, + removing: LockReceipt, + packages: LockReceipt, + current_dependents: LockReceipt, String>, + update_depenency_receipts: UpdateDependencyReceipts, +} +impl UninstallReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { + let mut locks = Vec::new(); + let setup = Self::setup(&mut locks, id); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + id: &PackageId, + ) -> impl FnOnce(&Verifier) -> Result { + let config = ConfigReceipts::setup(locks); + let removing = crate::db::DatabaseModel::new() + .package_data() + .idx_model(id) + .and_then(|pde| pde.removing()) + .make_locker(LockType::Write) + .add_to_keys(locks); + + let current_dependents = crate::db::DatabaseModel::new() + .package_data() + .star() + .installed() + .map(|x| x.current_dependents()) + .make_locker(LockType::Write) + .add_to_keys(locks); + let packages = crate::db::DatabaseModel::new() + .package_data() + .make_locker(LockType::Write) + .add_to_keys(locks); + let update_depenency_receipts = UpdateDependencyReceipts::setup(locks); + move |skeleton_key| { + Ok(Self { + config: config(skeleton_key)?, + removing: removing.verify(skeleton_key)?, + current_dependents: current_dependents.verify(skeleton_key)?, + update_depenency_receipts: update_depenency_receipts(skeleton_key)?, + packages: packages.verify(skeleton_key)?, + }) + } + } +} #[instrument(skip(ctx, secrets, db))] pub async fn uninstall( ctx: &RpcContext, @@ -245,37 +339,24 @@ where for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, { let mut tx = db.begin().await?; - crate::db::DatabaseModel::new() - .package_data() - .lock(&mut tx, LockType::Write) - .await?; - let entry = crate::db::DatabaseModel::new() - .package_data() - .idx_model(id) - .and_then(|pde| pde.removing()) - .get(&mut tx, true) - .await? - .into_owned() - .ok_or_else(|| { - Error::new( - eyre!("Package not in removing state: {}", id), - crate::ErrorKind::NotFound, - ) - })?; + let receipts = UninstallReceipts::new(&mut tx, id).await?; + let entry = receipts.removing.get(&mut tx).await?; cleanup(ctx, &entry.manifest.id, &entry.manifest.version).await?; - crate::db::DatabaseModel::new() - .package_data() - .remove(&mut tx, id) - .await?; - + let packages = { + let mut packages = receipts.packages.get(&mut tx).await?; + packages.0.remove(id); + packages + }; + receipts.packages.set(&mut tx, packages).await?; // once we have removed the package entry, we can change all the dependent pointers to null - reconfigure_dependents_with_live_pointers(ctx, &mut tx, &entry).await?; + reconfigure_dependents_with_live_pointers(ctx, &mut tx, &receipts.config, &entry).await?; remove_from_current_dependents_lists( &mut tx, &entry.manifest.id, entry.current_dependencies.keys(), + &receipts.current_dependents, ) .await?; update_dependency_errors_of_dependents( @@ -283,6 +364,7 @@ where &mut tx, &entry.manifest.id, entry.current_dependents.keys(), + &receipts.update_depenency_receipts, ) .await?; let volumes = ctx diff --git a/backend/src/install/mod.rs b/backend/src/install/mod.rs index 282aade61..534f0913f 100644 --- a/backend/src/install/mod.rs +++ b/backend/src/install/mod.rs @@ -14,7 +14,7 @@ use futures::{FutureExt, StreamExt, TryStreamExt}; use http::header::CONTENT_LENGTH; use http::{Request, Response, StatusCode}; use hyper::Body; -use patch_db::{DbHandle, LockType}; +use patch_db::{DbHandle, LockReceipt, LockType}; use reqwest::Url; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::{command, Context}; @@ -25,8 +25,6 @@ use tokio_stream::wrappers::ReadDirStream; use tracing::instrument; use self::cleanup::{cleanup_failed, remove_from_current_dependents_lists}; -use crate::context::{CliContext, RpcContext}; -use crate::core::rpc_continuations::{RequestGuid, RpcContinuation}; use crate::db::model::{ CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry, RecoveredPackageInfo, StaticDependencyInfo, StaticFiles, @@ -47,16 +45,24 @@ use crate::util::serde::{display_serializable, IoFormat, Port}; use crate::util::{display_none, AsyncFileExt, Version}; use crate::version::{Current, VersionT}; use crate::volume::asset_dir; +use crate::{ + config::ConfigReceipts, + context::{CliContext, RpcContext}, +}; +use crate::{ + core::rpc_continuations::{RequestGuid, RpcContinuation}, + dependencies::BreakTransitiveReceipts, +}; use crate::{Error, ErrorKind, ResultExt}; pub mod cleanup; pub mod progress; pub mod update; -pub const PKG_ARCHIVE_DIR: &'static str = "package-data/archive"; -pub const PKG_PUBLIC_DIR: &'static str = "package-data/public"; -pub const PKG_DOCKER_DIR: &'static str = "package-data/docker"; -pub const PKG_WASM_DIR: &'static str = "package-data/wasm"; +pub const PKG_ARCHIVE_DIR: &str = "package-data/archive"; +pub const PKG_PUBLIC_DIR: &str = "package-data/public"; +pub const PKG_DOCKER_DIR: &str = "package-data/docker"; +pub const PKG_WASM_DIR: &str = "package-data/wasm"; #[command(display(display_serializable))] pub async fn list(#[context] ctx: RpcContext) -> Result, Error> { @@ -447,7 +453,7 @@ pub async fn sideload( }); let cont = RpcContinuation { created_at: Instant::now(), // TODO - handler: handler, + handler, }; // gc the map let mut guard = ctx.rpc_stream_continuations.lock().await; @@ -562,8 +568,15 @@ pub async fn uninstall_dry( let mut db = ctx.db.handle(); let mut tx = db.begin().await?; let mut breakages = BTreeMap::new(); - break_all_dependents_transitive(&mut tx, &id, DependencyError::NotInstalled, &mut breakages) - .await?; + let receipts = BreakTransitiveReceipts::new(&mut tx).await?; + break_all_dependents_transitive( + &mut tx, + &id, + DependencyError::NotInstalled, + &mut breakages, + &receipts, + ) + .await?; tx.abort().await?; @@ -678,6 +691,35 @@ pub async fn delete_recovered( }) } +pub struct DownloadInstallReceipts { + package_receipts: crate::db::package::PackageReceipts, + manifest_receipts: crate::db::package::ManifestReceipts, +} + +impl DownloadInstallReceipts { + pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks, id); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + id: &PackageId, + ) -> impl FnOnce(&patch_db::Verifier) -> Result { + let package_receipts = crate::db::package::PackageReceipts::setup(locks); + let manifest_receipts = crate::db::package::ManifestReceipts::setup(locks, id); + + move |skeleton_key| { + Ok(Self { + package_receipts: package_receipts(skeleton_key)?, + manifest_receipts: manifest_receipts(skeleton_key)?, + }) + } + } +} + #[instrument(skip(ctx, temp_manifest, s9pk))] pub async fn download_install_s9pk( ctx: &RpcContext, @@ -692,14 +734,14 @@ pub async fn download_install_s9pk( if let Err(e) = async { let mut db_handle = ctx.db.handle(); let mut tx = db_handle.begin().await?; + let receipts = DownloadInstallReceipts::new(&mut tx, &pkg_id).await?; // Build set of existing manifests let mut manifests = Vec::new(); - for pkg in crate::db::package::get_packages(&mut tx).await? { - match crate::db::package::get_manifest(&mut tx, &pkg).await? { - Some(m) => { - manifests.push(m); - } - None => {} + for pkg in crate::db::package::get_packages(&mut tx, &receipts.package_receipts).await? { + if let Some(m) = + crate::db::package::get_manifest(&mut tx, &pkg, &receipts.manifest_receipts).await? + { + manifests.push(m); } } // Build map of current port -> ssl mappings @@ -732,6 +774,7 @@ pub async fn download_install_s9pk( } } } + drop(receipts); tx.save().await?; drop(db_handle); @@ -792,8 +835,9 @@ pub async fn download_install_s9pk( { let mut handle = ctx.db.handle(); let mut tx = handle.begin().await?; + let receipts = cleanup::CleanupFailedReceipts::new(&mut tx).await?; - if let Err(e) = cleanup_failed(&ctx, &mut tx, pkg_id).await { + if let Err(e) = cleanup_failed(&ctx, &mut tx, pkg_id, &receipts).await { tracing::error!("Failed to clean up {}@{}: {}", pkg_id, version, e); tracing::debug!("{:?}", e); } else { @@ -805,6 +849,39 @@ pub async fn download_install_s9pk( } } +pub struct InstallS9Receipts { + config: ConfigReceipts, + + recovered_packages: LockReceipt, ()>, +} + +impl InstallS9Receipts { + pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result { + let mut locks = Vec::new(); + + let setup = Self::setup(&mut locks); + Ok(setup(&db.lock_all(locks).await?)?) + } + + pub fn setup( + locks: &mut Vec, + ) -> impl FnOnce(&patch_db::Verifier) -> Result { + let config = ConfigReceipts::setup(locks); + + let recovered_packages = crate::db::DatabaseModel::new() + .recovered_packages() + .make_locker(LockType::Write) + .add_to_keys(locks); + + move |skeleton_key| { + Ok(Self { + config: config(skeleton_key)?, + recovered_packages: recovered_packages.verify(skeleton_key)?, + }) + } + } +} + #[instrument(skip(ctx, rdr))] pub async fn install_s9pk( ctx: &RpcContext, @@ -1183,6 +1260,8 @@ pub async fn install_s9pk( }, ); pde.save(&mut tx).await?; + let receipts = InstallS9Receipts::new(&mut tx).await?; + // UpdateDependencyReceipts let mut dep_errs = model .expect(&mut tx) .await? @@ -1193,7 +1272,14 @@ pub async fn install_s9pk( .dependency_errors() .get_mut(&mut tx) .await?; - *dep_errs = DependencyErrors::init(ctx, &mut tx, &manifest, ¤t_dependencies).await?; + *dep_errs = DependencyErrors::init( + ctx, + &mut tx, + &manifest, + ¤t_dependencies, + &receipts.config.try_heal_receipts, + ) + .await?; dep_errs.save(&mut tx).await?; if let PackageDataEntry::Updating { @@ -1244,6 +1330,7 @@ pub async fn install_s9pk( false, &mut BTreeMap::new(), &mut BTreeMap::new(), + &receipts.config, ) .await?; let mut main_status = crate::db::DatabaseModel::new() @@ -1261,9 +1348,20 @@ pub async fn install_s9pk( *main_status = prev.status.main; main_status.save(&mut tx).await?; } - remove_from_current_dependents_lists(&mut tx, pkg_id, prev.current_dependencies.keys()) - .await?; // remove previous - add_dependent_to_current_dependents_lists(&mut tx, pkg_id, ¤t_dependencies).await?; // add new + remove_from_current_dependents_lists( + &mut tx, + pkg_id, + prev.current_dependencies.keys(), + &receipts.config.current_dependents, + ) + .await?; // remove previous + add_dependent_to_current_dependents_lists( + &mut tx, + pkg_id, + ¤t_dependencies, + &receipts.config.current_dependents, + ) + .await?; // add new update_dependency_errors_of_dependents( ctx, &mut tx, @@ -1272,6 +1370,7 @@ pub async fn install_s9pk( .keys() .chain(prev.current_dependents.keys()) .collect::>(), + &receipts.config.update_dependency_receipts, ) .await?; if &prev.manifest.version != version { @@ -1290,39 +1389,84 @@ pub async fn install_s9pk( &manifest.volumes, ) .await?; - add_dependent_to_current_dependents_lists(&mut tx, pkg_id, ¤t_dependencies).await?; - update_dependency_errors_of_dependents(ctx, &mut tx, pkg_id, current_dependents.keys()) - .await?; + add_dependent_to_current_dependents_lists( + &mut tx, + pkg_id, + ¤t_dependencies, + &receipts.config.current_dependents, + ) + .await?; + update_dependency_errors_of_dependents( + ctx, + &mut tx, + pkg_id, + current_dependents.keys(), + &receipts.config.update_dependency_receipts, + ) + .await?; } else if let Some(recovered) = { - // solve taxonomy escalation - crate::db::DatabaseModel::new() - .recovered_packages() - .lock(&mut tx, LockType::Write) - .await?; - crate::db::DatabaseModel::new() - .recovered_packages() - .idx_model(pkg_id) - .get(&mut tx, true) + receipts + .recovered_packages + .get(&mut tx) .await? - .into_owned() + .remove(pkg_id) } { - handle_recovered_package(recovered, manifest, ctx, pkg_id, version, &mut tx).await?; - add_dependent_to_current_dependents_lists(&mut tx, pkg_id, ¤t_dependencies).await?; - update_dependency_errors_of_dependents(ctx, &mut tx, pkg_id, current_dependents.keys()) - .await?; + handle_recovered_package( + recovered, + manifest, + ctx, + pkg_id, + version, + &mut tx, + &receipts.config, + ) + .await?; + add_dependent_to_current_dependents_lists( + &mut tx, + pkg_id, + ¤t_dependencies, + &receipts.config.current_dependents, + ) + .await?; + update_dependency_errors_of_dependents( + ctx, + &mut tx, + pkg_id, + current_dependents.keys(), + &receipts.config.update_dependency_receipts, + ) + .await?; } else { - add_dependent_to_current_dependents_lists(&mut tx, pkg_id, ¤t_dependencies).await?; - update_dependency_errors_of_dependents(ctx, &mut tx, pkg_id, current_dependents.keys()) - .await?; + add_dependent_to_current_dependents_lists( + &mut tx, + pkg_id, + ¤t_dependencies, + &receipts.config.current_dependents, + ) + .await?; + update_dependency_errors_of_dependents( + ctx, + &mut tx, + pkg_id, + current_dependents.keys(), + &receipts.config.update_dependency_receipts, + ) + .await?; } - crate::db::DatabaseModel::new() - .recovered_packages() - .remove(&mut tx, pkg_id) + let recovered_packages = { + let mut r = receipts.recovered_packages.get(&mut tx).await?; + r.remove(pkg_id); + r + }; + receipts + .recovered_packages + .set(&mut tx, recovered_packages) .await?; if let Some(installed) = pde.installed() { - reconfigure_dependents_with_live_pointers(ctx, &mut tx, installed).await?; + reconfigure_dependents_with_live_pointers(ctx, &mut tx, &receipts.config, installed) + .await?; } sql_tx.commit().await?; @@ -1333,7 +1477,7 @@ pub async fn install_s9pk( Ok(()) } -#[instrument(skip(ctx, tx))] +#[instrument(skip(ctx, tx, receipts))] async fn handle_recovered_package( recovered: RecoveredPackageInfo, manifest: Manifest, @@ -1341,6 +1485,7 @@ async fn handle_recovered_package( pkg_id: &PackageId, version: &Version, tx: &mut patch_db::Transaction<&mut patch_db::PatchDbHandle>, + receipts: &ConfigReceipts, ) -> Result<(), Error> { let configured = if let Some(migration) = manifest @@ -1361,6 +1506,7 @@ async fn handle_recovered_package( false, &mut BTreeMap::new(), &mut BTreeMap::new(), + &receipts, ) .await?; } diff --git a/backend/src/install/update.rs b/backend/src/install/update.rs index a99418bce..9b4b7b42f 100644 --- a/backend/src/install/update.rs +++ b/backend/src/install/update.rs @@ -24,10 +24,12 @@ pub async fn dry( let mut db = ctx.db.handle(); let mut tx = db.begin().await?; let mut breakages = BTreeMap::new(); + let receipts = crate::dependencies::BreakTransitiveReceipts::new(&mut tx).await?; crate::db::DatabaseModel::new() .package_data() .lock(&mut tx, LockType::Read) .await?; + for dependent in crate::db::DatabaseModel::new() .package_data() .idx_model(&id) @@ -65,6 +67,7 @@ pub async fn dry( received: version.clone(), }, &mut breakages, + &receipts, ) .await?; } diff --git a/backend/src/manager/health.rs b/backend/src/manager/health.rs index 81aa28627..fc0e66974 100644 --- a/backend/src/manager/health.rs +++ b/backend/src/manager/health.rs @@ -4,12 +4,12 @@ use std::sync::atomic::{AtomicBool, Ordering}; use patch_db::{DbHandle, LockType}; use tracing::instrument; -use crate::context::RpcContext; use crate::dependencies::{break_transitive, heal_transitive, DependencyError}; use crate::s9pk::manifest::PackageId; use crate::status::health_check::{HealthCheckId, HealthCheckResult}; use crate::status::MainStatus; use crate::Error; +use crate::{context::RpcContext, dependencies::BreakTransitiveReceipts}; #[instrument(skip(ctx, db))] pub async fn check( @@ -98,6 +98,10 @@ pub async fn check( checkpoint.save().await?; + tracing::debug!("Checking health of {}", id); + let receipts = crate::dependencies::BreakTransitiveReceipts::new(&mut tx).await?; + tracing::debug!("Got receipts {}", id); + for (dependent, info) in &*current_dependents { let failures: BTreeMap = health_results .iter() @@ -113,10 +117,11 @@ pub async fn check( id, DependencyError::HealthChecksFailed { failures }, &mut BTreeMap::new(), + &receipts, ) .await?; } else { - heal_transitive(ctx, &mut tx, &dependent, id).await?; + heal_transitive(ctx, &mut tx, &dependent, id, &receipts.dependency_receipt).await?; } } diff --git a/backend/src/s9pk/mod.rs b/backend/src/s9pk/mod.rs index f34bfad9a..94e1f0666 100644 --- a/backend/src/s9pk/mod.rs +++ b/backend/src/s9pk/mod.rs @@ -26,7 +26,6 @@ pub const SIG_CONTEXT: &'static [u8] = b"s9pk"; #[instrument(skip(ctx))] pub fn pack(#[context] ctx: SdkContext, #[arg] path: Option) -> Result<(), Error> { use std::fs::File; - use std::io::Read; let path = if let Some(path) = path { path diff --git a/backend/src/util/logger.rs b/backend/src/util/logger.rs index 196686311..8208f7de6 100644 --- a/backend/src/util/logger.rs +++ b/backend/src/util/logger.rs @@ -1,4 +1,3 @@ -use tracing::metadata::LevelFilter; use tracing::Subscriber; use tracing_subscriber::util::SubscriberInitExt; diff --git a/backend/src/util/mod.rs b/backend/src/util/mod.rs index 4e061d0bb..9646b9f17 100644 --- a/backend/src/util/mod.rs +++ b/backend/src/util/mod.rs @@ -131,6 +131,9 @@ impl Version { pub fn as_str(&self) -> &str { self.string.as_str() } + pub fn into_version(self) -> emver::Version { + self.version + } } impl std::fmt::Display for Version { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/backend/src/version/mod.rs b/backend/src/version/mod.rs index 4c74588e5..7021f6c1a 100644 --- a/backend/src/version/mod.rs +++ b/backend/src/version/mod.rs @@ -2,11 +2,10 @@ use std::cmp::Ordering; use async_trait::async_trait; use color_eyre::eyre::eyre; -use patch_db::json_ptr::JsonPointer; -use patch_db::{DbHandle, LockType}; +use patch_db::DbHandle; use rpc_toolkit::command; -use crate::{Error, ResultExt}; +use crate::{init::InitReceipts, Error}; mod v0_3_0; mod v0_3_0_1; @@ -15,7 +14,7 @@ mod v0_3_0_3; pub type Current = v0_3_0_3::Version; -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[serde(untagged)] enum Version { V0_3_0(Wrapper), @@ -25,6 +24,27 @@ enum Version { Other(emver::Version), } +impl Version { + fn from_util_version(version: crate::util::Version) -> Self { + serde_json::to_value(version.clone()) + .and_then(serde_json::from_value) + .unwrap_or_else(|_e| { + tracing::warn!("Can't deserialize: {:?} and falling back to other", version); + Version::Other(version.into_version()) + }) + } + #[cfg(test)] + fn as_sem_ver(&self) -> emver::Version { + match self { + Version::V0_3_0(Wrapper(x)) => x.semver(), + Version::V0_3_0_1(Wrapper(x)) => x.semver(), + Version::V0_3_0_2(Wrapper(x)) => x.semver(), + Version::V0_3_0_3(Wrapper(x)) => x.semver(), + Version::Other(x) => x.clone(), + } + } +} + #[async_trait] pub trait VersionT where @@ -36,16 +56,18 @@ where fn compat(&self) -> &'static emver::VersionRange; async fn up(&self, db: &mut Db) -> Result<(), Error>; async fn down(&self, db: &mut Db) -> Result<(), Error>; - async fn commit(&self, db: &mut Db) -> Result<(), Error> { - crate::db::DatabaseModel::new() - .server_info() - .eos_version_compat() - .put(db, &self.compat()) + async fn commit( + &self, + db: &mut Db, + receipts: &InitReceipts, + ) -> Result<(), Error> { + receipts + .version_range + .set(db, self.compat().clone()) .await?; - crate::db::DatabaseModel::new() - .server_info() - .version() - .put(db, &self.semver().into()) + receipts + .server_version + .set(db, self.semver().into()) .await?; Ok(()) @@ -54,10 +76,11 @@ where &self, version: &V, db: &mut Db, + receipts: &InitReceipts, ) -> Result<(), Error> { match self.semver().cmp(&version.semver()) { - Ordering::Greater => self.rollback_to_unchecked(version, db).await, - Ordering::Less => version.migrate_from_unchecked(self, db).await, + Ordering::Greater => self.rollback_to_unchecked(version, db, receipts).await, + Ordering::Less => version.migrate_from_unchecked(self, db, receipts).await, Ordering::Equal => Ok(()), } } @@ -65,31 +88,38 @@ where &self, version: &V, db: &mut Db, + receipts: &InitReceipts, ) -> Result<(), Error> { let previous = Self::Previous::new(); if version.semver() != previous.semver() { - previous.migrate_from_unchecked(version, db).await?; + previous + .migrate_from_unchecked(version, db, receipts) + .await?; } tracing::info!("{} -> {}", previous.semver(), self.semver(),); self.up(db).await?; - self.commit(db).await?; + self.commit(db, receipts).await?; Ok(()) } async fn rollback_to_unchecked( &self, version: &V, db: &mut Db, + receipts: &InitReceipts, ) -> Result<(), Error> { let previous = Self::Previous::new(); tracing::info!("{} -> {}", self.semver(), previous.semver(),); self.down(db).await?; - previous.commit(db).await?; + previous.commit(db, receipts).await?; if version.semver() != previous.semver() { - previous.rollback_to_unchecked(version, db).await?; + previous + .rollback_to_unchecked(version, db, receipts) + .await?; } Ok(()) } } +#[derive(Debug, Clone)] struct Wrapper(T); impl serde::Serialize for Wrapper where @@ -106,7 +136,7 @@ where fn deserialize>(deserializer: D) -> Result { let v = crate::util::Version::deserialize(deserializer)?; let version = T::new(); - if &*v == &version.semver() { + if *v == version.semver() { Ok(Wrapper(version)) } else { Err(serde::de::Error::custom("Mismatched Version")) @@ -114,17 +144,16 @@ where } } -pub async fn init(db: &mut Db) -> Result<(), Error> { - let ptr: JsonPointer = "/server-info/version" - .parse() - .with_kind(crate::ErrorKind::Database)?; - db.lock(ptr.clone(), LockType::Write).await?; - let version: Version = db.get(&ptr).await?; +pub async fn init( + db: &mut Db, + receipts: &crate::init::InitReceipts, +) -> Result<(), Error> { + let version = Version::from_util_version(receipts.server_version.get(db).await?); match version { - Version::V0_3_0(v) => v.0.migrate_to(&Current::new(), db).await?, - Version::V0_3_0_1(v) => v.0.migrate_to(&Current::new(), db).await?, - Version::V0_3_0_2(v) => v.0.migrate_to(&Current::new(), db).await?, - Version::V0_3_0_3(v) => v.0.migrate_to(&Current::new(), db).await?, + Version::V0_3_0(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, + Version::V0_3_0_1(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, + Version::V0_3_0_2(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, + Version::V0_3_0_3(v) => v.0.migrate_to(&Current::new(), db, receipts).await?, Version::Other(_) => { return Err(Error::new( eyre!("Cannot downgrade"), @@ -135,10 +164,47 @@ pub async fn init(db: &mut Db) -> Result<(), Error> { Ok(()) } -pub const COMMIT_HASH: &'static str = +pub const COMMIT_HASH: &str = git_version::git_version!(args = ["--always", "--abbrev=40", "--dirty=-modified"]); #[command(rename = "git-info", local, metadata(authenticated = false))] pub fn git_info() -> Result<&'static str, Error> { Ok(COMMIT_HASH) } + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + fn em_version() -> impl Strategy { + any::<(usize, usize, usize, usize)>().prop_map(|(major, minor, patch, super_minor)| { + emver::Version::new(major, minor, patch, super_minor) + }) + } + + fn versions() -> impl Strategy { + prop_oneof![ + Just(Version::V0_3_0(Wrapper(v0_3_0::Version::new()))), + Just(Version::V0_3_0_1(Wrapper(v0_3_0_1::Version::new()))), + Just(Version::V0_3_0_2(Wrapper(v0_3_0_2::Version::new()))), + Just(Version::V0_3_0_3(Wrapper(v0_3_0_3::Version::new()))), + em_version().prop_map(Version::Other), + ] + } + + proptest! { + #[test] + fn emversion_isomorphic_version(original in em_version()) { + let version = Version::from_util_version(original.clone().into()); + let back = version.as_sem_ver(); + prop_assert_eq!(original, back, "All versions should round trip"); + } + #[test] + fn version_isomorphic_em_version(version in versions()) { + let sem_ver = version.as_sem_ver(); + let back = Version::from_util_version(sem_ver.into()); + prop_assert_eq!(format!("{:?}",version), format!("{:?}", back), "All versions should round trip"); + } + } +} diff --git a/backend/src/version/v0_3_0.rs b/backend/src/version/v0_3_0.rs index e2c660cf1..087cbccef 100644 --- a/backend/src/version/v0_3_0.rs +++ b/backend/src/version/v0_3_0.rs @@ -14,6 +14,7 @@ lazy_static! { ); } +#[derive(Debug, Clone)] pub struct Version; #[async_trait] impl VersionT for Version { diff --git a/backend/src/version/v0_3_0_1.rs b/backend/src/version/v0_3_0_1.rs index dd858ed27..b6fe14bb3 100644 --- a/backend/src/version/v0_3_0_1.rs +++ b/backend/src/version/v0_3_0_1.rs @@ -11,6 +11,7 @@ use crate::util::Invoke; const V0_3_0_1: emver::Version = emver::Version::new(0, 3, 0, 1); +#[derive(Debug, Clone)] pub struct Version; #[async_trait] impl VersionT for Version { diff --git a/backend/src/version/v0_3_0_2.rs b/backend/src/version/v0_3_0_2.rs index fe8397b8a..29d6720ae 100644 --- a/backend/src/version/v0_3_0_2.rs +++ b/backend/src/version/v0_3_0_2.rs @@ -4,6 +4,7 @@ use super::*; const V0_3_0_2: emver::Version = emver::Version::new(0, 3, 0, 2); +#[derive(Debug, Clone)] pub struct Version; #[async_trait] impl VersionT for Version { diff --git a/backend/src/version/v0_3_0_3.rs b/backend/src/version/v0_3_0_3.rs index 4f6c9ffda..0042f925e 100644 --- a/backend/src/version/v0_3_0_3.rs +++ b/backend/src/version/v0_3_0_3.rs @@ -4,6 +4,7 @@ use super::*; const V0_3_0_3: emver::Version = emver::Version::new(0, 3, 0, 3); +#[derive(Clone, Debug)] pub struct Version; #[async_trait] impl VersionT for Version { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5886e2e91..ebc1f29b8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -318,6 +318,55 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics/node_modules/ajv": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@angular-devkit/schematics/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/@angular/animations": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.3.0.tgz", @@ -368,6 +417,70 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1303.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", + "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "13.3.0", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/cli/node_modules/ajv": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@angular/cli/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/@angular/common": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.3.0.tgz", @@ -3132,6 +3245,55 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "dependencies": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + }, + "engines": { + "node": "^12.20.0 || ^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@schematics/angular/node_modules/ajv": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@schematics/angular/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/@start9labs/argon2": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@start9labs/argon2/-/argon2-0.1.0.tgz", @@ -14282,6 +14444,40 @@ "magic-string": "0.25.7", "ora": "5.4.1", "rxjs": "6.6.7" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } } }, "@angular/animations": { @@ -14317,6 +14513,50 @@ "semver": "7.3.5", "symbol-observable": "4.0.0", "uuid": "8.3.2" + }, + "dependencies": { + "@angular-devkit/architect": { + "version": "0.1303.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.0.tgz", + "integrity": "sha512-kTcKB917ICA8j53SGo4gn+qAlzx8si+iHnOTbp5QlMr7qt/Iz07SVVI8mRlMD6c6lr7eE/fVlCLzEZ1+WCQpTA==", + "dev": true, + "requires": { + "@angular-devkit/core": "13.3.0", + "rxjs": "6.6.7" + } + }, + "@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } } }, "@angular/common": { @@ -16309,6 +16549,40 @@ "@angular-devkit/core": "13.3.0", "@angular-devkit/schematics": "13.3.0", "jsonc-parser": "3.0.0" + }, + "dependencies": { + "@angular-devkit/core": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.0.tgz", + "integrity": "sha512-8YrreVbWlJVZnk5zs4vfkRItrPEtWhUcxWOBfYT/Kwu4FwJVAnNuhJAxxXOAQ2Ckd7cv30Idh/RFVLbTZ5Gs9w==", + "dev": true, + "requires": { + "ajv": "8.9.0", + "ajv-formats": "2.1.1", + "fast-json-stable-stringify": "2.1.0", + "magic-string": "0.25.7", + "rxjs": "6.6.7", + "source-map": "0.7.3" + } + }, + "ajv": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } } }, "@start9labs/argon2": { diff --git a/patch-db b/patch-db index d3426671a..35973d7ae 160000 --- a/patch-db +++ b/patch-db @@ -1 +1 @@ -Subproject commit d3426671a1e77f9f636847aee965892eb6b4b177 +Subproject commit 35973d7aef054842faa13c82f357252563108949