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
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import abc
4
4
  import collections.abc as c
5
+ import enum
5
6
  import inspect
6
7
  import itertools
7
8
  import typing as t
@@ -9,7 +10,7 @@ import typing as t
9
10
  from jinja2 import UndefinedError, StrictUndefined, TemplateRuntimeError
10
11
  from jinja2.utils import missing
11
12
 
12
- from ansible.module_utils.common.messages import ErrorSummary, Detail
13
+ from ...module_utils._internal import _messages
13
14
  from ansible.constants import config
14
15
  from ansible.errors import AnsibleUndefinedVariable, AnsibleTypeError
15
16
  from ansible._internal._errors._handler import ErrorHandler
@@ -24,10 +25,16 @@ from ...module_utils.datatag import native_type_name
24
25
  _patch_jinja() # apply Jinja2 patches before types are declared that are dependent on the changes
25
26
 
26
27
 
28
+ class _SandboxMode(enum.Enum):
29
+ DEFAULT = enum.auto()
30
+ ALLOW_UNSAFE_ATTRIBUTES = enum.auto()
31
+
32
+
27
33
  class _TemplateConfig:
28
34
  allow_embedded_templates: bool = config.get_config_value("ALLOW_EMBEDDED_TEMPLATES")
29
35
  allow_broken_conditionals: bool = config.get_config_value('ALLOW_BROKEN_CONDITIONALS')
30
36
  jinja_extensions: list[str] = config.get_config_value('DEFAULT_JINJA2_EXTENSIONS')
37
+ sandbox_mode: _SandboxMode = _SandboxMode.__members__[config.get_config_value('_TEMPLAR_SANDBOX_MODE').upper()]
31
38
 
32
39
  unknown_type_encountered_handler = ErrorHandler.from_config('_TEMPLAR_UNKNOWN_TYPE_ENCOUNTERED')
33
40
  unknown_type_conversion_handler = ErrorHandler.from_config('_TEMPLAR_UNKNOWN_TYPE_CONVERSION')
@@ -55,7 +62,7 @@ class Marker(StrictUndefined, Tripwire):
55
62
 
56
63
  __slots__ = ('_marker_template_source',)
57
64
 
58
- concrete_subclasses: t.ClassVar[set[type[Marker]]] = set()
65
+ _concrete_subclasses: t.ClassVar[set[type[Marker]]] = set()
59
66
 
