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
@@ -10,12 +10,21 @@ from ....config import (
10
10
 
11
11
  from ....docker_util import (
12
12
  docker_cp_to,
13
+ docker_exec,
13
14
  )
14
15
 
15
16
  from ....containers import (
16
17
  run_support_container,
17
18
  )
18
19
 
20
+ from ....encoding import (
21
+ to_text,
22
+ )
23
+
24
+ from ....util import (
25
+ display,
26
+ )
27
+
19
28
  from . import (
20
29
  CloudEnvironment,
21
30
  CloudEnvironmentConfig,
@@ -23,53 +32,59 @@ from . import (
23
32
  )
24
33
 
25
34
 
26
- # We add BasicAuthentication, to make the tasks that deal with
27
- # direct API access easier to deal with across galaxy_ng and pulp
28
- SETTINGS = b'''
29
- CONTENT_ORIGIN = 'http://ansible-ci-pulp:80'
30
- ANSIBLE_API_HOSTNAME = 'http://ansible-ci-pulp:80'
31
- ANSIBLE_CONTENT_HOSTNAME = 'http://ansible-ci-pulp:80/pulp/content'
32
- TOKEN_AUTH_DISABLED = True
33
- GALAXY_REQUIRE_CONTENT_APPROVAL = False
34
- GALAXY_AUTHENTICATION_CLASSES = [
35
- "rest_framework.authentication.SessionAuthentication",
36
- "rest_framework.authentication.TokenAuthentication",
37
- "rest_framework.authentication.BasicAuthentication",
38
- ]
39
- '''
40
-
41
- SET_ADMIN_PASSWORD = b'''#!/usr/bin/execlineb -S0
42
- foreground {
43
- redirfd -w 1 /dev/null
44
- redirfd -w 2 /dev/null
45
- export DJANGO_SETTINGS_MODULE pulpcore.app.settings
46
- export PULP_CONTENT_ORIGIN localhost
47
- s6-setuidgid postgres
48
- if { /usr/local/bin/django-admin reset-admin-password --password password }
49
- if { /usr/local/bin/pulpcore-manager create-group system:partner-engineers --users admin }
50
- }
51
- '''
52
-
53
- # There are 2 overrides here:
54
- # 1. Change the gunicorn bind address from 127.0.0.1 to 0.0.0.0 now that Galaxy NG does not allow us to access the
55
- # Pulp API through it.
56
- # 2. Grant access allowing us to DELETE a namespace in Galaxy NG. This is as CI deletes and recreates repos and
57
- # distributions in Pulp which now breaks the namespace in Galaxy NG. Recreating it is the "simple" fix to get it
58
- # working again.
59
- # These may not be needed in the future, especially if 1 becomes configurable by an env var but for now they must be
60
- # done.
61
- OVERRIDES = b'''#!/usr/bin/execlineb -S0
62
- foreground {
63
- sed -i "0,/\\"127.0.0.1:24817\\"/s//\\"0.0.0.0:24817\\"/" /etc/services.d/pulpcore-api/run
35
+ GALAXY_HOST_NAME = 'galaxy-pulp'
36
+ SETTINGS = {
37
+ 'PULP_CONTENT_ORIGIN': f'http://{GALAXY_HOST_NAME}',
38
+ 'PULP_ANSIBLE_API_HOSTNAME': f'http://{GALAXY_HOST_NAME}',
39
+ 'PULP_GALAXY_API_PATH_PREFIX': '/api/galaxy/',
40
+ # These paths are unique to the container image which has an nginx location for /pulp/content to route
41
+ # requests to the content backend
42
+ 'PULP_ANSIBLE_CONTENT_HOSTNAME': f'http://{GALAXY_HOST_NAME}/pulp/content/api/galaxy/v3/artifacts/collections/',
43
+ 'PULP_CONTENT_PATH_PREFIX': '/pulp/content/api/galaxy/v3/artifacts/collections/',
44
+ 'PULP_GALAXY_AUTHENTICATION_CLASSES': [
45
+ 'rest_framework.authentication.SessionAuthentication',
46
+ 'rest_framework.authentication.TokenAuthentication',
47
+ 'rest_framework.authentication.BasicAuthentication',
48
+ 'django.contrib.auth.backends.ModelBackend',
49
+ ],
50
+ # This should probably be false see https://issues.redhat.com/browse/AAH-2328
51
+ 'PULP_GALAXY_REQUIRE_CONTENT_APPROVAL': 'true',
52
+ 'PULP_GALAXY_DEPLOYMENT_MODE': 'standalone',
53
+ 'PULP_GALAXY_AUTO_SIGN_COLLECTIONS': 'false',
54
+ 'PULP_GALAXY_COLLECTION_SIGNING_SERVICE': 'ansible-default',
55
+ 'PULP_RH_ENTITLEMENT_REQUIRED': 'insights',
56
+ 'PULP_TOKEN_AUTH_DISABLED': 'false',
57
+ 'PULP_TOKEN_SERVER': f'http://{GALAXY_HOST_NAME}/token/',
58
+ 'PULP_TOKEN_SIGNATURE_ALGORITHM': 'ES256',
59
+ 'PULP_PUBLIC_KEY_PATH': '/src/galaxy_ng/dev/common/container_auth_public_key.pem',
60
+ 'PULP_PRIVATE_KEY_PATH': '/src/galaxy_ng/dev/common/container_auth_private_key.pem',
61
+ 'PULP_ANALYTICS': 'false',
62
+ 'PULP_GALAXY_ENABLE_UNAUTHENTICATED_COLLECTION_ACCESS': 'true',
63
+ 'PULP_GALAXY_ENABLE_UNAUTHENTICATED_COLLECTION_DOWNLOAD': 'true',
64
+ 'PULP_GALAXY_ENABLE_LEGACY_ROLES': 'true',
65
+ 'PULP_GALAXY_FEATURE_FLAGS__execution_environments': 'false',
66
+ 'PULP_SOCIAL_AUTH_LOGIN_REDIRECT_URL': '/',
67
+ 'PULP_GALAXY_FEATURE_FLAGS__ai_deny_index': 'true',
68
+ 'PULP_DEFAULT_ADMIN_PASSWORD': 'password'
64
69
  }
65
70
 
66
- # This sed calls changes the first occurrence to "allow" which is conveniently the delete operation for a namespace.
67
- # https://github.com/ansible/galaxy_ng/blob/master/galaxy_ng/app/access_control/statements/standalone.py#L9-L11.
68
- backtick NG_PREFIX { python -c "import galaxy_ng; print(galaxy_ng.__path__[0], end='')" }
69
- importas ng_prefix NG_PREFIX
70
- foreground {
71
- sed -i "0,/\\"effect\\": \\"deny\\"/s//\\"effect\\": \\"allow\\"/" ${ng_prefix}/app/access_control/statements/standalone.py
72
- }'''
71
+
72
+ GALAXY_IMPORTER = b'''
73
+ [galaxy-importer]
74
+ ansible_local_tmp=~/.ansible/tmp
75
+ ansible_test_local_image=false
76
+ check_required_tags=false
77
+ check_runtime_yaml=false
78
+ check_changelog=false
79
+ infra_osd=false
80
+ local_image_docker=false
81
+ log_level_main=INFO
82
+ require_v1_or_greater=false
83
+ run_ansible_doc=false
84
+ run_ansible_lint=false
85
+ run_ansible_test=false
86
+ run_flake8=false
87
+ '''.strip()
73
88
 
74
89
 
75
90
  class GalaxyProvider(CloudProvider):
@@ -81,13 +96,9 @@ class GalaxyProvider(CloudProvider):
81
96
  def __init__(self, args: IntegrationConfig) -> None:
82
97
  super().__init__(args)
83
98
 
84
- # Cannot use the latest container image as either galaxy_ng 4.2.0rc2 or pulp 0.5.0 has sporatic issues with
85
- # dropping published collections in CI. Try running the tests multiple times when updating. Will also need to
86
- # comment out the cache tests in 'test/integration/targets/ansible-galaxy-collection/tasks/install.yml' when
87
- # the newer update is available.
88
- self.pulp = os.environ.get(
99
+ self.image = os.environ.get(
89
100
  'ANSIBLE_PULP_CONTAINER',
90
- 'quay.io/ansible/pulp-galaxy-ng:b79a7be64eff'
101
+ 'quay.io/pulp/galaxy:4.7.1'
91
102
  )
92
103
 
93
104
  self.uses_docker = True
@@ -96,48 +107,46 @@ class GalaxyProvider(CloudProvider):
96
107
  """Setup cloud resource before delegation and reg cleanup callback."""
97
108
  super().setup()
98
109
 
99
- galaxy_port = 80
100
- pulp_host = 'ansible-ci-pulp'
101
- pulp_port = 24817
102
-
103
- ports = [
104
- galaxy_port,
105
- pulp_port,
106
- ]
107
-
108
- # Create the container, don't run it, we need to inject configs before it starts
109
- descriptor = run_support_container(
110
- self.args,
111
- self.platform,
112
- self.pulp,
113
- pulp_host,
114
- ports,
115
- start=False,
116
- allow_existing=True,
117
- )
110
+ with tempfile.NamedTemporaryFile(mode='w+') as env_fd:
111
+ settings = '\n'.join(
112
+ f'{key}={value}' for key, value in SETTINGS.items()
113
+ )
114
+ env_fd.write(settings)
115
+ env_fd.flush()
116
+ display.info(f'>>> galaxy_ng Configuration\n{settings}', verbosity=3)
117
+ descriptor = run_support_container(
118
+ self.args,
119
+ self.platform,
120
+ self.image,
121
+ GALAXY_HOST_NAME,
122
+ [
123
+ 80,
124
+ ],
125
+ aliases=[
126
+ GALAXY_HOST_NAME,
127
+ ],
128
+ start=True,
129
+ options=[
130
+ '--env-file', env_fd.name,
131
+ ],
132
+ )
118
133
 
119
134
  if not descriptor:
120
135
  return
121
136
 
122
- if not descriptor.running:
123
- pulp_id = descriptor.container_id
124
-
125
- injected_files = {
126
- '/etc/pulp/settings.py': SETTINGS,
127
- '/etc/cont-init.d/111-postgres': SET_ADMIN_PASSWORD,
128
- '/etc/cont-init.d/000-ansible-test-overrides': OVERRIDES,
129
- }
130
- for path, content in injected_files.items():
131
- with tempfile.NamedTemporaryFile() as temp_fd:
132
- temp_fd.write(content)
133
- temp_fd.flush()
134
- docker_cp_to(self.args, pulp_id, temp_fd.name, path)
135
-
136
- descriptor.start(self.args)
137
-
138
- self._set_cloud_config('PULP_HOST', pulp_host)
139
- self._set_cloud_config('PULP_PORT', str(pulp_port))
140
- self._set_cloud_config('GALAXY_PORT', str(galaxy_port))
137
+ injected_files = [
138
+ ('/etc/galaxy-importer/galaxy-importer.cfg', GALAXY_IMPORTER, 'galaxy-importer'),
139
+ ]
140
+ for path, content, friendly_name in injected_files:
141
+ with tempfile.NamedTemporaryFile() as temp_fd:
142
+ temp_fd.write(content)
143
+ temp_fd.flush()
144
+ display.info(f'>>> {friendly_name} Configuration\n{to_text(content)}', verbosity=3)
145
+ docker_exec(self.args, descriptor.container_id, ['mkdir', '-p', os.path.dirname(path)], True)
146
+ docker_cp_to(self.args, descriptor.container_id, temp_fd.name, path)
147
+ docker_exec(self.args, descriptor.container_id, ['chown', 'pulp:pulp', path], True)
148
+
149
+ self._set_cloud_config('PULP_HOST', GALAXY_HOST_NAME)
141
150
  self._set_cloud_config('PULP_USER', 'admin')
142
151
  self._set_cloud_config('PULP_PASSWORD', 'password')
143
152
 
@@ -150,21 +159,19 @@ class GalaxyEnvironment(CloudEnvironment):
150
159
  pulp_user = str(self._get_cloud_config('PULP_USER'))
151
160
  pulp_password = str(self._get_cloud_config('PULP_PASSWORD'))
152
161
  pulp_host = self._get_cloud_config('PULP_HOST')
153
- galaxy_port = self._get_cloud_config('GALAXY_PORT')
154
- pulp_port = self._get_cloud_config('PULP_PORT')
155
162
 
156
163
  return CloudEnvironmentConfig(
157
164
  ansible_vars=dict(
158
165
  pulp_user=pulp_user,
159
166
  pulp_password=pulp_password,
160
- pulp_api='http://%s:%s' % (pulp_host, pulp_port),
161
- pulp_server='http://%s:%s/pulp_ansible/galaxy/' % (pulp_host, pulp_port),
162
- galaxy_ng_server='http://%s:%s/api/galaxy/' % (pulp_host, galaxy_port),
167
+ pulp_api=f'http://{pulp_host}',
168
+ pulp_server=f'http://{pulp_host}/pulp_ansible/galaxy/',
169
+ galaxy_ng_server=f'http://{pulp_host}/api/galaxy/',
163
170
  ),
164
171
  env_vars=dict(
165
172
  PULP_USER=pulp_user,
166
173
  PULP_PASSWORD=pulp_password,
167
- PULP_SERVER='http://%s:%s/pulp_ansible/galaxy/api/' % (pulp_host, pulp_port),
168
- GALAXY_NG_SERVER='http://%s:%s/api/galaxy/' % (pulp_host, galaxy_port),
174
+ PULP_SERVER=f'http://{pulp_host}/pulp_ansible/galaxy/api/',
175
+ GALAXY_NG_SERVER=f'http://{pulp_host}/api/galaxy/',
169
176
  ),
170
177
  )
@@ -13,7 +13,6 @@ from ....config import (
13
13
  )
14
14
 
15
15
  from ....containers import (
16
- CleanupMode,
17
16
  run_support_container,
18
17
  )
19
18
 
@@ -62,8 +61,6 @@ class HttptesterProvider(CloudProvider):
62
61
  'http-test-container',
63
62
  ports,
64
63
  aliases=aliases,
65
- allow_existing=True,
66
- cleanup=CleanupMode.YES,
67
64
  env={
68
65
  KRB5_PASSWORD_ENV: generate_password(),
69
66
  },
@@ -8,7 +8,6 @@ from ....config import (
8
8
  )
9
9
 
10
10
  from ....containers import (
11
- CleanupMode,
12
11
  run_support_container,
13
12
  )
14
13
 
@@ -22,8 +21,6 @@ from . import (
22
21
  class NiosProvider(CloudProvider):
23
22
  """Nios plugin. Sets up NIOS mock server for tests."""
24
23
 
25
- DOCKER_SIMULATOR_NAME = 'nios-simulator'
26
-
27
24
  # Default image to run the nios simulator.
28
25
  #
29
26
  # The simulator must be pinned to a specific version
@@ -31,7 +28,7 @@ class NiosProvider(CloudProvider):
31
28
  #
32
29
  # It's source source itself resides at:
33
30
  # https://github.com/ansible/nios-test-container
34
- DOCKER_IMAGE = 'quay.io/ansible/nios-test-container:1.5.0'
31
+ DOCKER_IMAGE = 'quay.io/ansible/nios-test-container:2.0.0'
35
32
 
36
33
  def __init__(self, args: IntegrationConfig) -> None:
37
34
  super().__init__(args)
@@ -65,17 +62,18 @@ class NiosProvider(CloudProvider):
65
62
  nios_port,
66
63
  ]
67
64
 
68
- run_support_container(
65
+ descriptor = run_support_container(
69
66
  self.args,
70
67
  self.platform,
71
68
  self.image,
72
- self.DOCKER_SIMULATOR_NAME,
69
+ 'nios-simulator',
73
70
  ports,
74
- allow_existing=True,
75
- cleanup=CleanupMode.YES,
76
71
  )
77
72
 
78
- self._set_cloud_config('NIOS_HOST', self.DOCKER_SIMULATOR_NAME)
73
+ if not descriptor:
74
+ return
75
+
76
+ self._set_cloud_config('NIOS_HOST', descriptor.name)
79
77
 
80
78
  def _setup_static(self) -> None:
81
79
  raise NotImplementedError()
@@ -16,7 +16,6 @@ from ....config import (
16
16
  )
17
17
 
18
18
  from ....containers import (
19
- CleanupMode,
20
19
  run_support_container,
21
20
  wait_for_file,
22
21
  )
@@ -31,8 +30,6 @@ from . import (
31
30
  class OpenShiftCloudProvider(CloudProvider):
32
31
  """OpenShift cloud provider plugin. Sets up cloud resources before delegation."""
33
32
 
34
- DOCKER_CONTAINER_NAME = 'openshift-origin'
35
-
36
33
  def __init__(self, args: IntegrationConfig) -> None:
37
34
  super().__init__(args, config_extension='.kubeconfig')
38
35
 
@@ -74,10 +71,8 @@ class OpenShiftCloudProvider(CloudProvider):
74
71
  self.args,
75
72
  self.platform,
76
73
  self.image,
77
- self.DOCKER_CONTAINER_NAME,
74
+ 'openshift-origin',
78
75
  ports,
79
- allow_existing=True,
80
- cleanup=CleanupMode.YES,
81
76
  cmd=cmd,
82
77
  )
83
78
 
@@ -87,7 +82,7 @@ class OpenShiftCloudProvider(CloudProvider):
87
82
  if self.args.explain:
88
83
  config = '# Unknown'
89
84
  else:
90
- config = self._get_config(self.DOCKER_CONTAINER_NAME, 'https://%s:%s/' % (self.DOCKER_CONTAINER_NAME, port))
85
+ config = self._get_config(descriptor.name, 'https://%s:%s/' % (descriptor.name, port))
91
86
 
92
87
  self._write_config(config)
93
88
 
@@ -2,7 +2,6 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import configparser
5
- import os
6
5
 
7
6
  from ....util import (
8
7
  ApplicationError,
@@ -13,15 +12,6 @@ from ....config import (
13
12
  IntegrationConfig,
14
13
  )
15
14
 
16
- from ....containers import (
17
- CleanupMode,
18
- run_support_container,
19
- )
20
-
21
- from .... data import (
22
- data_context,
23
- )
24
-
25
15
  from . import (
26
16
  CloudEnvironment,
27
17
  CloudEnvironmentConfig,
@@ -32,74 +22,16 @@ from . import (
32
22
  class VcenterProvider(CloudProvider):
33
23
  """VMware vcenter/esx plugin. Sets up cloud resources for tests."""
34
24
 
35
- DOCKER_SIMULATOR_NAME = 'vcenter-simulator'
36
-
37
25
  def __init__(self, args: IntegrationConfig) -> None:
38
26
  super().__init__(args)
39
27
 
40
- # The simulator must be pinned to a specific version to guarantee CI passes with the version used.
41
- if os.environ.get('ANSIBLE_VCSIM_CONTAINER'):
42
- self.image = os.environ.get('ANSIBLE_VCSIM_CONTAINER')
43
- else:
44
- self.image = 'quay.io/ansible/vcenter-test-container:1.7.0'
45
-
46
- # VMware tests can be run on govcsim or BYO with a static config file.
47
- # When testing ansible-core, the simulator is the default if no config is provided. This facilitates easier testing of the plugin's container support.
48
- # When testing a collection, static config is the default if no config is provided.
49
- default_mode = 'govcsim' if data_context().content.is_ansible else 'static'
50
-
51
- self.vmware_test_platform = os.environ.get('VMWARE_TEST_PLATFORM', default_mode)
52
-
53
- if self.vmware_test_platform == 'govcsim':
54
- display.warning(
55
- 'The govcsim simulator is deprecated and will be removed in a future version of ansible-test. Use a static configuration instead.',
56
- unique=True,
57
- )
58
-
59
- self.uses_docker = True
60
- self.uses_config = False
61
- elif self.vmware_test_platform == 'static':
62
- self.uses_docker = False
63
- self.uses_config = True
28
+ self.uses_config = True
64
29
 
65
30
  def setup(self) -> None:
66
31
  """Setup the cloud resource before delegation and register a cleanup callback."""
67
32
  super().setup()
68
33
 
69
- self._set_cloud_config('vmware_test_platform', self.vmware_test_platform)
70
-
71
- if self.vmware_test_platform == 'govcsim':
72
- self._setup_dynamic_simulator()
73
- self.managed = True
74
- elif self.vmware_test_platform == 'static':
75
- self._use_static_config()
76
- self._setup_static()
77
- else:
78
- raise ApplicationError('Unknown vmware_test_platform: %s' % self.vmware_test_platform)
79
-
80
- def _setup_dynamic_simulator(self) -> None:
81
- """Create a vcenter simulator using docker."""
82
- ports = [
83
- 443,
84
- 8080,
85
- 8989,
86
- 5000, # control port for flask app in simulator
87
- ]
88
-
89
- run_support_container(
90
- self.args,
91
- self.platform,
92
- self.image,
93
- self.DOCKER_SIMULATOR_NAME,
94
- ports,
95
- allow_existing=True,
96
- cleanup=CleanupMode.YES,
97
- )
98
-
99
- self._set_cloud_config('vcenter_hostname', self.DOCKER_SIMULATOR_NAME)
100
-
101
- def _setup_static(self) -> None:
102
- if not os.path.exists(self.config_static_path):
34
+ if not self._use_static_config():
103
35
  raise ApplicationError('Configuration file does not exist: %s' % self.config_static_path)
104
36
 
105
37
 
@@ -108,37 +40,21 @@ class VcenterEnvironment(CloudEnvironment):
108
40
 
109
41
  def get_environment_config(self) -> CloudEnvironmentConfig:
110
42
  """Return environment configuration for use in the test environment after delegation."""
111
- try:
112
- # We may be in a container, so we cannot just reach VMWARE_TEST_PLATFORM,
113
- # We do a try/except instead
114
- parser = configparser.ConfigParser()
115
- parser.read(self.config_path) # static
116
-
117
- env_vars = {}
118
- ansible_vars = dict(
119
- resource_prefix=self.resource_prefix,
120
- )
121
- ansible_vars.update(dict(parser.items('DEFAULT', raw=True)))
122
- except KeyError: # govcsim
123
- env_vars = dict(
124
- VCENTER_HOSTNAME=str(self._get_cloud_config('vcenter_hostname')),
125
- VCENTER_USERNAME='user',
126
- VCENTER_PASSWORD='pass',
127
- )
128
-
129
- ansible_vars = dict(
130
- vcsim=str(self._get_cloud_config('vcenter_hostname')),
131
- vcenter_hostname=str(self._get_cloud_config('vcenter_hostname')),
132
- vcenter_username='user',
133
- vcenter_password='pass',
134
- )
43
+ # We may be in a container, so we cannot just reach VMWARE_TEST_PLATFORM,
44
+ # We do a try/except instead
45
+ parser = configparser.ConfigParser()
46
+ parser.read(self.config_path) # static
47
+
48
+ ansible_vars = dict(
49
+ resource_prefix=self.resource_prefix,
50
+ )
51
+ ansible_vars.update(dict(parser.items('DEFAULT', raw=True)))
135
52
 
136
53
  for key, value in ansible_vars.items():
137
54
  if key.endswith('_password'):
138
55
  display.sensitive.add(value)
139
56
 
140
57
  return CloudEnvironmentConfig(
141
- env_vars=env_vars,
142
58
  ansible_vars=ansible_vars,
143
59
  module_defaults={
144
60
  'group/vmware': {
@@ -879,6 +879,7 @@ class SanityCodeSmellTest(SanitySingleVersion):
879
879
  self.__include_directories: bool = self.config.get('include_directories')
880
880
  self.__include_symlinks: bool = self.config.get('include_symlinks')
881
881
  self.__py2_compat: bool = self.config.get('py2_compat', False)
882
+ self.__error_code: str | None = self.config.get('error_code', None)
882
883
  else:
883
884
  self.output = None
884
885
  self.extensions = []
@@ -894,6 +895,7 @@ class SanityCodeSmellTest(SanitySingleVersion):
894
895
  self.__include_directories = False
895
896
  self.__include_symlinks = False
896
897
  self.__py2_compat = False
898
+ self.__error_code = None
897
899
 
898
900
  if self.no_targets:
899
901
  mutually_exclusive = (
@@ -912,6 +914,11 @@ class SanityCodeSmellTest(SanitySingleVersion):
912
914
  if problems:
913
915
  raise ApplicationError('Sanity test "%s" option "no_targets" is mutually exclusive with options: %s' % (self.name, ', '.join(problems)))
914
916
 
917
+ @property
918
+ def error_code(self) -> t.Optional[str]:
919
+ """Error code for ansible-test matching the format used by the underlying test program, or None if the program does not use error codes."""
920
+ return self.__error_code
921
+
915
922
  @property
916
923
  def all_targets(self) -> bool:
917
924
  """True if test targets will not be filtered using includes, excludes, requires or changes. Mutually exclusive with no_targets."""
@@ -996,6 +1003,8 @@ class SanityCodeSmellTest(SanitySingleVersion):
996
1003
  pattern = '^(?P<path>[^:]*):(?P<line>[0-9]+):(?P<column>[0-9]+): (?P<message>.*)$'
997
1004
  elif self.output == 'path-message':
998
1005
  pattern = '^(?P<path>[^:]*): (?P<message>.*)$'
1006
+ elif self.output == 'path-line-column-code-message':
1007
+ pattern = '^(?P<path>[^:]*):(?P<line>[0-9]+):(?P<column>[0-9]+): (?P<code>[^:]*): (?P<message>.*)$'
999
1008
  else:
1000
1009
  raise ApplicationError('Unsupported output type: %s' % self.output)
1001
1010
 
@@ -1025,6 +1034,7 @@ class SanityCodeSmellTest(SanitySingleVersion):
1025
1034
  path=m['path'],
1026
1035
  line=int(m.get('line', 0)),
1027
1036
  column=int(m.get('column', 0)),
1037
+ code=m.get('code'),
1028
1038
  ) for m in matches]
1029
1039
 
1030
1040
  messages = settings.process_errors(messages, paths)
@@ -127,14 +127,20 @@ class ImportTest(SanityMultipleVersion):
127
127
  ('plugin', _get_module_test(False)),
128
128
  ):
129
129
  if import_type == 'plugin' and python.version in REMOTE_ONLY_PYTHON_VERSIONS:
130
- continue
130
+ # Plugins are not supported on remote-only Python versions.
131
+ # However, the collection loader is used by the import sanity test and unit tests on remote-only Python versions.
132
+ # To support this, it is tested as a plugin, but using a venv which installs no requirements.
133
+ # Filtering of paths relevant to the Python version tested has already been performed by filter_remote_targets.
134
+ venv_type = 'empty'
135
+ else:
136
+ venv_type = import_type
131
137
 
132
138
  data = '\n'.join([path for path in paths if test(path)])
133
139
 
134
140
  if not data and not args.prime_venvs:
135
141
  continue
136
142
 
137
- virtualenv_python = create_sanity_virtualenv(args, python, f'{self.name}.{import_type}', coverage=args.coverage, minimize=True)
143
+ virtualenv_python = create_sanity_virtualenv(args, python, f'{self.name}.{venv_type}', coverage=args.coverage, minimize=True)
138
144
 
139
145
  if not virtualenv_python:
140
146
  display.warning(f'Skipping sanity test "{self.name}" on Python {python.version} due to missing virtual environment support.')
@@ -18,6 +18,11 @@ from . import (
18
18
  SANITY_ROOT,
19
19
  )
20
20
 
21
+ from ...constants import (
22
+ CONTROLLER_PYTHON_VERSIONS,
23
+ REMOTE_ONLY_PYTHON_VERSIONS,
24
+ )
25
+
21
26
  from ...io import (
22
27
  make_dirs,
23
28
  )
@@ -38,6 +43,7 @@ from ...util import (
38
43
 
39
44
  from ...util_common import (
40
45
  run_command,
46
+ process_scoped_temporary_file,
41
47
  )
42
48
 
43
49
  from ...ansible_util import (
@@ -81,6 +87,8 @@ class PylintTest(SanitySingleVersion):
81
87
  return [target for target in targets if os.path.splitext(target.path)[1] == '.py' or is_subdir(target.path, 'bin')]
82
88
 
83
89
  def test(self, args: SanityConfig, targets: SanityTargets, python: PythonConfig) -> TestResult:
90
+ min_python_version_db_path = self.create_min_python_db(args, targets.targets)
91
+
84
92
  plugin_dir = os.path.join(SANITY_ROOT, 'pylint', 'plugins')
85
93
  plugin_names = sorted(p[0] for p in [
86
94
  os.path.splitext(p) for p in os.listdir(plugin_dir)] if p[1] == '.py' and p[0] != '__init__')
@@ -163,7 +171,7 @@ class PylintTest(SanitySingleVersion):
163
171
  continue
164
172
 
165
173
  context_start = datetime.datetime.now(tz=datetime.timezone.utc)
166
- messages += self.pylint(args, context, context_paths, plugin_dir, plugin_names, python, collection_detail)
174
+ messages += self.pylint(args, context, context_paths, plugin_dir, plugin_names, python, collection_detail, min_python_version_db_path)
167
175
  context_end = datetime.datetime.now(tz=datetime.timezone.utc)
168
176
 
169
177
  context_times.append('%s: %d (%s)' % (context, len(context_paths), context_end - context_start))
@@ -194,6 +202,22 @@ class PylintTest(SanitySingleVersion):
194
202
 
195
203
  return SanitySuccess(self.name)
196
204
 
205
+ def create_min_python_db(self, args: SanityConfig, targets: t.Iterable[TestTarget]) -> str:
206
+ """Create a database of target file paths and their minimum required Python version, returning the path to the database."""
207
+ target_paths = set(target.path for target in self.filter_remote_targets(list(targets)))
208
+ controller_min_version = CONTROLLER_PYTHON_VERSIONS[0]
209
+ target_min_version = REMOTE_ONLY_PYTHON_VERSIONS[0]
210
+ min_python_versions = {
211
+ os.path.abspath(target.path): target_min_version if target.path in target_paths else controller_min_version for target in targets
212
+ }
213
+
214
+ min_python_version_db_path = process_scoped_temporary_file(args)
215
+
216
+ with open(min_python_version_db_path, 'w') as database_file:
217
+ json.dump(min_python_versions, database_file)
218
+
219
+ return min_python_version_db_path
220
+
197
221
  @staticmethod
198
222
  def pylint(
199
223
  args: SanityConfig,
@@ -203,6 +227,7 @@ class PylintTest(SanitySingleVersion):
203
227
  plugin_names: list[str],
204
228
  python: PythonConfig,
205
229
  collection_detail: CollectionDetail,
230
+ min_python_version_db_path: str,
206
231
  ) -> list[dict[str, str]]:
207
232
  """Run pylint using the config specified by the context on the specified paths."""
208
233
  rcfile = os.path.join(SANITY_ROOT, 'pylint', 'config', context.split('/')[0] + '.cfg')
@@ -234,6 +259,7 @@ class PylintTest(SanitySingleVersion):
234
259
  '--rcfile', rcfile,
235
260
  '--output-format', 'json',
236
261
  '--load-plugins', ','.join(sorted(load_plugins)),
262
+ '--min-python-version-db', min_python_version_db_path,
237
263
  ] + paths # fmt: skip
238
264
 
239
265
  if data_context().content.collection: