ansible-core 2.19.0b4__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 (163) hide show
  1. ansible/_internal/__init__.py +1 -1
  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 +5 -5
  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/_yaml/_dumper.py +1 -1
  26. ansible/_internal/_yaml/_errors.py +7 -7
  27. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +1 -1
  28. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +1 -1
  29. ansible/cli/__init__.py +5 -82
  30. ansible/cli/arguments/option_helpers.py +2 -3
  31. ansible/cli/doc.py +84 -28
  32. ansible/cli/inventory.py +1 -1
  33. ansible/compat/importlib_resources.py +9 -12
  34. ansible/config/base.yml +22 -0
  35. ansible/errors/__init__.py +96 -49
  36. ansible/executor/module_common.py +8 -10
  37. ansible/executor/powershell/async_watchdog.ps1 +2 -2
  38. ansible/executor/powershell/async_wrapper.ps1 +3 -3
  39. ansible/executor/powershell/become_wrapper.ps1 +20 -2
  40. ansible/executor/powershell/bootstrap_wrapper.ps1 +28 -6
  41. ansible/executor/powershell/coverage_wrapper.ps1 +15 -6
  42. ansible/executor/powershell/exec_wrapper.ps1 +219 -6
  43. ansible/executor/powershell/module_manifest.py +52 -0
  44. ansible/executor/powershell/module_wrapper.ps1 +47 -21
  45. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  46. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  47. ansible/executor/process/worker.py +38 -113
  48. ansible/executor/task_executor.py +26 -61
  49. ansible/executor/task_result.py +2 -4
  50. ansible/galaxy/collection/__init__.py +1 -4
  51. ansible/inventory/manager.py +1 -1
  52. ansible/module_utils/_internal/__init__.py +0 -3
  53. ansible/module_utils/_internal/_ambient_context.py +3 -3
  54. ansible/module_utils/_internal/_ansiballz.py +4 -2
  55. ansible/module_utils/_internal/_datatag/__init__.py +20 -14
  56. ansible/module_utils/_internal/_datatag/_tags.py +2 -2
  57. ansible/module_utils/_internal/_deprecator.py +66 -48
  58. ansible/module_utils/_internal/_errors.py +88 -17
  59. ansible/module_utils/_internal/_event_utils.py +61 -0
  60. ansible/module_utils/_internal/_json/_profiles/__init__.py +21 -4
  61. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +2 -0
  62. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +2 -0
  63. ansible/module_utils/_internal/_json/_profiles/_tagless.py +3 -1
  64. ansible/module_utils/{common/messages.py → _internal/_messages.py} +28 -47
  65. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +1 -3
  66. ansible/module_utils/_internal/_plugin_info.py +1 -1
  67. ansible/module_utils/_internal/_stack.py +22 -0
  68. ansible/module_utils/_internal/_text_utils.py +6 -0
  69. ansible/module_utils/_internal/_traceback.py +11 -8
  70. ansible/module_utils/ansible_release.py +1 -1
  71. ansible/module_utils/basic.py +49 -15
  72. ansible/module_utils/common/arg_spec.py +2 -2
  73. ansible/module_utils/common/collections.py +6 -0
  74. ansible/module_utils/common/json.py +2 -2
  75. ansible/module_utils/common/text/converters.py +3 -3
  76. ansible/module_utils/common/validation.py +1 -1
  77. ansible/module_utils/common/warnings.py +80 -23
  78. ansible/module_utils/common/yaml.py +1 -1
  79. ansible/module_utils/datatag.py +5 -2
  80. ansible/module_utils/facts/system/distribution.py +16 -3
  81. ansible/module_utils/facts/virtual/linux.py +1 -1
  82. ansible/module_utils/service.py +2 -9
  83. ansible/modules/apt_repository.py +7 -29
  84. ansible/modules/async_status.py +13 -11
  85. ansible/modules/async_wrapper.py +5 -5
  86. ansible/modules/dnf5.py +14 -22
  87. ansible/modules/hostname.py +0 -1
  88. ansible/modules/service.py +3 -9
  89. ansible/parsing/ajson.py +3 -5
  90. ansible/parsing/dataloader.py +4 -4
  91. ansible/parsing/mod_args.py +1 -1
  92. ansible/parsing/plugin_docs.py +2 -2
  93. ansible/parsing/utils/yaml.py +3 -3
  94. ansible/parsing/vault/__init__.py +4 -4
  95. ansible/playbook/playbook_include.py +1 -1
  96. ansible/playbook/taggable.py +0 -3
  97. ansible/plugins/__init__.py +0 -25
  98. ansible/plugins/action/__init__.py +8 -31
  99. ansible/plugins/action/add_host.py +1 -1
  100. ansible/plugins/action/assemble.py +8 -16
  101. ansible/plugins/action/async_status.py +7 -2
  102. ansible/plugins/action/copy.py +8 -7
  103. ansible/plugins/action/gather_facts.py +8 -8
  104. ansible/plugins/action/package.py +5 -8
  105. ansible/plugins/action/script.py +8 -15
  106. ansible/plugins/action/service.py +3 -7
  107. ansible/plugins/action/template.py +3 -8
  108. ansible/plugins/action/unarchive.py +5 -15
  109. ansible/plugins/action/uri.py +9 -20
  110. ansible/plugins/callback/__init__.py +4 -6
  111. ansible/plugins/callback/junit.py +4 -2
  112. ansible/plugins/connection/local.py +2 -2
  113. ansible/plugins/connection/ssh.py +17 -9
  114. ansible/plugins/connection/winrm.py +5 -2
  115. ansible/plugins/doc_fragments/constructed.py +2 -2
  116. ansible/plugins/filter/core.py +13 -6
  117. ansible/plugins/filter/encryption.py +4 -4
  118. ansible/plugins/inventory/__init__.py +11 -10
  119. ansible/plugins/inventory/script.py +1 -1
  120. ansible/plugins/list.py +69 -16
  121. ansible/plugins/loader.py +7 -7
  122. ansible/plugins/lookup/csvfile.py +16 -71
  123. ansible/plugins/lookup/first_found.py +2 -1
  124. ansible/plugins/shell/__init__.py +56 -2
  125. ansible/plugins/shell/powershell.py +66 -9
  126. ansible/plugins/shell/sh.py +9 -5
  127. ansible/plugins/test/core.py +21 -15
  128. ansible/plugins/test/finished.yml +1 -1
  129. ansible/plugins/test/uri.py +2 -5
  130. ansible/release.py +1 -1
  131. ansible/template/__init__.py +30 -2
  132. ansible/utils/display.py +103 -128
  133. ansible/utils/hashing.py +0 -1
  134. ansible/utils/listify.py +6 -4
  135. ansible/utils/unsafe_proxy.py +1 -1
  136. ansible/vars/hostvars.py +1 -1
  137. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/METADATA +1 -1
  138. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/RECORD +162 -151
  139. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/WHEEL +1 -1
  140. ansible_test/_data/completion/docker.txt +3 -3
  141. ansible_test/_data/completion/remote.txt +1 -0
  142. ansible_test/_data/requirements/sanity.ansible-doc.txt +1 -1
  143. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  144. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  145. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  146. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  147. ansible_test/_internal/util.py +20 -0
  148. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +1 -0
  149. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +1 -0
  150. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +1 -0
  151. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  152. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  153. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +61 -7
  154. ansible_test/_util/target/setup/bootstrap.sh +31 -0
  155. ansible/_internal/_errors/_utils.py +0 -310
  156. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/entry_points.txt +0 -0
  157. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/COPYING +0 -0
  158. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  159. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  160. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  161. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  162. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  163. {ansible_core-2.19.0b4.dist-info → ansible_core-2.19.0b5.dist-info}/top_level.txt +0 -0
