ansible-core 2.17.6__py3-none-any.whl → 2.18.0__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 (325) 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 +3 -49
  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 +8 -8
  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 +54 -29
  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/plugins/__init__.py +2 -0
  192. ansible/plugins/action/__init__.py +7 -9
  193. ansible/plugins/action/dnf.py +7 -5
  194. ansible/plugins/action/package.py +5 -4
  195. ansible/plugins/action/reboot.py +2 -2
  196. ansible/plugins/become/__init__.py +1 -1
  197. ansible/plugins/callback/__init__.py +44 -3
  198. ansible/plugins/callback/default.py +1 -1
  199. ansible/plugins/cliconf/__init__.py +1 -1
  200. ansible/plugins/connection/paramiko_ssh.py +2 -80
  201. ansible/plugins/connection/psrp.py +33 -82
  202. ansible/plugins/connection/ssh.py +0 -8
  203. ansible/plugins/connection/winrm.py +46 -1
  204. ansible/plugins/doc_fragments/connection_pipelining.py +2 -2
  205. ansible/plugins/doc_fragments/constructed.py +10 -10
  206. ansible/plugins/doc_fragments/default_callback.py +8 -8
  207. ansible/plugins/doc_fragments/files.py +5 -5
  208. ansible/plugins/doc_fragments/inventory_cache.py +2 -2
  209. ansible/plugins/doc_fragments/result_format_callback.py +6 -6
  210. ansible/plugins/doc_fragments/return_common.py +1 -1
  211. ansible/plugins/doc_fragments/shell_common.py +2 -10
  212. ansible/plugins/doc_fragments/shell_windows.py +0 -9
  213. ansible/plugins/doc_fragments/url.py +2 -2
  214. ansible/plugins/doc_fragments/url_windows.py +4 -5
  215. ansible/plugins/doc_fragments/validate.py +1 -1
  216. ansible/plugins/filter/core.py +2 -0
  217. ansible/plugins/filter/human_to_bytes.yml +9 -0
  218. ansible/plugins/filter/password_hash.yml +1 -1
  219. ansible/plugins/filter/strftime.yml +1 -1
  220. ansible/plugins/filter/to_nice_json.yml +7 -3
  221. ansible/plugins/filter/to_uuid.yml +1 -1
  222. ansible/plugins/inventory/script.py +1 -1
  223. ansible/plugins/list.py +1 -1
  224. ansible/plugins/loader.py +0 -11
  225. ansible/plugins/lookup/config.py +1 -1
  226. ansible/plugins/lookup/csvfile.py +21 -9
  227. ansible/plugins/lookup/env.py +8 -9
  228. ansible/plugins/lookup/ini.py +10 -1
  229. ansible/plugins/lookup/random_choice.py +2 -2
  230. ansible/plugins/lookup/url.py +7 -2
  231. ansible/plugins/shell/__init__.py +15 -20
  232. ansible/plugins/shell/powershell.py +9 -6
  233. ansible/plugins/strategy/__init__.py +16 -7
  234. ansible/plugins/test/core.py +23 -1
  235. ansible/plugins/test/issubset.yml +1 -1
  236. ansible/plugins/test/subset.yml +1 -1
  237. ansible/plugins/test/timedout.yml +20 -0
  238. ansible/plugins/test/vault_encrypted.yml +6 -6
  239. ansible/plugins/test/vaulted_file.yml +19 -0
  240. ansible/release.py +2 -2
  241. ansible/template/__init__.py +3 -8
  242. ansible/utils/collection_loader/_collection_finder.py +23 -55
  243. ansible/utils/display.py +44 -31
  244. ansible/utils/jsonrpc.py +1 -1
  245. ansible/utils/listify.py +1 -5
  246. ansible/utils/path.py +3 -0
  247. ansible/utils/vars.py +18 -27
  248. ansible/vars/manager.py +7 -150
  249. ansible/vars/plugins.py +1 -1
  250. ansible_core-2.18.0.dist-info/Apache-License.txt +202 -0
  251. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/METADATA +36 -23
  252. ansible_core-2.18.0.dist-info/MIT-license.txt +14 -0
  253. ansible_core-2.18.0.dist-info/PSF-license.txt +48 -0
  254. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/RECORD +316 -311
  255. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/entry_points.txt +1 -1
  256. ansible_core-2.18.0.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 +6 -8
  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/encoding.py +4 -4
  288. ansible_test/_internal/host_configs.py +10 -0
  289. ansible_test/_internal/host_profiles.py +9 -13
  290. ansible_test/_internal/pypi_proxy.py +1 -1
  291. ansible_test/_internal/python_requirements.py +5 -14
  292. ansible_test/_internal/timeout.py +1 -1
  293. ansible_test/_internal/util.py +40 -0
  294. ansible_test/_internal/util_common.py +5 -1
  295. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.json +3 -1
  296. ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +6 -3
  297. ansible_test/_util/controller/sanity/code-smell/empty-init.json +0 -2
  298. ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +5 -0
  299. ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +5 -0
  300. ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +5 -0
  301. ansible_test/_util/controller/sanity/pylint/config/collection.cfg +6 -0
  302. ansible_test/_util/controller/sanity/pylint/config/default.cfg +6 -0
  303. ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -19
  304. ansible_test/_util/controller/sanity/shellcheck/exclude.txt +1 -0
  305. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +67 -2
  306. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +27 -5
  307. ansible_test/_util/target/cli/ansible_test_cli_stub.py +0 -0
  308. ansible_test/_util/target/common/constants.py +2 -2
  309. ansible_test/_util/target/injector/python.py +5 -0
  310. ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +6 -0
  311. ansible_test/_util/target/sanity/import/importer.py +1 -1
  312. ansible_test/_util/target/setup/bootstrap.sh +6 -17
  313. ansible_test/_util/target/setup/requirements.py +18 -24
  314. ansible_test/config/config.yml +1 -1
  315. ansible_core-2.17.6.data/scripts/ansible-test +0 -44
  316. ansible_test/_data/requirements/sanity.mypy.in +0 -10
  317. ansible_test/_data/requirements/sanity.mypy.txt +0 -18
  318. ansible_test/_internal/commands/sanity/mypy.py +0 -274
  319. ansible_test/_util/controller/sanity/mypy/ansible-core.ini +0 -116
  320. ansible_test/_util/controller/sanity/mypy/ansible-test.ini +0 -27
  321. ansible_test/_util/controller/sanity/mypy/modules.ini +0 -92
  322. ansible_test/_util/controller/sanity/mypy/packaging.ini +0 -20
  323. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/COPYING +0 -0
  324. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/WHEEL +0 -0
  325. {ansible_core-2.17.6.dist-info → ansible_core-2.18.0.dist-info}/top_level.txt +0 -0
@@ -838,6 +838,46 @@ class ModuleValidator(Validator):
838
838
  msg='%s: %s' % (combined_path, error_message)
839
839
  )
840
840
 
841
+ def _validate_option_docs(self, options, context=None):
842
+ if not isinstance(options, dict):
843
+ return
844
+ if context is None:
845
+ context = []
846
+
847
+ normalized_option_alias_names = dict()
848
+
849
+ def add_option_alias_name(name, option_name):
850
+ normalized_name = str(name).lower()
851
+ normalized_option_alias_names.setdefault(normalized_name, {}).setdefault(option_name, set()).add(name)
852
+
853
+ for option, data in options.items():
854
+ if 'suboptions' in data:
855
+ self._validate_option_docs(data.get('suboptions'), context + [option])
856
+ add_option_alias_name(option, option)
857
+ if 'aliases' in data and isinstance(data['aliases'], list):
858
+ for alias in data['aliases']:
859
+ add_option_alias_name(alias, option)
860
+
861
+ for normalized_name, options in normalized_option_alias_names.items():
862
+ if len(options) < 2:
863
+ continue
864
+
865
+ what = []
866
+ for option_name, names in sorted(options.items()):
867
+ if option_name in names:
868
+ what.append("option '%s'" % option_name)
869
+ else:
870
+ what.append("alias '%s' of option '%s'" % (sorted(names)[0], option_name))
871
+ msg = "Multiple options/aliases"
872
+ if context:
873
+ msg += " found in %s" % " -> ".join(context)
874
+ msg += " are equal up to casing: %s" % ", ".join(what)
875
+ self.reporter.error(
876
+ path=self.object_path,
877
+ code='option-equal-up-to-casing',
878
+ msg=msg,
879
+ )
880
+
841
881
  def _validate_docs(self):
842
882
  doc = None
