Alerting: update email template (#34205)

This commit is contained in:
Domas
2021-05-19 18:58:31 +02:00
committed by GitHub
parent 5dca9fd4d8
commit 54c33c6cdd
7 changed files with 528 additions and 287 deletions
@@ -3,6 +3,7 @@ package channels
import (
"context"
"fmt"
"net/url"
"path"
gokit_log "github.com/go-kit/kit/log"
@@ -66,12 +67,25 @@ func NewEmailNotifier(model *NotificationChannelConfig, t *template.Template) (*
// Notify sends the alert notification.
func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
// We only need ExternalURL from this template object. This hack should go away with https://github.com/prometheus/alertmanager/pull/2508.
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: en.tmpl.ExternalURL}, as, gokit_log.NewLogfmtLogger(logging.NewWrapper(en.log)))
data, err := ExtendData(notify.GetTemplateData(ctx, &template.Template{ExternalURL: en.tmpl.ExternalURL}, as, gokit_log.NewLogfmtLogger(logging.NewWrapper(en.log))))
if err != nil {
return false, err
}
var tmplErr error
tmpl := notify.TmplText(en.tmpl, data, &tmplErr)
tmpl := TmplText(en.tmpl, data, &tmplErr)
title := tmpl(`{{ template "default.title" . }}`)
u, err := url.Parse(en.tmpl.ExternalURL.String())
if err != nil {
return false, fmt.Errorf("failed to parse external URL: %w", err)
}
basePath := u.Path
u.Path = path.Join(basePath, "/alerting/list")
ruleURL := u.String()
u.RawQuery = "alertState=firing&view=state"
alertPageURL := u.String()
cmd := &models.SendEmailCommandSync{
SendEmailCommand: models.SendEmailCommand{
Subject: title,
@@ -84,8 +98,8 @@ func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
"CommonLabels": data.CommonLabels,
"CommonAnnotations": data.CommonAnnotations,
"ExternalURL": data.ExternalURL,
"RuleUrl": path.Join(en.tmpl.ExternalURL.String(), "/alerting/list"),
"AlertPageUrl": path.Join(en.tmpl.ExternalURL.String(), "/alerting/list?alertState=firing&view=state"),
"RuleUrl": ruleURL,
"AlertPageUrl": alertPageURL,
},
To: en.Addresses,
SingleEmail: en.SingleEmail,
@@ -18,7 +18,7 @@ import (
func TestEmailNotifier(t *testing.T) {
tmpl := templateForTests(t)
externalURL, err := url.Parse("http://localhost")
externalURL, err := url.Parse("http://localhost/base")
require.NoError(t, err)
tmpl.ExternalURL = externalURL
@@ -67,7 +67,7 @@ func TestEmailNotifier(t *testing.T) {
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "AlwaysFiring", "severity": "warning"},
Annotations: model.LabelSet{"runbook_url": "http://fix.me"},
Annotations: model.LabelSet{"runbook_url": "http://fix.me", "__dashboardUid__": "abc", "__panelId__": "5"},
},
},
}
@@ -85,20 +85,23 @@ func TestEmailNotifier(t *testing.T) {
"Title": "[FIRING:1] (AlwaysFiring warning)",
"Message": "[FIRING:1] (AlwaysFiring warning)",
"Status": "firing",
"Alerts": template.Alerts{
template.Alert{
Status: "firing",
Labels: template.KV{"alertname": "AlwaysFiring", "severity": "warning"},
Annotations: template.KV{"runbook_url": "http://fix.me"},
Fingerprint: "15a37193dce72bab",
"Alerts": ExtendedAlerts{
ExtendedAlert{
Status: "firing",
Labels: template.KV{"alertname": "AlwaysFiring", "severity": "warning"},
Annotations: template.KV{"runbook_url": "http://fix.me"},
Fingerprint: "15a37193dce72bab",
SilenceURL: "http://localhost/base/alerting/silence/new?alertmanager=grafana&matchers=alertname%3DAlwaysFiring%2Cseverity%3Dwarning",
DashboardURL: "http://localhost/base/d/abc",
PanelURL: "http://localhost/base/d/abc?viewPanel=5",
},
},
"GroupLabels": template.KV{},
"CommonLabels": template.KV{"alertname": "AlwaysFiring", "severity": "warning"},
"CommonAnnotations": template.KV{"runbook_url": "http://fix.me"},
"ExternalURL": "http://localhost",
"RuleUrl": "http:/localhost/alerting/list",
"AlertPageUrl": "http:/localhost/alerting/list?alertState=firing&view=state",
"ExternalURL": "http://localhost/base",
"RuleUrl": "http://localhost/base/alerting/list",
"AlertPageUrl": "http://localhost/base/alerting/list?alertState=firing&view=state",
},
}, expected)
})
@@ -0,0 +1,153 @@
package channels
import (
"fmt"
"net/url"
"path"
"sort"
"strings"
"time"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/common/model"
)
type ExtendedAlert struct {
Status string `json:"status"`
Labels template.KV `json:"labels"`
Annotations template.KV `json:"annotations"`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
GeneratorURL string `json:"generatorURL"`
Fingerprint string `json:"fingerprint"`
SilenceURL string `json:"silenceURL"`
DashboardURL string `json:"dashboardURL"`
PanelURL string `json:"panelURL"`
}
type ExtendedAlerts []ExtendedAlert
type ExtendedData struct {
Receiver string `json:"receiver"`
Status string `json:"status"`
Alerts ExtendedAlerts `json:"alerts"`
GroupLabels template.KV `json:"groupLabels"`
CommonLabels template.KV `json:"commonLabels"`
CommonAnnotations template.KV `json:"commonAnnotations"`
ExternalURL string `json:"externalURL"`
}
func removePrivateItems(kv template.KV) template.KV {
for key := range kv {
if strings.HasPrefix(key, "__") && strings.HasSuffix(key, "__") {
kv = kv.Remove([]string{key})
}
}
return kv
}
func extendAlert(alert template.Alert, externalURL string) (*ExtendedAlert, error) {
extended := ExtendedAlert{
Status: alert.Status,
Labels: alert.Labels,
Annotations: alert.Annotations,
StartsAt: alert.StartsAt,
EndsAt: alert.EndsAt,
GeneratorURL: alert.GeneratorURL,
Fingerprint: alert.Fingerprint,
}
// fill in some grafana-specific urls
if len(externalURL) > 0 {
u, err := url.Parse(externalURL)
if err != nil {
return nil, fmt.Errorf("failed to parse external URL: %w", err)
}
externalPath := u.Path
dashboardUid := alert.Annotations["__dashboardUid__"]
if len(dashboardUid) > 0 {
u.Path = path.Join(externalPath, "/d/", dashboardUid)
extended.DashboardURL = u.String()
panelId := alert.Annotations["__panelId__"]
if len(panelId) > 0 {
u.RawQuery = "viewPanel=" + panelId
extended.PanelURL = u.String()
}
}
matchers := make([]string, 0)
for key, value := range alert.Labels {
if !(strings.HasPrefix(key, "__") && strings.HasSuffix(key, "__")) {
matchers = append(matchers, key+"="+value)
}
}
sort.Strings(matchers)
u.Path = path.Join(externalPath, "/alerting/silence/new")
u.RawQuery = "alertmanager=grafana&matchers=" + url.QueryEscape(strings.Join(matchers, ","))
extended.SilenceURL = u.String()
}
// remove "private" annotations & labels so they don't show up in the template
extended.Annotations = removePrivateItems(extended.Annotations)
extended.Labels = removePrivateItems(extended.Labels)
return &extended, nil
}
func ExtendData(data *template.Data) (*ExtendedData, error) {
alerts := []ExtendedAlert{}
for _, alert := range data.Alerts {
extendedAlert, err := extendAlert(alert, data.ExternalURL)
if err != nil {
return nil, err
}
alerts = append(alerts, *extendedAlert)
}
extended := &ExtendedData{
Receiver: data.Receiver,
Status: data.Status,
Alerts: alerts,
GroupLabels: data.GroupLabels,
CommonLabels: removePrivateItems(data.CommonLabels),
CommonAnnotations: removePrivateItems(data.CommonAnnotations),
ExternalURL: data.ExternalURL,
}
return extended, nil
}
func TmplText(tmpl *template.Template, data *ExtendedData, err *error) func(string) string {
return func(name string) (s string) {
if *err != nil {
return
}
s, *err = tmpl.ExecuteTextString(name, data)
return s
}
}
// Firing returns the subset of alerts that are firing.
func (as ExtendedAlerts) Firing() []ExtendedAlert {
res := []ExtendedAlert{}
for _, a := range as {
if a.Status == string(model.AlertFiring) {
res = append(res, a)
}
}
return res
}
// Resolved returns the subset of alerts that are resolved.
func (as ExtendedAlerts) Resolved() []ExtendedAlert {
res := []ExtendedAlert{}
for _, a := range as {
if a.Status == string(model.AlertResolved) {
res = append(res, a)
}
}
return res
}