60
67
  def __init__(
61
68
  self,
@@ -129,7 +136,7 @@ class Marker(StrictUndefined, Tripwire):
129
136
  def __init_subclass__(cls, **kwargs) -> None:
130
137
  if not inspect.isabstract(cls):
131
138
  _untaggable_types.add(cls)
132
- cls.concrete_subclasses.add(cls)
139
+ cls._concrete_subclasses.add(cls)
133
140
 
134
141
  @classmethod
135
142
  def _init_class(cls):
@@ -197,8 +204,6 @@ class TruncationMarker(Marker):
197
204
  It will only be visible if the previous `Marker` was ignored/replaced instead of being tripped, which would raise an exception.
198
205
  """
199
206
 
200
- # DTFIX-RELEASE: make this a singleton?
201
-
202
207
  __slots__ = ()
203
208
 
204
209
  def __init__(self) -> None:
@@ -252,28 +257,18 @@ class UndecryptableVaultError(_captured.AnsibleCapturedError):
252
257
  class VaultExceptionMarker(ExceptionMarker):
253
258
  """A `Marker` value that represents an error accessing a vaulted value during templating."""
254
259
 
255
- __slots__ = ('_marker_undecryptable_ciphertext', '_marker_undecryptable_reason', '_marker_undecryptable_traceback')
260
+ __slots__ = ('_marker_undecryptable_ciphertext', '_marker_event')
256
261
 
257
- def __init__(self, ciphertext: str, reason: str, traceback: str | None) -> None:
258
- # DTFIX-RELEASE: when does this show up, should it contain more details?
259
- # see also CapturedExceptionMarker for a similar issue
262
+ def __init__(self, ciphertext: str, event: _messages.Event) -> None:
260
263
  super().__init__(hint='A vault exception marker was tripped.')
261
264
 
262
265
  self._marker_undecryptable_ciphertext = ciphertext
263
- self._marker_undecryptable_reason = reason
264
- self._marker_undecryptable_traceback = traceback
266
+ self._marker_event = event
265
267
 
266
268
  def _as_exception(self) -> Exception:
267
269
  return UndecryptableVaultError(
268
270
  obj=self._marker_undecryptable_ciphertext,
269
- error_summary=ErrorSummary(
270
- details=(
271
- Detail(
272
- msg=self._marker_undecryptable_reason,
273
- ),
274
- ),
275
- formatted_traceback=self._marker_undecryptable_traceback,
276
- ),
271
+ event=self._marker_event,
277
272
  )
278
273
 
279
274
  def _disarm(self) -> str:
@@ -282,16 +277,12 @@ class VaultExceptionMarker(ExceptionMarker):
282
277
 
283
278
  def get_first_marker_arg(args: c.Sequence, kwargs: dict[str, t.Any]) -> Marker | None:
284
279
  """Utility method to inspect plugin args and return the first `Marker` encountered, otherwise `None`."""
285
- # DTFIX-RELEASE: this may or may not need to be public API, move back to utils or once usage is wrapped in a decorator?
286
- for arg in iter_marker_args(args, kwargs):
287
- return arg
288
-
289
- return None
280
+ # CAUTION: This function is exposed in public API as ansible.template.get_first_marker_arg.
281
+ return next(iter_marker_args(args, kwargs), None)
290
282
 
291
283
 
292
284
  def iter_marker_args(args: c.Sequence, kwargs: dict[str, t.Any]) -> t.Generator[Marker]:
293
285
  """Utility method to iterate plugin args and yield any `Marker` encountered."""
294
- # DTFIX-RELEASE: this may or may not need to be public API, move back to utils or once usage is wrapped in a decorator?
295
286
  for arg in itertools.chain(args, kwargs.values()):
296
287
  if isinstance(arg, Marker):
297
288
  yield arg
@@ -306,7 +297,7 @@ class JinjaCallContext(NotifiableAccessContextBase):
306
297
  _mask = True
307
298
 
308
299
  def __init__(self, accept_lazy_markers: bool) -> None:
309
- self._type_interest = frozenset() if accept_lazy_markers else frozenset(Marker.concrete_subclasses)
300
+ self._type_interest = frozenset() if accept_lazy_markers else frozenset(Marker._concrete_subclasses)
310
301
 
311
302
  def _notify(self, o: Marker) -> t.NoReturn:
312
303
  o.trip()
@@ -314,7 +305,7 @@ class JinjaCallContext(NotifiableAccessContextBase):
314
305
 
315
306
  def validate_arg_type(name: str, value: t.Any, allowed_type_or_types: type | tuple[type, ...], /) -> None:
316
307
  """Validate the type of the given argument while preserving context for Marker values."""
317
- # DTFIX-RELEASE: find a home for this as a general-purpose utliity method and expose it after some API review
308
+ # DTFIX-FUTURE: find a home for this as a general-purpose utliity method and expose it after some API review
318
309
  if isinstance(value, allowed_type_or_types):
319
310
  return
320
311
 
@@ -6,8 +6,12 @@ import collections.abc as c
6
6
  import dataclasses
7
7
  import datetime
8
8
  import functools
9
+ import inspect
10
+ import re
9
11
  import typing as t
10
12
 
13
+ from jinja2 import defaults
14
+
11
15
  from ansible.module_utils._internal._ambient_context import AmbientContextBase
12
16
  from ansible.module_utils.common.collections import is_sequence
13
17
  from ansible.module_utils._internal._datatag import AnsibleTagHelper
@@ -115,7 +119,7 @@ class JinjaPluginIntercept(c.MutableMapping):
115
119
  except MarkerError as ex:
116
120
  return ex.source
117
121
  except Exception as ex:
118
- raise AnsibleTemplatePluginRuntimeError(instance.plugin_type, instance.ansible_name) from ex # DTFIX-RELEASE: which name to use? use plugin info?
122
+ raise AnsibleTemplatePluginRuntimeError(instance.plugin_type, instance.ansible_name) from ex # DTFIX-FUTURE: which name to use? use plugin info?
119
123
 
120
124
  def _wrap_test(self, instance: AnsibleJinja2Plugin) -> t.Callable:
121
125
  """Intercept point for all test plugins to ensure that args are properly templated/lazified."""
@@ -127,7 +131,6 @@ class JinjaPluginIntercept(c.MutableMapping):
127
131
  if not isinstance(result, bool):
128
132
  template = TemplateContext.current().template_value
129
133
 
130
- # DTFIX-RELEASE: which name to use? use plugin info?
131
134
  _display.deprecated(
132
135
  msg=f"The test plugin {instance.ansible_name!r} returned a non-boolean result of type {type(result)!r}. "
133
136
  "Test plugins must have a boolean result.",
@@ -254,7 +257,7 @@ def _invoke_lookup(*, plugin_name: str, lookup_terms: list, lookup_kwargs: dict[
254
257
  except MarkerError as ex:
255
258
  return ex.source
256
259
  except Exception as ex:
257
- # DTFIX-RELEASE: convert this to the new error/warn/ignore context manager
260
+ # DTFIX-FUTURE: convert this to the new error/warn/ignore context manager
258
261
  if errors == 'warn':
259
262
  _display.error_as_warning(
260
263
  msg=f'An error occurred while running the lookup plugin {plugin_name!r}.',
@@ -339,3 +342,28 @@ def _wrap_plugin_output(o: t.Any) -> t.Any:
339
342
  o = list(o)
340
343
 
341
344
  return _AnsibleLazyTemplateMixin._try_create(o, LazyOptions.SKIP_TEMPLATES)
345
+
346
+
347
+ _PLUGIN_SOURCES = dict(
348
+ filter=defaults.DEFAULT_FILTERS,
349
+ test=defaults.DEFAULT_TESTS,
350
+ )
351
+
352
+
353
+ def _get_builtin_short_description(plugin: object) -> str:
354
+ """
355
+ Make a reasonable effort to break a function docstring down to a single sentence.
356
+ We can't use the full docstring due to embedded formatting, particularly RST.
357
+ This isn't intended to be perfect, just good enough until we can write our own docs for these.
358
+ """
359
+ value = re.split(r'(\.|!|\s\(|:\s)', inspect.getdoc(plugin), 1)[0].replace('\n', ' ')
360
+
361
+ if value:
362
+ value += '.'
363
+
364
+ return value
365
+
366
+
367
+ def get_jinja_builtin_plugin_descriptions(plugin_type: str) -> dict[str, str]:
368
+ """Returns a dictionary of Jinja builtin plugin names and their short descriptions."""
369
+ return {f'ansible.builtin.{name}': _get_builtin_short_description(plugin) for name, plugin in _PLUGIN_SOURCES[plugin_type].items() if name.isidentifier()}
@@ -43,7 +43,7 @@ _KNOWN_TYPES: t.Final[set[type]] = (
43
43
  TemplateModule, # example: '{% import "importme.j2" as im %}{{ im | type_debug }}'
44
44
  }
45
45
  | set(PASS_THROUGH_SCALAR_VAR_TYPES)
46
- | set(Marker.concrete_subclasses)
46
+ | set(Marker._concrete_subclasses)
47
47
  )
48
48
  """
49
49
  These types are known to the templating system.
@@ -195,7 +195,7 @@ class _AnsibleLazyTemplateMixin:
195
195
  Return an iterable that wraps each of the given elements in a lazy wrapper.
196
196
  Only elements wrapped this way will receive lazy processing when retrieved from the collection.
197
197
  """
198
- # DTFIX-RELEASE: check relative performance of method-local vs stored generator expressions on implementations of this method
198
+ # DTFIX-FUTURE: check relative performance of method-local vs stored generator expressions on implementations of this method
199
199
  raise NotImplementedError() # pragma: nocover
200
200
 
201
201
  def _proxy_or_render_lazy_value(self, key: t.Any, value: t.Any) -> t.Any:
@@ -346,13 +346,13 @@ class _AnsibleLazyTemplateDict(_AnsibleTaggedDict, _AnsibleLazyTemplateMixin):
346
346
  return super().__ne__(other)
347
347
 
348
348
  def __or__(self, other):
349
- # DTFIX-RELEASE: support preservation of laziness when possible like we do for list
349
+ # DTFIX-FUTURE: support preservation of laziness when possible like we do for list
350
350
  # Both sides end up going through _proxy_or_render_lazy_value, so there's no Templar preservation needed.
351
351
  # In the future this could be made more lazy when both Templar instances are the same, or if per-value Templar tracking was used.
352
352
  return super().__or__(other)
353
353
 
354
354
  def __ror__(self, other):
355
- # DTFIX-RELEASE: support preservation of laziness when possible like we do for list
355
+ # DTFIX-FUTURE: support preservation of laziness when possible like we do for list
356
356
  # Both sides end up going through _proxy_or_render_lazy_value, so there's no Templar preservation needed.
357
357
  # In the future this could be made more lazy when both Templar instances are the same, or if per-value Templar tracking was used.
358
358
  return super().__ror__(other)
@@ -549,7 +549,7 @@ class _AnsibleLazyAccessTuple(_AnsibleTaggedTuple, _AnsibleLazyTemplateMixin):
549
549
  created as a results of managed access.
550
550
  """
551
551
 
552
- # DTFIX-RELEASE: ensure we have tests that explicitly verify this behavior
552
+ # DTFIX5: ensure we have tests that explicitly verify this behavior
553
553
 
554
554
  # nonempty __slots__ not supported for subtype of 'tuple'
555
555
 
@@ -5,39 +5,41 @@ from __future__ import annotations
5
5
  import dataclasses
6
6
  import typing as t
7
7
 
8
- from ansible.module_utils._internal import _traceback
9
- from ansible.module_utils.common.messages import PluginInfo, ErrorSummary, WarningSummary, DeprecationSummary
8
+ from ansible.module_utils._internal import _traceback, _event_utils, _messages
10
9
  from ansible.parsing.vault import EncryptedString, VaultHelper
11
10
  from ansible.utils.display import Display
12
11
 
13
12
  from ._jinja_common import VaultExceptionMarker
14
- from .._errors import _captured, _utils
13
+ from .._errors import _captured, _error_factory
14
+ from .. import _event_formatting
15
15
 
16
16
  display = Display()
17
17
 
18
18
 
19
- def plugin_info(value: PluginInfo) -> dict[str, str]:
19
+ def plugin_info(value: _messages.PluginInfo) -> dict[str, str]:
20
20
  """Render PluginInfo as a dictionary."""
21
21
  return dataclasses.asdict(value)
22
22
 
23
23
 
24
- def error_summary(value: ErrorSummary) -> str:
24
+ def error_summary(value: _messages.ErrorSummary) -> str:
25
25
  """Render ErrorSummary as a formatted traceback for backward-compatibility with pre-2.19 TaskResult.exception."""
26
- return value.formatted_traceback or '(traceback unavailable)'
26
+ if _traceback._is_traceback_enabled(_traceback.TracebackEvent.ERROR):
27
+ return _event_formatting.format_event_traceback(value.event)
27
28
 
29
+ return '(traceback unavailable)'
28
30
 
29
- def warning_summary(value: WarningSummary) -> str:
31
+
32
+ def warning_summary(value: _messages.WarningSummary) -> str:
30
33
  """Render WarningSummary as a simple message string for backward-compatibility with pre-2.19 TaskResult.warnings."""
31
- return value._format()
34
+ return _event_utils.format_event_brief_message(value.event)
32
35
 
33
36
 
34
- def deprecation_summary(value: DeprecationSummary) -> dict[str, t.Any]:
37
+ def deprecation_summary(value: _messages.DeprecationSummary) -> dict[str, t.Any]:
35
38
  """Render DeprecationSummary as dict values for backward-compatibility with pre-2.19 TaskResult.deprecations."""
36
- # DTFIX-RELEASE: reconsider which deprecation fields should be exposed here, taking into account that collection_name is to be deprecated
37
- result = value._as_simple_dict()
38
- result.pop('details')
39
+ transformed = _event_utils.deprecation_as_dict(value)
40
+ transformed.update(deprecator=value.deprecator)
39
41
 
40
- return result
42
+ return transformed
41
43
 
42
44
 
43
45
  def encrypted_string(value: EncryptedString) -> str | VaultExceptionMarker:
@@ -47,17 +49,16 @@ def encrypted_string(value: EncryptedString) -> str | VaultExceptionMarker:
47
49
  except Exception as ex:
48
50
  return VaultExceptionMarker(
49
51
  ciphertext=VaultHelper.get_ciphertext(value, with_tags=True),
50
- reason=_utils.get_chained_message(ex),
51
- traceback=_traceback.maybe_extract_traceback(ex, _traceback.TracebackEvent.ERROR),
52
+ event=_error_factory.ControllerEventFactory.from_exception(ex, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR)),
52
53
  )
53
54
 
54
55
 
55
56
  _type_transform_mapping: dict[type, t.Callable[[t.Any], t.Any]] = {
56
57
  _captured.CapturedErrorSummary: error_summary,
57
- PluginInfo: plugin_info,
58
- ErrorSummary: error_summary,
59
- WarningSummary: warning_summary,
60
- DeprecationSummary: deprecation_summary,
58
+ _messages.PluginInfo: plugin_info,
59
+ _messages.ErrorSummary: error_summary,
60
+ _messages.WarningSummary: warning_summary,
61
+ _messages.DeprecationSummary: deprecation_summary,
61
62
  EncryptedString: encrypted_string,
62
63
  }
63
64
  """This mapping is consulted by `Templar.template` to provide custom views of some objects."""
@@ -99,7 +99,7 @@ Omit = object.__new__(_OmitType)
99
99
  _datatag._untaggable_types.add(_OmitType)
100
100
 
101
101
 
102
- # DTFIX-RELEASE: review these type sets to ensure they're not overly permissive/dynamic
102
+ # DTFIX5: review these type sets to ensure they're not overly permissive/dynamic
103
103
  IGNORE_SCALAR_VAR_TYPES = {value for value in _datatag._ANSIBLE_ALLOWED_SCALAR_VAR_TYPES if not issubclass(value, str)}
104
104
 
105
105
  PASS_THROUGH_SCALAR_VAR_TYPES = _datatag._ANSIBLE_ALLOWED_SCALAR_VAR_TYPES | {
@@ -32,7 +32,7 @@ class _BaseDumper(SafeDumper, metaclass=abc.ABCMeta):
32
32
  class AnsibleDumper(_BaseDumper):
33
33
  """A simple stub class that allows us to add representers for our custom types."""
34
34
 
35
- # DTFIX-RELEASE: need a better way to handle serialization controls during YAML dumping
35
+ # DTFIX0: need a better way to handle serialization controls during YAML dumping
36
36
  def __init__(self, *args, dump_vault_tags: bool | None = None, **kwargs):
37
37
  super().__init__(*args, **kwargs)
38
38
 
@@ -7,7 +7,7 @@ import typing as t
7
7
  from yaml import MarkedYAMLError
8
8
  from yaml.constructor import ConstructorError
9
9
 
10
- from ansible._internal._errors import _utils
10
+ from ansible._internal._errors import _error_utils
11
11
  from ansible.errors import AnsibleParserError
12
12
  from ansible._internal._datatag._tags import Origin
13
13
 
@@ -34,7 +34,7 @@ class AnsibleYAMLParserError(AnsibleParserError):
34
34
  if isinstance(exception, MarkedYAMLError):
35
35
  origin = origin.replace(line_num=exception.problem_mark.line + 1, col_num=exception.problem_mark.column + 1)
36
36
 
37
- source_context = _utils.SourceContext.from_origin(origin)
37
+ source_context = _error_utils.SourceContext.from_origin(origin)
38
38
 
39
39
  target_line = source_context.target_line or '' # for these cases, we don't need to distinguish between None and empty string
40
40
 
@@ -66,12 +66,12 @@ class AnsibleYAMLParserError(AnsibleParserError):
66
66
  # There may be cases where there is a valid tab in a line that has other errors.
67
67
  # That's OK, users should "fix" their tab usage anyway -- at which point later error handling logic will hopefully find the real issue.
68
68
  elif (tab_idx := target_line.find('\t')) >= 0:
69
- source_context = _utils.SourceContext.from_origin(origin.replace(col_num=tab_idx + 1))
69
+ source_context = _error_utils.SourceContext.from_origin(origin.replace(col_num=tab_idx + 1))
70
70
  message = "Tabs are usually invalid in YAML."
71
71
 
72
72
  # Check for unquoted templates.
73
73
  elif match := re.search(r'^\s*(?:-\s+)*(?:[\w\s]+:\s+)?(?P<value>\{\{.*}})', target_line):
74
- source_context = _utils.SourceContext.from_origin(origin.replace(col_num=match.start('value') + 1))
74
+ source_context = _error_utils.SourceContext.from_origin(origin.replace(col_num=match.start('value') + 1))
75
75
  message = 'This may be an issue with missing quotes around a template block.'
76
76
  # FIXME: Use the captured value to show the actual fix required.
77
77
  help_text = """
@@ -95,7 +95,7 @@ Should be:
95
95
  # look for an unquoted colon in the value
96
96
  and (colon_match := re.search(r':($| )', target_fragment))
97
97
  ):
98
- source_context = _utils.SourceContext.from_origin(origin.replace(col_num=value_match.start('value') + colon_match.start() + 1))
98
+ source_context = _error_utils.SourceContext.from_origin(origin.replace(col_num=value_match.start('value') + colon_match.start() + 1))
99
99
  message = 'Colons in unquoted values must be followed by a non-space character.'
100
100
  # FIXME: Use the captured value to show the actual fix required.
101
101
  help_text = """
@@ -114,7 +114,7 @@ Should be:
114
114
  first, last = suspected_value[0], suspected_value[-1]
115
115
 
116
116
  if first != last: # "foo" in bar
117
- source_context = _utils.SourceContext.from_origin(origin.replace(col_num=match.start('value') + 1))
117
+ source_context = _error_utils.SourceContext.from_origin(origin.replace(col_num=match.start('value') + 1))
118
118
  message = 'Values starting with a quote must end with the same quote.'
119
119
  # FIXME: Use the captured value to show the actual fix required, and use that same logic to improve the origin further.
120
120
  help_text = """
@@ -127,7 +127,7 @@ Should be:
127
127
  raw: '"foo" in bar'
128
128
  """
129
129
  elif first == last and target_line.count(first) > 2: # "foo" and "bar"
130
- source_context = _utils.SourceContext.from_origin(origin.replace(col_num=match.start('value') + 1))
130
+ source_context = _error_utils.SourceContext.from_origin(origin.replace(col_num=match.start('value') + 1))
131
131
  message = 'Values starting with a quote must end with the same quote, and not contain that quote.'
132
132
  # FIXME: Use the captured value to show the actual fix required, and use that same logic to improve the origin further.
133
133
  help_text = """
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import typing as t
4
4
 
5
- from ansible.plugins import accept_args_markers
5
+ from ansible.template import accept_args_markers
6
6
 
7
7
 
8
8
  @accept_args_markers
@@ -12,7 +12,7 @@ from ansible.errors import AnsibleError
12
12
 
13
13
  def unmask(value: object, type_names: str | list[str]) -> object:
14
14
  """
15
- Internal filter to suppress automatic type transformation in Jinja (e.g., WarningMessageDetail, DeprecationMessageDetail, ErrorDetail).
15
+ Internal filter to suppress automatic type transformation in Jinja (e.g., WarningSummary, DeprecationSummary, ErrorSummary).
16
16
  Lazy collection caching is in play - the first attempt to access a value in a given lazy container must be with unmasking in place, or the transformed value
17
17
  will already be cached.
18
18
  """
ansible/cli/__init__.py CHANGED
@@ -7,7 +7,6 @@ from __future__ import annotations
7
7
 
8
8
  import locale
9
9
  import os
10
- import signal
11
10
  import sys
12
11
 
13
12
  # We overload the ``ansible`` adhoc command to provide the functionality for
@@ -75,8 +74,6 @@ def initialize_locale():
75
74
 
76
75
  initialize_locale()
77
76
 
78
-
79
- import atexit
80
77
  import errno
81
78
  import getpass
82
79
  import subprocess
@@ -96,7 +93,7 @@ try:
96
93
  display = Display()
97
94
  except Exception as ex:
98
95
  if isinstance(ex, AnsibleError):
99
- ex_msg = ' '.join((ex.message, ex._help_text)).strip()
96
+ ex_msg = ' '.join((ex.message, ex._help_text or '')).strip()
100
97
  else:
101
98
  ex_msg = str(ex)
102
99
 
@@ -112,17 +109,17 @@ from ansible.module_utils.six import string_types
112
109
  from ansible.module_utils.common.text.converters import to_bytes, to_text
113
110
  from ansible.module_utils.common.collections import is_sequence
114
111
  from ansible.module_utils.common.file import is_executable
115
- from ansible.module_utils.common.process import get_bin_path
116
112
  from ansible.parsing.dataloader import DataLoader
117
113
  from ansible.parsing.vault import PromptVaultSecret, get_file_vault_secret, VaultSecretsContext
118
114
  from ansible.plugins.loader import add_all_plugin_dirs, init_plugin_loader
119
115
  from ansible.release import __version__
120
- from ansible.utils._ssh_agent import SshAgentClient
121
116
  from ansible.utils.collection_loader import AnsibleCollectionConfig
122
117
  from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path
123
118
  from ansible.utils.path import unfrackpath
124
119
  from ansible.vars.manager import VariableManager
125
120
  from ansible.module_utils._internal import _deprecator
121
+ from ansible._internal._ssh import _agent_launch
122
+
126
123
 
127
124
  try:
128
125
  import argcomplete
@@ -131,77 +128,6 @@ except ImportError:
131
128
  HAS_ARGCOMPLETE = False
132
129
 
133
130
 
134
- _SSH_AGENT_STDOUT_READ_TIMEOUT = 5 # seconds
135
-
136
-
137
- def _ssh_agent_timeout_handler(signum, frame):
138
- raise TimeoutError
139
-
140
-
141
- def _launch_ssh_agent() -> None:
142
- ssh_agent_cfg = C.config.get_config_value('SSH_AGENT')
143
- match ssh_agent_cfg:
144
- case 'none':
145
- display.debug('SSH_AGENT set to none')
146
- return
147
- case 'auto':
148
- try:
149
- ssh_agent_bin = get_bin_path('ssh-agent')
150
- except ValueError as e:
151
- raise AnsibleError('SSH_AGENT set to auto, but cannot find ssh-agent binary') from e
152
- ssh_agent_dir = os.path.join(C.DEFAULT_LOCAL_TMP, 'ssh_agent')
153
- os.mkdir(ssh_agent_dir, 0o700)
154
- sock = os.path.join(ssh_agent_dir, 'agent.sock')
155
- display.vvv('SSH_AGENT: starting...')
156
- try:
157
- p = subprocess.Popen(
158
- [ssh_agent_bin, '-D', '-s', '-a', sock],
159
- stdin=subprocess.PIPE,
160
- stdout=subprocess.PIPE,
161
- stderr=subprocess.PIPE,
162
- )
163
- except OSError as e:
164
- raise AnsibleError(
165
- f'Could not start ssh-agent: {e}'
166
- ) from e
167
-
168
- if p.poll() is not None:
169
- raise AnsibleError(
170
- f'Could not start ssh-agent: rc={p.returncode} stderr="{p.stderr.read().decode()}"'
171
- )
172
-
173
- old_sigalrm_handler = signal.signal(signal.SIGALRM, _ssh_agent_timeout_handler)
174
- signal.alarm(_SSH_AGENT_STDOUT_READ_TIMEOUT)
175
- try:
176
- stdout = p.stdout.read(13)
177
- except TimeoutError:
178
- stdout = b''
179
- finally:
180
- signal.alarm(0)
181
- signal.signal(signal.SIGALRM, old_sigalrm_handler)
182
-
183
- if stdout != b'SSH_AUTH_SOCK':
184
- display.warning(
185
- f'The first 13 characters of stdout did not match the '
186
- f'expected SSH_AUTH_SOCK. This may not be the right binary, '
187
- f'or an incompatible agent: {stdout.decode()}'
188
- )
189
- display.vvv(f'SSH_AGENT: ssh-agent[{p.pid}] started and bound to {sock}')
190
- atexit.register(p.terminate)
191
- case _:
192
- sock = ssh_agent_cfg
193
-
194
- try:
195
- with SshAgentClient(sock) as client:
196
- client.list()
197
- except Exception as e:
198
- raise AnsibleError(
199
- f'Could not communicate with ssh-agent using auth sock {sock}: {e}'
200
- ) from e
201
-
202
- os.environ['SSH_AUTH_SOCK'] = os.environ['ANSIBLE_SSH_AGENT'] = sock
203
-
204
-
205
131
  class CLI(ABC):
206
132
  """ code behind bin/ansible* programs """
207
133
 
@@ -636,10 +562,7 @@ class CLI(ABC):
636
562
  loader.set_vault_secrets(vault_secrets)
637
563
 
638
564
  if self.USES_CONNECTION:
639
- try:
640
- _launch_ssh_agent()
641
- except Exception as e:
642
- raise AnsibleError('Failed to launch ssh agent', orig_exc=e)
565
+ _agent_launch.launch_ssh_agent()
643
566
 
644
567
  # create the inventory, and filter it based on the subset specified (if any)
645
568
  inventory = InventoryManager(loader=loader, sources=options['inventory'], cache=(not options.get('flush_cache')))
@@ -750,7 +673,7 @@ class CLI(ABC):
750
673
  try:
751
674
  raise AnsibleError("Unexpected Exception, this is probably a bug.") from ex
752
675
  except AnsibleError as ex2:
753
- # DTFIX-RELEASE: clean this up so we're not hacking the internals- re-wrap in an AnsibleCLIUnhandledError that always shows TB, or?
676
+ # DTFIX-FUTURE: clean this up so we're not hacking the internals- re-wrap in an AnsibleCLIUnhandledError that always shows TB, or?
754
677
  from ansible.module_utils._internal import _traceback
755
678
  _traceback._is_traceback_enabled = lambda *_args, **_kwargs: True
756
679
  display.error(ex2)
@@ -16,10 +16,9 @@ import typing as t
16
16
 
17
17
  import yaml
18
18
 
19
- from jinja2 import __version__ as j2_version
20
-
21
19
  import ansible
22
20
  from ansible import constants as C
21
+ from ansible._internal import _templating
23
22
  from ansible.module_utils.common.text.converters import to_native
24
23
  from ansible.module_utils.common.yaml import HAS_LIBYAML, yaml_load
25
24
  from ansible.release import __version__
@@ -313,7 +312,7 @@ def version(prog=None):
313
312
  result.append(" ansible collection location = %s" % ':'.join(C.COLLECTIONS_PATHS))
314
313
  result.append(" executable location = %s" % sys.argv[0])
315
314
  result.append(" python version = %s (%s)" % (''.join(sys.version.splitlines()), to_native(sys.executable)))
316
- result.append(" jinja version = %s" % j2_version)
315
+ result.append(f" jinja version = {_templating.jinja2_version}")
317
316
  result.append(f" pyyaml version = {yaml.__version__} ({libyaml_fragment})")
318
317
 
319
318
  return "\n".join(result)