ansible-core 2.19.3rc1__py3-none-any.whl → 2.20.0b2__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (201) hide show
  1. ansible/_internal/__init__.py +1 -4
  2. ansible/_internal/_ansiballz/_builder.py +1 -3
  3. ansible/_internal/_collection_proxy.py +7 -9
  4. ansible/_internal/_json/__init__.py +3 -4
  5. ansible/_internal/_templating/_engine.py +1 -1
  6. ansible/_internal/_templating/_jinja_plugins.py +1 -2
  7. ansible/_internal/_wrapt.py +105 -301
  8. ansible/cli/__init__.py +11 -10
  9. ansible/cli/adhoc.py +1 -2
  10. ansible/cli/arguments/option_helpers.py +1 -1
  11. ansible/cli/config.py +5 -6
  12. ansible/cli/doc.py +70 -68
  13. ansible/cli/galaxy.py +15 -24
  14. ansible/cli/inventory.py +0 -1
  15. ansible/cli/playbook.py +0 -1
  16. ansible/cli/pull.py +0 -1
  17. ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
  18. ansible/collections/list.py +4 -2
  19. ansible/config/base.yml +1 -25
  20. ansible/config/manager.py +0 -2
  21. ansible/executor/play_iterator.py +42 -20
  22. ansible/executor/playbook_executor.py +0 -9
  23. ansible/executor/task_executor.py +26 -18
  24. ansible/executor/task_queue_manager.py +1 -3
  25. ansible/galaxy/api.py +33 -80
  26. ansible/galaxy/collection/__init__.py +4 -17
  27. ansible/galaxy/dependency_resolution/dataclasses.py +0 -10
  28. ansible/galaxy/dependency_resolution/providers.py +24 -118
  29. ansible/galaxy/role.py +1 -33
  30. ansible/inventory/manager.py +2 -3
  31. ansible/keyword_desc.yml +0 -3
  32. ansible/module_utils/_internal/_datatag/__init__.py +2 -10
  33. ansible/module_utils/_internal/_no_six.py +86 -0
  34. ansible/module_utils/_text.py +28 -8
  35. ansible/module_utils/ansible_release.py +2 -2
  36. ansible/module_utils/basic.py +26 -23
  37. ansible/module_utils/common/_collections_compat.py +11 -2
  38. ansible/module_utils/common/collections.py +8 -3
  39. ansible/module_utils/common/dict_transformations.py +1 -2
  40. ansible/module_utils/common/network.py +4 -2
  41. ansible/module_utils/common/parameters.py +32 -41
  42. ansible/module_utils/common/text/converters.py +109 -23
  43. ansible/module_utils/common/text/formatters.py +6 -2
  44. ansible/module_utils/common/validation.py +11 -9
  45. ansible/module_utils/connection.py +8 -3
  46. ansible/module_utils/facts/hardware/linux.py +23 -7
  47. ansible/module_utils/facts/hardware/netbsd.py +1 -1
  48. ansible/module_utils/facts/hardware/sunos.py +2 -1
  49. ansible/module_utils/facts/packages.py +6 -2
  50. ansible/module_utils/facts/system/distribution.py +2 -1
  51. ansible/module_utils/facts/system/env.py +6 -3
  52. ansible/module_utils/facts/system/local.py +3 -1
  53. ansible/module_utils/parsing/convert_bool.py +6 -2
  54. ansible/module_utils/service.py +2 -3
  55. ansible/module_utils/six/__init__.py +19 -6
  56. ansible/module_utils/yumdnf.py +0 -5
  57. ansible/modules/apt.py +18 -13
  58. ansible/modules/apt_repository.py +1 -1
  59. ansible/modules/assemble.py +5 -9
  60. ansible/modules/blockinfile.py +39 -23
  61. ansible/modules/cron.py +26 -35
  62. ansible/modules/deb822_repository.py +83 -12
  63. ansible/modules/dnf.py +3 -7
  64. ansible/modules/dnf5.py +4 -6
  65. ansible/modules/expect.py +0 -3
  66. ansible/modules/find.py +1 -2
  67. ansible/modules/get_url.py +1 -1
  68. ansible/modules/git.py +4 -5
  69. ansible/modules/include_vars.py +1 -1
  70. ansible/modules/known_hosts.py +7 -1
  71. ansible/modules/lineinfile.py +71 -63
  72. ansible/modules/package_facts.py +1 -1
  73. ansible/modules/pip.py +8 -2
  74. ansible/modules/replace.py +6 -6
  75. ansible/modules/service.py +3 -4
  76. ansible/modules/stat.py +20 -0
  77. ansible/modules/uri.py +9 -10
  78. ansible/modules/user.py +1 -2
  79. ansible/modules/wait_for.py +2 -2
  80. ansible/modules/wait_for_connection.py +2 -1
  81. ansible/modules/yum_repository.py +1 -16
  82. ansible/parsing/dataloader.py +24 -31
  83. ansible/parsing/mod_args.py +3 -0
  84. ansible/parsing/vault/__init__.py +1 -2
  85. ansible/playbook/base.py +8 -56
  86. ansible/playbook/block.py +1 -63
  87. ansible/playbook/collectionsearch.py +1 -2
  88. ansible/playbook/handler.py +1 -7
  89. ansible/playbook/helpers.py +15 -20
  90. ansible/playbook/included_file.py +1 -1
  91. ansible/playbook/play.py +105 -49
  92. ansible/playbook/play_context.py +4 -0
  93. ansible/playbook/role/__init__.py +10 -65
  94. ansible/playbook/role/definition.py +3 -4
  95. ansible/playbook/role/include.py +2 -3
  96. ansible/playbook/role/metadata.py +1 -12
  97. ansible/playbook/role/requirement.py +1 -2
  98. ansible/playbook/role_include.py +1 -2
  99. ansible/playbook/taggable.py +16 -5
  100. ansible/playbook/task.py +51 -55
  101. ansible/plugins/action/__init__.py +20 -19
  102. ansible/plugins/action/add_host.py +1 -2
  103. ansible/plugins/action/fetch.py +3 -5
  104. ansible/plugins/action/group_by.py +1 -2
  105. ansible/plugins/action/include_vars.py +20 -22
  106. ansible/plugins/action/script.py +1 -3
  107. ansible/plugins/action/template.py +1 -2
  108. ansible/plugins/action/uri.py +4 -2
  109. ansible/plugins/cache/__init__.py +1 -0
  110. ansible/plugins/callback/__init__.py +13 -6
  111. ansible/plugins/connection/__init__.py +3 -7
  112. ansible/plugins/connection/local.py +2 -3
  113. ansible/plugins/connection/psrp.py +0 -2
  114. ansible/plugins/connection/ssh.py +2 -7
  115. ansible/plugins/connection/winrm.py +0 -2
  116. ansible/plugins/doc_fragments/result_format_callback.py +15 -0
  117. ansible/plugins/filter/core.py +4 -5
  118. ansible/plugins/filter/encryption.py +3 -27
  119. ansible/plugins/filter/mathstuff.py +1 -2
  120. ansible/plugins/filter/to_nice_yaml.yml +31 -3
  121. ansible/plugins/filter/to_yaml.yml +29 -12
  122. ansible/plugins/inventory/__init__.py +1 -2
  123. ansible/plugins/inventory/toml.py +3 -6
  124. ansible/plugins/inventory/yaml.py +1 -2
  125. ansible/plugins/loader.py +3 -4
  126. ansible/plugins/lookup/password.py +1 -2
  127. ansible/plugins/lookup/subelements.py +2 -3
  128. ansible/plugins/lookup/url.py +1 -1
  129. ansible/plugins/lookup/varnames.py +1 -2
  130. ansible/plugins/shell/__init__.py +9 -4
  131. ansible/plugins/shell/powershell.py +8 -24
  132. ansible/plugins/strategy/__init__.py +6 -3
  133. ansible/plugins/test/core.py +4 -1
  134. ansible/plugins/test/falsy.yml +1 -1
  135. ansible/plugins/test/regex.yml +18 -6
  136. ansible/plugins/test/truthy.yml +1 -1
  137. ansible/release.py +2 -2
  138. ansible/template/__init__.py +3 -7
  139. ansible/utils/collection_loader/_collection_config.py +5 -0
  140. ansible/utils/collection_loader/_collection_finder.py +11 -14
  141. ansible/utils/context_objects.py +7 -4
  142. ansible/utils/display.py +7 -6
  143. ansible/utils/encrypt.py +0 -5
  144. ansible/utils/helpers.py +6 -2
  145. ansible/utils/jsonrpc.py +7 -3
  146. ansible/utils/plugin_docs.py +49 -38
  147. ansible/utils/ssh_functions.py +0 -19
  148. ansible/utils/unsafe_proxy.py +7 -7
  149. ansible/vars/clean.py +2 -3
  150. ansible/vars/manager.py +28 -22
  151. ansible/vars/plugins.py +1 -31
  152. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/METADATA +3 -3
  153. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/RECORD +199 -200
  154. ansible_test/_data/completion/docker.txt +7 -7
  155. ansible_test/_data/completion/network.txt +0 -1
  156. ansible_test/_data/completion/remote.txt +4 -4
  157. ansible_test/_data/requirements/ansible-test.txt +1 -1
  158. ansible_test/_data/requirements/sanity.changelog.txt +1 -1
  159. ansible_test/_data/requirements/sanity.pep8.txt +1 -1
  160. ansible_test/_data/requirements/sanity.pylint.txt +4 -4
  161. ansible_test/_internal/cache.py +2 -5
  162. ansible_test/_internal/cli/compat.py +1 -1
  163. ansible_test/_internal/commands/coverage/combine.py +1 -3
  164. ansible_test/_internal/commands/integration/__init__.py +3 -7
  165. ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
  166. ansible_test/_internal/commands/integration/coverage.py +1 -3
  167. ansible_test/_internal/commands/integration/filters.py +5 -10
  168. ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
  169. ansible_test/_internal/commands/units/__init__.py +1 -13
  170. ansible_test/_internal/completion.py +2 -5
  171. ansible_test/_internal/config.py +2 -7
  172. ansible_test/_internal/coverage_util.py +1 -1
  173. ansible_test/_internal/delegation.py +2 -0
  174. ansible_test/_internal/docker_util.py +1 -1
  175. ansible_test/_internal/host_profiles.py +6 -11
  176. ansible_test/_internal/provider/__init__.py +2 -5
  177. ansible_test/_internal/provisioning.py +2 -5
  178. ansible_test/_internal/pypi_proxy.py +1 -1
  179. ansible_test/_internal/target.py +2 -6
  180. ansible_test/_internal/thread.py +1 -4
  181. ansible_test/_internal/util.py +9 -14
  182. ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
  183. ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +40 -27
  184. ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
  185. ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
  186. ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
  187. ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
  188. ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
  189. ansible_test/_util/target/common/constants.py +2 -2
  190. ansible_test/_util/target/setup/bootstrap.sh +0 -6
  191. ansible/utils/py3compat.py +0 -27
  192. ansible_test/_data/pytest/config/legacy.ini +0 -4
  193. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/WHEEL +0 -0
  194. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/entry_points.txt +0 -0
  195. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/COPYING +0 -0
  196. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/Apache-License.txt +0 -0
  197. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
  198. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/MIT-license.txt +0 -0
  199. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/PSF-license.txt +0 -0
  200. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
  201. {ansible_core-2.19.3rc1.dist-info → ansible_core-2.20.0b2.dist-info}/top_level.txt +0 -0
