diff options
author | metamuffin <metamuffin@disroot.org> | 2024-05-03 19:34:45 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-05-03 19:34:45 +0200 |
commit | d60bedc20dc0cc54cb697f3a45e5b222de6f1479 (patch) | |
tree | be310b8ca7075cef3676d4b05ac99f9267692b95 /src/check.rs | |
download | statuspage-d60bedc20dc0cc54cb697f3a45e5b222de6f1479.tar statuspage-d60bedc20dc0cc54cb697f3a45e5b222de6f1479.tar.bz2 statuspage-d60bedc20dc0cc54cb697f3a45e5b222de6f1479.tar.zst |
works
Diffstat (limited to 'src/check.rs')
-rw-r--r-- | src/check.rs | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/src/check.rs b/src/check.rs new file mode 100644 index 0000000..bfde1b9 --- /dev/null +++ b/src/check.rs @@ -0,0 +1,91 @@ +use crate::{Check, Config, Success, STATUS}; +use anyhow::{anyhow, bail, Context, Result}; +use futures::{stream::FuturesUnordered, StreamExt}; +use log::info; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use tokio::{ + process::Command, + time::{sleep, timeout}, +}; + +pub async fn check_loop(config: Arc<Config>, i: usize) { + loop { + check_service(&config, i).await; + sleep(Duration::from_secs(config.interval)).await; + } +} + +async fn check_service(config: &Arc<Config>, i: usize) { + let service = &config.services[i]; + let mut futs = FuturesUnordered::from_iter(service.checks.iter().enumerate().map( + |(j, check)| async move { + let r = match timeout(Duration::from_secs(30), check.check()).await { + Ok(Ok(succ)) => Ok(succ), + Ok(Err(e)) => Err(e), + Err(_) => Err(anyhow!("timed out")), + }; + info!("check {i}:{j} => {r:?}"); + { + let mut g = STATUS.write().await; + g.insert((i, j), r); + } + }, + )); + while let Some(_) = futs.next().await {} +} + +impl Check { + pub async fn check(&self) -> Result<Success> { + match self { + Check::Systemd(sname) => { + let output = Command::new("systemctl") + .arg("show") + .arg("--no-pager") + .arg(sname) + .output() + .await + .context("systemctl")?; + let output = String::from_utf8(output.stdout).context("systemctl output")?; + + for line in output.split("\n") { + if let Some((key, value)) = line.split_once("=") { + match key { + "ActiveState" if value != "active" => { + bail!("{value}") + } + _ => (), + } + } + } + Ok(Success::default()) + } + Check::Shell { command, .. } => { + let args = shlex::split(&command).ok_or(anyhow!("command syntax invalid"))?; + let status = Command::new(args.get(0).ok_or(anyhow!("argv0 missing"))?) + .args(&args[1..]) + .status() + .await; + + match status { + Ok(status) if status.success() => Ok(Success::default()), + Ok(status) => bail!("failed with code {}", status.code().unwrap_or(1)), + Err(e) => bail!("command failed to execute: {e}"), + } + } + Check::Http { url, .. } => { + let k = Instant::now(); + let r = reqwest::get(url).await?; + if !r.status().is_success() { + bail!("http status: {}", r.status().as_str()) + } + Ok(Success { + latency: Some(k.elapsed()), + ..Default::default() + }) + } + } + } +} |