ansible-core 2.17.4rc1__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.4rc1.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.4rc1.dist-info → ansible_core-2.18.0b1.dist-info}/RECORD +311 -306
  254. {ansible_core-2.17.4rc1.dist-info → ansible_core-2.18.0b1.dist-info}/WHEEL +1 -1
  255. {ansible_core-2.17.4rc1.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.4rc1.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.4rc1.dist-info → ansible_core-2.18.0b1.dist-info}/COPYING +0 -0
  320. {ansible_core-2.17.4rc1.dist-info → ansible_core-2.18.0b1.dist-info}/top_level.txt +0 -0
ansible/constants.py CHANGED
@@ -15,6 +15,10 @@ from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
15
15
  from ansible.release import __version__
16
16
  from ansible.utils.fqcn import add_internal_fqcns
17
17
 
18
+ # initialize config manager/config data to read/store global settings
19
+ # and generate 'pseudo constants' for app consumption.
20
+ config = ConfigManager()
21
+
18
22
 
19
23
  def _warning(msg):
20
24
  ''' display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write '''
@@ -36,6 +40,28 @@ def _deprecated(msg, version):
36
40
  sys.stderr.write(' [DEPRECATED] %s, to be removed in %s\n' % (msg, version))
37
41
 
38
42
 
43
+ def handle_config_noise(display=None):
44
+
45
+ if display is not None:
46
+ w = display.warning
47
+ d = display.deprecated
48
+ else:
49
+ w = _warning
50
+ d = _deprecated
51
+
52
+ while config.WARNINGS:
53
+ warn = config.WARNINGS.pop()
54
+ w(warn)
55
+
56
+ while config.DEPRECATED:
57
+ # tuple with name and options
58
+ dep = config.DEPRECATED.pop(0)
59
+ msg = config.get_deprecated_msg_from_config(dep[1])
60
+ # use tabs only for ansible-doc?
61
+ msg = msg.replace("\t", "")
62
+ d(f"{dep[0]} option. {msg}", version=dep[1]['version'])
63
+
64
+
39
65
  def set_constant(name, value, export=vars()):
40
66
  ''' sets constants and returns resolved options dict '''
41
67
  export[name] = value
@@ -152,10 +178,10 @@ INTERNAL_STATIC_VARS = frozenset(
152
178
  ]
153
179
  )
154
180
  LOCALHOST = ('127.0.0.1', 'localhost', '::1')
155
- MODULE_REQUIRE_ARGS = tuple(add_internal_fqcns(('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell',
156
- 'ansible.windows.win_shell', 'raw', 'script')))
157
- MODULE_NO_JSON = tuple(add_internal_fqcns(('command', 'win_command', 'ansible.windows.win_command', 'shell', 'win_shell',
158
- 'ansible.windows.win_shell', 'raw')))
181
+ WIN_MOVED = ['ansible.windows.win_command', 'ansible.windows.win_shell']
182
+ MODULE_REQUIRE_ARGS_SIMPLE = ['command', 'raw', 'script', 'shell', 'win_command', 'win_shell']
183
+ MODULE_REQUIRE_ARGS = tuple(add_internal_fqcns(MODULE_REQUIRE_ARGS_SIMPLE) + WIN_MOVED)
184
+ MODULE_NO_JSON = tuple(add_internal_fqcns(('command', 'win_command', 'shell', 'win_shell', 'raw')) + WIN_MOVED)
159
185
  RESTRICTED_RESULT_KEYS = ('ansible_rsync_path', 'ansible_playbook_python', 'ansible_facts')
160
186
  SYNTHETIC_COLLECTIONS = ('ansible.builtin', 'ansible.legacy')
161
187
  TREE_DIR = None
@@ -218,11 +244,8 @@ MAGIC_VARIABLE_MAPPING = dict(
218
244
  )
219
245
 
220
246
  # POPULATE SETTINGS FROM CONFIG ###
