summaryrefslogtreecommitdiffstats
path: root/src/service.rs
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-09-21 18:09:50 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-09-21 18:09:50 -0500
commitcbfca14b38806798847e3f2008038b25194a9b8b (patch)
tree1afcb3e3f34aba38e6d84e64e863024b4f1ead2a /src/service.rs
chore: initial commit
Diffstat (limited to 'src/service.rs')
-rw-r--r--src/service.rs160
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}"),
+ }
+ }
+}