ansible-core 2.19.0b5__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 (117) 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 +7 -4
  5. ansible/_internal/_templating/_jinja_plugins.py +5 -2
  6. ansible/_internal/_templating/_template_vars.py +72 -0
  7. ansible/_internal/_templating/_transform.py +6 -0
  8. ansible/_internal/_yaml/_constructor.py +4 -4
  9. ansible/_internal/_yaml/_dumper.py +26 -18
  10. ansible/cli/__init__.py +7 -12
  11. ansible/cli/arguments/option_helpers.py +1 -1
  12. ansible/cli/console.py +1 -1
  13. ansible/cli/doc.py +2 -2
  14. ansible/cli/inventory.py +5 -7
  15. ansible/config/base.yml +24 -0
  16. ansible/errors/__init__.py +2 -1
  17. ansible/executor/module_common.py +67 -39
  18. ansible/executor/process/worker.py +2 -2
  19. ansible/galaxy/api.py +1 -4
  20. ansible/galaxy/collection/__init__.py +1 -6
  21. ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
  22. ansible/galaxy/role.py +2 -2
  23. ansible/module_utils/_internal/__init__.py +7 -4
  24. ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
  25. ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
  26. ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
  27. ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
  28. ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +10 -38
  29. ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
  30. ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
  31. ansible/module_utils/_internal/_datatag/__init__.py +23 -1
  32. ansible/module_utils/_internal/_deprecator.py +27 -33
  33. ansible/module_utils/_internal/_json/_profiles/__init__.py +1 -0
  34. ansible/module_utils/_internal/_messages.py +26 -2
  35. ansible/module_utils/_internal/_plugin_info.py +14 -1
  36. ansible/module_utils/ansible_release.py +1 -1
  37. ansible/module_utils/basic.py +46 -56
  38. ansible/module_utils/common/respawn.py +4 -41
  39. ansible/module_utils/connection.py +8 -11
  40. ansible/module_utils/facts/hardware/linux.py +1 -1
  41. ansible/module_utils/facts/sysctl.py +4 -6
  42. ansible/module_utils/facts/system/caps.py +2 -2
  43. ansible/module_utils/facts/system/local.py +1 -1
  44. ansible/module_utils/facts/virtual/linux.py +1 -1
  45. ansible/module_utils/service.py +1 -1
  46. ansible/module_utils/urls.py +4 -4
  47. ansible/modules/apt_repository.py +10 -10
  48. ansible/modules/assemble.py +2 -2
  49. ansible/modules/async_wrapper.py +7 -17
  50. ansible/modules/command.py +3 -3
  51. ansible/modules/copy.py +4 -4
  52. ansible/modules/cron.py +1 -1
  53. ansible/modules/file.py +16 -17
  54. ansible/modules/find.py +3 -3
  55. ansible/modules/get_url.py +17 -0
  56. ansible/modules/git.py +9 -7
  57. ansible/modules/known_hosts.py +12 -14
  58. ansible/modules/package.py +6 -0
  59. ansible/modules/replace.py +2 -2
  60. ansible/modules/slurp.py +10 -13
  61. ansible/modules/stat.py +5 -7
  62. ansible/modules/unarchive.py +6 -6
  63. ansible/modules/user.py +1 -1
  64. ansible/modules/wait_for.py +28 -30
  65. ansible/modules/yum_repository.py +4 -3
  66. ansible/parsing/dataloader.py +2 -2
  67. ansible/parsing/vault/__init__.py +6 -10
  68. ansible/playbook/base.py +7 -2
  69. ansible/playbook/included_file.py +3 -1
  70. ansible/playbook/play_context.py +2 -0
  71. ansible/playbook/taggable.py +19 -5
  72. ansible/playbook/task.py +2 -0
  73. ansible/plugins/action/fetch.py +3 -3
  74. ansible/plugins/action/template.py +8 -2
  75. ansible/plugins/cache/__init__.py +17 -19
  76. ansible/plugins/callback/tree.py +5 -5
  77. ansible/plugins/connection/local.py +4 -4
  78. ansible/plugins/connection/paramiko_ssh.py +5 -5
  79. ansible/plugins/connection/ssh.py +8 -6
  80. ansible/plugins/connection/winrm.py +1 -1
  81. ansible/plugins/filter/core.py +19 -21
  82. ansible/plugins/filter/encryption.py +10 -2
  83. ansible/plugins/list.py +5 -4
  84. ansible/plugins/lookup/template.py +9 -4
  85. ansible/plugins/shell/powershell.py +3 -2
  86. ansible/plugins/shell/sh.py +3 -2
  87. ansible/plugins/strategy/__init__.py +3 -3
  88. ansible/plugins/test/core.py +2 -2
  89. ansible/release.py +1 -1
  90. ansible/template/__init__.py +9 -53
  91. ansible/utils/collection_loader/_collection_finder.py +3 -3
  92. ansible/utils/display.py +23 -12
  93. ansible/utils/galaxy.py +2 -2
  94. ansible/utils/hashing.py +6 -7
  95. ansible/utils/path.py +5 -7
  96. ansible/utils/py3compat.py +2 -1
  97. ansible/utils/ssh_functions.py +3 -2
  98. ansible/vars/plugins.py +3 -3
  99. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
  100. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +117 -108
  101. ansible_test/_internal/commands/integration/coverage.py +7 -2
  102. ansible_test/_internal/host_profiles.py +62 -10
  103. ansible_test/_internal/provisioning.py +10 -4
  104. ansible_test/_internal/ssh.py +1 -5
  105. ansible_test/_internal/thread.py +2 -1
  106. ansible_test/_internal/timeout.py +1 -1
  107. ansible_test/_internal/util.py +20 -12
  108. ansible_test/_util/target/setup/requirements.py +3 -9
  109. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/WHEEL +0 -0
  110. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
  111. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
  112. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  113. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  114. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  115. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  116. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  117. {ansible_core-2.19.0b5.dist-info → ansible_core-2.19.0b6.dist-info}/top_level.txt +0 -0
