1
0
Fork 0
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 commit bc6120907e.

* Revert "Slack: drop unused validate_certs option"

This reverts commit a981ee6bca.

* 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:
Lee Goolsbee 2020-07-29 15:11:32 -05:00 committed by GitHub
parent 08f10d5758
commit 9822d8172b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 8 deletions

View 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).

View file

@ -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:

View file

@ -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({