diff options
author | Toby Vincent <tobyv@tobyvin.dev> | 2024-09-21 18:09:50 -0500 |
---|---|---|
committer | Toby Vincent <tobyv@tobyvin.dev> | 2024-09-21 18:09:50 -0500 |
commit | cbfca14b38806798847e3f2008038b25194a9b8b (patch) | |
tree | 1afcb3e3f34aba38e6d84e64e863024b4f1ead2a /src/service.rs |
chore: initial commit
Diffstat (limited to 'src/service.rs')
-rw-r--r-- | src/service.rs | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 0000000..c5eb0d7 --- /dev/null +++ b/src/service.rs @@ -0,0 +1,160 @@ +use std::{fmt::Display, process::Command}; + +use reqwest::blocking::Client; +use serde::Deserialize; + +use crate::Error; + +#[derive(Debug, Clone, Deserialize)] +pub struct Service { + pub name: String, + #[serde(flatten)] + pub kind: Kind, + #[serde(skip)] + pub state: State, +} + +impl Service { + pub fn check(&mut self, client: Client) -> Result<bool, Error> { + self.state = self.kind.get_state(client)?; + Ok(self.state.is_operational()) + } +} + +#[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_code() -> u16 { + 200 + } + + 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) + } +} + +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}"), + } + } +} + +#[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), +} + +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 { + match self { + State::Unknown => "warning", + State::Operational => "ok", + State::Down(_) => "error", + } + .to_string() + } +} + +impl Display for State { + 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}"), + } + } +} |