ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b6__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 (225) hide show
  1. ansible/_internal/__init__.py +1 -1
  2. ansible/_internal/_ansiballz/__init__.py +0 -0
  3. ansible/_internal/_ansiballz/_builder.py +101 -0
  4. ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
  5. ansible/_internal/_collection_proxy.py +1 -1
  6. ansible/_internal/_errors/_alarm_timeout.py +66 -0
  7. ansible/_internal/_errors/_captured.py +25 -30
  8. ansible/_internal/_errors/_error_factory.py +89 -0
  9. ansible/_internal/_errors/_error_utils.py +240 -0
  10. ansible/_internal/_errors/_task_timeout.py +28 -0
  11. ansible/_internal/_event_formatting.py +127 -0
  12. ansible/_internal/_json/__init__.py +5 -5
  13. ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
  14. ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
  15. ansible/_internal/_json/_profiles/_legacy.py +3 -11
  16. ansible/_internal/_ssh/__init__.py +0 -0
  17. ansible/_internal/_ssh/_agent_launch.py +91 -0
  18. ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
  19. ansible/_internal/_templating/__init__.py +5 -3
  20. ansible/_internal/_templating/_datatag.py +2 -1
  21. ansible/_internal/_templating/_engine.py +3 -4
  22. ansible/_internal/_templating/_jinja_bits.py +28 -20
  23. ansible/_internal/_templating/_jinja_common.py +18 -27
  24. ansible/_internal/_templating/_jinja_plugins.py +36 -5
  25. ansible/_internal/_templating/_lazy_containers.py +5 -5
  26. ansible/_internal/_templating/_template_vars.py +72 -0
  27. ansible/_internal/_templating/_transform.py +26 -19
  28. ansible/_internal/_templating/_utils.py +1 -1
  29. ansible/_internal/_yaml/_constructor.py +4 -4
  30. ansible/_internal/_yaml/_dumper.py +26 -18
  31. ansible/_internal/_yaml/_errors.py +7 -7
  32. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
  33. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
  34. ansible/cli/__init__.py +11 -93
  35. ansible/cli/arguments/option_helpers.py +3 -4
  36. ansible/cli/console.py +1 -1
  37. ansible/cli/doc.py +86 -30
  38. ansible/cli/inventory.py +5 -7
  39. ansible/compat/importlib_resources.py +9 -12
  40. ansible/config/base.yml +46 -0
  41. ansible/errors/__init__.py +98 -50
  42. ansible/executor/module_common.py +75 -49
  43. ansible/executor/powershell/async_watchdog.ps1 +2 -2
  44. ansible/executor/powershell/async_wrapper.ps1 +3 -3
  45. ansible/executor/powershell/become_wrapper.ps1 +20 -2
  46. ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
  47. ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
  48. ansible/executor/powershell/exec_wrapper.ps1 +219 -6
  49. ansible/executor/powershell/module_manifest.py +52 -0
  50. ansible/executor/powershell/module_wrapper.ps1 +47 -21
  51. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  52. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  53. ansible/executor/process/worker.py +40 -115
  54. ansible/executor/task_executor.py +26 -61
  55. ansible/executor/task_result.py +2 -4
  56. ansible/galaxy/api.py +1 -4
  57. ansible/galaxy/collection/__init__.py +2 -10
  58. ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
  59. ansible/galaxy/role.py +2 -2
  60. ansible/inventory/manager.py +1 -1
  61. ansible/module_utils/_internal/__init__.py +7 -7
  62. ansible/module_utils/_internal/_ambient_context.py +3 -3
  63. ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
  64. ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
  65. ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
  66. ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
  67. ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +13 -39
  68. ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
  69. ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
  70. ansible/module_utils/_internal/_datatag/__init__.py +43 -15
  71. ansible/module_utils/_internal/_datatag/_tags.py +2 -2
  72. ansible/module_utils/_internal/_deprecator.py +67 -55
  73. ansible/module_utils/_internal/_errors.py +88 -17
  74. ansible/module_utils/_internal/_event_utils.py +61 -0
  75. ansible/module_utils/_internal/_json/_profiles/__init__.py +22 -4
  76. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
  77. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
  78. ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
  79. ansible/module_utils/{common/messages.py → _internal/_messages.py} +54 -49
  80. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
  81. ansible/module_utils/_internal/_plugin_info.py +15 -2
  82. ansible/module_utils/_internal/_stack.py +22 -0
  83. ansible/module_utils/_internal/_text_utils.py +6 -0
  84. ansible/module_utils/_internal/_traceback.py +11 -8
  85. ansible/module_utils/ansible_release.py +1 -1
  86. ansible/module_utils/basic.py +95 -71
  87. ansible/module_utils/common/arg_spec.py +2 -2
  88. ansible/module_utils/common/collections.py +6 -0
  89. ansible/module_utils/common/json.py +2 -2
  90. ansible/module_utils/common/respawn.py +4 -41
  91. ansible/module_utils/common/text/converters.py +3 -3
  92. ansible/module_utils/common/validation.py +1 -1
  93. ansible/module_utils/common/warnings.py +80 -23
  94. ansible/module_utils/common/yaml.py +1 -1
  95. ansible/module_utils/connection.py +8 -11
  96. ansible/module_utils/datatag.py +5 -2
  97. ansible/module_utils/facts/hardware/linux.py +1 -1
  98. ansible/module_utils/facts/sysctl.py +4 -6
  99. ansible/module_utils/facts/system/caps.py +2 -2
  100. ansible/module_utils/facts/system/distribution.py +16 -3
  101. ansible/module_utils/facts/system/local.py +1 -1
  102. ansible/module_utils/facts/virtual/linux.py +2 -2
  103. ansible/module_utils/service.py +3 -10
  104. ansible/module_utils/urls.py +4 -4
  105. ansible/modules/apt_repository.py +17 -39
  106. ansible/modules/assemble.py +2 -2
  107. ansible/modules/async_status.py +13 -11
  108. ansible/modules/async_wrapper.py +12 -22
  109. ansible/modules/command.py +3 -3
  110. ansible/modules/copy.py +4 -4
  111. ansible/modules/cron.py +1 -1
  112. ansible/modules/dnf5.py +14 -22
  113. ansible/modules/file.py +16 -17
  114. ansible/modules/find.py +3 -3
  115. ansible/modules/get_url.py +17 -0
  116. ansible/modules/git.py +9 -7
  117. ansible/modules/hostname.py +0 -1
  118. ansible/modules/known_hosts.py +12 -14
  119. ansible/modules/package.py +6 -0
  120. ansible/modules/replace.py +2 -2
  121. ansible/modules/service.py +3 -9
  122. ansible/modules/slurp.py +10 -13
  123. ansible/modules/stat.py +5 -7
  124. ansible/modules/unarchive.py +6 -6
  125. ansible/modules/user.py +1 -1
  126. ansible/modules/wait_for.py +28 -30
  127. ansible/modules/yum_repository.py +4 -3
  128. ansible/parsing/ajson.py +3 -5
  129. ansible/parsing/dataloader.py +6 -6
  130. ansible/parsing/mod_args.py +1 -1
  131. ansible/parsing/plugin_docs.py +2 -2
  132. ansible/parsing/utils/yaml.py +3 -3
  133. ansible/parsing/vault/__init__.py +10 -14
  134. ansible/playbook/base.py +7 -2
  135. ansible/playbook/included_file.py +3 -1
  136. ansible/playbook/play_context.py +2 -0
  137. ansible/playbook/playbook_include.py +1 -1
  138. ansible/playbook/taggable.py +19 -8
  139. ansible/playbook/task.py +2 -0
  140. ansible/plugins/__init__.py +0 -25
  141. ansible/plugins/action/__init__.py +8 -31
  142. ansible/plugins/action/add_host.py +1 -1
  143. ansible/plugins/action/assemble.py +8 -16
  144. ansible/plugins/action/async_status.py +7 -2
  145. ansible/plugins/action/copy.py +8 -7
  146. ansible/plugins/action/fetch.py +3 -3
  147. ansible/plugins/action/gather_facts.py +8 -8
  148. ansible/plugins/action/package.py +5 -8
  149. ansible/plugins/action/script.py +8 -15
  150. ansible/plugins/action/service.py +3 -7
  151. ansible/plugins/action/template.py +11 -10
  152. ansible/plugins/action/unarchive.py +5 -15
  153. ansible/plugins/action/uri.py +9 -20
  154. ansible/plugins/cache/__init__.py +17 -19
  155. ansible/plugins/callback/__init__.py +4 -6
  156. ansible/plugins/callback/junit.py +4 -2
  157. ansible/plugins/callback/tree.py +5 -5
  158. ansible/plugins/connection/local.py +6 -6
  159. ansible/plugins/connection/paramiko_ssh.py +5 -5
  160. ansible/plugins/connection/ssh.py +25 -15
  161. ansible/plugins/connection/winrm.py +6 -3
  162. ansible/plugins/doc_fragments/constructed.py +2 -2
  163. ansible/plugins/filter/core.py +32 -27
  164. ansible/plugins/filter/encryption.py +14 -6
  165. ansible/plugins/inventory/__init__.py +11 -10
  166. ansible/plugins/inventory/script.py +1 -1
  167. ansible/plugins/list.py +73 -19
  168. ansible/plugins/loader.py +7 -7
  169. ansible/plugins/lookup/csvfile.py +16 -71
  170. ansible/plugins/lookup/first_found.py +2 -1
  171. ansible/plugins/lookup/template.py +9 -4
  172. ansible/plugins/shell/__init__.py +56 -2
  173. ansible/plugins/shell/powershell.py +67 -9
  174. ansible/plugins/shell/sh.py +10 -5
  175. ansible/plugins/strategy/__init__.py +3 -3
  176. ansible/plugins/test/core.py +22 -16
  177. ansible/plugins/test/finished.yml +1 -1
  178. ansible/plugins/test/uri.py +2 -5
  179. ansible/release.py +1 -1
  180. ansible/template/__init__.py +38 -54
  181. ansible/utils/collection_loader/_collection_finder.py +3 -3
  182. ansible/utils/display.py +124 -138
  183. ansible/utils/galaxy.py +2 -2
  184. ansible/utils/hashing.py +6 -8
  185. ansible/utils/listify.py +6 -4
  186. ansible/utils/path.py +5 -7
  187. ansible/utils/py3compat.py +2 -1
  188. ansible/utils/ssh_functions.py +3 -2
  189. ansible/utils/unsafe_proxy.py +1 -1
  190. ansible/vars/hostvars.py +1 -1
  191. ansible/vars/plugins.py +3 -3
  192. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
  193. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +224 -204
  194. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/WHEEL +1 -1
  195. ansible_test/_data/completion/docker.txt +3 -3
  196. ansible_test/_data/completion/remote.txt +1 -0
  197. ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
  198. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  199. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  200. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  201. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  202. ansible_test/_internal/commands/integration/coverage.py +7 -2
  203. ansible_test/_internal/host_profiles.py +62 -10
  204. ansible_test/_internal/provisioning.py +10 -4
  205. ansible_test/_internal/ssh.py +1 -5
  206. ansible_test/_internal/thread.py +2 -1
  207. ansible_test/_internal/timeout.py +1 -1
  208. ansible_test/_internal/util.py +40 -12
  209. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
  210. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
  211. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
  212. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  213. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  214. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
  215. ansible_test/_util/target/setup/bootstrap.sh +31 -0
  216. ansible_test/_util/target/setup/requirements.py +3 -9
  217. ansible/_internal/_errors/_utils.py +0 -310
  218. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
  219. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
  220. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  221. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  222. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  223. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  224. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  225. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/top_level.txt +0 -0
