ansible-core 2.19.0b1__py3-none-any.whl → 2.19.0b3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible/_internal/_ansiballz.py +1 -4
- ansible/_internal/_collection_proxy.py +47 -0
- ansible/_internal/_errors/_handler.py +4 -4
- ansible/_internal/_json/__init__.py +47 -4
- ansible/_internal/_json/_profiles/_legacy.py +2 -3
- ansible/_internal/_templating/_datatag.py +3 -4
- ansible/_internal/_templating/_engine.py +6 -1
- ansible/_internal/_templating/_jinja_bits.py +4 -4
- ansible/_internal/_templating/_jinja_plugins.py +7 -17
- ansible/cli/__init__.py +12 -5
- ansible/cli/arguments/option_helpers.py +4 -1
- ansible/cli/doc.py +14 -8
- ansible/config/base.yml +17 -20
- ansible/config/manager.py +2 -2
- ansible/constants.py +0 -62
- ansible/errors/__init__.py +6 -2
- ansible/executor/module_common.py +11 -7
- ansible/executor/process/worker.py +31 -26
- ansible/executor/task_executor.py +38 -31
- ansible/executor/task_queue_manager.py +62 -52
- ansible/executor/task_result.py +168 -72
- ansible/galaxy/api.py +1 -1
- ansible/galaxy/collection/__init__.py +3 -3
- ansible/inventory/manager.py +2 -1
- ansible/module_utils/_internal/_ansiballz.py +4 -30
- ansible/module_utils/_internal/_datatag/_tags.py +3 -25
- ansible/module_utils/_internal/_deprecator.py +134 -0
- ansible/module_utils/_internal/_plugin_info.py +25 -0
- ansible/module_utils/_internal/_validation.py +14 -0
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +68 -23
- ansible/module_utils/common/arg_spec.py +8 -3
- ansible/module_utils/common/messages.py +40 -23
- ansible/module_utils/common/process.py +0 -1
- ansible/module_utils/common/respawn.py +0 -7
- ansible/module_utils/common/warnings.py +13 -13
- ansible/module_utils/datatag.py +13 -13
- ansible/modules/async_status.py +1 -1
- ansible/modules/dnf5.py +1 -1
- ansible/modules/get_url.py +1 -1
- ansible/parsing/utils/jsonify.py +40 -0
- ansible/parsing/yaml/objects.py +16 -5
- ansible/playbook/included_file.py +25 -12
- ansible/playbook/task.py +0 -2
- ansible/plugins/__init__.py +18 -8
- ansible/plugins/action/__init__.py +6 -14
- ansible/plugins/action/gather_facts.py +2 -4
- ansible/plugins/callback/__init__.py +173 -86
- ansible/plugins/callback/default.py +79 -79
- ansible/plugins/callback/junit.py +20 -19
- ansible/plugins/callback/minimal.py +17 -17
- ansible/plugins/callback/oneline.py +23 -16
- ansible/plugins/callback/tree.py +13 -6
- ansible/plugins/connection/local.py +1 -1
- ansible/plugins/connection/paramiko_ssh.py +9 -2
- ansible/plugins/doc_fragments/action_core.py +1 -1
- ansible/plugins/filter/core.py +12 -2
- ansible/plugins/inventory/__init__.py +2 -2
- ansible/plugins/loader.py +194 -130
- ansible/plugins/lookup/url.py +2 -2
- ansible/plugins/strategy/__init__.py +76 -82
- ansible/plugins/strategy/free.py +4 -4
- ansible/plugins/strategy/linear.py +11 -9
- ansible/plugins/test/core.py +1 -1
- ansible/release.py +1 -1
- ansible/template/__init__.py +8 -6
- ansible/utils/collection_loader/_collection_meta.py +5 -3
- ansible/utils/display.py +141 -79
- ansible/utils/py3compat.py +1 -7
- ansible/utils/ssh_functions.py +4 -1
- ansible/utils/vars.py +23 -0
- ansible/vars/clean.py +1 -1
- ansible/vars/manager.py +18 -27
- ansible/vars/plugins.py +4 -4
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/RECORD +89 -85
- ansible_test/_internal/commands/sanity/pylint.py +1 -0
- ansible_test/_internal/docker_util.py +4 -3
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +475 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
- ansible/module_utils/_internal/_dataclass_annotation_patch.py +0 -64
- ansible/module_utils/_internal/_plugin_exec_context.py +0 -49
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -399
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/Apache-License.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/COPYING +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/MIT-license.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/PSF-license.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/top_level.txt +0 -0
ansible/errors/__init__.py
CHANGED
@@ -18,6 +18,9 @@ from ..module_utils.datatag import native_type_name
|
|
18
18
|
from ansible._internal._datatag import _tags
|
19
19
|
from .._internal._errors import _utils
|
20
20
|
|
21
|
+
if t.TYPE_CHECKING:
|
22
|
+
from ansible.plugins import loader as _t_loader
|
23
|
+
|
21
24
|
|
22
25
|
class ExitCode(enum.IntEnum):
|
23
26
|
SUCCESS = 0 # used by TQM, must be bit-flag safe
|
@@ -374,8 +377,9 @@ class _AnsibleActionDone(AnsibleAction):
|
|
374
377
|
class AnsiblePluginError(AnsibleError):
|
375
378
|
"""Base class for Ansible plugin-related errors that do not need AnsibleError contextual data."""
|
376
379
|
|
377
|
-
def __init__(self, message=None, plugin_load_context=None):
|
378
|
-
super(AnsiblePluginError, self).__init__(message)
|
380
|
+
def __init__(self, message: str | None = None, plugin_load_context: _t_loader.PluginLoadContext | None = None, help_text: str | None = None) -> None:
|
381
|
+
super(AnsiblePluginError, self).__init__(message, help_text=help_text)
|
382
|
+
|
379
383
|
self.plugin_load_context = plugin_load_context
|
380
384
|
|
381
385
|
|
@@ -39,7 +39,6 @@ from io import BytesIO
|
|
39
39
|
from ansible._internal import _locking
|
40
40
|
from ansible._internal._datatag import _utils
|
41
41
|
from ansible.module_utils._internal import _dataclass_validation
|
42
|
-
from ansible.module_utils.common.messages import PluginInfo
|
43
42
|
from ansible.module_utils.common.yaml import yaml_load
|
44
43
|
from ansible._internal._datatag._tags import Origin
|
45
44
|
from ansible.module_utils.common.json import Direction, get_module_encoder
|
@@ -56,6 +55,7 @@ from ansible.template import Templar
|
|
56
55
|
from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
|
57
56
|
from ansible.module_utils._internal import _json, _ansiballz
|
58
57
|
from ansible.module_utils import basic as _basic
|
58
|
+
from ansible.module_utils.common import messages as _messages
|
59
59
|
|
60
60
|
if t.TYPE_CHECKING:
|
61
61
|
from ansible import template as _template
|
@@ -434,7 +434,13 @@ class ModuleUtilLocatorBase:
|
|
434
434
|
else:
|
435
435
|
msg += '.'
|
436
436
|
|
437
|
-
display.deprecated(
|
437
|
+
display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
438
|
+
msg=msg,
|
439
|
+
version=removal_version,
|
440
|
+
removed=removed,
|
441
|
+
date=removal_date,
|
442
|
+
deprecator=_messages.PluginInfo._from_collection_name(self._collection_name),
|
443
|
+
)
|
438
444
|
if 'redirect' in routing_entry:
|
439
445
|
self.redirected = True
|
440
446
|
source_pkg = '.'.join(name_parts)
|
@@ -944,7 +950,6 @@ class _CachedModule:
|
|
944
950
|
def _find_module_utils(
|
945
951
|
*,
|
946
952
|
module_name: str,
|
947
|
-
plugin: PluginInfo,
|
948
953
|
b_module_data: bytes,
|
949
954
|
module_path: str,
|
950
955
|
module_args: dict[object, object],
|
@@ -1020,7 +1025,9 @@ def _find_module_utils(
|
|
1020
1025
|
# People should start writing collections instead of modules in roles so we
|
1021
1026
|
# may never fix this
|
1022
1027
|
display.debug('ANSIBALLZ: Could not determine module FQN')
|
1023
|
-
|
1028
|
+
# FIXME: add integration test to validate that builtins and legacy modules with the same name are tracked separately by the caching mechanism
|
1029
|
+
# FIXME: surrogate FQN should be unique per source path- role-packaged modules with name collisions can still be aliased
|
1030
|
+
remote_module_fqn = 'ansible.legacy.%s' % module_name
|
1024
1031
|
|
1025
1032
|
if module_substyle == 'python':
|
1026
1033
|
date_time = datetime.datetime.now(datetime.timezone.utc)
|
@@ -1126,7 +1133,6 @@ def _find_module_utils(
|
|
1126
1133
|
module_fqn=remote_module_fqn,
|
1127
1134
|
params=encoded_params,
|
1128
1135
|
profile=module_metadata.serialization_profile,
|
1129
|
-
plugin_info_dict=dataclasses.asdict(plugin),
|
1130
1136
|
date_time=date_time,
|
1131
1137
|
coverage_config=coverage_config,
|
1132
1138
|
coverage_output=coverage_output,
|
@@ -1236,7 +1242,6 @@ def _extract_interpreter(b_module_data):
|
|
1236
1242
|
def modify_module(
|
1237
1243
|
*,
|
1238
1244
|
module_name: str,
|
1239
|
-
plugin: PluginInfo,
|
1240
1245
|
module_path,
|
1241
1246
|
module_args,
|
1242
1247
|
templar,
|
@@ -1277,7 +1282,6 @@ def modify_module(
|
|
1277
1282
|
|
1278
1283
|
module_bits = _find_module_utils(
|
1279
1284
|
module_name=module_name,
|
1280
|
-
plugin=plugin,
|
1281
1285
|
b_module_data=b_module_data,
|
1282
1286
|
module_path=module_path,
|
1283
1287
|
module_args=module_args,
|
@@ -32,6 +32,7 @@ from ansible._internal import _task
|
|
32
32
|
from ansible.errors import AnsibleConnectionFailure, AnsibleError
|
33
33
|
from ansible.executor.task_executor import TaskExecutor
|
34
34
|
from ansible.executor.task_queue_manager import FinalQueue, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
|
35
|
+
from ansible.executor.task_result import _RawTaskResult
|
35
36
|
from ansible.inventory.host import Host
|
36
37
|
from ansible.module_utils.common.collections import is_sequence
|
37
38
|
from ansible.module_utils.common.text.converters import to_text
|
@@ -226,7 +227,7 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
|
|
226
227
|
init_plugin_loader(cli_collections_path)
|
227
228
|
|
228
229
|
try:
|
229
|
-
# execute the task and build a
|
230
|
+
# execute the task and build a _RawTaskResult from the result
|
230
231
|
display.debug("running TaskExecutor() for %s/%s" % (self._host, self._task))
|
231
232
|
executor_result = TaskExecutor(
|
232
233
|
self._host,
|
@@ -256,48 +257,52 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
|
|
256
257
|
# put the result on the result queue
|
257
258
|
display.debug("sending task result for task %s" % self._task._uuid)
|
258
259
|
try:
|
259
|
-
self._final_q.send_task_result(
|
260
|
-
self._host
|
261
|
-
self._task
|
262
|
-
executor_result,
|
260
|
+
self._final_q.send_task_result(_RawTaskResult(
|
261
|
+
host=self._host,
|
262
|
+
task=self._task,
|
263
|
+
return_data=executor_result,
|
263
264
|
task_fields=self._task.dump_attrs(),
|
264
|
-
)
|
265
|
+
))
|
265
266
|
except Exception as ex:
|
266
267
|
try:
|
267
268
|
raise AnsibleError("Task result omitted due to queue send failure.") from ex
|
268
269
|
except Exception as ex_wrapper:
|
269
|
-
self._final_q.send_task_result(
|
270
|
-
self._host
|
271
|
-
self._task
|
272
|
-
ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure
|
273
|
-
{}, # The failure pickling may have been caused by the task attrs, omit for safety
|
274
|
-
)
|
270
|
+
self._final_q.send_task_result(_RawTaskResult(
|
271
|
+
host=self._host,
|
272
|
+
task=self._task,
|
273
|
+
return_data=ActionBase.result_dict_from_exception(ex_wrapper), # Overriding the task result, to represent the failure
|
274
|
+
task_fields={}, # The failure pickling may have been caused by the task attrs, omit for safety
|
275
|
+
))
|
275
276
|
|
276
277
|
display.debug("done sending task result for task %s" % self._task._uuid)
|
277
278
|
|
278
|
-
except AnsibleConnectionFailure:
|
279
|
+
except AnsibleConnectionFailure as ex:
|
280
|
+
return_data = ActionBase.result_dict_from_exception(ex)
|
281
|
+
return_data.pop('failed')
|
282
|
+
return_data.update(unreachable=True)
|
283
|
+
|
279
284
|
self._host.vars = dict()
|
280
285
|
self._host.groups = []
|
281
|
-
self._final_q.send_task_result(
|
282
|
-
self._host
|
283
|
-
self._task
|
284
|
-
|
286
|
+
self._final_q.send_task_result(_RawTaskResult(
|
287
|
+
host=self._host,
|
288
|
+
task=self._task,
|
289
|
+
return_data=return_data,
|
285
290
|
task_fields=self._task.dump_attrs(),
|
286
|
-
)
|
291
|
+
))
|
287
292
|
|
288
|
-
except Exception as
|
289
|
-
if not isinstance(
|
293
|
+
except Exception as ex:
|
294
|
+
if not isinstance(ex, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(ex, TemplateNotFound):
|
290
295
|
try:
|
291
296
|
self._host.vars = dict()
|
292
297
|
self._host.groups = []
|
293
|
-
self._final_q.send_task_result(
|
294
|
-
self._host
|
295
|
-
self._task
|
296
|
-
|
298
|
+
self._final_q.send_task_result(_RawTaskResult(
|
299
|
+
host=self._host,
|
300
|
+
task=self._task,
|
301
|
+
return_data=ActionBase.result_dict_from_exception(ex),
|
297
302
|
task_fields=self._task.dump_attrs(),
|
298
|
-
)
|
303
|
+
))
|
299
304
|
except Exception:
|
300
|
-
display.debug(u"WORKER EXCEPTION: %s" % to_text(
|
305
|
+
display.debug(u"WORKER EXCEPTION: %s" % to_text(ex))
|
301
306
|
display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc()))
|
302
307
|
finally:
|
303
308
|
self._clean_up()
|
@@ -20,10 +20,9 @@ from ansible.errors import (
|
|
20
20
|
AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleConnectionFailure, AnsibleActionFail, AnsibleActionSkip, AnsibleTaskError,
|
21
21
|
AnsibleValueOmittedError,
|
22
22
|
)
|
23
|
-
from ansible.executor.task_result import
|
23
|
+
from ansible.executor.task_result import _RawTaskResult
|
24
24
|
from ansible._internal._datatag import _utils
|
25
|
-
from ansible.module_utils.
|
26
|
-
from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary
|
25
|
+
from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary, PluginInfo
|
27
26
|
from ansible.module_utils.datatag import native_type_name
|
28
27
|
from ansible._internal._datatag._tags import TrustedAsTemplate
|
29
28
|
from ansible.module_utils.parsing.convert_bool import boolean
|
@@ -44,6 +43,9 @@ from ansible.vars.clean import namespace_facts, clean_facts
|
|
44
43
|
from ansible.vars.manager import _deprecate_top_level_fact
|
45
44
|
from ansible._internal._errors import _captured
|
46
45
|
|
46
|
+
if t.TYPE_CHECKING:
|
47
|
+
from ansible.executor.task_queue_manager import FinalQueue
|
48
|
+
|
47
49
|
display = Display()
|
48
50
|
|
49
51
|
|
@@ -79,7 +81,7 @@ class TaskExecutor:
|
|
79
81
|
class.
|
80
82
|
"""
|
81
83
|
|
82
|
-
def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q, variable_manager):
|
84
|
+
def __init__(self, host, task: Task, job_vars, play_context, loader, shared_loader_obj, final_q: FinalQueue, variable_manager):
|
83
85
|
self._host = host
|
84
86
|
self._task = task
|
85
87
|
self._job_vars = job_vars
|
@@ -361,10 +363,10 @@ class TaskExecutor:
|
|
361
363
|
if self._connection and not isinstance(self._connection, string_types):
|
362
364
|
task_fields['connection'] = getattr(self._connection, 'ansible_name')
|
363
365
|
|
364
|
-
tr =
|
365
|
-
self._host
|
366
|
-
self._task
|
367
|
-
res,
|
366
|
+
tr = _RawTaskResult(
|
367
|
+
host=self._host,
|
368
|
+
task=self._task,
|
369
|
+
return_data=res,
|
368
370
|
task_fields=task_fields,
|
369
371
|
)
|
370
372
|
|
@@ -637,8 +639,8 @@ class TaskExecutor:
|
|
637
639
|
if self._task.timeout:
|
638
640
|
old_sig = signal.signal(signal.SIGALRM, task_timeout)
|
639
641
|
signal.alarm(self._task.timeout)
|
640
|
-
|
641
|
-
|
642
|
+
|
643
|
+
result = self._handler.run(task_vars=vars_copy)
|
642
644
|
|
643
645
|
# DTFIX-RELEASE: nuke this, it hides a lot of error detail- remove the active exception propagation hack from AnsibleActionFail at the same time
|
644
646
|
except (AnsibleActionFail, AnsibleActionSkip) as e:
|
@@ -666,17 +668,23 @@ class TaskExecutor:
|
|
666
668
|
if result.get('failed'):
|
667
669
|
self._final_q.send_callback(
|
668
670
|
'v2_runner_on_async_failed',
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
671
|
+
_RawTaskResult(
|
672
|
+
host=self._host,
|
673
|
+
task=self._task,
|
674
|
+
return_data=result,
|
675
|
+
task_fields=self._task.dump_attrs(),
|
676
|
+
),
|
677
|
+
)
|
673
678
|
else:
|
674
679
|
self._final_q.send_callback(
|
675
680
|
'v2_runner_on_async_ok',
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
681
|
+
_RawTaskResult(
|
682
|
+
host=self._host,
|
683
|
+
task=self._task,
|
684
|
+
return_data=result,
|
685
|
+
task_fields=self._task.dump_attrs(),
|
686
|
+
),
|
687
|
+
)
|
680
688
|
|
681
689
|
if 'ansible_facts' in result and self._task.action not in C._ACTION_DEBUG:
|
682
690
|
if self._task.action in C._ACTION_WITH_CLEAN_FACTS:
|
@@ -756,12 +764,12 @@ class TaskExecutor:
|
|
756
764
|
display.debug('Retrying task, attempt %d of %d' % (attempt, retries))
|
757
765
|
self._final_q.send_callback(
|
758
766
|
'v2_runner_retry',
|
759
|
-
|
760
|
-
self._host
|
761
|
-
self._task
|
762
|
-
result,
|
767
|
+
_RawTaskResult(
|
768
|
+
host=self._host,
|
769
|
+
task=self._task,
|
770
|
+
return_data=result,
|
763
771
|
task_fields=self._task.dump_attrs()
|
764
|
-
)
|
772
|
+
),
|
765
773
|
)
|
766
774
|
time.sleep(delay)
|
767
775
|
self._handler = self._get_action_handler(templar=templar)
|
@@ -835,13 +843,12 @@ class TaskExecutor:
|
|
835
843
|
if not isinstance(deprecation, DeprecationSummary):
|
836
844
|
# translate non-DeprecationMessageDetail message dicts
|
837
845
|
try:
|
838
|
-
if deprecation.pop('collection_name', ...) is not ...:
|
846
|
+
if (collection_name := deprecation.pop('collection_name', ...)) is not ...:
|
839
847
|
# deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
|
848
|
+
# CAUTION: This deprecation cannot be enabled until the replacement (deprecator) has been documented, and the schema finalized.
|
840
849
|
# self.deprecated('The `collection_name` key in the `deprecations` dictionary is deprecated.', version='2.27')
|
841
|
-
|
850
|
+
deprecation.update(deprecator=PluginInfo._from_collection_name(collection_name))
|
842
851
|
|
843
|
-
# DTFIX-RELEASE: when plugin isn't set, do it at the boundary where we receive the module/action results
|
844
|
-
# that may even allow us to never set it in modules/actions directly and to populate it at the boundary
|
845
852
|
deprecation = DeprecationSummary(
|
846
853
|
details=(
|
847
854
|
Detail(msg=deprecation.pop('msg')),
|
@@ -926,10 +933,10 @@ class TaskExecutor:
|
|
926
933
|
time_left -= self._task.poll
|
927
934
|
self._final_q.send_callback(
|
928
935
|
'v2_runner_on_async_poll',
|
929
|
-
|
930
|
-
self._host
|
931
|
-
async_task
|
932
|
-
async_result,
|
936
|
+
_RawTaskResult(
|
937
|
+
host=self._host,
|
938
|
+
task=async_task,
|
939
|
+
return_data=async_result,
|
933
940
|
task_fields=async_task.dump_attrs(),
|
934
941
|
),
|
935
942
|
)
|
@@ -17,6 +17,7 @@
|
|
17
17
|
|
18
18
|
from __future__ import annotations
|
19
19
|
|
20
|
+
import dataclasses
|
20
21
|
import os
|
21
22
|
import sys
|
22
23
|
import tempfile
|
@@ -31,7 +32,7 @@ from ansible.errors import AnsibleError, ExitCode, AnsibleCallbackError
|
|
31
32
|
from ansible._internal._errors._handler import ErrorHandler
|
32
33
|
from ansible.executor.play_iterator import PlayIterator
|
33
34
|
from ansible.executor.stats import AggregateStats
|
34
|
-
from ansible.executor.task_result import
|
35
|
+
from ansible.executor.task_result import _RawTaskResult, _WireTaskResult
|
35
36
|
from ansible.inventory.data import InventoryData
|
36
37
|
from ansible.module_utils.six import string_types
|
37
38
|
from ansible.module_utils.common.text.converters import to_native
|
@@ -47,7 +48,8 @@ from ansible.utils.display import Display
|
|
47
48
|
from ansible.utils.lock import lock_decorator
|
48
49
|
from ansible.utils.multiprocessing import context as multiprocessing_context
|
49
50
|
|
50
|
-
|
51
|
+
if t.TYPE_CHECKING:
|
52
|
+
from ansible.executor.process.worker import WorkerProcess
|
51
53
|
|
52
54
|
__all__ = ['TaskQueueManager']
|
53
55
|
|
@@ -57,12 +59,13 @@ STDERR_FILENO = 2
|
|
57
59
|
|
58
60
|
display = Display()
|
59
61
|
|
62
|
+
_T = t.TypeVar('_T')
|
60
63
|
|
64
|
+
|
65
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
61
66
|
class CallbackSend:
|
62
|
-
|
63
|
-
|
64
|
-
self.args = args
|
65
|
-
self.kwargs = kwargs
|
67
|
+
method_name: str
|
68
|
+
wire_task_result: _WireTaskResult
|
66
69
|
|
67
70
|
|
68
71
|
class DisplaySend:
|
@@ -72,7 +75,7 @@ class DisplaySend:
|
|
72
75
|
self.kwargs = kwargs
|
73
76
|
|
74
77
|
|
75
|
-
@dataclass
|
78
|
+
@dataclasses.dataclass
|
76
79
|
class PromptSend:
|
77
80
|
worker_id: int
|
78
81
|
prompt: str
|
@@ -87,19 +90,11 @@ class FinalQueue(multiprocessing.queues.SimpleQueue):
|
|
87
90
|
kwargs['ctx'] = multiprocessing_context
|
88
91
|
super().__init__(*args, **kwargs)
|
89
92
|
|
90
|
-
def send_callback(self, method_name
|
91
|
-
self.put(
|
92
|
-
CallbackSend(method_name, *args, **kwargs),
|
93
|
-
)
|
93
|
+
def send_callback(self, method_name: str, task_result: _RawTaskResult) -> None:
|
94
|
+
self.put(CallbackSend(method_name=method_name, wire_task_result=task_result.as_wire_task_result()))
|
94
95
|
|
95
|
-
def send_task_result(self,
|
96
|
-
|
97
|
-
tr = args[0]
|
98
|
-
else:
|
99
|
-
tr = TaskResult(*args, **kwargs)
|
100
|
-
self.put(
|
101
|
-
tr,
|
102
|
-
)
|
96
|
+
def send_task_result(self, task_result: _RawTaskResult) -> None:
|
97
|
+
self.put(task_result.as_wire_task_result())
|
103
98
|
|
104
99
|
def send_display(self, method, *args, **kwargs):
|
105
100
|
self.put(
|
@@ -194,11 +189,8 @@ class TaskQueueManager:
|
|
194
189
|
# plugins for inter-process locking.
|
195
190
|
self._connection_lockfile = tempfile.TemporaryFile()
|
196
191
|
|
197
|
-
def _initialize_processes(self, num):
|
198
|
-
self._workers = []
|
199
|
-
|
200
|
-
for i in range(num):
|
201
|
-
self._workers.append(None)
|
192
|
+
def _initialize_processes(self, num: int) -> None:
|
193
|
+
self._workers: list[WorkerProcess | None] = [None] * num
|
202
194
|
|
203
195
|
def load_callbacks(self):
|
204
196
|
"""
|
@@ -438,54 +430,72 @@ class TaskQueueManager:
|
|
438
430
|
defunct = True
|
439
431
|
return defunct
|
440
432
|
|
433
|
+
@staticmethod
|
434
|
+
def _first_arg_of_type(value_type: t.Type[_T], args: t.Sequence) -> _T | None:
|
435
|
+
return next((arg for arg in args if isinstance(arg, value_type)), None)
|
436
|
+
|
441
437
|
@lock_decorator(attr='_callback_lock')
|
442
438
|
def send_callback(self, method_name, *args, **kwargs):
|
443
439
|
# We always send events to stdout callback first, rest should follow config order
|
444
440
|
for callback_plugin in [self._stdout_callback] + self._callback_plugins:
|
445
441
|
# a plugin that set self.disabled to True will not be called
|
446
442
|
# see osx_say.py example for such a plugin
|
447
|
-
if
|
443
|
+
if callback_plugin.disabled:
|
448
444
|
continue
|
449
445
|
|
450
446
|
# a plugin can opt in to implicit tasks (such as meta). It does this
|
451
447
|
# by declaring self.wants_implicit_tasks = True.
|
452
|
-
wants_implicit_tasks
|
448
|
+
if not callback_plugin.wants_implicit_tasks and (task_arg := self._first_arg_of_type(Task, args)) and task_arg.implicit:
|
449
|
+
continue
|
453
450
|
|
454
451
|
# try to find v2 method, fallback to v1 method, ignore callback if no method found
|
455
452
|
methods = []
|
453
|
+
|
456
454
|
for possible in [method_name, 'v2_on_any']:
|
457
|
-
|
458
|
-
if gotit is None:
|
459
|
-
gotit = getattr(callback_plugin, possible.removeprefix('v2_'), None)
|
460
|
-
if gotit is not None:
|
461
|
-
methods.append(gotit)
|
462
|
-
|
463
|
-
# send clean copies
|
464
|
-
new_args = []
|
465
|
-
|
466
|
-
# If we end up being given an implicit task, we'll set this flag in
|
467
|
-
# the loop below. If the plugin doesn't care about those, then we
|
468
|
-
# check and continue to the next iteration of the outer loop.
|
469
|
-
is_implicit_task = False
|
470
|
-
|
471
|
-
for arg in args:
|
472
|
-
# FIXME: add play/task cleaners
|
473
|
-
if isinstance(arg, TaskResult):
|
474
|
-
new_args.append(arg.clean_copy())
|
475
|
-
# elif isinstance(arg, Play):
|
476
|
-
# elif isinstance(arg, Task):
|
477
|
-
else:
|
478
|
-
new_args.append(arg)
|
455
|
+
method = getattr(callback_plugin, possible, None)
|
479
456
|
|
480
|
-
if
|
481
|
-
|
457
|
+
if method is None:
|
458
|
+
method = getattr(callback_plugin, possible.removeprefix('v2_'), None)
|
482
459
|
|
483
|
-
|
484
|
-
|
460
|
+
if method is not None:
|
461
|
+
display.deprecated(
|
462
|
+
msg='The v1 callback API is deprecated.',
|
463
|
+
version='2.23',
|
464
|
+
help_text='Use `v2_` prefixed callback methods instead.',
|
465
|
+
)
|
466
|
+
|
467
|
+
if method is not None and not getattr(method, '_base_impl', False): # don't bother dispatching to the base impls
|
468
|
+
if possible == 'v2_on_any':
|
469
|
+
display.deprecated(
|
470
|
+
msg='The `v2_on_any` callback method is deprecated.',
|
471
|
+
version='2.23',
|
472
|
+
help_text='Use event-specific callback methods instead.',
|
473
|
+
)
|
474
|
+
|
475
|
+
methods.append(method)
|
485
476
|
|
486
477
|
for method in methods:
|
478
|
+
# send clean copies
|
479
|
+
new_args = []
|
480
|
+
|
481
|
+
for arg in args:
|
482
|
+
# FIXME: add play/task cleaners
|
483
|
+
if isinstance(arg, _RawTaskResult):
|
484
|
+
copied_tr = arg.as_callback_task_result()
|
485
|
+
new_args.append(copied_tr)
|
486
|
+
# this state hack requires that no callback ever accepts > 1 TaskResult object
|
487
|
+
callback_plugin._current_task_result = copied_tr
|
488
|
+
else:
|
489
|
+
new_args.append(arg)
|
490
|
+
|
487
491
|
with self._callback_dispatch_error_handler.handle(AnsibleCallbackError):
|
488
492
|
try:
|
489
493
|
method(*new_args, **kwargs)
|
494
|
+
except AssertionError:
|
495
|
+
# Using an `assert` in integration tests is useful.
|
496
|
+
# Production code should never use `assert` or raise `AssertionError`.
|
497
|
+
raise
|
490
498
|
except Exception as ex:
|
491
499
|
raise AnsibleCallbackError(f"Callback dispatch {method_name!r} failed for plugin {callback_plugin._load_name!r}.") from ex
|
500
|
+
|
501
|
+
callback_plugin._current_task_result = None
|