switch to alertmanager interface with askama template

This commit is contained in:
Astro 2022-12-18 03:18:34 +01:00
parent 2cf4e1dbc0
commit 6ecff10377
4 changed files with 153 additions and 51 deletions

99
Cargo.lock generated
View File

@ -6,6 +6,7 @@ version = 3
name = "alert2muc"
version = "0.1.0"
dependencies = [
"askama",
"axum",
"futures",
"serde",
@ -18,6 +19,54 @@ dependencies = [
"xmpp-parsers",
]
[[package]]
name = "askama"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb98f10f371286b177db5eeb9a6e5396609555686a35e1d4f7b9a9c6d8af0139"
dependencies = [
"askama_derive",
"askama_escape",
"askama_shared",
]
[[package]]
name = "askama_derive"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87bf87e6e8b47264efa9bde63d6225c6276a52e05e91bf37eaa8afd0032d6b71"
dependencies = [
"askama_shared",
"proc-macro2",
"syn",
]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "askama_shared"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf722b94118a07fcbc6640190f247334027685d4e218b794dbfe17c32bf38ed0"
dependencies = [
"askama_escape",
"humansize",
"mime",
"mime_guess",
"nom",
"num-traits",
"percent-encoding",
"proc-macro2",
"quote",
"serde",
"syn",
"toml",
]
[[package]]
name = "async-trait"
version = "0.1.59"
@ -503,6 +552,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "humansize"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026"
[[package]]
name = "hyper"
version = "0.14.23"
@ -694,6 +749,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minidom"
version = "0.15.0"
@ -703,6 +768,12 @@ dependencies = [
"rxml",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mio"
version = "0.8.5"
@ -733,6 +804,16 @@ dependencies = [
"tempfile",
]
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -1512,6 +1593,15 @@ dependencies = [
"xmpp-parsers",
]
[[package]]
name = "toml"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [
"serde",
]
[[package]]
name = "tower"
version = "0.4.13"
@ -1679,6 +1769,15 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.8"

View File

@ -17,3 +17,4 @@ tokio-xmpp = "3"
xmpp-parsers = "0.19"
axum = "0.6"
systemd = "0.10"
askama = "0.11"

View File

@ -1,8 +1,5 @@
use std::{
net::SocketAddr,
collections::HashMap,
sync::{Arc, Mutex},
};
use std::net::SocketAddr;
use askama::Template;
use axum::{
extract::State,
routing::post,
@ -14,61 +11,59 @@ use serde::Deserialize;
mod jabber;
#[derive(Deserialize, Clone, Debug)]
#[allow(non_snake_case)]
#[derive(Debug, Clone, Deserialize)]
struct Payload {
alerts: Vec<Alert>,
}
#[derive(Debug, Clone, Deserialize, Template)]
#[template(path="alert.txt", escape="none")]
struct Alert {
generatorURL: String,
startsAt: String,
status: String,
labels: AlertLabels,
annotations: AlertAnnotations,
labels: HashMap<String, String>,
#[serde(rename = "generatorURL")]
generator_url: String,
}
#[derive(Deserialize, Clone, Debug)]
#[derive(Debug, Clone, Deserialize)]
struct AlertLabels {
alertname: Option<String>,
host: Option<String>,
instance: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
struct AlertAnnotations {
summary: String,
}
#[derive(Clone)]
struct AppState {
jabber: jabber::Handle,
alerts: Arc<Mutex<HashMap<String, Alert>>>,
message: Option<String>,
description: Option<String>,
}
async fn alerts(
State(state): State<AppState>,
Json(payload): Json<Vec<Alert>>,
State(jabber): State<jabber::Handle>,
Json(payload): Json<Payload>,
) -> Response {
let mut message = String::new();
let mut alerts = state.alerts.lock().unwrap();
for alert in payload {
let is_old = if let Some(old_alert) = alerts.get(&alert.generatorURL) {
old_alert.startsAt == alert.startsAt
} else {
false
};
let mut error_message = None;
if ! is_old {
if !message.is_empty() {
message += "\n";
for alert in payload.alerts {
match alert.render() {
Ok(message) => {
jabber.send_message(message).await;
}
Err(e) => {
error_message = Some(format!("{}", e));
}
let title = match (alert.labels.get("alertname"), alert.labels.get("instance")) {
(Some(name), Some(instance)) => format!("{}@{}", name, instance),
_ => alert.annotations.summary.clone(),
};
message += &format!("{}: {}", title, alert.generatorURL);
alerts.insert(alert.generatorURL.clone(), alert);
}
}
drop(alerts);
if !message.is_empty() {
let jabber = state.jabber.clone();
tokio::spawn(async move {
jabber.send_message(message).await;
});
match error_message {
None =>
StatusCode::OK.into_response(),
Some(error_message) =>
(StatusCode::INTERNAL_SERVER_ERROR,
error_message
).into_response()
}
StatusCode::OK.into_response()
}
#[derive(Deserialize)]
@ -91,15 +86,11 @@ async fn main() {
).expect("read config")
).expect("parse config");
let jabber = jabber::run(config.jid, config.password, config.muc).await;
let state = AppState {
jabber,
alerts: Arc::new(Mutex::new(HashMap::new())),
};
// build our application with a route
let app = Router::new()
.route("/api/v2/alerts", post(alerts))
.with_state(state);
.route("/alert", post(alerts))
.with_state(jabber);
let addr = SocketAddr::from(([127, 0, 0, 1], config.listen_port));
tracing::debug!("listening on {}", addr);

11
templates/alert.txt Normal file
View File

@ -0,0 +1,11 @@
{{ status|upper -}}:
{% if labels.alertname.as_ref().is_some() %}*{{ labels.alertname.as_ref().unwrap() }}*{% endif -%}
{% if labels.host.as_ref().is_some() -%}
at {{ labels.host.as_ref().unwrap() -}}
{% else if labels.instance.as_ref().is_some() -%}
at {{ labels.instance.as_ref().unwrap() }}{% endif -%}
{% if annotations.message.as_ref().is_some() %}
{{ annotations.message.as_ref().unwrap() }}{% endif %}
{% if annotations.description.as_ref().is_some() %}
{{ annotations.description.as_ref().unwrap() }}{% endif %}
Link: {{ generator_url }}