mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-05-06 01:01:32 -07:00
Add threading to slack notification module (#47333)
* Add threading to slack notification module * Fix sanity check * Change thread_ts to thread_id
This commit is contained in:
parent
9949629e5a
commit
43bbc505a1
2 changed files with 100 additions and 3 deletions
|
@ -55,6 +55,10 @@ options:
|
||||||
channel:
|
channel:
|
||||||
description:
|
description:
|
||||||
- Channel to send the message to. If absent, the message goes to the channel selected for the I(token).
|
- 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:
|
username:
|
||||||
description:
|
description:
|
||||||
- This is the sender of the message.
|
- This is the sender of the message.
|
||||||
|
@ -113,6 +117,7 @@ EXAMPLES = """
|
||||||
token: thetoken/generatedby/slack
|
token: thetoken/generatedby/slack
|
||||||
msg: '{{ inventory_hostname }} completed'
|
msg: '{{ inventory_hostname }} completed'
|
||||||
channel: '#ansible'
|
channel: '#ansible'
|
||||||
|
thread_id: 1539917263.000100
|
||||||
username: 'Ansible on {{ inventory_hostname }}'
|
username: 'Ansible on {{ inventory_hostname }}'
|
||||||
icon_url: http://www.example.com/some-image-file.png
|
icon_url: http://www.example.com/some-image-file.png
|
||||||
link_names: 0
|
link_names: 0
|
||||||
|
@ -173,7 +178,8 @@ def escape_quotes(text):
|
||||||
return "".join(escape_table.get(c, c) for c in 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 = {}
|
payload = {}
|
||||||
if color == "normal" and text is not None:
|
if color == "normal" and text is not None:
|
||||||
payload = dict(text=escape_quotes(text))
|
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
|
payload['channel'] = channel
|
||||||
else:
|
else:
|
||||||
payload['channel'] = '#' + channel
|
payload['channel'] = '#' + channel
|
||||||
|
if thread_id is not None:
|
||||||
|
payload['thread_ts'] = thread_id
|
||||||
if username is not None:
|
if username is not None:
|
||||||
payload['username'] = username
|
payload['username'] = username
|
||||||
if icon_emoji is not None:
|
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)
|
slack_incoming_webhook = SLACK_INCOMING_WEBHOOK % (token)
|
||||||
else:
|
else:
|
||||||
if not domain:
|
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)
|
slack_incoming_webhook = OLD_SLACK_INCOMING_WEBHOOK % (domain, token)
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
|
@ -249,6 +258,7 @@ def main():
|
||||||
token=dict(type='str', required=True, no_log=True),
|
token=dict(type='str', required=True, no_log=True),
|
||||||
msg=dict(type='str', required=False, default=None),
|
msg=dict(type='str', required=False, default=None),
|
||||||
channel=dict(type='str', default=None),
|
channel=dict(type='str', default=None),
|
||||||
|
thread_id=dict(type='float', default=None),
|
||||||
username=dict(type='str', default='Ansible'),
|
username=dict(type='str', default='Ansible'),
|
||||||
icon_url=dict(type='str', default='https://www.ansible.com/favicon.ico'),
|
icon_url=dict(type='str', default='https://www.ansible.com/favicon.ico'),
|
||||||
icon_emoji=dict(type='str', default=None),
|
icon_emoji=dict(type='str', default=None),
|
||||||
|
@ -264,6 +274,7 @@ def main():
|
||||||
token = module.params['token']
|
token = module.params['token']
|
||||||
text = module.params['msg']
|
text = module.params['msg']
|
||||||
channel = module.params['channel']
|
channel = module.params['channel']
|
||||||
|
thread_id = module.params['thread_id']
|
||||||
username = module.params['username']
|
username = module.params['username']
|
||||||
icon_url = module.params['icon_url']
|
icon_url = module.params['icon_url']
|
||||||
icon_emoji = module.params['icon_emoji']
|
icon_emoji = module.params['icon_emoji']
|
||||||
|
@ -272,7 +283,8 @@ def main():
|
||||||
color = module.params['color']
|
color = module.params['color']
|
||||||
attachments = module.params['attachments']
|
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)
|
do_notify_slack(module, domain, token, payload)
|
||||||
|
|
||||||
module.exit_json(msg="OK")
|
module.exit_json(msg="OK")
|
||||||
|
|
85
test/units/modules/notification/test_slack.py
Normal file
85
test/units/modules/notification/test_slack.py
Normal file
|
@ -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…
Add table
Add a link
Reference in a new issue