Add support for loop-tasks and improved output
This PR includes: - Support for loop-tasks with proper subject/error content - Improved output (and proper indentation) - Complex data structures are now pretty printed - Better selection of mail subject
This commit is contained in:
parent
0e22afef52
commit
57b85198fe
1 changed files with 77 additions and 37 deletions
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2012, Dag Wieers <dag@wieers.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
|
@ -6,27 +7,27 @@ from __future__ import (absolute_import, division, print_function)
|
|||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: mail
|
||||
type: notification
|
||||
short_description: Sends failure events via email
|
||||
description:
|
||||
- This callback will report failures via email
|
||||
version_added: "2.3"
|
||||
requirements:
|
||||
- whitelisting in configuration
|
||||
- logstash (python library)
|
||||
options:
|
||||
mta:
|
||||
description: Mail Transfer Agent, server that accepts SMTP
|
||||
env:
|
||||
- name: SMTPHOST
|
||||
default: localhost
|
||||
note:
|
||||
- "TODO: expand configuration options now that plugins can leverage Ansible's configuration"
|
||||
callback: mail
|
||||
type: notification
|
||||
short_description: Sends failure events via email
|
||||
description:
|
||||
- This callback will report failures via email
|
||||
version_added: '2.0'
|
||||
requirements:
|
||||
- whitelisting in configuration
|
||||
options:
|
||||
mta:
|
||||
description: Mail Transfer Agent, server that accepts SMTP
|
||||
env:
|
||||
- name: SMTPHOST
|
||||
default: localhost
|
||||
note:
|
||||
- "TODO: expand configuration options now that plugins can leverage Ansible's configuration"
|
||||
'''
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import smtplib
|
||||
|
||||
from ansible.module_utils.six import string_types
|
||||
|
@ -81,52 +82,85 @@ class CallbackModule(CallbackBase):
|
|||
def subject_msg(self, multiline, failtype, linenr):
|
||||
return '%s: %s' % (failtype, multiline.strip('\r\n').splitlines()[linenr])
|
||||
|
||||
def indent(self, multiline, indent=8):
|
||||
return re.sub('^', ' '*indent, multiline, flags=re.MULTILINE)
|
||||
|
||||
def body_blob(self, multiline, texttype):
|
||||
''' Turn some text output in a well-indented block for sending in a mail body '''
|
||||
blob = 'with the following %s:\n\n' % texttype
|
||||
intro = 'with the following %s:\n\n' % texttype
|
||||
blob = ''
|
||||
for line in multiline.strip('\r\n').splitlines():
|
||||
blob += '\t%s\n' % line
|
||||
return blob + '\n'
|
||||
blob += '%s\n' % line
|
||||
return intro + self.indent(blob) + '\n'
|
||||
|
||||
def mail_result(self, result, failtype):
|
||||
host = result._host.get_name()
|
||||
|
||||
sender = '"Ansible: %s" <root>' % host
|
||||
subject = '%s: %s' % (failtype, result._task.name or result._task.action)
|
||||
|
||||
body = ''
|
||||
body += 'Playbook: %s\n' % os.path.basename(self.playbook._file_name)
|
||||
# Add subject
|
||||
if self.itembody:
|
||||
subject = self.itemsubject
|
||||
elif result._result.get('failed_when_result') == True:
|
||||
subject = "Failed due to 'failed_when' condition"
|
||||
elif result._result.get('exception'):
|
||||
subject = self.subject_msg(result._result['exception'], failtype, -1)
|
||||
elif result._result.get('msg'):
|
||||
subject = self.subject_msg(result._result['msg'], failtype, 0)
|
||||
elif result._result.get('stderr'):
|
||||
subject = self.subject_msg(result._result['stderr'], failtype, -1)
|
||||
elif result._result.get('stdout'):
|
||||
subject = self.subject_msg(result._result['stdout'], failtype, -1)
|
||||
else:
|
||||
subject = '%s: %s' % (failtype, result._task.name or result._task.action)
|
||||
|
||||
# Make playbook name visible (e.g. in Outlook/Gmail condensed view)
|
||||
body = 'Playbook: %s\n' % os.path.basename(self.playbook._file_name)
|
||||
if result._task.name:
|
||||
body += 'Task: %s\n' % result._task.name
|
||||
body += 'Module: %s\n' % result._task.action
|
||||
body += 'Host: %s\n' % host
|
||||
body += '\n'
|
||||
|
||||
# Add task information (as much as possible)
|
||||
body += 'The following task failed:\n\n'
|
||||
if 'invocation' in result._result:
|
||||
body += '\t%s: %s\n\n' % (result._task.action, json.dumps(result._result['invocation']['module_args']))
|
||||
body += self.indent('%s: %s\n' % (result._task.action, json.dumps(result._result['invocation']['module_args'], indent=4)))
|
||||
elif result._task.name:
|
||||
body += '\t%s (%s)\n\n' % (result._task.name, result._task.action)
|
||||
body += self.indent('%s (%s)\n' % (result._task.name, result._task.action))
|
||||
else:
|
||||
body += '\t%s\n\n' % result._task.action
|
||||
body += self.indent('%s\n' % result._task.action)
|
||||
body += '\n'
|
||||
|
||||
if result._result.get('stdout'):
|
||||
subject = self.subject_msg(result._result['stdout'], failtype, -1)
|
||||
body += self.body_blob(result._result['stdout'], 'standard output')
|
||||
if result._result.get('stderr'):
|
||||
subject = self.subject_msg(result._result['stderr'], failtype, -1)
|
||||
body += self.body_blob(result._result['stderr'], 'error output')
|
||||
if result._result.get('msg'):
|
||||
subject = self.subject_msg(result._result['msg'], failtype, 0)
|
||||
# Add item / message
|
||||
if self.itembody:
|
||||
body += self.itembody
|
||||
elif result._result.get('failed_when_result') == True:
|
||||
body += "due to the following condition:\n\n" + self.indent('failed_when:\n- ' + '\n- '.join(result._task.failed_when)) + '\n\n'
|
||||
elif result._result.get('msg'):
|
||||
body += self.body_blob(result._result['msg'], 'message')
|
||||
|
||||
body += 'A complete dump of the error:\n\n'
|
||||
body += '\t%s: %s' % (failtype, json.dumps(result._result))
|
||||
# Add stdout / stderr / exception / warnings / deprecations
|
||||
if result._result.get('exception'):
|
||||
body += self.body_blob(result._result['exception'], 'exception')
|
||||
if result._result.get('stdout'):
|
||||
body += self.body_blob(result._result['stdout'], 'standard output')
|
||||
if result._result.get('stderr'):
|
||||
body += self.body_blob(result._result['stderr'], 'error output')
|
||||
if result._result.get('warnings'):
|
||||
for i in range(len(result._result.get('warnings'))):
|
||||
body += self.body_blob(result._result['warnings'][i], 'exception %d' % i+1)
|
||||
if result._result.get('deprecations'):
|
||||
for i in range(len(result._result.get('deprecations'))):
|
||||
body += self.body_blob(result._result['deprecations'][i], 'exception %d' % i+1)
|
||||
|
||||
body += 'and a complete dump of the error:\n\n'
|
||||
body += self.indent('%s: %s' % (failtype, json.dumps(result._result, indent=4)))
|
||||
|
||||
self.mail(sender=sender, subject=subject, body=body)
|
||||
|
||||
def v2_playbook_on_start(self, playbook):
|
||||
self.playbook = playbook
|
||||
self.itembody = ''
|
||||
|
||||
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||
if ignore_errors:
|
||||
|
@ -139,3 +173,9 @@ class CallbackModule(CallbackBase):
|
|||
|
||||
def v2_runner_on_async_failed(self, result):
|
||||
self.mail_result(result, 'Async failure')
|
||||
|
||||
def v2_runner_item_on_failed(self, result):
|
||||
# Pass item information to task failure
|
||||
self.itemsubject = result._result['msg']
|
||||
self.itembody += self.body_blob(json.dumps(result._result, indent=4), "failed item dump '%(item)s'" % result._result)
|
||||
# self.itembody += self.body_blob(json.dumps(dir(result), indent=4), "failed full dump '%(item)s'" % result._result)
|
||||
|
|
Loading…
Reference in a new issue