ansible-core 2.15.4rc1__py3-none-any.whl → 2.16.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.

Potentially problematic release.


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

Files changed (427) hide show
  1. ansible/cli/__init__.py +3 -3
  2. ansible/cli/adhoc.py +1 -1
  3. ansible/cli/arguments/option_helpers.py +15 -5
  4. ansible/cli/config.py +2 -2
  5. ansible/cli/console.py +21 -17
  6. ansible/cli/doc.py +8 -9
  7. ansible/cli/galaxy.py +60 -27
  8. ansible/cli/inventory.py +1 -1
  9. ansible/cli/playbook.py +1 -1
  10. ansible/cli/pull.py +2 -2
  11. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
  12. ansible/cli/vault.py +11 -6
  13. ansible/collections/__init__.py +0 -29
  14. ansible/collections/list.py +23 -44
  15. ansible/config/ansible_builtin_runtime.yml +8 -4
  16. ansible/config/base.yml +34 -22
  17. ansible/config/manager.py +1 -1
  18. ansible/constants.py +3 -5
  19. ansible/errors/__init__.py +1 -1
  20. ansible/executor/interpreter_discovery.py +1 -1
  21. ansible/executor/module_common.py +39 -32
  22. ansible/executor/play_iterator.py +0 -15
  23. ansible/executor/playbook_executor.py +3 -3
  24. ansible/executor/powershell/module_manifest.py +1 -1
  25. ansible/executor/powershell/module_wrapper.ps1 +4 -1
  26. ansible/executor/process/worker.py +22 -7
  27. ansible/executor/task_executor.py +39 -40
  28. ansible/executor/task_queue_manager.py +8 -11
  29. ansible/galaxy/__init__.py +1 -1
  30. ansible/galaxy/api.py +8 -11
  31. ansible/galaxy/collection/__init__.py +17 -4
  32. ansible/galaxy/collection/concrete_artifact_manager.py +7 -2
  33. ansible/galaxy/collection/galaxy_api_proxy.py +1 -1
  34. ansible/galaxy/data/container/README.md +3 -5
  35. ansible/galaxy/dependency_resolution/__init__.py +1 -6
  36. ansible/galaxy/dependency_resolution/dataclasses.py +22 -1
  37. ansible/galaxy/dependency_resolution/providers.py +61 -69
  38. ansible/galaxy/role.py +31 -13
  39. ansible/galaxy/token.py +2 -2
  40. ansible/inventory/group.py +1 -1
  41. ansible/inventory/manager.py +1 -1
  42. ansible/module_utils/ansible_release.py +2 -2
  43. ansible/module_utils/basic.py +11 -41
  44. ansible/module_utils/common/file.py +0 -100
  45. ansible/module_utils/common/json.py +1 -1
  46. ansible/module_utils/common/locale.py +1 -1
  47. ansible/module_utils/common/text/converters.py +2 -2
  48. ansible/module_utils/common/validation.py +1 -1
  49. ansible/module_utils/compat/_selectors2.py +4 -4
  50. ansible/module_utils/compat/datetime.py +40 -0
  51. ansible/module_utils/compat/selinux.py +1 -1
  52. ansible/module_utils/compat/typing.py +1 -1
  53. ansible/module_utils/connection.py +1 -1
  54. ansible/module_utils/facts/hardware/linux.py +2 -2
  55. ansible/module_utils/facts/hardware/openbsd.py +1 -1
  56. ansible/module_utils/facts/network/linux.py +3 -3
  57. ansible/module_utils/facts/other/facter.py +8 -15
  58. ansible/module_utils/facts/sysctl.py +1 -1
  59. ansible/module_utils/facts/system/date_time.py +2 -2
  60. ansible/module_utils/facts/system/distribution.py +1 -1
  61. ansible/module_utils/facts/system/local.py +6 -2
  62. ansible/module_utils/facts/system/pkg_mgr.py +6 -1
  63. ansible/module_utils/facts/system/service_mgr.py +4 -2
  64. ansible/module_utils/parsing/convert_bool.py +1 -1
  65. ansible/module_utils/service.py +9 -6
  66. ansible/module_utils/urls.py +40 -22
  67. ansible/modules/add_host.py +2 -2
  68. ansible/modules/apt.py +48 -31
  69. ansible/modules/apt_key.py +4 -4
  70. ansible/modules/apt_repository.py +5 -5
  71. ansible/modules/assemble.py +7 -7
  72. ansible/modules/assert.py +1 -1
  73. ansible/modules/async_status.py +11 -7
  74. ansible/modules/async_wrapper.py +1 -1
  75. ansible/modules/blockinfile.py +60 -17
  76. ansible/modules/command.py +37 -15
  77. ansible/modules/copy.py +35 -30
  78. ansible/modules/cron.py +14 -14
  79. ansible/modules/deb822_repository.py +4 -3
  80. ansible/modules/debconf.py +35 -14
  81. ansible/modules/debug.py +1 -1
  82. ansible/modules/dnf.py +29 -27
  83. ansible/modules/dnf5.py +22 -22
  84. ansible/modules/dpkg_selections.py +9 -2
  85. ansible/modules/expect.py +4 -4
  86. ansible/modules/fetch.py +7 -7
  87. ansible/modules/file.py +30 -30
  88. ansible/modules/find.py +82 -22
  89. ansible/modules/gather_facts.py +6 -2
  90. ansible/modules/get_url.py +29 -29
  91. ansible/modules/getent.py +4 -4
  92. ansible/modules/git.py +27 -27
  93. ansible/modules/group.py +5 -12
  94. ansible/modules/hostname.py +21 -2
  95. ansible/modules/include_role.py +5 -5
  96. ansible/modules/include_tasks.py +2 -2
  97. ansible/modules/include_vars.py +5 -5
  98. ansible/modules/iptables.py +70 -65
  99. ansible/modules/known_hosts.py +7 -7
  100. ansible/modules/lineinfile.py +33 -33
  101. ansible/modules/meta.py +13 -13
  102. ansible/modules/package.py +8 -8
  103. ansible/modules/package_facts.py +3 -3
  104. ansible/modules/pause.py +2 -2
  105. ansible/modules/ping.py +5 -5
  106. ansible/modules/pip.py +80 -46
  107. ansible/modules/reboot.py +8 -4
  108. ansible/modules/replace.py +20 -15
  109. ansible/modules/rpm_key.py +2 -2
  110. ansible/modules/script.py +16 -10
  111. ansible/modules/service.py +26 -98
  112. ansible/modules/service_facts.py +36 -12
  113. ansible/modules/set_fact.py +2 -2
  114. ansible/modules/set_stats.py +2 -2
  115. ansible/modules/setup.py +18 -18
  116. ansible/modules/shell.py +3 -3
  117. ansible/modules/stat.py +9 -30
  118. ansible/modules/subversion.py +9 -9
  119. ansible/modules/systemd.py +20 -19
  120. ansible/modules/systemd_service.py +20 -19
  121. ansible/modules/sysvinit.py +26 -21
  122. ansible/modules/tempfile.py +5 -4
  123. ansible/modules/template.py +60 -6
  124. ansible/modules/unarchive.py +21 -18
  125. ansible/modules/uri.py +39 -39
  126. ansible/modules/user.py +81 -53
  127. ansible/modules/wait_for.py +22 -21
  128. ansible/modules/wait_for_connection.py +4 -4
  129. ansible/modules/yum.py +38 -38
  130. ansible/modules/yum_repository.py +58 -80
  131. ansible/parsing/dataloader.py +27 -27
  132. ansible/parsing/mod_args.py +1 -1
  133. ansible/parsing/plugin_docs.py +3 -3
  134. ansible/parsing/splitter.py +14 -16
  135. ansible/parsing/utils/yaml.py +1 -1
  136. ansible/parsing/vault/__init__.py +8 -6
  137. ansible/parsing/yaml/constructor.py +1 -1
  138. ansible/parsing/yaml/objects.py +1 -1
  139. ansible/playbook/__init__.py +1 -1
  140. ansible/playbook/base.py +2 -2
  141. ansible/playbook/block.py +0 -1
  142. ansible/playbook/conditional.py +40 -114
  143. ansible/playbook/helpers.py +5 -28
  144. ansible/playbook/included_file.py +8 -7
  145. ansible/playbook/play.py +1 -1
  146. ansible/playbook/play_context.py +2 -2
  147. ansible/playbook/playbook_include.py +2 -2
  148. ansible/playbook/role/__init__.py +1 -1
  149. ansible/playbook/role/include.py +1 -1
  150. ansible/playbook/role/metadata.py +1 -1
  151. ansible/playbook/role_include.py +1 -1
  152. ansible/playbook/task.py +2 -2
  153. ansible/playbook/task_include.py +1 -24
  154. ansible/plugins/__init__.py +13 -5
  155. ansible/plugins/action/__init__.py +17 -43
  156. ansible/plugins/action/add_host.py +2 -3
  157. ansible/plugins/action/assemble.py +1 -1
  158. ansible/plugins/action/assert.py +2 -1
  159. ansible/plugins/action/copy.py +2 -2
  160. ansible/plugins/action/debug.py +2 -1
  161. ansible/plugins/action/fail.py +1 -0
  162. ansible/plugins/action/fetch.py +3 -1
  163. ansible/plugins/action/gather_facts.py +37 -13
  164. ansible/plugins/action/group_by.py +1 -0
  165. ansible/plugins/action/include_vars.py +3 -2
  166. ansible/plugins/action/normal.py +3 -3
  167. ansible/plugins/action/pause.py +1 -1
  168. ansible/plugins/action/reboot.py +21 -16
  169. ansible/plugins/action/script.py +23 -8
  170. ansible/plugins/action/set_fact.py +1 -0
  171. ansible/plugins/action/set_stats.py +1 -0
  172. ansible/plugins/action/shell.py +6 -0
  173. ansible/plugins/action/template.py +1 -1
  174. ansible/plugins/action/unarchive.py +1 -1
  175. ansible/plugins/action/uri.py +1 -1
  176. ansible/plugins/action/validate_argument_spec.py +1 -0
  177. ansible/plugins/action/wait_for_connection.py +4 -4
  178. ansible/plugins/become/__init__.py +1 -1
  179. ansible/plugins/become/su.py +1 -1
  180. ansible/plugins/cache/__init__.py +1 -1
  181. ansible/plugins/callback/junit.py +1 -1
  182. ansible/plugins/callback/oneline.py +1 -1
  183. ansible/plugins/callback/tree.py +1 -1
  184. ansible/plugins/cliconf/__init__.py +2 -2
  185. ansible/plugins/connection/__init__.py +65 -37
  186. ansible/plugins/connection/local.py +9 -8
  187. ansible/plugins/connection/paramiko_ssh.py +34 -28
  188. ansible/plugins/connection/psrp.py +56 -43
  189. ansible/plugins/connection/ssh.py +67 -43
  190. ansible/plugins/connection/winrm.py +77 -30
  191. ansible/plugins/doc_fragments/constructed.py +4 -4
  192. ansible/plugins/doc_fragments/files.py +12 -12
  193. ansible/plugins/doc_fragments/inventory_cache.py +0 -6
  194. ansible/plugins/doc_fragments/result_format_callback.py +5 -5
  195. ansible/plugins/doc_fragments/shell_common.py +2 -2
  196. ansible/plugins/doc_fragments/shell_windows.py +1 -1
  197. ansible/plugins/doc_fragments/template_common.py +6 -6
  198. ansible/plugins/doc_fragments/url.py +10 -10
  199. ansible/plugins/doc_fragments/url_windows.py +15 -15
  200. ansible/plugins/doc_fragments/vars_plugin_staging.py +4 -4
  201. ansible/plugins/filter/b64decode.yml +1 -1
  202. ansible/plugins/filter/b64encode.yml +2 -2
  203. ansible/plugins/filter/bool.yml +5 -5
  204. ansible/plugins/filter/combine.yml +1 -1
  205. ansible/plugins/filter/commonpath.yml +2 -1
  206. ansible/plugins/filter/core.py +6 -8
  207. ansible/plugins/filter/dict2items.yml +11 -1
  208. ansible/plugins/filter/difference.yml +1 -0
  209. ansible/plugins/filter/encryption.py +1 -1
  210. ansible/plugins/filter/extract.yml +1 -1
  211. ansible/plugins/filter/flatten.yml +1 -1
  212. ansible/plugins/filter/from_yaml.yml +1 -1
  213. ansible/plugins/filter/from_yaml_all.yml +2 -2
  214. ansible/plugins/filter/hash.yml +1 -1
  215. ansible/plugins/filter/human_readable.yml +1 -1
  216. ansible/plugins/filter/human_to_bytes.yml +2 -2
  217. ansible/plugins/filter/intersect.yml +1 -0
  218. ansible/plugins/filter/mandatory.yml +7 -0
  219. ansible/plugins/filter/mathstuff.py +15 -17
  220. ansible/plugins/filter/normpath.yml +1 -1
  221. ansible/plugins/filter/path_join.yml +8 -1
  222. ansible/plugins/filter/realpath.yml +3 -2
  223. ansible/plugins/filter/regex_findall.yml +8 -2
  224. ansible/plugins/filter/regex_replace.yml +9 -3
  225. ansible/plugins/filter/regex_search.yml +8 -2
  226. ansible/plugins/filter/relpath.yml +2 -2
  227. ansible/plugins/filter/root.yml +1 -1
  228. ansible/plugins/filter/splitext.yml +1 -1
  229. ansible/plugins/filter/subelements.yml +2 -2
  230. ansible/plugins/filter/symmetric_difference.yml +1 -0
  231. ansible/plugins/filter/ternary.yml +5 -5
  232. ansible/plugins/filter/to_json.yml +7 -7
  233. ansible/plugins/filter/to_nice_json.yml +5 -5
  234. ansible/plugins/filter/to_yaml.yml +2 -2
  235. ansible/plugins/filter/type_debug.yml +1 -1
  236. ansible/plugins/filter/union.yml +1 -0
  237. ansible/plugins/filter/unvault.yml +2 -2
  238. ansible/plugins/filter/urldecode.yml +13 -32
  239. ansible/plugins/filter/urlsplit.py +1 -1
  240. ansible/plugins/filter/vault.yml +1 -1
  241. ansible/plugins/filter/zip.yml +1 -1
  242. ansible/plugins/filter/zip_longest.yml +1 -1
  243. ansible/plugins/inventory/__init__.py +1 -1
  244. ansible/plugins/inventory/advanced_host_list.py +1 -1
  245. ansible/plugins/inventory/constructed.py +2 -2
  246. ansible/plugins/inventory/host_list.py +1 -1
  247. ansible/plugins/inventory/ini.py +6 -3
  248. ansible/plugins/inventory/script.py +8 -2
  249. ansible/plugins/inventory/toml.py +1 -1
  250. ansible/plugins/inventory/yaml.py +1 -1
  251. ansible/plugins/list.py +21 -17
  252. ansible/plugins/loader.py +66 -88
  253. ansible/plugins/lookup/__init__.py +1 -1
  254. ansible/plugins/lookup/config.py +16 -6
  255. ansible/plugins/lookup/csvfile.py +7 -4
  256. ansible/plugins/lookup/env.py +1 -1
  257. ansible/plugins/lookup/file.py +5 -2
  258. ansible/plugins/lookup/fileglob.py +5 -2
  259. ansible/plugins/lookup/first_found.py +20 -14
  260. ansible/plugins/lookup/ini.py +6 -3
  261. ansible/plugins/lookup/lines.py +2 -1
  262. ansible/plugins/lookup/password.py +7 -7
  263. ansible/plugins/lookup/pipe.py +1 -0
  264. ansible/plugins/lookup/random_choice.py +2 -2
  265. ansible/plugins/lookup/sequence.py +1 -1
  266. ansible/plugins/lookup/subelements.py +2 -2
  267. ansible/plugins/lookup/template.py +4 -1
  268. ansible/plugins/lookup/unvault.py +4 -1
  269. ansible/plugins/lookup/url.py +6 -6
  270. ansible/plugins/lookup/varnames.py +1 -1
  271. ansible/plugins/netconf/__init__.py +3 -3
  272. ansible/plugins/shell/__init__.py +1 -1
  273. ansible/plugins/shell/cmd.py +7 -7
  274. ansible/plugins/shell/powershell.py +1 -1
  275. ansible/plugins/strategy/__init__.py +8 -10
  276. ansible/plugins/strategy/free.py +1 -1
  277. ansible/plugins/strategy/linear.py +3 -3
  278. ansible/plugins/terminal/__init__.py +2 -2
  279. ansible/plugins/test/abs.yml +1 -1
  280. ansible/plugins/test/all.yml +1 -1
  281. ansible/plugins/test/any.yml +1 -1
  282. ansible/plugins/test/change.yml +2 -2
  283. ansible/plugins/test/changed.yml +2 -2
  284. ansible/plugins/test/contains.yml +1 -1
  285. ansible/plugins/test/core.py +1 -1
  286. ansible/plugins/test/directory.yml +1 -1
  287. ansible/plugins/test/exists.yml +3 -2
  288. ansible/plugins/test/failed.yml +2 -2
  289. ansible/plugins/test/failure.yml +2 -2
  290. ansible/plugins/test/falsy.yml +2 -2
  291. ansible/plugins/test/file.yml +1 -1
  292. ansible/plugins/test/finished.yml +2 -2
  293. ansible/plugins/test/is_abs.yml +1 -1
  294. ansible/plugins/test/is_dir.yml +1 -1
  295. ansible/plugins/test/is_file.yml +1 -1
  296. ansible/plugins/test/is_link.yml +1 -1
  297. ansible/plugins/test/is_mount.yml +1 -1
  298. ansible/plugins/test/is_same_file.yml +1 -1
  299. ansible/plugins/test/isnan.yml +1 -1
  300. ansible/plugins/test/issubset.yml +1 -2
  301. ansible/plugins/test/issuperset.yml +1 -2
  302. ansible/plugins/test/link.yml +1 -1
  303. ansible/plugins/test/link_exists.yml +1 -1
  304. ansible/plugins/test/match.yml +2 -2
  305. ansible/plugins/test/mount.yml +1 -1
  306. ansible/plugins/test/nan.yml +1 -1
  307. ansible/plugins/test/reachable.yml +2 -2
  308. ansible/plugins/test/regex.yml +1 -1
  309. ansible/plugins/test/same_file.yml +1 -1
  310. ansible/plugins/test/search.yml +2 -2
  311. ansible/plugins/test/skip.yml +3 -3
  312. ansible/plugins/test/skipped.yml +3 -3
  313. ansible/plugins/test/started.yml +2 -2
  314. ansible/plugins/test/subset.yml +1 -2
  315. ansible/plugins/test/succeeded.yml +2 -2
  316. ansible/plugins/test/success.yml +2 -2
  317. ansible/plugins/test/successful.yml +2 -2
  318. ansible/plugins/test/superset.yml +1 -2
  319. ansible/plugins/test/truthy.yml +3 -3
  320. ansible/plugins/test/unreachable.yml +2 -2
  321. ansible/plugins/test/uri.yml +1 -1
  322. ansible/plugins/test/url.yml +1 -1
  323. ansible/plugins/test/urn.yml +1 -1
  324. ansible/plugins/test/vault_encrypted.yml +1 -1
  325. ansible/plugins/test/version.yml +7 -7
  326. ansible/plugins/test/version_compare.yml +7 -7
  327. ansible/plugins/vars/host_group_vars.py +1 -1
  328. ansible/release.py +2 -2
  329. ansible/template/__init__.py +24 -26
  330. ansible/template/native_helpers.py +1 -1
  331. ansible/template/vars.py +1 -1
  332. ansible/utils/_junit_xml.py +1 -1
  333. ansible/utils/cmd_functions.py +1 -1
  334. ansible/utils/collection_loader/_collection_finder.py +12 -1
  335. ansible/utils/display.py +113 -62
  336. ansible/utils/encrypt.py +11 -14
  337. ansible/utils/hashing.py +1 -1
  338. ansible/utils/jsonrpc.py +1 -1
  339. ansible/utils/path.py +1 -1
  340. ansible/utils/plugin_docs.py +1 -1
  341. ansible/utils/py3compat.py +1 -1
  342. ansible/utils/shlex.py +2 -10
  343. ansible/utils/ssh_functions.py +5 -4
  344. ansible/utils/unicode.py +1 -1
  345. ansible/utils/unsafe_proxy.py +1 -1
  346. ansible/utils/vars.py +4 -29
  347. ansible/vars/hostvars.py +1 -2
  348. ansible/vars/manager.py +13 -9
  349. ansible/vars/plugins.py +2 -2
  350. {ansible_core-2.15.4rc1.dist-info → ansible_core-2.16.0b2.dist-info}/COPYING +4 -5
  351. {ansible_core-2.15.4rc1.dist-info → ansible_core-2.16.0b2.dist-info}/METADATA +2 -4
  352. {ansible_core-2.15.4rc1.dist-info → ansible_core-2.16.0b2.dist-info}/RECORD +424 -425
  353. ansible_test/_data/completion/docker.txt +9 -9
  354. ansible_test/_data/completion/remote.txt +4 -7
  355. ansible_test/_data/completion/windows.txt +0 -2
  356. ansible_test/_data/requirements/ansible-test.txt +2 -1
  357. ansible_test/_data/requirements/ansible.txt +0 -3
  358. ansible_test/_data/requirements/constraints.txt +0 -2
  359. ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -5
  360. ansible_test/_data/requirements/sanity.changelog.in +1 -2
  361. ansible_test/_data/requirements/sanity.changelog.txt +4 -6
  362. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -4
  363. ansible_test/_data/requirements/sanity.import.txt +1 -3
  364. ansible_test/_data/requirements/sanity.integration-aliases.txt +1 -3
  365. ansible_test/_data/requirements/sanity.mypy.txt +12 -12
  366. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  367. ansible_test/_data/requirements/sanity.pylint.txt +6 -12
  368. ansible_test/_data/requirements/sanity.runtime-metadata.txt +1 -3
  369. ansible_test/_data/requirements/sanity.validate-modules.in +1 -1
  370. ansible_test/_data/requirements/sanity.validate-modules.txt +3 -5
  371. ansible_test/_data/requirements/sanity.yamllint.txt +3 -5
  372. ansible_test/_data/requirements/units.txt +0 -1
  373. ansible_test/_internal/ci/azp.py +4 -4
  374. ansible_test/_internal/cli/environments.py +0 -13
  375. ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +4 -4
  376. ansible_test/_internal/commands/coverage/combine.py +1 -1
  377. ansible_test/_internal/commands/integration/cloud/acme.py +6 -8
  378. ansible_test/_internal/commands/integration/cloud/cs.py +4 -9
  379. ansible_test/_internal/commands/integration/cloud/galaxy.py +103 -96
  380. ansible_test/_internal/commands/integration/cloud/httptester.py +0 -3
  381. ansible_test/_internal/commands/integration/cloud/nios.py +7 -9
  382. ansible_test/_internal/commands/integration/cloud/openshift.py +2 -7
  383. ansible_test/_internal/commands/integration/cloud/vcenter.py +11 -95
  384. ansible_test/_internal/commands/sanity/__init__.py +10 -0
  385. ansible_test/_internal/commands/sanity/import.py +8 -2
  386. ansible_test/_internal/commands/sanity/pylint.py +27 -1
  387. ansible_test/_internal/commands/units/__init__.py +2 -1
  388. ansible_test/_internal/config.py +0 -7
  389. ansible_test/_internal/containers.py +11 -56
  390. ansible_test/_internal/core_ci.py +0 -7
  391. ansible_test/_internal/coverage_util.py +8 -3
  392. ansible_test/_internal/delegation.py +0 -1
  393. ansible_test/_internal/diff.py +1 -1
  394. ansible_test/_internal/docker_util.py +9 -2
  395. ansible_test/_internal/host_profiles.py +6 -6
  396. ansible_test/_internal/http.py +1 -1
  397. ansible_test/_internal/junit_xml.py +1 -1
  398. ansible_test/_internal/pypi_proxy.py +1 -1
  399. ansible_test/_internal/python_requirements.py +3 -8
  400. ansible_test/_internal/util.py +1 -6
  401. ansible_test/_util/controller/sanity/code-smell/no-get-exception.json +4 -0
  402. ansible_test/_util/controller/sanity/code-smell/replace-urlopen.json +4 -0
  403. ansible_test/_util/controller/sanity/code-smell/use-compat-six.json +4 -0
  404. ansible_test/_util/controller/sanity/mypy/ansible-core.ini +3 -0
  405. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +2 -0
  406. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +0 -1
  407. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  408. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +172 -10
  409. ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +13 -2
  410. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +7 -1
  411. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +6 -6
  412. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -1
  413. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -1
  414. ansible_test/_util/controller/sanity/yamllint/yamllinter.py +3 -3
  415. ansible_test/_util/controller/tools/collection_detail.py +2 -2
  416. ansible_test/_util/target/common/constants.py +2 -2
  417. ansible_test/_util/target/pytest/plugins/ansible_forked.py +103 -0
  418. ansible_test/_util/target/sanity/import/importer.py +0 -8
  419. ansible_test/_util/target/setup/bootstrap.sh +36 -16
  420. ansible_test/_util/target/setup/quiet_pip.py +0 -4
  421. ansible/modules/_include.py +0 -80
  422. ansible_test/_internal/commands/integration/cloud/foreman.py +0 -102
  423. ansible_test/_util/target/setup/ConfigureRemotingForAnsible.ps1 +0 -435
  424. {ansible_core-2.15.4rc1.data → ansible_core-2.16.0b2.data}/scripts/ansible-test +0 -0
  425. {ansible_core-2.15.4rc1.dist-info → ansible_core-2.16.0b2.dist-info}/WHEEL +0 -0
  426. {ansible_core-2.15.4rc1.dist-info → ansible_core-2.16.0b2.dist-info}/entry_points.txt +0 -0
  427. {ansible_core-2.15.4rc1.dist-info → ansible_core-2.16.0b2.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@
4
4
  # Copyright (c) 2017 Ansible Project
5
5
  # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
6
 
7
- from __future__ import (absolute_import, division, print_function)
7
+ from __future__ import (annotations, absolute_import, division, print_function)
8
8
  __metaclass__ = type
9
9
 
10
10
  DOCUMENTATION = '''
@@ -20,7 +20,7 @@ DOCUMENTATION = '''
20
20
  - connection_pipelining
21
21
  version_added: historical
22
22
  notes:
23
- - Many options default to C(None) here but that only means we do not override the SSH tool's defaults and/or configuration.
23
+ - Many options default to V(None) here but that only means we do not override the SSH tool's defaults and/or configuration.
24
24
  For example, if you specify the port in this plugin it will override any C(Port) entry in your C(.ssh/config).
25
25
  - The ssh CLI tool uses return code 255 as a 'connection error', this can conflict with commands/tools that
26
26
  also return 255 as an error code and will look like an 'unreachable' condition or 'connection error' to this plugin.
@@ -55,7 +55,7 @@ DOCUMENTATION = '''
55
55
  - name: ansible_ssh_host_key_checking
56
56
  version_added: '2.5'
57
57
  password:
58
- description: Authentication password for the C(remote_user). Can be supplied as CLI option.
58
+ description: Authentication password for the O(remote_user). Can be supplied as CLI option.
59
59
  type: string
60
60
  vars:
61
61
  - name: ansible_password
@@ -105,7 +105,7 @@ DOCUMENTATION = '''
105
105
  ssh_executable:
106
106
  default: ssh
107
107
  description:
108
- - This defines the location of the SSH binary. It defaults to C(ssh) which will use the first SSH binary available in $PATH.
108
+ - This defines the location of the SSH binary. It defaults to V(ssh) which will use the first SSH binary available in $PATH.
109
109
  - This option is usually not required, it might be useful when access to system SSH is restricted,
110
110
  or when using SSH wrappers to connect to remote hosts.
111
111
  type: string
@@ -120,7 +120,7 @@ DOCUMENTATION = '''
120
120
  sftp_executable:
121
121
  default: sftp
122
122
  description:
123
- - This defines the location of the sftp binary. It defaults to C(sftp) which will use the first binary available in $PATH.
123
+ - This defines the location of the sftp binary. It defaults to V(sftp) which will use the first binary available in $PATH.
124
124
  type: string
125
125
  env: [{name: ANSIBLE_SFTP_EXECUTABLE}]
126
126
  ini:
@@ -132,7 +132,7 @@ DOCUMENTATION = '''
132
132
  scp_executable:
133
133
  default: scp
134
134
  description:
135
- - This defines the location of the scp binary. It defaults to C(scp) which will use the first binary available in $PATH.
135
+ - This defines the location of the scp binary. It defaults to V(scp) which will use the first binary available in $PATH.
136
136
  type: string
137
137
  env: [{name: ANSIBLE_SCP_EXECUTABLE}]
138
138
  ini:
@@ -319,16 +319,16 @@ DOCUMENTATION = '''
319
319
  version_added: '2.12'
320
320
  scp_if_ssh:
321
321
  deprecated:
322
- why: In favor of the "ssh_transfer_method" option.
322
+ why: In favor of the O(ssh_transfer_method) option.
323
323
  version: "2.17"
324
- alternatives: ssh_transfer_method
324
+ alternatives: O(ssh_transfer_method)
325
325
  default: smart
326
326
  description:
327
327
  - "Preferred method to use when transferring files over SSH."
328
- - When set to I(smart), Ansible will try them until one succeeds or they all fail.
329
- - If set to I(True), it will force 'scp', if I(False) it will use 'sftp'.
330
- - For OpenSSH >=9.0 you must add an additional option to enable scp (scp_extra_args="-O")
331
- - This setting will overridden by ssh_transfer_method if set.
328
+ - When set to V(smart), Ansible will try them until one succeeds or they all fail.
329
+ - If set to V(True), it will force 'scp', if V(False) it will use 'sftp'.
330
+ - For OpenSSH >=9.0 you must add an additional option to enable scp (C(scp_extra_args="-O"))
331
+ - This setting will overridden by O(ssh_transfer_method) if set.
332
332
  env: [{name: ANSIBLE_SCP_IF_SSH}]
333
333
  ini:
334
334
  - {key: scp_if_ssh, section: ssh_connection}
@@ -381,15 +381,18 @@ DOCUMENTATION = '''
381
381
  - name: ansible_ssh_pkcs11_provider
382
382
  '''
383
383
 
384
+ import collections.abc as c
384
385
  import errno
385
386
  import fcntl
386
387
  import hashlib
388
+ import io
387
389
  import os
388
390
  import pty
389
391
  import re
390
392
  import shlex
391
393
  import subprocess
392
394
  import time
395
+ import typing as t
393
396
 
394
397
  from functools import wraps
395
398
  from ansible.errors import (
@@ -401,7 +404,7 @@ from ansible.errors import (
401
404
  from ansible.errors import AnsibleOptionsError
402
405
  from ansible.module_utils.compat import selectors
403
406
  from ansible.module_utils.six import PY3, text_type, binary_type
404
- from ansible.module_utils._text import to_bytes, to_native, to_text
407
+ from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
405
408
  from ansible.module_utils.parsing.convert_bool import BOOLEANS, boolean
406
409
  from ansible.plugins.connection import ConnectionBase, BUFSIZE
407
410
  from ansible.plugins.shell.powershell import _parse_clixml
@@ -410,6 +413,8 @@ from ansible.utils.path import unfrackpath, makedirs_safe
410
413
 
411
414
  display = Display()
412
415
 
416
+ P = t.ParamSpec('P')
417
+
413
418
  # error messages that indicate 255 return code is not from ssh itself.
414
419
  b_NOT_SSH_ERRORS = (b'Traceback (most recent call last):', # Python-2.6 when there's an exception
415
420
  # while invoking a script via -m
@@ -427,7 +432,14 @@ class AnsibleControlPersistBrokenPipeError(AnsibleError):
427
432
  pass
428
433
 
429
434
 
430
- def _handle_error(remaining_retries, command, return_tuple, no_log, host, display=display):
435
+ def _handle_error(
436
+ remaining_retries: int,
437
+ command: bytes,
438
+ return_tuple: tuple[int, bytes, bytes],
439
+ no_log: bool,
440
+ host: str,
441
+ display: Display = display,
442
+ ) -> None:
431
443
 
432
444
  # sshpass errors
433
445
  if command == b'sshpass':
@@ -483,7 +495,9 @@ def _handle_error(remaining_retries, command, return_tuple, no_log, host, displa
483
495
  display.vvv(msg, host=host)
484
496
 
485
497
 
486
- def _ssh_retry(func):
498
+ def _ssh_retry(
499
+ func: c.Callable[t.Concatenate[Connection, P], tuple[int, bytes, bytes]],
500
+ ) -> c.Callable[t.Concatenate[Connection, P], tuple[int, bytes, bytes]]:
487
501
  """
488
502
  Decorator to retry ssh/scp/sftp in the case of a connection failure
489
503
 
@@ -496,12 +510,12 @@ def _ssh_retry(func):
496
510
  * retries limit reached
497
511
  """
498
512
  @wraps(func)
499
- def wrapped(self, *args, **kwargs):
513
+ def wrapped(self: Connection, *args: P.args, **kwargs: P.kwargs) -> tuple[int, bytes, bytes]:
500
514
  remaining_tries = int(self.get_option('reconnection_retries')) + 1
501
515
  cmd_summary = u"%s..." % to_text(args[0])
502
516
  conn_password = self.get_option('password') or self._play_context.password
503
517
  for attempt in range(remaining_tries):
504
- cmd = args[0]
518
+ cmd = t.cast(list[bytes], args[0])
505
519
  if attempt != 0 and conn_password and isinstance(cmd, list):
506
520
  # If this is a retry, the fd/pipe for sshpass is closed, and we need a new one
507
521
  self.sshpass_pipe = os.pipe()
@@ -514,13 +528,13 @@ def _ssh_retry(func):
514
528
  if self._play_context.no_log:
515
529
  display.vvv(u'rc=%s, stdout and stderr censored due to no log' % return_tuple[0], host=self.host)
516
530
  else:
517
- display.vvv(return_tuple, host=self.host)
531
+ display.vvv(str(return_tuple), host=self.host)
518
532
  # 0 = success
519
533
  # 1-254 = remote command return code
520
534
  # 255 could be a failure from the ssh command itself
521
535
  except (AnsibleControlPersistBrokenPipeError):
522
536
  # Retry one more time because of the ControlPersist broken pipe (see #16731)
523
- cmd = args[0]
537
+ cmd = t.cast(list[bytes], args[0])
524
538
  if conn_password and isinstance(cmd, list):
525
539
  # This is a retry, so the fd/pipe for sshpass is closed, and we need a new one
526
540
  self.sshpass_pipe = os.pipe()
@@ -568,15 +582,15 @@ class Connection(ConnectionBase):
568
582
  transport = 'ssh'
569
583
  has_pipelining = True
570
584
 
571
- def __init__(self, *args, **kwargs):
585
+ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
572
586
  super(Connection, self).__init__(*args, **kwargs)
573
587
 
574
588
  # TODO: all should come from get_option(), but not might be set at this point yet
575
589
  self.host = self._play_context.remote_addr
576
590
  self.port = self._play_context.port
577
591
  self.user = self._play_context.remote_user
578
- self.control_path = None
579
- self.control_path_dir = None
592
+ self.control_path: str | None = None
593
+ self.control_path_dir: str | None = None
580
594
 
581
595
  # Windows operates differently from a POSIX connection/shell plugin,
582
596
  # we need to set various properties to ensure SSH on Windows continues
@@ -591,11 +605,17 @@ class Connection(ConnectionBase):
591
605
  # put_file, and fetch_file methods, so we don't need to do any connection
592
606
  # management here.
593
607
 
594
- def _connect(self):
608
+ def _connect(self) -> Connection:
595
609
  return self
596
610
 
597
611
  @staticmethod
598
- def _create_control_path(host, port, user, connection=None, pid=None):
612
+ def _create_control_path(
613
+ host: str | None,
614
+ port: int | None,
615
+ user: str | None,
616
+ connection: ConnectionBase | None = None,
617
+ pid: int | None = None,
618
+ ) -> str:
599
619
  '''Make a hash for the controlpath based on con attributes'''
600
620
  pstring = '%s-%s-%s' % (host, port, user)
601
621
  if connection:
@@ -609,7 +629,7 @@ class Connection(ConnectionBase):
609
629
  return cpath
610
630
 
611
631
  @staticmethod
612
- def _sshpass_available():
632
+ def _sshpass_available() -> bool:
613
633
  global SSHPASS_AVAILABLE
614
634
 
615
635
  # We test once if sshpass is available, and remember the result. It
@@ -627,7 +647,7 @@ class Connection(ConnectionBase):
627
647
  return SSHPASS_AVAILABLE
628
648
 
629
649
  @staticmethod
630
- def _persistence_controls(b_command):
650
+ def _persistence_controls(b_command: list[bytes]) -> tuple[bool, bool]:
631
651
  '''
632
652
  Takes a command array and scans it for ControlPersist and ControlPath
633
653
  settings and returns two booleans indicating whether either was found.
@@ -646,7 +666,7 @@ class Connection(ConnectionBase):
646
666
 
647
667
  return controlpersist, controlpath
648
668
 
649
- def _add_args(self, b_command, b_args, explanation):
669
+ def _add_args(self, b_command: list[bytes], b_args: t.Iterable[bytes], explanation: str) -> None:
650
670
  """
651
671
  Adds arguments to the ssh command and displays a caller-supplied explanation of why.
652
672
 
@@ -662,7 +682,7 @@ class Connection(ConnectionBase):
662
682
  display.vvvvv(u'SSH: %s: (%s)' % (explanation, ')('.join(to_text(a) for a in b_args)), host=self.host)
663
683
  b_command += b_args
664
684
 
665
- def _build_command(self, binary, subsystem, *other_args):
685
+ def _build_command(self, binary: str, subsystem: str, *other_args: bytes | str) -> list[bytes]:
666
686
  '''
667
687
  Takes a executable (ssh, scp, sftp or wrapper) and optional extra arguments and returns the remote command
668
688
  wrapped in local ssh shell commands and ready for execution.
@@ -719,6 +739,7 @@ class Connection(ConnectionBase):
719
739
  # be disabled if the client side doesn't support the option. However,
720
740
  # sftp batch mode does not prompt for passwords so it must be disabled
721
741
  # if not using controlpersist and using sshpass
742
+ b_args: t.Iterable[bytes]
722
743
  if subsystem == 'sftp' and self.get_option('sftp_batch_mode'):
723
744
  if conn_password:
724
745
  b_args = [b'-o', b'BatchMode=no']
@@ -818,7 +839,7 @@ class Connection(ConnectionBase):
818
839
 
819
840
  return b_command
820
841
 
821
- def _send_initial_data(self, fh, in_data, ssh_process):
842
+ def _send_initial_data(self, fh: io.IOBase, in_data: bytes, ssh_process: subprocess.Popen) -> None:
822
843
  '''
823
844
  Writes initial data to the stdin filehandle of the subprocess and closes
824
845
  it. (The handle must be closed; otherwise, for example, "sftp -b -" will
@@ -845,7 +866,7 @@ class Connection(ConnectionBase):
845
866
 
846
867
  # Used by _run() to kill processes on failures
847
868
  @staticmethod
848
- def _terminate_process(p):
869
+ def _terminate_process(p: subprocess.Popen) -> None:
849
870
  """ Terminate a process, ignoring errors """
850
871
  try:
851
872
  p.terminate()
@@ -854,7 +875,7 @@ class Connection(ConnectionBase):
854
875
 
855
876
  # This is separate from _run() because we need to do the same thing for stdout
856
877
  # and stderr.
857
- def _examine_output(self, source, state, b_chunk, sudoable):
878
+ def _examine_output(self, source: str, state: str, b_chunk: bytes, sudoable: bool) -> tuple[bytes, bytes]:
858
879
  '''
859
880
  Takes a string, extracts complete lines from it, tests to see if they
860
881
  are a prompt, error message, etc., and sets appropriate flags in self.
@@ -903,7 +924,7 @@ class Connection(ConnectionBase):
903
924
 
904
925
  return b''.join(output), remainder
905
926
 
906
- def _bare_run(self, cmd, in_data, sudoable=True, checkrc=True):
927
+ def _bare_run(self, cmd: list[bytes], in_data: bytes | None, sudoable: bool = True, checkrc: bool = True) -> tuple[int, bytes, bytes]:
907
928
  '''
908
929
  Starts the command and communicates with it until it ends.
909
930
  '''
@@ -949,7 +970,7 @@ class Connection(ConnectionBase):
949
970
  else:
950
971
  p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
951
972
  stderr=subprocess.PIPE)
952
- stdin = p.stdin
973
+ stdin = p.stdin # type: ignore[assignment] # stdin will be set and not None due to the calls above
953
974
  except (OSError, IOError) as e:
954
975
  raise AnsibleError('Unable to execute ssh command line on a controller due to: %s' % to_native(e))
955
976
 
@@ -1199,13 +1220,13 @@ class Connection(ConnectionBase):
1199
1220
  return (p.returncode, b_stdout, b_stderr)
1200
1221
 
1201
1222
  @_ssh_retry
1202
- def _run(self, cmd, in_data, sudoable=True, checkrc=True):
1223
+ def _run(self, cmd: list[bytes], in_data: bytes | None, sudoable: bool = True, checkrc: bool = True) -> tuple[int, bytes, bytes]:
1203
1224
  """Wrapper around _bare_run that retries the connection
1204
1225
  """
1205
1226
  return self._bare_run(cmd, in_data, sudoable=sudoable, checkrc=checkrc)
1206
1227
 
1207
1228
  @_ssh_retry
1208
- def _file_transport_command(self, in_path, out_path, sftp_action):
1229
+ def _file_transport_command(self, in_path: str, out_path: str, sftp_action: str) -> tuple[int, bytes, bytes]:
1209
1230
  # scp and sftp require square brackets for IPv6 addresses, but
1210
1231
  # accept them for hostnames and IPv4 addresses too.
1211
1232
  host = '[%s]' % self.host
@@ -1293,7 +1314,7 @@ class Connection(ConnectionBase):
1293
1314
  raise AnsibleError("failed to transfer file to %s %s:\n%s\n%s" %
1294
1315
  (to_native(in_path), to_native(out_path), to_native(stdout), to_native(stderr)))
1295
1316
 
1296
- def _escape_win_path(self, path):
1317
+ def _escape_win_path(self, path: str) -> str:
1297
1318
  """ converts a Windows path to one that's supported by SFTP and SCP """
1298
1319
  # If using a root path then we need to start with /
1299
1320
  prefix = ""
@@ -1306,7 +1327,7 @@ class Connection(ConnectionBase):
1306
1327
  #
1307
1328
  # Main public methods
1308
1329
  #
1309
- def exec_command(self, cmd, in_data=None, sudoable=True):
1330
+ def exec_command(self, cmd: str, in_data: bytes | None = None, sudoable: bool = True) -> tuple[int, bytes, bytes]:
1310
1331
  ''' run a command on the remote host '''
1311
1332
 
1312
1333
  super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
@@ -1323,8 +1344,10 @@ class Connection(ConnectionBase):
1323
1344
 
1324
1345
  # Make sure our first command is to set the console encoding to
1325
1346
  # utf-8, this must be done via chcp to get utf-8 (65001)
1326
- cmd_parts = ["chcp.com", "65001", self._shell._SHELL_REDIRECT_ALLNULL, self._shell._SHELL_AND]
1327
- cmd_parts.extend(self._shell._encode_script(cmd, as_list=True, strict_mode=False, preserve_rc=False))
1347
+ # union-attr ignores rely on internal powershell shell plugin details,
1348
+ # this should be fixed at a future point in time.
1349
+ cmd_parts = ["chcp.com", "65001", self._shell._SHELL_REDIRECT_ALLNULL, self._shell._SHELL_AND] # type: ignore[union-attr]
1350
+ cmd_parts.extend(self._shell._encode_script(cmd, as_list=True, strict_mode=False, preserve_rc=False)) # type: ignore[union-attr]
1328
1351
  cmd = ' '.join(cmd_parts)
1329
1352
 
1330
1353
  # we can only use tty when we are not pipelining the modules. piping
@@ -1338,6 +1361,7 @@ class Connection(ConnectionBase):
1338
1361
  # to disable it as a troubleshooting method.
1339
1362
  use_tty = self.get_option('use_tty')
1340
1363
 
1364
+ args: tuple[str, ...]
1341
1365
  if not in_data and sudoable and use_tty:
1342
1366
  args = ('-tt', self.host, cmd)
1343
1367
  else:
@@ -1352,7 +1376,7 @@ class Connection(ConnectionBase):
1352
1376
 
1353
1377
  return (returncode, stdout, stderr)
1354
1378
 
1355
- def put_file(self, in_path, out_path):
1379
+ def put_file(self, in_path: str, out_path: str) -> tuple[int, bytes, bytes]: # type: ignore[override] # Used by tests and would break API
1356
1380
  ''' transfer a file from local to remote '''
1357
1381
 
1358
1382
  super(Connection, self).put_file(in_path, out_path)
@@ -1368,7 +1392,7 @@ class Connection(ConnectionBase):
1368
1392
 
1369
1393
  return self._file_transport_command(in_path, out_path, 'put')
1370
1394
 
1371
- def fetch_file(self, in_path, out_path):
1395
+ def fetch_file(self, in_path: str, out_path: str) -> tuple[int, bytes, bytes]: # type: ignore[override] # Used by tests and would break API
1372
1396
  ''' fetch a file from remote to local '''
1373
1397
 
1374
1398
  super(Connection, self).fetch_file(in_path, out_path)
@@ -1383,7 +1407,7 @@ class Connection(ConnectionBase):
1383
1407
 
1384
1408
  return self._file_transport_command(in_path, out_path, 'get')
1385
1409
 
1386
- def reset(self):
1410
+ def reset(self) -> None:
1387
1411
 
1388
1412
  run_reset = False
1389
1413
  self.host = self.get_option('host') or self._play_context.remote_addr
@@ -1412,5 +1436,5 @@ class Connection(ConnectionBase):
1412
1436
 
1413
1437
  self.close()
1414
1438
 
1415
- def close(self):
1439
+ def close(self) -> None:
1416
1440
  self._connected = False
@@ -2,7 +2,7 @@
2
2
  # Copyright (c) 2017 Ansible Project
3
3
  # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
4
 
5
- from __future__ import (absolute_import, division, print_function)
5
+ from __future__ import (annotations, absolute_import, division, print_function)
6
6
  __metaclass__ = type
7
7
 
8
8
  DOCUMENTATION = """
@@ -39,7 +39,7 @@ DOCUMENTATION = """
39
39
  - name: remote_user
40
40
  type: str
41
41
  remote_password:
42
- description: Authentication password for the C(remote_user). Can be supplied as CLI option.
42
+ description: Authentication password for the O(remote_user). Can be supplied as CLI option.
43
43
  vars:
44
44
  - name: ansible_password
45
45
  - name: ansible_winrm_pass
@@ -61,8 +61,8 @@ DOCUMENTATION = """
61
61
  scheme:
62
62
  description:
63
63
  - URI scheme to use
64
- - If not set, then will default to C(https) or C(http) if I(port) is
65
- C(5985).
64
+ - If not set, then will default to V(https) or V(http) if O(port) is
65
+ V(5985).
66
66
  choices: [http, https]
67
67
  vars:
68
68
  - name: ansible_winrm_scheme
@@ -119,7 +119,7 @@ DOCUMENTATION = """
119
119
  - The managed option means Ansible will obtain kerberos ticket.
120
120
  - While the manual one means a ticket must already have been obtained by the user.
121
121
  - If having issues with Ansible freezing when trying to obtain the
122
- Kerberos ticket, you can either set this to C(manual) and obtain
122
+ Kerberos ticket, you can either set this to V(manual) and obtain
123
123
  it outside Ansible or install C(pexpect) through pip and try
124
124
  again.
125
125
  choices: [managed, manual]
@@ -170,6 +170,8 @@ import json
170
170
  import tempfile
171
171
  import shlex
172
172
  import subprocess
173
+ import time
174
+ import typing as t
173
175
 
174
176
  from inspect import getfullargspec
175
177
  from urllib.parse import urlunsplit
@@ -186,10 +188,11 @@ from ansible.errors import AnsibleError, AnsibleConnectionFailure
186
188
  from ansible.errors import AnsibleFileNotFound
187
189
  from ansible.module_utils.json_utils import _filter_non_json_lines
188
190
  from ansible.module_utils.parsing.convert_bool import boolean
189
- from ansible.module_utils._text import to_bytes, to_native, to_text
191
+ from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
190
192
  from ansible.module_utils.six import binary_type
191
193
  from ansible.plugins.connection import ConnectionBase
192
194
  from ansible.plugins.shell.powershell import _parse_clixml
195
+ from ansible.plugins.shell.powershell import ShellBase as PowerShellBase
193
196
  from ansible.utils.hashing import secure_hash
194
197
  from ansible.utils.display import Display
195
198
 
@@ -197,6 +200,7 @@ from ansible.utils.display import Display
197
200
  try:
198
201
  import winrm
199
202
  from winrm import Response
203
+ from winrm.exceptions import WinRMError, WinRMOperationTimeoutError
200
204
  from winrm.protocol import Protocol
201
205
  import requests.exceptions
202
206
  HAS_WINRM = True
@@ -245,14 +249,15 @@ class Connection(ConnectionBase):
245
249
  has_pipelining = True
246
250
  allow_extras = True
247
251
 
248
- def __init__(self, *args, **kwargs):
252
+ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
249
253
 
250
254
  self.always_pipeline_modules = True
251
255
  self.has_native_async = True
252
256
 
253
- self.protocol = None
254
- self.shell_id = None
257
+ self.protocol: winrm.Protocol | None = None
258
+ self.shell_id: str | None = None
255
259
  self.delegate = None
260
+ self._shell: PowerShellBase
256
261
  self._shell_type = 'powershell'
257
262
 
258
263
  super(Connection, self).__init__(*args, **kwargs)
@@ -262,7 +267,7 @@ class Connection(ConnectionBase):
262
267
  logging.getLogger('requests_kerberos').setLevel(logging.INFO)
263
268
  logging.getLogger('urllib3').setLevel(logging.INFO)
264
269
 
265
- def _build_winrm_kwargs(self):
270
+ def _build_winrm_kwargs(self) -> None:
266
271
  # this used to be in set_options, as win_reboot needs to be able to
267
272
  # override the conn timeout, we need to be able to build the args
268
273
  # after setting individual options. This is called by _connect before
@@ -336,7 +341,7 @@ class Connection(ConnectionBase):
336
341
 
337
342
  # Until pykerberos has enough goodies to implement a rudimentary kinit/klist, simplest way is to let each connection
338
343
  # auth itself with a private CCACHE.
339
- def _kerb_auth(self, principal, password):
344
+ def _kerb_auth(self, principal: str, password: str) -> None:
340
345
  if password is None:
341
346
  password = ""
342
347
 
@@ -401,8 +406,8 @@ class Connection(ConnectionBase):
401
406
  rc = child.exitstatus
402
407
  else:
403
408
  proc_mechanism = "subprocess"
404
- password = to_bytes(password, encoding='utf-8',
405
- errors='surrogate_or_strict')
409
+ b_password = to_bytes(password, encoding='utf-8',
410
+ errors='surrogate_or_strict')
406
411
 
407
412
  display.vvvv("calling kinit with subprocess for principal %s"
408
413
  % principal)
@@ -417,7 +422,7 @@ class Connection(ConnectionBase):
417
422
  "'%s': %s" % (self._kinit_cmd, to_native(err))
418
423
  raise AnsibleConnectionFailure(err_msg)
419
424
 
420
- stdout, stderr = p.communicate(password + b'\n')
425
+ stdout, stderr = p.communicate(b_password + b'\n')
421
426
  rc = p.returncode != 0
422
427
 
423
428
  if rc != 0:
@@ -432,7 +437,7 @@ class Connection(ConnectionBase):
432
437
 
433
438
  display.vvvvv("kinit succeeded for principal %s" % principal)
434
439
 
435
- def _winrm_connect(self):
440
+ def _winrm_connect(self) -> winrm.Protocol:
436
441
  '''
437
442
  Establish a WinRM connection over HTTP/HTTPS.
438
443
  '''
@@ -491,7 +496,44 @@ class Connection(ConnectionBase):
491
496
  else:
492
497
  raise AnsibleError('No transport found for WinRM connection')
493
498
 
494
- def _winrm_send_input(self, protocol, shell_id, command_id, stdin, eof=False):
499
+ def _winrm_write_stdin(self, command_id: str, stdin_iterator: t.Iterable[tuple[bytes, bool]]) -> None:
500
+ for (data, is_last) in stdin_iterator:
501
+ for attempt in range(1, 4):
502
+ try:
503
+ self._winrm_send_input(self.protocol, self.shell_id, command_id, data, eof=is_last)
504
+
505
+ except WinRMOperationTimeoutError:
506
+ # A WSMan OperationTimeout can be received for a Send
507
+ # operation when the server is under severe load. On manual
508
+ # testing the input is still processed and it's safe to
509
+ # continue. As the calling method still tries to wait for
510
+ # the proc to end if this failed it shouldn't hurt to just
511
+ # treat this as a warning.
512
+ display.warning(
513
+ "WSMan OperationTimeout during send input, attempting to continue. "
514
+ "If this continues to occur, try increasing the connection_timeout "
515
+ "value for this host."
516
+ )
517
+ if not is_last:
518
+ time.sleep(5)
519
+
520
+ except WinRMError as e:
521
+ # Error 170 == ERROR_BUSY. This could be the result of a
522
+ # timed out Send from above still being processed on the
523
+ # server. Add a 5 second delay and try up to 3 times before
524
+ # fully giving up.
525
+ # pywinrm does not expose the internal WSMan fault details
526
+ # through an actual object but embeds it as a repr.
527
+ if attempt == 3 or "'wsmanfault_code': '170'" not in str(e):
528
+ raise
529
+
530
+ display.warning(f"WSMan send failed on attempt {attempt} as the command is busy, trying to send data again")
531
+ time.sleep(5)
532
+ continue
533
+
534
+ break
535
+
536
+ def _winrm_send_input(self, protocol: winrm.Protocol, shell_id: str, command_id: str, stdin: bytes, eof: bool = False) -> None:
495
537
  rq = {'env:Envelope': protocol._get_soap_header(
496
538
  resource_uri='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd',
497
539
  action='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send',
@@ -505,7 +547,13 @@ class Connection(ConnectionBase):
505
547
  stream['@End'] = 'true'
506
548
  protocol.send_message(xmltodict.unparse(rq))
507
549
 
508
- def _winrm_exec(self, command, args=(), from_exec=False, stdin_iterator=None):
550
+ def _winrm_exec(
551
+ self,
552
+ command: str,
553
+ args: t.Iterable[bytes] = (),
554
+ from_exec: bool = False,
555
+ stdin_iterator: t.Iterable[tuple[bytes, bool]] = None,
556
+ ) -> winrm.Response:
509
557
  if not self.protocol:
510
558
  self.protocol = self._winrm_connect()
511
559
  self._connected = True
@@ -520,8 +568,7 @@ class Connection(ConnectionBase):
520
568
 
521
569
  try:
522
570
  if stdin_iterator:
523
- for (data, is_last) in stdin_iterator:
524
- self._winrm_send_input(self.protocol, self.shell_id, command_id, data, eof=is_last)
571
+ self._winrm_write_stdin(command_id, stdin_iterator)
525
572
 
526
573
  except Exception as ex:
527
574
  display.warning("ERROR DURING WINRM SEND INPUT - attempting to recover: %s %s"
@@ -567,7 +614,7 @@ class Connection(ConnectionBase):
567
614
  if command_id:
568
615
  self.protocol.cleanup_command(self.shell_id, command_id)
569
616
 
570
- def _connect(self):
617
+ def _connect(self) -> Connection:
571
618
 
572
619
  if not HAS_WINRM:
573
620
  raise AnsibleError("winrm or requests is not installed: %s" % to_native(WINRM_IMPORT_ERR))
@@ -581,20 +628,20 @@ class Connection(ConnectionBase):
581
628
  self._connected = True
582
629
  return self
583
630
 
584
- def reset(self):
631
+ def reset(self) -> None:
585
632
  if not self._connected:
586
633
  return
587
634
  self.protocol = None
588
635
  self.shell_id = None
589
636
  self._connect()
590
637
 
591
- def _wrapper_payload_stream(self, payload, buffer_size=200000):
638
+ def _wrapper_payload_stream(self, payload: bytes, buffer_size: int = 200000) -> t.Iterable[tuple[bytes, bool]]:
592
639
  payload_bytes = to_bytes(payload)
593
640
  byte_count = len(payload_bytes)
594
641
  for i in range(0, byte_count, buffer_size):
595
642
  yield payload_bytes[i:i + buffer_size], i + buffer_size >= byte_count
596
643
 
597
- def exec_command(self, cmd, in_data=None, sudoable=True):
644
+ def exec_command(self, cmd: str, in_data: bytes | None = None, sudoable: bool = True) -> tuple[int, bytes, bytes]:
598
645
  super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
599
646
  cmd_parts = self._shell._encode_script(cmd, as_list=True, strict_mode=False, preserve_rc=False)
600
647
 
@@ -622,7 +669,7 @@ class Connection(ConnectionBase):
622
669
  return (result.status_code, result.std_out, result.std_err)
623
670
 
624
671
  # FUTURE: determine buffer size at runtime via remote winrm config?
625
- def _put_file_stdin_iterator(self, in_path, out_path, buffer_size=250000):
672
+ def _put_file_stdin_iterator(self, in_path: str, out_path: str, buffer_size: int = 250000) -> t.Iterable[tuple[bytes, bool]]:
626
673
  in_size = os.path.getsize(to_bytes(in_path, errors='surrogate_or_strict'))
627
674
  offset = 0
628
675
  with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
@@ -635,9 +682,9 @@ class Connection(ConnectionBase):
635
682
  yield b64_data, (in_file.tell() == in_size)
636
683
 
637
684
  if offset == 0: # empty file, return an empty buffer + eof to close it
638
- yield "", True
685
+ yield b"", True
639
686
 
640
- def put_file(self, in_path, out_path):
687
+ def put_file(self, in_path: str, out_path: str) -> None:
641
688
  super(Connection, self).put_file(in_path, out_path)
642
689
  out_path = self._shell._unquote(out_path)
643
690
  display.vvv('PUT "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host)
@@ -700,7 +747,7 @@ class Connection(ConnectionBase):
700
747
  if not remote_sha1 == local_sha1:
701
748
  raise AnsibleError("Remote sha1 hash {0} does not match local hash {1}".format(to_native(remote_sha1), to_native(local_sha1)))
702
749
 
703
- def fetch_file(self, in_path, out_path):
750
+ def fetch_file(self, in_path: str, out_path: str) -> None:
704
751
  super(Connection, self).fetch_file(in_path, out_path)
705
752
  in_path = self._shell._unquote(in_path)
706
753
  out_path = out_path.replace('\\', '/')
@@ -714,7 +761,7 @@ class Connection(ConnectionBase):
714
761
  try:
715
762
  script = '''
716
763
  $path = '%(path)s'
717
- If (Test-Path -Path $path -PathType Leaf)
764
+ If (Test-Path -LiteralPath $path -PathType Leaf)
718
765
  {
719
766
  $buffer_size = %(buffer_size)d
720
767
  $offset = %(offset)d
@@ -729,7 +776,7 @@ class Connection(ConnectionBase):
729
776
  }
730
777
  $stream.Close() > $null
731
778
  }
732
- ElseIf (Test-Path -Path $path -PathType Container)
779
+ ElseIf (Test-Path -LiteralPath $path -PathType Container)
733
780
  {
734
781
  Write-Host "[DIR]";
735
782
  }
@@ -767,7 +814,7 @@ class Connection(ConnectionBase):
767
814
  if out_file:
768
815
  out_file.close()
769
816
 
770
- def close(self):
817
+ def close(self) -> None:
771
818
  if self.protocol and self.shell_id:
772
819
  display.vvvvv('WINRM CLOSE SHELL: %s' % self.shell_id, host=self._winrm_host)
773
820
  self.protocol.close_shell(self.shell_id)