ansible-core 2.18.5rc1__py3-none-any.whl → 2.19.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (709) hide show
  1. ansible/_internal/__init__.py +53 -0
  2. ansible/_internal/_ansiballz.py +265 -0
  3. ansible/_internal/_datatag/__init__.py +0 -0
  4. ansible/_internal/_datatag/_tags.py +130 -0
  5. ansible/_internal/_datatag/_utils.py +19 -0
  6. ansible/_internal/_datatag/_wrappers.py +33 -0
  7. ansible/_internal/_errors/__init__.py +0 -0
  8. ansible/_internal/_errors/_captured.py +128 -0
  9. ansible/_internal/_errors/_handler.py +91 -0
  10. ansible/_internal/_errors/_utils.py +310 -0
  11. ansible/_internal/_json/__init__.py +160 -0
  12. ansible/_internal/_json/_legacy_encoder.py +34 -0
  13. ansible/_internal/_json/_profiles/__init__.py +0 -0
  14. ansible/_internal/_json/_profiles/_cache_persistence.py +55 -0
  15. ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
  16. ansible/_internal/_json/_profiles/_legacy.py +198 -0
  17. ansible/_internal/_locking.py +21 -0
  18. ansible/_internal/_plugins/__init__.py +0 -0
  19. ansible/_internal/_plugins/_cache.py +57 -0
  20. ansible/_internal/_task.py +78 -0
  21. ansible/_internal/_templating/__init__.py +10 -0
  22. ansible/_internal/_templating/_access.py +86 -0
  23. ansible/_internal/_templating/_chain_templar.py +63 -0
  24. ansible/_internal/_templating/_datatag.py +95 -0
  25. ansible/_internal/_templating/_engine.py +588 -0
  26. ansible/_internal/_templating/_errors.py +28 -0
  27. ansible/_internal/_templating/_jinja_bits.py +1066 -0
  28. ansible/_internal/_templating/_jinja_common.py +332 -0
  29. ansible/_internal/_templating/_jinja_patches.py +44 -0
  30. ansible/_internal/_templating/_jinja_plugins.py +351 -0
  31. ansible/_internal/_templating/_lazy_containers.py +633 -0
  32. ansible/_internal/_templating/_marker_behaviors.py +103 -0
  33. ansible/_internal/_templating/_transform.py +63 -0
  34. ansible/_internal/_templating/_utils.py +107 -0
  35. ansible/_internal/_wrapt.py +1052 -0
  36. ansible/_internal/_yaml/__init__.py +0 -0
  37. ansible/_internal/_yaml/_constructor.py +240 -0
  38. ansible/_internal/_yaml/_dumper.py +62 -0
  39. ansible/_internal/_yaml/_errors.py +166 -0
  40. ansible/_internal/_yaml/_loader.py +66 -0
  41. ansible/_internal/ansible_collections/ansible/_protomatter/README.md +11 -0
  42. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/action/debug.py +36 -0
  43. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/apply_trust.py +19 -0
  44. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +18 -0
  45. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/finalize.py +16 -0
  46. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/origin.py +18 -0
  47. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.py +24 -0
  48. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.yml +33 -0
  49. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/tag_names.py +16 -0
  50. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +17 -0
  51. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +49 -0
  52. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.py +21 -0
  53. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.yml +2 -0
  54. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.py +15 -0
  55. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.yml +19 -0
  56. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.py +18 -0
  57. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.yml +19 -0
  58. ansible/cli/__init__.py +153 -89
  59. ansible/cli/_ssh_askpass.py +47 -0
  60. ansible/cli/adhoc.py +14 -7
  61. ansible/cli/arguments/option_helpers.py +154 -7
  62. ansible/cli/config.py +43 -68
  63. ansible/cli/console.py +10 -8
  64. ansible/cli/doc.py +48 -46
  65. ansible/cli/galaxy.py +27 -20
  66. ansible/cli/inventory.py +28 -26
  67. ansible/cli/playbook.py +4 -12
  68. ansible/cli/pull.py +51 -11
  69. ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
  70. ansible/cli/vault.py +12 -11
  71. ansible/compat/__init__.py +2 -2
  72. ansible/config/base.yml +165 -108
  73. ansible/config/manager.py +52 -49
  74. ansible/constants.py +3 -4
  75. ansible/errors/__init__.py +277 -235
  76. ansible/executor/interpreter_discovery.py +28 -149
  77. ansible/executor/module_common.py +426 -493
  78. ansible/executor/play_iterator.py +22 -27
  79. ansible/executor/playbook_executor.py +11 -11
  80. ansible/executor/powershell/async_watchdog.ps1 +97 -102
  81. ansible/executor/powershell/async_wrapper.ps1 +202 -151
  82. ansible/executor/powershell/become_wrapper.ps1 +89 -144
  83. ansible/executor/powershell/bootstrap_wrapper.ps1 +24 -9
  84. ansible/executor/powershell/coverage_wrapper.ps1 +82 -135
  85. ansible/executor/powershell/exec_wrapper.ps1 +462 -196
  86. ansible/executor/powershell/module_manifest.py +417 -265
  87. ansible/executor/powershell/module_wrapper.ps1 +169 -186
  88. ansible/executor/powershell/psrp_fetch_file.ps1 +41 -0
  89. ansible/executor/powershell/psrp_put_file.ps1 +122 -0
  90. ansible/executor/powershell/winrm_fetch_file.ps1 +46 -0
  91. ansible/executor/powershell/winrm_put_file.ps1 +36 -0
  92. ansible/executor/process/worker.py +136 -76
  93. ansible/executor/stats.py +5 -5
  94. ansible/executor/task_executor.py +237 -236
  95. ansible/executor/task_queue_manager.py +62 -38
  96. ansible/executor/task_result.py +21 -12
  97. ansible/galaxy/__init__.py +2 -2
  98. ansible/galaxy/api.py +22 -18
  99. ansible/galaxy/collection/__init__.py +1 -1
  100. ansible/galaxy/collection/concrete_artifact_manager.py +8 -11
  101. ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
  102. ansible/galaxy/dependency_resolution/providers.py +1 -1
  103. ansible/galaxy/dependency_resolution/reporters.py +81 -0
  104. ansible/galaxy/role.py +4 -8
  105. ansible/galaxy/token.py +28 -21
  106. ansible/inventory/data.py +47 -57
  107. ansible/inventory/group.py +44 -72
  108. ansible/inventory/helpers.py +9 -0
  109. ansible/inventory/host.py +32 -54
  110. ansible/inventory/manager.py +77 -34
  111. ansible/keyword_desc.yml +1 -1
  112. ansible/module_utils/_internal/__init__.py +55 -0
  113. ansible/module_utils/_internal/_ambient_context.py +58 -0
  114. ansible/module_utils/_internal/_ansiballz.py +133 -0
  115. ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
  116. ansible/module_utils/_internal/_dataclass_annotation_patch.py +64 -0
  117. ansible/module_utils/_internal/_dataclass_validation.py +217 -0
  118. ansible/module_utils/_internal/_datatag/__init__.py +928 -0
  119. ansible/module_utils/_internal/_datatag/_tags.py +38 -0
  120. ansible/module_utils/_internal/_debugging.py +31 -0
  121. ansible/module_utils/_internal/_errors.py +30 -0
  122. ansible/module_utils/_internal/_json/__init__.py +63 -0
  123. ansible/module_utils/_internal/_json/_legacy_encoder.py +26 -0
  124. ansible/module_utils/_internal/_json/_profiles/__init__.py +410 -0
  125. ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
  126. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +31 -0
  127. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +35 -0
  128. ansible/module_utils/_internal/_json/_profiles/_module_modern_c2m.py +35 -0
  129. ansible/module_utils/_internal/_json/_profiles/_module_modern_m2c.py +33 -0
  130. ansible/module_utils/_internal/_json/_profiles/_tagless.py +50 -0
  131. ansible/module_utils/_internal/_patches/__init__.py +66 -0
  132. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +55 -0
  133. ansible/module_utils/_internal/_patches/_socket_patch.py +34 -0
  134. ansible/module_utils/_internal/_patches/_sys_intern_patch.py +34 -0
  135. ansible/module_utils/_internal/_plugin_exec_context.py +49 -0
  136. ansible/module_utils/_internal/_testing.py +0 -0
  137. ansible/module_utils/_internal/_traceback.py +89 -0
  138. ansible/module_utils/ansible_release.py +2 -2
  139. ansible/module_utils/api.py +1 -2
  140. ansible/module_utils/basic.py +154 -120
  141. ansible/module_utils/common/_utils.py +24 -28
  142. ansible/module_utils/common/collections.py +1 -2
  143. ansible/module_utils/common/dict_transformations.py +2 -2
  144. ansible/module_utils/common/file.py +2 -2
  145. ansible/module_utils/common/json.py +90 -84
  146. ansible/module_utils/common/locale.py +2 -2
  147. ansible/module_utils/common/messages.py +108 -0
  148. ansible/module_utils/common/parameters.py +27 -24
  149. ansible/module_utils/common/process.py +2 -2
  150. ansible/module_utils/common/respawn.py +41 -19
  151. ansible/module_utils/common/sentinel.py +66 -0
  152. ansible/module_utils/common/sys_info.py +8 -8
  153. ansible/module_utils/common/text/converters.py +16 -37
  154. ansible/module_utils/common/validation.py +35 -24
  155. ansible/module_utils/common/warnings.py +86 -25
  156. ansible/module_utils/common/yaml.py +29 -3
  157. ansible/module_utils/compat/datetime.py +33 -21
  158. ansible/module_utils/compat/paramiko.py +21 -10
  159. ansible/module_utils/compat/typing.py +6 -5
  160. ansible/module_utils/connection.py +2 -2
  161. ansible/module_utils/csharp/Ansible.Basic.cs +14 -11
  162. ansible/module_utils/csharp/Ansible.Become.cs +1 -0
  163. ansible/module_utils/csharp/Ansible._Async.cs +517 -0
  164. ansible/module_utils/datatag.py +46 -0
  165. ansible/module_utils/distro/__init__.py +2 -2
  166. ansible/module_utils/facts/ansible_collector.py +4 -5
  167. ansible/module_utils/facts/collector.py +13 -14
  168. ansible/module_utils/facts/compat.py +4 -4
  169. ansible/module_utils/facts/default_collectors.py +1 -1
  170. ansible/module_utils/facts/hardware/aix.py +34 -0
  171. ansible/module_utils/facts/hardware/base.py +1 -1
  172. ansible/module_utils/facts/hardware/darwin.py +1 -3
  173. ansible/module_utils/facts/hardware/freebsd.py +2 -2
  174. ansible/module_utils/facts/hardware/linux.py +4 -4
  175. ansible/module_utils/facts/namespace.py +1 -1
  176. ansible/module_utils/facts/network/base.py +1 -1
  177. ansible/module_utils/facts/network/fc_wwn.py +1 -2
  178. ansible/module_utils/facts/network/iscsi.py +1 -2
  179. ansible/module_utils/facts/network/nvme.py +1 -2
  180. ansible/module_utils/facts/other/facter.py +1 -2
  181. ansible/module_utils/facts/other/ohai.py +2 -3
  182. ansible/module_utils/facts/system/apparmor.py +1 -2
  183. ansible/module_utils/facts/system/caps.py +1 -1
  184. ansible/module_utils/facts/system/chroot.py +1 -2
  185. ansible/module_utils/facts/system/cmdline.py +1 -2
  186. ansible/module_utils/facts/system/date_time.py +5 -3
  187. ansible/module_utils/facts/system/distribution.py +9 -8
  188. ansible/module_utils/facts/system/dns.py +1 -1
  189. ansible/module_utils/facts/system/env.py +1 -2
  190. ansible/module_utils/facts/system/fips.py +7 -20
  191. ansible/module_utils/facts/system/loadavg.py +1 -2
  192. ansible/module_utils/facts/system/local.py +1 -2
  193. ansible/module_utils/facts/system/lsb.py +1 -2
  194. ansible/module_utils/facts/system/pkg_mgr.py +1 -2
  195. ansible/module_utils/facts/system/platform.py +1 -2
  196. ansible/module_utils/facts/system/python.py +1 -2
  197. ansible/module_utils/facts/system/selinux.py +1 -1
  198. ansible/module_utils/facts/system/service_mgr.py +1 -2
  199. ansible/module_utils/facts/system/ssh_pub_keys.py +1 -1
  200. ansible/module_utils/facts/system/systemd.py +1 -1
  201. ansible/module_utils/facts/system/user.py +1 -2
  202. ansible/module_utils/facts/utils.py +3 -3
  203. ansible/module_utils/facts/virtual/base.py +1 -1
  204. ansible/module_utils/facts/virtual/sunos.py +3 -15
  205. ansible/module_utils/facts/virtual/sysctl.py +3 -16
  206. ansible/module_utils/json_utils.py +2 -2
  207. ansible/module_utils/parsing/convert_bool.py +1 -1
  208. ansible/module_utils/service.py +18 -21
  209. ansible/module_utils/splitter.py +7 -7
  210. ansible/module_utils/testing.py +31 -0
  211. ansible/module_utils/urls.py +60 -31
  212. ansible/modules/add_host.py +4 -4
  213. ansible/modules/apt.py +60 -46
  214. ansible/modules/apt_key.py +19 -12
  215. ansible/modules/apt_repository.py +19 -16
  216. ansible/modules/assemble.py +6 -6
  217. ansible/modules/assert.py +4 -4
  218. ansible/modules/async_status.py +10 -12
  219. ansible/modules/async_wrapper.py +8 -3
  220. ansible/modules/blockinfile.py +6 -7
  221. ansible/modules/command.py +10 -17
  222. ansible/modules/copy.py +57 -144
  223. ansible/modules/cron.py +20 -15
  224. ansible/modules/deb822_repository.py +8 -9
  225. ansible/modules/debconf.py +5 -5
  226. ansible/modules/debug.py +4 -4
  227. ansible/modules/dnf.py +8 -8
  228. ansible/modules/dnf5.py +39 -13
  229. ansible/modules/dpkg_selections.py +4 -4
  230. ansible/modules/expect.py +8 -10
  231. ansible/modules/fail.py +4 -4
  232. ansible/modules/fetch.py +4 -4
  233. ansible/modules/file.py +174 -133
  234. ansible/modules/find.py +19 -17
  235. ansible/modules/gather_facts.py +3 -3
  236. ansible/modules/get_url.py +59 -53
  237. ansible/modules/getent.py +7 -9
  238. ansible/modules/git.py +28 -25
  239. ansible/modules/group.py +6 -6
  240. ansible/modules/group_by.py +4 -4
  241. ansible/modules/hostname.py +13 -29
  242. ansible/modules/import_playbook.py +6 -6
  243. ansible/modules/import_role.py +6 -6
  244. ansible/modules/import_tasks.py +6 -6
  245. ansible/modules/include_role.py +6 -6
  246. ansible/modules/include_tasks.py +6 -6
  247. ansible/modules/include_vars.py +6 -6
  248. ansible/modules/iptables.py +86 -73
  249. ansible/modules/known_hosts.py +10 -10
  250. ansible/modules/lineinfile.py +5 -5
  251. ansible/modules/meta.py +4 -4
  252. ansible/modules/mount_facts.py +2 -2
  253. ansible/modules/package.py +4 -4
  254. ansible/modules/package_facts.py +22 -10
  255. ansible/modules/pause.py +6 -6
  256. ansible/modules/ping.py +6 -6
  257. ansible/modules/pip.py +10 -11
  258. ansible/modules/raw.py +4 -4
  259. ansible/modules/reboot.py +6 -6
  260. ansible/modules/replace.py +9 -13
  261. ansible/modules/rpm_key.py +7 -8
  262. ansible/modules/script.py +4 -4
  263. ansible/modules/service.py +7 -8
  264. ansible/modules/service_facts.py +87 -10
  265. ansible/modules/set_fact.py +5 -5
  266. ansible/modules/set_stats.py +4 -4
  267. ansible/modules/setup.py +2 -2
  268. ansible/modules/shell.py +6 -6
  269. ansible/modules/slurp.py +6 -6
  270. ansible/modules/stat.py +9 -23
  271. ansible/modules/subversion.py +15 -15
  272. ansible/modules/systemd.py +6 -6
  273. ansible/modules/systemd_service.py +6 -6
  274. ansible/modules/sysvinit.py +6 -6
  275. ansible/modules/tempfile.py +5 -6
  276. ansible/modules/template.py +6 -6
  277. ansible/modules/unarchive.py +32 -11
  278. ansible/modules/uri.py +33 -26
  279. ansible/modules/user.py +53 -34
  280. ansible/modules/validate_argument_spec.py +10 -7
  281. ansible/modules/wait_for.py +32 -27
  282. ansible/modules/wait_for_connection.py +6 -6
  283. ansible/modules/yum_repository.py +6 -6
  284. ansible/parsing/ajson.py +14 -32
  285. ansible/parsing/dataloader.py +99 -54
  286. ansible/parsing/mod_args.py +28 -44
  287. ansible/parsing/plugin_docs.py +21 -86
  288. ansible/parsing/quoting.py +1 -1
  289. ansible/parsing/splitter.py +27 -12
  290. ansible/parsing/utils/addresses.py +24 -24
  291. ansible/parsing/utils/yaml.py +32 -61
  292. ansible/parsing/vault/__init__.py +319 -87
  293. ansible/parsing/yaml/__init__.py +0 -18
  294. ansible/parsing/yaml/dumper.py +6 -120
  295. ansible/parsing/yaml/loader.py +6 -39
  296. ansible/parsing/yaml/objects.py +36 -339
  297. ansible/playbook/__init__.py +1 -1
  298. ansible/playbook/attribute.py +8 -3
  299. ansible/playbook/base.py +182 -132
  300. ansible/playbook/block.py +26 -24
  301. ansible/playbook/collectionsearch.py +1 -15
  302. ansible/playbook/conditional.py +3 -77
  303. ansible/playbook/handler.py +8 -2
  304. ansible/playbook/helpers.py +41 -53
  305. ansible/playbook/included_file.py +6 -15
  306. ansible/playbook/loop_control.py +2 -2
  307. ansible/playbook/play.py +85 -44
  308. ansible/playbook/play_context.py +12 -17
  309. ansible/playbook/playbook_include.py +14 -15
  310. ansible/playbook/role/__init__.py +24 -26
  311. ansible/playbook/role/definition.py +15 -17
  312. ansible/playbook/role/include.py +2 -4
  313. ansible/playbook/role/metadata.py +10 -11
  314. ansible/playbook/role_include.py +3 -3
  315. ansible/playbook/taggable.py +13 -8
  316. ansible/playbook/task.py +188 -118
  317. ansible/playbook/task_include.py +5 -5
  318. ansible/plugins/__init__.py +68 -21
  319. ansible/plugins/action/__init__.py +209 -176
  320. ansible/plugins/action/add_host.py +1 -1
  321. ansible/plugins/action/assemble.py +1 -1
  322. ansible/plugins/action/assert.py +54 -66
  323. ansible/plugins/action/copy.py +7 -11
  324. ansible/plugins/action/debug.py +37 -31
  325. ansible/plugins/action/dnf.py +3 -4
  326. ansible/plugins/action/fail.py +1 -1
  327. ansible/plugins/action/fetch.py +4 -5
  328. ansible/plugins/action/gather_facts.py +7 -6
  329. ansible/plugins/action/group_by.py +1 -1
  330. ansible/plugins/action/include_vars.py +10 -11
  331. ansible/plugins/action/package.py +3 -6
  332. ansible/plugins/action/pause.py +2 -2
  333. ansible/plugins/action/script.py +15 -8
  334. ansible/plugins/action/service.py +6 -11
  335. ansible/plugins/action/set_fact.py +3 -12
  336. ansible/plugins/action/set_stats.py +3 -8
  337. ansible/plugins/action/template.py +35 -59
  338. ansible/plugins/action/unarchive.py +1 -1
  339. ansible/plugins/action/validate_argument_spec.py +5 -5
  340. ansible/plugins/action/wait_for_connection.py +1 -1
  341. ansible/plugins/become/__init__.py +31 -8
  342. ansible/plugins/become/runas.py +71 -0
  343. ansible/plugins/become/su.py +13 -8
  344. ansible/plugins/become/sudo.py +19 -0
  345. ansible/plugins/cache/__init__.py +35 -44
  346. ansible/plugins/cache/base.py +8 -0
  347. ansible/plugins/cache/jsonfile.py +10 -16
  348. ansible/plugins/cache/memory.py +6 -12
  349. ansible/plugins/callback/__init__.py +141 -123
  350. ansible/plugins/callback/default.py +30 -23
  351. ansible/plugins/callback/junit.py +28 -24
  352. ansible/plugins/callback/minimal.py +17 -14
  353. ansible/plugins/callback/oneline.py +13 -7
  354. ansible/plugins/callback/tree.py +10 -6
  355. ansible/plugins/connection/__init__.py +47 -34
  356. ansible/plugins/connection/local.py +150 -54
  357. ansible/plugins/connection/paramiko_ssh.py +21 -18
  358. ansible/plugins/connection/psrp.py +76 -165
  359. ansible/plugins/connection/ssh.py +301 -78
  360. ansible/plugins/connection/winrm.py +58 -140
  361. ansible/plugins/doc_fragments/action_common_attributes.py +14 -14
  362. ansible/plugins/doc_fragments/action_core.py +6 -6
  363. ansible/plugins/doc_fragments/backup.py +2 -2
  364. ansible/plugins/doc_fragments/checksum_common.py +27 -0
  365. ansible/plugins/doc_fragments/constructed.py +6 -2
  366. ansible/plugins/doc_fragments/decrypt.py +2 -2
  367. ansible/plugins/doc_fragments/default_callback.py +2 -2
  368. ansible/plugins/doc_fragments/files.py +2 -2
  369. ansible/plugins/doc_fragments/inventory_cache.py +2 -2
  370. ansible/plugins/doc_fragments/result_format_callback.py +2 -2
  371. ansible/plugins/doc_fragments/return_common.py +2 -2
  372. ansible/plugins/doc_fragments/template_common.py +4 -4
  373. ansible/plugins/doc_fragments/url.py +17 -1
  374. ansible/plugins/doc_fragments/url_windows.py +2 -2
  375. ansible/plugins/doc_fragments/validate.py +2 -2
  376. ansible/plugins/doc_fragments/vars_plugin_staging.py +2 -2
  377. ansible/plugins/filter/__init__.py +6 -2
  378. ansible/plugins/filter/b64decode.yml +22 -0
  379. ansible/plugins/filter/b64encode.yml +22 -0
  380. ansible/plugins/filter/bool.yml +11 -4
  381. ansible/plugins/filter/core.py +218 -108
  382. ansible/plugins/filter/encryption.py +32 -32
  383. ansible/plugins/filter/flatten.yml +3 -2
  384. ansible/plugins/filter/human_to_bytes.yml +1 -1
  385. ansible/plugins/filter/mathstuff.py +30 -37
  386. ansible/plugins/filter/password_hash.yml +8 -0
  387. ansible/plugins/filter/regex_search.yml +1 -4
  388. ansible/plugins/filter/split.yml +1 -1
  389. ansible/plugins/filter/to_nice_yaml.yml +0 -4
  390. ansible/plugins/filter/to_yaml.yml +0 -4
  391. ansible/plugins/filter/unvault.yml +1 -1
  392. ansible/plugins/filter/urls.py +1 -1
  393. ansible/plugins/filter/urlsplit.py +8 -9
  394. ansible/plugins/filter/vault.yml +14 -9
  395. ansible/plugins/filter/win_basename.yml +6 -1
  396. ansible/plugins/filter/win_dirname.yml +5 -0
  397. ansible/plugins/inventory/__init__.py +97 -77
  398. ansible/plugins/inventory/advanced_host_list.py +7 -5
  399. ansible/plugins/inventory/auto.py +11 -4
  400. ansible/plugins/inventory/constructed.py +21 -24
  401. ansible/plugins/inventory/generator.py +16 -11
  402. ansible/plugins/inventory/host_list.py +7 -5
  403. ansible/plugins/inventory/ini.py +78 -44
  404. ansible/plugins/inventory/script.py +189 -119
  405. ansible/plugins/inventory/toml.py +16 -126
  406. ansible/plugins/inventory/yaml.py +10 -8
  407. ansible/plugins/list.py +3 -3
  408. ansible/plugins/loader.py +197 -82
  409. ansible/plugins/lookup/__init__.py +21 -4
  410. ansible/plugins/lookup/config.py +21 -35
  411. ansible/plugins/lookup/csvfile.py +3 -2
  412. ansible/plugins/lookup/dict.py +1 -6
  413. ansible/plugins/lookup/env.py +12 -9
  414. ansible/plugins/lookup/file.py +5 -8
  415. ansible/plugins/lookup/first_found.py +86 -55
  416. ansible/plugins/lookup/indexed_items.py +1 -10
  417. ansible/plugins/lookup/ini.py +14 -13
  418. ansible/plugins/lookup/items.py +1 -1
  419. ansible/plugins/lookup/lines.py +8 -1
  420. ansible/plugins/lookup/list.py +1 -1
  421. ansible/plugins/lookup/nested.py +2 -18
  422. ansible/plugins/lookup/password.py +5 -5
  423. ansible/plugins/lookup/pipe.py +5 -7
  424. ansible/plugins/lookup/sequence.py +18 -8
  425. ansible/plugins/lookup/subelements.py +1 -4
  426. ansible/plugins/lookup/template.py +42 -36
  427. ansible/plugins/lookup/together.py +0 -12
  428. ansible/plugins/lookup/unvault.py +1 -5
  429. ansible/plugins/lookup/url.py +2 -8
  430. ansible/plugins/lookup/vars.py +16 -24
  431. ansible/plugins/shell/__init__.py +2 -2
  432. ansible/plugins/shell/cmd.py +2 -2
  433. ansible/plugins/shell/powershell.py +39 -22
  434. ansible/plugins/shell/sh.py +3 -2
  435. ansible/plugins/strategy/__init__.py +94 -113
  436. ansible/plugins/strategy/debug.py +2 -2
  437. ansible/plugins/strategy/free.py +13 -28
  438. ansible/plugins/strategy/host_pinned.py +2 -2
  439. ansible/plugins/strategy/linear.py +31 -33
  440. ansible/plugins/terminal/__init__.py +4 -4
  441. ansible/plugins/test/__init__.py +7 -2
  442. ansible/plugins/test/core.py +54 -20
  443. ansible/plugins/test/files.py +1 -1
  444. ansible/plugins/test/mathstuff.py +3 -3
  445. ansible/plugins/test/uri.py +3 -3
  446. ansible/plugins/vars/host_group_vars.py +7 -14
  447. ansible/release.py +2 -2
  448. ansible/template/__init__.py +368 -944
  449. ansible/utils/__init__.py +0 -18
  450. ansible/utils/_ssh_agent.py +657 -0
  451. ansible/utils/collection_loader/__init__.py +52 -5
  452. ansible/utils/collection_loader/_collection_config.py +5 -6
  453. ansible/utils/collection_loader/_collection_finder.py +79 -93
  454. ansible/utils/collection_loader/_collection_meta.py +13 -8
  455. ansible/utils/display.py +428 -58
  456. ansible/utils/encrypt.py +27 -19
  457. ansible/utils/fqcn.py +2 -2
  458. ansible/utils/hashing.py +2 -2
  459. ansible/utils/helpers.py +2 -2
  460. ansible/utils/listify.py +8 -8
  461. ansible/utils/lock.py +2 -2
  462. ansible/utils/path.py +4 -4
  463. ansible/utils/plugin_docs.py +14 -13
  464. ansible/utils/sentinel.py +4 -62
  465. ansible/utils/singleton.py +2 -0
  466. ansible/utils/ssh_functions.py +1 -1
  467. ansible/utils/unsafe_proxy.py +23 -332
  468. ansible/utils/vars.py +28 -8
  469. ansible/utils/version.py +2 -2
  470. ansible/vars/clean.py +4 -4
  471. ansible/vars/hostvars.py +60 -90
  472. ansible/vars/manager.py +205 -264
  473. ansible/vars/reserved.py +8 -9
  474. ansible_core-2.19.0b1.dist-info/BSD-3-Clause.txt +28 -0
  475. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b1.dist-info}/METADATA +5 -4
  476. ansible_core-2.19.0b1.dist-info/RECORD +1070 -0
  477. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b1.dist-info}/WHEEL +1 -1
  478. ansible_test/_data/completion/docker.txt +7 -7
  479. ansible_test/_data/completion/remote.txt +6 -6
  480. ansible_test/_data/completion/windows.txt +1 -0
  481. ansible_test/_data/requirements/ansible.txt +2 -2
  482. ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
  483. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  484. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  485. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  486. ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
  487. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  488. ansible_test/_data/requirements/units.txt +1 -0
  489. ansible_test/_internal/__init__.py +1 -0
  490. ansible_test/_internal/ansible_util.py +2 -0
  491. ansible_test/_internal/become.py +1 -0
  492. ansible_test/_internal/bootstrap.py +1 -0
  493. ansible_test/_internal/cache.py +1 -0
  494. ansible_test/_internal/cgroup.py +1 -0
  495. ansible_test/_internal/ci/__init__.py +1 -0
  496. ansible_test/_internal/ci/azp.py +1 -0
  497. ansible_test/_internal/ci/local.py +1 -0
  498. ansible_test/_internal/classification/__init__.py +1 -0
  499. ansible_test/_internal/classification/common.py +1 -0
  500. ansible_test/_internal/classification/csharp.py +1 -0
  501. ansible_test/_internal/classification/powershell.py +1 -0
  502. ansible_test/_internal/classification/python.py +1 -0
  503. ansible_test/_internal/cli/__init__.py +1 -0
  504. ansible_test/_internal/cli/actions.py +1 -0
  505. ansible_test/_internal/cli/argparsing/__init__.py +1 -0
  506. ansible_test/_internal/cli/argparsing/actions.py +1 -0
  507. ansible_test/_internal/cli/argparsing/argcompletion.py +1 -0
  508. ansible_test/_internal/cli/argparsing/parsers.py +1 -0
  509. ansible_test/_internal/cli/commands/__init__.py +11 -0
  510. ansible_test/_internal/cli/commands/coverage/__init__.py +1 -0
  511. ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +1 -0
  512. ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +1 -0
  513. ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +1 -0
  514. ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +1 -0
  515. ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +1 -0
  516. ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +1 -0
  517. ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +1 -0
  518. ansible_test/_internal/cli/commands/coverage/combine.py +1 -0
  519. ansible_test/_internal/cli/commands/coverage/erase.py +1 -0
  520. ansible_test/_internal/cli/commands/coverage/html.py +1 -0
  521. ansible_test/_internal/cli/commands/coverage/report.py +1 -0
  522. ansible_test/_internal/cli/commands/coverage/xml.py +1 -0
  523. ansible_test/_internal/cli/commands/env.py +1 -0
  524. ansible_test/_internal/cli/commands/integration/__init__.py +1 -0
  525. ansible_test/_internal/cli/commands/integration/network.py +1 -0
  526. ansible_test/_internal/cli/commands/integration/posix.py +1 -0
  527. ansible_test/_internal/cli/commands/integration/windows.py +1 -0
  528. ansible_test/_internal/cli/commands/sanity.py +9 -0
  529. ansible_test/_internal/cli/commands/shell.py +1 -0
  530. ansible_test/_internal/cli/commands/units.py +1 -0
  531. ansible_test/_internal/cli/compat.py +1 -0
  532. ansible_test/_internal/cli/completers.py +1 -0
  533. ansible_test/_internal/cli/converters.py +1 -0
  534. ansible_test/_internal/cli/environments.py +1 -0
  535. ansible_test/_internal/cli/epilog.py +1 -0
  536. ansible_test/_internal/cli/parsers/__init__.py +1 -0
  537. ansible_test/_internal/cli/parsers/base_argument_parsers.py +1 -0
  538. ansible_test/_internal/cli/parsers/helpers.py +1 -0
  539. ansible_test/_internal/cli/parsers/host_config_parsers.py +1 -0
  540. ansible_test/_internal/cli/parsers/key_value_parsers.py +1 -0
  541. ansible_test/_internal/cli/parsers/value_parsers.py +1 -0
  542. ansible_test/_internal/commands/__init__.py +1 -0
  543. ansible_test/_internal/commands/coverage/__init__.py +2 -1
  544. ansible_test/_internal/commands/coverage/analyze/__init__.py +1 -0
  545. ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +1 -0
  546. ansible_test/_internal/commands/coverage/analyze/targets/combine.py +1 -0
  547. ansible_test/_internal/commands/coverage/analyze/targets/expand.py +1 -0
  548. ansible_test/_internal/commands/coverage/analyze/targets/filter.py +1 -0
  549. ansible_test/_internal/commands/coverage/analyze/targets/generate.py +1 -0
  550. ansible_test/_internal/commands/coverage/analyze/targets/missing.py +1 -0
  551. ansible_test/_internal/commands/coverage/combine.py +2 -1
  552. ansible_test/_internal/commands/coverage/erase.py +1 -0
  553. ansible_test/_internal/commands/coverage/html.py +1 -0
  554. ansible_test/_internal/commands/coverage/report.py +1 -0
  555. ansible_test/_internal/commands/coverage/xml.py +1 -0
  556. ansible_test/_internal/commands/env/__init__.py +2 -0
  557. ansible_test/_internal/commands/integration/__init__.py +4 -0
  558. ansible_test/_internal/commands/integration/cloud/__init__.py +1 -0
  559. ansible_test/_internal/commands/integration/cloud/acme.py +2 -1
  560. ansible_test/_internal/commands/integration/cloud/aws.py +1 -0
  561. ansible_test/_internal/commands/integration/cloud/azure.py +1 -0
  562. ansible_test/_internal/commands/integration/cloud/cs.py +1 -0
  563. ansible_test/_internal/commands/integration/cloud/digitalocean.py +1 -0
  564. ansible_test/_internal/commands/integration/cloud/galaxy.py +3 -2
  565. ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -0
  566. ansible_test/_internal/commands/integration/cloud/httptester.py +2 -1
  567. ansible_test/_internal/commands/integration/cloud/nios.py +2 -1
  568. ansible_test/_internal/commands/integration/cloud/opennebula.py +1 -0
  569. ansible_test/_internal/commands/integration/cloud/openshift.py +1 -0
  570. ansible_test/_internal/commands/integration/cloud/scaleway.py +1 -0
  571. ansible_test/_internal/commands/integration/cloud/vcenter.py +1 -0
  572. ansible_test/_internal/commands/integration/cloud/vultr.py +1 -0
  573. ansible_test/_internal/commands/integration/coverage.py +1 -0
  574. ansible_test/_internal/commands/integration/filters.py +1 -0
  575. ansible_test/_internal/commands/integration/network.py +1 -0
  576. ansible_test/_internal/commands/integration/posix.py +1 -0
  577. ansible_test/_internal/commands/integration/windows.py +1 -0
  578. ansible_test/_internal/commands/sanity/__init__.py +16 -1
  579. ansible_test/_internal/commands/sanity/ansible_doc.py +1 -0
  580. ansible_test/_internal/commands/sanity/bin_symlinks.py +1 -0
  581. ansible_test/_internal/commands/sanity/compile.py +1 -0
  582. ansible_test/_internal/commands/sanity/ignores.py +1 -0
  583. ansible_test/_internal/commands/sanity/import.py +1 -0
  584. ansible_test/_internal/commands/sanity/integration_aliases.py +1 -0
  585. ansible_test/_internal/commands/sanity/pep8.py +1 -0
  586. ansible_test/_internal/commands/sanity/pslint.py +1 -0
  587. ansible_test/_internal/commands/sanity/pylint.py +24 -26
  588. ansible_test/_internal/commands/sanity/shellcheck.py +1 -0
  589. ansible_test/_internal/commands/sanity/validate_modules.py +1 -0
  590. ansible_test/_internal/commands/sanity/yamllint.py +1 -0
  591. ansible_test/_internal/commands/shell/__init__.py +1 -0
  592. ansible_test/_internal/commands/units/__init__.py +1 -0
  593. ansible_test/_internal/compat/__init__.py +1 -0
  594. ansible_test/_internal/compat/packaging.py +1 -0
  595. ansible_test/_internal/compat/yaml.py +1 -0
  596. ansible_test/_internal/completion.py +1 -0
  597. ansible_test/_internal/config.py +2 -0
  598. ansible_test/_internal/connections.py +1 -0
  599. ansible_test/_internal/constants.py +1 -0
  600. ansible_test/_internal/containers.py +1 -0
  601. ansible_test/_internal/content_config.py +1 -0
  602. ansible_test/_internal/core_ci.py +1 -0
  603. ansible_test/_internal/coverage_util.py +11 -10
  604. ansible_test/_internal/data.py +1 -0
  605. ansible_test/_internal/delegation.py +1 -0
  606. ansible_test/_internal/dev/__init__.py +1 -0
  607. ansible_test/_internal/dev/container_probe.py +1 -0
  608. ansible_test/_internal/diff.py +3 -2
  609. ansible_test/_internal/docker_util.py +2 -1
  610. ansible_test/_internal/encoding.py +1 -0
  611. ansible_test/_internal/executor.py +1 -0
  612. ansible_test/_internal/git.py +1 -0
  613. ansible_test/_internal/host_configs.py +1 -0
  614. ansible_test/_internal/host_profiles.py +1 -0
  615. ansible_test/_internal/http.py +1 -0
  616. ansible_test/_internal/init.py +1 -0
  617. ansible_test/_internal/inventory.py +35 -3
  618. ansible_test/_internal/io.py +1 -0
  619. ansible_test/_internal/metadata.py +1 -0
  620. ansible_test/_internal/payload.py +1 -0
  621. ansible_test/_internal/provider/__init__.py +1 -0
  622. ansible_test/_internal/provider/layout/__init__.py +1 -0
  623. ansible_test/_internal/provider/layout/ansible.py +1 -0
  624. ansible_test/_internal/provider/layout/collection.py +1 -0
  625. ansible_test/_internal/provider/layout/unsupported.py +1 -0
  626. ansible_test/_internal/provider/source/__init__.py +1 -0
  627. ansible_test/_internal/provider/source/git.py +1 -0
  628. ansible_test/_internal/provider/source/installed.py +1 -0
  629. ansible_test/_internal/provider/source/unsupported.py +1 -0
  630. ansible_test/_internal/provider/source/unversioned.py +1 -0
  631. ansible_test/_internal/provisioning.py +1 -0
  632. ansible_test/_internal/pypi_proxy.py +6 -5
  633. ansible_test/_internal/python_requirements.py +1 -0
  634. ansible_test/_internal/ssh.py +1 -0
  635. ansible_test/_internal/target.py +1 -0
  636. ansible_test/_internal/test.py +3 -2
  637. ansible_test/_internal/thread.py +1 -0
  638. ansible_test/_internal/timeout.py +1 -0
  639. ansible_test/_internal/util.py +1 -0
  640. ansible_test/_internal/util_common.py +5 -2
  641. ansible_test/_internal/venv.py +1 -0
  642. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +1 -0
  643. ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py +1 -0
  644. ansible_test/_util/controller/sanity/code-smell/changelog.py +1 -0
  645. ansible_test/_util/controller/sanity/code-smell/empty-init.py +1 -0
  646. ansible_test/_util/controller/sanity/code-smell/line-endings.py +1 -0
  647. ansible_test/_util/controller/sanity/code-smell/no-assert.py +1 -0
  648. ansible_test/_util/controller/sanity/code-smell/no-get-exception.py +1 -0
  649. ansible_test/_util/controller/sanity/code-smell/no-illegal-filenames.py +1 -0
  650. ansible_test/_util/controller/sanity/code-smell/no-smart-quotes.py +1 -0
  651. ansible_test/_util/controller/sanity/code-smell/replace-urlopen.py +1 -0
  652. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +28 -1
  653. ansible_test/_util/controller/sanity/code-smell/shebang.py +1 -0
  654. ansible_test/_util/controller/sanity/code-smell/symlinks.py +1 -0
  655. ansible_test/_util/controller/sanity/code-smell/use-argspec-type-path.py +1 -0
  656. ansible_test/_util/controller/sanity/code-smell/use-compat-six.py +1 -0
  657. ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py +2 -1
  658. ansible_test/_util/controller/sanity/pep8/current-ignore.txt +4 -0
  659. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +7 -5
  660. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +7 -5
  661. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +7 -5
  662. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +3 -5
  663. ansible_test/_util/controller/sanity/pylint/config/default.cfg +7 -7
  664. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -13
  665. ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +1 -0
  666. ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +1 -8
  667. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +1 -8
  668. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +55 -28
  669. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +12 -5
  670. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +13 -2
  671. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -0
  672. ansible_test/_util/controller/sanity/yamllint/yamllinter.py +35 -17
  673. ansible_test/_util/controller/tools/collection_detail.py +1 -0
  674. ansible_test/_util/controller/tools/yaml_to_json.py +2 -1
  675. ansible_test/_util/target/pytest/plugins/ansible_forked.py +6 -1
  676. ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +2 -1
  677. ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -0
  678. ansible_test/_util/target/sanity/compile/compile.py +1 -0
  679. ansible_test/_util/target/sanity/import/importer.py +15 -16
  680. ansible_test/_util/target/setup/bootstrap.sh +9 -20
  681. ansible_test/_util/target/setup/probe_cgroups.py +1 -0
  682. ansible_test/_util/target/setup/quiet_pip.py +1 -0
  683. ansible_test/_util/target/setup/requirements.py +35 -27
  684. ansible_test/_util/target/tools/virtualenvcheck.py +2 -1
  685. ansible_test/_util/target/tools/yamlcheck.py +2 -1
  686. ansible/compat/selectors.py +0 -32
  687. ansible/errors/yaml_strings.py +0 -138
  688. ansible/executor/action_write_locks.py +0 -44
  689. ansible/executor/discovery/python_target.py +0 -47
  690. ansible/executor/powershell/module_powershell_wrapper.ps1 +0 -86
  691. ansible/executor/powershell/module_script_wrapper.ps1 +0 -22
  692. ansible/module_utils/compat/importlib.py +0 -26
  693. ansible/module_utils/compat/selectors.py +0 -32
  694. ansible/module_utils/pycompat24.py +0 -73
  695. ansible/parsing/utils/jsonify.py +0 -36
  696. ansible/parsing/yaml/constructor.py +0 -178
  697. ansible/template/native_helpers.py +0 -251
  698. ansible/template/template.py +0 -43
  699. ansible/template/vars.py +0 -77
  700. ansible/utils/native_jinja.py +0 -11
  701. ansible/vars/fact_cache.py +0 -71
  702. ansible_core-2.18.5rc1.dist-info/RECORD +0 -992
  703. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b1.dist-info}/Apache-License.txt +0 -0
  704. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b1.dist-info}/COPYING +0 -0
  705. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b1.dist-info}/MIT-license.txt +0 -0
  706. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b1.dist-info}/PSF-license.txt +0 -0
  707. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b1.dist-info}/entry_points.txt +0 -0
  708. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b1.dist-info}/simplified_bsd.txt +0 -0
  709. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b1.dist-info}/top_level.txt +0 -0
