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
@@ -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."""
@@ -124,10 +128,12 @@ class JinjaPluginIntercept(c.MutableMapping):
124
128
  def wrapper(*args, **kwargs) -> bool | Marker:
125
129
  result = self._invoke_plugin(instance, *args, **kwargs)
126
130
 
131
+ if isinstance(result, Marker):
132
+ return result
133
+
127
134
  if not isinstance(result, bool):
128
135
  template = TemplateContext.current().template_value
129
136
 
130
- # DTFIX-RELEASE: which name to use? use plugin info?
131
137
  _display.deprecated(
132
138
  msg=f"The test plugin {instance.ansible_name!r} returned a non-boolean result of type {type(result)!r}. "
133
139
  "Test plugins must have a boolean result.",
@@ -157,7 +163,7 @@ class JinjaPluginIntercept(c.MutableMapping):
157
163
  class _DirectCall:
158
164
  """Functions/methods marked `_DirectCall` bypass Jinja Environment checks for `Marker`."""
159
165
 
160
- _marker_attr: str = "_directcall"
166
+ _marker_attr: t.Final[str] = "_directcall"
161
167
 
162
168
  @classmethod
163
169
  def mark(cls, src: _TCallable) -> _TCallable:
@@ -166,7 +172,7 @@ class _DirectCall:
166
172
 
167
173
  @classmethod
168
174
  def is_marked(cls, value: t.Callable) -> bool:
169
- return callable(value) and getattr(value, "_directcall", False)
175
+ return callable(value) and getattr(value, cls._marker_attr, False)
170
176
 
171
177
 
172
178
  @_DirectCall.mark
@@ -254,7 +260,7 @@ def _invoke_lookup(*, plugin_name: str, lookup_terms: list, lookup_kwargs: dict[
254
260
  except MarkerError as ex:
255
261
  return ex.source
256
262
  except Exception as ex:
257
- # DTFIX-RELEASE: convert this to the new error/warn/ignore context manager
263
+ # DTFIX-FUTURE: convert this to the new error/warn/ignore context manager
258
264
  if errors == 'warn':
259
265
  _display.error_as_warning(
260
266
  msg=f'An error occurred while running the lookup plugin {plugin_name!r}.',
@@ -339,3 +345,28 @@ def _wrap_plugin_output(o: t.Any) -> t.Any:
339
345
  o = list(o)
340
346
 
341
347
  return _AnsibleLazyTemplateMixin._try_create(o, LazyOptions.SKIP_TEMPLATES)
348
+
349
+
350
+ _PLUGIN_SOURCES = dict(
351
+ filter=defaults.DEFAULT_FILTERS,
352
+ test=defaults.DEFAULT_TESTS,
353
+ )
354
+
355
+
356
+ def _get_builtin_short_description(plugin: object) -> str:
357
+ """
358
+ Make a reasonable effort to break a function docstring down to a single sentence.
359
+ We can't use the full docstring due to embedded formatting, particularly RST.
360
+ This isn't intended to be perfect, just good enough until we can write our own docs for these.
361
+ """
362
+ value = re.split(r'(\.|!|\s\(|:\s)', inspect.getdoc(plugin), 1)[0].replace('\n', ' ')
363
+
364
+ if value:
365
+ value += '.'
366
+
367
+ return value
368
+
369
+
370
+ def get_jinja_builtin_plugin_descriptions(plugin_type: str) -> dict[str, str]:
371
+ """Returns a dictionary of Jinja builtin plugin names and their short descriptions."""
372
+ 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
 
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ import datetime as _datetime
4
+ import os as _os
5
+ import pwd as _pwd
6
+ import time as _time
7
+
8
+ from ansible import constants as _constants
9
+ from ansible.module_utils._internal import _datatag
10
+
11
+
12
+ def generate_ansible_template_vars(
13
+ path: str,
14
+ fullpath: str | None = None,
15
+ dest_path: str | None = None,
16
+ include_ansible_managed: bool = True,
17
+ ) -> dict[str, object]:
18
+ """
19
+ Generate and return a dictionary with variable metadata about the template specified by `fullpath`.
20
+ If `fullpath` is `None`, `path` will be used instead.
21
+ """
22
+ # deprecated description="update the ansible.windows collection to inline this logic instead of calling this internal function" core_version="2.23"
23
+ if fullpath is None:
24
+ fullpath = _os.path.abspath(path)
25
+
26
+ template_path = fullpath
27
+ template_stat = _os.stat(template_path)
28
+
29
+ template_uid: int | str
30
+
31
+ try:
32
+ template_uid = _pwd.getpwuid(template_stat.st_uid).pw_name
33
+ except KeyError:
34
+ template_uid = template_stat.st_uid
35
+
36
+ temp_vars = dict(
37
+ template_host=_os.uname()[1],
38
+ template_path=path,
39
+ template_mtime=_datetime.datetime.fromtimestamp(template_stat.st_mtime),
40
+ template_uid=template_uid,
41
+ template_run_date=_datetime.datetime.now(),
42
+ template_destpath=dest_path,
43
+ template_fullpath=fullpath,
44
+ )
45
+
46
+ if include_ansible_managed: # only inject the config default value if the variable wasn't set
47
+ temp_vars['ansible_managed'] = _generate_ansible_managed(template_stat)
48
+
49
+ return temp_vars
50
+
51
+
52
+ def _generate_ansible_managed(template_stat: _os.stat_result) -> str:
53
+ """Generate and return the `ansible_managed` variable."""
54
+ # deprecated description="remove the `_generate_ansible_managed` function and use a constant instead" core_version="2.23"
55
+
56
+ from ansible.template import trust_as_template
57
+
58
+ managed_default = _constants.config.get_config_value('DEFAULT_MANAGED_STR')
59
+
60
+ managed_str = managed_default.format(
61
+ # IMPORTANT: These values must be constant strings to avoid template injection.
62
+ # Use Jinja template expressions where variables are needed.
63
+ host="{{ template_host }}",
64
+ uid="{{ template_uid }}",
65
+ file="{{ template_path }}",
66
+ )
67
+
68
+ ansible_managed = _time.strftime(managed_str, _time.localtime(template_stat.st_mtime))
69
+ ansible_managed = _datatag.AnsibleTagHelper.tag_copy(managed_default, ansible_managed)
70
+ ansible_managed = trust_as_template(ansible_managed)
71
+
72
+ return ansible_managed
@@ -5,39 +5,46 @@ 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 plugin_type(value: _messages.PluginType) -> str:
25
+ """Render PluginType as a string."""
26
+ return value.value
27
+
28
+
29
+ def error_summary(value: _messages.ErrorSummary) -> str:
25
30
  """Render ErrorSummary as a formatted traceback for backward-compatibility with pre-2.19 TaskResult.exception."""
26
- return value.formatted_traceback or '(traceback unavailable)'
31
+ if _traceback._is_traceback_enabled(_traceback.TracebackEvent.ERROR):
32
+ return _event_formatting.format_event_traceback(value.event)
33
+
34
+ return '(traceback unavailable)'
27
35
 
28
36
 
29
- def warning_summary(value: WarningSummary) -> str:
37
+ def warning_summary(value: _messages.WarningSummary) -> str:
30
38
  """Render WarningSummary as a simple message string for backward-compatibility with pre-2.19 TaskResult.warnings."""
31
- return value._format()
39
+ return _event_utils.format_event_brief_message(value.event)
32
40
 
33
41
 
34
- def deprecation_summary(value: DeprecationSummary) -> dict[str, t.Any]:
42
+ def deprecation_summary(value: _messages.DeprecationSummary) -> dict[str, t.Any]:
35
43
  """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')
