ansible-core 2.18.5rc1__py3-none-any.whl → 2.19.0b2__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 (710) hide show
  1. ansible/_internal/__init__.py +53 -0
  2. ansible/_internal/_ansiballz.py +265 -0
  3. ansible/_internal/_collection_proxy.py +47 -0
  4. ansible/_internal/_datatag/__init__.py +0 -0
  5. ansible/_internal/_datatag/_tags.py +130 -0
  6. ansible/_internal/_datatag/_utils.py +19 -0
  7. ansible/_internal/_datatag/_wrappers.py +33 -0
  8. ansible/_internal/_errors/__init__.py +0 -0
  9. ansible/_internal/_errors/_captured.py +128 -0
  10. ansible/_internal/_errors/_handler.py +91 -0
  11. ansible/_internal/_errors/_utils.py +310 -0
  12. ansible/_internal/_json/__init__.py +203 -0
  13. ansible/_internal/_json/_legacy_encoder.py +34 -0
  14. ansible/_internal/_json/_profiles/__init__.py +0 -0
  15. ansible/_internal/_json/_profiles/_cache_persistence.py +55 -0
  16. ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
  17. ansible/_internal/_json/_profiles/_legacy.py +197 -0
  18. ansible/_internal/_locking.py +21 -0
  19. ansible/_internal/_plugins/__init__.py +0 -0
  20. ansible/_internal/_plugins/_cache.py +57 -0
  21. ansible/_internal/_task.py +78 -0
  22. ansible/_internal/_templating/__init__.py +10 -0
  23. ansible/_internal/_templating/_access.py +86 -0
  24. ansible/_internal/_templating/_chain_templar.py +63 -0
  25. ansible/_internal/_templating/_datatag.py +95 -0
  26. ansible/_internal/_templating/_engine.py +588 -0
  27. ansible/_internal/_templating/_errors.py +28 -0
  28. ansible/_internal/_templating/_jinja_bits.py +1066 -0
  29. ansible/_internal/_templating/_jinja_common.py +332 -0
  30. ansible/_internal/_templating/_jinja_patches.py +44 -0
  31. ansible/_internal/_templating/_jinja_plugins.py +345 -0
  32. ansible/_internal/_templating/_lazy_containers.py +633 -0
  33. ansible/_internal/_templating/_marker_behaviors.py +103 -0
  34. ansible/_internal/_templating/_transform.py +63 -0
  35. ansible/_internal/_templating/_utils.py +107 -0
  36. ansible/_internal/_wrapt.py +1052 -0
  37. ansible/_internal/_yaml/__init__.py +0 -0
  38. ansible/_internal/_yaml/_constructor.py +240 -0
  39. ansible/_internal/_yaml/_dumper.py +62 -0
  40. ansible/_internal/_yaml/_errors.py +166 -0
  41. ansible/_internal/_yaml/_loader.py +66 -0
  42. ansible/_internal/ansible_collections/ansible/_protomatter/README.md +11 -0
  43. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/action/debug.py +36 -0
  44. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/apply_trust.py +19 -0
  45. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +18 -0
  46. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/finalize.py +16 -0
  47. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/origin.py +18 -0
  48. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.py +24 -0
  49. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.yml +33 -0
  50. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/tag_names.py +16 -0
  51. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +17 -0
  52. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +49 -0
  53. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.py +21 -0
  54. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.yml +2 -0
  55. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.py +15 -0
  56. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.yml +19 -0
  57. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.py +18 -0
  58. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.yml +19 -0
  59. ansible/cli/__init__.py +159 -89
  60. ansible/cli/_ssh_askpass.py +47 -0
  61. ansible/cli/adhoc.py +14 -7
  62. ansible/cli/arguments/option_helpers.py +154 -7
  63. ansible/cli/config.py +43 -68
  64. ansible/cli/console.py +10 -8
  65. ansible/cli/doc.py +62 -53
  66. ansible/cli/galaxy.py +27 -20
  67. ansible/cli/inventory.py +28 -26
  68. ansible/cli/playbook.py +4 -12
  69. ansible/cli/pull.py +51 -11
  70. ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
  71. ansible/cli/vault.py +12 -11
  72. ansible/compat/__init__.py +2 -2
  73. ansible/config/base.yml +166 -112
  74. ansible/config/manager.py +52 -49
  75. ansible/constants.py +3 -4
  76. ansible/errors/__init__.py +277 -235
  77. ansible/executor/interpreter_discovery.py +28 -149
  78. ansible/executor/module_common.py +426 -493
  79. ansible/executor/play_iterator.py +22 -27
  80. ansible/executor/playbook_executor.py +11 -11
  81. ansible/executor/powershell/async_watchdog.ps1 +97 -102
  82. ansible/executor/powershell/async_wrapper.ps1 +202 -151
  83. ansible/executor/powershell/become_wrapper.ps1 +89 -144
  84. ansible/executor/powershell/bootstrap_wrapper.ps1 +24 -9
  85. ansible/executor/powershell/coverage_wrapper.ps1 +82 -135
  86. ansible/executor/powershell/exec_wrapper.ps1 +462 -196
  87. ansible/executor/powershell/module_manifest.py +417 -265
  88. ansible/executor/powershell/module_wrapper.ps1 +169 -186
  89. ansible/executor/powershell/psrp_fetch_file.ps1 +41 -0
  90. ansible/executor/powershell/psrp_put_file.ps1 +122 -0
  91. ansible/executor/powershell/winrm_fetch_file.ps1 +46 -0
  92. ansible/executor/powershell/winrm_put_file.ps1 +36 -0
  93. ansible/executor/process/worker.py +161 -96
  94. ansible/executor/stats.py +5 -5
  95. ansible/executor/task_executor.py +268 -258
  96. ansible/executor/task_queue_manager.py +124 -90
  97. ansible/executor/task_result.py +183 -78
  98. ansible/galaxy/__init__.py +2 -2
  99. ansible/galaxy/api.py +22 -18
  100. ansible/galaxy/collection/__init__.py +1 -1
  101. ansible/galaxy/collection/concrete_artifact_manager.py +8 -11
  102. ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
  103. ansible/galaxy/dependency_resolution/providers.py +1 -1
  104. ansible/galaxy/dependency_resolution/reporters.py +81 -0
  105. ansible/galaxy/role.py +4 -8
  106. ansible/galaxy/token.py +28 -21
  107. ansible/inventory/data.py +47 -57
  108. ansible/inventory/group.py +44 -72
  109. ansible/inventory/helpers.py +9 -0
  110. ansible/inventory/host.py +32 -54
  111. ansible/inventory/manager.py +78 -34
  112. ansible/keyword_desc.yml +1 -1
  113. ansible/module_utils/_internal/__init__.py +55 -0
  114. ansible/module_utils/_internal/_ambient_context.py +58 -0
  115. ansible/module_utils/_internal/_ansiballz.py +133 -0
  116. ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
  117. ansible/module_utils/_internal/_dataclass_annotation_patch.py +64 -0
  118. ansible/module_utils/_internal/_dataclass_validation.py +217 -0
  119. ansible/module_utils/_internal/_datatag/__init__.py +928 -0
  120. ansible/module_utils/_internal/_datatag/_tags.py +38 -0
  121. ansible/module_utils/_internal/_debugging.py +31 -0
  122. ansible/module_utils/_internal/_errors.py +30 -0
  123. ansible/module_utils/_internal/_json/__init__.py +63 -0
  124. ansible/module_utils/_internal/_json/_legacy_encoder.py +26 -0
  125. ansible/module_utils/_internal/_json/_profiles/__init__.py +410 -0
  126. ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
  127. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +31 -0
  128. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +35 -0
  129. ansible/module_utils/_internal/_json/_profiles/_module_modern_c2m.py +35 -0
  130. ansible/module_utils/_internal/_json/_profiles/_module_modern_m2c.py +33 -0
  131. ansible/module_utils/_internal/_json/_profiles/_tagless.py +50 -0
  132. ansible/module_utils/_internal/_patches/__init__.py +66 -0
  133. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +55 -0
  134. ansible/module_utils/_internal/_patches/_socket_patch.py +34 -0
  135. ansible/module_utils/_internal/_patches/_sys_intern_patch.py +34 -0
  136. ansible/module_utils/_internal/_plugin_exec_context.py +49 -0
  137. ansible/module_utils/_internal/_testing.py +0 -0
  138. ansible/module_utils/_internal/_traceback.py +89 -0
  139. ansible/module_utils/ansible_release.py +2 -2
  140. ansible/module_utils/api.py +1 -2
  141. ansible/module_utils/basic.py +152 -120
  142. ansible/module_utils/common/_utils.py +24 -28
  143. ansible/module_utils/common/collections.py +1 -2
  144. ansible/module_utils/common/dict_transformations.py +2 -2
  145. ansible/module_utils/common/file.py +2 -2
  146. ansible/module_utils/common/json.py +90 -84
  147. ansible/module_utils/common/locale.py +2 -2
  148. ansible/module_utils/common/messages.py +108 -0
  149. ansible/module_utils/common/parameters.py +27 -24
  150. ansible/module_utils/common/process.py +2 -2
  151. ansible/module_utils/common/respawn.py +41 -19
  152. ansible/module_utils/common/sentinel.py +66 -0
  153. ansible/module_utils/common/sys_info.py +8 -8
  154. ansible/module_utils/common/text/converters.py +16 -37
  155. ansible/module_utils/common/validation.py +35 -24
  156. ansible/module_utils/common/warnings.py +86 -25
  157. ansible/module_utils/common/yaml.py +29 -3
  158. ansible/module_utils/compat/datetime.py +33 -21
  159. ansible/module_utils/compat/paramiko.py +21 -10
  160. ansible/module_utils/compat/typing.py +6 -5
  161. ansible/module_utils/connection.py +2 -2
  162. ansible/module_utils/csharp/Ansible.Basic.cs +14 -11
  163. ansible/module_utils/csharp/Ansible.Become.cs +1 -0
  164. ansible/module_utils/csharp/Ansible._Async.cs +517 -0
  165. ansible/module_utils/datatag.py +46 -0
  166. ansible/module_utils/distro/__init__.py +2 -2
  167. ansible/module_utils/facts/ansible_collector.py +4 -5
  168. ansible/module_utils/facts/collector.py +13 -14
  169. ansible/module_utils/facts/compat.py +4 -4
  170. ansible/module_utils/facts/default_collectors.py +1 -1
  171. ansible/module_utils/facts/hardware/aix.py +34 -0
  172. ansible/module_utils/facts/hardware/base.py +1 -1
  173. ansible/module_utils/facts/hardware/darwin.py +1 -3
  174. ansible/module_utils/facts/hardware/freebsd.py +2 -2
  175. ansible/module_utils/facts/hardware/linux.py +4 -4
  176. ansible/module_utils/facts/namespace.py +1 -1
  177. ansible/module_utils/facts/network/base.py +1 -1
  178. ansible/module_utils/facts/network/fc_wwn.py +1 -2
  179. ansible/module_utils/facts/network/iscsi.py +1 -2
  180. ansible/module_utils/facts/network/nvme.py +1 -2
  181. ansible/module_utils/facts/other/facter.py +1 -2
  182. ansible/module_utils/facts/other/ohai.py +2 -3
  183. ansible/module_utils/facts/system/apparmor.py +1 -2
  184. ansible/module_utils/facts/system/caps.py +1 -1
  185. ansible/module_utils/facts/system/chroot.py +1 -2
  186. ansible/module_utils/facts/system/cmdline.py +1 -2
  187. ansible/module_utils/facts/system/date_time.py +5 -3
  188. ansible/module_utils/facts/system/distribution.py +9 -8
  189. ansible/module_utils/facts/system/dns.py +1 -1
  190. ansible/module_utils/facts/system/env.py +1 -2
  191. ansible/module_utils/facts/system/fips.py +7 -20
  192. ansible/module_utils/facts/system/loadavg.py +1 -2
  193. ansible/module_utils/facts/system/local.py +1 -2
  194. ansible/module_utils/facts/system/lsb.py +1 -2
  195. ansible/module_utils/facts/system/pkg_mgr.py +1 -2
  196. ansible/module_utils/facts/system/platform.py +1 -2
  197. ansible/module_utils/facts/system/python.py +1 -2
  198. ansible/module_utils/facts/system/selinux.py +1 -1
  199. ansible/module_utils/facts/system/service_mgr.py +1 -2
  200. ansible/module_utils/facts/system/ssh_pub_keys.py +1 -1
  201. ansible/module_utils/facts/system/systemd.py +1 -1
  202. ansible/module_utils/facts/system/user.py +1 -2
  203. ansible/module_utils/facts/utils.py +3 -3
  204. ansible/module_utils/facts/virtual/base.py +1 -1
  205. ansible/module_utils/facts/virtual/sunos.py +3 -15
  206. ansible/module_utils/facts/virtual/sysctl.py +3 -16
  207. ansible/module_utils/json_utils.py +2 -2
  208. ansible/module_utils/parsing/convert_bool.py +1 -1
  209. ansible/module_utils/service.py +18 -21
  210. ansible/module_utils/splitter.py +7 -7
  211. ansible/module_utils/testing.py +31 -0
  212. ansible/module_utils/urls.py +60 -31
  213. ansible/modules/add_host.py +4 -4
  214. ansible/modules/apt.py +60 -46
  215. ansible/modules/apt_key.py +19 -12
  216. ansible/modules/apt_repository.py +19 -16
  217. ansible/modules/assemble.py +6 -6
  218. ansible/modules/assert.py +4 -4
  219. ansible/modules/async_status.py +10 -12
  220. ansible/modules/async_wrapper.py +8 -3
  221. ansible/modules/blockinfile.py +6 -7
  222. ansible/modules/command.py +10 -17
  223. ansible/modules/copy.py +57 -144
  224. ansible/modules/cron.py +20 -15
  225. ansible/modules/deb822_repository.py +8 -9
  226. ansible/modules/debconf.py +5 -5
  227. ansible/modules/debug.py +4 -4
  228. ansible/modules/dnf.py +8 -8
  229. ansible/modules/dnf5.py +39 -13
  230. ansible/modules/dpkg_selections.py +4 -4
  231. ansible/modules/expect.py +8 -10
  232. ansible/modules/fail.py +4 -4
  233. ansible/modules/fetch.py +4 -4
  234. ansible/modules/file.py +174 -133
  235. ansible/modules/find.py +19 -17
  236. ansible/modules/gather_facts.py +3 -3
  237. ansible/modules/get_url.py +59 -53
  238. ansible/modules/getent.py +7 -9
  239. ansible/modules/git.py +28 -25
  240. ansible/modules/group.py +6 -6
  241. ansible/modules/group_by.py +4 -4
  242. ansible/modules/hostname.py +13 -29
  243. ansible/modules/import_playbook.py +6 -6
  244. ansible/modules/import_role.py +6 -6
  245. ansible/modules/import_tasks.py +6 -6
  246. ansible/modules/include_role.py +6 -6
  247. ansible/modules/include_tasks.py +6 -6
  248. ansible/modules/include_vars.py +6 -6
  249. ansible/modules/iptables.py +86 -73
  250. ansible/modules/known_hosts.py +10 -10
  251. ansible/modules/lineinfile.py +5 -5
  252. ansible/modules/meta.py +4 -4
  253. ansible/modules/mount_facts.py +2 -2
  254. ansible/modules/package.py +4 -4
  255. ansible/modules/package_facts.py +22 -10
  256. ansible/modules/pause.py +6 -6
  257. ansible/modules/ping.py +6 -6
  258. ansible/modules/pip.py +10 -11
  259. ansible/modules/raw.py +4 -4
  260. ansible/modules/reboot.py +6 -6
  261. ansible/modules/replace.py +9 -13
  262. ansible/modules/rpm_key.py +7 -8
  263. ansible/modules/script.py +4 -4
  264. ansible/modules/service.py +7 -8
  265. ansible/modules/service_facts.py +87 -10
  266. ansible/modules/set_fact.py +5 -5
  267. ansible/modules/set_stats.py +4 -4
  268. ansible/modules/setup.py +2 -2
  269. ansible/modules/shell.py +6 -6
  270. ansible/modules/slurp.py +6 -6
  271. ansible/modules/stat.py +9 -23
  272. ansible/modules/subversion.py +15 -15
  273. ansible/modules/systemd.py +6 -6
  274. ansible/modules/systemd_service.py +6 -6
  275. ansible/modules/sysvinit.py +6 -6
  276. ansible/modules/tempfile.py +5 -6
  277. ansible/modules/template.py +6 -6
  278. ansible/modules/unarchive.py +32 -11
  279. ansible/modules/uri.py +33 -26
  280. ansible/modules/user.py +53 -34
  281. ansible/modules/validate_argument_spec.py +10 -7
  282. ansible/modules/wait_for.py +32 -27
  283. ansible/modules/wait_for_connection.py +6 -6
  284. ansible/modules/yum_repository.py +6 -6
  285. ansible/parsing/ajson.py +14 -32
  286. ansible/parsing/dataloader.py +99 -54
  287. ansible/parsing/mod_args.py +28 -44
  288. ansible/parsing/plugin_docs.py +21 -86
  289. ansible/parsing/quoting.py +1 -1
  290. ansible/parsing/splitter.py +27 -12
  291. ansible/parsing/utils/addresses.py +24 -24
  292. ansible/parsing/utils/jsonify.py +5 -1
  293. ansible/parsing/utils/yaml.py +32 -61
  294. ansible/parsing/vault/__init__.py +319 -87
  295. ansible/parsing/yaml/__init__.py +0 -18
  296. ansible/parsing/yaml/dumper.py +6 -120
  297. ansible/parsing/yaml/loader.py +6 -39
  298. ansible/parsing/yaml/objects.py +43 -335
  299. ansible/playbook/__init__.py +1 -1
  300. ansible/playbook/attribute.py +8 -3
  301. ansible/playbook/base.py +182 -132
  302. ansible/playbook/block.py +26 -24
  303. ansible/playbook/collectionsearch.py +1 -15
  304. ansible/playbook/conditional.py +3 -77
  305. ansible/playbook/handler.py +8 -2
  306. ansible/playbook/helpers.py +41 -53
  307. ansible/playbook/included_file.py +31 -27
  308. ansible/playbook/loop_control.py +2 -2
  309. ansible/playbook/play.py +85 -44
  310. ansible/playbook/play_context.py +12 -17
  311. ansible/playbook/playbook_include.py +14 -15
  312. ansible/playbook/role/__init__.py +24 -26
  313. ansible/playbook/role/definition.py +15 -17
  314. ansible/playbook/role/include.py +2 -4
  315. ansible/playbook/role/metadata.py +10 -11
  316. ansible/playbook/role_include.py +3 -3
  317. ansible/playbook/taggable.py +13 -8
  318. ansible/playbook/task.py +188 -118
  319. ansible/playbook/task_include.py +5 -5
  320. ansible/plugins/__init__.py +68 -21
  321. ansible/plugins/action/__init__.py +209 -176
  322. ansible/plugins/action/add_host.py +1 -1
  323. ansible/plugins/action/assemble.py +1 -1
  324. ansible/plugins/action/assert.py +54 -66
  325. ansible/plugins/action/copy.py +7 -11
  326. ansible/plugins/action/debug.py +37 -31
  327. ansible/plugins/action/dnf.py +3 -4
  328. ansible/plugins/action/fail.py +1 -1
  329. ansible/plugins/action/fetch.py +4 -5
  330. ansible/plugins/action/gather_facts.py +7 -6
  331. ansible/plugins/action/group_by.py +1 -1
  332. ansible/plugins/action/include_vars.py +10 -11
  333. ansible/plugins/action/package.py +3 -6
  334. ansible/plugins/action/pause.py +2 -2
  335. ansible/plugins/action/script.py +15 -8
  336. ansible/plugins/action/service.py +6 -11
  337. ansible/plugins/action/set_fact.py +3 -12
  338. ansible/plugins/action/set_stats.py +3 -8
  339. ansible/plugins/action/template.py +35 -59
  340. ansible/plugins/action/unarchive.py +1 -1
  341. ansible/plugins/action/validate_argument_spec.py +5 -5
  342. ansible/plugins/action/wait_for_connection.py +1 -1
  343. ansible/plugins/become/__init__.py +31 -8
  344. ansible/plugins/become/runas.py +71 -0
  345. ansible/plugins/become/su.py +13 -8
  346. ansible/plugins/become/sudo.py +19 -0
  347. ansible/plugins/cache/__init__.py +35 -44
  348. ansible/plugins/cache/base.py +8 -0
  349. ansible/plugins/cache/jsonfile.py +10 -16
  350. ansible/plugins/cache/memory.py +6 -12
  351. ansible/plugins/callback/__init__.py +284 -179
  352. ansible/plugins/callback/default.py +99 -92
  353. ansible/plugins/callback/junit.py +44 -39
  354. ansible/plugins/callback/minimal.py +28 -25
  355. ansible/plugins/callback/oneline.py +28 -21
  356. ansible/plugins/callback/tree.py +16 -11
  357. ansible/plugins/connection/__init__.py +47 -34
  358. ansible/plugins/connection/local.py +150 -54
  359. ansible/plugins/connection/paramiko_ssh.py +21 -18
  360. ansible/plugins/connection/psrp.py +76 -165
  361. ansible/plugins/connection/ssh.py +301 -78
  362. ansible/plugins/connection/winrm.py +58 -140
  363. ansible/plugins/doc_fragments/action_common_attributes.py +14 -14
  364. ansible/plugins/doc_fragments/action_core.py +6 -6
  365. ansible/plugins/doc_fragments/backup.py +2 -2
  366. ansible/plugins/doc_fragments/checksum_common.py +27 -0
  367. ansible/plugins/doc_fragments/constructed.py +6 -2
  368. ansible/plugins/doc_fragments/decrypt.py +2 -2
  369. ansible/plugins/doc_fragments/default_callback.py +2 -2
  370. ansible/plugins/doc_fragments/files.py +2 -2
  371. ansible/plugins/doc_fragments/inventory_cache.py +2 -2
  372. ansible/plugins/doc_fragments/result_format_callback.py +2 -2
  373. ansible/plugins/doc_fragments/return_common.py +2 -2
  374. ansible/plugins/doc_fragments/template_common.py +4 -4
  375. ansible/plugins/doc_fragments/url.py +17 -1
  376. ansible/plugins/doc_fragments/url_windows.py +2 -2
  377. ansible/plugins/doc_fragments/validate.py +2 -2
  378. ansible/plugins/doc_fragments/vars_plugin_staging.py +2 -2
  379. ansible/plugins/filter/__init__.py +6 -2
  380. ansible/plugins/filter/b64decode.yml +22 -0
  381. ansible/plugins/filter/b64encode.yml +22 -0
  382. ansible/plugins/filter/bool.yml +11 -4
  383. ansible/plugins/filter/core.py +225 -108
  384. ansible/plugins/filter/encryption.py +32 -32
  385. ansible/plugins/filter/flatten.yml +3 -2
  386. ansible/plugins/filter/human_to_bytes.yml +1 -1
  387. ansible/plugins/filter/mathstuff.py +30 -37
  388. ansible/plugins/filter/password_hash.yml +8 -0
  389. ansible/plugins/filter/regex_search.yml +1 -4
  390. ansible/plugins/filter/split.yml +1 -1
  391. ansible/plugins/filter/to_nice_yaml.yml +0 -4
  392. ansible/plugins/filter/to_yaml.yml +0 -4
  393. ansible/plugins/filter/unvault.yml +1 -1
  394. ansible/plugins/filter/urls.py +1 -1
  395. ansible/plugins/filter/urlsplit.py +8 -9
  396. ansible/plugins/filter/vault.yml +14 -9
  397. ansible/plugins/filter/win_basename.yml +6 -1
  398. ansible/plugins/filter/win_dirname.yml +5 -0
  399. ansible/plugins/inventory/__init__.py +97 -77
  400. ansible/plugins/inventory/advanced_host_list.py +7 -5
  401. ansible/plugins/inventory/auto.py +11 -4
  402. ansible/plugins/inventory/constructed.py +21 -24
  403. ansible/plugins/inventory/generator.py +16 -11
  404. ansible/plugins/inventory/host_list.py +7 -5
  405. ansible/plugins/inventory/ini.py +78 -44
  406. ansible/plugins/inventory/script.py +189 -119
  407. ansible/plugins/inventory/toml.py +16 -126
  408. ansible/plugins/inventory/yaml.py +10 -8
  409. ansible/plugins/list.py +3 -3
  410. ansible/plugins/loader.py +197 -82
  411. ansible/plugins/lookup/__init__.py +21 -4
  412. ansible/plugins/lookup/config.py +21 -35
  413. ansible/plugins/lookup/csvfile.py +3 -2
  414. ansible/plugins/lookup/dict.py +1 -6
  415. ansible/plugins/lookup/env.py +12 -9
  416. ansible/plugins/lookup/file.py +5 -8
  417. ansible/plugins/lookup/first_found.py +86 -55
  418. ansible/plugins/lookup/indexed_items.py +1 -10
  419. ansible/plugins/lookup/ini.py +14 -13
  420. ansible/plugins/lookup/items.py +1 -1
  421. ansible/plugins/lookup/lines.py +8 -1
  422. ansible/plugins/lookup/list.py +1 -1
  423. ansible/plugins/lookup/nested.py +2 -18
  424. ansible/plugins/lookup/password.py +5 -5
  425. ansible/plugins/lookup/pipe.py +5 -7
  426. ansible/plugins/lookup/sequence.py +18 -8
  427. ansible/plugins/lookup/subelements.py +1 -4
  428. ansible/plugins/lookup/template.py +42 -36
  429. ansible/plugins/lookup/together.py +0 -12
  430. ansible/plugins/lookup/unvault.py +1 -5
  431. ansible/plugins/lookup/url.py +2 -8
  432. ansible/plugins/lookup/vars.py +16 -24
  433. ansible/plugins/shell/__init__.py +2 -2
  434. ansible/plugins/shell/cmd.py +2 -2
  435. ansible/plugins/shell/powershell.py +39 -22
  436. ansible/plugins/shell/sh.py +3 -2
  437. ansible/plugins/strategy/__init__.py +159 -184
  438. ansible/plugins/strategy/debug.py +2 -2
  439. ansible/plugins/strategy/free.py +16 -31
  440. ansible/plugins/strategy/host_pinned.py +2 -2
  441. ansible/plugins/strategy/linear.py +41 -41
  442. ansible/plugins/terminal/__init__.py +4 -4
  443. ansible/plugins/test/__init__.py +7 -2
  444. ansible/plugins/test/core.py +55 -21
  445. ansible/plugins/test/files.py +1 -1
  446. ansible/plugins/test/mathstuff.py +3 -3
  447. ansible/plugins/test/uri.py +3 -3
  448. ansible/plugins/vars/host_group_vars.py +7 -14
  449. ansible/release.py +2 -2
  450. ansible/template/__init__.py +370 -944
  451. ansible/utils/__init__.py +0 -18
  452. ansible/utils/_ssh_agent.py +657 -0
  453. ansible/utils/collection_loader/__init__.py +52 -5
  454. ansible/utils/collection_loader/_collection_config.py +5 -6
  455. ansible/utils/collection_loader/_collection_finder.py +79 -93
  456. ansible/utils/collection_loader/_collection_meta.py +13 -8
  457. ansible/utils/display.py +433 -63
  458. ansible/utils/encrypt.py +27 -19
  459. ansible/utils/fqcn.py +2 -2
  460. ansible/utils/hashing.py +2 -2
  461. ansible/utils/helpers.py +2 -2
  462. ansible/utils/listify.py +8 -8
  463. ansible/utils/lock.py +2 -2
  464. ansible/utils/path.py +4 -4
  465. ansible/utils/plugin_docs.py +14 -13
  466. ansible/utils/sentinel.py +4 -62
  467. ansible/utils/singleton.py +2 -0
  468. ansible/utils/ssh_functions.py +1 -1
  469. ansible/utils/unsafe_proxy.py +23 -332
  470. ansible/utils/vars.py +51 -8
  471. ansible/utils/version.py +2 -2
  472. ansible/vars/clean.py +5 -5
  473. ansible/vars/hostvars.py +60 -90
  474. ansible/vars/manager.py +206 -282
  475. ansible/vars/reserved.py +8 -9
  476. ansible_core-2.19.0b2.dist-info/BSD-3-Clause.txt +28 -0
  477. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/METADATA +5 -4
  478. ansible_core-2.19.0b2.dist-info/RECORD +1072 -0
  479. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/WHEEL +1 -1
  480. ansible_test/_data/completion/docker.txt +7 -7
  481. ansible_test/_data/completion/remote.txt +6 -6
  482. ansible_test/_data/completion/windows.txt +1 -0
  483. ansible_test/_data/requirements/ansible.txt +2 -2
  484. ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
  485. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  486. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  487. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  488. ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
  489. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  490. ansible_test/_data/requirements/units.txt +1 -0
  491. ansible_test/_internal/__init__.py +1 -0
  492. ansible_test/_internal/ansible_util.py +2 -0
  493. ansible_test/_internal/become.py +1 -0
  494. ansible_test/_internal/bootstrap.py +1 -0
  495. ansible_test/_internal/cache.py +1 -0
  496. ansible_test/_internal/cgroup.py +1 -0
  497. ansible_test/_internal/ci/__init__.py +1 -0
  498. ansible_test/_internal/ci/azp.py +1 -0
  499. ansible_test/_internal/ci/local.py +1 -0
  500. ansible_test/_internal/classification/__init__.py +1 -0
  501. ansible_test/_internal/classification/common.py +1 -0
  502. ansible_test/_internal/classification/csharp.py +1 -0
  503. ansible_test/_internal/classification/powershell.py +1 -0
  504. ansible_test/_internal/classification/python.py +1 -0
  505. ansible_test/_internal/cli/__init__.py +1 -0
  506. ansible_test/_internal/cli/actions.py +1 -0
  507. ansible_test/_internal/cli/argparsing/__init__.py +1 -0
  508. ansible_test/_internal/cli/argparsing/actions.py +1 -0
  509. ansible_test/_internal/cli/argparsing/argcompletion.py +1 -0
  510. ansible_test/_internal/cli/argparsing/parsers.py +1 -0
  511. ansible_test/_internal/cli/commands/__init__.py +11 -0
  512. ansible_test/_internal/cli/commands/coverage/__init__.py +1 -0
  513. ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +1 -0
  514. ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +1 -0
  515. ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +1 -0
  516. ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +1 -0
  517. ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +1 -0
  518. ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +1 -0
  519. ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +1 -0
  520. ansible_test/_internal/cli/commands/coverage/combine.py +1 -0
  521. ansible_test/_internal/cli/commands/coverage/erase.py +1 -0
  522. ansible_test/_internal/cli/commands/coverage/html.py +1 -0
  523. ansible_test/_internal/cli/commands/coverage/report.py +1 -0
  524. ansible_test/_internal/cli/commands/coverage/xml.py +1 -0
  525. ansible_test/_internal/cli/commands/env.py +1 -0
  526. ansible_test/_internal/cli/commands/integration/__init__.py +1 -0
  527. ansible_test/_internal/cli/commands/integration/network.py +1 -0
  528. ansible_test/_internal/cli/commands/integration/posix.py +1 -0
  529. ansible_test/_internal/cli/commands/integration/windows.py +1 -0
  530. ansible_test/_internal/cli/commands/sanity.py +9 -0
  531. ansible_test/_internal/cli/commands/shell.py +1 -0
  532. ansible_test/_internal/cli/commands/units.py +1 -0
  533. ansible_test/_internal/cli/compat.py +1 -0
  534. ansible_test/_internal/cli/completers.py +1 -0
  535. ansible_test/_internal/cli/converters.py +1 -0
  536. ansible_test/_internal/cli/environments.py +1 -0
  537. ansible_test/_internal/cli/epilog.py +1 -0
  538. ansible_test/_internal/cli/parsers/__init__.py +1 -0
  539. ansible_test/_internal/cli/parsers/base_argument_parsers.py +1 -0
  540. ansible_test/_internal/cli/parsers/helpers.py +1 -0
  541. ansible_test/_internal/cli/parsers/host_config_parsers.py +1 -0
  542. ansible_test/_internal/cli/parsers/key_value_parsers.py +1 -0
  543. ansible_test/_internal/cli/parsers/value_parsers.py +1 -0
  544. ansible_test/_internal/commands/__init__.py +1 -0
  545. ansible_test/_internal/commands/coverage/__init__.py +2 -1
  546. ansible_test/_internal/commands/coverage/analyze/__init__.py +1 -0
  547. ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +1 -0
  548. ansible_test/_internal/commands/coverage/analyze/targets/combine.py +1 -0
  549. ansible_test/_internal/commands/coverage/analyze/targets/expand.py +1 -0
  550. ansible_test/_internal/commands/coverage/analyze/targets/filter.py +1 -0
  551. ansible_test/_internal/commands/coverage/analyze/targets/generate.py +1 -0
  552. ansible_test/_internal/commands/coverage/analyze/targets/missing.py +1 -0
  553. ansible_test/_internal/commands/coverage/combine.py +2 -1
  554. ansible_test/_internal/commands/coverage/erase.py +1 -0
  555. ansible_test/_internal/commands/coverage/html.py +1 -0
  556. ansible_test/_internal/commands/coverage/report.py +1 -0
  557. ansible_test/_internal/commands/coverage/xml.py +1 -0
  558. ansible_test/_internal/commands/env/__init__.py +2 -0
  559. ansible_test/_internal/commands/integration/__init__.py +4 -0
  560. ansible_test/_internal/commands/integration/cloud/__init__.py +1 -0
  561. ansible_test/_internal/commands/integration/cloud/acme.py +2 -1
  562. ansible_test/_internal/commands/integration/cloud/aws.py +1 -0
  563. ansible_test/_internal/commands/integration/cloud/azure.py +1 -0
  564. ansible_test/_internal/commands/integration/cloud/cs.py +1 -0
  565. ansible_test/_internal/commands/integration/cloud/digitalocean.py +1 -0
  566. ansible_test/_internal/commands/integration/cloud/galaxy.py +3 -2
  567. ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -0
  568. ansible_test/_internal/commands/integration/cloud/httptester.py +2 -1
  569. ansible_test/_internal/commands/integration/cloud/nios.py +2 -1
  570. ansible_test/_internal/commands/integration/cloud/opennebula.py +1 -0
  571. ansible_test/_internal/commands/integration/cloud/openshift.py +1 -0
  572. ansible_test/_internal/commands/integration/cloud/scaleway.py +1 -0
  573. ansible_test/_internal/commands/integration/cloud/vcenter.py +1 -0
  574. ansible_test/_internal/commands/integration/cloud/vultr.py +1 -0
  575. ansible_test/_internal/commands/integration/coverage.py +1 -0
  576. ansible_test/_internal/commands/integration/filters.py +1 -0
  577. ansible_test/_internal/commands/integration/network.py +1 -0
  578. ansible_test/_internal/commands/integration/posix.py +1 -0
  579. ansible_test/_internal/commands/integration/windows.py +1 -0
  580. ansible_test/_internal/commands/sanity/__init__.py +16 -1
  581. ansible_test/_internal/commands/sanity/ansible_doc.py +1 -0
  582. ansible_test/_internal/commands/sanity/bin_symlinks.py +1 -0
  583. ansible_test/_internal/commands/sanity/compile.py +1 -0
  584. ansible_test/_internal/commands/sanity/ignores.py +1 -0
  585. ansible_test/_internal/commands/sanity/import.py +1 -0
  586. ansible_test/_internal/commands/sanity/integration_aliases.py +1 -0
  587. ansible_test/_internal/commands/sanity/pep8.py +1 -0
  588. ansible_test/_internal/commands/sanity/pslint.py +1 -0
  589. ansible_test/_internal/commands/sanity/pylint.py +24 -26
  590. ansible_test/_internal/commands/sanity/shellcheck.py +1 -0
  591. ansible_test/_internal/commands/sanity/validate_modules.py +1 -0
  592. ansible_test/_internal/commands/sanity/yamllint.py +1 -0
  593. ansible_test/_internal/commands/shell/__init__.py +1 -0
  594. ansible_test/_internal/commands/units/__init__.py +1 -0
  595. ansible_test/_internal/compat/__init__.py +1 -0
  596. ansible_test/_internal/compat/packaging.py +1 -0
  597. ansible_test/_internal/compat/yaml.py +1 -0
  598. ansible_test/_internal/completion.py +1 -0
  599. ansible_test/_internal/config.py +2 -0
  600. ansible_test/_internal/connections.py +1 -0
  601. ansible_test/_internal/constants.py +1 -0
  602. ansible_test/_internal/containers.py +1 -0
  603. ansible_test/_internal/content_config.py +1 -0
  604. ansible_test/_internal/core_ci.py +1 -0
  605. ansible_test/_internal/coverage_util.py +11 -10
  606. ansible_test/_internal/data.py +1 -0
  607. ansible_test/_internal/delegation.py +1 -0
  608. ansible_test/_internal/dev/__init__.py +1 -0
  609. ansible_test/_internal/dev/container_probe.py +1 -0
  610. ansible_test/_internal/diff.py +3 -2
  611. ansible_test/_internal/docker_util.py +2 -1
  612. ansible_test/_internal/encoding.py +1 -0
  613. ansible_test/_internal/executor.py +1 -0
  614. ansible_test/_internal/git.py +1 -0
  615. ansible_test/_internal/host_configs.py +1 -0
  616. ansible_test/_internal/host_profiles.py +1 -0
  617. ansible_test/_internal/http.py +1 -0
  618. ansible_test/_internal/init.py +1 -0
  619. ansible_test/_internal/inventory.py +35 -3
  620. ansible_test/_internal/io.py +1 -0
  621. ansible_test/_internal/metadata.py +1 -0
  622. ansible_test/_internal/payload.py +1 -0
  623. ansible_test/_internal/provider/__init__.py +1 -0
  624. ansible_test/_internal/provider/layout/__init__.py +1 -0
  625. ansible_test/_internal/provider/layout/ansible.py +1 -0
  626. ansible_test/_internal/provider/layout/collection.py +1 -0
  627. ansible_test/_internal/provider/layout/unsupported.py +1 -0
  628. ansible_test/_internal/provider/source/__init__.py +1 -0
  629. ansible_test/_internal/provider/source/git.py +1 -0
  630. ansible_test/_internal/provider/source/installed.py +1 -0
  631. ansible_test/_internal/provider/source/unsupported.py +1 -0
  632. ansible_test/_internal/provider/source/unversioned.py +1 -0
  633. ansible_test/_internal/provisioning.py +1 -0
  634. ansible_test/_internal/pypi_proxy.py +6 -5
  635. ansible_test/_internal/python_requirements.py +1 -0
  636. ansible_test/_internal/ssh.py +1 -0
  637. ansible_test/_internal/target.py +1 -0
  638. ansible_test/_internal/test.py +3 -2
  639. ansible_test/_internal/thread.py +1 -0
  640. ansible_test/_internal/timeout.py +1 -0
  641. ansible_test/_internal/util.py +1 -0
  642. ansible_test/_internal/util_common.py +5 -2
  643. ansible_test/_internal/venv.py +1 -0
  644. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +1 -0
  645. ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py +1 -0
  646. ansible_test/_util/controller/sanity/code-smell/changelog.py +1 -0
  647. ansible_test/_util/controller/sanity/code-smell/empty-init.py +1 -0
  648. ansible_test/_util/controller/sanity/code-smell/line-endings.py +1 -0
  649. ansible_test/_util/controller/sanity/code-smell/no-assert.py +1 -0
  650. ansible_test/_util/controller/sanity/code-smell/no-get-exception.py +1 -0
  651. ansible_test/_util/controller/sanity/code-smell/no-illegal-filenames.py +1 -0
  652. ansible_test/_util/controller/sanity/code-smell/no-smart-quotes.py +1 -0
  653. ansible_test/_util/controller/sanity/code-smell/replace-urlopen.py +1 -0
  654. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +28 -1
  655. ansible_test/_util/controller/sanity/code-smell/shebang.py +1 -0
  656. ansible_test/_util/controller/sanity/code-smell/symlinks.py +1 -0
  657. ansible_test/_util/controller/sanity/code-smell/use-argspec-type-path.py +1 -0
  658. ansible_test/_util/controller/sanity/code-smell/use-compat-six.py +1 -0
  659. ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py +2 -1
  660. ansible_test/_util/controller/sanity/pep8/current-ignore.txt +4 -0
  661. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +7 -5
  662. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +7 -5
  663. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +7 -5
  664. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +3 -5
  665. ansible_test/_util/controller/sanity/pylint/config/default.cfg +7 -7
  666. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -13
  667. ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +1 -0
  668. ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +1 -8
  669. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +1 -8
  670. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +55 -28
  671. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +12 -5
  672. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +13 -2
  673. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -0
  674. ansible_test/_util/controller/sanity/yamllint/yamllinter.py +35 -17
  675. ansible_test/_util/controller/tools/collection_detail.py +1 -0
  676. ansible_test/_util/controller/tools/yaml_to_json.py +2 -1
  677. ansible_test/_util/target/pytest/plugins/ansible_forked.py +6 -1
  678. ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +2 -1
  679. ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -0
  680. ansible_test/_util/target/sanity/compile/compile.py +1 -0
  681. ansible_test/_util/target/sanity/import/importer.py +15 -16
  682. ansible_test/_util/target/setup/bootstrap.sh +9 -20
  683. ansible_test/_util/target/setup/probe_cgroups.py +1 -0
  684. ansible_test/_util/target/setup/quiet_pip.py +1 -0
  685. ansible_test/_util/target/setup/requirements.py +35 -27
  686. ansible_test/_util/target/tools/virtualenvcheck.py +2 -1
  687. ansible_test/_util/target/tools/yamlcheck.py +2 -1
  688. ansible/compat/selectors.py +0 -32
  689. ansible/errors/yaml_strings.py +0 -138
  690. ansible/executor/action_write_locks.py +0 -44
  691. ansible/executor/discovery/python_target.py +0 -47
  692. ansible/executor/powershell/module_powershell_wrapper.ps1 +0 -86
  693. ansible/executor/powershell/module_script_wrapper.ps1 +0 -22
  694. ansible/module_utils/compat/importlib.py +0 -26
  695. ansible/module_utils/compat/selectors.py +0 -32
  696. ansible/module_utils/pycompat24.py +0 -73
  697. ansible/parsing/yaml/constructor.py +0 -178
  698. ansible/template/native_helpers.py +0 -251
  699. ansible/template/template.py +0 -43
  700. ansible/template/vars.py +0 -77
  701. ansible/utils/native_jinja.py +0 -11
  702. ansible/vars/fact_cache.py +0 -71
  703. ansible_core-2.18.5rc1.dist-info/RECORD +0 -992
  704. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/Apache-License.txt +0 -0
  705. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/COPYING +0 -0
  706. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/MIT-license.txt +0 -0
  707. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/PSF-license.txt +0 -0
  708. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/entry_points.txt +0 -0
  709. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/simplified_bsd.txt +0 -0
  710. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/top_level.txt +0 -0
