ansible-core 2.18.7rc1__py3-none-any.whl → 2.19.0__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.

Potentially problematic release.


This version of ansible-core might be problematic. Click here for more details.

Files changed (757) hide show
  1. ansible/_internal/__init__.py +53 -0
  2. ansible/_internal/_ansiballz/__init__.py +0 -0
  3. ansible/_internal/_ansiballz/_builder.py +101 -0
  4. ansible/_internal/_ansiballz/_wrapper.py +262 -0
  5. ansible/_internal/_collection_proxy.py +47 -0
  6. ansible/_internal/_datatag/__init__.py +0 -0
  7. ansible/_internal/_datatag/_tags.py +130 -0
  8. ansible/_internal/_datatag/_utils.py +19 -0
  9. ansible/_internal/_datatag/_wrappers.py +33 -0
  10. ansible/_internal/_errors/__init__.py +0 -0
  11. ansible/_internal/_errors/_alarm_timeout.py +66 -0
  12. ansible/_internal/_errors/_captured.py +123 -0
  13. ansible/_internal/_errors/_error_factory.py +89 -0
  14. ansible/_internal/_errors/_error_utils.py +240 -0
  15. ansible/_internal/_errors/_handler.py +91 -0
  16. ansible/_internal/_errors/_task_timeout.py +28 -0
  17. ansible/_internal/_event_formatting.py +127 -0
  18. ansible/_internal/_json/__init__.py +214 -0
  19. ansible/_internal/_json/_legacy_encoder.py +34 -0
  20. ansible/_internal/_json/_profiles/__init__.py +0 -0
  21. ansible/_internal/_json/_profiles/_cache_persistence.py +57 -0
  22. ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
  23. ansible/_internal/_json/_profiles/_legacy.py +189 -0
  24. ansible/_internal/_locking.py +21 -0
  25. ansible/_internal/_plugins/__init__.py +0 -0
  26. ansible/_internal/_plugins/_cache.py +57 -0
  27. ansible/_internal/_ssh/__init__.py +0 -0
  28. ansible/_internal/_ssh/_agent_launch.py +91 -0
  29. ansible/_internal/_ssh/_ssh_agent.py +619 -0
  30. ansible/_internal/_task.py +78 -0
  31. ansible/_internal/_templating/__init__.py +12 -0
  32. ansible/_internal/_templating/_access.py +86 -0
  33. ansible/_internal/_templating/_chain_templar.py +63 -0
  34. ansible/_internal/_templating/_datatag.py +95 -0
  35. ansible/_internal/_templating/_engine.py +592 -0
  36. ansible/_internal/_templating/_errors.py +28 -0
  37. ansible/_internal/_templating/_jinja_bits.py +1106 -0
  38. ansible/_internal/_templating/_jinja_common.py +323 -0
  39. ansible/_internal/_templating/_jinja_patches.py +44 -0
  40. ansible/_internal/_templating/_jinja_plugins.py +375 -0
  41. ansible/_internal/_templating/_lazy_containers.py +633 -0
  42. ansible/_internal/_templating/_marker_behaviors.py +103 -0
  43. ansible/_internal/_templating/_template_vars.py +72 -0
  44. ansible/_internal/_templating/_transform.py +70 -0
  45. ansible/_internal/_templating/_utils.py +108 -0
  46. ansible/_internal/_testing.py +26 -0
  47. ansible/_internal/_wrapt.py +1052 -0
  48. ansible/_internal/_yaml/__init__.py +0 -0
  49. ansible/_internal/_yaml/_constructor.py +240 -0
  50. ansible/_internal/_yaml/_dumper.py +70 -0
  51. ansible/_internal/_yaml/_errors.py +166 -0
  52. ansible/_internal/_yaml/_loader.py +66 -0
  53. ansible/_internal/ansible_collections/ansible/_protomatter/README.md +11 -0
  54. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/action/debug.py +36 -0
  55. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/apply_trust.py +19 -0
  56. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +27 -0
  57. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/finalize.py +16 -0
  58. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/origin.py +18 -0
  59. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.py +24 -0
  60. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.yml +33 -0
  61. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/tag_names.py +16 -0
  62. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +17 -0
  63. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +49 -0
  64. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.py +21 -0
  65. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.yml +2 -0
  66. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.py +15 -0
  67. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.yml +19 -0
  68. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.py +18 -0
  69. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.yml +19 -0
  70. ansible/cli/__init__.py +93 -104
  71. ansible/cli/_ssh_askpass.py +54 -0
  72. ansible/cli/adhoc.py +20 -10
  73. ansible/cli/arguments/option_helpers.py +163 -10
  74. ansible/cli/config.py +43 -68
  75. ansible/cli/console.py +13 -11
  76. ansible/cli/doc.py +134 -77
  77. ansible/cli/galaxy.py +27 -20
  78. ansible/cli/inventory.py +28 -28
  79. ansible/cli/playbook.py +4 -12
  80. ansible/cli/pull.py +6 -3
  81. ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
  82. ansible/cli/vault.py +12 -11
  83. ansible/compat/__init__.py +2 -2
  84. ansible/compat/importlib_resources.py +9 -12
  85. ansible/config/base.yml +218 -133
  86. ansible/config/manager.py +220 -159
  87. ansible/constants.py +2 -65
  88. ansible/errors/__init__.py +350 -256
  89. ansible/executor/interpreter_discovery.py +28 -149
  90. ansible/executor/module_common.py +480 -514
  91. ansible/executor/play_iterator.py +22 -27
  92. ansible/executor/playbook_executor.py +11 -11
  93. ansible/executor/powershell/async_watchdog.ps1 +97 -102
  94. ansible/executor/powershell/async_wrapper.ps1 +204 -153
  95. ansible/executor/powershell/become_wrapper.ps1 +107 -144
  96. ansible/executor/powershell/bootstrap_wrapper.ps1 +46 -9
  97. ansible/executor/powershell/coverage_wrapper.ps1 +91 -135
  98. ansible/executor/powershell/exec_wrapper.ps1 +675 -196
  99. ansible/executor/powershell/module_manifest.py +469 -265
  100. ansible/executor/powershell/module_wrapper.ps1 +195 -186
  101. ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
  102. ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
  103. ansible/executor/powershell/psrp_fetch_file.ps1 +41 -0
  104. ansible/executor/powershell/psrp_put_file.ps1 +122 -0
  105. ansible/executor/powershell/winrm_fetch_file.ps1 +46 -0
  106. ansible/executor/powershell/winrm_put_file.ps1 +36 -0
  107. ansible/executor/process/worker.py +139 -149
  108. ansible/executor/stats.py +5 -5
  109. ansible/executor/task_executor.py +270 -297
  110. ansible/executor/task_queue_manager.py +135 -137
  111. ansible/executor/task_result.py +182 -79
  112. ansible/galaxy/__init__.py +2 -2
  113. ansible/galaxy/api.py +26 -25
  114. ansible/galaxy/collection/__init__.py +6 -14
  115. ansible/galaxy/collection/concrete_artifact_manager.py +12 -21
  116. ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
  117. ansible/galaxy/dependency_resolution/providers.py +4 -4
  118. ansible/galaxy/dependency_resolution/reporters.py +81 -0
  119. ansible/galaxy/role.py +6 -10
  120. ansible/galaxy/token.py +28 -21
  121. ansible/inventory/data.py +47 -57
  122. ansible/inventory/group.py +50 -73
  123. ansible/inventory/helpers.py +9 -0
  124. ansible/inventory/host.py +37 -54
  125. ansible/inventory/manager.py +79 -34
  126. ansible/keyword_desc.yml +1 -1
  127. ansible/module_utils/_internal/__init__.py +55 -0
  128. ansible/module_utils/_internal/_ambient_context.py +58 -0
  129. ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
  130. ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
  131. ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
  132. ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
  133. ansible/module_utils/_internal/_ansiballz/_loader.py +81 -0
  134. ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
  135. ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
  136. ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
  137. ansible/module_utils/_internal/_dataclass_validation.py +217 -0
  138. ansible/module_utils/_internal/_datatag/__init__.py +961 -0
  139. ansible/module_utils/_internal/_datatag/_tags.py +16 -0
  140. ansible/module_utils/_internal/_debugging.py +31 -0
  141. ansible/module_utils/_internal/_deprecator.py +157 -0
  142. ansible/module_utils/_internal/_errors.py +101 -0
  143. ansible/module_utils/_internal/_event_utils.py +61 -0
  144. ansible/module_utils/_internal/_json/__init__.py +63 -0
  145. ansible/module_utils/_internal/_json/_legacy_encoder.py +26 -0
  146. ansible/module_utils/_internal/_json/_profiles/__init__.py +428 -0
  147. ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
  148. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +33 -0
  149. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +37 -0
  150. ansible/module_utils/_internal/_json/_profiles/_module_modern_c2m.py +35 -0
  151. ansible/module_utils/_internal/_json/_profiles/_module_modern_m2c.py +33 -0
  152. ansible/module_utils/_internal/_json/_profiles/_tagless.py +52 -0
  153. ansible/module_utils/_internal/_messages.py +130 -0
  154. ansible/module_utils/_internal/_patches/__init__.py +66 -0
  155. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +53 -0
  156. ansible/module_utils/_internal/_patches/_socket_patch.py +34 -0
  157. ansible/module_utils/_internal/_patches/_sys_intern_patch.py +34 -0
  158. ansible/module_utils/_internal/_plugin_info.py +38 -0
  159. ansible/module_utils/_internal/_stack.py +22 -0
  160. ansible/module_utils/_internal/_testing.py +0 -0
  161. ansible/module_utils/_internal/_text_utils.py +6 -0
  162. ansible/module_utils/_internal/_traceback.py +92 -0
  163. ansible/module_utils/_internal/_validation.py +14 -0
  164. ansible/module_utils/ansible_release.py +2 -2
  165. ansible/module_utils/api.py +1 -2
  166. ansible/module_utils/basic.py +303 -202
  167. ansible/module_utils/common/_utils.py +24 -28
  168. ansible/module_utils/common/arg_spec.py +8 -3
  169. ansible/module_utils/common/collections.py +7 -2
  170. ansible/module_utils/common/dict_transformations.py +2 -2
  171. ansible/module_utils/common/file.py +2 -2
  172. ansible/module_utils/common/json.py +90 -84
  173. ansible/module_utils/common/locale.py +2 -2
  174. ansible/module_utils/common/parameters.py +27 -24
  175. ansible/module_utils/common/process.py +2 -3
  176. ansible/module_utils/common/respawn.py +11 -33
  177. ansible/module_utils/common/sentinel.py +66 -0
  178. ansible/module_utils/common/sys_info.py +8 -8
  179. ansible/module_utils/common/text/converters.py +16 -37
  180. ansible/module_utils/common/validation.py +35 -24
  181. ansible/module_utils/common/warnings.py +143 -25
  182. ansible/module_utils/common/yaml.py +29 -3
  183. ansible/module_utils/compat/datetime.py +33 -21
  184. ansible/module_utils/compat/paramiko.py +21 -10
  185. ansible/module_utils/compat/typing.py +6 -5
  186. ansible/module_utils/connection.py +10 -13
  187. ansible/module_utils/csharp/Ansible.Basic.cs +15 -12
  188. ansible/module_utils/csharp/Ansible.Become.cs +1 -0
  189. ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
  190. ansible/module_utils/csharp/Ansible._Async.cs +517 -0
  191. ansible/module_utils/datatag.py +49 -0
  192. ansible/module_utils/distro/__init__.py +2 -2
  193. ansible/module_utils/facts/ansible_collector.py +4 -5
  194. ansible/module_utils/facts/collector.py +13 -14
  195. ansible/module_utils/facts/compat.py +4 -4
  196. ansible/module_utils/facts/default_collectors.py +1 -1
  197. ansible/module_utils/facts/hardware/aix.py +34 -0
  198. ansible/module_utils/facts/hardware/base.py +2 -2
  199. ansible/module_utils/facts/hardware/darwin.py +1 -3
  200. ansible/module_utils/facts/hardware/freebsd.py +2 -2
  201. ansible/module_utils/facts/hardware/linux.py +5 -5
  202. ansible/module_utils/facts/namespace.py +1 -1
  203. ansible/module_utils/facts/network/base.py +1 -1
  204. ansible/module_utils/facts/network/fc_wwn.py +1 -2
  205. ansible/module_utils/facts/network/iscsi.py +1 -2
  206. ansible/module_utils/facts/network/nvme.py +1 -2
  207. ansible/module_utils/facts/other/facter.py +2 -3
  208. ansible/module_utils/facts/other/ohai.py +2 -3
  209. ansible/module_utils/facts/sysctl.py +4 -6
  210. ansible/module_utils/facts/system/apparmor.py +1 -2
  211. ansible/module_utils/facts/system/caps.py +3 -3
  212. ansible/module_utils/facts/system/chroot.py +1 -2
  213. ansible/module_utils/facts/system/cmdline.py +1 -2
  214. ansible/module_utils/facts/system/date_time.py +5 -3
  215. ansible/module_utils/facts/system/distribution.py +27 -13
  216. ansible/module_utils/facts/system/dns.py +1 -1
  217. ansible/module_utils/facts/system/env.py +1 -2
  218. ansible/module_utils/facts/system/fips.py +7 -20
  219. ansible/module_utils/facts/system/loadavg.py +1 -2
  220. ansible/module_utils/facts/system/local.py +2 -3
  221. ansible/module_utils/facts/system/lsb.py +1 -2
  222. ansible/module_utils/facts/system/pkg_mgr.py +1 -2
  223. ansible/module_utils/facts/system/platform.py +1 -2
  224. ansible/module_utils/facts/system/python.py +1 -2
  225. ansible/module_utils/facts/system/selinux.py +1 -1
  226. ansible/module_utils/facts/system/service_mgr.py +1 -2
  227. ansible/module_utils/facts/system/ssh_pub_keys.py +1 -1
  228. ansible/module_utils/facts/system/systemd.py +1 -1
  229. ansible/module_utils/facts/system/user.py +1 -2
  230. ansible/module_utils/facts/utils.py +3 -3
  231. ansible/module_utils/facts/virtual/base.py +1 -1
  232. ansible/module_utils/facts/virtual/linux.py +3 -3
  233. ansible/module_utils/facts/virtual/sunos.py +3 -15
  234. ansible/module_utils/facts/virtual/sysctl.py +3 -16
  235. ansible/module_utils/json_utils.py +2 -2
  236. ansible/module_utils/parsing/convert_bool.py +7 -1
  237. ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
  238. ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
  239. ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
  240. ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
  241. ansible/module_utils/service.py +21 -31
  242. ansible/module_utils/splitter.py +7 -7
  243. ansible/module_utils/testing.py +31 -0
  244. ansible/module_utils/urls.py +64 -35
  245. ansible/modules/add_host.py +4 -4
  246. ansible/modules/apt.py +69 -49
  247. ansible/modules/apt_key.py +19 -12
  248. ansible/modules/apt_repository.py +32 -51
  249. ansible/modules/assemble.py +16 -14
  250. ansible/modules/assert.py +4 -4
  251. ansible/modules/async_status.py +24 -24
  252. ansible/modules/async_wrapper.py +20 -25
  253. ansible/modules/blockinfile.py +6 -7
  254. ansible/modules/command.py +13 -20
  255. ansible/modules/copy.py +60 -147
  256. ansible/modules/cron.py +24 -21
  257. ansible/modules/deb822_repository.py +8 -9
  258. ansible/modules/debconf.py +5 -5
  259. ansible/modules/debug.py +4 -4
  260. ansible/modules/dnf.py +8 -8
  261. ansible/modules/dnf5.py +39 -13
  262. ansible/modules/dpkg_selections.py +4 -4
  263. ansible/modules/expect.py +13 -15
  264. ansible/modules/fail.py +4 -4
  265. ansible/modules/fetch.py +4 -4
  266. ansible/modules/file.py +184 -144
  267. ansible/modules/find.py +22 -20
  268. ansible/modules/gather_facts.py +3 -3
  269. ansible/modules/get_url.py +77 -54
  270. ansible/modules/getent.py +7 -9
  271. ansible/modules/git.py +38 -38
  272. ansible/modules/group.py +6 -6
  273. ansible/modules/group_by.py +4 -4
  274. ansible/modules/hostname.py +15 -32
  275. ansible/modules/import_playbook.py +6 -6
  276. ansible/modules/import_role.py +6 -6
  277. ansible/modules/import_tasks.py +6 -6
  278. ansible/modules/include_role.py +6 -6
  279. ansible/modules/include_tasks.py +6 -6
  280. ansible/modules/include_vars.py +6 -6
  281. ansible/modules/iptables.py +86 -73
  282. ansible/modules/known_hosts.py +22 -24
  283. ansible/modules/lineinfile.py +5 -5
  284. ansible/modules/meta.py +4 -4
  285. ansible/modules/mount_facts.py +2 -2
  286. ansible/modules/package.py +10 -4
  287. ansible/modules/package_facts.py +22 -10
  288. ansible/modules/pause.py +6 -6
  289. ansible/modules/ping.py +6 -6
  290. ansible/modules/pip.py +21 -26
  291. ansible/modules/raw.py +6 -6
  292. ansible/modules/reboot.py +6 -6
  293. ansible/modules/replace.py +10 -14
  294. ansible/modules/rpm_key.py +7 -8
  295. ansible/modules/script.py +4 -4
  296. ansible/modules/service.py +10 -17
  297. ansible/modules/service_facts.py +87 -10
  298. ansible/modules/set_fact.py +5 -5
  299. ansible/modules/set_stats.py +4 -4
  300. ansible/modules/setup.py +2 -2
  301. ansible/modules/shell.py +6 -6
  302. ansible/modules/slurp.py +16 -19
  303. ansible/modules/stat.py +15 -31
  304. ansible/modules/subversion.py +15 -15
  305. ansible/modules/systemd.py +7 -7
  306. ansible/modules/systemd_service.py +7 -7
  307. ansible/modules/sysvinit.py +9 -9
  308. ansible/modules/tempfile.py +5 -6
  309. ansible/modules/template.py +6 -6
  310. ansible/modules/unarchive.py +38 -17
  311. ansible/modules/uri.py +33 -26
  312. ansible/modules/user.py +45 -32
  313. ansible/modules/validate_argument_spec.py +10 -7
  314. ansible/modules/wait_for.py +70 -60
  315. ansible/modules/wait_for_connection.py +6 -6
  316. ansible/modules/yum_repository.py +10 -9
  317. ansible/parsing/ajson.py +17 -37
  318. ansible/parsing/dataloader.py +99 -54
  319. ansible/parsing/mod_args.py +62 -60
  320. ansible/parsing/plugin_docs.py +21 -86
  321. ansible/parsing/quoting.py +1 -1
  322. ansible/parsing/splitter.py +27 -12
  323. ansible/parsing/utils/addresses.py +24 -24
  324. ansible/parsing/utils/jsonify.py +5 -1
  325. ansible/parsing/utils/yaml.py +32 -61
  326. ansible/parsing/vault/__init__.py +327 -99
  327. ansible/parsing/yaml/__init__.py +0 -18
  328. ansible/parsing/yaml/dumper.py +6 -120
  329. ansible/parsing/yaml/loader.py +6 -39
  330. ansible/parsing/yaml/objects.py +43 -335
  331. ansible/playbook/__init__.py +1 -1
  332. ansible/playbook/attribute.py +8 -3
  333. ansible/playbook/base.py +187 -134
  334. ansible/playbook/block.py +26 -24
  335. ansible/playbook/collectionsearch.py +1 -15
  336. ansible/playbook/conditional.py +3 -77
  337. ansible/playbook/handler.py +8 -2
  338. ansible/playbook/helpers.py +41 -53
  339. ansible/playbook/included_file.py +32 -26
  340. ansible/playbook/loop_control.py +2 -2
  341. ansible/playbook/play.py +85 -44
  342. ansible/playbook/play_context.py +14 -17
  343. ansible/playbook/playbook_include.py +27 -62
  344. ansible/playbook/role/__init__.py +64 -49
  345. ansible/playbook/role/definition.py +15 -17
  346. ansible/playbook/role/include.py +2 -4
  347. ansible/playbook/role/metadata.py +10 -11
  348. ansible/playbook/role_include.py +3 -3
  349. ansible/playbook/taggable.py +28 -12
  350. ansible/playbook/task.py +192 -121
  351. ansible/playbook/task_include.py +5 -5
  352. ansible/plugins/__init__.py +58 -26
  353. ansible/plugins/action/__init__.py +188 -186
  354. ansible/plugins/action/add_host.py +2 -2
  355. ansible/plugins/action/assemble.py +11 -18
  356. ansible/plugins/action/assert.py +55 -67
  357. ansible/plugins/action/async_status.py +7 -2
  358. ansible/plugins/action/copy.py +14 -17
  359. ansible/plugins/action/debug.py +37 -31
  360. ansible/plugins/action/dnf.py +3 -4
  361. ansible/plugins/action/fail.py +1 -1
  362. ansible/plugins/action/fetch.py +7 -8
  363. ansible/plugins/action/gather_facts.py +13 -14
  364. ansible/plugins/action/group_by.py +1 -1
  365. ansible/plugins/action/include_vars.py +10 -11
  366. ansible/plugins/action/package.py +8 -14
  367. ansible/plugins/action/pause.py +2 -2
  368. ansible/plugins/action/script.py +27 -38
  369. ansible/plugins/action/service.py +9 -18
  370. ansible/plugins/action/set_fact.py +3 -12
  371. ansible/plugins/action/set_stats.py +3 -8
  372. ansible/plugins/action/template.py +47 -67
  373. ansible/plugins/action/unarchive.py +6 -16
  374. ansible/plugins/action/uri.py +9 -20
  375. ansible/plugins/action/validate_argument_spec.py +5 -5
  376. ansible/plugins/action/wait_for_connection.py +1 -1
  377. ansible/plugins/become/__init__.py +31 -8
  378. ansible/plugins/become/runas.py +71 -0
  379. ansible/plugins/become/su.py +13 -8
  380. ansible/plugins/become/sudo.py +19 -0
  381. ansible/plugins/cache/__init__.py +52 -63
  382. ansible/plugins/cache/base.py +8 -0
  383. ansible/plugins/cache/jsonfile.py +10 -16
  384. ansible/plugins/cache/memory.py +6 -12
  385. ansible/plugins/callback/__init__.py +294 -201
  386. ansible/plugins/callback/default.py +99 -95
  387. ansible/plugins/callback/junit.py +44 -43
  388. ansible/plugins/callback/minimal.py +28 -25
  389. ansible/plugins/callback/oneline.py +34 -21
  390. ansible/plugins/callback/tree.py +27 -16
  391. ansible/plugins/connection/__init__.py +47 -34
  392. ansible/plugins/connection/local.py +156 -60
  393. ansible/plugins/connection/paramiko_ssh.py +34 -24
  394. ansible/plugins/connection/psrp.py +76 -165
  395. ansible/plugins/connection/ssh.py +326 -86
  396. ansible/plugins/connection/winrm.py +62 -141
  397. ansible/plugins/doc_fragments/action_common_attributes.py +14 -14
  398. ansible/plugins/doc_fragments/action_core.py +6 -6
  399. ansible/plugins/doc_fragments/backup.py +2 -2
  400. ansible/plugins/doc_fragments/checksum_common.py +27 -0
  401. ansible/plugins/doc_fragments/constructed.py +8 -4
  402. ansible/plugins/doc_fragments/decrypt.py +2 -2
  403. ansible/plugins/doc_fragments/default_callback.py +2 -2
  404. ansible/plugins/doc_fragments/files.py +2 -2
  405. ansible/plugins/doc_fragments/inventory_cache.py +2 -2
  406. ansible/plugins/doc_fragments/result_format_callback.py +2 -2
  407. ansible/plugins/doc_fragments/return_common.py +2 -2
  408. ansible/plugins/doc_fragments/template_common.py +4 -4
  409. ansible/plugins/doc_fragments/url.py +17 -1
  410. ansible/plugins/doc_fragments/url_windows.py +2 -2
  411. ansible/plugins/doc_fragments/validate.py +2 -2
  412. ansible/plugins/doc_fragments/vars_plugin_staging.py +2 -2
  413. ansible/plugins/filter/__init__.py +6 -2
  414. ansible/plugins/filter/b64decode.yml +22 -0
  415. ansible/plugins/filter/b64encode.yml +22 -0
  416. ansible/plugins/filter/bool.yml +11 -4
  417. ansible/plugins/filter/core.py +245 -120
  418. ansible/plugins/filter/encryption.py +42 -34
  419. ansible/plugins/filter/flatten.yml +3 -2
  420. ansible/plugins/filter/human_to_bytes.yml +1 -1
  421. ansible/plugins/filter/mathstuff.py +30 -37
  422. ansible/plugins/filter/password_hash.yml +8 -0
  423. ansible/plugins/filter/pow.yml +1 -1
  424. ansible/plugins/filter/regex_search.yml +1 -4
  425. ansible/plugins/filter/root.yml +1 -1
  426. ansible/plugins/filter/split.yml +1 -1
  427. ansible/plugins/filter/strftime.yml +3 -3
  428. ansible/plugins/filter/to_nice_yaml.yml +0 -4
  429. ansible/plugins/filter/to_uuid.yml +1 -1
  430. ansible/plugins/filter/to_yaml.yml +0 -4
  431. ansible/plugins/filter/unvault.yml +1 -1
  432. ansible/plugins/filter/urls.py +1 -1
  433. ansible/plugins/filter/urlsplit.py +8 -9
  434. ansible/plugins/filter/vault.yml +14 -9
  435. ansible/plugins/filter/win_basename.yml +6 -1
  436. ansible/plugins/filter/win_dirname.yml +5 -0
  437. ansible/plugins/inventory/__init__.py +107 -86
  438. ansible/plugins/inventory/advanced_host_list.py +7 -5
  439. ansible/plugins/inventory/auto.py +11 -4
  440. ansible/plugins/inventory/constructed.py +21 -24
  441. ansible/plugins/inventory/generator.py +16 -11
  442. ansible/plugins/inventory/host_list.py +7 -5
  443. ansible/plugins/inventory/ini.py +78 -44
  444. ansible/plugins/inventory/script.py +190 -120
  445. ansible/plugins/inventory/toml.py +16 -126
  446. ansible/plugins/inventory/yaml.py +10 -8
  447. ansible/plugins/list.py +72 -19
  448. ansible/plugins/loader.py +383 -198
  449. ansible/plugins/lookup/__init__.py +21 -4
  450. ansible/plugins/lookup/config.py +21 -35
  451. ansible/plugins/lookup/csvfile.py +19 -73
  452. ansible/plugins/lookup/dict.py +1 -6
  453. ansible/plugins/lookup/env.py +12 -9
  454. ansible/plugins/lookup/file.py +5 -8
  455. ansible/plugins/lookup/first_found.py +87 -55
  456. ansible/plugins/lookup/indexed_items.py +1 -10
  457. ansible/plugins/lookup/ini.py +14 -13
  458. ansible/plugins/lookup/items.py +1 -1
  459. ansible/plugins/lookup/lines.py +8 -1
  460. ansible/plugins/lookup/list.py +1 -1
  461. ansible/plugins/lookup/nested.py +2 -18
  462. ansible/plugins/lookup/password.py +5 -5
  463. ansible/plugins/lookup/pipe.py +5 -7
  464. ansible/plugins/lookup/sequence.py +18 -8
  465. ansible/plugins/lookup/subelements.py +1 -4
  466. ansible/plugins/lookup/template.py +47 -36
  467. ansible/plugins/lookup/together.py +0 -12
  468. ansible/plugins/lookup/unvault.py +1 -5
  469. ansible/plugins/lookup/url.py +4 -10
  470. ansible/plugins/lookup/vars.py +16 -24
  471. ansible/plugins/shell/__init__.py +58 -4
  472. ansible/plugins/shell/cmd.py +2 -2
  473. ansible/plugins/shell/powershell.py +106 -31
  474. ansible/plugins/shell/sh.py +13 -7
  475. ansible/plugins/strategy/__init__.py +168 -193
  476. ansible/plugins/strategy/debug.py +2 -2
  477. ansible/plugins/strategy/free.py +16 -31
  478. ansible/plugins/strategy/host_pinned.py +2 -2
  479. ansible/plugins/strategy/linear.py +41 -41
  480. ansible/plugins/terminal/__init__.py +4 -4
  481. ansible/plugins/test/__init__.py +7 -2
  482. ansible/plugins/test/core.py +75 -35
  483. ansible/plugins/test/files.py +1 -1
  484. ansible/plugins/test/finished.yml +1 -1
  485. ansible/plugins/test/mathstuff.py +3 -3
  486. ansible/plugins/test/uri.py +5 -8
  487. ansible/plugins/vars/host_group_vars.py +7 -14
  488. ansible/release.py +2 -2
  489. ansible/template/__init__.py +353 -943
  490. ansible/utils/__init__.py +0 -18
  491. ansible/utils/collection_loader/__init__.py +54 -5
  492. ansible/utils/collection_loader/_collection_config.py +5 -6
  493. ansible/utils/collection_loader/_collection_finder.py +82 -96
  494. ansible/utils/collection_loader/_collection_meta.py +15 -8
  495. ansible/utils/display.py +485 -73
  496. ansible/utils/encrypt.py +27 -19
  497. ansible/utils/fqcn.py +2 -2
  498. ansible/utils/galaxy.py +2 -2
  499. ansible/utils/hashing.py +8 -10
  500. ansible/utils/helpers.py +2 -2
  501. ansible/utils/listify.py +10 -8
  502. ansible/utils/lock.py +2 -2
  503. ansible/utils/path.py +10 -12
  504. ansible/utils/plugin_docs.py +16 -14
  505. ansible/utils/py3compat.py +2 -7
  506. ansible/utils/sentinel.py +4 -62
  507. ansible/utils/singleton.py +2 -0
  508. ansible/utils/ssh_functions.py +6 -2
  509. ansible/utils/unsafe_proxy.py +23 -332
  510. ansible/utils/vars.py +55 -8
  511. ansible/utils/version.py +2 -2
  512. ansible/vars/clean.py +5 -5
  513. ansible/vars/hostvars.py +60 -90
  514. ansible/vars/manager.py +220 -285
  515. ansible/vars/plugins.py +4 -4
  516. ansible/vars/reserved.py +13 -12
  517. {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/METADATA +4 -3
  518. ansible_core-2.19.0.dist-info/RECORD +1097 -0
  519. ansible_core-2.19.0.dist-info/licenses/licenses/BSD-3-Clause.txt +28 -0
  520. ansible_test/_data/completion/docker.txt +7 -7
  521. ansible_test/_data/completion/remote.txt +6 -6
  522. ansible_test/_data/completion/windows.txt +1 -0
  523. ansible_test/_data/requirements/ansible.txt +2 -2
  524. ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
  525. ansible_test/_data/requirements/sanity.changelog.txt +2 -2
  526. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  527. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  528. ansible_test/_data/requirements/sanity.pylint.txt +5 -5
  529. ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
  530. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  531. ansible_test/_data/requirements/units.txt +1 -0
  532. ansible_test/_internal/__init__.py +6 -0
  533. ansible_test/_internal/ansible_util.py +3 -1
  534. ansible_test/_internal/become.py +1 -0
  535. ansible_test/_internal/bootstrap.py +1 -0
  536. ansible_test/_internal/cache.py +1 -0
  537. ansible_test/_internal/cgroup.py +1 -0
  538. ansible_test/_internal/ci/__init__.py +1 -0
  539. ansible_test/_internal/ci/azp.py +1 -0
  540. ansible_test/_internal/ci/local.py +1 -0
  541. ansible_test/_internal/classification/__init__.py +1 -0
  542. ansible_test/_internal/classification/common.py +1 -0
  543. ansible_test/_internal/classification/csharp.py +1 -0
  544. ansible_test/_internal/classification/powershell.py +1 -0
  545. ansible_test/_internal/classification/python.py +1 -0
  546. ansible_test/_internal/cli/__init__.py +1 -0
  547. ansible_test/_internal/cli/actions.py +1 -0
  548. ansible_test/_internal/cli/argparsing/__init__.py +1 -0
  549. ansible_test/_internal/cli/argparsing/actions.py +1 -0
  550. ansible_test/_internal/cli/argparsing/argcompletion.py +1 -0
  551. ansible_test/_internal/cli/argparsing/parsers.py +1 -0
  552. ansible_test/_internal/cli/commands/__init__.py +11 -5
  553. ansible_test/_internal/cli/commands/coverage/__init__.py +1 -0
  554. ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +1 -0
  555. ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +1 -0
  556. ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +1 -0
  557. ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +1 -0
  558. ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +1 -0
  559. ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +1 -0
  560. ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +1 -0
  561. ansible_test/_internal/cli/commands/coverage/combine.py +1 -0
  562. ansible_test/_internal/cli/commands/coverage/erase.py +1 -0
  563. ansible_test/_internal/cli/commands/coverage/html.py +1 -0
  564. ansible_test/_internal/cli/commands/coverage/report.py +1 -0
  565. ansible_test/_internal/cli/commands/coverage/xml.py +1 -0
  566. ansible_test/_internal/cli/commands/env.py +1 -0
  567. ansible_test/_internal/cli/commands/integration/__init__.py +1 -0
  568. ansible_test/_internal/cli/commands/integration/network.py +1 -0
  569. ansible_test/_internal/cli/commands/integration/posix.py +1 -0
  570. ansible_test/_internal/cli/commands/integration/windows.py +1 -0
  571. ansible_test/_internal/cli/commands/sanity.py +9 -0
  572. ansible_test/_internal/cli/commands/shell.py +1 -0
  573. ansible_test/_internal/cli/commands/units.py +1 -0
  574. ansible_test/_internal/cli/compat.py +1 -0
  575. ansible_test/_internal/cli/completers.py +1 -0
  576. ansible_test/_internal/cli/converters.py +1 -0
  577. ansible_test/_internal/cli/environments.py +52 -5
  578. ansible_test/_internal/cli/epilog.py +1 -0
  579. ansible_test/_internal/cli/parsers/__init__.py +1 -0
  580. ansible_test/_internal/cli/parsers/base_argument_parsers.py +1 -0
  581. ansible_test/_internal/cli/parsers/helpers.py +1 -0
  582. ansible_test/_internal/cli/parsers/host_config_parsers.py +1 -0
  583. ansible_test/_internal/cli/parsers/key_value_parsers.py +1 -0
  584. ansible_test/_internal/cli/parsers/value_parsers.py +1 -0
  585. ansible_test/_internal/commands/__init__.py +1 -0
  586. ansible_test/_internal/commands/coverage/__init__.py +3 -2
  587. ansible_test/_internal/commands/coverage/analyze/__init__.py +1 -0
  588. ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +1 -0
  589. ansible_test/_internal/commands/coverage/analyze/targets/combine.py +1 -0
  590. ansible_test/_internal/commands/coverage/analyze/targets/expand.py +1 -0
  591. ansible_test/_internal/commands/coverage/analyze/targets/filter.py +1 -0
  592. ansible_test/_internal/commands/coverage/analyze/targets/generate.py +1 -0
  593. ansible_test/_internal/commands/coverage/analyze/targets/missing.py +1 -0
  594. ansible_test/_internal/commands/coverage/combine.py +2 -1
  595. ansible_test/_internal/commands/coverage/erase.py +1 -0
  596. ansible_test/_internal/commands/coverage/html.py +1 -0
  597. ansible_test/_internal/commands/coverage/report.py +1 -0
  598. ansible_test/_internal/commands/coverage/xml.py +1 -0
  599. ansible_test/_internal/commands/env/__init__.py +2 -0
  600. ansible_test/_internal/commands/integration/__init__.py +22 -5
  601. ansible_test/_internal/commands/integration/cloud/__init__.py +1 -0
  602. ansible_test/_internal/commands/integration/cloud/acme.py +2 -1
  603. ansible_test/_internal/commands/integration/cloud/aws.py +1 -0
  604. ansible_test/_internal/commands/integration/cloud/azure.py +1 -0
  605. ansible_test/_internal/commands/integration/cloud/cs.py +1 -0
  606. ansible_test/_internal/commands/integration/cloud/digitalocean.py +1 -0
  607. ansible_test/_internal/commands/integration/cloud/galaxy.py +3 -2
  608. ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -0
  609. ansible_test/_internal/commands/integration/cloud/httptester.py +3 -2
  610. ansible_test/_internal/commands/integration/cloud/nios.py +2 -1
  611. ansible_test/_internal/commands/integration/cloud/opennebula.py +1 -0
  612. ansible_test/_internal/commands/integration/cloud/openshift.py +1 -0
  613. ansible_test/_internal/commands/integration/cloud/scaleway.py +1 -0
  614. ansible_test/_internal/commands/integration/cloud/vcenter.py +1 -0
  615. ansible_test/_internal/commands/integration/cloud/vultr.py +1 -0
  616. ansible_test/_internal/commands/integration/coverage.py +8 -2
  617. ansible_test/_internal/commands/integration/filters.py +1 -0
  618. ansible_test/_internal/commands/integration/network.py +1 -0
  619. ansible_test/_internal/commands/integration/posix.py +1 -0
  620. ansible_test/_internal/commands/integration/windows.py +1 -0
  621. ansible_test/_internal/commands/sanity/__init__.py +19 -2
  622. ansible_test/_internal/commands/sanity/ansible_doc.py +1 -0
  623. ansible_test/_internal/commands/sanity/bin_symlinks.py +1 -0
  624. ansible_test/_internal/commands/sanity/compile.py +1 -0
  625. ansible_test/_internal/commands/sanity/ignores.py +1 -0
  626. ansible_test/_internal/commands/sanity/import.py +1 -0
  627. ansible_test/_internal/commands/sanity/integration_aliases.py +12 -0
  628. ansible_test/_internal/commands/sanity/pep8.py +1 -0
  629. ansible_test/_internal/commands/sanity/pslint.py +1 -0
  630. ansible_test/_internal/commands/sanity/pylint.py +25 -26
  631. ansible_test/_internal/commands/sanity/shellcheck.py +1 -0
  632. ansible_test/_internal/commands/sanity/validate_modules.py +1 -0
  633. ansible_test/_internal/commands/sanity/yamllint.py +1 -0
  634. ansible_test/_internal/commands/shell/__init__.py +44 -4
  635. ansible_test/_internal/commands/units/__init__.py +5 -1
  636. ansible_test/_internal/compat/__init__.py +1 -0
  637. ansible_test/_internal/compat/packaging.py +1 -0
  638. ansible_test/_internal/compat/yaml.py +1 -0
  639. ansible_test/_internal/completion.py +1 -0
  640. ansible_test/_internal/config.py +23 -13
  641. ansible_test/_internal/connections.py +1 -0
  642. ansible_test/_internal/constants.py +1 -0
  643. ansible_test/_internal/containers.py +1 -0
  644. ansible_test/_internal/content_config.py +1 -0
  645. ansible_test/_internal/core_ci.py +1 -0
  646. ansible_test/_internal/coverage_util.py +11 -10
  647. ansible_test/_internal/data.py +1 -0
  648. ansible_test/_internal/debugging.py +166 -0
  649. ansible_test/_internal/delegation.py +22 -13
  650. ansible_test/_internal/dev/__init__.py +1 -0
  651. ansible_test/_internal/dev/container_probe.py +1 -0
  652. ansible_test/_internal/diff.py +3 -2
  653. ansible_test/_internal/docker_util.py +2 -1
  654. ansible_test/_internal/encoding.py +1 -0
  655. ansible_test/_internal/executor.py +1 -0
  656. ansible_test/_internal/git.py +1 -0
  657. ansible_test/_internal/host_configs.py +1 -0
  658. ansible_test/_internal/host_profiles.py +260 -16
  659. ansible_test/_internal/http.py +1 -0
  660. ansible_test/_internal/init.py +1 -0
  661. ansible_test/_internal/inventory.py +39 -3
  662. ansible_test/_internal/io.py +1 -0
  663. ansible_test/_internal/metadata.py +95 -4
  664. ansible_test/_internal/payload.py +1 -0
  665. ansible_test/_internal/processes.py +80 -0
  666. ansible_test/_internal/provider/__init__.py +1 -0
  667. ansible_test/_internal/provider/layout/__init__.py +1 -0
  668. ansible_test/_internal/provider/layout/ansible.py +1 -0
  669. ansible_test/_internal/provider/layout/collection.py +1 -0
  670. ansible_test/_internal/provider/layout/unsupported.py +1 -0
  671. ansible_test/_internal/provider/source/__init__.py +1 -0
  672. ansible_test/_internal/provider/source/git.py +1 -0
  673. ansible_test/_internal/provider/source/installed.py +1 -0
  674. ansible_test/_internal/provider/source/unsupported.py +1 -0
  675. ansible_test/_internal/provider/source/unversioned.py +1 -0
  676. ansible_test/_internal/provisioning.py +11 -4
  677. ansible_test/_internal/pypi_proxy.py +6 -5
  678. ansible_test/_internal/python_requirements.py +28 -0
  679. ansible_test/_internal/ssh.py +2 -5
  680. ansible_test/_internal/target.py +9 -0
  681. ansible_test/_internal/test.py +3 -2
  682. ansible_test/_internal/thread.py +3 -1
  683. ansible_test/_internal/timeout.py +2 -1
  684. ansible_test/_internal/util.py +41 -12
  685. ansible_test/_internal/util_common.py +18 -5
  686. ansible_test/_internal/venv.py +1 -0
  687. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +1 -0
  688. ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py +1 -0
  689. ansible_test/_util/controller/sanity/code-smell/changelog.py +1 -0
  690. ansible_test/_util/controller/sanity/code-smell/empty-init.py +1 -0
  691. ansible_test/_util/controller/sanity/code-smell/line-endings.py +1 -0
  692. ansible_test/_util/controller/sanity/code-smell/no-assert.py +1 -0
  693. ansible_test/_util/controller/sanity/code-smell/no-get-exception.py +1 -0
  694. ansible_test/_util/controller/sanity/code-smell/no-illegal-filenames.py +1 -0
  695. ansible_test/_util/controller/sanity/code-smell/no-smart-quotes.py +1 -0
  696. ansible_test/_util/controller/sanity/code-smell/replace-urlopen.py +1 -0
  697. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +28 -1
  698. ansible_test/_util/controller/sanity/code-smell/shebang.py +1 -0
  699. ansible_test/_util/controller/sanity/code-smell/symlinks.py +1 -0
  700. ansible_test/_util/controller/sanity/code-smell/use-argspec-type-path.py +1 -0
  701. ansible_test/_util/controller/sanity/code-smell/use-compat-six.py +1 -0
  702. ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py +2 -1
  703. ansible_test/_util/controller/sanity/pep8/current-ignore.txt +4 -0
  704. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +8 -5
  705. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +8 -5
  706. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +8 -5
  707. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +4 -5
  708. ansible_test/_util/controller/sanity/pylint/config/default.cfg +8 -7
  709. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +541 -0
  710. ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
  711. ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +1 -0
  712. ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +1 -8
  713. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +1 -8
  714. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +55 -28
  715. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +12 -5
  716. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +13 -2
  717. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -0
  718. ansible_test/_util/controller/sanity/yamllint/yamllinter.py +35 -17
  719. ansible_test/_util/controller/tools/collection_detail.py +1 -0
  720. ansible_test/_util/controller/tools/yaml_to_json.py +2 -1
  721. ansible_test/_util/target/injector/python.py +8 -0
  722. ansible_test/_util/target/pytest/plugins/ansible_forked.py +6 -1
  723. ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +2 -1
  724. ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -0
  725. ansible_test/_util/target/sanity/compile/compile.py +1 -0
  726. ansible_test/_util/target/sanity/import/importer.py +15 -16
  727. ansible_test/_util/target/setup/bootstrap.sh +9 -20
  728. ansible_test/_util/target/setup/probe_cgroups.py +1 -0
  729. ansible_test/_util/target/setup/quiet_pip.py +1 -0
  730. ansible_test/_util/target/setup/requirements.py +38 -36
  731. ansible_test/_util/target/tools/virtualenvcheck.py +2 -1
  732. ansible_test/_util/target/tools/yamlcheck.py +2 -1
  733. ansible/compat/selectors.py +0 -32
  734. ansible/errors/yaml_strings.py +0 -138
  735. ansible/executor/action_write_locks.py +0 -44
  736. ansible/executor/discovery/python_target.py +0 -47
  737. ansible/executor/powershell/module_powershell_wrapper.ps1 +0 -86
  738. ansible/executor/powershell/module_script_wrapper.ps1 +0 -22
  739. ansible/module_utils/compat/importlib.py +0 -26
  740. ansible/module_utils/compat/selectors.py +0 -32
  741. ansible/module_utils/pycompat24.py +0 -73
  742. ansible/parsing/yaml/constructor.py +0 -178
  743. ansible/template/native_helpers.py +0 -251
  744. ansible/template/template.py +0 -43
  745. ansible/template/vars.py +0 -77
  746. ansible/utils/native_jinja.py +0 -11
  747. ansible/vars/fact_cache.py +0 -71
  748. ansible_core-2.18.7rc1.dist-info/RECORD +0 -992
  749. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -411
  750. {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/WHEEL +0 -0
  751. {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/entry_points.txt +0 -0
  752. {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/COPYING +0 -0
  753. {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  754. {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  755. {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  756. {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  757. {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/top_level.txt +0 -0
ansible/plugins/loader.py CHANGED
@@ -6,35 +6,41 @@
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
+ import functools
9
10
  import glob
10
11
  import os
11
12
  import os.path
12
13
  import pkgutil
13
14
  import sys
15
+ import types
14
16
  import warnings
17
+ import typing as t
18
+
19
+ import yaml
15
20
 
16
21
  from collections import defaultdict, namedtuple
17
22
  from importlib import import_module
18
- from traceback import format_exc
19
-
20
- import ansible.module_utils.compat.typing as t
21
-
22
- from .filter import AnsibleJinja2Filter
23
- from .test import AnsibleJinja2Test
23
+ from yaml.parser import ParserError
24
24
 
25
25
  from ansible import __version__ as ansible_version
26
- from ansible import constants as C
26
+ from ansible import _internal, constants as C
27
27
  from ansible.errors import AnsibleError, AnsiblePluginCircularRedirect, AnsiblePluginRemovedError, AnsibleCollectionUnsupportedVersionError
28
28
  from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
29
+ from ansible.module_utils.datatag import deprecator_from_collection_name
29
30
  from ansible.module_utils.six import string_types
30
- from ansible.parsing.utils.yaml import from_yaml
31
31
  from ansible.parsing.yaml.loader import AnsibleLoader
32
- from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE
32
+ from ansible._internal._yaml._loader import AnsibleInstrumentedLoader
33
+ from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE, AnsibleJinja2Plugin
33
34
  from ansible.utils.collection_loader import AnsibleCollectionConfig, AnsibleCollectionRef
34
35
  from ansible.utils.collection_loader._collection_finder import _AnsibleCollectionFinder, _get_collection_metadata
35
36
  from ansible.utils.display import Display
36
37
  from ansible.utils.plugin_docs import add_fragments
37
- from ansible.utils.unsafe_proxy import _is_unsafe
38
+ from ansible._internal._datatag import _tags
39
+
40
+ from . import _AnsiblePluginInfoMixin
41
+ from .filter import AnsibleJinja2Filter
42
+ from .test import AnsibleJinja2Test
43
+ from .._internal._plugins import _cache
38
44
 
39
45
  # TODO: take the packaging dep, or vendor SpecifierSet?
40
46
 
@@ -47,18 +53,30 @@ except ImportError:
47
53
 
48
54
  import importlib.util
49
55
 
56
+ if t.TYPE_CHECKING:
57
+ from ansible.plugins.cache import BaseCacheModule
58
+
50
59
  _PLUGIN_FILTERS = defaultdict(frozenset) # type: t.DefaultDict[str, frozenset]
51
60
  display = Display()
52
61
 
53
62
  get_with_context_result = namedtuple('get_with_context_result', ['object', 'plugin_load_context'])
54
63
 
55
64
 
56
- def get_all_plugin_loaders():
65
+ @functools.cache
66
+ def get_all_plugin_loaders() -> list[tuple[str, 'PluginLoader']]:
57
67
  return [(name, obj) for (name, obj) in globals().items() if isinstance(obj, PluginLoader)]
58
68
 
59
69
 
70
+ @functools.cache
71
+ def get_plugin_loader_namespace() -> types.SimpleNamespace:
72
+ ns = types.SimpleNamespace()
73
+ for name, obj in get_all_plugin_loaders():
74
+ setattr(ns, name, obj)
75
+ return ns
76
+
77
+
60
78
  def add_all_plugin_dirs(path):
61
- ''' add any existing plugin dirs in the path provided '''
79
+ """ add any existing plugin dirs in the path provided """
62
80
  b_path = os.path.expanduser(to_bytes(path, errors='surrogate_or_strict'))
63
81
  if os.path.isdir(b_path):
64
82
  for name, obj in get_all_plugin_loaders():
@@ -117,29 +135,44 @@ class PluginPathContext(object):
117
135
 
118
136
 
119
137
  class PluginLoadContext(object):
120
- def __init__(self):
121
- self.original_name = None
122
- self.redirect_list = []
123
- self.error_list = []
124
- self.import_error_list = []
125
- self.load_attempts = []
126
- self.pending_redirect = None
127
- self.exit_reason = None
128
- self.plugin_resolved_path = None
129
- self.plugin_resolved_name = None
130
- self.plugin_resolved_collection = None # empty string for resolved plugins from user-supplied paths
131
- self.deprecated = False
132
- self.removal_date = None
133
- self.removal_version = None
134
- self.deprecation_warnings = []
135
- self.resolved = False
136
- self._resolved_fqcn = None
137
- self.action_plugin = None
138
+ def __init__(self, plugin_type: str, legacy_package_name: str) -> None:
139
+ self.original_name: str | None = None
140
+ self.redirect_list: list[str] = []
141
+ self.raw_error_list: list[Exception] = []
142
+ """All exception instances encountered during the plugin load."""
143
+ self.error_list: list[str] = []
144
+ """Stringified exceptions, excluding import errors."""
145
+ self.import_error_list: list[Exception] = []
146
+ """All ImportError exception instances encountered during the plugin load."""
147
+ self.load_attempts: list[str] = []
148
+ self.pending_redirect: str | None = None
149
+ self.exit_reason: str | None = None
150
+ self.plugin_resolved_path: str | None = None
151
+ self.plugin_resolved_name: str | None = None
152
+ """For collection plugins, the resolved Python module FQ __name__; for non-collections, the short name."""
153
+ self.plugin_resolved_collection: str | None = None # empty string for resolved plugins from user-supplied paths
154
+ """For collection plugins, the resolved collection {ns}.{col}; empty string for non-collection plugins."""
155
+ self.deprecated: bool = False
156
+ self.removal_date: str | None = None
157
+ self.removal_version: str | None = None
158
+ self.deprecation_warnings: list[str] = []
159
+ self.resolved: bool = False
160
+ self._resolved_fqcn: str | None = None
161
+ self.action_plugin: str | None = None
162
+ self._plugin_type: str = plugin_type
163
+ """The type of the plugin."""
164
+ self._legacy_package_name = legacy_package_name
165
+ """The legacy sys.modules package name from the plugin loader instance; stored to prevent potentially incorrect manual computation."""
166
+ self._python_module_name: str | None = None
167
+ """
168
+ The fully qualified Python module name for the plugin (accessible via `sys.modules`).
169
+ For non-collection non-core plugins, this may include a non-existent synthetic package element with a hash of the file path to avoid collisions.
170
+ """
138
171
 
139
172
  @property
140
- def resolved_fqcn(self):
173
+ def resolved_fqcn(self) -> str | None:
141
174
  if not self.resolved:
142
- return
175
+ return None
143
176
 
144
177
  if not self._resolved_fqcn:
145
178
  final_plugin = self.redirect_list[-1]
@@ -151,7 +184,7 @@ class PluginLoadContext(object):
151
184
 
152
185
  return self._resolved_fqcn
153
186
 
154
- def record_deprecation(self, name, deprecation, collection_name):
187
+ def record_deprecation(self, name: str, deprecation: dict[str, t.Any] | None, collection_name: str) -> t.Self:
155
188
  if not deprecation:
156
189
  return self
157
190
 
@@ -165,7 +198,12 @@ class PluginLoadContext(object):
165
198
  removal_version = None
166
199
  warning_text = '{0} has been deprecated.{1}{2}'.format(name, ' ' if warning_text else '', warning_text)
167
200
 
168
- display.deprecated(warning_text, date=removal_date, version=removal_version, collection_name=collection_name)
201
+ display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
202
+ msg=warning_text,
203
+ date=removal_date,
204
+ version=removal_version,
205
+ deprecator=deprecator_from_collection_name(collection_name),
206
+ )
169
207
 
170
208
  self.deprecated = True
171
209
  if removal_date:
@@ -175,38 +213,97 @@ class PluginLoadContext(object):
175
213
  self.deprecation_warnings.append(warning_text)
176
214
  return self
177
215
 
178
- def resolve(self, resolved_name, resolved_path, resolved_collection, exit_reason, action_plugin):
216
+ def resolve(self, resolved_name: str, resolved_path: str, resolved_collection: str, exit_reason: str, action_plugin: str) -> t.Self:
217
+ """Record a resolved collection plugin."""
179
218
  self.pending_redirect = None
180
219
  self.plugin_resolved_name = resolved_name
181
220
  self.plugin_resolved_path = resolved_path
182
221
  self.plugin_resolved_collection = resolved_collection
183
222
  self.exit_reason = exit_reason
223
+ self._python_module_name = resolved_name
184
224
  self.resolved = True
185
225
  self.action_plugin = action_plugin
226
+
227
+ return self
228
+
229
+ def resolve_legacy(self, name: str, pull_cache: dict[str, PluginPathContext]) -> t.Self:
230
+ """Record a resolved legacy plugin."""
231
+ plugin_path_context = pull_cache[name]
232
+
233
+ self.plugin_resolved_name = name
234
+ self.plugin_resolved_path = plugin_path_context.path
235
+ self.plugin_resolved_collection = 'ansible.builtin' if plugin_path_context.internal else ''
236
+ self._resolved_fqcn = 'ansible.builtin.' + name if plugin_path_context.internal else name
237
+ self._python_module_name = self._make_legacy_python_module_name()
238
+ self.resolved = True
239
+
240
+ return self
241
+
242
+ def resolve_legacy_jinja_plugin(self, name: str, known_plugin: AnsibleJinja2Plugin) -> t.Self:
243
+ """Record a resolved legacy Jinja plugin."""
244
+ internal = known_plugin.ansible_name.startswith('ansible.builtin.')
245
+
246
+ self.plugin_resolved_name = name
247
+ self.plugin_resolved_path = known_plugin._original_path
248
+ self.plugin_resolved_collection = 'ansible.builtin' if internal else ''
249
+ self._resolved_fqcn = known_plugin.ansible_name
250
+ self._python_module_name = self._make_legacy_python_module_name()
251
+ self.resolved = True
252
+
186
253
  return self
187
254
 
188
- def redirect(self, redirect_name):
255
+ def redirect(self, redirect_name: str) -> t.Self:
189
256
  self.pending_redirect = redirect_name
190
257
  self.exit_reason = 'pending redirect resolution from {0} to {1}'.format(self.original_name, redirect_name)
191
258
  self.resolved = False
259
+
192
260
  return self
193
261
 
194
- def nope(self, exit_reason):
262
+ def nope(self, exit_reason: str) -> t.Self:
195
263
  self.pending_redirect = None
196
264
  self.exit_reason = exit_reason
197
265
  self.resolved = False
266
+
198
267
  return self
199
268
 
269
+ def _make_legacy_python_module_name(self) -> str:
270
+ """
271
+ Generate a fully-qualified Python module name for a legacy/builtin plugin.
272
+
273
+ The same package namespace is shared for builtin and legacy plugins.
274
+ Explicit requests for builtins via `ansible.builtin` are handled elsewhere with an aliased collection package resolved by the collection loader.
275
+ Only unqualified and `ansible.legacy`-qualified requests land here; whichever plugin is visible at the time will end up in sys.modules.
276
+ Filter and test plugin host modules receive special name suffixes to avoid collisions unrelated to the actual plugin name.
277
+ """
278
+ name = os.path.splitext(self.plugin_resolved_path)[0]
279
+ basename = os.path.basename(name)
280
+
281
+ if self._plugin_type in ('filter', 'test'):
282
+ # Unlike other plugin types, filter and test plugin names are independent of the file where they are defined.
283
+ # As a result, the Python module name must be derived from the full path of the plugin.
284
+ # This prevents accidental shadowing of unrelated plugins of the same type.
285
+ basename += f'_{abs(hash(self.plugin_resolved_path))}'
286
+
287
+ return f'{self._legacy_package_name}.{basename}'
288
+
200
289
 
201
290
  class PluginLoader:
202
- '''
291
+ """
203
292
  PluginLoader loads plugins from the configured plugin directories.
204
293
 
205
294
  It searches for plugins by iterating through the combined list of play basedirs, configured
206
295
  paths, and the python path. The first match is used.
207
- '''
296
+ """
208
297
 
209
- def __init__(self, class_name, package, config, subdir, aliases=None, required_base_class=None):
298
+ def __init__(
299
+ self,
300
+ class_name: str,
301
+ package: str,
302
+ config: str | list[str],
303
+ subdir: str,
304
+ aliases: dict[str, str] | None = None,
305
+ required_base_class: str | None = None,
306
+ ) -> None:
210
307
  aliases = {} if aliases is None else aliases
211
308
 
212
309
  self.class_name = class_name
@@ -232,15 +329,15 @@ class PluginLoader:
232
329
  PLUGIN_PATH_CACHE[class_name] = defaultdict(dict)
233
330
 
234
331
  # hold dirs added at runtime outside of config
235
- self._extra_dirs = []
332
+ self._extra_dirs: list[str] = []
236
333
 
237
334
  # caches
238
335
  self._module_cache = MODULE_CACHE[class_name]
239
336
  self._paths = PATH_CACHE[class_name]
240
337
  self._plugin_path_cache = PLUGIN_PATH_CACHE[class_name]
241
- self._plugin_instance_cache = {} if self.subdir == 'vars_plugins' else None
338
+ self._plugin_instance_cache: dict[str, tuple[object, PluginLoadContext]] | None = {} if self.subdir == 'vars_plugins' else None
242
339
 
243
- self._searched_paths = set()
340
+ self._searched_paths: set[str] = set()
244
341
 
245
342
  @property
246
343
  def type(self):
@@ -267,9 +364,9 @@ class PluginLoader:
267
364
  self._searched_paths = set()
268
365
 
269
366
  def __setstate__(self, data):
270
- '''
367
+ """
271
368
  Deserializer.
272
- '''
369
+ """
273
370
 
274
371
  class_name = data.get('class_name')
275
372
  package = data.get('package')
@@ -286,9 +383,9 @@ class PluginLoader:
286
383
  self._searched_paths = data.get('_searched_paths', set())
287
384
 
288
385
  def __getstate__(self):
289
- '''
386
+ """
290
387
  Serializer.
291
- '''
388
+ """
292
389
 
293
390
  return dict(
294
391
  class_name=self.class_name,
@@ -304,7 +401,7 @@ class PluginLoader:
304
401
  )
305
402
 
306
403
  def format_paths(self, paths):
307
- ''' Returns a string suitable for printing of the search path '''
404
+ """ Returns a string suitable for printing of the search path """
308
405
 
309
406
  # Uses a list to get the order right
310
407
  ret = []
@@ -326,7 +423,7 @@ class PluginLoader:
326
423
  return results
327
424
 
328
425
  def _get_package_paths(self, subdirs=True):
329
- ''' Gets the path of a Python package '''
426
+ """ Gets the path of a Python package """
330
427
 
331
428
  if not self.package:
332
429
  return []
@@ -341,7 +438,7 @@ class PluginLoader:
341
438
  return [self.package_path]
342
439
 
343
440
  def _get_paths_with_context(self, subdirs=True):
344
- ''' Return a list of PluginPathContext objects to search for plugins in '''
441
+ """ Return a list of PluginPathContext objects to search for plugins in """
345
442
 
346
443
  # FIXME: This is potentially buggy if subdirs is sometimes True and sometimes False.
347
444
  # In current usage, everything calls this with subdirs=True except for module_utils_loader and ansible-doc
@@ -394,13 +491,13 @@ class PluginLoader:
394
491
  return ret
395
492
 
396
493
  def _get_paths(self, subdirs=True):
397
- ''' Return a list of paths to search for plugins in '''
494
+ """ Return a list of paths to search for plugins in """
398
495
 
399
496
  paths_with_context = self._get_paths_with_context(subdirs=subdirs)
400
497
  return [path_with_context.path for path_with_context in paths_with_context]
401
498
 
402
499
  def _load_config_defs(self, name, module, path):
403
- ''' Reads plugin docs to find configuration setting definitions, to push to config manager for later use '''
500
+ """ Reads plugin docs to find configuration setting definitions, to push to config manager for later use """
404
501
 
405
502
  # plugins w/o class name don't support config
406
503
  if self.class_name:
@@ -408,12 +505,16 @@ class PluginLoader:
408
505
 
409
506
  # if type name != 'module_doc_fragment':
410
507
  if type_name in C.CONFIGURABLE_PLUGINS and not C.config.has_configuration_definition(type_name, name):
411
- dstring = AnsibleLoader(getattr(module, 'DOCUMENTATION', ''), file_name=path).get_single_data()
508
+ # trust-tagged source propagates to loaded values; expressions and templates in config require trust
509
+ documentation_source = _tags.TrustedAsTemplate().tag(getattr(module, 'DOCUMENTATION', ''))
510
+ try:
511
+ dstring = yaml.load(_tags.Origin(path=path).tag(documentation_source), Loader=AnsibleLoader)
512
+ except ParserError as e:
513
+ raise AnsibleError(f"plugin {name} has malformed documentation!") from e
412
514
 
413
515
  # TODO: allow configurable plugins to use sidecar
414
516
  # if not dstring:
415
517
  # filename, cn = find_plugin_docfile( name, type_name, self, [os.path.dirname(path)], C.YAML_DOC_EXTENSIONS)
416
- # # TODO: dstring = AnsibleLoader(, file_name=path).get_single_data()
417
518
 
418
519
  if dstring:
419
520
  add_fragments(dstring, path, fragment_loader=fragment_loader, is_module=(type_name == 'module'))
@@ -423,7 +524,7 @@ class PluginLoader:
423
524
  display.debug('Loaded config def from plugin (%s/%s)' % (type_name, name))
424
525
 
425
526
  def add_directory(self, directory, with_subdir=False):
426
- ''' Adds an additional directory to the search path '''
527
+ """ Adds an additional directory to the search path """
427
528
 
428
529
  directory = os.path.realpath(directory)
429
530
 
@@ -467,7 +568,13 @@ class PluginLoader:
467
568
  entry = collection_meta.get('plugin_routing', {}).get(plugin_type, {}).get(subdir_qualified_resource, None)
468
569
  return entry
469
570
 
470
- def _find_fq_plugin(self, fq_name, extension, plugin_load_context, ignore_deprecated=False):
571
+ def _find_fq_plugin(
572
+ self,
573
+ fq_name: str,
574
+ extension: str | None,
575
+ plugin_load_context: PluginLoadContext,
576
+ ignore_deprecated: bool = False,
577
+ ) -> PluginLoadContext:
471
578
  """Search builtin paths to find a plugin. No external paths are searched,
472
579
  meaning plugins inside roles inside collections will be ignored.
473
580
  """
@@ -497,15 +604,20 @@ class PluginLoader:
497
604
  removal_date = tombstone.get('removal_date')
498
605
  removal_version = tombstone.get('removal_version')
499
606
  warning_text = tombstone.get('warning_text') or ''
500
- warning_text = '{0} has been removed.{1}{2}'.format(fq_name, ' ' if warning_text else '', warning_text)
501
- removed_msg = display.get_deprecation_message(msg=warning_text, version=removal_version,
502
- date=removal_date, removed=True,
503
- collection_name=acr.collection)
504
- plugin_load_context.removal_date = removal_date
505
- plugin_load_context.removal_version = removal_version
607
+ warning_plugin_type = "module" if self.type == "modules" else f'{self.type} plugin'
608
+ warning_text = f'The {fq_name!r} {warning_plugin_type} has been removed.{" " if warning_text else ""}{warning_text}'
609
+ removed_msg = display._get_deprecation_message_with_plugin_info(
610
+ msg=warning_text,
611
+ version=removal_version,
612
+ date=removal_date,
613
+ removed=True,
614
+ deprecator=deprecator_from_collection_name(acr.collection),
615
+ )
616
+ plugin_load_context.date = removal_date
617
+ plugin_load_context.version = removal_version
506
618
  plugin_load_context.resolved = True
507
619
  plugin_load_context.exit_reason = removed_msg
508
- raise AnsiblePluginRemovedError(removed_msg, plugin_load_context=plugin_load_context)
620
+ raise AnsiblePluginRemovedError(message=removed_msg, plugin_load_context=plugin_load_context)
509
621
 
510
622
  redirect = routing_metadata.get('redirect', None)
511
623
 
@@ -562,7 +674,7 @@ class PluginLoader:
562
674
  # look for any matching extension in the package location (sans filter)
563
675
  found_files = [f
564
676
  for f in glob.iglob(os.path.join(pkg_path, n_resource) + '.*')
565
- if os.path.isfile(f) and not f.endswith(C.MODULE_IGNORE_EXTS)]
677
+ if os.path.isfile(f) and not any(f.endswith(ext) for ext in C.MODULE_IGNORE_EXTS)]
566
678
 
567
679
  if not found_files:
568
680
  return plugin_load_context.nope('failed fuzzy extension match for {0} in {1}'.format(full_name, acr.collection))
@@ -577,16 +689,23 @@ class PluginLoader:
577
689
  'found fuzzy extension match for {0} in {1}'.format(full_name, acr.collection), action_plugin)
578
690
 
579
691
  def find_plugin(self, name, mod_type='', ignore_deprecated=False, check_aliases=False, collection_list=None):
580
- ''' Find a plugin named name '''
692
+ """ Find a plugin named name """
581
693
  result = self.find_plugin_with_context(name, mod_type, ignore_deprecated, check_aliases, collection_list)
582
694
  if result.resolved and result.plugin_resolved_path:
583
695
  return result.plugin_resolved_path
584
696
 
585
697
  return None
586
698
 
587
- def find_plugin_with_context(self, name, mod_type='', ignore_deprecated=False, check_aliases=False, collection_list=None):
588
- ''' Find a plugin named name, returning contextual info about the load, recursively resolving redirection '''
589
- plugin_load_context = PluginLoadContext()
699
+ def find_plugin_with_context(
700
+ self,
701
+ name: str,
702
+ mod_type: str = '',
703
+ ignore_deprecated: bool = False,
704
+ check_aliases: bool = False,
705
+ collection_list: list[str] | None = None,
706
+ ) -> PluginLoadContext:
707
+ """ Find a plugin named name, returning contextual info about the load, recursively resolving redirection """
708
+ plugin_load_context = PluginLoadContext(self.type, self.package)
590
709
  plugin_load_context.original_name = name
591
710
  while True:
592
711
  result = self._resolve_plugin_step(name, mod_type, ignore_deprecated, check_aliases, collection_list, plugin_load_context=plugin_load_context)
@@ -599,11 +718,8 @@ class PluginLoader:
599
718
  else:
600
719
  break
601
720
 
602
- # TODO: smuggle these to the controller when we're in a worker, reduce noise from normal things like missing plugin packages during collection search
603
- if plugin_load_context.error_list:
604
- display.warning("errors were encountered during the plugin load for {0}:\n{1}".format(name, plugin_load_context.error_list))
605
-
606
- # TODO: display/return import_error_list? Only useful for forensics...
721
+ for ex in plugin_load_context.raw_error_list:
722
+ display.error_as_warning(f"Error loading plugin {name!r}.", ex)
607
723
 
608
724
  # FIXME: store structured deprecation data in PluginLoadContext and use display.deprecate
609
725
  # if plugin_load_context.deprecated and C.config.get_config_value('DEPRECATION_WARNINGS'):
@@ -613,9 +729,15 @@ class PluginLoader:
613
729
 
614
730
  return plugin_load_context
615
731
 
616
- # FIXME: name bikeshed
617
- def _resolve_plugin_step(self, name, mod_type='', ignore_deprecated=False,
618
- check_aliases=False, collection_list=None, plugin_load_context=PluginLoadContext()):
732
+ def _resolve_plugin_step(
733
+ self,
734
+ name: str,
735
+ mod_type: str = '',
736
+ ignore_deprecated: bool = False,
737
+ check_aliases: bool = False,
738
+ collection_list: list[str] | None = None,
739
+ plugin_load_context: PluginLoadContext | None = None,
740
+ ) -> PluginLoadContext:
619
741
  if not plugin_load_context:
620
742
  raise ValueError('A PluginLoadContext is required')
621
743
 
@@ -670,11 +792,14 @@ class PluginLoader:
670
792
  except (AnsiblePluginRemovedError, AnsiblePluginCircularRedirect, AnsibleCollectionUnsupportedVersionError):
671
793
  # these are generally fatal, let them fly
672
794
  raise
673
- except ImportError as ie:
674
- plugin_load_context.import_error_list.append(ie)
675
795
  except Exception as ex:
676
- # FIXME: keep actual errors, not just assembled messages
677
- plugin_load_context.error_list.append(to_native(ex))
796
+ plugin_load_context.raw_error_list.append(ex)
797
+
798
+ # DTFIX-FUTURE: can we deprecate/remove these stringified versions?
799
+ if isinstance(ex, ImportError):
800
+ plugin_load_context.import_error_list.append(ex)
801
+ else:
802
+ plugin_load_context.error_list.append(str(ex))
678
803
 
679
804
  if plugin_load_context.error_list:
680
805
  display.debug(msg='plugin lookup for {0} failed; errors: {1}'.format(name, '; '.join(plugin_load_context.error_list)))
@@ -700,13 +825,7 @@ class PluginLoader:
700
825
  # requested mod_type
701
826
  pull_cache = self._plugin_path_cache[suffix]
702
827
  try:
703
- path_with_context = pull_cache[name]
704
- plugin_load_context.plugin_resolved_path = path_with_context.path
705
- plugin_load_context.plugin_resolved_name = name
706
- plugin_load_context.plugin_resolved_collection = 'ansible.builtin' if path_with_context.internal else ''
707
- plugin_load_context._resolved_fqcn = ('ansible.builtin.' + name if path_with_context.internal else name)
708
- plugin_load_context.resolved = True
709
- return plugin_load_context
828
+ return plugin_load_context.resolve_legacy(name=name, pull_cache=pull_cache)
710
829
  except KeyError:
711
830
  # Cache miss. Now let's find the plugin
712
831
  pass
@@ -759,13 +878,7 @@ class PluginLoader:
759
878
 
760
879
  self._searched_paths.add(path)
761
880
  try:
762
- path_with_context = pull_cache[name]
763
- plugin_load_context.plugin_resolved_path = path_with_context.path
764
- plugin_load_context.plugin_resolved_name = name
765
- plugin_load_context.plugin_resolved_collection = 'ansible.builtin' if path_with_context.internal else ''
766
- plugin_load_context._resolved_fqcn = 'ansible.builtin.' + name if path_with_context.internal else name
767
- plugin_load_context.resolved = True
768
- return plugin_load_context
881
+ return plugin_load_context.resolve_legacy(name=name, pull_cache=pull_cache)
769
882
  except KeyError:
770
883
  # Didn't find the plugin in this directory. Load modules from the next one
771
884
  pass
@@ -773,18 +886,18 @@ class PluginLoader:
773
886
  # if nothing is found, try finding alias/deprecated
774
887
  if not name.startswith('_'):
775
888
  alias_name = '_' + name
776
- # We've already cached all the paths at this point
777
- if alias_name in pull_cache:
778
- path_with_context = pull_cache[alias_name]
779
- if not ignore_deprecated and not os.path.islink(path_with_context.path):
780
- # FIXME: this is not always the case, some are just aliases
781
- display.deprecated('%s is kept for backwards compatibility but usage is discouraged. ' # pylint: disable=ansible-deprecated-no-version
782
- 'The module documentation details page may explain more about this rationale.' % name.lstrip('_'))
783
- plugin_load_context.plugin_resolved_path = path_with_context.path
784
- plugin_load_context.plugin_resolved_name = alias_name
785
- plugin_load_context.plugin_resolved_collection = 'ansible.builtin' if path_with_context.internal else ''
786
- plugin_load_context._resolved_fqcn = 'ansible.builtin.' + alias_name if path_with_context.internal else alias_name
787
- plugin_load_context.resolved = True
889
+
890
+ try:
891
+ plugin_load_context.resolve_legacy(name=alias_name, pull_cache=pull_cache)
892
+ except KeyError:
893
+ pass
894
+ else:
895
+ display.deprecated(
896
+ msg=f'Plugin {name!r} automatically redirected to {alias_name!r}.',
897
+ help_text=f'Use {alias_name!r} instead of {name!r} to refer to the plugin.',
898
+ version='2.23',
899
+ )
900
+
788
901
  return plugin_load_context
789
902
 
790
903
  # last ditch, if it's something that can be redirected, look for a builtin redirect before giving up
@@ -794,8 +907,8 @@ class PluginLoader:
794
907
 
795
908
  return plugin_load_context.nope('{0} is not eligible for last-chance resolution'.format(name))
796
909
 
797
- def has_plugin(self, name, collection_list=None):
798
- ''' Checks if a plugin named name exists '''
910
+ def has_plugin(self, name: str, collection_list: list[str] | None = None) -> bool:
911
+ """ Checks if a plugin named name exists """
799
912
 
800
913
  try:
801
914
  return self.find_plugin(name, collection_list=collection_list) is not None
@@ -805,45 +918,49 @@ class PluginLoader:
805
918
  # log and continue, likely an innocuous type/package loading failure in collections import
806
919
  display.debug('has_plugin error: {0}'.format(to_text(ex)))
807
920
 
808
- __contains__ = has_plugin
921
+ return False
809
922
 
810
- def _load_module_source(self, name, path):
811
-
812
- # avoid collisions across plugins
813
- if name.startswith('ansible_collections.'):
814
- full_name = name
815
- else:
816
- full_name = '.'.join([self.package, name])
923
+ __contains__ = has_plugin
817
924
 
818
- if full_name in sys.modules:
925
+ def _load_module_source(self, *, python_module_name: str, path: str) -> types.ModuleType:
926
+ if python_module_name in sys.modules:
819
927
  # Avoids double loading, See https://github.com/ansible/ansible/issues/13110
820
- return sys.modules[full_name]
928
+ return sys.modules[python_module_name]
821
929
 
822
930
  with warnings.catch_warnings():
823
931
  # FIXME: this still has issues if the module was previously imported but not "cached",
824
932
  # we should bypass this entire codepath for things that are directly importable
825
933
  warnings.simplefilter("ignore", RuntimeWarning)
826
- spec = importlib.util.spec_from_file_location(to_native(full_name), to_native(path))
934
+ spec = importlib.util.spec_from_file_location(to_native(python_module_name), to_native(path))
827
935
  module = importlib.util.module_from_spec(spec)
828
936
 
829
937
  # mimic import machinery; make the module-being-loaded available in sys.modules during import
830
938
  # and remove if there's a failure...
831
- sys.modules[full_name] = module
939
+ sys.modules[python_module_name] = module
832
940
 
833
941
  try:
834
942
  spec.loader.exec_module(module)
835
943
  except Exception:
836
- del sys.modules[full_name]
944
+ del sys.modules[python_module_name]
837
945
  raise
838
946
 
839
947
  return module
840
948
 
841
- def _update_object(self, obj, name, path, redirected_names=None, resolved=None):
949
+ def _update_object(
950
+ self,
951
+ *,
952
+ obj: _AnsiblePluginInfoMixin,
953
+ name: str,
954
+ path: str,
955
+ redirected_names: list[str] | None = None,
956
+ resolved: str | None = None,
957
+ ) -> None:
958
+ # DTFIX-FUTURE: clean this up- standardize types, document, split/remove redundant bits
842
959
 
843
960
  # set extra info on the module, in case we want it later
844
- setattr(obj, '_original_path', path)
845
- setattr(obj, '_load_name', name)
846
- setattr(obj, '_redirected_names', redirected_names or [])
961
+ obj._original_path = path
962
+ obj._load_name = name
963
+ obj._redirected_names = redirected_names or []
847
964
 
848
965
  names = []
849
966
  if resolved:
@@ -854,25 +971,26 @@ class PluginLoader:
854
971
  if not names:
855
972
  raise AnsibleError(f"Missing FQCN for plugin source {name}")
856
973
 
857
- setattr(obj, 'ansible_aliases', names)
858
- setattr(obj, 'ansible_name', names[0])
974
+ obj.ansible_aliases = names
975
+ obj.ansible_name = names[0]
859
976
 
860
977
  def get(self, name, *args, **kwargs):
861
- return self.get_with_context(name, *args, **kwargs).object
862
-
863
- def get_with_context(self, name, *args, **kwargs):
864
- ''' instantiates a plugin of the given name using arguments '''
865
- if _is_unsafe(name):
866
- # Objects constructed using the name wrapped as unsafe remain
867
- # (correctly) unsafe. Using such unsafe objects in places
868
- # where underlying types (builtin string in this case) are
869
- # expected can cause problems.
870
- # One such case is importlib.abc.Loader.exec_module failing
871
- # with "ValueError: unmarshallable object" because the module
872
- # object is created with the __path__ attribute being wrapped
873
- # as unsafe which isn't marshallable.
874
- # Manually removing the unsafe wrapper prevents such issues.
875
- name = name._strip_unsafe()
978
+ ctx = self.get_with_context(name, *args, **kwargs)
979
+ is_core_plugin = ctx.plugin_load_context.plugin_resolved_collection == 'ansible.builtin'
980
+ if self.class_name == 'StrategyModule' and not is_core_plugin:
981
+ display.deprecated( # pylint: disable=ansible-deprecated-no-version
982
+ msg='Use of strategy plugins not included in ansible.builtin are deprecated and do not carry '
983
+ 'any backwards compatibility guarantees. No alternative for third party strategy plugins '
984
+ 'is currently planned.',
985
+ )
986
+
987
+ return ctx.object
988
+
989
+ def get_with_context(self, name, *args, **kwargs) -> get_with_context_result:
990
+ """ instantiates a plugin of the given name using arguments """
991
+
992
+ if not name:
993
+ raise ValueError('A non-empty plugin name is required.')
876
994
 
877
995
  found_in_cache = True
878
996
  class_only = kwargs.pop('class_only', False)
@@ -893,8 +1011,6 @@ class PluginLoader:
893
1011
  return get_with_context_result(None, plugin_load_context)
894
1012
 
895
1013
  fq_name = plugin_load_context.resolved_fqcn
896
- if '.' not in fq_name and plugin_load_context.plugin_resolved_collection:
897
- fq_name = '.'.join((plugin_load_context.plugin_resolved_collection, fq_name))
898
1014
  resolved_type_name = plugin_load_context.plugin_resolved_name
899
1015
  path = plugin_load_context.plugin_resolved_path
900
1016
  if (cached_result := (self._plugin_instance_cache or {}).get(fq_name)) and cached_result[1].resolved:
@@ -904,7 +1020,7 @@ class PluginLoader:
904
1020
  redirected_names = plugin_load_context.redirect_list or []
905
1021
 
906
1022
  if path not in self._module_cache:
907
- self._module_cache[path] = self._load_module_source(resolved_type_name, path)
1023
+ self._module_cache[path] = self._load_module_source(python_module_name=plugin_load_context._python_module_name, path=path)
908
1024
  found_in_cache = False
909
1025
 
910
1026
  self._load_config_defs(resolved_type_name, self._module_cache[path], path)
@@ -921,6 +1037,7 @@ class PluginLoader:
921
1037
  except AttributeError:
922
1038
  return get_with_context_result(None, plugin_load_context)
923
1039
  if not issubclass(obj, plugin_class):
1040
+ display.warning(f"Ignoring {self.type} plugin {resolved_type_name!r} due to missing base class {self.base_class!r}.")
924
1041
  return get_with_context_result(None, plugin_load_context)
925
1042
 
926
1043
  # FIXME: update this to use the load context
@@ -931,7 +1048,7 @@ class PluginLoader:
931
1048
  # A plugin may need to use its _load_name in __init__ (for example, to set
932
1049
  # or get options from config), so update the object before using the constructor
933
1050
  instance = object.__new__(obj)
934
- self._update_object(instance, resolved_type_name, path, redirected_names, fq_name)
1051
+ self._update_object(obj=instance, name=resolved_type_name, path=path, redirected_names=redirected_names, resolved=fq_name)
935
1052
  obj.__init__(instance, *args, **kwargs) # pylint: disable=unnecessary-dunder-call
936
1053
  obj = instance
937
1054
  except TypeError as e:
@@ -941,16 +1058,16 @@ class PluginLoader:
941
1058
  return get_with_context_result(None, plugin_load_context)
942
1059
  raise
943
1060
 
944
- self._update_object(obj, resolved_type_name, path, redirected_names, fq_name)
1061
+ self._update_object(obj=obj, name=resolved_type_name, path=path, redirected_names=redirected_names, resolved=fq_name)
945
1062
  if self._plugin_instance_cache is not None and getattr(obj, 'is_stateless', False):
946
1063
  self._plugin_instance_cache[fq_name] = (obj, plugin_load_context)
947
1064
  elif self._plugin_instance_cache is not None:
948
1065
  # The cache doubles as the load order, so record the FQCN even if the plugin hasn't set is_stateless = True
949
- self._plugin_instance_cache[fq_name] = (None, PluginLoadContext())
1066
+ self._plugin_instance_cache[fq_name] = (None, PluginLoadContext(self.type, self.package))
950
1067
  return get_with_context_result(obj, plugin_load_context)
951
1068
 
952
1069
  def _display_plugin_load(self, class_name, name, searched_paths, path, found_in_cache=None, class_only=None):
953
- ''' formats data to display debug info for plugin loading, also avoids processing unless really needed '''
1070
+ """ formats data to display debug info for plugin loading, also avoids processing unless really needed """
954
1071
  if C.DEFAULT_DEBUG:
955
1072
  msg = 'Loading %s \'%s\' from %s' % (class_name, os.path.basename(name), path)
956
1073
 
@@ -963,7 +1080,7 @@ class PluginLoader:
963
1080
  display.debug(msg)
964
1081
 
965
1082
  def all(self, *args, **kwargs):
966
- '''
1083
+ """
967
1084
  Iterate through all plugins of this type, in configured paths (no collections)
968
1085
 
969
1086
  A plugin loader is initialized with a specific type. This function is an iterator returning
@@ -984,7 +1101,7 @@ class PluginLoader:
984
1101
  want to manage their own deduplication of the plugins.
985
1102
  :*args: Any extra arguments are passed to each plugin when it is instantiated.
986
1103
  :**kwargs: Any extra keyword arguments are passed to each plugin when it is instantiated.
987
- '''
1104
+ """
988
1105
  # TODO: Change the signature of this method to:
989
1106
  # def all(return_type='instance', args=None, kwargs=None):
990
1107
  # if args is None: args = []
@@ -1021,10 +1138,15 @@ class PluginLoader:
1021
1138
  basename = os.path.basename(name)
1022
1139
  is_j2 = isinstance(self, Jinja2Loader)
1023
1140
 
1141
+ if path in legacy_excluding_builtin:
1142
+ fqcn = basename
1143
+ else:
1144
+ fqcn = f"ansible.builtin.{basename}"
1145
+
1024
1146
  if is_j2:
1025
1147
  ref_name = path
1026
1148
  else:
1027
- ref_name = basename
1149
+ ref_name = fqcn
1028
1150
 
1029
1151
  if not is_j2 and basename in _PLUGIN_FILTERS[self.package]:
1030
1152
  # j2 plugins get processed in own class, here they would just be container files
@@ -1047,26 +1169,18 @@ class PluginLoader:
1047
1169
  yield path
1048
1170
  continue
1049
1171
 
1050
- if path in legacy_excluding_builtin:
1051
- fqcn = basename
1052
- else:
1053
- fqcn = f"ansible.builtin.{basename}"
1054
-
1055
1172
  if (cached_result := (self._plugin_instance_cache or {}).get(fqcn)) and cached_result[1].resolved:
1056
1173
  # Here just in case, but we don't call all() multiple times for vars plugins, so this should not be used.
1057
1174
  yield cached_result[0]
1058
1175
  continue
1059
1176
 
1060
1177
  if path not in self._module_cache:
1061
- if self.type in ('filter', 'test'):
1062
- # filter and test plugin files can contain multiple plugins
1063
- # they must have a unique python module name to prevent them from shadowing each other
1064
- full_name = '{0}_{1}'.format(abs(hash(path)), basename)
1065
- else:
1066
- full_name = basename
1178
+ path_context = PluginPathContext(path, path not in legacy_excluding_builtin)
1179
+ load_context = PluginLoadContext(self.type, self.package)
1180
+ load_context.resolve_legacy(basename, {basename: path_context})
1067
1181
 
1068
1182
  try:
1069
- module = self._load_module_source(full_name, path)
1183
+ module = self._load_module_source(python_module_name=load_context._python_module_name, path=path)
1070
1184
  except Exception as e:
1071
1185
  display.warning("Skipping plugin (%s), cannot load: %s" % (path, to_text(e)))
1072
1186
  continue
@@ -1104,7 +1218,7 @@ class PluginLoader:
1104
1218
  except TypeError as e:
1105
1219
  display.warning("Skipping plugin (%s) as it seems to be incomplete: %s" % (path, to_text(e)))
1106
1220
 
1107
- self._update_object(obj, basename, path, resolved=fqcn)
1221
+ self._update_object(obj=obj, name=basename, path=path, resolved=fqcn)
1108
1222
 
1109
1223
  if self._plugin_instance_cache is not None:
1110
1224
  needs_enabled = False
@@ -1117,6 +1231,21 @@ class PluginLoader:
1117
1231
  yield obj
1118
1232
 
1119
1233
 
1234
+ class _CacheLoader(PluginLoader):
1235
+ """Customized loader for cache plugins that wraps the requested plugin with an interposer that schema-qualifies keys and JSON encodes the values."""
1236
+
1237
+ def get(self, name: str, *args, **kwargs) -> BaseCacheModule:
1238
+ plugin = super().get(name, *args, **kwargs)
1239
+
1240
+ if not plugin:
1241
+ raise AnsibleError(f'Unable to load the cache plugin {name!r}.')
1242
+
1243
+ if plugin._persistent:
1244
+ return _cache.PluginInterposer(plugin)
1245
+
1246
+ return plugin
1247
+
1248
+
1120
1249
  class Jinja2Loader(PluginLoader):
1121
1250
  """
1122
1251
  PluginLoader optimized for Jinja2 plugins
@@ -1125,10 +1254,12 @@ class Jinja2Loader(PluginLoader):
1125
1254
  We need to do a few things differently in the base class because of file == plugin
1126
1255
  assumptions and dedupe logic.
1127
1256
  """
1128
- def __init__(self, class_name, package, config, subdir, plugin_wrapper_type, aliases=None, required_base_class=None):
1257
+
1258
+ def __init__(self, class_name, package, config, subdir, plugin_wrapper_type, aliases=None, required_base_class=None) -> None:
1129
1259
  super(Jinja2Loader, self).__init__(class_name, package, config, subdir, aliases=aliases, required_base_class=required_base_class)
1130
1260
  self._plugin_wrapper_type = plugin_wrapper_type
1131
- self._cached_non_collection_wrappers = {}
1261
+ self._plugin_type_friendly_name = 'filter' if plugin_wrapper_type is AnsibleJinja2Filter else 'test'
1262
+ self._cached_non_collection_wrappers: dict[str, AnsibleJinja2Filter | AnsibleJinja2Test | _DeferredPluginLoadFailure] = {}
1132
1263
 
1133
1264
  def _clear_caches(self):
1134
1265
  super(Jinja2Loader, self)._clear_caches()
@@ -1141,6 +1272,36 @@ class Jinja2Loader(PluginLoader):
1141
1272
  def method_map_name(self):
1142
1273
  return get_plugin_class(self.class_name) + 's'
1143
1274
 
1275
+ def _wrap_func(self, name: str, resolved: str, func: t.Callable) -> AnsibleJinja2Test | AnsibleJinja2Filter:
1276
+ """Wrap a Jinja builtin function in a `AnsibleJinja2Plugin` instance."""
1277
+ try:
1278
+ path = sys.modules[func.__module__].__file__
1279
+ except AttributeError:
1280
+ path = None
1281
+
1282
+ wrapper = self._plugin_wrapper_type(func)
1283
+
1284
+ self._update_object(obj=wrapper, name=name, path=path, resolved=resolved)
1285
+
1286
+ return wrapper
1287
+
1288
+ def _wrap_funcs(self, plugins: dict[str, t.Callable], aliases: dict[str, str]) -> dict[str, AnsibleJinja2Test | AnsibleJinja2Filter]:
1289
+ """Map a dictionary of Jinja builtin functions to one containing `AnsibleJinja2Plugin` instances."""
1290
+ wrappers: dict[str, AnsibleJinja2Test | AnsibleJinja2Filter] = {}
1291
+
1292
+ for load_name, func in plugins.items():
1293
+ name = aliases.get(load_name, load_name)
1294
+ resolved = f'ansible.builtin.{name}'
1295
+
1296
+ wrappers[load_name] = self._wrap_func(load_name, resolved, func)
1297
+
1298
+ if resolved not in wrappers:
1299
+ # When the resolved name hasn't been cached, do so.
1300
+ # Functions that have aliases will appear more than once, and we don't need to overwrite them.
1301
+ wrappers[resolved] = self._wrap_func(resolved, resolved, func)
1302
+
1303
+ return wrappers
1304
+
1144
1305
  def get_contained_plugins(self, collection, plugin_path, name):
1145
1306
 
1146
1307
  plugins = []
@@ -1149,7 +1310,7 @@ class Jinja2Loader(PluginLoader):
1149
1310
  try:
1150
1311
  # use 'parent' loader class to find files, but cannot return this as it can contain multiple plugins per file
1151
1312
  if plugin_path not in self._module_cache:
1152
- self._module_cache[plugin_path] = self._load_module_source(full_name, plugin_path)
1313
+ self._module_cache[plugin_path] = self._load_module_source(python_module_name=full_name, path=plugin_path)
1153
1314
  module = self._module_cache[plugin_path]
1154
1315
  obj = getattr(module, self.class_name)
1155
1316
  except Exception as e:
@@ -1172,19 +1333,21 @@ class Jinja2Loader(PluginLoader):
1172
1333
  plugin = self._plugin_wrapper_type(func)
1173
1334
  if plugin in plugins:
1174
1335
  continue
1175
- self._update_object(plugin, full, plugin_path, resolved=fq_name)
1336
+ self._update_object(obj=plugin, name=full, path=plugin_path, resolved=fq_name)
1176
1337
  plugins.append(plugin)
1177
1338
 
1178
1339
  return plugins
1179
1340
 
1180
1341
  # FUTURE: now that the resulting plugins are closer, refactor base class method with some extra
1181
1342
  # hooks so we can avoid all the duplicated plugin metadata logic, and also cache the collection results properly here
1182
- def get_with_context(self, name, *args, **kwargs):
1343
+ def get_with_context(self, name: str, *args, **kwargs) -> get_with_context_result:
1183
1344
  # pop N/A kwargs to avoid passthrough to parent methods
1184
1345
  kwargs.pop('class_only', False)
1185
1346
  kwargs.pop('collection_list', None)
1186
1347
 
1187
- context = PluginLoadContext()
1348
+ requested_name = name
1349
+
1350
+ context = PluginLoadContext(self.type, self.package)
1188
1351
 
1189
1352
  # avoid collection path for legacy
1190
1353
  name = name.removeprefix('ansible.legacy.')
@@ -1193,11 +1356,11 @@ class Jinja2Loader(PluginLoader):
1193
1356
 
1194
1357
  # check for stuff loaded via legacy/builtin paths first
1195
1358
  if known_plugin := self._cached_non_collection_wrappers.get(name):
1196
- context.resolved = True
1197
- context.plugin_resolved_name = name
1198
- context.plugin_resolved_path = known_plugin._original_path
1199
- context.plugin_resolved_collection = 'ansible.builtin' if known_plugin.ansible_name.startswith('ansible.builtin.') else ''
1200
- context._resolved_fqcn = known_plugin.ansible_name
1359
+ if isinstance(known_plugin, _DeferredPluginLoadFailure):
1360
+ raise known_plugin.ex
1361
+
1362
+ context.resolve_legacy_jinja_plugin(name, known_plugin)
1363
+
1201
1364
  return get_with_context_result(known_plugin, context)
1202
1365
 
1203
1366
  plugin = None
@@ -1219,7 +1382,7 @@ class Jinja2Loader(PluginLoader):
1219
1382
  ts = _get_collection_metadata(acr.collection)
1220
1383
  except ValueError as e:
1221
1384
  # no collection
1222
- raise KeyError('Invalid plugin FQCN ({0}): {1}'.format(key, to_native(e)))
1385
+ raise KeyError('Invalid plugin FQCN ({0}): {1}'.format(key, to_native(e))) from e
1223
1386
 
1224
1387
  # TODO: implement cycle detection (unified across collection redir as well)
1225
1388
  routing_entry = ts.get('plugin_routing', {}).get(self.type, {}).get(leaf_key, {})
@@ -1233,7 +1396,12 @@ class Jinja2Loader(PluginLoader):
1233
1396
 
1234
1397
  warning_text = f'{self.type.title()} "{key}" has been deprecated.{" " if warning_text else ""}{warning_text}'
1235
1398
 
1236
- display.deprecated(warning_text, version=removal_version, date=removal_date, collection_name=acr.collection)
1399
+ display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
1400
+ msg=warning_text,
1401
+ version=removal_version,
1402
+ date=removal_date,
1403
+ deprecator=deprecator_from_collection_name(acr.collection),
1404
+ )
1237
1405
 
1238
1406
  # check removal
1239
1407
  tombstone_entry = routing_entry.get('tombstone')
@@ -1241,11 +1409,15 @@ class Jinja2Loader(PluginLoader):
1241
1409
  warning_text = tombstone_entry.get('warning_text') or ''
1242
1410
  removal_date = tombstone_entry.get('removal_date')
1243
1411
  removal_version = tombstone_entry.get('removal_version')
1412
+ warning_text = f'The {key!r} {self.type} plugin has been removed.{" " if warning_text else ""}{warning_text}'
1244
1413
 
1245
- warning_text = f'{self.type.title()} "{key}" has been removed.{" " if warning_text else ""}{warning_text}'
1246
-
1247
- exc_msg = display.get_deprecation_message(warning_text, version=removal_version, date=removal_date,
1248
- collection_name=acr.collection, removed=True)
1414
+ exc_msg = display._get_deprecation_message_with_plugin_info(
1415
+ msg=warning_text,
1416
+ version=removal_version,
1417
+ date=removal_date,
1418
+ removed=True,
1419
+ deprecator=deprecator_from_collection_name(acr.collection),
1420
+ )
1249
1421
 
1250
1422
  raise AnsiblePluginRemovedError(exc_msg)
1251
1423
 
@@ -1297,20 +1469,16 @@ class Jinja2Loader(PluginLoader):
1297
1469
  plugin = self._plugin_wrapper_type(func)
1298
1470
  if plugin:
1299
1471
  context = plugin_impl.plugin_load_context
1300
- self._update_object(plugin, src_name, plugin_impl.object._original_path, resolved=fq_name)
1472
+ self._update_object(obj=plugin, name=requested_name, path=plugin_impl.object._original_path, resolved=fq_name)
1301
1473
  # context will have filename, which for tests/filters might not be correct
1302
1474
  context._resolved_fqcn = plugin.ansible_name
1303
1475
  # FIXME: once we start caching these results, we'll be missing functions that would have loaded later
1304
1476
  break # go to next file as it can override if dupe (dont break both loops)
1305
1477
 
1306
- except AnsiblePluginRemovedError as apre:
1307
- raise AnsibleError(to_native(apre), 0, orig_exc=apre)
1308
1478
  except (AnsibleError, KeyError):
1309
1479
  raise
1310
1480
  except Exception as ex:
1311
- display.warning('An unexpected error occurred during Jinja2 plugin loading: {0}'.format(to_native(ex)))
1312
- display.vvv('Unexpected error during Jinja2 plugin loading: {0}'.format(format_exc()))
1313
- raise AnsibleError(to_native(ex), 0, orig_exc=ex)
1481
+ raise AnsibleError('An unexpected error occurred during Jinja2 plugin loading.') from ex
1314
1482
 
1315
1483
  return get_with_context_result(plugin, context)
1316
1484
 
@@ -1324,10 +1492,13 @@ class Jinja2Loader(PluginLoader):
1324
1492
  raise AnsibleError('Do not set both path_only and class_only when calling PluginLoader.all()')
1325
1493
 
1326
1494
  self._ensure_non_collection_wrappers(*args, **kwargs)
1495
+
1496
+ plugins = [plugin for plugin in self._cached_non_collection_wrappers.values() if not isinstance(plugin, _DeferredPluginLoadFailure)]
1497
+
1327
1498
  if path_only:
1328
- yield from (w._original_path for w in self._cached_non_collection_wrappers.values())
1499
+ yield from (w._original_path for w in plugins)
1329
1500
  else:
1330
- yield from (w for w in self._cached_non_collection_wrappers.values())
1501
+ yield from (w for w in plugins)
1331
1502
 
1332
1503
  def _ensure_non_collection_wrappers(self, *args, **kwargs):
1333
1504
  if self._cached_non_collection_wrappers:
@@ -1354,21 +1525,26 @@ class Jinja2Loader(PluginLoader):
1354
1525
  display.debug("%s skipped due to a defined plugin filter" % plugin_name)
1355
1526
  continue
1356
1527
 
1357
- # the plugin class returned by the loader may host multiple Jinja plugins, but we wrap each plugin in
1358
- # its own surrogate wrapper instance here to ease the bookkeeping...
1359
- wrapper = self._plugin_wrapper_type(plugins[plugin_name])
1360
1528
  fqcn = plugin_name
1361
1529
  collection = '.'.join(p_map.ansible_name.split('.')[:2]) if p_map.ansible_name.count('.') >= 2 else ''
1362
1530
  if not plugin_name.startswith(collection):
1363
1531
  fqcn = f"{collection}.{plugin_name}"
1364
1532
 
1365
- self._update_object(wrapper, plugin_name, p_map._original_path, resolved=fqcn)
1366
-
1367
1533
  target_names = {plugin_name, fqcn}
1534
+
1368
1535
  if is_builtin:
1369
1536
  target_names.add(f'ansible.builtin.{plugin_name}')
1370
1537
 
1371
1538
  for target_name in target_names:
1539
+ # the plugin class returned by the loader may host multiple Jinja plugins, but we wrap each plugin in
1540
+ # its own surrogate wrapper instance here to ease the bookkeeping...
1541
+ try:
1542
+ wrapper = self._plugin_wrapper_type(plugins[plugin_name])
1543
+ except Exception as ex:
1544
+ wrapper = _DeferredPluginLoadFailure(ex)
1545
+
1546
+ self._update_object(obj=wrapper, name=target_name, path=p_map._original_path, resolved=fqcn)
1547
+
1372
1548
  if existing_plugin := self._cached_non_collection_wrappers.get(target_name):
1373
1549
  display.debug(f'Jinja plugin {target_name} from {p_map._original_path} skipped; '
1374
1550
  f'shadowed by plugin from {existing_plugin._original_path})')
@@ -1377,6 +1553,13 @@ class Jinja2Loader(PluginLoader):
1377
1553
  self._cached_non_collection_wrappers[target_name] = wrapper
1378
1554
 
1379
1555
 
1556
+ class _DeferredPluginLoadFailure:
1557
+ """Represents a plugin which failed to load. For internal use only within plugin loader."""
1558
+
1559
+ def __init__(self, ex: Exception) -> None:
1560
+ self.ex = ex
1561
+
1562
+
1380
1563
  def get_fqcr_and_name(resource, collection='ansible.builtin'):
1381
1564
  if '.' not in resource:
1382
1565
  name = resource
@@ -1400,7 +1583,7 @@ def _load_plugin_filter():
1400
1583
  if os.path.exists(filter_cfg):
1401
1584
  with open(filter_cfg, 'rb') as f:
1402
1585
  try:
1403
- filter_data = from_yaml(f.read())
1586
+ filter_data = yaml.load(f, Loader=AnsibleInstrumentedLoader)
1404
1587
  except Exception as e:
1405
1588
  display.warning(u'The plugin filter file, {0} was not parsable.'
1406
1589
  u' Skipping: {1}'.format(filter_cfg, to_text(e)))
@@ -1489,7 +1672,8 @@ def _configure_collection_loader(prefix_collections_path=None):
1489
1672
  if prefix_collections_path is None:
1490
1673
  prefix_collections_path = []
1491
1674
 
1492
- paths = list(prefix_collections_path) + C.COLLECTIONS_PATHS
1675
+ # insert the internal ansible._protomatter collection up front
1676
+ paths = [os.path.dirname(_internal.__file__)] + list(prefix_collections_path) + C.COLLECTIONS_PATHS
1493
1677
  finder = _AnsibleCollectionFinder(paths, C.COLLECTIONS_SCAN_SYS_PATH)
1494
1678
  finder._install()
1495
1679
 
@@ -1529,7 +1713,7 @@ action_loader = PluginLoader(
1529
1713
  required_base_class='ActionBase',
1530
1714
  )
1531
1715
 
1532
- cache_loader = PluginLoader(
1716
+ cache_loader = _CacheLoader(
1533
1717
  'CacheModule',
1534
1718
  'ansible.plugins.cache',
1535
1719
  C.DEFAULT_CACHE_PLUGIN_PATH,
@@ -1541,6 +1725,7 @@ callback_loader = PluginLoader(
1541
1725
  'ansible.plugins.callback',
1542
1726
  C.DEFAULT_CALLBACK_PLUGIN_PATH,
1543
1727
  'callback_plugins',
1728
+ required_base_class='CallbackBase',
1544
1729
  )
1545
1730
 
1546
1731
  connection_loader = PluginLoader(