44
+ transformed = _event_utils.deprecation_as_dict(value)
45
+ transformed.update(deprecator=value.deprecator)
39
46
 
40
- return result
47
+ return transformed
41
48
 
42
49
 
43
50
  def encrypted_string(value: EncryptedString) -> str | VaultExceptionMarker:
@@ -47,17 +54,17 @@ def encrypted_string(value: EncryptedString) -> str | VaultExceptionMarker:
47
54
  except Exception as ex:
48
55
  return VaultExceptionMarker(
49
56
  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),
57
+ event=_error_factory.ControllerEventFactory.from_exception(ex, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR)),
52
58
  )
53
59
 
54
60
 
55
61
  _type_transform_mapping: dict[type, t.Callable[[t.Any], t.Any]] = {
56
62
  _captured.CapturedErrorSummary: error_summary,
57
- PluginInfo: plugin_info,
58
- ErrorSummary: error_summary,
59
- WarningSummary: warning_summary,
60
- DeprecationSummary: deprecation_summary,
63
+ _messages.PluginInfo: plugin_info,
64
+ _messages.PluginType: plugin_type,
65
+ _messages.ErrorSummary: error_summary,
66
+ _messages.WarningSummary: warning_summary,
67
+ _messages.DeprecationSummary: deprecation_summary,
61
68
  EncryptedString: encrypted_string,
62
69
  }
