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:
parent
3f2213791a
commit
340ff8586d
1008 changed files with 61301 additions and 58309 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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']}'"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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))}")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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']})"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue