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":
This commit is contained in:
J M
2022-05-09 14:53:39 -06:00
committed by GitHub
parent 5d3bc8cfa5
commit 864555bcf0
26 changed files with 2080 additions and 743 deletions

View File

@@ -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"

View File

@@ -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(())
}

View File

@@ -81,7 +81,11 @@ async fn inner_main(cfg_path: Option<&str>) -> Result<Option<Shutdown>, 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!({

View File

@@ -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<String, Value>;
pub trait TypeOf {
@@ -163,6 +169,56 @@ pub fn config(#[arg] id: PackageId) -> Result<PackageId, Error> {
Ok(id)
}
pub struct ConfigGetReceipts {
manifest_volumes: LockReceipt<crate::volume::Volumes, ()>,
manifest_version: LockReceipt<crate::util::Version, ()>,
manifest_config: LockReceipt<Option<ConfigActions>, ()>,
}
impl ConfigGetReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
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<LockTargetId>,
id: &PackageId,
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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<IoFormat>,
) -> Result<ConfigRes, Error> {
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<bool, String>,
config_actions: LockReceipt<ConfigActions, String>,
dependencies: LockReceipt<Dependencies, String>,
volumes: LockReceipt<crate::volume::Volumes, String>,
version: LockReceipt<crate::util::Version, String>,
manifest: LockReceipt<Manifest, String>,
system_pointers: LockReceipt<Vec<spec::SystemPointerSpec>, String>,
pub current_dependents: LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
dependency_errors: LockReceipt<DependencyErrors, String>,
manifest_dependencies_config: LockReceipt<DependencyConfig, (String, String)>,
}
impl ConfigReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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<bool, String> = 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<Db: DbHandle>(
#[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<Config>,
timeout: &Option<Duration>,
dry_run: bool,
overrides: &mut BTreeMap<PackageId, Config>,
breakages: &mut BTreeMap<PackageId, TaggedDependencyError>,
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<PackageId, Config>,
breakages: &'a mut BTreeMap<PackageId, TaggedDependencyError>,
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<PackageId, CurrentDependencyInfo> = 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, &current_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,
&current_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, &current_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,
&current_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"
);
}

View File

@@ -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<PackageId, Config>,
value: &mut Value,
receipts: &ConfigPointerReceipts,
) -> Result<(), ConfigurationError>;
// returns all pointers that are live in the provided config
fn pointers(&self, value: &Value) -> Result<BTreeSet<ValueSpecPointer>, NoMatchWithPath>;
@@ -160,9 +161,10 @@ where
manifest: &Manifest,
config_overrides: &BTreeMap<PackageId, Config>,
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<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
@@ -204,9 +206,10 @@ where
manifest: &Manifest,
config_overrides: &BTreeMap<PackageId, Config>,
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<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
@@ -281,9 +284,10 @@ where
manifest: &Manifest,
config_overrides: &BTreeMap<PackageId, Config>,
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<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
@@ -343,7 +347,7 @@ pub enum ValueSpecAny {
Pointer(WithDescription<ValueSpecPointer>),
}
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<PackageId, Config>,
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<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
@@ -489,6 +518,7 @@ impl ValueSpec for ValueSpecBoolean {
_manifest: &Manifest,
_config_overrides: &BTreeMap<PackageId, Config>,
_value: &mut Value,
_receipts: &ConfigPointerReceipts,
) -> Result<(), ConfigurationError> {
Ok(())
}
@@ -578,6 +608,7 @@ impl ValueSpec for ValueSpecEnum {
_manifest: &Manifest,
_config_overrides: &BTreeMap<PackageId, Config>,
_value: &mut Value,
_receipts: &ConfigPointerReceipts,
) -> Result<(), ConfigurationError> {
Ok(())
}
@@ -664,12 +695,13 @@ where
manifest: &Manifest,
config_overrides: &BTreeMap<PackageId, Config>,
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<PackageId, Config>,
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<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
@@ -898,6 +946,7 @@ impl ValueSpec for ValueSpecNumber {
_manifest: &Manifest,
_config_overrides: &BTreeMap<PackageId, Config>,
_value: &mut Value,
_receipts: &ConfigPointerReceipts,
) -> Result<(), ConfigurationError> {
Ok(())
}
@@ -961,10 +1010,11 @@ impl ValueSpec for ValueSpecObject {
manifest: &Manifest,
config_overrides: &BTreeMap<PackageId, Config>,
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<PackageId, Config>,
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<PackageId, Config>,
_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<PackageId, Config>,
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<PackageId, Config>,
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<PackageId, Config>,
receipts: &ConfigPointerReceipts,
) -> Result<Value, ConfigurationError> {
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<PackageId, Config>,
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<BTreeSet<ValueSpecPointer>, NoMatchWithPath> {
@@ -1640,16 +1707,17 @@ pub struct TorAddressPointer {
interface: InterfaceId,
}
impl TorAddressPointer {
async fn deref<Db: DbHandle>(&self, db: &mut Db) -> Result<Value, ConfigurationError> {
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<Db: DbHandle>(
&self,
db: &mut Db,
receipt: &InterfaceAddressesReceipt,
) -> Result<Value, ConfigurationError> {
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<crate::db::model::InterfaceAddresses, (String, String)>,
}
impl InterfaceAddressesReceipt {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(
locks: &mut Vec<patch_db::LockTargetId>,
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
// 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<Db: DbHandle>(&self, db: &mut Db) -> Result<Value, ConfigurationError> {
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<Db: DbHandle>(
&self,
db: &mut Db,
receipts: &InterfaceAddressesReceipt,
) -> Result<Value, ConfigurationError> {
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<crate::volume::Volumes, String>,
manifest_version: LockReceipt<crate::util::Version, String>,
config_actions: LockReceipt<super::action::ConfigActions, String>,
}
impl ConfigPointerReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(
locks: &mut Vec<patch_db::LockTargetId>,
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
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<PackageId, Config>,
receipts: &ConfigPointerReceipts,
) -> Result<Value, ConfigurationError> {
if let Some(cfg) = config_overrides.get(&self.package_id) {
Ok(self.select(&Value::Object(cfg.clone())))
} else {
let manifest_model: OptionModel<Manifest> = 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<PackageId, Config>,
value: &mut Value,
receipts: &ConfigPointerReceipts,
) -> Result<(), ConfigurationError> {
*value = self.deref(db).await?;
Ok(())

View File

@@ -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<RwLock<WpaCli>>,
}
pub struct RpcCleanReceipts {
cleanup_receipts: CleanupFailedReceipts,
packages: LockReceipt<crate::db::model::AllPackageData, ()>,
package: LockReceipt<crate::db::model::PackageDataEntry, String>,
}
impl RpcCleanReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(
locks: &mut Vec<patch_db::LockTargetId>,
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
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<crate::db::model::ServerInfo, ()>,
}
impl RpcSetNginxReceipts {
pub async fn new(db: &'_ mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(
locks: &mut Vec<patch_db::LockTargetId>,
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
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<RpcContextSeed>);
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<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
#[instrument(skip(self, db, receipts))]
pub async fn set_nginx_conf<Db: DbHandle>(
&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>(())

View File

@@ -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<MainStatus, ()>,
version: LockReceipt<crate::util::Version, ()>,
}
impl StartReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
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<patch_db::LockTargetId>,
id: &PackageId,
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
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<WithRevision<()>, 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<MainStatus, ()>,
}
impl StopReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
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<patch_db::LockTargetId>,
id: &PackageId,
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
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<Db: DbHandle>(
@@ -77,27 +131,18 @@ async fn stop_common<Db: DbHandle>(
breakages: &mut BTreeMap<PackageId, TaggedDependencyError>,
) -> 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(())
}

View File

@@ -260,6 +260,7 @@ pub struct InstalledPackageDataEntry {
#[model]
pub manifest: Manifest,
pub last_backup: Option<DateTime<Utc>>,
#[model]
pub system_pointers: Vec<SystemPointerSpec>,
#[model]
pub dependency_info: BTreeMap<PackageId, StaticDependencyInfo>,

View File

@@ -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: DbHandle>(db: &mut Db) -> Result<Vec<PackageId>, Error> {
let packages = crate::db::DatabaseModel::new()
.package_data()
.get(db, false)
.await?;
pub struct PackageReceipts {
package_data: LockReceipt<super::model::AllPackageData, ()>,
}
impl PackageReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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: DbHandle>(
db: &mut Db,
receipts: &PackageReceipts,
) -> Result<Vec<PackageId>, Error> {
let packages = receipts.package_data.get(db).await?;
Ok(packages.0.keys().cloned().collect())
}
pub struct ManifestReceipts {
manifest: LockReceipt<Manifest, String>,
}
impl ManifestReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
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<LockTargetId>,
id: &PackageId,
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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: DbHandle>(
db: &mut Db,
pkg: &PackageId,
receipts: &ManifestReceipts,
) -> Result<Option<Manifest>, 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?)
}

View File

@@ -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<Status, String>,
manifest: LockReceipt<Manifest, String>,
manifest_version: LockReceipt<Version, String>,
current_dependencies: LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
dependency_errors: LockReceipt<DependencyErrors, String>,
}
impl TryHealReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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<Config>,
info: &'a DepInfo,
receipts: &'a TryHealReceipts,
) -> BoxFuture<'a, Result<Option<Self>, 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<Config>, // fetch if none
dependent_id: &PackageId,
receipts: &TryHealReceipts,
) -> Result<Result<(), DependencyError>, 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<Dependencies, ()>,
dependency_volumes: LockReceipt<Volumes, ()>,
dependency_version: LockReceipt<Version, ()>,
dependency_config_action: LockReceipt<ConfigActions, ()>,
package_volumes: LockReceipt<Volumes, ()>,
package_version: LockReceipt<Version, ()>,
}
impl DependencyConfigReceipts {
pub async fn new(
db: &'_ mut impl DbHandle,
package_id: &PackageId,
dependency_id: &PackageId,
) -> Result<Self, Error> {
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<ConfigDryRes, Error> {
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<ConfigDryRes, Error> {
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<BTreeMap<PackageId, CurrentDependencyInfo>, 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<PackageId, CurrentDependencyInfo>,
receipts: &TryHealReceipts,
) -> Result<DependencyErrors, Error> {
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<PackageId, TaggedDependencyError>,
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<DependencyErrors, String>,
current_dependents: LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
}
impl BreakTransitiveReceipts {
pub async fn new(db: &'_ mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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<PackageId, TaggedDependencyError>,
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<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
status: LockReceipt<Status, String>,
dependency: LockReceipt<DepInfo, (String, String)>,
}
impl DependencyReceipt {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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)?,
})
}
}
}

View File

@@ -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]

View File

@@ -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<bool, Error> {
== "NTPSynchronized=yes")
}
pub struct InitReceipts {
pub server_version: LockReceipt<crate::util::Version, ()>,
pub version_range: LockReceipt<emver::VersionRange, ()>,
pub last_wifi_region: LockReceipt<Option<isocountry::CountryCode>, ()>,
pub status_info: LockReceipt<ServerStatus, ()>,
}
impl InitReceipts {
pub async fn new(db: &mut impl DbHandle) -> Result<Self, Error> {
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?;

View File

@@ -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<DependencyErrors, String>,
manifest: LockReceipt<Manifest, String>,
}
impl UpdateDependencyReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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<PackageDataEntry, String>,
package_entries: LockReceipt<AllPackageData, String>,
}
impl CleanupFailedReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(locks: &mut Vec<LockTargetId>) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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<Db: DbHandle>(
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<Db: DbHandle>(
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<Db: DbHandle>(
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<BTreeMap<PackageId, CurrentDependencyInfo>, 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<PackageId, CurrentDependencyInfo>>(|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<InstalledPackageDataEntry, ()>,
packages: LockReceipt<AllPackageData, ()>,
current_dependents: LockReceipt<BTreeMap<PackageId, CurrentDependencyInfo>, String>,
update_depenency_receipts: UpdateDependencyReceipts,
}
impl UninstallReceipts {
pub async fn new<'a>(db: &'a mut impl DbHandle, id: &PackageId) -> Result<Self, Error> {
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<LockTargetId>,
id: &PackageId,
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
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<Ex>(
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

View File

@@ -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<Vec<(PackageId, Version)>, 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<Self, Error> {
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<patch_db::LockTargetId>,
id: &PackageId,
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
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<BTreeMap<PackageId, RecoveredPackageInfo>, ()>,
}
impl InstallS9Receipts {
pub async fn new<'a>(db: &'a mut impl DbHandle) -> Result<Self, Error> {
let mut locks = Vec::new();
let setup = Self::setup(&mut locks);
Ok(setup(&db.lock_all(locks).await?)?)
}
pub fn setup(
locks: &mut Vec<patch_db::LockTargetId>,
) -> impl FnOnce(&patch_db::Verifier) -> Result<Self, Error> {
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<R: AsyncRead + AsyncSeek + Unpin>(
ctx: &RpcContext,
@@ -1183,6 +1260,8 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
},
);
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<R: AsyncRead + AsyncSeek + Unpin>(
.dependency_errors()
.get_mut(&mut tx)
.await?;
*dep_errs = DependencyErrors::init(ctx, &mut tx, &manifest, &current_dependencies).await?;
*dep_errs = DependencyErrors::init(
ctx,
&mut tx,
&manifest,
&current_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<R: AsyncRead + AsyncSeek + Unpin>(
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<R: AsyncRead + AsyncSeek + Unpin>(
*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, &current_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,
&current_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<R: AsyncRead + AsyncSeek + Unpin>(
.keys()
.chain(prev.current_dependents.keys())
.collect::<BTreeSet<_>>(),
&receipts.config.update_dependency_receipts,
)
.await?;
if &prev.manifest.version != version {
@@ -1290,39 +1389,84 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
&manifest.volumes,
)
.await?;
add_dependent_to_current_dependents_lists(&mut tx, pkg_id, &current_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,
&current_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, &current_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,
&current_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, &current_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,
&current_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<R: AsyncRead + AsyncSeek + Unpin>(
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?;
}

View File

@@ -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?;
}

View File

@@ -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<Db: DbHandle>(
@@ -98,6 +98,10 @@ pub async fn check<Db: DbHandle>(
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<HealthCheckId, HealthCheckResult> = health_results
.iter()
@@ -113,10 +117,11 @@ pub async fn check<Db: DbHandle>(
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?;
}
}

View File

@@ -26,7 +26,6 @@ pub const SIG_CONTEXT: &'static [u8] = b"s9pk";
#[instrument(skip(ctx))]
pub fn pack(#[context] ctx: SdkContext, #[arg] path: Option<PathBuf>) -> Result<(), Error> {
use std::fs::File;
use std::io::Read;
let path = if let Some(path) = path {
path

View File

@@ -1,4 +1,3 @@
use tracing::metadata::LevelFilter;
use tracing::Subscriber;
use tracing_subscriber::util::SubscriberInitExt;

View File

@@ -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 {

View File

@@ -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<v0_3_0::Version>),
@@ -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<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error>;
async fn down<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error>;
async fn commit<Db: DbHandle>(&self, db: &mut Db) -> Result<(), Error> {
crate::db::DatabaseModel::new()
.server_info()
.eos_version_compat()
.put(db, &self.compat())
async fn commit<Db: DbHandle>(
&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<V: VersionT, Db: DbHandle>(
&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>(T);
impl<T> serde::Serialize for Wrapper<T>
where
@@ -106,7 +136,7 @@ where
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
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: DbHandle>(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: DbHandle>(
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: DbHandle>(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<Value = emver::Version> {
any::<(usize, usize, usize, usize)>().prop_map(|(major, minor, patch, super_minor)| {
emver::Version::new(major, minor, patch, super_minor)
})
}
fn versions() -> impl Strategy<Value = Version> {
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");
}
}
}

View File

@@ -14,6 +14,7 @@ lazy_static! {
);
}
#[derive(Debug, Clone)]
pub struct Version;
#[async_trait]
impl VersionT for Version {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {