1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-02-04 07:51:50 +00:00

Reformat everything.

This commit is contained in:
Felix Fontein 2025-11-01 12:08:41 +01:00
parent 3f2213791a
commit 340ff8586d
1008 changed files with 61301 additions and 58309 deletions

View file

@ -49,6 +49,7 @@ from ansible.plugins.callback import CallbackBase
class MemProf(threading.Thread):
"""Python thread for recording memory usage"""
def __init__(self, path, obj=None):
threading.Thread.__init__(self)
self.obj = obj
@ -66,8 +67,8 @@ class MemProf(threading.Thread):
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'aggregate'
CALLBACK_NAME = 'community.general.cgroup_memory_recap'
CALLBACK_TYPE = "aggregate"
CALLBACK_NAME = "community.general.cgroup_memory_recap"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, display=None):
@ -80,11 +81,11 @@ class CallbackModule(CallbackBase):
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.cgroup_max_file = self.get_option('max_mem_file')
self.cgroup_current_file = self.get_option('cur_mem_file')
self.cgroup_max_file = self.get_option("max_mem_file")
self.cgroup_current_file = self.get_option("cur_mem_file")
with open(self.cgroup_max_file, 'w+') as f:
f.write('0')
with open(self.cgroup_max_file, "w+") as f:
f.write("0")
def _profile_memory(self, obj=None):
prev_task = None
@ -112,8 +113,8 @@ class CallbackModule(CallbackBase):
with open(self.cgroup_max_file) as f:
max_results = int(f.read().strip()) / 1024 / 1024
self._display.banner('CGROUP MEMORY RECAP')
self._display.display(f'Execution Maximum: {max_results:0.2f}MB\n\n')
self._display.banner("CGROUP MEMORY RECAP")
self._display.display(f"Execution Maximum: {max_results:0.2f}MB\n\n")
for task, memory in self.task_results:
self._display.display(f'{task.get_name()} ({task._uuid}): {memory:0.2f}MB')
self._display.display(f"{task.get_name()} ({task._uuid}): {memory:0.2f}MB")

View file

@ -25,9 +25,10 @@ class CallbackModule(CallbackBase):
This is a very trivial example of how any callback function can get at play and task objects.
play will be 'None' for runner invocations, and task will be None for 'setup' invocations.
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'aggregate'
CALLBACK_NAME = 'community.general.context_demo'
CALLBACK_TYPE = "aggregate"
CALLBACK_NAME = "community.general.context_demo"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, *args, **kwargs):
@ -40,11 +41,11 @@ class CallbackModule(CallbackBase):
self._display.display(" --- ARGS ")
for i, a in enumerate(args):
self._display.display(f' {i}: {a}')
self._display.display(f" {i}: {a}")
self._display.display(" --- KWARGS ")
for k in kwargs:
self._display.display(f' {k}: {kwargs[k]}')
self._display.display(f" {k}: {kwargs[k]}")
def v2_playbook_on_play_start(self, play):
self.play = play

View file

@ -1,9 +1,9 @@
# Copyright (c) 2018, Ivan Aragones Muniesa <ivan.aragones.muniesa@gmail.com>
# 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
'''
Counter enabled Ansible callback plugin (See DOCUMENTATION for more information)
'''
"""
Counter enabled Ansible callback plugin (See DOCUMENTATION for more information)
"""
from __future__ import annotations
@ -29,15 +29,14 @@ from ansible.playbook.task_include import TaskInclude
class CallbackModule(CallbackBase):
'''
"""
This is the default callback interface, which simply prints messages
to stdout when new callback events are received.
'''
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'community.general.counter_enabled'
CALLBACK_TYPE = "stdout"
CALLBACK_NAME = "community.general.counter_enabled"
_task_counter = 1
_task_total = 0
@ -55,11 +54,7 @@ class CallbackModule(CallbackBase):
def _all_vars(self, host=None, task=None):
# host and task need to be specified in case 'magic variables' (host vars, group vars, etc)
# need to be loaded as well
return self._play.get_variable_manager().get_vars(
play=self._play,
host=host,
task=task
)
return self._play.get_variable_manager().get_vars(play=self._play, host=host, task=task)
def v2_playbook_on_start(self, playbook):
self._playbook = playbook
@ -77,8 +72,8 @@ class CallbackModule(CallbackBase):
self._play = play
self._previous_batch_total = self._current_batch_total
self._current_batch_total = self._previous_batch_total + len(self._all_vars()['vars']['ansible_play_batch'])
self._host_total = len(self._all_vars()['vars']['ansible_play_hosts_all'])
self._current_batch_total = self._previous_batch_total + len(self._all_vars()["vars"]["ansible_play_batch"])
self._host_total = len(self._all_vars()["vars"]["ansible_play_hosts_all"])
self._task_total = len(self._play.get_tasks()[0])
self._task_counter = 1
@ -93,39 +88,39 @@ class CallbackModule(CallbackBase):
f"{hostcolor(host, stat)} : {colorize('ok', stat['ok'], C.COLOR_OK)} {colorize('changed', stat['changed'], C.COLOR_CHANGED)} "
f"{colorize('unreachable', stat['unreachable'], C.COLOR_UNREACHABLE)} {colorize('failed', stat['failures'], C.COLOR_ERROR)} "
f"{colorize('rescued', stat['rescued'], C.COLOR_OK)} {colorize('ignored', stat['ignored'], C.COLOR_WARN)}",
screen_only=True
screen_only=True,
)
self._display.display(
f"{hostcolor(host, stat, False)} : {colorize('ok', stat['ok'], None)} {colorize('changed', stat['changed'], None)} "
f"{colorize('unreachable', stat['unreachable'], None)} {colorize('failed', stat['failures'], None)} "
f"{colorize('rescued', stat['rescued'], None)} {colorize('ignored', stat['ignored'], None)}",
log_only=True
log_only=True,
)
self._display.display("", screen_only=True)
# print custom stats
if self._plugin_options.get('show_custom_stats', C.SHOW_CUSTOM_STATS) and stats.custom:
if self._plugin_options.get("show_custom_stats", C.SHOW_CUSTOM_STATS) and stats.custom:
# fallback on constants for inherited plugins missing docs
self._display.banner("CUSTOM STATS: ")
# per host
# TODO: come up with 'pretty format'
for k in sorted(stats.custom.keys()):
if k == '_run':
if k == "_run":
continue
_custom_stats = self._dump_results(stats.custom[k], indent=1).replace('\n', '')
self._display.display(f'\t{k}: {_custom_stats}')
_custom_stats = self._dump_results(stats.custom[k], indent=1).replace("\n", "")
self._display.display(f"\t{k}: {_custom_stats}")
# print per run custom stats
if '_run' in stats.custom:
if "_run" in stats.custom:
self._display.display("", screen_only=True)
_custom_stats_run = self._dump_results(stats.custom['_run'], indent=1).replace('\n', '')
self._display.display(f'\tRUN: {_custom_stats_run}')
_custom_stats_run = self._dump_results(stats.custom["_run"], indent=1).replace("\n", "")
self._display.display(f"\tRUN: {_custom_stats_run}")
self._display.display("", screen_only=True)
def v2_playbook_on_task_start(self, task, is_conditional):
args = ''
args = ""
# args can be specified as no_log in several places: in the task or in
# the argument spec. We can check whether the task is no_log but the
# argument spec can't be because that is only run on the target
@ -135,8 +130,8 @@ class CallbackModule(CallbackBase):
# that they can secure this if they feel that their stdout is insecure
# (shoulder surfing, logging stdout straight to a file, etc).
if not task.no_log and C.DISPLAY_ARGS_TO_STDOUT:
args = ', '.join(('{k}={v}' for k, v in task.args.items()))
args = f' {args}'
args = ", ".join(("{k}={v}" for k, v in task.args.items()))
args = f" {args}"
self._display.banner(f"TASK {self._task_counter}/{self._task_total} [{task.get_name().strip()}{args}]")
if self._display.verbosity >= 2:
path = task.get_path()
@ -146,17 +141,16 @@ class CallbackModule(CallbackBase):
self._task_counter += 1
def v2_runner_on_ok(self, result):
self._host_counter += 1
delegated_vars = result._result.get('_ansible_delegated_vars', None)
delegated_vars = result._result.get("_ansible_delegated_vars", None)
if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid:
if self._play.strategy == "free" and self._last_task_banner != result._task._uuid:
self._print_task_banner(result._task)
if isinstance(result._task, TaskInclude):
return
elif result._result.get('changed', False):
elif result._result.get("changed", False):
if delegated_vars:
msg = f"changed: {self._host_counter}/{self._host_total} [{result._host.get_name()} -> {delegated_vars['ansible_host']}]"
else:
@ -171,7 +165,7 @@ class CallbackModule(CallbackBase):
self._handle_warnings(result._result)
if result._task.loop and 'results' in result._result:
if result._task.loop and "results" in result._result:
self._process_items(result)
else:
self._clean_results(result._result, result._task.action)
@ -181,19 +175,18 @@ class CallbackModule(CallbackBase):
self._display.display(msg, color=color)
def v2_runner_on_failed(self, result, ignore_errors=False):
self._host_counter += 1
delegated_vars = result._result.get('_ansible_delegated_vars', None)
delegated_vars = result._result.get("_ansible_delegated_vars", None)
self._clean_results(result._result, result._task.action)
if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid:
if self._play.strategy == "free" and self._last_task_banner != result._task._uuid:
self._print_task_banner(result._task)
self._handle_exception(result._result)
self._handle_warnings(result._result)
if result._task.loop and 'results' in result._result:
if result._task.loop and "results" in result._result:
self._process_items(result)
else:
@ -201,12 +194,12 @@ class CallbackModule(CallbackBase):
self._display.display(
f"fatal: {self._host_counter}/{self._host_total} [{result._host.get_name()} -> "
f"{delegated_vars['ansible_host']}]: FAILED! => {self._dump_results(result._result)}",
color=C.COLOR_ERROR
color=C.COLOR_ERROR,
)
else:
self._display.display(
f"fatal: {self._host_counter}/{self._host_total} [{result._host.get_name()}]: FAILED! => {self._dump_results(result._result)}",
color=C.COLOR_ERROR
color=C.COLOR_ERROR,
)
if ignore_errors:
@ -215,14 +208,15 @@ class CallbackModule(CallbackBase):
def v2_runner_on_skipped(self, result):
self._host_counter += 1
if self._plugin_options.get('show_skipped_hosts', C.DISPLAY_SKIPPED_HOSTS): # fallback on constants for inherited plugins missing docs
if self._plugin_options.get(
"show_skipped_hosts", C.DISPLAY_SKIPPED_HOSTS
): # fallback on constants for inherited plugins missing docs
self._clean_results(result._result, result._task.action)
if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid:
if self._play.strategy == "free" and self._last_task_banner != result._task._uuid:
self._print_task_banner(result._task)
if result._task.loop and 'results' in result._result:
if result._task.loop and "results" in result._result:
self._process_items(result)
else:
msg = f"skipping: {self._host_counter}/{self._host_total} [{result._host.get_name()}]"
@ -233,18 +227,18 @@ class CallbackModule(CallbackBase):
def v2_runner_on_unreachable(self, result):
self._host_counter += 1
if self._play.strategy == 'free' and self._last_task_banner != result._task._uuid:
if self._play.strategy == "free" and self._last_task_banner != result._task._uuid:
self._print_task_banner(result._task)
delegated_vars = result._result.get('_ansible_delegated_vars', None)
delegated_vars = result._result.get("_ansible_delegated_vars", None)
if delegated_vars:
self._display.display(
f"fatal: {self._host_counter}/{self._host_total} [{result._host.get_name()} -> "
f"{delegated_vars['ansible_host']}]: UNREACHABLE! => {self._dump_results(result._result)}",
color=C.COLOR_UNREACHABLE
color=C.COLOR_UNREACHABLE,
)
else:
self._display.display(
f"fatal: {self._host_counter}/{self._host_total} [{result._host.get_name()}]: UNREACHABLE! => {self._dump_results(result._result)}",
color=C.COLOR_UNREACHABLE
color=C.COLOR_UNREACHABLE,
)

View file

@ -1,4 +1,3 @@
# Copyright (c) 2024, Felix Fontein <felix@fontein.de>
# 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
@ -36,8 +35,8 @@ from ansible.plugins.callback.default import CallbackModule as Default
class CallbackModule(Default):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'community.general.default_without_diff'
CALLBACK_TYPE = "stdout"
CALLBACK_NAME = "community.general.default_without_diff"
def v2_on_file_diff(self, result):
pass

View file

@ -22,6 +22,7 @@ requirements:
HAS_OD = False
try:
from collections import OrderedDict
HAS_OD = True
except ImportError:
pass
@ -69,66 +70,66 @@ display = Display()
# FIXME: Importing constants as C simply does not work, beats me :-/
# from ansible import constants as C
class C:
COLOR_HIGHLIGHT = 'white'
COLOR_VERBOSE = 'blue'
COLOR_WARN = 'bright purple'
COLOR_ERROR = 'red'
COLOR_DEBUG = 'dark gray'
COLOR_DEPRECATE = 'purple'
COLOR_SKIP = 'cyan'
COLOR_UNREACHABLE = 'bright red'
COLOR_OK = 'green'
COLOR_CHANGED = 'yellow'
COLOR_HIGHLIGHT = "white"
COLOR_VERBOSE = "blue"
COLOR_WARN = "bright purple"
COLOR_ERROR = "red"
COLOR_DEBUG = "dark gray"
COLOR_DEPRECATE = "purple"
COLOR_SKIP = "cyan"
COLOR_UNREACHABLE = "bright red"
COLOR_OK = "green"
COLOR_CHANGED = "yellow"
# Taken from Dstat
class vt100:
black = '\033[0;30m'
darkred = '\033[0;31m'
darkgreen = '\033[0;32m'
darkyellow = '\033[0;33m'
darkblue = '\033[0;34m'
darkmagenta = '\033[0;35m'
darkcyan = '\033[0;36m'
gray = '\033[0;37m'
black = "\033[0;30m"
darkred = "\033[0;31m"
darkgreen = "\033[0;32m"
darkyellow = "\033[0;33m"
darkblue = "\033[0;34m"
darkmagenta = "\033[0;35m"
darkcyan = "\033[0;36m"
gray = "\033[0;37m"
darkgray = '\033[1;30m'
red = '\033[1;31m'
green = '\033[1;32m'
yellow = '\033[1;33m'
blue = '\033[1;34m'
magenta = '\033[1;35m'
cyan = '\033[1;36m'
white = '\033[1;37m'
darkgray = "\033[1;30m"
red = "\033[1;31m"
green = "\033[1;32m"
yellow = "\033[1;33m"
blue = "\033[1;34m"
magenta = "\033[1;35m"
cyan = "\033[1;36m"
white = "\033[1;37m"
blackbg = '\033[40m'
redbg = '\033[41m'
greenbg = '\033[42m'
yellowbg = '\033[43m'
bluebg = '\033[44m'
magentabg = '\033[45m'
cyanbg = '\033[46m'
whitebg = '\033[47m'
blackbg = "\033[40m"
redbg = "\033[41m"
greenbg = "\033[42m"
yellowbg = "\033[43m"
bluebg = "\033[44m"
magentabg = "\033[45m"
cyanbg = "\033[46m"
whitebg = "\033[47m"
reset = '\033[0;0m'
bold = '\033[1m'
reverse = '\033[2m'
underline = '\033[4m'
reset = "\033[0;0m"
bold = "\033[1m"
reverse = "\033[2m"
underline = "\033[4m"
clear = '\033[2J'
# clearline = '\033[K'
clearline = '\033[2K'
save = '\033[s'
restore = '\033[u'
save_all = '\0337'
restore_all = '\0338'
linewrap = '\033[7h'
nolinewrap = '\033[7l'
clear = "\033[2J"
# clearline = '\033[K'
clearline = "\033[2K"
save = "\033[s"
restore = "\033[u"
save_all = "\0337"
restore_all = "\0338"
linewrap = "\033[7h"
nolinewrap = "\033[7l"
up = '\033[1A'
down = '\033[1B'
right = '\033[1C'
left = '\033[1D'
up = "\033[1A"
down = "\033[1B"
right = "\033[1C"
left = "\033[1D"
colors = dict(
@ -140,26 +141,23 @@ colors = dict(
unreachable=vt100.red,
)
states = ('skipped', 'ok', 'changed', 'failed', 'unreachable')
states = ("skipped", "ok", "changed", "failed", "unreachable")
class CallbackModule(CallbackModule_default):
'''
"""
This is the dense callback interface, where screen estate is still valued.
'''
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'dense'
CALLBACK_TYPE = "stdout"
CALLBACK_NAME = "dense"
def __init__(self):
# From CallbackModule
self._display = display
if HAS_OD:
self.disabled = False
self.super_ref = super()
self.super_ref.__init__()
@ -167,14 +165,14 @@ class CallbackModule(CallbackModule_default):
# Attributes to remove from results for more density
self.removed_attributes = (
# 'changed',
'delta',
"delta",
# 'diff',
'end',
'failed',
'failed_when_result',
'invocation',
'start',
'stdout_lines',
"end",
"failed",
"failed_when_result",
"invocation",
"start",
"stdout_lines",
)
# Initiate data structures
@ -182,13 +180,15 @@ class CallbackModule(CallbackModule_default):
self.keep = False
self.shown_title = False
self.count = dict(play=0, handler=0, task=0)
self.type = 'foo'
self.type = "foo"
# Start immediately on the first line
sys.stdout.write(vt100.reset + vt100.save + vt100.clearline)
sys.stdout.flush()
else:
display.warning("The 'dense' callback plugin requires OrderedDict which is not available in this version of python, disabling.")
display.warning(
"The 'dense' callback plugin requires OrderedDict which is not available in this version of python, disabling."
)
self.disabled = True
def __del__(self):
@ -198,27 +198,27 @@ class CallbackModule(CallbackModule_default):
name = result._host.get_name()
# Add a new status in case a failed task is ignored
if status == 'failed' and result._task.ignore_errors:
status = 'ignored'
if status == "failed" and result._task.ignore_errors:
status = "ignored"
# Check if we have to update an existing state (when looping over items)
if name not in self.hosts:
self.hosts[name] = dict(state=status)
elif states.index(self.hosts[name]['state']) < states.index(status):
self.hosts[name]['state'] = status
elif states.index(self.hosts[name]["state"]) < states.index(status):
self.hosts[name]["state"] = status
# Store delegated hostname, if needed
delegated_vars = result._result.get('_ansible_delegated_vars', None)
delegated_vars = result._result.get("_ansible_delegated_vars", None)
if delegated_vars:
self.hosts[name]['delegate'] = delegated_vars['ansible_host']
self.hosts[name]["delegate"] = delegated_vars["ansible_host"]
# Print progress bar
self._display_progress(result)
# # Ensure that tasks with changes/failures stay on-screen, and during diff-mode
# if status in ['changed', 'failed', 'unreachable'] or (result.get('_diff_mode', False) and result._resultget('diff', False)):
# # Ensure that tasks with changes/failures stay on-screen, and during diff-mode
# if status in ['changed', 'failed', 'unreachable'] or (result.get('_diff_mode', False) and result._resultget('diff', False)):
# Ensure that tasks with changes/failures stay on-screen
if status in ['changed', 'failed', 'unreachable']:
if status in ["changed", "failed", "unreachable"]:
self.keep = True
if self._display.verbosity == 1:
@ -239,9 +239,9 @@ class CallbackModule(CallbackModule_default):
del result[attr]
def _handle_exceptions(self, result):
if 'exception' in result:
if "exception" in result:
# Remove the exception from the result so it is not shown every time
del result['exception']
del result["exception"]
if self._display.verbosity == 1:
return "An exception occurred during task execution. To see the full traceback, use -vvv."
@ -249,16 +249,16 @@ class CallbackModule(CallbackModule_default):
def _display_progress(self, result=None):
# Always rewrite the complete line
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline + vt100.nolinewrap + vt100.underline)
sys.stdout.write(f'{self.type} {self.count[self.type]}:')
sys.stdout.write(f"{self.type} {self.count[self.type]}:")
sys.stdout.write(vt100.reset)
sys.stdout.flush()
# Print out each host in its own status-color
for name in self.hosts:
sys.stdout.write(' ')
if self.hosts[name].get('delegate', None):
sys.stdout.write(" ")
if self.hosts[name].get("delegate", None):
sys.stdout.write(f"{self.hosts[name]['delegate']}>")
sys.stdout.write(colors[self.hosts[name]['state']] + name + vt100.reset)
sys.stdout.write(colors[self.hosts[name]["state"]] + name + vt100.reset)
sys.stdout.flush()
sys.stdout.write(vt100.linewrap)
@ -267,7 +267,7 @@ class CallbackModule(CallbackModule_default):
if not self.shown_title:
self.shown_title = True
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline + vt100.underline)
sys.stdout.write(f'{self.type} {self.count[self.type]}: {self.task.get_name().strip()}')
sys.stdout.write(f"{self.type} {self.count[self.type]}: {self.task.get_name().strip()}")
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
sys.stdout.flush()
else:
@ -284,29 +284,31 @@ class CallbackModule(CallbackModule_default):
self._clean_results(result._result)
dump = ''
if result._task.action == 'include':
dump = ""
if result._task.action == "include":
return
elif status == 'ok':
elif status == "ok":
return
elif status == 'ignored':
elif status == "ignored":
dump = self._handle_exceptions(result._result)
elif status == 'failed':
elif status == "failed":
dump = self._handle_exceptions(result._result)
elif status == 'unreachable':
dump = result._result['msg']
elif status == "unreachable":
dump = result._result["msg"]
if not dump:
dump = self._dump_results(result._result)
if result._task.loop and 'results' in result._result:
if result._task.loop and "results" in result._result:
self._process_items(result)
else:
sys.stdout.write(f"{colors[status] + status}: ")
delegated_vars = result._result.get('_ansible_delegated_vars', None)
delegated_vars = result._result.get("_ansible_delegated_vars", None)
if delegated_vars:
sys.stdout.write(f"{vt100.reset}{result._host.get_name()}>{colors[status]}{delegated_vars['ansible_host']}")
sys.stdout.write(
f"{vt100.reset}{result._host.get_name()}>{colors[status]}{delegated_vars['ansible_host']}"
)
else:
sys.stdout.write(result._host.get_name())
@ -314,7 +316,7 @@ class CallbackModule(CallbackModule_default):
sys.stdout.write(f"{vt100.reset}{vt100.save}{vt100.clearline}")
sys.stdout.flush()
if status == 'changed':
if status == "changed":
self._handle_warnings(result._result)
def v2_playbook_on_play_start(self, play):
@ -327,13 +329,13 @@ class CallbackModule(CallbackModule_default):
# Reset at the start of each play
self.keep = False
self.count.update(dict(handler=0, task=0))
self.count['play'] += 1
self.count["play"] += 1
self.play = play
# Write the next play on screen IN UPPERCASE, and make it permanent
name = play.get_name().strip()
if not name:
name = 'unnamed'
name = "unnamed"
sys.stdout.write(f"PLAY {self.count['play']}: {name.upper()}")
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
sys.stdout.flush()
@ -351,14 +353,14 @@ class CallbackModule(CallbackModule_default):
self.shown_title = False
self.hosts = OrderedDict()
self.task = task
self.type = 'task'
self.type = "task"
# Enumerate task if not setup (task names are too long for dense output)
if task.get_name() != 'setup':
self.count['task'] += 1
if task.get_name() != "setup":
self.count["task"] += 1
# Write the next task on screen (behind the prompt is the previous output)
sys.stdout.write(f'{self.type} {self.count[self.type]}.')
sys.stdout.write(f"{self.type} {self.count[self.type]}.")
sys.stdout.write(vt100.reset)
sys.stdout.flush()
@ -374,36 +376,36 @@ class CallbackModule(CallbackModule_default):
self.shown_title = False
self.hosts = OrderedDict()
self.task = task
self.type = 'handler'
self.type = "handler"
# Enumerate handler if not setup (handler names may be too long for dense output)
if task.get_name() != 'setup':
if task.get_name() != "setup":
self.count[self.type] += 1
# Write the next task on screen (behind the prompt is the previous output)
sys.stdout.write(f'{self.type} {self.count[self.type]}.')
sys.stdout.write(f"{self.type} {self.count[self.type]}.")
sys.stdout.write(vt100.reset)
sys.stdout.flush()
def v2_playbook_on_cleanup_task_start(self, task):
# TBD
sys.stdout.write('cleanup.')
sys.stdout.write("cleanup.")
sys.stdout.flush()
def v2_runner_on_failed(self, result, ignore_errors=False):
self._add_host(result, 'failed')
self._add_host(result, "failed")
def v2_runner_on_ok(self, result):
if result._result.get('changed', False):
self._add_host(result, 'changed')
if result._result.get("changed", False):
self._add_host(result, "changed")
else:
self._add_host(result, 'ok')
self._add_host(result, "ok")
def v2_runner_on_skipped(self, result):
self._add_host(result, 'skipped')
self._add_host(result, "skipped")
def v2_runner_on_unreachable(self, result):
self._add_host(result, 'unreachable')
self._add_host(result, "unreachable")
def v2_runner_on_include(self, included_file):
pass
@ -423,24 +425,24 @@ class CallbackModule(CallbackModule_default):
self.v2_runner_item_on_ok(result)
def v2_runner_item_on_ok(self, result):
if result._result.get('changed', False):
self._add_host(result, 'changed')
if result._result.get("changed", False):
self._add_host(result, "changed")
else:
self._add_host(result, 'ok')
self._add_host(result, "ok")
# Old definition in v2.0
def v2_playbook_item_on_failed(self, result):
self.v2_runner_item_on_failed(result)
def v2_runner_item_on_failed(self, result):
self._add_host(result, 'failed')
self._add_host(result, "failed")
# Old definition in v2.0
def v2_playbook_item_on_skipped(self, result):
self.v2_runner_item_on_skipped(result)
def v2_runner_item_on_skipped(self, result):
self._add_host(result, 'skipped')
self._add_host(result, "skipped")
def v2_playbook_on_no_hosts_remaining(self):
if self._display.verbosity == 0 and self.keep:
@ -467,7 +469,7 @@ class CallbackModule(CallbackModule_default):
return
sys.stdout.write(vt100.bold + vt100.underline)
sys.stdout.write('SUMMARY')
sys.stdout.write("SUMMARY")
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
sys.stdout.flush()
@ -479,7 +481,7 @@ class CallbackModule(CallbackModule_default):
f"{hostcolor(h, t)} : {colorize('ok', t['ok'], C.COLOR_OK)} {colorize('changed', t['changed'], C.COLOR_CHANGED)} "
f"{colorize('unreachable', t['unreachable'], C.COLOR_UNREACHABLE)} {colorize('failed', t['failures'], C.COLOR_ERROR)} "
f"{colorize('rescued', t['rescued'], C.COLOR_OK)} {colorize('ignored', t['ignored'], C.COLOR_WARN)}",
screen_only=True
screen_only=True,
)

View file

@ -1,4 +1,3 @@
# Copyright (c) 2019, Trevor Highfill <trevor.highfill@outlook.com>
# 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
@ -786,6 +785,7 @@ from ansible.module_utils.common.text.converters import to_text
try:
from ansible.template import trust_as_template # noqa: F401, pylint: disable=unused-import
SUPPORTS_DATA_TAGGING = True
except ImportError:
SUPPORTS_DATA_TAGGING = False
@ -806,11 +806,12 @@ class CallbackModule(Default):
"""
Callback plugin that allows you to supply your own custom callback templates to be output.
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'community.general.diy'
DIY_NS = 'ansible_callback_diy'
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = "stdout"
CALLBACK_NAME = "community.general.diy"
DIY_NS = "ansible_callback_diy"
@contextmanager
def _suppress_stdout(self, enabled):
@ -823,50 +824,48 @@ class CallbackModule(Default):
def _get_output_specification(self, loader, variables):
_ret = {}
_calling_method = sys._getframe(1).f_code.co_name
_callback_type = (_calling_method[3:] if _calling_method[:3] == "v2_" else _calling_method)
_callback_options = ['msg', 'msg_color']
_callback_type = _calling_method[3:] if _calling_method[:3] == "v2_" else _calling_method
_callback_options = ["msg", "msg_color"]
for option in _callback_options:
_option_name = f'{_callback_type}_{option}'
_option_template = variables.get(
f"{self.DIY_NS}_{_option_name}",
self.get_option(_option_name)
)
_ret.update({option: self._template(
loader=loader,
template=_option_template,
variables=variables
)})
_option_name = f"{_callback_type}_{option}"
_option_template = variables.get(f"{self.DIY_NS}_{_option_name}", self.get_option(_option_name))
_ret.update({option: self._template(loader=loader, template=_option_template, variables=variables)})
_ret.update({'vars': variables})
_ret.update({"vars": variables})
return _ret
def _using_diy(self, spec):
sentinel = object()
omit = spec['vars'].get('omit', sentinel)
omit = spec["vars"].get("omit", sentinel)
# With Data Tagging, omit is sentinel
return (spec['msg'] is not None) and (spec['msg'] != omit or omit is sentinel)
return (spec["msg"] is not None) and (spec["msg"] != omit or omit is sentinel)
def _parent_has_callback(self):
return hasattr(super(), sys._getframe(1).f_code.co_name)
def _template(self, loader, template, variables):
_templar = Templar(loader=loader, variables=variables)
return _templar.template(
template,
preserve_trailing_newlines=True,
convert_data=False,
escape_backslashes=True
)
return _templar.template(template, preserve_trailing_newlines=True, convert_data=False, escape_backslashes=True)
def _output(self, spec, stderr=False):
_msg = to_text(spec['msg'])
_msg = to_text(spec["msg"])
if len(_msg) > 0:
self._display.display(msg=_msg, color=spec['msg_color'], stderr=stderr)
self._display.display(msg=_msg, color=spec["msg_color"], stderr=stderr)
def _get_vars(self, playbook, play=None, host=None, task=None, included_file=None,
handler=None, result=None, stats=None, remove_attr_ref_loop=True):
def _get_vars(
self,
playbook,
play=None,
host=None,
task=None,
included_file=None,
handler=None,
result=None,
stats=None,
remove_attr_ref_loop=True,
):
def _get_value(obj, attr=None, method=None):
if attr:
return getattr(obj, attr, getattr(obj, f"_{attr}", None))
@ -876,8 +875,8 @@ class CallbackModule(Default):
return _method()
def _remove_attr_ref_loop(obj, attributes):
_loop_var = getattr(obj, 'loop_control', None)
_loop_var = (_loop_var or 'item')
_loop_var = getattr(obj, "loop_control", None)
_loop_var = _loop_var or "item"
for attr in attributes:
if str(_loop_var) in str(_get_value(obj=obj, attr=attr)):
@ -896,56 +895,128 @@ class CallbackModule(Default):
_all = _variable_manager.get_vars()
if play:
_all = play.get_variable_manager().get_vars(
play=play,
host=(host if host else getattr(result, '_host', None)),
task=(handler if handler else task)
play=play, host=(host if host else getattr(result, "_host", None)), task=(handler if handler else task)
)
_ret.update(_all)
_ret.update(_ret.get(self.DIY_NS, {self.DIY_NS: {} if SUPPORTS_DATA_TAGGING else CallbackDIYDict()}))
_ret[self.DIY_NS].update({'playbook': {}})
_playbook_attributes = ['entries', 'file_name', 'basedir']
_ret[self.DIY_NS].update({"playbook": {}})
_playbook_attributes = ["entries", "file_name", "basedir"]
for attr in _playbook_attributes:
_ret[self.DIY_NS]['playbook'].update({attr: _get_value(obj=playbook, attr=attr)})
_ret[self.DIY_NS]["playbook"].update({attr: _get_value(obj=playbook, attr=attr)})
if play:
_ret[self.DIY_NS].update({'play': {}})
_play_attributes = ['any_errors_fatal', 'become', 'become_flags', 'become_method',
'become_user', 'check_mode', 'collections', 'connection',
'debugger', 'diff', 'environment', 'fact_path', 'finalized',
'force_handlers', 'gather_facts', 'gather_subset',
'gather_timeout', 'handlers', 'hosts', 'ignore_errors',
'ignore_unreachable', 'included_conditional', 'included_path',
'max_fail_percentage', 'module_defaults', 'name', 'no_log',
'only_tags', 'order', 'port', 'post_tasks', 'pre_tasks',
'remote_user', 'removed_hosts', 'roles', 'run_once', 'serial',
'skip_tags', 'squashed', 'strategy', 'tags', 'tasks', 'uuid',
'validated', 'vars_files', 'vars_prompt']
_ret[self.DIY_NS].update({"play": {}})
_play_attributes = [
"any_errors_fatal",
"become",
"become_flags",
"become_method",
"become_user",
"check_mode",
"collections",
"connection",
"debugger",
"diff",
"environment",
"fact_path",
"finalized",
"force_handlers",
"gather_facts",
"gather_subset",
"gather_timeout",
"handlers",
"hosts",
"ignore_errors",
"ignore_unreachable",
"included_conditional",
"included_path",
"max_fail_percentage",
"module_defaults",
"name",
"no_log",
"only_tags",
"order",
"port",
"post_tasks",
"pre_tasks",
"remote_user",
"removed_hosts",
"roles",
"run_once",
"serial",
"skip_tags",
"squashed",
"strategy",
"tags",
"tasks",
"uuid",
"validated",
"vars_files",
"vars_prompt",
]
for attr in _play_attributes:
_ret[self.DIY_NS]['play'].update({attr: _get_value(obj=play, attr=attr)})
_ret[self.DIY_NS]["play"].update({attr: _get_value(obj=play, attr=attr)})
if host:
_ret[self.DIY_NS].update({'host': {}})
_host_attributes = ['name', 'uuid', 'address', 'implicit']
_ret[self.DIY_NS].update({"host": {}})
_host_attributes = ["name", "uuid", "address", "implicit"]
for attr in _host_attributes:
_ret[self.DIY_NS]['host'].update({attr: _get_value(obj=host, attr=attr)})
_ret[self.DIY_NS]["host"].update({attr: _get_value(obj=host, attr=attr)})
if task:
_ret[self.DIY_NS].update({'task': {}})
_task_attributes = ['action', 'any_errors_fatal', 'args', 'async', 'async_val',
'become', 'become_flags', 'become_method', 'become_user',
'changed_when', 'check_mode', 'collections', 'connection',
'debugger', 'delay', 'delegate_facts', 'delegate_to', 'diff',
'environment', 'failed_when', 'finalized', 'ignore_errors',
'ignore_unreachable', 'loop', 'loop_control', 'loop_with',
'module_defaults', 'name', 'no_log', 'notify', 'parent', 'poll',
'port', 'register', 'remote_user', 'retries', 'role', 'run_once',
'squashed', 'tags', 'untagged', 'until', 'uuid', 'validated',
'when']
_ret[self.DIY_NS].update({"task": {}})
_task_attributes = [
"action",
"any_errors_fatal",
"args",
"async",
"async_val",
"become",
"become_flags",
"become_method",
"become_user",
"changed_when",
"check_mode",
"collections",
"connection",
"debugger",
"delay",
"delegate_facts",
"delegate_to",
"diff",
"environment",
"failed_when",
"finalized",
"ignore_errors",
"ignore_unreachable",
"loop",
"loop_control",
"loop_with",
"module_defaults",
"name",
"no_log",
"notify",
"parent",
"poll",
"port",
"register",
"remote_user",
"retries",
"role",
"run_once",
"squashed",
"tags",
"untagged",
"until",
"uuid",
"validated",
"when",
]
# remove arguments that reference a loop var because they cause templating issues in
# callbacks that do not have the loop context(e.g. playbook_on_task_start)
@ -953,74 +1024,114 @@ class CallbackModule(Default):
_task_attributes = _remove_attr_ref_loop(obj=task, attributes=_task_attributes)
for attr in _task_attributes:
_ret[self.DIY_NS]['task'].update({attr: _get_value(obj=task, attr=attr)})
_ret[self.DIY_NS]["task"].update({attr: _get_value(obj=task, attr=attr)})
if included_file:
_ret[self.DIY_NS].update({'included_file': {}})
_included_file_attributes = ['args', 'filename', 'hosts', 'is_role', 'task']
_ret[self.DIY_NS].update({"included_file": {}})
_included_file_attributes = ["args", "filename", "hosts", "is_role", "task"]
for attr in _included_file_attributes:
_ret[self.DIY_NS]['included_file'].update({attr: _get_value(
obj=included_file,
attr=attr
)})
_ret[self.DIY_NS]["included_file"].update({attr: _get_value(obj=included_file, attr=attr)})
if handler:
_ret[self.DIY_NS].update({'handler': {}})
_handler_attributes = ['action', 'any_errors_fatal', 'args', 'async', 'async_val',
'become', 'become_flags', 'become_method', 'become_user',
'changed_when', 'check_mode', 'collections', 'connection',
'debugger', 'delay', 'delegate_facts', 'delegate_to', 'diff',
'environment', 'failed_when', 'finalized', 'ignore_errors',
'ignore_unreachable', 'listen', 'loop', 'loop_control',
'loop_with', 'module_defaults', 'name', 'no_log',
'notified_hosts', 'notify', 'parent', 'poll', 'port',
'register', 'remote_user', 'retries', 'role', 'run_once',
'squashed', 'tags', 'untagged', 'until', 'uuid', 'validated',
'when']
_ret[self.DIY_NS].update({"handler": {}})
_handler_attributes = [
"action",
"any_errors_fatal",
"args",
"async",
"async_val",
"become",
"become_flags",
"become_method",
"become_user",
"changed_when",
"check_mode",
"collections",
"connection",
"debugger",
"delay",
"delegate_facts",
"delegate_to",
"diff",
"environment",
"failed_when",
"finalized",
"ignore_errors",
"ignore_unreachable",
"listen",
"loop",
"loop_control",
"loop_with",
"module_defaults",
"name",
"no_log",
"notified_hosts",
"notify",
"parent",
"poll",
"port",
"register",
"remote_user",
"retries",
"role",
"run_once",
"squashed",
"tags",
"untagged",
"until",
"uuid",
"validated",
"when",
]
if handler.loop and remove_attr_ref_loop:
_handler_attributes = _remove_attr_ref_loop(obj=handler,
attributes=_handler_attributes)
_handler_attributes = _remove_attr_ref_loop(obj=handler, attributes=_handler_attributes)
for attr in _handler_attributes:
_ret[self.DIY_NS]['handler'].update({attr: _get_value(obj=handler, attr=attr)})
_ret[self.DIY_NS]["handler"].update({attr: _get_value(obj=handler, attr=attr)})
_ret[self.DIY_NS]['handler'].update({'is_host_notified': handler.is_host_notified(host)})
_ret[self.DIY_NS]["handler"].update({"is_host_notified": handler.is_host_notified(host)})
if result:
_ret[self.DIY_NS].update({'result': {}})
_result_attributes = ['host', 'task', 'task_name']
_ret[self.DIY_NS].update({"result": {}})
_result_attributes = ["host", "task", "task_name"]
for attr in _result_attributes:
_ret[self.DIY_NS]['result'].update({attr: _get_value(obj=result, attr=attr)})
_ret[self.DIY_NS]["result"].update({attr: _get_value(obj=result, attr=attr)})
_result_methods = ['is_changed', 'is_failed', 'is_skipped', 'is_unreachable']
_result_methods = ["is_changed", "is_failed", "is_skipped", "is_unreachable"]
for method in _result_methods:
_ret[self.DIY_NS]['result'].update({method: _get_value(obj=result, method=method)})
_ret[self.DIY_NS]["result"].update({method: _get_value(obj=result, method=method)})
_ret[self.DIY_NS]['result'].update({'output': getattr(result, '_result', None)})
_ret[self.DIY_NS]["result"].update({"output": getattr(result, "_result", None)})
_ret.update(result._result)
if stats:
_ret[self.DIY_NS].update({'stats': {}})
_stats_attributes = ['changed', 'custom', 'dark', 'failures', 'ignored',
'ok', 'processed', 'rescued', 'skipped']
_ret[self.DIY_NS].update({"stats": {}})
_stats_attributes = [
"changed",
"custom",
"dark",
"failures",
"ignored",
"ok",
"processed",
"rescued",
"skipped",
]
for attr in _stats_attributes:
_ret[self.DIY_NS]['stats'].update({attr: _get_value(obj=stats, attr=attr)})
_ret[self.DIY_NS]["stats"].update({attr: _get_value(obj=stats, attr=attr)})
_ret[self.DIY_NS].update({'top_level_var_names': list(_ret.keys())})
_ret[self.DIY_NS].update({"top_level_var_names": list(_ret.keys())})
return _ret
def v2_on_any(self, *args, **kwargs):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._diy_spec['vars']
)
self._diy_spec = self._get_output_specification(loader=self._diy_loader, variables=self._diy_spec["vars"])
if self._using_diy(spec=self._diy_spec):
self._output(spec=self._diy_spec)
@ -1033,11 +1144,8 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
task=self._diy_task,
result=result
)
playbook=self._diy_playbook, play=self._diy_play, task=self._diy_task, result=result
),
)
if self._using_diy(spec=self._diy_spec):
@ -1051,11 +1159,8 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
task=self._diy_task,
result=result
)
playbook=self._diy_playbook, play=self._diy_play, task=self._diy_task, result=result
),
)
if self._using_diy(spec=self._diy_spec):
@ -1069,11 +1174,8 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
task=self._diy_task,
result=result
)
playbook=self._diy_playbook, play=self._diy_play, task=self._diy_task, result=result
),
)
if self._using_diy(spec=self._diy_spec):
@ -1087,11 +1189,8 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
task=self._diy_task,
result=result
)
playbook=self._diy_playbook, play=self._diy_play, task=self._diy_task, result=result
),
)
if self._using_diy(spec=self._diy_spec):
@ -1121,8 +1220,8 @@ class CallbackModule(Default):
play=self._diy_play,
task=self._diy_task,
result=result,
remove_attr_ref_loop=False
)
remove_attr_ref_loop=False,
),
)
if self._using_diy(spec=self._diy_spec):
@ -1140,8 +1239,8 @@ class CallbackModule(Default):
play=self._diy_play,
task=self._diy_task,
result=result,
remove_attr_ref_loop=False
)
remove_attr_ref_loop=False,
),
)
if self._using_diy(spec=self._diy_spec):
@ -1159,8 +1258,8 @@ class CallbackModule(Default):
play=self._diy_play,
task=self._diy_task,
result=result,
remove_attr_ref_loop=False
)
remove_attr_ref_loop=False,
),
)
if self._using_diy(spec=self._diy_spec):
@ -1174,11 +1273,8 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
task=self._diy_task,
result=result
)
playbook=self._diy_playbook, play=self._diy_play, task=self._diy_task, result=result
),
)
if self._using_diy(spec=self._diy_spec):
@ -1195,11 +1291,8 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
host=self._diy_host,
task=self._diy_task
)
playbook=self._diy_playbook, play=self._diy_play, host=self._diy_host, task=self._diy_task
),
)
if self._using_diy(spec=self._diy_spec):
@ -1214,10 +1307,7 @@ class CallbackModule(Default):
self._diy_loader = self._diy_playbook.get_loader()
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook
)
loader=self._diy_loader, variables=self._get_vars(playbook=self._diy_playbook)
)
if self._using_diy(spec=self._diy_spec):
@ -1234,11 +1324,8 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
host=self._diy_host,
handler=self._diy_handler
)
playbook=self._diy_playbook, play=self._diy_play, host=self._diy_host, handler=self._diy_handler
),
)
if self._using_diy(spec=self._diy_spec):
@ -1249,10 +1336,7 @@ class CallbackModule(Default):
super().v2_playbook_on_notify(handler, host)
def v2_playbook_on_no_hosts_matched(self):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._diy_spec['vars']
)
self._diy_spec = self._get_output_specification(loader=self._diy_loader, variables=self._diy_spec["vars"])
if self._using_diy(spec=self._diy_spec):
self._output(spec=self._diy_spec)
@ -1262,10 +1346,7 @@ class CallbackModule(Default):
super().v2_playbook_on_no_hosts_matched()
def v2_playbook_on_no_hosts_remaining(self):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._diy_spec['vars']
)
self._diy_spec = self._get_output_specification(loader=self._diy_loader, variables=self._diy_spec["vars"])
if self._using_diy(spec=self._diy_spec):
self._output(spec=self._diy_spec)
@ -1279,11 +1360,7 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
task=self._diy_task
)
variables=self._get_vars(playbook=self._diy_playbook, play=self._diy_play, task=self._diy_task),
)
if self._using_diy(spec=self._diy_spec):
@ -1302,11 +1379,7 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
task=self._diy_task
)
variables=self._get_vars(playbook=self._diy_playbook, play=self._diy_play, task=self._diy_task),
)
if self._using_diy(spec=self._diy_spec):
@ -1316,13 +1389,19 @@ class CallbackModule(Default):
with self._suppress_stdout(enabled=self._using_diy(spec=self._diy_spec)):
super().v2_playbook_on_handler_task_start(task)
def v2_playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None,
confirm=False, salt_size=None, salt=None, default=None,
unsafe=None):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._diy_spec['vars']
)
def v2_playbook_on_vars_prompt(
self,
varname,
private=True,
prompt=None,
encrypt=None,
confirm=False,
salt_size=None,
salt=None,
default=None,
unsafe=None,
):
self._diy_spec = self._get_output_specification(loader=self._diy_loader, variables=self._diy_spec["vars"])
if self._using_diy(spec=self._diy_spec):
self._output(spec=self._diy_spec)
@ -1330,9 +1409,7 @@ class CallbackModule(Default):
if self._parent_has_callback():
with self._suppress_stdout(enabled=self._using_diy(spec=self._diy_spec)):
super().v2_playbook_on_vars_prompt(
varname, private, prompt, encrypt,
confirm, salt_size, salt, default,
unsafe
varname, private, prompt, encrypt, confirm, salt_size, salt, default, unsafe
)
# not implemented as the call to this is not implemented yet
@ -1347,11 +1424,7 @@ class CallbackModule(Default):
self._diy_play = play
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play
)
loader=self._diy_loader, variables=self._get_vars(playbook=self._diy_playbook, play=self._diy_play)
)
if self._using_diy(spec=self._diy_spec):
@ -1366,11 +1439,7 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
stats=self._diy_stats
)
variables=self._get_vars(playbook=self._diy_playbook, play=self._diy_play, stats=self._diy_stats),
)
if self._using_diy(spec=self._diy_spec):
@ -1389,8 +1458,8 @@ class CallbackModule(Default):
playbook=self._diy_playbook,
play=self._diy_play,
task=self._diy_included_file._task,
included_file=self._diy_included_file
)
included_file=self._diy_included_file,
),
)
if self._using_diy(spec=self._diy_spec):
@ -1404,11 +1473,8 @@ class CallbackModule(Default):
self._diy_spec = self._get_output_specification(
loader=self._diy_loader,
variables=self._get_vars(
playbook=self._diy_playbook,
play=self._diy_play,
task=self._diy_task,
result=result
)
playbook=self._diy_playbook, play=self._diy_play, task=self._diy_task, result=result
),
)
if self._using_diy(spec=self._diy_spec):

View file

@ -116,9 +116,9 @@ class TaskData:
def add_host(self, host):
if host.uuid in self.host_data:
if host.status == 'included':
if host.status == "included":
# concatenate task include output from multiple items
host.result = f'{self.host_data[host.uuid].result}\n{host.result}'
host.result = f"{self.host_data[host.uuid].result}\n{host.result}"
else:
return
@ -152,7 +152,7 @@ class ElasticSource:
self._display = display
def start_task(self, tasks_data, hide_task_arguments, play_name, task):
""" record the start of a task for one or more hosts """
"""record the start of a task for one or more hosts"""
uuid = task._uuid
@ -165,29 +165,39 @@ class ElasticSource:
args = None
if not task.no_log and not hide_task_arguments:
args = ', '.join((f'{k}={v}' for k, v in task.args.items()))
args = ", ".join((f"{k}={v}" for k, v in task.args.items()))
tasks_data[uuid] = TaskData(uuid, name, path, play_name, action, args)
def finish_task(self, tasks_data, status, result):
""" record the results of a task for a single host """
"""record the results of a task for a single host"""
task_uuid = result._task._uuid
if hasattr(result, '_host') and result._host is not None:
if hasattr(result, "_host") and result._host is not None:
host_uuid = result._host._uuid
host_name = result._host.name
else:
host_uuid = 'include'
host_name = 'include'
host_uuid = "include"
host_name = "include"
task = tasks_data[task_uuid]
task.add_host(HostData(host_uuid, host_name, status, result))
def generate_distributed_traces(self, tasks_data, status, end_time, traceparent, apm_service_name,
apm_server_url, apm_verify_server_cert, apm_secret_token, apm_api_key):
""" generate distributed traces from the collected TaskData and HostData """
def generate_distributed_traces(
self,
tasks_data,
status,
end_time,
traceparent,
apm_service_name,
apm_server_url,
apm_verify_server_cert,
apm_secret_token,
apm_api_key,
):
"""generate distributed traces from the collected TaskData and HostData"""
tasks = []
parent_start_time = None
@ -196,7 +206,9 @@ class ElasticSource:
parent_start_time = task.start
tasks.append(task)
apm_cli = self.init_apm_client(apm_server_url, apm_service_name, apm_verify_server_cert, apm_secret_token, apm_api_key)
apm_cli = self.init_apm_client(
apm_server_url, apm_service_name, apm_verify_server_cert, apm_secret_token, apm_api_key
)
if apm_cli:
with closing(apm_cli):
instrument() # Only call this once, as early as possible.
@ -218,72 +230,80 @@ class ElasticSource:
apm_cli.end_transaction(name=__name__, result=status, duration=end_time - parent_start_time)
def create_span_data(self, apm_cli, task_data, host_data):
""" create the span with the given TaskData and HostData """
"""create the span with the given TaskData and HostData"""
name = f'[{host_data.name}] {task_data.play}: {task_data.name}'
name = f"[{host_data.name}] {task_data.play}: {task_data.name}"
message = "success"
status = "success"
enriched_error_message = None
if host_data.status == 'included':
if host_data.status == "included":
rc = 0
else:
res = host_data.result._result
rc = res.get('rc', 0)
if host_data.status == 'failed':
rc = res.get("rc", 0)
if host_data.status == "failed":
message = self.get_error_message(res)
enriched_error_message = self.enrich_error_message(res)
status = "failure"
elif host_data.status == 'skipped':
if 'skip_reason' in res:
message = res['skip_reason']
elif host_data.status == "skipped":
if "skip_reason" in res:
message = res["skip_reason"]
else:
message = 'skipped'
message = "skipped"
status = "unknown"
with capture_span(task_data.name,
start=task_data.start,
span_type="ansible.task.run",
duration=host_data.finish - task_data.start,
labels={"ansible.task.args": task_data.args,
"ansible.task.message": message,
"ansible.task.module": task_data.action,
"ansible.task.name": name,
"ansible.task.result": rc,
"ansible.task.host.name": host_data.name,
"ansible.task.host.status": host_data.status}) as span:
with capture_span(
task_data.name,
start=task_data.start,
span_type="ansible.task.run",
duration=host_data.finish - task_data.start,
labels={
"ansible.task.args": task_data.args,
"ansible.task.message": message,
"ansible.task.module": task_data.action,
"ansible.task.name": name,
"ansible.task.result": rc,
"ansible.task.host.name": host_data.name,
"ansible.task.host.status": host_data.status,
},
) as span:
span.outcome = status
if 'failure' in status:
exception = AnsibleRuntimeError(message=f"{task_data.action}: {name} failed with error message {enriched_error_message}")
if "failure" in status:
exception = AnsibleRuntimeError(
message=f"{task_data.action}: {name} failed with error message {enriched_error_message}"
)
apm_cli.capture_exception(exc_info=(type(exception), exception, exception.__traceback__), handled=True)
def init_apm_client(self, apm_server_url, apm_service_name, apm_verify_server_cert, apm_secret_token, apm_api_key):
if apm_server_url:
return Client(service_name=apm_service_name,
server_url=apm_server_url,
verify_server_cert=False,
secret_token=apm_secret_token,
api_key=apm_api_key,
use_elastic_traceparent_header=True,
debug=True)
return Client(
service_name=apm_service_name,
server_url=apm_server_url,
verify_server_cert=False,
secret_token=apm_secret_token,
api_key=apm_api_key,
use_elastic_traceparent_header=True,
debug=True,
)
@staticmethod
def get_error_message(result):
if result.get('exception') is not None:
return ElasticSource._last_line(result['exception'])
return result.get('msg', 'failed')
if result.get("exception") is not None:
return ElasticSource._last_line(result["exception"])
return result.get("msg", "failed")
@staticmethod
def _last_line(text):
lines = text.strip().split('\n')
lines = text.strip().split("\n")
return lines[-1]
@staticmethod
def enrich_error_message(result):
message = result.get('msg', 'failed')
exception = result.get('exception')
stderr = result.get('stderr')
return f"message: \"{message}\"\nexception: \"{exception}\"\nstderr: \"{stderr}\""
message = result.get("msg", "failed")
exception = result.get("exception")
stderr = result.get("stderr")
return f'message: "{message}"\nexception: "{exception}"\nstderr: "{stderr}"'
class CallbackModule(CallbackBase):
@ -292,8 +312,8 @@ class CallbackModule(CallbackBase):
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.elastic'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.elastic"
CALLBACK_NEEDS_ENABLED = True
def __init__(self, display=None):
@ -308,7 +328,9 @@ class CallbackModule(CallbackBase):
self.disabled = False
if ELASTIC_LIBRARY_IMPORT_ERROR:
raise AnsibleError('The `elastic-apm` must be installed to use this plugin') from ELASTIC_LIBRARY_IMPORT_ERROR
raise AnsibleError(
"The `elastic-apm` must be installed to use this plugin"
) from ELASTIC_LIBRARY_IMPORT_ERROR
self.tasks_data = OrderedDict()
@ -317,17 +339,17 @@ class CallbackModule(CallbackBase):
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.hide_task_arguments = self.get_option('hide_task_arguments')
self.hide_task_arguments = self.get_option("hide_task_arguments")
self.apm_service_name = self.get_option('apm_service_name')
self.apm_service_name = self.get_option("apm_service_name")
if not self.apm_service_name:
self.apm_service_name = 'ansible'
self.apm_service_name = "ansible"
self.apm_server_url = self.get_option('apm_server_url')
self.apm_secret_token = self.get_option('apm_secret_token')
self.apm_api_key = self.get_option('apm_api_key')
self.apm_verify_server_cert = self.get_option('apm_verify_server_cert')
self.traceparent = self.get_option('traceparent')
self.apm_server_url = self.get_option("apm_server_url")
self.apm_secret_token = self.get_option("apm_secret_token")
self.apm_api_key = self.get_option("apm_api_key")
self.apm_verify_server_cert = self.get_option("apm_verify_server_cert")
self.traceparent = self.get_option("traceparent")
def v2_playbook_on_start(self, playbook):
self.ansible_playbook = basename(playbook._file_name)
@ -336,65 +358,29 @@ class CallbackModule(CallbackBase):
self.play_name = play.get_name()
def v2_runner_on_no_hosts(self, task):
self.elastic.start_task(
self.tasks_data,
self.hide_task_arguments,
self.play_name,
task
)
self.elastic.start_task(self.tasks_data, self.hide_task_arguments, self.play_name, task)
def v2_playbook_on_task_start(self, task, is_conditional):
self.elastic.start_task(
self.tasks_data,
self.hide_task_arguments,
self.play_name,
task
)
self.elastic.start_task(self.tasks_data, self.hide_task_arguments, self.play_name, task)
def v2_playbook_on_cleanup_task_start(self, task):
self.elastic.start_task(
self.tasks_data,
self.hide_task_arguments,
self.play_name,
task
)
self.elastic.start_task(self.tasks_data, self.hide_task_arguments, self.play_name, task)
def v2_playbook_on_handler_task_start(self, task):
self.elastic.start_task(
self.tasks_data,
self.hide_task_arguments,
self.play_name,
task
)
self.elastic.start_task(self.tasks_data, self.hide_task_arguments, self.play_name, task)
def v2_runner_on_failed(self, result, ignore_errors=False):
self.errors += 1
self.elastic.finish_task(
self.tasks_data,
'failed',
result
)
self.elastic.finish_task(self.tasks_data, "failed", result)
def v2_runner_on_ok(self, result):
self.elastic.finish_task(
self.tasks_data,
'ok',
result
)
self.elastic.finish_task(self.tasks_data, "ok", result)
def v2_runner_on_skipped(self, result):
self.elastic.finish_task(
self.tasks_data,
'skipped',
result
)
self.elastic.finish_task(self.tasks_data, "skipped", result)
def v2_playbook_on_include(self, included_file):
self.elastic.finish_task(
self.tasks_data,
'included',
included_file
)
self.elastic.finish_task(self.tasks_data, "included", included_file)
def v2_playbook_on_stats(self, stats):
if self.errors == 0:
@ -410,7 +396,7 @@ class CallbackModule(CallbackBase):
self.apm_server_url,
self.apm_verify_server_cert,
self.apm_secret_token,
self.apm_api_key
self.apm_api_key,
)
def v2_runner_on_async_failed(self, result, **kwargs):

View file

@ -54,29 +54,31 @@ from ansible.plugins.callback import CallbackBase
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.jabber'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.jabber"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, display=None):
super().__init__(display=display)
if not HAS_XMPP:
self._display.warning("The required python xmpp library (xmpppy) is not installed. "
"pip install git+https://github.com/ArchipelProject/xmpppy")
self._display.warning(
"The required python xmpp library (xmpppy) is not installed. "
"pip install git+https://github.com/ArchipelProject/xmpppy"
)
self.disabled = True
self.serv = os.getenv('JABBER_SERV')
self.j_user = os.getenv('JABBER_USER')
self.j_pass = os.getenv('JABBER_PASS')
self.j_to = os.getenv('JABBER_TO')
self.serv = os.getenv("JABBER_SERV")
self.j_user = os.getenv("JABBER_USER")
self.j_pass = os.getenv("JABBER_PASS")
self.j_to = os.getenv("JABBER_TO")
if (self.j_user or self.j_pass or self.serv or self.j_to) is None:
self.disabled = True
self._display.warning('Jabber CallBack wants the JABBER_SERV, JABBER_USER, JABBER_PASS and JABBER_TO environment variables')
self._display.warning(
"Jabber CallBack wants the JABBER_SERV, JABBER_USER, JABBER_PASS and JABBER_TO environment variables"
)
def send_msg(self, msg):
"""Send message"""
@ -85,7 +87,7 @@ class CallbackModule(CallbackBase):
client.connect(server=(self.serv, 5222))
client.auth(jid.getNode(), self.j_pass, resource=jid.getResource())
message = xmpp.Message(self.j_to, msg)
message.setAttr('type', 'chat')
message.setAttr("type", "chat")
client.send(message)
client.disconnect()
@ -109,9 +111,9 @@ class CallbackModule(CallbackBase):
unreachable = False
for h in hosts:
s = stats.summarize(h)
if s['failures'] > 0:
if s["failures"] > 0:
failures = True
if s['unreachable'] > 0:
if s["unreachable"] > 0:
unreachable = True
if failures or unreachable:

View file

@ -49,9 +49,10 @@ class CallbackModule(CallbackBase):
"""
logs playbook results, per host, in /var/log/ansible/hosts
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.log_plays'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.log_plays"
CALLBACK_NEEDS_WHITELIST = True
TIME_FORMAT = "%b %d %Y %H:%M:%S"
@ -61,7 +62,6 @@ class CallbackModule(CallbackBase):
return f"{now} - {playbook} - {task_name} - {task_action} - {category} - {data}\n\n"
def __init__(self):
super().__init__()
def set_options(self, task_keys=None, var_options=None, direct=None):
@ -75,12 +75,12 @@ class CallbackModule(CallbackBase):
def log(self, result, category):
data = result._result
if isinstance(data, MutableMapping):
if '_ansible_verbose_override' in data:
if "_ansible_verbose_override" in data:
# avoid logging extraneous data
data = 'omitted'
data = "omitted"
else:
data = data.copy()
invocation = data.pop('invocation', None)
invocation = data.pop("invocation", None)
data = json.dumps(data, cls=AnsibleJSONEncoder)
if invocation is not None:
data = f"{json.dumps(invocation)} => {data} "
@ -93,25 +93,25 @@ class CallbackModule(CallbackBase):
fd.write(msg)
def v2_runner_on_failed(self, result, ignore_errors=False):
self.log(result, 'FAILED')
self.log(result, "FAILED")
def v2_runner_on_ok(self, result):
self.log(result, 'OK')
self.log(result, "OK")
def v2_runner_on_skipped(self, result):
self.log(result, 'SKIPPED')
self.log(result, "SKIPPED")
def v2_runner_on_unreachable(self, result):
self.log(result, 'UNREACHABLE')
self.log(result, "UNREACHABLE")
def v2_runner_on_async_failed(self, result):
self.log(result, 'ASYNC_FAILED')
self.log(result, "ASYNC_FAILED")
def v2_playbook_on_start(self, playbook):
self.playbook = playbook._file_name
def v2_playbook_on_import_for_host(self, result, imported_file):
self.log(result, 'IMPORTED', imported_file)
self.log(result, "IMPORTED", imported_file)
def v2_playbook_on_not_import_for_host(self, result, missing_file):
self.log(result, 'NOTIMPORTED', missing_file)
self.log(result, "NOTIMPORTED", missing_file)