@@ -6,6 +6,7 @@
6
6
  from __future__ import annotations
7
7
 
8
8
  import base64
9
+ import contextlib
9
10
  import json
10
11
  import os
11
12
  import re
@@ -13,29 +14,44 @@ import secrets
13
14
  import shlex
14
15
  import stat
15
16
  import tempfile
17
+ import typing as t
16
18
 
17
19
  from abc import ABC, abstractmethod
18
20
  from collections.abc import Sequence
19
21
 
20
22
  from ansible import constants as C
23
+ from ansible._internal._errors import _captured
21
24
  from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleActionSkip, AnsibleActionFail, AnsibleAuthenticationFailure
22
- from ansible.executor.module_common import modify_module
25
+ from ansible._internal._errors import _utils
26
+ from ansible.executor.module_common import modify_module, _BuiltModule
23
27
  from ansible.executor.interpreter_discovery import discover_interpreter, InterpreterDiscoveryRequiredError
28
+ from ansible.module_utils._internal import _traceback
24
29
  from ansible.module_utils.common.arg_spec import ArgumentSpecValidator
25
30
  from ansible.module_utils.errors import UnsupportedError
26
31
  from ansible.module_utils.json_utils import _filter_non_json_lines
32
+ from ansible.module_utils.common.json import Direction, get_module_encoder, get_module_decoder
27
33
  from ansible.module_utils.six import binary_type, string_types, text_type
