mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
refactor dependency errors (#574)
* refactor dependency errors * fix hostname after restart * fix errors from testing * fix updating of current_dependents * fixes
This commit is contained in:
4
appmgr/Cargo.lock
generated
4
appmgr/Cargo.lock
generated
@@ -829,9 +829,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "emver"
|
||||
version = "0.1.2"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79b2c25504998d1069f80a8082e3c558acd7c103a2f2399306b12ed06fec5db8"
|
||||
checksum = "ed260c4d7efaec031b9c4f6c4d3cf136e3df2bbfe50925800236f5e847f28704"
|
||||
dependencies = [
|
||||
"either",
|
||||
"fp-core",
|
||||
|
||||
@@ -57,7 +57,7 @@ cookie_store = "0.15.0"
|
||||
digest = "0.9.0"
|
||||
divrem = "1.0.0"
|
||||
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
|
||||
emver = { version = "0.1.2", features = ["serde"] }
|
||||
emver = { version = "0.1.6", features = ["serde"] }
|
||||
fd-lock-rs = "0.1.3"
|
||||
futures = "0.3.17"
|
||||
git-version = "0.3.5"
|
||||
|
||||
@@ -117,7 +117,7 @@ pub async fn login(
|
||||
res.headers.insert(
|
||||
"set-cookie",
|
||||
HeaderValue::from_str(&format!(
|
||||
"session={}; SameSite=Lax; Expires=Fri, 31 Dec 9999 23:59:59 GMT;",
|
||||
"session={}; Path=/; SameSite=Lax; Expires=Fri, 31 Dec 9999 23:59:59 GMT;",
|
||||
token
|
||||
))
|
||||
.with_kind(crate::ErrorKind::Unknown)?, // Should be impossible, but don't want to panic
|
||||
|
||||
@@ -63,6 +63,9 @@ async fn init(cfg_path: Option<&str>) -> Result<(), Error> {
|
||||
})
|
||||
.await
|
||||
.with_kind(embassy::ErrorKind::Network)?;
|
||||
let pool_name = ctx.zfs_pool_name.clone();
|
||||
drop(ctx);
|
||||
embassy::disk::main::export(&*pool_name).await?;
|
||||
}
|
||||
|
||||
embassy::disk::main::load(
|
||||
|
||||
@@ -19,11 +19,11 @@ use crate::db::model::{
|
||||
};
|
||||
use crate::db::util::WithRevision;
|
||||
use crate::dependencies::{
|
||||
update_current_dependents, BreakageRes, DependencyError, TaggedDependencyError,
|
||||
break_transitive, update_current_dependents, BreakageRes, DependencyError, DependencyErrors,
|
||||
TaggedDependencyError,
|
||||
};
|
||||
use crate::install::cleanup::update_dependents;
|
||||
use crate::install::cleanup::{remove_current_dependents, update_dependents};
|
||||
use crate::s9pk::manifest::{Manifest, ManifestModel, PackageId};
|
||||
use crate::status::{handle_broken_dependents, DependencyErrors};
|
||||
use crate::util::{
|
||||
display_none, display_serializable, parse_duration, parse_stdin_deserializable, IoFormat,
|
||||
};
|
||||
@@ -414,15 +414,16 @@ pub fn configure<'a, Db: DbHandle>(
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut deps = pkg_model.clone().current_dependencies().get_mut(db).await?;
|
||||
*deps = current_dependencies.clone();
|
||||
deps.save(db).await?;
|
||||
res.signal
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// update dependencies
|
||||
let mut deps = pkg_model.clone().current_dependencies().get_mut(db).await?;
|
||||
remove_current_dependents(db, id, deps.keys()).await?;
|
||||
*deps = current_dependencies.clone();
|
||||
deps.save(db).await?;
|
||||
update_current_dependents(db, id, ¤t_dependencies).await?;
|
||||
let mut errs = pkg_model
|
||||
.clone()
|
||||
@@ -431,7 +432,7 @@ pub fn configure<'a, Db: DbHandle>(
|
||||
.get_mut(db)
|
||||
.await?;
|
||||
*errs = DependencyErrors::init(ctx, db, &*manifest, ¤t_dependencies).await?;
|
||||
errs.save(db).await;
|
||||
errs.save(db).await?;
|
||||
|
||||
// cache current config for dependents
|
||||
overrides.insert(id.clone(), config.clone());
|
||||
@@ -471,15 +472,7 @@ pub fn configure<'a, Db: DbHandle>(
|
||||
.await?
|
||||
{
|
||||
let dep_err = DependencyError::ConfigUnsatisfied { error };
|
||||
handle_broken_dependents(
|
||||
db,
|
||||
dependent,
|
||||
id,
|
||||
dependent_model,
|
||||
dep_err,
|
||||
breakages,
|
||||
)
|
||||
.await?;
|
||||
break_transitive(db, dependent, id, dep_err, breakages).await?;
|
||||
}
|
||||
|
||||
// handle backreferences
|
||||
@@ -492,17 +485,10 @@ pub fn configure<'a, Db: DbHandle>(
|
||||
.await
|
||||
{
|
||||
if e.kind == crate::ErrorKind::ConfigRulesViolation {
|
||||
let dependent_model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(dependent)
|
||||
.and_then(|pkg| pkg.installed())
|
||||
.expect(db)
|
||||
.await?;
|
||||
handle_broken_dependents(
|
||||
break_transitive(
|
||||
db,
|
||||
dependent,
|
||||
id,
|
||||
dependent_model,
|
||||
DependencyError::ConfigUnsatisfied {
|
||||
error: format!("{}", e),
|
||||
},
|
||||
|
||||
@@ -7,9 +7,13 @@ use rpc_toolkit::command;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::util::WithRevision;
|
||||
use crate::dependencies::{
|
||||
break_all_dependents_transitive, heal_all_dependents_transitive, BreakageRes, DependencyError,
|
||||
TaggedDependencyError,
|
||||
};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::status::MainStatus;
|
||||
use crate::util::display_none;
|
||||
use crate::util::{display_none, display_serializable};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
#[command(display(display_none))]
|
||||
@@ -46,12 +50,17 @@ pub async fn start(
|
||||
};
|
||||
status
|
||||
.synchronize(
|
||||
&*ctx.managers.get(&(id, version)).await.ok_or_else(|| {
|
||||
Error::new(anyhow!("Manager not found"), crate::ErrorKind::Docker)
|
||||
})?,
|
||||
&*ctx
|
||||
.managers
|
||||
.get(&(id.clone(), version))
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
Error::new(anyhow!("Manager not found"), crate::ErrorKind::Docker)
|
||||
})?,
|
||||
)
|
||||
.await?;
|
||||
status.save(&mut tx).await?;
|
||||
heal_all_dependents_transitive(&ctx, &mut tx, &id).await?;
|
||||
|
||||
Ok(WithRevision {
|
||||
revision: tx.commit(None).await?,
|
||||
@@ -59,19 +68,16 @@ pub async fn start(
|
||||
})
|
||||
}
|
||||
|
||||
#[command(display(display_none))]
|
||||
pub async fn stop(
|
||||
#[context] ctx: RpcContext,
|
||||
#[arg] id: PackageId,
|
||||
) -> Result<WithRevision<()>, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
async fn stop_common<Db: DbHandle>(
|
||||
db: &mut Db,
|
||||
id: &PackageId,
|
||||
breakages: &mut BTreeMap<PackageId, TaggedDependencyError>,
|
||||
) -> Result<(), Error> {
|
||||
let mut status = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&id)
|
||||
.and_then(|pkg| pkg.installed())
|
||||
.expect(&mut tx)
|
||||
.expect(db)
|
||||
.await
|
||||
.with_ctx(|_| {
|
||||
(
|
||||
@@ -81,11 +87,43 @@ pub async fn stop(
|
||||
})?
|
||||
.status()
|
||||
.main()
|
||||
.get_mut(&mut tx)
|
||||
.get_mut(db)
|
||||
.await?;
|
||||
|
||||
*status = MainStatus::Stopping;
|
||||
status.save(&mut tx).await?;
|
||||
status.save(db).await?;
|
||||
break_all_dependents_transitive(db, &id, DependencyError::NotRunning, breakages).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command(subcommands(self(stop_impl(async)), stop_dry), display(display_none))]
|
||||
pub fn stop(#[arg] id: PackageId) -> Result<PackageId, Error> {
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
#[command(rename = "dry", display(display_serializable))]
|
||||
pub async fn stop_dry(
|
||||
#[context] ctx: RpcContext,
|
||||
#[parent_data] id: PackageId,
|
||||
) -> Result<BreakageRes, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
let mut breakages = BTreeMap::new();
|
||||
stop_common(&mut tx, &id, &mut breakages).await?;
|
||||
|
||||
Ok(BreakageRes {
|
||||
breakages,
|
||||
patch: tx.abort().await?,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<WithRevision<()>, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
stop_common(&mut tx, &id, &mut BTreeMap::new()).await?;
|
||||
|
||||
Ok(WithRevision {
|
||||
revision: tx.commit(None).await?,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
pub mod model;
|
||||
pub mod util;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use futures::{FutureExt, SinkExt, StreamExt};
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use patch_db::{Dump, Revision};
|
||||
@@ -21,6 +23,7 @@ use tokio_tungstenite::WebSocketStream;
|
||||
pub use self::model::DatabaseModel;
|
||||
use self::util::WithRevision;
|
||||
use crate::context::RpcContext;
|
||||
use crate::middleware::auth::hash_token;
|
||||
use crate::util::{display_serializable, GeneralGuard, IoFormat};
|
||||
use crate::{Error, ResultExt};
|
||||
|
||||
@@ -39,7 +42,7 @@ async fn ws_handler<
|
||||
// add 1 to the session counter and issue an RAII guard to subtract 1 on drop
|
||||
ctx.websocket_count
|
||||
.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
let decrementer = GeneralGuard::new(|| {
|
||||
let _decrementer = GeneralGuard::new(|| {
|
||||
let new_count = ctx
|
||||
.websocket_count
|
||||
.fetch_sub(1, std::sync::atomic::Ordering::SeqCst);
|
||||
@@ -51,13 +54,22 @@ async fn ws_handler<
|
||||
});
|
||||
|
||||
loop {
|
||||
if let Some(Message::Text(_)) = stream
|
||||
if let Some(Message::Text(cookie)) = stream
|
||||
.next()
|
||||
.await
|
||||
.transpose()
|
||||
.with_kind(crate::ErrorKind::Network)?
|
||||
{
|
||||
// TODO: check auth
|
||||
let cookie_str = serde_json::from_str::<Cow<str>>(&cookie)
|
||||
.with_kind(crate::ErrorKind::Deserialization)?;
|
||||
let id = basic_cookies::Cookie::parse(&cookie_str)
|
||||
.with_kind(crate::ErrorKind::Authorization)?
|
||||
.into_iter()
|
||||
.find(|c| c.get_name() == "session")
|
||||
.ok_or_else(|| {
|
||||
Error::new(anyhow!("UNAUTHORIZED"), crate::ErrorKind::Authorization)
|
||||
})?;
|
||||
crate::middleware::auth::is_authed(&ctx, &hash_token(id.get_value())).await?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use emver::VersionRange;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt;
|
||||
use patch_db::{DbHandle, DiffPatch, HasModel, Map, MapModel};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -9,9 +11,9 @@ use crate::action::{ActionImplementation, NoOutput};
|
||||
use crate::config::Config;
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::CurrentDependencyInfo;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::status::health_check::{HealthCheckId, HealthCheckResult, HealthCheckResultVariant};
|
||||
use crate::status::MainStatus;
|
||||
use crate::status::{MainStatus, Status};
|
||||
use crate::util::Version;
|
||||
use crate::volume::Volumes;
|
||||
use crate::Error;
|
||||
@@ -21,59 +23,278 @@ use crate::Error;
|
||||
#[serde(tag = "type")]
|
||||
pub enum DependencyError {
|
||||
NotInstalled, // { "type": "not-installed" }
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
IncorrectVersion {
|
||||
expected: VersionRange,
|
||||
received: Version,
|
||||
}, // { "type": "incorrect-version", "expected": "0.1.0", "received": "^0.2.0" }
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
ConfigUnsatisfied {
|
||||
error: String,
|
||||
}, // { "type": "config-unsatisfied", "error": "Bitcoin Core must have pruning set to manual." }
|
||||
NotRunning, // { "type": "not-running" }
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
HealthChecksFailed {
|
||||
failures: BTreeMap<HealthCheckId, HealthCheckResult>,
|
||||
}, // { "type": "health-checks-failed", "checks": { "rpc": { "time": "2021-05-11T18:21:29Z", "result": "warming-up" } } }
|
||||
}, // { "type": "health-checks-failed", "checks": { "rpc": { "time": "2021-05-11T18:21:29Z", "result": "starting" } } }
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
Transitive, // { "type": "transitive" }
|
||||
}
|
||||
impl DependencyError {
|
||||
pub fn merge_with(self, other: DependencyError) -> DependencyError {
|
||||
use DependencyError::*;
|
||||
match (self, other) {
|
||||
(NotInstalled, _) => NotInstalled,
|
||||
(_, NotInstalled) => NotInstalled,
|
||||
(IncorrectVersion { expected, received }, _) => IncorrectVersion { expected, received },
|
||||
(_, IncorrectVersion { expected, received }) => IncorrectVersion { expected, received },
|
||||
(ConfigUnsatisfied { error: e0 }, ConfigUnsatisfied { error: e1 }) => {
|
||||
ConfigUnsatisfied {
|
||||
error: e0 + "\n" + &e1,
|
||||
}
|
||||
(DependencyError::NotInstalled, _) | (_, DependencyError::NotInstalled) => {
|
||||
DependencyError::NotInstalled
|
||||
}
|
||||
(ConfigUnsatisfied { error }, _) => ConfigUnsatisfied { error },
|
||||
(_, ConfigUnsatisfied { error }) => ConfigUnsatisfied { error },
|
||||
(NotRunning, _) => NotRunning,
|
||||
(_, NotRunning) => NotRunning,
|
||||
(HealthChecksFailed { failures: f0 }, HealthChecksFailed { failures: f1 }) => {
|
||||
HealthChecksFailed {
|
||||
failures: f0.into_iter().chain(f1.into_iter()).collect(),
|
||||
}
|
||||
(DependencyError::IncorrectVersion { expected, received }, _)
|
||||
| (_, DependencyError::IncorrectVersion { expected, received }) => {
|
||||
DependencyError::IncorrectVersion { expected, received }
|
||||
}
|
||||
(
|
||||
DependencyError::ConfigUnsatisfied { error: e0 },
|
||||
DependencyError::ConfigUnsatisfied { error: e1 },
|
||||
) => DependencyError::ConfigUnsatisfied {
|
||||
error: e0 + "\n" + &e1,
|
||||
},
|
||||
(DependencyError::ConfigUnsatisfied { error }, _)
|
||||
| (_, DependencyError::ConfigUnsatisfied { error }) => {
|
||||
DependencyError::ConfigUnsatisfied { error }
|
||||
}
|
||||
(DependencyError::NotRunning, _) | (_, DependencyError::NotRunning) => {
|
||||
DependencyError::NotRunning
|
||||
}
|
||||
(
|
||||
DependencyError::HealthChecksFailed { failures: f0 },
|
||||
DependencyError::HealthChecksFailed { failures: f1 },
|
||||
) => DependencyError::HealthChecksFailed {
|
||||
failures: f0.into_iter().chain(f1.into_iter()).collect(),
|
||||
},
|
||||
(DependencyError::HealthChecksFailed { failures }, _)
|
||||
| (_, DependencyError::HealthChecksFailed { failures }) => {
|
||||
DependencyError::HealthChecksFailed { failures }
|
||||
}
|
||||
(DependencyError::Transitive, _) | (_, DependencyError::Transitive) => {
|
||||
DependencyError::Transitive
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn try_heal<'a, Db: DbHandle>(
|
||||
self,
|
||||
ctx: &'a RpcContext,
|
||||
db: &'a mut Db,
|
||||
id: &'a PackageId,
|
||||
dependency: &'a PackageId,
|
||||
mut dependency_config: Option<Config>,
|
||||
info: &'a DepInfo,
|
||||
) -> 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?
|
||||
{
|
||||
DependencyError::IncorrectVersion {
|
||||
expected: info.version.clone(),
|
||||
received: Default::default(),
|
||||
}
|
||||
.try_heal(ctx, db, id, dependency, dependency_config, info)
|
||||
.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)
|
||||
.await?
|
||||
.into_owned()
|
||||
.unwrap_or_default();
|
||||
if version.satisfies(&expected) {
|
||||
DependencyError::ConfigUnsatisfied {
|
||||
error: String::new(),
|
||||
}
|
||||
.try_heal(ctx, db, id, dependency, dependency_config, info)
|
||||
.await?
|
||||
} else {
|
||||
Some(DependencyError::IncorrectVersion {
|
||||
expected,
|
||||
received: version,
|
||||
})
|
||||
}
|
||||
}
|
||||
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)
|
||||
.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?;
|
||||
let dependency_config = if let Some(cfg) = dependency_config.take() {
|
||||
cfg
|
||||
} else if let Some(cfg_info) = &dependency_manifest.config {
|
||||
cfg_info
|
||||
.get(
|
||||
ctx,
|
||||
dependency,
|
||||
&dependency_manifest.version,
|
||||
&dependency_manifest.volumes,
|
||||
)
|
||||
.await?
|
||||
.config
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
Config::default()
|
||||
};
|
||||
if let Some(cfg_req) = &info.config {
|
||||
if let Err(e) = cfg_req
|
||||
.check(
|
||||
ctx,
|
||||
id,
|
||||
&dependent_manifest.version,
|
||||
&dependent_manifest.volumes,
|
||||
&dependency_config,
|
||||
)
|
||||
.await
|
||||
{
|
||||
if e.kind == crate::ErrorKind::ConfigRulesViolation {
|
||||
return Ok(Some(DependencyError::ConfigUnsatisfied {
|
||||
error: format!("{}", e),
|
||||
}));
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
DependencyError::NotRunning
|
||||
.try_heal(ctx, db, id, dependency, Some(dependency_config), info)
|
||||
.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)
|
||||
.await?
|
||||
.get(db, true)
|
||||
.await?;
|
||||
if status.main.running() {
|
||||
DependencyError::HealthChecksFailed {
|
||||
failures: BTreeMap::new(),
|
||||
}
|
||||
.try_heal(ctx, db, id, dependency, dependency_config, info)
|
||||
.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)
|
||||
.await?
|
||||
.get(db, true)
|
||||
.await?
|
||||
.into_owned();
|
||||
match status.main {
|
||||
MainStatus::BackingUp {
|
||||
started: Some(_),
|
||||
health,
|
||||
}
|
||||
| MainStatus::Running { health, .. } => {
|
||||
let mut failures = BTreeMap::new();
|
||||
for (check, res) in health {
|
||||
if !matches!(res.result, HealthCheckResultVariant::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)
|
||||
.await?
|
||||
.into_owned()
|
||||
.map(|i| i.health_checks)
|
||||
.unwrap_or_default()
|
||||
.contains(&check)
|
||||
{
|
||||
failures.insert(check.clone(), res.clone());
|
||||
}
|
||||
}
|
||||
if !failures.is_empty() {
|
||||
Some(DependencyError::HealthChecksFailed { failures })
|
||||
} else {
|
||||
DependencyError::Transitive
|
||||
.try_heal(ctx, db, id, dependency, dependency_config, info)
|
||||
.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)
|
||||
.await?
|
||||
.into_owned()
|
||||
.unwrap_or_default()
|
||||
.0
|
||||
.is_empty()
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(DependencyError::Transitive)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for DependencyError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use DependencyError::*;
|
||||
match self {
|
||||
NotInstalled => write!(f, "Not Installed"),
|
||||
IncorrectVersion { expected, received } => write!(
|
||||
DependencyError::NotInstalled => write!(f, "Not Installed"),
|
||||
DependencyError::IncorrectVersion { expected, received } => write!(
|
||||
f,
|
||||
"Incorrect Version: Expected {}, Received {}",
|
||||
expected,
|
||||
received.as_str()
|
||||
),
|
||||
ConfigUnsatisfied { error } => {
|
||||
DependencyError::ConfigUnsatisfied { error } => {
|
||||
write!(f, "Configuration Requirements Not Satisfied: {}", error)
|
||||
}
|
||||
NotRunning => write!(f, "Not Running"),
|
||||
HealthChecksFailed { failures } => {
|
||||
DependencyError::NotRunning => write!(f, "Not Running"),
|
||||
DependencyError::HealthChecksFailed { failures } => {
|
||||
write!(f, "Failed Health Check(s): ")?;
|
||||
let mut comma = false;
|
||||
for (check, res) in failures {
|
||||
@@ -86,6 +307,9 @@ impl std::fmt::Display for DependencyError {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
DependencyError::Transitive => {
|
||||
write!(f, "Dependency Error(s)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,79 +374,24 @@ impl DepInfo {
|
||||
dependency_id: &PackageId,
|
||||
dependency_config: Option<Config>, // fetch if none
|
||||
dependent_id: &PackageId,
|
||||
dependent_version: &Version,
|
||||
dependent_volumes: &Volumes,
|
||||
) -> Result<Result<(), DependencyError>, Error> {
|
||||
let (manifest, info) = if let Some(dep_model) = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(dependency_id)
|
||||
.and_then(|pde| pde.installed())
|
||||
.check(db)
|
||||
.await?
|
||||
{
|
||||
(
|
||||
dep_model.clone().manifest().get(db, true).await?,
|
||||
dep_model.get(db, true).await?,
|
||||
)
|
||||
} else {
|
||||
return Ok(Err(DependencyError::NotInstalled));
|
||||
};
|
||||
if !&manifest.version.satisfies(&self.version) {
|
||||
return Ok(Err(DependencyError::IncorrectVersion {
|
||||
expected: self.version.clone(),
|
||||
received: manifest.version.clone(),
|
||||
}));
|
||||
}
|
||||
let dependency_config = if let Some(cfg) = dependency_config {
|
||||
cfg
|
||||
} else if let Some(cfg_info) = &manifest.config {
|
||||
cfg_info
|
||||
.get(ctx, dependency_id, &manifest.version, &manifest.volumes)
|
||||
.await?
|
||||
.config
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
Config::default()
|
||||
};
|
||||
if let Some(cfg_req) = &self.config {
|
||||
if let Err(e) = cfg_req
|
||||
.check(
|
||||
Ok(
|
||||
if let Some(err) = DependencyError::NotInstalled
|
||||
.try_heal(
|
||||
ctx,
|
||||
db,
|
||||
dependent_id,
|
||||
dependent_version,
|
||||
dependent_volumes,
|
||||
&dependency_config,
|
||||
dependency_id,
|
||||
dependency_config,
|
||||
self,
|
||||
)
|
||||
.await
|
||||
.await?
|
||||
{
|
||||
if e.kind == crate::ErrorKind::ConfigRulesViolation {
|
||||
return Ok(Err(DependencyError::ConfigUnsatisfied {
|
||||
error: format!("{}", e),
|
||||
}));
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
match &info.status.main {
|
||||
MainStatus::BackingUp {
|
||||
started: Some(_),
|
||||
health,
|
||||
}
|
||||
| MainStatus::Running { health, .. } => {
|
||||
let mut failures = BTreeMap::new();
|
||||
for (check, res) in health {
|
||||
if !matches!(res.result, HealthCheckResultVariant::Success) {
|
||||
failures.insert(check.clone(), res.clone());
|
||||
}
|
||||
}
|
||||
if !failures.is_empty() {
|
||||
return Ok(Err(DependencyError::HealthChecksFailed { failures }));
|
||||
}
|
||||
}
|
||||
_ => return Ok(Err(DependencyError::NotRunning)),
|
||||
}
|
||||
Ok(Ok(()))
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,3 +472,202 @@ pub async fn update_current_dependents<
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct DependencyErrors(pub BTreeMap<PackageId, DependencyError>);
|
||||
impl Map for DependencyErrors {
|
||||
type Key = PackageId;
|
||||
type Value = DependencyError;
|
||||
fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
|
||||
self.0.get(key)
|
||||
}
|
||||
}
|
||||
impl HasModel for DependencyErrors {
|
||||
type Model = MapModel<Self>;
|
||||
}
|
||||
impl DependencyErrors {
|
||||
pub async fn init<Db: DbHandle>(
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
current_dependencies: &BTreeMap<PackageId, CurrentDependencyInfo>,
|
||||
) -> Result<DependencyErrors, Error> {
|
||||
let mut res = BTreeMap::new();
|
||||
for (dependency_id, info) in current_dependencies.keys().filter_map(|dependency_id| {
|
||||
manifest
|
||||
.dependencies
|
||||
.0
|
||||
.get(dependency_id)
|
||||
.map(|info| (dependency_id, info))
|
||||
}) {
|
||||
if let Err(e) = info
|
||||
.satisfied(ctx, db, dependency_id, None, &manifest.id)
|
||||
.await?
|
||||
{
|
||||
res.insert(dependency_id.clone(), e);
|
||||
}
|
||||
}
|
||||
Ok(DependencyErrors(res))
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for DependencyErrors {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{{ ")?;
|
||||
for (idx, (id, err)) in self.0.iter().enumerate() {
|
||||
write!(f, "{}: {}", id, err)?;
|
||||
if idx < self.0.len() - 1 {
|
||||
// not last
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
}
|
||||
write!(f, " }}")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn break_all_dependents_transitive<'a, Db: DbHandle>(
|
||||
db: &'a mut Db,
|
||||
id: &'a PackageId,
|
||||
error: DependencyError,
|
||||
breakages: &'a mut BTreeMap<PackageId, TaggedDependencyError>,
|
||||
) -> Result<(), Error> {
|
||||
for dependent in crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|m| m.installed())
|
||||
.expect(db)
|
||||
.await?
|
||||
.current_dependents()
|
||||
.keys(db, true)
|
||||
.await?
|
||||
{
|
||||
break_transitive(db, &dependent, id, error.clone(), breakages).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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>,
|
||||
) -> BoxFuture<'a, Result<(), Error>> {
|
||||
async move {
|
||||
let model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|m| m.installed())
|
||||
.expect(db)
|
||||
.await?;
|
||||
let mut status = model.clone().status().get_mut(db).await?;
|
||||
|
||||
let old = status.dependency_errors.0.remove(dependency);
|
||||
let newly_broken = old.is_none();
|
||||
status.dependency_errors.0.insert(
|
||||
dependency.clone(),
|
||||
if let Some(old) = old {
|
||||
old.merge_with(error.clone())
|
||||
} else {
|
||||
error.clone()
|
||||
},
|
||||
);
|
||||
if newly_broken {
|
||||
breakages.insert(
|
||||
id.clone(),
|
||||
TaggedDependencyError {
|
||||
dependency: dependency.clone(),
|
||||
error: error.clone(),
|
||||
},
|
||||
);
|
||||
if status.main.running() {
|
||||
let transitive_error = if model
|
||||
.clone()
|
||||
.manifest()
|
||||
.dependencies()
|
||||
.idx_model(dependency)
|
||||
.get(db, true)
|
||||
.await?
|
||||
.into_owned()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
anyhow!("{} not in listed dependencies", dependency),
|
||||
crate::ErrorKind::Database,
|
||||
)
|
||||
})?
|
||||
.critical
|
||||
{
|
||||
status.main.stop();
|
||||
DependencyError::NotRunning
|
||||
} else {
|
||||
DependencyError::Transitive
|
||||
};
|
||||
break_all_dependents_transitive(db, id, transitive_error, breakages).await?;
|
||||
}
|
||||
}
|
||||
|
||||
status.save(db).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
pub async fn heal_all_dependents_transitive<'a, Db: DbHandle>(
|
||||
ctx: &'a RpcContext,
|
||||
db: &'a mut Db,
|
||||
id: &'a PackageId,
|
||||
) -> Result<(), Error> {
|
||||
for dependent in crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|m| m.installed())
|
||||
.expect(db)
|
||||
.await?
|
||||
.current_dependents()
|
||||
.keys(db, true)
|
||||
.await?
|
||||
{
|
||||
heal_transitive(ctx, db, &dependent, id).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn heal_transitive<'a, Db: DbHandle>(
|
||||
ctx: &'a RpcContext,
|
||||
db: &'a mut Db,
|
||||
id: &'a PackageId,
|
||||
dependency: &'a PackageId,
|
||||
) -> BoxFuture<'a, Result<(), Error>> {
|
||||
async move {
|
||||
let model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
.and_then(|m| m.installed())
|
||||
.expect(db)
|
||||
.await?;
|
||||
let mut status = model.clone().status().get_mut(db).await?;
|
||||
|
||||
let old = status.dependency_errors.0.remove(dependency);
|
||||
|
||||
if let Some(old) = old {
|
||||
let info = model
|
||||
.manifest()
|
||||
.dependencies()
|
||||
.idx_model(dependency)
|
||||
.expect(db)
|
||||
.await?
|
||||
.get(db, true)
|
||||
.await?;
|
||||
if let Some(new) = old.try_heal(ctx, db, id, dependency, None, &*info).await? {
|
||||
status.dependency_errors.0.insert(dependency.clone(), new);
|
||||
} else {
|
||||
heal_all_dependents_transitive(ctx, db, id).await?;
|
||||
}
|
||||
}
|
||||
|
||||
status.save(db).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
@@ -38,8 +38,13 @@ pub async fn get_id() -> Result<String, Error> {
|
||||
Ok(hex::encode(&res[0..4]))
|
||||
}
|
||||
|
||||
// cat /embassy-os/product_key.txt | shasum -a 256 | head -c 8 | awk '{print "start9-"$1}' | xargs hostnamectl set-hostname
|
||||
// cat /embassy-os/product_key.txt | shasum -a 256 | head -c 8 | awk '{print "start9-"$1}' | xargs hostnamectl set-hostname && systemctl restart avahi-daemon
|
||||
pub async fn sync_hostname() -> Result<(), Error> {
|
||||
set_hostname(&format!("start9-{}", get_id().await?)).await?;
|
||||
Command::new("systemctl")
|
||||
.arg("restart")
|
||||
.arg("avahi-daemon")
|
||||
.invoke(crate::ErrorKind::Network)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
|
||||
use bollard::image::ListImagesOptions;
|
||||
use patch_db::{DbHandle, PatchDbHandle};
|
||||
|
||||
use super::PKG_DOCKER_DIR;
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::{InstalledPackageDataEntry, PackageDataEntry};
|
||||
use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntry, PackageDataEntry};
|
||||
use crate::dependencies::update_current_dependents;
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Version;
|
||||
use crate::Error;
|
||||
@@ -29,8 +30,7 @@ pub async fn update_dependents<'a, Db: DbHandle, I: IntoIterator<Item = &'a Pack
|
||||
.get(db, true)
|
||||
.await?;
|
||||
if let Err(e) = if let Some(info) = man.dependencies.0.get(id) {
|
||||
info.satisfied(ctx, db, id, None, dep, &man.version, &man.volumes)
|
||||
.await?
|
||||
info.satisfied(ctx, db, id, None, dep).await?
|
||||
} else {
|
||||
Ok(())
|
||||
} {
|
||||
@@ -167,6 +167,26 @@ pub async fn cleanup_failed<Db: DbHandle>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_current_dependents<'a, Db: DbHandle, I: IntoIterator<Item = &'a PackageId>>(
|
||||
db: &mut Db,
|
||||
id: &PackageId,
|
||||
current_dependencies: I,
|
||||
) -> Result<(), Error> {
|
||||
for dep in current_dependencies {
|
||||
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?
|
||||
{
|
||||
current_dependents.remove(db, id).await?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn uninstall(
|
||||
ctx: &RpcContext,
|
||||
db: &mut PatchDbHandle,
|
||||
@@ -178,6 +198,12 @@ pub async fn uninstall(
|
||||
.package_data()
|
||||
.remove(&mut tx, &entry.manifest.id)
|
||||
.await?;
|
||||
remove_current_dependents(
|
||||
&mut tx,
|
||||
&entry.manifest.id,
|
||||
entry.current_dependencies.keys(),
|
||||
)
|
||||
.await?;
|
||||
update_dependents(
|
||||
ctx,
|
||||
&mut tx,
|
||||
|
||||
@@ -24,12 +24,15 @@ use crate::db::model::{
|
||||
StaticFiles,
|
||||
};
|
||||
use crate::db::util::WithRevision;
|
||||
use crate::dependencies::{update_current_dependents, BreakageRes, DependencyError};
|
||||
use crate::dependencies::{
|
||||
break_all_dependents_transitive, update_current_dependents, BreakageRes, DependencyError,
|
||||
DependencyErrors,
|
||||
};
|
||||
use crate::install::cleanup::{cleanup, update_dependents};
|
||||
use crate::install::progress::{InstallProgress, InstallProgressTracker};
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
use crate::s9pk::reader::S9pkReader;
|
||||
use crate::status::{handle_broken_dependents, DependencyErrors, MainStatus, Status};
|
||||
use crate::status::{MainStatus, Status};
|
||||
use crate::util::io::copy_and_shutdown;
|
||||
use crate::util::{display_none, display_serializable, AsyncFileExt, Version};
|
||||
use crate::volume::asset_dir;
|
||||
@@ -134,38 +137,10 @@ pub async fn uninstall_dry(
|
||||
#[parent_data] id: PackageId,
|
||||
) -> Result<BreakageRes, Error> {
|
||||
let mut db = ctx.db.handle();
|
||||
let deps = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&id)
|
||||
.expect(&mut db)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(&mut db)
|
||||
.await?
|
||||
.current_dependents()
|
||||
.get(&mut db, true)
|
||||
.await?;
|
||||
let mut tx = db.begin().await?;
|
||||
let mut breakages = BTreeMap::new();
|
||||
for dep_id in deps.keys() {
|
||||
let model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(&dep_id)
|
||||
.expect(&mut tx)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(&mut tx)
|
||||
.await?;
|
||||
handle_broken_dependents(
|
||||
&mut tx,
|
||||
dep_id,
|
||||
&id,
|
||||
model,
|
||||
DependencyError::NotInstalled,
|
||||
&mut breakages,
|
||||
)
|
||||
break_all_dependents_transitive(&mut tx, &id, DependencyError::NotInstalled, &mut breakages)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(BreakageRes {
|
||||
breakages,
|
||||
@@ -575,7 +550,7 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
log::info!("Install {}@{}: Created manager", pkg_id, version);
|
||||
|
||||
let static_files = StaticFiles::local(pkg_id, version, manifest.assets.icon_type());
|
||||
let current_dependencies = manifest
|
||||
let current_dependencies: BTreeMap<_, _> = manifest
|
||||
.dependencies
|
||||
.0
|
||||
.iter()
|
||||
@@ -616,22 +591,21 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
status: Status {
|
||||
configured: manifest.config.is_none(),
|
||||
main: MainStatus::Stopped,
|
||||
dependency_errors: DependencyErrors::init(
|
||||
ctx,
|
||||
&mut tx,
|
||||
&manifest,
|
||||
¤t_dependencies,
|
||||
)
|
||||
.await?,
|
||||
dependency_errors: DependencyErrors::default(),
|
||||
},
|
||||
manifest: manifest.clone(),
|
||||
system_pointers: Vec::new(),
|
||||
dependency_info,
|
||||
current_dependents: current_dependents.clone(),
|
||||
current_dependencies,
|
||||
current_dependencies: current_dependencies.clone(),
|
||||
interface_addresses,
|
||||
};
|
||||
let mut pde = model.expect(&mut tx).await?.get_mut(&mut tx).await?;
|
||||
let mut pde = model
|
||||
.clone()
|
||||
.expect(&mut tx)
|
||||
.await?
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
let prev = std::mem::replace(
|
||||
&mut *pde,
|
||||
PackageDataEntry::Installed {
|
||||
@@ -641,6 +615,18 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin>(
|
||||
},
|
||||
);
|
||||
pde.save(&mut tx).await?;
|
||||
let mut dep_errs = model
|
||||
.expect(&mut tx)
|
||||
.await?
|
||||
.installed()
|
||||
.expect(&mut tx)
|
||||
.await?
|
||||
.status()
|
||||
.dependency_errors()
|
||||
.get_mut(&mut tx)
|
||||
.await?;
|
||||
*dep_errs = DependencyErrors::init(ctx, &mut tx, &manifest, ¤t_dependencies).await?;
|
||||
dep_errs.save(&mut tx).await?;
|
||||
|
||||
if let PackageDataEntry::Updating {
|
||||
installed: prev,
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::anyhow;
|
||||
use basic_cookies::Cookie;
|
||||
use digest::Digest;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt;
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use http::StatusCode;
|
||||
use rpc_toolkit::command_helpers::prelude::RequestParts;
|
||||
use rpc_toolkit::hyper::header::COOKIE;
|
||||
@@ -44,8 +44,7 @@ pub fn hash_token(token: &str) -> String {
|
||||
.to_lowercase()
|
||||
}
|
||||
|
||||
pub async fn is_authed(ctx: &RpcContext, req: &RequestParts) -> Result<(), Error> {
|
||||
let id = get_id(req)?;
|
||||
pub async fn is_authed(ctx: &RpcContext, id: &str) -> Result<(), Error> {
|
||||
let session = sqlx::query!("UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = ? AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP", id)
|
||||
.execute(&mut ctx.secret_store.acquire().await?)
|
||||
.await?;
|
||||
@@ -73,7 +72,10 @@ pub fn auth<M: Metadata>(ctx: RpcContext) -> DynMiddleware<M> {
|
||||
.get(rpc_req.method.as_str(), "authenticated")
|
||||
.unwrap_or(true)
|
||||
{
|
||||
if let Err(e) = is_authed(&ctx, req).await {
|
||||
if let Err(e) = async { get_id(req) }
|
||||
.and_then(|id| async move { is_authed(&ctx, &id).await })
|
||||
.await
|
||||
{
|
||||
let (res_parts, _) = Response::new(()).into_parts();
|
||||
return Ok(Err(to_response(
|
||||
&req.headers,
|
||||
|
||||
@@ -103,7 +103,7 @@ pub async fn execute_inner(
|
||||
let mut guid_file = File::create("/embassy-os/disk.guid").await?;
|
||||
guid_file.write_all(guid.as_bytes()).await?;
|
||||
guid_file.sync_all().await?;
|
||||
crate::disk::main::export(&ctx.zfs_pool_name).await?;
|
||||
sqlite_pool.close().await;
|
||||
|
||||
ctx.shutdown.send(()).expect("failed to shutdown");
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ use serde::{Deserialize, Serialize};
|
||||
use self::health_check::{HealthCheckId, HealthCheckResult};
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::{CurrentDependencyInfo, InstalledPackageDataEntryModel};
|
||||
use crate::dependencies::{DependencyError, TaggedDependencyError};
|
||||
use crate::dependencies::{
|
||||
break_transitive, DependencyError, DependencyErrors, TaggedDependencyError,
|
||||
};
|
||||
use crate::manager::{Manager, Status as ManagerStatus};
|
||||
use crate::notifications::{notify, NotificationLevel, NotificationSubtype};
|
||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||
@@ -212,15 +214,7 @@ pub async fn check_all(ctx: &RpcContext) -> Result<(), Error> {
|
||||
}
|
||||
_ => Some(DependencyError::NotRunning),
|
||||
} {
|
||||
handle_broken_dependents(
|
||||
&mut db,
|
||||
id,
|
||||
dep_id,
|
||||
model.clone(),
|
||||
err,
|
||||
&mut BTreeMap::new(),
|
||||
)
|
||||
.await?;
|
||||
break_transitive(&mut db, id, dep_id, err, &mut BTreeMap::new()).await?;
|
||||
} else {
|
||||
let mut errs = model
|
||||
.clone()
|
||||
@@ -262,6 +256,7 @@ pub async fn check_all(ctx: &RpcContext) -> Result<(), Error> {
|
||||
pub struct Status {
|
||||
pub configured: bool,
|
||||
pub main: MainStatus,
|
||||
#[model]
|
||||
pub dependency_errors: DependencyErrors,
|
||||
}
|
||||
|
||||
@@ -391,131 +386,3 @@ impl MainStatus {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct DependencyErrors(pub BTreeMap<PackageId, DependencyError>);
|
||||
impl Map for DependencyErrors {
|
||||
type Key = PackageId;
|
||||
type Value = DependencyError;
|
||||
fn get(&self, key: &Self::Key) -> Option<&Self::Value> {
|
||||
self.0.get(key)
|
||||
}
|
||||
}
|
||||
impl HasModel for DependencyErrors {
|
||||
type Model = MapModel<Self>;
|
||||
}
|
||||
impl DependencyErrors {
|
||||
pub async fn init<Db: DbHandle>(
|
||||
ctx: &RpcContext,
|
||||
db: &mut Db,
|
||||
manifest: &Manifest,
|
||||
current_dependencies: &BTreeMap<PackageId, CurrentDependencyInfo>,
|
||||
) -> Result<DependencyErrors, Error> {
|
||||
let mut res = BTreeMap::new();
|
||||
for (dep_id, info) in current_dependencies.keys().filter_map(|dep_id| {
|
||||
manifest
|
||||
.dependencies
|
||||
.0
|
||||
.get(dep_id)
|
||||
.map(|info| (dep_id, info))
|
||||
}) {
|
||||
if let Err(e) = info
|
||||
.satisfied(
|
||||
ctx,
|
||||
db,
|
||||
dep_id,
|
||||
None,
|
||||
&manifest.id,
|
||||
&manifest.version,
|
||||
&manifest.volumes,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
res.insert(dep_id.clone(), e);
|
||||
}
|
||||
}
|
||||
Ok(DependencyErrors(res))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_broken_dependents<'a, Db: DbHandle>(
|
||||
db: &'a mut Db,
|
||||
id: &'a PackageId,
|
||||
dependency: &'a PackageId,
|
||||
model: InstalledPackageDataEntryModel,
|
||||
error: DependencyError,
|
||||
breakages: &'a mut BTreeMap<PackageId, TaggedDependencyError>,
|
||||
) -> BoxFuture<'a, Result<(), Error>> {
|
||||
async move {
|
||||
let mut status = model.clone().status().get_mut(db).await?;
|
||||
|
||||
let old = status.dependency_errors.0.remove(id);
|
||||
let newly_broken = old.is_none();
|
||||
status.dependency_errors.0.insert(
|
||||
id.clone(),
|
||||
if let Some(old) = old {
|
||||
old.merge_with(error.clone())
|
||||
} else {
|
||||
error.clone()
|
||||
},
|
||||
);
|
||||
if newly_broken {
|
||||
breakages.insert(
|
||||
id.clone(),
|
||||
TaggedDependencyError {
|
||||
dependency: dependency.clone(),
|
||||
error: error.clone(),
|
||||
},
|
||||
);
|
||||
if status.main.running() {
|
||||
if model
|
||||
.clone()
|
||||
.manifest()
|
||||
.dependencies()
|
||||
.idx_model(dependency)
|
||||
.get(db, true)
|
||||
.await?
|
||||
.into_owned()
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
anyhow!("{} not in listed dependencies", dependency),
|
||||
crate::ErrorKind::Database,
|
||||
)
|
||||
})?
|
||||
.critical
|
||||
{
|
||||
status.main.stop();
|
||||
let dependents = model.current_dependents().get(db, true).await?;
|
||||
for (dependent, _) in &*dependents {
|
||||
let dependent_model = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(dependent)
|
||||
.and_then(|pkg| pkg.installed())
|
||||
.check(db)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
Error::new(
|
||||
anyhow!("{} is not installed", dependent),
|
||||
crate::ErrorKind::NotFound,
|
||||
)
|
||||
})?;
|
||||
handle_broken_dependents(
|
||||
db,
|
||||
dependent,
|
||||
id,
|
||||
dependent_model,
|
||||
DependencyError::NotRunning,
|
||||
breakages,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status.save(db).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
@@ -365,6 +365,11 @@ impl From<Version> for emver::Version {
|
||||
v.version
|
||||
}
|
||||
}
|
||||
impl Default for Version {
|
||||
fn default() -> Self {
|
||||
Self::from(emver::Version::default())
|
||||
}
|
||||
}
|
||||
impl Deref for Version {
|
||||
type Target = emver::Version;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
||||
2
patch-db
2
patch-db
Submodule patch-db updated: f0e6968a79...44f7150bad
Reference in New Issue
Block a user