mirror of https://github.com/grafana/grafana
AlertingNG: PagerDuty notification channel (#32604)
* AlertingNG: PagerDuty notification channel Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com> * Add tests Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com> * Fix lint Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com> * Fix reviews Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com>pull/32813/head
parent
b1c84c795f
commit
e3a1d3d158
@ -0,0 +1,210 @@ |
||||
package channels |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"net/url" |
||||
"os" |
||||
|
||||
gokit_log "github.com/go-kit/kit/log" |
||||
"github.com/pkg/errors" |
||||
"github.com/prometheus/alertmanager/notify" |
||||
"github.com/prometheus/alertmanager/template" |
||||
"github.com/prometheus/alertmanager/types" |
||||
"github.com/prometheus/common/model" |
||||
|
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/alerting" |
||||
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" |
||||
) |
||||
|
||||
const ( |
||||
pagerDutyEventTrigger = "trigger" |
||||
pagerDutyEventResolve = "resolve" |
||||
) |
||||
|
||||
var ( |
||||
pagerdutyEventAPIURL = "https://events.pagerduty.com/v2/enqueue" |
||||
) |
||||
|
||||
// PagerdutyNotifier is responsible for sending
|
||||
// alert notifications to pagerduty
|
||||
type PagerdutyNotifier struct { |
||||
old_notifiers.NotifierBase |
||||
Key string |
||||
Severity string |
||||
AutoResolve bool |
||||
CustomDetails map[string]string |
||||
Class string |
||||
Component string |
||||
Group string |
||||
Summary string |
||||
tmpl *template.Template |
||||
log log.Logger |
||||
externalUrl *url.URL |
||||
} |
||||
|
||||
// NewPagerdutyNotifier is the constructor for the PagerDuty notifier
|
||||
func NewPagerdutyNotifier(model *models.AlertNotification, t *template.Template, externalUrl *url.URL) (*PagerdutyNotifier, error) { |
||||
key := model.DecryptedValue("integrationKey", model.Settings.Get("integrationKey").MustString()) |
||||
if key == "" { |
||||
return nil, alerting.ValidationError{Reason: "Could not find integration key property in settings"} |
||||
} |
||||
|
||||
customDetails := model.Settings.Get("customDetails").MustMap(map[string]interface{}{ |
||||
"firing": `{{ template "pagerduty.default.instances" .Alerts.Firing }}`, |
||||
"resolved": `{{ template "pagerduty.default.instances" .Alerts.Resolved }}`, |
||||
"num_firing": `{{ .Alerts.Firing | len }}`, |
||||
"num_resolved": `{{ .Alerts.Resolved | len }}`, |
||||
}) |
||||
|
||||
details := make(map[string]string, len(customDetails)) |
||||
for k, v := range customDetails { |
||||
if val, ok := v.(string); ok { |
||||
details[k] = val |
||||
} |
||||
} |
||||
|
||||
return &PagerdutyNotifier{ |
||||
NotifierBase: old_notifiers.NewNotifierBase(model), |
||||
Key: key, |
||||
CustomDetails: details, |
||||
Severity: model.Settings.Get("severity").MustString("critical"), |
||||
AutoResolve: model.Settings.Get("autoResolve").MustBool(true), |
||||
Class: model.Settings.Get("class").MustString("todo_class"), // TODO
|
||||
Component: model.Settings.Get("component").MustString("Grafana"), |
||||
Group: model.Settings.Get("group").MustString("todo_group"), // TODO
|
||||
Summary: model.Settings.Get("summary").MustString(`{{ template "pagerduty.default.description" .}}`), |
||||
tmpl: t, |
||||
externalUrl: externalUrl, |
||||
log: log.New("alerting.notifier." + model.Name), |
||||
}, nil |
||||
} |
||||
|
||||
// Notify sends an alert notification to PagerDuty
|
||||
func (pn *PagerdutyNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { |
||||
alerts := types.Alerts(as...) |
||||
if alerts.Status() == model.AlertResolved && !pn.AutoResolve { |
||||
pn.log.Debug("Not sending a trigger to Pagerduty", "status", alerts.Status(), "auto resolve", pn.AutoResolve) |
||||
return true, nil |
||||
} |
||||
|
||||
msg, eventType, err := pn.buildPagerdutyMessage(ctx, alerts, as) |
||||
if err != nil { |
||||
return false, errors.Wrap(err, "build pagerduty message") |
||||
} |
||||
|
||||
body, err := json.Marshal(msg) |
||||
if err != nil { |
||||
return false, errors.Wrap(err, "marshal json") |
||||
} |
||||
|
||||
pn.log.Info("Notifying Pagerduty", "event_type", eventType) |
||||
cmd := &models.SendWebhookSync{ |
||||
Url: pagerdutyEventAPIURL, |
||||
Body: string(body), |
||||
HttpMethod: "POST", |
||||
HttpHeader: map[string]string{ |
||||
"Content-Type": "application/json", |
||||
}, |
||||
} |
||||
if err := bus.DispatchCtx(ctx, cmd); err != nil { |
||||
return false, errors.Wrap(err, "send notification to Pagerduty") |
||||
} |
||||
|
||||
return true, nil |
||||
} |
||||
|
||||
func (pn *PagerdutyNotifier) buildPagerdutyMessage(ctx context.Context, alerts model.Alerts, as []*types.Alert) (*pagerDutyMessage, string, error) { |
||||
key, err := notify.ExtractGroupKey(ctx) |
||||
if err != nil { |
||||
return nil, "", err |
||||
} |
||||
|
||||
eventType := pagerDutyEventTrigger |
||||
if alerts.Status() == model.AlertResolved { |
||||
eventType = pagerDutyEventResolve |
||||
} |
||||
|
||||
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: pn.externalUrl}, as, gokit_log.NewNopLogger()) |
||||
var tmplErr error |
||||
tmpl := notify.TmplText(pn.tmpl, data, &tmplErr) |
||||
|
||||
details := make(map[string]string, len(pn.CustomDetails)) |
||||
for k, v := range pn.CustomDetails { |
||||
detail, err := pn.tmpl.ExecuteTextString(v, data) |
||||
if err != nil { |
||||
return nil, "", errors.Wrapf(err, "%q: failed to template %q", k, v) |
||||
} |
||||
details[k] = detail |
||||
} |
||||
|
||||
msg := &pagerDutyMessage{ |
||||
Client: "Grafana", |
||||
ClientURL: pn.externalUrl.String(), |
||||
RoutingKey: pn.Key, |
||||
EventAction: eventType, |
||||
DedupKey: key.Hash(), |
||||
Links: []pagerDutyLink{{ |
||||
HRef: pn.externalUrl.String(), |
||||
Text: "External URL", |
||||
}}, |
||||
Description: getTitleFromTemplateData(data), // TODO: this can be configurable template.
|
||||
Payload: &pagerDutyPayload{ |
||||
Component: tmpl(pn.Component), |
||||
Summary: tmpl(pn.Summary), |
||||
Severity: tmpl(pn.Severity), |
||||
CustomDetails: details, |
||||
Class: tmpl(pn.Class), |
||||
Group: tmpl(pn.Group), |
||||
}, |
||||
} |
||||
|
||||
if hostname, err := os.Hostname(); err == nil { |
||||
// TODO: should this be configured like in Prometheus AM?
|
||||
msg.Payload.Source = hostname |
||||
} |
||||
|
||||
if tmplErr != nil { |
||||
return nil, "", errors.Wrap(tmplErr, "failed to template PagerDuty message") |
||||
} |
||||
|
||||
return msg, eventType, nil |
||||
} |
||||
|
||||
func (pn *PagerdutyNotifier) SendResolved() bool { |
||||
return pn.AutoResolve |
||||
} |
||||
|
||||
type pagerDutyMessage struct { |
||||
RoutingKey string `json:"routing_key,omitempty"` |
||||
ServiceKey string `json:"service_key,omitempty"` |
||||
DedupKey string `json:"dedup_key,omitempty"` |
||||
IncidentKey string `json:"incident_key,omitempty"` |
||||
EventType string `json:"event_type,omitempty"` |
||||
Description string `json:"description,omitempty"` |
||||
EventAction string `json:"event_action"` |
||||
Payload *pagerDutyPayload `json:"payload"` |
||||
Client string `json:"client,omitempty"` |
||||
ClientURL string `json:"client_url,omitempty"` |
||||
Details map[string]string `json:"details,omitempty"` |
||||
Links []pagerDutyLink `json:"links,omitempty"` |
||||
} |
||||
|
||||
type pagerDutyLink struct { |
||||
HRef string `json:"href"` |
||||
Text string `json:"text"` |
||||
} |
||||
|
||||
type pagerDutyPayload struct { |
||||
Summary string `json:"summary"` |
||||
Source string `json:"source"` |
||||
Severity string `json:"severity"` |
||||
Timestamp string `json:"timestamp,omitempty"` |
||||
Class string `json:"class,omitempty"` |
||||
Component string `json:"component,omitempty"` |
||||
Group string `json:"group,omitempty"` |
||||
CustomDetails map[string]string `json:"custom_details,omitempty"` |
||||
} |
@ -0,0 +1,180 @@ |
||||
package channels |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"errors" |
||||
"net/url" |
||||
"os" |
||||
"testing" |
||||
|
||||
"github.com/prometheus/alertmanager/notify" |
||||
"github.com/prometheus/alertmanager/template" |
||||
"github.com/prometheus/alertmanager/types" |
||||
"github.com/prometheus/common/model" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/alerting" |
||||
) |
||||
|
||||
func TestPagerdutyNotifier(t *testing.T) { |
||||
tmpl, err := template.FromGlobs("templates/default.tmpl") |
||||
require.NoError(t, err) |
||||
|
||||
hostname, err := os.Hostname() |
||||
require.NoError(t, err) |
||||
|
||||
cases := []struct { |
||||
name string |
||||
settings string |
||||
alerts []*types.Alert |
||||
expMsg *pagerDutyMessage |
||||
expInitError error |
||||
expMsgError error |
||||
}{ |
||||
{ |
||||
name: "Default config with one alert", |
||||
settings: `{"integrationKey": "abcdefgh0123456789"}`, |
||||
alerts: []*types.Alert{ |
||||
{ |
||||
Alert: model.Alert{ |
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, |
||||
Annotations: model.LabelSet{"ann1": "annv1"}, |
||||
}, |
||||
}, |
||||
}, |
||||
expMsg: &pagerDutyMessage{ |
||||
RoutingKey: "abcdefgh0123456789", |
||||
DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733", |
||||
Description: "[firing:1] (val1)", |
||||
EventAction: "trigger", |
||||
Payload: &pagerDutyPayload{ |
||||
Summary: "[FIRING:1] (val1)", |
||||
Source: hostname, |
||||
Severity: "critical", |
||||
Class: "todo_class", |
||||
Component: "Grafana", |
||||
Group: "todo_group", |
||||
CustomDetails: map[string]string{ |
||||
"firing": "Labels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n", |
||||
"num_firing": "1", |
||||
"num_resolved": "0", |
||||
"resolved": "", |
||||
}, |
||||
}, |
||||
Client: "Grafana", |
||||
ClientURL: "http://localhost", |
||||
Links: []pagerDutyLink{{HRef: "http://localhost", Text: "External URL"}}, |
||||
}, |
||||
expInitError: nil, |
||||
expMsgError: nil, |
||||
}, { |
||||
name: "Custom config with multiple alerts", |
||||
settings: `{ |
||||
"integrationKey": "abcdefgh0123456789", |
||||
"severity": "warning", |
||||
"class": "{{ .Status }}", |
||||
"component": "My Grafana", |
||||
"group": "my_group" |
||||
}`, |
||||
alerts: []*types.Alert{ |
||||
{ |
||||
Alert: model.Alert{ |
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, |
||||
Annotations: model.LabelSet{"ann1": "annv1"}, |
||||
}, |
||||
}, { |
||||
Alert: model.Alert{ |
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val2"}, |
||||
Annotations: model.LabelSet{"ann1": "annv2"}, |
||||
}, |
||||
}, |
||||
}, |
||||
expMsg: &pagerDutyMessage{ |
||||
RoutingKey: "abcdefgh0123456789", |
||||
DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733", |
||||
Description: "[firing:2] ", |
||||
EventAction: "trigger", |
||||
Payload: &pagerDutyPayload{ |
||||
Summary: "[FIRING:2] ", |
||||
Source: hostname, |
||||
Severity: "warning", |
||||
Class: "firing", |
||||
Component: "My Grafana", |
||||
Group: "my_group", |
||||
CustomDetails: map[string]string{ |
||||
"firing": "Labels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \nLabels:\n - alertname = alert1\n - lbl1 = val2\nAnnotations:\n - ann1 = annv2\nSource: \n", |
||||
"num_firing": "2", |
||||
"num_resolved": "0", |
||||
"resolved": "", |
||||
}, |
||||
}, |
||||
Client: "Grafana", |
||||
ClientURL: "http://localhost", |
||||
Links: []pagerDutyLink{{HRef: "http://localhost", Text: "External URL"}}, |
||||
}, |
||||
expInitError: nil, |
||||
expMsgError: nil, |
||||
}, { |
||||
name: "Error in initing", |
||||
settings: `{}`, |
||||
expInitError: alerting.ValidationError{Reason: "Could not find integration key property in settings"}, |
||||
}, { |
||||
name: "Error in building message", |
||||
settings: `{ |
||||
"integrationKey": "abcdefgh0123456789", |
||||
"class": "{{ .Status }" |
||||
}`, |
||||
expMsgError: errors.New("build pagerduty message: failed to template PagerDuty message: template: :1: unexpected \"}\" in operand"), |
||||
}, |
||||
} |
||||
|
||||
for _, c := range cases { |
||||
t.Run(c.name, func(t *testing.T) { |
||||
settingsJSON, err := simplejson.NewJson([]byte(c.settings)) |
||||
require.NoError(t, err) |
||||
|
||||
m := &models.AlertNotification{ |
||||
Name: "pageduty_testing", |
||||
Type: "pagerduty", |
||||
Settings: settingsJSON, |
||||
} |
||||
|
||||
externalURL, err := url.Parse("http://localhost") |
||||
require.NoError(t, err) |
||||
pn, err := NewPagerdutyNotifier(m, tmpl, externalURL) |
||||
if c.expInitError != nil { |
||||
require.Error(t, err) |
||||
require.Equal(t, c.expInitError.Error(), err.Error()) |
||||
return |
||||
} |
||||
require.NoError(t, err) |
||||
|
||||
body := "" |
||||
bus.AddHandlerCtx("test", func(ctx context.Context, webhook *models.SendWebhookSync) error { |
||||
body = webhook.Body |
||||
return nil |
||||
}) |
||||
|
||||
ctx := notify.WithGroupKey(context.Background(), "alertname") |
||||
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""}) |
||||
ok, err := pn.Notify(ctx, c.alerts...) |
||||
if c.expMsgError != nil { |
||||
require.False(t, ok) |
||||
require.Error(t, err) |
||||
require.Equal(t, c.expMsgError.Error(), err.Error()) |
||||
return |
||||
} |
||||
require.True(t, ok) |
||||
require.NoError(t, err) |
||||
|
||||
expBody, err := json.Marshal(c.expMsg) |
||||
require.NoError(t, err) |
||||
|
||||
require.JSONEq(t, string(expBody), body) |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,219 @@ |
||||
{{ define "__alertmanager" }}Alertmanager{{ end }} |
||||
{{ define "__alertmanagerURL" }}{{ .ExternalURL }}/#/alerts?receiver={{ .Receiver | urlquery }}{{ end }} |
||||
|
||||
{{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }} |
||||
{{ define "__description" }}{{ end }} |
||||
|
||||
{{ define "__text_alert_list" }}{{ range . }}Labels: |
||||
{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }} |
||||
{{ end }}Annotations: |
||||
{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }} |
||||
{{ end }}Source: {{ .GeneratorURL }} |
||||
{{ end }}{{ end }} |
||||
|
||||
|
||||
{{ define "slack.default.title" }}{{ template "__subject" . }}{{ end }} |
||||
{{ define "slack.default.username" }}{{ template "__alertmanager" . }}{{ end }} |
||||
{{ define "slack.default.fallback" }}{{ template "slack.default.title" . }} | {{ template "slack.default.titlelink" . }}{{ end }} |
||||
{{ define "slack.default.callbackid" }}{{ end }} |
||||
{{ define "slack.default.pretext" }}{{ end }} |
||||
{{ define "slack.default.titlelink" }}{{ template "__alertmanagerURL" . }}{{ end }} |
||||
{{ define "slack.default.iconemoji" }}{{ end }} |
||||
{{ define "slack.default.iconurl" }}{{ end }} |
||||
{{ define "slack.default.text" }}{{ end }} |
||||
{{ define "slack.default.footer" }}{{ end }} |
||||
|
||||
|
||||
{{ define "pagerduty.default.description" }}{{ template "__subject" . }}{{ end }} |
||||
{{ define "pagerduty.default.client" }}{{ template "__alertmanager" . }}{{ end }} |
||||
{{ define "pagerduty.default.clientURL" }}{{ template "__alertmanagerURL" . }}{{ end }} |
||||
{{ define "pagerduty.default.instances" }}{{ template "__text_alert_list" . }}{{ end }} |
||||
|
||||
|
||||
{{ define "opsgenie.default.message" }}{{ template "__subject" . }}{{ end }} |
||||
{{ define "opsgenie.default.description" }}{{ .CommonAnnotations.SortedPairs.Values | join " " }} |
||||
{{ if gt (len .Alerts.Firing) 0 -}} |
||||
Alerts Firing: |
||||
{{ template "__text_alert_list" .Alerts.Firing }} |
||||
{{- end }} |
||||
{{ if gt (len .Alerts.Resolved) 0 -}} |
||||
Alerts Resolved: |
||||
{{ template "__text_alert_list" .Alerts.Resolved }} |
||||
{{- end }} |
||||
{{- end }} |
||||
{{ define "opsgenie.default.source" }}{{ template "__alertmanagerURL" . }}{{ end }} |
||||
|
||||
|
||||
{{ define "wechat.default.message" }}{{ template "__subject" . }} |
||||
{{ .CommonAnnotations.SortedPairs.Values | join " " }} |
||||
{{ if gt (len .Alerts.Firing) 0 -}} |
||||
Alerts Firing: |
||||
{{ template "__text_alert_list" .Alerts.Firing }} |
||||
{{- end }} |
||||
{{ if gt (len .Alerts.Resolved) 0 -}} |
||||
Alerts Resolved: |
||||
{{ template "__text_alert_list" .Alerts.Resolved }} |
||||
{{- end }} |
||||
AlertmanagerUrl: |
||||
{{ template "__alertmanagerURL" . }} |
||||
{{- end }} |
||||
{{ define "wechat.default.to_user" }}{{ end }} |
||||
{{ define "wechat.default.to_party" }}{{ end }} |
||||
{{ define "wechat.default.to_tag" }}{{ end }} |
||||
{{ define "wechat.default.agent_id" }}{{ end }} |
||||
|
||||
|
||||
|
||||
{{ define "victorops.default.state_message" }}{{ .CommonAnnotations.SortedPairs.Values | join " " }} |
||||
{{ if gt (len .Alerts.Firing) 0 -}} |
||||
Alerts Firing: |
||||
{{ template "__text_alert_list" .Alerts.Firing }} |
||||
{{- end }} |
||||
{{ if gt (len .Alerts.Resolved) 0 -}} |
||||
Alerts Resolved: |
||||
{{ template "__text_alert_list" .Alerts.Resolved }} |
||||
{{- end }} |
||||
{{- end }} |
||||
{{ define "victorops.default.entity_display_name" }}{{ template "__subject" . }}{{ end }} |
||||
{{ define "victorops.default.monitoring_tool" }}{{ template "__alertmanager" . }}{{ end }} |
||||
|
||||
{{ define "email.default.subject" }}{{ template "__subject" . }}{{ end }} |
||||
{{ define "email.default.html" }} |
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
||||
<!-- |
||||
Style and HTML derived from https://github.com/mailgun/transactional-email-templates |
||||
|
||||
|
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2014 Mailgun |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
||||
--> |
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns="http://www.w3.org/1999/xhtml" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<head style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<meta name="viewport" content="width=device-width" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /> |
||||
<title style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">{{ template "__subject" . }}</title> |
||||
|
||||
</head> |
||||
|
||||
<body itemscope="" itemtype="http://schema.org/EmailMessage" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; height: 100%; line-height: 1.6em; width: 100% !important; background-color: #f6f6f6; margin: 0; padding: 0;" bgcolor="#f6f6f6"> |
||||
|
||||
<table style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;" bgcolor="#f6f6f6"> |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;" valign="top"></td> |
||||
<td width="600" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; width: 100% !important; margin: 0 auto; padding: 0;" valign="top"> |
||||
<div style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 0;"> |
||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;" bgcolor="#fff"> |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; color: #fff; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; background-color: #E6522C; margin: 0; padding: 20px;" align="center" bgcolor="#E6522C" valign="top"> |
||||
{{ .Alerts | len }} alert{{ if gt (len .Alerts) 1 }}s{{ end }} for {{ range .GroupLabels.SortedPairs }} |
||||
{{ .Name }}={{ .Value }} |
||||
{{ end }} |
||||
</td> |
||||
</tr> |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 10px;" valign="top"> |
||||
<table width="100%" cellpadding="0" cellspacing="0" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> |
||||
<a href="{{ template "__alertmanagerURL" . }}" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #FFF; text-decoration: none; line-height: 2em; font-weight: bold; text-align: center; cursor: pointer; display: inline-block; border-radius: 5px; text-transform: capitalize; background-color: #348eda; margin: 0; border-color: #348eda; border-style: solid; border-width: 10px 20px;">View in {{ template "__alertmanager" . }}</a> |
||||
</td> |
||||
</tr> |
||||
{{ if gt (len .Alerts.Firing) 0 }} |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> |
||||
<strong style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">[{{ .Alerts.Firing | len }}] Firing</strong> |
||||
</td> |
||||
</tr> |
||||
{{ end }} |
||||
{{ range .Alerts.Firing }} |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> |
||||
<strong style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">Labels</strong><br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /> |
||||
{{ range .Labels.SortedPairs }}{{ .Name }} = {{ .Value }}<br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" />{{ end }} |
||||
{{ if gt (len .Annotations) 0 }}<strong style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">Annotations</strong><br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" />{{ end }} |
||||
{{ range .Annotations.SortedPairs }}{{ .Name }} = {{ .Value }}<br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" />{{ end }} |
||||
<a href="{{ .GeneratorURL }}" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #348eda; text-decoration: underline; margin: 0;">Source</a><br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /> |
||||
</td> |
||||
</tr> |
||||
{{ end }} |
||||
|
||||
{{ if gt (len .Alerts.Resolved) 0 }} |
||||
{{ if gt (len .Alerts.Firing) 0 }} |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> |
||||
<br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /> |
||||
<hr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /> |
||||
<br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /> |
||||
</td> |
||||
</tr> |
||||
{{ end }} |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> |
||||
<strong style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">[{{ .Alerts.Resolved | len }}] Resolved</strong> |
||||
</td> |
||||
</tr> |
||||
{{ end }} |
||||
{{ range .Alerts.Resolved }} |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;" valign="top"> |
||||
<strong style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">Labels</strong><br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /> |
||||
{{ range .Labels.SortedPairs }}{{ .Name }} = {{ .Value }}<br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" />{{ end }} |
||||
{{ if gt (len .Annotations) 0 }}<strong style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">Annotations</strong><br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" />{{ end }} |
||||
{{ range .Annotations.SortedPairs }}{{ .Name }} = {{ .Value }}<br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" />{{ end }} |
||||
<a href="{{ .GeneratorURL }}" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; color: #348eda; text-decoration: underline; margin: 0;">Source</a><br style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;" /> |
||||
</td> |
||||
</tr> |
||||
{{ end }} |
||||
</table> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
|
||||
<div style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;"> |
||||
<table width="100%" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<tr style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; text-align: center; color: #999; margin: 0; padding: 0 0 20px;" align="center" valign="top"><a href="{{ .ExternalURL }}" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">Sent by {{ template "__alertmanager" . }}</a></td> |
||||
</tr> |
||||
</table> |
||||
</div></div> |
||||
</td> |
||||
<td style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;" valign="top"></td> |
||||
</tr> |
||||
</table> |
||||
|
||||
</body> |
||||
</html> |
||||
|
||||
{{ end }} |
||||
|
||||
{{ define "pushover.default.title" }}{{ template "__subject" . }}{{ end }} |
||||
{{ define "pushover.default.message" }}{{ .CommonAnnotations.SortedPairs.Values | join " " }} |
||||
{{ if gt (len .Alerts.Firing) 0 }} |
||||
Alerts Firing: |
||||
{{ template "__text_alert_list" .Alerts.Firing }} |
||||
{{ end }} |
||||
{{ if gt (len .Alerts.Resolved) 0 }} |
||||
Alerts Resolved: |
||||
{{ template "__text_alert_list" .Alerts.Resolved }} |
||||
{{ end }} |
||||
{{ end }} |
||||
{{ define "pushover.default.url" }}{{ template "__alertmanagerURL" . }}{{ end }} |
Loading…
Reference in new issue