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
@@ -20,42 +20,79 @@ from __future__ import annotations
20
20
 
21
21
  import ast
22
22
  import base64
23
+ import dataclasses
23
24
  import datetime
24
25
  import json
25
26
  import os
27
+ import pathlib
28
+ import pickle
26
29
  import shlex
27
- import time
28
30
  import zipfile
29
31
  import re
30
32
  import pkgutil
33
+ import types
34
+ import typing as t
31
35
 
32
36
  from ast import AST, Import, ImportFrom
33
37
  from io import BytesIO
34
38
 
39
+ from ansible._internal import _locking
40
+ from ansible._internal._ansiballz import _builder
41
+ from ansible._internal import _ansiballz
42
+ from ansible._internal._datatag import _utils
43
+ from ansible.module_utils._internal import _dataclass_validation
44
+ from ansible.module_utils.common.yaml import yaml_load
45
+ from ansible.module_utils.datatag import deprecator_from_collection_name
46
+ from ansible._internal._datatag._tags import Origin
47
+ from ansible.module_utils.common.json import Direction, get_module_encoder
35
48
  from ansible.release import __version__, __author__
36
49
  from ansible import constants as C
37
50
  from ansible.errors import AnsibleError
38
51
  from ansible.executor.interpreter_discovery import InterpreterDiscoveryRequiredError
39
52
  from ansible.executor.powershell import module_manifest as ps_manifest
40
- from ansible.module_utils.common.json import AnsibleJSONEncoder
41
53
  from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
54
+ from ansible.plugins.become import BecomeBase
42
55
  from ansible.plugins.loader import module_utils_loader
56
+ from ansible._internal._templating._engine import TemplateOptions, TemplateEngine
57
+ from ansible.template import Templar
43
58
  from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
59
+ from ansible.module_utils._internal import _json
60
+ from ansible.module_utils._internal._ansiballz import _loader
61
+ from ansible.module_utils import basic as _basic
44
62
 
45
- # Must import strategy and use write_locks from there
46
- # If we import write_locks directly then we end up binding a
47
- # variable to the object and then it never gets updated.
48
- from ansible.executor import action_write_locks
63
+ if t.TYPE_CHECKING:
64
+ from ansible import template as _template
65
+ from ansible.playbook.task import Task
49
66
 
50
67
  from ansible.utils.display import Display
51
- from collections import namedtuple
52
68
 
53
69
  import importlib.util
54
70
  import importlib.machinery
55
71
 
56
72
  display = Display()
57
73
 
58
- ModuleUtilsProcessEntry = namedtuple('ModuleUtilsProcessEntry', ['name_parts', 'is_ambiguous', 'has_redirected_child', 'is_optional'])
74
+
75
+ @dataclasses.dataclass(frozen=True, order=True)
76
+ class _ModuleUtilsProcessEntry:
77
+ """Represents a module/module_utils item awaiting import analysis."""
78
+ name_parts: tuple[str, ...]
79
+ is_ambiguous: bool = False
80
+ child_is_redirected: bool = False
81
+ is_optional: bool = False
82
+
83
+ @classmethod
84
+ def from_module(cls, module: types.ModuleType, append: str | None = None) -> t.Self:
85
+ name = module.__name__
86
+
87
+ if append:
88
+ name += '.' + append
89
+
90
+ return cls.from_module_name(name)
91
+
92
+ @classmethod
93
+ def from_module_name(cls, module_name: str) -> t.Self:
94
+ return cls(tuple(module_name.split('.')))
95
+
59
96
 
60
97
  REPLACER = b"#<<INCLUDE_ANSIBLE_MODULE_COMMON>>"
61
98
  REPLACER_VERSION = b"\"<<ANSIBLE_VERSION>>\""
@@ -64,348 +101,45 @@ REPLACER_WINDOWS = b"# POWERSHELL_COMMON"
64
101
  REPLACER_JSONARGS = b"<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>"
65
102
  REPLACER_SELINUX = b"<<SELINUX_SPECIAL_FILESYSTEMS>>"
66
103
 
67
- # We could end up writing out parameters with unicode characters so we need to
68
- # specify an encoding for the python source file
69
- ENCODING_STRING = u'# -*- coding: utf-8 -*-'
70
- b_ENCODING_STRING = b'# -*- coding: utf-8 -*-'
71
-
72
104
  # module_common is relative to module_utils, so fix the path
73
105
  _MODULE_UTILS_PATH = os.path.join(os.path.dirname(__file__), '..', 'module_utils')
106
+ _SHEBANG_PLACEHOLDER = '# shebang placeholder'
74
107
 
75
108
  # ******************************************************************************
76
109
 
