ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b5__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.
Files changed (163) hide show
  1. ansible/_internal/__init__.py +1 -1
  2. ansible/_internal/_collection_proxy.py +1 -1
  3. ansible/_internal/_errors/_alarm_timeout.py +66 -0
  4. ansible/_internal/_errors/_captured.py +25 -30
  5. ansible/_internal/_errors/_error_factory.py +89 -0
  6. ansible/_internal/_errors/_error_utils.py +240 -0
  7. ansible/_internal/_errors/_task_timeout.py +28 -0
  8. ansible/_internal/_event_formatting.py +127 -0
  9. ansible/_internal/_json/__init__.py +5 -5
  10. ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
  11. ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
  12. ansible/_internal/_json/_profiles/_legacy.py +3 -11
  13. ansible/_internal/_ssh/__init__.py +0 -0
  14. ansible/_internal/_ssh/_agent_launch.py +91 -0
  15. ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
  16. ansible/_internal/_templating/__init__.py +5 -3
  17. ansible/_internal/_templating/_datatag.py +2 -1
  18. ansible/_internal/_templating/_engine.py +3 -4
  19. ansible/_internal/_templating/_jinja_bits.py +21 -16
  20. ansible/_internal/_templating/_jinja_common.py +18 -27
  21. ansible/_internal/_templating/_jinja_plugins.py +31 -3
  22. ansible/_internal/_templating/_lazy_containers.py +5 -5
  23. ansible/_internal/_templating/_transform.py +20 -19
  24. ansible/_internal/_templating/_utils.py +1 -1
  25. ansible/_internal/_yaml/_dumper.py +1 -1
  26. ansible/_internal/_yaml/_errors.py +7 -7
  27. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
  28. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
  29. ansible/cli/__init__.py +5 -82
  30. ansible/cli/arguments/option_helpers.py +2 -3
  31. ansible/cli/doc.py +84 -28
  32. ansible/cli/inventory.py +1 -1
  33. ansible/compat/importlib_resources.py +9 -12
  34. ansible/config/base.yml +22 -0
  35. ansible/errors/__init__.py +96 -49
  36. ansible/executor/module_common.py +8 -10
  37. ansible/executor/powershell/async_watchdog.ps1 +2 -2
  38. ansible/executor/powershell/async_wrapper.ps1 +3 -3
  39. ansible/executor/powershell/become_wrapper.ps1 +20 -2
  40. ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
  41. ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
  42. ansible/executor/powershell/exec_wrapper.ps1 +219 -6
  43. ansible/executor/powershell/module_manifest.py +52 -0
  44. ansible/executor/powershell/module_wrapper.ps1 +47 -21
  45. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  46. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  47. ansible/executor/process/worker.py +38 -113
  48. ansible/executor/task_executor.py +26 -61
  49. ansible/executor/task_result.py +2 -4
  50. ansible/galaxy/collection/__init__.py +1 -4
  51. ansible/inventory/manager.py +1 -1
  52. ansible/module_utils/_internal/__init__.py +0 -3
  53. ansible/module_utils/_internal/_ambient_context.py +3 -3
  54. ansible/module_utils/_internal/_ansiballz.py +4 -2
  55. ansible/module_utils/_internal/_datatag/__init__.py +20 -14
  56. ansible/module_utils/_internal/_datatag/_tags.py +2 -2
  57. ansible/module_utils/_internal/_deprecator.py +66 -48
  58. ansible/module_utils/_internal/_errors.py +88 -17
  59. ansible/module_utils/_internal/_event_utils.py +61 -0
  60. ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
  61. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
  62. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
  63. ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
  64. ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
  65. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
  66. ansible/module_utils/_internal/_plugin_info.py +1 -1
  67. ansible/module_utils/_internal/_stack.py +22 -0
  68. ansible/module_utils/_internal/_text_utils.py +6 -0
  69. ansible/module_utils/_internal/_traceback.py +11 -8
  70. ansible/module_utils/ansible_release.py +1 -1
  71. ansible/module_utils/basic.py +49 -15
  72. ansible/module_utils/common/arg_spec.py +2 -2
  73. ansible/module_utils/common/collections.py +6 -0
  74. ansible/module_utils/common/json.py +2 -2
  75. ansible/module_utils/common/text/converters.py +3 -3
  76. ansible/module_utils/common/validation.py +1 -1
  77. ansible/module_utils/common/warnings.py +80 -23
  78. ansible/module_utils/common/yaml.py +1 -1
  79. ansible/module_utils/datatag.py +5 -2
  80. ansible/module_utils/facts/system/distribution.py +16 -3
  81. ansible/module_utils/facts/virtual/linux.py +1 -1
  82. ansible/module_utils/service.py +2 -9
  83. ansible/modules/apt_repository.py +7 -29
  84. ansible/modules/async_status.py +13 -11
  85. ansible/modules/async_wrapper.py +5 -5
  86. ansible/modules/dnf5.py +14 -22
  87. ansible/modules/hostname.py +0 -1
  88. ansible/modules/service.py +3 -9
  89. ansible/parsing/ajson.py +3 -5
  90. ansible/parsing/dataloader.py +4 -4
  91. ansible/parsing/mod_args.py +1 -1
  92. ansible/parsing/plugin_docs.py +2 -2
  93. ansible/parsing/utils/yaml.py +3 -3
  94. ansible/parsing/vault/__init__.py +4 -4
  95. ansible/playbook/playbook_include.py +1 -1
  96. ansible/playbook/taggable.py +0 -3
  97. ansible/plugins/__init__.py +0 -25
  98. ansible/plugins/action/__init__.py +8 -31
  99. ansible/plugins/action/add_host.py +1 -1
  100. ansible/plugins/action/assemble.py +8 -16
  101. ansible/plugins/action/async_status.py +7 -2
  102. ansible/plugins/action/copy.py +8 -7
  103. ansible/plugins/action/gather_facts.py +8 -8
  104. ansible/plugins/action/package.py +5 -8
  105. ansible/plugins/action/script.py +8 -15
  106. ansible/plugins/action/service.py +3 -7
  107. ansible/plugins/action/template.py +3 -8
  108. ansible/plugins/action/unarchive.py +5 -15
  109. ansible/plugins/action/uri.py +9 -20
  110. ansible/plugins/callback/__init__.py +4 -6
  111. ansible/plugins/callback/junit.py +4 -2
  112. ansible/plugins/connection/local.py +2 -2
  113. ansible/plugins/connection/ssh.py +17 -9
  114. ansible/plugins/connection/winrm.py +5 -2
  115. ansible/plugins/doc_fragments/constructed.py +2 -2
  116. ansible/plugins/filter/core.py +13 -6
  117. ansible/plugins/filter/encryption.py +4 -4
  118. ansible/plugins/inventory/__init__.py +11 -10
  119. ansible/plugins/inventory/script.py +1 -1
  120. ansible/plugins/list.py +69 -16
  121. ansible/plugins/loader.py +7 -7
  122. ansible/plugins/lookup/csvfile.py +16 -71
  123. ansible/plugins/lookup/first_found.py +2 -1
  124. ansible/plugins/shell/__init__.py +56 -2
  125. ansible/plugins/shell/powershell.py +66 -9
  126. ansible/plugins/shell/sh.py +9 -5
  127. ansible/plugins/test/core.py +21 -15
  128. ansible/plugins/test/finished.yml +1 -1
  129. ansible/plugins/test/uri.py +2 -5
  130. ansible/release.py +1 -1
  131. ansible/template/__init__.py +30 -2
  132. ansible/utils/display.py +103 -128
  133. ansible/utils/hashing.py +0 -1
  134. ansible/utils/listify.py +6 -4
  135. ansible/utils/unsafe_proxy.py +1 -1
  136. ansible/vars/hostvars.py +1 -1
  137. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +1 -1
  138. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +162 -151
  139. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
  140. ansible_test/_data/completion/docker.txt +3 -3
  141. ansible_test/_data/completion/remote.txt +1 -0
  142. ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
  143. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  144. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  145. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  146. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  147. ansible_test/_internal/util.py +20 -0
  148. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
  149. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
  150. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
  151. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  152. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  153. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
  154. ansible_test/_util/target/setup/bootstrap.sh +31 -0
  155. ansible/_internal/_errors/_utils.py +0 -310
  156. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
  157. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/COPYING +0 -0
  158. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  159. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  160. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  161. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  162. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  163. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
@@ -25,29 +25,25 @@ import textwrap
25
25
  import traceback
26
26
  import types
27
27
  import typing as t
28
+
28
29
  from multiprocessing.queues import Queue
29
30
 
30
- from ansible import context
31
31
  from ansible._internal import _task
32
- from ansible.errors import AnsibleConnectionFailure, AnsibleError
32
+ from ansible._internal._errors import _error_utils
33
+ from ansible.errors import AnsibleError
33
34
  from ansible.executor.task_executor import TaskExecutor
34
35
  from ansible.executor.task_queue_manager import FinalQueue, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO
35
36
  from ansible.executor.task_result import _RawTaskResult
36
37
  from ansible.inventory.host import Host
37
- from ansible.module_utils.common.collections import is_sequence
38
38
  from ansible.module_utils.common.text.converters import to_text
39
39
  from ansible.parsing.dataloader import DataLoader
40
40
  from ansible.playbook.task import Task
41
41
  from ansible.playbook.play_context import PlayContext
42
- from ansible.plugins.loader import init_plugin_loader
43
42
  from ansible.utils.context_objects import CLIArgs
44
- from ansible.plugins.action import ActionBase
45
43
  from ansible.utils.display import Display
46
44
  from ansible.utils.multiprocessing import context as multiprocessing_context
47
45
  from ansible.vars.manager import VariableManager
48
46
 
49
- from jinja2.exceptions import TemplateNotFound
50
-
51
47
  __all__ = ['WorkerProcess']
52
48
 
53
49
  display = Display()
@@ -204,120 +200,49 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
204
200
  signify that they are ready for their next task.
205
201
  """
206
202
 
207
- # import cProfile, pstats, StringIO
208
- # pr = cProfile.Profile()
209
- # pr.enable()
210
-
211
203
  global current_worker
204
+
212
205
  current_worker = self
213
206
 
214
- if multiprocessing_context.get_start_method() != 'fork':
215
- # This branch is unused currently, as we hardcode fork
216
- # TODO
217
- # * move into a setup func run in `run`, before `_detach`
218
- # * playbook relative content
219
- # * display verbosity
220
- # * ???
221
- context.CLIARGS = self._cliargs
222
- # Initialize plugin loader after parse, so that the init code can utilize parsed arguments
223
- cli_collections_path = context.CLIARGS.get('collections_path') or []
224
- if not is_sequence(cli_collections_path):
225
- # In some contexts ``collections_path`` is singular
226
- cli_collections_path = [cli_collections_path]
227
- init_plugin_loader(cli_collections_path)
207
+ executor_result = TaskExecutor(
208
+ self._host,
209
+ self._task,
210
+ self._task_vars,
211
+ self._play_context,
212
+ self._loader,
213
+ self._shared_loader_obj,
214
+ self._final_q,
215
+ self._variable_manager,
216
+ ).run()
217
+
218
+ self._host.vars = dict()
219
+ self._host.groups = []
220
+
221
+ for name, stdio in (('stdout', sys.stdout), ('stderr', sys.stderr)):
222
+ if data := stdio.getvalue(): # type: ignore[union-attr]
223
+ display.warning(
224
+ (
225
+ f'WorkerProcess for [{self._host}/{self._task}] errantly sent data directly to {name} instead of using Display:\n'
226
+ f'{textwrap.indent(data[:256], " ")}\n'
227
+ ),
228
+ formatted=True
229
+ )
228
230
 
229
231
  try:
230
- # execute the task and build a _RawTaskResult from the result
231
- display.debug("running TaskExecutor() for %s/%s" % (self._host, self._task))
232
- executor_result = TaskExecutor(
233
- self._host,
234
- self._task,
235
- self._task_vars,
236
- self._play_context,
237
- self._loader,
238
- self._shared_loader_obj,
239
- self._final_q,
240
- self._variable_manager,
241
- ).run()
242
-
243
- display.debug("done running TaskExecutor() for %s/%s [%s]" % (self._host, self._task, self._task._uuid))
244
- self._host.vars = dict()
245
- self._host.groups = []
246
-
247
- for name, stdio in (('stdout', sys.stdout), ('stderr', sys.stderr)):
248
- if data := stdio.getvalue(): # type: ignore[union-attr]
249
- display.warning(
250
- (
251
- f'WorkerProcess for [{self._host}/{self._task}] errantly sent data directly to {name} instead of using Display:\n'
252
- f'{textwrap.indent(data[:256], " ")}\n'
253
- ),
254
- formatted=True
255
- )
256
-
257
- # put the result on the result queue
258
- display.debug("sending task result for task %s" % self._task._uuid)
259
- try:
260
- self._final_q.send_task_result(_RawTaskResult(
261
- host=self._host,
262
- task=self._task,
263
- return_data=executor_result,
264
- task_fields=self._task.dump_attrs(),
265
- ))
266
- except Exception as ex:
267
- try:
268
- raise AnsibleError("Task result omitted due to queue send failure.") from ex
269
- except Exception as ex_wrapper:
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
- ))
276
-
277
- display.debug("done sending task result for task %s" % self._task._uuid)
278
-
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
-
284
- self._host.vars = dict()
285
- self._host.groups = []
286
232
  self._final_q.send_task_result(_RawTaskResult(
287
233
  host=self._host,
288
234
  task=self._task,
289
- return_data=return_data,
235
+ return_data=executor_result,
290
236
  task_fields=self._task.dump_attrs(),
291
237
  ))
292
-
293
238
  except Exception as ex:
294
- if not isinstance(ex, (IOError, EOFError, KeyboardInterrupt, SystemExit)) or isinstance(ex, TemplateNotFound):
295
- try:
296
- self._host.vars = dict()
297
- self._host.groups = []
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),
302
- task_fields=self._task.dump_attrs(),
303
- ))
304
- except Exception:
305
- display.debug(u"WORKER EXCEPTION: %s" % to_text(ex))
306
- display.debug(u"WORKER TRACEBACK: %s" % to_text(traceback.format_exc()))
307
- finally:
308
- self._clean_up()
309
-
310
- display.debug("WORKER PROCESS EXITING")
311
-
312
- # pr.disable()
313
- # s = StringIO.StringIO()
314
- # sortby = 'time'
315
- # ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
316
- # ps.print_stats()
317
- # with open('worker_%06d.stats' % os.getpid(), 'w') as f:
318
- # f.write(s.getvalue())
319
-
320
- def _clean_up(self) -> None:
321
- # NOTE: see note in init about forks
322
- # ensure we cleanup all temp files for this worker
323
- self._loader.cleanup_all_tmp_files()
239
+ try:
240
+ raise AnsibleError("Task result omitted due to queue send failure.") from ex
241
+ except Exception as ex_wrapper:
242
+ self._final_q.send_task_result(_RawTaskResult(
243
+ host=self._host,
244
+ task=self._task,
245
+ # ignore the real task result and don't allow result object contribution from the exception (in case the pickling error was related)
246
+ return_data=_error_utils.result_dict_from_exception(ex_wrapper),
247
+ task_fields={}, # The failure pickling may have been caused by the task attrs, omit for safety
248
+ ))
@@ -7,7 +7,6 @@ import os
7
7
  import time
8
8
  import json
9
9
  import pathlib
10
- import signal
11
10
  import subprocess
12
11
  import sys
13
12
 
@@ -17,13 +16,13 @@ import typing as t
17
16
  from ansible import constants as C
18
17
  from ansible.cli import scripts
19
18
  from ansible.errors import (
20
- AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleConnectionFailure, AnsibleActionFail, AnsibleActionSkip, AnsibleTaskError,
19
+ AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleTaskError,
21
20
  AnsibleValueOmittedError,
22
21
  )
23
22
  from ansible.executor.task_result import _RawTaskResult
24
23
  from ansible._internal._datatag import _utils
25
- from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary, PluginInfo
26
- from ansible.module_utils.datatag import native_type_name
24
+ from ansible.module_utils._internal import _messages
25
+ from ansible.module_utils.datatag import native_type_name, deprecator_from_collection_name
27
26
  from ansible._internal._datatag._tags import TrustedAsTemplate
28
27
  from ansible.module_utils.parsing.convert_bool import boolean
29
28
  from ansible.module_utils.common.text.converters import to_text, to_native
@@ -31,7 +30,6 @@ from ansible.module_utils.connection import write_to_stream
31
30
  from ansible.module_utils.six import string_types
32
31
  from ansible.playbook.task import Task
33
32
  from ansible.plugins import get_plugin_class
34
- from ansible.plugins.action import ActionBase
35
33
  from ansible.plugins.loader import become_loader, cliconf_loader, connection_loader, httpapi_loader, netconf_loader, terminal_loader
36
34
  from ansible._internal._templating._jinja_plugins import _invoke_lookup, _DirectCall
37
35
  from ansible._internal._templating._engine import TemplateEngine
@@ -41,7 +39,7 @@ from ansible.utils.display import Display, _DeferredWarningContext
41
39
  from ansible.utils.vars import combine_vars
42
40
  from ansible.vars.clean import namespace_facts, clean_facts
43
41
  from ansible.vars.manager import _deprecate_top_level_fact
44
- from ansible._internal._errors import _captured
42
+ from ansible._internal._errors import _captured, _task_timeout, _error_utils
45
43
 
46
44
  if t.TYPE_CHECKING:
47
45
  from ansible.executor.task_queue_manager import FinalQueue
@@ -54,24 +52,6 @@ RETURN_VARS = [x for x in C.MAGIC_VARIABLE_MAPPING.items() if 'become' not in x
54
52
  __all__ = ['TaskExecutor']
55
53
 
56
54
 
57
- class TaskTimeoutError(BaseException):
58
- def __init__(self, message="", frame=None):
59
-
60
- if frame is not None:
61
- orig = frame
62
- root = pathlib.Path(__file__).parent
63
- while not pathlib.Path(frame.f_code.co_filename).is_relative_to(root):
64
- frame = frame.f_back
65
-
66
- self.frame = 'Interrupted at %s called from %s' % (orig, frame)
67
-
68
- super(TaskTimeoutError, self).__init__(message)
69
-
70
-
71
- def task_timeout(signum, frame):
72
- raise TaskTimeoutError(frame=frame)
73
-
74
-
75
55
  class TaskExecutor:
76
56
 
77
57
  """
@@ -176,7 +156,7 @@ class TaskExecutor:
176
156
 
177
157
  return res
178
158
  except Exception as ex:
179
- result = ActionBase.result_dict_from_exception(ex)
159
+ result = _error_utils.result_dict_from_exception(ex)
180
160
 
181
161
  self._task.update_result_no_log(self._task_templar, result)
182
162
 
@@ -442,11 +422,11 @@ class TaskExecutor:
442
422
  result = self._execute_internal(templar, variables)
443
423
  self._apply_task_result_compat(result, warning_ctx)
444
424
  _captured.AnsibleActionCapturedError.maybe_raise_on_result(result)
445
- except Exception as ex:
425
+ except (Exception, _task_timeout.TaskTimeoutError) as ex: # TaskTimeoutError is BaseException
446
426
  try:
447
427
  raise AnsibleTaskError(obj=self._task.get_ds()) from ex
448
428
  except AnsibleTaskError as atex:
449
- result = ActionBase.result_dict_from_exception(atex)
429
+ result = _error_utils.result_dict_from_exception(atex, accept_result_contribution=True)
450
430
  result.setdefault('changed', False)
451
431
 
452
432
  self._task.update_result_no_log(templar, result)
@@ -530,7 +510,7 @@ class TaskExecutor:
530
510
  # if we ran into an error while setting up the PlayContext, raise it now, unless is known issue with delegation
531
511
  # and undefined vars (correct values are in cvars later on and connection plugins, if still error, blows up there)
532
512
 
533
- # DTFIX-RELEASE: this should probably be declaratively handled in post_validate (or better, get rid of play_context)
513
+ # DTFIX-FUTURE: this should probably be declaratively handled in post_validate (or better, get rid of play_context)
534
514
  if context_validation_error is not None:
535
515
  raiseit = True
536
516
  if self._task.delegate_to:
@@ -539,7 +519,7 @@ class TaskExecutor:
539
519
  if isinstance(context_validation_error.__cause__, AnsibleUndefinedVariable):
540
520
  raiseit = False
541
521
  elif isinstance(context_validation_error, AnsibleUndefinedVariable):
542
- # DTFIX-RELEASE: should not be possible to hit this now (all are AnsibleFieldAttributeError)?
522
+ # DTFIX-FUTURE: should not be possible to hit this now (all are AnsibleFieldAttributeError)?
543
523
  raiseit = False
544
524
  if raiseit:
545
525
  raise context_validation_error # pylint: disable=raising-bad-type
@@ -636,24 +616,9 @@ class TaskExecutor:
636
616
  for attempt in range(1, retries + 1):
637
617
  display.debug("running the handler")
638
618
  try:
639
- if self._task.timeout:
640
- old_sig = signal.signal(signal.SIGALRM, task_timeout)
641
- signal.alarm(self._task.timeout)
642
-
643
- result = self._handler.run(task_vars=vars_copy)
644
-
645
- # DTFIX-RELEASE: nuke this, it hides a lot of error detail- remove the active exception propagation hack from AnsibleActionFail at the same time
646
- except (AnsibleActionFail, AnsibleActionSkip) as e:
647
- return e.result
648
- except AnsibleConnectionFailure as e:
649
- return dict(unreachable=True, msg=to_text(e))
650
- except TaskTimeoutError as e:
651
- msg = 'The %s action failed to execute in the expected time frame (%d) and was terminated' % (self._task.action, self._task.timeout)
652
- return dict(failed=True, msg=msg, timedout={'frame': e.frame, 'period': self._task.timeout})
619
+ with _task_timeout.TaskTimeoutError.alarm_timeout(self._task.timeout):
620
+ result = self._handler.run(task_vars=vars_copy)
653
621
  finally:
654
- if self._task.timeout:
655
- signal.alarm(0)
656
- old_sig = signal.signal(signal.SIGALRM, old_sig)
657
622
  self._handler.cleanup()
658
623
  display.debug("handler run complete")
659
624
 
@@ -731,7 +696,7 @@ class TaskExecutor:
731
696
  if 'skipped' not in result:
732
697
  condname = 'changed'
733
698
 
734
- # DTFIX-RELEASE: error normalization has not yet occurred; this means that the expressions used for until/failed_when/changed_when/break_when
699
+ # DTFIX-FUTURE: error normalization has not yet occurred; this means that the expressions used for until/failed_when/changed_when/break_when
735
700
  # and when (for loops on the second and later iterations) cannot see the normalized error shapes. This, and the current impl of the expression
736
701
  # handling here causes a number of problems:
737
702
  # * any error in one of the post-task exec expressions is silently ignored and detail lost (eg: `failed_when: syntax ERROR @$123`)
@@ -825,11 +790,11 @@ class TaskExecutor:
825
790
  if warnings := result.get('warnings'):
826
791
  if isinstance(warnings, list):
827
792
  for warning in warnings:
828
- if not isinstance(warning, WarningSummary):
793
+ if not isinstance(warning, _messages.WarningSummary):
829
794
  # translate non-WarningMessageDetail messages
830
- warning = WarningSummary(
831
- details=(
832
- Detail(msg=str(warning)),
795
+ warning = _messages.WarningSummary(
796
+ event=_messages.Event(
797
+ msg=str(warning),
833
798
  ),
834
799
  )
835
800
 
@@ -840,18 +805,18 @@ class TaskExecutor:
840
805
  if deprecations := result.get('deprecations'):
841
806
  if isinstance(deprecations, list):
842
807
  for deprecation in deprecations:
843
- if not isinstance(deprecation, DeprecationSummary):
844
- # translate non-DeprecationMessageDetail message dicts
808
+ if not isinstance(deprecation, _messages.DeprecationSummary):
809
+ # translate non-DeprecationSummary message dicts
845
810
  try:
846
811
  if (collection_name := deprecation.pop('collection_name', ...)) is not ...:
847
812
  # deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
848
813
  # CAUTION: This deprecation cannot be enabled until the replacement (deprecator) has been documented, and the schema finalized.
849
814
  # self.deprecated('The `collection_name` key in the `deprecations` dictionary is deprecated.', version='2.27')
850
- deprecation.update(deprecator=PluginInfo._from_collection_name(collection_name))
815
+ deprecation.update(deprecator=deprecator_from_collection_name(collection_name))
851
816
 
852
- deprecation = DeprecationSummary(
853
- details=(
854
- Detail(msg=deprecation.pop('msg')),
817
+ deprecation = _messages.DeprecationSummary(
818
+ event=_messages.Event(
819
+ msg=deprecation.pop('msg'),
855
820
  ),
856
821
  **deprecation,
857
822
  )
@@ -910,9 +875,9 @@ class TaskExecutor:
910
875
  # have issues which result in a half-written/unparseable result
911
876
  # file on disk, which manifests to the user as a timeout happening
912
877
  # before it's time to timeout.
913
- if (int(async_result.get('finished', 0)) == 1 or
914
- ('failed' in async_result and async_result.get('_ansible_parsed', False)) or
915
- 'skipped' in async_result):
878
+ if (async_result.get('finished', False) or
879
+ (async_result.get('failed', False) and async_result.get('_ansible_parsed', False)) or
880
+ async_result.get('skipped', False)):
916
881
  break
917
882
  except Exception as e:
918
883
  # Connections can raise exceptions during polling (eg, network bounce, reboot); these should be non-fatal.
@@ -941,7 +906,7 @@ class TaskExecutor:
941
906
  ),
942
907
  )
943
908
 
944
- if int(async_result.get('finished', 0)) != 1:
909
+ if not async_result.get('finished', False):
945
910
  if async_result.get('_ansible_parsed'):
946
911
  return dict(failed=True, msg="async task did not complete within the requested time - %ss" % self._task.async_val, async_result=async_result)
947
912
  else:
@@ -12,7 +12,7 @@ import typing as t
12
12
  from ansible import constants
13
13
  from ansible.utils import vars as _vars
14
14
  from ansible.vars.clean import module_response_deepcopy, strip_internal_keys
15
- from ansible.module_utils.common import messages as _messages
15
+ from ansible.module_utils._internal import _messages
16
16
  from ansible._internal import _collection_proxy
17
17
 
18
18
  if t.TYPE_CHECKING:
@@ -20,7 +20,7 @@ if t.TYPE_CHECKING:
20
20
  from ansible.playbook.task import Task
21
21
 
22
22
  _IGNORE = ('failed', 'skipped')
23
- _PRESERVE = {'attempts', 'changed', 'retries', '_ansible_no_log'}
23
+ _PRESERVE = {'attempts', 'changed', 'retries', '_ansible_no_log', 'exception', 'warnings', 'deprecations'}
24
24
  _SUB_PRESERVE = {'_ansible_delegated_vars': {'ansible_host', 'ansible_port', 'ansible_user', 'ansible_connection'}}
25
25
 
26
26
  # stuff callbacks need
@@ -230,8 +230,6 @@ class _RawTaskResult(_BaseTaskResult):
230
230
  class CallbackTaskResult(_BaseTaskResult):
231
231
  """Public contract of TaskResult """
232
232
 
233
- # DTFIX-RELEASE: find a better home for this since it's public API
234
-
235
233
  @property
236
234
  def _result(self) -> _c.MutableMapping[str, t.Any]:
237
235
  """Use the `result` property when supporting only ansible-core 2.19 or later."""
@@ -31,6 +31,7 @@ from dataclasses import dataclass
31
31
  from hashlib import sha256
32
32
  from io import BytesIO
33
33
  from importlib.metadata import distribution
34
+ from importlib.resources import files
34
35
  from itertools import chain
35
36
 
36
37
  try:
@@ -85,7 +86,6 @@ if t.TYPE_CHECKING:
85
86
  FilesManifestType = t.Dict[t.Literal['files', 'format'], t.Union[t.List[FileManifestEntryType], int]]
86
87
 
87
88
  import ansible.constants as C
88
- from ansible.compat.importlib_resources import files
89
89
  from ansible.errors import AnsibleError
90
90
  from ansible.galaxy.api import GalaxyAPI
91
91
  from ansible.galaxy.collection.concrete_artifact_manager import (
@@ -1433,9 +1433,6 @@ def find_existing_collections(path_filter, artifacts_manager, namespace_filter=N
1433
1433
  :param path: Collection dirs layout search path.
1434
1434
  :param artifacts_manager: Artifacts manager.
1435
1435
  """
1436
- if files is None:
1437
- raise AnsibleError('importlib_resources is not installed and is required')
1438
-
1439
1436
  if path_filter and not is_sequence(path_filter):
1440
1437
  path_filter = [path_filter]
1441
1438
  if namespace_filter and not is_sequence(namespace_filter):
@@ -313,7 +313,7 @@ class InventoryManager(object):
313
313
  ex.obj = origin
314
314
  failures.append({'src': source, 'plugin': plugin_name, 'exc': ex})
315
315
  except Exception as ex:
316
- # DTFIX-RELEASE: fix this error handling to correctly deal with messaging
316
+ # DTFIX-FUTURE: fix this error handling to correctly deal with messaging
317
317
  try:
318
318
  # omit line number to prevent contextual display of script or possibly sensitive info
319
319
  raise AnsibleError(str(ex), obj=origin) from ex
@@ -5,7 +5,6 @@ import collections.abc as c
5
5
  import typing as t
6
6
 
7
7
 
8
- # DTFIX-RELEASE: bikeshed "intermediate"
9
8
  INTERMEDIATE_MAPPING_TYPES = (c.Mapping,)
10
9
  """
11
10
  Mapping types which are supported for recursion and runtime usage, such as in serialization and templating.
@@ -25,13 +24,11 @@ ITERABLE_SCALARS_NOT_TO_ITERATE_FIXME = (str, bytes)
25
24
 
26
25
  def is_intermediate_mapping(value: object) -> bool:
27
26
  """Returns `True` if `value` is a type supported for projection to a Python `dict`, otherwise returns `False`."""
28
- # DTFIX-RELEASE: bikeshed name
29
27
  return isinstance(value, INTERMEDIATE_MAPPING_TYPES)
30
28
 
31
29
 
32
30
  def is_intermediate_iterable(value: object) -> bool:
33
31
  """Returns `True` if `value` is a type supported for projection to a Python `list`, otherwise returns `False`."""
34
- # DTFIX-RELEASE: bikeshed name
35
32
  return isinstance(value, INTERMEDIATE_ITERABLE_TYPES) and not isinstance(value, ITERABLE_SCALARS_NOT_TO_ITERATE_FIXME)
36
33
 
37
34
 
@@ -19,9 +19,9 @@ class AmbientContextBase:
19
19
  __slots__ = ('_contextvar_token',)
20
20
 
21
21
  # DTFIX-FUTURE: subclasses need to be able to opt-in to blocking nested contexts of the same type (basically optional per-callstack singleton behavior)
22
- # DTFIX-RELEASE: this class should enforce strict nesting of contexts; overlapping context lifetimes leads to incredibly difficult to
22
+ # DTFIX-FUTURE: this class should enforce strict nesting of contexts; overlapping context lifetimes leads to incredibly difficult to
23
23
  # debug situations with undefined behavior, so it should fail fast.
24
- # DTFIX-RELEASE: make frozen=True dataclass subclasses work (fix the mutability of the contextvar instance)
24
+ # DTFIX-FUTURE: make frozen=True dataclass subclasses work (fix the mutability of the contextvar instance)
25
25
 
26
26
  _contextvar: t.ClassVar[contextvars.ContextVar] # pylint: disable=declare-non-slot # pylint bug, see https://github.com/pylint-dev/pylint/issues/9950
27
27
  _contextvar_token: contextvars.Token
@@ -49,7 +49,7 @@ class AmbientContextBase:
49
49
  raise ReferenceError(f"A required {cls.__name__} context is not active.") from None
50
50
 
51
51
  def __enter__(self) -> t.Self:
52
- # DTFIX-RELEASE: actively block multiple entry
52
+ # DTFIX-FUTURE: actively block multiple entry
53
53
  self._contextvar_token = self.__class__._contextvar.set(self)
54
54
  return self
55
55
 
@@ -13,7 +13,7 @@ import runpy
13
13
  import sys
14
14
  import typing as t
15
15
 
16
- from . import _errors
16
+ from . import _errors, _traceback, _messages
17
17
  from .. import basic
18
18
  from ..common.json import get_module_encoder, Direction
19
19
 
@@ -97,7 +97,9 @@ def _handle_exception(exception: BaseException, profile: str) -> t.NoReturn:
97
97
  """Handle the given exception."""
98
98
  result = dict(
99
99
  failed=True,
100
- exception=_errors.create_error_summary(exception),
100
+ exception=_messages.ErrorSummary(
101
+ event=_errors.EventFactory.from_exception(exception, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR)),
102
+ ),
101
103
  )
102
104
 
103
105
  encoder = get_module_encoder(profile, Direction.MODULE_TO_CONTROLLER)
@@ -37,15 +37,16 @@ _ANSIBLE_TAGGED_OBJECT_SLOTS = tuple(('_ansible_tags_mapping',))
37
37
  # shared empty frozenset for default values
38
38
  _empty_frozenset: t.FrozenSet = frozenset()
39
39
 
40
+ # Technical Notes
41
+ #
42
+ # Tagged values compare (and thus hash) the same as their base types, so a value that differs only by its tags will appear identical to non-tag-aware code.
43
+ # This will affect storage and update of tagged values in dictionary keys, sets, etc. While tagged values can be used as keys in hashable collections,
44
+ # updating a key usually requires removal and re-addition.
45
+
40
46
 
41
47
  class AnsibleTagHelper:
42
48
  """Utility methods for working with Ansible data tags."""
43
49
 
44
- # DTFIX-RELEASE: bikeshed the name and location of this class, also, related, how much more of it should be exposed as public API?
45
- # it may make sense to move this into another module, but the implementations should remain here (so they can be used without circular imports here)
46
- # if they're in a separate module, is a class even needed, or should they be globals?
47
- # DTFIX-RELEASE: add docstrings to all non-override methods in this class
48
-
49
50
  @staticmethod
50
51
  def untag(value: _T, *tag_types: t.Type[AnsibleDatatagBase]) -> _T:
51
52
  """
@@ -105,7 +106,7 @@ class AnsibleTagHelper:
105
106
  if issubclass(the_type, AnsibleTaggedObject):
106
107
  the_type = type_or_value._native_type
107
108
 
108
- # DTFIX-RELEASE: provide a way to report the real type for debugging purposes
109
+ # DTFIX-FUTURE: provide a knob to optionally report the real type for debugging purposes
109
110
  return the_type
110
111
 
111
112
  @staticmethod
@@ -339,10 +340,11 @@ class AnsibleSerializableDateTime(AnsibleSerializableWrapper[datetime.datetime])
339
340
  @dataclasses.dataclass(**_tag_dataclass_kwargs)
340
341
  class AnsibleSerializableDataclass(AnsibleSerializable, metaclass=abc.ABCMeta):
341
342
  _validation_allow_subclasses = True
343
+ _validation_auto_enabled = True
342
344
 
343
345
  def _as_dict(self) -> t.Dict[str, t.Any]:
344
346
  # omit None values when None is the field default
345
- # DTFIX-RELEASE: this implementation means we can never change the default on fields which have None for their default
347
+ # DTFIX-FUTURE: this implementation means we can never change the default on fields which have None for their default
346
348
  # other defaults can be changed -- but there's no way to override this behavior either way for other default types
347
349
  # it's a trip hazard to have the default logic here, rather than per field (or not at all)
348
350
  # consider either removing the filtering or requiring it to be explicitly set per field using dataclass metadata
@@ -351,7 +353,7 @@ class AnsibleSerializableDataclass(AnsibleSerializable, metaclass=abc.ABCMeta):
351
353
 
352
354
  @classmethod
353
355
  def _from_dict(cls, d: t.Dict[str, t.Any]) -> t.Self:
354
- # DTFIX-RELEASE: optimize this to avoid the dataclasses fields metadata and get_origin stuff at runtime
356
+ # DTFIX-FUTURE: optimize this to avoid the dataclasses fields metadata and get_origin stuff at runtime
355
357
  type_hints = t.get_type_hints(cls)
356
358
  mutated_dict: dict[str, t.Any] | None = None
357
359
 
@@ -368,7 +370,11 @@ class AnsibleSerializableDataclass(AnsibleSerializable, metaclass=abc.ABCMeta):
368
370
  def __init_subclass__(cls, **kwargs) -> None:
369
371
  super(AnsibleSerializableDataclass, cls).__init_subclass__(**kwargs) # cannot use super() without arguments when using slots
370
372
 
371
- _dataclass_validation.inject_post_init_validation(cls, cls._validation_allow_subclasses) # code gen a real __post_init__ method
373
+ if cls._validation_auto_enabled:
374
+ try:
375
+ _dataclass_validation.inject_post_init_validation(cls, cls._validation_allow_subclasses) # code gen a real __post_init__ method
376
+ except Exception as ex:
377
+ raise Exception(f'Validation code generation failed on {cls}.') from ex
372
378
 
373
379
 
374
380
  class Tripwire:
@@ -524,7 +530,6 @@ class CollectionWithMro(c.Collection, t.Protocol):
524
530
  __mro__: tuple[type, ...]
525
531
 
526
532
 
527
- # DTFIX-RELEASE: This should probably reside elsewhere.
528
533
  def is_non_scalar_collection_type(value: type) -> t.TypeGuard[type[CollectionWithMro]]:
529
534
  """Returns True if the value is a non-scalar collection type, otherwise returns False."""
530
535
  return issubclass(value, c.Collection) and not issubclass(value, str) and not issubclass(value, bytes)
@@ -878,7 +883,6 @@ class _AnsibleTaggedList(list, AnsibleTaggedObject):
878
883
  # Propagation of tags in these cases is left to the caller, based on needs specific to their use case.
879
884
 
880
885
 
881
- # DTFIX-RELEASE: do we want frozenset too?
882
886
  class _AnsibleTaggedSet(set, AnsibleTaggedObject):
883
887
  __slots__ = _ANSIBLE_TAGGED_OBJECT_SLOTS
884
888
 
@@ -914,10 +918,12 @@ class _AnsibleTaggedTuple(tuple, AnsibleTaggedObject):
914
918
  return super()._copy_collection()
915
919
 
916
920
 
917
- # This set gets augmented with additional types when some controller-only types are imported.
918
- # While we could proxy or subclass builtin singletons, they're idiomatically compared with "is" reference
919
- # equality, which we can't customize.
920
921
  _untaggable_types = {type(None), bool}
922
+ """
923
+ Attempts to apply tags to values of these types will be silently ignored.
924
+ While we could proxy or subclass builtin singletons, they're idiomatically compared with "is" reference equality, which we can't customize.
925
+ This set gets augmented with additional types when some controller-only types are imported.
926
+ """
921
927
 
922
928
  # noinspection PyProtectedMember
923
929
  _ANSIBLE_ALLOWED_VAR_TYPES = frozenset({type(None), bool}) | set(AnsibleTaggedObject._tagged_type_map) | set(AnsibleTaggedObject._tagged_type_map.values())
@@ -3,8 +3,7 @@ from __future__ import annotations
3
3
  import dataclasses
4
4
  import typing as t
5
5
 
6
- from ansible.module_utils.common import messages as _messages
7
- from ansible.module_utils._internal import _datatag
6
+ from ansible.module_utils._internal import _datatag, _messages
8
7
 
9
8
 
10
9
  @dataclasses.dataclass(**_datatag._tag_dataclass_kwargs)
@@ -14,3 +13,4 @@ class Deprecated(_datatag.AnsibleDatatagBase):
14
13
  date: t.Optional[str] = None
15
14
  version: t.Optional[str] = None
16
15
  deprecator: t.Optional[_messages.PluginInfo] = None
16
+ formatted_traceback: t.Optional[str] = None