Further work to allow customization of slack payload:

- Refactor template expansion logic to be used in several places
- Implement SLACK_PAYLOAD, which allows complete customization of the post payload,
  unlocking all Slack API features and providing Jinja2 templating
- The payload support reverts the need for the SLACK_ATTACHMENT_SIZE_MAP.
- Put back the default behavior with SLACK_ATTACHMENTS
- Update doc to show how to use SLACK_PAYLOAD
- Add example use including button to launch alert console
This commit is contained in:
Kurt Westerfeld 2018-09-07 04:50:50 -07:00
parent 52629caed7
commit 89ef711ee6
2 changed files with 119 additions and 70 deletions

View file

@ -55,10 +55,17 @@ the Alerta console:
DASHBOARD_URL = '' # default="not set"
```
The `SLACK_SUMMARY_FMT` configuration variable is a Jinja2 template
string and accepts any Jinja2 syntax. The formatter has access to two
variables in the template environment, 'alert' for all alert details
and 'config' for access to the alerta configuration.
The `SLACK_SUMMARY_FMT` configuration variable is a Jinja2 template string and
accepts any Jinja2 syntax. The formatter has access to the following variables
in the template environment:
| Variable | Description |
| `alert` | For all alert details |
| `status` | The updated alert status |
| `config` | alerta configuration |
| `color` | The computed severity color |
| `channel` | The computed channel |
| `emoji` | The computed emojii |
If you have Jinja2 available you can try customizing the message like
this:
@ -67,6 +74,42 @@ this:
SLACK_SUMMARY_FMT = '*[{{ alert.status|capitalize }}]* [{{ alert.severity|capitalize }}] Event {{ alert.event }} on *{{ alert.environment }} - {{ alert.resource }}*: {{alert.value}}\n{{alert.text}}\nAlert Console: <{{ config.DASHBOARD_URL }}|click here> / Alert: <{{ config.DASHBOARD_URL }}/#/alert/{{ alert.id }}|{{ alert.id[:8] }}>'
```
Slack Payload
-------------
Sometimes the defaults chosen by the plugin won't be flexible enough. In this case,
it is possible to use the full capability of the Slack post message API, documented
here: https://api.slack.com/methods/chat.postMessage.
To utilize the API, one must craft the HTTP POST payload, which is possible by utilizing
the Jinja2 template format mechanism, and supplying a `SLACK_PAYLOAD` configuration.
Each part of the template can be fully customized by template expansion.
You can utilize the full power of the Slack API with this approach, including adding
images, interactive buttons and menus.
Example:
```python
SLACK_PAYLOAD = {
"channel": "{{ channel }}",
"emoji": ":fire:",
"text": "*[{{ alert.environment }}]* :: _{{ status }}_ :: _{{ alert.severity|capitalize }}_ :: _{{ alert.value }}_\n```{{ alert.text }}```",
"attachments": [{
"color": "{{ color }}",
"fields": [
{"title": "Resource", "value": "{{ alert.resource }}", "short": False },
{"title": "Services", "value": "{{ alert.service|join(', ') }}", "short": False },
{"title": "Event", "value": "{{ alert.event }}", "short": True },
{"title": "Origin", "value": "{{ alert.origin }}", "short": True },
],
"actions": [
{ "type": "button", "text": "Alert Console", "url": "{{ config.DASHBOARD_URL }}/#/alert/{{ alert.id }}" },
]
}]
}
```
Slack Apps API
--------------
To use the Slack "Apps" API instead of an Incoming Webhook, create an application and
@ -85,6 +128,7 @@ References
----------
* Slack Incoming Webhooks: https://api.slack.com/incoming-webhooks
* Slack post message API: https://api.slack.com/methods/chat.postMessage
License
-------

View file

