ansible-core 2.19.0b3__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 (174) hide show
  1. ansible/_internal/__init__.py +2 -2
  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 +6 -6
  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/_testing.py +26 -0
  26. ansible/_internal/_yaml/_dumper.py +1 -1
  27. ansible/_internal/_yaml/_errors.py +7 -7
  28. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
  29. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
  30. ansible/cli/__init__.py +5 -82
  31. ansible/cli/arguments/option_helpers.py +8 -5
  32. ansible/cli/doc.py +84 -28
  33. ansible/cli/inventory.py +1 -1
  34. ansible/compat/importlib_resources.py +9 -12
  35. ansible/config/base.yml +27 -23
  36. ansible/config/manager.py +142 -101
  37. ansible/constants.py +1 -1
  38. ansible/errors/__init__.py +96 -49
  39. ansible/executor/module_common.py +8 -10
  40. ansible/executor/powershell/async_watchdog.ps1 +2 -2
  41. ansible/executor/powershell/async_wrapper.ps1 +3 -3
  42. ansible/executor/powershell/become_wrapper.ps1 +20 -2
  43. ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
  44. ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
  45. ansible/executor/powershell/exec_wrapper.ps1 +219 -6
  46. ansible/executor/powershell/module_manifest.py +52 -0
  47. ansible/executor/powershell/module_wrapper.ps1 +47 -21
  48. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  49. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  50. ansible/executor/process/worker.py +38 -113
  51. ansible/executor/task_executor.py +26 -61
  52. ansible/executor/task_result.py +2 -4
  53. ansible/galaxy/collection/__init__.py +1 -4
  54. ansible/inventory/manager.py +1 -0
  55. ansible/module_utils/_internal/__init__.py +0 -3
  56. ansible/module_utils/_internal/_ambient_context.py +3 -3
  57. ansible/module_utils/_internal/_ansiballz.py +4 -2
  58. ansible/module_utils/_internal/_datatag/__init__.py +20 -14
  59. ansible/module_utils/_internal/_datatag/_tags.py +2 -2
  60. ansible/module_utils/_internal/_deprecator.py +66 -48
  61. ansible/module_utils/_internal/_errors.py +88 -17
  62. ansible/module_utils/_internal/_event_utils.py +61 -0
  63. ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
  64. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
  65. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
  66. ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
  67. ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
  68. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
  69. ansible/module_utils/_internal/_plugin_info.py +1 -1
  70. ansible/module_utils/_internal/_stack.py +22 -0
  71. ansible/module_utils/_internal/_text_utils.py +6 -0
  72. ansible/module_utils/_internal/_traceback.py +11 -8
  73. ansible/module_utils/ansible_release.py +1 -1
  74. ansible/module_utils/basic.py +49 -15
  75. ansible/module_utils/common/arg_spec.py +2 -2
  76. ansible/module_utils/common/collections.py +6 -0
  77. ansible/module_utils/common/json.py +2 -2
  78. ansible/module_utils/common/text/converters.py +3 -3
  79. ansible/module_utils/common/validation.py +1 -1
  80. ansible/module_utils/common/warnings.py +80 -23
  81. ansible/module_utils/common/yaml.py +1 -1
  82. ansible/module_utils/datatag.py +5 -2
  83. ansible/module_utils/facts/system/distribution.py +16 -3
  84. ansible/module_utils/facts/virtual/linux.py +2 -2
  85. ansible/module_utils/parsing/convert_bool.py +6 -0
  86. ansible/module_utils/service.py +2 -9
  87. ansible/modules/apt_repository.py +7 -29
  88. ansible/modules/assemble.py +4 -4
  89. ansible/modules/async_status.py +13 -11
  90. ansible/modules/async_wrapper.py +5 -5
  91. ansible/modules/cron.py +3 -5
  92. ansible/modules/dnf5.py +15 -22
  93. ansible/modules/git.py +1 -6
  94. ansible/modules/hostname.py +0 -1
  95. ansible/modules/pip.py +2 -4
  96. ansible/modules/service.py +3 -9
  97. ansible/modules/sysvinit.py +3 -3
  98. ansible/parsing/ajson.py +3 -5
  99. ansible/parsing/dataloader.py +4 -4
  100. ansible/parsing/mod_args.py +1 -1
  101. ansible/parsing/plugin_docs.py +2 -2
  102. ansible/parsing/utils/yaml.py +3 -3
  103. ansible/parsing/vault/__init__.py +4 -4
  104. ansible/playbook/playbook_include.py +1 -1
  105. ansible/playbook/taggable.py +0 -3
  106. ansible/plugins/__init__.py +0 -25
  107. ansible/plugins/action/__init__.py +9 -32
  108. ansible/plugins/action/add_host.py +1 -1
  109. ansible/plugins/action/assemble.py +8 -16
  110. ansible/plugins/action/async_status.py +7 -2
  111. ansible/plugins/action/copy.py +8 -7
  112. ansible/plugins/action/gather_facts.py +8 -8
  113. ansible/plugins/action/package.py +5 -8
  114. ansible/plugins/action/script.py +8 -15
  115. ansible/plugins/action/service.py +3 -7
  116. ansible/plugins/action/template.py +6 -8
  117. ansible/plugins/action/unarchive.py +5 -15
  118. ansible/plugins/action/uri.py +9 -20
  119. ansible/plugins/callback/__init__.py +4 -6
  120. ansible/plugins/callback/junit.py +4 -2
  121. ansible/plugins/connection/local.py +2 -2
  122. ansible/plugins/connection/ssh.py +17 -9
  123. ansible/plugins/connection/winrm.py +5 -2
  124. ansible/plugins/doc_fragments/constructed.py +2 -2
  125. ansible/plugins/filter/core.py +13 -6
  126. ansible/plugins/filter/encryption.py +4 -4
  127. ansible/plugins/inventory/__init__.py +11 -10
  128. ansible/plugins/inventory/script.py +1 -1
  129. ansible/plugins/list.py +69 -16
  130. ansible/plugins/loader.py +10 -9
  131. ansible/plugins/lookup/csvfile.py +16 -71
  132. ansible/plugins/lookup/first_found.py +2 -1
  133. ansible/plugins/shell/__init__.py +56 -2
  134. ansible/plugins/shell/powershell.py +66 -9
  135. ansible/plugins/shell/sh.py +9 -5
  136. ansible/plugins/test/core.py +21 -15
  137. ansible/plugins/test/finished.yml +1 -1
  138. ansible/plugins/test/uri.py +2 -5
  139. ansible/release.py +1 -1
  140. ansible/template/__init__.py +30 -2
  141. ansible/utils/collection_loader/__init__.py +2 -0
  142. ansible/utils/display.py +107 -128
  143. ansible/utils/hashing.py +0 -1
  144. ansible/utils/listify.py +6 -4
  145. ansible/utils/plugin_docs.py +2 -1
  146. ansible/utils/unsafe_proxy.py +1 -1
  147. ansible/vars/hostvars.py +1 -1
  148. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +3 -2
  149. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +173 -161
  150. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
  151. ansible_test/_data/completion/docker.txt +3 -3
  152. ansible_test/_data/completion/remote.txt +1 -0
  153. ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
  154. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  155. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  156. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  157. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  158. ansible_test/_internal/util.py +20 -0
  159. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
  160. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
  161. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
  162. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  163. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  164. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +73 -8
  165. ansible_test/_util/target/setup/bootstrap.sh +31 -0
  166. ansible/_internal/_errors/_utils.py +0 -310
  167. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
  168. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses}/COPYING +0 -0
  169. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/Apache-License.txt +0 -0
  170. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/BSD-3-Clause.txt +0 -0
  171. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/MIT-license.txt +0 -0
  172. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/PSF-license.txt +0 -0
  173. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info/licenses/licenses}/simplified_bsd.txt +0 -0
  174. {ansible_core-2.19.0b3.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
@@ -103,76 +103,26 @@ RETURN = """
103
103
  elements: str
104
104
  """
105
105
 
106
- import codecs
107
106
  import csv
108
107
 
109
108
  from collections.abc import MutableSequence
110
109
 
111
- from ansible.errors import AnsibleError, AnsibleAssertionError
110
+ from ansible.errors import AnsibleError
112
111
  from ansible.parsing.splitter import parse_kv
113
112
  from ansible.plugins.lookup import LookupBase
114
- from ansible.module_utils.six import PY2
115
- from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
116
-
117
-
118
- class CSVRecoder:
119
- """
120
- Iterator that reads an encoded stream and encodes the input to UTF-8
121
- """
122
- def __init__(self, f, encoding='utf-8'):
123
- self.reader = codecs.getreader(encoding)(f)
124
-
125
- def __iter__(self):
126
- return self
127
-
128
- def __next__(self):
129
- return next(self.reader).encode("utf-8")
130
-
131
- next = __next__ # For Python 2
132
-
133
-
134
- class CSVReader:
135
- """
136
- A CSV reader which will iterate over lines in the CSV file "f",
137
- which is encoded in the given encoding.
138
- """
139
-
140
- def __init__(self, f, dialect=csv.excel, encoding='utf-8', **kwds):
141
- if PY2:
142
- f = CSVRecoder(f, encoding)
143
- else:
144
- f = codecs.getreader(encoding)(f)
145
-
146
- self.reader = csv.reader(f, dialect=dialect, **kwds)
147
-
148
- def __next__(self):
149
- row = next(self.reader)
150
- return [to_text(s) for s in row]
151
-
152
- next = __next__ # For Python 2
153
-
154
- def __iter__(self):
155
- return self
156
113
 
157
114
 
158
115
  class LookupModule(LookupBase):
159
116
 
160
117
  def read_csv(self, filename, key, delimiter, encoding='utf-8', dflt=None, col=1, keycol=0):
161
-
162
- try:
163
- f = open(to_bytes(filename), 'rb')
164
- creader = CSVReader(f, delimiter=to_native(delimiter), encoding=encoding)
165
-
166
- for row in creader:
167
- if len(row) and row[keycol] == key:
118
+ with open(filename, encoding=encoding) as f:
119
+ for row in csv.reader(f, dialect=csv.excel, delimiter=delimiter):
120
+ if row and row[keycol] == key:
168
121
  return row[col]
169
- except Exception as e:
170
- raise AnsibleError("csvfile: %s" % to_native(e))
171
122
 
172
123
  return dflt
173
124
 
174
125
  def run(self, terms, variables=None, **kwargs):
175
-
176
126
  ret = []
177
127
 
178
128
  self.set_options(var_options=variables, direct=kwargs)
@@ -192,23 +142,19 @@ class LookupModule(LookupBase):
192
142
  key = kv['_raw_params']
193
143
 
194
144
  # parameters override per term using k/v
195
- try:
196
- reset_params = False
197
- for name, value in kv.items():
198
- if name == '_raw_params':
199
- continue
200
- if name not in paramvals:
201
- raise AnsibleAssertionError('%s is not a valid option' % name)
202
-
203
- self._deprecate_inline_kv()
204
- self.set_option(name, value)
205
- reset_params = True
145
+ reset_params = False
146
+ for name, value in kv.items():
147
+ if name == '_raw_params':
148
+ continue
149
+ if name not in paramvals:
150
+ raise ValueError(f'{name!r} is not a valid option')
206
151
 
207
- if reset_params:
208
- paramvals = self.get_options()
152
+ self._deprecate_inline_kv()
153
+ self.set_option(name, value)
154
+ reset_params = True
209
155
 
210
- except (ValueError, AssertionError) as e:
211
- raise AnsibleError(e)
156
+ if reset_params:
157
+ paramvals = self.get_options()
212
158
 
213
159
  # default is just placeholder for real tab
214
160
  if paramvals['delimiter'] == 'TAB':
@@ -218,8 +164,7 @@ class LookupModule(LookupBase):
218
164
  var = self.read_csv(lookupfile, key, paramvals['delimiter'], paramvals['encoding'], paramvals['default'], paramvals['col'], paramvals['keycol'])
219
165
  if var is not None:
220
166
  if isinstance(var, MutableSequence):
221
- for v in var:
222
- ret.append(v)
167
+ ret.extend(var)
223
168
  else:
224
169
  ret.append(var)
225
170
 
@@ -149,6 +149,7 @@ from ansible.errors import AnsibleError
149
149
  from ansible.plugins.lookup import LookupBase
150
150
  from ansible._internal._templating import _jinja_common
151
151
  from ansible._internal._templating import _jinja_plugins
152
+ from ansible import template as _template
152
153
  from ansible.utils.path import unfrackpath
153
154
  from ansible.utils.display import Display
154
155
  from ansible.module_utils.datatag import native_type_name
@@ -222,7 +223,7 @@ class LookupModule(LookupBase):
222
223
  return total_search
223
224
 
224
225
  def run(self, terms: list, variables=None, **kwargs):
225
- if (first_marker := _jinja_common.get_first_marker_arg((), kwargs)) is not None:
226
+ if (first_marker := _template.get_first_marker_arg((), kwargs)) is not None:
226
227
  first_marker.trip()
227
228
 
228
229
  if _jinja_plugins._LookupContext.current().invoked_as_with:
@@ -16,6 +16,7 @@
16
16
  # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
17
17
  from __future__ import annotations
18
18
 
19
+ import dataclasses
19
20
  import os
20
21
  import os.path
21
22
  import re
@@ -33,6 +34,13 @@ from ansible.plugins import AnsiblePlugin
33
34
  _USER_HOME_PATH_RE = re.compile(r'^~[_.A-Za-z0-9][-_.A-Za-z0-9]*$')
34
35
 
35
36
 
37
+ @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
38
+ class _ShellCommand:
39
+ """Internal type returned by shell subsystems that may require both an execution payload and a command (eg powershell)."""
40
+ command: str
41
+ input_data: bytes | None = None
42
+
43
+
36
44
  class ShellBase(AnsiblePlugin):
37
45
  def __init__(self):
38
46
 
@@ -121,7 +129,13 @@ class ShellBase(AnsiblePlugin):
121
129
  cmd = ['test', '-e', self.quote(path)]
122
130
  return ' '.join(cmd)
123
131
 
124
- def mkdtemp(self, basefile=None, system=False, mode=0o700, tmpdir=None):
132
+ def mkdtemp(
133
+ self,
134
+ basefile: str | None = None,
135
+ system: bool = False,
136
+ mode: int = 0o700,
137
+ tmpdir: str | None = None,
138
+ ) -> str:
125
139
  if not basefile:
126
140
  basefile = self.__class__._generate_temp_dir_name()
127
141
 
@@ -163,7 +177,31 @@ class ShellBase(AnsiblePlugin):
163
177
 
164
178
  return cmd
165
179
 
166
- def expand_user(self, user_home_path, username=''):
180
+ def _mkdtemp2(
181
+ self,
182
+ basefile: str | None = None,
183
+ system: bool = False,
184
+ mode: int = 0o700,
185
+ tmpdir: str | None = None,
186
+ ) -> _ShellCommand:
187
+ """Gets command info to create a temporary directory.
188
+
189
+ This is an internal API that should not be used publicly.
190
+
191
+ :args basefile: The base name of the temporary directory.
192
+ :args system: If True, create the directory in a system-wide location.
193
+ :args mode: The permissions mode for the directory.
194
+ :args tmpdir: The directory in which to create the temporary directory.
195
+ :returns: The shell command to run to create the temp directory.
196
+ """
197
+ cmd = self.mkdtemp(basefile=basefile, system=system, mode=mode, tmpdir=tmpdir)
198
+ return _ShellCommand(command=cmd, input_data=None)
199
+
200
+ def expand_user(
201
+ self,
202
+ user_home_path: str,
203
+ username: str = '',
204
+ ) -> str:
167
205
  """ Return a command to expand tildes in a path
168
206
 
169
207
  It can be either "~" or "~username". We just ignore $HOME
@@ -184,6 +222,22 @@ class ShellBase(AnsiblePlugin):
184
222
 
185
223
  return 'echo %s' % user_home_path
186
224
 
225
+ def _expand_user2(
226
+ self,
227
+ user_home_path: str,
228
+ username: str = '',
229
+ ) -> _ShellCommand:
230
+ """Gets command to expand user path.
231
+
232
+ This is an internal API that should not be used publicly.
233
+
234
+ :args user_home_path: The path to expand.
235
+ :args username: The username to use for expansion.
236
+ :returns: The shell command to run to get the expanded user path.
237
+ """
238
+ cmd = self.expand_user(user_home_path, username=username)
239
+ return _ShellCommand(command=cmd, input_data=None)
240
+
187
241
  def pwd(self):
188
242
  """Return the working directory after connecting"""
189
243
  return 'echo %spwd%s' % (self._SHELL_SUB_LEFT, self._SHELL_SUB_RIGHT)
@@ -21,9 +21,13 @@ import shlex
21
21
  import xml.etree.ElementTree as ET
22
22
  import ntpath
23
23
 
24
- from ansible.executor.powershell.module_manifest import _get_powershell_script
24
+ from ansible.executor.powershell.module_manifest import _bootstrap_powershell_script, _get_powershell_script
25
25
  from ansible.module_utils.common.text.converters import to_bytes, to_text
26
- from ansible.plugins.shell import ShellBase
26
+ from ansible.plugins.shell import ShellBase, _ShellCommand
27
+ from ansible.utils.display import Display
28
+
29
+
30
+ display = Display()
27
31
 
28
32
  # This is weird, we are matching on byte sequences that match the utf-16-be
29
33
  # matches for '_x(a-fA-F0-9){4}_'. The \x00 and {4} will match the hex sequence
@@ -225,9 +229,15 @@ class ShellModule(ShellBase):
225
229
  else:
226
230
  return self._encode_script("""Remove-Item '%s' -Force;""" % path)
227
231
 
228
- def mkdtemp(self, basefile=None, system=False, mode=None, tmpdir=None):
229
- # Windows does not have an equivalent for the system temp files, so
230
- # the param is ignored
232
+ def mkdtemp(
233
+ self,
234
+ basefile: str | None = None,
235
+ system: bool = False,
236
+ mode: int = 0o700,
237
+ tmpdir: str | None = None,
238
+ ) -> str:
239
+ # This is not called in Ansible anymore but it is kept for backwards
240
+ # compatibility in case other action plugins outside Ansible calls this.
231
241
  if not basefile:
232
242
  basefile = self.__class__._generate_temp_dir_name()
233
243
  basefile = self._escape(self._unquote(basefile))
@@ -241,10 +251,38 @@ class ShellModule(ShellBase):
241
251
  """
242
252
  return self._encode_script(script.strip())
243
253
 
244
- def expand_user(self, user_home_path, username=''):
245
- # PowerShell only supports "~" (not "~username"). Resolve-Path ~ does
246
- # not seem to work remotely, though by default we are always starting
247
- # in the user's home directory.
254
+ def _mkdtemp2(
255
+ self,
256
+ basefile: str | None = None,
257
+ system: bool = False,
258
+ mode: int = 0o700,
259
+ tmpdir: str | None = None,
260
+ ) -> _ShellCommand:
261
+ # Windows does not have an equivalent for the system temp files, so
262
+ # the param is ignored
263
+ if not basefile:
264
+ basefile = self.__class__._generate_temp_dir_name()
265
+
266
+ basefile = self._unquote(basefile)
267
+ basetmpdir = tmpdir if tmpdir else self.get_option('remote_tmp')
268
+
269
+ script, stdin = _bootstrap_powershell_script("powershell_mkdtemp.ps1", {
270
+ 'Directory': basetmpdir,
271
+ 'Name': basefile,
272
+ })
273
+
274
+ return _ShellCommand(
275
+ command=self._encode_script(script),
276
+ input_data=stdin,
277
+ )
278
+
279
+ def expand_user(
280
+ self,
281
+ user_home_path: str,
282
+ username: str = '',
283
+ ) -> str:
284
+ # This is not called in Ansible anymore but it is kept for backwards
285
+ # compatibility in case other actions plugins outside Ansible called this.
248
286
  user_home_path = self._unquote(user_home_path)
249
287
  if user_home_path == '~':
250
288
  script = 'Write-Output (Get-Location).Path'
@@ -254,6 +292,21 @@ class ShellModule(ShellBase):
254
292
  script = "Write-Output '%s'" % self._escape(user_home_path)
255
293
  return self._encode_script(f"{self._CONSOLE_ENCODING}; {script}")
256
294
 
295
+ def _expand_user2(
296
+ self,
297
+ user_home_path: str,
298
+ username: str = '',
299
+ ) -> _ShellCommand:
300
+ user_home_path = self._unquote(user_home_path)
301
+ script, stdin = _bootstrap_powershell_script("powershell_expand_user.ps1", {
302
+ 'Path': user_home_path,
303
+ })
304
+
305
+ return _ShellCommand(
306
+ command=self._encode_script(script),
307
+ input_data=stdin,
308
+ )
309
+
257
310
  def exists(self, path):
258
311
  path = self._escape(self._unquote(path))
259
312
  script = """
@@ -271,6 +324,10 @@ class ShellModule(ShellBase):
271
324
  return self._encode_script(script)
272
325
 
273
326
  def checksum(self, path, *args, **kwargs):
327
+ display.deprecated(
328
+ "The 'ShellModule.checksum' method is deprecated. Use 'ActionBase._execute_remote_stat()' instead.",
329
+ version="2.23"
330
+ )
274
331
  path = self._escape(self._unquote(path))
275
332
  script = """
276
333
  If (Test-Path -PathType Leaf '%(path)s')
@@ -14,6 +14,10 @@ extends_documentation_fragment:
14
14
  """
15
15
 
16
16
  from ansible.plugins.shell import ShellBase
17
+ from ansible.utils.display import Display
18
+
19
+
20
+ display = Display()
17
21
 
18
22
 
19
23
  class ShellModule(ShellBase):
@@ -43,6 +47,10 @@ class ShellModule(ShellBase):
43
47
  _SHELL_GROUP_RIGHT = ')'
44
48
 
45
49
  def checksum(self, path, python_interp):
50
+ display.deprecated(
51
+ "The 'ShellModule.checksum' method is deprecated. Use 'ActionBase._execute_remote_stat()' instead.",
52
+ version="2.23"
53
+ )
46
54
  # In the following test, each condition is a check and logical
47
55
  # comparison (|| or &&) that sets the rc value. Every check is run so
48
56
  # the last check in the series to fail will be the rc that is returned.
@@ -67,11 +75,7 @@ class ShellModule(ShellBase):
67
75
  # "one-liner".
68
76
  shell_escaped_path = self.quote(path)
69
77
  test = "rc=flag; [ -r %(p)s ] %(shell_or)s rc=2; [ -f %(p)s ] %(shell_or)s rc=1; [ -d %(p)s ] %(shell_and)s rc=3; %(i)s -V 2>/dev/null %(shell_or)s rc=4; [ x\"$rc\" != \"xflag\" ] %(shell_and)s echo \"${rc} \"%(p)s %(shell_and)s exit 0" % dict(p=shell_escaped_path, i=python_interp, shell_and=self._SHELL_AND, shell_or=self._SHELL_OR) # NOQA
70
- csums = [
71
- u"({0} -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL), # NOQA Python > 2.4 (including python3)
72
- u"({0} -c 'import sha; BLOCKSIZE = 65536; hasher = sha.sha();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL), # NOQA Python == 2.4
73
- ]
78
+ cmd = "({0} -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL) # NOQA
74
79
 
75
- cmd = (" %s " % self._SHELL_OR).join(csums)
76
80
  cmd = "%s; %s %s (echo \'0 \'%s)" % (test, cmd, self._SHELL_OR, shell_escaped_path)
77
81
  return cmd
@@ -31,7 +31,7 @@ from ansible import errors
31
31
  from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
32
32
  from ansible._internal._templating._jinja_common import Marker, UndefinedMarker
33
33
  from ansible.module_utils.parsing.convert_bool import boolean
34
- from ansible.plugins import accept_args_markers
34
+ from ansible.template import accept_args_markers
35
35
  from ansible.parsing.vault import is_encrypted_file, VaultHelper, VaultLib
36
36
  from ansible.utils.display import Display
37
37
  from ansible.utils.version import SemanticVersion
@@ -49,37 +49,41 @@ def timedout(result):
49
49
  """ Test if task result yields a time out"""
50
50
  if not isinstance(result, MutableMapping):
51
51
  raise errors.AnsibleFilterError("The 'timedout' test expects a dictionary")
52
- return result.get('timedout', False) and bool(result['timedout'].get('period', False))
52
+
53
+ return bool(result.get('timedout') and bool(result['timedout'].get('period')))
53
54
 
54
55
 
55
56
  def failed(result):
56
57
  """ Test if task result yields failed """
57
58
  if not isinstance(result, MutableMapping):
58
59
  raise errors.AnsibleFilterError("The 'failed' test expects a dictionary")
59
- return result.get('failed', False)
60
+
61
+ return bool(result.get('failed'))
60
62
 
61
63
 
62
64
  def success(result):
63
65
  """ Test if task result yields success """
64
- return not failed(result)
66
+ return not bool(failed(result))
65
67
 
66
68
 
67
69
  def unreachable(result):
68
70
  """ Test if task result yields unreachable """
69
71
  if not isinstance(result, MutableMapping):
70
72
  raise errors.AnsibleFilterError("The 'unreachable' test expects a dictionary")
71
- return result.get('unreachable', False)
73
+
74
+ return bool(result.get('unreachable'))
72
75
 
73
76
 
74
77
  def reachable(result):
75
78
  """ Test if task result yields reachable """
76
- return not unreachable(result)
79
+ return bool(not unreachable(result))
77
80
 
78
81
 
79
82
  def changed(result):
80
83
  """ Test if task result yields changed """
81
84
  if not isinstance(result, MutableMapping):
82
85
  raise errors.AnsibleFilterError("The 'changed' test expects a dictionary")
86
+
83
87
  if 'changed' not in result:
84
88
  changed = False
85
89
  if (
@@ -88,29 +92,31 @@ def changed(result):
88
92
  isinstance(result['results'][0], MutableMapping)
89
93
  ):
90
94
  for res in result['results']:
91
- if res.get('changed', False):
95
+ if res.get('changed'):
92
96
  changed = True
93
97
  break
94
98
  else:
95
- changed = result.get('changed', False)
96
- return changed
99
+ changed = result.get('changed')
100
+
101
+ return bool(changed)
97
102
 
98
103
 
99
104
  def skipped(result):
100
105
  """ Test if task result yields skipped """
101
106
  if not isinstance(result, MutableMapping):
102
107
  raise errors.AnsibleFilterError("The 'skipped' test expects a dictionary")
103
- return result.get('skipped', False)
108
+
109
+ return bool(result.get('skipped'))
104
110
 
105
111
 
106
112
  def started(result):
107
113
  """ Test if async task has started """
108
114
  if not isinstance(result, MutableMapping):
109
115
  raise errors.AnsibleFilterError("The 'started' test expects a dictionary")
116
+
110
117
  if 'started' in result:
111
118
  # For async tasks, return status
112
- # NOTE: The value of started is 0 or 1, not False or True :-/
113
- return result.get('started', 0) == 1
119
+ return bool(result.get('started'))
114
120
  else:
115
121
  # For non-async tasks, warn user, but return as if started
116
122
  display.warning("The 'started' test expects an async task, but a non-async task was tested")
@@ -121,10 +127,10 @@ def finished(result):
121
127
  """ Test if async task has finished """
122
128
  if not isinstance(result, MutableMapping):
123
129
  raise errors.AnsibleFilterError("The 'finished' test expects a dictionary")
130
+
124
131
  if 'finished' in result:
125
132
  # For async tasks, return status
126
- # NOTE: The value of finished is 0 or 1, not False or True :-/
127
- return result.get('finished', 0) == 1
133
+ return bool(result.get('finished'))
128
134
  else:
129
135
  # For non-async tasks, warn user, but return as if finished
130
136
  display.warning("The 'finished' test expects an async task, but a non-async task was tested")
@@ -170,7 +176,7 @@ def vaulted_file(value):
170
176
  with open(to_bytes(value), 'rb') as f:
171
177
  return is_encrypted_file(f)
172
178
  except (OSError, IOError) as e:
173
- raise errors.AnsibleFilterError(f"Cannot test if the file {value} is a vault", orig_exc=e)
179
+ raise errors.AnsibleFilterError(f"Cannot test if the file {value} is a vault.") from e
174
180
 
175
181
 
176
182
  def match(value, pattern='', ignorecase=False, multiline=False):
@@ -5,7 +5,7 @@ DOCUMENTATION:
5
5
  short_description: Did async task finish
6
6
  description:
7
7
  - Used to test if an async task has finished, it will also work with normal tasks but will issue a warning.
8
- - This test checks for the existence of a C(finished) key in the input dictionary and that it is V(1) if present
8
+ - This test checks for the existence of a C(finished) key in the input dictionary and that it is V(True) if present
9
9
  options:
10
10
  _input:
11
11
  description: registered result from an Ansible task
@@ -20,11 +20,8 @@ def is_url(value, schemes=None):
20
20
 
21
21
  isit = is_uri(value, schemes)
22
22
  if isit:
23
- try:
24
- x = urlparse(value)
25
- isit = bool(x.netloc or x.scheme == 'file')
26
- except Exception as e:
27
- isit = False
23
+ x = urlparse(value)
24
+ isit = bool(x.netloc or x.scheme == 'file')
28
25
  return isit
29
26
 
30
27
 
ansible/release.py CHANGED
@@ -17,6 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = '2.19.0b3'
20
+ __version__ = '2.19.0b5'
21
21
  __author__ = 'Ansible, Inc.'
22
22
  __codename__ = "What Is and What Should Never Be"
@@ -98,7 +98,7 @@ class Templar:
98
98
  @property
99
99
  def basedir(self) -> str:
100
100
  """The basedir from DataLoader."""
101
- # DTFIX-RELEASE: come up with a better way to handle this so it can be deprecated
101
+ # DTFIX-FUTURE: come up with a better way to handle this so it can be deprecated
102
102
  return self._engine.basedir
103
103
 
104
104
  @property
@@ -381,7 +381,7 @@ def generate_ansible_template_vars(path: str, fullpath: str | None = None, dest_
381
381
  )
382
382
 
383
383
  ansible_managed = _time.strftime(managed_str, _time.localtime(template_stat.st_mtime))
384
- # DTFIX-RELEASE: this should not be tag_copy, it should either be an origin copy or some kind of derived origin
384
+ # DTFIX7: this should not be tag_copy, it should either be an origin copy or some kind of derived origin
385
385
  ansible_managed = _datatag.AnsibleTagHelper.tag_copy(managed_default, ansible_managed)
386
386
  ansible_managed = trust_as_template(ansible_managed)
387
387
  ansible_managed = _module_utils_datatag.deprecate_value(
@@ -427,3 +427,31 @@ def is_trusted_as_template(value: object) -> bool:
427
427
  This function should not be needed for production code, but may be useful in unit tests.
428
428
  """
429
429
  return isinstance(value, _TRUSTABLE_TYPES) and _tags.TrustedAsTemplate.is_tagged_on(value)
430
+
431
+
432
+ _TCallable = _t.TypeVar('_TCallable', bound=_t.Callable)
433
+
434
+
435
+ def accept_args_markers(plugin: _TCallable) -> _TCallable:
436
+ """
437
+ A decorator to mark a Jinja plugin as capable of handling `Marker` values for its top-level arguments.
438
+ Non-decorated plugin invocation is skipped when a top-level argument is a `Marker`, with the first such value substituted as the plugin result.
439
+ This ensures that only plugins which understand `Marker` instances for top-level arguments will encounter them.
440
+ """
441
+ plugin.accept_args_markers = True
442
+
443
+ return plugin
444
+
445
+
446
+ def accept_lazy_markers(plugin: _TCallable) -> _TCallable:
447
+ """
448
+ A decorator to mark a Jinja plugin as capable of handling `Marker` values retrieved from lazy containers.
449
+ Non-decorated plugins will trigger a `MarkerError` exception when attempting to retrieve a `Marker` from a lazy container.
450
+ This ensures that only plugins which understand lazy retrieval of `Marker` instances will encounter them.
451
+ """
452
+ plugin.accept_lazy_markers = True
453
+
454
+ return plugin
455
+
456
+
457
+ get_first_marker_arg = _jinja_common.get_first_marker_arg
@@ -13,6 +13,8 @@ import typing as t
13
13
  class _EncryptedStringProtocol(t.Protocol):
14
14
  """Protocol representing an `EncryptedString`, since it cannot be imported here."""
15
15
 
16
+ # DTFIX-FUTURE: collapse this with the one in config, once we can
17
+
16
18
  def _decrypt(self) -> str: ...
17
19
 
18
20