@@ -174,6 +174,27 @@ class PlayIterator:
174
174
  setup_task.when = self._play._included_conditional[:]
175
175
  setup_block.block = [setup_task]
176
176
 
177
+ validation_task = Task.load({
178
+ 'name': f'Validating arguments against arg spec {self._play.validate_argspec}',
179
+ 'action': 'ansible.builtin.validate_argument_spec',
180
+ 'args': {
181
+ # 'provided_arguments': {}, # allow configuration via module_defaults
182
+ 'argument_spec': self._play.argument_spec,
183
+ 'validate_args_context': {
184
+ 'type': 'play',
185
+ 'name': self._play.validate_argspec,
186
+ 'argument_spec_name': self._play.validate_argspec,
187
+ 'path': self._play._metadata_path,
188
+ },
189
+ },
190
+ 'tags': ['always'],
191
+ }, block=setup_block)
192
+
193
+ validation_task.set_loader(self._play._loader)
194
+ if self._play._included_conditional is not None:
195
+ validation_task.when = self._play._included_conditional[:]
196
+ setup_block.block.append(validation_task)
197
+
177
198
  setup_block = setup_block.filter_tagged_tasks(all_vars)
178
199
  self._blocks.append(setup_block)
179
200
 
@@ -271,35 +292,36 @@ class PlayIterator:
271
292
  return (state, None)
