aion_server/update_check/
install.rs1use aion::Engine;
22
23use super::document::{EmbeddedUpdateCheck, embedded_update_check};
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub enum UpdateCheckInstall {
28 Installed {
31 workflow_type: String,
33 content_hash: String,
35 },
36 AlreadyCurrent {
38 workflow_type: String,
40 content_hash: String,
42 },
43 Deferred {
46 workflow_type: String,
48 embedded_hash: String,
50 routed_hash: Option<String>,
53 },
54 Failed {
57 reason: String,
59 },
60}
61
62impl UpdateCheckInstall {
63 #[must_use]
65 pub const fn outcome(&self) -> &'static str {
66 match self {
67 Self::Installed { .. } => "installed",
68 Self::AlreadyCurrent { .. } => "already_current",
69 Self::Deferred { .. } => "deferred",
70 Self::Failed { .. } => "failed",
71 }
72 }
73}
74
75pub async fn install_embedded_update_check(engine: &Engine) -> UpdateCheckInstall {
82 let embedded = match embedded_update_check() {
83 Ok(embedded) => embedded,
84 Err(error) => {
85 let reason = error.to_string();
86 tracing::error!(
87 operation = "update_check.install",
88 outcome = "failed",
89 %reason,
90 "the embedded update-check document could not be prepared; this server cannot \
91 run the built-in update check"
92 );
93 return UpdateCheckInstall::Failed { reason };
94 }
95 };
96 let outcome = install_verified(engine, embedded).await;
97 log_outcome(&outcome);
98 outcome
99}
100
101pub async fn install_embedded_update_check_for_server(
105 state: &crate::ServerState,
106) -> UpdateCheckInstall {
107 match state.engine() {
108 Ok(engine) => install_embedded_update_check(engine.as_ref()).await,
109 Err(error) => {
110 let reason = format!("the engine is not available: {error}");
111 let outcome = UpdateCheckInstall::Failed { reason };
112 log_outcome(&outcome);
113 outcome
114 }
115 }
116}
117
118async fn install_verified(engine: &Engine, embedded: &EmbeddedUpdateCheck) -> UpdateCheckInstall {
120 let workflow_type = embedded.workflow_type().to_owned();
121 let embedded_hash = embedded.content_hash().to_string();
122
123 let versions = match engine.list_workflow_versions() {
124 Ok(versions) => versions,
125 Err(error) => {
126 return UpdateCheckInstall::Failed {
127 reason: format!("the engine catalog could not be read: {error}"),
128 };
129 }
130 };
131 let resident: Vec<_> = versions
132 .into_iter()
133 .filter(|version| version.workflow_type == workflow_type)
134 .collect();
135
136 if resident.is_empty() {
137 return match engine.load_package(embedded.package().clone()).await {
138 Ok(_) => UpdateCheckInstall::Installed {
139 workflow_type,
140 content_hash: embedded_hash,
141 },
142 Err(error) => UpdateCheckInstall::Failed {
143 reason: format!(
144 "the embedded update-check package `{embedded_hash}` did not load: {error}"
145 ),
146 },
147 };
148 }
149
150 let routed_hash = resident
151 .iter()
152 .find(|version| version.route_active)
153 .map(|version| version.content_hash.to_string());
154 if routed_hash.as_deref() == Some(embedded_hash.as_str()) {
155 return UpdateCheckInstall::AlreadyCurrent {
156 workflow_type,
157 content_hash: embedded_hash,
158 };
159 }
160 UpdateCheckInstall::Deferred {
161 workflow_type,
162 embedded_hash,
163 routed_hash,
164 }
165}
166
167fn log_outcome(outcome: &UpdateCheckInstall) {
170 match outcome {
171 UpdateCheckInstall::Installed {
172 workflow_type,
173 content_hash,
174 } => tracing::info!(
175 operation = "update_check.install",
176 outcome = outcome.outcome(),
177 %workflow_type,
178 %content_hash,
179 "the built-in update check was installed and routed on a catalog that held no \
180 version of it; nothing was started — every check is an explicit operator act"
181 ),
182 UpdateCheckInstall::AlreadyCurrent {
183 workflow_type,
184 content_hash,
185 } => tracing::info!(
186 operation = "update_check.install",
187 outcome = outcome.outcome(),
188 %workflow_type,
189 %content_hash,
190 "the built-in update check is already the routed version"
191 ),
192 UpdateCheckInstall::Deferred {
193 workflow_type,
194 embedded_hash,
195 routed_hash,
196 } => tracing::warn!(
197 operation = "update_check.install",
198 outcome = outcome.outcome(),
199 %workflow_type,
200 %embedded_hash,
201 routed_hash = routed_hash.as_deref().unwrap_or("none"),
202 "a version of the update-check workflow type is already resident on this catalog, \
203 and routing was NOT changed — a restart must never move a route an operator \
204 chose. To return to the embedded document deliberately, retire the resident \
205 versions (`aion unload update_check <hash>`) and restart"
206 ),
207 UpdateCheckInstall::Failed { reason } => tracing::error!(
208 operation = "update_check.install",
209 outcome = outcome.outcome(),
210 %reason,
211 "the built-in update check was not installed"
212 ),
213 }
214}
215
216#[cfg(test)]
217#[path = "install_tests.rs"]
218mod install_tests;