use aion::Engine;
use super::document::{EmbeddedUpdateCheck, embedded_update_check};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UpdateCheckInstall {
Installed {
workflow_type: String,
content_hash: String,
},
AlreadyCurrent {
workflow_type: String,
content_hash: String,
},
Deferred {
workflow_type: String,
embedded_hash: String,
routed_hash: Option<String>,
},
Failed {
reason: String,
},
}
impl UpdateCheckInstall {
#[must_use]
pub const fn outcome(&self) -> &'static str {
match self {
Self::Installed { .. } => "installed",
Self::AlreadyCurrent { .. } => "already_current",
Self::Deferred { .. } => "deferred",
Self::Failed { .. } => "failed",
}
}
}
pub async fn install_embedded_update_check(engine: &Engine) -> UpdateCheckInstall {
let embedded = match embedded_update_check() {
Ok(embedded) => embedded,
Err(error) => {
let reason = error.to_string();
tracing::error!(
operation = "update_check.install",
outcome = "failed",
%reason,
"the embedded update-check document could not be prepared; this server cannot \
run the built-in update check"
);
return UpdateCheckInstall::Failed { reason };
}
};
let outcome = install_verified(engine, embedded).await;
log_outcome(&outcome);
outcome
}
pub async fn install_embedded_update_check_for_server(
state: &crate::ServerState,
) -> UpdateCheckInstall {
match state.engine() {
Ok(engine) => install_embedded_update_check(engine.as_ref()).await,
Err(error) => {
let reason = format!("the engine is not available: {error}");
let outcome = UpdateCheckInstall::Failed { reason };
log_outcome(&outcome);
outcome
}
}
}
async fn install_verified(engine: &Engine, embedded: &EmbeddedUpdateCheck) -> UpdateCheckInstall {
let workflow_type = embedded.workflow_type().to_owned();
let embedded_hash = embedded.content_hash().to_string();
let versions = match engine.list_workflow_versions() {
Ok(versions) => versions,
Err(error) => {
return UpdateCheckInstall::Failed {
reason: format!("the engine catalog could not be read: {error}"),
};
}
};
let resident: Vec<_> = versions
.into_iter()
.filter(|version| version.workflow_type == workflow_type)
.collect();
if resident.is_empty() {
return match engine.load_package(embedded.package().clone()).await {
Ok(_) => UpdateCheckInstall::Installed {
workflow_type,
content_hash: embedded_hash,
},
Err(error) => UpdateCheckInstall::Failed {
reason: format!(
"the embedded update-check package `{embedded_hash}` did not load: {error}"
),
},
};
}
let routed_hash = resident
.iter()
.find(|version| version.route_active)
.map(|version| version.content_hash.to_string());
if routed_hash.as_deref() == Some(embedded_hash.as_str()) {
return UpdateCheckInstall::AlreadyCurrent {
workflow_type,
content_hash: embedded_hash,
};
}
UpdateCheckInstall::Deferred {
workflow_type,
embedded_hash,
routed_hash,
}
}
fn log_outcome(outcome: &UpdateCheckInstall) {
match outcome {
UpdateCheckInstall::Installed {
workflow_type,
content_hash,
} => tracing::info!(
operation = "update_check.install",
outcome = outcome.outcome(),
%workflow_type,
%content_hash,
"the built-in update check was installed and routed on a catalog that held no \
version of it; nothing was started — every check is an explicit operator act"
),
UpdateCheckInstall::AlreadyCurrent {
workflow_type,
content_hash,
} => tracing::info!(
operation = "update_check.install",
outcome = outcome.outcome(),
%workflow_type,
%content_hash,
"the built-in update check is already the routed version"
),
UpdateCheckInstall::Deferred {
workflow_type,
embedded_hash,
routed_hash,
} => tracing::warn!(
operation = "update_check.install",
outcome = outcome.outcome(),
%workflow_type,
%embedded_hash,
routed_hash = routed_hash.as_deref().unwrap_or("none"),
"a version of the update-check workflow type is already resident on this catalog, \
and routing was NOT changed — a restart must never move a route an operator \
chose. To return to the embedded document deliberately, retire the resident \
versions (`aion unload update_check <hash>`) and restart"
),
UpdateCheckInstall::Failed { reason } => tracing::error!(
operation = "update_check.install",
outcome = outcome.outcome(),
%reason,
"the built-in update check was not installed"
),
}
}
#[cfg(test)]
#[path = "install_tests.rs"]
mod install_tests;