272
293
 
273
294
  if state.run_state == IteratingStates.SETUP:
274
- # First, we check to see if we were pending setup. If not, this is
275
- # the first trip through IteratingStates.SETUP, so we set the pending_setup
276
- # flag and try to determine if we do in fact want to gather facts for
277
- # the specified host.
278
- if not state.pending_setup:
279
- state.pending_setup = True
295
+ # First, we check to see if we completed both setup tasks injected
296
+ # during play compilation in __init__ above.
297
+ # If not, below we will determine if we do in fact want to gather
298
+ # facts or validate arguments for the specified host.
299
+ state.pending_setup = state.cur_regular_task < len(block.block)
300
+ if state.pending_setup:
301
+ task = block.block[state.cur_regular_task]
280
302
 
281
303
  # Gather facts if the default is 'smart' and we have not yet
282
304
  # done it for this host; or if 'explicit' and the play sets
283
305
  # gather_facts to True; or if 'implicit' and the play does
284
306
  # NOT explicitly set gather_facts to False.
285
-
307
+ gather_facts = bool(state.cur_regular_task == 0)
286
308
  gathering = C.DEFAULT_GATHERING
287
309
  implied = self._play.gather_facts is None or boolean(self._play.gather_facts, strict=False)
288
310
 
289
- if (gathering == 'implicit' and implied) or \
290
- (gathering == 'explicit' and boolean(self._play.gather_facts, strict=False)) or \
291
- (gathering == 'smart' and implied and not self._variable_manager._facts_gathered_for_host(host.name)):
292
- # The setup block is always self._blocks[0], as we inject it
293
- # during the play compilation in __init__ above.
294
- setup_block = self._blocks[0]
295
- if setup_block.has_tasks() and len(setup_block.block) > 0:
296
- task = setup_block.block[0]
297
- else:
298
- # This is the second trip through IteratingStates.SETUP, so we clear
299
- # the flag and move onto the next block in the list while setting
300
- # the run state to IteratingStates.TASKS
301
- state.pending_setup = False
311
+ if gather_facts and not (
312
+ (gathering == 'implicit' and implied) or
313
+ (gathering == 'explicit' and boolean(self._play.gather_facts, strict=False)) or
314
+ (gathering == 'smart' and implied and not self._variable_manager._facts_gathered_for_host(host.name))
315
+ ):
316
+ task = None
317
+ elif not gather_facts and not self._play.validate_argspec:
318
+ task = None
302
319
 
320
+ state.cur_regular_task += 1
321
+ else:
322
+ # This is the last trip through IteratingStates.SETUP, so we
323
+ # move onto the next block in the list while setting the run
324
+ # state to IteratingStates.TASKS
303
325
  state.run_state = IteratingStates.TASKS
304
326
  if not state.did_start_at_task:
305
327
  state.cur_block += 1
@@ -31,7 +31,6 @@ from ansible.utils.helpers import pct_to_int
31
31
  from ansible.utils.collection_loader import AnsibleCollectionConfig
32
32
  from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path, _get_collection_playbook_path
33
33
  from ansible.utils.path import makedirs_safe
34
- from ansible.utils.ssh_functions import set_default_transport
35
34
  from ansible.utils.display import Display
36
35
 
37
36
 
@@ -65,14 +64,6 @@ class PlaybookExecutor:
65
64
  forks=context.CLIARGS.get('forks'),
66
65
  )
67
66
 
68
- # Note: We run this here to cache whether the default ansible ssh
69
- # executable supports control persist. Sometime in the future we may
70
- # need to enhance this to check that ansible_ssh_executable specified
71
- # in inventory is also cached. We can't do this caching at the point
72
- # where it is used (in task_executor) because that is post-fork and
73
- # therefore would be discarded after every task.
74
- set_default_transport()
75
-
76
67
  def run(self):
77
68
  """
78
69
  Run the given playbook, based on the settings in the play which
@@ -21,7 +21,7 @@ from ansible.errors import (
21
21
  )
22
22
 
23
23
  from ansible._internal import _display_utils
24
- from ansible.executor.task_result import _RawTaskResult
24
+ from ansible.executor.task_result import _RawTaskResult, _SUB_PRESERVE
25
25
  from ansible._internal._datatag import _utils
26
26
  from ansible.module_utils._internal import _messages
27
27
  from ansible.module_utils.datatag import native_type_name, deprecator_from_collection_name
@@ -29,7 +29,6 @@ from ansible._internal._datatag._tags import TrustedAsTemplate
29
29
  from ansible.module_utils.parsing.convert_bool import boolean
30
30
  from ansible.module_utils.common.text.converters import to_text, to_native
31
31
  from ansible.module_utils.connection import write_to_stream
32
- from ansible.module_utils.six import string_types
33
32
  from ansible.playbook.task import Task
34
33
  from ansible.plugins import get_plugin_class
35
34
  from ansible.plugins.loader import become_loader, cliconf_loader, connection_loader, httpapi_loader, netconf_loader, terminal_loader
@@ -50,6 +49,7 @@ display = Display()
50
49
 
51
50
 
52
51
  RETURN_VARS = [x for x in C.MAGIC_VARIABLE_MAPPING.items() if 'become' not in x and '_pass' not in x]
52
+ _INJECT_FACTS, _INJECT_FACTS_ORIGIN = C.config.get_config_value_and_origin('INJECT_FACTS_AS_VARS')
53
53
 
54
54
  __all__ = ['TaskExecutor']
55
55
 
@@ -342,7 +342,7 @@ class TaskExecutor:
342
342
  })
343
343
 
344
344
  # if plugin is loaded, get resolved name, otherwise leave original task connection
345
- if self._connection and not isinstance(self._connection, string_types):
345
+ if self._connection and not isinstance(self._connection, str):
346
346
  task_fields['connection'] = getattr(self._connection, 'ansible_name')
347
347
 
348
348
  tr = _RawTaskResult(
@@ -666,8 +666,11 @@ class TaskExecutor:
666
666
  # TODO: cleaning of facts should eventually become part of taskresults instead of vars
667
667
  af = result['ansible_facts']
668
668
  vars_copy['ansible_facts'] = combine_vars(vars_copy.get('ansible_facts', {}), namespace_facts(af))
669
- if C.INJECT_FACTS_AS_VARS:
670
- cleaned_toplevel = {k: _deprecate_top_level_fact(v) for k, v in clean_facts(af).items()}
669
+ if _INJECT_FACTS:
670
+ if _INJECT_FACTS_ORIGIN == 'default':
671
+ cleaned_toplevel = {k: _deprecate_top_level_fact(v) for k, v in clean_facts(af).items()}
672
+ else:
673
+ cleaned_toplevel = clean_facts(af)
671
674
  vars_copy.update(cleaned_toplevel)
672
675
 
673
676
  # set the failed property if it was missing.
@@ -761,9 +764,13 @@ class TaskExecutor:
761
764
  # TODO: cleaning of facts should eventually become part of taskresults instead of vars
762
765
  af = result['ansible_facts']
763
766
  variables['ansible_facts'] = combine_vars(variables.get('ansible_facts', {}), namespace_facts(af))
764
- if C.INJECT_FACTS_AS_VARS:
765
- # DTFIX-FUTURE: why is this happening twice, esp since we're post-fork and these will be discarded?
766
- cleaned_toplevel = {k: _deprecate_top_level_fact(v) for k, v in clean_facts(af).items()}
767
+ if _INJECT_FACTS:
768
+ if _INJECT_FACTS_ORIGIN == 'default':
769
+ # This happens x2 due to loops and being able to use values in subsequent iterations
770
+ # these copies are later discared in favor of 'total/final' one on loop end.
771
+ cleaned_toplevel = {k: _deprecate_top_level_fact(v) for k, v in clean_facts(af).items()}
772
+ else:
773
+ cleaned_toplevel = clean_facts(af)
767
774
  variables.update(cleaned_toplevel)
768
775
 
769
776
  # save the notification target in the result, if it was specified, as
@@ -776,14 +783,18 @@ class TaskExecutor:
776
783
  # on the results side without having to do any further templating
777
784
  # also now add connection vars results when delegating
778
785
  if self._task.delegate_to:
779
- result["_ansible_delegated_vars"] = {'ansible_delegated_host': self._task.delegate_to}
780
- for k in plugin_vars:
781
- result["_ansible_delegated_vars"][k] = cvars.get(k)
786
+ result["_ansible_delegated_vars"] = {
787
+ "ansible_delegated_host": self._task.delegate_to,
788
+ "ansible_connection": current_connection,
789
+ }
782
790
 
783
791
  # note: here for callbacks that rely on this info to display delegation
784
- for requireshed in ('ansible_host', 'ansible_port', 'ansible_user', 'ansible_connection'):
785
- if requireshed not in result["_ansible_delegated_vars"] and requireshed in cvars:
786
- result["_ansible_delegated_vars"][requireshed] = cvars.get(requireshed)
792
+ for k in plugin_vars:
793
+ if k not in _SUB_PRESERVE["_ansible_delegated_vars"]:
794
+ continue
795
+
796
+ for o in C.config.get_plugin_options_from_var("connection", current_connection, k):
797
+ result["_ansible_delegated_vars"][k] = self._connection.get_option(o)
787
798
 
788
799
  # and return
789
800
  display.debug("attempt loop complete, returning result")
@@ -958,9 +969,6 @@ class TaskExecutor:
958
969
 
959
970
  self._play_context.connection = current_connection
960
971
 
961
- # TODO: play context has logic to update the connection for 'smart'
962
- # (default value, will chose between ssh and paramiko) and 'persistent'
963
- # (really paramiko), eventually this should move to task object itself.
964
972
  conn_type = self._play_context.connection
965
973
 
966
974
  connection, plugin_load_context = self._shared_loader_obj.connection_loader.get_with_context(
@@ -1215,7 +1223,7 @@ def start_connection(play_context, options, task_uuid):
1215
1223
  )
1216
1224
 
1217
1225
  write_to_stream(p.stdin, options)
1218
- write_to_stream(p.stdin, play_context.serialize())
1226
+ write_to_stream(p.stdin, play_context.dump_attrs())
1219
1227
 
1220
1228
  (stdout, stderr) = p.communicate()
1221
1229
 
@@ -58,8 +58,6 @@ STDERR_FILENO = 2
58
58
 
59
59
  display = Display()
60
60
 
61
- _T = t.TypeVar('_T')
62
-
63
61
 
64
62
  @dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
65
63
  class CallbackSend:
@@ -413,7 +411,7 @@ class TaskQueueManager:
413
411
  return defunct
414
412
 
415
413
  @staticmethod
416
- def _first_arg_of_type(value_type: t.Type[_T], args: t.Sequence) -> _T | None:
414
+ def _first_arg_of_type[T](value_type: t.Type[T], args: t.Sequence) -> T | None:
417
415
  return next((arg for arg in args if isinstance(arg, value_type)), None)
418
416
 
419
417
  @lock_decorator(attr='_callback_lock')
ansible/galaxy/api.py CHANGED
@@ -25,7 +25,6 @@ from ansible.errors import AnsibleError
25
25
  from ansible.galaxy.user_agent import user_agent
26
26
  from ansible.module_utils.api import retry_with_delays_and_condition
27
27
  from ansible.module_utils.api import generate_jittered_backoff
28
- from ansible.module_utils.six import string_types
29
28
  from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
30
29
  from ansible.module_utils.urls import open_url, prepare_multipart
31
30
  from ansible.utils.display import Display
@@ -114,13 +113,7 @@ def g_connect(versions):
114
113
  # url + '/api/' appended.
115
114
  self.api_server = n_url
116
115
 
117
- # Default to only supporting v1, if only v1 is returned we also assume that v2 is available even though
118
- # it isn't returned in the available_versions dict.
119
- available_versions = data.get('available_versions', {u'v1': u'v1/'})
120
- if list(available_versions.keys()) == [u'v1']:
121
- available_versions[u'v2'] = u'v2/'
122
-
123
- self._available_api_versions = available_versions
116
+ self._available_api_versions = available_versions = data['available_versions']
124
117
  display.vvvv("Found API version '%s' with Galaxy server %s (%s)"
125
118
  % (', '.join(available_versions.keys()), self.name, self.api_server))
126
119
 
@@ -132,15 +125,6 @@ def g_connect(versions):
132
125
  % (method.__name__, ", ".join(versions), ", ".join(available_versions),
133
126
  self.name, self.api_server))
134
127
 
135
- # Warn only when we know we are talking to a collections API
136
- if common_versions == {'v2'}:
137
- display.deprecated(
138
- 'The v2 Ansible Galaxy API is deprecated and no longer supported. '
139
- 'Ensure that you have configured the ansible-galaxy CLI to utilize an '
140
- 'updated and supported version of Ansible Galaxy.',
141
- version='2.20',
142
- )
143
-
144
128
  return method(self, *args, **kwargs)
145
129
  return wrapped
146
130
  return decorator
@@ -214,11 +198,7 @@ class GalaxyError(AnsibleError):
214
198
  err_info = {}
215
199
 
216
200
  url_split = self.url.split('/')
217
- if 'v2' in url_split:
218
- galaxy_msg = err_info.get('message', http_error.reason)
219
- code = err_info.get('code', 'Unknown')
220
- full_error_msg = u"%s (HTTP Code: %d, Message: %s Code: %s)" % (message, self.http_code, galaxy_msg, code)
221
- elif 'v3' in url_split:
201
+ if 'v3' in url_split:
222
202
  errors = err_info.get('errors', [])
223
203
  if not errors:
224
204
  errors = [{}] # Defaults are set below, we just need to make sure 1 error is present.
@@ -340,7 +320,7 @@ class GalaxyAPI:
340
320
  return self._priority > other_galaxy_api._priority
341
321
 
342
322
  @property # type: ignore[misc] # https://github.com/python/mypy/issues/1362
343
- @g_connect(['v1', 'v2', 'v3'])
323
+ @g_connect(['v1', 'v3'])
344
324
  def available_api_versions(self):
345
325
  # Calling g_connect will populate self._available_api_versions
346
326
  return self._available_api_versions
@@ -595,11 +575,11 @@ class GalaxyAPI:
595
575
  page_size = kwargs.get('page_size', None)
596
576
  author = kwargs.get('author', None)
597
577
 
598
- if tags and isinstance(tags, string_types):
578
+ if tags and isinstance(tags, str):
599
579
  tags = tags.split(',')
600
580
  search_url += '&tags_autocomplete=' + '+'.join(tags)
601
581
 
602
- if platforms and isinstance(platforms, string_types):
582
+ if platforms and isinstance(platforms, str):
603
583
  platforms = platforms.split(',')
604
584
  search_url += '&platforms_autocomplete=' + '+'.join(platforms)
605
585
 
@@ -645,7 +625,7 @@ class GalaxyAPI:
645
625
 
646
626
  # Collection APIs #
647
627
 
648
- @g_connect(['v2', 'v3'])
628
+ @g_connect(['v3'])
649
629
  def publish_collection(self, collection_path):
650
630
  """