77
- ANSIBALLZ_TEMPLATE = u'''%(shebang)s
78
- %(coding)s
79
- _ANSIBALLZ_WRAPPER = True # For test-module.py script to tell this is a ANSIBALLZ_WRAPPER
80
- # This code is part of Ansible, but is an independent component.
81
- # The code in this particular templatable string, and this templatable string
82
- # only, is BSD licensed. Modules which end up using this snippet, which is
83
- # dynamically combined together by Ansible still belong to the author of the
84
- # module, and they may assign their own license to the complete work.
85
- #
86
- # Copyright (c), James Cammarata, 2016
87
- # Copyright (c), Toshio Kuratomi, 2016
88
- #
89
- # Redistribution and use in source and binary forms, with or without modification,
90
- # are permitted provided that the following conditions are met:
91
- #
92
- # * Redistributions of source code must retain the above copyright
93
- # notice, this list of conditions and the following disclaimer.
94
- # * Redistributions in binary form must reproduce the above copyright notice,
95
- # this list of conditions and the following disclaimer in the documentation
96
- # and/or other materials provided with the distribution.
97
- #
98
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
99
- # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
100
- # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
101
- # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
102
- # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
103
- # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
104
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
105
- # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
106
- # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
107
- def _ansiballz_main():
108
- import os
109
- import os.path
110
-
111
- # Access to the working directory is required by Python when using pipelining, as well as for the coverage module.
112
- # Some platforms, such as macOS, may not allow querying the working directory when using become to drop privileges.
113
- try:
114
- os.getcwd()
115
- except OSError:
116
- try:
117
- os.chdir(os.path.expanduser('~'))
118
- except OSError:
119
- os.chdir('/')
120
-
121
- %(rlimit)s
122
-
123
- import sys
124
- import __main__
125
-
126
- # For some distros and python versions we pick up this script in the temporary
127
- # directory. This leads to problems when the ansible module masks a python
128
- # library that another import needs. We have not figured out what about the
129
- # specific distros and python versions causes this to behave differently.
130
- #
131
- # Tested distros:
132
- # Fedora23 with python3.4 Works
133
- # Ubuntu15.10 with python2.7 Works
134
- # Ubuntu15.10 with python3.4 Fails without this
135
- # Ubuntu16.04.1 with python3.5 Fails without this
136
- # To test on another platform:
137
- # * use the copy module (since this shadows the stdlib copy module)
138
- # * Turn off pipelining
139
- # * Make sure that the destination file does not exist
140
- # * ansible ubuntu16-test -m copy -a 'src=/etc/motd dest=/var/tmp/m'
141
- # This will traceback in shutil. Looking at the complete traceback will show
142
- # that shutil is importing copy which finds the ansible module instead of the
143
- # stdlib module
144
- scriptdir = None
145
- try:
146
- scriptdir = os.path.dirname(os.path.realpath(__main__.__file__))
147
- except (AttributeError, OSError):
148
- # Some platforms don't set __file__ when reading from stdin
149
- # OSX raises OSError if using abspath() in a directory we don't have
150
- # permission to read (realpath calls abspath)
151
- pass
152
-
153
- # Strip cwd from sys.path to avoid potential permissions issues
154
- excludes = set(('', '.', scriptdir))
155
- sys.path = [p for p in sys.path if p not in excludes]
156
-
157
- import base64
158
- import runpy
159
- import shutil
160
- import tempfile
161
- import zipfile
162
-
163
- if sys.version_info < (3,):
164
- PY3 = False
165
- else:
166
- PY3 = True
167
-
168
- ZIPDATA = %(zipdata)r
169
-
170
- # Note: temp_path isn't needed once we switch to zipimport
171
- def invoke_module(modlib_path, temp_path, json_params):
172
- # When installed via setuptools (including python setup.py install),
173
- # ansible may be installed with an easy-install.pth file. That file
174
- # may load the system-wide install of ansible rather than the one in
175
- # the module. sitecustomize is the only way to override that setting.
176
- z = zipfile.ZipFile(modlib_path, mode='a')
177
-
178
- # py3: modlib_path will be text, py2: it's bytes. Need bytes at the end
179
- sitecustomize = u'import sys\\nsys.path.insert(0,"%%s")\\n' %% modlib_path
180
- sitecustomize = sitecustomize.encode('utf-8')
181
- # Use a ZipInfo to work around zipfile limitation on hosts with
182
- # clocks set to a pre-1980 year (for instance, Raspberry Pi)
183
- zinfo = zipfile.ZipInfo()
184
- zinfo.filename = 'sitecustomize.py'
185
- zinfo.date_time = %(date_time)s
186
- z.writestr(zinfo, sitecustomize)
187
- z.close()
188
-
189
- # Put the zipped up module_utils we got from the controller first in the python path so that we
190
- # can monkeypatch the right basic
191
- sys.path.insert(0, modlib_path)
192
-
193
- # Monkeypatch the parameters into basic
194
- from ansible.module_utils import basic
195
- basic._ANSIBLE_ARGS = json_params
196
- %(coverage)s
197
- # Run the module! By importing it as '__main__', it thinks it is executing as a script
198
- runpy.run_module(mod_name=%(module_fqn)r, init_globals=dict(_module_fqn=%(module_fqn)r, _modlib_path=modlib_path),
199
- run_name='__main__', alter_sys=True)
200
-
201
- # Ansible modules must exit themselves
202
- print('{"msg": "New-style module did not handle its own exit", "failed": true}')
203
- sys.exit(1)
204
-
205
- def debug(command, zipped_mod, json_params):
206
- # The code here normally doesn't run. It's only used for debugging on the
207
- # remote machine.
208
- #
209
- # The subcommands in this function make it easier to debug ansiballz
210
- # modules. Here's the basic steps:
211
- #
212
- # Run ansible with the environment variable: ANSIBLE_KEEP_REMOTE_FILES=1 and -vvv
213
- # to save the module file remotely::
214
- # $ ANSIBLE_KEEP_REMOTE_FILES=1 ansible host1 -m ping -a 'data=october' -vvv
215
- #
216
- # Part of the verbose output will tell you where on the remote machine the
217
- # module was written to::
218
- # [...]
219
- # <host1> SSH: EXEC ssh -C -q -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o
220
- # PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o
221
- # ControlPath=/home/badger/.ansible/cp/ansible-ssh-%%h-%%p-%%r -tt rhel7 '/bin/sh -c '"'"'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
222
- # LC_MESSAGES=en_US.UTF-8 /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping'"'"''
223
- # [...]
224
- #
225
- # Login to the remote machine and run the module file via from the previous
226
- # step with the explode subcommand to extract the module payload into
227
- # source files::
228
- # $ ssh host1
229
- # $ /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping explode
230
- # Module expanded into:
231
- # /home/badger/.ansible/tmp/ansible-tmp-1461173408.08-279692652635227/ansible
232
- #
233
- # You can now edit the source files to instrument the code or experiment with
234
- # different parameter values. When you're ready to run the code you've modified
235
- # (instead of the code from the actual zipped module), use the execute subcommand like this::
236
- # $ /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping execute
237
-
238
- # Okay to use __file__ here because we're running from a kept file
239
- basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'debug_dir')
240
- args_path = os.path.join(basedir, 'args')
241
-
242
- if command == 'explode':
243
- # transform the ZIPDATA into an exploded directory of code and then
244
- # print the path to the code. This is an easy way for people to look
245
- # at the code on the remote machine for debugging it in that
246
- # environment
247
- z = zipfile.ZipFile(zipped_mod)
248
- for filename in z.namelist():
249
- if filename.startswith('/'):
250
- raise Exception('Something wrong with this module zip file: should not contain absolute paths')
251
-
252
- dest_filename = os.path.join(basedir, filename)
253
- if dest_filename.endswith(os.path.sep) and not os.path.exists(dest_filename):
254
- os.makedirs(dest_filename)
255
- else:
256
- directory = os.path.dirname(dest_filename)
257
- if not os.path.exists(directory):
258
- os.makedirs(directory)
259
- f = open(dest_filename, 'wb')
260
- f.write(z.read(filename))
261
- f.close()
262
-
263
- # write the args file
264
- f = open(args_path, 'wb')
265
- f.write(json_params)
266
- f.close()
267
-
268
- print('Module expanded into:')
269
- print('%%s' %% basedir)
270
- exitcode = 0
271
-
272
- elif command == 'execute':
273
- # Execute the exploded code instead of executing the module from the
274
- # embedded ZIPDATA. This allows people to easily run their modified
275
- # code on the remote machine to see how changes will affect it.
276
-
277
- # Set pythonpath to the debug dir
278
- sys.path.insert(0, basedir)
279
-
280
- # read in the args file which the user may have modified
281
- with open(args_path, 'rb') as f:
282
- json_params = f.read()
283
-
284
- # Monkeypatch the parameters into basic
285
- from ansible.module_utils import basic
286
- basic._ANSIBLE_ARGS = json_params
287
-
288
- # Run the module! By importing it as '__main__', it thinks it is executing as a script
289
- runpy.run_module(mod_name=%(module_fqn)r, init_globals=None, run_name='__main__', alter_sys=True)
290
-
291
- # Ansible modules must exit themselves
292
- print('{"msg": "New-style module did not handle its own exit", "failed": true}')
293
- sys.exit(1)
294
-
295
- else:
296
- print('WARNING: Unknown debug command. Doing nothing.')
297
- exitcode = 0
298
-
299
- return exitcode
300
-
301
- #
302
- # See comments in the debug() method for information on debugging
303
- #
304
-
305
- ANSIBALLZ_PARAMS = %(params)s
306
- if PY3:
307
- ANSIBALLZ_PARAMS = ANSIBALLZ_PARAMS.encode('utf-8')
308
- try:
309
- # There's a race condition with the controller removing the
310
- # remote_tmpdir and this module executing under async. So we cannot
311
- # store this in remote_tmpdir (use system tempdir instead)
312
- # Only need to use [ansible_module]_payload_ in the temp_path until we move to zipimport
313
- # (this helps ansible-test produce coverage stats)
314
- temp_path = tempfile.mkdtemp(prefix='ansible_' + %(ansible_module)r + '_payload_')
315
-
316
- zipped_mod = os.path.join(temp_path, 'ansible_' + %(ansible_module)r + '_payload.zip')
317
-
318
- with open(zipped_mod, 'wb') as modlib:
319
- modlib.write(base64.b64decode(ZIPDATA))
320
-
321
- if len(sys.argv) == 2:
322
- exitcode = debug(sys.argv[1], zipped_mod, ANSIBALLZ_PARAMS)
323
- else:
324
- # Note: temp_path isn't needed once we switch to zipimport
325
- invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
326
- finally:
327
- try:
328
- shutil.rmtree(temp_path)
329
- except (NameError, OSError):
330
- # tempdir creation probably failed
331
- pass
332
- sys.exit(exitcode)
333
-
334
- if __name__ == '__main__':
335
- _ansiballz_main()
336
- '''
337
-
338
- ANSIBALLZ_COVERAGE_TEMPLATE = '''
339
- os.environ['COVERAGE_FILE'] = %(coverage_output)r + '=python-%%s=coverage' %% '.'.join(str(v) for v in sys.version_info[:2])
340
-
341
- import atexit
342
-
343
- try:
344
- import coverage
345
- except ImportError:
346
- print('{"msg": "Could not import `coverage` module.", "failed": true}')
347
- sys.exit(1)
348
-
349
- cov = coverage.Coverage(config_file=%(coverage_config)r)
350
110
 
351
- def atexit_coverage():
352
- cov.stop()
353
- cov.save()
111
+ def _strip_comments(source: str) -> str:
112
+ # Strip comments and blank lines from the wrapper
113
+ buf = []
114
+ for line in source.splitlines():
115
+ l = line.strip()
116
+ if (not l or l.startswith('#')) and l != _SHEBANG_PLACEHOLDER:
117
+ line = ''
118
+ buf.append(line)
119
+ return '\n'.join(buf)
354
120
 
355
- atexit.register(atexit_coverage)
356
121
 
357
- cov.start()
358
- '''
122
+ def _read_ansiballz_code() -> str:
123
+ code = (pathlib.Path(_ansiballz.__file__).parent / '_wrapper.py').read_text()
359
124
 