@@ -20,42 +20,76 @@ 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._datatag import _utils
41
+ from ansible.module_utils._internal import _dataclass_validation
42
+ from ansible.module_utils.common.messages import PluginInfo
43
+ from ansible.module_utils.common.yaml import yaml_load
44
+ from ansible._internal._datatag._tags import Origin
45
+ from ansible.module_utils.common.json import Direction, get_module_encoder
35
46
  from ansible.release import __version__, __author__
36
47
  from ansible import constants as C
37
48
  from ansible.errors import AnsibleError
38
49
  from ansible.executor.interpreter_discovery import InterpreterDiscoveryRequiredError
39
50
  from ansible.executor.powershell import module_manifest as ps_manifest
40
- from ansible.module_utils.common.json import AnsibleJSONEncoder
41
51
  from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
52
+ from ansible.plugins.become import BecomeBase
42
53
  from ansible.plugins.loader import module_utils_loader
54
+ from ansible._internal._templating._engine import TemplateOptions, TemplateEngine
55
+ from ansible.template import Templar
43
56
  from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
57
+ from ansible.module_utils._internal import _json, _ansiballz
58
+ from ansible.module_utils import basic as _basic
44
59
 
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
60
+ if t.TYPE_CHECKING:
61
+ from ansible import template as _template
62
+ from ansible.playbook.task import Task
49
63
 
