grafana/pkg/services/alerting/notifiers/opsgenie.go
Serge Zaitsev 43b15b92ad
Chore: Remove bus from the alerting service (#44496)
* propagate notificationservice down to the notifiers

* replace dispatch in result handler

* remove dispatch from the rule reader

* remove dispatch from eval context

* remove dispatch from alerting usage

* remove dispatch from alerting usage

* remove dispatch from notifier

* attempt to fix tests in alerting

* hello linter, my old friend; also disable some tests for now

* use mocks to fix the tests

* resolving wire providers

* make linter happy

* remove yet another bus.dispatch

* fix tests using store mock
2022-02-03 13:26:05 +01:00

247 lines
7.1 KiB
Go

package notifiers
import (
"context"
"fmt"
"strconv"
"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"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/setting"
)
const (
sendTags = "tags"
sendDetails = "details"
sendBoth = "both"
)
func init() {
alerting.RegisterNotifier(&alerting.NotifierPlugin{
Type: "opsgenie",
Name: "OpsGenie",
Description: "Sends notifications to OpsGenie",
Heading: "OpsGenie settings",
Factory: NewOpsGenieNotifier,
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 tag",
PropertyName: "overridePriority",
},
{
Label: "Send notification tags as",
Element: alerting.ElementTypeSelect,
SelectOptions: []alerting.SelectOption{
{
Value: sendTags,
Label: "Tags",
},
{
Value: sendDetails,
Label: "Extra Properties",
},
{
Value: sendBoth,
Label: "Tags & Extra Properties",
},
},
Description: "Send the notification tags to Opsgenie as either Extra Properties, Tags or both",
PropertyName: "sendTagsAs",
},
},
})
}
const (
opsgenieAlertURL = "https://api.opsgenie.com/v2/alerts"
)
// NewOpsGenieNotifier is the constructor for OpsGenie.
func NewOpsGenieNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
autoClose := model.Settings.Get("autoClose").MustBool(true)
overridePriority := model.Settings.Get("overridePriority").MustBool(true)
apiKey := fn(context.Background(), model.SecureSettings, "apiKey", model.Settings.Get("apiKey").MustString(), setting.SecretKey)
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(sendTags)
if sendTagsAs != sendTags && sendTagsAs != sendDetails && sendTagsAs != sendBoth {
return nil, alerting.ValidationError{
Reason: fmt.Sprintf("Invalid value for sendTagsAs: %q", sendTagsAs),
}
}
return &OpsGenieNotifier{
NotifierBase: NewNotifierBase(model, ns),
APIKey: apiKey,
APIUrl: apiURL,
AutoClose: autoClose,
OverridePriority: overridePriority,
SendTagsAs: sendTagsAs,
log: log.New("alerting.notifier.opsgenie"),
}, nil
}
// OpsGenieNotifier is responsible for sending
// alert notifications to OpsGenie
type OpsGenieNotifier struct {
NotifierBase
APIKey string
APIUrl string
AutoClose bool
OverridePriority bool
SendTagsAs string
log log.Logger
}
// Notify sends an alert notification to OpsGenie.
func (on *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error {
var err error
switch evalContext.Rule.State {
case models.AlertStateOK:
if on.AutoClose {
err = on.closeAlert(evalContext)
}
case models.AlertStateAlerting:
err = on.createAlert(evalContext)
default:
// Handle other cases?
}
return err
}
func (on *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) error {
on.log.Info("Creating OpsGenie alert", "ruleId", evalContext.Rule.ID, "notification", on.Name)
ruleURL, err := evalContext.GetRuleURL()
if err != nil {
on.log.Error("Failed get rule link", "error", err)
return err
}
customData := triggMetrString
for _, evt := range evalContext.EvalMatches {
customData += fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value)
}
bodyJSON := simplejson.New()
bodyJSON.Set("message", evalContext.Rule.Name)
bodyJSON.Set("source", "Grafana")
bodyJSON.Set("alias", "alertId-"+strconv.FormatInt(evalContext.Rule.ID, 10))
bodyJSON.Set("description", fmt.Sprintf("%s - %s\n%s\n%s", evalContext.Rule.Name, ruleURL, evalContext.Rule.Message, customData))
details := simplejson.New()
details.Set("url", ruleURL)
if on.NeedsImage() && evalContext.ImagePublicURL != "" {
details.Set("image", evalContext.ImagePublicURL)
}
tags := make([]string, 0)
for _, tag := range evalContext.Rule.AlertRuleTags {
if on.sendDetails() {
details.Set(tag.Key, tag.Value)
}
if on.sendTags() {
if len(tag.Value) > 0 {
tags = append(tags, fmt.Sprintf("%s:%s", tag.Key, tag.Value))
} else {
tags = append(tags, tag.Key)
}
}
if tag.Key == "og_priority" {
if on.OverridePriority {
validPriorities := map[string]bool{"P1": true, "P2": true, "P3": true, "P4": true, "P5": true}
if validPriorities[tag.Value] {
bodyJSON.Set("priority", tag.Value)
}
}
}
}
bodyJSON.Set("tags", tags)
bodyJSON.Set("details", details)
body, _ := bodyJSON.MarshalJSON()
cmd := &models.SendWebhookSync{
Url: on.APIUrl,
Body: string(body),
HttpMethod: "POST",
HttpHeader: map[string]string{
"Content-Type": "application/json",
"Authorization": fmt.Sprintf("GenieKey %s", on.APIKey),
},
}
if err := on.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
}
return nil
}
func (on *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) error {
on.log.Info("Closing OpsGenie alert", "ruleId", evalContext.Rule.ID, "notification", on.Name)
bodyJSON := simplejson.New()
bodyJSON.Set("source", "Grafana")
body, _ := bodyJSON.MarshalJSON()
cmd := &models.SendWebhookSync{
Url: fmt.Sprintf("%s/alertId-%d/close?identifierType=alias", on.APIUrl, evalContext.Rule.ID),
Body: string(body),
HttpMethod: "POST",
HttpHeader: map[string]string{
"Content-Type": "application/json",
"Authorization": fmt.Sprintf("GenieKey %s", on.APIKey),
},
}
if err := on.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
return err
}
return nil
}
func (on *OpsGenieNotifier) sendDetails() bool {
return on.SendTagsAs == sendDetails || on.SendTagsAs == sendBoth
}
func (on *OpsGenieNotifier) sendTags() bool {
return on.SendTagsAs == sendTags || on.SendTagsAs == sendBoth
}