@@ -45,7 +45,7 @@ def setup() -> None:
45
45
  """No-op function to ensure that side-effect only imports of this module are not flagged/removed as 'unused'."""
46
46
 
47
47
 
48
- # DTFIX-RELEASE: this is really fragile- disordered/incorrect imports (among other things) can mess it up. Consider a hosting-env-managed context
48
+ # DTFIX-FUTURE: this is really fragile- disordered/incorrect imports (among other things) can mess it up. Consider a hosting-env-managed context
49
49
  # with an enum with at least Controller/Target/Unknown values, and possibly using lazy-init module shims or some other mechanism to allow controller-side
50
50
  # notification/augmentation of this kind of metadata.
51
51
  _internal.get_controller_serialize_map = get_controller_serialize_map
File without changes
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import json
5
+
6
+ import typing as t
7
+
8
+ from ansible.module_utils._internal._ansiballz import _extensions
9
+ from ansible.module_utils._internal._ansiballz._extensions import _pydevd, _coverage
10
+ from ansible.constants import config
11
+
12
+ _T = t.TypeVar('_T')
13
+
14
+
15
+ class ExtensionManager:
16
+ """AnsiballZ extension manager."""
17
+
18
+ def __init__(
19
+ self,
20
+ debugger: _pydevd.Options | None = None,
21
+ coverage: _coverage.Options | None = None,
22
+ ) -> None:
23
+ options = dict(
24
+ _pydevd=debugger,
25
+ _coverage=coverage,
26
+ )
27
+
28
+ self._debugger = debugger
29
+ self._coverage = coverage
30
+ self._extension_names = tuple(name for name, option in options.items() if option)
31
+ self._module_names = tuple(f'{_extensions.__name__}.{name}' for name in self._extension_names)
32
+
33
+ self.source_mapping: dict[str, str] = {}
34
+
35
+ @property
36
+ def debugger_enabled(self) -> bool:
37
+ """Returns True if the debugger extension is enabled, otherwise False."""
38
+ return bool(self._debugger)
39
+
40
+ @property
41
+ def extension_names(self) -> tuple[str, ...]:
42
+ """Names of extensions to include in the AnsiballZ payload."""
43
+ return self._extension_names
44
+
45
+ @property
46
+ def module_names(self) -> tuple[str, ...]:
47
+ """Python module names of extensions to include in the AnsiballZ payload."""
48
+ return self._module_names
49
+
50
+ def get_extensions(self) -> dict[str, dict[str, object]]:
51
+ """Return the configured extensions and their options."""
52
+ extension_options: dict[str, t.Any] = {}
53
+
54
+ if self._debugger:
55
+ extension_options['_pydevd'] = dataclasses.replace(
56
+ self._debugger,
57
+ source_mapping=self._get_source_mapping(),
58
+ )
59
+
60
+ if self._coverage:
61
+ extension_options['_coverage'] = self._coverage
62
+
63
+ extensions = {extension: dataclasses.asdict(options) for extension, options in extension_options.items()}
64
+
65
+ return extensions
66
+
67
+ def _get_source_mapping(self) -> dict[str, str]:
68
+ """Get the source mapping, adjusting the source root as needed."""
69
+ if self._debugger.source_mapping:
70
+ source_mapping = {self._translate_path(key): value for key, value in self.source_mapping.items()}
71
+ else:
72
+ source_mapping = self.source_mapping
73
+
74
+ return source_mapping
75
+
76
+ def _translate_path(self, path: str) -> str:
77
+ """Translate a local path to a foreign path."""
78
+ for replace, match in self._debugger.source_mapping.items():
79
+ if path.startswith(match):
80
+ return replace + path[len(match) :]
81
+
82
+ return path
83
+
84
+ @classmethod
85
+ def create(cls, task_vars: dict[str, object]) -> t.Self:
86
+ """Create an instance using the provided task vars."""
87
+ return cls(
88
+ debugger=cls._get_options('_ANSIBALLZ_DEBUGGER_CONFIG', _pydevd.Options, task_vars),
89
+ coverage=cls._get_options('_ANSIBALLZ_COVERAGE_CONFIG', _coverage.Options, task_vars),
90
+ )
91
+
92
+ @classmethod
93
+ def _get_options(cls, name: str, config_type: type[_T], task_vars: dict[str, object]) -> _T | None:
94
+ """Parse configuration from the named environment variable as the specified type, or None if not configured."""
95
+ if (value := config.get_config_value(name, variables=task_vars)) is None:
96
+ return None
97
+
98
+ data = json.loads(value) if isinstance(value, str) else value
99
+ options = config_type(**data)
100
+
101
+ return options
@@ -37,14 +37,13 @@ _ANSIBALLZ_WRAPPER = True
37
37
 
38
38
 
39
39
  def _ansiballz_main(
40
- zipdata: str,
40
+ zip_data: str,
41
41
  ansible_module: str,
42
42
  module_fqn: str,
43
43
  params: str,
44
44
  profile: str,
45
45
  date_time: datetime.datetime,
46
- coverage_config: str | None,
47
- coverage_output: str | None,
46
+ extensions: dict[str, dict[str, object]],
48
47
  rlimit_nofile: int,
49
48
  ) -> None:
50
49
  import os
@@ -136,15 +135,14 @@ def _ansiballz_main(
136
135
  # can monkeypatch the right basic
137
136
  sys.path.insert(0, modlib_path)
138
137
 
139
- from ansible.module_utils._internal._ansiballz import run_module
138
+ from ansible.module_utils._internal._ansiballz import _loader
140
139
 
141
- run_module(
140
+ _loader.run_module(
142
141
  json_params=json_params,
143
142
  profile=profile,
144
143
  module_fqn=module_fqn,
145
144
  modlib_path=modlib_path,
146
- coverage_config=coverage_config,
147
- coverage_output=coverage_output,
145
+ extensions=extensions,
148
146
  )
149
147
 
150
148
  def debug(command: str, modlib_path: str, json_params: bytes) -> None:
@@ -223,13 +221,14 @@ def _ansiballz_main(
223
221
  with open(args_path, 'rb') as reader:
224
222
  json_params = reader.read()
225
223
 
226
- from ansible.module_utils._internal._ansiballz import run_module
224
+ from ansible.module_utils._internal._ansiballz import _loader
227
225
 
228
- run_module(
226
+ _loader.run_module(
229
227
  json_params=json_params,
230
228
  profile=profile,
231
229
  module_fqn=module_fqn,
232
230
  modlib_path=modlib_path,
231
+ extensions=extensions,
233
232
  )
234
233
 
235
234
  else:
@@ -246,13 +245,14 @@ def _ansiballz_main(
246
245
  # store this in remote_tmpdir (use system tempdir instead)
247
246
  # Only need to use [ansible_module]_payload_ in the temp_path until we move to zipimport
248
247
  # (this helps ansible-test produce coverage stats)
249
- temp_path = tempfile.mkdtemp(prefix='ansible_' + ansible_module + '_payload_')
248
+ # IMPORTANT: The real path must be used here to ensure a remote debugger such as PyCharm (using pydevd) can resolve paths correctly.
249
+ temp_path = os.path.realpath(tempfile.mkdtemp(prefix='ansible_' + ansible_module + '_payload_'))
250
250
 
251
251
  try:
252
252
  zipped_mod = os.path.join(temp_path, 'ansible_' + ansible_module + '_payload.zip')
253
253
 
254
254
  with open(zipped_mod, 'wb') as modlib:
255
- modlib.write(base64.b64decode(zipdata))
255
+ modlib.write(base64.b64decode(zip_data))
256
256
 
257
257
  if len(sys.argv) == 2:
258
258
  debug(sys.argv[1], zipped_mod, encoded_params)
@@ -9,7 +9,7 @@ _T_co = _t.TypeVar('_T_co', covariant=True)
9
9
  class SequenceProxy(_c.Sequence[_T_co]):
10
10
  """A read-only sequence proxy."""
11
11
 
12
- # DTFIX-RELEASE: needs unit test coverage
12
+ # DTFIX5: needs unit test coverage
13
13
 
14
14
  __slots__ = ('__value',)
15
15
 
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ import signal
5
+ import types
6
+ import typing as _t
7
+
8
+ from ansible.module_utils import datatag
9
+
10
+
11
+ class AnsibleTimeoutError(BaseException):
12
+ """A general purpose timeout."""
13
+
14
+ _MAX_TIMEOUT = 100_000_000
15
+ """
16
+ The maximum supported timeout value.
17
+ This value comes from BSD's alarm limit, which is due to that function using setitimer.
18
+ """
19
+
20
+ def __init__(self, timeout: int) -> None:
21
+ self.timeout = timeout
22
+
23
+ super().__init__(f"Timed out after {timeout} second(s).")
24
+
25
+ @classmethod
26
+ @contextlib.contextmanager
27
+ def alarm_timeout(cls, timeout: int | None) -> _t.Iterator[None]:
28
+ """
29
+ Context for running code under an optional timeout.
30
+ Raises an instance of this class if the timeout occurs.
31
+
32
+ New usages of this timeout mechanism are discouraged.
33
+ """
34
+ if timeout is not None:
35
+ if not isinstance(timeout, int):
36
+ raise TypeError(f"Timeout requires 'int' argument, not {datatag.native_type_name(timeout)!r}.")
37
+
38
+ if timeout < 0 or timeout > cls._MAX_TIMEOUT:
39
+ # On BSD based systems, alarm is implemented using setitimer.
40
+ # If out-of-bounds values are passed to alarm, they will return -1, which would be interpreted as an existing timer being set.
41
+ # To avoid that, bounds checking is performed in advance.
42
+ raise ValueError(f'Timeout {timeout} is invalid, it must be between 0 and {cls._MAX_TIMEOUT}.')
43
+
44
+ if not timeout:
45
+ yield # execute the context manager's body
46
+ return # no timeout to deal with, exit immediately
47
+
48
+ def on_alarm(_signal: int, _frame: types.FrameType) -> None:
49
+ raise cls(timeout)
50
+
51
+ if signal.signal(signal.SIGALRM, on_alarm):
52
+ raise RuntimeError("An existing alarm handler was present.")
53
+
54
+ try:
55
+ try:
56
+ if signal.alarm(timeout):
57
+ raise RuntimeError("An existing alarm was set.")
58
+
59
+ yield # execute the context manager's body
60
+ finally:
61
+ # Disable the alarm.
62
+ # If the alarm fires inside this finally block, the alarm is still disabled.
63
+ # This guarantees the cleanup code in the outer finally block runs without risk of encountering the `TaskTimeoutError` from the alarm.
64
+ signal.alarm(0)
65
+ finally:
66
+ signal.signal(signal.SIGALRM, signal.SIG_DFL)
@@ -1,10 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import collections.abc as _c
3
4
  import dataclasses
4
5
  import typing as t
5
6
 
7
+ from ansible._internal._errors import _error_utils
6
8
  from ansible.errors import AnsibleRuntimeError
7
- from ansible.module_utils.common.messages import ErrorSummary, Detail, _dataclass_kwargs
9
+ from ansible.module_utils._internal import _messages
8
10
 
9
11
 
10
12
  class AnsibleCapturedError(AnsibleRuntimeError):
@@ -16,43 +18,36 @@ class AnsibleCapturedError(AnsibleRuntimeError):
16
18
  self,
17
19
  *,
18
20
  obj: t.Any = None,
19
- error_summary: ErrorSummary,
21
+ event: _messages.Event,
20
22
  ) -> None:
21
23
  super().__init__(
22
24
  obj=obj,
23
25
  )
24
26
 
25
- self._error_summary = error_summary
27
+ self._event = event
26
28
 
27
- @property
28
- def error_summary(self) -> ErrorSummary:
29
- return self._error_summary
30
29
 
30
+ class AnsibleResultCapturedError(AnsibleCapturedError, _error_utils.ContributesToTaskResult):
31
+ """
32
+ An exception representing error detail captured in a foreign context where an action/module result dictionary is involved.
31
33
 
32
- class AnsibleResultCapturedError(AnsibleCapturedError):
33
- """An exception representing error detail captured in a foreign context where an action/module result dictionary is involved."""
34
+ This exception provides a result dictionary via the ContributesToTaskResult mixin.
35
+ """
34
36
 
35
- def __init__(self, error_summary: ErrorSummary, result: dict[str, t.Any]) -> None:
36
- super().__init__(error_summary=error_summary)
37
+ def __init__(self, event: _messages.Event, result: dict[str, t.Any]) -> None:
38
+ super().__init__(event=event)
37
39
 
38
40
  self._result = result
39
41
 
42
+ @property
43
+ def result_contribution(self) -> _c.Mapping[str, object]:
44
+ return self._result
45
+
40
46
  @classmethod
41
47
  def maybe_raise_on_result(cls, result: dict[str, t.Any]) -> None:
42
48
  """Normalize the result and raise an exception if the result indicated failure."""
43
49
  if error_summary := cls.normalize_result_exception(result):
44
- raise error_summary.error_type(error_summary, result)
45
-
46
- @classmethod
47
- def find_first_remoted_error(cls, exception: BaseException) -> t.Self | None:
48
- """Find the first captured module error in the cause chain, starting with the given exception, returning None if not found."""
49
- while exception:
50
- if isinstance(exception, cls):
51
- return exception
52
-
53
- exception = exception.__cause__
54
-
55
- return None
50
+ raise error_summary.error_type(error_summary.event, result)
56
51
 
57
52
  @classmethod
58
53
  def normalize_result_exception(cls, result: dict[str, t.Any]) -> CapturedErrorSummary | None:
@@ -76,17 +71,18 @@ class AnsibleResultCapturedError(AnsibleCapturedError):
76
71
 
77
72
  if isinstance(exception, CapturedErrorSummary):
78
73
  error_summary = exception
79
- elif isinstance(exception, ErrorSummary):
74
+ elif isinstance(exception, _messages.ErrorSummary):
80
75
  error_summary = CapturedErrorSummary(
81
- details=exception.details,
82
- formatted_traceback=cls._normalize_traceback(exception.formatted_traceback),
76
+ event=exception.event,
83
77
  error_type=cls,
84
78
  )
85
79
  else:
86
80
  # translate non-ErrorDetail errors
87
81
  error_summary = CapturedErrorSummary(
88
- details=(Detail(msg=str(result.get('msg', 'Unknown error.'))),),
89
- formatted_traceback=cls._normalize_traceback(exception),
82
+ event=_messages.Event(
83
+ msg=str(result.get('msg', 'Unknown error.')),
84
+ formatted_traceback=cls._normalize_traceback(exception),
85
+ ),
90
86
  error_type=cls,
91
87
  )
92
88
 
@@ -122,7 +118,6 @@ class AnsibleModuleCapturedError(AnsibleResultCapturedError):
122
118
  context = 'target'
123
119
 
124
120
 
125
- @dataclasses.dataclass(**_dataclass_kwargs)
126
- class CapturedErrorSummary(ErrorSummary):
127
- # DTFIX-RELEASE: where to put this, name, etc. since it shows up in results, it's not exactly private (and contains a type ref to an internal type)
121
+ @dataclasses.dataclass(**_messages._dataclass_kwargs)
122
+ class CapturedErrorSummary(_messages.ErrorSummary):
128
123
  error_type: type[AnsibleResultCapturedError] | None = None
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ from ansible.module_utils._internal import _errors, _messages
4
+
5
+
6
+ class ControllerEventFactory(_errors.EventFactory):
7
+ """Factory for creating `Event` instances from `BaseException` instances on the controller."""
8
+
9
+ def _get_msg(self, exception: BaseException) -> str | None:
10
+ from ansible.errors import AnsibleError
11
+
12
+ if not isinstance(exception, AnsibleError):
13
+ return super()._get_msg(exception)
14
+
15
+ return exception._original_message.strip()
16
+
17
+ def _get_formatted_source_context(self, exception: BaseException) -> str | None:
18
+ from ansible.errors import AnsibleError
19
+
20
+ if not isinstance(exception, AnsibleError):
21
+ return super()._get_formatted_source_context(exception)
22
+
23
+ return exception._formatted_source_context
24
+
25
+ def _get_help_text(self, exception: BaseException) -> str | None:
26
+ from ansible.errors import AnsibleError
27
+
28
+ if not isinstance(exception, AnsibleError):
29
+ return super()._get_help_text(exception)
30
+
31
+ return exception._help_text
32
+
33
+ def _get_chain(self, exception: BaseException) -> _messages.EventChain | None:
34
+ from ansible._internal._errors import _captured # avoid circular import due to AnsibleError import
35
+
36
+ if isinstance(exception, _captured.AnsibleCapturedError):
37
+ # a captured error provides its own cause event, it never has a normal __cause__
38
+ return _messages.EventChain(
39
+ msg_reason=_errors.MSG_REASON_DIRECT_CAUSE,
40
+ traceback_reason=f'The above {exception.context} exception was the direct cause of the following controller exception:',
41
+ event=exception._event,
42
+ )
43
+
44
+ return super()._get_chain(exception)
45
+
46
+ def _follow_cause(self, exception: BaseException) -> bool:
47
+ from ansible.errors import AnsibleError
48
+
49
+ return not isinstance(exception, AnsibleError) or exception._include_cause_message
50
+
51
+ def _get_cause(self, exception: BaseException) -> BaseException | None:
52
+ # deprecated: description='remove support for orig_exc (deprecated in 2.23)' core_version='2.27'
53
+
54
+ cause = super()._get_cause(exception)
55
+
56
+ from ansible.errors import AnsibleError
57
+
58
+ if not isinstance(exception, AnsibleError):
59
+ return cause
60
+
61
+ try:
62
+ from ansible.utils.display import _display
63
+ except Exception: # pylint: disable=broad-except # if config is broken, this can raise things other than ImportError
64
+ _display = None
65
+
66
+ if cause:
67
+ if exception.orig_exc and exception.orig_exc is not cause and _display:
68
+ _display.warning(
69
+ msg=f"The `orig_exc` argument to `{type(exception).__name__}` was given, but differed from the cause given by `raise ... from`.",
70
+ )
71
+
72
+ return cause
73
+
74
+ if exception.orig_exc:
75
+ if _display:
76
+ # encourage the use of `raise ... from` before deprecating `orig_exc`
77
+ _display.warning(
78
+ msg=f"The `orig_exc` argument to `{type(exception).__name__}` was given without using `raise ... from orig_exc`.",
79
+ )
80
+
81
+ return exception.orig_exc
82
+
83
+ return None
84
+
85
+ def _get_events(self, exception: BaseException) -> tuple[_messages.Event, ...] | None:
86
+ if isinstance(exception, BaseExceptionGroup):
87
+ return tuple(self._convert_exception(ex) for ex in exception.exceptions)
88
+
89
+ return None