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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (710) hide show
  1. ansible/_internal/__init__.py +53 -0
  2. ansible/_internal/_ansiballz.py +265 -0
  3. ansible/_internal/_collection_proxy.py +47 -0
  4. ansible/_internal/_datatag/__init__.py +0 -0
  5. ansible/_internal/_datatag/_tags.py +130 -0
  6. ansible/_internal/_datatag/_utils.py +19 -0
  7. ansible/_internal/_datatag/_wrappers.py +33 -0
  8. ansible/_internal/_errors/__init__.py +0 -0
  9. ansible/_internal/_errors/_captured.py +128 -0
  10. ansible/_internal/_errors/_handler.py +91 -0
  11. ansible/_internal/_errors/_utils.py +310 -0
  12. ansible/_internal/_json/__init__.py +203 -0
  13. ansible/_internal/_json/_legacy_encoder.py +34 -0
  14. ansible/_internal/_json/_profiles/__init__.py +0 -0
  15. ansible/_internal/_json/_profiles/_cache_persistence.py +55 -0
  16. ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
  17. ansible/_internal/_json/_profiles/_legacy.py +197 -0
  18. ansible/_internal/_locking.py +21 -0
  19. ansible/_internal/_plugins/__init__.py +0 -0
  20. ansible/_internal/_plugins/_cache.py +57 -0
  21. ansible/_internal/_task.py +78 -0
  22. ansible/_internal/_templating/__init__.py +10 -0
  23. ansible/_internal/_templating/_access.py +86 -0
  24. ansible/_internal/_templating/_chain_templar.py +63 -0
  25. ansible/_internal/_templating/_datatag.py +95 -0
  26. ansible/_internal/_templating/_engine.py +588 -0
  27. ansible/_internal/_templating/_errors.py +28 -0
  28. ansible/_internal/_templating/_jinja_bits.py +1066 -0
  29. ansible/_internal/_templating/_jinja_common.py +332 -0
  30. ansible/_internal/_templating/_jinja_patches.py +44 -0
  31. ansible/_internal/_templating/_jinja_plugins.py +345 -0
  32. ansible/_internal/_templating/_lazy_containers.py +633 -0
  33. ansible/_internal/_templating/_marker_behaviors.py +103 -0
  34. ansible/_internal/_templating/_transform.py +63 -0
  35. ansible/_internal/_templating/_utils.py +107 -0
  36. ansible/_internal/_wrapt.py +1052 -0
  37. ansible/_internal/_yaml/__init__.py +0 -0
  38. ansible/_internal/_yaml/_constructor.py +240 -0
  39. ansible/_internal/_yaml/_dumper.py +62 -0
  40. ansible/_internal/_yaml/_errors.py +166 -0
  41. ansible/_internal/_yaml/_loader.py +66 -0
  42. ansible/_internal/ansible_collections/ansible/_protomatter/README.md +11 -0
  43. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/action/debug.py +36 -0
  44. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/apply_trust.py +19 -0
  45. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +18 -0
  46. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/finalize.py +16 -0
  47. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/origin.py +18 -0
  48. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.py +24 -0
  49. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.yml +33 -0
  50. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/tag_names.py +16 -0
  51. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +17 -0
  52. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +49 -0
  53. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.py +21 -0
  54. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.yml +2 -0
  55. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.py +15 -0
  56. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.yml +19 -0
  57. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.py +18 -0
  58. ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.yml +19 -0
  59. ansible/cli/__init__.py +159 -89
  60. ansible/cli/_ssh_askpass.py +47 -0
  61. ansible/cli/adhoc.py +14 -7
  62. ansible/cli/arguments/option_helpers.py +154 -7
  63. ansible/cli/config.py +43 -68
  64. ansible/cli/console.py +10 -8
  65. ansible/cli/doc.py +62 -53
  66. ansible/cli/galaxy.py +27 -20
  67. ansible/cli/inventory.py +28 -26
  68. ansible/cli/playbook.py +4 -12
  69. ansible/cli/pull.py +51 -11
  70. ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
  71. ansible/cli/vault.py +12 -11
  72. ansible/compat/__init__.py +2 -2
  73. ansible/config/base.yml +166 -112
  74. ansible/config/manager.py +52 -49
  75. ansible/constants.py +3 -4
  76. ansible/errors/__init__.py +277 -235
  77. ansible/executor/interpreter_discovery.py +28 -149
  78. ansible/executor/module_common.py +426 -493
  79. ansible/executor/play_iterator.py +22 -27
  80. ansible/executor/playbook_executor.py +11 -11
  81. ansible/executor/powershell/async_watchdog.ps1 +97 -102
  82. ansible/executor/powershell/async_wrapper.ps1 +202 -151
  83. ansible/executor/powershell/become_wrapper.ps1 +89 -144
  84. ansible/executor/powershell/bootstrap_wrapper.ps1 +24 -9
  85. ansible/executor/powershell/coverage_wrapper.ps1 +82 -135
  86. ansible/executor/powershell/exec_wrapper.ps1 +462 -196
  87. ansible/executor/powershell/module_manifest.py +417 -265
  88. ansible/executor/powershell/module_wrapper.ps1 +169 -186
  89. ansible/executor/powershell/psrp_fetch_file.ps1 +41 -0
  90. ansible/executor/powershell/psrp_put_file.ps1 +122 -0
  91. ansible/executor/powershell/winrm_fetch_file.ps1 +46 -0
  92. ansible/executor/powershell/winrm_put_file.ps1 +36 -0
  93. ansible/executor/process/worker.py +161 -96
  94. ansible/executor/stats.py +5 -5
  95. ansible/executor/task_executor.py +268 -258
  96. ansible/executor/task_queue_manager.py +124 -90
  97. ansible/executor/task_result.py +183 -78
  98. ansible/galaxy/__init__.py +2 -2
  99. ansible/galaxy/api.py +22 -18
  100. ansible/galaxy/collection/__init__.py +1 -1
  101. ansible/galaxy/collection/concrete_artifact_manager.py +8 -11
  102. ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
  103. ansible/galaxy/dependency_resolution/providers.py +1 -1
  104. ansible/galaxy/dependency_resolution/reporters.py +81 -0
  105. ansible/galaxy/role.py +4 -8
  106. ansible/galaxy/token.py +28 -21
  107. ansible/inventory/data.py +47 -57
  108. ansible/inventory/group.py +44 -72
  109. ansible/inventory/helpers.py +9 -0
  110. ansible/inventory/host.py +32 -54
  111. ansible/inventory/manager.py +78 -34
  112. ansible/keyword_desc.yml +1 -1
  113. ansible/module_utils/_internal/__init__.py +55 -0
  114. ansible/module_utils/_internal/_ambient_context.py +58 -0
  115. ansible/module_utils/_internal/_ansiballz.py +133 -0
  116. ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
  117. ansible/module_utils/_internal/_dataclass_annotation_patch.py +64 -0
  118. ansible/module_utils/_internal/_dataclass_validation.py +217 -0
  119. ansible/module_utils/_internal/_datatag/__init__.py +928 -0
  120. ansible/module_utils/_internal/_datatag/_tags.py +38 -0
  121. ansible/module_utils/_internal/_debugging.py +31 -0
  122. ansible/module_utils/_internal/_errors.py +30 -0
  123. ansible/module_utils/_internal/_json/__init__.py +63 -0
  124. ansible/module_utils/_internal/_json/_legacy_encoder.py +26 -0
  125. ansible/module_utils/_internal/_json/_profiles/__init__.py +410 -0
  126. ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
  127. ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +31 -0
  128. ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +35 -0
  129. ansible/module_utils/_internal/_json/_profiles/_module_modern_c2m.py +35 -0
  130. ansible/module_utils/_internal/_json/_profiles/_module_modern_m2c.py +33 -0
  131. ansible/module_utils/_internal/_json/_profiles/_tagless.py +50 -0
  132. ansible/module_utils/_internal/_patches/__init__.py +66 -0
  133. ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +55 -0
  134. ansible/module_utils/_internal/_patches/_socket_patch.py +34 -0
  135. ansible/module_utils/_internal/_patches/_sys_intern_patch.py +34 -0
  136. ansible/module_utils/_internal/_plugin_exec_context.py +49 -0
  137. ansible/module_utils/_internal/_testing.py +0 -0
  138. ansible/module_utils/_internal/_traceback.py +89 -0
  139. ansible/module_utils/ansible_release.py +2 -2
  140. ansible/module_utils/api.py +1 -2
  141. ansible/module_utils/basic.py +152 -120
  142. ansible/module_utils/common/_utils.py +24 -28
  143. ansible/module_utils/common/collections.py +1 -2
  144. ansible/module_utils/common/dict_transformations.py +2 -2
  145. ansible/module_utils/common/file.py +2 -2
  146. ansible/module_utils/common/json.py +90 -84
  147. ansible/module_utils/common/locale.py +2 -2
  148. ansible/module_utils/common/messages.py +108 -0
  149. ansible/module_utils/common/parameters.py +27 -24
  150. ansible/module_utils/common/process.py +2 -2
  151. ansible/module_utils/common/respawn.py +41 -19
  152. ansible/module_utils/common/sentinel.py +66 -0
  153. ansible/module_utils/common/sys_info.py +8 -8
  154. ansible/module_utils/common/text/converters.py +16 -37
  155. ansible/module_utils/common/validation.py +35 -24
  156. ansible/module_utils/common/warnings.py +86 -25
  157. ansible/module_utils/common/yaml.py +29 -3
  158. ansible/module_utils/compat/datetime.py +33 -21
  159. ansible/module_utils/compat/paramiko.py +21 -10
  160. ansible/module_utils/compat/typing.py +6 -5
  161. ansible/module_utils/connection.py +2 -2
  162. ansible/module_utils/csharp/Ansible.Basic.cs +14 -11
  163. ansible/module_utils/csharp/Ansible.Become.cs +1 -0
  164. ansible/module_utils/csharp/Ansible._Async.cs +517 -0
  165. ansible/module_utils/datatag.py +46 -0
  166. ansible/module_utils/distro/__init__.py +2 -2
  167. ansible/module_utils/facts/ansible_collector.py +4 -5
  168. ansible/module_utils/facts/collector.py +13 -14
  169. ansible/module_utils/facts/compat.py +4 -4
  170. ansible/module_utils/facts/default_collectors.py +1 -1
  171. ansible/module_utils/facts/hardware/aix.py +34 -0
  172. ansible/module_utils/facts/hardware/base.py +1 -1
  173. ansible/module_utils/facts/hardware/darwin.py +1 -3
  174. ansible/module_utils/facts/hardware/freebsd.py +2 -2
  175. ansible/module_utils/facts/hardware/linux.py +4 -4
  176. ansible/module_utils/facts/namespace.py +1 -1
  177. ansible/module_utils/facts/network/base.py +1 -1
  178. ansible/module_utils/facts/network/fc_wwn.py +1 -2
  179. ansible/module_utils/facts/network/iscsi.py +1 -2
  180. ansible/module_utils/facts/network/nvme.py +1 -2
  181. ansible/module_utils/facts/other/facter.py +1 -2
  182. ansible/module_utils/facts/other/ohai.py +2 -3
  183. ansible/module_utils/facts/system/apparmor.py +1 -2
  184. ansible/module_utils/facts/system/caps.py +1 -1
  185. ansible/module_utils/facts/system/chroot.py +1 -2
  186. ansible/module_utils/facts/system/cmdline.py +1 -2
  187. ansible/module_utils/facts/system/date_time.py +5 -3
  188. ansible/module_utils/facts/system/distribution.py +9 -8
  189. ansible/module_utils/facts/system/dns.py +1 -1
  190. ansible/module_utils/facts/system/env.py +1 -2
  191. ansible/module_utils/facts/system/fips.py +7 -20
  192. ansible/module_utils/facts/system/loadavg.py +1 -2
  193. ansible/module_utils/facts/system/local.py +1 -2
  194. ansible/module_utils/facts/system/lsb.py +1 -2
  195. ansible/module_utils/facts/system/pkg_mgr.py +1 -2
  196. ansible/module_utils/facts/system/platform.py +1 -2
  197. ansible/module_utils/facts/system/python.py +1 -2
  198. ansible/module_utils/facts/system/selinux.py +1 -1
  199. ansible/module_utils/facts/system/service_mgr.py +1 -2
  200. ansible/module_utils/facts/system/ssh_pub_keys.py +1 -1
  201. ansible/module_utils/facts/system/systemd.py +1 -1
  202. ansible/module_utils/facts/system/user.py +1 -2
  203. ansible/module_utils/facts/utils.py +3 -3
  204. ansible/module_utils/facts/virtual/base.py +1 -1
  205. ansible/module_utils/facts/virtual/sunos.py +3 -15
  206. ansible/module_utils/facts/virtual/sysctl.py +3 -16
  207. ansible/module_utils/json_utils.py +2 -2
  208. ansible/module_utils/parsing/convert_bool.py +1 -1
  209. ansible/module_utils/service.py +18 -21
  210. ansible/module_utils/splitter.py +7 -7
  211. ansible/module_utils/testing.py +31 -0
  212. ansible/module_utils/urls.py +60 -31
  213. ansible/modules/add_host.py +4 -4
  214. ansible/modules/apt.py +60 -46
  215. ansible/modules/apt_key.py +19 -12
  216. ansible/modules/apt_repository.py +19 -16
  217. ansible/modules/assemble.py +6 -6
  218. ansible/modules/assert.py +4 -4
  219. ansible/modules/async_status.py +10 -12
  220. ansible/modules/async_wrapper.py +8 -3
  221. ansible/modules/blockinfile.py +6 -7
  222. ansible/modules/command.py +10 -17
  223. ansible/modules/copy.py +57 -144
  224. ansible/modules/cron.py +20 -15
  225. ansible/modules/deb822_repository.py +8 -9
  226. ansible/modules/debconf.py +5 -5
  227. ansible/modules/debug.py +4 -4
  228. ansible/modules/dnf.py +8 -8
  229. ansible/modules/dnf5.py +39 -13
  230. ansible/modules/dpkg_selections.py +4 -4
  231. ansible/modules/expect.py +8 -10
  232. ansible/modules/fail.py +4 -4
  233. ansible/modules/fetch.py +4 -4
  234. ansible/modules/file.py +174 -133
  235. ansible/modules/find.py +19 -17
  236. ansible/modules/gather_facts.py +3 -3
  237. ansible/modules/get_url.py +59 -53
  238. ansible/modules/getent.py +7 -9
  239. ansible/modules/git.py +28 -25
  240. ansible/modules/group.py +6 -6
  241. ansible/modules/group_by.py +4 -4
  242. ansible/modules/hostname.py +13 -29
  243. ansible/modules/import_playbook.py +6 -6
  244. ansible/modules/import_role.py +6 -6
  245. ansible/modules/import_tasks.py +6 -6
  246. ansible/modules/include_role.py +6 -6
  247. ansible/modules/include_tasks.py +6 -6
  248. ansible/modules/include_vars.py +6 -6
  249. ansible/modules/iptables.py +86 -73
  250. ansible/modules/known_hosts.py +10 -10
  251. ansible/modules/lineinfile.py +5 -5
  252. ansible/modules/meta.py +4 -4
  253. ansible/modules/mount_facts.py +2 -2
  254. ansible/modules/package.py +4 -4
  255. ansible/modules/package_facts.py +22 -10
  256. ansible/modules/pause.py +6 -6
  257. ansible/modules/ping.py +6 -6
  258. ansible/modules/pip.py +10 -11
  259. ansible/modules/raw.py +4 -4
  260. ansible/modules/reboot.py +6 -6
  261. ansible/modules/replace.py +9 -13
  262. ansible/modules/rpm_key.py +7 -8
  263. ansible/modules/script.py +4 -4
  264. ansible/modules/service.py +7 -8
  265. ansible/modules/service_facts.py +87 -10
  266. ansible/modules/set_fact.py +5 -5
  267. ansible/modules/set_stats.py +4 -4
  268. ansible/modules/setup.py +2 -2
  269. ansible/modules/shell.py +6 -6
  270. ansible/modules/slurp.py +6 -6
  271. ansible/modules/stat.py +9 -23
  272. ansible/modules/subversion.py +15 -15
  273. ansible/modules/systemd.py +6 -6
  274. ansible/modules/systemd_service.py +6 -6
  275. ansible/modules/sysvinit.py +6 -6
  276. ansible/modules/tempfile.py +5 -6
  277. ansible/modules/template.py +6 -6
  278. ansible/modules/unarchive.py +32 -11
  279. ansible/modules/uri.py +33 -26
  280. ansible/modules/user.py +53 -34
  281. ansible/modules/validate_argument_spec.py +10 -7
  282. ansible/modules/wait_for.py +32 -27
  283. ansible/modules/wait_for_connection.py +6 -6
  284. ansible/modules/yum_repository.py +6 -6
  285. ansible/parsing/ajson.py +14 -32
  286. ansible/parsing/dataloader.py +99 -54
  287. ansible/parsing/mod_args.py +28 -44
  288. ansible/parsing/plugin_docs.py +21 -86
  289. ansible/parsing/quoting.py +1 -1
  290. ansible/parsing/splitter.py +27 -12
  291. ansible/parsing/utils/addresses.py +24 -24
  292. ansible/parsing/utils/jsonify.py +5 -1
  293. ansible/parsing/utils/yaml.py +32 -61
  294. ansible/parsing/vault/__init__.py +319 -87
  295. ansible/parsing/yaml/__init__.py +0 -18
  296. ansible/parsing/yaml/dumper.py +6 -120
  297. ansible/parsing/yaml/loader.py +6 -39
  298. ansible/parsing/yaml/objects.py +43 -335
  299. ansible/playbook/__init__.py +1 -1
  300. ansible/playbook/attribute.py +8 -3
  301. ansible/playbook/base.py +182 -132
  302. ansible/playbook/block.py +26 -24
  303. ansible/playbook/collectionsearch.py +1 -15
  304. ansible/playbook/conditional.py +3 -77
  305. ansible/playbook/handler.py +8 -2
  306. ansible/playbook/helpers.py +41 -53
  307. ansible/playbook/included_file.py +31 -27
  308. ansible/playbook/loop_control.py +2 -2
  309. ansible/playbook/play.py +85 -44
  310. ansible/playbook/play_context.py +12 -17
  311. ansible/playbook/playbook_include.py +14 -15
  312. ansible/playbook/role/__init__.py +24 -26
  313. ansible/playbook/role/definition.py +15 -17
  314. ansible/playbook/role/include.py +2 -4
  315. ansible/playbook/role/metadata.py +10 -11
  316. ansible/playbook/role_include.py +3 -3
  317. ansible/playbook/taggable.py +13 -8
  318. ansible/playbook/task.py +188 -118
  319. ansible/playbook/task_include.py +5 -5
  320. ansible/plugins/__init__.py +68 -21
  321. ansible/plugins/action/__init__.py +209 -176
  322. ansible/plugins/action/add_host.py +1 -1
  323. ansible/plugins/action/assemble.py +1 -1
  324. ansible/plugins/action/assert.py +54 -66
  325. ansible/plugins/action/copy.py +7 -11
  326. ansible/plugins/action/debug.py +37 -31
  327. ansible/plugins/action/dnf.py +3 -4
  328. ansible/plugins/action/fail.py +1 -1
  329. ansible/plugins/action/fetch.py +4 -5
  330. ansible/plugins/action/gather_facts.py +7 -6
  331. ansible/plugins/action/group_by.py +1 -1
  332. ansible/plugins/action/include_vars.py +10 -11
  333. ansible/plugins/action/package.py +3 -6
  334. ansible/plugins/action/pause.py +2 -2
  335. ansible/plugins/action/script.py +15 -8
  336. ansible/plugins/action/service.py +6 -11
  337. ansible/plugins/action/set_fact.py +3 -12
  338. ansible/plugins/action/set_stats.py +3 -8
  339. ansible/plugins/action/template.py +35 -59
  340. ansible/plugins/action/unarchive.py +1 -1
  341. ansible/plugins/action/validate_argument_spec.py +5 -5
  342. ansible/plugins/action/wait_for_connection.py +1 -1
  343. ansible/plugins/become/__init__.py +31 -8
  344. ansible/plugins/become/runas.py +71 -0
  345. ansible/plugins/become/su.py +13 -8
  346. ansible/plugins/become/sudo.py +19 -0
  347. ansible/plugins/cache/__init__.py +35 -44
  348. ansible/plugins/cache/base.py +8 -0
  349. ansible/plugins/cache/jsonfile.py +10 -16
  350. ansible/plugins/cache/memory.py +6 -12
  351. ansible/plugins/callback/__init__.py +284 -179
  352. ansible/plugins/callback/default.py +99 -92
  353. ansible/plugins/callback/junit.py +44 -39
  354. ansible/plugins/callback/minimal.py +28 -25
  355. ansible/plugins/callback/oneline.py +28 -21
  356. ansible/plugins/callback/tree.py +16 -11
  357. ansible/plugins/connection/__init__.py +47 -34
  358. ansible/plugins/connection/local.py +150 -54
  359. ansible/plugins/connection/paramiko_ssh.py +21 -18
  360. ansible/plugins/connection/psrp.py +76 -165
  361. ansible/plugins/connection/ssh.py +301 -78
  362. ansible/plugins/connection/winrm.py +58 -140
  363. ansible/plugins/doc_fragments/action_common_attributes.py +14 -14
  364. ansible/plugins/doc_fragments/action_core.py +6 -6
  365. ansible/plugins/doc_fragments/backup.py +2 -2
  366. ansible/plugins/doc_fragments/checksum_common.py +27 -0
  367. ansible/plugins/doc_fragments/constructed.py +6 -2
  368. ansible/plugins/doc_fragments/decrypt.py +2 -2
  369. ansible/plugins/doc_fragments/default_callback.py +2 -2
  370. ansible/plugins/doc_fragments/files.py +2 -2
  371. ansible/plugins/doc_fragments/inventory_cache.py +2 -2
  372. ansible/plugins/doc_fragments/result_format_callback.py +2 -2
  373. ansible/plugins/doc_fragments/return_common.py +2 -2
  374. ansible/plugins/doc_fragments/template_common.py +4 -4
  375. ansible/plugins/doc_fragments/url.py +17 -1
  376. ansible/plugins/doc_fragments/url_windows.py +2 -2
  377. ansible/plugins/doc_fragments/validate.py +2 -2
  378. ansible/plugins/doc_fragments/vars_plugin_staging.py +2 -2
  379. ansible/plugins/filter/__init__.py +6 -2
  380. ansible/plugins/filter/b64decode.yml +22 -0
  381. ansible/plugins/filter/b64encode.yml +22 -0
  382. ansible/plugins/filter/bool.yml +11 -4
  383. ansible/plugins/filter/core.py +225 -108
  384. ansible/plugins/filter/encryption.py +32 -32
  385. ansible/plugins/filter/flatten.yml +3 -2
  386. ansible/plugins/filter/human_to_bytes.yml +1 -1
  387. ansible/plugins/filter/mathstuff.py +30 -37
  388. ansible/plugins/filter/password_hash.yml +8 -0
  389. ansible/plugins/filter/regex_search.yml +1 -4
  390. ansible/plugins/filter/split.yml +1 -1
  391. ansible/plugins/filter/to_nice_yaml.yml +0 -4
  392. ansible/plugins/filter/to_yaml.yml +0 -4
  393. ansible/plugins/filter/unvault.yml +1 -1
  394. ansible/plugins/filter/urls.py +1 -1
  395. ansible/plugins/filter/urlsplit.py +8 -9
  396. ansible/plugins/filter/vault.yml +14 -9
  397. ansible/plugins/filter/win_basename.yml +6 -1
  398. ansible/plugins/filter/win_dirname.yml +5 -0
  399. ansible/plugins/inventory/__init__.py +97 -77
  400. ansible/plugins/inventory/advanced_host_list.py +7 -5
  401. ansible/plugins/inventory/auto.py +11 -4
  402. ansible/plugins/inventory/constructed.py +21 -24
  403. ansible/plugins/inventory/generator.py +16 -11
  404. ansible/plugins/inventory/host_list.py +7 -5
  405. ansible/plugins/inventory/ini.py +78 -44
  406. ansible/plugins/inventory/script.py +189 -119
  407. ansible/plugins/inventory/toml.py +16 -126
  408. ansible/plugins/inventory/yaml.py +10 -8
  409. ansible/plugins/list.py +3 -3
  410. ansible/plugins/loader.py +197 -82
  411. ansible/plugins/lookup/__init__.py +21 -4
  412. ansible/plugins/lookup/config.py +21 -35
  413. ansible/plugins/lookup/csvfile.py +3 -2
  414. ansible/plugins/lookup/dict.py +1 -6
  415. ansible/plugins/lookup/env.py +12 -9
  416. ansible/plugins/lookup/file.py +5 -8
  417. ansible/plugins/lookup/first_found.py +86 -55
  418. ansible/plugins/lookup/indexed_items.py +1 -10
  419. ansible/plugins/lookup/ini.py +14 -13
  420. ansible/plugins/lookup/items.py +1 -1
  421. ansible/plugins/lookup/lines.py +8 -1
  422. ansible/plugins/lookup/list.py +1 -1
  423. ansible/plugins/lookup/nested.py +2 -18
  424. ansible/plugins/lookup/password.py +5 -5
  425. ansible/plugins/lookup/pipe.py +5 -7
  426. ansible/plugins/lookup/sequence.py +18 -8
  427. ansible/plugins/lookup/subelements.py +1 -4
  428. ansible/plugins/lookup/template.py +42 -36
  429. ansible/plugins/lookup/together.py +0 -12
  430. ansible/plugins/lookup/unvault.py +1 -5
  431. ansible/plugins/lookup/url.py +2 -8
  432. ansible/plugins/lookup/vars.py +16 -24
  433. ansible/plugins/shell/__init__.py +2 -2
  434. ansible/plugins/shell/cmd.py +2 -2
  435. ansible/plugins/shell/powershell.py +39 -22
  436. ansible/plugins/shell/sh.py +3 -2
  437. ansible/plugins/strategy/__init__.py +159 -184
  438. ansible/plugins/strategy/debug.py +2 -2
  439. ansible/plugins/strategy/free.py +16 -31
  440. ansible/plugins/strategy/host_pinned.py +2 -2
  441. ansible/plugins/strategy/linear.py +41 -41
  442. ansible/plugins/terminal/__init__.py +4 -4
  443. ansible/plugins/test/__init__.py +7 -2
  444. ansible/plugins/test/core.py +55 -21
  445. ansible/plugins/test/files.py +1 -1
  446. ansible/plugins/test/mathstuff.py +3 -3
  447. ansible/plugins/test/uri.py +3 -3
  448. ansible/plugins/vars/host_group_vars.py +7 -14
  449. ansible/release.py +2 -2
  450. ansible/template/__init__.py +370 -944
  451. ansible/utils/__init__.py +0 -18
  452. ansible/utils/_ssh_agent.py +657 -0
  453. ansible/utils/collection_loader/__init__.py +52 -5
  454. ansible/utils/collection_loader/_collection_config.py +5 -6
  455. ansible/utils/collection_loader/_collection_finder.py +79 -93
  456. ansible/utils/collection_loader/_collection_meta.py +13 -8
  457. ansible/utils/display.py +433 -63
  458. ansible/utils/encrypt.py +27 -19
  459. ansible/utils/fqcn.py +2 -2
  460. ansible/utils/hashing.py +2 -2
  461. ansible/utils/helpers.py +2 -2
  462. ansible/utils/listify.py +8 -8
  463. ansible/utils/lock.py +2 -2
  464. ansible/utils/path.py +4 -4
  465. ansible/utils/plugin_docs.py +14 -13
  466. ansible/utils/sentinel.py +4 -62
  467. ansible/utils/singleton.py +2 -0
  468. ansible/utils/ssh_functions.py +1 -1
  469. ansible/utils/unsafe_proxy.py +23 -332
  470. ansible/utils/vars.py +51 -8
  471. ansible/utils/version.py +2 -2
  472. ansible/vars/clean.py +5 -5
  473. ansible/vars/hostvars.py +60 -90
  474. ansible/vars/manager.py +206 -282
  475. ansible/vars/reserved.py +8 -9
  476. ansible_core-2.19.0b2.dist-info/BSD-3-Clause.txt +28 -0
  477. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/METADATA +5 -4
  478. ansible_core-2.19.0b2.dist-info/RECORD +1072 -0
  479. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/WHEEL +1 -1
  480. ansible_test/_data/completion/docker.txt +7 -7
  481. ansible_test/_data/completion/remote.txt +6 -6
  482. ansible_test/_data/completion/windows.txt +1 -0
  483. ansible_test/_data/requirements/ansible.txt +2 -2
  484. ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
  485. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  486. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  487. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  488. ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
  489. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  490. ansible_test/_data/requirements/units.txt +1 -0
  491. ansible_test/_internal/__init__.py +1 -0
  492. ansible_test/_internal/ansible_util.py +2 -0
  493. ansible_test/_internal/become.py +1 -0
  494. ansible_test/_internal/bootstrap.py +1 -0
  495. ansible_test/_internal/cache.py +1 -0
  496. ansible_test/_internal/cgroup.py +1 -0
  497. ansible_test/_internal/ci/__init__.py +1 -0
  498. ansible_test/_internal/ci/azp.py +1 -0
  499. ansible_test/_internal/ci/local.py +1 -0
  500. ansible_test/_internal/classification/__init__.py +1 -0
  501. ansible_test/_internal/classification/common.py +1 -0
  502. ansible_test/_internal/classification/csharp.py +1 -0
  503. ansible_test/_internal/classification/powershell.py +1 -0
  504. ansible_test/_internal/classification/python.py +1 -0
  505. ansible_test/_internal/cli/__init__.py +1 -0
  506. ansible_test/_internal/cli/actions.py +1 -0
  507. ansible_test/_internal/cli/argparsing/__init__.py +1 -0
  508. ansible_test/_internal/cli/argparsing/actions.py +1 -0
  509. ansible_test/_internal/cli/argparsing/argcompletion.py +1 -0
  510. ansible_test/_internal/cli/argparsing/parsers.py +1 -0
  511. ansible_test/_internal/cli/commands/__init__.py +11 -0
  512. ansible_test/_internal/cli/commands/coverage/__init__.py +1 -0
  513. ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +1 -0
  514. ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +1 -0
  515. ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +1 -0
  516. ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +1 -0
  517. ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +1 -0
  518. ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +1 -0
  519. ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +1 -0
  520. ansible_test/_internal/cli/commands/coverage/combine.py +1 -0
  521. ansible_test/_internal/cli/commands/coverage/erase.py +1 -0
  522. ansible_test/_internal/cli/commands/coverage/html.py +1 -0
  523. ansible_test/_internal/cli/commands/coverage/report.py +1 -0
  524. ansible_test/_internal/cli/commands/coverage/xml.py +1 -0
  525. ansible_test/_internal/cli/commands/env.py +1 -0
  526. ansible_test/_internal/cli/commands/integration/__init__.py +1 -0
  527. ansible_test/_internal/cli/commands/integration/network.py +1 -0
  528. ansible_test/_internal/cli/commands/integration/posix.py +1 -0
  529. ansible_test/_internal/cli/commands/integration/windows.py +1 -0
  530. ansible_test/_internal/cli/commands/sanity.py +9 -0
  531. ansible_test/_internal/cli/commands/shell.py +1 -0
  532. ansible_test/_internal/cli/commands/units.py +1 -0
  533. ansible_test/_internal/cli/compat.py +1 -0
  534. ansible_test/_internal/cli/completers.py +1 -0
  535. ansible_test/_internal/cli/converters.py +1 -0
  536. ansible_test/_internal/cli/environments.py +1 -0
  537. ansible_test/_internal/cli/epilog.py +1 -0
  538. ansible_test/_internal/cli/parsers/__init__.py +1 -0
  539. ansible_test/_internal/cli/parsers/base_argument_parsers.py +1 -0
  540. ansible_test/_internal/cli/parsers/helpers.py +1 -0
  541. ansible_test/_internal/cli/parsers/host_config_parsers.py +1 -0
  542. ansible_test/_internal/cli/parsers/key_value_parsers.py +1 -0
  543. ansible_test/_internal/cli/parsers/value_parsers.py +1 -0
  544. ansible_test/_internal/commands/__init__.py +1 -0
  545. ansible_test/_internal/commands/coverage/__init__.py +2 -1
  546. ansible_test/_internal/commands/coverage/analyze/__init__.py +1 -0
  547. ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +1 -0
  548. ansible_test/_internal/commands/coverage/analyze/targets/combine.py +1 -0
  549. ansible_test/_internal/commands/coverage/analyze/targets/expand.py +1 -0
  550. ansible_test/_internal/commands/coverage/analyze/targets/filter.py +1 -0
  551. ansible_test/_internal/commands/coverage/analyze/targets/generate.py +1 -0
  552. ansible_test/_internal/commands/coverage/analyze/targets/missing.py +1 -0
  553. ansible_test/_internal/commands/coverage/combine.py +2 -1
  554. ansible_test/_internal/commands/coverage/erase.py +1 -0
  555. ansible_test/_internal/commands/coverage/html.py +1 -0
  556. ansible_test/_internal/commands/coverage/report.py +1 -0
  557. ansible_test/_internal/commands/coverage/xml.py +1 -0
  558. ansible_test/_internal/commands/env/__init__.py +2 -0
  559. ansible_test/_internal/commands/integration/__init__.py +4 -0
  560. ansible_test/_internal/commands/integration/cloud/__init__.py +1 -0
  561. ansible_test/_internal/commands/integration/cloud/acme.py +2 -1
  562. ansible_test/_internal/commands/integration/cloud/aws.py +1 -0
  563. ansible_test/_internal/commands/integration/cloud/azure.py +1 -0
  564. ansible_test/_internal/commands/integration/cloud/cs.py +1 -0
  565. ansible_test/_internal/commands/integration/cloud/digitalocean.py +1 -0
  566. ansible_test/_internal/commands/integration/cloud/galaxy.py +3 -2
  567. ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -0
  568. ansible_test/_internal/commands/integration/cloud/httptester.py +2 -1
  569. ansible_test/_internal/commands/integration/cloud/nios.py +2 -1
  570. ansible_test/_internal/commands/integration/cloud/opennebula.py +1 -0
  571. ansible_test/_internal/commands/integration/cloud/openshift.py +1 -0
  572. ansible_test/_internal/commands/integration/cloud/scaleway.py +1 -0
  573. ansible_test/_internal/commands/integration/cloud/vcenter.py +1 -0
  574. ansible_test/_internal/commands/integration/cloud/vultr.py +1 -0
  575. ansible_test/_internal/commands/integration/coverage.py +1 -0
  576. ansible_test/_internal/commands/integration/filters.py +1 -0
  577. ansible_test/_internal/commands/integration/network.py +1 -0
  578. ansible_test/_internal/commands/integration/posix.py +1 -0
  579. ansible_test/_internal/commands/integration/windows.py +1 -0
  580. ansible_test/_internal/commands/sanity/__init__.py +16 -1
  581. ansible_test/_internal/commands/sanity/ansible_doc.py +1 -0
  582. ansible_test/_internal/commands/sanity/bin_symlinks.py +1 -0
  583. ansible_test/_internal/commands/sanity/compile.py +1 -0
  584. ansible_test/_internal/commands/sanity/ignores.py +1 -0
  585. ansible_test/_internal/commands/sanity/import.py +1 -0
  586. ansible_test/_internal/commands/sanity/integration_aliases.py +1 -0
  587. ansible_test/_internal/commands/sanity/pep8.py +1 -0
  588. ansible_test/_internal/commands/sanity/pslint.py +1 -0
  589. ansible_test/_internal/commands/sanity/pylint.py +24 -26
  590. ansible_test/_internal/commands/sanity/shellcheck.py +1 -0
  591. ansible_test/_internal/commands/sanity/validate_modules.py +1 -0
  592. ansible_test/_internal/commands/sanity/yamllint.py +1 -0
  593. ansible_test/_internal/commands/shell/__init__.py +1 -0
  594. ansible_test/_internal/commands/units/__init__.py +1 -0
  595. ansible_test/_internal/compat/__init__.py +1 -0
  596. ansible_test/_internal/compat/packaging.py +1 -0
  597. ansible_test/_internal/compat/yaml.py +1 -0
  598. ansible_test/_internal/completion.py +1 -0
  599. ansible_test/_internal/config.py +2 -0
  600. ansible_test/_internal/connections.py +1 -0
  601. ansible_test/_internal/constants.py +1 -0
  602. ansible_test/_internal/containers.py +1 -0
  603. ansible_test/_internal/content_config.py +1 -0
  604. ansible_test/_internal/core_ci.py +1 -0
  605. ansible_test/_internal/coverage_util.py +11 -10
  606. ansible_test/_internal/data.py +1 -0
  607. ansible_test/_internal/delegation.py +1 -0
  608. ansible_test/_internal/dev/__init__.py +1 -0
  609. ansible_test/_internal/dev/container_probe.py +1 -0
  610. ansible_test/_internal/diff.py +3 -2
  611. ansible_test/_internal/docker_util.py +2 -1
  612. ansible_test/_internal/encoding.py +1 -0
  613. ansible_test/_internal/executor.py +1 -0
  614. ansible_test/_internal/git.py +1 -0
  615. ansible_test/_internal/host_configs.py +1 -0
  616. ansible_test/_internal/host_profiles.py +1 -0
  617. ansible_test/_internal/http.py +1 -0
  618. ansible_test/_internal/init.py +1 -0
  619. ansible_test/_internal/inventory.py +35 -3
  620. ansible_test/_internal/io.py +1 -0
  621. ansible_test/_internal/metadata.py +1 -0
  622. ansible_test/_internal/payload.py +1 -0
  623. ansible_test/_internal/provider/__init__.py +1 -0
  624. ansible_test/_internal/provider/layout/__init__.py +1 -0
  625. ansible_test/_internal/provider/layout/ansible.py +1 -0
  626. ansible_test/_internal/provider/layout/collection.py +1 -0
  627. ansible_test/_internal/provider/layout/unsupported.py +1 -0
  628. ansible_test/_internal/provider/source/__init__.py +1 -0
  629. ansible_test/_internal/provider/source/git.py +1 -0
  630. ansible_test/_internal/provider/source/installed.py +1 -0
  631. ansible_test/_internal/provider/source/unsupported.py +1 -0
  632. ansible_test/_internal/provider/source/unversioned.py +1 -0
  633. ansible_test/_internal/provisioning.py +1 -0
  634. ansible_test/_internal/pypi_proxy.py +6 -5
  635. ansible_test/_internal/python_requirements.py +1 -0
  636. ansible_test/_internal/ssh.py +1 -0
  637. ansible_test/_internal/target.py +1 -0
  638. ansible_test/_internal/test.py +3 -2
  639. ansible_test/_internal/thread.py +1 -0
  640. ansible_test/_internal/timeout.py +1 -0
  641. ansible_test/_internal/util.py +1 -0
  642. ansible_test/_internal/util_common.py +5 -2
  643. ansible_test/_internal/venv.py +1 -0
  644. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +1 -0
  645. ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py +1 -0
  646. ansible_test/_util/controller/sanity/code-smell/changelog.py +1 -0
  647. ansible_test/_util/controller/sanity/code-smell/empty-init.py +1 -0
  648. ansible_test/_util/controller/sanity/code-smell/line-endings.py +1 -0
  649. ansible_test/_util/controller/sanity/code-smell/no-assert.py +1 -0
  650. ansible_test/_util/controller/sanity/code-smell/no-get-exception.py +1 -0
  651. ansible_test/_util/controller/sanity/code-smell/no-illegal-filenames.py +1 -0
  652. ansible_test/_util/controller/sanity/code-smell/no-smart-quotes.py +1 -0
  653. ansible_test/_util/controller/sanity/code-smell/replace-urlopen.py +1 -0
  654. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +28 -1
  655. ansible_test/_util/controller/sanity/code-smell/shebang.py +1 -0
  656. ansible_test/_util/controller/sanity/code-smell/symlinks.py +1 -0
  657. ansible_test/_util/controller/sanity/code-smell/use-argspec-type-path.py +1 -0
  658. ansible_test/_util/controller/sanity/code-smell/use-compat-six.py +1 -0
  659. ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py +2 -1
  660. ansible_test/_util/controller/sanity/pep8/current-ignore.txt +4 -0
  661. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +7 -5
  662. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +7 -5
  663. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +7 -5
  664. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +3 -5
  665. ansible_test/_util/controller/sanity/pylint/config/default.cfg +7 -7
  666. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -13
  667. ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +1 -0
  668. ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +1 -8
  669. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +1 -8
  670. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +55 -28
  671. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +12 -5
  672. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +13 -2
  673. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -0
  674. ansible_test/_util/controller/sanity/yamllint/yamllinter.py +35 -17
  675. ansible_test/_util/controller/tools/collection_detail.py +1 -0
  676. ansible_test/_util/controller/tools/yaml_to_json.py +2 -1
  677. ansible_test/_util/target/pytest/plugins/ansible_forked.py +6 -1
  678. ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +2 -1
  679. ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -0
  680. ansible_test/_util/target/sanity/compile/compile.py +1 -0
  681. ansible_test/_util/target/sanity/import/importer.py +15 -16
  682. ansible_test/_util/target/setup/bootstrap.sh +9 -20
  683. ansible_test/_util/target/setup/probe_cgroups.py +1 -0
  684. ansible_test/_util/target/setup/quiet_pip.py +1 -0
  685. ansible_test/_util/target/setup/requirements.py +35 -27
  686. ansible_test/_util/target/tools/virtualenvcheck.py +2 -1
  687. ansible_test/_util/target/tools/yamlcheck.py +2 -1
  688. ansible/compat/selectors.py +0 -32
  689. ansible/errors/yaml_strings.py +0 -138
  690. ansible/executor/action_write_locks.py +0 -44
  691. ansible/executor/discovery/python_target.py +0 -47
  692. ansible/executor/powershell/module_powershell_wrapper.ps1 +0 -86
  693. ansible/executor/powershell/module_script_wrapper.ps1 +0 -22
  694. ansible/module_utils/compat/importlib.py +0 -26
  695. ansible/module_utils/compat/selectors.py +0 -32
  696. ansible/module_utils/pycompat24.py +0 -73
  697. ansible/parsing/yaml/constructor.py +0 -178
  698. ansible/template/native_helpers.py +0 -251
  699. ansible/template/template.py +0 -43
  700. ansible/template/vars.py +0 -77
  701. ansible/utils/native_jinja.py +0 -11
  702. ansible/vars/fact_cache.py +0 -71
  703. ansible_core-2.18.5rc1.dist-info/RECORD +0 -992
  704. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/Apache-License.txt +0 -0
  705. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/COPYING +0 -0
  706. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/MIT-license.txt +0 -0
  707. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/PSF-license.txt +0 -0
  708. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/entry_points.txt +0 -0
  709. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/simplified_bsd.txt +0 -0
  710. {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/top_level.txt +0 -0
@@ -6,7 +6,7 @@
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- DOCUMENTATION = '''
9
+ DOCUMENTATION = """
10
10
  name: ssh
11
11
  short_description: connect via SSH client binary
12
12
  description:
@@ -62,10 +62,27 @@ DOCUMENTATION = '''
62
62
  - name: ansible_password
63
63
  - name: ansible_ssh_pass
64
64
  - name: ansible_ssh_password
65
+ password_mechanism:
66
+ description: Mechanism to use for handling ssh password prompt
67
+ type: string
68
+ default: ssh_askpass
69
+ choices:
70
+ - ssh_askpass
71
+ - sshpass
72
+ - disable
73
+ version_added: '2.19'
74
+ env:
75
+ - name: ANSIBLE_SSH_PASSWORD_MECHANISM
76
+ ini:
77
+ - {key: password_mechanism, section: ssh_connection}
78
+ vars:
79
+ - name: ansible_ssh_password_mechanism
65
80
  sshpass_prompt:
66
81
  description:
67
- - Password prompt that sshpass should search for. Supported by sshpass 1.06 and up.
82
+ - Password prompt that C(sshpass)/C(SSH_ASKPASS) should search for.
83
+ - Supported by sshpass 1.06 and up when O(password_mechanism) set to V(sshpass).
68
84
  - Defaults to C(Enter PIN for) when pkcs11_provider is set.
85
+ - Defaults to C(assword) when O(password_mechanism) set to V(ssh_askpass).
69
86
  default: ''
70
87
  type: string
71
88
  ini:
@@ -248,7 +265,6 @@ DOCUMENTATION = '''
248
265
  vars:
249
266
  - name: ansible_pipelining
250
267
  - name: ansible_ssh_pipelining
251
-
252
268
  private_key_file:
253
269
  description:
254
270
  - Path to private key file to use for authentication.
@@ -264,7 +280,27 @@ DOCUMENTATION = '''
264
280
  cli:
265
281
  - name: private_key_file
266
282
  option: '--private-key'
267
-
283
+ private_key:
284
+ description:
285
+ - Private key contents in PEM format. Requires the C(SSH_AGENT) configuration to be enabled.
286
+ type: string
287
+ env:
288
+ - name: ANSIBLE_PRIVATE_KEY
289
+ vars:
290
+ - name: ansible_private_key
291
+ - name: ansible_ssh_private_key
292
+ version_added: '2.19'
293
+ private_key_passphrase:
294
+ description:
295
+ - Private key passphrase, dependent on O(private_key).
296
+ - This does NOT have any effect when used with O(private_key_file).
297
+ type: string
298
+ env:
299
+ - name: ANSIBLE_PRIVATE_KEY_PASSPHRASE
300
+ vars:
301
+ - name: ansible_private_key_passphrase
302
+ - name: ansible_ssh_private_key_passphrase
303
+ version_added: '2.19'
268
304
  control_path:
269
305
  description:
270
306
  - This is the location to save SSH's ControlPath sockets, it uses SSH's variable substitution.
@@ -357,41 +393,58 @@ DOCUMENTATION = '''
357
393
  type: string
358
394
  description:
359
395
  - "PKCS11 SmartCard provider such as opensc, example: /usr/local/lib/opensc-pkcs11.so"
360
- - Requires sshpass version 1.06+, sshpass must support the -P option.
361
396
  env: [{name: ANSIBLE_PKCS11_PROVIDER}]
362
397
  ini:
363
398
  - {key: pkcs11_provider, section: ssh_connection}
364
399
  vars:
365
400
  - name: ansible_ssh_pkcs11_provider
366
- '''
401
+ """
367
402
 
368
403
  import collections.abc as c
404
+ import argparse
369
405
  import errno
406
+ import contextlib
370
407
  import fcntl
371
408
  import hashlib
372
409
  import io
410
+ import json
373
411
  import os
412
+ import pathlib
374
413
  import pty
375
414
  import re
376
415
  import selectors
377
416
  import shlex
417
+ import shutil
378
418
  import subprocess
419
+ import sys
420
+ import tempfile
379
421
  import time
380
422
  import typing as t
381
-
382
423
  from functools import wraps
424
+ from multiprocessing.shared_memory import SharedMemory
425
+
426
+ from ansible import constants as C
383
427
  from ansible.errors import (
384
428
  AnsibleAuthenticationFailure,
385
429
  AnsibleConnectionFailure,
386
430
  AnsibleError,
387
431
  AnsibleFileNotFound,
388
432
  )
389
- from ansible.module_utils.six import PY3, text_type, binary_type
433
+ from ansible.module_utils.six import text_type, binary_type
390
434
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
391
435
  from ansible.plugins.connection import ConnectionBase, BUFSIZE
392
436
  from ansible.plugins.shell.powershell import _replace_stderr_clixml
393
437
  from ansible.utils.display import Display
394
438
  from ansible.utils.path import unfrackpath, makedirs_safe
439
+ from ansible.utils._ssh_agent import SshAgentClient, _key_data_into_crypto_objects
440
+
441
+ try:
442
+ from cryptography.hazmat.primitives import serialization
443
+ except ImportError:
444
+ HAS_CRYPTOGRAPHY = False
445
+ else:
446
+ HAS_CRYPTOGRAPHY = True
447
+
395
448
 
396
449
  display = Display()
397
450
 
@@ -408,9 +461,14 @@ b_NOT_SSH_ERRORS = (b'Traceback (most recent call last):', # Python-2.6 when th
408
461
  SSHPASS_AVAILABLE = None
409
462
  SSH_DEBUG = re.compile(r'^debug\d+: .*')
410
463
 
464
+ _HAS_RESOURCE_TRACK = sys.version_info[:2] >= (3, 13)
465
+
466
+ PKCS11_DEFAULT_PROMPT = 'Enter PIN for '
467
+ SSH_ASKPASS_DEFAULT_PROMPT = 'assword'
468
+
411
469
 
412
470
  class AnsibleControlPersistBrokenPipeError(AnsibleError):
413
- ''' ControlPersist broken pipe '''
471
+ """ ControlPersist broken pipe """
414
472
  pass
415
473
 
416
474
 
@@ -450,6 +508,7 @@ def _handle_error(
450
508
  'Upgrade sshpass to use sshpass_prompt, or otherwise switch to ssh keys.'
451
509
  raise AnsibleError('{0} {1}'.format(msg, details))
452
510
  msg = '{0} {1}'.format(msg, details)
511
+ raise AnsibleConnectionFailure(msg)
453
512
 
454
513
  if return_tuple[0] == 255:
455
514
  SSH_ERROR = True
@@ -496,9 +555,10 @@ def _ssh_retry(
496
555
  remaining_tries = int(self.get_option('reconnection_retries')) + 1
497
556
  cmd_summary = u"%s..." % to_text(args[0])
498
557
  conn_password = self.get_option('password') or self._play_context.password
558
+ is_sshpass = self.get_option('password_mechanism') == 'sshpass'
499
559
  for attempt in range(remaining_tries):
500
560
  cmd = t.cast(list[bytes], args[0])
501
- if attempt != 0 and conn_password and isinstance(cmd, list):
561
+ if attempt != 0 and is_sshpass and conn_password and isinstance(cmd, list):
502
562
  # If this is a retry, the fd/pipe for sshpass is closed, and we need a new one
503
563
  self.sshpass_pipe = os.pipe()
504
564
  cmd[1] = b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict')
@@ -517,7 +577,7 @@ def _ssh_retry(
517
577
  except (AnsibleControlPersistBrokenPipeError):
518
578
  # Retry one more time because of the ControlPersist broken pipe (see #16731)
519
579
  cmd = t.cast(list[bytes], args[0])
520
- if conn_password and isinstance(cmd, list):
580
+ if is_sshpass and conn_password and isinstance(cmd, list):
521
581
  # This is a retry, so the fd/pipe for sshpass is closed, and we need a new one
522
582
  self.sshpass_pipe = os.pipe()
523
583
  cmd[1] = b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict')
@@ -558,8 +618,26 @@ def _ssh_retry(
558
618
  return wrapped
559
619
 
560
620
 
621
+ def _clean_shm(func):
622
+ def inner(self, *args, **kwargs):
623
+ try:
624
+ ret = func(self, *args, **kwargs)
625
+ finally:
626
+ if self.shm:
627
+ self.shm.close()
628
+ with contextlib.suppress(FileNotFoundError):
629
+ self.shm.unlink()
630
+ if not _HAS_RESOURCE_TRACK:
631
+ # deprecated: description='unneeded due to track argument for SharedMemory' python_version='3.12'
632
+ # There is a resource tracking issue where the resource is deleted, but tracking still has a record
633
+ # This will effectively overwrite the record and remove it
634
+ SharedMemory(name=self.shm.name, create=True, size=1).unlink()
635
+ return ret
636
+ return inner
637
+
638
+
561
639
  class Connection(ConnectionBase):
562
- ''' ssh based connections '''
640
+ """ ssh based connections """
563
641
 
564
642
  transport = 'ssh'
565
643
  has_pipelining = True
@@ -573,6 +651,8 @@ class Connection(ConnectionBase):
573
651
  self.user = self._play_context.remote_user
574
652
  self.control_path: str | None = None
575
653
  self.control_path_dir: str | None = None
654
+ self.shm: SharedMemory | None = None
655
+ self.sshpass_pipe: tuple[int, int] | None = None
576
656
 
577
657
  # Windows operates differently from a POSIX connection/shell plugin,
578
658
  # we need to set various properties to ensure SSH on Windows continues
@@ -583,6 +663,13 @@ class Connection(ConnectionBase):
583
663
  self.module_implementation_preferences = ('.ps1', '.exe', '')
584
664
  self.allow_executable = False
585
665
 
666
+ # parser to discover 'passed options', used later on for pipelining resolution
667
+ self._tty_parser = argparse.ArgumentParser()
668
+ self._tty_parser.add_argument('-t', action='count')
669
+ self._tty_parser.add_argument('-o', action='append')
670
+
671
+ self._populated_agent: pathlib.Path | None = None
672
+
586
673
  # The connection is created by running ssh/scp/sftp from the exec_command,
587
674
  # put_file, and fetch_file methods, so we don't need to do any connection
588
675
  # management here.
@@ -598,7 +685,7 @@ class Connection(ConnectionBase):
598
685
  connection: ConnectionBase | None = None,
599
686
  pid: int | None = None,
600
687
  ) -> str:
601
- '''Make a hash for the controlpath based on con attributes'''
688
+ """Make a hash for the controlpath based on con attributes"""
602
689
  pstring = '%s-%s-%s' % (host, port, user)
603
690
  if connection:
604
691
  pstring += '-%s' % connection
@@ -614,28 +701,21 @@ class Connection(ConnectionBase):
614
701
  def _sshpass_available() -> bool:
615
702
  global SSHPASS_AVAILABLE
616
703
 
617
- # We test once if sshpass is available, and remember the result. It
618
- # would be nice to use distutils.spawn.find_executable for this, but
619
- # distutils isn't always available; shutils.which() is Python3-only.
704
+ # We test once if sshpass is available, and remember the result.
620
705
 
621
706
  if SSHPASS_AVAILABLE is None:
622
- try:
623
- p = subprocess.Popen(["sshpass"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
624
- p.communicate()
625
- SSHPASS_AVAILABLE = True
626
- except OSError:
627
- SSHPASS_AVAILABLE = False
707
+ SSHPASS_AVAILABLE = shutil.which('sshpass') is not None
628
708
 
629
709
  return SSHPASS_AVAILABLE
630
710
 
631
711
  @staticmethod
632
712
  def _persistence_controls(b_command: list[bytes]) -> tuple[bool, bool]:
633
- '''
713
+ """
634
714
  Takes a command array and scans it for ControlPersist and ControlPath
635
715
  settings and returns two booleans indicating whether either was found.
636
716
  This could be smarter, e.g. returning false if ControlPersist is 'no',
637
717
  but for now we do it simple way.
638
- '''
718
+ """
639
719
 
640
720
  controlpersist = False
641
721
  controlpath = False
@@ -664,8 +744,54 @@ class Connection(ConnectionBase):
664
744
  display.vvvvv(u'SSH: %s: (%s)' % (explanation, ')('.join(to_text(a) for a in b_args)), host=self.host)
665
745
  b_command += b_args
666
746
 
747
+ def _populate_agent(self) -> pathlib.Path:
748
+ """Adds configured private key identity to the SSH agent. Returns a path to a file containing the public key."""
749
+ if self._populated_agent:
750
+ return self._populated_agent
751
+
752
+ if (auth_sock := C.config.get_config_value('SSH_AGENT')) == 'none':
753
+ raise AnsibleError('Cannot utilize private_key with SSH_AGENT disabled')
754
+
755
+ key_data = self.get_option('private_key')
756
+ passphrase = self.get_option('private_key_passphrase')
757
+
758
+ private_key, public_key, fingerprint = _key_data_into_crypto_objects(
759
+ to_bytes(key_data),
760
+ to_bytes(passphrase) if passphrase else None,
761
+ )
762
+
763
+ with SshAgentClient(auth_sock) as client:
764
+ if public_key not in client:
765
+ display.vvv(f'SSH: SSH_AGENT adding {fingerprint} to agent', host=self.host)
766
+ client.add(
767
+ private_key,
768
+ f'[added by ansible: PID={os.getpid()}, UID={os.getuid()}, EUID={os.geteuid()}, TIME={time.time()}]',
769
+ C.config.get_config_value('SSH_AGENT_KEY_LIFETIME'),
770
+ )
771
+ else:
772
+ display.vvv(f'SSH: SSH_AGENT {fingerprint} exists in agent', host=self.host)
773
+ # Write the public key to disk, to be provided as IdentityFile.
774
+ # This allows ssh to pick an explicit key in the agent to use,
775
+ # preventing ssh from attempting all keys in the agent.
776
+ pubkey_path = self._populated_agent = pathlib.Path(C.DEFAULT_LOCAL_TMP).joinpath(
777
+ fingerprint.replace('/', '-') + '.pub'
778
+ )
779
+ if os.path.exists(pubkey_path):
780
+ return pubkey_path
781
+
782
+ with tempfile.NamedTemporaryFile(dir=C.DEFAULT_LOCAL_TMP, delete=False) as f:
783
+ f.write(public_key.public_bytes(
784
+ encoding=serialization.Encoding.OpenSSH,
785
+ format=serialization.PublicFormat.OpenSSH
786
+ ))
787
+ # move atomically to prevent race conditions, silently succeeds if the target exists
788
+ os.rename(f.name, pubkey_path)
789
+ os.chmod(pubkey_path, mode=0o400)
790
+
791
+ return self._populated_agent
792
+
667
793
  def _build_command(self, binary: str, subsystem: str, *other_args: bytes | str) -> list[bytes]:
668
- '''
794
+ """
669
795
  Takes a executable (ssh, scp, sftp or wrapper) and optional extra arguments and returns the remote command
670
796
  wrapped in local ssh shell commands and ready for execution.
671
797
 
@@ -673,21 +799,22 @@ class Connection(ConnectionBase):
673
799
  :arg subsystem: type of executable provided, ssh/sftp/scp, needed because wrappers for ssh might have diff names.
674
800
  :arg other_args: dict of, value pairs passed as arguments to the ssh binary
675
801
 
676
- '''
802
+ """
677
803
 
678
804
  b_command = []
679
805
  conn_password = self.get_option('password') or self._play_context.password
806
+ pkcs11_provider = self.get_option("pkcs11_provider")
807
+ password_mechanism = self.get_option('password_mechanism')
680
808
 
681
809
  #
682
810
  # First, the command to invoke
683
811
  #
684
812
 
685
- # If we want to use password authentication, we have to set up a pipe to
813
+ # If we want to use sshpass for password authentication, we have to set up a pipe to
686
814
  # write the password to sshpass.
687
- pkcs11_provider = self.get_option("pkcs11_provider")
688
- if conn_password or pkcs11_provider:
815
+ if password_mechanism == 'sshpass' and (conn_password or pkcs11_provider):
689
816
  if not self._sshpass_available():
690
- raise AnsibleError("to use the 'ssh' connection type with passwords or pkcs11_provider, you must install the sshpass program")
817
+ raise AnsibleError("to use the password_mechanism=sshpass, you must install the sshpass program")
691
818
  if not conn_password and pkcs11_provider:
692
819
  raise AnsibleError("to use pkcs11_provider you must specify a password/pin")
693
820
 
@@ -697,7 +824,7 @@ class Connection(ConnectionBase):
697
824
  password_prompt = self.get_option('sshpass_prompt')
698
825
  if not password_prompt and pkcs11_provider:
699
826
  # Set default password prompt for pkcs11_provider to make it clear its a PIN
700
- password_prompt = 'Enter PIN for '
827
+ password_prompt = PKCS11_DEFAULT_PROMPT
701
828
 
702
829
  if password_prompt:
703
830
  b_command += [b'-P', to_bytes(password_prompt, errors='surrogate_or_strict')]
@@ -720,12 +847,12 @@ class Connection(ConnectionBase):
720
847
  # sftp batch mode allows us to correctly catch failed transfers, but can
721
848
  # be disabled if the client side doesn't support the option. However,
722
849
  # sftp batch mode does not prompt for passwords so it must be disabled
723
- # if not using controlpersist and using sshpass
850
+ # if not using controlpersist and using password auth
724
851
  b_args: t.Iterable[bytes]
725
852
  if subsystem == 'sftp' and self.get_option('sftp_batch_mode'):
726
853
  if conn_password:
727
854
  b_args = [b'-o', b'BatchMode=no']
728
- self._add_args(b_command, b_args, u'disable batch mode for sshpass')
855
+ self._add_args(b_command, b_args, u'disable batch mode for password auth')
729
856
  b_command += [b'-b', b'-']
730
857
 
731
858
  if display.verbosity:
@@ -748,8 +875,17 @@ class Connection(ConnectionBase):
748
875
  b_args = (b"-o", b"Port=" + to_bytes(self.port, nonstring='simplerepr', errors='surrogate_or_strict'))
749
876
  self._add_args(b_command, b_args, u"ANSIBLE_REMOTE_PORT/remote_port/ansible_port set")
750
877
 
751
- key = self.get_option('private_key_file')
752
- if key:
878
+ if self.get_option('private_key'):
879
+ try:
880
+ key = self._populate_agent()
881
+ except Exception as e:
882
+ raise AnsibleAuthenticationFailure(
883
+ 'Failed to add configured private key into ssh-agent',
884
+ orig_exc=e,
885
+ )
886
+ b_args = (b'-o', b'IdentitiesOnly=yes', b'-o', to_bytes(f'IdentityFile="{key}"', errors='surrogate_or_strict'))
887
+ self._add_args(b_command, b_args, "ANSIBLE_PRIVATE_KEY/private_key set")
888
+ elif key := self.get_option('private_key_file'):
753
889
  b_args = (b"-o", b'IdentityFile="' + to_bytes(os.path.expanduser(key), errors='surrogate_or_strict') + b'"')
754
890
  self._add_args(b_command, b_args, u"ANSIBLE_PRIVATE_KEY_FILE/private_key_file/ansible_ssh_private_key_file set")
755
891
 
@@ -822,27 +958,24 @@ class Connection(ConnectionBase):
822
958
  return b_command
823
959
 
824
960
  def _send_initial_data(self, fh: io.IOBase, in_data: bytes, ssh_process: subprocess.Popen) -> None:
825
- '''
961
+ """
826
962
  Writes initial data to the stdin filehandle of the subprocess and closes
827
963
  it. (The handle must be closed; otherwise, for example, "sftp -b -" will
828
964
  just hang forever waiting for more commands.)
829
- '''
965
+ """
830
966
 
831
967
  display.debug(u'Sending initial data')
832
968
 
833
969
  try:
834
970
  fh.write(to_bytes(in_data))
835
971
  fh.close()
836
- except (OSError, IOError) as e:
972
+ except (OSError, IOError) as ex:
837
973
  # The ssh connection may have already terminated at this point, with a more useful error
838
974
  # Only raise AnsibleConnectionFailure if the ssh process is still alive
839
975
  time.sleep(0.001)
840
976
  ssh_process.poll()
841
977
  if getattr(ssh_process, 'returncode', None) is None:
842
- raise AnsibleConnectionFailure(
843
- 'Data could not be sent to remote host "%s". Make sure this host can be reached '
844
- 'over ssh: %s' % (self.host, to_native(e)), orig_exc=e
845
- )
978
+ raise AnsibleConnectionFailure(f'Data could not be sent to remote host {self.host!r}. Make sure this host can be reached over SSH.') from ex
846
979
 
847
980
  display.debug(u'Sent initial data (%d bytes)' % len(in_data))
848
981
 
@@ -858,14 +991,14 @@ class Connection(ConnectionBase):
858
991
  # This is separate from _run() because we need to do the same thing for stdout
859
992
  # and stderr.
860
993
  def _examine_output(self, source: str, state: str, b_chunk: bytes, sudoable: bool) -> tuple[bytes, bytes]:
861
- '''
994
+ """
862
995
  Takes a string, extracts complete lines from it, tests to see if they
863
996
  are a prompt, error message, etc., and sets appropriate flags in self.
864
997
  Prompt and success lines are removed.
865
998
 
866
999
  Returns the processed (i.e. possibly-edited) output and the unprocessed
867
1000
  remainder (to be processed with the next chunk) as strings.
868
- '''
1001
+ """
869
1002
 
870
1003
  output = []
871
1004
  for b_line in b_chunk.splitlines(True):
@@ -906,15 +1039,69 @@ class Connection(ConnectionBase):
906
1039
 
907
1040
  return b''.join(output), remainder
908
1041
 
1042
+ def _init_shm(self) -> dict[str, t.Any]:
1043
+ env = os.environ.copy()
1044
+ popen_kwargs: dict[str, t.Any] = {}
1045
+
1046
+ if self.get_option('password_mechanism') != 'ssh_askpass':
1047
+ return popen_kwargs
1048
+
1049
+ conn_password = self.get_option('password') or self._play_context.password
1050
+ pkcs11_provider = self.get_option("pkcs11_provider")
1051
+ if not conn_password and pkcs11_provider:
1052
+ raise AnsibleError("to use pkcs11_provider you must specify a password/pin")
1053
+
1054
+ if not conn_password:
1055
+ return popen_kwargs
1056
+
1057
+ kwargs = {}
1058
+ if _HAS_RESOURCE_TRACK:
1059
+ # deprecated: description='track argument for SharedMemory always available' python_version='3.12'
1060
+ kwargs['track'] = False
1061
+ self.shm = shm = SharedMemory(create=True, size=16384, **kwargs) # type: ignore[arg-type]
1062
+
1063
+ sshpass_prompt = self.get_option('sshpass_prompt')
1064
+ if not sshpass_prompt and pkcs11_provider:
1065
+ sshpass_prompt = PKCS11_DEFAULT_PROMPT
1066
+ elif not sshpass_prompt:
1067
+ sshpass_prompt = SSH_ASKPASS_DEFAULT_PROMPT
1068
+
1069
+ data = json.dumps({
1070
+ 'password': conn_password,
1071
+ 'prompt': sshpass_prompt,
1072
+ }).encode('utf-8')
1073
+ shm.buf[:len(data)] = bytearray(data)
1074
+ shm.close()
1075
+
1076
+ env['_ANSIBLE_SSH_ASKPASS_SHM'] = str(self.shm.name)
1077
+ adhoc = pathlib.Path(sys.argv[0]).with_name('ansible')
1078
+ env['SSH_ASKPASS'] = str(adhoc) if adhoc.is_file() else 'ansible'
1079
+
1080
+ # SSH_ASKPASS_REQUIRE was added in openssh 8.4, prior to 8.4 there must be no tty, and DISPLAY must be set
1081
+ env['SSH_ASKPASS_REQUIRE'] = 'force'
1082
+ if not env.get('DISPLAY'):
1083
+ # If the user has DISPLAY set, assume it is there for a reason
1084
+ env['DISPLAY'] = '-'
1085
+
1086
+ popen_kwargs['env'] = env
1087
+ # start_new_session runs setsid which detaches the tty to support the use of ASKPASS prior to openssh 8.4
1088
+ popen_kwargs['start_new_session'] = True
1089
+
1090
+ return popen_kwargs
1091
+
1092
+ @_clean_shm
909
1093
  def _bare_run(self, cmd: list[bytes], in_data: bytes | None, sudoable: bool = True, checkrc: bool = True) -> tuple[int, bytes, bytes]:
910
- '''
1094
+ """
911
1095
  Starts the command and communicates with it until it ends.
912
- '''
1096
+ """
913
1097
 
914
1098
  # We don't use _shell.quote as this is run on the controller and independent from the shell plugin chosen
915
1099
  display_cmd = u' '.join(shlex.quote(to_text(c)) for c in cmd)
916
1100
  display.vvv(u'SSH: EXEC {0}'.format(display_cmd), host=self.host)
917
1101
 
1102
+ conn_password = self.get_option('password') or self._play_context.password
1103
+ password_mechanism = self.get_option('password_mechanism')
1104
+
918
1105
  # Start the given command. If we don't need to pipeline data, we can try
919
1106
  # to use a pseudo-tty (ssh will have been invoked with -tt). If we are
920
1107
  # pipelining data, or can't create a pty, we fall back to using plain
@@ -927,17 +1114,16 @@ class Connection(ConnectionBase):
927
1114
  else:
928
1115
  cmd = list(map(to_bytes, cmd))
929
1116
 
930
- conn_password = self.get_option('password') or self._play_context.password
1117
+ popen_kwargs = self._init_shm()
1118
+
1119
+ if self.sshpass_pipe:
1120
+ popen_kwargs['pass_fds'] = self.sshpass_pipe
931
1121
 
932
1122
  if not in_data:
933
1123
  try:
934
1124
  # Make sure stdin is a proper pty to avoid tcgetattr errors
935
1125
  master, slave = pty.openpty()
936
- if PY3 and conn_password:
937
- # pylint: disable=unexpected-keyword-arg
938
- p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE, pass_fds=self.sshpass_pipe)
939
- else:
940
- p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1126
+ p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **popen_kwargs)
941
1127
  stdin = os.fdopen(master, 'wb', 0)
942
1128
  os.close(slave)
943
1129
  except (OSError, IOError):
@@ -945,21 +1131,13 @@ class Connection(ConnectionBase):
945
1131
 
946
1132
  if not p:
947
1133
  try:
948
- if PY3 and conn_password:
949
- # pylint: disable=unexpected-keyword-arg
950
- p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
951
- stderr=subprocess.PIPE, pass_fds=self.sshpass_pipe)
952
- else:
953
- p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
954
- stderr=subprocess.PIPE)
1134
+ p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
1135
+ stderr=subprocess.PIPE, **popen_kwargs)
955
1136
  stdin = p.stdin # type: ignore[assignment] # stdin will be set and not None due to the calls above
956
1137
  except (OSError, IOError) as e:
957
1138
  raise AnsibleError('Unable to execute ssh command line on a controller due to: %s' % to_native(e))
958
1139
 
959
- # If we are using SSH password authentication, write the password into
960
- # the pipe we opened in _build_command.
961
-
962
- if conn_password:
1140
+ if password_mechanism == 'sshpass' and conn_password:
963
1141
  os.close(self.sshpass_pipe[0])
964
1142
  try:
965
1143
  os.write(self.sshpass_pipe[1], to_bytes(conn_password) + b'\n')
@@ -1047,7 +1225,9 @@ class Connection(ConnectionBase):
1047
1225
  if poll is not None:
1048
1226
  break
1049
1227
  self._terminate_process(p)
1050
- raise AnsibleError('Timeout (%ds) waiting for privilege escalation prompt: %s' % (timeout, to_native(b_stdout)))
1228
+ raise AnsibleConnectionFailure('Timeout (%ds) waiting for privilege escalation prompt: %s' % (timeout, to_native(b_stdout)))
1229
+
1230
+ display.vvvvv(f'SSH: Timeout ({timeout}s) waiting for the output', host=self.host)
1051
1231
 
1052
1232
  # Read whatever output is available on stdout and stderr, and stop
1053
1233
  # listening to the pipe if it's been closed.
@@ -1117,23 +1297,23 @@ class Connection(ConnectionBase):
1117
1297
 
1118
1298
  if states[state] == 'awaiting_escalation':
1119
1299
  if self._flags['become_success']:
1120
- display.vvv(u'Escalation succeeded')
1300
+ display.vvv(u'Escalation succeeded', host=self.host)
1121
1301
  self._flags['become_success'] = False
1122
1302
  state += 1
1123
1303
  elif self._flags['become_error']:
1124
- display.vvv(u'Escalation failed')
1304
+ display.vvv(u'Escalation failed', host=self.host)
1125
1305
  self._terminate_process(p)
1126
1306
  self._flags['become_error'] = False
