AlertingNG: Add webhook notification channel (#33229)

Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com>
pull/33289/head^2
Ganesh Vernekar 4 years ago committed by GitHub
parent 67f6611d85
commit d66a5e65a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      pkg/services/ngalert/notifier/alertmanager.go
  2. 18
      pkg/services/ngalert/notifier/channels/dingding.go
  3. 8
      pkg/services/ngalert/notifier/channels/dingding_test.go
  4. 8
      pkg/services/ngalert/notifier/channels/email.go
  5. 8
      pkg/services/ngalert/notifier/channels/email_test.go
  6. 11
      pkg/services/ngalert/notifier/channels/pagerduty.go
  7. 8
      pkg/services/ngalert/notifier/channels/pagerduty_test.go
  8. 10
      pkg/services/ngalert/notifier/channels/slack.go
  9. 8
      pkg/services/ngalert/notifier/channels/slack_test.go
  10. 24
      pkg/services/ngalert/notifier/channels/teams.go
  11. 8
      pkg/services/ngalert/notifier/channels/teams_test.go
  12. 17
      pkg/services/ngalert/notifier/channels/telegram.go
  13. 8
      pkg/services/ngalert/notifier/channels/telegram_test.go
  14. 130
      pkg/services/ngalert/notifier/channels/webhook.go
  15. 222
      pkg/services/ngalert/notifier/channels/webhook_test.go

@ -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())

@ -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()
}

@ -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)
})
}
}
Loading…
Cancel
Save