mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AlertingNG: Add webhook notification channel (#33229)
Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com>
This commit is contained in:
@@ -274,6 +274,11 @@ func (am *Alertmanager) applyConfig(cfg *apimodels.PostableUserConfig) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
externalURL, err := url.Parse(am.Settings.AppURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
// Finally, build the integrations map using the receiver configuration and templates.
|
||||
integrationsMap, err := am.buildIntegrationsMap(cfg.AlertmanagerConfig.Receivers, tmpl)
|
||||
@@ -355,23 +360,21 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
|
||||
n NotificationChannel
|
||||
err error
|
||||
)
|
||||
externalURL, err := url.Parse(am.Settings.AppURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch r.Type {
|
||||
case "email":
|
||||
n, err = channels.NewEmailNotifier(cfg, externalURL, am.Settings.AppURL)
|
||||
n, err = channels.NewEmailNotifier(cfg, tmpl.ExternalURL) // Email notifier already has a default template.
|
||||
case "pagerduty":
|
||||
n, err = channels.NewPagerdutyNotifier(cfg, tmpl, externalURL)
|
||||
n, err = channels.NewPagerdutyNotifier(cfg, tmpl)
|
||||
case "slack":
|
||||
n, err = channels.NewSlackNotifier(cfg, tmpl, externalURL)
|
||||
n, err = channels.NewSlackNotifier(cfg, tmpl)
|
||||
case "telegram":
|
||||
n, err = channels.NewTelegramNotifier(cfg, tmpl, externalURL)
|
||||
n, err = channels.NewTelegramNotifier(cfg, tmpl)
|
||||
case "teams":
|
||||
n, err = channels.NewTeamsNotifier(cfg, tmpl, externalURL)
|
||||
n, err = channels.NewTeamsNotifier(cfg, tmpl)
|
||||
case "dingding":
|
||||
n, err = channels.NewDingDingNotifier(cfg, tmpl, externalURL)
|
||||
n, err = channels.NewDingDingNotifier(cfg, tmpl)
|
||||
case "webhook":
|
||||
n, err = channels.NewWebHookNotifier(cfg, tmpl)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
const defaultDingdingMsgType = "link"
|
||||
|
||||
// NewDingDingNotifier is the constructor for the Dingding notifier
|
||||
func NewDingDingNotifier(model *models.AlertNotification, t *template.Template, externalUrl *url.URL) (*DingDingNotifier, error) {
|
||||
func NewDingDingNotifier(model *models.AlertNotification, t *template.Template) (*DingDingNotifier, error) {
|
||||
if model.Settings == nil {
|
||||
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
|
||||
}
|
||||
@@ -40,19 +40,17 @@ func NewDingDingNotifier(model *models.AlertNotification, t *template.Template,
|
||||
Message: model.Settings.Get("message").MustString(`{{ template "default.message" .}}`),
|
||||
log: log.New("alerting.notifier.dingding"),
|
||||
tmpl: t,
|
||||
externalUrl: externalUrl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DingDingNotifier is responsible for sending alert notifications to ding ding.
|
||||
type DingDingNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
MsgType string
|
||||
URL string
|
||||
Message string
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
externalUrl *url.URL
|
||||
MsgType string
|
||||
URL string
|
||||
Message string
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// Notify sends the alert notification to dingding.
|
||||
@@ -61,14 +59,14 @@ func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
|
||||
|
||||
q := url.Values{
|
||||
"pc_slide": {"false"},
|
||||
"url": {dd.externalUrl.String()}, // TODO: should this be rule URL according to original?
|
||||
"url": {dd.tmpl.ExternalURL.String()}, // TODO: should this be rule URL according to original?
|
||||
}
|
||||
|
||||
// Use special link to auto open the message url outside of Dingding
|
||||
// Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9
|
||||
messageURL := "dingtalk://dingtalkclient/page/link?" + q.Encode()
|
||||
|
||||
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: dd.externalUrl}, as, gokit_log.NewNopLogger())
|
||||
data := notify.GetTemplateData(ctx, dd.tmpl, as, gokit_log.NewNopLogger())
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(dd.tmpl, data, &tmplErr)
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ func TestDingdingNotifier(t *testing.T) {
|
||||
tmpl, err := template.FromGlobs("templates/default.tmpl")
|
||||
require.NoError(t, err)
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
settings string
|
||||
@@ -108,9 +112,7 @@ func TestDingdingNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
pn, err := NewDingDingNotifier(m, tmpl, externalURL)
|
||||
pn, err := NewDingDingNotifier(m, tmpl)
|
||||
if c.expInitError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, c.expInitError.Error(), err.Error())
|
||||
|
||||
@@ -26,12 +26,11 @@ type EmailNotifier struct {
|
||||
SingleEmail bool
|
||||
log log.Logger
|
||||
externalUrl *url.URL
|
||||
appURL string
|
||||
}
|
||||
|
||||
// NewEmailNotifier is the constructor function
|
||||
// for the EmailNotifier.
|
||||
func NewEmailNotifier(model *models.AlertNotification, externalUrl *url.URL, appURL string) (*EmailNotifier, error) {
|
||||
func NewEmailNotifier(model *models.AlertNotification, externalUrl *url.URL) (*EmailNotifier, error) {
|
||||
if model.Settings == nil {
|
||||
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
|
||||
}
|
||||
@@ -50,7 +49,6 @@ func NewEmailNotifier(model *models.AlertNotification, externalUrl *url.URL, app
|
||||
NotifierBase: old_notifiers.NewNotifierBase(model),
|
||||
Addresses: addresses,
|
||||
SingleEmail: singleEmail,
|
||||
appURL: appURL,
|
||||
log: log.New("alerting.notifier.email"),
|
||||
externalUrl: externalUrl,
|
||||
}, nil
|
||||
@@ -74,8 +72,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.appURL, "/alerting/list"),
|
||||
"AlertPageUrl": path.Join(en.appURL, "/alerting/list?alertState=firing&view=state"),
|
||||
"RuleUrl": path.Join(en.externalUrl.String(), "/alerting/list"),
|
||||
"AlertPageUrl": path.Join(en.externalUrl.String(), "/alerting/list?alertState=firing&view=state"),
|
||||
},
|
||||
To: en.Addresses,
|
||||
SingleEmail: en.SingleEmail,
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestEmailNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewEmailNotifier(model, externalURL, "")
|
||||
_, err := NewEmailNotifier(model, externalURL)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestEmailNotifier(t *testing.T) {
|
||||
Type: "email",
|
||||
|
||||
Settings: settingsJSON,
|
||||
}, externalURL, "")
|
||||
}, externalURL)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -91,8 +91,8 @@ func TestEmailNotifier(t *testing.T) {
|
||||
"CommonLabels": template.KV{"alertname": "AlwaysFiring", "severity": "warning"},
|
||||
"CommonAnnotations": template.KV{"runbook_url": "http://fix.me"},
|
||||
"ExternalURL": "http://localhost",
|
||||
"RuleUrl": "/alerting/list",
|
||||
"AlertPageUrl": "/alerting/list?alertState=firing&view=state",
|
||||
"RuleUrl": "http:/localhost/alerting/list",
|
||||
"AlertPageUrl": "http:/localhost/alerting/list?alertState=firing&view=state",
|
||||
},
|
||||
}, expected)
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
@@ -42,11 +41,10 @@ type PagerdutyNotifier struct {
|
||||
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) {
|
||||
func NewPagerdutyNotifier(model *models.AlertNotification, t *template.Template) (*PagerdutyNotifier, error) {
|
||||
if model.Settings == nil {
|
||||
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
|
||||
}
|
||||
@@ -80,7 +78,6 @@ func NewPagerdutyNotifier(model *models.AlertNotification, t *template.Template,
|
||||
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
|
||||
}
|
||||
@@ -130,7 +127,7 @@ func (pn *PagerdutyNotifier) buildPagerdutyMessage(ctx context.Context, alerts m
|
||||
eventType = pagerDutyEventResolve
|
||||
}
|
||||
|
||||
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: pn.externalUrl}, as, gokit_log.NewNopLogger())
|
||||
data := notify.GetTemplateData(ctx, pn.tmpl, as, gokit_log.NewNopLogger())
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(pn.tmpl, data, &tmplErr)
|
||||
|
||||
@@ -145,12 +142,12 @@ func (pn *PagerdutyNotifier) buildPagerdutyMessage(ctx context.Context, alerts m
|
||||
|
||||
msg := &pagerDutyMessage{
|
||||
Client: "Grafana",
|
||||
ClientURL: pn.externalUrl.String(),
|
||||
ClientURL: pn.tmpl.ExternalURL.String(),
|
||||
RoutingKey: pn.Key,
|
||||
EventAction: eventType,
|
||||
DedupKey: key.Hash(),
|
||||
Links: []pagerDutyLink{{
|
||||
HRef: pn.externalUrl.String(),
|
||||
HRef: pn.tmpl.ExternalURL.String(),
|
||||
Text: "External URL",
|
||||
}},
|
||||
Description: getTitleFromTemplateData(data), // TODO: this can be configurable template.
|
||||
|
||||
@@ -24,6 +24,10 @@ func TestPagerdutyNotifier(t *testing.T) {
|
||||
tmpl, err := template.FromGlobs("templates/default.tmpl")
|
||||
require.NoError(t, err)
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
hostname, err := os.Hostname()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -143,9 +147,7 @@ func TestPagerdutyNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
pn, err := NewPagerdutyNotifier(m, tmpl, externalURL)
|
||||
pn, err := NewPagerdutyNotifier(m, tmpl)
|
||||
if c.expInitError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, c.expInitError.Error(), err.Error())
|
||||
|
||||
@@ -31,9 +31,8 @@ import (
|
||||
// alert notification to Slack.
|
||||
type SlackNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
externalUrl *url.URL
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
|
||||
URL *url.URL
|
||||
Username string
|
||||
@@ -54,7 +53,7 @@ var reRecipient *regexp.Regexp = regexp.MustCompile("^((@[a-z0-9][a-zA-Z0-9._-]*
|
||||
const slackAPIEndpoint = "https://slack.com/api/chat.postMessage"
|
||||
|
||||
// NewSlackNotifier is the constructor for the Slack notifier
|
||||
func NewSlackNotifier(model *models.AlertNotification, t *template.Template, externalUrl *url.URL) (*SlackNotifier, error) {
|
||||
func NewSlackNotifier(model *models.AlertNotification, t *template.Template) (*SlackNotifier, error) {
|
||||
if model.Settings == nil {
|
||||
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
|
||||
}
|
||||
@@ -127,7 +126,6 @@ func NewSlackNotifier(model *models.AlertNotification, t *template.Template, ext
|
||||
Fallback: model.Settings.Get("fallback").MustString(`{{ template "slack.default.title" . }}`),
|
||||
log: log.New("alerting.notifier.slack"),
|
||||
tmpl: t,
|
||||
externalUrl: externalUrl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -242,7 +240,7 @@ var sendSlackRequest = func(request *http.Request, logger log.Logger) error {
|
||||
}
|
||||
|
||||
func (sn *SlackNotifier) buildSlackMessage(ctx context.Context, as []*types.Alert) (*slackMessage, error) {
|
||||
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: sn.externalUrl}, as, gokit_log.NewNopLogger())
|
||||
data := notify.GetTemplateData(ctx, sn.tmpl, as, gokit_log.NewNopLogger())
|
||||
alerts := types.Alerts(as...)
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(sn.tmpl, data, &tmplErr)
|
||||
|
||||
@@ -25,6 +25,10 @@ func TestSlackNotifier(t *testing.T) {
|
||||
tmpl, err := template.FromGlobs("templates/default.tmpl")
|
||||
require.NoError(t, err)
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
settings string
|
||||
@@ -180,9 +184,7 @@ func TestSlackNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
pn, err := NewSlackNotifier(m, tmpl, externalURL)
|
||||
pn, err := NewSlackNotifier(m, tmpl)
|
||||
if c.expInitError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, c.expInitError.Error(), err.Error())
|
||||
|
||||
@@ -3,33 +3,32 @@ package channels
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
|
||||
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/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"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
// TeamsNotifier is responsible for sending
|
||||
// alert notifications to Microsoft teams.
|
||||
type TeamsNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
URL string
|
||||
Message string
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
externalUrl *url.URL
|
||||
URL string
|
||||
Message string
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewTeamsNotifier is the constructor for Teams notifier.
|
||||
func NewTeamsNotifier(model *models.AlertNotification, t *template.Template, externalUrl *url.URL) (*TeamsNotifier, error) {
|
||||
func NewTeamsNotifier(model *models.AlertNotification, t *template.Template) (*TeamsNotifier, error) {
|
||||
if model.Settings == nil {
|
||||
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
|
||||
}
|
||||
@@ -44,14 +43,13 @@ func NewTeamsNotifier(model *models.AlertNotification, t *template.Template, ext
|
||||
URL: u,
|
||||
Message: model.Settings.Get("message").MustString(`{{ template "default.message" .}}`),
|
||||
log: log.New("alerting.notifier.teams"),
|
||||
externalUrl: externalUrl,
|
||||
tmpl: t,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Notify send an alert notification to Microsoft teams.
|
||||
func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: tn.externalUrl}, as, gokit_log.NewNopLogger())
|
||||
data := notify.GetTemplateData(ctx, tn.tmpl, as, gokit_log.NewNopLogger())
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(tn.tmpl, data, &tmplErr)
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ func TestTeamsNotifier(t *testing.T) {
|
||||
tmpl, err := template.FromGlobs("templates/default.tmpl")
|
||||
require.NoError(t, err)
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
settings string
|
||||
@@ -132,9 +136,7 @@ func TestTeamsNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
pn, err := NewTeamsNotifier(m, tmpl, externalURL)
|
||||
pn, err := NewTeamsNotifier(m, tmpl)
|
||||
if c.expInitError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, c.expInitError.Error(), err.Error())
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/url"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
@@ -27,16 +26,15 @@ const (
|
||||
// alert notifications to Telegram.
|
||||
type TelegramNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
BotToken string
|
||||
ChatID string
|
||||
Message string
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
externalUrl *url.URL
|
||||
BotToken string
|
||||
ChatID string
|
||||
Message string
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
}
|
||||
|
||||
// NewTelegramNotifier is the constructor for the Telegram notifier
|
||||
func NewTelegramNotifier(model *models.AlertNotification, t *template.Template, externalUrl *url.URL) (*TelegramNotifier, error) {
|
||||
func NewTelegramNotifier(model *models.AlertNotification, t *template.Template) (*TelegramNotifier, error) {
|
||||
if model.Settings == nil {
|
||||
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
|
||||
}
|
||||
@@ -60,7 +58,6 @@ func NewTelegramNotifier(model *models.AlertNotification, t *template.Template,
|
||||
Message: message,
|
||||
tmpl: t,
|
||||
log: log.New("alerting.notifier.telegram"),
|
||||
externalUrl: externalUrl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -114,7 +111,7 @@ func (tn *TelegramNotifier) buildTelegramMessage(ctx context.Context, as []*type
|
||||
msg["chat_id"] = tn.ChatID
|
||||
msg["parse_mode"] = "html"
|
||||
|
||||
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: tn.externalUrl}, as, gokit_log.NewNopLogger())
|
||||
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: tn.tmpl.ExternalURL}, as, gokit_log.NewNopLogger())
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(tn.tmpl, data, &tmplErr)
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ func TestTelegramNotifier(t *testing.T) {
|
||||
tmpl, err := template.FromGlobs("templates/default.tmpl")
|
||||
require.NoError(t, err)
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
settings string
|
||||
@@ -105,9 +109,7 @@ func TestTelegramNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
pn, err := NewTelegramNotifier(m, tmpl, externalURL)
|
||||
pn, err := NewTelegramNotifier(m, tmpl)
|
||||
if c.expInitError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, c.expInitError.Error(), err.Error())
|
||||
|
||||
130
pkg/services/ngalert/notifier/channels/webhook.go
Normal file
130
pkg/services/ngalert/notifier/channels/webhook.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"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"
|
||||
)
|
||||
|
||||
// WebhookNotifier is responsible for sending
|
||||
// alert notifications as webhooks.
|
||||
type WebhookNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
URL string
|
||||
User string
|
||||
Password string
|
||||
HTTPMethod string
|
||||
MaxAlerts int
|
||||
log log.Logger
|
||||
tmpl *template.Template
|
||||
}
|
||||
|
||||
// NewWebHookNotifier is the constructor for
|
||||
// the WebHook notifier.
|
||||
func NewWebHookNotifier(model *models.AlertNotification, t *template.Template) (*WebhookNotifier, error) {
|
||||
url := model.Settings.Get("url").MustString()
|
||||
if url == "" {
|
||||
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
|
||||
}
|
||||
return &WebhookNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(model),
|
||||
URL: url,
|
||||
User: model.Settings.Get("username").MustString(),
|
||||
Password: model.DecryptedValue("password", model.Settings.Get("password").MustString()),
|
||||
HTTPMethod: model.Settings.Get("httpMethod").MustString("POST"),
|
||||
MaxAlerts: model.Settings.Get("maxAlerts").MustInt(0),
|
||||
log: log.New("alerting.notifier.webhook"),
|
||||
tmpl: t,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// webhookMessage defines the JSON object send to webhook endpoints.
|
||||
type webhookMessage struct {
|
||||
*template.Data
|
||||
|
||||
// The protocol version.
|
||||
Version string `json:"version"`
|
||||
GroupKey string `json:"groupKey"`
|
||||
TruncatedAlerts int `json:"truncatedAlerts"`
|
||||
|
||||
// Deprecated, to be removed in 8.1.
|
||||
// These are present to make migration a little less disruptive.
|
||||
Title string `json:"title"`
|
||||
State string `json:"state"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Notify implements the Notifier interface.
|
||||
func (wn *WebhookNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
groupKey, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
as, numTruncated := truncateAlerts(wn.MaxAlerts, as)
|
||||
data := notify.GetTemplateData(ctx, wn.tmpl, as, gokit_log.NewNopLogger())
|
||||
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(wn.tmpl, data, &tmplErr)
|
||||
msg := &webhookMessage{
|
||||
Version: "1",
|
||||
Data: data,
|
||||
GroupKey: groupKey.String(),
|
||||
TruncatedAlerts: numTruncated,
|
||||
Title: tmpl(`{{ template "default.title" . }}`),
|
||||
Message: tmpl(`{{ template "default.message" . }}`),
|
||||
}
|
||||
|
||||
if types.Alerts(as...).Status() == model.AlertFiring {
|
||||
msg.State = string(models.AlertStateAlerting)
|
||||
} else {
|
||||
msg.State = string(models.AlertStateOK)
|
||||
}
|
||||
|
||||
if tmplErr != nil {
|
||||
return false, fmt.Errorf("failed to template webhook message: %w", tmplErr)
|
||||
}
|
||||
|
||||
body, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
cmd := &models.SendWebhookSync{
|
||||
Url: wn.URL,
|
||||
User: wn.User,
|
||||
Password: wn.Password,
|
||||
Body: string(body),
|
||||
HttpMethod: wn.HTTPMethod,
|
||||
}
|
||||
|
||||
if err := bus.DispatchCtx(ctx, cmd); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func truncateAlerts(maxAlerts int, alerts []*types.Alert) ([]*types.Alert, int) {
|
||||
if maxAlerts > 0 && len(alerts) > maxAlerts {
|
||||
return alerts[:maxAlerts], len(alerts) - maxAlerts
|
||||
}
|
||||
|
||||
return alerts, 0
|
||||
}
|
||||
|
||||
func (wn *WebhookNotifier) SendResolved() bool {
|
||||
return !wn.GetDisableResolveMessage()
|
||||
}
|
||||
222
pkg/services/ngalert/notifier/channels/webhook_test.go
Normal file
222
pkg/services/ngalert/notifier/channels/webhook_test.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"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 TestWebhookNotifier(t *testing.T) {
|
||||
tmpl, err := template.FromGlobs("templates/default.tmpl")
|
||||
require.NoError(t, err)
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
settings string
|
||||
alerts []*types.Alert
|
||||
expMsg *webhookMessage
|
||||
expUrl string
|
||||
expUsername string
|
||||
expPassword string
|
||||
expHttpMethod string
|
||||
expInitError error
|
||||
expMsgError error
|
||||
}{
|
||||
{
|
||||
name: "Default config with one alert",
|
||||
settings: `{"url": "http://localhost/test"}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expUrl: "http://localhost/test",
|
||||
expHttpMethod: "POST",
|
||||
expMsg: &webhookMessage{
|
||||
Data: &template.Data{
|
||||
Receiver: "my_receiver",
|
||||
Status: "firing",
|
||||
Alerts: template.Alerts{
|
||||
{
|
||||
Status: "firing",
|
||||
Labels: template.KV{
|
||||
"alertname": "alert1",
|
||||
"lbl1": "val1",
|
||||
},
|
||||
Annotations: template.KV{
|
||||
"ann1": "annv1",
|
||||
},
|
||||
Fingerprint: "fac0861a85de433a",
|
||||
},
|
||||
},
|
||||
GroupLabels: template.KV{
|
||||
"alertname": "",
|
||||
},
|
||||
CommonLabels: template.KV{
|
||||
"alertname": "alert1",
|
||||
"lbl1": "val1",
|
||||
},
|
||||
CommonAnnotations: template.KV{
|
||||
"ann1": "annv1",
|
||||
},
|
||||
ExternalURL: "http://localhost",
|
||||
},
|
||||
Version: "1",
|
||||
GroupKey: "alertname",
|
||||
Title: "[FIRING:1] (val1)",
|
||||
State: "alerting",
|
||||
Message: "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
},
|
||||
expInitError: nil,
|
||||
expMsgError: nil,
|
||||
}, {
|
||||
name: "Custom config with multiple alerts",
|
||||
settings: `{
|
||||
"url": "http://localhost/test1",
|
||||
"username": "user1",
|
||||
"password": "mysecret",
|
||||
"httpMethod": "PUT",
|
||||
"maxAlerts": 2
|
||||
}`,
|
||||
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"},
|
||||
},
|
||||
}, {
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val3"},
|
||||
Annotations: model.LabelSet{"ann1": "annv3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expUrl: "http://localhost/test1",
|
||||
expHttpMethod: "PUT",
|
||||
expUsername: "user1",
|
||||
expPassword: "mysecret",
|
||||
expMsg: &webhookMessage{
|
||||
Data: &template.Data{
|
||||
Receiver: "my_receiver",
|
||||
Status: "firing",
|
||||
Alerts: template.Alerts{
|
||||
{
|
||||
Status: "firing",
|
||||
Labels: template.KV{
|
||||
"alertname": "alert1",
|
||||
"lbl1": "val1",
|
||||
},
|
||||
Annotations: template.KV{
|
||||
"ann1": "annv1",
|
||||
},
|
||||
Fingerprint: "fac0861a85de433a",
|
||||
}, {
|
||||
Status: "firing",
|
||||
Labels: template.KV{
|
||||
"alertname": "alert1",
|
||||
"lbl1": "val2",
|
||||
},
|
||||
Annotations: template.KV{
|
||||
"ann1": "annv2",
|
||||
},
|
||||
Fingerprint: "fab6861a85d5eeb5",
|
||||
},
|
||||
},
|
||||
GroupLabels: template.KV{
|
||||
"alertname": "",
|
||||
},
|
||||
CommonLabels: template.KV{
|
||||
"alertname": "alert1",
|
||||
},
|
||||
CommonAnnotations: template.KV{},
|
||||
ExternalURL: "http://localhost",
|
||||
},
|
||||
Version: "1",
|
||||
GroupKey: "alertname",
|
||||
TruncatedAlerts: 1,
|
||||
Title: "[FIRING:2] ",
|
||||
State: "alerting",
|
||||
Message: "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \nLabels:\n - alertname = alert1\n - lbl1 = val2\nAnnotations:\n - ann1 = annv2\nSource: \n\n\n\n\n",
|
||||
},
|
||||
expInitError: nil,
|
||||
expMsgError: nil,
|
||||
}, {
|
||||
name: "Error in initing",
|
||||
settings: `{}`,
|
||||
expInitError: alerting.ValidationError{Reason: "Could not find url property in settings"},
|
||||
},
|
||||
}
|
||||
|
||||
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: "webhook_testing",
|
||||
Type: "webhook",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
pn, err := NewWebHookNotifier(m, tmpl)
|
||||
if c.expInitError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, c.expInitError.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
var payload *models.SendWebhookSync
|
||||
bus.AddHandlerCtx("test", func(ctx context.Context, webhook *models.SendWebhookSync) error {
|
||||
payload = webhook
|
||||
return nil
|
||||
})
|
||||
|
||||
ctx := notify.WithGroupKey(context.Background(), "alertname")
|
||||
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""})
|
||||
ctx = notify.WithReceiverName(ctx, "my_receiver")
|
||||
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.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
expBody, err := json.Marshal(c.expMsg)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.JSONEq(t, string(expBody), payload.Body)
|
||||
require.Equal(t, c.expUrl, payload.Url)
|
||||
require.Equal(t, c.expUsername, payload.User)
|
||||
require.Equal(t, c.expPassword, payload.Password)
|
||||
require.Equal(t, c.expHttpMethod, payload.HttpMethod)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user