843
883
  # We have three ways of marking deprecated/removed files. Have to check each one
@@ -1015,6 +1055,9 @@ class ModuleValidator(Validator):
1015
1055
  'invalid-documentation',
1016
1056
  )
1017
1057
 
1058
+ if doc:
1059
+ self._validate_option_docs(doc.get('options'))
1060
+
1018
1061
  self._validate_all_semantic_markup(doc, returns)
1019
1062
 
1020
1063
  if not self.collection:
@@ -1235,7 +1278,7 @@ class ModuleValidator(Validator):
1235
1278
  self._validate_semantic_markup(entry.get(key))
1236
1279
 
1237
1280
  if isinstance(docs.get('deprecated'), dict):
1238
- for key in ('why', 'alternative'):
1281
+ for key in ('why', 'alternative', 'alternatives'):
1239
1282
  self._validate_semantic_markup(docs.get('deprecated').get(key))
1240
1283
 
1241
1284
  self._validate_semantic_markup_options(docs.get('options'))
@@ -1876,8 +1919,10 @@ class ModuleValidator(Validator):
1876
1919
  if len(doc_options_args) == 0:
1877
1920
  # Undocumented arguments will be handled later (search for undocumented-parameter)
1878
1921
  doc_options_arg = {}
1922
+ doc_option_name = None
1879
1923
  else:
1880
- doc_options_arg = doc_options[doc_options_args[0]]
1924
+ doc_option_name = doc_options_args[0]
1925
+ doc_options_arg = doc_options[doc_option_name]
1881
1926
  if len(doc_options_args) > 1:
1882
1927
  msg = "Argument '%s' in argument_spec" % arg
1883
1928
  if context:
@@ -1892,6 +1937,26 @@ class ModuleValidator(Validator):
1892
1937
  msg=msg
1893
1938
  )
1894
1939
 
1940
+ all_aliases = set(aliases + [arg])
1941
+ all_docs_aliases = set(
1942
+ ([doc_option_name] if doc_option_name is not None else [])
1943
+ +
1944
+ (doc_options_arg['aliases'] if isinstance(doc_options_arg.get('aliases'), list) else [])
1945
+ )
1946
+ if all_docs_aliases and all_aliases != all_docs_aliases:
1947
+ msg = "Argument '%s' in argument_spec" % arg
1948
+ if context:
1949
+ msg += " found in %s" % " -> ".join(context)
1950
+ msg += " has names %s, but its documentation has names %s" % (
1951
+ ", ".join([("'%s'" % alias) for alias in sorted(all_aliases)]),
1952
+ ", ".join([("'%s'" % alias) for alias in sorted(all_docs_aliases)])
1953
+ )
1954
+ self.reporter.error(
1955
+ path=self.object_path,
1956
+ code='parameter-documented-aliases-differ',
1957
+ msg=msg
1958
+ )
1959
+
1895
1960
  try:
1896
1961
  doc_default = None
1897
1962
  if 'default' in doc_options_arg and doc_options_arg['default'] is not None:
@@ -84,6 +84,22 @@ def date(error_code=None):
84
84
  return Any(isodate, error_code=error_code)
85
85
 
86
86
 
87
+ def require_only_one(keys):
88
+ def f(obj):
89
+ found = None
90
+ for k in obj.keys():
91
+ if k in keys:
92
+ if found is None:
93
+ found = k
94
+ else:
95
+ raise Invalid('Found conflicting keys, must contain only one of {}'.format(keys))
96
+ if found is None:
97
+ raise Invalid('Must contain one of {}'.format(keys))
98
+
99
+ return obj
100
+ return f
101
+
102
+
87
103
  # Roles can also be referenced by semantic markup
88
104
  _VALID_PLUGIN_TYPES = set(DOCUMENTABLE_PLUGINS + ('role', ))
89
105
 
