2023-03-20 23:39:34 +01:00
import ast
2014-12-17 00:14:03 +00:00
import json
2016-09-15 10:16:11 +01:00
import logging
2017-09-17 15:56:55 +01:00
import os
2018-09-25 10:34:33 -04:00
import traceback
2023-03-20 23:39:34 +01:00
import requests
from alerta . plugins import PluginBase
2017-09-17 15:56:55 +01:00
2020-02-19 22:45:42 +01:00
LOG = logging . getLogger ( ' alerta.plugins.slack ' )
2018-09-05 10:38:07 -04:00
try :
from jinja2 import Template
except Exception as e :
2023-03-20 23:39:34 +01:00
LOG . error (
' SLACK: ERROR - Jinja template error: %s , template functionality will be unavailable ' , e )
2018-09-05 10:38:07 -04:00
2017-09-17 15:56:55 +01:00
try :
from alerta . plugins import app # alerta >= 5.0
except ImportError :
from alerta . app import app # alerta < 5.0
2014-12-17 00:14:03 +00:00
2016-09-15 10:16:11 +01:00
LOG = logging . getLogger ( ' alerta.plugins.slack ' )
2014-12-17 00:14:03 +00:00
2017-09-04 10:20:08 +01:00
SLACK_ATTACHMENTS = True if os . environ . get (
' SLACK_ATTACHMENTS ' , ' False ' ) == ' True ' else app . config . get ( ' SLACK_ATTACHMENTS ' , False )
2019-04-23 04:53:13 -04:00
try :
SLACK_CHANNEL_ENV_MAP = json . loads (
os . environ . get ( ' SLACK_CHANNEL_ENV_MAP ' ) )
2023-03-20 23:39:34 +01:00
except Exception :
2019-04-23 04:53:13 -04:00
SLACK_CHANNEL_ENV_MAP = app . config . get ( ' SLACK_CHANNEL_ENV_MAP ' , dict ( ) )
2019-10-28 22:17:52 +01:00
try :
SLACK_CHANNEL_EVENT_MAP = json . loads (
os . environ . get ( ' SLACK_CHANNEL_EVENT_MAP ' ) )
2023-03-20 23:39:34 +01:00
except Exception :
2019-10-28 22:17:52 +01:00
SLACK_CHANNEL_EVENT_MAP = app . config . get ( ' SLACK_CHANNEL_EVENT_MAP ' , dict ( ) )
try :
SLACK_CHANNEL_SEVERITY_MAP = json . loads (
os . environ . get ( ' SLACK_CHANNEL_SEVERITY_MAP ' ) )
2023-03-20 23:39:34 +01:00
except Exception :
SLACK_CHANNEL_SEVERITY_MAP = app . config . get (
' SLACK_CHANNEL_SEVERITY_MAP ' , dict ( ) )
2020-06-14 13:26:40 -07:00
2020-06-18 10:58:35 -04:00
try :
SLACK_CHANNEL_MAP = json . loads (
os . environ . get ( ' SLACK_CHANNEL_MAP ' ) )
2023-03-20 23:39:34 +01:00
except Exception :
2020-06-18 10:58:35 -04:00
SLACK_CHANNEL_MAP = app . config . get ( ' SLACK_CHANNEL_MAP ' , dict ( ) )
2020-06-14 13:26:40 -07:00
try :
SLACK_SEVERITY_FILTER = ast . literal_eval (
os . environ . get ( ' SLACK_SEVERITY_FILTER ' ) )
2023-03-20 23:39:34 +01:00
except Exception :
2020-06-14 13:26:40 -07:00
SLACK_SEVERITY_FILTER = app . config . get ( ' SLACK_SEVERITY_FILTER ' , list ( ) )
2017-09-04 10:20:08 +01:00
SLACK_SEND_ON_ACK = os . environ . get (
' SLACK_SEND_ON_ACK ' ) or app . config . get ( ' SLACK_SEND_ON_ACK ' , False )
SLACK_SEVERITY_MAP = app . config . get ( ' SLACK_SEVERITY_MAP ' , { } )
2023-03-20 23:39:34 +01:00
SLACK_DEFAULT_SEVERITY_MAP = { ' security ' : ' #000000 ' , # black
' critical ' : ' #FF0000 ' , # red
' major ' : ' #FFA500 ' , # orange
' minor ' : ' #FFFF00 ' , # yellow
' warning ' : ' #1E90FF ' , # blue
' informational ' : ' #808080 ' , # gray
' debug ' : ' #808080 ' , # gray
' trace ' : ' #808080 ' , # gray
' ok ' : ' #00CC00 ' } # green
SLACK_DEFAULT_SUMMARY_FMT = ' *[ {status} ] {environment} {service} {severity} * - _ {event} on {resource} _ < {dashboard} /#/alert/ {alert_id} | {short_id} > '
2018-08-23 16:50:28 -04:00
SLACK_HEADERS = {
' Content-Type ' : ' application/json '
}
2014-12-17 00:14:03 +00:00
2020-02-19 22:45:42 +01:00
2014-12-17 00:14:03 +00:00
class ServiceIntegration ( PluginBase ) :
2017-09-17 19:42:44 +01:00
def __init__ ( self , name = None ) :
2017-09-04 10:20:08 +01:00
# override user-defined severities
2017-09-17 19:42:44 +01:00
self . _severities = SLACK_DEFAULT_SEVERITY_MAP
self . _severities . update ( SLACK_SEVERITY_MAP )
2017-09-04 10:20:08 +01:00
2023-03-20 23:39:34 +01:00
super ( ) . __init__ ( name )
2014-12-17 00:14:03 +00:00
def pre_receive ( self , alert ) :
return alert
2018-09-07 13:17:15 -04:00
def _format_template ( self , templateFmt , templateVars ) :
try :
LOG . debug ( ' SLACK: generating template: %s ' % templateFmt )
template = Template ( templateFmt )
except Exception as e :
LOG . error ( ' SLACK: ERROR - Template init failed: %s ' , e )
return
try :
LOG . debug ( ' SLACK: rendering template: %s ' % templateFmt )
LOG . debug ( ' SLACK: rendering variables: %s ' % templateVars )
return template . render ( * * templateVars )
except Exception as e :
LOG . error ( ' SLACK: ERROR - Template render failed: %s ' , e )
return
2019-07-05 18:12:59 +01:00
def _slack_prepare_payload ( self , alert , status = None , text = None , * * kwargs ) :
2023-03-20 23:39:34 +01:00
SLACK_CHANNEL = self . get_config (
' SLACK_CHANNEL ' , default = ' ' , type = str , * * kwargs )
SLACK_SUMMARY_FMT = self . get_config (
' SLACK_SUMMARY_FMT ' , type = str , * * kwargs ) # Message summary format
SLACK_PAYLOAD = self . get_config (
' SLACK_PAYLOAD ' , type = str , * * kwargs ) # Full API control
2020-06-18 10:58:35 -04:00
ICON_EMOJI = self . get_config ( ' ICON_EMOJI ' , type = str , * * kwargs )
2023-03-20 23:39:34 +01:00
ALERTA_USERNAME = self . get_config (
' ALERTA_USERNAME ' , default = ' alerta ' , type = str , * * kwargs )
DASHBOARD_URL = self . get_config (
' DASHBOARD_URL ' , default = ' ' , type = str , * * kwargs )
2019-07-05 18:12:59 +01:00
SLACK_TOKEN = self . get_config ( ' SLACK_TOKEN ' , type = str , * * kwargs )
if SLACK_TOKEN :
SLACK_HEADERS [ ' Authorization ' ] = ' Bearer ' + SLACK_TOKEN
2014-12-17 00:14:03 +00:00
2017-09-04 10:20:08 +01:00
if alert . severity in self . _severities :
color = self . _severities [ alert . severity ]
2014-12-17 00:14:03 +00:00
else :
2017-09-04 10:20:08 +01:00
color = ' #00CC00 ' # green
2019-10-28 22:17:52 +01:00
channel = SLACK_CHANNEL_SEVERITY_MAP . get ( alert . severity , SLACK_CHANNEL )
if SLACK_CHANNEL_SEVERITY_MAP . get ( alert . severity ) :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' Found severity mapping. Channel: %s ' % channel )
2019-10-28 22:17:52 +01:00
else :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' No severity mapping. Channel: %s ' % channel )
2019-10-28 22:17:52 +01:00
channel = SLACK_CHANNEL_ENV_MAP . get ( alert . environment , channel )
if SLACK_CHANNEL_ENV_MAP . get ( alert . environment ) :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' Found env mapping. Channel: %s ' % channel )
2019-10-28 22:17:52 +01:00
else :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' No env mapping. Channel: %s ' % channel )
2019-10-28 22:17:52 +01:00
channel = SLACK_CHANNEL_EVENT_MAP . get ( alert . event , channel )
if SLACK_CHANNEL_EVENT_MAP . get ( alert . event ) :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' Found event mapping. Channel: %s ' % channel )
2019-10-28 22:17:52 +01:00
else :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' No event mapping. Channel: %s ' % channel )
channel = SLACK_CHANNEL_MAP . get (
alert . environment , dict ( ) ) . get ( alert . severity , channel )
2020-06-18 10:58:35 -04:00
if SLACK_CHANNEL_MAP . get ( alert . environment , dict ( ) ) . get ( alert . severity , channel ) :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' Found env-severity mapping. Channel: %s ' % channel )
2020-06-18 10:58:35 -04:00
else :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' No env-severity mapping. Channel: %s ' % channel )
2019-10-28 22:17:52 +01:00
2018-09-07 13:17:15 -04:00
templateVars = {
' alert ' : alert ,
' status ' : status if status else alert . status ,
' config ' : app . config ,
' color ' : color ,
' channel ' : channel ,
' emoji ' : ICON_EMOJI ,
}
if SLACK_PAYLOAD :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' Formatting with slack payload template ' )
formattedPayload = self . _format_template ( json . dumps (
SLACK_PAYLOAD ) , templateVars ) . replace ( ' \n ' , ' \\ n ' )
LOG . debug ( ' Formatted slack payload: \n %s ' % formattedPayload )
2018-09-25 10:34:33 -04:00
payload = json . loads ( formattedPayload )
2014-12-17 00:14:03 +00:00
else :
2018-09-07 13:17:15 -04:00
if type ( SLACK_SUMMARY_FMT ) is str :
2023-03-20 23:39:34 +01:00
summary = self . _format_template (
SLACK_SUMMARY_FMT , templateVars )
2018-09-07 13:17:15 -04:00
else :
summary = SLACK_DEFAULT_SUMMARY_FMT . format (
status = alert . status . capitalize ( ) ,
environment = alert . environment . upper ( ) ,
service = ' , ' . join ( alert . service ) ,
severity = alert . severity . capitalize ( ) ,
event = alert . event ,
resource = alert . resource ,
alert_id = alert . id ,
short_id = alert . get_id ( short = True ) ,
dashboard = DASHBOARD_URL
)
2020-06-18 10:58:35 -04:00
payload = { }
payload [ ' username ' ] = ALERTA_USERNAME
payload [ ' channel ' ] = channel
payload [ ' text ' ] = summary
if ICON_EMOJI :
payload [ ' icon_emoji ' ] = ICON_EMOJI
if SLACK_ATTACHMENTS :
payload [ ' attachments ' ] = [ {
2023-03-20 23:39:34 +01:00
' fallback ' : summary ,
' color ' : color ,
' fields ' : [
{ ' title ' : ' Status ' , ' value ' : ( status if status else alert . status ) . capitalize ( ) ,
' short ' : True } ,
{ ' title ' : ' Environment ' ,
' value ' : alert . environment , ' short ' : True } ,
{ ' title ' : ' Resource ' , ' value ' : alert . resource , ' short ' : True } ,
{ ' title ' : ' Services ' , ' value ' : ' , ' . join (
alert . service ) , ' short ' : True }
2020-06-18 10:58:35 -04:00
]
} ]
2014-12-17 00:14:03 +00:00
2017-09-04 10:20:08 +01:00
return payload
2019-07-05 18:12:59 +01:00
def post_receive ( self , alert , * * kwargs ) :
2023-03-20 23:39:34 +01:00
SLACK_WEBHOOK_URL = self . get_config (
' SLACK_WEBHOOK_URL ' , type = str , * * kwargs )
2017-09-04 10:20:08 +01:00
if alert . repeat :
return
2020-06-14 13:26:40 -07:00
if alert . severity in SLACK_SEVERITY_FILTER :
2023-03-20 23:39:34 +01:00
LOG . debug (
' Alert severity %s is included in SLACK_SEVERITY_FILTER list, thus it will not be forwarded to Slack. ' % alert . severity )
2020-06-14 13:26:40 -07:00
return
2020-11-19 22:50:05 +01:00
if alert . severity in [ ' ok ' , ' normal ' , ' cleared ' , app . config . get ( ' DEFAULT_NORMAL_SEVERITY ' ) ] and alert . previous_severity in SLACK_SEVERITY_FILTER :
2023-03-20 23:39:34 +01:00
LOG . debug ( ' Alert severity is {} but previous_severity was {} (included in SLACK_SEVERITY_FILTER list), thus it will not be forwarded to Slack. ' . format (
alert . severity , alert . previous_severity ) )
2020-11-19 22:50:05 +01:00
return
2018-09-25 10:34:33 -04:00
try :
2019-07-05 18:12:59 +01:00
payload = self . _slack_prepare_payload ( alert , * * kwargs )
2018-09-25 10:34:33 -04:00
LOG . debug ( ' Slack payload: %s ' , payload )
except Exception as e :
2023-03-20 23:39:34 +01:00
LOG . error ( ' Exception formatting payload: %s \n %s ' %
( e , traceback . format_exc ( ) ) )
2018-09-25 10:34:33 -04:00
return
2014-12-17 00:14:03 +00:00
try :
2017-09-04 10:20:08 +01:00
r = requests . post ( SLACK_WEBHOOK_URL ,
2018-08-23 16:50:28 -04:00
data = json . dumps ( payload ) , headers = SLACK_HEADERS , timeout = 2 )
2014-12-17 00:14:03 +00:00
except Exception as e :
2023-03-20 23:39:34 +01:00
raise RuntimeError ( ' Slack connection error: %s ' , e )
2014-12-17 00:14:03 +00:00
2023-03-20 23:39:34 +01:00
LOG . debug ( ' Slack response: {} \n {} ' . format ( r . status_code , r . text ) )
2014-12-17 00:14:03 +00:00
2019-07-05 18:12:59 +01:00
def status_change ( self , alert , status , text , * * kwargs ) :
2023-03-20 23:39:34 +01:00
SLACK_WEBHOOK_URL = self . get_config (
' SLACK_WEBHOOK_URL ' , type = str , * * kwargs )
2019-07-05 18:12:59 +01:00
2023-03-20 23:39:34 +01:00
if not SLACK_SEND_ON_ACK or status not in [ ' ack ' , ' assign ' ] :
2017-09-04 10:20:08 +01:00
return
2018-09-25 10:34:33 -04:00
try :
2023-03-20 23:39:34 +01:00
payload = self . _slack_prepare_payload (
alert , status , text , * * kwargs )
2018-09-25 10:34:33 -04:00
LOG . debug ( ' Slack payload: %s ' , payload )
except Exception as e :
2023-03-20 23:39:34 +01:00
LOG . error ( ' Exception formatting payload: %s \n %s ' %
( e , traceback . format_exc ( ) ) )
2018-09-25 10:34:33 -04:00
return
2017-09-04 10:20:08 +01:00
try :
r = requests . post ( SLACK_WEBHOOK_URL ,
2018-08-23 16:50:28 -04:00
data = json . dumps ( payload ) , headers = SLACK_HEADERS , timeout = 2 )
2017-09-04 10:20:08 +01:00
except Exception as e :
2023-03-20 23:39:34 +01:00
raise RuntimeError ( ' Slack connection error: %s ' , e )
2017-09-04 10:20:08 +01:00
2023-03-20 23:39:34 +01:00
LOG . debug ( ' Slack response: {} \n {} ' . format ( r . status_code , r . text ) )