forked from github-starred/komodo
* consolidate deserializers * key value list doc * use string list deserializers for all entity Vec<String> * add additional env files support * plumbing for Action resource * js client readme indentation * regen lock * add action UI * action backend * start on action frontend * update lock * get up to speed * get action started * clean up default action file * seems to work * toml export include action * action works * action works part 2 * bump rust version to 1.82.0 * copy deno bin from bin image * action use local dir * update not having changes doesn't return error * format with prettier * support yaml formatting with prettier * variable no change is Ok
217 lines
5.4 KiB
Rust
217 lines
5.4 KiB
Rust
use ::slack::types::Block;
|
|
use anyhow::{anyhow, Context};
|
|
use derive_variants::ExtractVariant;
|
|
use futures::future::join_all;
|
|
use komodo_client::entities::{
|
|
alert::{Alert, AlertData, SeverityLevel},
|
|
alerter::*,
|
|
deployment::DeploymentState,
|
|
stack::StackState,
|
|
ResourceTargetVariant,
|
|
};
|
|
use mungos::{find::find_collect, mongodb::bson::doc};
|
|
use tracing::Instrument;
|
|
|
|
use crate::{config::core_config, state::db_client};
|
|
|
|
mod discord;
|
|
mod slack;
|
|
|
|
pub async fn send_alerts(alerts: &[Alert]) {
|
|
if alerts.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let span =
|
|
info_span!("send_alerts", alerts = format!("{alerts:?}"));
|
|
async {
|
|
let Ok(alerters) = find_collect(
|
|
&db_client().alerters,
|
|
doc! { "config.enabled": true },
|
|
None,
|
|
)
|
|
.await
|
|
.inspect_err(|e| {
|
|
error!(
|
|
"ERROR sending alerts | failed to get alerters from db | {e:#}"
|
|
)
|
|
}) else {
|
|
return;
|
|
};
|
|
|
|
let handles =
|
|
alerts.iter().map(|alert| send_alert(&alerters, alert));
|
|
|
|
join_all(handles).await;
|
|
}
|
|
.instrument(span)
|
|
.await
|
|
}
|
|
|
|
#[instrument(level = "debug")]
|
|
async fn send_alert(alerters: &[Alerter], alert: &Alert) {
|
|
if alerters.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let alert_type = alert.data.extract_variant();
|
|
|
|
let handles = alerters.iter().map(|alerter| async {
|
|
// Don't send if not enabled
|
|
if !alerter.config.enabled {
|
|
return Ok(());
|
|
}
|
|
|
|
// Don't send if alert type not configured on the alerter
|
|
if !alerter.config.alert_types.is_empty()
|
|
&& !alerter.config.alert_types.contains(&alert_type)
|
|
{
|
|
return Ok(());
|
|
}
|
|
|
|
// Don't send if resource is in the blacklist
|
|
if alerter.config.except_resources.contains(&alert.target) {
|
|
return Ok(());
|
|
}
|
|
|
|
// Don't send if whitelist configured and target is not included
|
|
if !alerter.config.resources.is_empty()
|
|
&& !alerter.config.resources.contains(&alert.target)
|
|
{
|
|
return Ok(());
|
|
}
|
|
|
|
match &alerter.config.endpoint {
|
|
AlerterEndpoint::Custom(CustomAlerterEndpoint { url }) => {
|
|
send_custom_alert(url, alert).await.with_context(|| {
|
|
format!(
|
|
"failed to send alert to custom alerter {}",
|
|
alerter.name
|
|
)
|
|
})
|
|
}
|
|
AlerterEndpoint::Slack(SlackAlerterEndpoint { url }) => {
|
|
slack::send_alert(url, alert).await.with_context(|| {
|
|
format!(
|
|
"failed to send alert to slack alerter {}",
|
|
alerter.name
|
|
)
|
|
})
|
|
}
|
|
AlerterEndpoint::Discord(DiscordAlerterEndpoint { url }) => {
|
|
discord::send_alert(url, alert).await.with_context(|| {
|
|
format!(
|
|
"failed to send alert to Discord alerter {}",
|
|
alerter.name
|
|
)
|
|
})
|
|
}
|
|
}
|
|
});
|
|
|
|
join_all(handles)
|
|
.await
|
|
.into_iter()
|
|
.filter_map(|res| res.err())
|
|
.for_each(|e| error!("{e:#}"));
|
|
}
|
|
|
|
#[instrument(level = "debug")]
|
|
async fn send_custom_alert(
|
|
url: &str,
|
|
alert: &Alert,
|
|
) -> anyhow::Result<()> {
|
|
let res = reqwest::Client::new()
|
|
.post(url)
|
|
.json(alert)
|
|
.send()
|
|
.await
|
|
.context("failed at post request to alerter")?;
|
|
let status = res.status();
|
|
if !status.is_success() {
|
|
let text = res
|
|
.text()
|
|
.await
|
|
.context("failed to get response text on alerter response")?;
|
|
return Err(anyhow!(
|
|
"post to alerter failed | {status} | {text}"
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn fmt_region(region: &Option<String>) -> String {
|
|
match region {
|
|
Some(region) => format!(" ({region})"),
|
|
None => String::new(),
|
|
}
|
|
}
|
|
|
|
fn fmt_docker_container_state(state: &DeploymentState) -> String {
|
|
match state {
|
|
DeploymentState::Running => String::from("Running ▶️"),
|
|
DeploymentState::Exited => String::from("Exited 🛑"),
|
|
DeploymentState::Restarting => String::from("Restarting 🔄"),
|
|
DeploymentState::NotDeployed => String::from("Not Deployed"),
|
|
_ => state.to_string(),
|
|
}
|
|
}
|
|
|
|
fn fmt_stack_state(state: &StackState) -> String {
|
|
match state {
|
|
StackState::Running => String::from("Running ▶️"),
|
|
StackState::Stopped => String::from("Stopped 🛑"),
|
|
StackState::Restarting => String::from("Restarting 🔄"),
|
|
StackState::Down => String::from("Down ⬇️"),
|
|
_ => state.to_string(),
|
|
}
|
|
}
|
|
|
|
fn fmt_level(level: SeverityLevel) -> &'static str {
|
|
match level {
|
|
SeverityLevel::Critical => "CRITICAL 🚨",
|
|
SeverityLevel::Warning => "WARNING ‼️",
|
|
SeverityLevel::Ok => "OK ✅",
|
|
}
|
|
}
|
|
|
|
fn resource_link(
|
|
resource_type: ResourceTargetVariant,
|
|
id: &str,
|
|
) -> String {
|
|
let path = match resource_type {
|
|
ResourceTargetVariant::System => unreachable!(),
|
|
ResourceTargetVariant::Build => format!("/builds/{id}"),
|
|
ResourceTargetVariant::Builder => {
|
|
format!("/builders/{id}")
|
|
}
|
|
ResourceTargetVariant::Deployment => {
|
|
format!("/deployments/{id}")
|
|
}
|
|
ResourceTargetVariant::Stack => {
|
|
format!("/stacks/{id}")
|
|
}
|
|
ResourceTargetVariant::Server => {
|
|
format!("/servers/{id}")
|
|
}
|
|
ResourceTargetVariant::Repo => format!("/repos/{id}"),
|
|
ResourceTargetVariant::Alerter => {
|
|
format!("/alerters/{id}")
|
|
}
|
|
ResourceTargetVariant::Procedure => {
|
|
format!("/procedures/{id}")
|
|
}
|
|
ResourceTargetVariant::Action => {
|
|
format!("/actions/{id}")
|
|
}
|
|
ResourceTargetVariant::ServerTemplate => {
|
|
format!("/server-templates/{id}")
|
|
}
|
|
ResourceTargetVariant::ResourceSync => {
|
|
format!("/resource-syncs/{id}")
|
|
}
|
|
};
|
|
|
|
format!("{}{path}", core_config().host)
|
|
}
|