2016-10-27 14:45:55 -05:00
package notifiers
import (
"time"
"github.com/grafana/grafana/pkg/bus"
2017-05-21 03:02:48 -05:00
"github.com/grafana/grafana/pkg/components/simplejson"
2019-05-13 01:45:54 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2016-10-27 14:45:55 -05:00
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
)
// AlertStateCritical - Victorops uses "CRITICAL" string to indicate "Alerting" state
const AlertStateCritical = "CRITICAL"
2020-05-19 02:58:57 -05:00
// AlertStateWarning - VictorOps "WARNING" message type
const AlertStateWarning = "WARNING"
2019-05-20 08:23:06 -05:00
const alertStateRecovery = "RECOVERY"
2017-05-21 03:02:48 -05:00
2016-10-27 14:45:55 -05:00
func init ( ) {
2017-01-06 05:04:25 -06:00
alerting . RegisterNotifier ( & alerting . NotifierPlugin {
Type : "victorops" ,
Name : "VictorOps" ,
Description : "Sends notifications to VictorOps" ,
2020-06-29 06:39:12 -05:00
Heading : "VictorOps settings" ,
2017-01-06 05:04:25 -06:00
Factory : NewVictoropsNotifier ,
OptionsTemplate : `
< h3 class = "page-heading" > VictorOps settings < / h3 >
< div class = "gf-form" >
< span class = "gf-form-label width-6" > Url < / span >
< input type = "text" required class = "gf-form-input max-width-30" ng - model = "ctrl.model.settings.url" placeholder = "VictorOps url" > < / input >
< / div >
2020-05-19 02:58:57 -05:00
< div class = "gf-form" >
< span class = "gf-form-label width-10" > No Data Alert Type < / span >
< div class = "gf-form-select-wrapper width-14" >
< select class = "gf-form-input" ng - model = "ctrl.model.settings.noDataAlertType" ng - options = "t for t in ['CRITICAL', 'WARNING']" ng - init = "ctrl.model.settings.noDataAlertType=ctrl.model.settings.noDataAlertType || '` + AlertStateWarning + `'" >
< / select >
< / div >
< / div >
2017-05-21 03:02:48 -05:00
< div class = "gf-form" >
< gf - form - switch
class = "gf-form"
label = "Auto resolve incidents"
label - class = "width-14"
checked = "ctrl.model.settings.autoResolve"
tooltip = "Resolve incidents in VictorOps once the alert goes back to ok." >
< / gf - form - switch >
< / div >
2017-01-06 05:04:25 -06:00
` ,
2020-06-29 06:39:12 -05:00
Options : [ ] alerting . NotifierOption {
{
Label : "Url" ,
Element : alerting . ElementTypeInput ,
InputType : alerting . InputTypeText ,
Placeholder : "VictorOps url" ,
PropertyName : "url" ,
Required : true ,
} ,
{
Label : "Auto resolve incidents" ,
Description : "Resolve incidents in VictorOps once the alert goes back to ok." ,
Element : alerting . ElementTypeSwitch ,
PropertyName : "autoResolve" ,
} ,
} ,
2017-01-06 05:04:25 -06:00
} )
2016-10-27 14:45:55 -05:00
}
// NewVictoropsNotifier creates an instance of VictoropsNotifier that
// handles posting notifications to Victorops REST API
func NewVictoropsNotifier ( model * models . AlertNotification ) ( alerting . Notifier , error ) {
2017-05-21 03:02:48 -05:00
autoResolve := model . Settings . Get ( "autoResolve" ) . MustBool ( true )
2016-10-27 14:45:55 -05:00
url := model . Settings . Get ( "url" ) . MustString ( )
if url == "" {
return nil , alerting . ValidationError { Reason : "Could not find victorops url property in settings" }
}
2020-05-19 02:58:57 -05:00
noDataAlertType := model . Settings . Get ( "noDataAlertType" ) . MustString ( AlertStateWarning )
2016-10-27 14:45:55 -05:00
return & VictoropsNotifier {
2020-05-19 02:58:57 -05:00
NotifierBase : NewNotifierBase ( model ) ,
URL : url ,
NoDataAlertType : noDataAlertType ,
AutoResolve : autoResolve ,
log : log . New ( "alerting.notifier.victorops" ) ,
2016-10-27 14:45:55 -05:00
} , nil
}
// 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 {
NotifierBase
2020-05-19 02:58:57 -05:00
URL string
NoDataAlertType string
AutoResolve bool
log log . Logger
2016-10-27 14:45:55 -05:00
}
// Notify sends notification to Victorops via POST to URL endpoint
2019-05-20 08:23:06 -05:00
func ( vn * VictoropsNotifier ) Notify ( evalContext * alerting . EvalContext ) error {
2019-06-03 03:25:58 -05:00
vn . log . Info ( "Executing victorops notification" , "ruleId" , evalContext . Rule . ID , "notification" , vn . Name )
2016-10-27 14:45:55 -05:00
2019-06-03 03:25:58 -05:00
ruleURL , err := evalContext . GetRuleURL ( )
2016-11-16 18:13:33 -06:00
if err != nil {
2019-05-20 08:23:06 -05:00
vn . log . Error ( "Failed get rule link" , "error" , err )
2016-11-16 18:13:33 -06:00
return err
}
2019-05-20 08:23:06 -05:00
if evalContext . Rule . State == models . AlertStateOK && ! vn . AutoResolve {
vn . log . Info ( "Not alerting VictorOps" , "state" , evalContext . Rule . State , "auto resolve" , vn . AutoResolve )
2017-05-21 03:02:48 -05:00
return nil
}
2020-05-19 02:58:57 -05:00
messageType := AlertStateCritical // Default to alerting and change based on state checks (Ensures string type)
if evalContext . Rule . State == models . AlertStateNoData { // translate 'NODATA' to set alert
messageType = vn . NoDataAlertType
2016-10-27 14:45:55 -05:00
}
2017-05-21 03:02:48 -05:00
if evalContext . Rule . State == models . AlertStateOK {
2019-05-20 08:23:06 -05:00
messageType = alertStateRecovery
2017-05-21 03:02:48 -05:00
}
2019-05-21 00:50:44 -05:00
fields := make ( map [ string ] interface { } )
2019-03-14 02:46:28 -05:00
fieldLimitCount := 4
for index , evt := range evalContext . EvalMatches {
fields [ evt . Metric ] = evt . Value
if index > fieldLimitCount {
break
}
}
2017-05-21 03:02:48 -05:00
bodyJSON := simplejson . New ( )
bodyJSON . Set ( "message_type" , messageType )
bodyJSON . Set ( "entity_id" , evalContext . Rule . Name )
2019-03-14 02:46:28 -05:00
bodyJSON . Set ( "entity_display_name" , evalContext . GetNotificationTitle ( ) )
2017-05-21 03:02:48 -05:00
bodyJSON . Set ( "timestamp" , time . Now ( ) . Unix ( ) )
bodyJSON . Set ( "state_start_time" , evalContext . StartTime . Unix ( ) )
bodyJSON . Set ( "state_message" , evalContext . Rule . Message )
bodyJSON . Set ( "monitoring_tool" , "Grafana v" + setting . BuildVersion )
2019-05-20 08:23:06 -05:00
bodyJSON . Set ( "alert_url" , ruleURL )
2019-03-14 02:46:28 -05:00
bodyJSON . Set ( "metrics" , fields )
if evalContext . Error != nil {
bodyJSON . Set ( "error_message" , evalContext . Error . Error ( ) )
}
2017-05-21 03:02:48 -05:00
2020-03-30 17:46:01 -05:00
if vn . NeedsImage ( ) && evalContext . ImagePublicURL != "" {
2019-06-03 03:25:58 -05:00
bodyJSON . Set ( "image_url" , evalContext . ImagePublicURL )
2016-10-27 14:45:55 -05:00
}
2017-05-21 03:02:48 -05:00
data , _ := bodyJSON . MarshalJSON ( )
2019-05-20 08:23:06 -05:00
cmd := & models . SendWebhookSync { Url : vn . URL , Body : string ( data ) }
2016-10-27 14:45:55 -05:00
2016-11-16 18:13:33 -06:00
if err := bus . DispatchCtx ( evalContext . Ctx , cmd ) ; err != nil {
2019-05-20 08:23:06 -05:00
vn . log . Error ( "Failed to send Victorops notification" , "error" , err , "webhook" , vn . Name )
2016-12-12 03:20:50 -06:00
return err
2016-10-27 14:45:55 -05:00
}
return nil
}