Templates for Slack notifications
This change adds an optional, user configurable template which may be used to format the text contained in Slack notifications. Story: 2001308 Task: 5859 Change-Id: Id936c3dc8b4f3e2430de20c8b69d0e703b1cf9ef
This commit is contained in:
parent
ac837fa3cc
commit
39a906b8fb
92
README.rst
92
README.rst
@ -131,6 +131,94 @@ StatsD server launched by monasca-agent. Default host and port points to
|
|||||||
- ConfigDBTime
|
- ConfigDBTime
|
||||||
- SendNotificationTime
|
- SendNotificationTime
|
||||||
|
|
||||||
|
Plugins
|
||||||
|
-------
|
||||||
|
|
||||||
|
The following notification plugins are available:
|
||||||
|
|
||||||
|
- Email
|
||||||
|
- HipChat
|
||||||
|
- Jira
|
||||||
|
- Pagerduty
|
||||||
|
- Slack
|
||||||
|
- Webhook
|
||||||
|
|
||||||
|
The plugins can be configured via the Monasca Notification config file. In
|
||||||
|
general you will need to follow these steps to enable a plugin:
|
||||||
|
|
||||||
|
- Make sure that the plugin is enabled in the config file
|
||||||
|
- Make sure that the plugin is configured in the config file
|
||||||
|
- Restart the Monasca Notification service
|
||||||
|
|
||||||
|
Slack plugin
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To use the Slack plugin you must first configure an incoming `webhook`_
|
||||||
|
for the Slack channel you wish to post notifications to. The notification can
|
||||||
|
then be created as follows:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
monasca notification-create slack_notification slack https://hooks.slack.com/services/MY/SECRET/WEBHOOK/URL
|
||||||
|
|
||||||
|
Note that whilst it is also possible to use a token instead of a webhook,
|
||||||
|
this approach is now `deprecated`_.
|
||||||
|
|
||||||
|
By default the Slack notification will dump all available information into
|
||||||
|
the alert. For example, a notification may be posted to Slack which looks
|
||||||
|
like this:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
"metrics":[
|
||||||
|
{
|
||||||
|
"dimensions":{
|
||||||
|
"hostname":"operator"
|
||||||
|
},
|
||||||
|
"id":null,
|
||||||
|
"name":"cpu.user_perc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"alarm_id":"20a54a65-44b8-4ac9-a398-1f2d888827d2",
|
||||||
|
"state":"ALARM",
|
||||||
|
"alarm_timestamp":1556703552,
|
||||||
|
"tenant_id":"62f7a7a314904aa3ab137d569d6b4fde",
|
||||||
|
"old_state":"OK",
|
||||||
|
"alarm_description":"Dummy alarm",
|
||||||
|
"message":"Thresholds were exceeded for the sub-alarms: count(cpu.user_perc, deterministic) >= 1.0 with the values: [1.0]",
|
||||||
|
"alarm_definition_id":"78ce7b53-f7e6-4b51-88d0-cb741e7dc906",
|
||||||
|
"alarm_name":"dummy_alarm"
|
||||||
|
}
|
||||||
|
|
||||||
|
The format of the above message can be customised with a Jinja template. All fields
|
||||||
|
from the raw Slack message are available in the template. For example, you may
|
||||||
|
configure the plugin as follows:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
[notification_types]
|
||||||
|
enabled = slack
|
||||||
|
|
||||||
|
[slack_notifier]
|
||||||
|
message_template = /etc/monasca/slack_template.j2
|
||||||
|
timeout = 10
|
||||||
|
ca_certs = /etc/ssl/certs/ca-bundle.crt
|
||||||
|
insecure = False
|
||||||
|
|
||||||
|
With the following contents of `/etc/monasca/slack_template.j2`:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{{ alarm_name }} has triggered on {% for item in metrics %}host {{ item.dimensions.hostname }}{% if not loop.last %}, {% endif %}{% endfor %}.
|
||||||
|
|
||||||
|
With this configuration, the raw Slack message above would be transformed
|
||||||
|
into:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
dummy_alarm has triggered on host(s): operator.
|
||||||
|
|
||||||
Future Considerations
|
Future Considerations
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
@ -146,3 +234,7 @@ Future Considerations
|
|||||||
NotificationEngine instance using webhooks to a local http server. Is
|
NotificationEngine instance using webhooks to a local http server. Is
|
||||||
that fast enough?
|
that fast enough?
|
||||||
- Are we putting too much load on Kafka at ~200 commits per second?
|
- Are we putting too much load on Kafka at ~200 commits per second?
|
||||||
|
|
||||||
|
.. _webhook: https://api.slack.com/incoming-webhooks
|
||||||
|
|
||||||
|
.. _deprecated: https://api.slack.com/custom-integrations/legacy-tokens
|
||||||
|
@ -14,11 +14,13 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
import requests
|
import requests
|
||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
import ujson as json
|
import ujson as json
|
||||||
|
|
||||||
from debtcollector import removals
|
from debtcollector import removals
|
||||||
|
import jinja2
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from monasca_notification.plugins import abstract_notifier
|
from monasca_notification.plugins import abstract_notifier
|
||||||
@ -86,10 +88,19 @@ class SlackNotifier(abstract_notifier.AbstractNotifier):
|
|||||||
'metrics': notification.metrics}
|
'metrics': notification.metrics}
|
||||||
|
|
||||||
slack_request = {}
|
slack_request = {}
|
||||||
slack_request['text'] = json.dumps(body, indent=3)
|
if CONF.slack_notifier.message_template:
|
||||||
|
slack_request['text'] = self._render_message_template(body)
|
||||||
|
else:
|
||||||
|
slack_request['text'] = json.dumps(body, indent=3)
|
||||||
|
|
||||||
return slack_request
|
return slack_request
|
||||||
|
|
||||||
|
def _render_message_template(self, params):
|
||||||
|
path, name = os.path.split(CONF.slack_notifier.message_template)
|
||||||
|
loader = jinja2.FileSystemLoader(path)
|
||||||
|
env = jinja2.Environment(loader=loader, autoescape=True)
|
||||||
|
return env.get_template(name).render(params)
|
||||||
|
|
||||||
def _check_response(self, result):
|
def _check_response(self, result):
|
||||||
if 'application/json' in result.headers.get('Content-Type'):
|
if 'application/json' in result.headers.get('Content-Type'):
|
||||||
response = result.json()
|
response = result.json()
|
||||||
@ -192,7 +203,8 @@ slack_notifier_opts = [
|
|||||||
cfg.IntOpt(name='timeout', default=5, min=1),
|
cfg.IntOpt(name='timeout', default=5, min=1),
|
||||||
cfg.BoolOpt(name='insecure', default=True),
|
cfg.BoolOpt(name='insecure', default=True),
|
||||||
cfg.StrOpt(name='ca_certs', default=None),
|
cfg.StrOpt(name='ca_certs', default=None),
|
||||||
cfg.StrOpt(name='proxy', default=None)
|
cfg.StrOpt(name='proxy', default=None),
|
||||||
|
cfg.StrOpt(name='message_template', default=None)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
1
tests/resources/test_slackformat.template
Normal file
1
tests/resources/test_slackformat.template
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{ alarm_name }} has triggered on {% for item in metrics %}host {{ metrics[0].dimensions.hostname }}, service {{ item.dimensions.service }}{% if not loop.last %}, {% endif %}{% endfor %}.
|
@ -110,6 +110,15 @@ class TestSlack(base.PluginTestCase):
|
|||||||
def _validate_post_args(self, post_args, data_format):
|
def _validate_post_args(self, post_args, data_format):
|
||||||
self.assertEqual(slack_text(),
|
self.assertEqual(slack_text(),
|
||||||
json.loads(post_args.get(data_format).get('text')))
|
json.loads(post_args.get(data_format).get('text')))
|
||||||
|
self._validate_post_args_base(post_args)
|
||||||
|
|
||||||
|
def _validate_templated_post_args(self, post_args, data_format,
|
||||||
|
expected_slack_text):
|
||||||
|
self.assertEqual(expected_slack_text,
|
||||||
|
post_args.get(data_format).get('text'))
|
||||||
|
self._validate_post_args_base(post_args)
|
||||||
|
|
||||||
|
def _validate_post_args_base(self, post_args):
|
||||||
self.assertEqual({'https': 'http://yourid:password@proxyserver:8080'},
|
self.assertEqual({'https': 'http://yourid:password@proxyserver:8080'},
|
||||||
post_args.get('proxies'))
|
post_args.get('proxies'))
|
||||||
self.assertEqual(50, post_args.get('timeout'))
|
self.assertEqual(50, post_args.get('timeout'))
|
||||||
@ -128,6 +137,24 @@ class TestSlack(base.PluginTestCase):
|
|||||||
self._validate_post_args(mock_method.call_args_list[0][1], 'json')
|
self._validate_post_args(mock_method.call_args_list[0][1], 'json')
|
||||||
self.assertEqual([], slack_notifier.SlackNotifier._raw_data_url_caches)
|
self.assertEqual([], slack_notifier.SlackNotifier._raw_data_url_caches)
|
||||||
|
|
||||||
|
def test_templated_slack_webhook_success(self):
|
||||||
|
"""A templated message is successfully sent as json
|
||||||
|
"""
|
||||||
|
self.conf_override(
|
||||||
|
message_template='tests/resources/test_slackformat.template',
|
||||||
|
group='slack_notifier'
|
||||||
|
)
|
||||||
|
response_list = [RequestsResponse(200, 'ok',
|
||||||
|
{'Content-Type': 'application/text'})]
|
||||||
|
mock_method, result = self._notify(response_list)
|
||||||
|
self.assertTrue(result)
|
||||||
|
mock_method.assert_called_once()
|
||||||
|
expected_text = 'test Alarm has triggered on host foo1, service bar1.'
|
||||||
|
self._validate_templated_post_args(mock_method.call_args_list[0][1],
|
||||||
|
'json',
|
||||||
|
expected_text)
|
||||||
|
self.assertEqual([], slack_notifier.SlackNotifier._raw_data_url_caches)
|
||||||
|
|
||||||
def test_slack_webhook_fail(self):
|
def test_slack_webhook_fail(self):
|
||||||
"""data is sent twice as json and raw data, and slack returns failure for
|
"""data is sent twice as json and raw data, and slack returns failure for
|
||||||
both requests
|
both requests
|
||||||
|
Loading…
Reference in New Issue
Block a user