View file

@ -83,11 +83,10 @@ class AzureLogAnalyticsSource:
def __build_signature(self, date, workspace_id, shared_key, content_length):
# Build authorisation signature for Azure log analytics API call
sigs = f"POST\n{content_length}\napplication/json\nx-ms-date:{date}\n/api/logs"
utf8_sigs = sigs.encode('utf-8')
utf8_sigs = sigs.encode("utf-8")
decoded_shared_key = base64.b64decode(shared_key)
hmac_sha256_sigs = hmac.new(
decoded_shared_key, utf8_sigs, digestmod=hashlib.sha256).digest()
encoded_hash = base64.b64encode(hmac_sha256_sigs).decode('utf-8')
hmac_sha256_sigs = hmac.new(decoded_shared_key, utf8_sigs, digestmod=hashlib.sha256).digest()
encoded_hash = base64.b64encode(hmac_sha256_sigs).decode("utf-8")
signature = f"SharedKey {workspace_id}:{encoded_hash}"
return signature
@ -95,10 +94,10 @@ class AzureLogAnalyticsSource:
return f"https://{workspace_id}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01"
def __rfc1123date(self):
return now().strftime('%a, %d %b %Y %H:%M:%S GMT')
return now().strftime("%a, %d %b %Y %H:%M:%S GMT")
def send_event(self, workspace_id, shared_key, state, result, runtime):
if result._task_fields['args'].get('_ansible_check_mode') is True:
if result._task_fields["args"].get("_ansible_check_mode") is True:
self.ansible_check_mode = True
if result._task._role:
@ -107,31 +106,31 @@ class AzureLogAnalyticsSource:
ansible_role = None
data = {}
data['uuid'] = result._task._uuid
data['session'] = self.session
data['status'] = state
data['timestamp'] = self.__rfc1123date()
data['host'] = self.host
data['user'] = self.user
data['runtime'] = runtime
data['ansible_version'] = ansible_version
data['ansible_check_mode'] = self.ansible_check_mode
data['ansible_host'] = result._host.name
data['ansible_playbook'] = self.ansible_playbook
data['ansible_role'] = ansible_role
data['ansible_task'] = result._task_fields
data["uuid"] = result._task._uuid
data["session"] = self.session
data["status"] = state
data["timestamp"] = self.__rfc1123date()
data["host"] = self.host
data["user"] = self.user
data["runtime"] = runtime
data["ansible_version"] = ansible_version
data["ansible_check_mode"] = self.ansible_check_mode
data["ansible_host"] = result._host.name
data["ansible_playbook"] = self.ansible_playbook
data["ansible_role"] = ansible_role
data["ansible_task"] = result._task_fields
# Removing args since it can contain sensitive data
if 'args' in data['ansible_task']:
data['ansible_task'].pop('args')
data['ansible_result'] = result._result
if 'content' in data['ansible_result']:
data['ansible_result'].pop('content')
if "args" in data["ansible_task"]:
data["ansible_task"].pop("args")
data["ansible_result"] = result._result
if "content" in data["ansible_result"]:
data["ansible_result"].pop("content")
# Adding extra vars info
data['extra_vars'] = self.extra_vars
data["extra_vars"] = self.extra_vars
# Preparing the playbook logs as JSON format and send to Azure log analytics
jsondata = json.dumps({'event': data}, cls=AnsibleJSONEncoder, sort_keys=True)
jsondata = json.dumps({"event": data}, cls=AnsibleJSONEncoder, sort_keys=True)
content_length = len(jsondata)
rfc1123date = self.__rfc1123date()
signature = self.__build_signature(rfc1123date, workspace_id, shared_key, content_length)
@ -141,19 +140,19 @@ class AzureLogAnalyticsSource:
workspace_url,
jsondata,
headers={
'content-type': 'application/json',
'Authorization': signature,
'Log-Type': 'ansible_playbook',
'x-ms-date': rfc1123date
"content-type": "application/json",
"Authorization": signature,
"Log-Type": "ansible_playbook",
"x-ms-date": rfc1123date,
},
method='POST'
method="POST",
)
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'loganalytics'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "loganalytics"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, display=None):
@ -164,15 +163,12 @@ class CallbackModule(CallbackBase):
self.loganalytics = AzureLogAnalyticsSource()
def _seconds_since_start(self, result):
return (
now() -
self.start_datetimes[result._task._uuid]
).total_seconds()
return (now() - self.start_datetimes[result._task._uuid]).total_seconds()
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.workspace_id = self.get_option('workspace_id')
self.shared_key = self.get_option('shared_key')
self.workspace_id = self.get_option("workspace_id")
self.shared_key = self.get_option("shared_key")
def v2_playbook_on_play_start(self, play):
vm = play.get_variable_manager()
@ -190,45 +186,25 @@ class CallbackModule(CallbackBase):
def v2_runner_on_ok(self, result, **kwargs):
self.loganalytics.send_event(
self.workspace_id,
self.shared_key,
'OK',
result,
self._seconds_since_start(result)
self.workspace_id, self.shared_key, "OK", result, self._seconds_since_start(result)
)
def v2_runner_on_skipped(self, result, **kwargs):
self.loganalytics.send_event(
self.workspace_id,
self.shared_key,
'SKIPPED',
result,
self._seconds_since_start(result)
self.workspace_id, self.shared_key, "SKIPPED", result, self._seconds_since_start(result)
)
def v2_runner_on_failed(self, result, **kwargs):
self.loganalytics.send_event(
self.workspace_id,
self.shared_key,
'FAILED',
result,
self._seconds_since_start(result)
self.workspace_id, self.shared_key, "FAILED", result, self._seconds_since_start(result)
)
def runner_on_async_failed(self, result, **kwargs):
self.loganalytics.send_event(
self.workspace_id,
self.shared_key,
'FAILED',
result,
self._seconds_since_start(result)
self.workspace_id, self.shared_key, "FAILED", result, self._seconds_since_start(result)
)
def v2_runner_on_unreachable(self, result, **kwargs):
self.loganalytics.send_event(
self.workspace_id,
self.shared_key,
'UNREACHABLE',
result,
self._seconds_since_start(result)
self.workspace_id, self.shared_key, "UNREACHABLE", result, self._seconds_since_start(result)
)

View file

@ -64,6 +64,7 @@ from ansible.parsing.ajson import AnsibleJSONEncoder
try:
from logdna import LogDNAHandler
HAS_LOGDNA = True
except ImportError:
HAS_LOGDNA = False
@ -72,12 +73,12 @@ except ImportError:
# Getting MAC Address of system:
def get_mac():
mac = f"{getnode():012x}"
return ":".join(map(lambda index: mac[index:index + 2], range(int(len(mac) / 2))))
return ":".join(map(lambda index: mac[index : index + 2], range(int(len(mac) / 2))))
# Getting hostname of system:
def get_hostname():
return str(socket.gethostname()).split('.local', 1)[0]
return str(socket.gethostname()).split(".local", 1)[0]
# Getting IP of system:
@ -87,10 +88,10 @@ def get_ip():
except Exception:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('10.255.255.255', 1))
s.connect(("10.255.255.255", 1))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
IP = "127.0.0.1"
finally:
s.close()
return IP
@ -107,10 +108,9 @@ def isJSONable(obj):
# LogDNA Callback Module:
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 0.1
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.logdna'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.logdna"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, display=None):
@ -127,27 +127,27 @@ class CallbackModule(CallbackBase):
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.conf_key = self.get_option('conf_key')
self.plugin_ignore_errors = self.get_option('plugin_ignore_errors')
self.conf_hostname = self.get_option('conf_hostname')
self.conf_tags = self.get_option('conf_tags')
self.conf_key = self.get_option("conf_key")
self.plugin_ignore_errors = self.get_option("plugin_ignore_errors")
self.conf_hostname = self.get_option("conf_hostname")
self.conf_tags = self.get_option("conf_tags")
self.mac = get_mac()
self.ip = get_ip()
if self.conf_hostname is None:
self.conf_hostname = get_hostname()
self.conf_tags = self.conf_tags.split(',')
self.conf_tags = self.conf_tags.split(",")
if HAS_LOGDNA:
self.log = logging.getLogger('logdna')
self.log = logging.getLogger("logdna")
self.log.setLevel(logging.INFO)
self.options = {'hostname': self.conf_hostname, 'mac': self.mac, 'index_meta': True}
self.options = {"hostname": self.conf_hostname, "mac": self.mac, "index_meta": True}
self.log.addHandler(LogDNAHandler(self.conf_key, self.options))
self.disabled = False
else:
self.disabled = True
self._display.warning('WARNING:\nPlease, install LogDNA Python Package: `pip install logdna`')
self._display.warning("WARNING:\nPlease, install LogDNA Python Package: `pip install logdna`")
def metaIndexing(self, meta):
invalidKeys = []
@ -159,25 +159,25 @@ class CallbackModule(CallbackBase):
if ninvalidKeys > 0:
for key in invalidKeys:
del meta[key]
meta['__errors'] = f"These keys have been sanitized: {', '.join(invalidKeys)}"
meta["__errors"] = f"These keys have been sanitized: {', '.join(invalidKeys)}"
return meta
def sanitizeJSON(self, data):
try:
return json.loads(json.dumps(data, sort_keys=True, cls=AnsibleJSONEncoder))
except Exception:
return {'warnings': ['JSON Formatting Issue', json.dumps(data, sort_keys=True, cls=AnsibleJSONEncoder)]}
return {"warnings": ["JSON Formatting Issue", json.dumps(data, sort_keys=True, cls=AnsibleJSONEncoder)]}
def flush(self, log, options):
if HAS_LOGDNA:
self.log.info(json.dumps(log), options)
def sendLog(self, host, category, logdata):
options = {'app': 'ansible', 'meta': {'playbook': self.playbook_name, 'host': host, 'category': category}}
logdata['info'].pop('invocation', None)
warnings = logdata['info'].pop('warnings', None)
options = {"app": "ansible", "meta": {"playbook": self.playbook_name, "host": host, "category": category}}
logdata["info"].pop("invocation", None)
warnings = logdata["info"].pop("warnings", None)
if warnings is not None:
self.flush({'warn': warnings}, options)
self.flush({"warn": warnings}, options)
self.flush(logdata, options)
def v2_playbook_on_start(self, playbook):
@ -188,21 +188,21 @@ class CallbackModule(CallbackBase):
result = dict()
for host in stats.processed.keys():
result[host] = stats.summarize(host)
self.sendLog(self.conf_hostname, 'STATS', {'info': self.sanitizeJSON(result)})
self.sendLog(self.conf_hostname, "STATS", {"info": self.sanitizeJSON(result)})
def runner_on_failed(self, host, res, ignore_errors=False):
if self.plugin_ignore_errors:
ignore_errors = self.plugin_ignore_errors
self.sendLog(host, 'FAILED', {'info': self.sanitizeJSON(res), 'ignore_errors': ignore_errors})
self.sendLog(host, "FAILED", {"info": self.sanitizeJSON(res), "ignore_errors": ignore_errors})
def runner_on_ok(self, host, res):
self.sendLog(host, 'OK', {'info': self.sanitizeJSON(res)})
self.sendLog(host, "OK", {"info": self.sanitizeJSON(res)})
def runner_on_unreachable(self, host, res):
self.sendLog(host, 'UNREACHABLE', {'info': self.sanitizeJSON(res)})
self.sendLog(host, "UNREACHABLE", {"info": self.sanitizeJSON(res)})
def runner_on_async_failed(self, host, res, jid):
self.sendLog(host, 'ASYNC_FAILED', {'info': self.sanitizeJSON(res), 'job_id': jid})
self.sendLog(host, "ASYNC_FAILED", {"info": self.sanitizeJSON(res), "job_id": jid})
def runner_on_async_ok(self, host, res, jid):
self.sendLog(host, 'ASYNC_OK', {'info': self.sanitizeJSON(res), 'job_id': jid})
self.sendLog(host, "ASYNC_OK", {"info": self.sanitizeJSON(res), "job_id": jid})

View file

@ -103,12 +103,14 @@ import uuid
try:
import certifi
HAS_CERTIFI = True
except ImportError:
HAS_CERTIFI = False
try:
import flatdict
HAS_FLATDICT = True
except ImportError:
HAS_FLATDICT = False
@ -121,8 +123,7 @@ from ansible.plugins.callback import CallbackBase
class PlainTextSocketAppender:
def __init__(self, display, LE_API='data.logentries.com', LE_PORT=80, LE_TLS_PORT=443):
def __init__(self, display, LE_API="data.logentries.com", LE_PORT=80, LE_TLS_PORT=443):
self.LE_API = LE_API
self.LE_PORT = LE_PORT
self.LE_TLS_PORT = LE_TLS_PORT
@ -131,7 +132,7 @@ class PlainTextSocketAppender:
# Error message displayed when an incorrect Token has been detected
self.INVALID_TOKEN = "\n\nIt appears the LOGENTRIES_TOKEN parameter you entered is incorrect!\n\n"
# Unicode Line separator character \u2028
self.LINE_SEP = '\u2028'
self.LINE_SEP = "\u2028"
self._display = display
self._conn = None
@ -170,13 +171,13 @@ class PlainTextSocketAppender:
def put(self, data):
# Replace newlines with Unicode line separator
# for multi-line events
data = to_text(data, errors='surrogate_or_strict')
multiline = data.replace('\n', self.LINE_SEP)
data = to_text(data, errors="surrogate_or_strict")
multiline = data.replace("\n", self.LINE_SEP)
multiline += "\n"
# Send data, reconnect if needed
while True:
try:
self._conn.send(to_bytes(multiline, errors='surrogate_or_strict'))
self._conn.send(to_bytes(multiline, errors="surrogate_or_strict"))
except socket.error:
self.reopen_connection()
continue
@ -187,6 +188,7 @@ class PlainTextSocketAppender:
try:
import ssl
HAS_SSL = True
except ImportError: # for systems without TLS support.
SocketAppender = PlainTextSocketAppender
@ -198,11 +200,13 @@ else:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
context = ssl.create_default_context(
purpose=ssl.Purpose.SERVER_AUTH,
cafile=certifi.where(), )
cafile=certifi.where(),
)
sock = context.wrap_socket(
sock=sock,
do_handshake_on_connect=True,
suppress_ragged_eofs=True, )
suppress_ragged_eofs=True,
)
sock.connect((self.LE_API, self.LE_TLS_PORT))
self._conn = sock
@ -211,12 +215,11 @@ else:
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.logentries'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.logentries"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self):
# TODO: allow for alternate posting methods (REST/UDP/agent/etc)
super().__init__()
@ -226,7 +229,9 @@ class CallbackModule(CallbackBase):
if not HAS_CERTIFI:
self.disabled = True
self._display.warning('The `certifi` python module is not installed.\nDisabling the Logentries callback plugin.')
self._display.warning(
"The `certifi` python module is not installed.\nDisabling the Logentries callback plugin."
)
self.le_jobid = str(uuid.uuid4())
@ -234,41 +239,47 @@ class CallbackModule(CallbackBase):
self.timeout = 10
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
# get options
try:
self.api_url = self.get_option('api')
self.api_port = self.get_option('port')
self.api_tls_port = self.get_option('tls_port')
self.use_tls = self.get_option('use_tls')
self.flatten = self.get_option('flatten')
self.api_url = self.get_option("api")
self.api_port = self.get_option("port")
self.api_tls_port = self.get_option("tls_port")
self.use_tls = self.get_option("use_tls")
self.flatten = self.get_option("flatten")
except KeyError as e:
self._display.warning(f"Missing option for Logentries callback plugin: {e}")
self.disabled = True
try:
self.token = self.get_option('token')
self.token = self.get_option("token")
except KeyError as e:
self._display.warning('Logentries token was not provided, this is required for this callback to operate, disabling')
self._display.warning(
"Logentries token was not provided, this is required for this callback to operate, disabling"
)
self.disabled = True
if self.flatten and not HAS_FLATDICT:
self.disabled = True
self._display.warning('You have chosen to flatten and the `flatdict` python module is not installed.\nDisabling the Logentries callback plugin.')
self._display.warning(
"You have chosen to flatten and the `flatdict` python module is not installed.\nDisabling the Logentries callback plugin."
)
self._initialize_connections()
def _initialize_connections(self):
if not self.disabled:
if self.use_tls:
self._display.vvvv(f"Connecting to {self.api_url}:{self.api_tls_port} with TLS")
self._appender = TLSSocketAppender(display=self._display, LE_API=self.api_url, LE_TLS_PORT=self.api_tls_port)
self._appender = TLSSocketAppender(
display=self._display, LE_API=self.api_url, LE_TLS_PORT=self.api_tls_port
)
else:
self._display.vvvv(f"Connecting to {self.api_url}:{self.api_port}")
self._appender = PlainTextSocketAppender(display=self._display, LE_API=self.api_url, LE_PORT=self.api_port)
self._appender = PlainTextSocketAppender(
display=self._display, LE_API=self.api_url, LE_PORT=self.api_port
)
self._appender.reopen_connection()
def emit_formatted(self, record):
@ -279,50 +290,50 @@ class CallbackModule(CallbackBase):
self.emit(self._dump_results(record))
def emit(self, record):
msg = record.rstrip('\n')
msg = record.rstrip("\n")
msg = f"{self.token} {msg}"
self._appender.put(msg)
self._display.vvvv("Sent event to logentries")
def _set_info(self, host, res):
return {'le_jobid': self.le_jobid, 'hostname': host, 'results': res}
return {"le_jobid": self.le_jobid, "hostname": host, "results": res}
def runner_on_ok(self, host, res):
results = self._set_info(host, res)
results['status'] = 'OK'
results["status"] = "OK"
self.emit_formatted(results)
def runner_on_failed(self, host, res, ignore_errors=False):
results = self._set_info(host, res)
results['status'] = 'FAILED'
results["status"] = "FAILED"
self.emit_formatted(results)
def runner_on_skipped(self, host, item=None):
results = self._set_info(host, item)
del results['results']
results['status'] = 'SKIPPED'
del results["results"]
results["status"] = "SKIPPED"
self.emit_formatted(results)
def runner_on_unreachable(self, host, res):
results = self._set_info(host, res)
results['status'] = 'UNREACHABLE'
results["status"] = "UNREACHABLE"
self.emit_formatted(results)
def runner_on_async_failed(self, host, res, jid):
results = self._set_info(host, res)
results['jid'] = jid
results['status'] = 'ASYNC_FAILED'
results["jid"] = jid
results["status"] = "ASYNC_FAILED"
self.emit_formatted(results)
def v2_playbook_on_play_start(self, play):
results = {}
results['le_jobid'] = self.le_jobid
results['started_by'] = os.getlogin()
results["le_jobid"] = self.le_jobid
results["started_by"] = os.getlogin()
if play.name:
results['play'] = play.name
results['hosts'] = play.hosts
results["play"] = play.name
results["hosts"] = play.hosts
self.emit_formatted(results)
def playbook_on_stats(self, stats):
""" close connection """
"""close connection"""
self._appender.close_connection()

