mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-04 14:29:45 +00:00
Refactor/service manager (#2401)
* wip: Pulling in the features of the refactor since march * chore: Fixes to make the system able to build * chore: Adding in the documentation for the manager stuff * feat: Restarting and wait for stop * feat: Add a soft shutdown not commit to db. * chore: Remove the comments of bluj * chore: Clean up some of the linting errors * chore: Clean up the signal * chore: Some more cleanup * fix: The configure * fix: A missing config * fix: typo * chore: Remove a comment of BLUJ that needed to be removed
This commit is contained in:
@@ -65,7 +65,7 @@ impl Action {
|
|||||||
image_ids: &BTreeSet<ImageId>,
|
image_ids: &BTreeSet<ImageId>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.implementation
|
self.implementation
|
||||||
.validate(container, eos_version, volumes, image_ids, true)
|
.validate(eos_version, volumes, image_ids, true)
|
||||||
.with_ctx(|_| {
|
.with_ctx(|_| {
|
||||||
(
|
(
|
||||||
crate::ErrorKind::ValidateS9pk,
|
crate::ErrorKind::ValidateS9pk,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
@@ -7,8 +8,7 @@ use color_eyre::eyre::eyre;
|
|||||||
use helpers::AtomicFile;
|
use helpers::AtomicFile;
|
||||||
use patch_db::{DbHandle, LockType, PatchDbHandle};
|
use patch_db::{DbHandle, LockType, PatchDbHandle};
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::{io::AsyncWriteExt, sync::Mutex};
|
||||||
use tokio::process::Command;
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use super::target::BackupTargetId;
|
use super::target::BackupTargetId;
|
||||||
@@ -21,12 +21,12 @@ use crate::db::model::BackupProgress;
|
|||||||
use crate::disk::mount::backup::BackupMountGuard;
|
use crate::disk::mount::backup::BackupMountGuard;
|
||||||
use crate::disk::mount::filesystem::ReadWrite;
|
use crate::disk::mount::filesystem::ReadWrite;
|
||||||
use crate::disk::mount::guard::TmpMountGuard;
|
use crate::disk::mount::guard::TmpMountGuard;
|
||||||
|
use crate::manager::BackupReturn;
|
||||||
use crate::notifications::NotificationLevel;
|
use crate::notifications::NotificationLevel;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::status::MainStatus;
|
use crate::util::display_none;
|
||||||
use crate::util::io::dir_copy;
|
use crate::util::io::dir_copy;
|
||||||
use crate::util::serde::IoFormat;
|
use crate::util::serde::IoFormat;
|
||||||
use crate::util::{display_none, Invoke};
|
|
||||||
use crate::version::VersionT;
|
use crate::version::VersionT;
|
||||||
use crate::{Error, ErrorKind, ResultExt};
|
use crate::{Error, ErrorKind, ResultExt};
|
||||||
|
|
||||||
@@ -206,10 +206,12 @@ async fn assure_backing_up(
|
|||||||
async fn perform_backup<Db: DbHandle>(
|
async fn perform_backup<Db: DbHandle>(
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
mut db: Db,
|
mut db: Db,
|
||||||
mut backup_guard: BackupMountGuard<TmpMountGuard>,
|
backup_guard: BackupMountGuard<TmpMountGuard>,
|
||||||
package_ids: &BTreeSet<PackageId>,
|
package_ids: &BTreeSet<PackageId>,
|
||||||
) -> Result<BTreeMap<PackageId, PackageBackupReport>, Error> {
|
) -> Result<BTreeMap<PackageId, PackageBackupReport>, Error> {
|
||||||
let mut backup_report = BTreeMap::new();
|
let mut backup_report = BTreeMap::new();
|
||||||
|
let backup_guard = Arc::new(Mutex::new(backup_guard));
|
||||||
|
|
||||||
for package_id in crate::db::DatabaseModel::new()
|
for package_id in crate::db::DatabaseModel::new()
|
||||||
.package_data()
|
.package_data()
|
||||||
.keys(&mut db)
|
.keys(&mut db)
|
||||||
@@ -231,93 +233,55 @@ async fn perform_backup<Db: DbHandle>(
|
|||||||
};
|
};
|
||||||
let main_status_model = installed_model.clone().status().main();
|
let main_status_model = installed_model.clone().status().main();
|
||||||
|
|
||||||
main_status_model.lock(&mut tx, LockType::Write).await?;
|
let manifest = installed_model.clone().manifest().get(&mut tx).await?;
|
||||||
let (started, health) = match main_status_model.get(&mut tx).await?.into_owned() {
|
|
||||||
MainStatus::Starting { .. } => (Some(Utc::now()), Default::default()),
|
|
||||||
MainStatus::Running { started, health } => (Some(started), health.clone()),
|
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
|
||||||
(None, Default::default())
|
|
||||||
}
|
|
||||||
MainStatus::BackingUp { .. } => {
|
|
||||||
backup_report.insert(
|
|
||||||
package_id,
|
|
||||||
PackageBackupReport {
|
|
||||||
error: Some(
|
|
||||||
"Can't do backup because service is in a backing up state".to_owned(),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
main_status_model
|
|
||||||
.put(
|
|
||||||
&mut tx,
|
|
||||||
&MainStatus::BackingUp {
|
|
||||||
started,
|
|
||||||
health: health.clone(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
tx.save().await?; // drop locks
|
|
||||||
|
|
||||||
let manifest = installed_model.clone().manifest().get(&mut db).await?;
|
let (response, report) = match ctx
|
||||||
|
.managers
|
||||||
ctx.managers
|
|
||||||
.get(&(manifest.id.clone(), manifest.version.clone()))
|
.get(&(manifest.id.clone(), manifest.version.clone()))
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest)
|
Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest)
|
||||||
})?
|
})?
|
||||||
.synchronize()
|
.backup(backup_guard.clone())
|
||||||
.await;
|
.await
|
||||||
|
{
|
||||||
let mut tx = db.begin().await?;
|
BackupReturn::Ran { report, res } => (res, report),
|
||||||
|
BackupReturn::AlreadyRunning(report) => {
|
||||||
installed_model.lock(&mut tx, LockType::Write).await?;
|
backup_report.insert(package_id, report);
|
||||||
|
continue;
|
||||||
let guard = backup_guard.mount_package_backup(&package_id).await?;
|
}
|
||||||
let res = manifest
|
BackupReturn::Error(error) => {
|
||||||
.backup
|
tracing::warn!("Backup thread error");
|
||||||
.create(
|
tracing::debug!("{error:?}");
|
||||||
ctx,
|
backup_report.insert(
|
||||||
&mut tx,
|
package_id,
|
||||||
&package_id,
|
PackageBackupReport {
|
||||||
&manifest.title,
|
error: Some("Backup thread error".to_owned()),
|
||||||
&manifest.version,
|
},
|
||||||
&manifest.interfaces,
|
);
|
||||||
&manifest.volumes,
|
continue;
|
||||||
)
|
}
|
||||||
.await;
|
};
|
||||||
guard.unmount().await?;
|
|
||||||
backup_report.insert(
|
backup_report.insert(
|
||||||
package_id.clone(),
|
package_id.clone(),
|
||||||
PackageBackupReport {
|
PackageBackupReport {
|
||||||
error: res.as_ref().err().map(|e| e.to_string()),
|
error: response.as_ref().err().map(|e| e.to_string()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Ok(pkg_meta) = res {
|
if let Ok(pkg_meta) = response {
|
||||||
installed_model
|
installed_model
|
||||||
.last_backup()
|
.last_backup()
|
||||||
.put(&mut tx, &Some(pkg_meta.timestamp))
|
.put(&mut tx, &Some(pkg_meta.timestamp))
|
||||||
.await?;
|
.await?;
|
||||||
backup_guard
|
backup_guard
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
.metadata
|
.metadata
|
||||||
.package_backups
|
.package_backups
|
||||||
.insert(package_id.clone(), pkg_meta);
|
.insert(package_id.clone(), pkg_meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
main_status_model
|
|
||||||
.put(
|
|
||||||
&mut tx,
|
|
||||||
&match started {
|
|
||||||
Some(started) => MainStatus::Running { started, health },
|
|
||||||
None => MainStatus::Stopped,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut backup_progress = crate::db::DatabaseModel::new()
|
let mut backup_progress = crate::db::DatabaseModel::new()
|
||||||
.server_info()
|
.server_info()
|
||||||
.status_info()
|
.status_info()
|
||||||
@@ -344,7 +308,7 @@ async fn perform_backup<Db: DbHandle>(
|
|||||||
.into_owned();
|
.into_owned();
|
||||||
|
|
||||||
let mut os_backup_file = AtomicFile::new(
|
let mut os_backup_file = AtomicFile::new(
|
||||||
backup_guard.as_ref().join("os-backup.cbor"),
|
backup_guard.lock().await.as_ref().join("os-backup.cbor"),
|
||||||
None::<PathBuf>,
|
None::<PathBuf>,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -360,11 +324,11 @@ async fn perform_backup<Db: DbHandle>(
|
|||||||
.await
|
.await
|
||||||
.with_kind(ErrorKind::Filesystem)?;
|
.with_kind(ErrorKind::Filesystem)?;
|
||||||
|
|
||||||
let luks_folder_old = backup_guard.as_ref().join("luks.old");
|
let luks_folder_old = backup_guard.lock().await.as_ref().join("luks.old");
|
||||||
if tokio::fs::metadata(&luks_folder_old).await.is_ok() {
|
if tokio::fs::metadata(&luks_folder_old).await.is_ok() {
|
||||||
tokio::fs::remove_dir_all(&luks_folder_old).await?;
|
tokio::fs::remove_dir_all(&luks_folder_old).await?;
|
||||||
}
|
}
|
||||||
let luks_folder_bak = backup_guard.as_ref().join("luks");
|
let luks_folder_bak = backup_guard.lock().await.as_ref().join("luks");
|
||||||
if tokio::fs::metadata(&luks_folder_bak).await.is_ok() {
|
if tokio::fs::metadata(&luks_folder_bak).await.is_ok() {
|
||||||
tokio::fs::rename(&luks_folder_bak, &luks_folder_old).await?;
|
tokio::fs::rename(&luks_folder_bak, &luks_folder_old).await?;
|
||||||
}
|
}
|
||||||
@@ -374,6 +338,14 @@ async fn perform_backup<Db: DbHandle>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let timestamp = Some(Utc::now());
|
let timestamp = Some(Utc::now());
|
||||||
|
let mut backup_guard = Arc::try_unwrap(backup_guard)
|
||||||
|
.map_err(|_err| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("Backup guard could not ensure that the others where dropped"),
|
||||||
|
ErrorKind::Unknown,
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.into_inner();
|
||||||
|
|
||||||
backup_guard.unencrypted_metadata.version = crate::version::Current::new().semver().into();
|
backup_guard.unencrypted_metadata.version = crate::version::Current::new().semver().into();
|
||||||
backup_guard.unencrypted_metadata.full = true;
|
backup_guard.unencrypted_metadata.full = true;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ pub struct ServerBackupReport {
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct PackageBackupReport {
|
pub struct PackageBackupReport {
|
||||||
error: Option<String>,
|
pub error: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command(subcommands(backup_bulk::backup_all, target::target))]
|
#[command(subcommands(backup_bulk::backup_all, target::target))]
|
||||||
@@ -84,10 +84,10 @@ impl BackupActions {
|
|||||||
image_ids: &BTreeSet<ImageId>,
|
image_ids: &BTreeSet<ImageId>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.create
|
self.create
|
||||||
.validate(container, eos_version, volumes, image_ids, false)
|
.validate(eos_version, volumes, image_ids, false)
|
||||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Create"))?;
|
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Create"))?;
|
||||||
self.restore
|
self.restore
|
||||||
.validate(container, eos_version, volumes, image_ids, false)
|
.validate(eos_version, volumes, image_ids, false)
|
||||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Restore"))?;
|
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Backup Restore"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use crate::init::STANDBY_MODE_PATH;
|
|||||||
use crate::net::web_server::WebServer;
|
use crate::net::web_server::WebServer;
|
||||||
use crate::shutdown::Shutdown;
|
use crate::shutdown::Shutdown;
|
||||||
use crate::sound::CHIME;
|
use crate::sound::CHIME;
|
||||||
use crate::util::logger::EmbassyLogger;
|
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
|
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ impl ConfigActions {
|
|||||||
image_ids: &BTreeSet<ImageId>,
|
image_ids: &BTreeSet<ImageId>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.get
|
self.get
|
||||||
.validate(container, eos_version, volumes, image_ids, true)
|
.validate(eos_version, volumes, image_ids, true)
|
||||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Get"))?;
|
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Get"))?;
|
||||||
self.set
|
self.set
|
||||||
.validate(container, eos_version, volumes, image_ids, true)
|
.validate(eos_version, volumes, image_ids, true)
|
||||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Set"))?;
|
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Config Set"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,6 @@ impl ConfigActions {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
Ok(SetResult {
|
Ok(SetResult {
|
||||||
signal: res.signal,
|
|
||||||
depends_on: res
|
depends_on: res
|
||||||
.depends_on
|
.depends_on
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -112,9 +111,5 @@ impl ConfigActions {
|
|||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct SetResult {
|
pub struct SetResult {
|
||||||
#[serde(default)]
|
|
||||||
#[serde(deserialize_with = "crate::util::serde::deserialize_from_str_opt")]
|
|
||||||
#[serde(serialize_with = "crate::util::serde::serialize_display_opt")]
|
|
||||||
pub signal: Option<Signal>,
|
|
||||||
pub depends_on: BTreeMap<PackageId, BTreeSet<HealthCheckId>>,
|
pub depends_on: BTreeMap<PackageId, BTreeSet<HealthCheckId>>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::BTreeMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use futures::future::{BoxFuture, FutureExt};
|
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use models::ErrorKind;
|
||||||
use patch_db::{DbHandle, LockReceipt, LockTarget, LockTargetId, LockType, Verifier};
|
use patch_db::{DbHandle, LockReceipt, LockTarget, LockTargetId, LockType, Verifier};
|
||||||
use rand::SeedableRng;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::{CurrentDependencies, CurrentDependencyInfo, CurrentDependents};
|
use crate::db::model::{CurrentDependencies, CurrentDependents};
|
||||||
use crate::dependencies::{
|
use crate::dependencies::{
|
||||||
add_dependent_to_current_dependents_lists, break_transitive, heal_all_dependents_transitive,
|
BreakTransitiveReceipts, BreakageRes, Dependencies, DependencyConfig, DependencyErrors,
|
||||||
BreakTransitiveReceipts, BreakageRes, Dependencies, DependencyConfig, DependencyError,
|
DependencyReceipt, TaggedDependencyError, TryHealReceipts,
|
||||||
DependencyErrors, DependencyReceipt, TaggedDependencyError, TryHealReceipts,
|
|
||||||
};
|
};
|
||||||
use crate::install::cleanup::{remove_from_current_dependents_lists, UpdateDependencyReceipts};
|
use crate::install::cleanup::UpdateDependencyReceipts;
|
||||||
use crate::procedure::docker::DockerContainers;
|
use crate::procedure::docker::DockerContainers;
|
||||||
use crate::s9pk::manifest::{Manifest, PackageId};
|
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||||
use crate::util::display_none;
|
use crate::util::display_none;
|
||||||
@@ -35,7 +33,7 @@ pub use spec::{ConfigSpec, Defaultable};
|
|||||||
use util::NumRange;
|
use util::NumRange;
|
||||||
|
|
||||||
use self::action::{ConfigActions, ConfigRes};
|
use self::action::{ConfigActions, ConfigRes};
|
||||||
use self::spec::{ConfigPointerReceipts, PackagePointerSpec, ValueSpecPointer};
|
use self::spec::{ConfigPointerReceipts, ValueSpecPointer};
|
||||||
|
|
||||||
pub type Config = serde_json::Map<String, Value>;
|
pub type Config = serde_json::Map<String, Value>;
|
||||||
pub trait TypeOf {
|
pub trait TypeOf {
|
||||||
@@ -264,18 +262,18 @@ pub struct ConfigReceipts {
|
|||||||
pub update_dependency_receipts: UpdateDependencyReceipts,
|
pub update_dependency_receipts: UpdateDependencyReceipts,
|
||||||
pub try_heal_receipts: TryHealReceipts,
|
pub try_heal_receipts: TryHealReceipts,
|
||||||
pub break_transitive_receipts: BreakTransitiveReceipts,
|
pub break_transitive_receipts: BreakTransitiveReceipts,
|
||||||
configured: LockReceipt<bool, String>,
|
pub configured: LockReceipt<bool, String>,
|
||||||
config_actions: LockReceipt<ConfigActions, String>,
|
pub config_actions: LockReceipt<ConfigActions, String>,
|
||||||
dependencies: LockReceipt<Dependencies, String>,
|
pub dependencies: LockReceipt<Dependencies, String>,
|
||||||
volumes: LockReceipt<crate::volume::Volumes, String>,
|
pub volumes: LockReceipt<crate::volume::Volumes, String>,
|
||||||
version: LockReceipt<crate::util::Version, String>,
|
pub version: LockReceipt<crate::util::Version, String>,
|
||||||
manifest: LockReceipt<Manifest, String>,
|
pub manifest: LockReceipt<Manifest, String>,
|
||||||
system_pointers: LockReceipt<Vec<spec::SystemPointerSpec>, String>,
|
pub system_pointers: LockReceipt<Vec<spec::SystemPointerSpec>, String>,
|
||||||
pub current_dependents: LockReceipt<CurrentDependents, String>,
|
pub current_dependents: LockReceipt<CurrentDependents, String>,
|
||||||
pub current_dependencies: LockReceipt<CurrentDependencies, String>,
|
pub current_dependencies: LockReceipt<CurrentDependencies, String>,
|
||||||
dependency_errors: LockReceipt<DependencyErrors, String>,
|
pub dependency_errors: LockReceipt<DependencyErrors, String>,
|
||||||
manifest_dependencies_config: LockReceipt<DependencyConfig, (String, String)>,
|
pub manifest_dependencies_config: LockReceipt<DependencyConfig, (String, String)>,
|
||||||
docker_containers: LockReceipt<DockerContainers, String>,
|
pub docker_containers: LockReceipt<DockerContainers, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigReceipts {
|
impl ConfigReceipts {
|
||||||
@@ -418,370 +416,78 @@ pub async fn set_dry(
|
|||||||
#[context] ctx: RpcContext,
|
#[context] ctx: RpcContext,
|
||||||
#[parent_data] (id, config, timeout): (PackageId, Option<Config>, Option<Duration>),
|
#[parent_data] (id, config, timeout): (PackageId, Option<Config>, Option<Duration>),
|
||||||
) -> Result<BreakageRes, Error> {
|
) -> Result<BreakageRes, Error> {
|
||||||
let mut db = ctx.db.handle();
|
let breakages = BTreeMap::new();
|
||||||
let mut tx = db.begin().await?;
|
let overrides = Default::default();
|
||||||
let mut breakages = BTreeMap::new();
|
|
||||||
let locks = ConfigReceipts::new(&mut tx).await?;
|
let configure_context = ConfigureContext {
|
||||||
configure(
|
breakages,
|
||||||
&ctx,
|
timeout,
|
||||||
&mut tx,
|
config,
|
||||||
&id,
|
dry_run: true,
|
||||||
config,
|
overrides,
|
||||||
&timeout,
|
};
|
||||||
true,
|
let breakages = configure(&ctx, &id, configure_context).await?;
|
||||||
&mut BTreeMap::new(),
|
|
||||||
&mut breakages,
|
|
||||||
&locks,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
locks.configured.set(&mut tx, true, &id).await?;
|
|
||||||
tx.abort().await?;
|
|
||||||
Ok(BreakageRes(breakages))
|
Ok(BreakageRes(breakages))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ConfigureContext {
|
||||||
|
pub breakages: BTreeMap<PackageId, TaggedDependencyError>,
|
||||||
|
pub timeout: Option<Duration>,
|
||||||
|
pub config: Option<Config>,
|
||||||
|
pub overrides: BTreeMap<PackageId, Config>,
|
||||||
|
pub dry_run: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn set_impl(
|
pub async fn set_impl(
|
||||||
ctx: RpcContext,
|
ctx: RpcContext,
|
||||||
(id, config, timeout): (PackageId, Option<Config>, Option<Duration>),
|
(id, config, timeout): (PackageId, Option<Config>, Option<Duration>),
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut db = ctx.db.handle();
|
let breakages = BTreeMap::new();
|
||||||
let mut tx = db.begin().await?;
|
let overrides = Default::default();
|
||||||
let mut breakages = BTreeMap::new();
|
|
||||||
let locks = ConfigReceipts::new(&mut tx).await?;
|
let configure_context = ConfigureContext {
|
||||||
configure(
|
breakages,
|
||||||
&ctx,
|
timeout,
|
||||||
&mut tx,
|
|
||||||
&id,
|
|
||||||
config,
|
config,
|
||||||
&timeout,
|
dry_run: false,
|
||||||
false,
|
overrides,
|
||||||
&mut BTreeMap::new(),
|
};
|
||||||
&mut breakages,
|
configure(&ctx, &id, configure_context).await?;
|
||||||
&locks,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
tx.commit().await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn configure<'a, Db: DbHandle>(
|
pub async fn configure(
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
db: &'a mut Db,
|
|
||||||
id: &PackageId,
|
id: &PackageId,
|
||||||
config: Option<Config>,
|
configure_context: ConfigureContext,
|
||||||
timeout: &Option<Duration>,
|
) -> Result<BTreeMap<PackageId, TaggedDependencyError>, Error> {
|
||||||
dry_run: bool,
|
let mut db = ctx.db.handle();
|
||||||
overrides: &mut BTreeMap<PackageId, Config>,
|
let version = crate::db::DatabaseModel::new()
|
||||||
breakages: &mut BTreeMap<PackageId, TaggedDependencyError>,
|
.package_data()
|
||||||
receipts: &ConfigReceipts,
|
.idx_model(id)
|
||||||
) -> Result<(), Error> {
|
.expect(&mut db)
|
||||||
configure_rec(
|
.await?
|
||||||
ctx, db, id, config, timeout, dry_run, overrides, breakages, receipts,
|
.installed()
|
||||||
)
|
.expect(&mut db)
|
||||||
.await?;
|
.await?
|
||||||
receipts.configured.set(db, true, &id).await?;
|
.manifest()
|
||||||
Ok(())
|
.version()
|
||||||
}
|
.get(&mut ctx.db.handle())
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
|
||||||
pub fn configure_rec<'a, Db: DbHandle>(
|
|
||||||
ctx: &'a RpcContext,
|
|
||||||
db: &'a mut Db,
|
|
||||||
id: &'a PackageId,
|
|
||||||
config: Option<Config>,
|
|
||||||
timeout: &'a Option<Duration>,
|
|
||||||
dry_run: bool,
|
|
||||||
overrides: &'a mut BTreeMap<PackageId, Config>,
|
|
||||||
breakages: &'a mut BTreeMap<PackageId, TaggedDependencyError>,
|
|
||||||
receipts: &'a ConfigReceipts,
|
|
||||||
) -> BoxFuture<'a, Result<(), Error>> {
|
|
||||||
async move {
|
|
||||||
// fetch data from db
|
|
||||||
let action = receipts
|
|
||||||
.config_actions
|
|
||||||
.get(db, id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| not_found!(id))?;
|
|
||||||
let dependencies = receipts
|
|
||||||
.dependencies
|
|
||||||
.get(db, id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| not_found!(id))?;
|
|
||||||
let volumes = receipts
|
|
||||||
.volumes
|
|
||||||
.get(db, id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| not_found!(id))?;
|
|
||||||
let is_needs_config = !receipts
|
|
||||||
.configured
|
|
||||||
.get(db, id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| not_found!(id))?;
|
|
||||||
let version = receipts
|
|
||||||
.version
|
|
||||||
.get(db, id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| not_found!(id))?;
|
|
||||||
|
|
||||||
// get current config and current spec
|
|
||||||
let ConfigRes {
|
|
||||||
config: old_config,
|
|
||||||
spec,
|
|
||||||
} = 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()) {
|
|
||||||
config
|
|
||||||
} else {
|
|
||||||
spec.gen(&mut rand::rngs::StdRng::from_entropy(), timeout)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let manifest = receipts
|
|
||||||
.manifest
|
|
||||||
.get(db, id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| not_found!(id))?;
|
|
||||||
|
|
||||||
spec.validate(&manifest)?;
|
|
||||||
spec.matches(&config)?; // check that new config matches spec
|
|
||||||
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 = receipts
|
|
||||||
.system_pointers
|
|
||||||
.get(db, &id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| not_found!(id))?;
|
|
||||||
sys.truncate(0);
|
|
||||||
let mut current_dependencies: CurrentDependencies = CurrentDependencies(
|
|
||||||
dependencies
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.filter_map(|(id, info)| {
|
|
||||||
if info.requirement.required() {
|
|
||||||
Some((id.clone(), CurrentDependencyInfo::default()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
for ptr in spec.pointers(&config)? {
|
|
||||||
match ptr {
|
|
||||||
ValueSpecPointer::Package(pkg_ptr) => {
|
|
||||||
if let Some(current_dependency) =
|
|
||||||
current_dependencies.0.get_mut(pkg_ptr.package_id())
|
|
||||||
{
|
|
||||||
current_dependency.pointers.push(pkg_ptr);
|
|
||||||
} else {
|
|
||||||
current_dependencies.0.insert(
|
|
||||||
pkg_ptr.package_id().to_owned(),
|
|
||||||
CurrentDependencyInfo {
|
|
||||||
pointers: vec![pkg_ptr],
|
|
||||||
health_checks: BTreeSet::new(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ValueSpecPointer::System(s) => sys.push(s),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// track dependencies with no pointers
|
|
||||||
for (package_id, health_checks) in res.depends_on.into_iter() {
|
|
||||||
if let Some(current_dependency) = current_dependencies.0.get_mut(&package_id) {
|
|
||||||
current_dependency.health_checks.extend(health_checks);
|
|
||||||
} else {
|
|
||||||
current_dependencies.0.insert(
|
|
||||||
package_id,
|
|
||||||
CurrentDependencyInfo {
|
|
||||||
pointers: Vec::new(),
|
|
||||||
health_checks,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// track dependency health checks
|
|
||||||
current_dependencies = current_dependencies.map(|x| {
|
|
||||||
x.into_iter()
|
|
||||||
.filter(|(dep_id, _)| {
|
|
||||||
if dep_id != id && !manifest.dependencies.0.contains_key(dep_id) {
|
|
||||||
tracing::warn!("Illegal dependency specified: {}", dep_id);
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
res.signal
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// update dependencies
|
|
||||||
let prev_current_dependencies = receipts
|
|
||||||
.current_dependencies
|
|
||||||
.get(db, &id)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
|
||||||
remove_from_current_dependents_lists(
|
|
||||||
db,
|
|
||||||
id,
|
|
||||||
&prev_current_dependencies,
|
|
||||||
&receipts.current_dependents,
|
|
||||||
)
|
|
||||||
.await?; // remove previous
|
|
||||||
add_dependent_to_current_dependents_lists(
|
|
||||||
db,
|
|
||||||
id,
|
|
||||||
¤t_dependencies,
|
|
||||||
&receipts.current_dependents,
|
|
||||||
)
|
|
||||||
.await?; // add new
|
|
||||||
current_dependencies.0.remove(id);
|
|
||||||
receipts
|
|
||||||
.current_dependencies
|
|
||||||
.set(db, current_dependencies.clone(), &id)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let errs = receipts
|
|
||||||
.dependency_errors
|
|
||||||
.get(db, &id)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| not_found!(id))?;
|
|
||||||
tracing::warn!("Dependency Errors: {:?}", errs);
|
|
||||||
let errs = DependencyErrors::init(
|
|
||||||
ctx,
|
|
||||||
db,
|
|
||||||
&manifest,
|
|
||||||
¤t_dependencies,
|
|
||||||
&receipts.dependency_receipt.try_heal,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
receipts.dependency_errors.set(db, errs, &id).await?;
|
ctx.managers
|
||||||
|
.get(&(id.clone(), version.clone()))
|
||||||
// cache current config for dependents
|
.await
|
||||||
overrides.insert(id.clone(), config.clone());
|
.ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
// handle dependents
|
eyre!("There is no manager running for {id:?} and {version:?}"),
|
||||||
let dependents = receipts
|
ErrorKind::Unknown,
|
||||||
.current_dependents
|
)
|
||||||
.get(db, id)
|
})?
|
||||||
.await?
|
.configure(configure_context)
|
||||||
.ok_or_else(|| not_found!(id))?;
|
.await
|
||||||
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.0.iter().filter(|(dep_id, _)| dep_id != &id) {
|
|
||||||
let dependent_container = receipts.docker_containers.get(db, &dependent).await?;
|
|
||||||
let dependent_container = &dependent_container;
|
|
||||||
// check if config passes dependent check
|
|
||||||
if let Some(cfg) = receipts
|
|
||||||
.manifest_dependencies_config
|
|
||||||
.get(db, (&dependent, &id))
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
let manifest = receipts
|
|
||||||
.manifest
|
|
||||||
.get(db, &dependent)
|
|
||||||
.await?
|
|
||||||
.ok_or_else(|| not_found!(id))?;
|
|
||||||
if let Err(error) = cfg
|
|
||||||
.check(
|
|
||||||
ctx,
|
|
||||||
dependent_container,
|
|
||||||
dependent,
|
|
||||||
&manifest.version,
|
|
||||||
&manifest.volumes,
|
|
||||||
id,
|
|
||||||
&config,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
let dep_err = DependencyError::ConfigUnsatisfied { error };
|
|
||||||
break_transitive(
|
|
||||||
db,
|
|
||||||
dependent,
|
|
||||||
id,
|
|
||||||
dep_err,
|
|
||||||
breakages,
|
|
||||||
&receipts.break_transitive_receipts,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle backreferences
|
|
||||||
for ptr in &dep_info.pointers {
|
|
||||||
if let PackagePointerSpec::Config(cfg_ptr) = ptr {
|
|
||||||
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
|
|
||||||
{
|
|
||||||
if e.kind == crate::ErrorKind::ConfigRulesViolation {
|
|
||||||
break_transitive(
|
|
||||||
db,
|
|
||||||
dependent,
|
|
||||||
id,
|
|
||||||
DependencyError::ConfigUnsatisfied {
|
|
||||||
error: format!("{}", e),
|
|
||||||
},
|
|
||||||
breakages,
|
|
||||||
&receipts.break_transitive_receipts,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
heal_all_dependents_transitive(ctx, db, id, &receipts.dependency_receipt).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(signal) = signal {
|
|
||||||
match ctx.managers.get(&(id.clone(), version.clone())).await {
|
|
||||||
None => {
|
|
||||||
// in theory this should never happen, which indicates this function should be moved behind the
|
|
||||||
// Manager interface
|
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("Manager Not Found for package being configured"),
|
|
||||||
crate::ErrorKind::Incoherent,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Some(m) => {
|
|
||||||
m.signal(&signal).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
.boxed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! not_found {
|
macro_rules! not_found {
|
||||||
|
|||||||
@@ -366,14 +366,12 @@ impl RpcContext {
|
|||||||
let main = match status.main {
|
let main = match status.main {
|
||||||
MainStatus::BackingUp { started, .. } => {
|
MainStatus::BackingUp { started, .. } => {
|
||||||
if let Some(_) = started {
|
if let Some(_) = started {
|
||||||
MainStatus::Starting { restarting: false }
|
MainStatus::Starting
|
||||||
} else {
|
} else {
|
||||||
MainStatus::Stopped
|
MainStatus::Stopped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MainStatus::Running { .. } => {
|
MainStatus::Running { .. } => MainStatus::Starting,
|
||||||
MainStatus::Starting { restarting: false }
|
|
||||||
}
|
|
||||||
a => a.clone(),
|
a => a.clone(),
|
||||||
};
|
};
|
||||||
let new_package = PackageDataEntry::Installed {
|
let new_package = PackageDataEntry::Installed {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl StartReceipts {
|
|||||||
let mut locks = Vec::new();
|
let mut locks = Vec::new();
|
||||||
|
|
||||||
let setup = Self::setup(&mut locks, id);
|
let setup = Self::setup(&mut locks, id);
|
||||||
Ok(setup(&db.lock_all(locks).await?)?)
|
setup(&db.lock_all(locks).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup(
|
pub fn setup(
|
||||||
@@ -67,10 +67,7 @@ pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(
|
|||||||
let mut tx = db.begin().await?;
|
let mut tx = db.begin().await?;
|
||||||
let receipts = StartReceipts::new(&mut tx, &id).await?;
|
let receipts = StartReceipts::new(&mut tx, &id).await?;
|
||||||
let version = receipts.version.get(&mut tx).await?;
|
let version = receipts.version.get(&mut tx).await?;
|
||||||
receipts
|
receipts.status.set(&mut tx, MainStatus::Starting).await?;
|
||||||
.status
|
|
||||||
.set(&mut tx, MainStatus::Starting { restarting: false })
|
|
||||||
.await?;
|
|
||||||
heal_all_dependents_transitive(&ctx, &mut tx, &id, &receipts.dependency_receipt).await?;
|
heal_all_dependents_transitive(&ctx, &mut tx, &id, &receipts.dependency_receipt).await?;
|
||||||
|
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
@@ -80,8 +77,7 @@ pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(
|
|||||||
.get(&(id, version))
|
.get(&(id, version))
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
||||||
.synchronize()
|
.start();
|
||||||
.await;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -96,7 +92,7 @@ impl StopReceipts {
|
|||||||
let mut locks = Vec::new();
|
let mut locks = Vec::new();
|
||||||
|
|
||||||
let setup = Self::setup(&mut locks, id);
|
let setup = Self::setup(&mut locks, id);
|
||||||
Ok(setup(&db.lock_all(locks).await?)?)
|
setup(&db.lock_all(locks).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup(
|
pub fn setup(
|
||||||
@@ -174,10 +170,28 @@ pub async fn stop_dry(
|
|||||||
pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<MainStatus, Error> {
|
pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<MainStatus, Error> {
|
||||||
let mut db = ctx.db.handle();
|
let mut db = ctx.db.handle();
|
||||||
let mut tx = db.begin().await?;
|
let mut tx = db.begin().await?;
|
||||||
|
let version = crate::db::DatabaseModel::new()
|
||||||
|
.package_data()
|
||||||
|
.idx_model(&id)
|
||||||
|
.expect(&mut tx)
|
||||||
|
.await?
|
||||||
|
.installed()
|
||||||
|
.expect(&mut tx)
|
||||||
|
.await?
|
||||||
|
.manifest()
|
||||||
|
.version()
|
||||||
|
.get(&mut tx)
|
||||||
|
.await?
|
||||||
|
.clone();
|
||||||
|
|
||||||
let last_statuts = stop_common(&mut tx, &id, &mut BTreeMap::new()).await?;
|
let last_statuts = stop_common(&mut tx, &id, &mut BTreeMap::new()).await?;
|
||||||
|
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
ctx.managers
|
||||||
|
.get(&(id, version))
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
||||||
|
.stop();
|
||||||
|
|
||||||
Ok(last_statuts)
|
Ok(last_statuts)
|
||||||
}
|
}
|
||||||
@@ -186,23 +200,28 @@ pub async fn stop_impl(ctx: RpcContext, id: PackageId) -> Result<MainStatus, Err
|
|||||||
pub async fn restart(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> {
|
pub async fn restart(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(), Error> {
|
||||||
let mut db = ctx.db.handle();
|
let mut db = ctx.db.handle();
|
||||||
let mut tx = db.begin().await?;
|
let mut tx = db.begin().await?;
|
||||||
|
let version = crate::db::DatabaseModel::new()
|
||||||
let mut status = crate::db::DatabaseModel::new()
|
|
||||||
.package_data()
|
.package_data()
|
||||||
.idx_model(&id)
|
.idx_model(&id)
|
||||||
.and_then(|pde| pde.installed())
|
.expect(&mut tx)
|
||||||
.map(|i| i.status().main())
|
.await?
|
||||||
.get_mut(&mut tx)
|
.installed()
|
||||||
.await?;
|
.expect(&mut tx)
|
||||||
if !matches!(&*status, Some(MainStatus::Running { .. })) {
|
.await?
|
||||||
return Err(Error::new(
|
.manifest()
|
||||||
eyre!("{} is not running", id),
|
.version()
|
||||||
crate::ErrorKind::InvalidRequest,
|
.get(&mut tx)
|
||||||
));
|
.await?
|
||||||
}
|
.clone();
|
||||||
*status = Some(MainStatus::Restarting);
|
|
||||||
status.save(&mut tx).await?;
|
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
|
ctx.managers
|
||||||
|
.get(&(id, version))
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| Error::new(eyre!("Manager not found"), crate::ErrorKind::InvalidRequest))?
|
||||||
|
.restart()
|
||||||
|
.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use rpc_toolkit::hyper::{Body, Error as HyperError, Request, Response};
|
|||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tokio::io::AsyncWrite;
|
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use tokio::task::JoinError;
|
use tokio::task::JoinError;
|
||||||
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ use tracing::instrument;
|
|||||||
|
|
||||||
use crate::config::action::{ConfigActions, ConfigRes};
|
use crate::config::action::{ConfigActions, ConfigRes};
|
||||||
use crate::config::spec::PackagePointerSpec;
|
use crate::config::spec::PackagePointerSpec;
|
||||||
use crate::config::{not_found, Config, ConfigReceipts, ConfigSpec};
|
use crate::config::{not_found, Config, ConfigReceipts, ConfigSpec, ConfigureContext};
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::db::model::{CurrentDependencies, CurrentDependents, InstalledPackageDataEntry};
|
use crate::db::model::{CurrentDependencies, CurrentDependents, InstalledPackageDataEntry};
|
||||||
use crate::procedure::docker::DockerContainers;
|
use crate::procedure::docker::DockerContainers;
|
||||||
@@ -519,7 +519,6 @@ impl DependencyConfig {
|
|||||||
Ok(self
|
Ok(self
|
||||||
.check
|
.check
|
||||||
.sandboxed(
|
.sandboxed(
|
||||||
container,
|
|
||||||
ctx,
|
ctx,
|
||||||
dependent_id,
|
dependent_id,
|
||||||
dependent_version,
|
dependent_version,
|
||||||
@@ -542,7 +541,6 @@ impl DependencyConfig {
|
|||||||
) -> Result<Config, Error> {
|
) -> Result<Config, Error> {
|
||||||
self.auto_configure
|
self.auto_configure
|
||||||
.sandboxed(
|
.sandboxed(
|
||||||
container,
|
|
||||||
ctx,
|
ctx,
|
||||||
dependent_id,
|
dependent_id,
|
||||||
dependent_version,
|
dependent_version,
|
||||||
@@ -557,7 +555,6 @@ impl DependencyConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct DependencyConfigReceipts {
|
pub struct DependencyConfigReceipts {
|
||||||
config: ConfigReceipts,
|
|
||||||
dependencies: LockReceipt<Dependencies, ()>,
|
dependencies: LockReceipt<Dependencies, ()>,
|
||||||
dependency_volumes: LockReceipt<Volumes, ()>,
|
dependency_volumes: LockReceipt<Volumes, ()>,
|
||||||
dependency_version: LockReceipt<Version, ()>,
|
dependency_version: LockReceipt<Version, ()>,
|
||||||
@@ -584,7 +581,6 @@ impl DependencyConfigReceipts {
|
|||||||
package_id: &PackageId,
|
package_id: &PackageId,
|
||||||
dependency_id: &PackageId,
|
dependency_id: &PackageId,
|
||||||
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||||
let config = ConfigReceipts::setup(locks);
|
|
||||||
let dependencies = crate::db::DatabaseModel::new()
|
let dependencies = crate::db::DatabaseModel::new()
|
||||||
.package_data()
|
.package_data()
|
||||||
.idx_model(package_id)
|
.idx_model(package_id)
|
||||||
@@ -636,7 +632,6 @@ impl DependencyConfigReceipts {
|
|||||||
.add_to_keys(locks);
|
.add_to_keys(locks);
|
||||||
move |skeleton_key| {
|
move |skeleton_key| {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
config: config(skeleton_key)?,
|
|
||||||
dependencies: dependencies.verify(&skeleton_key)?,
|
dependencies: dependencies.verify(&skeleton_key)?,
|
||||||
dependency_volumes: dependency_volumes.verify(&skeleton_key)?,
|
dependency_volumes: dependency_volumes.verify(&skeleton_key)?,
|
||||||
dependency_version: dependency_version.verify(&skeleton_key)?,
|
dependency_version: dependency_version.verify(&skeleton_key)?,
|
||||||
@@ -665,6 +660,8 @@ pub async fn configure_impl(
|
|||||||
(pkg_id, dep_id): (PackageId, PackageId),
|
(pkg_id, dep_id): (PackageId, PackageId),
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut db = ctx.db.handle();
|
let mut db = ctx.db.handle();
|
||||||
|
let breakages = BTreeMap::new();
|
||||||
|
let overrides = Default::default();
|
||||||
let receipts = DependencyConfigReceipts::new(&mut db, &pkg_id, &dep_id).await?;
|
let receipts = DependencyConfigReceipts::new(&mut db, &pkg_id, &dep_id).await?;
|
||||||
let ConfigDryRes {
|
let ConfigDryRes {
|
||||||
old_config: _,
|
old_config: _,
|
||||||
@@ -672,19 +669,15 @@ pub async fn configure_impl(
|
|||||||
spec: _,
|
spec: _,
|
||||||
} = configure_logic(ctx.clone(), &mut db, (pkg_id, dep_id.clone()), &receipts).await?;
|
} = configure_logic(ctx.clone(), &mut db, (pkg_id, dep_id.clone()), &receipts).await?;
|
||||||
|
|
||||||
let locks = &receipts.config;
|
let configure_context = ConfigureContext {
|
||||||
Ok(crate::config::configure(
|
breakages,
|
||||||
&ctx,
|
timeout: Some(Duration::from_secs(3).into()),
|
||||||
&mut db,
|
config: Some(new_config),
|
||||||
&dep_id,
|
dry_run: false,
|
||||||
Some(new_config),
|
overrides,
|
||||||
&Some(Duration::from_secs(3).into()),
|
};
|
||||||
false,
|
crate::config::configure(&ctx, &dep_id, configure_context).await?;
|
||||||
&mut BTreeMap::new(),
|
Ok(())
|
||||||
&mut BTreeMap::new(),
|
|
||||||
locks,
|
|
||||||
)
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
@@ -769,7 +762,6 @@ pub async fn configure_logic(
|
|||||||
let new_config = dependency
|
let new_config = dependency
|
||||||
.auto_configure
|
.auto_configure
|
||||||
.sandboxed(
|
.sandboxed(
|
||||||
&pkg_docker_container,
|
|
||||||
&ctx,
|
&ctx,
|
||||||
&pkg_id,
|
&pkg_id,
|
||||||
&pkg_version,
|
&pkg_version,
|
||||||
@@ -1049,7 +1041,7 @@ pub fn heal_transitive<'a, Db: DbHandle>(
|
|||||||
|
|
||||||
pub async fn reconfigure_dependents_with_live_pointers(
|
pub async fn reconfigure_dependents_with_live_pointers(
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
mut tx: impl DbHandle,
|
tx: impl DbHandle,
|
||||||
receipts: &ConfigReceipts,
|
receipts: &ConfigReceipts,
|
||||||
pde: &InstalledPackageDataEntry,
|
pde: &InstalledPackageDataEntry,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
@@ -1064,18 +1056,17 @@ pub async fn reconfigure_dependents_with_live_pointers(
|
|||||||
PackagePointerSpec::TorKey(_) => false,
|
PackagePointerSpec::TorKey(_) => false,
|
||||||
PackagePointerSpec::Config(_) => false,
|
PackagePointerSpec::Config(_) => false,
|
||||||
}) {
|
}) {
|
||||||
crate::config::configure(
|
let breakages = BTreeMap::new();
|
||||||
ctx,
|
let overrides = Default::default();
|
||||||
&mut tx,
|
|
||||||
dependent_id,
|
let configure_context = ConfigureContext {
|
||||||
None,
|
breakages,
|
||||||
&None,
|
timeout: None,
|
||||||
false,
|
config: None,
|
||||||
&mut BTreeMap::new(),
|
dry_run: false,
|
||||||
&mut BTreeMap::new(),
|
overrides,
|
||||||
receipts,
|
};
|
||||||
)
|
crate::config::configure(&ctx, dependent_id, configure_context).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use sha2::Sha256;
|
|||||||
|
|
||||||
use super::{FileSystem, MountType, ReadOnly};
|
use super::{FileSystem, MountType, ReadOnly};
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::{Error, ResultExt};
|
use crate::Error;
|
||||||
|
|
||||||
pub struct EfiVarFs;
|
pub struct EfiVarFs;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path::Path;
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::{Error, ResultExt};
|
use crate::Error;
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn bind<P0: AsRef<Path>, P1: AsRef<Path>>(
|
pub async fn bind<P0: AsRef<Path>, P1: AsRef<Path>>(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use color_eyre::eyre::{self, eyre};
|
use color_eyre::eyre::{self, eyre};
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
use indexmap::IndexSet;
|
|
||||||
use nom::bytes::complete::{tag, take_till1};
|
use nom::bytes::complete::{tag, take_till1};
|
||||||
use nom::character::complete::multispace1;
|
use nom::character::complete::multispace1;
|
||||||
use nom::character::is_space;
|
use nom::character::is_space;
|
||||||
@@ -62,8 +61,8 @@ pub struct EmbassyOsRecoveryInfo {
|
|||||||
pub wrapped_key: Option<String>,
|
pub wrapped_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DISK_PATH: &'static str = "/dev/disk/by-path";
|
const DISK_PATH: &str = "/dev/disk/by-path";
|
||||||
const SYS_BLOCK_PATH: &'static str = "/sys/block";
|
const SYS_BLOCK_PATH: &str = "/sys/block";
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref PARTITION_REGEX: Regex = Regex::new("-part[0-9]+$").unwrap();
|
static ref PARTITION_REGEX: Regex = Regex::new("-part[0-9]+$").unwrap();
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use patch_db::DbHandle;
|
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::account::AccountInfo;
|
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::{Error, ErrorKind};
|
use crate::{Error, ErrorKind};
|
||||||
#[derive(Clone, serde::Deserialize, serde::Serialize, Debug)]
|
#[derive(Clone, serde::Deserialize, serde::Serialize, Debug)]
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use patch_db::{DbHandle, LockType};
|
|||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use rpc_toolkit::yajrc::RpcError;
|
use rpc_toolkit::yajrc::RpcError;
|
||||||
|
use serde_json::{json, Value};
|
||||||
use tokio::fs::{File, OpenOptions};
|
use tokio::fs::{File, OpenOptions};
|
||||||
use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt};
|
use tokio::io::{AsyncRead, AsyncSeek, AsyncSeekExt};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -26,7 +27,7 @@ use tokio_stream::wrappers::ReadDirStream;
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use self::cleanup::{cleanup_failed, remove_from_current_dependents_lists};
|
use self::cleanup::{cleanup_failed, remove_from_current_dependents_lists};
|
||||||
use crate::config::ConfigReceipts;
|
use crate::config::{ConfigReceipts, ConfigureContext};
|
||||||
use crate::context::{CliContext, RpcContext};
|
use crate::context::{CliContext, RpcContext};
|
||||||
use crate::core::rpc_continuations::{RequestGuid, RpcContinuation};
|
use crate::core::rpc_continuations::{RequestGuid, RpcContinuation};
|
||||||
use crate::db::model::{
|
use crate::db::model::{
|
||||||
@@ -48,7 +49,6 @@ use crate::status::{MainStatus, Status};
|
|||||||
use crate::util::io::{copy_and_shutdown, response_to_reader};
|
use crate::util::io::{copy_and_shutdown, response_to_reader};
|
||||||
use crate::util::serde::{display_serializable, Port};
|
use crate::util::serde::{display_serializable, Port};
|
||||||
use crate::util::{display_none, AsyncFileExt, Version};
|
use crate::util::{display_none, AsyncFileExt, Version};
|
||||||
use crate::version::{Current, VersionT};
|
|
||||||
use crate::volume::{asset_dir, script_dir};
|
use crate::volume::{asset_dir, script_dir};
|
||||||
use crate::{Error, ErrorKind, ResultExt};
|
use crate::{Error, ErrorKind, ResultExt};
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ pub const PKG_PUBLIC_DIR: &str = "package-data/public";
|
|||||||
pub const PKG_WASM_DIR: &str = "package-data/wasm";
|
pub const PKG_WASM_DIR: &str = "package-data/wasm";
|
||||||
|
|
||||||
#[command(display(display_serializable))]
|
#[command(display(display_serializable))]
|
||||||
pub async fn list(#[context] ctx: RpcContext) -> Result<Vec<(PackageId, Version)>, Error> {
|
pub async fn list(#[context] ctx: RpcContext) -> Result<Value, Error> {
|
||||||
let mut hdl = ctx.db.handle();
|
let mut hdl = ctx.db.handle();
|
||||||
let package_data = crate::db::DatabaseModel::new()
|
let package_data = crate::db::DatabaseModel::new()
|
||||||
.package_data()
|
.package_data()
|
||||||
@@ -71,11 +71,25 @@ pub async fn list(#[context] ctx: RpcContext) -> Result<Vec<(PackageId, Version)
|
|||||||
Ok(package_data
|
Ok(package_data
|
||||||
.0
|
.0
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(id, pde)| match pde {
|
.filter_map(|(id, pde)| {
|
||||||
PackageDataEntry::Installed { installed, .. } => {
|
serde_json::to_value(match pde {
|
||||||
Some((id.clone(), installed.manifest.version.clone()))
|
PackageDataEntry::Installed { installed, .. } => {
|
||||||
}
|
json!({ "status":"installed","id": id.clone(), "version": installed.manifest.version.clone()})
|
||||||
_ => None,
|
}
|
||||||
|
PackageDataEntry::Installing { manifest, install_progress, .. } => {
|
||||||
|
json!({ "status":"installing","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()})
|
||||||
|
}
|
||||||
|
PackageDataEntry::Updating { manifest, installed, install_progress, .. } => {
|
||||||
|
json!({ "status":"updating","id": id.clone(), "version": installed.manifest.version.clone(), "progress": install_progress.clone()})
|
||||||
|
}
|
||||||
|
PackageDataEntry::Restoring { manifest, install_progress, .. } => {
|
||||||
|
json!({ "status":"restoring","id": id.clone(), "version": manifest.version.clone(), "progress": install_progress.clone()})
|
||||||
|
}
|
||||||
|
PackageDataEntry::Removing { manifest, .. } => {
|
||||||
|
json!({ "status":"removing", "id": id.clone(), "version": manifest.version.clone()})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
@@ -1248,7 +1262,6 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
|||||||
current_dependencies: current_dependencies.clone(),
|
current_dependencies: current_dependencies.clone(),
|
||||||
interface_addresses,
|
interface_addresses,
|
||||||
};
|
};
|
||||||
|
|
||||||
let prev = std::mem::replace(
|
let prev = std::mem::replace(
|
||||||
&mut *pde,
|
&mut *pde,
|
||||||
PackageDataEntry::Installed {
|
PackageDataEntry::Installed {
|
||||||
@@ -1328,18 +1341,17 @@ pub async fn install_s9pk<R: AsyncRead + AsyncSeek + Unpin + Send + Sync>(
|
|||||||
false
|
false
|
||||||
};
|
};
|
||||||
if configured && manifest.config.is_some() {
|
if configured && manifest.config.is_some() {
|
||||||
crate::config::configure(
|
let breakages = BTreeMap::new();
|
||||||
ctx,
|
let overrides = Default::default();
|
||||||
&mut tx,
|
|
||||||
pkg_id,
|
let configure_context = ConfigureContext {
|
||||||
None,
|
breakages,
|
||||||
&None,
|
timeout: None,
|
||||||
false,
|
config: None,
|
||||||
&mut BTreeMap::new(),
|
dry_run: false,
|
||||||
&mut BTreeMap::new(),
|
overrides,
|
||||||
&receipts.config,
|
};
|
||||||
)
|
crate::config::configure(&ctx, pkg_id, configure_context).await?;
|
||||||
.await?;
|
|
||||||
} else {
|
} else {
|
||||||
add_dependent_to_current_dependents_lists(
|
add_dependent_to_current_dependents_lists(
|
||||||
&mut tx,
|
&mut tx,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
use patch_db::{DbHandle, LockReceipt, LockType};
|
use patch_db::{DbHandle, LockReceipt, LockType};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
@@ -91,12 +89,12 @@ impl HealthCheckStatusReceipt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// So, this is used for a service to run a health check cycle, go out and run the health checks, and store those in the db
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn check<Db: DbHandle>(
|
pub async fn check<Db: DbHandle>(
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
db: &mut Db,
|
db: &mut Db,
|
||||||
id: &PackageId,
|
id: &PackageId,
|
||||||
should_commit: &AtomicBool,
|
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut tx = db.begin().await?;
|
let mut tx = db.begin().await?;
|
||||||
let (manifest, started) = {
|
let (manifest, started) = {
|
||||||
@@ -115,40 +113,12 @@ pub async fn check<Db: DbHandle>(
|
|||||||
tracing::debug!("Checking health of {}", id);
|
tracing::debug!("Checking health of {}", id);
|
||||||
manifest
|
manifest
|
||||||
.health_checks
|
.health_checks
|
||||||
.check_all(
|
.check_all(ctx, started, id, &manifest.version, &manifest.volumes)
|
||||||
ctx,
|
|
||||||
&manifest.containers,
|
|
||||||
started,
|
|
||||||
id,
|
|
||||||
&manifest.version,
|
|
||||||
&manifest.volumes,
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
} else {
|
} else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
if !should_commit.load(Ordering::SeqCst) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if !health_results
|
|
||||||
.iter()
|
|
||||||
.any(|(_, res)| matches!(res, HealthCheckResult::Failure { .. }))
|
|
||||||
{
|
|
||||||
tracing::debug!("All health checks succeeded for {}", id);
|
|
||||||
} else {
|
|
||||||
tracing::debug!(
|
|
||||||
"Some health checks failed for {}: {}",
|
|
||||||
id,
|
|
||||||
health_results
|
|
||||||
.iter()
|
|
||||||
.filter(|(_, res)| matches!(res, HealthCheckResult::Failure { .. }))
|
|
||||||
.map(|(id, _)| &*id)
|
|
||||||
.join(", ")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let current_dependents = {
|
let current_dependents = {
|
||||||
let mut checkpoint = tx.begin().await?;
|
let mut checkpoint = tx.begin().await?;
|
||||||
let receipts = HealthCheckStatusReceipt::new(&mut checkpoint, id).await?;
|
let receipts = HealthCheckStatusReceipt::new(&mut checkpoint, id).await?;
|
||||||
|
|||||||
343
backend/src/manager/manager_container.rs
Normal file
343
backend/src/manager/manager_container.rs
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use futures::FutureExt;
|
||||||
|
use patch_db::PatchDbHandle;
|
||||||
|
use tokio::sync::watch;
|
||||||
|
use tokio::sync::watch::Sender;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use super::start_stop::StartStop;
|
||||||
|
use super::{manager_seed, run_main, ManagerPersistentContainer, RunMainResult};
|
||||||
|
use crate::procedure::NoOutput;
|
||||||
|
use crate::s9pk::manifest::Manifest;
|
||||||
|
use crate::status::MainStatus;
|
||||||
|
use crate::util::{GeneralBoxedGuard, NonDetachingJoinHandle};
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
pub type ManageContainerOverride = Arc<watch::Sender<Option<MainStatus>>>;
|
||||||
|
|
||||||
|
/// This is the thing describing the state machine actor for a service
|
||||||
|
/// state and current running/ desired states.
|
||||||
|
pub struct ManageContainer {
|
||||||
|
pub(super) current_state: Arc<watch::Sender<StartStop>>,
|
||||||
|
pub(super) desired_state: Arc<watch::Sender<StartStop>>,
|
||||||
|
_service: NonDetachingJoinHandle<()>,
|
||||||
|
_save_state: NonDetachingJoinHandle<()>,
|
||||||
|
override_main_status: ManageContainerOverride,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManageContainer {
|
||||||
|
pub async fn new(
|
||||||
|
seed: Arc<manager_seed::ManagerSeed>,
|
||||||
|
persistent_container: ManagerPersistentContainer,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let mut db = seed.ctx.db.handle();
|
||||||
|
let current_state = Arc::new(watch::channel(StartStop::Stop).0);
|
||||||
|
let desired_state = Arc::new(
|
||||||
|
watch::channel::<StartStop>(get_status(&mut db, &seed.manifest).await.into()).0,
|
||||||
|
);
|
||||||
|
let override_main_status: ManageContainerOverride = Arc::new(watch::channel(None).0);
|
||||||
|
let service = tokio::spawn(create_service_manager(
|
||||||
|
desired_state.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
current_state.clone(),
|
||||||
|
persistent_container,
|
||||||
|
))
|
||||||
|
.into();
|
||||||
|
let save_state = tokio::spawn(save_state(
|
||||||
|
desired_state.clone(),
|
||||||
|
current_state.clone(),
|
||||||
|
override_main_status.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
))
|
||||||
|
.into();
|
||||||
|
Ok(ManageContainer {
|
||||||
|
current_state,
|
||||||
|
desired_state,
|
||||||
|
_service: service,
|
||||||
|
override_main_status,
|
||||||
|
_save_state: save_state,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set override is used during something like a restart of a service. We want to show certain statuses be different
|
||||||
|
/// from the actual status of the service.
|
||||||
|
pub fn set_override(&self, override_status: Option<MainStatus>) -> GeneralBoxedGuard {
|
||||||
|
self.override_main_status
|
||||||
|
.send_modify(|x| *x = override_status);
|
||||||
|
let override_main_status = self.override_main_status.clone();
|
||||||
|
GeneralBoxedGuard::new(move || {
|
||||||
|
override_main_status.send_modify(|x| *x = None);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the override, but don't have a guard to revert it. Used only on the mananger to do a shutdown.
|
||||||
|
pub(super) async fn lock_state_forever(&self, seed: &manager_seed::ManagerSeed) {
|
||||||
|
let mut db = seed.ctx.db.handle();
|
||||||
|
let current_state = get_status(&mut db, &seed.manifest).await;
|
||||||
|
self.override_main_status
|
||||||
|
.send_modify(|x| *x = Some(current_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// We want to set the state of the service, like to start or stop
|
||||||
|
pub fn to_desired(&self, new_state: StartStop) {
|
||||||
|
self.desired_state.send_modify(|x| *x = new_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is a tool to say wait for the service to be in a certain state.
|
||||||
|
pub async fn wait_for_desired(&self, new_state: StartStop) {
|
||||||
|
let mut current_state = self.current_state();
|
||||||
|
self.to_desired(new_state);
|
||||||
|
while *current_state.borrow() != new_state {
|
||||||
|
current_state.changed().await.unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Getter
|
||||||
|
pub fn current_state(&self) -> watch::Receiver<StartStop> {
|
||||||
|
self.current_state.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Getter
|
||||||
|
pub fn desired_state(&self) -> watch::Receiver<StartStop> {
|
||||||
|
self.desired_state.subscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_service_manager(
|
||||||
|
desired_state: Arc<Sender<StartStop>>,
|
||||||
|
seed: Arc<manager_seed::ManagerSeed>,
|
||||||
|
current_state: Arc<Sender<StartStop>>,
|
||||||
|
persistent_container: Arc<Option<super::persistent_container::PersistentContainer>>,
|
||||||
|
) {
|
||||||
|
let mut desired_state_receiver = desired_state.subscribe();
|
||||||
|
let mut running_service: Option<NonDetachingJoinHandle<()>> = None;
|
||||||
|
let seed = seed.clone();
|
||||||
|
loop {
|
||||||
|
let current: StartStop = *current_state.borrow();
|
||||||
|
let desired: StartStop = *desired_state_receiver.borrow();
|
||||||
|
match (current, desired) {
|
||||||
|
(StartStop::Start, StartStop::Start) => (),
|
||||||
|
(StartStop::Start, StartStop::Stop) => {
|
||||||
|
if persistent_container.is_none() {
|
||||||
|
if let Err(err) = seed.stop_container().await {
|
||||||
|
tracing::error!("Could not stop container");
|
||||||
|
tracing::debug!("{:?}", err)
|
||||||
|
}
|
||||||
|
running_service = None;
|
||||||
|
} else if let Some(current_service) = running_service.take() {
|
||||||
|
tokio::select! {
|
||||||
|
_ = current_service => (),
|
||||||
|
_ = tokio::time::sleep(Duration::from_secs_f64(seed.manifest
|
||||||
|
.containers
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|c| c.main.sigterm_timeout).map(|x| x.as_secs_f64()).unwrap_or_default())) => {
|
||||||
|
tracing::error!("Could not stop service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_state.send_modify(|x| *x = StartStop::Stop);
|
||||||
|
}
|
||||||
|
(StartStop::Stop, StartStop::Start) => starting_service(
|
||||||
|
current_state.clone(),
|
||||||
|
desired_state.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
persistent_container.clone(),
|
||||||
|
&mut running_service,
|
||||||
|
),
|
||||||
|
(StartStop::Stop, StartStop::Stop) => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
if desired_state_receiver.changed().await.is_err() {
|
||||||
|
tracing::error!("Desired state error");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_state(
|
||||||
|
desired_state: Arc<Sender<StartStop>>,
|
||||||
|
current_state: Arc<Sender<StartStop>>,
|
||||||
|
override_main_status: Arc<Sender<Option<MainStatus>>>,
|
||||||
|
seed: Arc<manager_seed::ManagerSeed>,
|
||||||
|
) {
|
||||||
|
let mut desired_state_receiver = desired_state.subscribe();
|
||||||
|
let mut current_state_receiver = current_state.subscribe();
|
||||||
|
let mut override_main_status_receiver = override_main_status.subscribe();
|
||||||
|
loop {
|
||||||
|
let current: StartStop = *current_state_receiver.borrow();
|
||||||
|
let desired: StartStop = *desired_state_receiver.borrow();
|
||||||
|
let override_status = override_main_status_receiver.borrow().clone();
|
||||||
|
let mut db = seed.ctx.db.handle();
|
||||||
|
let res = match (override_status, current, desired) {
|
||||||
|
(Some(status), _, _) => set_status(&mut db, &seed.manifest, &status).await,
|
||||||
|
(None, StartStop::Start, StartStop::Start) => {
|
||||||
|
set_status(
|
||||||
|
&mut db,
|
||||||
|
&seed.manifest,
|
||||||
|
&MainStatus::Running {
|
||||||
|
started: chrono::Utc::now(),
|
||||||
|
health: Default::default(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
(None, StartStop::Start, StartStop::Stop) => {
|
||||||
|
set_status(&mut db, &seed.manifest, &MainStatus::Stopping).await
|
||||||
|
}
|
||||||
|
(None, StartStop::Stop, StartStop::Start) => {
|
||||||
|
set_status(&mut db, &seed.manifest, &MainStatus::Starting).await
|
||||||
|
}
|
||||||
|
(None, StartStop::Stop, StartStop::Stop) => {
|
||||||
|
set_status(&mut db, &seed.manifest, &MainStatus::Stopped).await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(err) = res {
|
||||||
|
tracing::error!("Did not set status for {}", seed.container_name);
|
||||||
|
tracing::debug!("{:?}", err);
|
||||||
|
}
|
||||||
|
tokio::select! {
|
||||||
|
_ = desired_state_receiver.changed() =>{},
|
||||||
|
_ = current_state_receiver.changed() => {},
|
||||||
|
_ = override_main_status_receiver.changed() => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn starting_service(
|
||||||
|
current_state: Arc<Sender<StartStop>>,
|
||||||
|
desired_state: Arc<Sender<StartStop>>,
|
||||||
|
seed: Arc<manager_seed::ManagerSeed>,
|
||||||
|
persistent_container: ManagerPersistentContainer,
|
||||||
|
running_service: &mut Option<NonDetachingJoinHandle<()>>,
|
||||||
|
) {
|
||||||
|
let set_running = {
|
||||||
|
let current_state = current_state.clone();
|
||||||
|
Arc::new(move || {
|
||||||
|
current_state.send_modify(|x| *x = StartStop::Start);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let set_stopped = { move || current_state.send_modify(|x| *x = StartStop::Stop) };
|
||||||
|
let running_main_loop = async move {
|
||||||
|
while desired_state.borrow().is_start() {
|
||||||
|
let result = run_main(
|
||||||
|
seed.clone(),
|
||||||
|
persistent_container.clone(),
|
||||||
|
set_running.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
set_stopped();
|
||||||
|
run_main_log_result(result, seed.clone()).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
*running_service = Some(tokio::spawn(running_main_loop).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_main_log_result(result: RunMainResult, seed: Arc<manager_seed::ManagerSeed>) {
|
||||||
|
match result {
|
||||||
|
Ok(Ok(NoOutput)) => (), // restart
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
#[cfg(feature = "unstable")]
|
||||||
|
{
|
||||||
|
use crate::notifications::NotificationLevel;
|
||||||
|
let mut db = seed.ctx.db.handle();
|
||||||
|
let started = crate::db::DatabaseModel::new()
|
||||||
|
.package_data()
|
||||||
|
.idx_model(&seed.manifest.id)
|
||||||
|
.and_then(|pde| pde.installed())
|
||||||
|
.map::<_, MainStatus>(|i| i.status().main())
|
||||||
|
.get(&mut db)
|
||||||
|
.await;
|
||||||
|
match started.as_deref() {
|
||||||
|
Ok(Some(MainStatus::Running { .. })) => {
|
||||||
|
let res = seed.ctx.notification_manager
|
||||||
|
.notify(
|
||||||
|
&mut db,
|
||||||
|
Some(seed.manifest.id.clone()),
|
||||||
|
NotificationLevel::Warning,
|
||||||
|
String::from("Service Crashed"),
|
||||||
|
format!("The service {} has crashed with the following exit code: {}\nDetails: {}", seed.manifest.id.clone(), e.0, e.1),
|
||||||
|
(),
|
||||||
|
Some(3600) // 1 hour
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
if let Err(e) = res {
|
||||||
|
tracing::error!("Failed to issue notification: {}", e);
|
||||||
|
tracing::debug!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
tracing::error!("service just started. not issuing crash notification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tracing::error!(
|
||||||
|
"The service {} has crashed with the following exit code: {}",
|
||||||
|
seed.manifest.id.clone(),
|
||||||
|
e.0
|
||||||
|
);
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(15)).await;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("failed to start service: {}", e);
|
||||||
|
tracing::debug!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used only in the mod where we are doing a backup
|
||||||
|
#[instrument(skip(db, manifest))]
|
||||||
|
pub(super) async fn get_status(db: &mut PatchDbHandle, manifest: &Manifest) -> MainStatus {
|
||||||
|
async move {
|
||||||
|
Ok::<_, Error>(
|
||||||
|
crate::db::DatabaseModel::new()
|
||||||
|
.package_data()
|
||||||
|
.idx_model(&manifest.id)
|
||||||
|
.expect(db)
|
||||||
|
.await?
|
||||||
|
.installed()
|
||||||
|
.expect(db)
|
||||||
|
.await?
|
||||||
|
.status()
|
||||||
|
.main()
|
||||||
|
.get(db)
|
||||||
|
.await?
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.map(|x| x.unwrap_or_else(|_| MainStatus::Stopped))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip(db, manifest))]
|
||||||
|
async fn set_status(
|
||||||
|
db: &mut PatchDbHandle,
|
||||||
|
manifest: &Manifest,
|
||||||
|
main_status: &MainStatus,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
if crate::db::DatabaseModel::new()
|
||||||
|
.package_data()
|
||||||
|
.idx_model(&manifest.id)
|
||||||
|
.expect(db)
|
||||||
|
.await?
|
||||||
|
.installed()
|
||||||
|
.exists(db)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
crate::db::DatabaseModel::new()
|
||||||
|
.package_data()
|
||||||
|
.idx_model(&manifest.id)
|
||||||
|
.expect(db)
|
||||||
|
.await?
|
||||||
|
.installed()
|
||||||
|
.expect(db)
|
||||||
|
.await?
|
||||||
|
.status()
|
||||||
|
.main()
|
||||||
|
.put(db, main_status)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
111
backend/src/manager/manager_map.rs
Normal file
111
backend/src/manager/manager_map.rs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
|
use patch_db::DbHandle;
|
||||||
|
use sqlx::{Executor, Postgres};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use super::Manager;
|
||||||
|
use crate::context::RpcContext;
|
||||||
|
use crate::s9pk::manifest::{Manifest, PackageId};
|
||||||
|
use crate::util::Version;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
/// This is the structure to contain all the service managers
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ManagerMap(RwLock<BTreeMap<(PackageId, Version), Arc<Manager>>>);
|
||||||
|
impl ManagerMap {
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn init<Db: DbHandle, Ex>(
|
||||||
|
&self,
|
||||||
|
ctx: &RpcContext,
|
||||||
|
db: &mut Db,
|
||||||
|
secrets: &mut Ex,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
for<'a> &'a mut Ex: Executor<'a, Database = Postgres>,
|
||||||
|
{
|
||||||
|
let mut res = BTreeMap::new();
|
||||||
|
for package in crate::db::DatabaseModel::new()
|
||||||
|
.package_data()
|
||||||
|
.keys(db)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
let man: Manifest = if let Some(manifest) = crate::db::DatabaseModel::new()
|
||||||
|
.package_data()
|
||||||
|
.idx_model(&package)
|
||||||
|
.and_then(|pkg| pkg.installed())
|
||||||
|
.map(|m| m.manifest())
|
||||||
|
.get(db)
|
||||||
|
.await?
|
||||||
|
.to_owned()
|
||||||
|
{
|
||||||
|
manifest
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
res.insert(
|
||||||
|
(package, man.version.clone()),
|
||||||
|
Arc::new(Manager::new(ctx.clone(), man).await?),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*self.0.write().await = res;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used during the install process
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn add(&self, ctx: RpcContext, manifest: Manifest) -> Result<(), Error> {
|
||||||
|
let mut lock = self.0.write().await;
|
||||||
|
let id = (manifest.id.clone(), manifest.version.clone());
|
||||||
|
if let Some(man) = lock.remove(&id) {
|
||||||
|
man.exit().await;
|
||||||
|
}
|
||||||
|
lock.insert(id, Arc::new(Manager::new(ctx, manifest).await?));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is ran during the cleanup, so when we are uninstalling the service
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn remove(&self, id: &(PackageId, Version)) {
|
||||||
|
if let Some(man) = self.0.write().await.remove(id) {
|
||||||
|
man.exit().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used during a shutdown
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn empty(&self) -> Result<(), Error> {
|
||||||
|
let res =
|
||||||
|
futures::future::join_all(std::mem::take(&mut *self.0.write().await).into_iter().map(
|
||||||
|
|((id, version), man)| async move {
|
||||||
|
tracing::debug!("Manager for {}@{} shutting down", id, version);
|
||||||
|
man.shutdown().await;
|
||||||
|
tracing::debug!("Manager for {}@{} is shutdown", id, version);
|
||||||
|
if let Err(e) = Arc::try_unwrap(man) {
|
||||||
|
tracing::trace!(
|
||||||
|
"Manager for {}@{} still has {} other open references",
|
||||||
|
id,
|
||||||
|
version,
|
||||||
|
Arc::strong_count(&e) - 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok::<_, Error>(())
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
res.into_iter().fold(Ok(()), |res, x| match (res, x) {
|
||||||
|
(Ok(()), x) => x,
|
||||||
|
(Err(e), Ok(())) => Err(e),
|
||||||
|
(Err(e1), Err(e2)) => Err(Error::new(eyre!("{}, {}", e1.source, e2.source), e1.kind)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn get(&self, id: &(PackageId, Version)) -> Option<Arc<Manager>> {
|
||||||
|
self.0.read().await.get(id).cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
63
backend/src/manager/manager_seed.rs
Normal file
63
backend/src/manager/manager_seed.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
use bollard::container::{StopContainerOptions, WaitContainerOptions};
|
||||||
|
use tokio_stream::StreamExt;
|
||||||
|
|
||||||
|
use crate::context::RpcContext;
|
||||||
|
use crate::s9pk::manifest::Manifest;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
/// This is helper structure for a service, the seed of the data that is needed for the manager_container
|
||||||
|
pub struct ManagerSeed {
|
||||||
|
pub ctx: RpcContext,
|
||||||
|
pub manifest: Manifest,
|
||||||
|
pub container_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManagerSeed {
|
||||||
|
pub async fn stop_container(&self) -> Result<(), Error> {
|
||||||
|
match self
|
||||||
|
.ctx
|
||||||
|
.docker
|
||||||
|
.stop_container(
|
||||||
|
&self.container_name,
|
||||||
|
Some(StopContainerOptions {
|
||||||
|
t: self
|
||||||
|
.manifest
|
||||||
|
.containers
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|c| c.main.sigterm_timeout)
|
||||||
|
.map(|d| d.as_secs())
|
||||||
|
.unwrap_or(30) as i64,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(bollard::errors::Error::DockerResponseServerError {
|
||||||
|
status_code: 404, // NOT FOUND
|
||||||
|
..
|
||||||
|
})
|
||||||
|
| Err(bollard::errors::Error::DockerResponseServerError {
|
||||||
|
status_code: 409, // CONFLICT
|
||||||
|
..
|
||||||
|
})
|
||||||
|
| Err(bollard::errors::Error::DockerResponseServerError {
|
||||||
|
status_code: 304, // NOT MODIFIED
|
||||||
|
..
|
||||||
|
}) => (), // Already stopped
|
||||||
|
a => a?,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the container to stop
|
||||||
|
{
|
||||||
|
let mut waiting = self.ctx.docker.wait_container(
|
||||||
|
&self.container_name,
|
||||||
|
Some(WaitContainerOptions {
|
||||||
|
condition: "not-running",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
while let Some(_) = waiting.next().await {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
101
backend/src/manager/persistent_container.rs
Normal file
101
backend/src/manager/persistent_container.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
|
use helpers::UnixRpcClient;
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
use tokio::sync::watch::{self, Receiver};
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
|
use super::manager_seed::ManagerSeed;
|
||||||
|
use super::{
|
||||||
|
add_network_for_main, get_long_running_ip, long_running_docker, remove_network_for_main,
|
||||||
|
GetRunningIp,
|
||||||
|
};
|
||||||
|
use crate::procedure::docker::DockerContainer;
|
||||||
|
use crate::util::NonDetachingJoinHandle;
|
||||||
|
use crate::Error;
|
||||||
|
|
||||||
|
/// Persistant container are the old containers that need to run all the time
|
||||||
|
/// The goal is that all services will be persistent containers, waiting to run the main system.
|
||||||
|
pub struct PersistentContainer {
|
||||||
|
_running_docker: NonDetachingJoinHandle<()>,
|
||||||
|
pub rpc_client: Receiver<Arc<UnixRpcClient>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PersistentContainer {
|
||||||
|
#[instrument(skip_all)]
|
||||||
|
pub async fn init(seed: &Arc<ManagerSeed>) -> Result<Option<Self>, Error> {
|
||||||
|
Ok(if let Some(containers) = &seed.manifest.containers {
|
||||||
|
let (running_docker, rpc_client) =
|
||||||
|
spawn_persistent_container(seed.clone(), containers.main.clone()).await?;
|
||||||
|
Some(Self {
|
||||||
|
_running_docker: running_docker,
|
||||||
|
rpc_client,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rpc_client(&self) -> Arc<UnixRpcClient> {
|
||||||
|
self.rpc_client.borrow().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn spawn_persistent_container(
|
||||||
|
seed: Arc<ManagerSeed>,
|
||||||
|
container: DockerContainer,
|
||||||
|
) -> Result<(NonDetachingJoinHandle<()>, Receiver<Arc<UnixRpcClient>>), Error> {
|
||||||
|
let (send_inserter, inserter) = oneshot::channel();
|
||||||
|
Ok((
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
let mut inserter_send: Option<watch::Sender<Arc<UnixRpcClient>>> = None;
|
||||||
|
let mut send_inserter: Option<oneshot::Sender<Receiver<Arc<UnixRpcClient>>>> = Some(send_inserter);
|
||||||
|
loop {
|
||||||
|
if let Err(e) = async {
|
||||||
|
let (mut runtime, inserter) =
|
||||||
|
long_running_docker(&seed, &container).await?;
|
||||||
|
|
||||||
|
|
||||||
|
let ip = match get_long_running_ip(&seed, &mut runtime).await {
|
||||||
|
GetRunningIp::Ip(x) => x,
|
||||||
|
GetRunningIp::Error(e) => return Err(e),
|
||||||
|
GetRunningIp::EarlyExit(e) => {
|
||||||
|
tracing::error!("Early Exit");
|
||||||
|
tracing::debug!("{:?}", e);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let svc = add_network_for_main(&seed, ip).await?;
|
||||||
|
|
||||||
|
if let Some(inserter_send) = inserter_send.as_mut() {
|
||||||
|
let _ = inserter_send.send(Arc::new(inserter));
|
||||||
|
} else {
|
||||||
|
let (s, r) = watch::channel(Arc::new(inserter));
|
||||||
|
inserter_send = Some(s);
|
||||||
|
if let Some(send_inserter) = send_inserter.take() {
|
||||||
|
let _ = send_inserter.send(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = tokio::select! {
|
||||||
|
a = runtime.running_output => a.map_err(|_| Error::new(eyre!("Manager runtime panicked!"), crate::ErrorKind::Docker)).map(|_| ()),
|
||||||
|
};
|
||||||
|
|
||||||
|
remove_network_for_main(svc).await?;
|
||||||
|
|
||||||
|
res
|
||||||
|
}.await {
|
||||||
|
tracing::error!("Error in persistent container: {}", e);
|
||||||
|
tracing::debug!("{:?}", e);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(200)).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
inserter.await.map_err(|_| Error::new(eyre!("Container handle dropped before inserter sent"), crate::ErrorKind::Unknown))?,
|
||||||
|
))
|
||||||
|
}
|
||||||
26
backend/src/manager/start_stop.rs
Normal file
26
backend/src/manager/start_stop.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use crate::status::MainStatus;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum StartStop {
|
||||||
|
Start,
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StartStop {
|
||||||
|
pub(crate) fn is_start(&self) -> bool {
|
||||||
|
matches!(self, StartStop::Start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<MainStatus> for StartStop {
|
||||||
|
fn from(value: MainStatus) -> Self {
|
||||||
|
match value {
|
||||||
|
MainStatus::Stopped => StartStop::Stop,
|
||||||
|
MainStatus::Restarting => StartStop::Start,
|
||||||
|
MainStatus::Stopping => StartStop::Stop,
|
||||||
|
MainStatus::Starting => StartStop::Start,
|
||||||
|
MainStatus::Running { started, health } => StartStop::Start,
|
||||||
|
MainStatus::BackingUp { started, health } if started.is_some() => StartStop::Start,
|
||||||
|
MainStatus::BackingUp { started, health } => StartStop::Stop,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use chrono::Utc;
|
|
||||||
|
|
||||||
use super::{pause, resume, start, stop, ManagerSharedState, Status};
|
|
||||||
use crate::status::MainStatus;
|
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
/// Allocates a db handle. DO NOT CALL with a db handle already in scope
|
|
||||||
async fn synchronize_once(shared: &ManagerSharedState) -> Result<Status, Error> {
|
|
||||||
let mut db = shared.seed.ctx.db.handle();
|
|
||||||
let mut status = crate::db::DatabaseModel::new()
|
|
||||||
.package_data()
|
|
||||||
.idx_model(&shared.seed.manifest.id)
|
|
||||||
.expect(&mut db)
|
|
||||||
.await?
|
|
||||||
.installed()
|
|
||||||
.expect(&mut db)
|
|
||||||
.await?
|
|
||||||
.status()
|
|
||||||
.main()
|
|
||||||
.get_mut(&mut db)
|
|
||||||
.await?;
|
|
||||||
let manager_status = *shared.status.1.borrow();
|
|
||||||
match manager_status {
|
|
||||||
Status::Stopped => match &mut *status {
|
|
||||||
MainStatus::Stopped => (),
|
|
||||||
MainStatus::Stopping => {
|
|
||||||
*status = MainStatus::Stopped;
|
|
||||||
}
|
|
||||||
MainStatus::Restarting => {
|
|
||||||
*status = MainStatus::Starting { restarting: true };
|
|
||||||
}
|
|
||||||
MainStatus::Starting { .. } => {
|
|
||||||
start(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::Running { started, .. } => {
|
|
||||||
*started = Utc::now();
|
|
||||||
start(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::BackingUp { .. } => (),
|
|
||||||
},
|
|
||||||
Status::Starting => match *status {
|
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
|
||||||
stop(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::Starting { .. } | MainStatus::Running { .. } => (),
|
|
||||||
MainStatus::BackingUp { .. } => {
|
|
||||||
pause(shared).await?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Status::Running => match *status {
|
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
|
||||||
stop(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::Starting { .. } => {
|
|
||||||
*status = MainStatus::Running {
|
|
||||||
started: Utc::now(),
|
|
||||||
health: BTreeMap::new(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
MainStatus::Running { .. } => (),
|
|
||||||
MainStatus::BackingUp { .. } => {
|
|
||||||
pause(shared).await?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Status::Paused => match *status {
|
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
|
||||||
stop(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::Starting { .. } | MainStatus::Running { .. } => {
|
|
||||||
resume(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::BackingUp { .. } => (),
|
|
||||||
},
|
|
||||||
Status::Shutdown => (),
|
|
||||||
}
|
|
||||||
status.save(&mut db).await?;
|
|
||||||
Ok(manager_status)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn synchronizer(shared: &ManagerSharedState) {
|
|
||||||
let mut status_recv = shared.status.0.subscribe();
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = tokio::time::sleep(Duration::from_secs(5)) => (),
|
|
||||||
_ = shared.synchronize_now.notified() => (),
|
|
||||||
_ = status_recv.changed() => (),
|
|
||||||
}
|
|
||||||
let status = match synchronize_once(shared).await {
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(
|
|
||||||
"Synchronizer for {}@{} failed: {}",
|
|
||||||
shared.seed.manifest.id,
|
|
||||||
shared.seed.manifest.version,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
tracing::debug!("{:?}", e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Ok(status) => status,
|
|
||||||
};
|
|
||||||
tracing::trace!("{} status synchronized", shared.seed.manifest.id);
|
|
||||||
shared.synchronized.notify_waiters();
|
|
||||||
match status {
|
|
||||||
Status::Shutdown => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
30
backend/src/manager/transition_state.rs
Normal file
30
backend/src/manager/transition_state.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use helpers::NonDetachingJoinHandle;
|
||||||
|
|
||||||
|
/// Used only in the manager/mod and is used to keep track of the state of the manager during the
|
||||||
|
/// transitional states
|
||||||
|
pub(super) enum TransitionState {
|
||||||
|
BackingUp(NonDetachingJoinHandle<()>),
|
||||||
|
Restarting(NonDetachingJoinHandle<()>),
|
||||||
|
Configuring(NonDetachingJoinHandle<()>),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransitionState {
|
||||||
|
pub(super) fn join_handle(&self) -> Option<&NonDetachingJoinHandle<()>> {
|
||||||
|
Some(match self {
|
||||||
|
TransitionState::BackingUp(a) => a,
|
||||||
|
TransitionState::Restarting(a) => a,
|
||||||
|
TransitionState::Configuring(a) => a,
|
||||||
|
TransitionState::None => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub(super) fn abort(&self) {
|
||||||
|
self.join_handle().map(|transition| transition.abort());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TransitionState {
|
||||||
|
fn default() -> Self {
|
||||||
|
TransitionState::None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ impl Migrations {
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
for (version, migration) in &self.from {
|
for (version, migration) in &self.from {
|
||||||
migration
|
migration
|
||||||
.validate(container, eos_version, volumes, image_ids, true)
|
.validate(eos_version, volumes, image_ids, true)
|
||||||
.with_ctx(|_| {
|
.with_ctx(|_| {
|
||||||
(
|
(
|
||||||
crate::ErrorKind::ValidateS9pk,
|
crate::ErrorKind::ValidateS9pk,
|
||||||
@@ -44,7 +44,7 @@ impl Migrations {
|
|||||||
}
|
}
|
||||||
for (version, migration) in &self.to {
|
for (version, migration) in &self.to {
|
||||||
migration
|
migration
|
||||||
.validate(container, eos_version, volumes, image_ids, true)
|
.validate(eos_version, volumes, image_ids, true)
|
||||||
.with_ctx(|_| {
|
.with_ctx(|_| {
|
||||||
(
|
(
|
||||||
crate::ErrorKind::ValidateS9pk,
|
crate::ErrorKind::ValidateS9pk,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::fs::Metadata;
|
use std::fs::Metadata;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -10,7 +9,6 @@ use digest::Digest;
|
|||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use http::header::ACCEPT_ENCODING;
|
use http::header::ACCEPT_ENCODING;
|
||||||
use http::request::Parts as RequestParts;
|
use http::request::Parts as RequestParts;
|
||||||
use http::response::Builder;
|
|
||||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||||
use include_dir::{include_dir, Dir};
|
use include_dir::{include_dir, Dir};
|
||||||
use new_mime_guess::MimeGuess;
|
use new_mime_guess::MimeGuess;
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ impl hyper::server::accept::Accept for TcpListeners {
|
|||||||
type Error = std::io::Error;
|
type Error = std::io::Error;
|
||||||
|
|
||||||
fn poll_accept(
|
fn poll_accept(
|
||||||
mut self: std::pin::Pin<&mut Self>,
|
self: std::pin::Pin<&mut Self>,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> std::task::Poll<Option<Result<Self::Conn, Self::Error>>> {
|
) -> std::task::Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||||
for listener in self.listeners.iter() {
|
for listener in self.listeners.iter() {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use async_stream::stream;
|
use async_stream::stream;
|
||||||
use bollard::container::RemoveContainerOptions;
|
use bollard::container::RemoveContainerOptions;
|
||||||
use chrono::format::Item;
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use color_eyre::Report;
|
use color_eyre::Report;
|
||||||
use futures::future::{BoxFuture, Either as EitherFuture};
|
use futures::future::{BoxFuture, Either as EitherFuture};
|
||||||
@@ -199,7 +198,7 @@ impl DockerProcedure {
|
|||||||
image_ids: &BTreeSet<ImageId>,
|
image_ids: &BTreeSet<ImageId>,
|
||||||
expected_io: bool,
|
expected_io: bool,
|
||||||
) -> Result<(), color_eyre::eyre::Report> {
|
) -> Result<(), color_eyre::eyre::Report> {
|
||||||
for (volume, _) in &self.mounts {
|
for volume in self.mounts.keys() {
|
||||||
if !volumes.contains_key(volume) && !matches!(&volume, &VolumeId::Backup) {
|
if !volumes.contains_key(volume) && !matches!(&volume, &VolumeId::Backup) {
|
||||||
color_eyre::eyre::bail!("unknown volume: {}", volume);
|
color_eyre::eyre::bail!("unknown volume: {}", volume);
|
||||||
}
|
}
|
||||||
@@ -229,7 +228,7 @@ impl DockerProcedure {
|
|||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
let name = name.docker_name();
|
let name = name.docker_name();
|
||||||
let name: Option<&str> = name.as_ref().map(|x| &**x);
|
let name: Option<&str> = name.as_deref();
|
||||||
let mut cmd = tokio::process::Command::new("docker");
|
let mut cmd = tokio::process::Command::new("docker");
|
||||||
let container_name = Self::container_name(pkg_id, name);
|
let container_name = Self::container_name(pkg_id, name);
|
||||||
cmd.arg("run")
|
cmd.arg("run")
|
||||||
@@ -408,8 +407,6 @@ impl DockerProcedure {
|
|||||||
input: Option<I>,
|
input: Option<I>,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
let name = name.docker_name();
|
|
||||||
let name: Option<&str> = name.as_deref();
|
|
||||||
let mut cmd = tokio::process::Command::new("docker");
|
let mut cmd = tokio::process::Command::new("docker");
|
||||||
|
|
||||||
cmd.arg("exec");
|
cmd.arg("exec");
|
||||||
@@ -559,7 +556,7 @@ impl DockerProcedure {
|
|||||||
pkg_version: &Version,
|
pkg_version: &Version,
|
||||||
volumes: &Volumes,
|
volumes: &Volumes,
|
||||||
input: Option<I>,
|
input: Option<I>,
|
||||||
timeout: Option<Duration>,
|
_timeout: Option<Duration>,
|
||||||
) -> Result<Result<O, (i32, String)>, Error> {
|
) -> Result<Result<O, (i32, String)>, Error> {
|
||||||
let mut cmd = tokio::process::Command::new("docker");
|
let mut cmd = tokio::process::Command::new("docker");
|
||||||
cmd.arg("run").arg("--rm").arg("--network=none");
|
cmd.arg("run").arg("--rm").arg("--network=none");
|
||||||
@@ -726,7 +723,7 @@ impl DockerProcedure {
|
|||||||
if fty.is_block_device() || fty.is_char_device() {
|
if fty.is_block_device() || fty.is_char_device() {
|
||||||
res.push(entry.path());
|
res.push(entry.path());
|
||||||
} else if fty.is_dir() {
|
} else if fty.is_dir() {
|
||||||
get_devices(&*entry.path(), res).await?;
|
get_devices(&entry.path(), res).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -745,7 +742,7 @@ impl DockerProcedure {
|
|||||||
res.push(OsStr::new("--entrypoint").into());
|
res.push(OsStr::new("--entrypoint").into());
|
||||||
res.push(OsStr::new(&self.entrypoint).into());
|
res.push(OsStr::new(&self.entrypoint).into());
|
||||||
if self.system {
|
if self.system {
|
||||||
res.push(OsString::from(self.image.for_package(&*SYSTEM_PACKAGE_ID, None)).into());
|
res.push(OsString::from(self.image.for_package(&SYSTEM_PACKAGE_ID, None)).into());
|
||||||
} else {
|
} else {
|
||||||
res.push(OsString::from(self.image.for_package(pkg_id, Some(pkg_version))).into());
|
res.push(OsString::from(self.image.for_package(pkg_id, Some(pkg_version))).into());
|
||||||
}
|
}
|
||||||
@@ -833,7 +830,7 @@ impl LongRunning {
|
|||||||
.arg("'{{.Architecture}}'");
|
.arg("'{{.Architecture}}'");
|
||||||
|
|
||||||
if docker.system {
|
if docker.system {
|
||||||
cmd.arg(docker.image.for_package(&*SYSTEM_PACKAGE_ID, None));
|
cmd.arg(docker.image.for_package(&SYSTEM_PACKAGE_ID, None));
|
||||||
} else {
|
} else {
|
||||||
cmd.arg(docker.image.for_package(pkg_id, Some(pkg_version)));
|
cmd.arg(docker.image.for_package(pkg_id, Some(pkg_version)));
|
||||||
}
|
}
|
||||||
@@ -855,7 +852,7 @@ impl LongRunning {
|
|||||||
input = socket_path.display()
|
input = socket_path.display()
|
||||||
))
|
))
|
||||||
.arg("--name")
|
.arg("--name")
|
||||||
.arg(&container_name)
|
.arg(container_name)
|
||||||
.arg(format!("--hostname={}", &container_name))
|
.arg(format!("--hostname={}", &container_name))
|
||||||
.arg("--entrypoint")
|
.arg("--entrypoint")
|
||||||
.arg(format!("{INIT_EXEC}.{image_architecture}"))
|
.arg(format!("{INIT_EXEC}.{image_architecture}"))
|
||||||
@@ -885,7 +882,7 @@ impl LongRunning {
|
|||||||
}
|
}
|
||||||
cmd.arg("--log-driver=journald");
|
cmd.arg("--log-driver=journald");
|
||||||
if docker.system {
|
if docker.system {
|
||||||
cmd.arg(docker.image.for_package(&*SYSTEM_PACKAGE_ID, None));
|
cmd.arg(docker.image.for_package(&SYSTEM_PACKAGE_ID, None));
|
||||||
} else {
|
} else {
|
||||||
cmd.arg(docker.image.for_package(pkg_id, Some(pkg_version)));
|
cmd.arg(docker.image.for_package(pkg_id, Some(pkg_version)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use serde::de::DeserializeOwned;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use self::docker::{DockerContainers, DockerProcedure};
|
use self::docker::DockerProcedure;
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::Version;
|
use crate::util::Version;
|
||||||
@@ -43,7 +43,6 @@ impl PackageProcedure {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn validate(
|
pub fn validate(
|
||||||
&self,
|
&self,
|
||||||
container: &Option<DockerContainers>,
|
|
||||||
eos_version: &Version,
|
eos_version: &Version,
|
||||||
volumes: &Volumes,
|
volumes: &Volumes,
|
||||||
image_ids: &BTreeSet<ImageId>,
|
image_ids: &BTreeSet<ImageId>,
|
||||||
@@ -83,25 +82,21 @@ impl PackageProcedure {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "js_engine")]
|
#[cfg(feature = "js_engine")]
|
||||||
PackageProcedure::Script(procedure) => {
|
PackageProcedure::Script(procedure) => {
|
||||||
let (gid, rpc_client) = match ctx
|
let man = ctx
|
||||||
.managers
|
.managers
|
||||||
.get(&(pkg_id.clone(), pkg_version.clone()))
|
.get(&(pkg_id.clone(), pkg_version.clone()))
|
||||||
.await
|
.await
|
||||||
{
|
.ok_or_else(|| {
|
||||||
None => {
|
Error::new(
|
||||||
return Err(Error::new(
|
|
||||||
eyre!("No manager found for {}", pkg_id),
|
eyre!("No manager found for {}", pkg_id),
|
||||||
ErrorKind::NotFound,
|
ErrorKind::NotFound,
|
||||||
))
|
)
|
||||||
}
|
})?;
|
||||||
Some(man) => (
|
let rpc_client = man.rpc_client();
|
||||||
if matches!(name, ProcedureName::Main) {
|
let gid = if matches!(name, ProcedureName::Main) {
|
||||||
man.new_main_gid()
|
man.gid.new_main_gid()
|
||||||
} else {
|
} else {
|
||||||
man.new_gid()
|
man.gid.new_gid()
|
||||||
},
|
|
||||||
man.rpc_client(),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
procedure
|
procedure
|
||||||
@@ -124,7 +119,6 @@ impl PackageProcedure {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn sandboxed<I: Serialize, O: DeserializeOwned>(
|
pub async fn sandboxed<I: Serialize, O: DeserializeOwned>(
|
||||||
&self,
|
&self,
|
||||||
container: &Option<DockerContainers>,
|
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
pkg_id: &PackageId,
|
pkg_id: &PackageId,
|
||||||
pkg_version: &Version,
|
pkg_version: &Version,
|
||||||
|
|||||||
@@ -185,18 +185,14 @@ impl<R: AsyncRead + AsyncSeek + Unpin + Send + Sync> S9pkReader<R> {
|
|||||||
.map(|i| i.validate(&man.id, &man.version).map(|_| i.image_id))
|
.map(|i| i.validate(&man.id, &man.version).map(|_| i.image_id))
|
||||||
.collect::<Result<BTreeSet<ImageId>, _>>()?;
|
.collect::<Result<BTreeSet<ImageId>, _>>()?;
|
||||||
man.description.validate()?;
|
man.description.validate()?;
|
||||||
man.actions
|
man.actions.0.iter().try_for_each(|(_, action)| {
|
||||||
.0
|
action.validate(
|
||||||
.iter()
|
containers,
|
||||||
.map(|(_, action)| {
|
&man.eos_version,
|
||||||
action.validate(
|
&man.volumes,
|
||||||
containers,
|
&validated_image_ids,
|
||||||
&man.eos_version,
|
)
|
||||||
&man.volumes,
|
})?;
|
||||||
&validated_image_ids,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Result<(), Error>>()?;
|
|
||||||
man.backup.validate(
|
man.backup.validate(
|
||||||
containers,
|
containers,
|
||||||
&man.eos_version,
|
&man.eos_version,
|
||||||
@@ -211,21 +207,11 @@ impl<R: AsyncRead + AsyncSeek + Unpin + Send + Sync> S9pkReader<R> {
|
|||||||
&validated_image_ids,
|
&validated_image_ids,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
man.health_checks.validate(
|
man.health_checks
|
||||||
containers,
|
.validate(&man.eos_version, &man.volumes, &validated_image_ids)?;
|
||||||
&man.eos_version,
|
|
||||||
&man.volumes,
|
|
||||||
&validated_image_ids,
|
|
||||||
)?;
|
|
||||||
man.interfaces.validate()?;
|
man.interfaces.validate()?;
|
||||||
man.main
|
man.main
|
||||||
.validate(
|
.validate(&man.eos_version, &man.volumes, &validated_image_ids, false)
|
||||||
containers,
|
|
||||||
&man.eos_version,
|
|
||||||
&man.volumes,
|
|
||||||
&validated_image_ids,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Main"))?;
|
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Main"))?;
|
||||||
man.migrations.validate(
|
man.migrations.validate(
|
||||||
containers,
|
containers,
|
||||||
@@ -273,13 +259,7 @@ impl<R: AsyncRead + AsyncSeek + Unpin + Send + Sync> S9pkReader<R> {
|
|||||||
}
|
}
|
||||||
if let Some(props) = &man.properties {
|
if let Some(props) = &man.properties {
|
||||||
props
|
props
|
||||||
.validate(
|
.validate(&man.eos_version, &man.volumes, &validated_image_ids, true)
|
||||||
containers,
|
|
||||||
&man.eos_version,
|
|
||||||
&man.volumes,
|
|
||||||
&validated_image_ids,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Properties"))?;
|
.with_ctx(|_| (crate::ErrorKind::ValidateS9pk, "Properties"))?;
|
||||||
}
|
}
|
||||||
man.volumes.validate(&man.interfaces)?;
|
man.volumes.validate(&man.interfaces)?;
|
||||||
@@ -387,7 +367,7 @@ impl<R: AsyncRead + AsyncSeek + Unpin + Send + Sync> S9pkReader<R> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn manifest_raw<'a>(&'a mut self) -> Result<ReadHandle<'a, R>, Error> {
|
pub async fn manifest_raw(&mut self) -> Result<ReadHandle<'_, R>, Error> {
|
||||||
self.read_handle(self.toc.manifest).await
|
self.read_handle(self.toc.manifest).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,27 +377,27 @@ impl<R: AsyncRead + AsyncSeek + Unpin + Send + Sync> S9pkReader<R> {
|
|||||||
.with_ctx(|_| (crate::ErrorKind::ParseS9pk, "Deserializing Manifest (CBOR)"))
|
.with_ctx(|_| (crate::ErrorKind::ParseS9pk, "Deserializing Manifest (CBOR)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn license<'a>(&'a mut self) -> Result<ReadHandle<'a, R>, Error> {
|
pub async fn license(&mut self) -> Result<ReadHandle<'_, R>, Error> {
|
||||||
Ok(self.read_handle(self.toc.license).await?)
|
self.read_handle(self.toc.license).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn instructions<'a>(&'a mut self) -> Result<ReadHandle<'a, R>, Error> {
|
pub async fn instructions(&mut self) -> Result<ReadHandle<'_, R>, Error> {
|
||||||
Ok(self.read_handle(self.toc.instructions).await?)
|
self.read_handle(self.toc.instructions).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn icon<'a>(&'a mut self) -> Result<ReadHandle<'a, R>, Error> {
|
pub async fn icon(&mut self) -> Result<ReadHandle<'_, R>, Error> {
|
||||||
Ok(self.read_handle(self.toc.icon).await?)
|
self.read_handle(self.toc.icon).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn docker_images<'a>(&'a mut self) -> Result<DockerReader<ReadHandle<'a, R>>, Error> {
|
pub async fn docker_images(&mut self) -> Result<DockerReader<ReadHandle<'_, R>>, Error> {
|
||||||
DockerReader::new(self.read_handle(self.toc.docker_images).await?).await
|
DockerReader::new(self.read_handle(self.toc.docker_images).await?).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn assets<'a>(&'a mut self) -> Result<ReadHandle<'a, R>, Error> {
|
pub async fn assets(&mut self) -> Result<ReadHandle<'_, R>, Error> {
|
||||||
Ok(self.read_handle(self.toc.assets).await?)
|
self.read_handle(self.toc.assets).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn scripts<'a>(&'a mut self) -> Result<Option<ReadHandle<'a, R>>, Error> {
|
pub async fn scripts(&mut self) -> Result<Option<ReadHandle<'_, R>>, Error> {
|
||||||
Ok(match self.toc.scripts {
|
Ok(match self.toc.scripts {
|
||||||
None => None,
|
None => None,
|
||||||
Some(a) => Some(self.read_handle(a).await?),
|
Some(a) => Some(self.read_handle(a).await?),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use futures::StreamExt;
|
|
||||||
use josekit::jwk::Jwk;
|
use josekit::jwk::Jwk;
|
||||||
use openssl::x509::X509;
|
use openssl::x509::X509;
|
||||||
use patch_db::DbHandle;
|
use patch_db::DbHandle;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::disk::main::export;
|
|||||||
use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
|
use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
|
||||||
use crate::sound::SHUTDOWN;
|
use crate::sound::SHUTDOWN;
|
||||||
use crate::util::{display_none, Invoke};
|
use crate::util::{display_none, Invoke};
|
||||||
use crate::{Error, ErrorKind, OS_ARCH};
|
use crate::{Error, OS_ARCH};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Shutdown {
|
pub struct Shutdown {
|
||||||
@@ -72,18 +72,16 @@ impl Shutdown {
|
|||||||
Command::new("sync").spawn().unwrap().wait().unwrap();
|
Command::new("sync").spawn().unwrap().wait().unwrap();
|
||||||
}
|
}
|
||||||
Command::new("reboot").spawn().unwrap().wait().unwrap();
|
Command::new("reboot").spawn().unwrap().wait().unwrap();
|
||||||
|
} else if self.restart {
|
||||||
|
Command::new("reboot").spawn().unwrap().wait().unwrap();
|
||||||
} else {
|
} else {
|
||||||
if self.restart {
|
Command::new("shutdown")
|
||||||
Command::new("reboot").spawn().unwrap().wait().unwrap();
|
.arg("-h")
|
||||||
} else {
|
.arg("now")
|
||||||
Command::new("shutdown")
|
.spawn()
|
||||||
.arg("-h")
|
.unwrap()
|
||||||
.arg("now")
|
.wait()
|
||||||
.spawn()
|
.unwrap();
|
||||||
.unwrap()
|
|
||||||
.wait()
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ use chrono::Utc;
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use rpc_toolkit::command;
|
use rpc_toolkit::command;
|
||||||
use sqlx::{Executor, Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
use ssh_key::private::Ed25519PrivateKey;
|
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::procedure::docker::DockerContainers;
|
|
||||||
use crate::procedure::{NoOutput, PackageProcedure, ProcedureName};
|
use crate::procedure::{NoOutput, PackageProcedure, ProcedureName};
|
||||||
use crate::s9pk::manifest::PackageId;
|
use crate::s9pk::manifest::PackageId;
|
||||||
use crate::util::serde::Duration;
|
use crate::util::serde::Duration;
|
||||||
@@ -21,15 +20,14 @@ impl HealthChecks {
|
|||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub fn validate(
|
pub fn validate(
|
||||||
&self,
|
&self,
|
||||||
container: &Option<DockerContainers>,
|
|
||||||
eos_version: &Version,
|
eos_version: &Version,
|
||||||
volumes: &Volumes,
|
volumes: &Volumes,
|
||||||
image_ids: &BTreeSet<ImageId>,
|
image_ids: &BTreeSet<ImageId>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
for (_, check) in &self.0 {
|
for check in self.0.values() {
|
||||||
check
|
check
|
||||||
.implementation
|
.implementation
|
||||||
.validate(container, eos_version, &volumes, image_ids, false)
|
.validate(eos_version, volumes, image_ids, false)
|
||||||
.with_ctx(|_| {
|
.with_ctx(|_| {
|
||||||
(
|
(
|
||||||
crate::ErrorKind::ValidateS9pk,
|
crate::ErrorKind::ValidateS9pk,
|
||||||
@@ -42,7 +40,6 @@ impl HealthChecks {
|
|||||||
pub async fn check_all(
|
pub async fn check_all(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
container: &Option<DockerContainers>,
|
|
||||||
started: DateTime<Utc>,
|
started: DateTime<Utc>,
|
||||||
pkg_id: &PackageId,
|
pkg_id: &PackageId,
|
||||||
pkg_version: &Version,
|
pkg_version: &Version,
|
||||||
@@ -52,7 +49,7 @@ impl HealthChecks {
|
|||||||
Ok::<_, Error>((
|
Ok::<_, Error>((
|
||||||
id.clone(),
|
id.clone(),
|
||||||
check
|
check
|
||||||
.check(ctx, container, id, started, pkg_id, pkg_version, volumes)
|
.check(ctx, id, started, pkg_id, pkg_version, volumes)
|
||||||
.await?,
|
.await?,
|
||||||
))
|
))
|
||||||
}))
|
}))
|
||||||
@@ -75,7 +72,6 @@ impl HealthCheck {
|
|||||||
pub async fn check(
|
pub async fn check(
|
||||||
&self,
|
&self,
|
||||||
ctx: &RpcContext,
|
ctx: &RpcContext,
|
||||||
container: &Option<DockerContainers>,
|
|
||||||
id: &HealthCheckId,
|
id: &HealthCheckId,
|
||||||
started: DateTime<Utc>,
|
started: DateTime<Utc>,
|
||||||
pkg_id: &PackageId,
|
pkg_id: &PackageId,
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ pub enum MainStatus {
|
|||||||
Stopped,
|
Stopped,
|
||||||
Restarting,
|
Restarting,
|
||||||
Stopping,
|
Stopping,
|
||||||
Starting {
|
Starting,
|
||||||
restarting: bool,
|
|
||||||
},
|
|
||||||
Running {
|
Running {
|
||||||
started: DateTime<Utc>,
|
started: DateTime<Utc>,
|
||||||
health: BTreeMap<HealthCheckId, HealthCheckResult>,
|
health: BTreeMap<HealthCheckId, HealthCheckResult>,
|
||||||
@@ -73,6 +71,17 @@ impl MainStatus {
|
|||||||
MainStatus::Starting { .. } => None,
|
MainStatus::Starting { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn backing_up(&self) -> Self {
|
||||||
|
let (started, health) = match self {
|
||||||
|
MainStatus::Starting { .. } => (Some(Utc::now()), Default::default()),
|
||||||
|
MainStatus::Running { started, health } => (Some(started.clone()), health.clone()),
|
||||||
|
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
||||||
|
(None, Default::default())
|
||||||
|
}
|
||||||
|
MainStatus::BackingUp { .. } => return self.clone(),
|
||||||
|
};
|
||||||
|
MainStatus::BackingUp { started, health }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl MainStatusModel {
|
impl MainStatusModel {
|
||||||
pub fn started(self) -> Model<Option<DateTime<Utc>>> {
|
pub fn started(self) -> Model<Option<DateTime<Utc>>> {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ use crate::sound::{
|
|||||||
};
|
};
|
||||||
use crate::update::latest_information::LatestInformation;
|
use crate::update::latest_information::LatestInformation;
|
||||||
use crate::util::Invoke;
|
use crate::util::Invoke;
|
||||||
use crate::version::{Current, VersionT};
|
|
||||||
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
|
use crate::{Error, ErrorKind, ResultExt, OS_ARCH};
|
||||||
|
|
||||||
mod latest_information;
|
mod latest_information;
|
||||||
|
|||||||
@@ -522,7 +522,6 @@ pub fn dir_copy<'a, P0: AsRef<Path> + 'a + Send + Sync, P1: AsRef<Path> + 'a + S
|
|||||||
let src_path = e.path();
|
let src_path = e.path();
|
||||||
let dst_path = dst_path.join(e.file_name());
|
let dst_path = dst_path.join(e.file_name());
|
||||||
if m.is_file() {
|
if m.is_file() {
|
||||||
let len = m.len();
|
|
||||||
let mut dst_file = tokio::fs::File::create(&dst_path).await.with_ctx(|_| {
|
let mut dst_file = tokio::fs::File::create(&dst_path).await.with_ctx(|_| {
|
||||||
(
|
(
|
||||||
crate::ErrorKind::Filesystem,
|
crate::ErrorKind::Filesystem,
|
||||||
@@ -638,7 +637,7 @@ impl<S: AsyncRead + AsyncWrite> AsyncWrite for TimeoutStream<S> {
|
|||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||||
let mut this = self.project();
|
let this = self.project();
|
||||||
let res = this.stream.poll_write(cx, buf);
|
let res = this.stream.poll_write(cx, buf);
|
||||||
if res.is_ready() {
|
if res.is_ready() {
|
||||||
this.sleep.reset(Instant::now() + *this.timeout);
|
this.sleep.reset(Instant::now() + *this.timeout);
|
||||||
@@ -649,7 +648,7 @@ impl<S: AsyncRead + AsyncWrite> AsyncWrite for TimeoutStream<S> {
|
|||||||
self: std::pin::Pin<&mut Self>,
|
self: std::pin::Pin<&mut Self>,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||||
let mut this = self.project();
|
let this = self.project();
|
||||||
let res = this.stream.poll_flush(cx);
|
let res = this.stream.poll_flush(cx);
|
||||||
if res.is_ready() {
|
if res.is_ready() {
|
||||||
this.sleep.reset(Instant::now() + *this.timeout);
|
this.sleep.reset(Instant::now() + *this.timeout);
|
||||||
@@ -660,7 +659,7 @@ impl<S: AsyncRead + AsyncWrite> AsyncWrite for TimeoutStream<S> {
|
|||||||
self: std::pin::Pin<&mut Self>,
|
self: std::pin::Pin<&mut Self>,
|
||||||
cx: &mut std::task::Context<'_>,
|
cx: &mut std::task::Context<'_>,
|
||||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||||
let mut this = self.project();
|
let this = self.project();
|
||||||
let res = this.stream.poll_shutdown(cx);
|
let res = this.stream.poll_shutdown(cx);
|
||||||
if res.is_ready() {
|
if res.is_ready() {
|
||||||
this.sleep.reset(Instant::now() + *this.timeout);
|
this.sleep.reset(Instant::now() + *this.timeout);
|
||||||
|
|||||||
@@ -170,9 +170,7 @@ impl<W: std::fmt::Write> std::io::Write for FmtWriter<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_none<T>(_: T, _: &ArgMatches) {
|
pub fn display_none<T>(_: T, _: &ArgMatches) {}
|
||||||
()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Container<T>(RwLock<Option<T>>);
|
pub struct Container<T>(RwLock<Option<T>>);
|
||||||
impl<T> Container<T> {
|
impl<T> Container<T> {
|
||||||
@@ -256,6 +254,29 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GeneralBoxedGuard(Option<Box<dyn FnOnce() + Send + Sync>>);
|
||||||
|
impl GeneralBoxedGuard {
|
||||||
|
pub fn new(f: impl FnOnce() + 'static + Send + Sync) -> Self {
|
||||||
|
GeneralBoxedGuard(Some(Box::new(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drop(mut self) {
|
||||||
|
self.0.take().unwrap()()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drop_without_action(mut self) {
|
||||||
|
self.0 = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for GeneralBoxedGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(destroy) = self.0.take() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GeneralGuard<F: FnOnce() -> T, T = ()>(Option<F>);
|
pub struct GeneralGuard<F: FnOnce() -> T, T = ()>(Option<F>);
|
||||||
impl<F: FnOnce() -> T, T> GeneralGuard<F, T> {
|
impl<F: FnOnce() -> T, T> GeneralGuard<F, T> {
|
||||||
pub fn new(f: F) -> Self {
|
pub fn new(f: F) -> Self {
|
||||||
|
|||||||
@@ -212,24 +212,3 @@ impl RpcMethod for SignalGroup {
|
|||||||
"signal-group"
|
"signal-group"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn example_echo_line() {
|
|
||||||
let input = r#"{"id":0,"jsonrpc":"2.0","method":"command","params":{"command":"echo","args":["world I am here"]}}"#;
|
|
||||||
let new_input = JsonRpc::<Input>::maybe_parse(input);
|
|
||||||
assert!(new_input.is_some());
|
|
||||||
assert_eq!(input, &serde_json::to_string(&new_input.unwrap()).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn example_input_line() {
|
|
||||||
let output = JsonRpc::new(RpcId::UInt(0), Output::Line("world I am here".to_string()));
|
|
||||||
let output_str = output.maybe_serialize();
|
|
||||||
assert!(output_str.is_some());
|
|
||||||
let output_str = output_str.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
&output_str,
|
|
||||||
r#"{"id":0,"jsonrpc":"2.0","method":"line","params":"world I am here"}"#
|
|
||||||
);
|
|
||||||
assert_eq!(output, serde_json::from_str(&output_str).unwrap());
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ use std::process::Stdio;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use embassy_container_init::{
|
use embassy_container_init::{
|
||||||
LogParams, OutputParams, OutputStrategy, ProcessGroupId, ProcessId, ReadLineStderrParams,
|
LogParams, OutputParams, OutputStrategy, ProcessGroupId, ProcessId, RunCommandParams, SendSignalParams, SignalGroupParams,
|
||||||
ReadLineStdoutParams, RunCommandParams, SendSignalParams, SignalGroupParams,
|
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use helpers::NonDetachingJoinHandle;
|
use helpers::NonDetachingJoinHandle;
|
||||||
@@ -15,7 +14,7 @@ use nix::sys::signal::Signal;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
use tokio::process::{Child, ChildStderr, ChildStdout, Command};
|
use tokio::process::{Child, Command};
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::sync::{watch, Mutex};
|
use tokio::sync::{watch, Mutex};
|
||||||
use yajrc::{Id, RpcError};
|
use yajrc::{Id, RpcError};
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use std::future::Future;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
use color_eyre::eyre::{eyre, Context, Error};
|
use color_eyre::eyre::{eyre, Context, Error};
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
@@ -74,6 +77,18 @@ impl<T> From<JoinHandle<T>> for NonDetachingJoinHandle<T> {
|
|||||||
NonDetachingJoinHandle(t)
|
NonDetachingJoinHandle(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for NonDetachingJoinHandle<T> {
|
||||||
|
type Target = JoinHandle<T>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> DerefMut for NonDetachingJoinHandle<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
#[pin_project::pinned_drop]
|
#[pin_project::pinned_drop]
|
||||||
impl<T> PinnedDrop for NonDetachingJoinHandle<T> {
|
impl<T> PinnedDrop for NonDetachingJoinHandle<T> {
|
||||||
fn drop(self: std::pin::Pin<&mut Self>) {
|
fn drop(self: std::pin::Pin<&mut Self>) {
|
||||||
|
|||||||
@@ -145,17 +145,17 @@ impl ModuleLoader for ModsLoader {
|
|||||||
"file:///deno_global.js" => Ok(ModuleSource::new(
|
"file:///deno_global.js" => Ok(ModuleSource::new(
|
||||||
ModuleType::JavaScript,
|
ModuleType::JavaScript,
|
||||||
FastString::Static("const old_deno = Deno; Deno = null; export default old_deno"),
|
FastString::Static("const old_deno = Deno; Deno = null; export default old_deno"),
|
||||||
&*DENO_GLOBAL_JS,
|
&DENO_GLOBAL_JS,
|
||||||
)),
|
)),
|
||||||
"file:///loadModule.js" => Ok(ModuleSource::new(
|
"file:///loadModule.js" => Ok(ModuleSource::new(
|
||||||
ModuleType::JavaScript,
|
ModuleType::JavaScript,
|
||||||
FastString::Static(include_str!("./artifacts/loadModule.js")),
|
FastString::Static(include_str!("./artifacts/loadModule.js")),
|
||||||
&*LOAD_MODULE_JS,
|
&LOAD_MODULE_JS,
|
||||||
)),
|
)),
|
||||||
"file:///embassy.js" => Ok(ModuleSource::new(
|
"file:///embassy.js" => Ok(ModuleSource::new(
|
||||||
ModuleType::JavaScript,
|
ModuleType::JavaScript,
|
||||||
self.code.0.clone().into(),
|
self.code.0.clone().into(),
|
||||||
&*EMBASSY_JS,
|
&EMBASSY_JS,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
x => Err(anyhow!("Not allowed to import: {}", x)),
|
x => Err(anyhow!("Not allowed to import: {}", x)),
|
||||||
@@ -340,7 +340,7 @@ impl JsExecutionEnvironment {
|
|||||||
.ops(Self::declarations())
|
.ops(Self::declarations())
|
||||||
.state(move |state| {
|
.state(move |state| {
|
||||||
state.put(ext_answer_state.clone());
|
state.put(ext_answer_state.clone());
|
||||||
state.put(js_ctx.clone());
|
state.put(js_ctx);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -397,7 +397,7 @@ mod fns {
|
|||||||
};
|
};
|
||||||
use helpers::{to_tmp_path, AtomicFile, Rsync, RsyncOptions};
|
use helpers::{to_tmp_path, AtomicFile, Rsync, RsyncOptions};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use models::{ErrorKind, VolumeId};
|
use models::VolumeId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
@@ -578,7 +578,7 @@ mod fns {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let path_in = path_in.strip_prefix("/").unwrap_or(&path_in);
|
let path_in = path_in.strip_prefix("/").unwrap_or(&path_in);
|
||||||
let new_file = volume_path.join(&path_in);
|
let new_file = volume_path.join(path_in);
|
||||||
let parent_new_file = new_file
|
let parent_new_file = new_file
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or_else(|| anyhow!("Expecting that file is not root"))?;
|
.ok_or_else(|| anyhow!("Expecting that file is not root"))?;
|
||||||
@@ -706,7 +706,7 @@ mod fns {
|
|||||||
volume_path.to_string_lossy(),
|
volume_path.to_string_lossy(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if let Err(_) = tokio::fs::metadata(&src).await {
|
if tokio::fs::metadata(&src).await.is_err() {
|
||||||
bail!("Source at {} does not exists", src.to_string_lossy());
|
bail!("Source at {} does not exists", src.to_string_lossy());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -870,11 +870,9 @@ mod fns {
|
|||||||
let volume_path = {
|
let volume_path = {
|
||||||
let state = state.borrow();
|
let state = state.borrow();
|
||||||
let ctx: &JsContext = state.borrow();
|
let ctx: &JsContext = state.borrow();
|
||||||
let volume_path = ctx
|
ctx.volumes
|
||||||
.volumes
|
|
||||||
.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id)
|
.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id)
|
||||||
.ok_or_else(|| anyhow!("There is no {} in volumes", volume_id))?;
|
.ok_or_else(|| anyhow!("There is no {} in volumes", volume_id))?
|
||||||
volume_path
|
|
||||||
};
|
};
|
||||||
let path_in = path_in.strip_prefix("/").unwrap_or(&path_in);
|
let path_in = path_in.strip_prefix("/").unwrap_or(&path_in);
|
||||||
let new_file = volume_path.join(path_in);
|
let new_file = volume_path.join(path_in);
|
||||||
@@ -945,8 +943,7 @@ mod fns {
|
|||||||
.stdout,
|
.stdout,
|
||||||
)?
|
)?
|
||||||
.lines()
|
.lines()
|
||||||
.skip(1)
|
.nth(1)
|
||||||
.next()
|
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.parse()?;
|
.parse()?;
|
||||||
let used = String::from_utf8(
|
let used = String::from_utf8(
|
||||||
@@ -976,8 +973,7 @@ mod fns {
|
|||||||
.stdout,
|
.stdout,
|
||||||
)?
|
)?
|
||||||
.lines()
|
.lines()
|
||||||
.skip(1)
|
.nth(1)
|
||||||
.next()
|
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.split_ascii_whitespace()
|
.split_ascii_whitespace()
|
||||||
.next_tuple()
|
.next_tuple()
|
||||||
@@ -994,8 +990,10 @@ mod fns {
|
|||||||
|
|
||||||
#[op]
|
#[op]
|
||||||
async fn log_trace(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
async fn log_trace(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
||||||
let state = state.borrow();
|
let ctx = {
|
||||||
let ctx = state.borrow::<JsContext>().clone();
|
let state = state.borrow();
|
||||||
|
state.borrow::<JsContext>().clone()
|
||||||
|
};
|
||||||
if let Some(rpc_client) = ctx.container_rpc_client {
|
if let Some(rpc_client) = ctx.container_rpc_client {
|
||||||
return rpc_client
|
return rpc_client
|
||||||
.request(
|
.request(
|
||||||
@@ -1018,8 +1016,10 @@ mod fns {
|
|||||||
}
|
}
|
||||||
#[op]
|
#[op]
|
||||||
async fn log_warn(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
async fn log_warn(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
||||||
let state = state.borrow();
|
let ctx = {
|
||||||
let ctx = state.borrow::<JsContext>().clone();
|
let state = state.borrow();
|
||||||
|
state.borrow::<JsContext>().clone()
|
||||||
|
};
|
||||||
if let Some(rpc_client) = ctx.container_rpc_client {
|
if let Some(rpc_client) = ctx.container_rpc_client {
|
||||||
return rpc_client
|
return rpc_client
|
||||||
.request(
|
.request(
|
||||||
@@ -1042,8 +1042,10 @@ mod fns {
|
|||||||
}
|
}
|
||||||
#[op]
|
#[op]
|
||||||
async fn log_error(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
async fn log_error(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
||||||
let state = state.borrow();
|
let ctx = {
|
||||||
let ctx = state.borrow::<JsContext>().clone();
|
let state = state.borrow();
|
||||||
|
state.borrow::<JsContext>().clone()
|
||||||
|
};
|
||||||
if let Some(rpc_client) = ctx.container_rpc_client {
|
if let Some(rpc_client) = ctx.container_rpc_client {
|
||||||
return rpc_client
|
return rpc_client
|
||||||
.request(
|
.request(
|
||||||
@@ -1066,8 +1068,10 @@ mod fns {
|
|||||||
}
|
}
|
||||||
#[op]
|
#[op]
|
||||||
async fn log_debug(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
async fn log_debug(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
||||||
let state = state.borrow();
|
let ctx = {
|
||||||
let ctx = state.borrow::<JsContext>().clone();
|
let state = state.borrow();
|
||||||
|
state.borrow::<JsContext>().clone()
|
||||||
|
};
|
||||||
if let Some(rpc_client) = ctx.container_rpc_client {
|
if let Some(rpc_client) = ctx.container_rpc_client {
|
||||||
return rpc_client
|
return rpc_client
|
||||||
.request(
|
.request(
|
||||||
@@ -1090,14 +1094,22 @@ mod fns {
|
|||||||
}
|
}
|
||||||
#[op]
|
#[op]
|
||||||
async fn log_info(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
async fn log_info(state: Rc<RefCell<OpState>>, input: String) -> Result<(), AnyError> {
|
||||||
let state = state.borrow();
|
let (container_rpc_client, container_process_gid, package_id, run_function) = {
|
||||||
let ctx = state.borrow::<JsContext>().clone();
|
let state = state.borrow();
|
||||||
if let Some(rpc_client) = ctx.container_rpc_client {
|
let ctx: JsContext = state.borrow::<JsContext>().clone();
|
||||||
|
(
|
||||||
|
ctx.container_rpc_client,
|
||||||
|
ctx.container_process_gid,
|
||||||
|
ctx.package_id,
|
||||||
|
ctx.run_function,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if let Some(rpc_client) = container_rpc_client {
|
||||||
return rpc_client
|
return rpc_client
|
||||||
.request(
|
.request(
|
||||||
embassy_container_init::Log,
|
embassy_container_init::Log,
|
||||||
embassy_container_init::LogParams {
|
embassy_container_init::LogParams {
|
||||||
gid: Some(ctx.container_process_gid),
|
gid: Some(container_process_gid),
|
||||||
level: embassy_container_init::LogLevel::Info(input),
|
level: embassy_container_init::LogLevel::Info(input),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1105,8 +1117,8 @@ mod fns {
|
|||||||
.map_err(|e| anyhow!("{}: {:?}", e.message, e.data));
|
.map_err(|e| anyhow!("{}: {:?}", e.message, e.data));
|
||||||
}
|
}
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
package_id = tracing::field::display(&ctx.package_id),
|
package_id = tracing::field::display(&package_id),
|
||||||
run_function = tracing::field::display(&ctx.run_function),
|
run_function = tracing::field::display(&run_function),
|
||||||
"{}",
|
"{}",
|
||||||
input
|
input
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,11 +60,7 @@ pub fn validate_configuration(
|
|||||||
)?;
|
)?;
|
||||||
std::fs::rename(config_path.with_extension("tmp"), config_path)?;
|
std::fs::rename(config_path.with_extension("tmp"), config_path)?;
|
||||||
// return set result
|
// return set result
|
||||||
Ok(SetResult {
|
Ok(SetResult { depends_on })
|
||||||
depends_on,
|
|
||||||
// sending sigterm so service is restarted - in 0.3.x services, this is whatever signal is needed to send to the process to pick up the configuration
|
|
||||||
signal: Some(nix::sys::signal::SIGTERM),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
Err(e) => Err(anyhow!("{}", e)),
|
Err(e) => Err(anyhow!("{}", e)),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user