221
- config = ConfigManager()
222
-
223
- # Generate constants from config
224
247
  for setting in config.get_configuration_definitions():
225
248
  set_constant(setting, config.get_config_value(setting, variables=vars()))
226
249
 
227
- for warn in config.WARNINGS:
228
- _warning(warn)
250
+ # emit any warnings or deprecations
251
+ handle_config_noise()
@@ -227,6 +227,11 @@ class AnsibleOptionsError(AnsibleError):
227
227
  pass
228
228
 
229
229
 
230
+ class AnsibleRequiredOptionError(AnsibleOptionsError):
231
+ ''' bad or incomplete options passed '''
232
+ pass
233
+
234
+
230
235
  class AnsibleParserError(AnsibleError):
231
236
  ''' something was detected early that is wrong about a playbook or data file '''
232
237
  pass
@@ -41,7 +41,7 @@ class InterpreterDiscoveryRequiredError(Exception):
41
41
  def discover_interpreter(action, interpreter_name, discovery_mode, task_vars):
42
42
  # interpreter discovery is a 2-step process with the target. First, we use a simple shell-agnostic bootstrap to
43
43
  # get the system type from uname, and find any random Python that can get us the info we need. For supported
44
- # target OS types, we'll dispatch a Python script that calls plaform.dist() (for older platforms, where available)
44
+ # target OS types, we'll dispatch a Python script that calls platform.dist() (for older platforms, where available)
45
45
  # and brings back /etc/os-release (if present). The proper Python path is looked up in a table of known
46
46
  # distros/versions with included Pythons; if nothing is found, depending on the discovery mode, either the
47
47
  # default fallback of /usr/bin/python is used (if we know it's there), or discovery fails.
@@ -635,3 +635,19 @@ class PlayIterator:
635
635
 
636
636
  def clear_notification(self, hostname: str, notification: str) -> None:
637
637
  self._host_states[hostname].handler_notifications.remove(notification)
638
+
639
+ def end_host(self, hostname: str) -> None:
640
+ """Used by ``end_host``, ``end_batch`` and ``end_play`` meta tasks to end executing given host."""
641
+ state = self.get_active_state(self.get_state_for_host(hostname))
642
+ if state.run_state == IteratingStates.RESCUE:
643
+ # This is a special case for when ending a host occurs in rescue.
644
+ # By definition the meta task responsible for ending the host
645
+ # is the last task, so we need to clear the fail state to mark
646
+ # the host as rescued.
647
+ # The reason we need to do that is because this operation is
648
+ # normally done when PlayIterator transitions from rescue to
649
+ # always when only then we can say that rescue didn't fail
650
+ # but with ending a host via meta task, we don't get to that transition.
651
+ self.set_fail_state_for_host(hostname, FailedStates.NONE)
652
+ self.set_run_state_for_host(hostname, IteratingStates.COMPLETE)
653
+ self._play._removed_hosts.append(hostname)
@@ -195,10 +195,7 @@ class PlaybookExecutor:
195
195
  result = self._tqm.RUN_FAILED_HOSTS
196
196
  break_play = True
197
197
 
198
- # check the number of failures here, to see if they're above the maximum
199
- # failure percentage allowed, or if any errors are fatal. If either of those
200
- # conditions are met, we break out, otherwise we only break out if the entire
201
- # batch failed
198
+ # check the number of failures here and break out if the entire batch failed
202
199
  failed_hosts_count = len(self._tqm._failed_hosts) + len(self._tqm._unreachable_hosts) - \
203
200
  (previously_failed + previously_unreachable)
204
201
 
@@ -116,12 +116,11 @@ Write-AnsibleLog "INFO - parsed become input, user: '$username', type: '$logon_t
116
116
  # set to Stop and cannot be changed. Also need to split the payload from the wrapper to prevent potentially
117
117
  # sensitive content from being logged by the scriptblock logger.