651
631
  Publishes a collection to a Galaxy server and returns the import task URI.
@@ -680,19 +660,15 @@ class GalaxyAPI:
680
660
  'Content-length': len(b_form_data),
681
661
  }
682
662
 
683
- if 'v3' in self.available_api_versions:
684
- n_url = _urljoin(self.api_server, self.available_api_versions['v3'], 'artifacts', 'collections') + '/'
685
- else:
686
- n_url = _urljoin(self.api_server, self.available_api_versions['v2'], 'collections') + '/'
687
-
663
+ n_url = _urljoin(self.api_server, self.available_api_versions['v3'], 'artifacts', 'collections') + '/'
688
664
  resp = self._call_galaxy(n_url, args=b_form_data, headers=headers, method='POST', auth_required=True,
689
665
  error_context_msg='Error when publishing collection to %s (%s)'
690
666
  % (self.name, self.api_server))
691
667
 
692
- return resp['task']
668
+ return urljoin(self.api_server, resp['task'])
693
669
 
694
- @g_connect(['v2', 'v3'])
695
- def wait_import_task(self, task_id, timeout=0):
670
+ @g_connect(['v3'])
671
+ def wait_import_task(self, task_url, timeout=0):
696
672
  """
697
673
  Waits until the import process on the Galaxy server has completed or the timeout is reached.
698
674
 
@@ -703,22 +679,14 @@ class GalaxyAPI:
703
679
  state = 'waiting'
704
680
  data = None
705
681
 
706
- # Construct the appropriate URL per version
707
- if 'v3' in self.available_api_versions:
708
- full_url = _urljoin(self.api_server, self.available_api_versions['v3'],
709
- 'imports/collections', task_id, '/')
710
- else:
711
- full_url = _urljoin(self.api_server, self.available_api_versions['v2'],
712
- 'collection-imports', task_id, '/')
713
-
714
- display.display("Waiting until Galaxy import task %s has completed" % full_url)
682
+ display.display("Waiting until Galaxy import task %s has completed" % task_url)
715
683
  start = time.time()
716
684
  wait = C.GALAXY_COLLECTION_IMPORT_POLL_INTERVAL
717
685
 
718
686
  while timeout == 0 or (time.time() - start) < timeout:
719
687
  try:
720
- data = self._call_galaxy(full_url, method='GET', auth_required=True,
721
- error_context_msg='Error when getting import task results at %s' % full_url)
688
+ data = self._call_galaxy(task_url, method='GET', auth_required=True,
689
+ error_context_msg='Error when getting import task results at %s' % task_url)
722
690
  except GalaxyError as e:
723
691
  if e.http_code != 404:
724
692
  raise
@@ -740,7 +708,7 @@ class GalaxyAPI:
740
708
  wait = min(30, wait * C.GALAXY_COLLECTION_IMPORT_POLL_FACTOR)
741
709
  if state == 'waiting':
742
710
  raise AnsibleError("Timeout while waiting for the Galaxy import process to finish, check progress at '%s'"
743
- % to_native(full_url))
711
+ % to_native(task_url))
744
712
 
745
713
  for message in data.get('messages', []):
746
714
  level = message['level']
@@ -754,10 +722,10 @@ class GalaxyAPI:
754
722
  if state == 'failed':
755
723
  code = to_native(data['error'].get('code', 'UNKNOWN'))
756
724
  description = to_native(
757
- data['error'].get('description', "Unknown error, see %s for more details" % full_url))
725
+ data['error'].get('description', "Unknown error, see %s for more details" % task_url))
758
726
  raise AnsibleError("Galaxy import process failed: %s (Code: %s)" % (description, code))
759
727
 
760
- @g_connect(['v2', 'v3'])
728
+ @g_connect(['v3'])
761
729
  def get_collection_metadata(self, namespace, name):
762
730
  """
