From fd992d7e3c03f37fbcafe9d3f26c72a2ead3b2a7 Mon Sep 17 00:00:00 2001 From: Toby Vincent Date: Thu, 26 Sep 2024 17:31:16 -0500 Subject: feat!: impl full api --- src/service.rs | 203 +++++++++++++++++++++------------------------------------ 1 file changed, 74 insertions(+), 129 deletions(-) (limited to 'src/service.rs') diff --git a/src/service.rs b/src/service.rs index c5eb0d7..677db17 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,160 +1,105 @@ -use std::{fmt::Display, process::Command}; +use std::{collections::HashMap, fmt::Display}; -use reqwest::blocking::Client; +use futures::{stream::FuturesOrdered, TryStreamExt}; +use http::Http; use serde::Deserialize; +use systemd::Systemd; +use tcp::Tcp; -use crate::Error; +use crate::{Check, Error}; + +pub mod http; +pub mod systemd; +pub mod tcp; #[derive(Debug, Clone, Deserialize)] -pub struct Service { - pub name: String, +pub struct Services { #[serde(flatten)] - pub kind: Kind, - #[serde(skip)] - pub state: State, + inner: HashMap, + #[serde(skip, default = "Services::default_client")] + client: reqwest::Client, } -impl Service { - pub fn check(&mut self, client: Client) -> Result { - self.state = self.kind.get_state(client)?; - Ok(self.state.is_operational()) +impl Services { + pub fn new(services: HashMap) -> Self { + let client = reqwest::Client::new(); + Self { + inner: services, + client, + } } -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum Kind { - Tcp { - address: String, - }, - Http { - url: String, - #[serde(default = "Kind::default_method")] - method: String, - #[serde(default = "Kind::default_code")] - status_code: u16, - }, - Systemd { - service: String, - }, -} -impl Kind { - fn default_method() -> String { - "GET".to_string() + fn default_client() -> reqwest::Client { + reqwest::Client::new() } - fn default_code() -> u16 { - 200 + pub async fn check(&self) -> Result, Error> { + let checks = self + .inner + .values() + .map(|service| service.check(self.client.clone())) + .collect::>() + .try_collect::>() + .await?; + + Ok(self + .inner + .keys() + .cloned() + .zip(checks) + .collect::>()) } - pub fn get_state(&self, client: Client) -> Result { - let state = match self { - Kind::Tcp { address } => { - if std::net::TcpStream::connect(address).is_ok() { - State::Operational - } else { - State::Down("Unreachable".to_string()) - } - } - Kind::Http { - method, - url, - status_code, - } => { - match client - .request(method.parse().map_err(|_| Error::Method)?, url) - .send()? - .status() - { - s if s.as_u16() == *status_code => State::Operational, - s => State::Down(s.to_string()), - } - } - Kind::Systemd { service } => { - let output = Command::new("systemctl") - .arg("is-active") - .arg(service) - .output()?; - - if output.status.success() { - State::Operational - } else { - State::Down(String::from_utf8_lossy(&output.stdout).to_string()) - } - } - }; - - Ok(state) + pub async fn check_one(&self, name: &str) -> Option> { + Some(self.inner.get(name)?.check(self.client.clone()).await) } -} -impl Display for Kind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Kind::Tcp { address } => write!(f, "tcp://{address}"), - Kind::Http { method, url, .. } => write!(f, "{method} {url}"), - Kind::Systemd { service } => write!(f, "{service}"), - } + pub async fn check_filtered

(&self, mut predicate: P) -> Result, Error> + where + P: FnMut(&String) -> bool, + { + let checks = self + .inner + .iter() + .filter_map(|(s, service)| predicate(s).then_some(service)) + .map(|service| service.check(self.client.clone())) + .collect::>() + .try_collect::>() + .await?; + + Ok(self + .inner + .keys() + .cloned() + .zip(checks) + .collect::>()) } } -#[derive(Debug, Clone, Default)] -pub struct Status { - pub info: String, - pub state: State, -} - -#[derive(Debug, Clone, Default)] -pub enum State { - #[default] - Unknown, - Operational, - Down(String), +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum Service { + Http(Http), + Tcp(Tcp), + Systemd(Systemd), } -impl State { - /// Returns `true` if this is a `Unknown` variant. - pub fn is_unknown(&self) -> bool { - matches!(self, Self::Unknown) - } - - /// Returns `true` if this is a `Operational` variant. - pub fn is_operational(&self) -> bool { - matches!(self, Self::Operational) - } - - /// Returns `true` if this is a `Down` variant. - pub fn is_down(&self) -> bool { - matches!(self, Self::Down(_)) - } - - /// Converts the `State` into an `Option` containing `String` description if the `State` was - /// `Down` and `None` otherwise. - pub fn down_value(self) -> Option { - match self { - State::Unknown => None, - State::Operational => None, - State::Down(s) => Some(s), - } - } - - pub fn as_level(&self) -> String { +impl Service { + pub async fn check(&self, client: reqwest::Client) -> Result { match self { - State::Unknown => "warning", - State::Operational => "ok", - State::Down(_) => "error", + Service::Http(http) => http.check(client).await, + Service::Tcp(tcp) => tcp.check().await, + Service::Systemd(systemd) => systemd.check().await, } - .to_string() } } -impl Display for State { +impl Display for Service { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - State::Unknown => write!(f, "Unknown"), - State::Operational => write!(f, "Operational"), - State::Down(s) => write!(f, "{s}"), + Service::Http(http) => http.fmt(f), + Service::Tcp(tcp) => tcp.fmt(f), + Service::Systemd(systemd) => systemd.fmt(f), } } } -- cgit v1.2.3-70-g09d2