@ -40,17 +40,11 @@ SLACK_DEFAULT_SEVERITY_MAP = {'security': '#000000', # black
'debug': '#808080', # gray
'trace': '#808080', # gray
'ok': '#00CC00'} # green
SLACK_SUMMARY_FMT = os.environ.get('SLACK_SUMMARY_FMT') or app.config.get('SLACK_SUMMARY_FMT', None) # Message summary format
SLACK_DEFAULT_SUMMARY_FMT='<b>[{status}] {environment} {service} {severity} - <i>{event} on {resource}</i></b> <a href="{dashboard}/#/alert/{alert_id}">{short_id}</a>'
SLACK_DEFAULT_ATTACHMENT_SIZE_MAP = {
'resource': False,
'severity': True,
'environment': True,
'status': True,
'services': True}
SLACK_ATTACHMENT_SIZE_MAP = app.config.get('SLACK_ATTACHMENT_SIZE_MAP', SLACK_DEFAULT_ATTACHMENT_SIZE_MAP)
SLACK_SUMMARY_FMT = app.config.get('SLACK_SUMMARY_FMT', None) # Message summary format
SLACK_DEFAULT_SUMMARY_FMT='*[{status}] {environment} {service} {severity}* - _{event} on {resource}_ <{dashboard}/#/alert/{alert_id}|{short_id}>'
ICON_EMOJI = os.environ.get('ICON_EMOJI') or app.config.get(
'ICON_EMOJI', ':rocket:')
SLACK_PAYLOAD = app.config.get('SLACK_PAYLOAD', None) # Full API control
DASHBOARD_URL = os.environ.get(
'DASHBOARD_URL') or app.config.get('DASHBOARD_URL', '')
SLACK_HEADERS = {
@ -72,72 +66,83 @@ class ServiceIntegration(PluginBase):
def pre_receive(self, alert):
return alert
def _slack_prepare_payload(self, alert, status=None, text=None):
if SLACK_SUMMARY_FMT:
try:
LOG.debug('SLACK: generating template: %s' % SLACK_SUMMARY_FMT)
template = Template(SLACK_SUMMARY_FMT)
except Exception as e:
LOG.error('SLACK: ERROR - Template init failed: %s', e)
return
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' % SLACK_SUMMARY_FMT)
template_vars = {
'alert': alert,
'config': app.config
}
summary = template.render(**template_vars)
except Exception as e:
LOG.error('SLACK: ERROR - Template render failed: %s', e)
return
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
)
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
def _slack_prepare_payload(self, alert, status=None, text=None):
if alert.severity in self._severities:
color = self._severities[alert.severity]
else:
color = '#00CC00' # green
channel = SLACK_CHANNEL_ENV_MAP.get(alert.environment, SLACK_CHANNEL)
templateVars = {
'alert': alert,
'status': status if status else alert.status,
'config': app.config,
'color': color,
'channel': channel,
'emoji': ICON_EMOJI,
}
if not SLACK_ATTACHMENTS:
payload = {
"username": ALERTA_USERNAME,
"channel": channel,
"text": summary,
"icon_emoji": ICON_EMOJI
}
if SLACK_PAYLOAD:
LOG.info("Formatting with slack payload")
payload = json.loads(self._format_template(json.dumps(SLACK_PAYLOAD), templateVars))
else:
payload = {
"username": ALERTA_USERNAME,
"channel": channel,
"icon_emoji": ICON_EMOJI,
"text": summary,
"attachments": [{
"color": color,
"fields": [
{"title": "Resource", "value": alert.resource, "short": SLACK_ATTACHMENT_SIZE_MAP['resource']},
{"title": "Severity", "value": alert.severity.capitalize(), "short": SLACK_ATTACHMENT_SIZE_MAP['severity']},
{"title": "Status", "value": (status if status else alert.status).capitalize(),
"short": SLACK_ATTACHMENT_SIZE_MAP['status']},
{"title": "Environment",
"value": alert.environment, "short": SLACK_ATTACHMENT_SIZE_MAP['environment']},
{"title": "Services", "value": ", ".join(
alert.service), "short": SLACK_ATTACHMENT_SIZE_MAP['services']}
]
}]
}
if type(SLACK_SUMMARY_FMT) is str:
summary = self._format_template(SLACK_SUMMARY_FMT, templateVars)
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
)
if not SLACK_ATTACHMENTS:
payload = {
"username": ALERTA_USERNAME,
"channel": channel,
"text": summary,
"icon_emoji": ICON_EMOJI
}
else:
payload = {
"username": ALERTA_USERNAME,
"channel": channel,
"icon_emoji": ICON_EMOJI,
"text": summary,
"attachments": [{
"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}
]
}]
}
return payload