View file

@ -103,6 +103,7 @@ import logging
try:
import logstash
HAS_LOGSTASH = True
except ImportError:
HAS_LOGSTASH = False
@ -115,10 +116,9 @@ from ansible_collections.community.general.plugins.module_utils.datetime import
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.logstash'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.logstash"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self):
@ -132,14 +132,11 @@ class CallbackModule(CallbackBase):
def _init_plugin(self):
if not self.disabled:
self.logger = logging.getLogger('python-logstash-logger')
self.logger = logging.getLogger("python-logstash-logger")
self.logger.setLevel(logging.DEBUG)
self.handler = logstash.TCPLogstashHandler(
self.ls_server,
self.ls_port,
version=1,
message_type=self.ls_type
self.ls_server, self.ls_port, version=1, message_type=self.ls_type
)
self.logger.addHandler(self.handler)
@ -147,42 +144,36 @@ class CallbackModule(CallbackBase):
self.session = str(uuid.uuid4())
self.errors = 0
self.base_data = {
'session': self.session,
'host': self.hostname
}
self.base_data = {"session": self.session, "host": self.hostname}
if self.ls_pre_command is not None:
self.base_data['ansible_pre_command_output'] = os.popen(
self.ls_pre_command).read()
self.base_data["ansible_pre_command_output"] = os.popen(self.ls_pre_command).read()
if context.CLIARGS is not None:
self.base_data['ansible_checkmode'] = context.CLIARGS.get('check')
self.base_data['ansible_tags'] = context.CLIARGS.get('tags')
self.base_data['ansible_skip_tags'] = context.CLIARGS.get('skip_tags')
self.base_data['inventory'] = context.CLIARGS.get('inventory')
self.base_data["ansible_checkmode"] = context.CLIARGS.get("check")
self.base_data["ansible_tags"] = context.CLIARGS.get("tags")
self.base_data["ansible_skip_tags"] = context.CLIARGS.get("skip_tags")
self.base_data["inventory"] = context.CLIARGS.get("inventory")
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.ls_server = self.get_option('server')
self.ls_port = int(self.get_option('port'))
self.ls_type = self.get_option('type')
self.ls_pre_command = self.get_option('pre_command')
self.ls_format_version = self.get_option('format_version')
self.ls_server = self.get_option("server")
self.ls_port = int(self.get_option("port"))
self.ls_type = self.get_option("type")
self.ls_pre_command = self.get_option("pre_command")
self.ls_format_version = self.get_option("format_version")
self._init_plugin()
def v2_playbook_on_start(self, playbook):
data = self.base_data.copy()
data['ansible_type'] = "start"
data['status'] = "OK"
data['ansible_playbook'] = playbook._file_name
data["ansible_type"] = "start"
data["status"] = "OK"
data["ansible_playbook"] = playbook._file_name
if self.ls_format_version == "v2":
self.logger.info(
"START PLAYBOOK | %s", data['ansible_playbook'], extra=data
)
self.logger.info("START PLAYBOOK | %s", data["ansible_playbook"], extra=data)
else:
self.logger.info("ansible start", extra=data)
@ -199,15 +190,13 @@ class CallbackModule(CallbackBase):
status = "FAILED"
data = self.base_data.copy()
data['ansible_type'] = "finish"
data['status'] = status
data['ansible_playbook_duration'] = runtime.total_seconds()
data['ansible_result'] = json.dumps(summarize_stat) # deprecated field
data["ansible_type"] = "finish"
data["status"] = status
data["ansible_playbook_duration"] = runtime.total_seconds()
data["ansible_result"] = json.dumps(summarize_stat) # deprecated field
if self.ls_format_version == "v2":
self.logger.info(
"FINISH PLAYBOOK | %s", json.dumps(summarize_stat), extra=data
)
self.logger.info("FINISH PLAYBOOK | %s", json.dumps(summarize_stat), extra=data)
else:
self.logger.info("ansible stats", extra=data)
@ -218,10 +207,10 @@ class CallbackModule(CallbackBase):
self.play_name = play.name
data = self.base_data.copy()
data['ansible_type'] = "start"
data['status'] = "OK"
data['ansible_play_id'] = self.play_id
data['ansible_play_name'] = self.play_name
data["ansible_type"] = "start"
data["status"] = "OK"
data["ansible_play_id"] = self.play_id
data["ansible_play_name"] = self.play_name
if self.ls_format_version == "v2":
self.logger.info("START PLAY | %s", self.play_name, extra=data)
@ -231,64 +220,61 @@ class CallbackModule(CallbackBase):
def v2_playbook_on_task_start(self, task, is_conditional):
self.task_id = str(task._uuid)
'''
"""
Tasks and handler tasks are dealt with here
'''
"""
def v2_runner_on_ok(self, result, **kwargs):
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
task_name = str(result._task).replace("TASK: ", "").replace("HANDLER: ", "")
data = self.base_data.copy()
if task_name == 'setup':
data['ansible_type'] = "setup"
data['status'] = "OK"
data['ansible_host'] = result._host.name
data['ansible_play_id'] = self.play_id
data['ansible_play_name'] = self.play_name
data['ansible_task'] = task_name
data['ansible_facts'] = self._dump_results(result._result)
if task_name == "setup":
data["ansible_type"] = "setup"
data["status"] = "OK"
data["ansible_host"] = result._host.name
data["ansible_play_id"] = self.play_id
data["ansible_play_name"] = self.play_name
data["ansible_task"] = task_name
data["ansible_facts"] = self._dump_results(result._result)
if self.ls_format_version == "v2":
self.logger.info(
"SETUP FACTS | %s", self._dump_results(result._result), extra=data
)
self.logger.info("SETUP FACTS | %s", self._dump_results(result._result), extra=data)
else:
self.logger.info("ansible facts", extra=data)
else:
if 'changed' in result._result.keys():
data['ansible_changed'] = result._result['changed']
if "changed" in result._result.keys():
data["ansible_changed"] = result._result["changed"]
else:
data['ansible_changed'] = False
data["ansible_changed"] = False
data['ansible_type'] = "task"
data['status'] = "OK"
data['ansible_host'] = result._host.name
data['ansible_play_id'] = self.play_id
data['ansible_play_name'] = self.play_name
data['ansible_task'] = task_name
data['ansible_task_id'] = self.task_id
data['ansible_result'] = self._dump_results(result._result)
data["ansible_type"] = "task"
data["status"] = "OK"
data["ansible_host"] = result._host.name
data["ansible_play_id"] = self.play_id
data["ansible_play_name"] = self.play_name
data["ansible_task"] = task_name
data["ansible_task_id"] = self.task_id
data["ansible_result"] = self._dump_results(result._result)
if self.ls_format_version == "v2":
self.logger.info(
"TASK OK | %s | RESULT | %s",
task_name, self._dump_results(result._result), extra=data
"TASK OK | %s | RESULT | %s", task_name, self._dump_results(result._result), extra=data
)
else:
self.logger.info("ansible ok", extra=data)
def v2_runner_on_skipped(self, result, **kwargs):
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
task_name = str(result._task).replace("TASK: ", "").replace("HANDLER: ", "")
data = self.base_data.copy()
data['ansible_type'] = "task"
data['status'] = "SKIPPED"
data['ansible_host'] = result._host.name
data['ansible_play_id'] = self.play_id
data['ansible_play_name'] = self.play_name
data['ansible_task'] = task_name
data['ansible_task_id'] = self.task_id
data['ansible_result'] = self._dump_results(result._result)
data["ansible_type"] = "task"
data["status"] = "SKIPPED"
data["ansible_host"] = result._host.name
data["ansible_play_id"] = self.play_id
data["ansible_play_name"] = self.play_name
data["ansible_task"] = task_name
data["ansible_task_id"] = self.task_id
data["ansible_result"] = self._dump_results(result._result)
if self.ls_format_version == "v2":
self.logger.info("TASK SKIPPED | %s", task_name, extra=data)
@ -297,12 +283,12 @@ class CallbackModule(CallbackBase):
def v2_playbook_on_import_for_host(self, result, imported_file):
data = self.base_data.copy()
data['ansible_type'] = "import"
data['status'] = "IMPORTED"
data['ansible_host'] = result._host.name
data['ansible_play_id'] = self.play_id
data['ansible_play_name'] = self.play_name
data['imported_file'] = imported_file
data["ansible_type"] = "import"
data["status"] = "IMPORTED"
data["ansible_host"] = result._host.name
data["ansible_play_id"] = self.play_id
data["ansible_play_name"] = self.play_name
data["imported_file"] = imported_file
if self.ls_format_version == "v2":
self.logger.info("IMPORT | %s", imported_file, extra=data)
@ -311,12 +297,12 @@ class CallbackModule(CallbackBase):
def v2_playbook_on_not_import_for_host(self, result, missing_file):
data = self.base_data.copy()
data['ansible_type'] = "import"
data['status'] = "NOT IMPORTED"
data['ansible_host'] = result._host.name
data['ansible_play_id'] = self.play_id
data['ansible_play_name'] = self.play_name
data['imported_file'] = missing_file
data["ansible_type"] = "import"
data["status"] = "NOT IMPORTED"
data["ansible_host"] = result._host.name
data["ansible_play_id"] = self.play_id
data["ansible_play_name"] = self.play_name
data["imported_file"] = missing_file
if self.ls_format_version == "v2":
self.logger.info("NOT IMPORTED | %s", missing_file, extra=data)
@ -324,75 +310,81 @@ class CallbackModule(CallbackBase):
self.logger.info("ansible import", extra=data)
def v2_runner_on_failed(self, result, **kwargs):
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
task_name = str(result._task).replace("TASK: ", "").replace("HANDLER: ", "")
data = self.base_data.copy()
if 'changed' in result._result.keys():
data['ansible_changed'] = result._result['changed']
if "changed" in result._result.keys():
data["ansible_changed"] = result._result["changed"]
else:
data['ansible_changed'] = False
data["ansible_changed"] = False
data['ansible_type'] = "task"
data['status'] = "FAILED"
data['ansible_host'] = result._host.name
data['ansible_play_id'] = self.play_id
data['ansible_play_name'] = self.play_name
data['ansible_task'] = task_name
data['ansible_task_id'] = self.task_id
data['ansible_result'] = self._dump_results(result._result)
data["ansible_type"] = "task"
data["status"] = "FAILED"
data["ansible_host"] = result._host.name
data["ansible_play_id"] = self.play_id
data["ansible_play_name"] = self.play_name
data["ansible_task"] = task_name
data["ansible_task_id"] = self.task_id
data["ansible_result"] = self._dump_results(result._result)
self.errors += 1
if self.ls_format_version == "v2":
self.logger.error(
"TASK FAILED | %s | HOST | %s | RESULT | %s",
task_name, self.hostname,
self._dump_results(result._result), extra=data
task_name,
self.hostname,
self._dump_results(result._result),
extra=data,
)
else:
self.logger.error("ansible failed", extra=data)
def v2_runner_on_unreachable(self, result, **kwargs):
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
task_name = str(result._task).replace("TASK: ", "").replace("HANDLER: ", "")
data = self.base_data.copy()
data['ansible_type'] = "task"
data['status'] = "UNREACHABLE"
data['ansible_host'] = result._host.name
data['ansible_play_id'] = self.play_id
data['ansible_play_name'] = self.play_name
data['ansible_task'] = task_name
data['ansible_task_id'] = self.task_id
data['ansible_result'] = self._dump_results(result._result)
data["ansible_type"] = "task"
data["status"] = "UNREACHABLE"
data["ansible_host"] = result._host.name
data["ansible_play_id"] = self.play_id
data["ansible_play_name"] = self.play_name
data["ansible_task"] = task_name
data["ansible_task_id"] = self.task_id
data["ansible_result"] = self._dump_results(result._result)
self.errors += 1
if self.ls_format_version == "v2":
self.logger.error(
"UNREACHABLE | %s | HOST | %s | RESULT | %s",
task_name, self.hostname,
self._dump_results(result._result), extra=data
task_name,
self.hostname,
self._dump_results(result._result),
extra=data,
)
else:
self.logger.error("ansible unreachable", extra=data)
def v2_runner_on_async_failed(self, result, **kwargs):
task_name = str(result._task).replace('TASK: ', '').replace('HANDLER: ', '')
task_name = str(result._task).replace("TASK: ", "").replace("HANDLER: ", "")
data = self.base_data.copy()
data['ansible_type'] = "task"
data['status'] = "FAILED"
data['ansible_host'] = result._host.name
data['ansible_play_id'] = self.play_id
data['ansible_play_name'] = self.play_name
data['ansible_task'] = task_name
data['ansible_task_id'] = self.task_id
data['ansible_result'] = self._dump_results(result._result)
data["ansible_type"] = "task"
data["status"] = "FAILED"
data["ansible_host"] = result._host.name
data["ansible_play_id"] = self.play_id
data["ansible_play_name"] = self.play_name
data["ansible_task"] = task_name
data["ansible_task_id"] = self.task_id
data["ansible_result"] = self._dump_results(result._result)
self.errors += 1
if self.ls_format_version == "v2":
self.logger.error(
"ASYNC FAILED | %s | HOST | %s | RESULT | %s",
task_name, self.hostname,
self._dump_results(result._result), extra=data
task_name,
self.hostname,
self._dump_results(result._result),
extra=data,
)
else:
self.logger.error("ansible async", extra=data)

View file

@ -1,4 +1,3 @@
# Copyright (c) 2012, Dag Wieers <dag@wieers.com>
# 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
@ -92,33 +91,33 @@ from ansible.plugins.callback import CallbackBase
class CallbackModule(CallbackBase):
''' This Ansible callback plugin mails errors to interested parties. '''
"""This Ansible callback plugin mails errors to interested parties."""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.mail'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.mail"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, display=None):
super().__init__(display=display)
self.sender = None
self.to = 'root'
self.smtphost = os.getenv('SMTPHOST', 'localhost')
self.to = "root"
self.smtphost = os.getenv("SMTPHOST", "localhost")
self.smtpport = 25
self.cc = None
self.bcc = None
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.sender = self.get_option('sender')
self.to = self.get_option('to')
self.smtphost = self.get_option('mta')
self.smtpport = self.get_option('mtaport')
self.cc = self.get_option('cc')
self.bcc = self.get_option('bcc')
self.sender = self.get_option("sender")
self.to = self.get_option("to")
self.smtphost = self.get_option("mta")
self.smtpport = self.get_option("mtaport")
self.cc = self.get_option("cc")
self.bcc = self.get_option("bcc")
def mail(self, subject='Ansible error mail', body=None):
def mail(self, subject="Ansible error mail", body=None):
if body is None:
body = subject
@ -132,14 +131,14 @@ class CallbackModule(CallbackBase):
if self.bcc:
bcc_addresses = email.utils.getaddresses(self.bcc)
content = f'Date: {email.utils.formatdate()}\n'
content += f'From: {email.utils.formataddr(sender_address)}\n'
content = f"Date: {email.utils.formatdate()}\n"
content += f"From: {email.utils.formataddr(sender_address)}\n"
if self.to:
content += f"To: {', '.join([email.utils.formataddr(pair) for pair in to_addresses])}\n"
if self.cc:
content += f"Cc: {', '.join([email.utils.formataddr(pair) for pair in cc_addresses])}\n"
content += f"Message-ID: {email.utils.make_msgid(domain=self.get_option('message_id_domain'))}\n"
content += f'Subject: {subject.strip()}\n\n'
content += f"Subject: {subject.strip()}\n\n"
content += body
addresses = to_addresses
@ -149,23 +148,23 @@ class CallbackModule(CallbackBase):
addresses += bcc_addresses
if not addresses:
self._display.warning('No receiver has been specified for the mail callback plugin.')
self._display.warning("No receiver has been specified for the mail callback plugin.")
smtp.sendmail(self.sender, [address for name, address in addresses], to_bytes(content))
smtp.quit()
def subject_msg(self, multiline, failtype, linenr):
msg = multiline.strip('\r\n').splitlines()[linenr]
return f'{failtype}: {msg}'
msg = multiline.strip("\r\n").splitlines()[linenr]
return f"{failtype}: {msg}"
def indent(self, multiline, indent=8):
return re.sub('^', ' ' * indent, multiline, flags=re.MULTILINE)
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 '''
intro = f'with the following {texttype}:\n\n'
blob = "\n".join(multiline.strip('\r\n').splitlines())
"""Turn some text output in a well-indented block for sending in a mail body"""
intro = f"with the following {texttype}:\n\n"
blob = "\n".join(multiline.strip("\r\n").splitlines())
return f"{intro}{self.indent(blob)}\n"
def mail_result(self, result, failtype):
@ -176,83 +175,87 @@ class CallbackModule(CallbackBase):
# Add subject
if self.itembody:
subject = self.itemsubject
elif result._result.get('failed_when_result') is True:
elif result._result.get("failed_when_result") is True:
subject = "Failed due to 'failed_when' condition"
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)
elif result._result.get('exception'): # Unrelated exceptions are added to output :-/
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)
elif result._result.get("exception"): # Unrelated exceptions are added to output :-/
subject = self.subject_msg(result._result["exception"], failtype, -1)
else:
subject = f'{failtype}: {result._task.name or result._task.action}'
subject = f"{failtype}: {result._task.name or result._task.action}"
# Make playbook name visible (e.g. in Outlook/Gmail condensed view)
body = f'Playbook: {os.path.basename(self.playbook._file_name)}\n'
body = f"Playbook: {os.path.basename(self.playbook._file_name)}\n"
if result._task.name:
body += f'Task: {result._task.name}\n'
body += f'Module: {result._task.action}\n'
body += f'Host: {host}\n'
body += '\n'
body += f"Task: {result._task.name}\n"
body += f"Module: {result._task.action}\n"
body += f"Host: {host}\n"
body += "\n"
# Add task information (as much as possible)
body += 'The following task failed:\n\n'
if 'invocation' in result._result:
body += self.indent(f"{result._task.action}: {json.dumps(result._result['invocation']['module_args'], indent=4)}\n")
body += "The following task failed:\n\n"
if "invocation" in result._result:
body += self.indent(
f"{result._task.action}: {json.dumps(result._result['invocation']['module_args'], indent=4)}\n"
)
elif result._task.name:
body += self.indent(f'{result._task.name} ({result._task.action})\n')
body += self.indent(f"{result._task.name} ({result._task.action})\n")
else:
body += self.indent(f'{result._task.action}\n')
body += '\n'
body += self.indent(f"{result._task.action}\n")
body += "\n"
# Add item / message
if self.itembody:
body += self.itembody
elif result._result.get('failed_when_result') is True:
fail_cond_list = '\n- '.join(result._task.failed_when)
elif result._result.get("failed_when_result") is True:
fail_cond_list = "\n- ".join(result._task.failed_when)
fail_cond = self.indent(f"failed_when:\n- {fail_cond_list}")
body += f"due to the following condition:\n\n{fail_cond}\n\n"
elif result._result.get('msg'):
body += self.body_blob(result._result['msg'], 'message')
elif result._result.get("msg"):
body += self.body_blob(result._result["msg"], "message")
# Add stdout / stderr / exception / warnings / deprecations
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('exception'): # Unrelated exceptions are added to output :-/
body += self.body_blob(result._result['exception'], 'exception')
if result._result.get('warnings'):
for i in range(len(result._result.get('warnings'))):
body += self.body_blob(result._result['warnings'][i], f'exception {i + 1}')
if result._result.get('deprecations'):
for i in range(len(result._result.get('deprecations'))):
body += self.body_blob(result._result['deprecations'][i], f'exception {i + 1}')
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("exception"): # Unrelated exceptions are added to output :-/
body += self.body_blob(result._result["exception"], "exception")
if result._result.get("warnings"):
for i in range(len(result._result.get("warnings"))):
body += self.body_blob(result._result["warnings"][i], f"exception {i + 1}")
if result._result.get("deprecations"):
for i in range(len(result._result.get("deprecations"))):
body += self.body_blob(result._result["deprecations"][i], f"exception {i + 1}")
body += 'and a complete dump of the error:\n\n'
body += self.indent(f'{failtype}: {json.dumps(result._result, cls=AnsibleJSONEncoder, indent=4)}')
body += "and a complete dump of the error:\n\n"
body += self.indent(f"{failtype}: {json.dumps(result._result, cls=AnsibleJSONEncoder, indent=4)}")
self.mail(subject=subject, body=body)
def v2_playbook_on_start(self, playbook):
self.playbook = playbook
self.itembody = ''
self.itembody = ""
def v2_runner_on_failed(self, result, ignore_errors=False):
if ignore_errors:
return
self.mail_result(result, 'Failed')
self.mail_result(result, "Failed")
def v2_runner_on_unreachable(self, result):
self.mail_result(result, 'Unreachable')
self.mail_result(result, "Unreachable")
def v2_runner_on_async_failed(self, result):
self.mail_result(result, 'Async failure')
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, cls=AnsibleJSONEncoder, indent=4), f"failed item dump '{result._result['item']}'")
self.itemsubject = result._result["msg"]
self.itembody += self.body_blob(
json.dumps(result._result, cls=AnsibleJSONEncoder, indent=4), f"failed item dump '{result._result['item']}'"
)

View file

@ -73,13 +73,13 @@ from ansible.plugins.callback import CallbackBase
class CallbackModule(CallbackBase):
'''
"""
send ansible-playbook to Nagios server using nrdp protocol
'''
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.nrdp'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.nrdp"
CALLBACK_NEEDS_WHITELIST = True
# Nagios states
@ -98,25 +98,26 @@ class CallbackModule(CallbackBase):
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.url = self.get_option('url')
if not self.url.endswith('/'):
self.url += '/'
self.token = self.get_option('token')
self.hostname = self.get_option('hostname')
self.servicename = self.get_option('servicename')
self.validate_nrdp_certs = self.get_option('validate_certs')
self.url = self.get_option("url")
if not self.url.endswith("/"):
self.url += "/"
self.token = self.get_option("token")
self.hostname = self.get_option("hostname")
self.servicename = self.get_option("servicename")
self.validate_nrdp_certs = self.get_option("validate_certs")
if (self.url or self.token or self.hostname or
self.servicename) is None:
self._display.warning("NRDP callback wants the NRDP_URL,"
" NRDP_TOKEN, NRDP_HOSTNAME,"
" NRDP_SERVICENAME"
" environment variables'."
" The NRDP callback plugin is disabled.")
if (self.url or self.token or self.hostname or self.servicename) is None:
self._display.warning(
"NRDP callback wants the NRDP_URL,"
" NRDP_TOKEN, NRDP_HOSTNAME,"
" NRDP_SERVICENAME"
" environment variables'."
" The NRDP callback plugin is disabled."
)
self.disabled = True
def _send_nrdp(self, state, msg):
'''
"""
nrpd service check send XMLDATA like this:
<?xml version='1.0'?>
<checkresults>
@ -127,7 +128,7 @@ class CallbackModule(CallbackBase):
<output>WARNING: Danger Will Robinson!|perfdata</output>
</checkresult>
</checkresults>
'''
"""
xmldata = "<?xml version='1.0'?>\n"
xmldata += "<checkresults>\n"
xmldata += "<checkresult type='service'>\n"
@ -138,31 +139,24 @@ class CallbackModule(CallbackBase):
xmldata += "</checkresult>\n"
xmldata += "</checkresults>\n"
body = {
'cmd': 'submitcheck',
'token': self.token,
'XMLDATA': to_bytes(xmldata)
}
body = {"cmd": "submitcheck", "token": self.token, "XMLDATA": to_bytes(xmldata)}
try:
response = open_url(self.url,
data=urlencode(body),
method='POST',
validate_certs=self.validate_nrdp_certs)
response = open_url(self.url, data=urlencode(body), method="POST", validate_certs=self.validate_nrdp_certs)
return response.read()
except Exception as ex:
self._display.warning(f"NRDP callback cannot send result {ex}")
def v2_playbook_on_play_start(self, play):
'''
"""
Display Playbook and play start messages
'''
"""
self.play = play
def v2_playbook_on_stats(self, stats):
'''
"""
Display info about playbook statistics
'''
"""
name = self.play
gstats = ""
hosts = sorted(stats.processed.keys())
@ -170,13 +164,14 @@ class CallbackModule(CallbackBase):
for host in hosts:
stat = stats.summarize(host)
gstats += (
f"'{host}_ok'={stat['ok']} '{host}_changed'={stat['changed']} '{host}_unreachable'={stat['unreachable']} '{host}_failed'={stat['failures']} "
f"'{host}_ok'={stat['ok']} '{host}_changed'={stat['changed']}"
f" '{host}_unreachable'={stat['unreachable']} '{host}_failed'={stat['failures']} "
)
# Critical when failed tasks or unreachable host
critical += stat['failures']
critical += stat['unreachable']
critical += stat["failures"]
critical += stat["unreachable"]
# Warning when changed tasks
warning += stat['changed']
warning += stat["changed"]
msg = f"{name} | {gstats}"
if critical:

View file

@ -20,11 +20,10 @@ from ansible.plugins.callback import CallbackBase
class CallbackModule(CallbackBase):
'''
"""
This callback won't print messages to stdout when new callback events are received.
'''
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'community.general.null'
CALLBACK_TYPE = "stdout"
CALLBACK_NAME = "community.general.null"

View file

@ -155,13 +155,8 @@ try:
from opentelemetry.trace.status import Status, StatusCode
from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
BatchSpanProcessor,
SimpleSpanProcessor
)
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter
)
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
except ImportError as imp_exc:
OTEL_LIBRARY_IMPORT_ERROR = imp_exc
else:
@ -186,9 +181,9 @@ class TaskData:
def add_host(self, host):
if host.uuid in self.host_data:
if host.status == 'included':
if host.status == "included":
# concatenate task include output from multiple items
host.result = f'{self.host_data[host.uuid].result}\n{host.result}'
host.result = f"{self.host_data[host.uuid].result}\n{host.result}"
else:
return
@ -223,11 +218,11 @@ class OpenTelemetrySource:
def traceparent_context(self, traceparent):
carrier = dict()
carrier['traceparent'] = traceparent
carrier["traceparent"] = traceparent
return TraceContextTextMapPropagator().extract(carrier=carrier)
def start_task(self, tasks_data, hide_task_arguments, play_name, task):
""" record the start of a task for one or more hosts """
"""record the start of a task for one or more hosts"""
uuid = task._uuid
@ -245,33 +240,35 @@ class OpenTelemetrySource:
tasks_data[uuid] = TaskData(uuid, name, path, play_name, action, args)
def finish_task(self, tasks_data, status, result, dump):
""" record the results of a task for a single host """
"""record the results of a task for a single host"""
task_uuid = result._task._uuid
if hasattr(result, '_host') and result._host is not None:
if hasattr(result, "_host") and result._host is not None:
host_uuid = result._host._uuid
host_name = result._host.name
else:
host_uuid = 'include'
host_name = 'include'
host_uuid = "include"
host_name = "include"
task = tasks_data[task_uuid]
task.dump = dump
task.add_host(HostData(host_uuid, host_name, status, result))
def generate_distributed_traces(self,
otel_service_name,
ansible_playbook,
tasks_data,
status,
traceparent,
disable_logs,
disable_attributes_in_logs,
otel_exporter_otlp_traces_protocol,
store_spans_in_file):
""" generate distributed traces from the collected TaskData and HostData """
def generate_distributed_traces(
self,
otel_service_name,
ansible_playbook,
tasks_data,
status,
traceparent,
disable_logs,
disable_attributes_in_logs,
otel_exporter_otlp_traces_protocol,
store_spans_in_file,
):
"""generate distributed traces from the collected TaskData and HostData"""
tasks = []
parent_start_time = None
@ -280,18 +277,14 @@ class OpenTelemetrySource:
parent_start_time = task.start
tasks.append(task)
trace.set_tracer_provider(
TracerProvider(
resource=Resource.create({SERVICE_NAME: otel_service_name})
)
)
trace.set_tracer_provider(TracerProvider(resource=Resource.create({SERVICE_NAME: otel_service_name})))
otel_exporter = None
if store_spans_in_file:
otel_exporter = InMemorySpanExporter()
processor = SimpleSpanProcessor(otel_exporter)
else:
if otel_exporter_otlp_traces_protocol == 'grpc':
if otel_exporter_otlp_traces_protocol == "grpc":
otel_exporter = GRPCOTLPSpanExporter()
else:
otel_exporter = HTTPOTLPSpanExporter()
@ -301,8 +294,12 @@ class OpenTelemetrySource:
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span(ansible_playbook, context=self.traceparent_context(traceparent),
start_time=parent_start_time, kind=SpanKind.SERVER) as parent:
with tracer.start_as_current_span(
ansible_playbook,
context=self.traceparent_context(traceparent),
start_time=parent_start_time,
kind=SpanKind.SERVER,
) as parent:
parent.set_status(status)
# Populate trace metadata attributes
parent.set_attribute("ansible.version", ansible_version)
@ -319,36 +316,38 @@ class OpenTelemetrySource:
return otel_exporter
def update_span_data(self, task_data, host_data, span, disable_logs, disable_attributes_in_logs):
""" update the span with the given TaskData and HostData """
"""update the span with the given TaskData and HostData"""
name = f'[{host_data.name}] {task_data.play}: {task_data.name}'
name = f"[{host_data.name}] {task_data.play}: {task_data.name}"
message = 'success'
message = "success"
res = {}
rc = 0
status = Status(status_code=StatusCode.OK)
if host_data.status != 'included':
if host_data.status != "included":
# Support loops
enriched_error_message = None
if 'results' in host_data.result._result:
if host_data.status == 'failed':
message = self.get_error_message_from_results(host_data.result._result['results'], task_data.action)
enriched_error_message = self.enrich_error_message_from_results(host_data.result._result['results'], task_data.action)
if "results" in host_data.result._result:
if host_data.status == "failed":
message = self.get_error_message_from_results(host_data.result._result["results"], task_data.action)
enriched_error_message = self.enrich_error_message_from_results(
host_data.result._result["results"], task_data.action
)
else:
res = host_data.result._result
rc = res.get('rc', 0)
if host_data.status == 'failed':
rc = res.get("rc", 0)
if host_data.status == "failed":
message = self.get_error_message(res)
enriched_error_message = self.enrich_error_message(res)
if host_data.status == 'failed':
if host_data.status == "failed":
status = Status(status_code=StatusCode.ERROR, description=message)
# Record an exception with the task message
span.record_exception(BaseException(enriched_error_message))
elif host_data.status == 'skipped':
message = res['skip_reason'] if 'skip_reason' in res else 'skipped'
elif host_data.status == "skipped":
message = res["skip_reason"] if "skip_reason" in res else "skipped"
status = Status(status_code=StatusCode.UNSET)
elif host_data.status == 'ignored':
elif host_data.status == "ignored":
status = Status(status_code=StatusCode.UNSET)
span.set_status(status)
@ -360,7 +359,7 @@ class OpenTelemetrySource:
"ansible.task.name": name,
"ansible.task.result": rc,
"ansible.task.host.name": host_data.name,
"ansible.task.host.status": host_data.status
"ansible.task.host.status": host_data.status,
}
if isinstance(task_data.args, dict) and "gather_facts" not in task_data.action:
names = tuple(self.transform_ansible_unicode_to_str(k) for k in task_data.args.keys())
@ -380,10 +379,10 @@ class OpenTelemetrySource:
span.end(end_time=host_data.finish)
def set_span_attributes(self, span, attributes):
""" update the span attributes with the given attributes if not None """
"""update the span attributes with the given attributes if not None"""
if span is None and self._display is not None:
self._display.warning('span object is None. Please double check if that is expected.')
self._display.warning("span object is None. Please double check if that is expected.")
else:
if attributes is not None:
span.set_attributes(attributes)
@ -411,7 +410,18 @@ class OpenTelemetrySource:
@staticmethod
def url_from_args(args):
# the order matters
url_args = ("url", "api_url", "baseurl", "repo", "server_url", "chart_repo_url", "registry_url", "endpoint", "uri", "updates_url")
url_args = (
"url",
"api_url",
"baseurl",
"repo",
"server_url",
"chart_repo_url",
"registry_url",
"endpoint",
"uri",
"updates_url",
)
for arg in url_args:
if args is not None and args.get(arg):
return args.get(arg)
@ -436,33 +446,33 @@ class OpenTelemetrySource:
@staticmethod
def get_error_message(result):
if result.get('exception') is not None:
return OpenTelemetrySource._last_line(result['exception'])
return result.get('msg', 'failed')
if result.get("exception") is not None:
return OpenTelemetrySource._last_line(result["exception"])
return result.get("msg", "failed")
@staticmethod
def get_error_message_from_results(results, action):
for result in results:
if result.get('failed', False):
if result.get("failed", False):
return f"{action}({result.get('item', 'none')}) - {OpenTelemetrySource.get_error_message(result)}"
@staticmethod
def _last_line(text):
lines = text.strip().split('\n')
lines = text.strip().split("\n")
return lines[-1]
@staticmethod
def enrich_error_message(result):
message = result.get('msg', 'failed')
exception = result.get('exception')
stderr = result.get('stderr')
return f"message: \"{message}\"\nexception: \"{exception}\"\nstderr: \"{stderr}\""
message = result.get("msg", "failed")
exception = result.get("exception")
stderr = result.get("stderr")
return f'message: "{message}"\nexception: "{exception}"\nstderr: "{stderr}"'
@staticmethod
def enrich_error_message_from_results(results, action):
message = ""
for result in results:
if result.get('failed', False):
if result.get("failed", False):
message = f"{action}({result.get('item', 'none')}) - {OpenTelemetrySource.enrich_error_message(result)}\n{message}"
return message
@ -473,8 +483,8 @@ class CallbackModule(CallbackBase):
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.opentelemetry'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.opentelemetry"
CALLBACK_NEEDS_ENABLED = True
def __init__(self, display=None):
@ -494,7 +504,7 @@ class CallbackModule(CallbackBase):
if OTEL_LIBRARY_IMPORT_ERROR:
raise AnsibleError(
'The `opentelemetry-api`, `opentelemetry-exporter-otlp` or `opentelemetry-sdk` must be installed to use this plugin'
"The `opentelemetry-api`, `opentelemetry-exporter-otlp` or `opentelemetry-sdk` must be installed to use this plugin"
) from OTEL_LIBRARY_IMPORT_ERROR
self.tasks_data = OrderedDict()
@ -504,33 +514,33 @@ class CallbackModule(CallbackBase):
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
environment_variable = self.get_option('enable_from_environment')
if environment_variable is not None and os.environ.get(environment_variable, 'false').lower() != 'true':
environment_variable = self.get_option("enable_from_environment")
if environment_variable is not None and os.environ.get(environment_variable, "false").lower() != "true":
self.disabled = True
self._display.warning(
f"The `enable_from_environment` option has been set and {environment_variable} is not enabled. Disabling the `opentelemetry` callback plugin."
)
self.hide_task_arguments = self.get_option('hide_task_arguments')
self.hide_task_arguments = self.get_option("hide_task_arguments")
self.disable_attributes_in_logs = self.get_option('disable_attributes_in_logs')
self.disable_attributes_in_logs = self.get_option("disable_attributes_in_logs")
self.disable_logs = self.get_option('disable_logs')
self.disable_logs = self.get_option("disable_logs")
self.store_spans_in_file = self.get_option('store_spans_in_file')
self.store_spans_in_file = self.get_option("store_spans_in_file")
self.otel_service_name = self.get_option('otel_service_name')
self.otel_service_name = self.get_option("otel_service_name")
if not self.otel_service_name:
self.otel_service_name = 'ansible'
self.otel_service_name = "ansible"
# See https://github.com/open-telemetry/opentelemetry-specification/issues/740
self.traceparent = self.get_option('traceparent')
self.traceparent = self.get_option("traceparent")
self.otel_exporter_otlp_traces_protocol = self.get_option('otel_exporter_otlp_traces_protocol')
self.otel_exporter_otlp_traces_protocol = self.get_option("otel_exporter_otlp_traces_protocol")
def dump_results(self, task, result):
""" dump the results if disable_logs is not enabled """
"""dump the results if disable_logs is not enabled"""
if self.disable_logs:
return ""
# ansible.builtin.uri contains the response in the json field
@ -550,74 +560,40 @@ class CallbackModule(CallbackBase):
self.play_name = play.get_name()
def v2_runner_on_no_hosts(self, task):
self.opentelemetry.start_task(
self.tasks_data,
self.hide_task_arguments,
self.play_name,
task
)
self.opentelemetry.start_task(self.tasks_data, self.hide_task_arguments, self.play_name, task)
def v2_playbook_on_task_start(self, task, is_conditional):
self.opentelemetry.start_task(
self.tasks_data,
self.hide_task_arguments,
self.play_name,
task
)
self.opentelemetry.start_task(self.tasks_data, self.hide_task_arguments, self.play_name, task)
def v2_playbook_on_cleanup_task_start(self, task):
self.opentelemetry.start_task(
self.tasks_data,
self.hide_task_arguments,
self.play_name,
task
)
self.opentelemetry.start_task(self.tasks_data, self.hide_task_arguments, self.play_name, task)
def v2_playbook_on_handler_task_start(self, task):
self.opentelemetry.start_task(
self.tasks_data,
self.hide_task_arguments,
self.play_name,
task
)
self.opentelemetry.start_task(self.tasks_data, self.hide_task_arguments, self.play_name, task)
def v2_runner_on_failed(self, result, ignore_errors=False):
if ignore_errors:
status = 'ignored'
status = "ignored"
else:
status = 'failed'
status = "failed"
self.errors += 1
self.opentelemetry.finish_task(
self.tasks_data,
status,
result,
self.dump_results(self.tasks_data[result._task._uuid], result)
self.tasks_data, status, result, self.dump_results(self.tasks_data[result._task._uuid], result)
)
def v2_runner_on_ok(self, result):
self.opentelemetry.finish_task(
self.tasks_data,
'ok',
result,
self.dump_results(self.tasks_data[result._task._uuid], result)
self.tasks_data, "ok", result, self.dump_results(self.tasks_data[result._task._uuid], result)
)
def v2_runner_on_skipped(self, result):
self.opentelemetry.finish_task(
self.tasks_data,
'skipped',
result,
self.dump_results(self.tasks_data[result._task._uuid], result)
self.tasks_data, "skipped", result, self.dump_results(self.tasks_data[result._task._uuid], result)
)
def v2_playbook_on_include(self, included_file):
self.opentelemetry.finish_task(
self.tasks_data,
'included',
included_file,
""
)
self.opentelemetry.finish_task(self.tasks_data, "included", included_file, "")
def v2_playbook_on_stats(self, stats):
if self.errors == 0:
@ -633,7 +609,7 @@ class CallbackModule(CallbackBase):
self.disable_logs,
self.disable_attributes_in_logs,
self.otel_exporter_otlp_traces_protocol,
self.store_spans_in_file
self.store_spans_in_file,
)
if self.store_spans_in_file:

View file

@ -37,9 +37,10 @@ class CallbackModule(CallbackBase):
"""
This callback module tells you how long your plays ran for.
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'aggregate'
CALLBACK_NAME = 'community.general.print_task'
CALLBACK_TYPE = "aggregate"
CALLBACK_NAME = "community.general.print_task"
CALLBACK_NEEDS_ENABLED = True
@ -48,7 +49,7 @@ class CallbackModule(CallbackBase):
self._printed_message = False
def _print_task(self, task):
if hasattr(task, '_ds'):
if hasattr(task, "_ds"):
task_snippet = load(str([task._ds.copy()]), Loader=SafeLoader)
task_yaml = dump(task_snippet, sort_keys=False, Dumper=SafeDumper)
self._display.display(f"\n{task_yaml}\n")

View file

@ -30,13 +30,13 @@ class CallbackModule(CallbackBase):
"""
makes Ansible much more exciting.
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.say'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.say"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self):
super().__init__()
self.FAILED_VOICE = None
@ -45,21 +45,23 @@ class CallbackModule(CallbackBase):
self.LASER_VOICE = None
try:
self.synthesizer = get_bin_path('say')
if platform.system() != 'Darwin':
self.synthesizer = get_bin_path("say")
if platform.system() != "Darwin":
# 'say' binary available, it might be GNUstep tool which doesn't support 'voice' parameter
self._display.warning(f"'say' executable found but system is '{platform.system()}': ignoring voice parameter")
self._display.warning(
f"'say' executable found but system is '{platform.system()}': ignoring voice parameter"
)
else:
self.FAILED_VOICE = 'Zarvox'
self.REGULAR_VOICE = 'Trinoids'
self.HAPPY_VOICE = 'Cellos'
self.LASER_VOICE = 'Princess'
self.FAILED_VOICE = "Zarvox"
self.REGULAR_VOICE = "Trinoids"
self.HAPPY_VOICE = "Cellos"
self.LASER_VOICE = "Princess"
except ValueError:
try:
self.synthesizer = get_bin_path('espeak')
self.FAILED_VOICE = 'klatt'
self.HAPPY_VOICE = 'f5'
self.LASER_VOICE = 'whisper'
self.synthesizer = get_bin_path("espeak")
self.FAILED_VOICE = "klatt"
self.HAPPY_VOICE = "f5"
self.LASER_VOICE = "whisper"
except ValueError:
self.synthesizer = None
@ -67,12 +69,14 @@ class CallbackModule(CallbackBase):
# ansible will not call any callback if disabled is set to True
if not self.synthesizer:
self.disabled = True
self._display.warning(f"Unable to find either 'say' or 'espeak' executable, plugin {os.path.basename(__file__)} disabled")
self._display.warning(
f"Unable to find either 'say' or 'espeak' executable, plugin {os.path.basename(__file__)} disabled"
)
def say(self, msg, voice):
cmd = [self.synthesizer, msg]
if voice:
cmd.extend(('-v', voice))
cmd.extend(("-v", voice))
subprocess.call(cmd)
def runner_on_failed(self, host, res, ignore_errors=False):

View file

@ -45,14 +45,14 @@ from ansible.module_utils.common.text.converters import to_text
DONT_COLORIZE = False
COLORS = {
'normal': '\033[0m',
'ok': f'\x1b[{C.COLOR_CODES[C.COLOR_OK]}m', # type: ignore
'bold': '\033[1m',
'not_so_bold': '\033[1m\033[34m',
'changed': f'\x1b[{C.COLOR_CODES[C.COLOR_CHANGED]}m', # type: ignore
'failed': f'\x1b[{C.COLOR_CODES[C.COLOR_ERROR]}m', # type: ignore
'endc': '\033[0m',
'skipped': f'\x1b[{C.COLOR_CODES[C.COLOR_SKIP]}m', # type: ignore
"normal": "\033[0m",
"ok": f"\x1b[{C.COLOR_CODES[C.COLOR_OK]}m", # type: ignore
"bold": "\033[1m",
"not_so_bold": "\033[1m\033[34m",
"changed": f"\x1b[{C.COLOR_CODES[C.COLOR_CHANGED]}m", # type: ignore
"failed": f"\x1b[{C.COLOR_CODES[C.COLOR_ERROR]}m", # type: ignore
"endc": "\033[0m",
"skipped": f"\x1b[{C.COLOR_CODES[C.COLOR_SKIP]}m", # type: ignore
}
@ -78,8 +78,8 @@ class CallbackModule(CallbackBase):
"""selective.py callback plugin."""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'community.general.selective'
CALLBACK_TYPE = "stdout"
CALLBACK_NAME = "community.general.selective"
def __init__(self, display=None):
"""selective.py callback plugin."""
@ -89,11 +89,10 @@ class CallbackModule(CallbackBase):
self.printed_last_task = False
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
global DONT_COLORIZE
DONT_COLORIZE = self.get_option('nocolor')
DONT_COLORIZE = self.get_option("nocolor")
def _print_task(self, task_name=None):
if task_name is None:
@ -105,7 +104,7 @@ class CallbackModule(CallbackBase):
if self.last_skipped:
print()
line = f"# {task_name} "
msg = colorize(f"{line}{'*' * (line_length - len(line))}", 'bold')
msg = colorize(f"{line}{'*' * (line_length - len(line))}", "bold")
print(msg)
def _indent_text(self, text, indent_level):
@ -113,48 +112,51 @@ class CallbackModule(CallbackBase):
result_lines = []
for l in lines:
result_lines.append(f"{' ' * indent_level}{l}")
return '\n'.join(result_lines)
return "\n".join(result_lines)
def _print_diff(self, diff, indent_level):
if isinstance(diff, dict):
try:
diff = '\n'.join(difflib.unified_diff(diff['before'].splitlines(),
diff['after'].splitlines(),
fromfile=diff.get('before_header',
'new_file'),
tofile=diff['after_header']))
diff = "\n".join(
difflib.unified_diff(
diff["before"].splitlines(),
diff["after"].splitlines(),
fromfile=diff.get("before_header", "new_file"),
tofile=diff["after_header"],
)
)
except AttributeError:
diff = dict_diff(diff['before'], diff['after'])
diff = dict_diff(diff["before"], diff["after"])
if diff:
diff = colorize(str(diff), 'changed')
diff = colorize(str(diff), "changed")
print(self._indent_text(diff, indent_level + 4))
def _print_host_or_item(self, host_or_item, changed, msg, diff, is_host, error, stdout, stderr):
if is_host:
indent_level = 0
name = colorize(host_or_item.name, 'not_so_bold')
name = colorize(host_or_item.name, "not_so_bold")
else:
indent_level = 4
if isinstance(host_or_item, dict):
if 'key' in host_or_item.keys():
host_or_item = host_or_item['key']
name = colorize(to_text(host_or_item), 'bold')
if "key" in host_or_item.keys():
host_or_item = host_or_item["key"]
name = colorize(to_text(host_or_item), "bold")
if error:
color = 'failed'
change_string = colorize('FAILED!!!', color)
color = "failed"
change_string = colorize("FAILED!!!", color)
else:
color = 'changed' if changed else 'ok'
color = "changed" if changed else "ok"
change_string = colorize(f"changed={changed}", color)
msg = colorize(msg, color)
line_length = 120
spaces = ' ' * (40 - len(name) - indent_level)
spaces = " " * (40 - len(name) - indent_level)
line = f"{' ' * indent_level} * {name}{spaces}- {change_string}"
if len(msg) < 50:
line += f' -- {msg}'
line += f" -- {msg}"
print(f"{line} {'-' * (line_length - len(line))}---------")
else:
print(f"{line} {'-' * (line_length - len(line))}")
@ -163,10 +165,10 @@ class CallbackModule(CallbackBase):
if diff:
self._print_diff(diff, indent_level)
if stdout:
stdout = colorize(stdout, 'failed')
stdout = colorize(stdout, "failed")
print(self._indent_text(stdout, indent_level + 4))
if stderr:
stderr = colorize(stderr, 'failed')
stderr = colorize(stderr, "failed")
print(self._indent_text(stderr, indent_level + 4))
def v2_playbook_on_play_start(self, play):
@ -181,61 +183,61 @@ class CallbackModule(CallbackBase):
def _print_task_result(self, result, error=False, **kwargs):
"""Run when a task finishes correctly."""
if 'print_action' in result._task.tags or error or self._display.verbosity > 1:
if "print_action" in result._task.tags or error or self._display.verbosity > 1:
self._print_task()
self.last_skipped = False
msg = to_text(result._result.get('msg', '')) or\
to_text(result._result.get('reason', ''))
msg = to_text(result._result.get("msg", "")) or to_text(result._result.get("reason", ""))
stderr = [result._result.get('exception', None),
result._result.get('module_stderr', None)]
stderr = [result._result.get("exception", None), result._result.get("module_stderr", None)]
stderr = "\n".join([e for e in stderr if e]).strip()
self._print_host_or_item(result._host,
result._result.get('changed', False),
msg,
result._result.get('diff', None),
is_host=True,
error=error,
stdout=result._result.get('module_stdout', None),
stderr=stderr.strip(),
)
if 'results' in result._result:
for r in result._result['results']:
failed = 'failed' in r and r['failed']
self._print_host_or_item(
result._host,
result._result.get("changed", False),
msg,
result._result.get("diff", None),
is_host=True,
error=error,
stdout=result._result.get("module_stdout", None),
stderr=stderr.strip(),
)
if "results" in result._result:
for r in result._result["results"]:
failed = "failed" in r and r["failed"]
stderr = [r.get('exception', None), r.get('module_stderr', None)]
stderr = [r.get("exception", None), r.get("module_stderr", None)]
stderr = "\n".join([e for e in stderr if e]).strip()
self._print_host_or_item(r[r['ansible_loop_var']],
r.get('changed', False),
to_text(r.get('msg', '')),
r.get('diff', None),
is_host=False,
error=failed,
stdout=r.get('module_stdout', None),
stderr=stderr.strip(),
)
self._print_host_or_item(
r[r["ansible_loop_var"]],
r.get("changed", False),
to_text(r.get("msg", "")),
r.get("diff", None),
is_host=False,
error=failed,
stdout=r.get("module_stdout", None),
stderr=stderr.strip(),
)
else:
self.last_skipped = True
print('.', end="")
print(".", end="")
def v2_playbook_on_stats(self, stats):
"""Display info about playbook statistics."""
print()
self.printed_last_task = False
self._print_task('STATS')
self._print_task("STATS")
hosts = sorted(stats.processed.keys())
for host in hosts:
s = stats.summarize(host)
if s['failures'] or s['unreachable']:
color = 'failed'
elif s['changed']:
color = 'changed'
if s["failures"] or s["unreachable"]:
color = "failed"
elif s["changed"]:
color = "changed"
else:
color = 'ok'
color = "ok"
msg = (
f"{host} : ok={s['ok']}\tchanged={s['changed']}\tfailed={s['failures']}\tunreachable="
@ -250,14 +252,13 @@ class CallbackModule(CallbackBase):
self.last_skipped = False
line_length = 120
spaces = ' ' * (31 - len(result._host.name) - 4)
spaces = " " * (31 - len(result._host.name) - 4)
line = f" * {colorize(result._host.name, 'not_so_bold')}{spaces}- {colorize('skipped', 'skipped')}"
reason = result._result.get('skipped_reason', '') or \
result._result.get('skip_reason', '')
reason = result._result.get("skipped_reason", "") or result._result.get("skip_reason", "")
if len(reason) < 50:
line += f' -- {reason}'
line += f" -- {reason}"
print(f"{line} {'-' * (line_length - len(line))}---------")
else:
print(f"{line} {'-' * (line_length - len(line))}")

View file

@ -70,6 +70,7 @@ from ansible.plugins.callback import CallbackBase
try:
import prettytable
HAS_PRETTYTABLE = True
except ImportError:
HAS_PRETTYTABLE = False
@ -79,20 +80,20 @@ 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_TYPE = "notification"
CALLBACK_NAME = "community.general.slack"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, display=None):
super().__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._display.warning(
"The `prettytable` python module is not installed. Disabling the Slack callback plugin."
)
self.playbook_name = None
@ -102,34 +103,34 @@ class CallbackModule(CallbackBase):
self.guid = uuid.uuid4().hex[:6]
def set_options(self, task_keys=None, var_options=None, direct=None):
super().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')
self.http_agent = self.get_option('http_agent')
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")
self.http_agent = self.get_option("http_agent")
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.')
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',
"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'),
"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)
@ -145,67 +146,63 @@ class CallbackModule(CallbackBase):
)
return response.read()
except Exception as e:
self._display.warning(f'Could not submit message to Slack: {e}')
self._display.warning(f"Could not submit message to Slack: {e}")
def v2_playbook_on_start(self, playbook):
self.playbook_name = os.path.basename(playbook._file_name)
title = [
f'*Playbook initiated* (_{self.guid}_)'
]
title = [f"*Playbook initiated* (_{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']]
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(f"Inventory: {', '.join(inventory)}")
if tags and tags != ['all']:
if tags and tags != ["all"]:
invocation_items.append(f"Tags: {', '.join(tags)}")
if skip_tags:
invocation_items.append(f"Skip Tags: {', '.join(skip_tags)}")
if subset:
invocation_items.append(f'Limit: {subset}')
invocation_items.append(f"Limit: {subset}")
if extra_vars:
invocation_items.append(f"Extra Vars: {' '.join(extra_vars)}")
title.append(f"by *{context.CLIARGS['remote_user']}*")
title.append(f'\n\n*{self.playbook_name}*')
msg_items = [' '.join(title)]
title.append(f"\n\n*{self.playbook_name}*")
msg_items = [" ".join(title)]
if invocation_items:
_inv_item = '\n'.join(invocation_items)
msg_items.append(f'```\n{_inv_item}\n```')
_inv_item = "\n".join(invocation_items)
msg_items.append(f"```\n{_inv_item}\n```")
msg = '\n'.join(msg_items)
msg = "\n".join(msg_items)
attachments = [{
'fallback': msg,
'fields': [
{
'value': msg
}
],
'color': 'warning',
'mrkdwn_in': ['text', 'fallback', 'fields'],
}]
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 f'Play name not specified ({play._uuid})'
msg = f'*Starting play* (_{self.guid}_)\n\n*{name}*'
name = play.name or f"Play name not specified ({play._uuid})"
msg = f"*Starting play* (_{self.guid}_)\n\n*{name}*"
attachments = [
{
'fallback': msg,
'text': msg,
'color': 'warning',
'mrkdwn_in': ['text', 'fallback', 'fields'],
"fallback": msg,
"text": msg,
"color": "warning",
"mrkdwn_in": ["text", "fallback", "fields"],
}
]
self.send_msg(attachments=attachments)
@ -215,8 +212,7 @@ class CallbackModule(CallbackBase):
hosts = sorted(stats.processed.keys())
t = prettytable.PrettyTable(['Host', 'Ok', 'Changed', 'Unreachable',
'Failures', 'Rescued', 'Ignored'])
t = prettytable.PrettyTable(["Host", "Ok", "Changed", "Unreachable", "Failures", "Rescued", "Ignored"])
failures = False
unreachable = False
@ -224,38 +220,28 @@ class CallbackModule(CallbackBase):
for h in hosts:
s = stats.summarize(h)
if s['failures'] > 0:
if s["failures"] > 0:
failures = True
if s['unreachable'] > 0:
if s["unreachable"] > 0:
unreachable = True
t.add_row([h] + [s[k] for k in ['ok', 'changed', 'unreachable',
'failures', 'rescued', 'ignored']])
t.add_row([h] + [s[k] for k in ["ok", "changed", "unreachable", "failures", "rescued", "ignored"]])
attachments = []
msg_items = [
f'*Playbook Complete* (_{self.guid}_)'
]
msg_items = [f"*Playbook Complete* (_{self.guid}_)"]
if failures or unreachable:
color = 'danger'
msg_items.append('\n*Failed!*')
color = "danger"
msg_items.append("\n*Failed!*")
else:
color = 'good'
msg_items.append('\n*Success!*')
color = "good"
msg_items.append("\n*Success!*")
msg_items.append(f'```\n{t}\n```')
msg_items.append(f"```\n{t}\n```")
msg = '\n'.join(msg_items)
msg = "\n".join(msg_items)
attachments.append({
'fallback': msg,
'fields': [
{
'value': msg
}
],
'color': color,
'mrkdwn_in': ['text', 'fallback', 'fields']
})
attachments.append(
{"fallback": msg, "fields": [{"value": msg}], "color": color, "mrkdwn_in": ["text", "fallback", "fields"]}
)
self.send_msg(attachments=attachments)

View file

@ -110,7 +110,7 @@ class SplunkHTTPCollectorSource:
self.user = getpass.getuser()
def send_event(self, url, authtoken, validate_certs, include_milliseconds, batch, state, result, runtime):
if result._task_fields['args'].get('_ansible_check_mode') is True:
if result._task_fields["args"].get("_ansible_check_mode") is True:
self.ansible_check_mode = True
if result._task._role:
@ -118,33 +118,33 @@ class SplunkHTTPCollectorSource:
else:
ansible_role = None
if 'args' in result._task_fields:
del result._task_fields['args']
if "args" in result._task_fields:
del result._task_fields["args"]
data = {}
data['uuid'] = result._task._uuid
data['session'] = self.session
data["uuid"] = result._task._uuid
data["session"] = self.session
if batch is not None:
data['batch'] = batch
data['status'] = state
data["batch"] = batch
data["status"] = state
if include_milliseconds:
time_format = '%Y-%m-%d %H:%M:%S.%f +0000'
time_format = "%Y-%m-%d %H:%M:%S.%f +0000"
else:
time_format = '%Y-%m-%d %H:%M:%S +0000'
time_format = "%Y-%m-%d %H:%M:%S +0000"
data['timestamp'] = now().strftime(time_format)
data['host'] = self.host
data['ip_address'] = self.ip_address
data['user'] = self.user
data['runtime'] = runtime
data['ansible_version'] = ansible_version
data['ansible_check_mode'] = self.ansible_check_mode
data['ansible_host'] = result._host.name
data['ansible_playbook'] = self.ansible_playbook
data['ansible_role'] = ansible_role
data['ansible_task'] = result._task_fields
data['ansible_result'] = result._result
data["timestamp"] = now().strftime(time_format)
data["host"] = self.host
data["ip_address"] = self.ip_address
data["user"] = self.user
data["runtime"] = runtime
data["ansible_version"] = ansible_version
data["ansible_check_mode"] = self.ansible_check_mode
data["ansible_host"] = result._host.name
data["ansible_playbook"] = self.ansible_playbook
data["ansible_role"] = ansible_role
data["ansible_task"] = result._task_fields
data["ansible_result"] = result._result
# This wraps the json payload in and outer json event needed by Splunk
jsondata = json.dumps({"event": data}, cls=AnsibleJSONEncoder, sort_keys=True)
@ -152,19 +152,16 @@ class SplunkHTTPCollectorSource:
open_url(
url,
jsondata,
headers={
'Content-type': 'application/json',
'Authorization': f"Splunk {authtoken}"
},
method='POST',
validate_certs=validate_certs
headers={"Content-type": "application/json", "Authorization": f"Splunk {authtoken}"},
method="POST",
validate_certs=validate_certs,
)
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.splunk'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.splunk"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, display=None):
@ -178,39 +175,40 @@ class CallbackModule(CallbackBase):
self.splunk = SplunkHTTPCollectorSource()
def _runtime(self, result):
return (
now() -
self.start_datetimes[result._task._uuid]
).total_seconds()
return (now() - self.start_datetimes[result._task._uuid]).total_seconds()
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.url = self.get_option('url')
self.url = self.get_option("url")
if self.url is None:
self.disabled = True
self._display.warning('Splunk HTTP collector source URL was '
'not provided. The Splunk HTTP collector '
'source URL can be provided using the '
'`SPLUNK_URL` environment variable or '
'in the ansible.cfg file.')
self._display.warning(
"Splunk HTTP collector source URL was "
"not provided. The Splunk HTTP collector "
"source URL can be provided using the "
"`SPLUNK_URL` environment variable or "
"in the ansible.cfg file."
)
self.authtoken = self.get_option('authtoken')
self.authtoken = self.get_option("authtoken")
if self.authtoken is None:
self.disabled = True
self._display.warning('Splunk HTTP collector requires an authentication'
'token. The Splunk HTTP collector '
'authentication token can be provided using the '
'`SPLUNK_AUTHTOKEN` environment variable or '
'in the ansible.cfg file.')
self._display.warning(
"Splunk HTTP collector requires an authentication"
"token. The Splunk HTTP collector "
"authentication token can be provided using the "
"`SPLUNK_AUTHTOKEN` environment variable or "
"in the ansible.cfg file."
)
self.validate_certs = self.get_option('validate_certs')
self.validate_certs = self.get_option("validate_certs")
self.include_milliseconds = self.get_option('include_milliseconds')
self.include_milliseconds = self.get_option("include_milliseconds")
self.batch = self.get_option('batch')
self.batch = self.get_option("batch")
def v2_playbook_on_start(self, playbook):
self.splunk.ansible_playbook = basename(playbook._file_name)
@ -228,9 +226,9 @@ class CallbackModule(CallbackBase):
self.validate_certs,
self.include_milliseconds,
self.batch,
'OK',
"OK",
result,
self._runtime(result)
self._runtime(result),
)
def v2_runner_on_skipped(self, result, **kwargs):
@ -240,9 +238,9 @@ class CallbackModule(CallbackBase):
self.validate_certs,
self.include_milliseconds,
self.batch,
'SKIPPED',
"SKIPPED",
result,
self._runtime(result)
self._runtime(result),
)
def v2_runner_on_failed(self, result, **kwargs):
@ -252,9 +250,9 @@ class CallbackModule(CallbackBase):
self.validate_certs,
self.include_milliseconds,
self.batch,
'FAILED',
"FAILED",
result,
self._runtime(result)
self._runtime(result),
)
def runner_on_async_failed(self, result, **kwargs):
@ -264,9 +262,9 @@ class CallbackModule(CallbackBase):
self.validate_certs,
self.include_milliseconds,
self.batch,
'FAILED',
"FAILED",
result,
self._runtime(result)
self._runtime(result),
)
def v2_runner_on_unreachable(self, result, **kwargs):
@ -276,7 +274,7 @@ class CallbackModule(CallbackBase):
self.validate_certs,
self.include_milliseconds,
self.batch,
'UNREACHABLE',
"UNREACHABLE",
result,
self._runtime(result)
self._runtime(result),
)

View file

@ -67,7 +67,7 @@ class SumologicHTTPCollectorSource:
self.user = getpass.getuser()
def send_event(self, url, state, result, runtime):
if result._task_fields['args'].get('_ansible_check_mode') is True:
if result._task_fields["args"].get("_ansible_check_mode") is True:
self.ansible_check_mode = True
if result._task._role:
@ -75,41 +75,38 @@ class SumologicHTTPCollectorSource:
else:
ansible_role = None
if 'args' in result._task_fields:
del result._task_fields['args']
if "args" in result._task_fields:
del result._task_fields["args"]
data = {}
data['uuid'] = result._task._uuid
data['session'] = self.session
data['status'] = state
data['timestamp'] = now().strftime('%Y-%m-%d %H:%M:%S +0000')
data['host'] = self.host
data['ip_address'] = self.ip_address
data['user'] = self.user
data['runtime'] = runtime
data['ansible_version'] = ansible_version
data['ansible_check_mode'] = self.ansible_check_mode
data['ansible_host'] = result._host.name
data['ansible_playbook'] = self.ansible_playbook
data['ansible_role'] = ansible_role
data['ansible_task'] = result._task_fields
data['ansible_result'] = result._result
data["uuid"] = result._task._uuid
data["session"] = self.session
data["status"] = state
data["timestamp"] = now().strftime("%Y-%m-%d %H:%M:%S +0000")
data["host"] = self.host
data["ip_address"] = self.ip_address
data["user"] = self.user
data["runtime"] = runtime
data["ansible_version"] = ansible_version
data["ansible_check_mode"] = self.ansible_check_mode
data["ansible_host"] = result._host.name
data["ansible_playbook"] = self.ansible_playbook
data["ansible_role"] = ansible_role
data["ansible_task"] = result._task_fields
data["ansible_result"] = result._result
open_url(
url,
data=json.dumps(data, cls=AnsibleJSONEncoder, sort_keys=True),
headers={
'Content-type': 'application/json',
'X-Sumo-Host': data['ansible_host']
},
method='POST'
headers={"Content-type": "application/json", "X-Sumo-Host": data["ansible_host"]},
method="POST",
)
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.sumologic'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.sumologic"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self, display=None):
@ -119,23 +116,22 @@ class CallbackModule(CallbackBase):
self.sumologic = SumologicHTTPCollectorSource()
def _runtime(self, result):
return (
now() -
self.start_datetimes[result._task._uuid]
).total_seconds()
return (now() - self.start_datetimes[result._task._uuid]).total_seconds()
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
self.url = self.get_option('url')
self.url = self.get_option("url")
if self.url is None:
self.disabled = True
self._display.warning('Sumologic HTTP collector source URL was '
'not provided. The Sumologic HTTP collector '
'source URL can be provided using the '
'`SUMOLOGIC_URL` environment variable or '
'in the ansible.cfg file.')
self._display.warning(
"Sumologic HTTP collector source URL was "
"not provided. The Sumologic HTTP collector "
"source URL can be provided using the "
"`SUMOLOGIC_URL` environment variable or "
"in the ansible.cfg file."
)
def v2_playbook_on_start(self, playbook):
self.sumologic.ansible_playbook = basename(playbook._file_name)
@ -147,41 +143,16 @@ class CallbackModule(CallbackBase):
self.start_datetimes[task._uuid] = now()
def v2_runner_on_ok(self, result, **kwargs):
self.sumologic.send_event(
self.url,
'OK',
result,
self._runtime(result)
)
self.sumologic.send_event(self.url, "OK", result, self._runtime(result))
def v2_runner_on_skipped(self, result, **kwargs):
self.sumologic.send_event(
self.url,
'SKIPPED',
result,
self._runtime(result)
)
self.sumologic.send_event(self.url, "SKIPPED", result, self._runtime(result))
def v2_runner_on_failed(self, result, **kwargs):
self.sumologic.send_event(
self.url,
'FAILED',
result,
self._runtime(result)
)
self.sumologic.send_event(self.url, "FAILED", result, self._runtime(result))
def runner_on_async_failed(self, result, **kwargs):
self.sumologic.send_event(
self.url,
'FAILED',
result,
self._runtime(result)
)
self.sumologic.send_event(self.url, "FAILED", result, self._runtime(result))
def v2_runner_on_unreachable(self, result, **kwargs):
self.sumologic.send_event(
self.url,
'UNREACHABLE',
result,
self._runtime(result)
)
self.sumologic.send_event(self.url, "UNREACHABLE", result, self._runtime(result))

View file

@ -68,62 +68,89 @@ class CallbackModule(CallbackBase):
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'notification'
CALLBACK_NAME = 'community.general.syslog_json'
CALLBACK_TYPE = "notification"
CALLBACK_NAME = "community.general.syslog_json"
CALLBACK_NEEDS_WHITELIST = True
def __init__(self):
super().__init__()
def set_options(self, task_keys=None, var_options=None, direct=None):
super().set_options(task_keys=task_keys, var_options=var_options, direct=direct)
syslog_host = self.get_option("server")
syslog_port = int(self.get_option("port"))
syslog_facility = self.get_option("facility")
self.logger = logging.getLogger('ansible logger')
self.logger = logging.getLogger("ansible logger")
self.logger.setLevel(logging.DEBUG)
self.handler = logging.handlers.SysLogHandler(
address=(syslog_host, syslog_port),
facility=syslog_facility
)
self.handler = logging.handlers.SysLogHandler(address=(syslog_host, syslog_port), facility=syslog_facility)
self.logger.addHandler(self.handler)
self.hostname = socket.gethostname()
def v2_runner_on_failed(self, result, ignore_errors=False):
res = result._result
host = result._host.get_name()
self.logger.error('%s ansible-command: task execution FAILED; host: %s; message: %s', self.hostname, host, self._dump_results(res))
self.logger.error(
"%s ansible-command: task execution FAILED; host: %s; message: %s",
self.hostname,
host,
self._dump_results(res),
)
def v2_runner_on_ok(self, result):
res = result._result
host = result._host.get_name()
if result._task.action != "gather_facts" or self.get_option("setup"):
self.logger.info('%s ansible-command: task execution OK; host: %s; message: %s', self.hostname, host, self._dump_results(res))
self.logger.info(
"%s ansible-command: task execution OK; host: %s; message: %s",
self.hostname,
host,
self._dump_results(res),
)
def v2_runner_on_skipped(self, result):
host = result._host.get_name()
self.logger.info('%s ansible-command: task execution SKIPPED; host: %s; message: %s', self.hostname, host, 'skipped')
self.logger.info(
"%s ansible-command: task execution SKIPPED; host: %s; message: %s", self.hostname, host, "skipped"
)
def v2_runner_on_unreachable(self, result):
res = result._result
host = result._host.get_name()
self.logger.error('%s ansible-command: task execution UNREACHABLE; host: %s; message: %s', self.hostname, host, self._dump_results(res))
self.logger.error(
"%s ansible-command: task execution UNREACHABLE; host: %s; message: %s",
self.hostname,
host,
self._dump_results(res),
)
def v2_runner_on_async_failed(self, result):
res = result._result
host = result._host.get_name()
jid = result._result.get('ansible_job_id')
self.logger.error('%s ansible-command: task execution FAILED; host: %s; message: %s', self.hostname, host, self._dump_results(res))
jid = result._result.get("ansible_job_id")
self.logger.error(
"%s ansible-command: task execution FAILED; host: %s; message: %s",
self.hostname,
host,
self._dump_results(res),
)
def v2_playbook_on_import_for_host(self, result, imported_file):
host = result._host.get_name()
self.logger.info('%s ansible-command: playbook IMPORTED; host: %s; message: imported file %s', self.hostname, host, imported_file)
self.logger.info(
"%s ansible-command: playbook IMPORTED; host: %s; message: imported file %s",
self.hostname,
host,
imported_file,
)
def v2_playbook_on_not_import_for_host(self, result, missing_file):
host = result._host.get_name()
self.logger.info('%s ansible-command: playbook NOT IMPORTED; host: %s; message: missing file %s', self.hostname, host, missing_file)
self.logger.info(
"%s ansible-command: playbook NOT IMPORTED; host: %s; message: missing file %s",
self.hostname,
host,
missing_file,
)

View file

@ -1,4 +1,3 @@
# Copyright (c) 2025, Felix Fontein <felix@fontein.de>
# 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
@ -51,8 +50,8 @@ from ansible.plugins.callback.default import CallbackModule as Default
class CallbackModule(Default):
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'community.general.tasks_only'
CALLBACK_TYPE = "stdout"
CALLBACK_NAME = "community.general.tasks_only"
def v2_playbook_on_play_start(self, play):
pass

View file

@ -1,4 +1,3 @@
# Copyright (c) 2024, kurokobo <kurokobo@protonmail.com>
# Copyright (c) 2014, Michael DeHaan <michael.dehaan@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)

View file

@ -28,8 +28,7 @@ from ansible.plugins.callback.default import CallbackModule as CallbackModule_de
class CallbackModule(CallbackModule_default):
'''
"""
Design goals:
- Print consolidated output that looks like a *NIX startup log
- Defaults should avoid displaying unnecessary information wherever possible
@ -39,14 +38,16 @@ class CallbackModule(CallbackModule_default):
- Add option to display all hostnames on a single line in the appropriate result color (failures may have a separate line)
- Consolidate stats display
- Don't show play name if no hosts found
'''
"""
CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'community.general.unixy'
CALLBACK_TYPE = "stdout"
CALLBACK_NAME = "community.general.unixy"
def _run_is_verbose(self, result):
return ((self._display.verbosity > 0 or '_ansible_verbose_always' in result._result) and '_ansible_verbose_override' not in result._result)
return (
self._display.verbosity > 0 or "_ansible_verbose_always" in result._result
) and "_ansible_verbose_override" not in result._result
def _get_task_display_name(self, task):
self.task_display_name = None
@ -59,8 +60,8 @@ class CallbackModule(CallbackModule_default):
self.task_display_name = task_display_name
def _preprocess_result(self, result):
self.delegated_vars = result._result.get('_ansible_delegated_vars', None)
self._handle_exception(result._result, use_stderr=self.get_option('display_failed_stderr'))
self.delegated_vars = result._result.get("_ansible_delegated_vars", None)
self._handle_exception(result._result, use_stderr=self.get_option("display_failed_stderr"))
self._handle_warnings(result._result)
def _process_result_output(self, result, msg):
@ -72,16 +73,16 @@ class CallbackModule(CallbackModule_default):
return task_result
if self.delegated_vars:
task_delegate_host = self.delegated_vars['ansible_host']
task_delegate_host = self.delegated_vars["ansible_host"]
task_result = f"{task_host} -> {task_delegate_host} {msg}"
if result._result.get('msg') and result._result.get('msg') != "All items completed":
if result._result.get("msg") and result._result.get("msg") != "All items completed":
task_result += f" | msg: {to_text(result._result.get('msg'))}"
if result._result.get('stdout'):
if result._result.get("stdout"):
task_result += f" | stdout: {result._result.get('stdout')}"
if result._result.get('stderr'):
if result._result.get("stderr"):
task_result += f" | stderr: {result._result.get('stderr')}"
return task_result
@ -89,7 +90,7 @@ class CallbackModule(CallbackModule_default):
def v2_playbook_on_task_start(self, task, is_conditional):
self._get_task_display_name(task)
if self.task_display_name is not None:
if task.check_mode and self.get_option('check_mode_markers'):
if task.check_mode and self.get_option("check_mode_markers"):
self._display.display(f"{self.task_display_name} (check mode)...")
else:
self._display.display(f"{self.task_display_name}...")
@ -97,14 +98,14 @@ class CallbackModule(CallbackModule_default):
def v2_playbook_on_handler_task_start(self, task):
self._get_task_display_name(task)
if self.task_display_name is not None:
if task.check_mode and self.get_option('check_mode_markers'):
if task.check_mode and self.get_option("check_mode_markers"):
self._display.display(f"{self.task_display_name} (via handler in check mode)... ")
else:
self._display.display(f"{self.task_display_name} (via handler)... ")
def v2_playbook_on_play_start(self, play):
name = play.get_name().strip()
if play.check_mode and self.get_option('check_mode_markers'):
if play.check_mode and self.get_option("check_mode_markers"):
if name and play.hosts:
msg = f"\n- {name} (in check mode) on hosts: {','.join(play.hosts)} -"
else:
@ -118,7 +119,7 @@ class CallbackModule(CallbackModule_default):
self._display.display(msg)
def v2_runner_on_skipped(self, result, ignore_errors=False):
if self.get_option('display_skipped_hosts'):
if self.get_option("display_skipped_hosts"):
self._preprocess_result(result)
display_color = C.COLOR_SKIP
msg = "skipped"
@ -137,12 +138,12 @@ class CallbackModule(CallbackModule_default):
msg += f" | item: {item_value}"
task_result = self._process_result_output(result, msg)
self._display.display(f" {task_result}", display_color, stderr=self.get_option('display_failed_stderr'))
self._display.display(f" {task_result}", display_color, stderr=self.get_option("display_failed_stderr"))
def v2_runner_on_ok(self, result, msg="ok", display_color=C.COLOR_OK):
self._preprocess_result(result)
result_was_changed = ('changed' in result._result and result._result['changed'])
result_was_changed = "changed" in result._result and result._result["changed"]
if result_was_changed:
msg = "done"
item_value = self._get_item_label(result._result)
@ -151,7 +152,7 @@ class CallbackModule(CallbackModule_default):
display_color = C.COLOR_CHANGED
task_result = self._process_result_output(result, msg)
self._display.display(f" {task_result}", display_color)
elif self.get_option('display_ok_hosts'):
elif self.get_option("display_ok_hosts"):
task_result = self._process_result_output(result, msg)
self._display.display(f" {task_result}", display_color)
@ -171,17 +172,17 @@ class CallbackModule(CallbackModule_default):
display_color = C.COLOR_UNREACHABLE
task_result = self._process_result_output(result, msg)
self._display.display(f" {task_result}", display_color, stderr=self.get_option('display_failed_stderr'))
self._display.display(f" {task_result}", display_color, stderr=self.get_option("display_failed_stderr"))
def v2_on_file_diff(self, result):
if result._task.loop and 'results' in result._result:
for res in result._result['results']:
if 'diff' in res and res['diff'] and res.get('changed', False):
diff = self._get_diff(res['diff'])
if result._task.loop and "results" in result._result:
for res in result._result["results"]:
if "diff" in res and res["diff"] and res.get("changed", False):
diff = self._get_diff(res["diff"])
if diff:
self._display.display(diff)
elif 'diff' in result._result and result._result['diff'] and result._result.get('changed', False):
diff = self._get_diff(result._result['diff'])
elif "diff" in result._result and result._result["diff"] and result._result.get("changed", False):
diff = self._get_diff(result._result["diff"])
if diff:
self._display.display(diff)
@ -197,30 +198,30 @@ class CallbackModule(CallbackModule_default):
f" {hostcolor(h, t)} : {colorize('ok', t['ok'], C.COLOR_OK)} {colorize('changed', t['changed'], C.COLOR_CHANGED)} "
f"{colorize('unreachable', t['unreachable'], C.COLOR_UNREACHABLE)} {colorize('failed', t['failures'], C.COLOR_ERROR)} "
f"{colorize('rescued', t['rescued'], C.COLOR_OK)} {colorize('ignored', t['ignored'], C.COLOR_WARN)}",
screen_only=True
screen_only=True,
)
self._display.display(
f" {hostcolor(h, t, False)} : {colorize('ok', t['ok'], None)} {colorize('changed', t['changed'], None)} "
f"{colorize('unreachable', t['unreachable'], None)} {colorize('failed', t['failures'], None)} {colorize('rescued', t['rescued'], None)} "
f"{colorize('ignored', t['ignored'], None)}",
log_only=True
log_only=True,
)
if stats.custom and self.get_option('show_custom_stats'):
if stats.custom and self.get_option("show_custom_stats"):
self._display.banner("CUSTOM STATS: ")
# per host
# TODO: come up with 'pretty format'
for k in sorted(stats.custom.keys()):
if k == '_run':
if k == "_run":
continue
stat_val = self._dump_results(stats.custom[k], indent=1).replace('\n', '')
self._display.display(f'\t{k}: {stat_val}')
stat_val = self._dump_results(stats.custom[k], indent=1).replace("\n", "")
self._display.display(f"\t{k}: {stat_val}")
# print per run custom stats
if '_run' in stats.custom:
if "_run" in stats.custom:
self._display.display("", screen_only=True)
stat_val_run = self._dump_results(stats.custom['_run'], indent=1).replace('\n', '')
self._display.display(f'\tRUN: {stat_val_run}')
stat_val_run = self._dump_results(stats.custom["_run"], indent=1).replace("\n", "")
self._display.display(f"\tRUN: {stat_val_run}")
self._display.display("", screen_only=True)
def v2_playbook_on_no_hosts_matched(self):
@ -230,21 +231,24 @@ class CallbackModule(CallbackModule_default):
self._display.display(" Ran out of hosts!", color=C.COLOR_ERROR)
def v2_playbook_on_start(self, playbook):
if context.CLIARGS['check'] and self.get_option('check_mode_markers'):
if context.CLIARGS["check"] and self.get_option("check_mode_markers"):
self._display.display(f"Executing playbook {basename(playbook._file_name)} in check mode")
else:
self._display.display(f"Executing playbook {basename(playbook._file_name)}")
# show CLI arguments
if self._display.verbosity > 3:
if context.CLIARGS.get('args'):
self._display.display(f"Positional arguments: {' '.join(context.CLIARGS['args'])}",
color=C.COLOR_VERBOSE, screen_only=True)
if context.CLIARGS.get("args"):
self._display.display(
f"Positional arguments: {' '.join(context.CLIARGS['args'])}",
color=C.COLOR_VERBOSE,
screen_only=True,
)
for argument in (a for a in context.CLIARGS if a != 'args'):
for argument in (a for a in context.CLIARGS if a != "args"):
val = context.CLIARGS[argument]
if val:
self._display.vvvv(f'{argument}: {val}')
self._display.vvvv(f"{argument}: {val}")
def v2_runner_retry(self, result):
msg = f" Retrying... ({result._result['attempts']} of {result._result['retries']})"