360
- ANSIBALLZ_COVERAGE_CHECK_TEMPLATE = '''
361
- try:
362
- if PY3:
363
- import importlib.util
364
- if importlib.util.find_spec('coverage') is None:
365
- raise ImportError
366
- else:
367
- import imp
368
- imp.find_module('coverage')
369
- except ImportError:
370
- print('{"msg": "Could not find `coverage` module.", "failed": true}')
371
- sys.exit(1)
372
- '''
125
+ if not C.DEFAULT_KEEP_REMOTE_FILES:
126
+ # Keep comments when KEEP_REMOTE_FILES is set. That way users will see
127
+ # the comments with some nice usage instructions.
128
+ # Otherwise, strip comments for smaller over the wire size.
129
+ code = _strip_comments(code)
373
130
 
374
- ANSIBALLZ_RLIMIT_TEMPLATE = '''
375
- import resource
131
+ return code
376
132
 
377
- existing_soft, existing_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
378
133
 
379
- # adjust soft limit subject to existing hard limit
380
- requested_soft = min(existing_hard, %(rlimit_nofile)d)
134
+ _ANSIBALLZ_CODE = _read_ansiballz_code() # read during startup to prevent individual workers from doing so
381
135
 
382
- if requested_soft != existing_soft:
383
- try:
384
- resource.setrlimit(resource.RLIMIT_NOFILE, (requested_soft, existing_hard))
385
- except ValueError:
386
- # some platforms (eg macOS) lie about their hard limit
387
- pass
388
- '''
389
136
 
137
+ def _get_ansiballz_code(shebang: str) -> str:
138
+ code = _ANSIBALLZ_CODE
139
+ code = code.replace(_SHEBANG_PLACEHOLDER, shebang)
390
140
 
391
- def _strip_comments(source):
392
- # Strip comments and blank lines from the wrapper
393
- buf = []
394
- for line in source.splitlines():
395
- l = line.strip()
396
- if not l or l.startswith(u'#'):
397
- continue
398
- buf.append(line)
399
- return u'\n'.join(buf)
400
-
141
+ return code
401
142
 
402
- if C.DEFAULT_KEEP_REMOTE_FILES:
403
- # Keep comments when KEEP_REMOTE_FILES is set. That way users will see
404
- # the comments with some nice usage instructions
405
- ACTIVE_ANSIBALLZ_TEMPLATE = ANSIBALLZ_TEMPLATE
406
- else:
407
- # ANSIBALLZ_TEMPLATE stripped of comments for smaller over the wire size
408
- ACTIVE_ANSIBALLZ_TEMPLATE = _strip_comments(ANSIBALLZ_TEMPLATE)
409
143
 
410
144
  # dirname(dirname(dirname(site-packages/ansible/executor/module_common.py) == site-packages
411
145
  # Do this instead of getting site-packages from distutils.sysconfig so we work when we
@@ -435,6 +169,7 @@ NEW_STYLE_PYTHON_MODULE_RE = re.compile(
435
169
 
436
170
 
437
171
  class ModuleDepFinder(ast.NodeVisitor):
172
+ # DTFIX-FUTURE: add support for ignoring imports with a "controller only" comment, this will allow replacing import_controller_module with standard imports
438
173
  def __init__(self, module_fqn, tree, is_pkg_init=False, *args, **kwargs):
439
174
  """
440
175
  Walk the ast tree for the python module.
@@ -581,7 +316,7 @@ def _slurp(path):
581
316
  return data
582
317
 
583
318
 
584
- def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=False):
319
+ def _get_shebang(interpreter, task_vars, templar: _template.Templar, args=tuple(), remote_is_local=False):
585
320
  """
586
321
  Handles the different ways ansible allows overriding the shebang target for a module.
587
322
  """
@@ -606,7 +341,8 @@ def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=
606
341
  elif C.config.get_configuration_definition(interpreter_config_key):
607
342
 
608
343
  interpreter_from_config = C.config.get_config_value(interpreter_config_key, variables=task_vars)
609
- interpreter_out = templar.template(interpreter_from_config.strip())
344
+ interpreter_out = templar._engine.template(_utils.str_problematic_strip(interpreter_from_config),
345
+ options=TemplateOptions(value_for_omit=C.config.get_config_default(interpreter_config_key)))
610
346
 
611
347
  # handle interpreter discovery if requested or empty interpreter was provided
612
348
  if not interpreter_out or interpreter_out in ['auto', 'auto_legacy', 'auto_silent', 'auto_legacy_silent']:
@@ -624,10 +360,11 @@ def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=
624
360
 
625
361
  elif interpreter_config in task_vars:
626
362
  # for non python we consult vars for a possible direct override
627
- interpreter_out = templar.template(task_vars.get(interpreter_config).strip())
363
+ interpreter_out = templar._engine.template(_utils.str_problematic_strip(task_vars.get(interpreter_config)),
364
+ options=TemplateOptions(value_for_omit=None))
628
365
 
629
366
  if not interpreter_out:
630
- # nothing matched(None) or in case someone configures empty string or empty intepreter
367
+ # nothing matched(None) or in case someone configures empty string or empty interpreter
631
368
  interpreter_out = interpreter
632
369
 
633
370
  # set shebang
@@ -700,7 +437,13 @@ class ModuleUtilLocatorBase:
700
437
  else:
701
438
  msg += '.'
702
439
 
703
- display.deprecated(msg, removal_version, removed, removal_date, self._collection_name)
440
+ display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
441
+ msg=msg,
442
+ version=removal_version,
443
+ removed=removed,
444
+ date=removal_date,
445
+ deprecator=deprecator_from_collection_name(self._collection_name),
446
+ )
704
447
  if 'redirect' in routing_entry:
705
448
  self.redirected = True
706
449
  source_pkg = '.'.join(name_parts)
@@ -803,12 +546,12 @@ class LegacyModuleUtilLocator(ModuleUtilLocatorBase):
803
546
 
804
547
  # find_spec needs the full module name
805
548
  self._info = info = importlib.machinery.PathFinder.find_spec('.'.join(name_parts), paths)
806
- if info is not None and os.path.splitext(info.origin)[1] in importlib.machinery.SOURCE_SUFFIXES:
549
+ if info is not None and info.origin is not None and os.path.splitext(info.origin)[1] in importlib.machinery.SOURCE_SUFFIXES:
807
550
  self.is_package = info.origin.endswith('/__init__.py')
808
551
  path = info.origin
809
552
  else:
810
553
  return False
811
- self.source_code = _slurp(path)
554
+ self.source_code = Origin(path=path).tag(_slurp(path))
812
555
 
813
556
  return True
814
557
 
@@ -843,9 +586,18 @@ class CollectionModuleUtilLocator(ModuleUtilLocatorBase):
843
586
  resource_base_path = os.path.join(*name_parts[3:])
844
587
 
845
588
  src = None
589
+
846
590
  # look for package_dir first, then module
591
+ src_path = to_native(os.path.join(resource_base_path, '__init__.py'))
592
+
847
593
  try:
848
- src = pkgutil.get_data(collection_pkg_name, to_native(os.path.join(resource_base_path, '__init__.py')))
594
+ collection_pkg = importlib.import_module(collection_pkg_name)
595
+ pkg_path = os.path.dirname(collection_pkg.__file__)
596
+ except (ImportError, AttributeError):
597
+ pkg_path = None
598
+
599
+ try:
600
+ src = pkgutil.get_data(collection_pkg_name, src_path)
849
601
  except ImportError:
850
602
  pass
851
603
 
@@ -854,32 +606,123 @@ class CollectionModuleUtilLocator(ModuleUtilLocatorBase):
854
606
  if src is not None: # empty string is OK
855
607
  self.is_package = True
856
608
  else:
609
+ src_path = to_native(resource_base_path + '.py')
610
+
857
611
  try:
858
- src = pkgutil.get_data(collection_pkg_name, to_native(resource_base_path + '.py'))
612
+ src = pkgutil.get_data(collection_pkg_name, src_path)
859
613
  except ImportError:
860
614
  pass
861
615
 
862
616
  if src is None: # empty string is OK
863
617
  return False
864
618
 
865
- self.source_code = src
619
+ # TODO: this feels brittle and funky; we should be able to more definitively assure the source path
620
+
621
+ if pkg_path:
622
+ origin = Origin(path=os.path.join(pkg_path, src_path))
623
+ else:
624
+ # DTFIX-FUTURE: not sure if this case is even reachable
625
+ origin = Origin(description=f'<synthetic collection package for {collection_pkg_name}!r>')
626
+
627
+ self.source_code = origin.tag(src)
866
628
  return True
867
629
 
868
630
  def _get_module_utils_remainder_parts(self, name_parts):
869
631
  return name_parts[5:] # eg, foo.bar for ansible_collections.ns.coll.plugins.module_utils.foo.bar
870
632
 
871
633
 
872
- def _make_zinfo(filename, date_time, zf=None):
634
+ def _make_zinfo(filename: str, date_time: datetime.datetime, zf: zipfile.ZipFile | None = None) -> zipfile.ZipInfo:
873
635
  zinfo = zipfile.ZipInfo(
874
636
  filename=filename,
875
- date_time=date_time
637
+ date_time=date_time.utctimetuple()[:6],
876
638
  )
639
+
877
640
  if zf:
878
641
  zinfo.compress_type = zf.compression
642
+
879
643
  return zinfo
880
644
 
881
645
 
882
- def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
646
+ @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
647
+ class ModuleMetadata:
648
+ @classmethod
649
+ def __post_init__(cls):
650
+ _dataclass_validation.inject_post_init_validation(cls)
651
+
652
+
653
+ @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
654
+ class ModuleMetadataV1(ModuleMetadata):
655
+ serialization_profile: str
656
+
657
+
658
+ metadata_versions: dict[t.Any, type[ModuleMetadata]] = {
659
+ 1: ModuleMetadataV1,
660
+ }
661
+
662
+ _DEFAULT_LEGACY_METADATA = ModuleMetadataV1(serialization_profile='legacy')
663
+
664
+
665
+ def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
666
+ # experimental module metadata; off by default
667
+ if not C.config.get_config_value('_MODULE_METADATA'):
668
+ return _DEFAULT_LEGACY_METADATA
669
+
670
+ metadata_nodes: list[ast.Assign] = []
671
+
672
+ for node in module.body:
673
+ if isinstance(node, ast.Assign):
674
+ if len(node.targets) == 1:
675
+ target = node.targets[0]
676
+
677
+ if isinstance(target, ast.Name):
678
+ if target.id == 'METADATA':
679
+ metadata_nodes.append(node)
680
+
681
+ if not metadata_nodes:
682
+ return _DEFAULT_LEGACY_METADATA
683
+
684
+ if len(metadata_nodes) > 1:
685
+ raise ValueError('Module METADATA must defined only once.')
686
+
687
+ metadata_node = metadata_nodes[0]
688
+
689
+ if not isinstance(metadata_node.value, ast.Constant):
690
+ raise TypeError(f'Module METADATA node must be {ast.Constant} not {type(metadata_node)}.')
691
+
692
+ unparsed_metadata = metadata_node.value.value
693
+
694
+ if not isinstance(unparsed_metadata, str):
695
+ raise TypeError(f'Module METADATA must be {str} not {type(unparsed_metadata)}.')
696
+
697
+ try:
698
+ parsed_metadata = yaml_load(unparsed_metadata)
699
+ except Exception as ex:
700
+ raise ValueError('Module METADATA must be valid YAML.') from ex
701
+
702
+ if not isinstance(parsed_metadata, dict):
703
+ raise TypeError(f'Module METADATA must parse to {dict} not {type(parsed_metadata)}.')
704
+
705
+ schema_version = parsed_metadata.pop('schema_version', None)
706
+
707
+ if not (metadata_type := metadata_versions.get(schema_version)):
708
+ raise ValueError(f'Module METADATA schema_version {schema_version} is unknown.')
709
+
710
+ try:
711
+ metadata = metadata_type(**parsed_metadata) # type: ignore
712
+ except Exception as ex:
713
+ raise ValueError('Module METADATA is invalid.') from ex
714
+
715
+ return metadata
716
+
717
+
718
+ def recursive_finder(
719
+ name: str,
720
+ module_fqn: str,
721
+ module_data: str | bytes,
722
+ zf: zipfile.ZipFile,
723
+ date_time: datetime.datetime,
724
+ extension_manager: _builder.ExtensionManager,
725
+ ) -> ModuleMetadata:
883
726
  """
884
727
  Using ModuleDepFinder, make sure we have all of the module_utils files that
885
728
  the module and its module_utils files needs. (no longer actually recursive)
@@ -889,9 +732,6 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
889
732
  :arg zf: An open :python:class:`zipfile.ZipFile` object that holds the Ansible module payload
890
733
  which we're assembling
891
734
  """
892
- if date_time is None:
893
- date_time = time.gmtime()[:6]
894
-
895
735
  # py_module_cache maps python module names to a tuple of the code in the module
896
736
  # and the pathname to the module.
897
737
  # Here we pre-load it with modules which we create without bothering to
@@ -913,49 +753,59 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
913
753
  module_utils_paths = [p for p in module_utils_loader._get_paths(subdirs=False) if os.path.isdir(p)]
914
754
  module_utils_paths.append(_MODULE_UTILS_PATH)
915
755
 
916
- # Parse the module code and find the imports of ansible.module_utils
917
- try:
918
- tree = compile(module_data, '<unknown>', 'exec', ast.PyCF_ONLY_AST)
919
- except (SyntaxError, IndentationError) as e:
920
- raise AnsibleError("Unable to import %s due to %s" % (name, e.msg))
921
-
756
+ tree = _compile_module_ast(name, module_data)
757
+ module_metadata = _get_module_metadata(tree)
922
758
  finder = ModuleDepFinder(module_fqn, tree)
923
759
 
924
- # the format of this set is a tuple of the module name and whether or not the import is ambiguous as a module name
925
- # or an attribute of a module (eg from x.y import z <-- is z a module or an attribute of x.y?)
926
- modules_to_process = [ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports) for m in finder.submodules]
760
+ if not isinstance(module_metadata, ModuleMetadataV1):
761
+ raise NotImplementedError()
927
762
 
928
- # HACK: basic is currently always required since module global init is currently tied up with AnsiballZ arg input
929
- modules_to_process.append(ModuleUtilsProcessEntry(('ansible', 'module_utils', 'basic'), False, False, is_optional=False))
763
+ profile = module_metadata.serialization_profile
764
+
765
+ # the format of this set is a tuple of the module name and whether the import is ambiguous as a module name
766
+ # or an attribute of a module (e.g. from x.y import z <-- is z a module or an attribute of x.y?)
767
+ modules_to_process = [_ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports) for m in finder.submodules]
768
+
769
+ # include module_utils that are always required
770
+ modules_to_process.extend((
771
+ _ModuleUtilsProcessEntry.from_module(_loader),
772
+ _ModuleUtilsProcessEntry.from_module(_basic),
773
+ _ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, True)),
774
+ _ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, False)),
775
+ ))
776
+
777
+ modules_to_process.extend(_ModuleUtilsProcessEntry.from_module_name(name) for name in extension_manager.module_names)
778
+
779
+ module_info: ModuleUtilLocatorBase
930
780
 
931
781
  # we'll be adding new modules inline as we discover them, so just keep going til we've processed them all
932
782
  while modules_to_process:
933
783
  modules_to_process.sort() # not strictly necessary, but nice to process things in predictable and repeatable order
934
- py_module_name, is_ambiguous, child_is_redirected, is_optional = modules_to_process.pop(0)
784
+ entry = modules_to_process.pop(0)
935
785
 
936
- if py_module_name in py_module_cache:
786
+ if entry.name_parts in py_module_cache:
937
787
  # this is normal; we'll often see the same module imported many times, but we only need to process it once
938
788
  continue
939
789
 
940
- if py_module_name[0:2] == ('ansible', 'module_utils'):
941
- module_info = LegacyModuleUtilLocator(py_module_name, is_ambiguous=is_ambiguous,
942
- mu_paths=module_utils_paths, child_is_redirected=child_is_redirected)
943
- elif py_module_name[0] == 'ansible_collections':
944
- module_info = CollectionModuleUtilLocator(py_module_name, is_ambiguous=is_ambiguous,
945
- child_is_redirected=child_is_redirected, is_optional=is_optional)
790
+ if entry.name_parts[0:2] == ('ansible', 'module_utils'):
791
+ module_info = LegacyModuleUtilLocator(entry.name_parts, is_ambiguous=entry.is_ambiguous,
792
+ mu_paths=module_utils_paths, child_is_redirected=entry.child_is_redirected)
793
+ elif entry.name_parts[0] == 'ansible_collections':
794
+ module_info = CollectionModuleUtilLocator(entry.name_parts, is_ambiguous=entry.is_ambiguous,
795
+ child_is_redirected=entry.child_is_redirected, is_optional=entry.is_optional)
946
796
  else:
947
797
  # FIXME: dot-joined result
948
798
  display.warning('ModuleDepFinder improperly found a non-module_utils import %s'
949
- % [py_module_name])
799
+ % [entry.name_parts])
950
800
  continue
951
801
 
952
802
  # Could not find the module. Construct a helpful error message.
953
803
  if not module_info.found:
954
- if is_optional:
804
+ if entry.is_optional:
955
805
  # this was a best-effort optional import that we couldn't find, oh well, move along...
956
806
  continue
957
807
  # FIXME: use dot-joined candidate names
958
- msg = 'Could not find imported module support code for {0}. Looked for ({1})'.format(module_fqn, module_info.candidate_names_joined)
808
+ msg = 'Could not find imported module support code for {0}. Looked for ({1})'.format(module_fqn, module_info.candidate_names_joined)
959
809
  raise AnsibleError(msg)
960
810
 
961
811
  # check the cache one more time with the module we actually found, since the name could be different than the input
@@ -963,14 +813,9 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
963
813
  if module_info.fq_name_parts in py_module_cache:
964
814
  continue
965
815
 
966
- # compile the source, process all relevant imported modules
967
- try:
968
- tree = compile(module_info.source_code, '<unknown>', 'exec', ast.PyCF_ONLY_AST)
969
- except (SyntaxError, IndentationError) as e:
970
- raise AnsibleError("Unable to import %s due to %s" % (module_info.fq_name_parts, e.msg))
971
-
816
+ tree = _compile_module_ast('.'.join(module_info.fq_name_parts), module_info.source_code)
972
817
  finder = ModuleDepFinder('.'.join(module_info.fq_name_parts), tree, module_info.is_package)
973
- modules_to_process.extend(ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports)
818
+ modules_to_process.extend(_ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports)
974
819
  for m in finder.submodules if m not in py_module_cache)
975
820
 
976
821
  # we've processed this item, add it to the output list
@@ -982,20 +827,36 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
982
827
  accumulated_pkg_name.append(pkg) # we're accumulating this across iterations
983
828
  normalized_name = tuple(accumulated_pkg_name) # extra machinations to get a hashable type (list is not)
984
829
  if normalized_name not in py_module_cache:
985
- modules_to_process.append(ModuleUtilsProcessEntry(normalized_name, False, module_info.redirected, is_optional=is_optional))
830
+ modules_to_process.append(_ModuleUtilsProcessEntry(normalized_name, False, module_info.redirected, is_optional=entry.is_optional))
986
831
 
987
832
  for py_module_name in py_module_cache:
988
- py_module_file_name = py_module_cache[py_module_name][1]
833
+ source_code, py_module_file_name = py_module_cache[py_module_name]
834
+
835
+ zf.writestr(_make_zinfo(py_module_file_name, date_time, zf=zf), source_code)
836
+
837
+ if extension_manager.debugger_enabled and (origin := Origin.get_tag(source_code)) and origin.path:
838
+ extension_manager.source_mapping[origin.path] = py_module_file_name
989
839
 
990
- zf.writestr(
991
- _make_zinfo(py_module_file_name, date_time, zf=zf),
992
- py_module_cache[py_module_name][0]
993
- )
994
840
  mu_file = to_text(py_module_file_name, errors='surrogate_or_strict')
995
841
  display.vvvvv("Including module_utils file %s" % mu_file)
996
842
 
843
+ return module_metadata
844
+
845
+
846
+ def _compile_module_ast(module_name: str, source_code: str | bytes) -> ast.Module:
847
+ origin = Origin.get_tag(source_code) or Origin.UNKNOWN
848
+
849
+ # compile the source, process all relevant imported modules
850
+ try:
851
+ tree = t.cast(ast.Module, compile(source_code, str(origin), 'exec', ast.PyCF_ONLY_AST))
852
+ except SyntaxError as ex:
853
+ raise AnsibleError(f"Unable to compile {module_name!r}.", obj=origin.replace(line_num=ex.lineno, col_num=ex.offset)) from ex
854
+
855
+ return tree
856
+
997
857
 
998
858
  def _is_binary(b_module_data):
859
+ """Heuristic to classify a file as binary by sniffing a 1k header; see https://stackoverflow.com/a/7392391"""
999
860
  textchars = bytearray(set([7, 8, 9, 10, 12, 13, 27]) | set(range(0x20, 0x100)) - set([0x7f]))
1000
861
  start = b_module_data[:1024]
1001
862
  return bool(start.translate(None, textchars))
@@ -1034,17 +895,29 @@ def _get_ansible_module_fqn(module_path):
1034
895
  return remote_module_fqn
1035
896
 
1036
897
 
1037
- def _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data):
898
+ def _add_module_to_zip(
899
+ zf: zipfile.ZipFile,
900
+ date_time: datetime.datetime,
901
+ remote_module_fqn: str,
902
+ b_module_data: bytes,
903
+ module_path: str,
904
+ extension_manager: _builder.ExtensionManager,
905
+ ) -> None:
1038
906
  """Add a module from ansible or from an ansible collection into the module zip"""
