mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Opsgenie notification channel (#34418)
* Alerting: Opsgenie notification channel This translate the opsgenie notification channel from the old alerting system to the new alerting system with a few changes: - The tag system has been replaced in favour of annotation. - TBD - TBD Signed-off-by: Josue Abreu <josue@grafana.com> * Fix template URL * Bugfig: dont send resolved when autoClose is false Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com> * Fix integration tests Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com> * Fix URLs in all other channels Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com> Co-authored-by: Ganesh Vernekar <ganeshvern@gmail.com>
This commit is contained in:
parent
615de9bf34
commit
7b04278834
@ -440,6 +440,8 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
|
||||
n, err = channels.NewLineNotifier(cfg, tmpl)
|
||||
case "threema":
|
||||
n, err = channels.NewThreemaNotifier(cfg, tmpl)
|
||||
case "opsgenie":
|
||||
n, err = channels.NewOpsgenieNotifier(cfg, tmpl)
|
||||
default:
|
||||
return nil, fmt.Errorf("notifier %s is not supported", r.Type)
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
package notifier
|
||||
|
||||
import "github.com/grafana/grafana/pkg/services/alerting"
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels"
|
||||
)
|
||||
|
||||
// GetAvailableNotifiers returns the metadata of all the notification channels that can be configured.
|
||||
func GetAvailableNotifiers() []*alerting.NotifierPlugin {
|
||||
@ -745,5 +748,61 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "opsgenie",
|
||||
Name: "OpsGenie",
|
||||
Description: "Sends notifications to OpsGenie",
|
||||
Heading: "OpsGenie settings",
|
||||
Options: []alerting.NotifierOption{
|
||||
{
|
||||
Label: "API Key",
|
||||
Element: alerting.ElementTypeInput,
|
||||
InputType: alerting.InputTypeText,
|
||||
Placeholder: "OpsGenie API Key",
|
||||
PropertyName: "apiKey",
|
||||
Required: true,
|
||||
Secure: true,
|
||||
},
|
||||
{
|
||||
Label: "Alert API Url",
|
||||
Element: alerting.ElementTypeInput,
|
||||
InputType: alerting.InputTypeText,
|
||||
Placeholder: "https://api.opsgenie.com/v2/alerts",
|
||||
PropertyName: "apiUrl",
|
||||
Required: true,
|
||||
},
|
||||
{
|
||||
Label: "Auto close incidents",
|
||||
Element: alerting.ElementTypeCheckbox,
|
||||
Description: "Automatically close alerts in OpsGenie once the alert goes back to ok.",
|
||||
PropertyName: "autoClose",
|
||||
}, {
|
||||
Label: "Override priority",
|
||||
Element: alerting.ElementTypeCheckbox,
|
||||
Description: "Allow the alert priority to be set using the og_priority annotation",
|
||||
PropertyName: "overridePriority",
|
||||
},
|
||||
{
|
||||
Label: "Send notification tags as",
|
||||
Element: alerting.ElementTypeSelect,
|
||||
SelectOptions: []alerting.SelectOption{
|
||||
{
|
||||
Value: channels.OpsgenieSendTags,
|
||||
Label: "Tags",
|
||||
},
|
||||
{
|
||||
Value: channels.OpsgenieSendDetails,
|
||||
Label: "Extra Properties",
|
||||
},
|
||||
{
|
||||
Value: channels.OpsgenieSendBoth,
|
||||
Label: "Tags & Extra Properties",
|
||||
},
|
||||
},
|
||||
Description: "Send the common annotations to Opsgenie as either Extra Properties, Tags or both",
|
||||
PropertyName: "sendTagsAs",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
@ -65,9 +64,14 @@ type DingDingNotifier struct {
|
||||
func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
dd.log.Info("Sending dingding")
|
||||
|
||||
ruleURL, err := joinUrlPath(dd.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
q := url.Values{
|
||||
"pc_slide": {"false"},
|
||||
"url": {path.Join(dd.tmpl.ExternalURL.String(), "/alerting/list")},
|
||||
"url": {ruleURL},
|
||||
}
|
||||
|
||||
// Use special link to auto open the message url outside of Dingding
|
||||
|
@ -47,7 +47,7 @@ func TestDingdingNotifier(t *testing.T) {
|
||||
expMsg: map[string]interface{}{
|
||||
"msgtype": "link",
|
||||
"link": map[string]interface{}{
|
||||
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%2Falerting%2Flist",
|
||||
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%2Falerting%2Flist",
|
||||
"text": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"title": "[FIRING:1] (val1)",
|
||||
},
|
||||
@ -77,7 +77,7 @@ func TestDingdingNotifier(t *testing.T) {
|
||||
expMsg: map[string]interface{}{
|
||||
"actionCard": map[string]interface{}{
|
||||
"singleTitle": "More",
|
||||
"singleURL": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%2Falerting%2Flist",
|
||||
"singleURL": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%2Falerting%2Flist",
|
||||
"text": "2 alerts are firing, 0 are resolved",
|
||||
"title": "[FIRING:2] ",
|
||||
},
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -85,12 +83,10 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
color, _ := strconv.ParseInt(strings.TrimLeft(getAlertStatusColor(alerts.Status()), "#"), 16, 0)
|
||||
embed.Set("color", color)
|
||||
|
||||
u, err := url.Parse(d.tmpl.ExternalURL.String())
|
||||
ruleURL, err := joinUrlPath(d.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse external URL: %w", err)
|
||||
return false, err
|
||||
}
|
||||
u.Path = path.Join(u.Path, "/alerting/list")
|
||||
ruleURL := u.String()
|
||||
embed.Set("url", ruleURL)
|
||||
|
||||
bodyJSON.Set("embeds", []interface{}{embed})
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
@ -69,6 +68,10 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) (
|
||||
})
|
||||
}
|
||||
|
||||
ruleURL, err := joinUrlPath(gcn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Add a button widget (link to Grafana).
|
||||
widgets = append(widgets, buttonWidget{
|
||||
Buttons: []button{
|
||||
@ -77,7 +80,7 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) (
|
||||
Text: "OPEN IN GRAFANA",
|
||||
OnClick: onClick{
|
||||
OpenLink: openLink{
|
||||
URL: path.Join(gcn.tmpl.ExternalURL.String(), "/alerting/list"),
|
||||
URL: ruleURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -68,7 +68,7 @@ func TestGoogleChatNotifier(t *testing.T) {
|
||||
Text: "OPEN IN GRAFANA",
|
||||
OnClick: onClick{
|
||||
OpenLink: openLink{
|
||||
URL: "http:/localhost/alerting/list",
|
||||
URL: "http://localhost/alerting/list",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -128,7 +128,7 @@ func TestGoogleChatNotifier(t *testing.T) {
|
||||
Text: "OPEN IN GRAFANA",
|
||||
OnClick: onClick{
|
||||
OpenLink: openLink{
|
||||
URL: "http:/localhost/alerting/list",
|
||||
URL: "http://localhost/alerting/list",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -3,7 +3,6 @@ package channels
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
@ -76,7 +75,12 @@ func (kn *KafkaNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
bodyJSON.Set("description", tmpl(`{{ template "default.title" . }}`))
|
||||
bodyJSON.Set("client", "Grafana")
|
||||
bodyJSON.Set("details", tmpl(`{{ template "default.message" . }}`))
|
||||
bodyJSON.Set("client_url", path.Join(kn.tmpl.ExternalURL.String(), "/alerting/list"))
|
||||
|
||||
ruleURL, err := joinUrlPath(kn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
bodyJSON.Set("client_url", ruleURL)
|
||||
|
||||
groupKey, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
|
@ -52,7 +52,7 @@ func TestKafkaNotifier(t *testing.T) {
|
||||
"value": {
|
||||
"alert_state": "alerting",
|
||||
"client": "Grafana",
|
||||
"client_url": "http:/localhost/alerting/list",
|
||||
"client_url": "http://localhost/alerting/list",
|
||||
"description": "[FIRING:1] (val1)",
|
||||
"details": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"incident_key": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733"
|
||||
@ -88,7 +88,7 @@ func TestKafkaNotifier(t *testing.T) {
|
||||
"value": {
|
||||
"alert_state": "alerting",
|
||||
"client": "Grafana",
|
||||
"client_url": "http:/localhost/alerting/list",
|
||||
"client_url": "http://localhost/alerting/list",
|
||||
"description": "[FIRING:2] ",
|
||||
"details": "\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",
|
||||
"incident_key": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733"
|
||||
|
227
pkg/services/ngalert/notifier/channels/opsgenie.go
Normal file
227
pkg/services/ngalert/notifier/channels/opsgenie.go
Normal file
@ -0,0 +1,227 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
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/components/simplejson"
|
||||
"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/grafana/grafana/pkg/services/ngalert/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
OpsgenieSendTags = "tags"
|
||||
OpsgenieSendDetails = "details"
|
||||
OpsgenieSendBoth = "both"
|
||||
)
|
||||
|
||||
var (
|
||||
OpsgenieAlertURL = "https://api.opsgenie.com/v2/alerts"
|
||||
ValidPriorities = map[string]bool{"P1": true, "P2": true, "P3": true, "P4": true, "P5": true}
|
||||
)
|
||||
|
||||
// OpsgenieNotifier is responsible for sending alert notifications to Opsgenie.
|
||||
type OpsgenieNotifier struct {
|
||||
old_notifiers.NotifierBase
|
||||
APIKey string
|
||||
APIUrl string
|
||||
AutoClose bool
|
||||
OverridePriority bool
|
||||
SendTagsAs string
|
||||
tmpl *template.Template
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewOpsgenieNotifier is the constructor for the Opsgenie notifier
|
||||
func NewOpsgenieNotifier(model *NotificationChannelConfig, t *template.Template) (*OpsgenieNotifier, error) {
|
||||
autoClose := model.Settings.Get("autoClose").MustBool(true)
|
||||
overridePriority := model.Settings.Get("overridePriority").MustBool(true)
|
||||
apiKey := model.DecryptedValue("apiKey", model.Settings.Get("apiKey").MustString())
|
||||
apiURL := model.Settings.Get("apiUrl").MustString()
|
||||
if apiKey == "" {
|
||||
return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"}
|
||||
}
|
||||
if apiURL == "" {
|
||||
apiURL = OpsgenieAlertURL
|
||||
}
|
||||
|
||||
sendTagsAs := model.Settings.Get("sendTagsAs").MustString(OpsgenieSendTags)
|
||||
if sendTagsAs != OpsgenieSendTags && sendTagsAs != OpsgenieSendDetails && sendTagsAs != OpsgenieSendBoth {
|
||||
return nil, alerting.ValidationError{
|
||||
Reason: fmt.Sprintf("Invalid value for sendTagsAs: %q", sendTagsAs),
|
||||
}
|
||||
}
|
||||
|
||||
return &OpsgenieNotifier{
|
||||
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
|
||||
Uid: model.UID,
|
||||
Name: model.Name,
|
||||
Type: model.Type,
|
||||
DisableResolveMessage: model.DisableResolveMessage,
|
||||
Settings: model.Settings,
|
||||
}),
|
||||
APIKey: apiKey,
|
||||
APIUrl: apiURL,
|
||||
AutoClose: autoClose,
|
||||
OverridePriority: overridePriority,
|
||||
SendTagsAs: sendTagsAs,
|
||||
tmpl: t,
|
||||
log: log.New("alerting.notifier." + model.Name),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Notify sends an alert notification to Opsgenie
|
||||
func (on *OpsgenieNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
on.log.Debug("Executing Opsgenie notification", "notification", on.Name)
|
||||
|
||||
alerts := types.Alerts(as...)
|
||||
if alerts.Status() == model.AlertResolved && !on.SendResolved() {
|
||||
on.log.Debug("Not sending a trigger to Opsgenie", "status", alerts.Status(), "auto resolve", on.SendResolved())
|
||||
return true, nil
|
||||
}
|
||||
|
||||
bodyJSON, url, err := on.buildOpsgenieMessage(ctx, alerts, as)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("build Opsgenie message: %w", err)
|
||||
}
|
||||
|
||||
if url == "" {
|
||||
// Resolved alert with no auto close.
|
||||
// Hence skip sending anything.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
body, err := json.Marshal(bodyJSON)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("marshal json: %w", err)
|
||||
}
|
||||
|
||||
cmd := &models.SendWebhookSync{
|
||||
Url: url,
|
||||
Body: string(body),
|
||||
HttpMethod: http.MethodPost,
|
||||
HttpHeader: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": fmt.Sprintf("GenieKey %s", on.APIKey),
|
||||
},
|
||||
}
|
||||
|
||||
if err := bus.DispatchCtx(ctx, cmd); err != nil {
|
||||
return false, fmt.Errorf("send notification to Opsgenie: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (on *OpsgenieNotifier) buildOpsgenieMessage(ctx context.Context, alerts model.Alerts, as []*types.Alert) (payload *simplejson.Json, apiURL string, err error) {
|
||||
key, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var (
|
||||
alias = key.Hash()
|
||||
bodyJSON = simplejson.New()
|
||||
details = simplejson.New()
|
||||
)
|
||||
|
||||
if alerts.Status() == model.AlertResolved {
|
||||
// For resolved notification, we only need the source.
|
||||
// Don't need to run other templates.
|
||||
if on.AutoClose {
|
||||
bodyJSON := simplejson.New()
|
||||
bodyJSON.Set("source", "Grafana")
|
||||
apiURL = fmt.Sprintf("%s/%s/close?identifierType=alias", on.APIUrl, alias)
|
||||
return bodyJSON, apiURL, nil
|
||||
}
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
ruleURL, err := joinUrlPath(on.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
data := notify.GetTemplateData(ctx, on.tmpl, as, gokit_log.NewLogfmtLogger(logging.NewWrapper(on.log)))
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(on.tmpl, data, &tmplErr)
|
||||
|
||||
title := tmpl(`{{ template "default.title" . }}`)
|
||||
description := fmt.Sprintf(
|
||||
"%s\n%s\n\n%s",
|
||||
tmpl(`{{ template "default.title" . }}`),
|
||||
ruleURL,
|
||||
tmpl(`{{ template "default.message" . }}`),
|
||||
)
|
||||
|
||||
var priority string
|
||||
|
||||
// In the new alerting system we've moved away from the grafana-tags. Instead, annotations on the rule itself should be used.
|
||||
annotations := make(map[string]string, len(data.CommonAnnotations))
|
||||
for k, v := range data.CommonAnnotations {
|
||||
annotations[k] = tmpl(v)
|
||||
|
||||
if k == "og_priority" {
|
||||
if ValidPriorities[v] {
|
||||
priority = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bodyJSON.Set("message", title)
|
||||
bodyJSON.Set("source", "Grafana")
|
||||
bodyJSON.Set("alias", alias)
|
||||
bodyJSON.Set("description", description)
|
||||
details.Set("url", ruleURL)
|
||||
|
||||
if on.sendDetails() {
|
||||
for k, v := range annotations {
|
||||
details.Set(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
tags := make([]string, 0, len(annotations))
|
||||
if on.sendTags() {
|
||||
for k, v := range annotations {
|
||||
tags = append(tags, fmt.Sprintf("%s:%s", k, v))
|
||||
}
|
||||
}
|
||||
|
||||
if priority != "" && on.OverridePriority {
|
||||
bodyJSON.Set("priority", priority)
|
||||
}
|
||||
|
||||
bodyJSON.Set("tags", tags)
|
||||
bodyJSON.Set("details", details)
|
||||
apiURL = on.APIUrl
|
||||
|
||||
if tmplErr != nil {
|
||||
return nil, "", fmt.Errorf("failed to template Opsgenie message: %w", tmplErr)
|
||||
}
|
||||
|
||||
return bodyJSON, apiURL, err
|
||||
}
|
||||
|
||||
func (on *OpsgenieNotifier) SendResolved() bool {
|
||||
return !on.GetDisableResolveMessage()
|
||||
}
|
||||
|
||||
func (on *OpsgenieNotifier) sendDetails() bool {
|
||||
return on.SendTagsAs == OpsgenieSendDetails || on.SendTagsAs == OpsgenieSendBoth
|
||||
}
|
||||
|
||||
func (on *OpsgenieNotifier) sendTags() bool {
|
||||
return on.SendTagsAs == OpsgenieSendTags || on.SendTagsAs == OpsgenieSendBoth
|
||||
}
|
206
pkg/services/ngalert/notifier/channels/opsgenie_test.go
Normal file
206
pkg/services/ngalert/notifier/channels/opsgenie_test.go
Normal file
@ -0,0 +1,206 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"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 TestOpsgenieNotifier(t *testing.T) {
|
||||
tmpl := templateForTests(t)
|
||||
|
||||
externalURL, err := url.Parse("http://localhost")
|
||||
require.NoError(t, err)
|
||||
tmpl.ExternalURL = externalURL
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
settings string
|
||||
alerts []*types.Alert
|
||||
expMsg string
|
||||
expInitError error
|
||||
expMsgError error
|
||||
}{
|
||||
{
|
||||
name: "Default config with one alert",
|
||||
settings: `{"apiKey": "abcdefgh0123456789"}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: `{
|
||||
"alias": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||
"description": "[FIRING:1] (val1)\nhttp://localhost/alerting/list\n\n\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"details": {
|
||||
"url": "http://localhost/alerting/list"
|
||||
},
|
||||
"message": "[FIRING:1] (val1)",
|
||||
"source": "Grafana",
|
||||
"tags": ["ann1:annv1"]
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "Default config with one alert and send tags as tags",
|
||||
settings: `{
|
||||
"apiKey": "abcdefgh0123456789",
|
||||
"sendTagsAs": "tags"
|
||||
}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: `{
|
||||
"alias": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||
"description": "[FIRING:1] (val1)\nhttp://localhost/alerting/list\n\n\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"details": {
|
||||
"url": "http://localhost/alerting/list"
|
||||
},
|
||||
"message": "[FIRING:1] (val1)",
|
||||
"source": "Grafana",
|
||||
"tags": ["ann1:annv1"]
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "Default config with one alert and send tags as details",
|
||||
settings: `{
|
||||
"apiKey": "abcdefgh0123456789",
|
||||
"sendTagsAs": "details"
|
||||
}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: `{
|
||||
"alias": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||
"description": "[FIRING:1] (val1)\nhttp://localhost/alerting/list\n\n\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"details": {
|
||||
"ann1": "annv1",
|
||||
"url": "http://localhost/alerting/list"
|
||||
},
|
||||
"message": "[FIRING:1] (val1)",
|
||||
"source": "Grafana",
|
||||
"tags": []
|
||||
}`,
|
||||
},
|
||||
{
|
||||
name: "Custom config with multiple alerts and send tags as both details and tag",
|
||||
settings: `{
|
||||
"apiKey": "abcdefgh0123456789",
|
||||
"sendTagsAs": "both"
|
||||
}`,
|
||||
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": "annv1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: `{
|
||||
"alias": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||
"description": "[FIRING:2] \nhttp://localhost/alerting/list\n\n\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \nLabels:\n - alertname = alert1\n - lbl1 = val2\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
"details": {
|
||||
"ann1": "annv1",
|
||||
"url": "http://localhost/alerting/list"
|
||||
},
|
||||
"message": "[FIRING:2] ",
|
||||
"source": "Grafana",
|
||||
"tags": ["ann1:annv1"]
|
||||
}`,
|
||||
expInitError: nil,
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Resolved is not sent when auto close is false",
|
||||
settings: `{"apiKey": "abcdefgh0123456789", "autoClose": false}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1"},
|
||||
EndsAt: time.Now().Add(-1 * time.Minute),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Error when incorrect settings",
|
||||
settings: `{}`,
|
||||
expInitError: alerting.ValidationError{Reason: "Could not find api key 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 := &NotificationChannelConfig{
|
||||
Name: "opsgenie_testing",
|
||||
Type: "opsgenie",
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
pn, err := NewOpsgenieNotifier(m, tmpl)
|
||||
if c.expInitError != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, c.expInitError.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
body := "<not-sent>"
|
||||
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)
|
||||
|
||||
if c.expMsg == "" {
|
||||
// No notification was expected.
|
||||
require.Equal(t, "<not-sent>", body)
|
||||
} else {
|
||||
require.JSONEq(t, c.expMsg, body)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -5,8 +5,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
@ -130,12 +128,10 @@ func (pn *PushoverNotifier) SendResolved() bool {
|
||||
func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Alert) (map[string]string, bytes.Buffer, error) {
|
||||
var b bytes.Buffer
|
||||
|
||||
u, err := url.Parse(pn.tmpl.ExternalURL.String())
|
||||
ruleURL, err := joinUrlPath(pn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return nil, b, fmt.Errorf("failed to parse ")
|
||||
return nil, b, err
|
||||
}
|
||||
u.Path = path.Join(u.Path, "/alerting/list")
|
||||
ruleURL := u.String()
|
||||
|
||||
alerts := types.Alerts(as...)
|
||||
|
||||
|
@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -109,12 +107,10 @@ func (sn *SensuGoNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
|
||||
handlers = []string{sn.Handler}
|
||||
}
|
||||
|
||||
u, err := url.Parse(sn.tmpl.ExternalURL.String())
|
||||
ruleURL, err := joinUrlPath(sn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to parse external URL: %w", err)
|
||||
return false, err
|
||||
}
|
||||
u.Path = path.Join(u.Path, "/alerting/list")
|
||||
ruleURL := u.String()
|
||||
bodyMsgType := map[string]interface{}{
|
||||
"entity": map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
@ -251,6 +250,11 @@ func (sn *SlackNotifier) buildSlackMessage(ctx context.Context, as []*types.Aler
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(sn.tmpl, data, &tmplErr)
|
||||
|
||||
ruleURL, err := joinUrlPath(sn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &slackMessage{
|
||||
Channel: tmpl(sn.Recipient),
|
||||
Username: tmpl(sn.Username),
|
||||
@ -264,7 +268,7 @@ func (sn *SlackNotifier) buildSlackMessage(ctx context.Context, as []*types.Aler
|
||||
Footer: "Grafana v" + setting.BuildVersion,
|
||||
FooterIcon: FooterIconURL,
|
||||
Ts: time.Now().Unix(),
|
||||
TitleLink: path.Join(sn.tmpl.ExternalURL.String(), "/alerting/list"),
|
||||
TitleLink: ruleURL,
|
||||
Text: tmpl(sn.Text),
|
||||
Fields: nil, // TODO. Should be a config.
|
||||
},
|
||||
|
@ -56,7 +56,7 @@ func TestSlackNotifier(t *testing.T) {
|
||||
Attachments: []attachment{
|
||||
{
|
||||
Title: "[FIRING:1] (val1)",
|
||||
TitleLink: "http:/localhost/alerting/list",
|
||||
TitleLink: "http://localhost/alerting/list",
|
||||
Text: "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
Fallback: "[FIRING:1] (val1)",
|
||||
Fields: nil,
|
||||
@ -92,7 +92,7 @@ func TestSlackNotifier(t *testing.T) {
|
||||
Attachments: []attachment{
|
||||
{
|
||||
Title: "[FIRING:1] (val1)",
|
||||
TitleLink: "http:/localhost/alerting/list",
|
||||
TitleLink: "http://localhost/alerting/list",
|
||||
Text: "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||
Fallback: "[FIRING:1] (val1)",
|
||||
Fields: nil,
|
||||
@ -135,7 +135,7 @@ func TestSlackNotifier(t *testing.T) {
|
||||
Attachments: []attachment{
|
||||
{
|
||||
Title: "2 firing, 0 resolved",
|
||||
TitleLink: "http:/localhost/alerting/list",
|
||||
TitleLink: "http://localhost/alerting/list",
|
||||
Text: "\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",
|
||||
Fallback: "2 firing, 0 resolved",
|
||||
Fields: nil,
|
||||
|
@ -3,7 +3,6 @@ package channels
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"path"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
"github.com/pkg/errors"
|
||||
@ -61,6 +60,11 @@ func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
var tmplErr error
|
||||
tmpl := notify.TmplText(tn.tmpl, data, &tmplErr)
|
||||
|
||||
ruleURL, err := joinUrlPath(tn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
title := tmpl(`{{ template "default.title" . }}`)
|
||||
body := map[string]interface{}{
|
||||
"@type": "MessageCard",
|
||||
@ -84,7 +88,7 @@ func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
"targets": []map[string]interface{}{
|
||||
{
|
||||
"os": "default",
|
||||
"uri": path.Join(tn.tmpl.ExternalURL.String(), "/alerting/list"),
|
||||
"uri": ruleURL,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -61,7 +61,7 @@ func TestTeamsNotifier(t *testing.T) {
|
||||
"@context": "http://schema.org",
|
||||
"@type": "OpenUri",
|
||||
"name": "View Rule",
|
||||
"targets": []map[string]interface{}{{"os": "default", "uri": "http:/localhost/alerting/list"}},
|
||||
"targets": []map[string]interface{}{{"os": "default", "uri": "http://localhost/alerting/list"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -103,7 +103,7 @@ func TestTeamsNotifier(t *testing.T) {
|
||||
"@context": "http://schema.org",
|
||||
"@type": "OpenUri",
|
||||
"name": "View Rule",
|
||||
"targets": []map[string]interface{}{{"os": "default", "uri": "http:/localhost/alerting/list"}},
|
||||
"targets": []map[string]interface{}{{"os": "default", "uri": "http://localhost/alerting/list"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@ -110,3 +111,14 @@ var sendHTTPRequest = func(ctx context.Context, url *url.URL, cfg httpCfg, logge
|
||||
logger.Debug("Sending HTTP request succeeded", "url", request.URL.String(), "statusCode", resp.Status)
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
func joinUrlPath(base, additionalPath string) (string, error) {
|
||||
u, err := url.Parse(base)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse URL: %w", err)
|
||||
}
|
||||
|
||||
u.Path = path.Join(u.Path, additionalPath)
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package channels
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -92,7 +91,12 @@ func (vn *VictoropsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bo
|
||||
bodyJSON.Set("timestamp", time.Now().Unix())
|
||||
bodyJSON.Set("state_message", tmpl(`{{ template "default.message" . }}`))
|
||||
bodyJSON.Set("monitoring_tool", "Grafana v"+setting.BuildVersion)
|
||||
bodyJSON.Set("alert_url", path.Join(vn.tmpl.ExternalURL.String(), "/alerting/list"))
|
||||
|
||||
ruleURL, err := joinUrlPath(vn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
bodyJSON.Set("alert_url", ruleURL)
|
||||
|
||||
b, err := bodyJSON.MarshalJSON()
|
||||
if err != nil {
|
||||
|
@ -43,7 +43,7 @@ func TestVictoropsNotifier(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expMsg: `{
|
||||
"alert_url": "http:/localhost/alerting/list",
|
||||
"alert_url": "http://localhost/alerting/list",
|
||||
"entity_display_name": "[FIRING:1] (val1)",
|
||||
"entity_id": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||
"message_type": "CRITICAL",
|
||||
@ -69,7 +69,7 @@ func TestVictoropsNotifier(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expMsg: `{
|
||||
"alert_url": "http:/localhost/alerting/list",
|
||||
"alert_url": "http://localhost/alerting/list",
|
||||
"entity_display_name": "[FIRING:2] ",
|
||||
"entity_id": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||
"message_type": "CRITICAL",
|
||||
|
@ -1462,6 +1462,108 @@ var expAvailableChannelJsonOutput = `
|
||||
"secure": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "opsgenie",
|
||||
"name": "OpsGenie",
|
||||
"heading": "OpsGenie settings",
|
||||
"description": "Sends notifications to OpsGenie",
|
||||
"info": "",
|
||||
"options": [
|
||||
{
|
||||
"element": "input",
|
||||
"inputType": "text",
|
||||
"label": "API Key",
|
||||
"description": "",
|
||||
"placeholder": "OpsGenie API Key",
|
||||
"propertyName": "apiKey",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": true,
|
||||
"validationRule": "",
|
||||
"secure": true
|
||||
},
|
||||
{
|
||||
"element": "input",
|
||||
"inputType": "text",
|
||||
"label": "Alert API Url",
|
||||
"description": "",
|
||||
"placeholder": "https://api.opsgenie.com/v2/alerts",
|
||||
"propertyName": "apiUrl",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": true,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "checkbox",
|
||||
"inputType": "",
|
||||
"label": "Auto close incidents",
|
||||
"description": "Automatically close alerts in OpsGenie once the alert goes back to ok.",
|
||||
"placeholder": "",
|
||||
"propertyName": "autoClose",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "checkbox",
|
||||
"inputType": "",
|
||||
"label": "Override priority",
|
||||
"description": "Allow the alert priority to be set using the og_priority annotation",
|
||||
"placeholder": "",
|
||||
"propertyName": "overridePriority",
|
||||
"selectOptions": null,
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
},
|
||||
{
|
||||
"element": "select",
|
||||
"inputType": "",
|
||||
"label": "Send notification tags as",
|
||||
"description": "Send the common annotations to Opsgenie as either Extra Properties, Tags or both",
|
||||
"placeholder": "",
|
||||
"propertyName": "sendTagsAs",
|
||||
"selectOptions": [
|
||||
{
|
||||
"value": "tags",
|
||||
"label": "Tags"
|
||||
},
|
||||
{
|
||||
"value": "details",
|
||||
"label": "Extra Properties"
|
||||
},
|
||||
{
|
||||
"value": "both",
|
||||
"label": "Tags & Extra Properties"
|
||||
}
|
||||
],
|
||||
"showWhen": {
|
||||
"field": "",
|
||||
"is": ""
|
||||
},
|
||||
"required": false,
|
||||
"validationRule": "",
|
||||
"secure": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
`
|
||||
|
@ -749,7 +749,7 @@ var expNotifications = map[string][]string{
|
||||
"attachments": [
|
||||
{
|
||||
"title": "Integration Test [FIRING:1] SlackAlert1 (UID_SlackAlert1)",
|
||||
"title_link": "http:/localhost:3000/alerting/list",
|
||||
"title_link": "http://localhost:3000/alerting/list",
|
||||
"text": "Integration Test ",
|
||||
"fallback": "Integration Test [FIRING:1] SlackAlert1 (UID_SlackAlert1)",
|
||||
"footer": "Grafana v",
|
||||
@ -776,7 +776,7 @@ var expNotifications = map[string][]string{
|
||||
"attachments": [
|
||||
{
|
||||
"title": "[FIRING:1] SlackAlert2 (UID_SlackAlert2)",
|
||||
"title_link": "http:/localhost:3000/alerting/list",
|
||||
"title_link": "http://localhost:3000/alerting/list",
|
||||
"text": "\n**Firing**\nLabels:\n - alertname = SlackAlert2\n - __alert_rule_uid__ = UID_SlackAlert2\nAnnotations:\nSource: \n\n\n\n\n",
|
||||
"fallback": "[FIRING:1] SlackAlert2 (UID_SlackAlert2)",
|
||||
"footer": "Grafana v",
|
||||
@ -829,7 +829,7 @@ var expNotifications = map[string][]string{
|
||||
"dingding_recv/dingding_test": {
|
||||
`{
|
||||
"link": {
|
||||
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%3A3000%2Falerting%2Flist",
|
||||
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%3A3000%2Falerting%2Flist",
|
||||
"text": "\n**Firing**\nLabels:\n - alertname = DingDingAlert\n - __alert_rule_uid__ = UID_DingDingAlert\nAnnotations:\nSource: \n\n\n\n\n",
|
||||
"title": "[FIRING:1] DingDingAlert (UID_DingDingAlert)"
|
||||
},
|
||||
@ -848,7 +848,7 @@ var expNotifications = map[string][]string{
|
||||
"targets": [
|
||||
{
|
||||
"os": "default",
|
||||
"uri": "http:/localhost:3000/alerting/list"
|
||||
"uri": "http://localhost:3000/alerting/list"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user