use std::cmp::Ordering; use std::collections::BTreeMap; use std::time::Duration; use color_eyre::eyre::eyre; use emver::VersionRange; use futures::future::BoxFuture; use futures::FutureExt; 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::config::action::{ConfigActions, ConfigRes}; use crate::config::spec::PackagePointerSpec; use crate::config::{not_found, Config, ConfigReceipts, ConfigSpec}; use crate::context::RpcContext; use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntry}; use crate::procedure::{NoOutput, PackageProcedure}; use crate::s9pk::manifest::{Manifest, PackageId}; use crate::status::health_check::{HealthCheckId, HealthCheckResult}; use crate::status::{MainStatus, Status}; use crate::util::serde::display_serializable; use crate::util::{display_none, Version}; use crate::volume::Volumes; use crate::Error; #[command(subcommands(configure))] pub fn dependency() -> Result<(), Error> { Ok(()) } #[derive(Clone, Debug, thiserror::Error, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[serde(tag = "type")] pub enum DependencyError { NotInstalled, // { "type": "not-installed" } #[serde(rename_all = "kebab-case")] IncorrectVersion { expected: VersionRange, received: Version, }, // { "type": "incorrect-version", "expected": "0.1.0", "received": "^0.2.0" } #[serde(rename_all = "kebab-case")] ConfigUnsatisfied { error: String, }, // { "type": "config-unsatisfied", "error": "Bitcoin Core must have pruning set to manual." } NotRunning, // { "type": "not-running" } #[serde(rename_all = "kebab-case")] HealthChecksFailed { failures: BTreeMap, }, // { "type": "health-checks-failed", "checks": { "rpc": { "time": "2021-05-11T18:21:29Z", "result": "starting" } } } #[serde(rename_all = "kebab-case")] 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::*; use DependencyError::*; match (self, other) { (NotInstalled, NotInstalled) => Equal, (NotInstalled, _) => Greater, (_, NotInstalled) => Less, (IncorrectVersion { .. }, IncorrectVersion { .. }) => Equal, (IncorrectVersion { .. }, _) => Greater, (_, IncorrectVersion { .. }) => Less, (ConfigUnsatisfied { .. }, ConfigUnsatisfied { .. }) => Equal, (ConfigUnsatisfied { .. }, _) => Greater, (_, ConfigUnsatisfied { .. }) => Less, (NotRunning, NotRunning) => Equal, (NotRunning, _) => Greater, (_, NotRunning) => Less, (HealthChecksFailed { .. }, HealthChecksFailed { .. }) => Equal, (HealthChecksFailed { .. }, _) => Greater, (_, HealthChecksFailed { .. }) => Less, (Transitive, Transitive) => Equal, } } pub fn merge_with(self, other: DependencyError) -> DependencyError { match (self, other) { (DependencyError::NotInstalled, _) | (_, DependencyError::NotInstalled) => { DependencyError::NotInstalled } (DependencyError::IncorrectVersion { expected, received }, _) | (_, DependencyError::IncorrectVersion { expected, received }) => { DependencyError::IncorrectVersion { expected, received } } ( DependencyError::ConfigUnsatisfied { error: e0 }, DependencyError::ConfigUnsatisfied { error: e1 }, ) => DependencyError::ConfigUnsatisfied { error: e0 + "\n" + &e1, }, (DependencyError::ConfigUnsatisfied { error }, _) | (_, DependencyError::ConfigUnsatisfied { error }) => { DependencyError::ConfigUnsatisfied { error } } (DependencyError::NotRunning, _) | (_, DependencyError::NotRunning) => { DependencyError::NotRunning } ( DependencyError::HealthChecksFailed { failures: f0 }, DependencyError::HealthChecksFailed { failures: f1 }, ) => DependencyError::HealthChecksFailed { failures: f0.into_iter().chain(f1.into_iter()).collect(), }, (DependencyError::HealthChecksFailed { failures }, _) | (_, DependencyError::HealthChecksFailed { failures }) => { DependencyError::HealthChecksFailed { failures } } (DependencyError::Transitive, _) => DependencyError::Transitive, } } #[instrument(skip(ctx, db, receipts))] pub fn try_heal<'a, Db: DbHandle>( self, ctx: &'a RpcContext, db: &'a mut Db, id: &'a PackageId, 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 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, receipts) .await? } else { Some(DependencyError::NotInstalled) } } DependencyError::IncorrectVersion { expected, .. } => { let version: Version = receipts .manifest_version .get(db, dependency) .await? .unwrap_or_default(); if version.satisfies(&expected) { DependencyError::ConfigUnsatisfied { error: String::new(), } .try_heal(ctx, db, id, dependency, dependency_config, info, receipts) .await? } else { Some(DependencyError::IncorrectVersion { expected, received: version, }) } } DependencyError::ConfigUnsatisfied { .. } => { let dependent_manifest = receipts.manifest.get(db, id).await?.ok_or_else(not_found)?; let dependency_manifest = receipts .manifest .get(db, dependency) .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 { cfg_info .get( ctx, dependency, &dependency_manifest.version, &dependency_manifest.volumes, ) .await? .config .unwrap_or_default() } else { Config::default() }; if let Some(cfg_req) = &info.config { if let Err(error) = cfg_req .check( ctx, id, &dependent_manifest.version, &dependent_manifest.volumes, &dependency_config, ) .await? { return Ok(Some(DependencyError::ConfigUnsatisfied { error })); } } DependencyError::NotRunning .try_heal( ctx, db, id, dependency, Some(dependency_config), info, receipts, ) .await? } DependencyError::NotRunning => { let status = receipts .status .get(db, dependency) .await? .ok_or_else(not_found)?; if status.main.running() { DependencyError::HealthChecksFailed { failures: BTreeMap::new(), } .try_heal(ctx, db, id, dependency, dependency_config, info, receipts) .await? } else { Some(DependencyError::NotRunning) } } DependencyError::HealthChecksFailed { .. } => { let status = receipts .status .get(db, dependency) .await? .ok_or_else(not_found)?; match status.main { MainStatus::BackingUp { started: Some(_), health, } | MainStatus::Running { health, .. } => { let mut failures = BTreeMap::new(); for (check, res) in health { if !matches!(res, HealthCheckResult::Success) && receipts .current_dependencies .get(db, id) .await? .ok_or_else(not_found)? .get(dependency) .map(|x| x.health_checks.contains(&check)) .unwrap_or(false) { failures.insert(check.clone(), res.clone()); } } if !failures.is_empty() { Some(DependencyError::HealthChecksFailed { failures }) } else { DependencyError::Transitive .try_heal( ctx, db, id, dependency, dependency_config, info, receipts, ) .await? } } MainStatus::Starting => { DependencyError::Transitive .try_heal( ctx, db, id, dependency, dependency_config, info, receipts, ) .await? } _ => return Ok(Some(DependencyError::NotRunning)), } } DependencyError::Transitive => { if receipts .dependency_errors .get(db, dependency) .await? .unwrap_or_default() .0 .is_empty() { None } else { Some(DependencyError::Transitive) } } }) } .boxed() } } impl std::fmt::Display for DependencyError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DependencyError::NotInstalled => write!(f, "Not Installed"), DependencyError::IncorrectVersion { expected, received } => write!( f, "Incorrect Version: Expected {}, Received {}", expected, received.as_str() ), DependencyError::ConfigUnsatisfied { error } => { write!(f, "Configuration Requirements Not Satisfied: {}", error) } DependencyError::NotRunning => write!(f, "Not Running"), DependencyError::HealthChecksFailed { failures } => { write!(f, "Failed Health Check(s): ")?; let mut comma = false; for (check, res) in failures { if !comma { comma = true; } else { write!(f, ", ")?; } write!(f, "{}: {}", check, res)?; } Ok(()) } DependencyError::Transitive => { write!(f, "Dependency Error(s)") } } } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct TaggedDependencyError { pub dependency: PackageId, pub error: DependencyError, } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct BreakageRes(pub BTreeMap); #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Dependencies(pub BTreeMap); impl Map for Dependencies { type Key = PackageId; type Value = DepInfo; fn get(&self, key: &Self::Key) -> Option<&Self::Value> { self.0.get(key) } } impl HasModel for Dependencies { type Model = MapModel; } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] #[serde(tag = "type")] pub enum DependencyRequirement { OptIn { how: String }, OptOut { how: String }, Required, } impl DependencyRequirement { pub fn required(&self) -> bool { matches!(self, &DependencyRequirement::Required) } } #[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct DepInfo { pub version: VersionRange, pub requirement: DependencyRequirement, pub description: Option, #[serde(default)] #[model] pub config: Option, } impl DepInfo { pub async fn satisfied( &self, ctx: &RpcContext, db: &mut Db, dependency_id: &PackageId, dependency_config: Option, // fetch if none dependent_id: &PackageId, receipts: &TryHealReceipts, ) -> Result, Error> { Ok( if let Some(err) = DependencyError::NotInstalled .try_heal( ctx, db, dependent_id, dependency_id, dependency_config, self, receipts, ) .await? { Err(err) } else { Ok(()) }, ) } } #[derive(Clone, Debug, Deserialize, Serialize, HasModel)] #[serde(rename_all = "kebab-case")] pub struct DependencyConfig { check: PackageProcedure, auto_configure: PackageProcedure, } impl DependencyConfig { pub async fn check( &self, ctx: &RpcContext, dependent_id: &PackageId, dependent_version: &Version, dependent_volumes: &Volumes, dependency_config: &Config, ) -> Result, Error> { Ok(self .check .sandboxed( ctx, dependent_id, dependent_version, dependent_volumes, Some(dependency_config), None, ) .await? .map_err(|(_, e)| e)) } pub async fn auto_configure( &self, ctx: &RpcContext, dependent_id: &PackageId, dependent_version: &Version, dependent_volumes: &Volumes, old: &Config, ) -> Result { self.auto_configure .sandboxed( ctx, dependent_id, dependent_version, dependent_volumes, Some(old), None, ) .await? .map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure)) } } 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) )] pub async fn configure( #[arg(rename = "dependent-id")] dependent_id: PackageId, #[arg(rename = "dependency-id")] dependency_id: PackageId, ) -> Result<(PackageId, PackageId), Error> { Ok((dependent_id, dependency_id)) } pub async fn configure_impl( ctx: RpcContext, (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()), &receipts).await?; let locks = ConfigReceipts::new(&mut db).await?; Ok(crate::config::configure( &ctx, &mut db, &dep_id, Some(new_config), &Some(Duration::from_secs(3).into()), false, &mut BTreeMap::new(), &mut BTreeMap::new(), &locks, ) .await?) } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct ConfigDryRes { pub old_config: Config, pub new_config: Config, pub spec: ConfigSpec, } #[command(rename = "dry", display(display_serializable))] #[instrument(skip(ctx))] pub async fn configure_dry( #[context] ctx: RpcContext, #[parent_data] (pkg_id, dependency_id): (PackageId, PackageId), ) -> Result { let mut db = ctx.db.handle(); 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 { 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( eyre!( "dependency for {} not found in the manifest for {}", dependency_id, pkg_id ), crate::ErrorKind::NotFound, ) })? .config .as_ref() .ok_or_else(|| { Error::new( eyre!( "dependency config for {} not found on {}", dependency_id, pkg_id ), crate::ErrorKind::NotFound, ) })?; let ConfigRes { config: maybe_config, spec, } = dependency_config_action .get( &ctx, &dependency_id, &dependency_version, &dependency_volumes, ) .await?; let old_config = if let Some(config) = maybe_config { config } else { spec.gen( &mut rand::rngs::StdRng::from_entropy(), &Some(Duration::new(10, 0)), )? }; let new_config = dependency .auto_configure .sandboxed( &ctx, &pkg_id, &pkg_version, &pkg_volumes, Some(&old_config), None, ) .await? .map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))?; Ok(ConfigDryRes { old_config, new_config, spec, }) } #[instrument(skip(db, current_dependencies, current_dependent_receipt))] pub async fn add_dependent_to_current_dependents_lists< 'a, Db: DbHandle, I: IntoIterator, >( 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(mut dependency_dependents) = current_dependent_receipt.get(db, dependency).await? { dependency_dependents.insert(dependent_id.clone(), dep_info.clone()); current_dependent_receipt .set(db, dependency_dependents, dependency) .await?; } } Ok(()) } #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct DependencyErrors(pub BTreeMap); impl Map for DependencyErrors { type Key = PackageId; type Value = DependencyError; fn get(&self, key: &Self::Key) -> Option<&Self::Value> { self.0.get(key) } } impl HasModel for DependencyErrors { type Model = MapModel; } impl DependencyErrors { pub async fn init( ctx: &RpcContext, 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| { manifest .dependencies .0 .get(dependency_id) .map(|info| (dependency_id, info)) }) { if let Err(e) = info .satisfied(ctx, db, dependency_id, None, &manifest.id, receipts) .await? { res.insert(dependency_id.clone(), e); } } Ok(DependencyErrors(res)) } } impl std::fmt::Display for DependencyErrors { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{{ ")?; for (idx, (id, err)) in self.0.iter().enumerate() { write!(f, "{}: {}", id, err)?; if idx < self.0.len() - 1 { // not last write!(f, ", ")?; } } write!(f, " }}") } } pub async fn break_all_dependents_transitive<'a, Db: DbHandle>( db: &'a mut Db, id: &'a PackageId, error: DependencyError, breakages: &'a mut BTreeMap, receipts: &'a BreakTransitiveReceipts, ) -> Result<(), Error> { for dependent in receipts .current_dependents .get(db, id) .await? .iter() .flat_map(|x| x.keys()) .filter(|dependent| id != *dependent) { break_transitive(db, dependent, id, error.clone(), breakages, receipts).await?; } Ok(()) } #[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 mut dependency_errors = receipts .dependency_errors .get(&mut tx, id) .await? .ok_or_else(not_found)?; let old = dependency_errors.0.remove(dependency); let newly_broken = if let Some(e) = &old { error.cmp_priority(&e) == Ordering::Greater } else { true }; dependency_errors.0.insert( dependency.clone(), if let Some(old) = old { old.merge_with(error.clone()) } else { error.clone() }, ); if newly_broken { breakages.insert( id.clone(), TaggedDependencyError { dependency: dependency.clone(), error: error.clone(), }, ); receipts .dependency_errors .set(&mut tx, dependency_errors, id) .await?; tx.save().await?; break_all_dependents_transitive( db, id, DependencyError::Transitive, breakages, receipts, ) .await?; } else { receipts .dependency_errors .set(&mut tx, dependency_errors, id) .await?; tx.save().await?; } Ok(()) } .boxed() } #[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> { let dependents = locks .current_dependents .get(db, 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, 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 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 = receipts .dependency .get(db, (id, dependency)) .await? .ok_or_else(not_found)?; if let Some(new) = old .try_heal(ctx, db, id, dependency, None, &info, &receipts.try_heal) .await? { status.dependency_errors.0.insert(dependency.clone(), new); receipts.status.set(db, status, id).await?; } else { receipts.status.set(db, status, id).await?; heal_all_dependents_transitive(ctx, db, id, receipts).await?; } } Ok(()) } .boxed() } 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; let me = &pde.manifest.id; for (dependent_id, dependency_info) in dependents { if dependency_info.pointers.iter().any(|ptr| match ptr { // dependency id matches the package being uninstalled PackagePointerSpec::TorAddress(ptr) => &ptr.package_id == me && dependent_id != me, PackagePointerSpec::LanAddress(ptr) => &ptr.package_id == me && dependent_id != me, // we never need to retarget these PackagePointerSpec::TorKey(_) => false, PackagePointerSpec::Config(_) => false, }) { crate::config::configure( ctx, &mut tx, dependent_id, None, &None, 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)?, }) } } }