Files
grafana/pkg/services/ngalert/notifier/channels/opsgenie.go
Alexander Weaver fd583a0e3b Alerting: Allow customization of Google chat message (#43568)
* Allow customizable googlechat message via optional setting

* Add optional message field in googlechat contact point configurator

* Fix strange error message on send if template fails to fully evaluate

* Elevate template evaluation failure logs to Warn level

* Extract default.title template embed from all channels to shared constant
2022-01-05 09:47:08 -06:00

227 lines
6.3 KiB
Go

package channels
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sort"
"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/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model"
)
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 {
*Base
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, fn GetDecryptedValueFn) (*OpsgenieNotifier, error) {
if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"}
}
if model.SecureSettings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no secure settings supplied"}
}
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())
apiURL := model.Settings.Get("apiUrl").MustString()
if apiKey == "" {
return nil, receiverInitError{Cfg: *model, 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, receiverInitError{Cfg: *model,
Reason: fmt.Sprintf("invalid value for sendTagsAs: %q", sendTagsAs),
}
}
return &OpsgenieNotifier{
Base: NewBase(&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.Dispatch(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 := joinUrlPath(on.tmpl.ExternalURL.String(), "/alerting/list", on.log)
var tmplErr error
tmpl, data := TmplText(ctx, on.tmpl, as, on.log, &tmplErr)
title := tmpl(DefaultMessageTitleEmbed)
description := fmt.Sprintf(
"%s\n%s\n\n%s",
tmpl(DefaultMessageTitleEmbed),
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.
lbls := make(map[string]string, len(data.CommonLabels))
for k, v := range data.CommonLabels {
lbls[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 lbls {
details.Set(k, v)
}
}
tags := make([]string, 0, len(lbls))
if on.sendTags() {
for k, v := range lbls {
tags = append(tags, fmt.Sprintf("%s:%s", k, v))
}
}
sort.Strings(tags)
if priority != "" && on.OverridePriority {
bodyJSON.Set("priority", priority)
}
bodyJSON.Set("tags", tags)
bodyJSON.Set("details", details)
apiURL = tmpl(on.APIUrl)
if tmplErr != nil {
on.log.Warn("failed to template Opsgenie message", "err", tmplErr.Error())
}
return bodyJSON, apiURL, nil
}
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
}