diff options
Diffstat (limited to 'ansible_collections/community/general/plugins/callback/slack.py')
-rw-r--r-- | ansible_collections/community/general/plugins/callback/slack.py | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/callback/slack.py b/ansible_collections/community/general/plugins/callback/slack.py new file mode 100644 index 000000000..e9b84bbb3 --- /dev/null +++ b/ansible_collections/community/general/plugins/callback/slack.py @@ -0,0 +1,253 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2014-2015, Matt Martz <matt@sivel.net> +# Copyright (c) 2017 Ansible Project +# 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 + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = ''' + author: Unknown (!UNKNOWN) + name: slack + type: notification + requirements: + - whitelist in configuration + - prettytable (python library) + short_description: Sends play events to a Slack channel + description: + - This is an ansible callback plugin that sends status updates to a Slack channel during playbook execution. + - Before Ansible 2.4 only environment variables were available for configuring this plugin. + options: + webhook_url: + required: true + description: Slack Webhook URL. + env: + - name: SLACK_WEBHOOK_URL + ini: + - section: callback_slack + key: webhook_url + channel: + default: "#ansible" + description: Slack room to post in. + env: + - name: SLACK_CHANNEL + ini: + - section: callback_slack + key: channel + username: + description: Username to post as. + env: + - name: SLACK_USERNAME + default: ansible + ini: + - section: callback_slack + key: username + validate_certs: + description: Validate the SSL certificate of the Slack server for HTTPS URLs. + env: + - name: SLACK_VALIDATE_CERTS + ini: + - section: callback_slack + key: validate_certs + default: true + type: bool +''' + +import json +import os +import uuid + +from ansible import context +from ansible.module_utils.common.text.converters import to_text +from ansible.module_utils.urls import open_url +from ansible.plugins.callback import CallbackBase + +try: + import prettytable + HAS_PRETTYTABLE = True +except ImportError: + HAS_PRETTYTABLE = False + + +class CallbackModule(CallbackBase): + """This is an ansible callback plugin that sends status + updates to a Slack channel during playbook execution. + """ + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'notification' + CALLBACK_NAME = 'community.general.slack' + CALLBACK_NEEDS_WHITELIST = True + + def __init__(self, display=None): + + super(CallbackModule, self).__init__(display=display) + + if not HAS_PRETTYTABLE: + self.disabled = True + self._display.warning('The `prettytable` python module is not ' + 'installed. Disabling the Slack callback ' + 'plugin.') + + self.playbook_name = None + + # This is a 6 character identifier provided with each message + # This makes it easier to correlate messages when there are more + # than 1 simultaneous playbooks running + self.guid = uuid.uuid4().hex[:6] + + def set_options(self, task_keys=None, var_options=None, direct=None): + + super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct) + + self.webhook_url = self.get_option('webhook_url') + self.channel = self.get_option('channel') + self.username = self.get_option('username') + self.show_invocation = (self._display.verbosity > 1) + self.validate_certs = self.get_option('validate_certs') + + if self.webhook_url is None: + self.disabled = True + self._display.warning('Slack Webhook URL was not provided. The ' + 'Slack Webhook URL can be provided using ' + 'the `SLACK_WEBHOOK_URL` environment ' + 'variable.') + + def send_msg(self, attachments): + headers = { + 'Content-type': 'application/json', + } + + payload = { + 'channel': self.channel, + 'username': self.username, + 'attachments': attachments, + 'parse': 'none', + 'icon_url': ('https://cdn2.hubspot.net/hub/330046/' + 'file-449187601-png/ansible_badge.png'), + } + + data = json.dumps(payload) + self._display.debug(data) + self._display.debug(self.webhook_url) + try: + response = open_url(self.webhook_url, data=data, validate_certs=self.validate_certs, + headers=headers) + return response.read() + except Exception as e: + self._display.warning(u'Could not submit message to Slack: %s' % + to_text(e)) + + def v2_playbook_on_start(self, playbook): + self.playbook_name = os.path.basename(playbook._file_name) + + title = [ + '*Playbook initiated* (_%s_)' % self.guid + ] + + invocation_items = [] + if context.CLIARGS and self.show_invocation: + tags = context.CLIARGS['tags'] + skip_tags = context.CLIARGS['skip_tags'] + extra_vars = context.CLIARGS['extra_vars'] + subset = context.CLIARGS['subset'] + inventory = [os.path.abspath(i) for i in context.CLIARGS['inventory']] + + invocation_items.append('Inventory: %s' % ', '.join(inventory)) + if tags and tags != ['all']: + invocation_items.append('Tags: %s' % ', '.join(tags)) + if skip_tags: + invocation_items.append('Skip Tags: %s' % ', '.join(skip_tags)) + if subset: + invocation_items.append('Limit: %s' % subset) + if extra_vars: + invocation_items.append('Extra Vars: %s' % + ' '.join(extra_vars)) + + title.append('by *%s*' % context.CLIARGS['remote_user']) + + title.append('\n\n*%s*' % self.playbook_name) + msg_items = [' '.join(title)] + if invocation_items: + msg_items.append('```\n%s\n```' % '\n'.join(invocation_items)) + + msg = '\n'.join(msg_items) + + attachments = [{ + 'fallback': msg, + 'fields': [ + { + 'value': msg + } + ], + 'color': 'warning', + 'mrkdwn_in': ['text', 'fallback', 'fields'], + }] + + self.send_msg(attachments=attachments) + + def v2_playbook_on_play_start(self, play): + """Display Play start messages""" + + name = play.name or 'Play name not specified (%s)' % play._uuid + msg = '*Starting play* (_%s_)\n\n*%s*' % (self.guid, name) + attachments = [ + { + 'fallback': msg, + 'text': msg, + 'color': 'warning', + 'mrkdwn_in': ['text', 'fallback', 'fields'], + } + ] + self.send_msg(attachments=attachments) + + def v2_playbook_on_stats(self, stats): + """Display info about playbook statistics""" + + hosts = sorted(stats.processed.keys()) + + t = prettytable.PrettyTable(['Host', 'Ok', 'Changed', 'Unreachable', + 'Failures', 'Rescued', 'Ignored']) + + failures = False + unreachable = False + + for h in hosts: + s = stats.summarize(h) + + if s['failures'] > 0: + failures = True + if s['unreachable'] > 0: + unreachable = True + + t.add_row([h] + [s[k] for k in ['ok', 'changed', 'unreachable', + 'failures', 'rescued', 'ignored']]) + + attachments = [] + msg_items = [ + '*Playbook Complete* (_%s_)' % self.guid + ] + if failures or unreachable: + color = 'danger' + msg_items.append('\n*Failed!*') + else: + color = 'good' + msg_items.append('\n*Success!*') + + msg_items.append('```\n%s\n```' % t) + + msg = '\n'.join(msg_items) + + attachments.append({ + 'fallback': msg, + 'fields': [ + { + 'value': msg + } + ], + 'color': color, + 'mrkdwn_in': ['text', 'fallback', 'fields'] + }) + + self.send_msg(attachments=attachments) |