2020-03-09 10:11:07 +01:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (c) 2017 Marc Sensenich <hello@marc-sensenich.com>
|
|
|
|
# Copyright (c) 2017 Ansible Project
|
2022-08-05 13:17:19 +02:00
|
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
2020-03-09 10:11:07 +01:00
|
|
|
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
module: office_365_connector_card
|
|
|
|
short_description: Use webhooks to create Connector Card messages within an Office 365 group
|
|
|
|
description:
|
|
|
|
- Creates Connector Card messages through
|
2023-04-23 16:50:32 +02:00
|
|
|
Office 365 Connectors
|
|
|
|
U(https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/cards/cards-reference#connector-card-for-microsoft-365-groups).
|
2020-03-09 10:11:07 +01:00
|
|
|
author: "Marc Sensenich (@marc-sensenich)"
|
|
|
|
notes:
|
|
|
|
- This module is not idempotent, therefore if the same task is run twice
|
|
|
|
there will be two Connector Cards created
|
|
|
|
options:
|
|
|
|
webhook:
|
2020-11-05 06:50:47 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- The webhook URL is given to you when you create a new Connector.
|
|
|
|
required: true
|
|
|
|
summary:
|
2020-11-05 06:50:47 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- A string used for summarizing card content.
|
|
|
|
- This will be shown as the message subject.
|
|
|
|
- This is required if the text parameter isn't populated.
|
|
|
|
color:
|
2020-11-05 06:50:47 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Accent color used for branding or indicating status in the card.
|
|
|
|
title:
|
2020-11-05 06:50:47 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- A title for the Connector message. Shown at the top of the message.
|
|
|
|
text:
|
2020-11-05 06:50:47 +01:00
|
|
|
type: str
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- The main text of the card.
|
|
|
|
- This will be rendered below the sender information and optional title,
|
|
|
|
- and above any sections or actions present.
|
|
|
|
actions:
|
2020-11-05 06:50:47 +01:00
|
|
|
type: list
|
2021-02-16 07:11:37 +01:00
|
|
|
elements: dict
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- This array of objects will power the action links
|
|
|
|
- found at the bottom of the card.
|
|
|
|
sections:
|
2020-11-05 06:50:47 +01:00
|
|
|
type: list
|
2021-02-16 07:11:37 +01:00
|
|
|
elements: dict
|
2020-03-09 10:11:07 +01:00
|
|
|
description:
|
|
|
|
- Contains a list of sections to display in the card.
|
2023-04-23 16:50:32 +02:00
|
|
|
- For more information see U(https://learn.microsoft.com/en-us/outlook/actionable-messages/message-card-reference#section-fields).
|
2020-03-09 10:11:07 +01:00
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = """
|
|
|
|
- name: Create a simple Connector Card
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.office_365_connector_card:
|
2020-03-09 10:11:07 +01:00
|
|
|
webhook: https://outlook.office.com/webhook/GUID/IncomingWebhook/GUID/GUID
|
|
|
|
text: 'Hello, World!'
|
|
|
|
|
|
|
|
- name: Create a Connector Card with the full format
|
2020-07-13 21:50:31 +02:00
|
|
|
community.general.office_365_connector_card:
|
2020-03-09 10:11:07 +01:00
|
|
|
webhook: https://outlook.office.com/webhook/GUID/IncomingWebhook/GUID/GUID
|
|
|
|
summary: This is the summary property
|
|
|
|
title: This is the **card's title** property
|
|
|
|
text: This is the **card's text** property. Lorem ipsum dolor sit amet, consectetur
|
|
|
|
adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|
|
|
color: E81123
|
|
|
|
sections:
|
|
|
|
- title: This is the **section's title** property
|
|
|
|
activity_image: http://connectorsdemo.azurewebsites.net/images/MSC12_Oscar_002.jpg
|
|
|
|
activity_title: This is the section's **activityTitle** property
|
|
|
|
activity_subtitle: This is the section's **activitySubtitle** property
|
|
|
|
activity_text: This is the section's **activityText** property.
|
|
|
|
hero_image:
|
|
|
|
image: http://connectorsdemo.azurewebsites.net/images/WIN12_Scene_01.jpg
|
|
|
|
title: This is the image's alternate text
|
|
|
|
text: This is the section's text property. Lorem ipsum dolor sit amet, consectetur
|
|
|
|
adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
|
|
|
facts:
|
|
|
|
- name: This is a fact name
|
|
|
|
value: This is a fact value
|
|
|
|
- name: This is a fact name
|
|
|
|
value: This is a fact value
|
|
|
|
- name: This is a fact name
|
|
|
|
value: This is a fact value
|
|
|
|
images:
|
|
|
|
- image: http://connectorsdemo.azurewebsites.net/images/MicrosoftSurface_024_Cafe_OH-06315_VS_R1c.jpg
|
|
|
|
title: This is the image's alternate text
|
|
|
|
- image: http://connectorsdemo.azurewebsites.net/images/WIN12_Scene_01.jpg
|
|
|
|
title: This is the image's alternate text
|
|
|
|
- image: http://connectorsdemo.azurewebsites.net/images/WIN12_Anthony_02.jpg
|
|
|
|
title: This is the image's alternate text
|
|
|
|
actions:
|
|
|
|
- "@type": ActionCard
|
|
|
|
name: Comment
|
|
|
|
inputs:
|
|
|
|
- "@type": TextInput
|
|
|
|
id: comment
|
|
|
|
is_multiline: true
|
|
|
|
title: Input's title property
|
|
|
|
actions:
|
|
|
|
- "@type": HttpPOST
|
|
|
|
name: Save
|
|
|
|
target: http://...
|
|
|
|
- "@type": ActionCard
|
|
|
|
name: Due Date
|
|
|
|
inputs:
|
|
|
|
- "@type": DateInput
|
|
|
|
id: dueDate
|
|
|
|
title: Input's title property
|
|
|
|
actions:
|
|
|
|
- "@type": HttpPOST
|
|
|
|
name: Save
|
|
|
|
target: http://...
|
|
|
|
- "@type": HttpPOST
|
|
|
|
name: Action's name prop.
|
|
|
|
target: http://...
|
|
|
|
- "@type": OpenUri
|
|
|
|
name: Action's name prop
|
|
|
|
targets:
|
|
|
|
- os: default
|
|
|
|
uri: http://...
|
|
|
|
- start_group: true
|
|
|
|
title: This is the title of a **second section**
|
|
|
|
text: This second section is visually separated from the first one by setting its
|
|
|
|
**startGroup** property to true.
|
|
|
|
"""
|
|
|
|
|
|
|
|
RETURN = """
|
|
|
|
"""
|
|
|
|
|
|
|
|
# import module snippets
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
from ansible.module_utils.urls import fetch_url
|
|
|
|
from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict
|
|
|
|
|
|
|
|
OFFICE_365_CARD_CONTEXT = "http://schema.org/extensions"
|
|
|
|
OFFICE_365_CARD_TYPE = "MessageCard"
|
|
|
|
OFFICE_365_CARD_EMPTY_PAYLOAD_MSG = "Summary or Text is required."
|
|
|
|
OFFICE_365_INVALID_WEBHOOK_MSG = "The Incoming Webhook was not reachable."
|
|
|
|
|
|
|
|
|
|
|
|
def build_actions(actions):
|
|
|
|
action_items = []
|
|
|
|
|
|
|
|
for action in actions:
|
|
|
|
action_item = snake_dict_to_camel_dict(action)
|
|
|
|
action_items.append(action_item)
|
|
|
|
|
|
|
|
return action_items
|
|
|
|
|
|
|
|
|
|
|
|
def build_sections(sections):
|
|
|
|
sections_created = []
|
|
|
|
|
|
|
|
for section in sections:
|
|
|
|
sections_created.append(build_section(section))
|
|
|
|
|
|
|
|
return sections_created
|
|
|
|
|
|
|
|
|
|
|
|
def build_section(section):
|
|
|
|
section_payload = dict()
|
|
|
|
|
|
|
|
if 'title' in section:
|
|
|
|
section_payload['title'] = section['title']
|
|
|
|
|
|
|
|
if 'start_group' in section:
|
|
|
|
section_payload['startGroup'] = section['start_group']
|
|
|
|
|
|
|
|
if 'activity_image' in section:
|
|
|
|
section_payload['activityImage'] = section['activity_image']
|
|
|
|
|
|
|
|
if 'activity_title' in section:
|
|
|
|
section_payload['activityTitle'] = section['activity_title']
|
|
|
|
|
|
|
|
if 'activity_subtitle' in section:
|
|
|
|
section_payload['activitySubtitle'] = section['activity_subtitle']
|
|
|
|
|
|
|
|
if 'activity_text' in section:
|
|
|
|
section_payload['activityText'] = section['activity_text']
|
|
|
|
|
|
|
|
if 'hero_image' in section:
|
|
|
|
section_payload['heroImage'] = section['hero_image']
|
|
|
|
|
|
|
|
if 'text' in section:
|
|
|
|
section_payload['text'] = section['text']
|
|
|
|
|
|
|
|
if 'facts' in section:
|
|
|
|
section_payload['facts'] = section['facts']
|
|
|
|
|
|
|
|
if 'images' in section:
|
|
|
|
section_payload['images'] = section['images']
|
|
|
|
|
|
|
|
if 'actions' in section:
|
|
|
|
section_payload['potentialAction'] = build_actions(section['actions'])
|
|
|
|
|
|
|
|
return section_payload
|
|
|
|
|
|
|
|
|
|
|
|
def build_payload_for_connector_card(module, summary=None, color=None, title=None, text=None, actions=None, sections=None):
|
|
|
|
payload = dict()
|
|
|
|
payload['@context'] = OFFICE_365_CARD_CONTEXT
|
|
|
|
payload['@type'] = OFFICE_365_CARD_TYPE
|
|
|
|
|
|
|
|
if summary is not None:
|
|
|
|
payload['summary'] = summary
|
|
|
|
|
|
|
|
if color is not None:
|
|
|
|
payload['themeColor'] = color
|
|
|
|
|
|
|
|
if title is not None:
|
|
|
|
payload['title'] = title
|
|
|
|
|
|
|
|
if text is not None:
|
|
|
|
payload['text'] = text
|
|
|
|
|
|
|
|
if actions:
|
|
|
|
payload['potentialAction'] = build_actions(actions)
|
|
|
|
|
|
|
|
if sections:
|
|
|
|
payload['sections'] = build_sections(sections)
|
|
|
|
|
|
|
|
payload = module.jsonify(payload)
|
|
|
|
return payload
|
|
|
|
|
|
|
|
|
|
|
|
def do_notify_connector_card_webhook(module, webhook, payload):
|
|
|
|
headers = {
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
}
|
|
|
|
|
|
|
|
response, info = fetch_url(
|
|
|
|
module=module,
|
|
|
|
url=webhook,
|
|
|
|
headers=headers,
|
|
|
|
method='POST',
|
|
|
|
data=payload
|
|
|
|
)
|
|
|
|
|
|
|
|
if info['status'] == 200:
|
|
|
|
module.exit_json(changed=True)
|
|
|
|
elif info['status'] == 400 and module.check_mode:
|
|
|
|
if info['body'] == OFFICE_365_CARD_EMPTY_PAYLOAD_MSG:
|
|
|
|
module.exit_json(changed=True)
|
|
|
|
else:
|
|
|
|
module.fail_json(msg=OFFICE_365_INVALID_WEBHOOK_MSG)
|
|
|
|
else:
|
|
|
|
module.fail_json(
|
|
|
|
msg="failed to send %s as a connector card to Incoming Webhook: %s"
|
|
|
|
% (payload, info['msg'])
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=dict(
|
|
|
|
webhook=dict(required=True, no_log=True),
|
|
|
|
summary=dict(type='str'),
|
|
|
|
color=dict(type='str'),
|
|
|
|
title=dict(type='str'),
|
|
|
|
text=dict(type='str'),
|
2021-02-16 07:11:37 +01:00
|
|
|
actions=dict(type='list', elements='dict'),
|
|
|
|
sections=dict(type='list', elements='dict')
|
2020-03-09 10:11:07 +01:00
|
|
|
),
|
|
|
|
supports_check_mode=True
|
|
|
|
)
|
|
|
|
|
|
|
|
webhook = module.params['webhook']
|
|
|
|
summary = module.params['summary']
|
|
|
|
color = module.params['color']
|
|
|
|
title = module.params['title']
|
|
|
|
text = module.params['text']
|
|
|
|
actions = module.params['actions']
|
|
|
|
sections = module.params['sections']
|
|
|
|
|
|
|
|
payload = build_payload_for_connector_card(
|
|
|
|
module,
|
|
|
|
summary,
|
|
|
|
color,
|
|
|
|
title,
|
|
|
|
text,
|
|
|
|
actions,
|
|
|
|
sections)
|
|
|
|
|
|
|
|
if module.check_mode:
|
|
|
|
# In check mode, send an empty payload to validate connection
|
|
|
|
check_mode_payload = build_payload_for_connector_card(module)
|
|
|
|
do_notify_connector_card_webhook(module, webhook, check_mode_payload)
|
|
|
|
|
|
|
|
do_notify_connector_card_webhook(module, webhook, payload)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|