ansible-core 2.19.0b4__py3-none-any.whl → 2.19.0b6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. ansible/_internal/__init__.py +1 -1
  2. ansible/_internal/_ansiballz/__init__.py +0 -0
  3. ansible/_internal/_ansiballz/_builder.py +101 -0
  4. ansible/_internal/{_ansiballz.py → _ansiballz/_wrapper.py} +11 -11
  5. ansible/_internal/_collection_proxy.py +1 -1
  6. ansible/_internal/_errors/_alarm_timeout.py +66 -0
  7. ansible/_internal/_errors/_captured.py +25 -30
  8. ansible/_internal/_errors/_error_factory.py +89 -0
  9. ansible/_internal/_errors/_error_utils.py +240 -0
  10. ansible/_internal/_errors/_task_timeout.py +28 -0
  11. ansible/_internal/_event_formatting.py +127 -0
  12. ansible/_internal/_json/__init__.py +5 -5
  13. ansible/_internal/_json/_profiles/_cache_persistence.py +2 -0
  14. ansible/_internal/_json/_profiles/_inventory_legacy.py +1 -1
  15. ansible/_internal/_json/_profiles/_legacy.py +3 -11
  16. ansible/_internal/_ssh/__init__.py +0 -0
  17. ansible/_internal/_ssh/_agent_launch.py +91 -0
  18. ansible/{utils → _internal/_ssh}/_ssh_agent.py +55 -93
  19. ansible/_internal/_templating/__init__.py +5 -3
  20. ansible/_internal/_templating/_datatag.py +2 -1
  21. ansible/_internal/_templating/_engine.py +3 -4
  22. ansible/_internal/_templating/_jinja_bits.py +28 -20
  23. ansible/_internal/_templating/_jinja_common.py +18 -27
  24. ansible/_internal/_templating/_jinja_plugins.py +36 -5
  25. ansible/_internal/_templating/_lazy_containers.py +5 -5
  26. ansible/_internal/_templating/_template_vars.py +72 -0
  27. ansible/_internal/_templating/_transform.py +26 -19
  28. ansible/_internal/_templating/_utils.py +1 -1
  29. ansible/_internal/_yaml/_constructor.py +4 -4
  30. ansible/_internal/_yaml/_dumper.py +26 -18
  31. ansible/_internal/_yaml/_errors.py +7 -7
  32. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
  33. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
  34. ansible/cli/__init__.py +11 -93
  35. ansible/cli/arguments/option_helpers.py +3 -4
  36. ansible/cli/console.py +1 -1
  37. ansible/cli/doc.py +86 -30
  38. ansible/cli/inventory.py +5 -7
  39. ansible/compat/importlib_resources.py +9 -12
  40. ansible/config/base.yml +46 -0
  41. ansible/errors/__init__.py +98 -50
  42. ansible/executor/module_common.py +75 -49
  43. ansible/executor/powershell/async_watchdog.ps1 +2 -2
  44. ansible/executor/powershell/async_wrapper.ps1 +3 -3
  45. ansible/executor/powershell/become_wrapper.ps1 +20 -2
  46. ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
  47. ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
  48. ansible/executor/powershell/exec_wrapper.ps1 +219 -6
  49. ansible/executor/powershell/module_manifest.py +52 -0
  50. ansible/executor/powershell/module_wrapper.ps1 +47 -21
  51. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  52. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  53. ansible/executor/process/worker.py +40 -115
  54. ansible/executor/task_executor.py +26 -61
  55. ansible/executor/task_result.py +2 -4
  56. ansible/galaxy/api.py +1 -4
  57. ansible/galaxy/collection/__init__.py +2 -10
  58. ansible/galaxy/collection/concrete_artifact_manager.py +2 -8
  59. ansible/galaxy/role.py +2 -2
  60. ansible/inventory/manager.py +1 -1
  61. ansible/module_utils/_internal/__init__.py +7 -7
  62. ansible/module_utils/_internal/_ambient_context.py +3 -3
  63. ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
  64. ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
  65. ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
  66. ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
  67. ansible/module_utils/_internal/{_ansiballz.py → _ansiballz/_loader.py} +13 -39
  68. ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
  69. ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
  70. ansible/module_utils/_internal/_datatag/__init__.py +43 -15
  71. ansible/module_utils/_internal/_datatag/_tags.py +2 -2
  72. ansible/module_utils/_internal/_deprecator.py +67 -55
  73. ansible/module_utils/_internal/_errors.py +88 -17
  74. ansible/module_utils/_internal/_event_utils.py +61 -0
  75. ansible/module_utils/_internal/_json/_profiles/__init__.py +22 -4
  76. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
  77. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
  78. ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
  79. ansible/module_utils/{common/messages.py → _internal/_messages.py} +54 -49
  80. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
  81. ansible/module_utils/_internal/_plugin_info.py +15 -2
  82. ansible/module_utils/_internal/_stack.py +22 -0
  83. ansible/module_utils/_internal/_text_utils.py +6 -0
  84. ansible/module_utils/_internal/_traceback.py +11 -8
  85. ansible/module_utils/ansible_release.py +1 -1
  86. ansible/module_utils/basic.py +95 -71
  87. ansible/module_utils/common/arg_spec.py +2 -2
  88. ansible/module_utils/common/collections.py +6 -0
  89. ansible/module_utils/common/json.py +2 -2
  90. ansible/module_utils/common/respawn.py +4 -41
  91. ansible/module_utils/common/text/converters.py +3 -3
  92. ansible/module_utils/common/validation.py +1 -1
  93. ansible/module_utils/common/warnings.py +80 -23
  94. ansible/module_utils/common/yaml.py +1 -1
  95. ansible/module_utils/connection.py +8 -11
  96. ansible/module_utils/datatag.py +5 -2
  97. ansible/module_utils/facts/hardware/linux.py +1 -1
  98. ansible/module_utils/facts/sysctl.py +4 -6
  99. ansible/module_utils/facts/system/caps.py +2 -2
  100. ansible/module_utils/facts/system/distribution.py +16 -3
  101. ansible/module_utils/facts/system/local.py +1 -1
  102. ansible/module_utils/facts/virtual/linux.py +2 -2
  103. ansible/module_utils/service.py +3 -10
  104. ansible/module_utils/urls.py +4 -4
  105. ansible/modules/apt_repository.py +17 -39
  106. ansible/modules/assemble.py +2 -2
  107. ansible/modules/async_status.py +13 -11
  108. ansible/modules/async_wrapper.py +12 -22
  109. ansible/modules/command.py +3 -3
  110. ansible/modules/copy.py +4 -4
  111. ansible/modules/cron.py +1 -1
  112. ansible/modules/dnf5.py +14 -22
  113. ansible/modules/file.py +16 -17
  114. ansible/modules/find.py +3 -3
  115. ansible/modules/get_url.py +17 -0
  116. ansible/modules/git.py +9 -7
  117. ansible/modules/hostname.py +0 -1
  118. ansible/modules/known_hosts.py +12 -14
  119. ansible/modules/package.py +6 -0
  120. ansible/modules/replace.py +2 -2
  121. ansible/modules/service.py +3 -9
  122. ansible/modules/slurp.py +10 -13
  123. ansible/modules/stat.py +5 -7
  124. ansible/modules/unarchive.py +6 -6
  125. ansible/modules/user.py +1 -1
  126. ansible/modules/wait_for.py +28 -30
  127. ansible/modules/yum_repository.py +4 -3
  128. ansible/parsing/ajson.py +3 -5
  129. ansible/parsing/dataloader.py +6 -6
  130. ansible/parsing/mod_args.py +1 -1
  131. ansible/parsing/plugin_docs.py +2 -2
  132. ansible/parsing/utils/yaml.py +3 -3
  133. ansible/parsing/vault/__init__.py +10 -14
  134. ansible/playbook/base.py +7 -2
  135. ansible/playbook/included_file.py +3 -1
  136. ansible/playbook/play_context.py +2 -0
  137. ansible/playbook/playbook_include.py +1 -1
  138. ansible/playbook/taggable.py +19 -8
  139. ansible/playbook/task.py +2 -0
  140. ansible/plugins/__init__.py +0 -25
  141. ansible/plugins/action/__init__.py +8 -31
  142. ansible/plugins/action/add_host.py +1 -1
  143. ansible/plugins/action/assemble.py +8 -16
  144. ansible/plugins/action/async_status.py +7 -2
  145. ansible/plugins/action/copy.py +8 -7
  146. ansible/plugins/action/fetch.py +3 -3
  147. ansible/plugins/action/gather_facts.py +8 -8
  148. ansible/plugins/action/package.py +5 -8
  149. ansible/plugins/action/script.py +8 -15
  150. ansible/plugins/action/service.py +3 -7
  151. ansible/plugins/action/template.py +11 -10
  152. ansible/plugins/action/unarchive.py +5 -15
  153. ansible/plugins/action/uri.py +9 -20
  154. ansible/plugins/cache/__init__.py +17 -19
  155. ansible/plugins/callback/__init__.py +4 -6
  156. ansible/plugins/callback/junit.py +4 -2
  157. ansible/plugins/callback/tree.py +5 -5
  158. ansible/plugins/connection/local.py +6 -6
  159. ansible/plugins/connection/paramiko_ssh.py +5 -5
  160. ansible/plugins/connection/ssh.py +25 -15
  161. ansible/plugins/connection/winrm.py +6 -3
  162. ansible/plugins/doc_fragments/constructed.py +2 -2
  163. ansible/plugins/filter/core.py +32 -27
  164. ansible/plugins/filter/encryption.py +14 -6
  165. ansible/plugins/inventory/__init__.py +11 -10
  166. ansible/plugins/inventory/script.py +1 -1
  167. ansible/plugins/list.py +73 -19
  168. ansible/plugins/loader.py +7 -7
  169. ansible/plugins/lookup/csvfile.py +16 -71
  170. ansible/plugins/lookup/first_found.py +2 -1
  171. ansible/plugins/lookup/template.py +9 -4
  172. ansible/plugins/shell/__init__.py +56 -2
  173. ansible/plugins/shell/powershell.py +67 -9
  174. ansible/plugins/shell/sh.py +10 -5
  175. ansible/plugins/strategy/__init__.py +3 -3
  176. ansible/plugins/test/core.py +22 -16
  177. ansible/plugins/test/finished.yml +1 -1
  178. ansible/plugins/test/uri.py +2 -5
  179. ansible/release.py +1 -1
  180. ansible/template/__init__.py +38 -54
  181. ansible/utils/collection_loader/_collection_finder.py +3 -3
  182. ansible/utils/display.py +124 -138
  183. ansible/utils/galaxy.py +2 -2
  184. ansible/utils/hashing.py +6 -8
  185. ansible/utils/listify.py +6 -4
  186. ansible/utils/path.py +5 -7
  187. ansible/utils/py3compat.py +2 -1
  188. ansible/utils/ssh_functions.py +3 -2
  189. ansible/utils/unsafe_proxy.py +1 -1
  190. ansible/vars/hostvars.py +1 -1
  191. ansible/vars/plugins.py +3 -3
  192. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/METADATA +1 -1
  193. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/RECORD +224 -204
  194. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/WHEEL +1 -1
  195. ansible_test/_data/completion/docker.txt +3 -3
  196. ansible_test/_data/completion/remote.txt +1 -0
  197. ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
  198. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  199. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  200. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  201. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  202. ansible_test/_internal/commands/integration/coverage.py +7 -2
  203. ansible_test/_internal/host_profiles.py +62 -10
  204. ansible_test/_internal/provisioning.py +10 -4
  205. ansible_test/_internal/ssh.py +1 -5
  206. ansible_test/_internal/thread.py +2 -1
  207. ansible_test/_internal/timeout.py +1 -1
  208. ansible_test/_internal/util.py +40 -12
  209. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
  210. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
  211. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
  212. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  213. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  214. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
  215. ansible_test/_util/target/setup/bootstrap.sh +31 -0
  216. ansible_test/_util/target/setup/requirements.py +3 -9
  217. ansible/_internal/_errors/_utils.py +0 -310
  218. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/entry_points.txt +0 -0
  219. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/COPYING +0 -0
  220. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  221. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  222. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  223. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  224. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  225. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b6.dist-info}/top_level.txt +0 -0
@@ -160,7 +160,7 @@ class ModuleArgsParser:
160
160
  final_args = dict()
161
161
  if additional_args:
162
162
  if isinstance(additional_args, (str, EncryptedString)):
163
- # DTFIX-RELEASE: should this be is_possibly_template?
163
+ # DTFIX5: should this be is_possibly_template?
164
164
  if TemplateEngine().is_template(additional_args):
165
165
  final_args['_variable_params'] = additional_args
166
166
  else:
@@ -41,7 +41,7 @@ def read_docstring_from_yaml_file(filename, verbose=True, ignore_errors=True):
41
41
  file_data = yaml.load(yamlfile, Loader=AnsibleLoader)
42
42
  except Exception as ex:
43
43
  msg = f"Unable to parse yaml file {filename}"
44
- # DTFIX-RELEASE: find a better pattern for this (can we use the new optional error behavior?)
44
+ # DTFIX-FUTURE: find a better pattern for this (can we use the new optional error behavior?)
45
45
  if not ignore_errors:
46
46
  raise AnsibleParserError(f'{msg}.') from ex
47
47
  elif verbose:
@@ -93,7 +93,7 @@ def read_docstring_from_python_file(filename, verbose=True, ignore_errors=True):
93
93
 
94
94
  except Exception as ex:
95
95
  msg = f"Unable to parse documentation in python file {filename!r}"
96
- # DTFIX-RELEASE: better pattern to conditionally raise/display
96
+ # DTFIX-FUTURE: better pattern to conditionally raise/display
97
97
  if not ignore_errors:
98
98
  raise AnsibleParserError(f'{msg}.') from ex
99
99
  elif verbose:
@@ -11,7 +11,7 @@ import typing as t
11
11
  import yaml
12
12
 
13
13
  from ansible.errors import AnsibleJSONParserError
14
- from ansible._internal._errors import _utils
14
+ from ansible._internal._errors import _error_utils
15
15
  from ansible.parsing.vault import VaultSecret
16
16
  from ansible.parsing.yaml.loader import AnsibleLoader
17
17
  from ansible._internal._yaml._errors import AnsibleYAMLParserError
@@ -34,7 +34,7 @@ def from_yaml(
34
34
 
35
35
  data = origin.tag(data)
36
36
 
37
- with _utils.RedactAnnotatedSourceContext.when(not show_content):
37
+ with _error_utils.RedactAnnotatedSourceContext.when(not show_content):
38
38
  try:
39
39
  # we first try to load this data as JSON.
40
40
  # Fixes issues with extra vars json strings not being parsed correctly by the yaml parser
@@ -48,6 +48,6 @@ def from_yaml(
48
48
  try:
49
49
  return yaml.load(data, Loader=AnsibleLoader) # type: ignore[arg-type]
50
50
  except Exception as yaml_ex:
51
- # DTFIX-RELEASE: how can we indicate in Origin that the data is in-memory only, to support context information -- is that useful?
51
+ # DTFIX-FUTURE: how can we indicate in Origin that the data is in-memory only, to support context information -- is that useful?
52
52
  # we'd need to pass data to handle_exception so it could be used as the content instead of reading from disk
53
53
  AnsibleYAMLParserError.handle_exception(yaml_ex, origin=origin)
@@ -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
@@ -149,7 +148,7 @@ def _parse_vaulttext_envelope(b_vaulttext_envelope, default_vault_id=None):
149
148
  vault_id = to_text(b_tmpheader[3].strip())
150
149
 
151
150
  b_ciphertext = b''.join(b_tmpdata[1:])
152
- # DTFIX-RELEASE: possible candidate for propagate_origin
151
+ # DTFIX7: possible candidate for propagate_origin
153
152
  b_ciphertext = AnsibleTagHelper.tag_copy(b_vaulttext_envelope, b_ciphertext)
154
153
 
155
154
  return b_ciphertext, b_version, cipher_name, vault_id
@@ -222,7 +221,7 @@ def format_vaulttext_envelope(b_ciphertext, cipher_name, version=None, vault_id=
222
221
 
223
222
  def _unhexlify(b_data):
224
223
  try:
225
- # DTFIX-RELEASE: possible candidate for propagate_origin
224
+ # DTFIX7: possible candidate for propagate_origin
226
225
  return AnsibleTagHelper.tag_copy(b_data, unhexlify(b_data))
227
226
  except (BinasciiError, TypeError) as ex:
228
227
  raise AnsibleVaultFormatError('Vault format unhexlify error.', obj=b_data) from ex
@@ -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
 
@@ -712,7 +711,7 @@ class VaultLib:
712
711
  # secret = self.secrets[vault_secret_id]
713
712
  display.vvvv(u'Trying secret %s for vault_id=%s' % (to_text(vault_secret), to_text(vault_secret_id)))
714
713
  b_plaintext = this_cipher.decrypt(b_vaulttext, vault_secret)
715
- # DTFIX-RELEASE: possible candidate for propagate_origin
714
+ # DTFIX7: possible candidate for propagate_origin
716
715
  b_plaintext = AnsibleTagHelper.tag_copy(vaulttext, b_plaintext)
717
716
  if b_plaintext is not None:
718
717
  vault_id_used = vault_secret_id
@@ -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
@@ -1520,7 +1516,7 @@ class VaultHelper:
1520
1516
  tags = AnsibleTagHelper.tags(ciphertext) # ciphertext has tags but value does not
1521
1517
  elif value_type is EncryptedString:
1522
1518
  ciphertext = value._ciphertext
1523
- elif value_type in _jinja_common.Marker.concrete_subclasses: # avoid wasteful raise/except of Marker when calling get_tag below
1519
+ elif value_type in _jinja_common.Marker._concrete_subclasses: # avoid wasteful raise/except of Marker when calling get_tag below
1524
1520
  ciphertext = None
1525
1521
  elif vaulted_value := VaultedValue.get_tag(value):
1526
1522
  ciphertext = vaulted_value.ciphertext
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')
@@ -164,5 +164,5 @@ class PlaybookInclude(Base, Conditional, Taggable):
164
164
  if len(items) == 0:
165
165
  raise AnsibleParserError("import_playbook statements must specify the file name to import", obj=ds)
166
166
 
167
- # DTFIX-RELEASE: investigate this as a possible "problematic strip"
167
+ # DTFIX3: investigate this as a possible "problematic strip"
168
168
  new_ds['import_playbook'] = AnsibleTagHelper.tag_copy(v, items[0].strip())
@@ -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):
@@ -45,23 +47,32 @@ class Taggable:
45
47
  return ds
46
48
 
47
49
  if isinstance(ds, str):
48
- # DTFIX-RELEASE: this allows each individual tag to be templated, but prevents the use of commas in templates, is that what we want?
49
- # DTFIX-RELEASE: this can return empty tags (including a list of nothing but empty tags), is that correct?
50
- # DTFIX-RELEASE: the original code seemed to attempt to preserve `ds` if there were no commas, but it never ran, what should it actually do?
51
50
  return [AnsibleTagHelper.tag_copy(ds, item.strip()) for item in ds.split(',')]
52
51
 
53
52
  raise AnsibleError('tags must be specified as a list', obj=ds)
54
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
+
55
66
  def evaluate_tags(self, only_tags, skip_tags, all_vars):
56
- """ 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.
57
68
 
69
+ NOTE this method is assumed to be called only on Task objects.
70
+ """
58
71
  if self.tags:
59
72
  templar = TemplateEngine(loader=self._loader, variables=all_vars)
60
- obj = self
61
- while obj is not None:
73
+ for obj in self._get_all_taggable_objects():
62
74
  if (_tags := getattr(obj, "_tags", Sentinel)) is not Sentinel:
63
75
  obj._tags = _flatten_tags(templar.template(_tags))
64
- obj = obj._parent
65
76
  tags = set(self.tags)
66
77
  else:
67
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
@@ -189,28 +189,3 @@ class AnsibleJinja2Plugin(AnsiblePlugin, metaclass=abc.ABCMeta):
189
189
  @property
190
190
  def j2_function(self) -> t.Callable:
191
191
  return self._function
192
-
193
-
194
- _TCallable = t.TypeVar('_TCallable', bound=t.Callable)
195
-
196
-
197
- def accept_args_markers(plugin: _TCallable) -> _TCallable:
198
- """
199
- A decorator to mark a Jinja plugin as capable of handling `Marker` values for its top-level arguments.
200
- Non-decorated plugin invocation is skipped when a top-level argument is a `Marker`, with the first such value substituted as the plugin result.
201
- This ensures that only plugins which understand `Marker` instances for top-level arguments will encounter them.
202
- """
203
- plugin.accept_args_markers = True
204
-
205
- return plugin
206
-
207
-
208
- def accept_lazy_markers(plugin: _TCallable) -> _TCallable:
209
- """
210
- A decorator to mark a Jinja plugin as capable of handling `Marker` values retrieved from lazy containers.
211
- Non-decorated plugins will trigger a `MarkerError` exception when attempting to retrieve a `Marker` from a lazy container.
212
- This ensures that only plugins which understand lazy retrieval of `Marker` instances will encounter them.
213
- """
214
- plugin.accept_lazy_markers = True
215
-
216
- return plugin
@@ -20,9 +20,8 @@ from abc import ABC, abstractmethod
20
20
  from collections.abc import Sequence
21
21
 
22
22
  from ansible import constants as C
23
- from ansible._internal._errors import _captured
23
+ from ansible._internal._errors import _captured, _error_utils
24
24
  from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleActionSkip, AnsibleActionFail, AnsibleAuthenticationFailure
25
- from ansible._internal._errors import _utils
26
25
  from ansible.executor.module_common import modify_module, _BuiltModule
27
26
  from ansible.executor.interpreter_discovery import discover_interpreter, InterpreterDiscoveryRequiredError
28
27
  from ansible.module_utils._internal import _traceback
@@ -41,7 +40,6 @@ from ansible import _internal
41
40
  from ansible._internal._templating import _engine
42
41
 
43
42
  from .. import _AnsiblePluginInfoMixin
44
- from ...module_utils.common.messages import PluginInfo
45
43
 
46
44
  display = Display()
47
45
 
@@ -121,8 +119,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
121
119
 
122
120
  * Module parameters. These are stored in self._task.args
123
121
  """
124
-
125
- # does not default to {'changed': False, 'failed': False}, as it breaks async
122
+ # does not default to {'changed': False, 'failed': False}, as it used to break async
126
123
  result = {}
127
124
 
128
125
  if tmp is not None:
@@ -476,8 +473,8 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
476
473
 
477
474
  become_unprivileged = self._is_become_unprivileged()
478
475
  basefile = self._connection._shell._generate_temp_dir_name()
479
- cmd = self._connection._shell.mkdtemp(basefile=basefile, system=become_unprivileged, tmpdir=tmpdir)
480
- result = self._low_level_execute_command(cmd, sudoable=False)
476
+ cmd = self._connection._shell._mkdtemp2(basefile=basefile, system=become_unprivileged, tmpdir=tmpdir)
477
+ result = self._low_level_execute_command(cmd.command, in_data=cmd.input_data, sudoable=False)
481
478
 
482
479
  # error handling on this seems a little aggressive?
483
480
  if result['rc'] != 0:
@@ -908,8 +905,8 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
908
905
  expand_path = '~%s' % (self._get_remote_user() or '')
909
906
 
910
907
  # use shell to construct appropriate command and execute
911
- cmd = self._connection._shell.expand_user(expand_path)
912
- data = self._low_level_execute_command(cmd, sudoable=False)
908
+ cmd = self._connection._shell._expand_user2(expand_path)
909
+ data = self._low_level_execute_command(cmd.command, in_data=cmd.input_data, sudoable=False)
913
910
 
914
911
  try:
915
912
  initial_fragment = data['stdout'].strip().splitlines()[-1]
@@ -1115,7 +1112,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
1115
1112
  if wrap_async and not self._connection.always_pipeline_modules:
1116
1113
  # configure, upload, and chmod the async_wrapper module
1117
1114
  (async_module_bits, async_module_path) = self._configure_module(module_name='ansible.legacy.async_wrapper', module_args=dict(), task_vars=task_vars)
1118
- (async_module_style, shebang, async_module_data) = (async_module_bits.module_style, async_module_bits.shebang, async_module_bits.b_module_data)
1115
+ (shebang, async_module_data) = (async_module_bits.shebang, async_module_bits.b_module_data)
1119
1116
  async_module_remote_filename = self._connection._shell.get_remote_filename(async_module_path)
1120
1117
  remote_async_module_path = self._connection._shell.join_path(tmpdir, async_module_remote_filename)
1121
1118
  self._transfer_data(remote_async_module_path, async_module_data)
@@ -1255,7 +1252,7 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
1255
1252
  except AnsibleError as ansible_ex:
1256
1253
  sentinel = object()
1257
1254
 
1258
- data = self.result_dict_from_exception(ansible_ex)
1255
+ data = _error_utils.result_dict_from_exception(ansible_ex)
1259
1256
  data.update(
1260
1257
  _ansible_parsed=False,
1261
1258
  module_stdout=res.get('stdout', ''),
@@ -1436,23 +1433,3 @@ class ActionBase(ABC, _AnsiblePluginInfoMixin):
1436
1433
 
1437
1434
  # if missing it will return a file not found exception
1438
1435
  return self._loader.path_dwim_relative_stack(path_stack, dirname, needle)
1439
-
1440
- @staticmethod
1441
- def result_dict_from_exception(exception: BaseException) -> dict[str, t.Any]:
1442
- """Return a failed task result dict from the given exception."""
1443
- if ansible_remoted_error := _captured.AnsibleResultCapturedError.find_first_remoted_error(exception):
1444
- result = ansible_remoted_error._result.copy()
1445
- else:
1446
- result = {}
1447
-
1448
- error_summary = _utils._create_error_summary(exception, _traceback.TracebackEvent.ERROR)
1449
-
1450
- result.update(
1451
- failed=True,
1452
- exception=error_summary,
1453
- )
1454
-
1455
- if 'msg' not in result:
1456
- result.update(msg=_utils._dedupe_and_concat_message_chain([md.msg for md in error_summary.details]))
1457
-
1458
- return result
@@ -77,7 +77,7 @@ class ActionModule(ActionBase):
77
77
  elif isinstance(groups, string_types):
78
78
  group_list = groups.split(",")
79
79
  else:
80
- raise AnsibleActionFail("Groups must be specified as a list.", obj=self._task)
80
+ raise AnsibleActionFail("Groups must be specified as a list.", obj=groups)
81
81
 
82
82
  for group_name in group_list:
83
83
  if group_name not in new_groups:
@@ -25,8 +25,8 @@ import re
25
25
  import tempfile
26
26
 
27
27
  from ansible import constants as C
28
- from ansible.errors import AnsibleError, AnsibleAction, _AnsibleActionDone, AnsibleActionFail
29
- from ansible.module_utils.common.text.converters import to_native, to_text
28
+ from ansible.errors import AnsibleActionFail
29
+ from ansible.module_utils.common.text.converters import to_text
30
30
  from ansible.module_utils.parsing.convert_bool import boolean
31
31
  from ansible.plugins.action import ActionBase
32
32
  from ansible.utils.hashing import checksum_s
@@ -83,7 +83,7 @@ class ActionModule(ActionBase):
83
83
 
84
84
  self._supports_check_mode = False
85
85
 
86
- result = super(ActionModule, self).run(tmp, task_vars)
86
+ super(ActionModule, self).run(tmp, task_vars)
87
87
  del tmp # tmp no longer has any effect
88
88
 
89
89
  if task_vars is None:
@@ -104,13 +104,9 @@ class ActionModule(ActionBase):
104
104
 
105
105
  if boolean(remote_src, strict=False):
106
106
  # call assemble via ansible.legacy to allow library/ overrides of the module without collection search
107
- result.update(self._execute_module(module_name='ansible.legacy.assemble', task_vars=task_vars))
108
- raise _AnsibleActionDone()
109
- else:
110
- try:
111
- src = self._find_needle('files', src)
112
- except AnsibleError as e:
113
- raise AnsibleActionFail(to_native(e))
107
+ return self._execute_module(module_name='ansible.legacy.assemble', task_vars=task_vars)
108
+
109
+ src = self._find_needle('files', src)
114
110
 
115
111
  if not os.path.isdir(src):
116
112
  raise AnsibleActionFail(u"Source (%s) is not a directory" % src)
@@ -153,13 +149,9 @@ class ActionModule(ActionBase):
153
149
  res = self._execute_module(module_name='ansible.legacy.copy', module_args=new_module_args, task_vars=task_vars)
154
150
  if diff:
155
151
  res['diff'] = diff
156
- result.update(res)
152
+ return res
157
153
  else:
158
- result.update(self._execute_module(module_name='ansible.legacy.file', module_args=new_module_args, task_vars=task_vars))
154
+ return self._execute_module(module_name='ansible.legacy.file', module_args=new_module_args, task_vars=task_vars)
159
155
 
160
- except AnsibleAction as e:
161
- result.update(e.result)
162
156
  finally:
163
157
  self._remove_tmp_path(self._connection._shell.tmpdir)
164
-
165
- return result
@@ -28,7 +28,7 @@ class ActionModule(ActionBase):
28
28
  )
29
29
 
30
30
  # initialize response
31
- results['started'] = results['finished'] = 0
31
+ results['started'] = results['finished'] = False
32
32
  results['stdout'] = results['stderr'] = ''
33
33
  results['stdout_lines'] = results['stderr_lines'] = []
34
34
 
@@ -43,9 +43,14 @@ class ActionModule(ActionBase):
43
43
  results['erased'] = log_path
44
44
  else:
45
45
  results['results_file'] = log_path
46
- results['started'] = 1
46
+ results['started'] = True
47
47
 
48
48
  new_module_args['_async_dir'] = async_dir
49
49
  results = merge_hash(results, self._execute_module(module_name='ansible.legacy.async_status', task_vars=task_vars, module_args=new_module_args))
50
50
 
51
+ # Backwards compat shim for when started/finished were ints,
52
+ # mostly to work with ansible.windows.async_status
53
+ for convert in ('started', 'finished'):
54
+ results[convert] = bool(results[convert])
55
+
51
56
  return results
@@ -27,7 +27,7 @@ import tempfile
27
27
  from ansible import constants as C
28
28
  from ansible.errors import AnsibleError, AnsibleActionFail, AnsibleFileNotFound
29
29
  from ansible.module_utils.basic import FILE_COMMON_ARGUMENTS
30
- from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
30
+ from ansible.module_utils.common.text.converters import to_bytes, to_text
31
31
  from ansible.module_utils.parsing.convert_bool import boolean
32
32
  from ansible.plugins.action import ActionBase
33
33
  from ansible.utils.hashing import checksum
@@ -409,6 +409,7 @@ class ActionModule(ActionBase):
409
409
  task_vars = dict()
410
410
 
411
411
  result = super(ActionModule, self).run(tmp, task_vars)
412
+
412
413
  del tmp # tmp no longer has any effect
413
414
 
414
415
  # ensure user is not setting internal parameters
@@ -450,10 +451,10 @@ class ActionModule(ActionBase):
450
451
  else:
451
452
  content_tempfile = self._create_content_tempfile(content)
452
453
  source = content_tempfile
453
- except Exception as err:
454
- result['failed'] = True
455
- result['msg'] = "could not write content temp file: %s" % to_native(err)
456
- return self._ensure_invocation(result)
454
+ except Exception as ex:
455
+ self._ensure_invocation(result)
456
+
457
+ raise AnsibleActionFail(message="could not write content temp file", result=result) from ex
457
458
 
458
459
  # if we have first_available_file in our vars
459
460
  # look up the files and use the first one we find as src
@@ -470,9 +471,9 @@ class ActionModule(ActionBase):
470
471
  # find in expected paths
471
472
  source = self._find_needle('files', source)
472
473
  except AnsibleError as ex:
473
- result.update(self.result_dict_from_exception(ex))
474
+ self._ensure_invocation(result)
474
475
 
475
- return self._ensure_invocation(result)
476
+ raise AnsibleActionFail(result=result) from ex
476
477
 
477
478
  if trailing_slash != source.endswith(os.path.sep):
478
479
  if source[-1] == os.path.sep:
@@ -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:
@@ -13,6 +13,7 @@ from ansible.executor.module_common import _apply_action_arg_defaults
13
13
  from ansible.module_utils.parsing.convert_bool import boolean
14
14
  from ansible.plugins.action import ActionBase
15
15
  from ansible.utils.vars import merge_hash
16
+ from ansible._internal._errors import _error_utils
16
17
 
17
18
 
18
19
  class ActionModule(ActionBase):
@@ -127,8 +128,6 @@ class ActionModule(ActionBase):
127
128
  # TODO: use gather_timeout to cut module execution if module itself does not support gather_timeout
128
129
  res = self._execute_module(module_name=fact_module, module_args=mod_args, task_vars=task_vars, wrap_async=False)
129
130
  if res.get('failed', False):
130
- # DTFIX-RELEASE: this trashes the individual failure details and does not work with the new error handling; need to do something to
131
- # invoke per-item error handling- perhaps returning this as a synthetic loop result?
132
131
  failed[fact_module] = res
133
132
  elif res.get('skipped', False):
134
133
  skipped[fact_module] = res
@@ -159,10 +158,8 @@ class ActionModule(ActionBase):
159
158
  for module in jobs:
160
159
  poll_args = {'jid': jobs[module]['ansible_job_id'], '_async_dir': os.path.dirname(jobs[module]['results_file'])}
161
160
  res = self._execute_module(module_name='ansible.legacy.async_status', module_args=poll_args, task_vars=task_vars, wrap_async=False)
162
- if res.get('finished', 0) == 1:
161
+ if res.get('finished', False):
163
162
  if res.get('failed', False):
164
- # DTFIX-RELEASE: this trashes the individual failure details and does not work with the new error handling; need to do something to
165
- # invoke per-item error handling- perhaps returning this as a synthetic loop result?
166
163
  failed[module] = res
167
164
  elif res.get('skipped', False):
168
165
  skipped[module] = res
@@ -180,16 +177,19 @@ class ActionModule(ActionBase):
180
177
  self._task.async_val = async_val
181
178
 
182
179
  if skipped:
183
- result['msg'] = "The following modules were skipped: %s\n" % (', '.join(skipped.keys()))
180
+ result['msg'] = f"The following modules were skipped: {', '.join(skipped.keys())}."
184
181
  result['skipped_modules'] = skipped
185
182
  if len(skipped) == len(modules):
186
183
  result['skipped'] = True
187
184
 
188
185
  if failed:
189
- result['failed'] = True
190
- result['msg'] = "The following modules failed to execute: %s\n" % (', '.join(failed.keys()))
191
186
  result['failed_modules'] = failed
192
187
 
188
+ result.update(_error_utils.result_dict_from_captured_errors(
189
+ msg=f"The following modules failed to execute: {', '.join(failed.keys())}.",
190
+ errors=[r['exception'] for r in failed.values()],
191
+ ))
192
+
193
193
  # tell executor facts were gathered
194
194
  result['ansible_facts']['_ansible_facts_gathered'] = True
195
195
 
@@ -16,7 +16,7 @@
16
16
  # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
17
17
  from __future__ import annotations
18
18
 
19
- from ansible.errors import AnsibleAction, AnsibleActionFail
19
+ from ansible.errors import AnsibleActionFail
20
20
  from ansible.executor.module_common import _apply_action_arg_defaults
21
21
  from ansible.module_utils.facts.system.pkg_mgr import PKG_MGRS
22
22
  from ansible.plugins.action import ActionBase
@@ -38,7 +38,7 @@ class ActionModule(ActionBase):
38
38
  self._supports_check_mode = True
39
39
  self._supports_async = True
40
40
 
41
- result = super(ActionModule, self).run(tmp, task_vars)
41
+ super(ActionModule, self).run(tmp, task_vars)
42
42
 
43
43
  module = self._task.args.get('use', 'auto')
44
44
 
@@ -99,11 +99,8 @@ class ActionModule(ActionBase):
99
99
  module = 'ansible.legacy.' + module
100
100
 
101
101
  display.vvvv("Running %s" % module)
102
- result.update(self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val))
102
+ return self._execute_module(module_name=module, module_args=new_module_args, task_vars=task_vars, wrap_async=self._task.async_val)
103
103
  else:
104
104
  raise AnsibleActionFail('Could not detect which package manager to use. Try gathering facts or setting the "use" option.')
105
-
106
- except AnsibleAction as e:
107
- result.update(e.result)
108
-
109
- return result
105
+ finally:
106
+ pass # avoid de-dent all on refactor