Alerting: update email template (#34205)
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user