2016-10-29 00:19:51 -05:00
package notifiers
import (
2021-10-07 09:33:50 -05:00
"context"
2018-01-16 11:12:42 -06:00
"os"
2016-11-08 13:37:11 -06:00
"strconv"
2020-01-23 06:20:07 -06:00
"strings"
2018-01-16 12:20:31 -06:00
"time"
2016-11-08 13:37:11 -06:00
2016-10-29 00:19:51 -05:00
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
2019-05-13 01:45:54 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2019-05-14 01:15:05 -05:00
"github.com/grafana/grafana/pkg/models"
2016-10-29 00:19:51 -05:00
"github.com/grafana/grafana/pkg/services/alerting"
2021-10-07 09:33:50 -05:00
"github.com/grafana/grafana/pkg/setting"
2016-10-29 00:19:51 -05:00
)
func init ( ) {
2017-01-06 05:04:25 -06:00
alerting . RegisterNotifier ( & alerting . NotifierPlugin {
Type : "pagerduty" ,
Name : "PagerDuty" ,
Description : "Sends notifications to PagerDuty" ,
2020-06-29 06:39:12 -05:00
Heading : "PagerDuty settings" ,
2017-01-06 05:04:25 -06:00
Factory : NewPagerdutyNotifier ,
2020-06-29 06:39:12 -05:00
Options : [ ] alerting . NotifierOption {
{
Label : "Integration Key" ,
Element : alerting . ElementTypeInput ,
InputType : alerting . InputTypeText ,
Placeholder : "Pagerduty Integration Key" ,
PropertyName : "integrationKey" ,
Required : true ,
2020-09-10 10:43:49 -05:00
Secure : true ,
2020-06-29 06:39:12 -05:00
} ,
{
Label : "Severity" ,
Element : alerting . ElementTypeSelect ,
SelectOptions : [ ] alerting . SelectOption {
{
Value : "critical" ,
Label : "Critical" ,
} ,
{
Value : "error" ,
Label : "Error" ,
} ,
{
Value : "warning" ,
Label : "Warning" ,
} ,
{
Value : "info" ,
Label : "Info" ,
} ,
} ,
PropertyName : "severity" ,
} ,
{
Label : "Auto resolve incidents" ,
2020-09-09 05:46:19 -05:00
Element : alerting . ElementTypeCheckbox ,
2020-06-29 06:39:12 -05:00
Description : "Resolve incidents in pagerduty once the alert goes back to ok." ,
PropertyName : "autoResolve" ,
} ,
2020-09-09 05:46:19 -05:00
{
Label : "Include message in details" ,
Element : alerting . ElementTypeCheckbox ,
Description : "Move the alert message from the PD summary into the custom details. This changes the custom details object and may break event rules you have configured" ,
PropertyName : "messageInDetails" ,
} ,
2020-06-29 06:39:12 -05:00
} ,
2017-01-06 05:04:25 -06:00
} )
2016-10-29 00:19:51 -05:00
}
2016-11-08 00:50:58 -06:00
var (
2019-05-20 08:23:06 -05:00
pagerdutyEventAPIURL = "https://events.pagerduty.com/v2/enqueue"
2016-11-08 00:50:58 -06:00
)
2019-05-20 08:23:06 -05:00
// NewPagerdutyNotifier is the constructor for the PagerDuty notifier
2021-10-07 09:33:50 -05:00
func NewPagerdutyNotifier ( model * models . AlertNotification , fn alerting . GetDecryptedValueFn ) ( alerting . Notifier , error ) {
2019-12-24 01:32:05 -06:00
severity := model . Settings . Get ( "severity" ) . MustString ( "critical" )
2017-12-22 03:54:48 -06:00
autoResolve := model . Settings . Get ( "autoResolve" ) . MustBool ( false )
2021-10-07 09:33:50 -05:00
key := fn ( context . Background ( ) , model . SecureSettings , "integrationKey" , model . Settings . Get ( "integrationKey" ) . MustString ( ) , setting . SecretKey )
2020-05-25 14:01:29 -05:00
messageInDetails := model . Settings . Get ( "messageInDetails" ) . MustBool ( false )
2016-10-29 00:19:51 -05:00
if key == "" {
return nil , alerting . ValidationError { Reason : "Could not find integration key property in settings" }
}
return & PagerdutyNotifier {
2020-05-25 14:01:29 -05:00
NotifierBase : NewNotifierBase ( model ) ,
Key : key ,
Severity : severity ,
AutoResolve : autoResolve ,
MessageInDetails : messageInDetails ,
log : log . New ( "alerting.notifier.pagerduty" ) ,
2016-10-29 00:19:51 -05:00
} , nil
}
2019-05-20 08:23:06 -05:00
// PagerdutyNotifier is responsible for sending
// alert notifications to pagerduty
2016-10-29 00:19:51 -05:00
type PagerdutyNotifier struct {
NotifierBase
2020-05-25 14:01:29 -05:00
Key string
Severity string
AutoResolve bool
MessageInDetails bool
log log . Logger
2016-10-29 00:19:51 -05:00
}
2020-01-23 06:20:07 -06:00
// buildEventPayload is responsible for building the event payload body for sending to Pagerduty v2 API
func ( pn * PagerdutyNotifier ) buildEventPayload ( evalContext * alerting . EvalContext ) ( [ ] byte , error ) {
2016-11-08 13:37:11 -06:00
eventType := "trigger"
2019-05-14 01:15:05 -05:00
if evalContext . Rule . State == models . AlertStateOK {
2016-11-08 13:37:11 -06:00
eventType = "resolve"
}
2019-12-18 03:42:25 -06:00
customData := simplejson . New ( )
2021-03-02 10:50:51 -06:00
customData . Set ( "state" , evalContext . Rule . State )
2020-05-25 14:01:29 -05:00
if pn . MessageInDetails {
queries := make ( map [ string ] interface { } )
for _ , evt := range evalContext . EvalMatches {
queries [ evt . Metric ] = evt . Value
}
customData . Set ( "queries" , queries )
customData . Set ( "message" , evalContext . Rule . Message )
} else {
for _ , evt := range evalContext . EvalMatches {
customData . Set ( evt . Metric , evt . Value )
}
2017-05-26 06:30:56 -05:00
}
2016-11-08 13:37:11 -06:00
2019-05-20 08:23:06 -05:00
pn . log . Info ( "Notifying Pagerduty" , "event_type" , eventType )
2016-11-08 13:37:11 -06:00
2018-01-16 11:12:42 -06:00
payloadJSON := simplejson . New ( )
2019-09-20 03:39:27 -05:00
2020-01-23 06:20:07 -06:00
// set default, override in following case switch if defined
payloadJSON . Set ( "component" , "Grafana" )
2020-03-23 13:32:55 -05:00
payloadJSON . Set ( "severity" , pn . Severity )
2020-09-07 11:42:06 -05:00
dedupKey := "alertId-" + strconv . FormatInt ( evalContext . Rule . ID , 10 )
2020-01-23 06:20:07 -06:00
for _ , tag := range evalContext . Rule . AlertRuleTags {
// Override tags appropriately if they are in the PagerDuty v2 API
switch strings . ToLower ( tag . Key ) {
case "group" :
payloadJSON . Set ( "group" , tag . Value )
case "class" :
payloadJSON . Set ( "class" , tag . Value )
case "component" :
payloadJSON . Set ( "component" , tag . Value )
2020-09-07 11:42:06 -05:00
case "dedup_key" :
if len ( tag . Value ) > 254 {
tag . Value = tag . Value [ 0 : 254 ]
}
dedupKey = tag . Value
2020-03-23 13:32:55 -05:00
case "severity" :
// Only set severity if it's one of the PD supported enum values
// Info, Warning, Error, or Critical (case insensitive)
switch sev := strings . ToLower ( tag . Value ) ; sev {
case "info" :
fallthrough
case "warning" :
fallthrough
case "error" :
fallthrough
case "critical" :
payloadJSON . Set ( "severity" , sev )
default :
pn . log . Warn ( "Ignoring invalid severity tag" , "severity" , sev )
}
2020-01-23 06:20:07 -06:00
}
2020-09-07 11:42:06 -05:00
customData . Set ( tag . Key , tag . Value )
2020-01-23 06:20:07 -06:00
}
2020-05-25 14:01:29 -05:00
var summary string
2021-05-03 01:51:22 -05:00
if pn . MessageInDetails || evalContext . Rule . Message == "" {
2020-05-25 14:01:29 -05:00
summary = evalContext . Rule . Name
} else {
summary = evalContext . Rule . Name + " - " + evalContext . Rule . Message
}
2019-09-20 03:39:27 -05:00
if len ( summary ) > 1024 {
summary = summary [ 0 : 1024 ]
}
payloadJSON . Set ( "summary" , summary )
2018-01-16 11:12:42 -06:00
if hostname , err := os . Hostname ( ) ; err == nil {
payloadJSON . Set ( "source" , hostname )
}
payloadJSON . Set ( "timestamp" , time . Now ( ) )
payloadJSON . Set ( "custom_details" , customData )
2016-11-08 13:37:11 -06:00
bodyJSON := simplejson . New ( )
2019-05-20 08:23:06 -05:00
bodyJSON . Set ( "routing_key" , pn . Key )
2018-01-16 11:12:42 -06:00
bodyJSON . Set ( "event_action" , eventType )
2020-09-07 11:42:06 -05:00
bodyJSON . Set ( "dedup_key" , dedupKey )
2018-01-16 11:12:42 -06:00
bodyJSON . Set ( "payload" , payloadJSON )
2016-11-08 13:37:11 -06:00
2019-06-03 03:25:58 -05:00
ruleURL , err := evalContext . GetRuleURL ( )
2016-11-08 13:37:11 -06:00
if err != nil {
2019-05-20 08:23:06 -05:00
pn . log . Error ( "Failed get rule link" , "error" , err )
2020-01-23 06:20:07 -06:00
return [ ] byte { } , err
2016-11-08 13:37:11 -06:00
}
2018-01-16 11:12:42 -06:00
links := make ( [ ] interface { } , 1 )
linkJSON := simplejson . New ( )
2019-05-20 08:23:06 -05:00
linkJSON . Set ( "href" , ruleURL )
bodyJSON . Set ( "client_url" , ruleURL )
2018-03-13 07:10:55 -05:00
bodyJSON . Set ( "client" , "Grafana" )
2020-01-23 06:20:07 -06:00
2018-01-16 11:12:42 -06:00
links [ 0 ] = linkJSON
bodyJSON . Set ( "links" , links )
2016-11-08 13:37:11 -06:00
2020-03-30 17:46:01 -05:00
if pn . NeedsImage ( ) && evalContext . ImagePublicURL != "" {
2016-11-08 13:37:11 -06:00
contexts := make ( [ ] interface { } , 1 )
imageJSON := simplejson . New ( )
2019-06-03 03:25:58 -05:00
imageJSON . Set ( "src" , evalContext . ImagePublicURL )
2016-11-08 13:37:11 -06:00
contexts [ 0 ] = imageJSON
2018-01-16 11:12:42 -06:00
bodyJSON . Set ( "images" , contexts )
2016-11-08 13:37:11 -06:00
}
body , _ := bodyJSON . MarshalJSON ( )
2020-01-23 06:20:07 -06:00
return body , nil
}
// Notify sends an alert notification to PagerDuty
func ( pn * PagerdutyNotifier ) Notify ( evalContext * alerting . EvalContext ) error {
if evalContext . Rule . State == models . AlertStateOK && ! pn . AutoResolve {
pn . log . Info ( "Not sending a trigger to Pagerduty" , "state" , evalContext . Rule . State , "auto resolve" , pn . AutoResolve )
return nil
}
body , err := pn . buildEventPayload ( evalContext )
if err != nil {
pn . log . Error ( "Unable to build PagerDuty event payload" , "error" , err )
return err
}
2019-05-14 01:15:05 -05:00
cmd := & models . SendWebhookSync {
2019-05-20 08:23:06 -05:00
Url : pagerdutyEventAPIURL ,
2016-11-08 13:37:11 -06:00
Body : string ( body ) ,
HttpMethod : "POST" ,
2018-01-16 11:12:42 -06:00
HttpHeader : map [ string ] string {
"Content-Type" : "application/json" ,
} ,
2016-11-08 13:37:11 -06:00
}
if err := bus . DispatchCtx ( evalContext . Ctx , cmd ) ; err != nil {
2019-05-20 08:23:06 -05:00
pn . log . Error ( "Failed to send notification to Pagerduty" , "error" , err , "body" , string ( body ) )
2016-12-12 03:20:50 -06:00
return err
2016-10-29 00:19:51 -05:00
}
return nil
}