@@ -568,7 +584,9 @@ def list_dict_option_schema(for_collection, plugin_type):
568
584
  {
569
585
  # This definition makes sure everything has the correct types/values
570
586
  'why': doc_string,
571
- 'alternatives': doc_string,
587
+ # TODO: phase out either plural or singular, 'alt' is exclusive group
588
+ Exclusive('alternative', 'alt'): doc_string,
589
+ Exclusive('alternatives', 'alt'): doc_string,
572
590
  # vod stands for 'version or date'; this is the name of the exclusive group
573
591
  Exclusive('removed_at_date', 'vod'): date(),
574
592
  Exclusive('version', 'vod'): version(for_collection),
@@ -577,7 +595,7 @@ def list_dict_option_schema(for_collection, plugin_type):
577
595
  {
578
596
  # This definition makes sure that everything we require is there
579
597
  Required('why'): Any(*string_types),
580
- 'alternatives': Any(*string_types),
598
+ Required(Any('alternatives', 'alternative')): Any(*string_types),
581
599
  Required(Any('removed_at_date', 'version')): Any(*string_types),
582
600
  Required('collection_name'): Any(*string_types),
583
601
  },
@@ -761,13 +779,16 @@ def return_schema(for_collection, plugin_type='module'):
761
779
 
762
780
 
763
781
  def deprecation_schema(for_collection):
782
+
764
783
  main_fields = {
765
784
  Required('why'): doc_string,
766
- Required('alternative'): doc_string,
767
- Required('removed_from_collection'): collection_name,
768
- 'removed': Any(True),
785
+ 'alternative': doc_string,
786
+ 'alternatives': doc_string,
769
787
  }
770
788
 
789
+ if for_collection:
790
+ main_fields.update({Required('removed_from_collection'): collection_name, 'removed': Any(True)})
791
+
771
792
  date_schema = {
772
793
  Required('removed_at_date'): date(),
773
794
  }
@@ -791,6 +812,7 @@ def deprecation_schema(for_collection):
791
812
  if for_collection:
792
813
  result = All(
793
814
  result,
815
+ require_only_one(['alternative', 'alternatives']),
794
816
  partial(check_removal_version,
795
817
  version_field='removed_in',
796
818
  collection_name_field='removed_from_collection',
File without changes
@@ -5,13 +5,13 @@
5
5
  from __future__ import annotations
6
6
 
7
7
  REMOTE_ONLY_PYTHON_VERSIONS = (
8
- '3.7',
9
8
  '3.8',
10
9
  '3.9',
10
+ '3.10',
11
11
  )
12
12
 
13
13
  CONTROLLER_PYTHON_VERSIONS = (
14
- '3.10',
15
14
  '3.11',
16
15
  '3.12',
16
+ '3.13',
17
17
  )
@@ -6,12 +6,15 @@ import importlib.util
6
6
  import os
7
7
  import sys
8
8
 
9
+ NETWORKING_CLI_STUB_SCRIPT = 'ansible_connection_cli_stub.py'
10
+
9
11
 
10
12
  def main():
11
13
  """Main entry point."""
12
14
  name = os.path.basename(__file__)
13
15
  args = [sys.executable]
14
16
 
17
+ ansible_lib_root = os.environ.get('ANSIBLE_TEST_ANSIBLE_LIB_ROOT')
15
18
  coverage_config = os.environ.get('COVERAGE_CONF')
16
19
  coverage_output = os.environ.get('COVERAGE_FILE')
17
20
 
@@ -33,6 +36,8 @@ def main():
33
36
  args += ['-m', 'pytest']
34
37
  elif name == 'importer.py':
35
38
  args += [find_program(name, False)]
39
+ elif name == NETWORKING_CLI_STUB_SCRIPT:
40
+ args += [os.path.join(ansible_lib_root, 'cli/scripts', NETWORKING_CLI_STUB_SCRIPT)]
36
41
  else:
37
42
  args += [find_program(name, True)]
38
43
 
@@ -53,6 +53,12 @@ def pytest_configure():
53
53
 
54
54
  def coverage_exit(*args, **kwargs):
55
55
  for instance in coverage_instances:
56
+ # skip coverage instances which have no collector, or the collector is not the active collector
57
+ # this avoids issues with coverage 7.4.0+ when tests create subprocesses which inherit our overridden os._exit method
58
+ # pylint: disable=protected-access
59
+ if not instance._collector or not instance._collector._collectors or instance._collector != instance._collector._collectors[-1]:
60
+ continue
61
+
56
62
  instance.stop()
57
63
  instance.save()
58
64
 
@@ -159,7 +159,7 @@ def main():
159
159
  loader = self._get_loader(fullname, path=path)
160
160
  if loader is not None:
161
161
  if has_py3_loader:
162
- # loader is expected to be Optional[importlib.abc.Loader], but RestrictedModuleLoader does not inherit from importlib.abc.Loder
162
+ # loader is expected to be Optional[importlib.abc.Loader], but RestrictedModuleLoader does not inherit from importlib.abc.Loader
163
163
  return spec_from_loader(fullname, loader) # type: ignore[arg-type]
164
164
  raise ImportError("Failed to import '%s' due to a bug in ansible-test. Check importlib imports for typos." % fullname)
165
165
  return None
@@ -53,14 +53,14 @@ customize_bashrc()
53
53
  fi
54
54
 
55
55
  # Improve shell prompts for interactive use.
56
- echo "export PS1='\[\e]0;\u@\h: \w\a\]\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '" >> ~/.bashrc
56
+ echo "export PS1='"'\[\e]0;\u@\h: \w\a\]\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '"'" >> ~/.bashrc
57
57
  }
58
58
 
59
59
  install_pip() {
60
60
  if ! "${python_interpreter}" -m pip.__main__ --version --disable-pip-version-check 2>/dev/null; then
61
61
  case "${python_version}" in
62
62
  *)
63
- pip_bootstrap_url="https://ci-files.testing.ansible.com/ansible-test/get-pip-23.1.2.py"
63
+ pip_bootstrap_url="https://ci-files.testing.ansible.com/ansible-test/get-pip-24.0.py"
64
64
  ;;
65
65
  esac
66
66
 
@@ -176,10 +176,10 @@ bootstrap_remote_freebsd()
176
176
  cryptography_pkg="" # not available
177
177
  pyyaml_pkg="" # not available
178
178
  ;;
179
- 14.0/3.9)
179
+ 14.1/3.9)
180
180
  # defaults above 'just work'TM
181
181
  ;;
182
- 14.0/3.11)
182
+ 14.1/3.11)
183
183
  cryptography_pkg="" # not available
184
184
  jinja2_pkg="" # not available
185
185
  pyyaml_pkg="" # not available
@@ -268,19 +268,12 @@ bootstrap_remote_rhel_9()
268
268
  packages="
269
269
  gcc
270
270
  ${py_pkg_prefix}-devel
271
+ ${py_pkg_prefix}-pip
271
272
  "
272
273
 
273
- # pip is not included in the Python devel package under Python 3.11
274
- if [ "${python_version}" != "3.9" ]; then
275
- packages="
276
- ${packages}
277
- ${py_pkg_prefix}-pip
278
- "
279
- fi
280
-
281
274
  # Jinja2 is not installed with an OS package since the provided version is too old.
282
275
  # Instead, ansible-test will install it using pip.
283
- # packaging and resolvelib are missing for Python 3.11 (and possible later) so we just
276
+ # packaging and resolvelib are missing for controller supported Python versions, so we just
284
277
  # skip them and let ansible-test install them from PyPI.
285
278
  if [ "${controller}" ]; then
286
279
  packages="
@@ -329,10 +322,6 @@ bootstrap_remote_ubuntu()
329
322
  # For these ansible-test will use pip to install the requirements instead.
330
323
  # Only the platform is checked since Ubuntu shares Python packages across Python versions.
331
324
  case "${platform_version}" in
332
- "20.04")
333
- jinja2_pkg="" # too old
334
- resolvelib_pkg="" # not available
335
- ;;
336
325
  esac
337
326
 
338
327
  packages="
@@ -22,27 +22,13 @@ import errno
22
22
  import io
23
23
  import json
24
24
  import os
25
+ import shlex
25
26
  import shutil
26
27
  import subprocess
27
28
  import sys
28
29
  import tempfile
29
-
30
- try:
31
- import typing as t
32
- except ImportError:
33
- t = None
34
-
35
- try:
36
- from shlex import quote as cmd_quote
37
- except ImportError:
38
- # noinspection PyProtectedMember
39
- from pipes import quote as cmd_quote
40
-
41
- try:
42
- from urllib.request import urlopen
43
- except ImportError:
44
- # noinspection PyCompatibility,PyUnresolvedReferences
45
- from urllib2 import urlopen # pylint: disable=ansible-bad-import-from
30
+ import typing as t
31
+ import urllib.request
46
32
 
47
33
  ENCODING = 'utf-8'
48
34
 
@@ -80,6 +66,8 @@ def bootstrap(pip, options): # type: (str, t.Dict[str, t.Any]) -> None
80
66
  """Bootstrap pip and related packages in an empty virtual environment."""