50
64
  from ansible.utils.display import Display
51
- from collections import namedtuple
52
65
 
53
66
  import importlib.util
54
67
  import importlib.machinery
55
68
 
56
69
  display = Display()
57
70
 
58
- ModuleUtilsProcessEntry = namedtuple('ModuleUtilsProcessEntry', ['name_parts', 'is_ambiguous', 'has_redirected_child', 'is_optional'])
71
+
72
+ @dataclasses.dataclass(frozen=True, order=True)
73
+ class _ModuleUtilsProcessEntry:
74
+ """Represents a module/module_utils item awaiting import analysis."""
75
+ name_parts: tuple[str, ...]
76
+ is_ambiguous: bool = False
77
+ child_is_redirected: bool = False
78
+ is_optional: bool = False
79
+
80
+ @classmethod
81
+ def from_module(cls, module: types.ModuleType, append: str | None = None) -> t.Self:
82
+ name = module.__name__
83
+
84
+ if append:
85
+ name += '.' + append
86
+
87
+ return cls.from_module_name(name)
88
+
89
+ @classmethod
90
+ def from_module_name(cls, module_name: str) -> t.Self:
91
+ return cls(tuple(module_name.split('.')))
92
+
59
93
 
60
94
  REPLACER = b"#<<INCLUDE_ANSIBLE_MODULE_COMMON>>"
61
95
  REPLACER_VERSION = b"\"<<ANSIBLE_VERSION>>\""
@@ -64,348 +98,45 @@ REPLACER_WINDOWS = b"# POWERSHELL_COMMON"
64
98
  REPLACER_JSONARGS = b"<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>"
65
99
  REPLACER_SELINUX = b"<<SELINUX_SPECIAL_FILESYSTEMS>>"
66
100
 
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
101
  # module_common is relative to module_utils, so fix the path
73
102
  _MODULE_UTILS_PATH = os.path.join(os.path.dirname(__file__), '..', 'module_utils')
103
+ _SHEBANG_PLACEHOLDER = '# shebang placeholder'
74
104
 
75
105
  # ******************************************************************************
76
106
 
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
107
 
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
-
351
- def atexit_coverage():
352
- cov.stop()
353
- cov.save()
354
-
355
- atexit.register(atexit_coverage)
108
+ def _strip_comments(source: str) -> str:
109
+ # Strip comments and blank lines from the wrapper
110
+ buf = []
111
+ for line in source.splitlines():
112
+ l = line.strip()
113
+ if (not l or l.startswith('#')) and l != _SHEBANG_PLACEHOLDER:
114
+ line = ''
115
+ buf.append(line)
116
+ return '\n'.join(buf)
356
117
 
357
- cov.start()
358
- '''
359
118
 
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
- '''
119
+ def _read_ansiballz_code() -> str:
120
+ code = (pathlib.Path(__file__).parent.parent / '_internal/_ansiballz.py').read_text()
373
121
 
374
- ANSIBALLZ_RLIMIT_TEMPLATE = '''
375
- import resource
122
+ if not C.DEFAULT_KEEP_REMOTE_FILES:
123
+ # Keep comments when KEEP_REMOTE_FILES is set. That way users will see
124
+ # the comments with some nice usage instructions.
125
+ # Otherwise, strip comments for smaller over the wire size.
126
+ code = _strip_comments(code)
376
127
 
377
- existing_soft, existing_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
128
+ return code
378
129
 
379
- # adjust soft limit subject to existing hard limit
380
- requested_soft = min(existing_hard, %(rlimit_nofile)d)
381
130
 
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
- '''
131
+ _ANSIBALLZ_CODE = _read_ansiballz_code() # read during startup to prevent individual workers from doing so
389
132
 
390
133
 
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)
134
+ def _get_ansiballz_code(shebang: str) -> str:
135
+ code = _ANSIBALLZ_CODE
136
+ code = code.replace(_SHEBANG_PLACEHOLDER, shebang)
400
137
 
138
+ return code
401
139
 
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
140
 
410
141
  # dirname(dirname(dirname(site-packages/ansible/executor/module_common.py) == site-packages
411
142
  # Do this instead of getting site-packages from distutils.sysconfig so we work when we
@@ -435,6 +166,7 @@ NEW_STYLE_PYTHON_MODULE_RE = re.compile(
435
166
 
436
167
 
437
168
  class ModuleDepFinder(ast.NodeVisitor):
169
+ # DTFIX-RELEASE: add support for ignoring imports with a "controller only" comment, this will allow replacing import_controller_module with standard imports
438
170
  def __init__(self, module_fqn, tree, is_pkg_init=False, *args, **kwargs):
439
171
  """
440
172
  Walk the ast tree for the python module.
@@ -581,7 +313,7 @@ def _slurp(path):
581
313
  return data
582
314
 
583
315
 
584
- def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=False):
316
+ def _get_shebang(interpreter, task_vars, templar: _template.Templar, args=tuple(), remote_is_local=False):
585
317
  """
586
318
  Handles the different ways ansible allows overriding the shebang target for a module.
587
319
  """
@@ -606,7 +338,8 @@ def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=
606
338
  elif C.config.get_configuration_definition(interpreter_config_key):
607
339
 
608
340
  interpreter_from_config = C.config.get_config_value(interpreter_config_key, variables=task_vars)
609
- interpreter_out = templar.template(interpreter_from_config.strip())
341
+ interpreter_out = templar._engine.template(_utils.str_problematic_strip(interpreter_from_config),
342
+ options=TemplateOptions(value_for_omit=C.config.get_config_default(interpreter_config_key)))
610
343
 
611
344
  # handle interpreter discovery if requested or empty interpreter was provided
612
345
  if not interpreter_out or interpreter_out in ['auto', 'auto_legacy', 'auto_silent', 'auto_legacy_silent']:
@@ -624,7 +357,8 @@ def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=
624
357
 
625
358
  elif interpreter_config in task_vars:
626
359
  # for non python we consult vars for a possible direct override
627
- interpreter_out = templar.template(task_vars.get(interpreter_config).strip())
360
+ interpreter_out = templar._engine.template(_utils.str_problematic_strip(task_vars.get(interpreter_config)),
361
+ options=TemplateOptions(value_for_omit=None))
628
362
 
629
363
  if not interpreter_out:
630
364
  # nothing matched(None) or in case someone configures empty string or empty intepreter
@@ -803,12 +537,12 @@ class LegacyModuleUtilLocator(ModuleUtilLocatorBase):
803
537
 
804
538
  # find_spec needs the full module name
805
539
  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:
540
+ if info is not None and info.origin is not None and os.path.splitext(info.origin)[1] in importlib.machinery.SOURCE_SUFFIXES:
807
541
  self.is_package = info.origin.endswith('/__init__.py')
808
542
  path = info.origin
809
543
  else:
810
544
  return False
811
- self.source_code = _slurp(path)
545
+ self.source_code = Origin(path=path).tag(_slurp(path))
812
546
 
813
547
  return True
814
548
 
@@ -843,9 +577,18 @@ class CollectionModuleUtilLocator(ModuleUtilLocatorBase):
843
577
  resource_base_path = os.path.join(*name_parts[3:])
844
578
 
845
579
  src = None
580
+
846
581
  # look for package_dir first, then module
582
+ src_path = to_native(os.path.join(resource_base_path, '__init__.py'))
583
+
584
+ try:
585
+ collection_pkg = importlib.import_module(collection_pkg_name)
586
+ pkg_path = os.path.dirname(collection_pkg.__file__)
587
+ except (ImportError, AttributeError):
588
+ pkg_path = None
589
+
847
590
  try:
848
- src = pkgutil.get_data(collection_pkg_name, to_native(os.path.join(resource_base_path, '__init__.py')))
591
+ src = pkgutil.get_data(collection_pkg_name, src_path)
849
592
  except ImportError:
850
593
  pass
851
594
 
@@ -854,32 +597,113 @@ class CollectionModuleUtilLocator(ModuleUtilLocatorBase):
854
597
  if src is not None: # empty string is OK
855
598
  self.is_package = True
856
599
  else:
600
+ src_path = to_native(resource_base_path + '.py')
601
+
857
602
  try:
858
- src = pkgutil.get_data(collection_pkg_name, to_native(resource_base_path + '.py'))
603
+ src = pkgutil.get_data(collection_pkg_name, src_path)
859
604
  except ImportError:
860
605
  pass
861
606
 
862
607
  if src is None: # empty string is OK
863
608
  return False
864
609
 
865
- self.source_code = src
610
+ # TODO: this feels brittle and funky; we should be able to more definitively assure the source path
611
+
612
+ if pkg_path:
613
+ origin = Origin(path=os.path.join(pkg_path, src_path))
614
+ else:
615
+ # DTFIX-RELEASE: not sure if this case is even reachable
616
+ origin = Origin(description=f'<synthetic collection package for {collection_pkg_name}!r>')
617
+
618
+ self.source_code = origin.tag(src)
866
619
  return True
867
620
 
868
621
  def _get_module_utils_remainder_parts(self, name_parts):
869
622
  return name_parts[5:] # eg, foo.bar for ansible_collections.ns.coll.plugins.module_utils.foo.bar
870
623
 
871
624
 
872
- def _make_zinfo(filename, date_time, zf=None):
625
+ def _make_zinfo(filename: str, date_time: datetime.datetime, zf: zipfile.ZipFile | None = None) -> zipfile.ZipInfo:
873
626
  zinfo = zipfile.ZipInfo(
874
627
  filename=filename,
875
- date_time=date_time
628
+ date_time=date_time.utctimetuple()[:6],
876
629
  )
630
+
877
631
  if zf:
878
632
  zinfo.compress_type = zf.compression
633
+
879
634
  return zinfo
880
635
 
881
636
 
882
- def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
637
+ @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
638
+ class ModuleMetadata:
639
+ @classmethod
640
+ def __post_init__(cls):
641
+ _dataclass_validation.inject_post_init_validation(cls)
642
+
643
+
644
+ @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
645
+ class ModuleMetadataV1(ModuleMetadata):
646
+ serialization_profile: str
647
+
648
+
649
+ metadata_versions: dict[t.Any, type[ModuleMetadata]] = {
650
+ 1: ModuleMetadataV1,
651
+ }
652
+
653
+
654
+ def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
655
+ # DTFIX-RELEASE: while module metadata works, this feature isn't fully baked and should be turned off before release
656
+ metadata_nodes: list[ast.Assign] = []
657
+
658
+ for node in module.body:
659
+ if isinstance(node, ast.Assign):
660
+ if len(node.targets) == 1:
661
+ target = node.targets[0]
662
+
663
+ if isinstance(target, ast.Name):
664
+ if target.id == 'METADATA':
665
+ metadata_nodes.append(node)
666
+
667
+ if not metadata_nodes:
668
+ return ModuleMetadataV1(
669
+ serialization_profile='legacy',
670
+ )
671
+
672
+ if len(metadata_nodes) > 1:
673
+ raise ValueError('Module METADATA must defined only once.')
674
+
675
+ metadata_node = metadata_nodes[0]
676
+
677
+ if not isinstance(metadata_node.value, ast.Constant):
678
+ raise TypeError(f'Module METADATA node must be {ast.Constant} not {type(metadata_node)}.')
679
+
680
+ unparsed_metadata = metadata_node.value.value
681
+
682
+ if not isinstance(unparsed_metadata, str):
683
+ raise TypeError(f'Module METADATA must be {str} not {type(unparsed_metadata)}.')
684
+
685
+ try:
686
+ parsed_metadata = yaml_load(unparsed_metadata)
687
+ except Exception as ex:
688
+ raise ValueError('Module METADATA must be valid YAML.') from ex
689
+
690
+ if not isinstance(parsed_metadata, dict):
691
+ raise TypeError(f'Module METADATA must parse to {dict} not {type(parsed_metadata)}.')
692
+
693
+ schema_version = parsed_metadata.pop('schema_version', None)
694
+
695
+ if not (metadata_type := metadata_versions.get(schema_version)):
696
+ raise ValueError(f'Module METADATA schema_version {schema_version} is unknown.')
697
+
698
+ try:
699
+ metadata = metadata_type(**parsed_metadata) # type: ignore
700
+ except Exception as ex:
701
+ raise ValueError('Module METADATA is invalid.') from ex
702
+
703
+ return metadata
704
+
705
+
706
+ def recursive_finder(name: str, module_fqn: str, module_data: str | bytes, zf: zipfile.ZipFile, date_time: datetime.datetime) -> ModuleMetadata:
883
707
  """
884
708
  Using ModuleDepFinder, make sure we have all of the module_utils files that
885
709
  the module and its module_utils files needs. (no longer actually recursive)
@@ -889,9 +713,6 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
889
713
  :arg zf: An open :python:class:`zipfile.ZipFile` object that holds the Ansible module payload
890
714
  which we're assembling
891
715
  """
892
- if date_time is None:
893
- date_time = time.gmtime()[:6]
894
-
895
716
  # py_module_cache maps python module names to a tuple of the code in the module
896
717
  # and the pathname to the module.
897
718
  # Here we pre-load it with modules which we create without bothering to
@@ -913,49 +734,57 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
913
734
  module_utils_paths = [p for p in module_utils_loader._get_paths(subdirs=False) if os.path.isdir(p)]
914
735
  module_utils_paths.append(_MODULE_UTILS_PATH)
915
736
 
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
-
737
+ tree = _compile_module_ast(name, module_data)
738
+ module_metadata = _get_module_metadata(tree)
922
739
  finder = ModuleDepFinder(module_fqn, tree)
923
740
 
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]
741
+ if not isinstance(module_metadata, ModuleMetadataV1):
742
+ raise NotImplementedError()
743
+
744
+ profile = module_metadata.serialization_profile
745
+
746
+ # the format of this set is a tuple of the module name and whether the import is ambiguous as a module name
747
+ # or an attribute of a module (e.g. from x.y import z <-- is z a module or an attribute of x.y?)
748
+ modules_to_process = [_ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports) for m in finder.submodules]
749
+
750
+ # include module_utils that are always required
751
+ modules_to_process.extend((
752
+ _ModuleUtilsProcessEntry.from_module(_ansiballz),
753
+ _ModuleUtilsProcessEntry.from_module(_basic),
754
+ _ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, True)),
755
+ _ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, False)),
756
+ ))
927
757
 
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))
758
+ module_info: ModuleUtilLocatorBase
930
759
 
931
760
  # we'll be adding new modules inline as we discover them, so just keep going til we've processed them all
932
761
  while modules_to_process:
933
762
  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)
763
+ entry = modules_to_process.pop(0)
935
764
 
936
- if py_module_name in py_module_cache:
765
+ if entry.name_parts in py_module_cache:
937
766
  # this is normal; we'll often see the same module imported many times, but we only need to process it once
938
767
  continue
939
768
 
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)
769
+ if entry.name_parts[0:2] == ('ansible', 'module_utils'):
770
+ module_info = LegacyModuleUtilLocator(entry.name_parts, is_ambiguous=entry.is_ambiguous,
771
+ mu_paths=module_utils_paths, child_is_redirected=entry.child_is_redirected)
772
+ elif entry.name_parts[0] == 'ansible_collections':
773
+ module_info = CollectionModuleUtilLocator(entry.name_parts, is_ambiguous=entry.is_ambiguous,
774
+ child_is_redirected=entry.child_is_redirected, is_optional=entry.is_optional)
946
775
  else:
947
776
  # FIXME: dot-joined result
948
777
  display.warning('ModuleDepFinder improperly found a non-module_utils import %s'
949
- % [py_module_name])
778
+ % [entry.name_parts])
950
779
  continue
951
780
 
952
781
  # Could not find the module. Construct a helpful error message.
953
782
  if not module_info.found:
954
- if is_optional:
783
+ if entry.is_optional:
955
784
  # this was a best-effort optional import that we couldn't find, oh well, move along...
956
785
  continue
957
786
  # 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)
787
+ msg = 'Could not find imported module support code for {0}. Looked for ({1})'.format(module_fqn, module_info.candidate_names_joined)
959
788
  raise AnsibleError(msg)
960
789
 
961
790
  # check the cache one more time with the module we actually found, since the name could be different than the input
@@ -963,14 +792,9 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
963
792
  if module_info.fq_name_parts in py_module_cache:
964
793
  continue
965
794
 
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
-
795
+ tree = _compile_module_ast('.'.join(module_info.fq_name_parts), module_info.source_code)
972
796
  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)
797
+ modules_to_process.extend(_ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports)
974
798
  for m in finder.submodules if m not in py_module_cache)
975
799
 
976
800
  # we've processed this item, add it to the output list
@@ -982,7 +806,7 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
982
806
  accumulated_pkg_name.append(pkg) # we're accumulating this across iterations
983
807
  normalized_name = tuple(accumulated_pkg_name) # extra machinations to get a hashable type (list is not)
984
808
  if normalized_name not in py_module_cache:
985
- modules_to_process.append(ModuleUtilsProcessEntry(normalized_name, False, module_info.redirected, is_optional=is_optional))
809
+ modules_to_process.append(_ModuleUtilsProcessEntry(normalized_name, False, module_info.redirected, is_optional=entry.is_optional))
986
810
 
987
811
  for py_module_name in py_module_cache:
988
812
  py_module_file_name = py_module_cache[py_module_name][1]
@@ -994,8 +818,23 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
994
818
  mu_file = to_text(py_module_file_name, errors='surrogate_or_strict')
995
819
  display.vvvvv("Including module_utils file %s" % mu_file)
996
820
 
821
+ return module_metadata
822
+
823
+
824
+ def _compile_module_ast(module_name: str, source_code: str | bytes) -> ast.Module:
825
+ origin = Origin.get_tag(source_code) or Origin.UNKNOWN
826
+
827
+ # compile the source, process all relevant imported modules
828
+ try:
829
+ tree = t.cast(ast.Module, compile(source_code, str(origin), 'exec', ast.PyCF_ONLY_AST))
830
+ except SyntaxError as ex:
831
+ raise AnsibleError(f"Unable to compile {module_name!r}.", obj=origin.replace(line_num=ex.lineno, col_num=ex.offset)) from ex
832
+
833
+ return tree
834
+
997
835
 
998
836
  def _is_binary(b_module_data):
837
+ """Heuristic to classify a file as binary by sniffing a 1k header; see https://stackoverflow.com/a/7392391"""
999
838
  textchars = bytearray(set([7, 8, 9, 10, 12, 13, 27]) | set(range(0x20, 0x100)) - set([0x7f]))
1000
839
  start = b_module_data[:1024]
1001
840
  return bool(start.translate(None, textchars))
@@ -1034,7 +873,7 @@ def _get_ansible_module_fqn(module_path):
1034
873
  return remote_module_fqn
1035
874
 
1036
875
 
1037
- def _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data):
876
+ def _add_module_to_zip(zf: zipfile.ZipFile, date_time: datetime.datetime, remote_module_fqn: str, b_module_data: bytes) -> None:
1038
877
  """Add a module from ansible or from an ansible collection into the module zip"""
1039
878
  module_path_parts = remote_module_fqn.split('.')
1040
879
 
@@ -1045,6 +884,8 @@ def _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data):
1045
884
  b_module_data
1046
885
  )
1047
886
 
887
+ existing_paths: frozenset[str]
888
+
1048
889
  # Write the __init__.py's necessary to get there
1049
890
  if module_path_parts[0] == 'ansible':
1050
891
  # The ansible namespace is setup as part of the module_utils setup...
@@ -1068,12 +909,59 @@ def _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data):
1068
909
  )
1069
910
 
1070
911
 
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):
912
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
913
+ class _BuiltModule:
914
+ """Payload required to execute an Ansible module, along with information required to do so."""
915
+ b_module_data: bytes
916
+ module_style: t.Literal['binary', 'new', 'non_native_want_json', 'old']
917
+ shebang: str | None
918
+ serialization_profile: str
919
+
920
+
921
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
922
+ class _CachedModule:
923
+ """Cached Python module created by AnsiballZ."""
924
+
925
+ # DTFIX-RELEASE: secure this (locked down pickle, don't use pickle, etc.)
926
+
927
+ zip_data: bytes
928
+ metadata: ModuleMetadata
929
+
930
+ def dump(self, path: str) -> None:
931
+ temp_path = pathlib.Path(path + '-part')
932
+
933
+ with temp_path.open('wb') as cache_file:
934
+ pickle.dump(self, cache_file)
935
+
936
+ temp_path.rename(path)
937
+
938
+ @classmethod
939
+ def load(cls, path: str) -> t.Self:
940
+ with pathlib.Path(path).open('rb') as cache_file:
941
+ return pickle.load(cache_file)
942
+
943
+
944
+ def _find_module_utils(
945
+ *,
946
+ module_name: str,
947
+ plugin: PluginInfo,
948
+ b_module_data: bytes,
949
+ module_path: str,
950
+ module_args: dict[object, object],
951
+ task_vars: dict[str, object],
952
+ templar: Templar,
953
+ module_compression: str,
954
+ async_timeout: int,
955
+ become_plugin: BecomeBase | None,
956
+ environment: dict[str, str],
957
+ remote_is_local: bool = False
958
+ ) -> _BuiltModule:
1073
959
  """
1074
960
  Given the source of the module, convert it to a Jinja2 template to insert
1075
961
  module code and return whether it's a new or old style module.
1076
962
  """
963
+ module_substyle: t.Literal['binary', 'jsonargs', 'non_native_want_json', 'old', 'powershell', 'python']
964
+ module_style: t.Literal['binary', 'new', 'non_native_want_json', 'old']
1077
965
  module_substyle = module_style = 'old'
1078
966
 
1079
967
  # module_style is something important to calling code (ActionBase). It
@@ -1096,7 +984,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1096
984
  elif REPLACER_WINDOWS in b_module_data:
1097
985
  module_style = 'new'
1098
986
  module_substyle = 'powershell'
1099
- b_module_data = b_module_data.replace(REPLACER_WINDOWS, b'#Requires -Module Ansible.ModuleUtils.Legacy')
987
+ b_module_data = b_module_data.replace(REPLACER_WINDOWS, b'#AnsibleRequires -PowerShell Ansible.ModuleUtils.Legacy')
1100
988
  elif re.search(b'#Requires -Module', b_module_data, re.IGNORECASE) \
1101
989
  or re.search(b'#Requires -Version', b_module_data, re.IGNORECASE)\
1102
990
  or re.search(b'#AnsibleRequires -OSVersion', b_module_data, re.IGNORECASE) \
@@ -1114,7 +1002,12 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1114
1002
  # Neither old-style, non_native_want_json nor binary modules should be modified
1115
1003
  # except for the shebang line (Done by modify_module)
1116
1004
  if module_style in ('old', 'non_native_want_json', 'binary'):
1117
- return b_module_data, module_style, shebang
1005
+ return _BuiltModule(
1006
+ b_module_data=b_module_data,
1007
+ module_style=module_style,
1008
+ shebang=shebang,
1009
+ serialization_profile='legacy',
1010
+ )
1118
1011
 
1119
1012
  output = BytesIO()
1120
1013
 
@@ -1130,15 +1023,9 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1130
1023
  remote_module_fqn = 'ansible.modules.%s' % module_name
1131
1024
 
1132
1025
  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))
1026
+ date_time = datetime.datetime.now(datetime.timezone.utc)
1027
+ if date_time.year < 1980:
1028
+ raise AnsibleError(f'Cannot create zipfile due to pre-1980 configured date: {date_time}')
1142
1029
 
1143
1030
  try:
1144
1031
  compression_method = getattr(zipfile, module_compression)
@@ -1146,30 +1033,24 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1146
1033
  display.warning(u'Bad module compression string specified: %s. Using ZIP_STORED (no compression)' % module_compression)
1147
1034
  compression_method = zipfile.ZIP_STORED
1148
1035
 
1149
- lookup_path = os.path.join(C.DEFAULT_LOCAL_TMP, 'ansiballz_cache')
1036
+ lookup_path = os.path.join(C.DEFAULT_LOCAL_TMP, 'ansiballz_cache') # type: ignore[attr-defined]
1150
1037
  cached_module_filename = os.path.join(lookup_path, "%s-%s" % (remote_module_fqn, module_compression))
1151
1038
 
1152
- zipdata = None
1039
+ os.makedirs(os.path.dirname(cached_module_filename), exist_ok=True)
1040
+
1041
+ zipdata: bytes | None = None
1042
+ module_metadata: ModuleMetadata | None = None
1043
+
1153
1044
  # Optimization -- don't lock if the module has already been cached
1154
1045
  if os.path.exists(cached_module_filename):
1155
1046
  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()
1047
+ cached_module = _CachedModule.load(cached_module_filename)
1048
+ zipdata, module_metadata = cached_module.zip_data, cached_module.metadata
1158
1049
  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
1050
  display.debug('ANSIBALLZ: Acquiring lock')
1171
- with lock:
1172
- display.debug('ANSIBALLZ: Lock acquired: %s' % id(lock))
1051
+ lock_path = f'{cached_module_filename}.lock'
1052
+ with _locking.named_mutex(lock_path):
1053
+ display.debug(f'ANSIBALLZ: Lock acquired: {lock_path}')
1173
1054
  # Check that no other process has created this while we were
1174
1055
  # waiting for the lock
1175
1056
  if not os.path.exists(cached_module_filename):
@@ -1179,7 +1060,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1179
1060
  zf = zipfile.ZipFile(zipoutput, mode='w', compression=compression_method)
1180
1061
 
1181
1062
  # 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)
1063
+ module_metadata = recursive_finder(module_name, remote_module_fqn, Origin(path=module_path).tag(b_module_data), zf, date_time)
1183
1064
 
1184
1065
  display.debug('ANSIBALLZ: Writing module into payload')
1185
1066
  _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data)
@@ -1190,42 +1071,24 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1190
1071
  # Write the assembled module to a temp file (write to temp
1191
1072
  # so that no one looking for the file reads a partially
1192
1073
  # 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
1074
+ os.makedirs(lookup_path, exist_ok=True)
1206
1075
  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)
1076
+ cached_module = _CachedModule(zip_data=zipdata, metadata=module_metadata)
1077
+ cached_module.dump(cached_module_filename)
1215
1078
  display.debug('ANSIBALLZ: Done creating module')
1216
1079
 
1217
- if zipdata is None:
1080
+ if not zipdata:
1218
1081
  display.debug('ANSIBALLZ: Reading module after lock')
1219
1082
  # Another process wrote the file while we were waiting for
1220
1083
  # the write lock. Go ahead and read the data from disk
1221
1084
  # instead of re-creating it.
1222
1085
  try:
1223
- with open(cached_module_filename, 'rb') as f:
1224
- zipdata = f.read()
1086
+ cached_module = _CachedModule.load(cached_module_filename)
1225
1087
  except IOError:
1226
1088
  raise AnsibleError('A different worker process failed to create module file. '
1227
1089
  'Look at traceback for that process for debugging information.')
1228
- zipdata = to_text(zipdata, errors='surrogate_or_strict')
1090
+
1091
+ zipdata, module_metadata = cached_module.zip_data, cached_module.metadata
1229
1092
 
1230
1093
  o_interpreter, o_args = _extract_interpreter(b_module_data)
1231
1094
  if o_interpreter is None:
@@ -1237,63 +1100,79 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1237
1100
  rlimit_nofile = C.config.get_config_value('PYTHON_MODULE_RLIMIT_NOFILE', variables=task_vars)
1238
1101
 
1239
1102
  if not isinstance(rlimit_nofile, int):
1240
- rlimit_nofile = int(templar.template(rlimit_nofile))
1241
-
1242
- if rlimit_nofile:
1243
- rlimit = ANSIBALLZ_RLIMIT_TEMPLATE % dict(
1244
- rlimit_nofile=rlimit_nofile,
1245
- )
1246
- else:
1247
- rlimit = ''
1103
+ rlimit_nofile = int(templar._engine.template(rlimit_nofile, options=TemplateOptions(value_for_omit=0)))
1248
1104
 
1249
1105
  coverage_config = os.environ.get('_ANSIBLE_COVERAGE_CONFIG')
1250
1106
 
1251
1107
  if coverage_config:
1252
1108
  coverage_output = os.environ['_ANSIBLE_COVERAGE_OUTPUT']
1253
-
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
1109
  else:
1266
- coverage = ''
1110
+ coverage_output = None
1111
+
1112
+ if not isinstance(module_metadata, ModuleMetadataV1):
1113
+ raise NotImplementedError()
1114
+
1115
+ params = dict(ANSIBLE_MODULE_ARGS=module_args,)
1116
+ encoder = get_module_encoder(module_metadata.serialization_profile, Direction.CONTROLLER_TO_MODULE)
1117
+ try:
1118
+ encoded_params = json.dumps(params, cls=encoder)
1119
+ except TypeError as ex:
1120
+ raise AnsibleError(f'Failed to serialize arguments for the {module_name!r} module.') from ex
1267
1121
 
1268
- output.write(to_bytes(ACTIVE_ANSIBALLZ_TEMPLATE % dict(
1269
- zipdata=zipdata,
1122
+ code = _get_ansiballz_code(shebang)
1123
+ args = dict(
1124
+ zipdata=to_text(zipdata),
1270
1125
  ansible_module=module_name,
1271
1126
  module_fqn=remote_module_fqn,
1272
- params=python_repred_params,
1273
- shebang=shebang,
1274
- coding=ENCODING_STRING,
1127
+ params=encoded_params,
1128
+ profile=module_metadata.serialization_profile,
1129
+ plugin_info_dict=dataclasses.asdict(plugin),
1275
1130
  date_time=date_time,
1276
- coverage=coverage,
1277
- rlimit=rlimit,
1278
- )))
1131
+ coverage_config=coverage_config,
1132
+ coverage_output=coverage_output,
1133
+ rlimit_nofile=rlimit_nofile,
1134
+ )
1135
+
1136
+ args_string = '\n'.join(f'{key}={value!r},' for key, value in args.items())
1137
+
1138
+ wrapper = f"""{code}
1139
+
1140
+ if __name__ == "__main__":
1141
+ _ansiballz_main(
1142
+ {args_string}
1143
+ )
1144
+ """
1145
+
1146
+ output.write(to_bytes(wrapper))
1147
+
1279
1148
  b_module_data = output.getvalue()
1280
1149
 
1281
1150
  elif module_substyle == 'powershell':
1151
+ module_metadata = ModuleMetadataV1(serialization_profile='legacy') # DTFIX-FUTURE: support serialization profiles for PowerShell modules
1152
+
1282
1153
  # Powershell/winrm don't actually make use of shebang so we can
1283
1154
  # safely set this here. If we let the fallback code handle this
1284
1155
  # it can fail in the presence of the UTF8 BOM commonly added by
1285
1156
  # Windows text editors
1286
- shebang = u'#!powershell'
1157
+ shebang = '#!powershell'
1287
1158
  # create the common exec wrapper payload and set that as the module_data
1288
1159
  # bytes
1289
1160
  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
1161
+ name=remote_module_fqn,
1162
+ module_data=b_module_data,
1163
+ module_path=module_path,
1164
+ module_args=module_args,
1165
+ environment=environment,
1166
+ async_timeout=async_timeout,
1167
+ become_plugin=become_plugin,
1168
+ substyle=module_substyle,
1169
+ task_vars=task_vars,
1170
+ profile=module_metadata.serialization_profile,
1293
1171
  )
1294
1172
 
1295
1173
  elif module_substyle == 'jsonargs':
1296
- module_args_json = to_bytes(json.dumps(module_args, cls=AnsibleJSONEncoder, vault_to_text=True))
1174
+ encoder = get_module_encoder('legacy', Direction.CONTROLLER_TO_MODULE)
1175
+ module_args_json = to_bytes(json.dumps(module_args, cls=encoder))
1297
1176
 
1298
1177
  # these strings could be included in a third-party module but
1299
1178
  # officially they were included in the 'basic' snippet for new-style
@@ -1303,15 +1182,32 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
1303
1182
  python_repred_args = to_bytes(repr(module_args_json))
1304
1183
  b_module_data = b_module_data.replace(REPLACER_VERSION, to_bytes(repr(__version__)))
1305
1184
  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)))
1185
+ b_module_data = b_module_data.replace(
1186
+ REPLACER_SELINUX,
1187
+ to_bytes(','.join(C.DEFAULT_SELINUX_SPECIAL_FS))) # type: ignore[attr-defined]
1307
1188
 
1308
1189
  # The main event -- substitute the JSON args string into the module
1309
1190
  b_module_data = b_module_data.replace(REPLACER_JSONARGS, module_args_json)
1310
1191
 
1311
- facility = b'syslog.' + to_bytes(task_vars.get('ansible_syslog_facility', C.DEFAULT_SYSLOG_FACILITY), errors='surrogate_or_strict')
1192
+ syslog_facility = task_vars.get(
1193
+ 'ansible_syslog_facility',
1194
+ C.DEFAULT_SYSLOG_FACILITY) # type: ignore[attr-defined]
1195
+ facility = b'syslog.' + to_bytes(syslog_facility, errors='surrogate_or_strict')
1312
1196
  b_module_data = b_module_data.replace(b'syslog.LOG_USER', facility)
1313
1197
 
1314
- return (b_module_data, module_style, shebang)
1198
+ module_metadata = ModuleMetadataV1(serialization_profile='legacy')
1199
+ else:
1200
+ module_metadata = ModuleMetadataV1(serialization_profile='legacy')
1201
+
1202
+ if not isinstance(module_metadata, ModuleMetadataV1):
1203
+ raise NotImplementedError(type(module_metadata))
1204
+
1205
+ return _BuiltModule(
1206
+ b_module_data=b_module_data,
1207
+ module_style=module_style,
1208
+ shebang=shebang,
1209
+ serialization_profile=module_metadata.serialization_profile,
1210
+ )
1315
1211
 
1316
1212
 
1317
1213
  def _extract_interpreter(b_module_data):
@@ -1337,8 +1233,20 @@ def _extract_interpreter(b_module_data):
1337
1233
  return interpreter, args
1338
1234
 
1339
1235
 
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):
1236
+ def modify_module(
1237
+ *,
1238
+ module_name: str,
1239
+ plugin: PluginInfo,
1240
+ module_path,
1241
+ module_args,
1242
+ templar,
1243
+ task_vars=None,
1244
+ module_compression='ZIP_STORED',
1245
+ async_timeout=0,
1246
+ become_plugin=None,
1247
+ environment=None,
1248
+ remote_is_local=False,
1249
+ ) -> _BuiltModule:
1342
1250
  """
1343
1251
  Used to insert chunks of code into modules before transfer rather than
1344
1252
  doing regular python imports. This allows for more efficient transfer in
@@ -1367,13 +1275,31 @@ def modify_module(module_name, module_path, module_args, templar, task_vars=None
1367
1275
  # read in the module source
1368
1276
  b_module_data = f.read()
1369
1277
 
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)
1278
+ module_bits = _find_module_utils(
1279
+ module_name=module_name,
1280
+ plugin=plugin,
1281
+ b_module_data=b_module_data,
1282
+ module_path=module_path,
1283
+ module_args=module_args,
1284
+ task_vars=task_vars,
1285
+ templar=templar,
1286
+ module_compression=module_compression,
1287
+ async_timeout=async_timeout,
1288
+ become_plugin=become_plugin,
1289
+ environment=environment,
1290
+ remote_is_local=remote_is_local,
1291
+ )
1292
+
1293
+ b_module_data = module_bits.b_module_data
1294
+ shebang = module_bits.shebang
1374
1295
 
1375
- if module_style == 'binary':
1376
- return (b_module_data, module_style, to_text(shebang, nonstring='passthru'))
1296
+ if module_bits.module_style == 'binary':
1297
+ return _BuiltModule(
1298
+ b_module_data=module_bits.b_module_data,
1299
+ module_style=module_bits.module_style,
1300
+ shebang=to_text(module_bits.shebang, nonstring='passthru'),
1301
+ serialization_profile=module_bits.serialization_profile,
1302
+ )
1377
1303
  elif shebang is None:
1378
1304
  interpreter, args = _extract_interpreter(b_module_data)
1379
1305
  # No interpreter/shebang, assume a binary module?
@@ -1387,15 +1313,20 @@ def modify_module(module_name, module_path, module_args, templar, task_vars=None
1387
1313
  if interpreter != new_interpreter:
1388
1314
  b_lines[0] = to_bytes(shebang, errors='surrogate_or_strict', nonstring='passthru')
1389
1315
 
1390
- if os.path.basename(interpreter).startswith(u'python'):
1391
- b_lines.insert(1, b_ENCODING_STRING)
1392
-
1393
1316
  b_module_data = b"\n".join(b_lines)
1394
1317
 
1395
- return (b_module_data, module_style, shebang)
1318
+ return _BuiltModule(
1319
+ b_module_data=b_module_data,
1320
+ module_style=module_bits.module_style,
1321
+ shebang=shebang,
1322
+ serialization_profile=module_bits.serialization_profile,
1323
+ )
1324
+
1396
1325
 
1326
+ def _get_action_arg_defaults(action: str, task: Task, templar: TemplateEngine) -> dict[str, t.Any]:
1327
+ action_groups = task._parent._play._action_groups
1328
+ defaults = task.module_defaults
1397
1329
 
1398
- def get_action_args_with_defaults(action, args, defaults, templar, action_groups=None):
1399
1330
  # Get the list of groups that contain this action
1400
1331
  if action_groups is None:
1401
1332
  msg = (
@@ -1408,7 +1339,7 @@ def get_action_args_with_defaults(action, args, defaults, templar, action_groups
1408
1339
  else:
1409
1340
  group_names = action_groups.get(action, [])
1410
1341
 
1411
- tmp_args = {}
1342
+ tmp_args: dict[str, t.Any] = {}
1412
1343
  module_defaults = {}
1413
1344
 
1414
1345
  # Merge latest defaults into dict, since they are a list of dicts
@@ -1416,18 +1347,20 @@ def get_action_args_with_defaults(action, args, defaults, templar, action_groups
1416
1347
  for default in defaults:
1417
1348
  module_defaults.update(default)
1418
1349
 
1419
- # module_defaults keys are static, but the values may be templated
1420
- module_defaults = templar.template(module_defaults)
1421
1350
  for default in module_defaults:
1422
1351
  if default.startswith('group/'):
1423
1352
  group_name = default.split('group/')[-1]
1424
1353
  if group_name in group_names:
1425
- tmp_args.update((module_defaults.get('group/%s' % group_name) or {}).copy())
1354
+ tmp_args.update(templar.resolve_to_container(module_defaults.get(f'group/{group_name}', {})))
1426
1355
 
1427
1356
  # handle specific action defaults
1428
- tmp_args.update(module_defaults.get(action, {}).copy())
1429
-
1430
- # direct args override all
1431
- tmp_args.update(args)
1357
+ tmp_args.update(templar.resolve_to_container(module_defaults.get(action, {})))
1432
1358
 
1433
1359
  return tmp_args
1360
+
1361
+
1362
+ def _apply_action_arg_defaults(action: str, task: Task, action_args: dict[str, t.Any], templar: Templar) -> dict[str, t.Any]:
1363
+ args = _get_action_arg_defaults(action, task, templar._engine)
1364
+ args.update(action_args)
1365
+
1366
+ return args