@@ -217,7 +217,7 @@ class DataLoader:
217
217
  except FileNotFoundError as ex:
218
218
  # DTFIX-FUTURE: why not just let the builtin one fly?
219
219
  raise AnsibleFileNotFound("Unable to retrieve file contents.", file_name=file_name) from ex
220
- except (IOError, OSError) as ex:
220
+ except OSError as ex:
221
221
  raise AnsibleParserError(f"An error occurred while trying to read the file {file_name!r}.") from ex
222
222
 
223
223
  data = Origin(path=file_name).tag(data)
@@ -448,7 +448,7 @@ class DataLoader:
448
448
 
449
449
  return real_path
450
450
 
451
- except (IOError, OSError) as ex:
451
+ except OSError as ex:
452
452
  raise AnsibleParserError(f"an error occurred while trying to read the file {to_text(real_path)!r}.") from ex
453
453
 
454
454
  def cleanup_tmp_file(self, file_path: str) -> None:
@@ -17,7 +17,6 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- import errno
21
20
  import fcntl
22
21
  import functools
23
22
  import os
@@ -414,8 +413,8 @@ class FileVaultSecret(VaultSecret):
414
413
  try:
415
414
  with open(filename, "rb") as f:
416
415
  vault_pass = f.read().strip()
417
- except (OSError, IOError) as e:
418
- raise AnsibleError("Could not read vault password file %s: %s" % (filename, e))
416
+ except OSError as ex:
417
+ raise AnsibleError(f"Could not read vault password file {filename!r}.") from ex
419
418
 
420
419
  b_vault_data, dummy = self.loader._decrypt_if_vault_data(vault_pass)
421
420
 
@@ -1071,13 +1070,10 @@ class VaultEditor:
1071
1070
  try:
1072
1071
  # create file with secure permissions
1073
1072
  fd = os.open(thefile, os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_TRUNC, mode)
1074
- except OSError as ose:
1075
- # Want to catch FileExistsError, which doesn't exist in Python 2, so catch OSError
1076
- # and compare the error number to get equivalent behavior in Python 2/3
1077
- if ose.errno == errno.EEXIST:
1078
- raise AnsibleError('Vault file got recreated while we were operating on it: %s' % to_native(ose))
1079
-
1080
- raise AnsibleError('Problem creating temporary vault file: %s' % to_native(ose))
1073
+ except FileExistsError as ex:
1074
+ raise AnsibleError('Vault file got recreated while we were operating on it.') from ex
1075
+ except OSError as ex:
1076
+ raise AnsibleError('Problem creating temporary vault file.') from ex
1081
1077
 
1082
1078
  try:
1083
1079
  # now write to the file and ensure ours is only data in it
ansible/playbook/base.py CHANGED
@@ -83,6 +83,11 @@ class _ClassProperty:
83
83
 
84
84
  class FieldAttributeBase:
85
85
 
86
+ _post_validate_object = False
87
+ """
88
+ `False` skips FieldAttribute post-validation on intermediate objects and mixins for attributes without `always_post_validate`.
89
+ Leaf objects (e.g., `Task`) should set this attribute `True` to opt-in to post-validation.
90
+ """
86
91
  fattributes = _ClassProperty()
87
92
 
88
93
  @classmethod
@@ -566,8 +571,8 @@ class FieldAttributeBase:
566
571
  # only import_role is checked here because import_tasks never reaches this point
567
572
  return Sentinel
568
573
 
