diff --git a/src/main.rs b/src/main.rs index a187849..a7850e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ mod cli; mod filters; +mod metrics; use crate::{ cli::Opts, filters::{FilterType, IpFilter}, + metrics::Metrics, }; use anyhow::{anyhow, bail, Result}; use clap::Parser; @@ -12,7 +14,6 @@ use log::{debug, error, info, trace, warn}; use rocket::{ data::{FromData, ToByteUnit}, futures::TryFutureExt, - get, http::{HeaderMap, Status}, outcome::Outcome::{self, Failure, Success}, post, routes, @@ -27,7 +28,7 @@ use std::{ fs::File, io::BufReader, net::{IpAddr, Ipv4Addr, SocketAddr}, - sync::atomic::{AtomicUsize, Ordering}, + sync::atomic::Ordering, }; use thiserror::Error; @@ -47,19 +48,6 @@ pub enum WebhookeyError { Serde(serde_json::Error), } -#[derive(Debug, Default)] -struct WebhookeyMetrics { - requests_received: AtomicUsize, - requests_invalid: AtomicUsize, - hooks_successful: AtomicUsize, - hooks_forbidden: AtomicUsize, - hooks_unmatched: AtomicUsize, - commands_executed: AtomicUsize, - commands_execution_failed: AtomicUsize, - commands_successful: AtomicUsize, - commands_failed: AtomicUsize, -} - #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] struct MetricsConfig { @@ -69,7 +57,7 @@ struct MetricsConfig { #[derive(Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] -struct Config { +pub struct Config { metrics: Option, hooks: BTreeMap, } @@ -311,48 +299,6 @@ fn get_config() -> Result { bail!("No configuration file found."); } -fn get_metrics(metrics: &WebhookeyMetrics) -> String { - format!( - r"# HELP webhookey_requests_received Number of requests received -# TYPE webhookey_requests_received gauge -webhookey_requests_received {} -# HELP webhookey_requests_invalid Number of invalid requests received -# TYPE webhookey_requests_invalid gauge -webhookey_requests_invalid {} -# HELP webhookey_hooks_successful Number of successfully executed hooks -# TYPE webhookey_hooks_successful gauge -webhookey_hooks_sucessful {} -# HELP webhookey_hooks_forbidden Number of forbidden requests -# TYPE webhookey_hooks_forbidden gauge -webhookey_hooks_forbidden {} -# HELP webhookey_hooks_unmatched Number of unmatched requests -# TYPE webhookey_hooks_unmatched gauge -webhookey_hooks_unmatched {} -# HELP webhookey_commands_executed Number of commands executed -# TYPE webhookey_commands_executed gauge -webhookey_commands_executed {} -# HELP webhookey_commands_execution_failed Number of commands failed to execute -# TYPE webhookey_commands_execution_failed gauge -webhookey_commands_execution_failed {} -# HELP webhookey_commands_successful Number of executed commands returning return code 0 -# TYPE webhookey_commands_successful gauge -webhookey_commands_successful {} -# HELP webhookey_commands_failed Number of executed commands returning different return code than 0 -# TYPE webhookey_commands_failed gauge -webhookey_commands_failed {} -", - metrics.requests_received.load(Ordering::Relaxed), - metrics.requests_invalid.load(Ordering::Relaxed), - metrics.hooks_successful.load(Ordering::Relaxed), - metrics.hooks_forbidden.load(Ordering::Relaxed), - metrics.hooks_unmatched.load(Ordering::Relaxed), - metrics.commands_executed.load(Ordering::Relaxed), - metrics.commands_execution_failed.load(Ordering::Relaxed), - metrics.commands_successful.load(Ordering::Relaxed), - metrics.commands_failed.load(Ordering::Relaxed), - ) -} - #[rocket::async_trait] impl<'r> FromData<'r> for Hooks { type Error = WebhookeyError; @@ -363,7 +309,7 @@ impl<'r> FromData<'r> for Hooks { ) -> Outcome> { { request - .guard::<&State>() + .guard::<&State>() .await .unwrap() // TODO: Check if unwrap need to be fixed .requests_received @@ -378,7 +324,7 @@ impl<'r> FromData<'r> for Hooks { .unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)); request - .guard::<&State>() + .guard::<&State>() .await .unwrap() // TODO: Check if unwrap need to be fixed .hooks_unmatched @@ -394,7 +340,7 @@ impl<'r> FromData<'r> for Hooks { error!("{}", WebhookeyError::Unauthorized(e)); request - .guard::<&State>() + .guard::<&State>() .await .unwrap() // TODO: Check if unwrap need to be fixed .hooks_forbidden @@ -406,7 +352,7 @@ impl<'r> FromData<'r> for Hooks { error!("{}", e); request - .guard::<&State>() + .guard::<&State>() .await .unwrap() // TODO: Check if unwrap need to be fixed .requests_invalid @@ -419,11 +365,7 @@ impl<'r> FromData<'r> for Hooks { } #[post("/", format = "json", data = "")] -async fn receive_hook<'a>( - address: SocketAddr, - hooks: Hooks, - metrics: &State, -) -> Status { +async fn receive_hook<'a>(address: SocketAddr, hooks: Hooks, metrics: &State) -> Status { info!("Post request received from: {}", address); hooks.inner.iter().for_each(|(name, command)| { @@ -455,33 +397,6 @@ async fn receive_hook<'a>( Status::Ok } -#[get("/metrics")] -async fn metrics( - address: SocketAddr, - metrics: &State, - config: &State, -) -> Option { - // Are metrics configured? - if let Some(metrics_config) = &config.metrics { - // Are metrics enabled? - if metrics_config.enabled { - // Is a filter configured? - if let Some(filter) = &metrics_config.ip_filter { - // Does the request match the filter? - if filter.validate(&address.ip()) { - return Some(get_metrics(metrics)); - } - } else { - return Some(get_metrics(metrics)); - } - } - } - - warn!("Forbidden request for metrics: {:?}", address); - - None -} - #[rocket::main] async fn main() -> Result<()> { env_logger::init(); @@ -502,9 +417,9 @@ async fn main() -> Result<()> { } rocket::build() - .mount("/", routes![receive_hook, metrics]) + .mount("/", routes![receive_hook, metrics::metrics]) .manage(config) - .manage(WebhookeyMetrics::default()) + .manage(Metrics::default()) .launch() .await?; @@ -548,7 +463,7 @@ mod tests { let rocket = rocket::build() .mount("/", routes![receive_hook]) .manage(config) - .manage(WebhookeyMetrics::default()); + .manage(Metrics::default()); let client = Client::tracked(rocket).await.unwrap(); let response = client @@ -764,7 +679,7 @@ mod tests { let rocket = rocket::build() .mount("/", routes![receive_hook]) .manage(config) - .manage(WebhookeyMetrics::default()); + .manage(Metrics::default()); let client = Client::tracked(rocket).await.unwrap(); @@ -822,7 +737,7 @@ mod tests { let rocket = rocket::build() .mount("/", routes![receive_hook]) .manage(config) - .manage(WebhookeyMetrics::default()); + .manage(Metrics::default()); let client = Client::tracked(rocket).await.unwrap(); diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 0000000..71c4fae --- /dev/null +++ b/src/metrics.rs @@ -0,0 +1,91 @@ +use crate::Config; +use log::warn; +use rocket::{get, State}; +use std::{ + net::SocketAddr, + sync::atomic::{AtomicUsize, Ordering}, +}; + +#[derive(Debug, Default)] +pub struct Metrics { + pub requests_received: AtomicUsize, + pub requests_invalid: AtomicUsize, + pub hooks_successful: AtomicUsize, + pub hooks_forbidden: AtomicUsize, + pub hooks_unmatched: AtomicUsize, + pub commands_executed: AtomicUsize, + pub commands_execution_failed: AtomicUsize, + pub commands_successful: AtomicUsize, + pub commands_failed: AtomicUsize, +} + +#[get("/metrics")] +pub async fn metrics( + address: SocketAddr, + metrics: &State, + config: &State, +) -> Option { + // Are metrics configured? + if let Some(metrics_config) = &config.metrics { + // Are metrics enabled? + if metrics_config.enabled { + // Is a filter configured? + if let Some(filter) = &metrics_config.ip_filter { + // Does the request match the filter? + if filter.validate(&address.ip()) { + return Some(metrics.get_metrics()); + } + } else { + return Some(metrics.get_metrics()); + } + } + } + + warn!("Forbidden request for metrics: {:?}", address); + + None +} + +impl Metrics { + fn get_metrics(&self) -> String { + format!( + r"# HELP webhookey_requests_received Number of requests received +# TYPE webhookey_requests_received gauge +webhookey_requests_received {} +# HELP webhookey_requests_invalid Number of invalid requests received +# TYPE webhookey_requests_invalid gauge +webhookey_requests_invalid {} +# HELP webhookey_hooks_successful Number of successfully executed hooks +# TYPE webhookey_hooks_successful gauge +webhookey_hooks_sucessful {} +# HELP webhookey_hooks_forbidden Number of forbidden requests +# TYPE webhookey_hooks_forbidden gauge +webhookey_hooks_forbidden {} +# HELP webhookey_hooks_unmatched Number of unmatched requests +# TYPE webhookey_hooks_unmatched gauge +webhookey_hooks_unmatched {} +# HELP webhookey_commands_executed Number of commands executed +# TYPE webhookey_commands_executed gauge +webhookey_commands_executed {} +# HELP webhookey_commands_execution_failed Number of commands failed to execute +# TYPE webhookey_commands_execution_failed gauge +webhookey_commands_execution_failed {} +# HELP webhookey_commands_successful Number of executed commands returning return code 0 +# TYPE webhookey_commands_successful gauge +webhookey_commands_successful {} +# HELP webhookey_commands_failed Number of executed commands returning different return code than 0 +# TYPE webhookey_commands_failed gauge +webhookey_commands_failed {} +", + self.requests_received.load(Ordering::Relaxed), + self.requests_invalid.load(Ordering::Relaxed), + self.hooks_successful.load(Ordering::Relaxed), + self.hooks_forbidden.load(Ordering::Relaxed), + self.hooks_unmatched.load(Ordering::Relaxed), + self.commands_executed.load(Ordering::Relaxed), + self.commands_execution_failed.load(Ordering::Relaxed), + self.commands_successful.load(Ordering::Relaxed), + self.commands_failed.load(Ordering::Relaxed), + ) + } +}