205 lines
4.5 KiB
Perl
Executable File
205 lines
4.5 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
use Mojolicious::Lite -signatures;
|
|
|
|
use Mojo::Headers;
|
|
use Mojo::URL;
|
|
use Mojo::Util qw(b64_encode);
|
|
|
|
my $config = plugin "Config" => {
|
|
default => {
|
|
max_request_size => 1024 * 1024,
|
|
ntfy_default_priority => 3,
|
|
ntfy_default_topic => "am",
|
|
ntfy_url => "https://ntfy.sh",
|
|
app_secrets => [app->moniker],
|
|
},
|
|
};
|
|
|
|
app->max_request_size($config->{max_request_size});
|
|
app->exception_format("txt");
|
|
app->secrets($config->{app_secrets});
|
|
|
|
|
|
sub am_validjson ($json) {
|
|
return $json->{version} eq "4" &&
|
|
defined($json->{status}) &&
|
|
ref $json->{alerts} eq "ARRAY" &&
|
|
$json->{alerts}->@* > 0;
|
|
}
|
|
|
|
sub am_labelpairs ($m) {
|
|
return map {"$_=$m->{$_}"} grep {$_ ne "alertname"} sort keys $m->%*;
|
|
}
|
|
|
|
sub ntfy_post ($ua, $json) {
|
|
state $url = Mojo::URL->new($config->{ntfy_url});
|
|
state $headers = do {
|
|
my $hs = Mojo::Headers->new();
|
|
$hs->accept("application/json");
|
|
$hs->content_type("application/json");
|
|
|
|
if (defined($config->{ntfy_token})) {
|
|
$hs->authorization("Bearer $config->{ntfy_token}");
|
|
} elsif (defined($config->{ntfy_user}) &&
|
|
defined($config->{ntfy_pass})) {
|
|
my $ui = "$config->{ntfy_user}:$config->{ntfy_pass}";
|
|
$hs->authorization("Basic " . b64_encode($ui));
|
|
}
|
|
|
|
$hs->to_hash;
|
|
};
|
|
|
|
return $ua->post($url, $headers, json => $json)->result;
|
|
}
|
|
|
|
|
|
get "/" => sub ($c) {
|
|
$c->render(template => "index");
|
|
};
|
|
|
|
post "/webhook" => sub ($c) {
|
|
my $query_params = $c->req->query_params;
|
|
my $req_json = $c->req->json;
|
|
|
|
return $c->reply->exception("Invalid input")->rendered(400) if
|
|
! am_validjson($req_json);
|
|
|
|
my $priority = int($query_params->param("priority") //
|
|
$config->{ntfy_default_priority});
|
|
my $topic = $query_params->param("topic") //
|
|
$config->{ntfy_default_topic};
|
|
|
|
my $title = uc($req_json->{status});
|
|
$title .= " - $req_json->{groupLabels}->{alertname}" if
|
|
defined $req_json->{groupLabels}->{alertname};
|
|
|
|
my @tags_common = am_labelpairs $req_json->{commonLabels};
|
|
my @tags_group = am_labelpairs $req_json->{groupLabels};
|
|
|
|
$c->stash(
|
|
"am.groupKey" => $req_json->{groupKey},
|
|
"am.truncatedAlerts" => $req_json->{truncatedAlerts},
|
|
"am.status" => $req_json->{status},
|
|
"am.receiver" => $req_json->{receiver},
|
|
"am.groupLabels" => $req_json->{groupLabels},
|
|
"am.commonLabels" => $req_json->{commonLabels},
|
|
"am.commonAnnotations" => $req_json->{commonAnnotations},
|
|
"am.externalURL" => $req_json->{externalURL},
|
|
"am.alerts" => $req_json->{alerts},
|
|
"am.alerts.Firing" => [grep {$_->{status} eq "firing"}
|
|
$req_json->{alerts}->@*],
|
|
"am.alerts.Resolved" => [grep {$_->{status} eq "resolved"}
|
|
$req_json->{alerts}->@*],
|
|
);
|
|
|
|
my $json = {
|
|
message => $c->render_to_string(
|
|
template => "message",
|
|
format => "txt",
|
|
),
|
|
priority => $priority,
|
|
tags => [@tags_group, @tags_common],
|
|
title => $title,
|
|
topic => $topic,
|
|
};
|
|
|
|
my $ua = $c->ua->request_timeout(10);
|
|
my $res = ntfy_post($ua, $json);
|
|
|
|
return $c->reply->exception("Upstream error")->rendered($res->code) if
|
|
!$res->is_success;
|
|
|
|
$c->render(json => $res->json);
|
|
};
|
|
|
|
app->start;
|
|
__DATA__
|
|
|
|
@@ message.txt.ep
|
|
% my $alerts_firing = stash "am.alerts.Firing";
|
|
% my $alerts_resolved = stash "am.alerts.Resolved";
|
|
% my $common_annotations = stash "am.commonAnnotations";
|
|
% if (defined $common_annotations->{summary}) {
|
|
%= $common_annotations->{summary}
|
|
% }
|
|
% if ($alerts_firing->@* > 0) {
|
|
|
|
FIRING:<%== scalar $alerts_firing->@* %>
|
|
% for my $alert ($alerts_firing->@*) {
|
|
%== $alert->{annotations}->{description};
|
|
% }
|
|
% }
|
|
% if ($alerts_resolved->@* > 0) {
|
|
|
|
RESOLVED:<%== scalar $alerts_resolved->@* %>
|
|
% for my $alert ($alerts_resolved->@*) {
|
|
- <%== $alert->{labels}->{instance} %>
|
|
% }
|
|
% }
|
|
|
|
@@ index.html.ep
|
|
% layout "main";
|
|
% title "damntfy";
|
|
<h1>damntfy</h1>
|
|
<p>Alertmanager–ntfy integrator</a>
|
|
<dl>
|
|
<dt>POST <a href="/webhook">/webhook</a></dt>
|
|
<dd>Receives an Alertmanager webhook payload and sends a ntfy payload.</dd>
|
|
</dl>
|
|
|
|
@@ layouts/main.html.ep
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
|
<title><%= title %></title>
|
|
<style type="text/css">
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
dd,
|
|
dl,
|
|
dt,
|
|
h1,
|
|
p {
|
|
margin: 0;
|
|
}
|
|
|
|
body {
|
|
margin: 0 auto;
|
|
min-height: 100vh;
|
|
max-width: 60rem;
|
|
font-size: 1rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
dl,
|
|
h1,
|
|
p {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
h1 {
|
|
text-align: center;
|
|
font-size: 3rem;
|
|
line-height: 1;
|
|
}
|
|
|
|
dt {
|
|
font-weight: bold;
|
|
}
|
|
|
|
dd {
|
|
margin-left: 3rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
%= content
|
|
</body>
|
|
</html>
|