Skip to main content

max / makenotwork

2.8 KB · 78 lines History Blame Raw
1 //! Local systemd daemon-health check storage.
2 //!
3 //! Stores the latest result per target the same way `scan_pipeline_checks` does,
4 //! so `/status.json` renders it from the ledger rather than shelling out to
5 //! `systemctl` on every viewer poll. The unit snapshots, host-wide failed units,
6 //! and issue lines are each held as a JSON blob.
7
8 use super::{Result, SqlitePool, SystemdCheckResult, SystemdUnitSnapshot};
9 use tracing::instrument;
10
11 #[instrument(skip_all)]
12 pub async fn insert_systemd_check(pool: &SqlitePool, result: &SystemdCheckResult) -> Result<i64> {
13 let units = serde_json::to_string(&result.units).unwrap_or_default();
14 let failed_units = serde_json::to_string(&result.failed_units).unwrap_or_default();
15 let issues = serde_json::to_string(&result.issues).unwrap_or_default();
16 let row = sqlx::query(
17 "INSERT INTO systemd_checks (target, status, units, failed_units, issues, checked_at, error)
18 VALUES (?, ?, ?, ?, ?, ?, ?)",
19 )
20 .bind(&result.target)
21 .bind(&result.status)
22 .bind(&units)
23 .bind(&failed_units)
24 .bind(&issues)
25 .bind(&result.checked_at)
26 .bind(&result.error)
27 .execute(pool)
28 .await?;
29 Ok(row.last_insert_rowid())
30 }
31
32 /// The latest systemd check for a target, if one has been recorded.
33 #[instrument(skip_all)]
34 pub async fn get_latest_systemd_check(
35 pool: &SqlitePool,
36 target: &str,
37 ) -> Result<Option<SystemdCheckRow>> {
38 Ok(sqlx::query_as::<_, SystemdCheckRow>(
39 "SELECT id, target, status, units, failed_units, issues, checked_at, error
40 FROM systemd_checks WHERE target = ? ORDER BY id DESC LIMIT 1",
41 )
42 .bind(target)
43 .fetch_optional(pool)
44 .await?)
45 }
46
47 #[derive(Debug, Clone, sqlx::FromRow, serde::Serialize)]
48 pub struct SystemdCheckRow {
49 pub id: i64,
50 pub target: String,
51 pub status: String,
52 /// JSON array of unit snapshots. Use [`Self::unit_list`].
53 pub units: String,
54 /// JSON array of host-wide failed unit names. Use [`Self::failed_unit_list`].
55 pub failed_units: String,
56 /// JSON array of the fired issue lines. Use [`Self::issue_list`].
57 pub issues: String,
58 pub checked_at: String,
59 pub error: Option<String>,
60 }
61
62 impl SystemdCheckRow {
63 /// Decode the stored unit snapshots, tolerating a malformed blob as empty.
64 pub fn unit_list(&self) -> Vec<SystemdUnitSnapshot> {
65 serde_json::from_str(&self.units).unwrap_or_default()
66 }
67
68 /// Decode the stored failed-unit names, tolerating a malformed blob as empty.
69 pub fn failed_unit_list(&self) -> Vec<String> {
70 serde_json::from_str(&self.failed_units).unwrap_or_default()
71 }
72
73 /// Decode the stored issue lines, tolerating a malformed blob as empty.
74 pub fn issue_list(&self) -> Vec<String> {
75 serde_json::from_str(&self.issues).unwrap_or_default()
76 }
77 }
78