28
34
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
29
- from ansible.parsing.utils.jsonify import jsonify
30
35
  from ansible.release import __version__
31
36
  from ansible.utils.collection_loader import resource_from_fqcr
32
37
  from ansible.utils.display import Display
33
- from ansible.utils.unsafe_proxy import wrap_var, AnsibleUnsafeText
34
38
  from ansible.vars.clean import remove_internal_keys
35
39
  from ansible.utils.plugin_docs import get_versioned_doclink
40
+ from ansible import _internal
41
+ from ansible._internal._templating import _engine
42
+
43
+ from .. import _AnsiblePluginInfoMixin
44
+ from ...module_utils.common.messages import PluginInfo
36
45
 
37
46
  display = Display()
38
47
 
48
+ if t.TYPE_CHECKING:
49
+ from ansible.parsing.dataloader import DataLoader
50
+ from ansible.playbook.play_context import PlayContext
51
+ from ansible.playbook.task import Task
52
+ from ansible.plugins.connection import ConnectionBase
53
+ from ansible.template import Templar
54
+
39
55
 
40
56
  def _validate_utf8_json(d):
41
57
  if isinstance(d, text_type):
@@ -49,14 +65,13 @@ def _validate_utf8_json(d):
49
65
  _validate_utf8_json(o)