763
731
  Gets the collection information from the Galaxy server about a specific Collection.
@@ -766,18 +734,11 @@ class GalaxyAPI:
766
734
  :param name: The collection name.
767
735
  return: CollectionMetadata about the collection.
768
736
  """
769
- if 'v3' in self.available_api_versions:
770
- api_path = self.available_api_versions['v3']
771
- field_map = [
772
- ('created_str', 'created_at'),
773
- ('modified_str', 'updated_at'),
774
- ]
775
- else:
776
- api_path = self.available_api_versions['v2']
777
- field_map = [
778
- ('created_str', 'created'),
779
- ('modified_str', 'modified'),
780
- ]
737
+ api_path = self.available_api_versions['v3']
738
+ field_map = [
739
+ ('created_str', 'created_at'),
740
+ ('modified_str', 'updated_at'),
741
+ ]
781
742
 
782
743
  info_url = _urljoin(self.api_server, api_path, 'collections', namespace, name, '/')
783
744
  error_context_msg = 'Error when getting the collection info for %s.%s from %s (%s)' \
@@ -790,7 +751,7 @@ class GalaxyAPI:
790
751
 
791
752
  return CollectionMetadata(namespace, name, **metadata)
792
753
 
793
- @g_connect(['v2', 'v3'])
754
+ @g_connect(['v3'])
794
755
  def get_collection_version_metadata(self, namespace, name, version):
795
756
  """
796
757
  Gets the collection information from the Galaxy server about a specific Collection version.
@@ -800,7 +761,7 @@ class GalaxyAPI:
800
761
  :param version: Version of the collection to get the information for.
801
762
  :return: CollectionVersionMetadata about the collection at the version requested.
802
763
  """
803
- api_path = self.available_api_versions.get('v3', self.available_api_versions.get('v2'))
764
+ api_path = self.available_api_versions['v3']
804
765
  url_paths = [self.api_server, api_path, 'collections', namespace, name, 'versions', version, '/']
805
766
 
806
767
  n_collection_url = _urljoin(*url_paths)
@@ -824,7 +785,7 @@ class GalaxyAPI:
824
785
  download_url, data['artifact']['sha256'],
825
786
  data['metadata']['dependencies'], data['href'], signatures)
826
787
 
827
- @g_connect(['v2', 'v3'])
788
+ @g_connect(['v3'])
828
789
  def get_collection_versions(self, namespace, name):
829
790
  """
830
791
  Gets a list of available versions for a collection on a Galaxy server.
@@ -833,17 +794,10 @@ class GalaxyAPI:
833
794
  :param name: The collection name.
834
795
  :return: A list of versions that are available.
835
796
  """
836
- relative_link = False
837
- if 'v3' in self.available_api_versions:
838
- api_path = self.available_api_versions['v3']
839
- pagination_path = ['links', 'next']
840
- relative_link = True # AH pagination results are relative an not an absolute URI.
841
- else:
842
- api_path = self.available_api_versions['v2']
843
- pagination_path = ['next']
797
+ api_path = self.available_api_versions['v3']
798
+ pagination_path = ['links', 'next']
844
799
 
845
- page_size_name = 'limit' if 'v3' in self.available_api_versions else 'page_size'
846
- versions_url = _urljoin(self.api_server, api_path, 'collections', namespace, name, 'versions', '/?%s=%d' % (page_size_name, COLLECTION_PAGE_SIZE))
800
+ versions_url = _urljoin(self.api_server, api_path, 'collections', namespace, name, 'versions', '/?limit=%d' % COLLECTION_PAGE_SIZE)
847
801
  versions_url_info = urlparse(versions_url)
848
802
  cache_key = versions_url_info.path
849
803
 
@@ -898,11 +852,10 @@ class GalaxyAPI:
898
852
 
899
853
  if not next_link:
900
854
  break
901
- elif relative_link:
902
- next_link_info = urlparse(next_link)
903
- if not next_link_info.scheme and not next_link_info.path.startswith('/'):
904
- raise AnsibleError(f'Invalid non absolute pagination link: {next_link}')
905
- next_link = urljoin(self.api_server, next_link)
855
+ next_link_info = urlparse(next_link)
856
+ if not next_link_info.scheme and not next_link_info.path.startswith('/'):
857
+ raise AnsibleError(f'Invalid non absolute pagination link: {next_link}')
858
+ next_link = urljoin(self.api_server, next_link)
906
859
 
907
860
  data = self._call_galaxy(to_native(next_link, errors='surrogate_or_strict'),
908
861
  error_context_msg=error_context_msg, cache=True, cache_key=cache_key)
@@ -910,7 +863,7 @@ class GalaxyAPI:
910
863
 
911
864
  return versions
912
865
 
913
- @g_connect(['v2', 'v3'])
866
+ @g_connect(['v3'])
914
867
  def get_collection_signatures(self, namespace, name, version):
915
868
  """
916
869
  Gets the collection signatures from the Galaxy server about a specific Collection version.
@@ -920,7 +873,7 @@ class GalaxyAPI:
920
873
  :param version: Version of the collection to get the information for.
921
874
  :return: A list of signature strings.
922
875
  """
923
- api_path = self.available_api_versions.get('v3', self.available_api_versions.get('v2'))
876
+ api_path = self.available_api_versions['v3']
924
877
  url_paths = [self.api_server, api_path, 'collections', namespace, name, 'versions', version, '/']
925
878
 
926
879
  n_collection_url = _urljoin(*url_paths)
@@ -339,12 +339,12 @@ def verify_local_collection(local_collection, remote_collection, artifacts_manag
339
339
  ]
340
340
 
341
341
  # Find any paths not in the FILES.json
342
- for root, dirs, files in os.walk(b_collection_path):
343
- for name in files:
342
+ for root, dirs, filenames in os.walk(b_collection_path):
343
+ for name in filenames:
344
344
  full_path = os.path.join(root, name)
345
345
  path = to_text(full_path[len(b_collection_path) + 1::], errors='surrogate_or_strict')
346
346
  if any(fnmatch.fnmatch(full_path, b_pattern) for b_pattern in b_ignore_patterns):
347
- display.v("Ignoring verification for %s" % full_path)
347
+ display.v("Ignoring verification for %s" % to_text(full_path))
348
348
  continue
349
349
 
350
350
  if full_path not in collection_files:
@@ -623,24 +623,11 @@ def publish_collection(collection_path, api, wait, timeout):
623
623
  import_uri = api.publish_collection(collection_path)
624
624
 
625
625
  if wait:
626
- # Galaxy returns a url fragment which differs between v2 and v3. The second to last entry is
627
- # always the task_id, though.
628
- # v2: {"task": "https://galaxy-dev.ansible.com/api/v2/collection-imports/35573/"}
629
- # v3: {"task": "/api/automation-hub/v3/imports/collections/838d1308-a8f4-402c-95cb-7823f3806cd8/"}
630
- task_id = None
631
- for path_segment in reversed(import_uri.split('/')):
632
- if path_segment:
633
- task_id = path_segment
634
- break
635
-
636
- if not task_id:
637
- raise AnsibleError("Publishing the collection did not return valid task info. Cannot wait for task status. Returned task info: '%s'" % import_uri)
638
-
639
626
  with _display_progress(
640
627
  "Collection has been published to the Galaxy server "
641
628
  "{api.name!s} {api.api_server!s}".format(api=api),
642
629
  ):
643
- api.wait_import_task(task_id, timeout)
630
+ api.wait_import_task(import_uri, timeout)
644
631
  display.display("Collection has been successfully published and imported to the Galaxy server %s %s"
645
632
  % (api.name, api.api_server))
646
633
  else:
@@ -26,9 +26,6 @@ if t.TYPE_CHECKING:
26
26
  '_ComputedReqKindsMixin',
27
27
  )
28
28
 
29
- import ansible
30
- import ansible.release
31
-
32
29
  from ansible.errors import AnsibleError, AnsibleAssertionError
33
30
  from ansible.galaxy.api import GalaxyAPI
34
31
  from ansible.galaxy.collection import HAS_PACKAGING, PkgReq
@@ -42,7 +39,6 @@ _ALLOW_CONCRETE_POINTER_IN_SOURCE = False # NOTE: This is a feature flag
42
39
  _GALAXY_YAML = b'galaxy.yml'
43
40
  _MANIFEST_JSON = b'MANIFEST.json'
44
41
  _SOURCE_METADATA_FILE = b'GALAXY.yml'
45
- _ANSIBLE_PACKAGE_PATH = pathlib.Path(ansible.__file__).parent
46
42
 
47
43
  display = Display()
48
44
 
@@ -229,12 +225,6 @@ class _ComputedReqKindsMixin:
229
225
  dir_path = dir_path.rstrip(to_bytes(os.path.sep))
230
226
  if not _is_collection_dir(dir_path):
231
227
  dir_pathlib = pathlib.Path(to_text(dir_path))
232
-
233
- # special handling for bundled collections without manifests, e.g., ansible._protomatter
234
- if dir_pathlib.is_relative_to(_ANSIBLE_PACKAGE_PATH):
235
- req_name = f'{dir_pathlib.parent.name}.{dir_pathlib.name}'
236
- return cls(req_name, ansible.release.__version__, dir_path, 'dir', None)
237
-
238
228
  display.warning(
239
229
  u"Collection at '{path!s}' does not have a {manifest_json!s} "
240
230
  u'file, nor has it {galaxy_yml!s}: cannot detect version.'.