1127
1307
  raise AnsibleError('Incorrect %s password' % self.become.name)
1128
1308
  elif self._flags['become_nopasswd_error']:
1129
- display.vvv(u'Escalation requires password')
1309
+ display.vvv(u'Escalation requires password', host=self.host)
1130
1310
  self._terminate_process(p)
1131
1311
  self._flags['become_nopasswd_error'] = False
1132
1312
  raise AnsibleError('Missing %s password' % self.become.name)
1133
1313
  elif self._flags['become_prompt']:
1134
1314
  # This shouldn't happen, because we should see the "Sorry,
1135
1315
  # try again" message first.
1136
- display.vvv(u'Escalation prompt repeated')
1316
+ display.vvv(u'Escalation prompt repeated', host=self.host)
1137
1317
  self._terminate_process(p)
1138
1318
  self._flags['become_prompt'] = False
1139
1319
  raise AnsibleError('Incorrect %s password' % self.become.name)
@@ -1176,10 +1356,15 @@ class Connection(ConnectionBase):
1176
1356
  p.stdout.close()
1177
1357
  p.stderr.close()
1178
1358
 
1179
- if self.get_option('host_key_checking'):
1180
- if cmd[0] == b"sshpass" and p.returncode == 6:
1181
- raise AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support '
1182
- 'this. Please add this host\'s fingerprint to your known_hosts file to manage this host.')
1359
+ conn_password = self.get_option('password') or self._play_context.password
1360
+ hostkey_fail = any((
1361
+ (cmd[0] == b"sshpass" and p.returncode == 6),
1362
+ b"read_passphrase: can't open /dev/tty" in b_stderr,
1363
+ b"Host key verification failed" in b_stderr,
1364
+ ))
1365
+ if password_mechanism and self.get_option('host_key_checking') and conn_password and hostkey_fail:
1366
+ raise AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled. '
1367
+ 'Please add this host\'s fingerprint to your known_hosts file to manage this host.')
1183
1368
 
1184
1369
  controlpersisterror = b'Bad configuration option: ControlPersist' in b_stderr or b'unknown configuration option: ControlPersist' in b_stderr
1185
1370
  if p.returncode != 0 and controlpersisterror:
@@ -1292,7 +1477,7 @@ class Connection(ConnectionBase):
1292
1477
  # Main public methods
1293
1478
  #
1294
1479
  def exec_command(self, cmd: str, in_data: bytes | None = None, sudoable: bool = True) -> tuple[int, bytes, bytes]:
1295
- ''' run a command on the remote host '''
1480
+ """ run a command on the remote host """
1296
1481
 
1297
1482
  super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
1298
1483
 
@@ -1333,7 +1518,7 @@ class Connection(ConnectionBase):
1333
1518
  return (returncode, stdout, stderr)
1334
1519
 
1335
1520
  def put_file(self, in_path: str, out_path: str) -> tuple[int, bytes, bytes]: # type: ignore[override] # Used by tests and would break API
1336
- ''' transfer a file from local to remote '''
1521
+ """ transfer a file from local to remote """
1337
1522
 
1338
1523
  super(Connection, self).put_file(in_path, out_path)
1339
1524
 
@@ -1349,7 +1534,7 @@ class Connection(ConnectionBase):
1349
1534
  return self._file_transport_command(in_path, out_path, 'put')
