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
@@ -11,15 +11,16 @@ import os
11
11
  import os.path
12
12
  import re
13
13
  import tempfile
14
+ import typing as t
14
15
 
15
16
  from ansible import constants as C
16
17
  from ansible.errors import AnsibleFileNotFound, AnsibleParserError
17
18
  from ansible.module_utils.basic import is_executable
18
19
  from ansible.module_utils.six import binary_type, text_type
19
- from ansible.module_utils._text import to_bytes, to_native, to_text
20
+ from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
20
21
  from ansible.parsing.quoting import unquote
21
22
  from ansible.parsing.utils.yaml import from_yaml
22
- from ansible.parsing.vault import VaultLib, b_HEADER, is_encrypted, is_encrypted_file, parse_vaulttext_envelope
23
+ from ansible.parsing.vault import VaultLib, b_HEADER, is_encrypted, is_encrypted_file, parse_vaulttext_envelope, PromptVaultSecret
23
24
  from ansible.utils.path import unfrackpath
24
25
  from ansible.utils.display import Display
25
26
 
@@ -45,7 +46,7 @@ class DataLoader:
45
46
  Usage:
46
47
 
47
48
  dl = DataLoader()
48
- # optionally: dl.set_vault_password('foo')
49
+ # optionally: dl.set_vault_secrets([('default', ansible.parsing.vault.PrompVaultSecret(...),)])
49
50
  ds = dl.load('...')
50
51
  ds = dl.load_from_file('/path/to/file')
51
52
  '''
@@ -66,20 +67,19 @@ class DataLoader:
66
67
  # initialize the vault stuff with an empty password
67
68
  # TODO: replace with a ref to something that can get the password
68
69
  # a creds/auth provider
69
- # self.set_vault_password(None)
70
70
  self._vaults = {}
71
71
  self._vault = VaultLib()
72
72
  self.set_vault_secrets(None)
73
73
 
74
74
  # TODO: since we can query vault_secrets late, we could provide this to DataLoader init
75
- def set_vault_secrets(self, vault_secrets):
75
+ def set_vault_secrets(self, vault_secrets: list[tuple[str, PromptVaultSecret]] | None) -> None:
76
76
  self._vault.secrets = vault_secrets
77
77
 
78
- def load(self, data, file_name='<string>', show_content=True, json_only=False):
78
+ def load(self, data: str, file_name: str = '<string>', show_content: bool = True, json_only: bool = False) -> t.Any:
79
79
  '''Backwards compat for now'''
80
80
  return from_yaml(data, file_name, show_content, self._vault.secrets, json_only=json_only)
81
81
 
82
- def load_from_file(self, file_name, cache=True, unsafe=False, json_only=False):
82
+ def load_from_file(self, file_name: str, cache: bool = True, unsafe: bool = False, json_only: bool = False) -> t.Any:
83
83
  ''' Loads data from a file, which can contain either JSON or YAML. '''
84
84
 
85
85
  file_name = self.path_dwim(file_name)
@@ -105,28 +105,28 @@ class DataLoader:
105
105
  # return a deep copy here, so the cache is not affected
106
106
  return copy.deepcopy(parsed_data)
107
107
 
108
- def path_exists(self, path):
108
+ def path_exists(self, path: str) -> bool:
109
109
  path = self.path_dwim(path)
110
110
  return os.path.exists(to_bytes(path, errors='surrogate_or_strict'))
111
111
 
112
- def is_file(self, path):
112
+ def is_file(self, path: str) -> bool:
113
113
  path = self.path_dwim(path)
114
114
  return os.path.isfile(to_bytes(path, errors='surrogate_or_strict')) or path == os.devnull
115
115
 
116
- def is_directory(self, path):
116
+ def is_directory(self, path: str) -> bool:
117
117
  path = self.path_dwim(path)
118
118
  return os.path.isdir(to_bytes(path, errors='surrogate_or_strict'))
119
119
 
120
- def list_directory(self, path):
120
+ def list_directory(self, path: str) -> list[str]:
121
121
  path = self.path_dwim(path)
122
122
  return os.listdir(path)
123
123
 
124
- def is_executable(self, path):
124
+ def is_executable(self, path: str) -> bool:
125
125
  '''is the given path executable?'''
126
126
  path = self.path_dwim(path)
127
127
  return is_executable(path)
128
128
 
129
- def _decrypt_if_vault_data(self, b_vault_data, b_file_name=None):
129
+ def _decrypt_if_vault_data(self, b_vault_data: bytes, b_file_name: bytes | None = None) -> tuple[bytes, bool]:
130
130
  '''Decrypt b_vault_data if encrypted and return b_data and the show_content flag'''
131
131
 
132
132
  if not is_encrypted(b_vault_data):
@@ -139,7 +139,7 @@ class DataLoader:
139
139
  show_content = False
140
140
  return b_data, show_content
141
141
 
142
- def _get_file_contents(self, file_name):
142
+ def _get_file_contents(self, file_name: str) -> tuple[bytes, bool]:
143
143
  '''
