ansible-core 2.19.0b5__py3-none-any.whl → 2.19.0b7__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 (184) hide show
  1. ansible/_internal/_ansiballz/__init__.py +0 -0
  2. ansible/_internal/_ansiballz/_builder.py +101 -0
  3. ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
  4. ansible/_internal/_templating/_jinja_bits.py +22 -4
  5. ansible/_internal/_templating/_jinja_common.py +1 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +5 -2
  7. ansible/_internal/_templating/_template_vars.py +72 -0
  8. ansible/_internal/_templating/_transform.py +6 -0
  9. ansible/_internal/_yaml/_constructor.py +4 -4
  10. ansible/_internal/_yaml/_dumper.py +26 -18
  11. ansible/cli/__init__.py +9 -14
  12. ansible/cli/adhoc.py +6 -3
  13. ansible/cli/arguments/option_helpers.py +1 -1
  14. ansible/cli/console.py +2 -2
  15. ansible/cli/doc.py +4 -4
  16. ansible/cli/inventory.py +5 -7
  17. ansible/config/base.yml +33 -6
  18. ansible/errors/__init__.py +2 -1
  19. ansible/executor/module_common.py +75 -44
  20. ansible/executor/powershell/psrp_put_file.ps1 +1 -1
  21. ansible/executor/process/worker.py +2 -2
  22. ansible/executor/task_executor.py +2 -2
  23. ansible/executor/task_queue_manager.py +34 -70
  24. ansible/executor/task_result.py +1 -1
  25. ansible/galaxy/api.py +3 -6
  26. ansible/galaxy/collection/__init__.py +1 -6
  27. ansible/galaxy/collection/concrete_artifact_manager.py +4 -10
  28. ansible/galaxy/dependency_resolution/providers.py +3 -3
  29. ansible/galaxy/role.py +2 -2
  30. ansible/inventory/group.py +6 -1
  31. ansible/inventory/host.py +6 -1
  32. ansible/module_utils/_internal/__init__.py +7 -4
  33. ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
  34. ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
  35. ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
  36. ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
  37. ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +10 -38
  38. ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
  39. ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
  40. ansible/module_utils/_internal/_datatag/__init__.py +23 -1
  41. ansible/module_utils/_internal/_deprecator.py +39 -34
  42. ansible/module_utils/_internal/_json/_profiles/__init__.py +1 -0
  43. ansible/module_utils/_internal/_messages.py +26 -2
  44. ansible/module_utils/_internal/_plugin_info.py +14 -1
  45. ansible/module_utils/ansible_release.py +1 -1
  46. ansible/module_utils/basic.py +58 -70
  47. ansible/module_utils/common/respawn.py +4 -41
  48. ansible/module_utils/common/yaml.py +1 -1
  49. ansible/module_utils/connection.py +8 -11
  50. ansible/module_utils/csharp/Ansible.Basic.cs +1 -1
  51. ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
  52. ansible/module_utils/facts/hardware/base.py +1 -1
  53. ansible/module_utils/facts/hardware/linux.py +1 -1
  54. ansible/module_utils/facts/other/facter.py +1 -1
  55. ansible/module_utils/facts/sysctl.py +4 -6
  56. ansible/module_utils/facts/system/caps.py +2 -2
  57. ansible/module_utils/facts/system/distribution.py +2 -2
  58. ansible/module_utils/facts/system/local.py +1 -1
  59. ansible/module_utils/facts/virtual/linux.py +1 -1
  60. ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
  61. ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
  62. ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
  63. ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
  64. ansible/module_utils/service.py +1 -1
  65. ansible/module_utils/urls.py +5 -5
  66. ansible/modules/apt.py +9 -3
  67. ansible/modules/apt_repository.py +10 -10
  68. ansible/modules/assemble.py +7 -5
  69. ansible/modules/async_wrapper.py +7 -17
  70. ansible/modules/command.py +3 -3
  71. ansible/modules/copy.py +4 -4
  72. ansible/modules/cron.py +1 -1
  73. ansible/modules/expect.py +5 -5
  74. ansible/modules/file.py +16 -17
  75. ansible/modules/find.py +3 -3
  76. ansible/modules/get_url.py +17 -0
  77. ansible/modules/git.py +9 -7
  78. ansible/modules/hostname.py +2 -2
  79. ansible/modules/known_hosts.py +12 -14
  80. ansible/modules/package.py +6 -0
  81. ansible/modules/pip.py +9 -11
  82. ansible/modules/raw.py +2 -2
  83. ansible/modules/replace.py +2 -2
  84. ansible/modules/slurp.py +10 -13
  85. ansible/modules/stat.py +6 -8
  86. ansible/modules/unarchive.py +6 -6
  87. ansible/modules/user.py +1 -1
  88. ansible/modules/wait_for.py +38 -33
  89. ansible/modules/yum_repository.py +4 -3
  90. ansible/parsing/dataloader.py +2 -2
  91. ansible/parsing/mod_args.py +38 -20
  92. ansible/parsing/vault/__init__.py +9 -13
  93. ansible/playbook/base.py +7 -4
  94. ansible/playbook/helpers.py +1 -1
  95. ansible/playbook/included_file.py +3 -1
  96. ansible/playbook/play_context.py +2 -0
  97. ansible/playbook/playbook_include.py +23 -56
  98. ansible/playbook/role/__init__.py +38 -21
  99. ansible/playbook/taggable.py +19 -5
  100. ansible/playbook/task.py +2 -0
  101. ansible/plugins/action/__init__.py +2 -2
  102. ansible/plugins/action/assemble.py +2 -1
  103. ansible/plugins/action/assert.py +2 -2
  104. ansible/plugins/action/fetch.py +3 -3
  105. ansible/plugins/action/script.py +5 -4
  106. ansible/plugins/action/template.py +9 -3
  107. ansible/plugins/cache/__init__.py +17 -19
  108. ansible/plugins/callback/__init__.py +77 -87
  109. ansible/plugins/callback/default.py +0 -3
  110. ansible/plugins/callback/junit.py +0 -6
  111. ansible/plugins/callback/tree.py +5 -5
  112. ansible/plugins/connection/local.py +4 -4
  113. ansible/plugins/connection/paramiko_ssh.py +5 -5
  114. ansible/plugins/connection/ssh.py +9 -7
  115. ansible/plugins/connection/winrm.py +1 -1
  116. ansible/plugins/filter/core.py +19 -21
  117. ansible/plugins/filter/encryption.py +10 -2
  118. ansible/plugins/filter/pow.yml +1 -1
  119. ansible/plugins/filter/root.yml +1 -1
  120. ansible/plugins/filter/strftime.yml +3 -3
  121. ansible/plugins/filter/to_uuid.yml +1 -1
  122. ansible/plugins/inventory/script.py +1 -1
  123. ansible/plugins/list.py +5 -4
  124. ansible/plugins/loader.py +5 -0
  125. ansible/plugins/lookup/password.py +4 -6
  126. ansible/plugins/lookup/template.py +9 -4
  127. ansible/plugins/shell/powershell.py +3 -2
  128. ansible/plugins/shell/sh.py +3 -2
  129. ansible/plugins/strategy/__init__.py +3 -3
  130. ansible/plugins/test/core.py +2 -2
  131. ansible/release.py +1 -1
  132. ansible/template/__init__.py +9 -53
  133. ansible/utils/collection_loader/_collection_finder.py +3 -3
  134. ansible/utils/display.py +38 -37
  135. ansible/utils/galaxy.py +2 -2
  136. ansible/utils/hashing.py +6 -7
  137. ansible/utils/path.py +6 -8
  138. ansible/utils/py3compat.py +2 -1
  139. ansible/utils/ssh_functions.py +3 -2
  140. ansible/utils/vars.py +4 -1
  141. ansible/vars/manager.py +6 -3
  142. ansible/vars/plugins.py +3 -3
  143. ansible/vars/reserved.py +6 -4
  144. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/METADATA +1 -1
  145. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/RECORD +184 -173
  146. ansible_test/_internal/__init__.py +5 -0
  147. ansible_test/_internal/ansible_util.py +1 -1
  148. ansible_test/_internal/classification/python.py +6 -0
  149. ansible_test/_internal/cli/commands/__init__.py +0 -5
  150. ansible_test/_internal/cli/environments.py +51 -5
  151. ansible_test/_internal/commands/coverage/__init__.py +1 -1
  152. ansible_test/_internal/commands/integration/__init__.py +18 -5
  153. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  154. ansible_test/_internal/commands/integration/coverage.py +7 -2
  155. ansible_test/_internal/commands/sanity/__init__.py +3 -1
  156. ansible_test/_internal/commands/sanity/integration_aliases.py +11 -0
  157. ansible_test/_internal/commands/shell/__init__.py +43 -4
  158. ansible_test/_internal/commands/units/__init__.py +4 -1
  159. ansible_test/_internal/config.py +21 -13
  160. ansible_test/_internal/debugging.py +166 -0
  161. ansible_test/_internal/delegation.py +21 -13
  162. ansible_test/_internal/host_profiles.py +259 -16
  163. ansible_test/_internal/inventory.py +4 -0
  164. ansible_test/_internal/metadata.py +94 -4
  165. ansible_test/_internal/processes.py +80 -0
  166. ansible_test/_internal/provisioning.py +10 -4
  167. ansible_test/_internal/python_requirements.py +27 -0
  168. ansible_test/_internal/ssh.py +1 -5
  169. ansible_test/_internal/target.py +8 -0
  170. ansible_test/_internal/thread.py +2 -1
  171. ansible_test/_internal/timeout.py +1 -1
  172. ansible_test/_internal/util.py +20 -12
  173. ansible_test/_internal/util_common.py +13 -3
  174. ansible_test/_util/target/injector/python.py +8 -0
  175. ansible_test/_util/target/setup/requirements.py +3 -9
  176. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/WHEEL +0 -0
  177. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/entry_points.txt +0 -0
  178. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/COPYING +0 -0
  179. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  180. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  181. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  182. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  183. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  184. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b7.dist-info}/top_level.txt +0 -0
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)
@@ -19,7 +19,7 @@ from jinja2.compiler import Frame
19
19
  from jinja2.lexer import TOKEN_VARIABLE_BEGIN, TOKEN_VARIABLE_END, TOKEN_STRING, Lexer