1350
1535
 
1351
1536
  def fetch_file(self, in_path: str, out_path: str) -> tuple[int, bytes, bytes]: # type: ignore[override] # Used by tests and would break API
1352
- ''' fetch a file from remote to local '''
1537
+ """ fetch a file from remote to local """
1353
1538
 
1354
1539
  super(Connection, self).fetch_file(in_path, out_path)
1355
1540
 
@@ -1372,18 +1557,18 @@ class Connection(ConnectionBase):
1372
1557
  # only run the reset if the ControlPath already exists or if it isn't configured and ControlPersist is set
1373
1558
  # 'check' will determine this.
1374
1559
  cmd = self._build_command(self.get_option('ssh_executable'), 'ssh', '-O', 'check', self.host)
1375
- display.vvv(u'sending connection check: %s' % to_text(cmd))
1560
+ display.vvv(u'sending connection check: %s' % to_text(cmd), host=self.host)
1376
1561
  p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1377
1562
  stdout, stderr = p.communicate()
1378
1563
  status_code = p.wait()
1379
1564
  if status_code != 0:
1380
- display.vvv(u"No connection to reset: %s" % to_text(stderr))
1565
+ display.vvv(u"No connection to reset: %s" % to_text(stderr), host=self.host)
1381
1566
  else:
1382
1567
  run_reset = True
1383
1568
 
1384
1569
  if run_reset:
1385
1570
  cmd = self._build_command(self.get_option('ssh_executable'), 'ssh', '-O', 'stop', self.host)
1386
- display.vvv(u'sending connection stop: %s' % to_text(cmd))
1571
+ display.vvv(u'sending connection stop: %s' % to_text(cmd), host=self.host)
1387
1572
  p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1388
1573
  stdout, stderr = p.communicate()
1389
1574
  status_code = p.wait()
@@ -1394,3 +1579,41 @@ class Connection(ConnectionBase):
1394
1579
 
1395
1580
  def close(self) -> None:
1396
1581
  self._connected = False
1582
+
1583
+ @property
1584
+ def has_tty(self):
1585
+ return self._is_tty_requested()
1586
+
1587
+ def _is_tty_requested(self):
1588
+
1589
+ # check if we require tty (only from our args, cannot see options in configuration files)
1590
+ opts = []
1591
+ for opt in ('ssh_args', 'ssh_common_args', 'ssh_extra_args'):
1592
+ attr = self.get_option(opt)
1593
+ if attr is not None:
1594
+ opts.extend(self._split_ssh_args(attr))
1595
+
1596
+ args, dummy = self._tty_parser.parse_known_args(opts)
1597
+
1598
+ if args.t:
1599
+ return True
1600
+
1601
+ for arg in args.o or []:
1602
+ if '=' in arg:
1603
+ val = arg.split('=', 1)
1604
+ else:
1605
+ val = arg.split(maxsplit=1)
1606
+
1607
+ if val[0].lower().strip() == 'requesttty':
1608
+ if val[1].lower().strip() in ('yes', 'force'):
1609
+ return True
1610
+
1611
+ return False
1612
+
1613
+ def is_pipelining_enabled(self, wrap_async=False):
1614
+ """ override parent method and ensure we don't request a tty """
1615
+
1616
+ if self._is_tty_requested():
1617
+ return False
1618
+ else:
1619
+ return super(Connection, self).is_pipelining_enabled(wrap_async)