81
67
  pip_version = options['pip_version']
82
68
  packages = options['packages']
69
+ setuptools = options['setuptools']
70
+ wheel = options['wheel']
83
71
 
84
72
  url = 'https://ci-files.testing.ansible.com/ansible-test/get-pip-%s.py' % pip_version
85
73
  cache_path = os.path.expanduser('~/.ansible/test/cache/get_pip_%s.py' % pip_version.replace(".", "_"))
@@ -115,6 +103,12 @@ https://github.com/ansible/ansible/issues/77304
115
103
  options = common_pip_options()
116
104
  options.extend(packages)
117
105
 
106
+ if not setuptools:
107
+ options.append('--no-setuptools')
108
+
109
+ if not wheel:
110
+ options.append('--no-wheel')
111
+
118
112
  command = [sys.executable, pip] + options
119
113
 
120
114
  execute_command(command, env=env)
@@ -280,7 +274,7 @@ def devnull(): # type: () -> t.IO[bytes]
280
274
  def download_file(url, path): # type: (str, str) -> None
281
275
  """Download the given URL to the specified file path."""
282
276
  with open(to_bytes(path), 'wb') as saved_file:
283
- with contextlib.closing(urlopen(url)) as download:
277
+ with contextlib.closing(urllib.request.urlopen(url)) as download:
284
278
  shutil.copyfileobj(download, saved_file)
285
279
 
286
280
 
@@ -291,7 +285,7 @@ class ApplicationError(Exception):
291
285
  class SubprocessError(ApplicationError):
292
286
  """A command returned a non-zero status."""
293
287
  def __init__(self, cmd, status, stdout, stderr): # type: (t.List[str], int, str, str) -> None
294
- message = 'A command failed with status %d: %s' % (status, ' '.join(cmd_quote(c) for c in cmd))
288
+ message = 'A command failed with status %d: %s' % (status, shlex.join(cmd))
295
289
 
296
290
  if stderr:
297
291
  message += '\n>>> Standard Error\n%s' % stderr.strip()
@@ -313,7 +307,7 @@ def log(message, verbosity=0): # type: (str, int) -> None
313
307
 
314
308
  def execute_command(cmd, cwd=None, capture=False, env=None): # type: (t.List[str], t.Optional[str], bool, t.Optional[t.Dict[str, str]]) -> None
315
309
  """Execute the specified command."""
316
- log('Execute command: %s' % ' '.join(cmd_quote(c) for c in cmd), verbosity=1)
310
+ log('Execute command: %s' % shlex.join(cmd), verbosity=1)
317
311
 
318
312
  cmd_bytes = [to_bytes(c) for c in cmd]
319
313
 
@@ -369,17 +363,17 @@ def open_binary_file(path, mode='rb'): # type: (str, str) -> t.IO[bytes]
369
363
  return io.open(to_bytes(path), mode) # pylint: disable=consider-using-with,unspecified-encoding
370
364
 
371
365
 
372
- def to_optional_bytes(value, errors='strict'): # type: (t.Optional[t.AnyStr], str) -> t.Optional[bytes]
366
+ def to_optional_bytes(value, errors='strict'): # type: (t.Optional[str | bytes], str) -> t.Optional[bytes]
373
367
  """Return the given value as bytes encoded using UTF-8 if not already bytes, or None if the value is None."""
374
368
  return None if value is None else to_bytes(value, errors)
375
369
 
376
370
 
377
- def to_optional_text(value, errors='strict'): # type: (t.Optional[t.AnyStr], str) -> t.Optional[t.Text]
371
+ def to_optional_text(value, errors='strict'): # type: (t.Optional[str | bytes], str) -> t.Optional[t.Text]
378
372
  """Return the given value as text decoded using UTF-8 if not already text, or None if the value is None."""
379
373
  return None if value is None else to_text(value, errors)
380
374
 
381
375
 
382
- def to_bytes(value, errors='strict'): # type: (t.AnyStr, str) -> bytes
376
+ def to_bytes(value, errors='strict'): # type: (str | bytes, str) -> bytes
383
377
  """Return the given value as bytes encoded using UTF-8 if not already bytes."""
384
378
  if isinstance(value, bytes):
385
379
  return value
