Add threading to slack notification module (#47333)

* Add threading to slack notification module

* Fix sanity check

* Change thread_ts to thread_id
pull/48173/head
Andrew Herrington 6 years ago committed by ansibot
parent 9949629e5a
commit 43bbc505a1

@ -55,6 +55,10 @@ options:
channel:
description:
- Channel to send the message to. If absent, the message goes to the channel selected for the I(token).
thread_id:
version_added: 2.8
description:
- Optional. Timestamp of message to thread this message to as a float. https://api.slack.com/docs/message-threading
username:
description:
- This is the sender of the message.
@ -113,6 +117,7 @@ EXAMPLES = """
token: thetoken/generatedby/slack
msg: '{{ inventory_hostname }} completed'
channel: '#ansible'
thread_id: 1539917263.000100
username: 'Ansible on {{ inventory_hostname }}'
icon_url: http://www.example.com/some-image-file.png
link_names: 0
@ -173,7 +178,8 @@ def escape_quotes(text):
return "".join(escape_table.get(c, c) for c in text)
def build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments):
def build_payload_for_slack(module, text, channel, thread_id, username, icon_url, icon_emoji, link_names,
parse, color, attachments):
payload = {}
if color == "normal" and text is not None:
payload = dict(text=escape_quotes(text))
@ -185,6 +191,8 @@ def build_payload_for_slack(module, text, channel, username, icon_url, icon_emoj
payload['channel'] = channel
else:
payload['channel'] = '#' + channel
if thread_id is not None:
payload['thread_ts'] = thread_id
if username is not None:
payload['username'] = username
if icon_emoji is not None:
@ -228,7 +236,8 @@ def do_notify_slack(module, domain, token, payload):
slack_incoming_webhook = SLACK_INCOMING_WEBHOOK % (token)
else:
if not domain:
module.fail_json(msg="Slack has updated its webhook API. You need to specify a token of the form XXXX/YYYY/ZZZZ in your playbook")
module.fail_json(msg="Slack has updated its webhook API. You need to specify a token of the form "
"XXXX/YYYY/ZZZZ in your playbook")
slack_incoming_webhook = OLD_SLACK_INCOMING_WEBHOOK % (domain, token)
headers = {
@ -249,6 +258,7 @@ def main():
token=dict(type='str', required=True, no_log=True),
msg=dict(type='str', required=False, default=None),
channel=dict(type='str', default=None),
thread_id=dict(type='float', default=None),
username=dict(type='str', default='Ansible'),
icon_url=dict(type='str', default='https://www.ansible.com/favicon.ico'),
icon_emoji=dict(type='str', default=None),
@ -264,6 +274,7 @@ def main():
token = module.params['token']
text = module.params['msg']
channel = module.params['channel']
thread_id = module.params['thread_id']
username = module.params['username']
icon_url = module.params['icon_url']
icon_emoji = module.params['icon_emoji']
@ -272,7 +283,8 @@ def main():
color = module.params['color']
attachments = module.params['attachments']
payload = build_payload_for_slack(module, text, channel, username, icon_url, icon_emoji, link_names, parse, color, attachments)
payload = build_payload_for_slack(module, text, channel, thread_id, username, icon_url, icon_emoji, link_names,
parse, color, attachments)
do_notify_slack(module, domain, token, payload)
module.exit_json(msg="OK")

@ -0,0 +1,85 @@
import json
import pytest
from units.compat.mock import patch
from ansible.modules.notification import slack
from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
from ansible import module_utils
class TestSlackModule(ModuleTestCase):
def setUp(self):
super(TestSlackModule, self).setUp()
self.module = slack
def tearDown(self):
super(TestSlackModule, self).tearDown()
@pytest.fixture
def fetch_url_mock(self, mocker):
return mocker.patch('ansible.module_utils.notification.slack.fetch_url')
def test_without_required_parameters(self):
"""Failure must occurs when all parameters are missing"""
with self.assertRaises(AnsibleFailJson):
set_module_args({})
self.module.main()
def test_invalid_old_token(self):
"""Failure if there is an old style token"""
set_module_args({
'token': 'test',
})
with self.assertRaises(AnsibleFailJson):
self.module.main()
def test_sucessful_message(self):
"""tests sending a message. This is example 1 from the docs"""
set_module_args({
'token': 'XXXX/YYYY/ZZZZ',
'msg': 'test'
})
with patch.object(slack, "fetch_url") as fetch_url_mock:
fetch_url_mock.return_value = (None, {"status": 200})
with self.assertRaises(AnsibleExitJson):
self.module.main()
self.assertTrue(fetch_url_mock.call_count, 1)
call_data = json.loads(fetch_url_mock.call_args[1]['data'])
assert call_data['username'] == "Ansible"
assert call_data['text'] == "test"
assert fetch_url_mock.call_args[1]['url'] == "https://hooks.slack.com/services/XXXX/YYYY/ZZZZ"
def test_failed_message(self):
"""tests failing to send a message"""
set_module_args({
'token': 'XXXX/YYYY/ZZZZ',
'msg': 'test'
})
with patch.object(slack, "fetch_url") as fetch_url_mock:
fetch_url_mock.return_value = (None, {"status": 404, 'msg': 'test'})
with self.assertRaises(AnsibleFailJson):
self.module.main()
def test_message_with_thread(self):
"""tests sending a message with a thread"""
set_module_args({
'token': 'XXXX/YYYY/ZZZZ',
'msg': 'test',
'thread_id': 100.00
})
with patch.object(slack, "fetch_url") as fetch_url_mock:
fetch_url_mock.return_value = (None, {"status": 200})
with self.assertRaises(AnsibleExitJson):
self.module.main()
self.assertTrue(fetch_url_mock.call_count, 1)
call_data = json.loads(fetch_url_mock.call_args[1]['data'])
assert call_data['username'] == "Ansible"
assert call_data['text'] == "test"
assert call_data['thread_ts'] == 100.00
assert fetch_url_mock.call_args[1]['url'] == "https://hooks.slack.com/services/XXXX/YYYY/ZZZZ"
Loading…
Cancel
Save