144
144
  Reads the file contents from the given file name
145
145
 
@@ -168,17 +168,17 @@ class DataLoader:
168
168
  except (IOError, OSError) as e:
169
169
  raise AnsibleParserError("an error occurred while trying to read the file '%s': %s" % (file_name, to_native(e)), orig_exc=e)
170
170
 
171
- def get_basedir(self):
171
+ def get_basedir(self) -> str:
172
172
  ''' returns the current basedir '''
173
173
  return self._basedir
174
174
 
175
- def set_basedir(self, basedir):
175
+ def set_basedir(self, basedir: str) -> None:
176
176
  ''' sets the base directory, used to find files when a relative path is given '''
177
177
 
178
178
  if basedir is not None:
179
179
  self._basedir = to_text(basedir)
180
180
 
181
- def path_dwim(self, given):
181
+ def path_dwim(self, given: str) -> str:
182
182
  '''
183
183
  make relative paths work like folks expect.
184
184
  '''
@@ -194,7 +194,7 @@ class DataLoader:
194
194
 
195
195
  return unfrackpath(path, follow=False)
196
196
 
197
- def _is_role(self, path):
197
+ def _is_role(self, path: str) -> bool:
198
198
  ''' imperfect role detection, roles are still valid w/o tasks|meta/main.yml|yaml|etc '''
199
199
 
200
200
  b_path = to_bytes(path, errors='surrogate_or_strict')
@@ -228,7 +228,7 @@ class DataLoader:
228
228
 
229
229
  return False
230
230
 
231
- def path_dwim_relative(self, path, dirname, source, is_role=False):
231
+ def path_dwim_relative(self, path: str, dirname: str, source: str, is_role: bool = False) -> str:
232
232
  '''
233
233
  find one file in either a role or playbook dir with or without
234
234
  explicitly named dirname subdirs
@@ -283,7 +283,7 @@ class DataLoader:
283
283
 
284
284
  return candidate
285
285
 
286
- def path_dwim_relative_stack(self, paths, dirname, source, is_role=False):
286
+ def path_dwim_relative_stack(self, paths: list[str], dirname: str, source: str, is_role: bool = False) -> str:
287
287
  '''
288
288
  find one file in first path in stack taking roles into account and adding play basedir as fallback
289
289
 
@@ -342,7 +342,7 @@ class DataLoader:
342
342
 
343
343
  return result
344
344
 
345
- def _create_content_tempfile(self, content):
345
+ def _create_content_tempfile(self, content: str | bytes) -> str:
346
346
  ''' Create a tempfile containing defined content '''
347
347
  fd, content_tempfile = tempfile.mkstemp(dir=C.DEFAULT_LOCAL_TMP)
348
348
  f = os.fdopen(fd, 'wb')
@@ -356,7 +356,7 @@ class DataLoader:
356
356
  f.close()
357
357
  return content_tempfile
358
358
 
359
- def get_real_file(self, file_path, decrypt=True):
359
+ def get_real_file(self, file_path: str, decrypt: bool = True) -> str:
360
360
  """
361
361
  If the file is vault encrypted return a path to a temporary decrypted file
362
362
  If the file is not encrypted then the path is returned
@@ -396,7 +396,7 @@ class DataLoader:
396
396
  except (IOError, OSError) as e:
397
397
  raise AnsibleParserError("an error occurred while trying to read the file '%s': %s" % (to_native(real_path), to_native(e)), orig_exc=e)
398
398
 
399
- def cleanup_tmp_file(self, file_path):
399
+ def cleanup_tmp_file(self, file_path: str) -> None:
400
400
  """