@@ -390,7 +384,7 @@ def to_bytes(value, errors='strict'): # type: (t.AnyStr, str) -> bytes
390
384
  raise Exception('value is not bytes or text: %s' % type(value))
391
385
 
392
386
 
393
- def to_text(value, errors='strict'): # type: (t.AnyStr, str) -> t.Text
387
+ def to_text(value, errors='strict'): # type: (str | bytes, str) -> t.Text
394
388
  """Return the given value as text decoded using UTF-8 if not already text."""
395
389
  if isinstance(value, bytes):
396
390
  return value.decode(ENCODING, errors)
@@ -17,7 +17,7 @@ modules:
17
17
  # This is the default value if no configuration is provided.
18
18
  # - 'controller' - All Python versions supported by the Ansible controller.
19
19
  # This indicates the modules/module_utils can only run on the controller.
20
- # Intended for use only with modules/module_utils that depend on ansible-connection, which only runs on the controller.
20
+ # Intended for use only with modules/module_utils that depend on the Ansible persistent connection helper, which only runs on the controller.
21
21
  # Unit tests for modules/module_utils will be permitted to import any Ansible code, instead of only module_utils.
22
22
  # - SpecifierSet - A PEP 440 specifier set indicating the supported Python versions.
23
23
  # This is only needed when modules/module_utils do not support all Python versions supported by Ansible.
@@ -1,44 +0,0 @@
1
- #!python
2
- # PYTHON_ARGCOMPLETE_OK
3
- """Command line entry point for ansible-test."""
4
-
5
- # NOTE: This file resides in the _util/target directory to ensure compatibility with all supported Python versions.
6
-
7
- from __future__ import annotations
8
-
9
- import os
10
- import sys
11
-
12
-
13
- def main(args=None):
14
- """Main program entry point."""
15
- ansible_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16
- source_root = os.path.join(ansible_root, 'test', 'lib')
17
-
18
- if os.path.exists(os.path.join(source_root, 'ansible_test', '_internal', '__init__.py')):
19
- # running from source, use that version of ansible-test instead of any version that may already be installed
20
- sys.path.insert(0, source_root)
21
-
22
- # noinspection PyProtectedMember
23
- from ansible_test._util.target.common.constants import CONTROLLER_PYTHON_VERSIONS
24
-
25
- if version_to_str(sys.version_info[:2]) not in CONTROLLER_PYTHON_VERSIONS:
26
- raise SystemExit('This version of ansible-test cannot be executed with Python version %s. Supported Python versions are: %s' % (
27
- version_to_str(sys.version_info[:3]), ', '.join(CONTROLLER_PYTHON_VERSIONS)))
28
-
29
- if any(not os.get_blocking(handle.fileno()) for handle in (sys.stdin, sys.stdout, sys.stderr)):
30
- raise SystemExit('Standard input, output and error file handles must be blocking to run ansible-test.')
31
-
32
- # noinspection PyProtectedMember
33
- from ansible_test._internal import main as cli_main
34
-
35
- cli_main(args)
36
-
37
-
38
- def version_to_str(version):
39
- """Return a version string from a version tuple."""
40
- return '.'.join(str(n) for n in version)
41
-
42
-
43
- if __name__ == '__main__':
44
- main()
@@ -1,10 +0,0 @@
1
- mypy
2
- cryptography # type stubs not published separately
3
- jinja2 # type stubs not published separately
4
- packaging # type stubs not published separately
5
- types-backports
6
- types-paramiko
7
- types-pyyaml
8
- types-requests
9
- types-setuptools
10
- types-toml
@@ -1,18 +0,0 @@
1
- # edit "sanity.mypy.in" and generate with: hacking/update-sanity-requirements.py --test mypy
2
- cffi==1.16.0
3
- cryptography==42.0.5
4
- Jinja2==3.1.3
5
- MarkupSafe==2.1.5
6
- mypy==1.9.0
7
- mypy-extensions==1.0.0
8
- packaging==24.0
9
- pycparser==2.21
10
- tomli==2.0.1
11
- types-backports==0.1.3
12
- types-paramiko==3.4.0.20240311
13
- types-PyYAML==6.0.12.20240311
14
- types-requests==2.31.0.20240311
15
- types-setuptools==69.2.0.20240317
16
- types-toml==0.10.8.20240310
17
- typing_extensions==4.10.0
18
- urllib3==2.2.1