1039
907
  module_path_parts = remote_module_fqn.split('.')
1040
908
 
1041
909
  # Write the module
1042
- module_path = '/'.join(module_path_parts) + '.py'
910
+ zip_module_path = '/'.join(module_path_parts) + '.py'
1043
911
  zf.writestr(
1044
- _make_zinfo(module_path, date_time, zf=zf),
912
+ _make_zinfo(zip_module_path, date_time, zf=zf),
1045
913
  b_module_data
1046
914
  )
1047
915
 
916
+ if extension_manager.debugger_enabled:
917
+ extension_manager.source_mapping[module_path] = zip_module_path
918
+
919
+ existing_paths: frozenset[str]
920
+
1048
921
  # Write the __init__.py's necessary to get there
1049
922
  if module_path_parts[0] == 'ansible':
1050
923
  # The ansible namespace is setup as part of the module_utils setup...
@@ -1068,12 +941,60 @@ def _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data):
1068
941
  )
1069
942
 
1070
943
 
1071
- def _find_module_utils(module_name, b_module_data, module_path, module_args, task_vars, templar, module_compression, async_timeout, become,
1072
- become_method, become_user, become_password, become_flags, environment, remote_is_local=False):
944
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
945
+ class _BuiltModule:
946
+ """Payload required to execute an Ansible module, along with information required to do so."""
947
+ b_module_data: bytes
948
+ module_style: t.Literal['binary', 'new', 'non_native_want_json', 'old']
949
+ shebang: str | None
950
+ serialization_profile: str
951
+
952
+
953
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
954
+ class _CachedModule:
955
+ """Cached Python module created by AnsiballZ."""
956
+
957
+ # FIXME: switch this to use a locked down pickle config or don't use pickle- easy to mess up and reach objects that shouldn't be pickled
958
+
959
+ zip_data: bytes
960
+ metadata: ModuleMetadata
961
+ source_mapping: dict[str, str]
962
+ """A mapping of controller absolute source locations to target relative source locations within the AnsiballZ payload."""
963
+
964
+ def dump(self, path: str) -> None:
965
+ temp_path = pathlib.Path(path + '-part')
966
+
967
+ with temp_path.open('wb') as cache_file:
968
+ pickle.dump(self, cache_file)
969
+
970
+ temp_path.rename(path)
971
+
972
+ @classmethod
973
+ def load(cls, path: str) -> t.Self:
974
+ with pathlib.Path(path).open('rb') as cache_file:
975
+ return pickle.load(cache_file)
976
+
977
+
978
+ def _find_module_utils(
979
+ *,
980
+ module_name: str,
981
+ b_module_data: bytes,
982
+ module_path: str,
983
+ module_args: dict[object, object],
984
+ task_vars: dict[str, object],
985
+ templar: Templar,
986
+ module_compression: str,
987
+ async_timeout: int,
988
+ become_plugin: BecomeBase | None,
989
+ environment: dict[str, str],
990
+ remote_is_local: bool = False
991
+ ) -> _BuiltModule:
1073
992
  """
1074
993
  Given the source of the module, convert it to a Jinja2 template to insert
1075
994
  module code and return whether it's a new or old style module.
1076
995
  """
996
+ module_substyle: t.Literal['binary', 'jsonargs', 'non_native_want_json', 'old', 'powershell', 'python']
997
+ module_style: t.Literal['binary', 'new', 'non_native_want_json', 'old']
1077
998
  module_substyle = module_style = 'old'
1078
999
 
1079
1000
  # module_style is something important to calling code (ActionBase). It
@@ -1096,12 +1017,10 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1096
1017
  elif REPLACER_WINDOWS in b_module_data:
1097
1018
  module_style = 'new'
1098
1019
  module_substyle = 'powershell'
1099
- b_module_data = b_module_data.replace(REPLACER_WINDOWS, b'#Requires -Module Ansible.ModuleUtils.Legacy')
1020
+ b_module_data = b_module_data.replace(REPLACER_WINDOWS, b'#AnsibleRequires -PowerShell Ansible.ModuleUtils.Legacy')
1100
1021
  elif re.search(b'#Requires -Module', b_module_data, re.IGNORECASE) \
1101
- or re.search(b'#Requires -Version', b_module_data, re.IGNORECASE)\
1102
- or re.search(b'#AnsibleRequires -OSVersion', b_module_data, re.IGNORECASE) \
1103
- or re.search(b'#AnsibleRequires -Powershell', b_module_data, re.IGNORECASE) \
1104
- or re.search(b'#AnsibleRequires -CSharpUtil', b_module_data, re.IGNORECASE):
1022
+ or re.search(b'#Requires -Version', b_module_data, re.IGNORECASE) \
1023
+ or re.search(b'#AnsibleRequires -(OSVersion|PowerShell|CSharpUtil|Wrapper)', b_module_data, re.IGNORECASE):
1105
1024
  module_style = 'new'
1106
1025
  module_substyle = 'powershell'
1107
1026
  elif REPLACER_JSONARGS in b_module_data:
@@ -1114,7 +1033,12 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1114
1033
  # Neither old-style, non_native_want_json nor binary modules should be modified
1115
1034
  # except for the shebang line (Done by modify_module)
1116
1035
  if module_style in ('old', 'non_native_want_json', 'binary'):
1117
- return b_module_data, module_style, shebang
1036
+ return _BuiltModule(
1037
+ b_module_data=b_module_data,
1038
+ module_style=module_style,
1039
+ shebang=shebang,
1040
+ serialization_profile='legacy',
1041
+ )
1118
1042
 
1119
1043
  output = BytesIO()
1120
1044
 
@@ -1127,18 +1051,15 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1127
1051
  # People should start writing collections instead of modules in roles so we
1128
1052
  # may never fix this
1129
1053
  display.debug('ANSIBALLZ: Could not determine module FQN')
1130
- remote_module_fqn = 'ansible.modules.%s' % module_name
1054
+ # FIXME: add integration test to validate that builtins and legacy modules with the same name are tracked separately by the caching mechanism
1055
+ # FIXME: surrogate FQN should be unique per source path- role-packaged modules with name collisions can still be aliased
1056
+ remote_module_fqn = 'ansible.legacy.%s' % module_name
1131
1057
 
1132
1058
  if module_substyle == 'python':
1133
- date_time = time.gmtime()[:6]
1134
- if date_time[0] < 1980:
1135
- date_string = datetime.datetime(*date_time, tzinfo=datetime.timezone.utc).strftime('%c')
1136
- raise AnsibleError(f'Cannot create zipfile due to pre-1980 configured date: {date_string}')
1137
- params = dict(ANSIBLE_MODULE_ARGS=module_args,)
1138
- try:
1139
- python_repred_params = repr(json.dumps(params, cls=AnsibleJSONEncoder, vault_to_text=True))
1140
- except TypeError as e:
1141
- raise AnsibleError("Unable to pass options to module, they must be JSON serializable: %s" % to_native(e))
1059
+ date_time = datetime.datetime.now(datetime.timezone.utc)
1060
+
1061
+ if date_time.year < 1980:
1062
+ raise AnsibleError(f'Cannot create zipfile due to pre-1980 configured date: {date_time}')
1142
1063
 
1143
1064
  try:
1144
1065
  compression_method = getattr(zipfile, module_compression)
@@ -1146,30 +1067,24 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1146
1067
  display.warning(u'Bad module compression string specified: %s. Using ZIP_STORED (no compression)' % module_compression)
1147
1068
  compression_method = zipfile.ZIP_STORED
1148
1069
 
1149
- lookup_path = os.path.join(C.DEFAULT_LOCAL_TMP, 'ansiballz_cache')
1150
- cached_module_filename = os.path.join(lookup_path, "%s-%s" % (remote_module_fqn, module_compression))
1070
+ extension_manager = _builder.ExtensionManager.create(task_vars=task_vars)
1071
+ extension_key = '~'.join(extension_manager.extension_names) if extension_manager.extension_names else 'none'
1072
+ lookup_path = os.path.join(C.DEFAULT_LOCAL_TMP, 'ansiballz_cache') # type: ignore[attr-defined]
1073
+ cached_module_filename = os.path.join(lookup_path, '-'.join((remote_module_fqn, module_compression, extension_key)))
1074
+
1075
+ os.makedirs(os.path.dirname(cached_module_filename), exist_ok=True)
1076
+
1077
+ cached_module: _CachedModule | None = None
1151
1078
 
1152
- zipdata = None
1153
1079
  # Optimization -- don't lock if the module has already been cached
1154
1080
  if os.path.exists(cached_module_filename):
1155
1081
  display.debug('ANSIBALLZ: using cached module: %s' % cached_module_filename)
1156
- with open(cached_module_filename, 'rb') as module_data:
1157
- zipdata = module_data.read()
1082
+ cached_module = _CachedModule.load(cached_module_filename)
1158
1083
  else:
1159
- if module_name in action_write_locks.action_write_locks:
1160
- display.debug('ANSIBALLZ: Using lock for %s' % module_name)
1161
- lock = action_write_locks.action_write_locks[module_name]
1162
- else:
1163
- # If the action plugin directly invokes the module (instead of
1164
- # going through a strategy) then we don't have a cross-process
1165
- # Lock specifically for this module. Use the "unexpected
1166
- # module" lock instead
1167
- display.debug('ANSIBALLZ: Using generic lock for %s' % module_name)
1168
- lock = action_write_locks.action_write_locks[None]
1169
-
1170
1084
  display.debug('ANSIBALLZ: Acquiring lock')
1171
- with lock:
1172
- display.debug('ANSIBALLZ: Lock acquired: %s' % id(lock))
1085
+ lock_path = f'{cached_module_filename}.lock'
1086
+ with _locking.named_mutex(lock_path):
1087
+ display.debug(f'ANSIBALLZ: Lock acquired: {lock_path}')
1173
1088
  # Check that no other process has created this while we were
1174
1089
  # waiting for the lock
1175
1090
  if not os.path.exists(cached_module_filename):
@@ -1179,53 +1094,40 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1179
1094
  zf = zipfile.ZipFile(zipoutput, mode='w', compression=compression_method)
1180
1095
 
1181
1096
  # walk the module imports, looking for module_utils to send- they'll be added to the zipfile
1182
- recursive_finder(module_name, remote_module_fqn, b_module_data, zf, date_time)
1097
+ module_metadata = recursive_finder(
1098
+ module_name,
1099
+ remote_module_fqn,
1100
+ Origin(path=module_path).tag(b_module_data),
1101
+ zf,
1102
+ date_time,
1103
+ extension_manager,
1104
+ )
1183
1105
 
1184
1106
  display.debug('ANSIBALLZ: Writing module into payload')
1185
- _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data)
1107
+ _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data, module_path, extension_manager)
1186
1108
 
