mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Encryption: Add support to encrypt/decrypt sjd * Add datasources.Service as a proxy to datasources db operations * Encrypt ds.SecureJsonData before calling SQLStore * Move ds cache code into ds service * Fix tlsmanager tests * Fix pluginproxy tests * Remove some securejsondata.GetEncryptedJsonData usages * Add pluginsettings.Service as a proxy for plugin settings db operations * Add AlertNotificationService as a proxy for alert notification db operations * Remove some securejsondata.GetEncryptedJsonData usages * Remove more securejsondata.GetEncryptedJsonData usages * Fix lint errors * Minor fixes * Remove encryption global functions usages from ngalert * Fix lint errors * Minor fixes * Minor fixes * Remove securejsondata.DecryptedValue usage * Refactor the refactor * Remove securejsondata.DecryptedValue usage * Move securejsondata to migrations package * Move securejsondata to migrations package * Minor fix * Fix integration test * Fix integration tests * Undo undesired changes * Fix tests * Add context.Context into encryption methods * Fix tests * Fix tests * Fix tests * Trigger CI * Fix test * Add names to params of encryption service interface * Remove bus from CacheServiceImpl * Add logging * Add keys to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Add missing key to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Undo changes in markdown files * Fix formatting * Add context to secrets service * Rename decryptSecureJsonData to decryptSecureJsonDataFn * Name args in GetDecryptedValueFn * Add template back to NewAlertmanagerNotifier * Copy GetDecryptedValueFn to ngalert * Add logging to pluginsettings * Fix pluginsettings test Co-authored-by: Tania B <yalyna.ts@gmail.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
223 lines
6.3 KiB
Go
223 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"
|
|
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"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 {
|
|
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, fn GetDecryptedValueFn) (*OpsgenieNotifier, 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, 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{
|
|
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 := joinUrlPath(on.tmpl.ExternalURL.String(), "/alerting/list", on.log)
|
|
|
|
var tmplErr error
|
|
tmpl, data := TmplText(ctx, on.tmpl, as, on.log, &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.
|
|
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.Debug("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
|
|
}
|