50
66
 
51
67
 
52
- class ActionBase(ABC):
53
-
54
- '''
68
+ class ActionBase(ABC, _AnsiblePluginInfoMixin):
69
+ """
55
70
  This class is the base class for all action plugins, and defines
56
71
  code common to all actions. The base class handles the connection
57
72
  by putting/getting files and executing commands based on the current
58
73
  action in use.
59
- '''
74
+ """
60
75
 
61
76
  # A set of valid arguments
62
77
  _VALID_ARGS = frozenset([]) # type: frozenset[str]
@@ -67,22 +82,24 @@ class ActionBase(ABC):
67
82
  _requires_connection = True
68
83
  _supports_check_mode = True
69
84
  _supports_async = False
85
+ supports_raw_params = False
70
86
 
71
- def __init__(self, task, connection, play_context, loader, templar, shared_loader_obj):
87
+ def __init__(self, task: Task, connection: ConnectionBase, play_context: PlayContext, loader: DataLoader, templar: Templar, shared_loader_obj=None):
72
88
  self._task = task
73
89
  self._connection = connection
74
90
  self._play_context = play_context
75
91
  self._loader = loader
76
92
  self._templar = templar
77
- self._shared_loader_obj = shared_loader_obj
93
+
94
+ from ansible.plugins import loader as plugin_loaders # avoid circular global import since PluginLoader needs ActionBase
95
+
96
+ self._shared_loader_obj = plugin_loaders # shared_loader_obj was just a ref to `ansible.plugins.loader` anyway; this lets us inherit its type
78
97
  self._cleanup_remote_tmp = False
79
98
 
80
99
  # interpreter discovery state
81
- self._discovered_interpreter_key = None
100
+ self._discovered_interpreter_key: str | None = None
82
101
  self._discovered_interpreter = False
83
- self._discovery_deprecation_warnings = []
84
- self._discovery_warnings = []
85
- self._used_interpreter = None
102
+ self._used_interpreter: str | None = None
86
103
 
87
104
  # Backwards compat: self._display isn't really needed, just import the global display and use that.
88
105
  self._display = display
@@ -109,9 +126,9 @@ class ActionBase(ABC):
109
126
  result = {}
110
127
 
111
128
  if tmp is not None:
112
- result['warning'] = ['ActionModule.run() no longer honors the tmp parameter. Action'
113
- ' plugins should set self._connection._shell.tmpdir to share'
114
- ' the tmpdir']
129
+ display.warning('ActionModule.run() no longer honors the tmp parameter. Action'
130
+ ' plugins should set self._connection._shell.tmpdir to share'
131
+ ' the tmpdir.')
115
132
  del tmp
116
133
 
117
134
  if self._task.async_val and not self._supports_async:
@@ -177,7 +194,7 @@ class ActionBase(ABC):
177
194
  if isinstance(error, UnsupportedError):
178
195
  msg = f"Unsupported parameters for ({self._load_name}) module: {msg}"
179
196
 
180
- raise AnsibleActionFail(msg)
197
+ raise AnsibleActionFail(msg, obj=self._task.args)
181
198
 
182
199
  return validation_result, new_module_args
183
200
 
@@ -193,6 +210,28 @@ class ActionBase(ABC):
193
210
  if force or not self._task.async_val:
194
211
  self._remove_tmp_path(self._connection._shell.tmpdir)
195
212
 
213
+ @classmethod
214
+ @contextlib.contextmanager
215
+ @_internal.experimental
216
+ def get_finalize_task_args_context(cls) -> t.Any:
217
+ """
218
+ EXPERIMENTAL: Unstable API subject to change at any time without notice.
219
+ Wraps task arg finalization with (optional) stateful context.
220
+ The context manager is entered during `Task.post_validate_args, and may yield a single value to be passed
221
+ as `context` to Task.finalize_task_arg for each task arg.
222
+ """
223
+ yield None
224
+
225
+ @classmethod
226
+ @_internal.experimental
227
+ def finalize_task_arg(cls, name: str, value: t.Any, templar: _engine.TemplateEngine, context: t.Any) -> t.Any:
228
+ """
229
+ EXPERIMENTAL: Unstable API subject to change at any time without notice.
230
+ Called for each task arg to allow for custom templating.
231
+ The optional `context` value is sourced from `Task.get_finalize_task_args_context`.
232
+ """
233
+ return templar.template(value)
234
+
196
235
  def get_plugin_option(self, plugin, option, default=None):
197
236
  """Helper to get an option from a plugin without having to use
198
237
  the try/except dance everywhere to set a default
@@ -218,11 +257,11 @@ class ActionBase(ABC):
218
257
  return True
219
258
  return False
220
259
 
221
- def _configure_module(self, module_name, module_args, task_vars):
222
- '''
260
+ def _configure_module(self, module_name, module_args, task_vars) -> tuple[_BuiltModule, str]:
261
+ """
223
262
  Handles the loading and templating of the module code through the
224
263
  modify_module() function.
225
- '''
264
+ """
226
265
  if self._task.delegate_to:
227
266
  use_vars = task_vars.get('ansible_delegated_vars')[self._task.delegate_to]
228
267
  else:
@@ -276,38 +315,37 @@ class ActionBase(ABC):
276
315
  raise AnsibleError("The module %s was not found in configured module paths" % (module_name))
277
316
 
278
317
  # insert shared code and arguments into the module
279
- final_environment = dict()
318
+ final_environment: dict[str, t.Any] = {}
280
319
  self._compute_environment_string(final_environment)
281
320
 
282
- become_kwargs = {}
283
- if self._connection.become:
284
- become_kwargs['become'] = True
285
- become_kwargs['become_method'] = self._connection.become.name
286
- become_kwargs['become_user'] = self._connection.become.get_option('become_user',
287
- playcontext=self._play_context)
288
- become_kwargs['become_password'] = self._connection.become.get_option('become_pass',
289
- playcontext=self._play_context)
290
- become_kwargs['become_flags'] = self._connection.become.get_option('become_flags',
291
- playcontext=self._play_context)
321
+ # `modify_module` adapts PluginInfo to allow target-side use of `PluginExecContext` since modules aren't plugins
322
+ plugin = PluginInfo(
323
+ requested_name=module_name,
324
+ resolved_name=result.resolved_fqcn,
325
+ type='module',
326
+ )
292
327
 
293
328
  # modify_module will exit early if interpreter discovery is required; re-run after if necessary
294
- for dummy in (1, 2):
329
+ for _dummy in (1, 2):
295
330
  try:
296
- (module_data, module_style, module_shebang) = modify_module(module_name, module_path, module_args, self._templar,
297
- task_vars=use_vars,
298
- module_compression=C.config.get_config_value('DEFAULT_MODULE_COMPRESSION',
299
- variables=task_vars),
300
- async_timeout=self._task.async_val,
301
- environment=final_environment,
302
- remote_is_local=bool(getattr(self._connection, '_remote_is_local', False)),
303
- **become_kwargs)
331
+ module_bits = modify_module(
332
+ module_name=module_name,
333
+ module_path=module_path,
334
+ module_args=module_args,
335
+ templar=self._templar,
336
+ task_vars=use_vars,
337
+ module_compression=C.config.get_config_value('DEFAULT_MODULE_COMPRESSION', variables=task_vars),
338
+ async_timeout=self._task.async_val,
339
+ environment=final_environment,
340
+ remote_is_local=bool(getattr(self._connection, '_remote_is_local', False)),
341
+ plugin=plugin,
342
+ become_plugin=self._connection.become,
343
+ )
344
+
304
345
  break
305
346
  except InterpreterDiscoveryRequiredError as idre:
306
- self._discovered_interpreter = AnsibleUnsafeText(discover_interpreter(
307
- action=self,
308
- interpreter_name=idre.interpreter_name,
309
- discovery_mode=idre.discovery_mode,
310
- task_vars=use_vars))
347
+ self._discovered_interpreter = discover_interpreter(action=self, interpreter_name=idre.interpreter_name,
348
+ discovery_mode=idre.discovery_mode, task_vars=use_vars)
311
349
 
312
350
  # update the local task_vars with the discovered interpreter (which might be None);
313
351
  # we'll propagate back to the controller in the task result
@@ -327,12 +365,12 @@ class ActionBase(ABC):
327
365
  else:
328
366
  task_vars['ansible_delegated_vars'][self._task.delegate_to]['ansible_facts'][discovered_key] = self._discovered_interpreter
329
367
 
330
- return (module_style, module_shebang, module_data, module_path)
368
+ return module_bits, module_path
331
369
 
332
370
  def _compute_environment_string(self, raw_environment_out=None):
333
- '''
371
+ """
334
372
  Builds the environment string to be used when executing the remote task.
335
- '''
373
+ """
336
374
 
337
375
  final_environment = dict()
338
376
  if self._task.environment is not None:
@@ -363,52 +401,28 @@ class ActionBase(ABC):
363
401
  return self._connection._shell.env_prefix(**final_environment)
364
402
 
365
403
  def _early_needs_tmp_path(self):
366
- '''
404
+ """
367
405
  Determines if a tmp path should be created before the action is executed.
368
- '''
406
+ """
369
407
 
370
408
  return getattr(self, 'TRANSFERS_FILES', False)
371
409
 
372
- def _is_pipelining_enabled(self, module_style, wrap_async=False):
373
- '''
374
- Determines if we are required and can do pipelining
375
- '''
376
-
377
- try:
378
- is_enabled = self._connection.get_option('pipelining')
379
- except (KeyError, AttributeError, ValueError):
380
- is_enabled = self._play_context.pipelining
381
-
382
- # winrm supports async pipeline
383
- # TODO: make other class property 'has_async_pipelining' to separate cases
384
- always_pipeline = self._connection.always_pipeline_modules
385
-
386
- # su does not work with pipelining
387
- # TODO: add has_pipelining class prop to become plugins
388
- become_exception = (self._connection.become.name if self._connection.become else '') != 'su'
389
-
390
- # any of these require a true
391
- conditions = [
392
- self._connection.has_pipelining, # connection class supports it
393
- is_enabled or always_pipeline, # enabled via config or forced via connection (eg winrm)
394
- module_style == "new", # old style modules do not support pipelining
395
- not C.DEFAULT_KEEP_REMOTE_FILES, # user wants remote files
396
- not wrap_async or always_pipeline, # async does not normally support pipelining unless it does (eg winrm)
397
- become_exception,
398
- ]
399
-
400
- return all(conditions)
410
+ def _is_pipelining_enabled(self, module_style: str, wrap_async: bool = False) -> bool:
411
+ """
412
+ Determines if we are required and can do pipelining, only 'new' style modules can support pipelining
413
+ """
414
+ return bool(module_style == 'new' and self._connection.is_pipelining_enabled(wrap_async))
401
415
 
402
416
  def _get_admin_users(self):
403
- '''
417
+ """
404
418
  Returns a list of admin users that are configured for the current shell
405
419
  plugin
406
- '''
420
+ """
407
421
 
408
422
  return self.get_shell_option('admin_users', ['root'])
409
423
 
410
424
  def _get_remote_addr(self, tvars):
411
- ''' consistently get the 'remote_address' for the action plugin '''
425
+ """ consistently get the 'remote_address' for the action plugin """
412
426
  remote_addr = tvars.get('delegated_vars', {}).get('ansible_host', tvars.get('ansible_host', tvars.get('inventory_hostname', None)))
413
427
  for variation in ('remote_addr', 'host'):
414
428
  try:
@@ -422,7 +436,7 @@ class ActionBase(ABC):
422
436
  return remote_addr
423
437
 
424
438
  def _get_remote_user(self):
425
- ''' consistently get the 'remote_user' for the action plugin '''
439
+ """ consistently get the 'remote_user' for the action plugin """
426
440
  # TODO: use 'current user running ansible' as fallback when moving away from play_context
427
441
  # pwd.getpwuid(os.getuid()).pw_name
428
442
  remote_user = None
@@ -437,10 +451,10 @@ class ActionBase(ABC):
437
451
  return remote_user
438
452
 
439
453
  def _is_become_unprivileged(self):
440
- '''
454
+ """
441
455
  The user is not the same as the connection user and is not part of the
442
456
  shell configured admin users
443
- '''
457
+ """
444
458
  # if we don't use become then we know we aren't switching to a
445
459
  # different unprivileged user
446
460
  if not self._connection.become:
@@ -454,9 +468,9 @@ class ActionBase(ABC):
454
468
  return bool(become_user and become_user not in admin_users + [remote_user])
455
469
 
456
470
  def _make_tmp_path(self, remote_user=None):
457
- '''
471
+ """
458
472
  Create and return a temporary path on a remote box.
459
- '''
473
+ """
460
474
 
461
475
  # Network connection plugins (network_cli, netconf, etc.) execute on the controller, rather than the remote host.
462
476
  # As such, we want to avoid using remote_user for paths as remote_user may not line up with the local user
@@ -517,11 +531,11 @@ class ActionBase(ABC):
517
531
  return rc
518
532
 
519
533
  def _should_remove_tmp_path(self, tmp_path):
520
- '''Determine if temporary path should be deleted or kept by user request/config'''
534
+ """Determine if temporary path should be deleted or kept by user request/config"""
521
535
  return tmp_path and self._cleanup_remote_tmp and not C.DEFAULT_KEEP_REMOTE_FILES and "-tmp-" in tmp_path
522
536
 
523
537
  def _remove_tmp_path(self, tmp_path, force=False):
524
- '''Remove a temporary path we created. '''
538
+ """Remove a temporary path we created. """
525
539
 
526
540
  if tmp_path is None and self._connection._shell.tmpdir:
527
541
  tmp_path = self._connection._shell.tmpdir
@@ -556,18 +570,19 @@ class ActionBase(ABC):
556
570
  self._connection.put_file(local_path, remote_path)
557
571
  return remote_path
558
572
 
559
- def _transfer_data(self, remote_path, data):
560
- '''
573
+ def _transfer_data(self, remote_path: str | bytes, data: str | bytes) -> str | bytes:
574
+ """
561
575
  Copies the module data out to the temporary module path.
562
- '''
576
+ """
563
577
 
564
- if isinstance(data, dict):
565
- data = jsonify(data)
578
+ if isinstance(data, str):
579
+ data = data.encode(errors='surrogateescape')
580
+ elif not isinstance(data, bytes):
581
+ raise TypeError('data must be either a string or bytes')
566
582
 
567
583
  afd, afile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP)
568
584
  afo = os.fdopen(afd, 'wb')
569
585
  try:
570
- data = to_bytes(data, errors='surrogate_or_strict')
571
586
  afo.write(data)
572
587
  except Exception as e:
573
588
  raise AnsibleError("failure writing module data to temporary file for transfer: %s" % to_native(e))
@@ -802,41 +817,41 @@ class ActionBase(ABC):
802
817
  to_native(res['stderr']), become_link))
803
818
 
804
819
  def _remote_chmod(self, paths, mode, sudoable=False):
805
- '''
820
+ """
806
821
  Issue a remote chmod command
807
- '''
822
+ """
808
823
  cmd = self._connection._shell.chmod(paths, mode)
809
824
  res = self._low_level_execute_command(cmd, sudoable=sudoable)
810
825
  return res
811
826
 
812
827
  def _remote_chown(self, paths, user, sudoable=False):
813
- '''
828
+ """
814
829
  Issue a remote chown command
815
- '''
830
+ """
816
831
  cmd = self._connection._shell.chown(paths, user)
817
832
  res = self._low_level_execute_command(cmd, sudoable=sudoable)
818
833
  return res
819
834
 
820
835
  def _remote_chgrp(self, paths, group, sudoable=False):
821
- '''
836
+ """
822
837
  Issue a remote chgrp command
823
- '''
838
+ """
824
839
  cmd = self._connection._shell.chgrp(paths, group)
825
840
  res = self._low_level_execute_command(cmd, sudoable=sudoable)
826
841
  return res
827
842
 
828
843
  def _remote_set_user_facl(self, paths, user, mode, sudoable=False):
829
- '''
844
+ """
830
845
  Issue a remote call to setfacl
831
- '''
846
+ """
832
847
  cmd = self._connection._shell.set_user_facl(paths, user, mode)
833
848
  res = self._low_level_execute_command(cmd, sudoable=sudoable)
834
849
  return res
835
850
 
836
851
  def _execute_remote_stat(self, path, all_vars, follow, tmp=None, checksum=True):
837
- '''
852
+ """
838
853
  Get information from remote file.
839
- '''
854
+ """
840
855
  if tmp is not None:
841
856
  display.warning('_execute_remote_stat no longer honors the tmp parameter. Action'
842
857
  ' plugins should set self._connection._shell.tmpdir to share'
@@ -876,7 +891,7 @@ class ActionBase(ABC):
876
891
  return mystat['stat']
877
892
 
878
893
  def _remote_expand_user(self, path, sudoable=True, pathsep=None):
879
- ''' takes a remote path and performs tilde/$HOME expansion on the remote host '''
894
+ """ takes a remote path and performs tilde/$HOME expansion on the remote host """
880
895
 
881
896
  # We only expand ~/path and ~username/path
882
897
  if not path.startswith('~'):
@@ -930,9 +945,9 @@ class ActionBase(ABC):
930
945
  return expanded
931
946
 
932
947
  def _strip_success_message(self, data):
933
- '''
948
+ """
934
949
  Removes the BECOME-SUCCESS message from the data.
935
- '''
950
+ """
936
951
  if data.strip().startswith('BECOME-SUCCESS-'):
937
952
  data = re.sub(r'^((\r)?\n)?BECOME-SUCCESS.*(\r)?\n', '', data)
938
953
  return data
@@ -972,9 +987,6 @@ class ActionBase(ABC):
972
987
  # let module know about filesystems that selinux treats specially
973
988
  module_args['_ansible_selinux_special_fs'] = C.DEFAULT_SELINUX_SPECIAL_FS
974
989
 
975
- # what to do when parameter values are converted to strings
976
- module_args['_ansible_string_conversion_action'] = C.STRING_CONVERSION_ACTION
977
-
978
990
  # give the module the socket for persistent connections
979
991
  module_args['_ansible_socket'] = getattr(self._connection, 'socket_path')
980
992
  if not module_args['_ansible_socket']:
@@ -1001,11 +1013,13 @@ class ActionBase(ABC):
1001
1013
  # allow user to insert string to add context to remote loggging
1002
1014
  module_args['_ansible_target_log_info'] = C.config.get_config_value('TARGET_LOG_INFO', variables=task_vars)
1003
1015
 
1016
+ module_args['_ansible_tracebacks_for'] = _traceback.traceback_for()
1017
+
1004
1018
  def _execute_module(self, module_name=None, module_args=None, tmp=None, task_vars=None, persist_files=False, delete_remote_tmp=None, wrap_async=False,
1005
1019
  ignore_unknown_opts: bool = False):
1006
- '''
1020
+ """
1007
1021
  Transfer and run a module along with its arguments.
1008
- '''
1022
+ """
1009
1023
  if tmp is not None:
1010
1024
  display.warning('_execute_module no longer honors the tmp parameter. Action plugins'
1011
1025
  ' should set self._connection._shell.tmpdir to share the tmpdir')
@@ -1047,7 +1061,8 @@ class ActionBase(ABC):
1047
1061
  self._task.environment.append({"ANSIBLE_ASYNC_DIR": async_dir})
1048
1062
 
1049
1063
  # FUTURE: refactor this along with module build process to better encapsulate "smart wrapper" functionality
1050
- (module_style, shebang, module_data, module_path) = self._configure_module(module_name=module_name, module_args=module_args, task_vars=task_vars)
1064
+ module_bits, module_path = self._configure_module(module_name=module_name, module_args=module_args, task_vars=task_vars)
1065
+ (module_style, shebang, module_data) = (module_bits.module_style, module_bits.shebang, module_bits.b_module_data)
1051
1066
  display.vvv("Using module file %s" % module_path)
1052
1067
  if not shebang and module_style != 'binary':
1053
1068
  raise AnsibleError("module (%s) is missing interpreter line" % module_name)
@@ -1083,7 +1098,8 @@ class ActionBase(ABC):
1083
1098
  args_data += '%s=%s ' % (k, shlex.quote(text_type(v)))
1084
1099
  self._transfer_data(args_file_path, args_data)
1085
1100
  elif module_style in ('non_native_want_json', 'binary'):
1086
- self._transfer_data(args_file_path, json.dumps(module_args))
1101
+ profile_encoder = get_module_encoder(module_bits.serialization_profile, Direction.CONTROLLER_TO_MODULE)
1102
+ self._transfer_data(args_file_path, json.dumps(module_args, cls=profile_encoder))
1087
1103
  display.debug("done transferring module to remote")
1088
1104
 
1089
1105
  environment_string = self._compute_environment_string()
@@ -1106,8 +1122,8 @@ class ActionBase(ABC):
1106
1122
 
1107
1123
  if wrap_async and not self._connection.always_pipeline_modules:
1108
1124
  # configure, upload, and chmod the async_wrapper module
1109
- (async_module_style, shebang, async_module_data, async_module_path) = self._configure_module(
1110
- module_name='ansible.legacy.async_wrapper', module_args=dict(), task_vars=task_vars)
1125
+ (async_module_bits, async_module_path) = self._configure_module(module_name='ansible.legacy.async_wrapper', module_args=dict(), task_vars=task_vars)
1126
+ (async_module_style, shebang, async_module_data) = (async_module_bits.module_style, async_module_bits.shebang, async_module_bits.b_module_data)
1111
1127
  async_module_remote_filename = self._connection._shell.get_remote_filename(async_module_path)
1112
1128
  remote_async_module_path = self._connection._shell.join_path(tmpdir, async_module_remote_filename)
1113
1129
  self._transfer_data(remote_async_module_path, async_module_data)
@@ -1156,7 +1172,7 @@ class ActionBase(ABC):
1156
1172
  res = self._low_level_execute_command(cmd, sudoable=sudoable, in_data=in_data)
1157
1173
 
1158
1174
  # parse the main result
1159
- data = self._parse_returned_data(res)
1175
+ data = self._parse_returned_data(res, module_bits.serialization_profile)
1160
1176
 
1161
1177
  # NOTE: INTERNAL KEYS ONLY ACCESSIBLE HERE
1162
1178
  # get internal info before cleaning
@@ -1197,74 +1213,71 @@ class ActionBase(ABC):
1197
1213
 
1198
1214
  data['ansible_facts'][self._discovered_interpreter_key] = self._discovered_interpreter
1199
1215
 
1200
- if self._discovery_warnings:
1201
- if data.get('warnings') is None:
1202
- data['warnings'] = []
1203
- data['warnings'].extend(self._discovery_warnings)
1204
-
1205
- if self._discovery_deprecation_warnings:
1206
- if data.get('deprecations') is None:
1207
- data['deprecations'] = []
1208
- data['deprecations'].extend(self._discovery_deprecation_warnings)
1209
-
1210
- # mark the entire module results untrusted as a template right here, since the current action could
1211
- # possibly template one of these values.
1212
- data = wrap_var(data)
1213
-
1214
1216
  display.debug("done with _execute_module (%s, %s)" % (module_name, module_args))
1215
1217
  return data
1216
1218
 
1217
- def _parse_returned_data(self, res):
1219
+ def _parse_returned_data(self, res: dict[str, t.Any], profile: str) -> dict[str, t.Any]:
1218
1220
  try:
1219
- filtered_output, warnings = _filter_non_json_lines(res.get('stdout', u''), objects_only=True)
1221
+ filtered_output, warnings = _filter_non_json_lines(res.get('stdout', ''), objects_only=True)
1222
+
1220
1223
  for w in warnings:
1221
1224
  display.warning(w)
1222
1225
 
1223
- data = json.loads(filtered_output)
1224
-
1225
- if C.MODULE_STRICT_UTF8_RESPONSE and not data.pop('_ansible_trusted_utf8', None):
1226
- try:
1227
- _validate_utf8_json(data)
1228
- except UnicodeEncodeError:
1229
- raise ValueError(
1230
- f'Module "{self._task.resolved_action or self._task.action}" returned non UTF-8 data in '
1231
- 'the JSON response.',
1232
- )
1233
-
1234
- data['_ansible_parsed'] = True
1235
- except ValueError as e:
1236
- # not valid json, lets try to capture error
1237
- data = dict(failed=True, _ansible_parsed=False)
1238
- data['module_stdout'] = res.get('stdout', u'')
1239
- if 'stderr' in res:
1240
- data['module_stderr'] = res['stderr']
1241
- if res['stderr'].startswith(u'Traceback'):
1242
- data['exception'] = res['stderr']
1243
-
1244
- # in some cases a traceback will arrive on stdout instead of stderr, such as when using ssh with -tt
1245
- if 'exception' not in data and data['module_stdout'].startswith(u'Traceback'):
1246
- data['exception'] = data['module_stdout']
1247
-
1248
- # The default
1249
- data['msg'] = f"MODULE FAILURE: {e}"
1250
-
1251
- # try to figure out if we are missing interpreter
1226
+ decoder = get_module_decoder(profile, Direction.MODULE_TO_CONTROLLER)
1227
+
1228
+ data = json.loads(filtered_output, cls=decoder)
1229
+
1230
+ _captured.AnsibleModuleCapturedError.normalize_result_exception(data)
1231
+
1232
+ data.update(_ansible_parsed=True) # this must occur after normalize_result_exception, since it checks the type of data to ensure it's a dict
1233
+ except ValueError as ex:
1234
+ message = "Module result deserialization failed."
1235
+ help_text = ""
1236
+ include_cause_message = True
1237
+
1252
1238
  if self._used_interpreter is not None:
1253
- interpreter = re.escape(self._used_interpreter.lstrip('!#'))
1254
- match = re.compile('%s: (?:No such file or directory|not found)' % interpreter)
1255
- if match.search(data['module_stderr']) or match.search(data['module_stdout']):
1256
- data['msg'] = "The module failed to execute correctly, you probably need to set the interpreter."
1239
+ interpreter = self._used_interpreter.lstrip('!#')
1240
+ # "not found" case is currently not tested; it was once reproducible
1241
+ # see: https://github.com/ansible/ansible/pull/53534
1242
+ not_found_err_re = re.compile(rf'{re.escape(interpreter)}: (?:No such file or directory|not found|command not found)')
1257
1243
 
1258
- # always append hint
1259
- data['msg'] += '\nSee stdout/stderr for the exact error'
1244
+ if not_found_err_re.search(res.get('stderr', '')) or not_found_err_re.search(res.get('stdout', '')):
1245
+ message = f"The module interpreter {interpreter!r} was not found."
1246
+ help_text = 'Consider overriding the configured interpreter path for this host. '
1247
+ include_cause_message = False # cause context *might* be useful in the traceback, but the JSON deserialization failure message is not
1248
+
1249
+ try:
1250
+ # Because the underlying action API is built on result dicts instead of exceptions (for all but the most catastrophic failures),
1251
+ # we're using a tweaked version of the module exception handler to get new ErrorDetail-backed errors from this part of the code.
1252
+ # Ideally this would raise immediately on failure, but this would likely break actions that assume `ActionBase._execute_module()`
1253
+ # does not raise on module failure.
1254
+
1255
+ error = AnsibleError(
1256
+ message=message,
1257
+ help_text=help_text + "See stdout/stderr for the returned output.",
1258
+ )
1259
+
1260
+ error._include_cause_message = include_cause_message
1261
+
1262
+ raise error from ex
1263
+ except AnsibleError as ansible_ex:
1264
+ sentinel = object()
1265
+
1266
+ data = self.result_dict_from_exception(ansible_ex)
1267
+ data.update(
1268
+ _ansible_parsed=False,
1269
+ module_stdout=res.get('stdout', ''),
1270
+ module_stderr=res.get('stderr', sentinel),
1271
+ rc=res.get('rc', sentinel),
1272
+ )
1273
+
1274
+ data = {k: v for k, v in data.items() if v is not sentinel}
1260
1275
 
1261
- if 'rc' in res:
1262
- data['rc'] = res['rc']
1263
1276
  return data
1264
1277
 
1265
1278
  # FIXME: move to connection base
1266
1279
  def _low_level_execute_command(self, cmd, sudoable=True, in_data=None, executable=None, encoding_errors='surrogate_then_replace', chdir=None):
1267
- '''
1280
+ """
1268
1281
  This is the function which executes the low level shell command, which
1269
1282
  may be commands to create/remove directories for temporary files, or to
1270
1283
  run the module code or python directly when pipelining.
@@ -1277,7 +1290,7 @@ class ActionBase(ABC):
1277
1290
  verbatim, then this won't work. May have to use some sort of
1278
1291
  replacement strategy (python3 could use surrogateescape)
1279
1292
  :kwarg chdir: cd into this directory before executing the command.
1280
- '''
1293
+ """
1281
1294
 
1282
1295
  display.debug("_low_level_execute_command(): starting")
1283
1296
  # if not cmd:
@@ -1373,7 +1386,7 @@ class ActionBase(ABC):
1373
1386
  elif peek_result.get('size') and C.MAX_FILE_SIZE_FOR_DIFF > 0 and peek_result['size'] > C.MAX_FILE_SIZE_FOR_DIFF:
1374
1387
  diff['dst_larger'] = C.MAX_FILE_SIZE_FOR_DIFF
1375
1388
  else:
1376
- display.debug(u"Slurping the file %s" % source)
1389
+ display.debug(u"Slurping the file %s" % destination)
1377
1390
  dest_result = self._execute_module(
1378
1391
  module_name='ansible.legacy.slurp', module_args=dict(path=destination),
1379
1392
  task_vars=task_vars, persist_files=True)
@@ -1420,14 +1433,34 @@ class ActionBase(ABC):
1420
1433
  return diff
1421
1434
 
1422
1435
  def _find_needle(self, dirname, needle):
1423
- '''
1436
+ """
1424
1437
  find a needle in haystack of paths, optionally using 'dirname' as a subdir.
1425
1438
  This will build the ordered list of paths to search and pass them to dwim
1426
1439
  to get back the first existing file found.
1427
- '''
1440
+ """
1428
1441
 
1429
1442
  # dwim already deals with playbook basedirs
1430
1443
  path_stack = self._task.get_search_path()
1431
1444
 
1432
1445
  # if missing it will return a file not found exception
1433
1446
  return self._loader.path_dwim_relative_stack(path_stack, dirname, needle)
1447
+
1448
+ @staticmethod
1449
+ def result_dict_from_exception(exception: BaseException) -> dict[str, t.Any]:
1450
+ """Return a failed task result dict from the given exception."""
1451
+ if ansible_remoted_error := _captured.AnsibleResultCapturedError.find_first_remoted_error(exception):
1452
+ result = ansible_remoted_error._result.copy()
1453
+ else:
1454
+ result = {}
1455
+
1456
+ error_summary = _utils._create_error_summary(exception, _traceback.TracebackEvent.ERROR)
1457
+
1458
+ result.update(
1459
+ failed=True,
1460
+ exception=error_summary,
1461
+ )
1462
+
1463
+ if 'msg' not in result:
1464
+ result.update(msg=_utils._dedupe_and_concat_message_chain([md.msg for md in error_summary.details]))
1465
+
1466
+ return result