401
401
  Removes any temporary files created from a previous call to
402
402
  get_real_file. file_path must be the path returned from a
@@ -406,7 +406,7 @@ class DataLoader:
406
406
  os.unlink(file_path)
407
407
  self._tempfiles.remove(file_path)
408
408
 
409
- def cleanup_all_tmp_files(self):
409
+ def cleanup_all_tmp_files(self) -> None:
410
410
  """
411
411
  Removes all temporary files that DataLoader has created
412
412
  NOTE: not thread safe, forks also need special handling see __init__ for details.
@@ -417,7 +417,7 @@ class DataLoader:
417
417
  except Exception as e:
418
418
  display.warning("Unable to cleanup temp files: %s" % to_text(e))
419
419
 
420
- def find_vars_files(self, path, name, extensions=None, allow_dir=True):
420
+ def find_vars_files(self, path: str, name: str, extensions: list[str] | None = None, allow_dir: bool = True) -> list[str]:
421
421
  """
422
422
  Find vars files in a given path with specified name. This will find
423
423
  files in a dir named <name>/ or a file called <name> ending in known
@@ -447,11 +447,11 @@ class DataLoader:
447
447
  else:
448
448
  continue
449
449
  else:
450
- found.append(full_path)
450
+ found.append(to_text(full_path))
451
451
  break
452
452
  return found
453
453
 
454
- def _get_dir_vars_files(self, path, extensions):
454
+ def _get_dir_vars_files(self, path: str, extensions: list[str]) -> list[str]:
455
455
  found = []
456
456
  for spath in sorted(self.list_directory(path)):
457
457
  if not spath.startswith(u'.') and not spath.endswith(u'~'): # skip hidden and backups
@@ -22,7 +22,7 @@ __metaclass__ = type
22
22
  import ansible.constants as C
23
23
  from ansible.errors import AnsibleParserError, AnsibleError, AnsibleAssertionError
24
24
  from ansible.module_utils.six import string_types
25
- from ansible.module_utils._text import to_text
25
+ from ansible.module_utils.common.text.converters import to_text
26
26
  from ansible.parsing.splitter import parse_kv, split_args
27
27
  from ansible.plugins.loader import module_loader, action_loader
28
28
  from ansible.template import Templar
@@ -9,7 +9,7 @@ import tokenize
9
9
 
10
10
  from ansible import constants as C
11
11
  from ansible.errors import AnsibleError, AnsibleParserError
12
- from ansible.module_utils._text import to_text, to_native
12
+ from ansible.module_utils.common.text.converters import to_text, to_native
13
13
  from ansible.parsing.yaml.loader import AnsibleLoader
14
14
  from ansible.utils.display import Display
15
15
 
@@ -151,10 +151,10 @@ def read_docstring_from_python_file(filename, verbose=True, ignore_errors=True):
151
151
  if theid == 'EXAMPLES':
152
152
  # examples 'can' be yaml, but even if so, we dont want to parse as such here
153
153
  # as it can create undesired 'objects' that don't display well as docs.
154
- data[varkey] = to_text(child.value.s)
154
+ data[varkey] = to_text(child.value.value)
155
155
  else:
156
156
  # string should be yaml if already not a dict
157
- data[varkey] = AnsibleLoader(child.value.s, file_name=filename).get_single_data()
157
+ data[varkey] = AnsibleLoader(child.value.value, file_name=filename).get_single_data()
158
158
 
159
159
  display.debug('Documentation assigned: %s' % varkey)
160
160
 
@@ -23,7 +23,7 @@ import codecs
23
23
  import re
24
24
 
25
25
  from ansible.errors import AnsibleParserError
26
- from ansible.module_utils._text import to_text
26
+ from ansible.module_utils.common.text.converters import to_text
27
27
  from ansible.parsing.quoting import unquote
28
28
 
29
29
 
@@ -58,15 +58,7 @@ def parse_kv(args, check_raw=False):
58
58
 
59
59
  options = {}
60
60
  if args is not None:
61
- try:
62
- vargs = split_args(args)
63
- except IndexError as e:
64
- raise AnsibleParserError("Unable to parse argument string", orig_exc=e)
65
- except ValueError as ve:
66
- if 'no closing quotation' in str(ve).lower():
67
- raise AnsibleParserError("error parsing argument string, try quoting the entire line.", orig_exc=ve)
68
- else:
69
- raise
61
+ vargs = split_args(args)
70
62
 
71
63
  raw_params = []
72
64
  for orig_x in vargs:
@@ -168,6 +160,9 @@ def split_args(args):
168
160
  how Ansible needs to use it.
169
161
  '''
170
162
 
163
+ if not args:
164
+ return []
165
+
171
166
  # the list of params parsed out of the arg string
172
167
  # this is going to be the result value when we are done
173
168
  params = []
@@ -204,6 +199,10 @@ def split_args(args):
204
199
  # Empty entries means we have subsequent spaces
205
200
  # We want to hold onto them so we can reconstruct them later
206
201
  if len(token) == 0 and idx != 0:
202
+ # Make sure there is a params item to store result in.
203
+ if not params:
204
+ params.append('')
205
+
207
206
  params[-1] += ' '
208
207
  continue
209
208
 
@@ -235,13 +234,11 @@ def split_args(args):
235
234
  elif print_depth or block_depth or comment_depth or inside_quotes or was_inside_quotes:
236
235
  if idx == 0 and was_inside_quotes:
237
236
  params[-1] = "%s%s" % (params[-1], token)
238
- elif len(tokens) > 1:
237
+ else:
239
238
  spacer = ''
240
239
  if idx > 0:
241
240
  spacer = ' '
242
241
  params[-1] = "%s%s%s" % (params[-1], spacer, token)
243
- else:
244
- params[-1] = "%s\n%s" % (params[-1], token)
245
242
  appended = True
246
243
 
247
244
  # if the number of paired block tags is not the same, the depth has changed, so we calculate that here
@@ -273,10 +270,11 @@ def split_args(args):
273
270
  # one item (meaning we split on newlines), add a newline back here
274
271
  # to preserve the original structure
275
272
  if len(items) > 1 and itemidx != len(items) - 1 and not line_continuation:
276
- params[-1] += '\n'
273
+ # Make sure there is a params item to store result in.
274
+ if not params:
275
+ params.append('')
277
276
 
278
- # always clear the line continuation flag
279
- line_continuation = False
277
+ params[-1] += '\n'
280
278
 
281
279
  # If we're done and things are not at zero depth or we're still inside quotes,
282
280
  # raise an error to indicate that the args were unbalanced
@@ -13,7 +13,7 @@ from yaml import YAMLError
13
13
 
14
14
  from ansible.errors import AnsibleParserError
15
15
  from ansible.errors.yaml_strings import YAML_SYNTAX_ERROR
16
- from ansible.module_utils._text import to_native
16
+ from ansible.module_utils.common.text.converters import to_native
17
17
  from ansible.parsing.yaml.loader import AnsibleLoader
18
18
  from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject
19
19
  from ansible.parsing.ajson import AnsibleJSONDecoder
@@ -55,7 +55,7 @@ except ImportError:
55
55
  from ansible.errors import AnsibleError, AnsibleAssertionError
56
56
  from ansible import constants as C
57
57
  from ansible.module_utils.six import binary_type
58
- from ansible.module_utils._text import to_bytes, to_text, to_native
58
+ from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
59
59
  from ansible.utils.display import Display
60
60
  from ansible.utils.path import makedirs_safe, unfrackpath
61
61
 
@@ -787,13 +787,13 @@ class VaultEditor:
787
787
 
788
788
  passes = 3
789
789
  with open(tmp_path, "wb") as fh:
790
- for _ in range(passes):
790
+ for dummy in range(passes):
791
791
  fh.seek(0, 0)
792
792
  # get a random chunk of data, each pass with other length
793
793
  chunk_len = random.randint(max_chunk_len // 2, max_chunk_len)
794
794
  data = os.urandom(chunk_len)
795
795
 
796
- for _ in range(0, file_len // chunk_len):
796
+ for dummy in range(0, file_len // chunk_len):
797
797
  fh.write(data)
798
798
  fh.write(data[:file_len % chunk_len])
799
799
 
@@ -1044,10 +1044,10 @@ class VaultEditor:
1044
1044
  since in the plaintext case, the original contents can be of any text encoding
1045
1045
  or arbitrary binary data.
1046
1046
 
1047
- When used to write the result of vault encryption, the val of the 'data' arg
1048
- should be a utf-8 encoded byte string and not a text typ and not a text type..
1047
+ When used to write the result of vault encryption, the value of the 'data' arg
1048
+ should be a utf-8 encoded byte string and not a text type.
1049
1049
 
1050
- When used to write the result of vault decryption, the val of the 'data' arg
1050
+ When used to write the result of vault decryption, the value of the 'data' arg
1051
1051
  should be a byte string and not a text type.
1052
1052
 
1053
1053
  :arg data: the byte string (bytes) data
@@ -1077,6 +1077,8 @@ class VaultEditor:
1077
1077
  output = getattr(sys.stdout, 'buffer', sys.stdout)
1078
1078
  output.write(b_file_data)
1079
1079
  else:
1080
+ if not os.access(os.path.dirname(thefile), os.W_OK):
1081
+ raise AnsibleError("Destination '%s' not writable" % (os.path.dirname(thefile)))
1080
1082
  # file names are insecure and prone to race conditions, so remove and create securely
1081
1083
  if os.path.isfile(thefile):
1082
1084
  if shred:
@@ -23,7 +23,7 @@ from yaml.constructor import SafeConstructor, ConstructorError
23
23
  from yaml.nodes import MappingNode
24
24
 
25
25
  from ansible import constants as C
26
- from ansible.module_utils._text import to_bytes, to_native
26
+ from ansible.module_utils.common.text.converters import to_bytes, to_native
27
27
  from ansible.parsing.yaml.objects import AnsibleMapping, AnsibleSequence, AnsibleUnicode, AnsibleVaultEncryptedUnicode
28
28
  from ansible.parsing.vault import VaultLib
29
29
  from ansible.utils.display import Display
@@ -24,7 +24,7 @@ import sys as _sys
24
24
  from collections.abc import Sequence
25
25
 
26
26
  from ansible.module_utils.six import text_type
27
- from ansible.module_utils._text import to_bytes, to_text, to_native
27
+ from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
28
28
 
29
29
 
30
30
  class AnsibleBaseYAMLObject(object):
@@ -23,7 +23,7 @@ import os
23
23
 
24
24
  from ansible import constants as C
25
25
  from ansible.errors import AnsibleParserError
26
- from ansible.module_utils._text import to_text, to_native
26
+ from ansible.module_utils.common.text.converters import to_text, to_native
27
27
  from ansible.playbook.play import Play
28
28
  from ansible.playbook.playbook_include import PlaybookInclude
29
29
  from ansible.plugins.loader import add_all_plugin_dirs
ansible/playbook/base.py CHANGED
@@ -19,7 +19,7 @@ from ansible import context
19
19
  from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleAssertionError
20
20
  from ansible.module_utils.six import string_types
21
21
  from ansible.module_utils.parsing.convert_bool import boolean
22
- from ansible.module_utils._text import to_text, to_native
22
+ from ansible.module_utils.common.text.converters import to_text, to_native
23
23
  from ansible.parsing.dataloader import DataLoader
24
24
  from ansible.playbook.attribute import Attribute, FieldAttribute, ConnectionFieldAttribute, NonInheritableFieldAttribute
25
25
  from ansible.plugins.loader import module_loader, action_loader
@@ -637,7 +637,7 @@ class FieldAttributeBase:
637
637
  else:
638
638
  combined = value + new_value
639
639
 
640
- return [i for i, _ in itertools.groupby(combined) if i is not None]
640
+ return [i for i, dummy in itertools.groupby(combined) if i is not None]
641
641
 
642
642
  def dump_attrs(self):
643
643
  '''
ansible/playbook/block.py CHANGED
@@ -377,7 +377,6 @@ class Block(Base, Conditional, CollectionSearch, Taggable, Notifiable, Delegatab
377
377
  if filtered_block.has_tasks():
378
378
  tmp_list.append(filtered_block)
379
379
  elif ((task.action in C._ACTION_META and task.implicit) or
380
- (task.action in C._ACTION_INCLUDE and task.evaluate_tags([], self._play.skip_tags, all_vars=all_vars)) or
381
380
  task.evaluate_tags(self._play.only_tags, self._play.skip_tags, all_vars=all_vars)):
382
381
  tmp_list.append(task)
383
382
  return tmp_list
@@ -19,15 +19,10 @@
19
19
  from __future__ import (absolute_import, division, print_function)
20
20
  __metaclass__ = type
21
21
 
22
- import ast
23
22
  import typing as t
24
23
 
25
- from jinja2.compiler import generate
26
- from jinja2.exceptions import UndefinedError
27
-
28
24
  from ansible.errors import AnsibleError, AnsibleUndefinedVariable
29
- from ansible.module_utils.six import text_type
30
- from ansible.module_utils._text import to_native
25
+ from ansible.module_utils.common.text.converters import to_native
31
26
  from ansible.playbook.attribute import FieldAttribute
32
27
  from ansible.template import Templar
33
28
  from ansible.utils.display import Display
@@ -36,7 +31,6 @@ display = Display()
36
31
 
37
32
 
38
33
  class Conditional:
39
-
40
34
  '''
41
35
  This is a mix-in class, to be used with Base to allow the object
42
36
  to be run conditionally when a condition is met or skipped.
@@ -53,7 +47,7 @@ class Conditional:
53
47
  raise AnsibleError("a loader must be specified when using Conditional() directly")
54
48
  else:
55
49
  self._loader = loader
56
- super(Conditional, self).__init__()
50
+ super().__init__()
57
51
 
58
52
  def _validate_when(self, attr, name, value):
59
53
  if not isinstance(value, list):
@@ -67,123 +61,55 @@ class Conditional:
67
61
  return self.evaluate_conditional_with_result(templar, all_vars)[0]
68
62
 
69
63
  def evaluate_conditional_with_result(self, templar: Templar, all_vars: dict[str, t.Any]) -> tuple[bool, t.Optional[str]]:
70
- """
71
- Loops through the conditionals set on this object, returning
64
+ """Loops through the conditionals set on this object, returning
72
65
  False if any of them evaluate as such as well as the condition
73
66
  that was false.
74
67
  """
75
-
76
- # since this is a mix-in, it may not have an underlying datastructure
77
- # associated with it, so we pull it out now in case we need it for
78
- # error reporting below
79
- ds = None
80
- if hasattr(self, '_ds'):
81
- ds = getattr(self, '_ds')
82
-
83
- result = True
84
- false_condition: t.Optional[str] = None
85
- try:
86
- for conditional in self.when:
87
-
88
- # do evaluation
89
- if conditional is None or conditional == '':
90
- res = True
91
- elif isinstance(conditional, bool):
92
- res = conditional
93
- else:
68
+ for conditional in self.when:
69
+ if conditional is None or conditional == "":
70
+ res = True
71
+ elif isinstance(conditional, bool):
72
+ res = conditional
73
+ else:
74
+ try:
94
75
  res = self._check_conditional(conditional, templar, all_vars)
76
+ except AnsibleError as e:
77
+ raise AnsibleError(
78
+ "The conditional check '%s' failed. The error was: %s" % (to_native(conditional), to_native(e)),
79
+ obj=getattr(self, '_ds', None)
80
+ )
95
81
 
96
- # only update if still true, preserve false
97
- if result:
98
- result = res
99
-
100
- display.debug("Evaluated conditional (%s): %s" % (conditional, res))
101
- if not result:
102
- false_condition = conditional
103
- break
82
+ display.debug("Evaluated conditional (%s): %s" % (conditional, res))
83
+ if not res:
84
+ return res, conditional
104
85
 
105
- except Exception as e:
106
- raise AnsibleError("The conditional check '%s' failed. The error was: %s" % (to_native(conditional), to_native(e)), obj=ds)
107
-
108
- return result, false_condition
109
-
110
- def _check_conditional(self, conditional, templar, all_vars):
111
- '''
112
- This method does the low-level evaluation of each conditional
113
- set on this object, using jinja2 to wrap the conditionals for
114
- evaluation.
115
- '''
86
+ return True, None
116
87
 
88
+ def _check_conditional(self, conditional: str, templar: Templar, all_vars: dict[str, t.Any]) -> bool:
117
89
  original = conditional
118
-
119
- if templar.is_template(conditional):
120
- display.warning('conditional statements should not include jinja2 '
121
- 'templating delimiters such as {{ }} or {%% %%}. '
122
- 'Found: %s' % conditional)
123
-
124
- # make sure the templar is using the variables specified with this method
125
90
  templar.available_variables = all_vars
126
-
127
91
  try:
128
- # if the conditional is "unsafe", disable lookups
92
+ if templar.is_template(conditional):
93
+ display.warning(
94
+ "conditional statements should not include jinja2 "
95
+ "templating delimiters such as {{ }} or {%% %%}. "
96
+ "Found: %s" % conditional
97
+ )
98
+ conditional = templar.template(conditional)
99
+ if isinstance(conditional, bool):
100
+ return conditional
101
+ elif conditional == "":
102
+ return False
103
+
104
+ # If the result of the first-pass template render (to resolve inline templates) is marked unsafe,
105
+ # explicitly disable lookups on the final pass to prevent evaluation of untrusted content in the
106
+ # constructed template.
129
107
  disable_lookups = hasattr(conditional, '__UNSAFE__')
130
- conditional = templar.template(conditional, disable_lookups=disable_lookups)
131
-
132
- if not isinstance(conditional, text_type) or conditional == "":
133
- return conditional
134
-
135
- # update the lookups flag, as the string returned above may now be unsafe
136
- # and we don't want future templating calls to do unsafe things
137
- disable_lookups |= hasattr(conditional, '__UNSAFE__')
138
-
139
- # First, we do some low-level jinja2 parsing involving the AST format of the
140
- # statement to ensure we don't do anything unsafe (using the disable_lookup flag above)
141
- class CleansingNodeVisitor(ast.NodeVisitor):
142
- def generic_visit(self, node, inside_call=False, inside_yield=False):
143
- if isinstance(node, ast.Call):
144
- inside_call = True
145
- elif isinstance(node, ast.Yield):
146
- inside_yield = True
147
- elif isinstance(node, ast.Str):
148
- if disable_lookups:
149
- if inside_call and node.s.startswith("__"):
150
- # calling things with a dunder is generally bad at this point...
151
- raise AnsibleError(
152
- "Invalid access found in the conditional: '%s'" % conditional
153
- )
154
- elif inside_yield:
155
- # we're inside a yield, so recursively parse and traverse the AST
156
- # of the result to catch forbidden syntax from executing
157
- parsed = ast.parse(node.s, mode='exec')
158
- cnv = CleansingNodeVisitor()
159
- cnv.visit(parsed)
160
- # iterate over all child nodes
161
- for child_node in ast.iter_child_nodes(node):
162
- self.generic_visit(
163
- child_node,
164
- inside_call=inside_call,
165
- inside_yield=inside_yield
166
- )
167
- try:
168
- res = templar.environment.parse(conditional, None, None)
169
- res = generate(res, templar.environment, None, None)
170
- parsed = ast.parse(res, mode='exec')
171
-
172
- cnv = CleansingNodeVisitor()
173
- cnv.visit(parsed)
174
- except Exception as e:
175
- raise AnsibleError("Invalid conditional detected: %s" % to_native(e))
176
-
177
- # and finally we generate and template the presented string and look at the resulting string
108
+
178
109
  # NOTE The spaces around True and False are intentional to short-circuit literal_eval for
179
110
  # jinja2_native=False and avoid its expensive calls.
180
- presented = "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional
181
- val = templar.template(presented, disable_lookups=disable_lookups).strip()
182
- if val == "True":
183
- return True
184
- elif val == "False":
185
- return False
186
- else:
187
- raise AnsibleError("unable to evaluate conditional: %s" % original)
188
- except (AnsibleUndefinedVariable, UndefinedError) as e:
111
+ return templar.template(
112
+ "{%% if %s %%} True {%% else %%} False {%% endif %%}" % conditional,
113
+ disable_lookups=disable_lookups).strip() == "True"
114
+ except AnsibleUndefinedVariable as e:
189
115
  raise AnsibleUndefinedVariable("error while evaluating conditional (%s): %s" % (original, e))