20
20
  from jinja2.nativetypes import NativeCodeGenerator
21
21
  from jinja2.nodes import Const, EvalContext
22
- from jinja2.runtime import Context
22
+ from jinja2.runtime import Context, Macro
23
23
  from jinja2.sandbox import ImmutableSandboxedEnvironment
24
24
  from jinja2.utils import missing, LRUCache
25
25
 
@@ -78,6 +78,21 @@ The values following this prefix up to the first newline are parsed as Jinja2 te
78
78
  To include this literal value at the start of a string, a space or other character must precede it.
79
79
  """
80
80
 
81
+ JINJA_KEYWORDS = frozenset(
82
+ {
83
+ # scalar singletons (see jinja2.nodes.Name.can_assign)
84
+ 'true',
85
+ 'false',
86
+ 'none',
87
+ 'True',
88
+ 'False',
89
+ 'None',
90
+ # other
91
+ 'not', # unary operator always applicable to names
92
+ }
93
+ )
94
+ """Names which have special meaning to Jinja and cannot be resolved as variable names."""
95
+
81
96
  display = Display()
82
97
 
83
98
 
@@ -799,11 +814,14 @@ class AnsibleEnvironment(ImmutableSandboxedEnvironment):
799
814
  # Performing either before calling them will interfere with that processing.
800
815
  return super().call(__context, __obj, *args, **kwargs)
801
816
 
802
- if (first_marker := get_first_marker_arg(args, kwargs)) is not None:
817
+ # Jinja's generated macro code handles Markers, so preemptive raise on Marker args and lazy retrieval should be disabled for the macro invocation.
818
+ is_macro = isinstance(__obj, Macro)
819
+
820
+ if not is_macro and (first_marker := get_first_marker_arg(args, kwargs)) is not None:
803
821
  return first_marker
804
822
 
805
823
  try:
806
- with JinjaCallContext(accept_lazy_markers=False):
824
+ with JinjaCallContext(accept_lazy_markers=is_macro):
807
825
  call_res = super().call(__context, __obj, *lazify_container_args(args), **lazify_container_kwargs(kwargs))
808
826
 
809
827
  if __obj is range:
@@ -826,7 +844,7 @@ _sentinel: t.Final[object] = object()
826
844
 
827
845
 
828
846
  @_DirectCall.mark
829
- def _undef(hint=None):
847
+ def _undef(hint: str | None = None) -> UndefinedMarker:
830
848
  """Jinja2 global function (undef) for creating getting a `UndefinedMarker` instance, optionally with a custom hint."""
831
849
  validate_arg_type('hint', hint, (str, type(None)))
832
850
 
@@ -96,7 +96,7 @@ class Marker(StrictUndefined, Tripwire):
96
96
  return AnsibleUndefinedVariable(self._undefined_message, obj=self._marker_template_source)
97
97
 
98
98
  def _as_message(self) -> str:
99
- """Return the error message to show when this marker must be represented as a string, such as for subsitutions or warnings."""
99
+ """Return the error message to show when this marker must be represented as a string, such as for substitutions or warnings."""
100
100
  return self._undefined_message
101
101
 
102
102
  def _fail_with_undefined_error(self, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
@@ -128,6 +128,9 @@ class JinjaPluginIntercept(c.MutableMapping):
128
128
  def wrapper(*args, **kwargs) -> bool | Marker:
129
129
  result = self._invoke_plugin(instance, *args, **kwargs)
130
130
 
131
+ if isinstance(result, Marker):
132
+ return result
133
+
131
134
  if not isinstance(result, bool):
132
135
  template = TemplateContext.current().template_value
133
136
 
@@ -160,7 +163,7 @@ class JinjaPluginIntercept(c.MutableMapping):
160
163
  class _DirectCall:
161
164
  """Functions/methods marked `_DirectCall` bypass Jinja Environment checks for `Marker`."""
162
165
 
163
- _marker_attr: str = "_directcall"
166
+ _marker_attr: t.Final[str] = "_directcall"
164
167
 
165
168
  @classmethod
166
169
  def mark(cls, src: _TCallable) -> _TCallable:
@@ -169,7 +172,7 @@ class _DirectCall:
169
172
 
170
173
  @classmethod
171
174
  def is_marked(cls, value: t.Callable) -> bool:
172
- return callable(value) and getattr(value, "_directcall", False)
175
+ return callable(value) and getattr(value, cls._marker_attr, False)
173
176
 
174
177
 
175
178
  @_DirectCall.mark
@@ -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
@@ -21,6 +21,11 @@ def plugin_info(value: _messages.PluginInfo) -> dict[str, str]:
21
21
  return dataclasses.asdict(value)
22
22
 
23
23
 
24
+ def plugin_type(value: _messages.PluginType) -> str:
25
+ """Render PluginType as a string."""
26
+ return value.value
27
+
28
+
24
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
31
  if _traceback._is_traceback_enabled(_traceback.TracebackEvent.ERROR):
@@ -56,6 +61,7 @@ def encrypted_string(value: EncryptedString) -> str | VaultExceptionMarker:
56
61
  _type_transform_mapping: dict[type, t.Callable[[t.Any], t.Any]] = {
57
62
  _captured.CapturedErrorSummary: error_summary,
58
63
  _messages.PluginInfo: plugin_info,
64
+ _messages.PluginType: plugin_type,
59
65
  _messages.ErrorSummary: error_summary,
60
66
  _messages.WarningSummary: warning_summary,
61
67
  _messages.DeprecationSummary: deprecation_summary,
@@ -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
- # DTFIX0: 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:
ansible/cli/__init__.py CHANGED
@@ -74,7 +74,7 @@ def initialize_locale():
74
74
 
75
75
  initialize_locale()
76
76
 
77
- import errno
77
+
78
78
  import getpass
79
79
  import subprocess
80
80
  import traceback
@@ -212,9 +212,9 @@ class CLI(ABC):
212
212
  # used by --vault-id and --vault-password-file
213
213
  vault_ids.append(id_slug)
214
214
 
215
- # if an action needs an encrypt password (create_new_password=True) and we dont
215
+ # if an action needs an encrypt password (create_new_password=True) and we don't
216
216
  # have other secrets setup, then automatically add a password prompt as well.
217
- # prompts cant/shouldnt work without a tty, so dont add prompt secrets
217
+ # prompts can't/shouldn't work without a tty, so don't add prompt secrets
218
218
  if ask_vault_pass or (not vault_ids and auto_prompt):
219
219
 
220
220
  id_slug = u'%s@%s' % (C.DEFAULT_VAULT_IDENTITY, u'prompt_ask_vault_pass')
@@ -525,9 +525,7 @@ class CLI(ABC):
525
525
  try:
526
526
  cmd = subprocess.Popen(CLI.PAGER, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout)
527
527
  cmd.communicate(input=to_bytes(text))
528
- except IOError:
529
- pass
530
- except KeyboardInterrupt:
528
+ except (OSError, KeyboardInterrupt):
531
529
  pass
532
530
 
533
531
  def _play_prereqs(self):
@@ -632,8 +630,8 @@ class CLI(ABC):
632
630
  try:
633
631
  with open(b_pwd_file, "rb") as password_file:
634
632
  secret = password_file.read().strip()
635
- except (OSError, IOError) as e:
636
- 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
637
635
 
638
636
  secret = secret.strip(b'\r\n')
639
637
 
@@ -652,12 +650,9 @@ class CLI(ABC):
652
650
 
653
651
  ansible_dir = Path(C.ANSIBLE_HOME).expanduser()
654
652
  try:
655
- ansible_dir.mkdir(mode=0o700)
656
- except OSError as exc:
657
- if exc.errno != errno.EEXIST:
658
- display.warning(
659
- "Failed to create the directory '%s': %s" % (ansible_dir, to_text(exc, errors='surrogate_or_replace'))
660
- )
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)
661
656
  else:
662
657
  display.debug("Created the '%s' directory" % ansible_dir)
663
658
 
ansible/cli/adhoc.py CHANGED
@@ -88,8 +88,11 @@ class AdHocCLI(CLI):
88
88
  if not module_args:
89
89
  module_args = parse_kv(module_args_raw, check_raw=check_raw)
90
90
 
91
- mytask = {'action': {'module': context.CLIARGS['module_name'], 'args': module_args},
92
- 'timeout': context.CLIARGS['task_timeout']}
91
+ mytask = dict(
92
+ action=context.CLIARGS['module_name'],
93
+ args=module_args,
94
+ timeout=context.CLIARGS['task_timeout'],
95
+ )
93
96
 
94
97
  mytask = Origin(description=f'<adhoc {context.CLIARGS["module_name"]!r} task>').tag(mytask)
95
98
 
@@ -184,7 +187,7 @@ class AdHocCLI(CLI):
184
187
  variable_manager=variable_manager,
185
188
  loader=loader,
186
189
  passwords=passwords,
187
- stdout_callback=cb,
190
+ stdout_callback_name=cb,
188
191
  run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS,
189
192
  run_tree=run_tree,
190
193
  forks=context.CLIARGS['forks'],
@@ -243,7 +243,7 @@ def _git_repo_info(repo_path):
243
243
  repo_path = gitdir
244
244
  else:
245
245
  repo_path = os.path.join(repo_path[:-4], gitdir)
246
- except (IOError, AttributeError):
246
+ except (OSError, AttributeError):
247
247
  return ''
248
248
  with open(os.path.join(repo_path, "HEAD")) as f:
249
249
  line = f.readline().rstrip("\n")
ansible/cli/console.py CHANGED
@@ -222,7 +222,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
222
222
  variable_manager=self.variable_manager,
223
223
  loader=self.loader,
224
224
  passwords=self.passwords,
225
- stdout_callback=cb,
225
+ stdout_callback_name=cb,
226
226
  run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS,
227
227
  run_tree=False,
228
228
  forks=self.forks,
@@ -573,7 +573,7 @@ class ConsoleCLI(CLI, cmd.Cmd):
573
573
  histfile = os.path.join(os.path.expanduser("~"), ".ansible-console_history")
574
574
  try:
575
575
  readline.read_history_file(histfile)
576
- except IOError:
576
+ except OSError:
577
577
  pass
578
578
 
579
579
  atexit.register(readline.write_history_file, histfile)
ansible/cli/doc.py CHANGED
@@ -137,8 +137,8 @@ class RoleMixin(object):
137
137
  data = yaml.load(trust_as_template(f), Loader=AnsibleLoader)
138
138
  if data is None:
139
139
  data = {}
140
- except (IOError, OSError) as ex:
141
- raise AnsibleParserError(f"Could not read the role {role_name!r} (at {path}).") from ex
140
+ except OSError as ex:
141
+ raise AnsibleParserError(f"Could not read the role {role_name!r} at {path!r}.") from ex
142
142
 
143
143
  return data
144
144
 
@@ -1309,7 +1309,7 @@ class DocCLI(CLI, RoleMixin):
1309
1309
  if ignore in item:
1310
1310
  del item[ignore]
1311
1311
 
1312
- # reformat cli optoins
1312
+ # reformat cli options
1313
1313
  if 'cli' in opt and opt['cli']:
1314
1314
  conf['cli'] = []
1315
1315
  for cli in opt['cli']:
@@ -1440,7 +1440,7 @@ class DocCLI(CLI, RoleMixin):
1440
1440
  pad = display.columns * 0.20
1441
1441
  limit = max(display.columns - int(pad), 70)
1442
1442
 
1443
- text.append("> %s %s (%s)" % (plugin_type.upper(), _format(doc.pop('plugin_name'), 'bold'), doc.pop('filename')))
1443
+ text.append("> %s %s (%s)" % (plugin_type.upper(), _format(doc.pop('plugin_name'), 'bold'), doc.pop('filename') or 'Jinja2'))
1444
1444
 
1445
1445
  if isinstance(doc['description'], list):
1446
1446
  descs = doc.pop('description')
ansible/cli/inventory.py CHANGED
@@ -14,13 +14,12 @@ import sys
14
14
  import typing as t
15
15
 
16
16
  import argparse
17
- import functools
18
17
 
19
18
  from ansible import constants as C
20
19
  from ansible import context
21
20
  from ansible.cli.arguments import option_helpers as opt_help
22
21
  from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleRuntimeError
23
- from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
22
+ from ansible.module_utils.common.text.converters import to_bytes, to_text
24
23
  from ansible._internal._json._profiles import _inventory_legacy
25
24
  from ansible.utils.vars import combine_vars
26
25
  from ansible.utils.display import Display
@@ -152,8 +151,8 @@ class InventoryCLI(CLI):
152
151
  try:
153
152
  with open(to_bytes(outfile), 'wb') as f:
154
153
  f.write(to_bytes(results))
155
- except (OSError, IOError) as e:
156
- raise AnsibleError('Unable to write to destination file (%s): %s' % (to_native(outfile), to_native(e)))
154
+ except OSError as ex:
155
+ raise AnsibleError(f'Unable to write to destination file {outfile!r}.') from ex
157
156
  sys.exit(0)
158
157
 
159
158
  sys.exit(1)
@@ -162,11 +161,10 @@ class InventoryCLI(CLI):
162
161
  def dump(stuff):
163
162
  if context.CLIARGS['yaml']:
164
163
  import yaml
164
+
165
165
  from ansible.parsing.yaml.dumper import AnsibleDumper
166
166
 
167
- # DTFIX0: need shared infra to smuggle custom kwargs to dumpers, since yaml.dump cannot (as of PyYAML 6.0.1)
168
- dumper = functools.partial(AnsibleDumper, dump_vault_tags=True)
169
- results = to_text(yaml.dump(stuff, Dumper=dumper, default_flow_style=False, allow_unicode=True))
167
+ results = to_text(yaml.dump(stuff, Dumper=AnsibleDumper, default_flow_style=False, allow_unicode=True))
170
168
  elif context.CLIARGS['toml']:
171
169
  results = toml_dumps(stuff)
172
170
  else: