summaryrefslogtreecommitdiffstats
path: root/src/service.rs
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-09-26 17:31:16 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-09-26 17:31:16 -0500
commitfd992d7e3c03f37fbcafe9d3f26c72a2ead3b2a7 (patch)
treef3e29427d1bbe4a8d6e050abbd9f66afb5fa2152 /src/service.rs
parentcbfca14b38806798847e3f2008038b25194a9b8b (diff)
feat!: impl full api
Diffstat (limited to 'src/service.rs')
-rw-r--r--src/service.rs203
1 files changed, 74 insertions, 129 deletions
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<String, Service>,
+ #[serde(skip, default = "Services::default_client")]
+ client: reqwest::Client,
}
-impl Service {
- pub fn check(&mut self, client: Client) -> Result<bool, Error> {
- self.state = self.kind.get_state(client)?;
- Ok(self.state.is_operational())
+impl Services {
+ pub fn new(services: HashMap<String, Service>) -> 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<HashMap<String, Check>, Error> {
+ let checks = self
+ .inner
+ .values()
+ .map(|service| service.check(self.client.clone()))
+ .collect::<FuturesOrdered<_>>()
+ .try_collect::<Vec<_>>()
+ .await?;
+
+ Ok(self
+ .inner
+ .keys()
+ .cloned()
+ .zip(checks)
+ .collect::<HashMap<_, _>>())
}
- pub fn get_state(&self, client: Client) -> Result<State, Error> {
- 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<Result<Check, Error>> {
+ 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<P>(&self, mut predicate: P) -> Result<HashMap<String, Check>, 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::<FuturesOrdered<_>>()
+ .try_collect::<Vec<_>>()
+ .await?;
+
+ Ok(self
+ .inner
+ .keys()
+ .cloned()
+ .zip(checks)
+ .collect::<HashMap<_, _>>())
}
}
-#[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<String> {
- 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<Check, Error> {
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),
}
}
}