63
70
  """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 | {
@@ -4,13 +4,13 @@ import abc
4
4
  import copy
5
5
  import typing as t
6
6
 
7
- from yaml import Node
7
+ from yaml import Node, ScalarNode
8
8
  from yaml.constructor import SafeConstructor
9
9
  from yaml.resolver import BaseResolver
10
10
 
11
11
  from ansible import constants as C
12
12
  from ansible.module_utils.common.text.converters import to_text
13
- from ansible.module_utils._internal._datatag import AnsibleTagHelper
13
+ from ansible.module_utils._internal._datatag import AnsibleTagHelper, AnsibleDatatagBase
14
14
  from ansible._internal._datatag._tags import Origin, TrustedAsTemplate
15
15
  from ansible.parsing.vault import EncryptedString
16
16
  from ansible.utils.display import Display
@@ -117,13 +117,13 @@ class AnsibleInstrumentedConstructor(_BaseConstructor):
117
117
  items = [origin.tag(item) for item in items]
118
118
  yield origin.tag(items)
119
119
 
120
- def construct_yaml_str(self, node):
120
+ def construct_yaml_str(self, node: ScalarNode) -> str:
121
121
  # Override the default string handling function
122
122
  # to always return unicode objects
123
123
  # DTFIX-FUTURE: is this to_text conversion still necessary under Py3?
124
124
  value = to_text(self.construct_scalar(node))
125
125
 
126
- tags = [self._node_position_info(node)]
126
+ tags: list[AnsibleDatatagBase] = [self._node_position_info(node)]
127
127
 
128
128
  if self.trusted_as_template:
129
129
  # NB: since we're not context aware, this will happily add trust to dictionary keys; this is actually necessary for
@@ -4,8 +4,10 @@ import abc
4
4
  import collections.abc as c
5
5
  import typing as t
6
6
 
7
- from yaml.representer import SafeRepresenter
7
+ from yaml.nodes import ScalarNode, Node
8
8
 
9
+ from ansible._internal._templating import _jinja_common
10
+ from ansible.module_utils import _internal
9
11
  from ansible.module_utils._internal._datatag import AnsibleTaggedObject, Tripwire, AnsibleTagHelper
10
12
  from ansible.parsing.vault import VaultHelper
11
13
  from ansible.module_utils.common.yaml import HAS_LIBYAML
@@ -32,30 +34,36 @@ class _BaseDumper(SafeDumper, metaclass=abc.ABCMeta):
32
34
  class AnsibleDumper(_BaseDumper):
33
35
  """A simple stub class that allows us to add representers for our custom types."""
34
36
 
35
- # DTFIX-RELEASE: need a better way to handle serialization controls during YAML dumping
36
- def __init__(self, *args, dump_vault_tags: bool | None = None, **kwargs):
37
- super().__init__(*args, **kwargs)
38
-
39
- self._dump_vault_tags = dump_vault_tags
40
-
41
37
  @classmethod
42
38
  def _register_representers(cls) -> None:
43
39
  cls.add_multi_representer(AnsibleTaggedObject, cls.represent_ansible_tagged_object)
44
40
  cls.add_multi_representer(Tripwire, cls.represent_tripwire)
45
- cls.add_multi_representer(c.Mapping, SafeRepresenter.represent_dict)
46
- cls.add_multi_representer(c.Sequence, SafeRepresenter.represent_list)
47
-
48
- def represent_ansible_tagged_object(self, data):
49
- if self._dump_vault_tags is not False and (ciphertext := VaultHelper.get_ciphertext(data, with_tags=False)):
50
- # deprecated: description='enable the deprecation warning below' core_version='2.23'
51
- # if self._dump_vault_tags is None:
52
- # Display().deprecated(
53
- # msg="Implicit YAML dumping of vaulted value ciphertext is deprecated. Set `dump_vault_tags` to explicitly specify the desired behavior",
54
- # version="2.27",
55
- # )
41
+ cls.add_multi_representer(c.Mapping, cls.represent_dict)
42
+ cls.add_multi_representer(c.Collection, cls.represent_list)
43
+ cls.add_multi_representer(_jinja_common.VaultExceptionMarker, cls.represent_vault_exception_marker)
56
44
 
45
+ def get_node_from_ciphertext(self, data: object) -> ScalarNode | None:
46
+ if ciphertext := VaultHelper.get_ciphertext(data, with_tags=False):
57
47
  return self.represent_scalar('!vault', ciphertext, style='|')
58
48
 
49
+ return None
50
+
51
+ def represent_vault_exception_marker(self, data: _jinja_common.VaultExceptionMarker) -> ScalarNode:
52
+ if node := self.get_node_from_ciphertext(data):
53
+ return node
54
+
55
+ data.trip()
56
+
57
+ def represent_ansible_tagged_object(self, data: AnsibleTaggedObject) -> Node:
58
+ if _internal.is_intermediate_mapping(data):
59
+ return self.represent_dict(data)
60
+
61
+ if _internal.is_intermediate_iterable(data):
62
+ return self.represent_list(data)
63
+
64
+ if node := self.get_node_from_ciphertext(data):
65
+ return node
66
+
59
67
  return self.represent_data(AnsibleTagHelper.as_native_type(data)) # automatically decrypts encrypted strings
60
68
 
61
69
  def represent_tripwire(self, data: Tripwire) -> t.NoReturn:
@@ -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
@@ -76,8 +75,6 @@ def initialize_locale():
76
75
  initialize_locale()
77
76
 
78
77
 
79
- import atexit
80
- import errno
81
78
  import getpass
82
79
  import subprocess
83
80
  import traceback
@@ -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
 
@@ -599,9 +525,7 @@ class CLI(ABC):
599
525
  try:
600
526
  cmd = subprocess.Popen(CLI.PAGER, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout)
601
527
  cmd.communicate(input=to_bytes(text))
602
- except IOError:
603
- pass
604
- except KeyboardInterrupt:
528
+ except (OSError, KeyboardInterrupt):
605
529
  pass
606
530
 
607
531
  def _play_prereqs(self):
@@ -636,10 +560,7 @@ class CLI(ABC):
636
560
  loader.set_vault_secrets(vault_secrets)
637
561
 
638
562
  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)
563
+ _agent_launch.launch_ssh_agent()
643
564
 
644
565
  # create the inventory, and filter it based on the subset specified (if any)
645
566
  inventory = InventoryManager(loader=loader, sources=options['inventory'], cache=(not options.get('flush_cache')))
@@ -709,8 +630,8 @@ class CLI(ABC):
709
630
  try:
710
631
  with open(b_pwd_file, "rb") as password_file:
711
632
  secret = password_file.read().strip()
712
- except (OSError, IOError) as e:
713
- raise AnsibleError("Could not read password file %s: %s" % (pwd_file, e))
633
+ except OSError as ex:
634
+ raise AnsibleError(f"Could not read password file {pwd_file!r}.") from ex
714
635
 
715
636
  secret = secret.strip(b'\r\n')
716
637
 
@@ -729,12 +650,9 @@ class CLI(ABC):
729
650
 
730
651
  ansible_dir = Path(C.ANSIBLE_HOME).expanduser()
731
652
  try:
732
- ansible_dir.mkdir(mode=0o700)
733
- except OSError as exc:
734
- if exc.errno != errno.EEXIST:
735
- display.warning(
736
- "Failed to create the directory '%s': %s" % (ansible_dir, to_text(exc, errors='surrogate_or_replace'))
737
- )
653
+ ansible_dir.mkdir(mode=0o700, exist_ok=True)
654
+ except OSError as ex:
655
+ display.error_as_warning(f"Failed to create the directory {ansible_dir!r}.", ex)
738
656
  else:
739
657
  display.debug("Created the '%s' directory" % ansible_dir)
740
658
 
@@ -750,7 +668,7 @@ class CLI(ABC):
750
668
  try:
751
669
  raise AnsibleError("Unexpected Exception, this is probably a bug.") from ex
752
670
  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?
671
+ # DTFIX-FUTURE: clean this up so we're not hacking the internals- re-wrap in an AnsibleCLIUnhandledError that always shows TB, or?
754
672
  from ansible.module_utils._internal import _traceback
755
673
  _traceback._is_traceback_enabled = lambda *_args, **_kwargs: True
756
674
  display.error(ex2)