1187
1109
  zf.close()
1188
- zipdata = base64.b64encode(zipoutput.getvalue())
1110
+ zip_data = base64.b64encode(zipoutput.getvalue())
1189
1111
 
1190
1112
  # Write the assembled module to a temp file (write to temp
1191
1113
  # so that no one looking for the file reads a partially
1192
1114
  # written file)
1193
- #
1194
- # FIXME: Once split controller/remote is merged, this can be simplified to
1195
- # os.makedirs(lookup_path, exist_ok=True)
1196
- if not os.path.exists(lookup_path):
1197
- try:
1198
- # Note -- if we have a global function to setup, that would
1199
- # be a better place to run this
1200
- os.makedirs(lookup_path)
1201
- except OSError:
1202
- # Multiple processes tried to create the directory. If it still does not
1203
- # exist, raise the original exception.
1204
- if not os.path.exists(lookup_path):
1205
- raise
1115
+ os.makedirs(lookup_path, exist_ok=True)
1206
1116
  display.debug('ANSIBALLZ: Writing module')
1207
- with open(cached_module_filename + '-part', 'wb') as f:
1208
- f.write(zipdata)
1209
-
1210
- # Rename the file into its final position in the cache so
1211
- # future users of this module can read it off the
1212
- # filesystem instead of constructing from scratch.
1213
- display.debug('ANSIBALLZ: Renaming module')
1214
- os.rename(cached_module_filename + '-part', cached_module_filename)
1117
+ cached_module = _CachedModule(zip_data=zip_data, metadata=module_metadata, source_mapping=extension_manager.source_mapping)
1118
+ cached_module.dump(cached_module_filename)
1215
1119
  display.debug('ANSIBALLZ: Done creating module')
1216
1120
 
1217
- if zipdata is None:
1121
+ if not cached_module:
1218
1122
  display.debug('ANSIBALLZ: Reading module after lock')
1219
1123
  # Another process wrote the file while we were waiting for
1220
1124
  # the write lock. Go ahead and read the data from disk
1221
1125
  # instead of re-creating it.
1222
1126
  try:
1223
- with open(cached_module_filename, 'rb') as f:
1224
- zipdata = f.read()
1225
- except IOError:
1127
+ cached_module = _CachedModule.load(cached_module_filename)
1128
+ except OSError as ex:
1226
1129
  raise AnsibleError('A different worker process failed to create module file. '
1227
- 'Look at traceback for that process for debugging information.')
1228
- zipdata = to_text(zipdata, errors='surrogate_or_strict')
1130
+ 'Look at traceback for that process for debugging information.') from ex
1229
1131
 
1230
1132
  o_interpreter, o_args = _extract_interpreter(b_module_data)
1231
1133
  if o_interpreter is None:
@@ -1237,63 +1139,75 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1237
1139
  rlimit_nofile = C.config.get_config_value('PYTHON_MODULE_RLIMIT_NOFILE', variables=task_vars)
1238
1140
 
1239
1141
  if not isinstance(rlimit_nofile, int):
1240
- rlimit_nofile = int(templar.template(rlimit_nofile))
1142
+ rlimit_nofile = int(templar._engine.template(rlimit_nofile, options=TemplateOptions(value_for_omit=0)))
1241
1143
 
1242
- if rlimit_nofile:
1243
- rlimit = ANSIBALLZ_RLIMIT_TEMPLATE % dict(
1244
- rlimit_nofile=rlimit_nofile,
1245
- )
1246
- else:
1247
- rlimit = ''
1144
+ if not isinstance(cached_module.metadata, ModuleMetadataV1):
1145
+ raise NotImplementedError()
1248
1146
 
1249
- coverage_config = os.environ.get('_ANSIBLE_COVERAGE_CONFIG')
1147
+ params = dict(ANSIBLE_MODULE_ARGS=module_args,)
1148
+ encoder = get_module_encoder(cached_module.metadata.serialization_profile, Direction.CONTROLLER_TO_MODULE)
1250
1149
 
1251
- if coverage_config:
1252
- coverage_output = os.environ['_ANSIBLE_COVERAGE_OUTPUT']
1150
+ try:
1151
+ encoded_params = json.dumps(params, cls=encoder)
1152
+ except TypeError as ex:
1153
+ raise AnsibleError(f'Failed to serialize arguments for the {module_name!r} module.') from ex
1253
1154
 
1254
- if coverage_output:
1255
- # Enable code coverage analysis of the module.
1256
- # This feature is for internal testing and may change without notice.
1257
- coverage = ANSIBALLZ_COVERAGE_TEMPLATE % dict(
1258
- coverage_config=coverage_config,
1259
- coverage_output=coverage_output,
1260
- )
1261
- else:
1262
- # Verify coverage is available without importing it.
1263
- # This will detect when a module would fail with coverage enabled with minimal overhead.
1264
- coverage = ANSIBALLZ_COVERAGE_CHECK_TEMPLATE
1265
- else:
1266
- coverage = ''
1155
+ extension_manager.source_mapping = cached_module.source_mapping
1267
1156
 
1268
- output.write(to_bytes(ACTIVE_ANSIBALLZ_TEMPLATE % dict(
1269
- zipdata=zipdata,
1157
+ code = _get_ansiballz_code(shebang)
1158
+ args = dict(
1270
1159
  ansible_module=module_name,
1271
1160
  module_fqn=remote_module_fqn,
1272
- params=python_repred_params,
1273
- shebang=shebang,
1274
- coding=ENCODING_STRING,
1161
+ profile=cached_module.metadata.serialization_profile,
1275
1162
  date_time=date_time,
1276
- coverage=coverage,
1277
- rlimit=rlimit,
1278
- )))
1163
+ rlimit_nofile=rlimit_nofile,
1164
+ params=encoded_params,
1165
+ extensions=extension_manager.get_extensions(),
1166
+ zip_data=to_text(cached_module.zip_data),
1167
+ )
1168
+
1169
+ args_string = '\n'.join(f'{key}={value!r},' for key, value in args.items())
1170
+
1171
+ wrapper = f"""{code}
1172
+
1173
+
1174
+ if __name__ == "__main__":
1175
+ _ansiballz_main(
1176
+ {args_string}
1177
+ )
1178
+ """
1179
+
1180
+ output.write(to_bytes(wrapper))
1181
+
1182
+ module_metadata = cached_module.metadata
1279
1183
  b_module_data = output.getvalue()
1280
1184
 
1281
1185
  elif module_substyle == 'powershell':
1186
+ module_metadata = ModuleMetadataV1(serialization_profile='legacy') # DTFIX-FUTURE: support serialization profiles for PowerShell modules
1187
+
1282
1188
  # Powershell/winrm don't actually make use of shebang so we can
1283
1189
  # safely set this here. If we let the fallback code handle this
1284
1190
  # it can fail in the presence of the UTF8 BOM commonly added by
1285
1191
  # Windows text editors
1286
- shebang = u'#!powershell'
1192
+ shebang = '#!powershell'
1287
1193
  # create the common exec wrapper payload and set that as the module_data
1288
1194
  # bytes
1289
1195
  b_module_data = ps_manifest._create_powershell_wrapper(
1290
- b_module_data, module_path, module_args, environment,
1291
- async_timeout, become, become_method, become_user, become_password,
1292
- become_flags, module_substyle, task_vars, remote_module_fqn
1196
+ name=remote_module_fqn,
1197
+ module_data=b_module_data,
1198
+ module_path=module_path,
1199
+ module_args=module_args,
1200
+ environment=environment,
1201
+ async_timeout=async_timeout,
1202
+ become_plugin=become_plugin,
1203
+ substyle=module_substyle,
1204
+ task_vars=task_vars,
1205
+ profile=module_metadata.serialization_profile,
1293
1206
  )
1294
1207
 
1295
1208
  elif module_substyle == 'jsonargs':
1296
- module_args_json = to_bytes(json.dumps(module_args, cls=AnsibleJSONEncoder, vault_to_text=True))
1209
+ encoder = get_module_encoder('legacy', Direction.CONTROLLER_TO_MODULE)
1210
+ module_args_json = to_bytes(json.dumps(module_args, cls=encoder))
1297
1211
 
1298
1212
  # these strings could be included in a third-party module but
1299
1213
  # officially they were included in the 'basic' snippet for new-style
@@ -1303,15 +1217,32 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1303
1217
  python_repred_args = to_bytes(repr(module_args_json))
1304
1218
  b_module_data = b_module_data.replace(REPLACER_VERSION, to_bytes(repr(__version__)))
1305
1219
  b_module_data = b_module_data.replace(REPLACER_COMPLEX, python_repred_args)
1306
- b_module_data = b_module_data.replace(REPLACER_SELINUX, to_bytes(','.join(C.DEFAULT_SELINUX_SPECIAL_FS)))
1220
+ b_module_data = b_module_data.replace(
1221
+ REPLACER_SELINUX,
1222
+ to_bytes(','.join(C.DEFAULT_SELINUX_SPECIAL_FS))) # type: ignore[attr-defined]
1307
1223
 
1308
1224
  # The main event -- substitute the JSON args string into the module
1309
1225
  b_module_data = b_module_data.replace(REPLACER_JSONARGS, module_args_json)
1310
1226
 
1311
- facility = b'syslog.' + to_bytes(task_vars.get('ansible_syslog_facility', C.DEFAULT_SYSLOG_FACILITY), errors='surrogate_or_strict')
1227
+ syslog_facility = task_vars.get(
1228
+ 'ansible_syslog_facility',
1229
+ C.DEFAULT_SYSLOG_FACILITY) # type: ignore[attr-defined]
1230
+ facility = b'syslog.' + to_bytes(syslog_facility, errors='surrogate_or_strict')
1312
1231
  b_module_data = b_module_data.replace(b'syslog.LOG_USER', facility)
1313
1232
 
1314
- return (b_module_data, module_style, shebang)
1233
+ module_metadata = ModuleMetadataV1(serialization_profile='legacy')
1234
+ else:
1235
+ module_metadata = ModuleMetadataV1(serialization_profile='legacy')
1236
+
1237
+ if not isinstance(module_metadata, ModuleMetadataV1):
1238
+ raise NotImplementedError(type(module_metadata))
1239
+
1240
+ return _BuiltModule(
1241
+ b_module_data=b_module_data,
1242
+ module_style=module_style,
1243
+ shebang=shebang,
1244
+ serialization_profile=module_metadata.serialization_profile,
1245
+ )
1315
1246
 
1316
1247
 
1317
1248
  def _extract_interpreter(b_module_data):
@@ -1337,8 +1268,19 @@ def _extract_interpreter(b_module_data):
1337
1268
  return interpreter, args
1338
1269
 
1339
1270
 
1340
- def modify_module(module_name, module_path, module_args, templar, task_vars=None, module_compression='ZIP_STORED', async_timeout=0, become=False,
1341
- become_method=None, become_user=None, become_password=None, become_flags=None, environment=None, remote_is_local=False):
1271
+ def modify_module(
1272
+ *,
1273
+ module_name: str,
1274
+ module_path,
1275
+ module_args,
1276
+ templar,
1277
+ task_vars=None,
1278
+ module_compression='ZIP_STORED',
1279
+ async_timeout=0,
1280
+ become_plugin=None,
1281
+ environment=None,
1282
+ remote_is_local=False,
1283
+ ) -> _BuiltModule:
1342
1284
  """
1343
1285
  Used to insert chunks of code into modules before transfer rather than
1344
1286
  doing regular python imports. This allows for more efficient transfer in
@@ -1367,13 +1309,30 @@ def modify_module(module_name, module_path, module_args, templar, task_vars=None
1367
1309
  # read in the module source
1368
1310
  b_module_data = f.read()
1369
1311
 
1370
- (b_module_data, module_style, shebang) = _find_module_utils(module_name, b_module_data, module_path, module_args, task_vars, templar, module_compression,
1371
- async_timeout=async_timeout, become=become, become_method=become_method,
1372
- become_user=become_user, become_password=become_password, become_flags=become_flags,
1373
- environment=environment, remote_is_local=remote_is_local)
1312
+ module_bits = _find_module_utils(
1313
+ module_name=module_name,
1314
+ b_module_data=b_module_data,
1315
+ module_path=module_path,
1316
+ module_args=module_args,
1317
+ task_vars=task_vars,
1318
+ templar=templar,
1319
+ module_compression=module_compression,
1320
+ async_timeout=async_timeout,
1321
+ become_plugin=become_plugin,
1322
+ environment=environment,
1323
+ remote_is_local=remote_is_local,
1324
+ )
1325
+
1326
+ b_module_data = module_bits.b_module_data
1327
+ shebang = module_bits.shebang
1374
1328
 
1375
- if module_style == 'binary':
1376
- return (b_module_data, module_style, to_text(shebang, nonstring='passthru'))
1329
+ if module_bits.module_style == 'binary':
1330
+ return _BuiltModule(
1331
+ b_module_data=module_bits.b_module_data,
1332
+ module_style=module_bits.module_style,
1333
+ shebang=to_text(module_bits.shebang, nonstring='passthru'),
1334
+ serialization_profile=module_bits.serialization_profile,
1335
+ )
1377
1336
  elif shebang is None:
1378
1337
  interpreter, args = _extract_interpreter(b_module_data)
1379
1338
  # No interpreter/shebang, assume a binary module?
@@ -1387,15 +1346,20 @@ def modify_module(module_name, module_path, module_args, templar, task_vars=None
1387
1346
  if interpreter != new_interpreter:
1388
1347
  b_lines[0] = to_bytes(shebang, errors='surrogate_or_strict', nonstring='passthru')
1389
1348
 
1390
- if os.path.basename(interpreter).startswith(u'python'):
1391
- b_lines.insert(1, b_ENCODING_STRING)
1392
-
1393
1349
  b_module_data = b"\n".join(b_lines)
1394
1350
 
1395
- return (b_module_data, module_style, shebang)
1351
+ return _BuiltModule(
1352
+ b_module_data=b_module_data,
1353
+ module_style=module_bits.module_style,
1354
+ shebang=shebang,
1355
+ serialization_profile=module_bits.serialization_profile,
1356
+ )
1357
+
1396
1358
 
1359
+ def _get_action_arg_defaults(action: str, task: Task, templar: TemplateEngine) -> dict[str, t.Any]:
1360
+ action_groups = task._parent._play._action_groups
1361
+ defaults = task.module_defaults
1397
1362
 
1398
- def get_action_args_with_defaults(action, args, defaults, templar, action_groups=None):
1399
1363
  # Get the list of groups that contain this action
1400
1364
  if action_groups is None:
1401
1365
  msg = (
@@ -1408,7 +1372,7 @@ def get_action_args_with_defaults(action, args, defaults, templar, action_groups
1408
1372
  else:
1409
1373
  group_names = action_groups.get(action, [])
1410
1374
 
1411
- tmp_args = {}
1375
+ tmp_args: dict[str, t.Any] = {}
1412
1376
  module_defaults = {}
1413
1377
 
1414
1378
  # Merge latest defaults into dict, since they are a list of dicts
@@ -1416,18 +1380,20 @@ def get_action_args_with_defaults(action, args, defaults, templar, action_groups
1416
1380
  for default in defaults:
1417
1381
  module_defaults.update(default)
1418
1382
 
1419
- # module_defaults keys are static, but the values may be templated
1420
- module_defaults = templar.template(module_defaults)
1421
1383
  for default in module_defaults:
1422
1384
  if default.startswith('group/'):
1423
1385
  group_name = default.split('group/')[-1]
1424
1386
  if group_name in group_names:
1425
- tmp_args.update((module_defaults.get('group/%s' % group_name) or {}).copy())
1387
+ tmp_args.update(templar.resolve_to_container(module_defaults.get(f'group/{group_name}', {})))
1426
1388
 
1427
1389
  # handle specific action defaults
1428
- tmp_args.update(module_defaults.get(action, {}).copy())
1429
-
1430
- # direct args override all
1431
- tmp_args.update(args)
1390
+ tmp_args.update(templar.resolve_to_container(module_defaults.get(action, {})))
1432
1391
 
1433
1392
  return tmp_args
1393
+
1394
+
1395
+ def _apply_action_arg_defaults(action: str, task: Task, action_args: dict[str, t.Any], templar: Templar) -> dict[str, t.Any]:
1396
+ args = _get_action_arg_defaults(action, task, templar._engine)
1397
+ args.update(action_args)
1398
+
1399
+ return args