mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
slack: support for blocks (#702)
* Slack: add support for blocks * Slack: drop unused validate_certs option * Slack: update docs to reflect thread_id can be sent with tokens other than WebAPI * Slack: drop escaping of quotes and apostrophes * Slack: typo * Revert "Slack: drop escaping of quotes and apostrophes" This reverts commitbc6120907e
. * Revert "Slack: drop unused validate_certs option" This reverts commita981ee6bca
. * Slack: other/minor PR feedback * Slack: add changelog fragment * Slack: clean-up/clarify use of recursive escaping function * Slack: PR feedback Co-authored-by: Lee Goolsbee <lgoolsbee@atlassian.com>
This commit is contained in:
parent
08f10d5758
commit
9822d8172b
3 changed files with 100 additions and 8 deletions
2
changelogs/fragments/702-slack-support-for-blocks.yaml
Normal file
2
changelogs/fragments/702-slack-support-for-blocks.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- slack - add support for sending messages built with block kit (https://github.com/ansible-collections/community.general/issues/380).
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# (c) 2020, Lee Goolsbee <lgoolsbee@atlassian.com>
|
||||||
# (c) 2020, Michal Middleton <mm.404@icloud.com>
|
# (c) 2020, Michal Middleton <mm.404@icloud.com>
|
||||||
# (c) 2017, Steve Pletcher <steve@steve-pletcher.com>
|
# (c) 2017, Steve Pletcher <steve@steve-pletcher.com>
|
||||||
# (c) 2016, René Moser <mail@renemoser.net>
|
# (c) 2016, René Moser <mail@renemoser.net>
|
||||||
|
@ -45,8 +46,7 @@ options:
|
||||||
URL given to you in that section."
|
URL given to you in that section."
|
||||||
- "WebAPI token:
|
- "WebAPI token:
|
||||||
Slack WebAPI requires a personal, bot or work application token. These tokens start with C(xoxp-), C(xoxb-)
|
Slack WebAPI requires a personal, bot or work application token. These tokens start with C(xoxp-), C(xoxb-)
|
||||||
or C(xoxa-), eg. C(xoxb-1234-56789abcdefghijklmnop). WebAPI token is required if you inted to receive and use
|
or C(xoxa-), eg. C(xoxb-1234-56789abcdefghijklmnop). WebAPI token is required if you intend to receive thread_id.
|
||||||
thread_id.
|
|
||||||
See Slack's documentation (U(https://api.slack.com/docs/token-types)) for more information."
|
See Slack's documentation (U(https://api.slack.com/docs/token-types)) for more information."
|
||||||
required: true
|
required: true
|
||||||
msg:
|
msg:
|
||||||
|
@ -100,7 +100,14 @@ options:
|
||||||
attachments:
|
attachments:
|
||||||
description:
|
description:
|
||||||
- Define a list of attachments. This list mirrors the Slack JSON API.
|
- Define a list of attachments. This list mirrors the Slack JSON API.
|
||||||
- For more information, see also in the (U(https://api.slack.com/docs/attachments)).
|
- For more information, see U(https://api.slack.com/docs/attachments).
|
||||||
|
blocks:
|
||||||
|
description:
|
||||||
|
- Define a list of blocks. This list mirrors the Slack JSON API.
|
||||||
|
- For more information, see U(https://api.slack.com/block-kit).
|
||||||
|
type: list
|
||||||
|
elements: dict
|
||||||
|
version_added: 1.0.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
EXAMPLES = """
|
EXAMPLES = """
|
||||||
|
@ -153,6 +160,27 @@ EXAMPLES = """
|
||||||
value: 'load average: 5,16, 4,64, 2,43'
|
value: 'load average: 5,16, 4,64, 2,43'
|
||||||
short: True
|
short: True
|
||||||
|
|
||||||
|
- name: Use the blocks API
|
||||||
|
community.general.slack:
|
||||||
|
token: thetoken/generatedby/slack
|
||||||
|
blocks:
|
||||||
|
- type: section
|
||||||
|
text:
|
||||||
|
type: mrkdwn
|
||||||
|
text: |-
|
||||||
|
*System load*
|
||||||
|
Display my system load on host A and B
|
||||||
|
- type: context
|
||||||
|
elements:
|
||||||
|
- type: mrkdwn
|
||||||
|
text: |-
|
||||||
|
*System A*
|
||||||
|
load average: 0,74, 0,66, 0,63
|
||||||
|
- type: mrkdwn
|
||||||
|
text: |-
|
||||||
|
*System B*
|
||||||
|
load average: 5,16, 4,64, 2,43
|
||||||
|
|
||||||
- name: Send a message with a link using Slack markup
|
- name: Send a message with a link using Slack markup
|
||||||
community.general.slack:
|
community.general.slack:
|
||||||
token: thetoken/generatedby/slack
|
token: thetoken/generatedby/slack
|
||||||
|
@ -206,8 +234,24 @@ 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 recursive_escape_quotes(obj, keys):
|
||||||
|
'''Recursively escape quotes inside supplied keys inside block kit objects'''
|
||||||
|
if isinstance(obj, dict):
|
||||||
|
escaped = {}
|
||||||
|
for k, v in obj.items():
|
||||||
|
if isinstance(v, str) and k in keys:
|
||||||
|
escaped[k] = escape_quotes(v)
|
||||||
|
else:
|
||||||
|
escaped[k] = recursive_escape_quotes(v, keys)
|
||||||
|
elif isinstance(obj, list):
|
||||||
|
escaped = [recursive_escape_quotes(v, keys) for v in obj]
|
||||||
|
else:
|
||||||
|
escaped = obj
|
||||||
|
return escaped
|
||||||
|
|
||||||
|
|
||||||
def build_payload_for_slack(module, text, channel, thread_id, username, icon_url, icon_emoji, link_names,
|
def build_payload_for_slack(module, text, channel, thread_id, username, icon_url, icon_emoji, link_names,
|
||||||
parse, color, attachments):
|
parse, color, attachments, blocks):
|
||||||
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))
|
||||||
|
@ -237,7 +281,7 @@ def build_payload_for_slack(module, text, channel, thread_id, username, icon_url
|
||||||
payload['attachments'] = []
|
payload['attachments'] = []
|
||||||
|
|
||||||
if attachments is not None:
|
if attachments is not None:
|
||||||
keys_to_escape = [
|
attachment_keys_to_escape = [
|
||||||
'title',
|
'title',
|
||||||
'text',
|
'text',
|
||||||
'author_name',
|
'author_name',
|
||||||
|
@ -245,7 +289,7 @@ def build_payload_for_slack(module, text, channel, thread_id, username, icon_url
|
||||||
'fallback',
|
'fallback',
|
||||||
]
|
]
|
||||||
for attachment in attachments:
|
for attachment in attachments:
|
||||||
for key in keys_to_escape:
|
for key in attachment_keys_to_escape:
|
||||||
if key in attachment:
|
if key in attachment:
|
||||||
attachment[key] = escape_quotes(attachment[key])
|
attachment[key] = escape_quotes(attachment[key])
|
||||||
|
|
||||||
|
@ -254,6 +298,13 @@ def build_payload_for_slack(module, text, channel, thread_id, username, icon_url
|
||||||
|
|
||||||
payload['attachments'].append(attachment)
|
payload['attachments'].append(attachment)
|
||||||
|
|
||||||
|
if blocks is not None:
|
||||||
|
block_keys_to_escape = [
|
||||||
|
'text',
|
||||||
|
'alt_text'
|
||||||
|
]
|
||||||
|
payload['blocks'] = recursive_escape_quotes(blocks, block_keys_to_escape)
|
||||||
|
|
||||||
payload = module.jsonify(payload)
|
payload = module.jsonify(payload)
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
@ -310,7 +361,8 @@ def main():
|
||||||
parse=dict(type='str', default=None, choices=['none', 'full']),
|
parse=dict(type='str', default=None, choices=['none', 'full']),
|
||||||
validate_certs=dict(default=True, type='bool'),
|
validate_certs=dict(default=True, type='bool'),
|
||||||
color=dict(type='str', default='normal'),
|
color=dict(type='str', default='normal'),
|
||||||
attachments=dict(type='list', required=False, default=None)
|
attachments=dict(type='list', required=False, default=None),
|
||||||
|
blocks=dict(type='list', elements='dict'),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -326,6 +378,7 @@ def main():
|
||||||
parse = module.params['parse']
|
parse = module.params['parse']
|
||||||
color = module.params['color']
|
color = module.params['color']
|
||||||
attachments = module.params['attachments']
|
attachments = module.params['attachments']
|
||||||
|
blocks = module.params['blocks']
|
||||||
|
|
||||||
color_choices = ['normal', 'good', 'warning', 'danger']
|
color_choices = ['normal', 'good', 'warning', 'danger']
|
||||||
if color not in color_choices and not is_valid_hex_color(color):
|
if color not in color_choices and not is_valid_hex_color(color):
|
||||||
|
@ -333,7 +386,7 @@ def main():
|
||||||
"or any valid hex value with length 3 or 6." % color_choices)
|
"or any valid hex value with length 3 or 6." % color_choices)
|
||||||
|
|
||||||
payload = build_payload_for_slack(module, text, channel, thread_id, username, icon_url, icon_emoji, link_names,
|
payload = build_payload_for_slack(module, text, channel, thread_id, username, icon_url, icon_emoji, link_names,
|
||||||
parse, color, attachments)
|
parse, color, attachments, blocks)
|
||||||
slack_response = do_notify_slack(module, domain, token, payload)
|
slack_response = do_notify_slack(module, domain, token, payload)
|
||||||
|
|
||||||
if 'ok' in slack_response:
|
if 'ok' in slack_response:
|
||||||
|
|
|
@ -88,6 +88,43 @@ class TestSlackModule(ModuleTestCase):
|
||||||
assert call_data['thread_ts'] == '100.00'
|
assert call_data['thread_ts'] == '100.00'
|
||||||
assert fetch_url_mock.call_args[1]['url'] == "https://hooks.slack.com/services/XXXX/YYYY/ZZZZ"
|
assert fetch_url_mock.call_args[1]['url'] == "https://hooks.slack.com/services/XXXX/YYYY/ZZZZ"
|
||||||
|
|
||||||
|
def test_message_with_blocks(self):
|
||||||
|
"""tests sending a message with blocks"""
|
||||||
|
set_module_args({
|
||||||
|
'token': 'XXXX/YYYY/ZZZZ',
|
||||||
|
'msg': 'test',
|
||||||
|
'blocks': [{
|
||||||
|
'type': 'section',
|
||||||
|
'text': {
|
||||||
|
'type': 'mrkdwn',
|
||||||
|
'text': '*test*'
|
||||||
|
},
|
||||||
|
'accessory': {
|
||||||
|
'type': 'image',
|
||||||
|
'image_url': 'https://www.ansible.com/favicon.ico',
|
||||||
|
'alt_text': 'test'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'type': 'section',
|
||||||
|
'text': {
|
||||||
|
'type': 'plain_text',
|
||||||
|
'text': 'test',
|
||||||
|
'emoji': True
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
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['blocks'][1]['text']['text'] == "test"
|
||||||
|
assert fetch_url_mock.call_args[1]['url'] == "https://hooks.slack.com/services/XXXX/YYYY/ZZZZ"
|
||||||
|
|
||||||
def test_message_with_invalid_color(self):
|
def test_message_with_invalid_color(self):
|
||||||
"""tests sending invalid color value to module"""
|
"""tests sending invalid color value to module"""
|
||||||
set_module_args({
|
set_module_args({
|
||||||
|
|
Loading…
Reference in a new issue