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)
|
n, err = channels.NewLineNotifier(cfg, tmpl)
|
||||||
case "threema":
|
case "threema":
|
||||||
n, err = channels.NewThreemaNotifier(cfg, tmpl)
|
n, err = channels.NewThreemaNotifier(cfg, tmpl)
|
||||||
|
case "opsgenie":
|
||||||
|
n, err = channels.NewOpsgenieNotifier(cfg, tmpl)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("notifier %s is not supported", r.Type)
|
return nil, fmt.Errorf("notifier %s is not supported", r.Type)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package notifier
|
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.
|
// GetAvailableNotifiers returns the metadata of all the notification channels that can be configured.
|
||||||
func GetAvailableNotifiers() []*alerting.NotifierPlugin {
|
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"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
|
|
||||||
gokit_log "github.com/go-kit/kit/log"
|
gokit_log "github.com/go-kit/kit/log"
|
||||||
"github.com/prometheus/alertmanager/notify"
|
"github.com/prometheus/alertmanager/notify"
|
||||||
@ -65,9 +64,14 @@ type DingDingNotifier struct {
|
|||||||
func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||||
dd.log.Info("Sending dingding")
|
dd.log.Info("Sending dingding")
|
||||||
|
|
||||||
|
ruleURL, err := joinUrlPath(dd.tmpl.ExternalURL.String(), "/alerting/list")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
q := url.Values{
|
q := url.Values{
|
||||||
"pc_slide": {"false"},
|
"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
|
// 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{}{
|
expMsg: map[string]interface{}{
|
||||||
"msgtype": "link",
|
"msgtype": "link",
|
||||||
"link": map[string]interface{}{
|
"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",
|
"text": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||||
"title": "[FIRING:1] (val1)",
|
"title": "[FIRING:1] (val1)",
|
||||||
},
|
},
|
||||||
@ -77,7 +77,7 @@ func TestDingdingNotifier(t *testing.T) {
|
|||||||
expMsg: map[string]interface{}{
|
expMsg: map[string]interface{}{
|
||||||
"actionCard": map[string]interface{}{
|
"actionCard": map[string]interface{}{
|
||||||
"singleTitle": "More",
|
"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",
|
"text": "2 alerts are firing, 0 are resolved",
|
||||||
"title": "[FIRING:2] ",
|
"title": "[FIRING:2] ",
|
||||||
},
|
},
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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)
|
color, _ := strconv.ParseInt(strings.TrimLeft(getAlertStatusColor(alerts.Status()), "#"), 16, 0)
|
||||||
embed.Set("color", color)
|
embed.Set("color", color)
|
||||||
|
|
||||||
u, err := url.Parse(d.tmpl.ExternalURL.String())
|
ruleURL, err := joinUrlPath(d.tmpl.ExternalURL.String(), "/alerting/list")
|
||||||
if err != nil {
|
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)
|
embed.Set("url", ruleURL)
|
||||||
|
|
||||||
bodyJSON.Set("embeds", []interface{}{embed})
|
bodyJSON.Set("embeds", []interface{}{embed})
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
gokit_log "github.com/go-kit/kit/log"
|
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).
|
// Add a button widget (link to Grafana).
|
||||||
widgets = append(widgets, buttonWidget{
|
widgets = append(widgets, buttonWidget{
|
||||||
Buttons: []button{
|
Buttons: []button{
|
||||||
@ -77,7 +80,7 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) (
|
|||||||
Text: "OPEN IN GRAFANA",
|
Text: "OPEN IN GRAFANA",
|
||||||
OnClick: onClick{
|
OnClick: onClick{
|
||||||
OpenLink: openLink{
|
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",
|
Text: "OPEN IN GRAFANA",
|
||||||
OnClick: onClick{
|
OnClick: onClick{
|
||||||
OpenLink: openLink{
|
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",
|
Text: "OPEN IN GRAFANA",
|
||||||
OnClick: onClick{
|
OnClick: onClick{
|
||||||
OpenLink: openLink{
|
OpenLink: openLink{
|
||||||
URL: "http:/localhost/alerting/list",
|
URL: "http://localhost/alerting/list",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -3,7 +3,6 @@ package channels
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
|
||||||
|
|
||||||
gokit_log "github.com/go-kit/kit/log"
|
gokit_log "github.com/go-kit/kit/log"
|
||||||
"github.com/prometheus/alertmanager/notify"
|
"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("description", tmpl(`{{ template "default.title" . }}`))
|
||||||
bodyJSON.Set("client", "Grafana")
|
bodyJSON.Set("client", "Grafana")
|
||||||
bodyJSON.Set("details", tmpl(`{{ template "default.message" . }}`))
|
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)
|
groupKey, err := notify.ExtractGroupKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -52,7 +52,7 @@ func TestKafkaNotifier(t *testing.T) {
|
|||||||
"value": {
|
"value": {
|
||||||
"alert_state": "alerting",
|
"alert_state": "alerting",
|
||||||
"client": "Grafana",
|
"client": "Grafana",
|
||||||
"client_url": "http:/localhost/alerting/list",
|
"client_url": "http://localhost/alerting/list",
|
||||||
"description": "[FIRING:1] (val1)",
|
"description": "[FIRING:1] (val1)",
|
||||||
"details": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
"details": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||||
"incident_key": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733"
|
"incident_key": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733"
|
||||||
@ -88,7 +88,7 @@ func TestKafkaNotifier(t *testing.T) {
|
|||||||
"value": {
|
"value": {
|
||||||
"alert_state": "alerting",
|
"alert_state": "alerting",
|
||||||
"client": "Grafana",
|
"client": "Grafana",
|
||||||
"client_url": "http:/localhost/alerting/list",
|
"client_url": "http://localhost/alerting/list",
|
||||||
"description": "[FIRING:2] ",
|
"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",
|
"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"
|
"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"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
gokit_log "github.com/go-kit/kit/log"
|
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) {
|
func (pn *PushoverNotifier) genPushoverBody(ctx context.Context, as ...*types.Alert) (map[string]string, bytes.Buffer, error) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
|
|
||||||
u, err := url.Parse(pn.tmpl.ExternalURL.String())
|
ruleURL, err := joinUrlPath(pn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||||
if err != nil {
|
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...)
|
alerts := types.Alerts(as...)
|
||||||
|
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -109,12 +107,10 @@ func (sn *SensuGoNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool
|
|||||||
handlers = []string{sn.Handler}
|
handlers = []string{sn.Handler}
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse(sn.tmpl.ExternalURL.String())
|
ruleURL, err := joinUrlPath(sn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||||
if err != nil {
|
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{}{
|
bodyMsgType := map[string]interface{}{
|
||||||
"entity": map[string]interface{}{
|
"entity": map[string]interface{}{
|
||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -251,6 +250,11 @@ func (sn *SlackNotifier) buildSlackMessage(ctx context.Context, as []*types.Aler
|
|||||||
var tmplErr error
|
var tmplErr error
|
||||||
tmpl := notify.TmplText(sn.tmpl, data, &tmplErr)
|
tmpl := notify.TmplText(sn.tmpl, data, &tmplErr)
|
||||||
|
|
||||||
|
ruleURL, err := joinUrlPath(sn.tmpl.ExternalURL.String(), "/alerting/list")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
req := &slackMessage{
|
req := &slackMessage{
|
||||||
Channel: tmpl(sn.Recipient),
|
Channel: tmpl(sn.Recipient),
|
||||||
Username: tmpl(sn.Username),
|
Username: tmpl(sn.Username),
|
||||||
@ -264,7 +268,7 @@ func (sn *SlackNotifier) buildSlackMessage(ctx context.Context, as []*types.Aler
|
|||||||
Footer: "Grafana v" + setting.BuildVersion,
|
Footer: "Grafana v" + setting.BuildVersion,
|
||||||
FooterIcon: FooterIconURL,
|
FooterIcon: FooterIconURL,
|
||||||
Ts: time.Now().Unix(),
|
Ts: time.Now().Unix(),
|
||||||
TitleLink: path.Join(sn.tmpl.ExternalURL.String(), "/alerting/list"),
|
TitleLink: ruleURL,
|
||||||
Text: tmpl(sn.Text),
|
Text: tmpl(sn.Text),
|
||||||
Fields: nil, // TODO. Should be a config.
|
Fields: nil, // TODO. Should be a config.
|
||||||
},
|
},
|
||||||
|
@ -56,7 +56,7 @@ func TestSlackNotifier(t *testing.T) {
|
|||||||
Attachments: []attachment{
|
Attachments: []attachment{
|
||||||
{
|
{
|
||||||
Title: "[FIRING:1] (val1)",
|
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",
|
Text: "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||||
Fallback: "[FIRING:1] (val1)",
|
Fallback: "[FIRING:1] (val1)",
|
||||||
Fields: nil,
|
Fields: nil,
|
||||||
@ -92,7 +92,7 @@ func TestSlackNotifier(t *testing.T) {
|
|||||||
Attachments: []attachment{
|
Attachments: []attachment{
|
||||||
{
|
{
|
||||||
Title: "[FIRING:1] (val1)",
|
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",
|
Text: "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||||
Fallback: "[FIRING:1] (val1)",
|
Fallback: "[FIRING:1] (val1)",
|
||||||
Fields: nil,
|
Fields: nil,
|
||||||
@ -135,7 +135,7 @@ func TestSlackNotifier(t *testing.T) {
|
|||||||
Attachments: []attachment{
|
Attachments: []attachment{
|
||||||
{
|
{
|
||||||
Title: "2 firing, 0 resolved",
|
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",
|
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",
|
Fallback: "2 firing, 0 resolved",
|
||||||
Fields: nil,
|
Fields: nil,
|
||||||
|
@ -3,7 +3,6 @@ package channels
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"path"
|
|
||||||
|
|
||||||
gokit_log "github.com/go-kit/kit/log"
|
gokit_log "github.com/go-kit/kit/log"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -61,6 +60,11 @@ func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
|||||||
var tmplErr error
|
var tmplErr error
|
||||||
tmpl := notify.TmplText(tn.tmpl, data, &tmplErr)
|
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" . }}`)
|
title := tmpl(`{{ template "default.title" . }}`)
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"@type": "MessageCard",
|
"@type": "MessageCard",
|
||||||
@ -84,7 +88,7 @@ func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
|||||||
"targets": []map[string]interface{}{
|
"targets": []map[string]interface{}{
|
||||||
{
|
{
|
||||||
"os": "default",
|
"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",
|
"@context": "http://schema.org",
|
||||||
"@type": "OpenUri",
|
"@type": "OpenUri",
|
||||||
"name": "View Rule",
|
"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",
|
"@context": "http://schema.org",
|
||||||
"@type": "OpenUri",
|
"@type": "OpenUri",
|
||||||
"name": "View Rule",
|
"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"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"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)
|
logger.Debug("Sending HTTP request succeeded", "url", request.URL.String(), "statusCode", resp.Status)
|
||||||
return respBody, nil
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -92,7 +91,12 @@ func (vn *VictoropsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bo
|
|||||||
bodyJSON.Set("timestamp", time.Now().Unix())
|
bodyJSON.Set("timestamp", time.Now().Unix())
|
||||||
bodyJSON.Set("state_message", tmpl(`{{ template "default.message" . }}`))
|
bodyJSON.Set("state_message", tmpl(`{{ template "default.message" . }}`))
|
||||||
bodyJSON.Set("monitoring_tool", "Grafana v"+setting.BuildVersion)
|
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()
|
b, err := bodyJSON.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -43,7 +43,7 @@ func TestVictoropsNotifier(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
expMsg: `{
|
expMsg: `{
|
||||||
"alert_url": "http:/localhost/alerting/list",
|
"alert_url": "http://localhost/alerting/list",
|
||||||
"entity_display_name": "[FIRING:1] (val1)",
|
"entity_display_name": "[FIRING:1] (val1)",
|
||||||
"entity_id": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
"entity_id": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||||
"message_type": "CRITICAL",
|
"message_type": "CRITICAL",
|
||||||
@ -69,7 +69,7 @@ func TestVictoropsNotifier(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
expMsg: `{
|
expMsg: `{
|
||||||
"alert_url": "http:/localhost/alerting/list",
|
"alert_url": "http://localhost/alerting/list",
|
||||||
"entity_display_name": "[FIRING:2] ",
|
"entity_display_name": "[FIRING:2] ",
|
||||||
"entity_id": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
"entity_id": "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||||
"message_type": "CRITICAL",
|
"message_type": "CRITICAL",
|
||||||
|
@ -1462,6 +1462,108 @@ var expAvailableChannelJsonOutput = `
|
|||||||
"secure": true
|
"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": [
|
"attachments": [
|
||||||
{
|
{
|
||||||
"title": "Integration Test [FIRING:1] SlackAlert1 (UID_SlackAlert1)",
|
"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 ",
|
"text": "Integration Test ",
|
||||||
"fallback": "Integration Test [FIRING:1] SlackAlert1 (UID_SlackAlert1)",
|
"fallback": "Integration Test [FIRING:1] SlackAlert1 (UID_SlackAlert1)",
|
||||||
"footer": "Grafana v",
|
"footer": "Grafana v",
|
||||||
@ -776,7 +776,7 @@ var expNotifications = map[string][]string{
|
|||||||
"attachments": [
|
"attachments": [
|
||||||
{
|
{
|
||||||
"title": "[FIRING:1] SlackAlert2 (UID_SlackAlert2)",
|
"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",
|
"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)",
|
"fallback": "[FIRING:1] SlackAlert2 (UID_SlackAlert2)",
|
||||||
"footer": "Grafana v",
|
"footer": "Grafana v",
|
||||||
@ -829,7 +829,7 @@ var expNotifications = map[string][]string{
|
|||||||
"dingding_recv/dingding_test": {
|
"dingding_recv/dingding_test": {
|
||||||
`{
|
`{
|
||||||
"link": {
|
"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",
|
"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)"
|
"title": "[FIRING:1] DingDingAlert (UID_DingDingAlert)"
|
||||||
},
|
},
|
||||||
@ -848,7 +848,7 @@ var expNotifications = map[string][]string{
|
|||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"os": "default",
|
"os": "default",
|
||||||
"uri": "http:/localhost:3000/alerting/list"
|
"uri": "http://localhost:3000/alerting/list"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user