@@ -149,7 +149,7 @@ def _run_module(wrapped_cmd, jid):
149
149
 
150
150
  # DTFIX-FUTURE: needs rework for serialization profiles
151
151
 
152
- jwrite({"started": 1, "finished": 0, "ansible_job_id": jid})
152
+ jwrite({"started": True, "finished": False, "ansible_job_id": jid})
153
153
 
154
154
  result = {}
155
155
 
@@ -203,7 +203,7 @@ def _run_module(wrapped_cmd, jid):
203
203
  except (OSError, IOError):
204
204
  e = sys.exc_info()[1]
205
205
  result = {
206
- "failed": 1,
206
+ "failed": True,
207
207
  "cmd": wrapped_cmd,
208
208
  "msg": to_text(e),
209
209
  "outdata": outdata, # temporary notice only
@@ -214,7 +214,7 @@ def _run_module(wrapped_cmd, jid):
214
214
 
215
215
  except (ValueError, Exception):
216
216
  result = {
217
- "failed": 1,
217
+ "failed": True,
218
218
  "cmd": wrapped_cmd,
219
219
  "data": outdata, # temporary notice only
220
220
  "stderr": stderr,
@@ -260,7 +260,7 @@ def main():
260
260
  _make_temp_dir(jobdir)
261
261
  except Exception as e:
262
262
  end({
263
- "failed": 1,
263
+ "failed": True,
264
264
  "msg": "could not create directory: %s - %s" % (jobdir, to_text(e)),
265
265
  "exception": to_text(traceback.format_exc()), # NB: task executor compat will coerce to the correct dataclass type
266
266
  }, 1)
@@ -293,7 +293,7 @@ def main():
293
293
  continue
294
294
 
295
295
  notice("Return async_wrapper task started.")
296
- end({"failed": 0, "started": 1, "finished": 0, "ansible_job_id": jid, "results_file": job_path,
296
+ end({"failed": False, "started": True, "finished": False, "ansible_job_id": jid, "results_file": job_path,
297
297
  "_ansible_suppress_tmpdir_delete": (not preserve_tmp)}, 0)
298
298
  else:
299
299
  # The actual wrapper process
ansible/modules/dnf5.py CHANGED
@@ -365,7 +365,7 @@ from ansible.module_utils.yumdnf import YumDnf, yumdnf_argument_spec
365
365
 
366
366
  libdnf5 = None
367
367
  # Through dnf5-5.2.12 all exceptions raised through swig became RuntimeError
368
- LIBDNF5_ERROR = RuntimeError
368
+ LIBDNF5_ERRORS = RuntimeError
369
369
 
370
370
 
371
371
  def is_installed(base, spec):
@@ -423,7 +423,9 @@ def is_newer_version_installed(base, spec):
423
423
 
424
424
  try:
425
425
  spec_nevra = next(iter(libdnf5.rpm.Nevra.parse(spec)))
426
- except (LIBDNF5_ERROR, StopIteration):
426
+ except LIBDNF5_ERRORS:
427
+ return False
428
+ except StopIteration:
427
429
  return False
428
430
 
429
431
  spec_version = spec_nevra.get_version()
@@ -517,7 +519,7 @@ class Dnf5Module(YumDnf):
517
519
  os.environ["LANGUAGE"] = os.environ["LANG"] = locale
518
520
 
519
521
  global libdnf5
520
- global LIBDNF5_ERROR
522
+ global LIBDNF5_ERRORS
521
523
  has_dnf = True
522
524
  try:
523
525
  import libdnf5 # type: ignore[import]
@@ -526,7 +528,7 @@ class Dnf5Module(YumDnf):
526
528
 
527
529
  try:
528
530
  import libdnf5.exception # type: ignore[import-not-found]
529
- LIBDNF5_ERROR = libdnf5.exception.Error
531
+ LIBDNF5_ERRORS = (libdnf5.exception.Error, libdnf5.exception.NonLibdnf5Exception)
530
532
  except (ImportError, AttributeError):
531
533
  pass
532
534
 
@@ -581,15 +583,7 @@ class Dnf5Module(YumDnf):
581
583
  if self.conf_file:
582
584
  conf.config_file_path = self.conf_file
583
585
 
584
- try:
585
- base.load_config()
586
- except LIBDNF5_ERROR as e:
587
- self.module.fail_json(
588
- msg=str(e),
589
- conf_file=self.conf_file,
590
- failures=[],
591
- rc=1,
592
- )
586
+ base.load_config()
593
587
 
594
588
  if self.releasever is not None:
595
589
  variables = base.get_vars()
@@ -745,19 +739,13 @@ class Dnf5Module(YumDnf):
745
739
  goal.add_install(spec, settings)
746
740
  elif self.state in {"absent", "removed"}:
747
741
  for spec in self.names:
748
- try:
749
- goal.add_remove(spec, settings)
750
- except LIBDNF5_ERROR as e:
751
- self.module.fail_json(msg=str(e), failures=[], rc=1)
742
+ goal.add_remove(spec, settings)
752
743
  if self.autoremove:
753
744
  for pkg in get_unneeded_pkgs(base):
754
745
  goal.add_rpm_remove(pkg, settings)
755
746
 
756
747
  goal.set_allow_erasing(self.allowerasing)
757
- try:
758
- transaction = goal.resolve()
759
- except LIBDNF5_ERROR as e:
760
- self.module.fail_json(msg=str(e), failures=[], rc=1)
748
+ transaction = goal.resolve()
761
749
 
762
750
  if transaction.get_problems():
763
751
  failures = []
@@ -833,7 +821,11 @@ def main():
833
821
  auto_install_module_deps=dict(type="bool", default=True),
834
822
  )
835
823
  )
836
- Dnf5Module(AnsibleModule(**yumdnf_argument_spec)).run()
824
+ module = AnsibleModule(**yumdnf_argument_spec)
825
+ try:
826
+ Dnf5Module(module).run()
827
+ except LIBDNF5_ERRORS as e:
828
+ module.fail_json(msg=str(e), failures=[], rc=1)
837
829
 
838
830
 
839
831
  if __name__ == "__main__":
@@ -1,4 +1,3 @@
1
- #!/usr/bin/python
2
1
  # -*- coding: utf-8 -*-
3
2
 
4
3
  # Copyright: (c) 2013, Hiroaki Nakamura <hnakamur@gmail.com>
@@ -180,7 +180,7 @@ from ansible.module_utils.basic import AnsibleModule
180
180
  from ansible.module_utils.common.locale import get_best_parsable_locale
181
181
  from ansible.module_utils.common.sys_info import get_platform_subclass
182
182
  from ansible.module_utils.service import fail_if_missing, is_systemd_managed
183
- from ansible.module_utils.six import PY2, b
183
+ from ansible.module_utils.six import b
184
184
 
185
185
 
186
186
  class Service(object):
@@ -285,14 +285,8 @@ class Service(object):
285
285
  os._exit(0)
286
286
 
287
287
  # Start the command
288
- if PY2:
289
- # Python 2.6's shlex.split can't handle text strings correctly
290
- cmd = to_bytes(cmd, errors='surrogate_or_strict')
291
- cmd = shlex.split(cmd)
292
- else:
293
- # Python3.x shex.split text strings.
294
- cmd = to_text(cmd, errors='surrogate_or_strict')
295
- cmd = [to_bytes(c, errors='surrogate_or_strict') for c in shlex.split(cmd)]
288
+ cmd = to_text(cmd, errors='surrogate_or_strict')
289
+ cmd = [to_bytes(c, errors='surrogate_or_strict') for c in shlex.split(cmd)]
296
290
  # In either of the above cases, pass a list of byte strings to Popen
297
291
 
298
292
  # chkconfig localizes messages and we're screen scraping so make
ansible/parsing/ajson.py CHANGED
@@ -4,15 +4,13 @@
4
4
  from __future__ import annotations as _annotations
5
5
 
6
6
  # from ansible.utils.display import Display as _Display
7
-
8
-
9
- # DTFIX-RELEASE: The pylint deprecated checker does not detect `Display().deprecated` calls, of which we have many.
10
-
7
+ #
8
+ #
11
9
  # deprecated: description='deprecate ajson' core_version='2.23'
12
10
  # _Display().deprecated(
13
11
  # msg='The `ansible.parsing.ajson` module is deprecated.',
14
12
  # version='2.27',
15
- # help_text="", # DTFIX-RELEASE: complete this help text
13
+ # help_text="", # DTFIX-FUTURE: complete this help text
16
14
  # )
17
15
 
18
16
  # Imported for backward compat
@@ -15,7 +15,7 @@ import typing as t
15
15
 
16
16
  from ansible import constants as C
17
17
  from ansible.errors import AnsibleFileNotFound, AnsibleParserError
18
- from ansible._internal._errors import _utils
18
+ from ansible._internal._errors import _error_utils
19
19
  from ansible.module_utils.basic import is_executable
20
20
  from ansible._internal._datatag._tags import Origin, TrustedAsTemplate, SourceWasEncrypted
21
21
  from ansible.module_utils._internal._datatag import AnsibleTagHelper
@@ -81,12 +81,12 @@ class DataLoader:
81
81
  def load(
82
82
  self,
83
83
  data: str,
84
- file_name: str | None = None, # DTFIX-RELEASE: consider deprecating this in favor of tagging Origin on data
85
- show_content: bool = True, # DTFIX-RELEASE: consider future deprecation, but would need RedactAnnotatedSourceContext public
84
+ file_name: str | None = None, # DTFIX-FUTURE: consider deprecating this in favor of tagging Origin on data
85
+ show_content: bool = True, # DTFIX-FUTURE: consider future deprecation, but would need RedactAnnotatedSourceContext public
86
86
  json_only: bool = False,
87
87
  ) -> t.Any:
88
88
  """Backwards compat for now"""
89
- with _utils.RedactAnnotatedSourceContext.when(not show_content):
89
+ with _error_utils.RedactAnnotatedSourceContext.when(not show_content):
90
90
  return from_yaml(data=data, file_name=file_name, json_only=json_only)
91
91
 
92
92
  def load_from_file(self, file_name: str, cache: str = 'all', unsafe: bool = False, json_only: bool = False, trusted_as_template: bool = False) -> t.Any:
@@ -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)
@@ -149,7 +149,7 @@ def _parse_vaulttext_envelope(b_vaulttext_envelope, default_vault_id=None):
149
149
  vault_id = to_text(b_tmpheader[3].strip())
150
150
 
151
151
  b_ciphertext = b''.join(b_tmpdata[1:])
152
- # DTFIX-RELEASE: possible candidate for propagate_origin
152
+ # DTFIX7: possible candidate for propagate_origin
153
153
  b_ciphertext = AnsibleTagHelper.tag_copy(b_vaulttext_envelope, b_ciphertext)
154
154
 
155
155
  return b_ciphertext, b_version, cipher_name, vault_id
@@ -222,7 +222,7 @@ def format_vaulttext_envelope(b_ciphertext, cipher_name, version=None, vault_id=
222
222
 
223
223
  def _unhexlify(b_data):
224
224
  try:
225
- # DTFIX-RELEASE: possible candidate for propagate_origin
225
+ # DTFIX7: possible candidate for propagate_origin
226
226
  return AnsibleTagHelper.tag_copy(b_data, unhexlify(b_data))
227
227
  except (BinasciiError, TypeError) as ex:
228
228
  raise AnsibleVaultFormatError('Vault format unhexlify error.', obj=b_data) from ex
@@ -712,7 +712,7 @@ class VaultLib:
712
712
  # secret = self.secrets[vault_secret_id]
713
713
  display.vvvv(u'Trying secret %s for vault_id=%s' % (to_text(vault_secret), to_text(vault_secret_id)))
714
714
  b_plaintext = this_cipher.decrypt(b_vaulttext, vault_secret)
715
- # DTFIX-RELEASE: possible candidate for propagate_origin
715
+ # DTFIX7: possible candidate for propagate_origin
716
716
  b_plaintext = AnsibleTagHelper.tag_copy(vaulttext, b_plaintext)
717
717
  if b_plaintext is not None:
718
718
  vault_id_used = vault_secret_id
@@ -1520,7 +1520,7 @@ class VaultHelper:
1520
1520
  tags = AnsibleTagHelper.tags(ciphertext) # ciphertext has tags but value does not
1521
1521
  elif value_type is EncryptedString:
1522
1522
  ciphertext = value._ciphertext
1523
- elif value_type in _jinja_common.Marker.concrete_subclasses: # avoid wasteful raise/except of Marker when calling get_tag below
1523
+ elif value_type in _jinja_common.Marker._concrete_subclasses: # avoid wasteful raise/except of Marker when calling get_tag below
1524
1524
  ciphertext = None
1525
1525
  elif vaulted_value := VaultedValue.get_tag(value):
1526
1526
  ciphertext = vaulted_value.ciphertext
@@ -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())
@@ -45,9 +45,6 @@ class Taggable:
45
45
  return ds
46
46
 
47
47
  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
48
  return [AnsibleTagHelper.tag_copy(ds, item.strip()) for item in ds.split(',')]
52
49
 
53
50
  raise AnsibleError('tags must be specified as a list', obj=ds)
@@ -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:
@@ -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