ansible-core 2.17.4__py3-none-any.whl → 2.18.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (320) hide show
  1. ansible/__main__.py +2 -17
  2. ansible/cli/__init__.py +3 -15
  3. ansible/cli/config.py +187 -24
  4. ansible/cli/console.py +1 -1
  5. ansible/cli/doc.py +38 -16
  6. ansible/cli/galaxy.py +30 -53
  7. ansible/cli/inventory.py +2 -2
  8. ansible/cli/pull.py +2 -2
  9. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -10
  10. ansible/config/base.yml +127 -57
  11. ansible/config/manager.py +89 -11
  12. ansible/constants.py +32 -9
  13. ansible/errors/__init__.py +5 -0
  14. ansible/executor/interpreter_discovery.py +1 -1
  15. ansible/executor/play_iterator.py +16 -0
  16. ansible/executor/playbook_executor.py +1 -4
  17. ansible/executor/powershell/become_wrapper.ps1 +4 -5
  18. ansible/executor/powershell/bootstrap_wrapper.ps1 +2 -3
  19. ansible/executor/powershell/exec_wrapper.ps1 +1 -1
  20. ansible/executor/powershell/module_manifest.py +2 -2
  21. ansible/executor/task_executor.py +50 -39
  22. ansible/executor/task_queue_manager.py +1 -1
  23. ansible/executor/task_result.py +1 -1
  24. ansible/galaxy/api.py +3 -4
  25. ansible/galaxy/collection/__init__.py +21 -10
  26. ansible/galaxy/collection/concrete_artifact_manager.py +2 -2
  27. ansible/galaxy/collection/galaxy_api_proxy.py +10 -16
  28. ansible/galaxy/collection/gpg.py +17 -23
  29. ansible/galaxy/data/COPYING +7 -0
  30. ansible/galaxy/data/apb/Dockerfile.j2 +1 -0
  31. ansible/galaxy/data/apb/Makefile.j2 +1 -0
  32. ansible/galaxy/data/apb/README.md +7 -3
  33. ansible/galaxy/data/apb/apb.yml.j2 +1 -0
  34. ansible/galaxy/data/apb/defaults/main.yml.j2 +1 -0
  35. ansible/galaxy/data/apb/handlers/main.yml.j2 +1 -0
  36. ansible/galaxy/data/apb/meta/main.yml.j2 +1 -0
  37. ansible/galaxy/data/apb/playbooks/deprovision.yml.j2 +1 -0
  38. ansible/galaxy/data/apb/playbooks/provision.yml.j2 +1 -0
  39. ansible/galaxy/data/apb/tasks/main.yml.j2 +1 -0
  40. ansible/galaxy/data/apb/tests/ansible.cfg +1 -0
  41. ansible/galaxy/data/apb/tests/inventory +1 -0
  42. ansible/galaxy/data/apb/tests/test.yml.j2 +1 -0
  43. ansible/galaxy/data/apb/vars/main.yml.j2 +1 -0
  44. ansible/galaxy/data/collections_galaxy_meta.yml +1 -0
  45. ansible/galaxy/data/container/defaults/main.yml.j2 +1 -0
  46. ansible/galaxy/data/container/handlers/main.yml.j2 +1 -0
  47. ansible/galaxy/data/container/meta/container.yml.j2 +1 -0
  48. ansible/galaxy/data/container/meta/main.yml.j2 +1 -0
  49. ansible/galaxy/data/container/tasks/main.yml.j2 +1 -0
  50. ansible/galaxy/data/container/tests/ansible.cfg +1 -0
  51. ansible/galaxy/data/container/tests/inventory +1 -0
  52. ansible/galaxy/data/container/tests/test.yml.j2 +1 -0
  53. ansible/galaxy/data/container/vars/main.yml.j2 +1 -0
  54. ansible/galaxy/data/default/collection/README.md.j2 +1 -0
  55. ansible/galaxy/data/default/collection/galaxy.yml.j2 +1 -0
  56. ansible/galaxy/data/default/collection/meta/runtime.yml +1 -0
  57. ansible/galaxy/data/default/collection/plugins/README.md.j2 +1 -0
  58. ansible/galaxy/data/default/role/defaults/main.yml.j2 +1 -0
  59. ansible/galaxy/data/default/role/handlers/main.yml.j2 +1 -0
  60. ansible/galaxy/data/default/role/meta/main.yml.j2 +1 -0
  61. ansible/galaxy/data/default/role/tasks/main.yml.j2 +1 -0
  62. ansible/galaxy/data/default/role/tests/inventory +1 -0
  63. ansible/galaxy/data/default/role/tests/test.yml.j2 +1 -0
  64. ansible/galaxy/data/default/role/vars/main.yml.j2 +1 -0
  65. ansible/galaxy/data/network/cliconf_plugins/example.py.j2 +1 -0
  66. ansible/galaxy/data/network/defaults/main.yml.j2 +1 -0
  67. ansible/galaxy/data/network/library/example_command.py.j2 +1 -0
  68. ansible/galaxy/data/network/library/example_config.py.j2 +1 -0
  69. ansible/galaxy/data/network/library/example_facts.py.j2 +1 -0
  70. ansible/galaxy/data/network/meta/main.yml.j2 +1 -0
  71. ansible/galaxy/data/network/module_utils/example.py.j2 +1 -0
  72. ansible/galaxy/data/network/netconf_plugins/example.py.j2 +1 -0
  73. ansible/galaxy/data/network/tasks/main.yml.j2 +1 -0
  74. ansible/galaxy/data/network/terminal_plugins/example.py.j2 +1 -0
  75. ansible/galaxy/data/network/tests/inventory +1 -0
  76. ansible/galaxy/data/network/tests/test.yml.j2 +1 -0
  77. ansible/galaxy/data/network/vars/main.yml.j2 +1 -0
  78. ansible/galaxy/dependency_resolution/providers.py +3 -3
  79. ansible/galaxy/role.py +1 -1
  80. ansible/galaxy/token.py +20 -8
  81. ansible/keyword_desc.yml +1 -1
  82. ansible/module_utils/_internal/__init__.py +0 -0
  83. ansible/module_utils/_internal/_concurrent/__init__.py +0 -0
  84. ansible/module_utils/_internal/_concurrent/_daemon_threading.py +28 -0
  85. ansible/module_utils/_internal/_concurrent/_futures.py +21 -0
  86. ansible/module_utils/ansible_release.py +2 -2
  87. ansible/module_utils/api.py +2 -2
  88. ansible/module_utils/basic.py +14 -11
  89. ansible/module_utils/common/collections.py +1 -1
  90. ansible/module_utils/common/file.py +0 -6
  91. ansible/module_utils/common/process.py +22 -9
  92. ansible/module_utils/common/text/converters.py +5 -8
  93. ansible/module_utils/common/text/formatters.py +20 -4
  94. ansible/module_utils/common/validation.py +33 -25
  95. ansible/module_utils/compat/paramiko.py +6 -1
  96. ansible/module_utils/compat/selinux.py +2 -2
  97. ansible/module_utils/connection.py +8 -24
  98. ansible/module_utils/csharp/Ansible.Become.cs +14 -25
  99. ansible/module_utils/csharp/Ansible.Process.cs +1 -1
  100. ansible/module_utils/distro/__init__.py +1 -1
  101. ansible/module_utils/distro/_distro.py +8 -4
  102. ansible/module_utils/facts/collector.py +2 -0
  103. ansible/module_utils/facts/default_collectors.py +3 -1
  104. ansible/module_utils/facts/hardware/aix.py +54 -52
  105. ansible/module_utils/facts/hardware/darwin.py +37 -34
  106. ansible/module_utils/facts/hardware/freebsd.py +55 -15
  107. ansible/module_utils/facts/hardware/hpux.py +3 -0
  108. ansible/module_utils/facts/hardware/linux.py +101 -57
  109. ansible/module_utils/facts/hardware/netbsd.py +3 -0
  110. ansible/module_utils/facts/hardware/openbsd.py +4 -1
  111. ansible/module_utils/facts/hardware/sunos.py +7 -1
  112. ansible/module_utils/facts/network/aix.py +16 -17
  113. ansible/module_utils/facts/network/fc_wwn.py +4 -1
  114. ansible/module_utils/facts/network/hpux.py +21 -4
  115. ansible/module_utils/facts/network/iscsi.py +7 -8
  116. ansible/module_utils/facts/network/linux.py +0 -2
  117. ansible/module_utils/facts/other/facter.py +9 -4
  118. ansible/module_utils/facts/other/ohai.py +5 -5
  119. ansible/module_utils/facts/packages.py +49 -7
  120. ansible/module_utils/facts/sysctl.py +33 -31
  121. ansible/module_utils/facts/system/distribution.py +1 -1
  122. ansible/module_utils/facts/system/local.py +12 -22
  123. ansible/module_utils/facts/system/service_mgr.py +3 -1
  124. ansible/module_utils/facts/system/systemd.py +47 -0
  125. ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
  126. ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
  127. ansible/module_utils/splitter.py +1 -1
  128. ansible/modules/add_host.py +1 -1
  129. ansible/modules/apt.py +43 -32
  130. ansible/modules/apt_key.py +6 -6
  131. ansible/modules/apt_repository.py +23 -14
  132. ansible/modules/assemble.py +7 -2
  133. ansible/modules/assert.py +4 -4
  134. ansible/modules/blockinfile.py +3 -6
  135. ansible/modules/command.py +1 -1
  136. ansible/modules/copy.py +4 -4
  137. ansible/modules/cron.py +13 -10
  138. ansible/modules/deb822_repository.py +16 -17
  139. ansible/modules/debconf.py +9 -9
  140. ansible/modules/debug.py +1 -1
  141. ansible/modules/dnf.py +79 -164
  142. ansible/modules/dnf5.py +48 -31
  143. ansible/modules/dpkg_selections.py +2 -2
  144. ansible/modules/expect.py +2 -2
  145. ansible/modules/fetch.py +2 -2
  146. ansible/modules/file.py +5 -3
  147. ansible/modules/find.py +40 -12
  148. ansible/modules/gather_facts.py +4 -2
  149. ansible/modules/get_url.py +29 -24
  150. ansible/modules/git.py +35 -35
  151. ansible/modules/group.py +71 -1
  152. ansible/modules/hostname.py +2 -4
  153. ansible/modules/include_vars.py +5 -5
  154. ansible/modules/iptables.py +13 -16
  155. ansible/modules/known_hosts.py +16 -13
  156. ansible/modules/lineinfile.py +1 -4
  157. ansible/modules/meta.py +6 -1
  158. ansible/modules/mount_facts.py +651 -0
  159. ansible/modules/package_facts.py +63 -80
  160. ansible/modules/pause.py +4 -3
  161. ansible/modules/pip.py +14 -14
  162. ansible/modules/replace.py +1 -4
  163. ansible/modules/rpm_key.py +31 -11
  164. ansible/modules/service.py +8 -8
  165. ansible/modules/service_facts.py +20 -5
  166. ansible/modules/set_stats.py +1 -1
  167. ansible/modules/setup.py +3 -3
  168. ansible/modules/stat.py +3 -3
  169. ansible/modules/subversion.py +1 -1
  170. ansible/modules/systemd.py +16 -10
  171. ansible/modules/systemd_service.py +16 -10
  172. ansible/modules/sysvinit.py +4 -4
  173. ansible/modules/unarchive.py +35 -22
  174. ansible/modules/uri.py +24 -18
  175. ansible/modules/user.py +145 -12
  176. ansible/modules/validate_argument_spec.py +3 -3
  177. ansible/modules/wait_for_connection.py +2 -1
  178. ansible/modules/yum_repository.py +136 -179
  179. ansible/parsing/dataloader.py +2 -2
  180. ansible/parsing/mod_args.py +11 -10
  181. ansible/parsing/vault/__init__.py +8 -3
  182. ansible/parsing/yaml/constructor.py +10 -8
  183. ansible/parsing/yaml/objects.py +1 -1
  184. ansible/playbook/base.py +12 -23
  185. ansible/playbook/helpers.py +4 -0
  186. ansible/playbook/loop_control.py +8 -0
  187. ansible/playbook/play.py +4 -22
  188. ansible/playbook/play_context.py +0 -16
  189. ansible/playbook/playbook_include.py +2 -2
  190. ansible/playbook/role/__init__.py +2 -2
  191. ansible/playbook/task.py +1 -1
  192. ansible/plugins/__init__.py +2 -0
  193. ansible/plugins/action/__init__.py +7 -9
  194. ansible/plugins/action/reboot.py +2 -2
  195. ansible/plugins/become/__init__.py +1 -1
  196. ansible/plugins/callback/__init__.py +44 -3
  197. ansible/plugins/callback/default.py +1 -1
  198. ansible/plugins/cliconf/__init__.py +1 -1
  199. ansible/plugins/connection/paramiko_ssh.py +2 -80
  200. ansible/plugins/connection/psrp.py +33 -82
  201. ansible/plugins/connection/ssh.py +0 -8
  202. ansible/plugins/connection/winrm.py +46 -1
  203. ansible/plugins/doc_fragments/connection_pipelining.py +2 -2
  204. ansible/plugins/doc_fragments/constructed.py +10 -10
  205. ansible/plugins/doc_fragments/default_callback.py +8 -8
  206. ansible/plugins/doc_fragments/files.py +5 -5
  207. ansible/plugins/doc_fragments/inventory_cache.py +2 -2
  208. ansible/plugins/doc_fragments/result_format_callback.py +6 -6
  209. ansible/plugins/doc_fragments/return_common.py +1 -1
  210. ansible/plugins/doc_fragments/shell_common.py +2 -10
  211. ansible/plugins/doc_fragments/shell_windows.py +0 -9
  212. ansible/plugins/doc_fragments/url.py +2 -2
  213. ansible/plugins/doc_fragments/url_windows.py +4 -5
  214. ansible/plugins/doc_fragments/validate.py +1 -1
  215. ansible/plugins/filter/core.py +2 -0
  216. ansible/plugins/filter/human_to_bytes.yml +9 -0
  217. ansible/plugins/filter/password_hash.yml +1 -1
  218. ansible/plugins/filter/strftime.yml +1 -1
  219. ansible/plugins/filter/to_nice_json.yml +7 -3
  220. ansible/plugins/filter/to_uuid.yml +1 -1
  221. ansible/plugins/inventory/script.py +1 -1
  222. ansible/plugins/list.py +1 -1
  223. ansible/plugins/loader.py +0 -11
  224. ansible/plugins/lookup/config.py +1 -1
  225. ansible/plugins/lookup/csvfile.py +21 -9
  226. ansible/plugins/lookup/env.py +8 -9
  227. ansible/plugins/lookup/ini.py +10 -1
  228. ansible/plugins/lookup/random_choice.py +2 -2
  229. ansible/plugins/lookup/url.py +7 -2
  230. ansible/plugins/shell/__init__.py +15 -20
  231. ansible/plugins/shell/powershell.py +9 -6
  232. ansible/plugins/strategy/__init__.py +16 -7
  233. ansible/plugins/test/core.py +23 -1
  234. ansible/plugins/test/issubset.yml +1 -1
  235. ansible/plugins/test/subset.yml +1 -1
  236. ansible/plugins/test/timedout.yml +20 -0
  237. ansible/plugins/test/vault_encrypted.yml +6 -6
  238. ansible/plugins/test/vaulted_file.yml +19 -0
  239. ansible/release.py +2 -2
  240. ansible/template/__init__.py +3 -8
  241. ansible/utils/collection_loader/_collection_finder.py +23 -55
  242. ansible/utils/display.py +44 -31
  243. ansible/utils/jsonrpc.py +1 -1
  244. ansible/utils/listify.py +1 -5
  245. ansible/utils/path.py +3 -0
  246. ansible/utils/vars.py +18 -27
  247. ansible/vars/manager.py +7 -150
  248. ansible/vars/plugins.py +1 -1
  249. ansible_core-2.18.0b1.dist-info/Apache-License.txt +202 -0
  250. {ansible_core-2.17.4.dist-info → ansible_core-2.18.0b1.dist-info}/METADATA +36 -23
  251. ansible_core-2.18.0b1.dist-info/MIT-license.txt +14 -0
  252. ansible_core-2.18.0b1.dist-info/PSF-license.txt +48 -0
  253. {ansible_core-2.17.4.dist-info → ansible_core-2.18.0b1.dist-info}/RECORD +311 -306
  254. {ansible_core-2.17.4.dist-info → ansible_core-2.18.0b1.dist-info}/WHEEL +1 -1
  255. {ansible_core-2.17.4.dist-info → ansible_core-2.18.0b1.dist-info}/entry_points.txt +1 -1
  256. ansible_core-2.18.0b1.dist-info/simplified_bsd.txt +8 -0
  257. ansible_test/_data/completion/docker.txt +7 -7
  258. ansible_test/_data/completion/remote.txt +5 -4
  259. ansible_test/_data/completion/windows.txt +4 -4
  260. ansible_test/_data/requirements/ansible-test.txt +1 -2
  261. ansible_test/_data/requirements/constraints.txt +1 -2
  262. ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
  263. ansible_test/_data/requirements/sanity.changelog.in +1 -1
  264. ansible_test/_data/requirements/sanity.changelog.txt +4 -4
  265. ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
  266. ansible_test/_data/requirements/sanity.import.txt +1 -1
  267. ansible_test/_data/requirements/sanity.integration-aliases.txt +1 -1
  268. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  269. ansible_test/_data/requirements/sanity.pylint.txt +5 -7
  270. ansible_test/_data/requirements/sanity.runtime-metadata.txt +2 -2
  271. ansible_test/_data/requirements/sanity.validate-modules.txt +3 -3
  272. ansible_test/_data/requirements/sanity.yamllint.in +1 -0
  273. ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
  274. ansible_test/_internal/ansible_util.py +8 -35
  275. ansible_test/_internal/ci/azp.py +1 -1
  276. ansible_test/_internal/classification/__init__.py +0 -2
  277. ansible_test/_internal/cli/parsers/key_value_parsers.py +3 -0
  278. ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -1
  279. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  280. ansible_test/_internal/commands/integration/cloud/nios.py +1 -1
  281. ansible_test/_internal/commands/sanity/__init__.py +96 -19
  282. ansible_test/_internal/commands/sanity/pylint.py +20 -24
  283. ansible_test/_internal/completion.py +2 -0
  284. ansible_test/_internal/constants.py +0 -1
  285. ansible_test/_internal/coverage_util.py +1 -2
  286. ansible_test/_internal/docker_util.py +1 -1
  287. ansible_test/_internal/host_configs.py +10 -0
  288. ansible_test/_internal/host_profiles.py +9 -13
  289. ansible_test/_internal/pypi_proxy.py +1 -1
  290. ansible_test/_internal/python_requirements.py +5 -14
  291. ansible_test/_internal/timeout.py +1 -1
  292. ansible_test/_internal/util.py +40 -0
  293. ansible_test/_internal/util_common.py +5 -1
  294. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.json +3 -1
  295. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +6 -3
  296. ansible_test/_util/controller/sanity/code-smell/empty-init.json +0 -2
  297. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +1 -0
  298. ansible_test/_util/controller/sanity/pylint/config/default.cfg +1 -0
  299. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -19
  300. ansible_test/_util/controller/sanity/shellcheck/exclude.txt +1 -0
  301. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +67 -2
  302. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +27 -5
  303. ansible_test/_util/target/cli/ansible_test_cli_stub.py +0 -0
  304. ansible_test/_util/target/common/constants.py +2 -2
  305. ansible_test/_util/target/injector/python.py +5 -0
  306. ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +6 -0
  307. ansible_test/_util/target/sanity/import/importer.py +1 -1
  308. ansible_test/_util/target/setup/bootstrap.sh +6 -17
  309. ansible_test/_util/target/setup/requirements.py +14 -20
  310. ansible_test/config/config.yml +1 -1
  311. ansible_core-2.17.4.data/scripts/ansible-test +0 -44
  312. ansible_test/_data/requirements/sanity.mypy.in +0 -10
  313. ansible_test/_data/requirements/sanity.mypy.txt +0 -18
  314. ansible_test/_internal/commands/sanity/mypy.py +0 -274
  315. ansible_test/_util/controller/sanity/mypy/ansible-core.ini +0 -116
  316. ansible_test/_util/controller/sanity/mypy/ansible-test.ini +0 -27
  317. ansible_test/_util/controller/sanity/mypy/modules.ini +0 -92
  318. ansible_test/_util/controller/sanity/mypy/packaging.ini +0 -20
  319. {ansible_core-2.17.4.dist-info → ansible_core-2.18.0b1.dist-info}/COPYING +0 -0
  320. {ansible_core-2.17.4.dist-info → ansible_core-2.18.0b1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: setuptools (75.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,11 +1,11 @@
1
1
  [console_scripts]
2
2
  ansible = ansible.cli.adhoc:main
3
3
  ansible-config = ansible.cli.config:main
4
- ansible-connection = ansible.cli.scripts.ansible_connection_cli_stub:main
5
4
  ansible-console = ansible.cli.console:main
6
5
  ansible-doc = ansible.cli.doc:main
7
6
  ansible-galaxy = ansible.cli.galaxy:main
8
7
  ansible-inventory = ansible.cli.inventory:main
9
8
  ansible-playbook = ansible.cli.playbook:main
10
9
  ansible-pull = ansible.cli.pull:main
10
+ ansible-test = ansible_test._util.target.cli.ansible_test_cli_stub:main
11
11
  ansible-vault = ansible.cli.vault:main
@@ -0,0 +1,8 @@
1
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
2
+
3
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
4
+
5
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
6
+
7
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
8
+
@@ -1,7 +1,7 @@
1
- base image=quay.io/ansible/base-test-container:6.2.0 python=3.12,3.7,3.8,3.9,3.10,3.11
2
- default image=quay.io/ansible/default-test-container:9.6.0 python=3.12,3.7,3.8,3.9,3.10,3.11 context=collection
3
- default image=quay.io/ansible/ansible-core-test-container:9.6.0 python=3.12,3.7,3.8,3.9,3.10,3.11 context=ansible-core
4
- alpine319 image=quay.io/ansible/alpine319-test-container:7.1.0 python=3.11 cgroup=none audit=none
5
- fedora39 image=quay.io/ansible/fedora39-test-container:7.1.0 python=3.12
6
- ubuntu2004 image=quay.io/ansible/ubuntu2004-test-container:7.1.0 python=3.8
7
- ubuntu2204 image=quay.io/ansible/ubuntu2204-test-container:7.1.0 python=3.10
1
+ base image=quay.io/ansible/base-test-container:7.5.0 python=3.12,3.8,3.9,3.10,3.11,3.13
2
+ default image=quay.io/ansible/default-test-container:10.5.0 python=3.12,3.8,3.9,3.10,3.11,3.13 context=collection
3
+ default image=quay.io/ansible/ansible-core-test-container:10.5.0 python=3.12,3.8,3.9,3.10,3.11,3.13 context=ansible-core
4
+ alpine320 image=quay.io/ansible/alpine320-test-container:8.1.0 python=3.12 cgroup=none audit=none
5
+ fedora40 image=quay.io/ansible/fedora40-test-container:8.1.0 python=3.12
6
+ ubuntu2204 image=quay.io/ansible/ubuntu2204-test-container:8.1.0 python=3.10
7
+ ubuntu2404 image=quay.io/ansible/ubuntu2404-test-container:8.1.0 python=3.12
@@ -1,13 +1,14 @@
1
- alpine/3.19 python=3.11 become=doas_sudo provider=aws arch=x86_64
1
+ alpine/3.20 python=3.12 become=doas_sudo provider=aws arch=x86_64
2
2
  alpine become=doas_sudo provider=aws arch=x86_64
3
- fedora/39 python=3.12 become=sudo provider=aws arch=x86_64
3
+ fedora/40 python=3.12 become=sudo provider=aws arch=x86_64
4
4
  fedora become=sudo provider=aws arch=x86_64
5
5
  freebsd/13.3 python=3.9,3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
6
- freebsd/14.0 python=3.9,3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
6
+ freebsd/14.1 python=3.9,3.11 python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
7
7
  freebsd python_dir=/usr/local/bin become=su_sudo provider=aws arch=x86_64
8
8
  macos/14.3 python=3.11 python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
9
9
  macos python_dir=/usr/local/bin become=sudo provider=parallels arch=x86_64
10
- rhel/9.3 python=3.9,3.11 become=sudo provider=aws arch=x86_64
10
+ rhel/9.4 python=3.9,3.12 become=sudo provider=aws arch=x86_64
11
11
  rhel become=sudo provider=aws arch=x86_64
12
12
  ubuntu/22.04 python=3.10 become=sudo provider=aws arch=x86_64
13
+ ubuntu/24.04 python=3.12 become=sudo provider=aws arch=x86_64
13
14
  ubuntu become=sudo provider=aws arch=x86_64
@@ -1,4 +1,4 @@
1
- windows/2016 provider=aws arch=x86_64
2
- windows/2019 provider=aws arch=x86_64
3
- windows/2022 provider=aws arch=x86_64
4
- windows provider=aws arch=x86_64
1
+ windows/2016 provider=aws arch=x86_64 connection=winrm+http
2
+ windows/2019 provider=aws arch=x86_64 connection=winrm+https
3
+ windows/2022 provider=aws arch=x86_64 connection=winrm+https
4
+ windows provider=aws arch=x86_64 connection=winrm+https
@@ -1,3 +1,2 @@
1
1
  # The test-constraints sanity test verifies this file, but changes must be made manually to keep it in up-to-date.
2
- coverage == 7.3.2 ; python_version >= '3.8' and python_version <= '3.12'
3
- coverage == 6.5.0 ; python_version >= '3.7' and python_version <= '3.7'
2
+ coverage == 7.6.1 ; python_version >= '3.8' and python_version <= '3.13'
@@ -1,8 +1,7 @@
1
1
  # do not add a cryptography or pyopenssl constraint to this file, they require special handling, see get_cryptography_requirements in python_requirements.py
2
2
  # do not add a coverage constraint to this file, it is handled internally by ansible-test
3
3
  pypsrp < 1.0.0 # in case the next major version is too big of a change
4
- pywinrm >= 0.3.0 ; python_version < '3.11' # message encryption support
5
- pywinrm >= 0.4.3 ; python_version >= '3.11' # support for Python 3.11
4
+ pywinrm >= 0.5.0 # support for WSManFaultError and type annotation
6
5
  pytest >= 4.5.0 # pytest 4.5.0 added support for --strict-markers
7
6
  ntlm-auth >= 1.3.0 # message encryption support using cryptography
8
7
  requests-ntlm >= 1.1.0 # message encryption support
@@ -1,5 +1,5 @@
1
1
  # edit "sanity.ansible-doc.in" and generate with: hacking/update-sanity-requirements.py --test ansible-doc
2
- Jinja2==3.1.3
2
+ Jinja2==3.1.4
3
3
  MarkupSafe==2.1.5
4
- packaging==24.0
5
- PyYAML==6.0.1
4
+ packaging==24.1
5
+ PyYAML==6.0.2
@@ -1,2 +1,2 @@
1
1
  rstcheck < 6 # newer versions have too many dependencies
2
- antsibull-changelog
2
+ antsibull-changelog == 0.29.0 # newer versions have additional dependencies
@@ -1,9 +1,9 @@
1
1
  # edit "sanity.changelog.in" and generate with: hacking/update-sanity-requirements.py --test changelog
2
- antsibull-changelog==0.26.0
2
+ antsibull-changelog==0.29.0
3
3
  docutils==0.18.1
4
- packaging==24.0
5
- PyYAML==6.0.1
4
+ packaging==24.1
5
+ PyYAML==6.0.2
6
6
  rstcheck==5.0.0
7
7
  semantic-version==2.10.0
8
8
  types-docutils==0.18.3
9
- typing_extensions==4.10.0
9
+ typing_extensions==4.12.2
@@ -1,4 +1,4 @@
1
1
  # edit "sanity.import.plugin.in" and generate with: hacking/update-sanity-requirements.py --test import.plugin
2
- Jinja2==3.1.3
2
+ Jinja2==3.1.4
3
3
  MarkupSafe==2.1.5
4
- PyYAML==6.0.1
4
+ PyYAML==6.0.2
@@ -1,2 +1,2 @@
1
1
  # edit "sanity.import.in" and generate with: hacking/update-sanity-requirements.py --test import
2
- PyYAML==6.0.1
2
+ PyYAML==6.0.2
@@ -1,2 +1,2 @@
1
1
  # edit "sanity.integration-aliases.in" and generate with: hacking/update-sanity-requirements.py --test integration-aliases
2
- PyYAML==6.0.1
2
+ PyYAML==6.0.2
@@ -1,2 +1,2 @@
1
1
  # edit "sanity.pep8.in" and generate with: hacking/update-sanity-requirements.py --test pep8
2
- pycodestyle==2.11.1
2
+ pycodestyle==2.12.1
@@ -1,11 +1,9 @@
1
1
  # edit "sanity.pylint.in" and generate with: hacking/update-sanity-requirements.py --test pylint
2
- astroid==3.1.0
2
+ astroid==3.2.4
3
3
  dill==0.3.8
4
4
  isort==5.13.2
5
5
  mccabe==0.7.0
6
- platformdirs==4.2.0
7
- pylint==3.1.0
8
- PyYAML==6.0.1
9
- tomli==2.0.1
10
- tomlkit==0.12.4
11
- typing_extensions==4.10.0
6
+ platformdirs==4.3.2
7
+ pylint==3.2.7
8
+ PyYAML==6.0.2
9
+ tomlkit==0.13.2
@@ -1,3 +1,3 @@
1
1
  # edit "sanity.runtime-metadata.in" and generate with: hacking/update-sanity-requirements.py --test runtime-metadata
2
- PyYAML==6.0.1
3
- voluptuous==0.14.2
2
+ PyYAML==6.0.2
3
+ voluptuous==0.15.2
@@ -1,6 +1,6 @@
1
1
  # edit "sanity.validate-modules.in" and generate with: hacking/update-sanity-requirements.py --test validate-modules
2
2
  antsibull-docs-parser==1.0.0
3
- Jinja2==3.1.3
3
+ Jinja2==3.1.4
4
4
  MarkupSafe==2.1.5
5
- PyYAML==6.0.1
6
- voluptuous==0.14.2
5
+ PyYAML==6.0.2
6
+ voluptuous==0.15.2
@@ -1 +1,2 @@
1
+ pyyaml
1
2
  yamllint
@@ -1,4 +1,4 @@
1
1
  # edit "sanity.yamllint.in" and generate with: hacking/update-sanity-requirements.py --test yamllint
2
2
  pathspec==0.12.1
3
- PyYAML==6.0.1
3
+ PyYAML==6.0.2
4
4
  yamllint==1.35.1
@@ -11,10 +11,6 @@ from .constants import (
11
11
  SOFT_RLIMIT_NOFILE,
12
12
  )
13
13
 
14
- from .io import (
15
- write_text_file,
16
- )
17
-
18
14
  from .util import (
19
15
  common_environment,
20
16
  ApplicationError,
@@ -25,7 +21,6 @@ from .util import (
25
21
  ANSIBLE_SOURCE_ROOT,
26
22
  ANSIBLE_TEST_TOOLS_ROOT,
27
23
  MODE_FILE_EXECUTE,
28
- get_ansible_version,
29
24
  raw_command,
30
25
  verified_chmod,
31
26
  )
@@ -115,14 +110,16 @@ def ansible_environment(args: CommonConfig, color: bool = True, ansible_config:
115
110
  # enabled even when not using code coverage to surface warnings when worker processes do not exit cleanly
116
111
  ANSIBLE_WORKER_SHUTDOWN_POLL_COUNT='100',
117
112
  ANSIBLE_WORKER_SHUTDOWN_POLL_DELAY='0.1',
113
+ # ansible-test specific environment variables require an 'ANSIBLE_TEST_' prefix to distinguish them from ansible-core env vars defined by config
114
+ ANSIBLE_TEST_ANSIBLE_LIB_ROOT=ANSIBLE_LIB_ROOT, # used by the coverage injector
118
115
  )
119
116
 
120
117
  if isinstance(args, IntegrationConfig) and args.coverage:
121
- # standard path injection is not effective for ansible-connection, instead the location must be configured
122
- # ansible-connection only requires the injector for code coverage
118
+ # standard path injection is not effective for the persistent connection helper, instead the location must be configured
119
+ # it only requires the injector for code coverage
123
120
  # the correct python interpreter is already selected using the sys.executable used to invoke ansible
124
121
  ansible.update(
125
- ANSIBLE_CONNECTION_PATH=os.path.join(get_injector_path(), 'ansible-connection'),
122
+ _ANSIBLE_CONNECTION_PATH=os.path.join(get_injector_path(), 'ansible_connection_cli_stub.py'),
126
123
  )
127
124
 
128
125
  if isinstance(args, PosixIntegrationConfig):
@@ -249,12 +246,15 @@ def get_cli_path(path: str) -> str:
249
246
  raise RuntimeError(path)
250
247
 
251
248
 
249
+ # noinspection PyUnusedLocal
252
250
  @mutex
253
251
  def get_ansible_python_path(args: CommonConfig) -> str:
254
252
  """
255
253
  Return a directory usable for PYTHONPATH, containing only the ansible package.
256
254
  If a temporary directory is required, it will be cached for the lifetime of the process and cleaned up at exit.
257
255
  """
256
+ del args # not currently used
257
+
258
258
  try:
259
259
  return get_ansible_python_path.python_path # type: ignore[attr-defined]
260
260
  except AttributeError:
@@ -271,38 +271,11 @@ def get_ansible_python_path(args: CommonConfig) -> str:
271
271
 
272
272
  os.symlink(ANSIBLE_LIB_ROOT, os.path.join(python_path, 'ansible'))
273
273
 
274
- if not args.explain:
275
- generate_egg_info(python_path)
276
-
277
274
  get_ansible_python_path.python_path = python_path # type: ignore[attr-defined]
278
275
 
279
276
  return python_path
280
277
 
281
278
 
282
- def generate_egg_info(path: str) -> None:
283
- """Generate an egg-info in the specified base directory."""
284
- # minimal PKG-INFO stub following the format defined in PEP 241
285
- # required for older setuptools versions to avoid a traceback when importing pkg_resources from packages like cryptography
286
- # newer setuptools versions are happy with an empty directory
287
- # including a stub here means we don't need to locate the existing file or run any tools to generate it when running from source
288
- pkg_info = '''
289
- Metadata-Version: 1.0
290
- Name: ansible
291
- Version: %s
292
- Platform: UNKNOWN
293
- Summary: Radically simple IT automation
294
- Author-email: info@ansible.com
295
- License: GPLv3+
296
- ''' % get_ansible_version()
297
-
298
- pkg_info_path = os.path.join(path, 'ansible_core.egg-info', 'PKG-INFO')
299
-
300
- if os.path.exists(pkg_info_path):
301
- return
302
-
303
- write_text_file(pkg_info_path, pkg_info.lstrip(), create_directories=True)
304
-
305
-
306
279
  class CollectionDetail:
307
280
  """Collection detail."""
308
281
 
@@ -221,7 +221,7 @@ class AzurePipelinesChanges:
221
221
  self.diff = []
222
222
 
223
223
  def get_successful_merge_run_commits(self) -> set[str]:
224
- """Return a set of recent successsful merge commits from Azure Pipelines."""
224
+ """Return a set of recent successful merge commits from Azure Pipelines."""
225
225
  parameters = dict(
226
226
  maxBuildsPerDefinition=100, # max 5000
227
227
  queryOrder='queueTimeDescending', # assumes under normal circumstances that later queued jobs are for later commits
@@ -834,8 +834,6 @@ class PathMapper:
834
834
  'MANIFEST.in',
835
835
  'pyproject.toml',
836
836
  'requirements.txt',
837
- 'setup.cfg',
838
- 'setup.py',
839
837
  ):
840
838
  return packaging
841
839
 
@@ -17,6 +17,7 @@ from ...completion import (
17
17
 
18
18
  from ...util import (
19
19
  REMOTE_ARCHITECTURES,
20
+ WINDOWS_CONNECTIONS,
20
21
  )
21
22
 
22
23
  from ...host_configs import (
@@ -177,6 +178,7 @@ class WindowsRemoteKeyValueParser(KeyValueParser):
177
178
  return dict(
178
179
  provider=ChoicesParser(REMOTE_PROVIDERS),
179
180
  arch=ChoicesParser(REMOTE_ARCHITECTURES),
181
+ connection=ChoicesParser(WINDOWS_CONNECTIONS),
180
182
  )
181
183
 
182
184
  def document(self, state: DocumentationState) -> t.Optional[str]:
@@ -186,6 +188,7 @@ class WindowsRemoteKeyValueParser(KeyValueParser):
186
188
  state.sections[f'target {section_name} (comma separated):'] = '\n'.join([
187
189
  f' provider={ChoicesParser(REMOTE_PROVIDERS).document(state)}',
188
190
  f' arch={ChoicesParser(REMOTE_ARCHITECTURES).document(state)}',
191
+ f' connection={ChoicesParser(WINDOWS_CONNECTIONS).document(state)}',
189
192
  ])
190
193
 
191
194
  return f'{{{section_name}}}'
@@ -78,7 +78,7 @@ class HcloudCloudProvider(CloudProvider):
78
78
  self._write_config(config)
79
79
 
80
80
  def _create_ansible_core_ci(self) -> AnsibleCoreCI:
81
- """Return a Heztner instance of AnsibleCoreCI."""
81
+ """Return a Hetzner instance of AnsibleCoreCI."""
82
82
  return AnsibleCoreCI(self.args, CloudResource(platform='hetzner'))
83
83
 
84
84
 
@@ -31,7 +31,7 @@ class HttptesterProvider(CloudProvider):
31
31
  def __init__(self, args: IntegrationConfig) -> None:
32
32
  super().__init__(args)
33
33
 
34
- self.image = os.environ.get('ANSIBLE_HTTP_TEST_CONTAINER', 'quay.io/ansible/http-test-container:2.1.0')
34
+ self.image = os.environ.get('ANSIBLE_HTTP_TEST_CONTAINER', 'quay.io/ansible/http-test-container:3.2.0')
35
35
 
36
36
  self.uses_docker = True
37
37
 
@@ -28,7 +28,7 @@ class NiosProvider(CloudProvider):
28
28
  #
29
29
  # It's source source itself resides at:
30
30
  # https://github.com/ansible/nios-test-container
31
- DOCKER_IMAGE = 'quay.io/ansible/nios-test-container:3.0.0'
31
+ DOCKER_IMAGE = 'quay.io/ansible/nios-test-container:5.0.0'
32
32
 
33
33
  def __init__(self, args: IntegrationConfig) -> None:
34
34
  super().__init__(args)
@@ -209,9 +209,7 @@ def command_sanity(args: SanityConfig) -> None:
209
209
  result.reason = f'Skipping sanity test "{test.name}" on Python {version} because it is unsupported.' \
210
210
  f' Supported Python versions: {", ".join(test.supported_python_versions)}'
211
211
  else:
212
- if isinstance(test, SanityCodeSmellTest):
213
- settings = test.load_processor(args)
214
- elif isinstance(test, SanityMultipleVersion):
212
+ if isinstance(test, SanityMultipleVersion):
215
213
  settings = test.load_processor(args, version)
216
214
  elif isinstance(test, SanitySingleVersion):
217
215
  settings = test.load_processor(args)
@@ -327,7 +325,7 @@ def collect_code_smell_tests() -> tuple[SanityTest, ...]:
327
325
  skip_tests = read_lines_without_comments(os.path.join(ansible_code_smell_root, 'skip.txt'), remove_blank_lines=True, optional=True)
328
326
  paths.extend(path for path in glob.glob(os.path.join(ansible_code_smell_root, '*.py')) if os.path.basename(path) not in skip_tests)
329
327
 
330
- tests = tuple(SanityCodeSmellTest(p) for p in paths)
328
+ tests = tuple(SanityScript.create(p) for p in paths)
331
329
 
332
330
  return tests
333
331
 
@@ -829,21 +827,34 @@ class SanitySingleVersion(SanityTest, metaclass=abc.ABCMeta):
829
827
  return SanityIgnoreProcessor(args, self, None)
830
828
 
831
829
 
832
- class SanityCodeSmellTest(SanitySingleVersion):
833
- """Sanity test script."""
830
+ class SanityScript(SanityTest, metaclass=abc.ABCMeta):
831
+ """Base class for sanity test scripts."""
834
832
 
835
- def __init__(self, path) -> None:
833
+ @classmethod
834
+ def create(cls, path: str) -> SanityScript:
835
+ """Create and return a SanityScript instance from the given path."""
836
836
  name = os.path.splitext(os.path.basename(path))[0]
837
837
  config_path = os.path.splitext(path)[0] + '.json'
838
838
 
839
+ if os.path.exists(config_path):
840
+ config = read_json_file(config_path)
841
+ else:
842
+ config = None
843
+
844
+ instance: SanityScript
845
+
846
+ if config.get('multi_version'):
847
+ instance = SanityScriptMultipleVersion(name=name, path=path, config=config)
848
+ else:
849
+ instance = SanityScriptSingleVersion(name=name, path=path, config=config)
850
+
851
+ return instance
852
+
853
+ def __init__(self, name: str, path: str, config: dict[str, t.Any] | None) -> None:
839
854
  super().__init__(name=name)
840
855
 
841
856
  self.path = path
842
- self.config_path = config_path if os.path.exists(config_path) else None
843
- self.config = None
844
-
845
- if self.config_path:
846
- self.config = read_json_file(self.config_path)
857
+ self.config = config
847
858
 
848
859
  if self.config:
849
860
  self.enabled = not self.config.get('disabled')
@@ -854,6 +865,8 @@ class SanityCodeSmellTest(SanitySingleVersion):
854
865
  self.files: list[str] = self.config.get('files')
855
866
  self.text: t.Optional[bool] = self.config.get('text')
856
867
  self.ignore_self: bool = self.config.get('ignore_self')
868
+ self.controller_only: bool = self.config.get('controller_only')
869
+ self.min_max_python_only: bool = self.config.get('min_max_python_only')
857
870
  self.minimum_python_version: t.Optional[str] = self.config.get('minimum_python_version')
858
871
  self.maximum_python_version: t.Optional[str] = self.config.get('maximum_python_version')
859
872
 
@@ -869,6 +882,8 @@ class SanityCodeSmellTest(SanitySingleVersion):
869
882
  self.files = []
870
883
  self.text = None
871
884
  self.ignore_self = False
885
+ self.controller_only = False
886
+ self.min_max_python_only = False
872
887
  self.minimum_python_version = None
873
888
  self.maximum_python_version = None
874
889
 
@@ -925,12 +940,18 @@ class SanityCodeSmellTest(SanitySingleVersion):
925
940
  """A tuple of supported Python versions or None if the test does not depend on specific Python versions."""
926
941
  versions = super().supported_python_versions
927
942
 
943
+ if self.controller_only:
944
+ versions = tuple(version for version in versions if version in CONTROLLER_PYTHON_VERSIONS)
945
+
928
946
  if self.minimum_python_version:
929
947
  versions = tuple(version for version in versions if str_to_version(version) >= str_to_version(self.minimum_python_version))
930
948
 
931
949
  if self.maximum_python_version:
932
950
  versions = tuple(version for version in versions if str_to_version(version) <= str_to_version(self.maximum_python_version))
933
951
 
952
+ if self.min_max_python_only:
953
+ versions = versions[0], versions[-1]
954
+
934
955
  return versions
935
956
 
936
957
  def filter_targets(self, targets: list[TestTarget]) -> list[TestTarget]:
@@ -960,17 +981,29 @@ class SanityCodeSmellTest(SanitySingleVersion):
960
981
 
961
982
  return targets
962
983
 
963
- def test(self, args: SanityConfig, targets: SanityTargets, python: PythonConfig) -> TestResult:
984
+ def test_script(self, args: SanityConfig, targets: SanityTargets, virtualenv_python: PythonConfig, python: PythonConfig) -> TestResult:
964
985
  """Run the sanity test and return the result."""
965
- cmd = [python.path, self.path]
986
+ cmd = [virtualenv_python.path, self.path]
966
987
 
967
988
  env = ansible_environment(args, color=False)
968
- env.update(PYTHONUTF8='1') # force all code-smell sanity tests to run with Python UTF-8 Mode enabled
989
+
990
+ env.update(
991
+ PYTHONUTF8='1', # force all code-smell sanity tests to run with Python UTF-8 Mode enabled
992
+ ANSIBLE_TEST_TARGET_PYTHON_VERSION=python.version,
993
+ ANSIBLE_TEST_CONTROLLER_PYTHON_VERSIONS=','.join(CONTROLLER_PYTHON_VERSIONS),
994
+ ANSIBLE_TEST_REMOTE_ONLY_PYTHON_VERSIONS=','.join(REMOTE_ONLY_PYTHON_VERSIONS),
995
+ )
996
+
997
+ if self.min_max_python_only:
998
+ min_python, max_python = self.supported_python_versions
999
+
1000
+ env.update(ANSIBLE_TEST_MIN_PYTHON=min_python)
1001
+ env.update(ANSIBLE_TEST_MAX_PYTHON=max_python)
969
1002
 
970
1003
  pattern = None
971
1004
  data = None
972
1005
 
973
- settings = self.load_processor(args)
1006
+ settings = self.conditionally_load_processor(args, python.version)
974
1007
 
975
1008
  paths = [target.path for target in targets.include]
976
1009
 
@@ -991,7 +1024,7 @@ class SanityCodeSmellTest(SanitySingleVersion):
991
1024
  display.info(data, verbosity=4)
992
1025
 
993
1026
  try:
994
- stdout, stderr = intercept_python(args, python, cmd, data=data, env=env, capture=True)
1027
+ stdout, stderr = intercept_python(args, virtualenv_python, cmd, data=data, env=env, capture=True)
995
1028
  status = 0
996
1029
  except SubprocessError as ex:
997
1030
  stdout = ex.stdout
@@ -1031,9 +1064,9 @@ class SanityCodeSmellTest(SanitySingleVersion):
1031
1064
 
1032
1065
  return SanitySuccess(self.name)
1033
1066
 
1034
- def load_processor(self, args: SanityConfig) -> SanityIgnoreProcessor:
1067
+ @abc.abstractmethod
1068
+ def conditionally_load_processor(self, args: SanityConfig, python_version: str) -> SanityIgnoreProcessor:
1035
1069
  """Load the ignore processor for this sanity test."""
1036
- return SanityIgnoreProcessor(args, self, None)
1037
1070
 
1038
1071
 
1039
1072
  class SanityVersionNeutral(SanityTest, metaclass=abc.ABCMeta):
@@ -1094,6 +1127,50 @@ class SanityMultipleVersion(SanityTest, metaclass=abc.ABCMeta):
1094
1127
  return targets
1095
1128
 
1096
1129
 
1130
+ class SanityScriptSingleVersion(SanityScript, SanitySingleVersion):
1131
+ """External sanity test script which should run on a single python version."""
1132
+
1133
+ def test(self, args: SanityConfig, targets: SanityTargets, python: PythonConfig) -> TestResult:
1134
+ """Run the sanity test and return the result."""
1135
+ return super().test_script(args, targets, python, python)
1136
+
1137
+ def conditionally_load_processor(self, args: SanityConfig, python_version: str) -> SanityIgnoreProcessor:
1138
+ """Load the ignore processor for this sanity test."""
1139
+ return SanityIgnoreProcessor(args, self, None)
1140
+
1141
+
1142
+ class SanityScriptMultipleVersion(SanityScript, SanityMultipleVersion):
1143
+ """External sanity test script which should run on multiple python versions."""
1144
+
1145
+ def test(self, args: SanityConfig, targets: SanityTargets, python: PythonConfig) -> TestResult:
1146
+ """Run the sanity test and return the result."""
1147
+ multi_version = self.config['multi_version']
1148
+
1149
+ if multi_version == 'controller':
1150
+ virtualenv_python_config = args.controller_python
1151
+ elif multi_version == 'target':
1152
+ virtualenv_python_config = python
1153
+ else:
1154
+ raise NotImplementedError(f'{multi_version=}')
1155
+
1156
+ virtualenv_python = create_sanity_virtualenv(args, virtualenv_python_config, self.name)
1157
+
1158
+ if not virtualenv_python:
1159
+ result = SanitySkipped(self.name, python.version)
1160
+ result.reason = f'Skipping sanity test "{self.name}" due to missing virtual environment support on Python {virtualenv_python_config.version}.'
1161
+
1162
+ return result
1163
+
1164
+ if args.prime_venvs:
1165
+ return SanitySkipped(self.name, python.version)
1166
+
1167
+ return super().test_script(args, targets, virtualenv_python, python)
1168
+
1169
+ def conditionally_load_processor(self, args: SanityConfig, python_version: str) -> SanityIgnoreProcessor:
1170
+ """Load the ignore processor for this sanity test."""
1171
+ return SanityIgnoreProcessor(args, self, python_version)
1172
+
1173
+
1097
1174
  @cache
1098
1175
  def sanity_get_tests() -> tuple[SanityTest, ...]:
1099
1176
  """Return a tuple of the available sanity tests."""