118
118
  $bootstrap_wrapper = {
119
- &chcp.com 65001 > $null
120
- $exec_wrapper_str = [System.Console]::In.ReadToEnd()
121
- $split_parts = $exec_wrapper_str.Split(@("`0`0`0`0"), 2, [StringSplitOptions]::RemoveEmptyEntries)
119
+ [Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding
120
+ $ew = [System.Console]::In.ReadToEnd()
121
+ $split_parts = $ew.Split(@("`0`0`0`0"), 2, [StringSplitOptions]::RemoveEmptyEntries)
122
122
  Set-Variable -Name json_raw -Value $split_parts[1]
123
- $exec_wrapper = [ScriptBlock]::Create($split_parts[0])
124
- &$exec_wrapper
123
+ &([ScriptBlock]::Create($split_parts[0]))
125
124
  }
126
125
  $exec_command = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($bootstrap_wrapper.ToString()))
127
126
  $lp_command_line = "powershell.exe -NonInteractive -NoProfile -ExecutionPolicy Bypass -EncodedCommand $exec_command"
@@ -1,4 +1,4 @@
1
- &chcp.com 65001 > $null
1
+ try { [Console]::InputEncoding = [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding } catch { $null = $_ }
2
2
 
3
3
  if ($PSVersionTable.PSVersion -lt [Version]"3.0") {
4
4
  '{"failed":true,"msg":"Ansible requires PowerShell v3.0 or newer"}'
@@ -9,5 +9,4 @@ $exec_wrapper_str = $input | Out-String
9
9
  $split_parts = $exec_wrapper_str.Split(@("`0`0`0`0"), 2, [StringSplitOptions]::RemoveEmptyEntries)
10
10
  If (-not $split_parts.Length -eq 2) { throw "invalid payload" }
11
11
  Set-Variable -Name json_raw -Value $split_parts[1]
12
- $exec_wrapper = [ScriptBlock]::Create($split_parts[0])
13
- &$exec_wrapper
12
+ & ([ScriptBlock]::Create($split_parts[0]))
@@ -16,7 +16,7 @@ begin {
16
16
  .SYNOPSIS
17
17
  Converts a JSON string to a Hashtable/Array in the fastest way
18
18
  possible. Unfortunately ConvertFrom-Json is still faster but outputs
19
- a PSCustomObject which is combersone for module consumption.
19
+ a PSCustomObject which is cumbersome for module consumption.
20
20
 
21
21
  .PARAMETER InputObject
22
22
  [String] The JSON string to deserialize.
@@ -8,7 +8,7 @@ import errno
8
8
  import json
9
9
  import os
10
10
  import pkgutil
11
- import random
11
+ import secrets
12
12
  import re
13
13
  from importlib import import_module
14
14
 
@@ -318,7 +318,7 @@ def _create_powershell_wrapper(b_module_data, module_path, module_args,
318
318
 
319
319
  exec_manifest["actions"].insert(0, 'async_watchdog')
320
320
  exec_manifest["actions"].insert(0, 'async_wrapper')
321
- exec_manifest["async_jid"] = f'j{random.randint(0, 999999999999)}'
321
+ exec_manifest["async_jid"] = f'j{secrets.randbelow(999999999999)}'
322
322
  exec_manifest["async_timeout_sec"] = async_timeout
323
323
  exec_manifest["async_startup_timeout"] = C.config.get_config_value("WIN_ASYNC_STARTUP_TIMEOUT", variables=task_vars)
324
324
 
@@ -4,23 +4,24 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import os
7
- import pty
8
7
  import time
9
8
  import json
9
+ import pathlib
10
10
  import signal
11
11
  import subprocess
12
12
  import sys
13
- import termios
14
13
  import traceback
15
14
 
16
15
  from ansible import constants as C
16
+ from ansible.cli import scripts
17
17
  from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleConnectionFailure, AnsibleActionFail, AnsibleActionSkip
18
18
  from ansible.executor.task_result import TaskResult
19
19
  from ansible.executor.module_common import get_action_args_with_defaults
20
20
  from ansible.module_utils.parsing.convert_bool import boolean
21
21
  from ansible.module_utils.six import binary_type
22
22
  from ansible.module_utils.common.text.converters import to_text, to_native
23
- from ansible.module_utils.connection import write_to_file_descriptor
23
+ from ansible.module_utils.connection import write_to_stream
24
+ from ansible.module_utils.six import string_types
24
25
  from ansible.playbook.conditional import Conditional
25
26
  from ansible.playbook.task import Task
26
27
  from ansible.plugins import get_plugin_class
@@ -42,11 +43,21 @@ __all__ = ['TaskExecutor']
42
43
 
43
44
 
44
45
  class TaskTimeoutError(BaseException):
45
- pass
46
+ def __init__(self, message="", frame=None):
47
+
48
+ if frame is not None:
49
+ orig = frame
50
+ root = pathlib.Path(__file__).parent
51
+ while not pathlib.Path(frame.f_code.co_filename).is_relative_to(root):
52
+ frame = frame.f_back
53
+
54
+ self.frame = 'Interrupted at %s called from %s' % (orig, frame)
55
+
56
+ super(TaskTimeoutError, self).__init__(message)
46
57
 
47
58
 
48
59
  def task_timeout(signum, frame):
49
- raise TaskTimeoutError
60
+ raise TaskTimeoutError(frame=frame)
50
61
 
51
62
 
52
63
  def remove_omit(task_args, omit_token):
@@ -369,12 +380,17 @@ class TaskExecutor:
369
380
  'msg': 'Failed to template loop_control.label: %s' % to_text(e)
370
381
  })
371
382
 
383
+ # if plugin is loaded, get resolved name, otherwise leave original task connection
384
+ if self._connection and not isinstance(self._connection, string_types):
385
+ task_fields['connection'] = getattr(self._connection, 'ansible_name')
386
+
372
387
  tr = TaskResult(
373
388
  self._host.name,
374
389
  self._task._uuid,
375
390
  res,
376
391
  task_fields=task_fields,
377
392
  )
393
+
378
394
  if tr.is_failed() or tr.is_unreachable():
379
395
  self._final_q.send_callback('v2_runner_item_on_failed', tr)
380
396
  elif tr.is_skipped():
@@ -386,6 +402,19 @@ class TaskExecutor:
386
402
  self._final_q.send_callback('v2_runner_item_on_ok', tr)
387
403
 
388
404
  results.append(res)
405
+
406
+ # break loop if break_when conditions are met
407
+ if self._task.loop_control and self._task.loop_control.break_when:
408
+ cond = Conditional(loader=self._loader)
409
+ cond.when = self._task.loop_control.get_validated_value(
410
+ 'break_when', self._task.loop_control.fattributes.get('break_when'), self._task.loop_control.break_when, templar
411
+ )
412
+ if cond.evaluate_conditional(templar, task_vars):
413
+ # delete loop vars before exiting loop
414
+ del task_vars[loop_var]
415
+ break
416
+
417
+ # done with loop var, remove for next iteration
389
418
  del task_vars[loop_var]
390
419
 
391
420
  # clear 'connection related' plugin variables for next iteration
@@ -647,7 +676,7 @@ class TaskExecutor:
647
676
  return dict(unreachable=True, msg=to_text(e))
648
677
  except TaskTimeoutError as e:
649
678
  msg = 'The %s action failed to execute in the expected time frame (%d) and was terminated' % (self._task.action, self._task.timeout)
650
- return dict(failed=True, msg=msg)
679
+ return dict(failed=True, msg=msg, timedout={'frame': e.frame, 'period': self._task.timeout})
651
680
  finally:
652
681
  if self._task.timeout:
653
682
  signal.alarm(0)
@@ -1183,26 +1212,19 @@ class TaskExecutor:
1183
1212
  return handler, module
1184
1213
 
1185
1214
 
1215
+ CLI_STUB_NAME = 'ansible_connection_cli_stub.py'
1216
+
1217
+
1186
1218
  def start_connection(play_context, options, task_uuid):
1187
1219
  '''
1188
1220
  Starts the persistent connection
1189
1221
  '''
1190
- candidate_paths = [C.ANSIBLE_CONNECTION_PATH or os.path.dirname(sys.argv[0])]
1191
- candidate_paths.extend(os.environ.get('PATH', '').split(os.pathsep))
1192
- for dirname in candidate_paths:
1193
- ansible_connection = os.path.join(dirname, 'ansible-connection')
1194
- if os.path.isfile(ansible_connection):
1195
- display.vvvv("Found ansible-connection at path {0}".format(ansible_connection))
1196
- break
1197
- else:
1198
- raise AnsibleError("Unable to find location of 'ansible-connection'. "
1199
- "Please set or check the value of ANSIBLE_CONNECTION_PATH")
1200
1222
 
1201
1223
  env = os.environ.copy()
1202
1224
  env.update({
1203
1225
  # HACK; most of these paths may change during the controller's lifetime
1204
1226
  # (eg, due to late dynamic role includes, multi-playbook execution), without a way
1205
- # to invalidate/update, ansible-connection won't always see the same plugins the controller
1227
+ # to invalidate/update, the persistent connection helper won't always see the same plugins the controller
1206
1228
  # can.
1207
1229
  'ANSIBLE_BECOME_PLUGINS': become_loader.print_paths(),
1208
1230
  'ANSIBLE_CLICONF_PLUGINS': cliconf_loader.print_paths(),
@@ -1215,30 +1237,19 @@ def start_connection(play_context, options, task_uuid):
1215
1237
  verbosity = []
1216
1238
  if display.verbosity:
1217
1239
  verbosity.append('-%s' % ('v' * display.verbosity))
1218
- python = sys.executable
1219
- master, slave = pty.openpty()
1240
+
1241
+ if not (cli_stub_path := C.config.get_config_value('_ANSIBLE_CONNECTION_PATH')):
1242
+ cli_stub_path = str(pathlib.Path(scripts.__file__).parent / CLI_STUB_NAME)
1243
+
1220
1244
  p = subprocess.Popen(
1221
- [python, ansible_connection, *verbosity, to_text(os.getppid()), to_text(task_uuid)],
1222
- stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env
1245
+ [sys.executable, cli_stub_path, *verbosity, to_text(os.getppid()), to_text(task_uuid)],
1246
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env,
1223
1247
  )
1224
- os.close(slave)
1225
-
1226
- # We need to set the pty into noncanonical mode. This ensures that we
1227
- # can receive lines longer than 4095 characters (plus newline) without
1228
- # truncating.
1229
- old = termios.tcgetattr(master)
1230
- new = termios.tcgetattr(master)
1231
- new[3] = new[3] & ~termios.ICANON
1232
-
1233
- try:
1234
- termios.tcsetattr(master, termios.TCSANOW, new)
1235
- write_to_file_descriptor(master, options)
1236
- write_to_file_descriptor(master, play_context.serialize())
1237
-
1238
- (stdout, stderr) = p.communicate()
1239
- finally:
1240
- termios.tcsetattr(master, termios.TCSANOW, old)
1241
- os.close(master)
1248
+
1249
+ write_to_stream(p.stdin, options)
1250
+ write_to_stream(p.stdin, play_context.serialize())
1251
+
1252
+ (stdout, stderr) = p.communicate()
1242
1253
 
1243
1254
  if p.returncode == 0:
1244
1255
  result = json.loads(to_text(stdout, errors='surrogate_then_replace'))
@@ -223,7 +223,7 @@ class TaskQueueManager:
223
223
  callback_type = getattr(callback_plugin, 'CALLBACK_TYPE', '')
224
224
  callback_needs_enabled = getattr(callback_plugin, 'CALLBACK_NEEDS_ENABLED', getattr(callback_plugin, 'CALLBACK_NEEDS_WHITELIST', False))
225
225
 
226
- # try to get colleciotn world name first
226
+ # try to get collection world name first
227
227
  cnames = getattr(callback_plugin, '_redirected_names', [])
228
228
  if cnames:
229
229
  # store the name the plugin was loaded as, as that's what we'll need to compare to the configured callback list later
@@ -139,7 +139,7 @@ class TaskResult:
139
139
  elif self._result:
140
140
  result._result = module_response_deepcopy(self._result)
141
141
 
142
- # actualy remove
142
+ # actually remove
143
143
  for remove_key in ignore:
144
144
  if remove_key in result._result:
145
145
  del result._result[remove_key]
ansible/galaxy/api.py CHANGED
@@ -62,8 +62,7 @@ def should_retry_error(exception):
62
62
  if isinstance(orig_exc, URLError):
63
63
  orig_exc = orig_exc.reason
64
64
 
65
- # Handle common URL related errors such as TimeoutError, and BadStatusLine
66
- # Note: socket.timeout is only required for Py3.9
65
+ # Handle common URL related errors
67
66
  if isinstance(orig_exc, (TimeoutError, BadStatusLine, IncompleteRead)):
68
67
  return True
69
68
 
@@ -720,7 +719,7 @@ class GalaxyAPI:
720
719
 
721
720
  display.display("Waiting until Galaxy import task %s has completed" % full_url)
722
721
  start = time.time()
723
- wait = 2
722
+ wait = C.GALAXY_COLLECTION_IMPORT_POLL_INTERVAL
724
723
 
725
724
  while timeout == 0 or (time.time() - start) < timeout:
726
725
  try:
@@ -744,7 +743,7 @@ class GalaxyAPI:
744
743
  time.sleep(wait)
745
744
 
746
745
  # poor man's exponential backoff algo so we don't flood the Galaxy API, cap at 30 seconds.
747
- wait = min(30, wait * 1.5)
746
+ wait = min(30, wait * C.GALAXY_COLLECTION_IMPORT_POLL_FACTOR)
748
747
  if state == 'waiting':
749
748
  raise AnsibleError("Timeout while waiting for the Galaxy import process to finish, check progress at '%s'"
750
749
  % to_native(full_url))
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
  import errno
9
9
  import fnmatch
10
10
  import functools
11
+ import glob
11
12
  import inspect
12
13
  import json
13
14
  import os
@@ -1525,6 +1526,7 @@ def install(collection, path, artifacts_manager): # FIXME: mv to dataclasses?
1525
1526
  artifacts_manager.required_successful_signature_count,
1526
1527
  artifacts_manager.ignore_signature_errors,
1527
1528
  )
1529
+ remove_source_metadata(collection, b_collection_path)
1528
1530
  if (collection.is_online_index_pointer and isinstance(collection.src, GalaxyAPI)):
1529
1531
  write_source_metadata(
1530
1532
  collection,
@@ -1561,6 +1563,22 @@ def write_source_metadata(collection, b_collection_path, artifacts_manager):
1561
1563
  raise
1562
1564
 
1563
1565
 
1566
+ def remove_source_metadata(collection, b_collection_path):
1567
+ pattern = f"{collection.namespace}.{collection.name}-*.info"
1568
+ info_path = os.path.join(
1569
+ b_collection_path,
1570
+ b'../../',
1571
+ to_bytes(pattern, errors='surrogate_or_strict')
1572
+ )
1573
+ if (outdated_info := glob.glob(info_path)):
1574
+ display.vvvv(f"Removing {pattern} metadata from previous installations")
1575
+ for info_dir in outdated_info:
1576
+ try:
1577
+ shutil.rmtree(info_dir)
1578
+ except Exception:
1579
+ pass
1580
+
1581
+
1564
1582
  def verify_artifact_manifest(manifest_file, signatures, keyring, required_signature_count, ignore_signature_errors):
1565
1583
  # type: (str, list[str], str, str, list[str]) -> None
1566
1584
  failed_verify = False
@@ -1584,13 +1602,6 @@ def install_artifact(b_coll_targz_path, b_collection_path, b_temp_path, signatur
1584
1602
  """
1585
1603
  try:
1586
1604
  with tarfile.open(b_coll_targz_path, mode='r') as collection_tar:
1587
- # Remove this once py3.11 is our controller minimum
1588
- # Workaround for https://bugs.python.org/issue47231
1589
- # See _extract_tar_dir
1590
- collection_tar._ansible_normalized_cache = {
1591
- m.name.removesuffix(os.path.sep): m for m in collection_tar.getmembers()
1592
- } # deprecated: description='TarFile member index' core_version='2.18' python_version='3.11'
1593
-
1594
1605
  # Verify the signature on the MANIFEST.json before extracting anything else
1595
1606
  _extract_tar_file(collection_tar, MANIFEST_FILENAME, b_collection_path, b_temp_path)
1596
1607
 
@@ -1671,10 +1682,10 @@ def install_src(collection, b_collection_path, b_collection_output_path, artifac
1671
1682
 
1672
1683
  def _extract_tar_dir(tar, dirname, b_dest):
1673
1684
  """ Extracts a directory from a collection tar. """
1674
- dirname = to_native(dirname, errors='surrogate_or_strict').removesuffix(os.path.sep)
1685
+ dirname = to_native(dirname, errors='surrogate_or_strict')
1675
1686
 
1676
1687
  try:
1677
- tar_member = tar._ansible_normalized_cache[dirname]
1688
+ tar_member = tar.getmember(dirname)
1678
1689
  except KeyError:
1679
1690
  raise AnsibleError("Unable to extract '%s' from collection" % dirname)
1680
1691
 
@@ -1896,7 +1907,7 @@ def _resolve_depenency_map(
1896
1907
 
1897
1908
  for req in dep_exc.criterion.iter_requirement():
1898
1909
  error_msg_lines.append(
1899
- '* {req.fqcn!s}:{req.ver!s}'.format(req=req)
1910
+ f'* {req.fqcn!s}:{req.ver!s}'
1900
1911
  )
1901
1912
  error_msg_lines.append(pre_release_hint)
1902
1913
 
@@ -61,7 +61,7 @@ class ConcreteArtifactsManager:
61
61
  """
62
62
  def __init__(self, b_working_directory, validate_certs=True, keyring=None, timeout=60, required_signature_count=None, ignore_signature_errors=None):
63
63
  # type: (bytes, bool, str, int, str, list[str]) -> None
64
- """Initialize ConcreteArtifactsManager caches and costraints."""
64
+ """Initialize ConcreteArtifactsManager caches and constraints."""
65
65
  self._validate_certs = validate_certs # type: bool
66
66
  self._artifact_cache = {} # type: dict[bytes, bytes]
67
67
  self._galaxy_artifact_cache = {} # type: dict[Candidate | Requirement, bytes]
@@ -413,7 +413,7 @@ def _extract_collection_from_git(repo_url, coll_ver, b_path):
413
413
  b_checkout_path = mkdtemp(
414
414
  dir=b_path,
415
415
  prefix=to_bytes(name, errors='surrogate_or_strict'),
416
- ) # type: bytes
416
+ )
417
417
 
418
418
  try:
419
419
  git_executable = get_bin_path('git')
@@ -27,8 +27,7 @@ display = Display()
27
27
  class MultiGalaxyAPIProxy:
28
28
  """A proxy that abstracts talking to multiple Galaxy instances."""
29
29
 
30
- def __init__(self, apis, concrete_artifacts_manager, offline=False):
31
- # type: (t.Iterable[GalaxyAPI], ConcreteArtifactsManager, bool) -> None
30
+ def __init__(self, apis: t.Iterable[GalaxyAPI], concrete_artifacts_manager: ConcreteArtifactsManager, offline: bool = False) -> None:
32
31
  """Initialize the target APIs list."""
33
32
  self._apis = apis
34
33
  self._concrete_art_mgr = concrete_artifacts_manager
@@ -38,22 +37,21 @@ class MultiGalaxyAPIProxy:
38
37
  def is_offline_mode_requested(self):
39
38
  return self._offline
40
39
 
41
- def _assert_that_offline_mode_is_not_requested(self): # type: () -> None
40
+ def _assert_that_offline_mode_is_not_requested(self) -> None:
42
41
  if self.is_offline_mode_requested:
43
42
  raise NotImplementedError("The calling code is not supposed to be invoked in 'offline' mode.")
44
43
 
45
- def _get_collection_versions(self, requirement):
46
- # type: (Requirement) -> t.Iterator[tuple[GalaxyAPI, str]]
44
+ def _get_collection_versions(self, requirement: Requirement) -> t.Iterator[tuple[GalaxyAPI, str]]:
47
45
  """Helper for get_collection_versions.
48
46
 
49
47
  Yield api, version pairs for all APIs,
50
48
  and reraise the last error if no valid API was found.
51
49
  """
52
50
  if self._offline:
53
- return []
51
+ return
54
52
 
55
53
  found_api = False
56
- last_error = None # type: Exception | None
54
+ last_error: Exception | None = None
57
55
 
58
56
  api_lookup_order = (
59
57
  (requirement.src, )
@@ -86,8 +84,7 @@ class MultiGalaxyAPIProxy:
86
84
  if not found_api and last_error is not None:
87
85
  raise last_error
88
86
 
89
- def get_collection_versions(self, requirement):
90
- # type: (Requirement) -> t.Iterable[tuple[str, GalaxyAPI]]
87
+ def get_collection_versions(self, requirement: Requirement) -> t.Iterable[tuple[str, GalaxyAPI]]:
91
88
  """Get a set of unique versions for FQCN on Galaxy servers."""
92
89
  if requirement.is_concrete_artifact:
93
90
  return {
@@ -110,8 +107,7 @@ class MultiGalaxyAPIProxy:
110
107
  )
111
108
  )
112
109
 
113
- def get_collection_version_metadata(self, collection_candidate):
114
- # type: (Candidate) -> CollectionVersionMetadata
110
+ def get_collection_version_metadata(self, collection_candidate: Candidate) -> CollectionVersionMetadata:
115
111
  """Retrieve collection metadata of a given candidate."""
116
112
  self._assert_that_offline_mode_is_not_requested()
117
113
 
@@ -160,8 +156,7 @@ class MultiGalaxyAPIProxy:
160
156
 
161
157
  raise last_err
162
158
 
163
- def get_collection_dependencies(self, collection_candidate):
164
- # type: (Candidate) -> dict[str, str]
159
+ def get_collection_dependencies(self, collection_candidate: Candidate) -> dict[str, str]:
165
160
  # FIXME: return Requirement instances instead?
166
161
  """Retrieve collection dependencies of a given candidate."""
167
162
  if collection_candidate.is_concrete_artifact:
@@ -177,13 +172,12 @@ class MultiGalaxyAPIProxy:
177
172
  dependencies
178
173
  )
179
174
 
180
- def get_signatures(self, collection_candidate):
181
- # type: (Candidate) -> list[str]
175
+ def get_signatures(self, collection_candidate: Candidate) -> list[str]:
182
176
  self._assert_that_offline_mode_is_not_requested()
183
177
  namespace = collection_candidate.namespace
184
178
  name = collection_candidate.name
185
179
  version = collection_candidate.ver
186
- last_err = None # type: Exception | None
180
+ last_err: Exception | None = None
187
181
 
188
182
  api_lookup_order = (
189
183
  (collection_candidate.src, )