grafana/pkg/services/ngalert/notifier/channels/victorops.go

164 lines
4.8 KiB
Go

package channels
import (
"context"
"errors"
"strings"
"time"
"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/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/setting"
)
const (
// victoropsAlertStateCritical - Victorops uses "CRITICAL" string to indicate "Alerting" state
victoropsAlertStateCritical = "CRITICAL"
// victoropsAlertStateRecovery - VictorOps "RECOVERY" message type
victoropsAlertStateRecovery = "RECOVERY"
)
type VictorOpsConfig struct {
*NotificationChannelConfig
URL string
MessageType string
}
func VictorOpsFactory(fc FactoryConfig) (NotificationChannel, error) {
cfg, err := NewVictorOpsConfig(fc.Config)
if err != nil {
return nil, receiverInitError{
Reason: err.Error(),
Cfg: *fc.Config,
}
}
return NewVictoropsNotifier(cfg, fc.ImageStore, fc.NotificationService, fc.Template), nil
}
func NewVictorOpsConfig(config *NotificationChannelConfig) (*VictorOpsConfig, error) {
url := config.Settings.Get("url").MustString()
if url == "" {
return nil, errors.New("could not find victorops url property in settings")
}
return &VictorOpsConfig{
NotificationChannelConfig: config,
URL: url,
MessageType: config.Settings.Get("messageType").MustString(),
}, nil
}
// NewVictoropsNotifier creates an instance of VictoropsNotifier that
// handles posting notifications to Victorops REST API
func NewVictoropsNotifier(config *VictorOpsConfig, images ImageStore, ns notifications.WebhookSender, t *template.Template) *VictoropsNotifier {
return &VictoropsNotifier{
Base: NewBase(&models.AlertNotification{
Uid: config.UID,
Name: config.Name,
Type: config.Type,
DisableResolveMessage: config.DisableResolveMessage,
Settings: config.Settings,
}),
URL: config.URL,
MessageType: config.MessageType,
log: log.New("alerting.notifier.victorops"),
images: images,
ns: ns,
tmpl: t,
}
}
// VictoropsNotifier defines URL property for Victorops REST API
// and handles notification process by formatting POST body according to
// Victorops specifications (http://victorops.force.com/knowledgebase/articles/Integration/Alert-Ingestion-API-Documentation/)
type VictoropsNotifier struct {
*Base
URL string
MessageType string
log log.Logger
images ImageStore
ns notifications.WebhookSender
tmpl *template.Template
}
// Notify sends notification to Victorops via POST to URL endpoint
func (vn *VictoropsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
vn.log.Debug("executing victorops notification", "notification", vn.Name)
var tmplErr error
tmpl, _ := TmplText(ctx, vn.tmpl, as, vn.log, &tmplErr)
messageType := strings.ToUpper(tmpl(vn.MessageType))
if messageType == "" {
messageType = victoropsAlertStateCritical
}
alerts := types.Alerts(as...)
if alerts.Status() == model.AlertResolved {
messageType = victoropsAlertStateRecovery
}
groupKey, err := notify.ExtractGroupKey(ctx)
if err != nil {
return false, err
}
bodyJSON := simplejson.New()
bodyJSON.Set("message_type", messageType)
bodyJSON.Set("entity_id", groupKey.Hash())
bodyJSON.Set("entity_display_name", tmpl(DefaultMessageTitleEmbed))
bodyJSON.Set("timestamp", time.Now().Unix())
bodyJSON.Set("state_message", tmpl(`{{ template "default.message" . }}`))
bodyJSON.Set("monitoring_tool", "Grafana v"+setting.BuildVersion)
_ = withStoredImages(ctx, vn.log, vn.images,
func(index int, image *ngmodels.Image) error {
if image != nil && image.URL != "" {
bodyJSON.Set("image_url", image.URL)
return ErrImagesDone
}
return nil
}, as...)
ruleURL := joinUrlPath(vn.tmpl.ExternalURL.String(), "/alerting/list", vn.log)
bodyJSON.Set("alert_url", ruleURL)
if tmplErr != nil {
vn.log.Warn("failed to template VictorOps message", "err", tmplErr.Error())
tmplErr = nil
}
u := tmpl(vn.URL)
if tmplErr != nil {
vn.log.Info("failed to template VictorOps URL", "err", tmplErr.Error(), "fallback", vn.URL)
u = vn.URL
}
b, err := bodyJSON.MarshalJSON()
if err != nil {
return false, err
}
cmd := &models.SendWebhookSync{
Url: u,
Body: string(b),
}
if err := vn.ns.SendWebhookSync(ctx, cmd); err != nil {
vn.log.Error("Failed to send Victorops notification", "err", err, "webhook", vn.Name)
return false, err
}
return true, nil
}
func (vn *VictoropsNotifier) SendResolved() bool {
return !vn.GetDisableResolveMessage()
}