569
- # FIXME: compare types, not strings
570
- if not attribute.always_post_validate and self.__class__.__name__ not in ('Task', 'Handler', 'PlayContext', 'IncludeRole', 'TaskInclude'):
574
+ # Skip post validation unless always_post_validate is True, or the object requires post validation.
575
+ if not attribute.always_post_validate and not self._post_validate_object:
571
576
  # Intermediate objects like Play() won't have their fields validated by
572
577
  # default, as their values are often inherited by other objects and validated
573
578
  # later, so we don't want them to fail out early
@@ -144,7 +144,9 @@ class IncludedFile:
144
144
  parent_include_dir = parent_include._role_path
145
145
  else:
146
146
  try:
147
- parent_include_dir = os.path.dirname(parent_include.args.get('_raw_params'))
147
+ # FUTURE: Since the parent include path has already been resolved, it should be used here.
148
+ # Unfortunately it's not currently stored anywhere, so it must be calculated again.
149
+ parent_include_dir = os.path.dirname(templar.template(parent_include.args.get('_raw_params')))
148
150
  except AnsibleError as e:
149
151
  parent_include_dir = ''
150
152
  display.warning(
@@ -71,6 +71,8 @@ class PlayContext(Base):
71
71
  connection/authentication information.
72
72
  """
73
73
 
74
+ _post_validate_object = True
75
+
74
76
  # base
75
77
  module_compression = FieldAttribute(isa='string', default=C.DEFAULT_MODULE_COMPRESSION)
76
78
  shell = FieldAttribute(isa='string')
@@ -17,6 +17,8 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
+ import typing as t
21
+
20
22
  from ansible.errors import AnsibleError
21
23
  from ansible.module_utils.six import string_types
22
24
  from ansible.module_utils.common.sentinel import Sentinel
@@ -25,7 +27,7 @@ from ansible.playbook.attribute import FieldAttribute
25
27
  from ansible._internal._templating._engine import TemplateEngine
26
28
 
27
29
 
28
- def _flatten_tags(tags: list) -> list:
30
+ def _flatten_tags(tags: list[str | int]) -> list[str | int]:
29
31
  rv = set()
30
32
  for tag in tags:
31
33
  if isinstance(tag, list):
@@ -49,16 +51,28 @@ class Taggable:
49
51
 
50
52
  raise AnsibleError('tags must be specified as a list', obj=ds)
51
53
 
54
+ def _get_all_taggable_objects(self) -> t.Iterable[Taggable]:
55
+ obj = self
56
+ while obj is not None:
57
+ yield obj
58
+
59
+ if (role := getattr(obj, "_role", Sentinel)) is not Sentinel:
60
+ yield role # type: ignore[misc]
61
+
62
+ obj = obj._parent
63
+
64
+ yield self.get_play()
65
+
52
66
  def evaluate_tags(self, only_tags, skip_tags, all_vars):
53
- """ this checks if the current item should be executed depending on tag options """
67
+ """Check if the current item should be executed depending on the specified tags.
54
68
 
69
+ NOTE this method is assumed to be called only on Task objects.
70
+ """
55
71
  if self.tags:
56
72
  templar = TemplateEngine(loader=self._loader, variables=all_vars)
57
- obj = self
58
- while obj is not None:
73
+ for obj in self._get_all_taggable_objects():
59
74
  if (_tags := getattr(obj, "_tags", Sentinel)) is not Sentinel:
60
75
  obj._tags = _flatten_tags(templar.template(_tags))
61
- obj = obj._parent
62
76
  tags = set(self.tags)
63
77
  else:
64
78
  # this makes isdisjoint work for untagged
ansible/playbook/task.py CHANGED
@@ -65,6 +65,8 @@ class Task(Base, Conditional, Taggable, CollectionSearch, Notifiable, Delegatabl
65
65
  Task.something(...)
66
66
  """
67
67
 
68
+ _post_validate_object = True
69
+
68
70
  # =================================================================================
69
71
  # ATTRIBUTES
70
72
  # load_<attribute_name> and
@@ -119,7 +119,7 @@ class ActionModule(ActionBase):
119
119
 
120
120
  if 'not found' in slurpres.get('msg', ''):
121
121
  result['msg'] = "the remote file does not exist, not transferring, ignored"
122
- elif slurpres.get('msg', '').startswith('source is a directory'):
122
+ elif slurpres.get('msg', '').lower().startswith('source is a directory'):
123
123
  result['msg'] = "remote file is a directory, fetch cannot work on directories"
124
124
 
125
125
  return result
@@ -180,8 +180,8 @@ class ActionModule(ActionBase):
180
180
  try:
181
181
  with open(to_bytes(dest, errors='surrogate_or_strict'), 'wb') as f:
182
182
  f.write(remote_data)
183
- except (IOError, OSError) as e:
184
- raise AnsibleActionFail("Failed to fetch the file: %s" % e)
183
+ except OSError as ex:
184
+ raise AnsibleActionFail("Failed to fetch the file.") from ex
185
185
  new_checksum = secure_hash(dest)
186
186
  # For backwards compatibility. We'll return None on FIPS enabled systems
187
187
  try:
@@ -25,7 +25,8 @@ from ansible.module_utils.common.text.converters import to_bytes, to_text, to_na
25
25
  from ansible.module_utils.parsing.convert_bool import boolean
26
26
  from ansible.module_utils.six import string_types
27
27
  from ansible.plugins.action import ActionBase
28
- from ansible.template import generate_ansible_template_vars, trust_as_template
28
+ from ansible.template import trust_as_template
29
+ from ansible._internal._templating import _template_vars
29
30
 
30
31
 
31
32
  class ActionModule(ActionBase):
@@ -115,7 +116,12 @@ class ActionModule(ActionBase):
115
116
 
116
117
  # add ansible 'template' vars
117
118
  temp_vars = task_vars.copy()
118
- temp_vars.update(generate_ansible_template_vars(self._task.args.get('src', None), fullpath=source, dest_path=dest))
119
+ temp_vars.update(_template_vars.generate_ansible_template_vars(
120
+ path=self._task.args.get('src', None),
121
+ fullpath=source,
122
+ dest_path=dest,
123
+ include_ansible_managed='ansible_managed' not in temp_vars, # do not clobber ansible_managed when set by the user
124
+ ))
119
125
 
120
126
  overrides = dict(
121
127
  block_start_string=block_start_string,
@@ -18,7 +18,6 @@
18
18
  from __future__ import annotations
19
19
 
20
20
  import copy
21
- import errno
22
21
  import os
23
22
  import tempfile
24
23
  import time
@@ -108,8 +107,8 @@ class BaseFileCacheModule(BaseCacheModule):
108
107
  if not os.path.exists(self._cache_dir):
109
108
  try:
110
109
  os.makedirs(self._cache_dir)
111
- except (OSError, IOError) as e:
112
- raise AnsibleError("error in '%s' cache plugin while trying to create cache dir %s : %s" % (self.plugin_name, self._cache_dir, to_bytes(e)))
110
+ except OSError as ex:
111
+ raise AnsibleError(f"Error in {self.plugin_name!r} cache plugin while trying to create cache dir {self._cache_dir!r}.") from ex
113
112
  else:
114
113
  for x in (os.R_OK, os.W_OK, os.X_OK):
115
114
  if not os.access(self._cache_dir, x):
@@ -160,13 +159,13 @@ class BaseFileCacheModule(BaseCacheModule):
160
159
  try:
161
160
  try:
162
161
  self._dump(value, tmpfile_path)
163
- except (OSError, IOError) as e:
164
- display.warning("error in '%s' cache plugin while trying to write to '%s' : %s" % (self.plugin_name, tmpfile_path, to_bytes(e)))
162
+ except OSError as ex:
163
+ display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to write to {tmpfile_path!r}.", exception=ex)
165
164
  try:
166
165
  os.rename(tmpfile_path, cachefile)
167
166
  os.chmod(cachefile, mode=S_IRWU_RG_RO)
168
- except (OSError, IOError) as e:
169
- display.warning("error in '%s' cache plugin while trying to move '%s' to '%s' : %s" % (self.plugin_name, tmpfile_path, cachefile, to_bytes(e)))
167
+ except OSError as ex:
168
+ display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to move {tmpfile_path!r} to {cachefile!r}.", exception=ex)
170
169
  finally:
171
170
  try:
172
171
  os.unlink(tmpfile_path)
@@ -181,12 +180,12 @@ class BaseFileCacheModule(BaseCacheModule):
181
180
  cachefile = self._get_cache_file_name(key)
182
181
  try:
183
182
  st = os.stat(cachefile)
184
- except (OSError, IOError) as e:
185
- if e.errno == errno.ENOENT:
186
- return False
187
- else:
188
- display.warning("error in '%s' cache plugin while trying to stat %s : %s" % (self.plugin_name, cachefile, to_bytes(e)))
189
- return False
183
+ except FileNotFoundError:
184
+ return False
185
+ except OSError as ex:
186
+ display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to stat {cachefile!r}.", exception=ex)
187
+
188
+ return False
190
189
 
191
190
  if time.time() - st.st_mtime <= self._timeout:
192
191
  return False
@@ -223,11 +222,10 @@ class BaseFileCacheModule(BaseCacheModule):
223
222
  try:
224
223
  os.stat(cachefile)
225
224
  return True
226
- except (OSError, IOError) as e:
227
- if e.errno == errno.ENOENT:
228
- return False
229
- else:
230
- display.warning("error in '%s' cache plugin while trying to stat %s : %s" % (self.plugin_name, cachefile, to_bytes(e)))
225
+ except FileNotFoundError:
226
+ return False
227
+ except OSError as ex:
228
+ display.error_as_warning(f"Error in {self.plugin_name!r} cache plugin while trying to stat {cachefile!r}.", exception=ex)
231
229
 
232
230
  def delete(self, key):
233
231
  try:
@@ -236,7 +234,7 @@ class BaseFileCacheModule(BaseCacheModule):
236
234
  pass
237
235
  try:
238
236
  os.remove(self._get_cache_file_name(key))
239
- except (OSError, IOError):
237
+ except OSError:
240
238
  pass # TODO: only pass on non existing?
241
239
 
242
240
  def flush(self):
@@ -31,7 +31,7 @@ import os
31
31
 
32
32
  from ansible.constants import TREE_DIR
33
33
  from ansible.executor.task_result import CallbackTaskResult
34
- from ansible.module_utils.common.text.converters import to_bytes, to_text
34
+ from ansible.module_utils.common.text.converters import to_bytes
35
35
  from ansible.plugins.callback import CallbackBase
36
36
  from ansible.utils.path import makedirs_safe, unfrackpath
37
37
  from ansible.module_utils._internal import _deprecator
@@ -73,15 +73,15 @@ class CallbackModule(CallbackBase):
73
73
  buf = to_bytes(buf)
74
74
  try:
75
75
  makedirs_safe(self.tree)
76
- except (OSError, IOError) as e:
77
- self._display.warning(u"Unable to access or create the configured directory (%s): %s" % (to_text(self.tree), to_text(e)))
76
+ except OSError as ex:
77
+ self._display.error_as_warning(f"Unable to access or create the configured directory {self.tree!r}.", exception=ex)
78
78
 
79
79
  try:
80
80
  path = to_bytes(os.path.join(self.tree, hostname))
81
81
  with open(path, 'wb+') as fd:
82
82
  fd.write(buf)
83
- except (OSError, IOError) as e:
84
- self._display.warning(u"Unable to write to %s's file: %s" % (hostname, to_text(e)))
83
+ except OSError as ex:
84
+ self._display.error_as_warning(f"Unable to write to {hostname!r}'s file.", exception=ex)
85
85
 
86
86
  def result_to_tree(self, result: CallbackTaskResult) -> None:
87
87
  self.write_tree_file(result.host.get_name(), self._dump_results(result.result))
@@ -114,8 +114,8 @@ class Connection(ConnectionBase):
114
114
  # privileges or the command otherwise needs a pty.
115
115
  try:
116
116
  pty_primary, stdin = pty.openpty()
117
- except (IOError, OSError) as e:
118
- display.debug("Unable to open pty: %s" % to_native(e))
117
+ except OSError as ex:
118
+ display.debug(f"Unable to open pty: {ex}")
119
119
 
120
120
  p = subprocess.Popen(
121
121
  cmd,
@@ -271,8 +271,8 @@ class Connection(ConnectionBase):
271
271
  shutil.copyfile(to_bytes(in_path, errors='surrogate_or_strict'), to_bytes(out_path, errors='surrogate_or_strict'))
272
272
  except shutil.Error:
273
273
  raise AnsibleError("failed to copy: {0} and {1} are the same".format(to_native(in_path), to_native(out_path)))
274
- except IOError as e:
275
- raise AnsibleError("failed to transfer file to {0}: {1}".format(to_native(out_path), to_native(e)))
274
+ except OSError as ex:
275
+ raise AnsibleError(f"Failed to transfer file to {out_path!r}.") from ex
276
276
 
277
277
  def fetch_file(self, in_path: str, out_path: str) -> None:
278
278
  """ fetch a file from local to local -- for compatibility """
@@ -413,7 +413,7 @@ class Connection(ConnectionBase):
413
413
  # TODO: check if we need to look at several possible locations, possible for loop
414
414
  ssh.load_system_host_keys(ssh_known_hosts)
415
415
  break
416
- except IOError:
416
+ except OSError:
417
417
  pass # file was not found, but not required to function
418
418
  ssh.load_system_host_keys()
419
419
 
@@ -567,8 +567,8 @@ class Connection(ConnectionBase):
567
567
 
568
568
  try:
569
569
  self.sftp.put(to_bytes(in_path, errors='surrogate_or_strict'), to_bytes(out_path, errors='surrogate_or_strict'))
570
- except IOError:
571
- raise AnsibleError("failed to transfer file to %s" % out_path)
570
+ except OSError as ex:
571
+ raise AnsibleError(f"Failed to transfer file to {out_path!r}.") from ex
572
572
 
573
573
  def _connect_sftp(self) -> paramiko.sftp_client.SFTPClient:
574
574
 
@@ -593,8 +593,8 @@ class Connection(ConnectionBase):
593
593
 
594
594
  try:
595
595
  self.sftp.get(to_bytes(in_path, errors='surrogate_or_strict'), to_bytes(out_path, errors='surrogate_or_strict'))
596
- except IOError:
597
- raise AnsibleError("failed to transfer file from %s" % in_path)
596
+ except OSError as ex:
597
+ raise AnsibleError(f"Failed to transfer file from {in_path!r}.") from ex
598
598
 
599
599
  def _any_keys_added(self) -> bool:
600
600
 
@@ -332,7 +332,9 @@ DOCUMENTATION = """
332
332
  version_added: '2.7'
333
333
  sftp_batch_mode:
334
334
  default: true
335
- description: 'TODO: write it'
335
+ description:
336
+ - When set to C(True), sftp will be run in batch mode, allowing detection of transfer errors.
337
+ - When set to C(False), sftp will not be run in batch mode, preventing detection of transfer errors.
336
338
  env: [{name: ANSIBLE_SFTP_BATCH_MODE}]
337
339
  ini:
338
340
  - {key: sftp_batch_mode, section: ssh_connection}
@@ -977,7 +979,7 @@ class Connection(ConnectionBase):
977
979
  try:
978
980
  fh.write(to_bytes(in_data))
979
981
  fh.close()
980
- except (OSError, IOError) as ex:
982
+ except OSError as ex:
981
983
  # The ssh connection may have already terminated at this point, with a more useful error
982
984
  # Only raise AnsibleConnectionFailure if the ssh process is still alive
983
985
  time.sleep(0.001)
@@ -993,7 +995,7 @@ class Connection(ConnectionBase):
993
995
  """ Terminate a process, ignoring errors """
994
996
  try:
995
997
  p.terminate()
996
- except (OSError, IOError):
998
+ except OSError:
997
999
  pass
998
1000
 
999
1001
  # This is separate from _run() because we need to do the same thing for stdout
@@ -1134,7 +1136,7 @@ class Connection(ConnectionBase):
1134
1136
  p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **popen_kwargs)
1135
1137
  stdin = os.fdopen(master, 'wb', 0)
1136
1138
  os.close(slave)
1137
- except (OSError, IOError):
1139
+ except OSError:
1138
1140
  p = None
1139
1141
 
1140
1142
  if not p:
@@ -1142,8 +1144,8 @@ class Connection(ConnectionBase):
1142
1144
  p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1143
1145
  stderr=subprocess.PIPE, **popen_kwargs)
1144
1146
  stdin = p.stdin # type: ignore[assignment] # stdin will be set and not None due to the calls above
1145
- except (OSError, IOError) as e:
1146
- raise AnsibleError('Unable to execute ssh command line on a controller due to: %s' % to_native(e))
1147
+ except OSError as ex:
1148
+ raise AnsibleError('Unable to execute ssh command line on a controller.') from ex
1147
1149
 
1148
1150
  if password_mechanism == 'sshpass' and conn_password:
1149
1151
  os.close(self.sshpass_pipe[0])
@@ -828,7 +828,7 @@ class Connection(ConnectionBase):
828
828
  stderr = to_text(b_stderr)
829
829
 
830
830
  if status_code != 0:
831
- raise IOError(stderr)
831
+ raise OSError(stderr)
832
832
  if stdout.strip() == '[DIR]':
833
833
  data = None
834
834
  else:
@@ -32,10 +32,10 @@ from ansible.module_utils.common.json import get_encoder, get_decoder
32
32
  from ansible.module_utils.six import string_types, integer_types, text_type
33
33
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
34
34
  from ansible.module_utils.common.collections import is_sequence
35
- from ansible.module_utils.common.yaml import yaml_load, yaml_load_all
36
35
  from ansible.parsing.yaml.dumper import AnsibleDumper
37
36
  from ansible.template import accept_args_markers, accept_lazy_markers
38
37
  from ansible._internal._templating._jinja_common import MarkerError, UndefinedMarker, validate_arg_type
38
+ from ansible._internal._yaml import _loader as _yaml_loader
39
39
  from ansible.utils.display import Display
40
40
  from ansible.utils.encrypt import do_encrypt, PASSLIB_AVAILABLE
41
41
  from ansible.utils.hashing import md5s, checksum_s
@@ -47,13 +47,13 @@ display = Display()
47
47
  UUID_NAMESPACE_ANSIBLE = uuid.UUID('361E6D51-FAEC-444A-9079-341386DA8E2E')
48
48
 
49
49
 
50
- def to_yaml(a, *_args, default_flow_style: bool | None = None, dump_vault_tags: bool | None = None, **kwargs) -> str:
50
+ @accept_lazy_markers
51
+ def to_yaml(a, *_args, default_flow_style: bool | None = None, **kwargs) -> str:
51
52
  """Serialize input as terse flow-style YAML."""
52
- dumper = partial(AnsibleDumper, dump_vault_tags=dump_vault_tags)
53
-
54
- return yaml.dump(a, Dumper=dumper, allow_unicode=True, default_flow_style=default_flow_style, **kwargs)
53
+ return yaml.dump(a, Dumper=AnsibleDumper, allow_unicode=True, default_flow_style=default_flow_style, **kwargs)
55
54
 
56
55
 
56
+ @accept_lazy_markers
57
57
  def to_nice_yaml(a, indent=4, *_args, default_flow_style=False, **kwargs) -> str:
58
58
  """Serialize input as verbose multi-line YAML."""
59
59
  return to_yaml(a, indent=indent, default_flow_style=default_flow_style, **kwargs)
@@ -98,6 +98,7 @@ _valid_bool_false = {'no', 'off', 'false', '0'}
98
98
  def to_bool(value: object) -> bool:
99
99
  """Convert well-known input values to a boolean value."""
100
100
  value_to_check: object
101
+
101
102
  if isinstance(value, str):
102
103
  value_to_check = value.lower() # accept mixed case variants
103
104
  elif isinstance(value, int): # bool is also an int
@@ -105,14 +106,17 @@ def to_bool(value: object) -> bool:
105
106
  else:
106
107
  value_to_check = value
107
108
 
108
- if value_to_check in _valid_bool_true:
109
- return True
109
+ try:
110
+ if value_to_check in _valid_bool_true:
111
+ return True
110
112
 
111
- if value_to_check in _valid_bool_false:
112
- return False
113
+ if value_to_check in _valid_bool_false:
114
+ return False
113
115
 
114
- # if we're still here, the value is unsupported- always fire a deprecation warning
115
- result = value_to_check == 1 # backwards compatibility with the old code which checked: value in ('yes', 'on', '1', 'true', 1)
116
+ # if we're still here, the value is unsupported- always fire a deprecation warning
117
+ result = value_to_check == 1 # backwards compatibility with the old code which checked: value in ('yes', 'on', '1', 'true', 1)
118
+ except TypeError:
119
+ result = False
116
120
 
117
121
  # NB: update the doc string to reflect reality once this fallback is removed
118
122
  display.deprecated(
@@ -250,11 +254,8 @@ def from_yaml(data):
250
254
  if data is None:
251
255
  return None
252
256
 
253
- if isinstance(data, string_types):
254
- # The ``text_type`` call here strips any custom
255
- # string wrapper class, so that CSafeLoader can
256
- # read the data
257
- return yaml_load(text_type(to_text(data, errors='surrogate_or_strict')))
257
+ if isinstance(data, str):
258
+ return yaml.load(data, Loader=_yaml_loader.AnsibleInstrumentedLoader) # type: ignore[arg-type]
258
259
 
259
260
  display.deprecated(f"The from_yaml filter ignored non-string input of type {native_type_name(data)!r}.", version='2.23', obj=data)
260
261
  return data
@@ -264,11 +265,8 @@ def from_yaml_all(data):
264
265
  if data is None:
265
266
  return [] # backward compatibility; ensure consistent result between classic/native Jinja for None/empty string input
266
267
 
267
- if isinstance(data, string_types):
268
- # The ``text_type`` call here strips any custom
269
- # string wrapper class, so that CSafeLoader can
270
- # read the data
271
- return yaml_load_all(text_type(to_text(data, errors='surrogate_or_strict')))
268
+ if isinstance(data, str):
269
+ return yaml.load_all(data, Loader=_yaml_loader.AnsibleInstrumentedLoader) # type: ignore[arg-type]
272
270
 
273
271
  display.deprecated(f"The from_yaml_all filter ignored non-string input of type {native_type_name(data)!r}.", version='2.23', obj=data)
274
272
  return data
@@ -21,7 +21,11 @@ def do_vault(data, secret, salt=None, vault_id='filter_default', wrap_object=Fal
21
21
  raise TypeError(f"Can only vault strings, instead we got {type(data)}.")
22
22
 
23
23
  if vaultid is not None:
24
- display.deprecated("Use of undocumented 'vaultid', use 'vault_id' instead", version='2.20')
24
+ display.deprecated(
25
+ msg="Use of undocumented `vaultid`.",
26
+ version="2.20",
27
+ help_text="Use `vault_id` instead.",
28
+ )
25
29
 
26
30
  if vault_id == 'filter_default':
27
31
  vault_id = vaultid
@@ -58,7 +62,11 @@ def do_unvault(vault, secret, vault_id='filter_default', vaultid=None):
58
62
  raise TypeError(f"Vault should be in the form of a string, instead we got {type(vault)}.")
59
63
 
60
64
  if vaultid is not None:
61
- display.deprecated("Use of undocumented 'vaultid', use 'vault_id' instead", version='2.20')
65
+ display.deprecated(
66
+ msg="Use of undocumented `vaultid`.",
67
+ version="2.20",
68
+ help_text="Use `vault_id` instead.",
69
+ )
62
70
 
63
71
  if vault_id == 'filter_default':
64
72
  vault_id = vaultid
ansible/plugins/list.py CHANGED
@@ -58,7 +58,7 @@ def get_composite_name(collection, name, path, depth):
58
58
  return '.'.join(composite)
59
59
 
60
60
 
61
- def _list_plugins_from_paths(ptype, dirs, collection, depth=0):
61
+ def _list_plugins_from_paths(ptype, dirs, collection, depth=0, docs=False):
62
62
  # TODO: update to use importlib.resources
63
63
 
64
64
  plugins = {}
@@ -93,14 +93,15 @@ def _list_plugins_from_paths(ptype, dirs, collection, depth=0):
93
93
  continue
94
94
 
95
95
  # actually recurse dirs
96
- plugins.update(_list_plugins_from_paths(ptype, [to_native(full_path)], collection, depth=depth + 1))
96
+ plugins.update(_list_plugins_from_paths(ptype, [to_native(full_path)], collection, depth=depth + 1, docs=docs))
97
97
  else:
98
98
  if any([
99
99
  plugin in C.IGNORE_FILES, # general files to ignore
100
100
  to_native(b_ext) in C.REJECT_EXTS, # general extensions to ignore
101
- b_ext in (b'.yml', b'.yaml', b'.json'), # ignore docs files TODO: constant!
101
+ b_ext in (b'.yml', b'.yaml', b'.json'), # ignore docs files
102
102
  plugin in IGNORE.get(bkey, ()), # plugin in reject list
103
103
  os.path.islink(full_path), # skip aliases, author should document in 'aliases' field
104
+ not docs and b_ext in (b''), # ignore no ext when looking for docs files
104
105
  ]):
105
106
  continue
106
107
 
@@ -179,7 +180,7 @@ def _list_collection_plugins_with_info(
179
180
 
180
181
  # raise Exception('bad acr for %s, %s' % (collection, ptype))
181
182
 
182
- plugin_paths.update(_list_plugins_from_paths(ptype, dirs, collection))
183
+ plugin_paths.update(_list_plugins_from_paths(ptype, dirs, collection, docs=True))
183
184
 
184
185
  plugins = {}
185
186
  if ptype in ('module',):
@@ -22,7 +22,7 @@ DOCUMENTATION = """
22
22
  default: true
23
23
  deprecated:
24
24
  why: This option is no longer used in the Ansible Core code base.
25
- version: "2.21"
25
+ version: "2.23"
26
26
  alternatives: Jinja2 native mode is now the default and only option, which is mutually exclusive with this option.
27
27
  variable_start_string:
28
28
  description: The string marking the beginning of a print statement.
@@ -45,7 +45,7 @@ DOCUMENTATION = """
45
45
  type: bool
46
46
  deprecated:
47
47
  why: This option is no longer used in the Ansible Core code base.
48
- version: "2.21"
48
+ version: "2.23"
49
49
  alternatives: Jinja2 native mode is now the default and only option.
50
50
  template_vars:
51
51
  description: A dictionary, the keys become additional variables available for templating.
@@ -105,7 +105,8 @@ import os
105
105
 
106
106
  from ansible.errors import AnsibleError
107
107
  from ansible.plugins.lookup import LookupBase
108
- from ansible.template import generate_ansible_template_vars, trust_as_template
108
+ from ansible.template import trust_as_template
109
+ from ansible._internal._templating import _template_vars
109
110
  from ansible.utils.display import Display
110
111
 
111
112
 
@@ -157,7 +158,11 @@ class LookupModule(LookupBase):
157
158
  # argument.
158
159
  # FIXME: why isn't this a chainmap with a sacrificial bottom layer?
159
160
  vars = deepcopy(variables)
160
- vars.update(generate_ansible_template_vars(term, fullpath=lookupfile))
161
+ vars.update(_template_vars.generate_ansible_template_vars(
162
+ path=term,
163
+ fullpath=lookupfile,
164
+ include_ansible_managed='ansible_managed' not in vars, # do not clobber ansible_managed when set by the user
165
+ ))
161
166
  vars.update(lookup_template_vars)
162
167
 
163
168
  overrides = dict(
@@ -325,8 +325,9 @@ class ShellModule(ShellBase):
325
325
 
326
326
  def checksum(self, path, *args, **kwargs):
327
327
  display.deprecated(
328
- "The 'ShellModule.checksum' method is deprecated. Use 'ActionBase._execute_remote_stat()' instead.",
329
- version="2.23"
328
+ msg="The `ShellModule.checksum` method is deprecated.",
329
+ version="2.23",
330
+ help_text="Use `ActionBase._execute_remote_stat()` instead.",
330
331
  )
331
332
  path = self._escape(self._unquote(path))
332
333
  script = """