ansible-core 2.19.4rc1__py3-none-any.whl → 2.20.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.
- ansible/_internal/__init__.py +1 -4
- ansible/_internal/_ansiballz/_builder.py +1 -3
- ansible/_internal/_collection_proxy.py +7 -9
- ansible/_internal/_json/__init__.py +3 -4
- ansible/_internal/_templating/_engine.py +1 -1
- ansible/_internal/_templating/_jinja_plugins.py +1 -2
- ansible/_internal/_wrapt.py +105 -301
- ansible/cli/__init__.py +11 -10
- ansible/cli/adhoc.py +1 -2
- ansible/cli/arguments/option_helpers.py +1 -1
- ansible/cli/config.py +5 -6
- ansible/cli/doc.py +67 -67
- ansible/cli/galaxy.py +15 -24
- ansible/cli/inventory.py +0 -1
- ansible/cli/playbook.py +0 -1
- ansible/cli/pull.py +0 -1
- ansible/cli/scripts/ansible_connection_cli_stub.py +1 -1
- ansible/config/base.yml +1 -25
- ansible/config/manager.py +0 -2
- ansible/executor/play_iterator.py +42 -20
- ansible/executor/playbook_executor.py +0 -9
- ansible/executor/task_executor.py +26 -18
- ansible/executor/task_queue_manager.py +1 -3
- ansible/galaxy/api.py +33 -80
- ansible/galaxy/collection/__init__.py +11 -21
- ansible/galaxy/dependency_resolution/__init__.py +10 -9
- ansible/galaxy/dependency_resolution/dataclasses.py +86 -70
- ansible/galaxy/dependency_resolution/providers.py +54 -134
- ansible/galaxy/dependency_resolution/versioning.py +2 -4
- ansible/galaxy/role.py +1 -33
- ansible/inventory/manager.py +2 -3
- ansible/keyword_desc.yml +0 -3
- ansible/module_utils/_internal/_datatag/__init__.py +2 -10
- ansible/module_utils/_internal/_no_six.py +86 -0
- ansible/module_utils/_text.py +28 -8
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/basic.py +26 -23
- ansible/module_utils/common/_collections_compat.py +11 -2
- ansible/module_utils/common/collections.py +8 -3
- ansible/module_utils/common/dict_transformations.py +1 -2
- ansible/module_utils/common/network.py +4 -2
- ansible/module_utils/common/parameters.py +32 -41
- ansible/module_utils/common/text/converters.py +109 -23
- ansible/module_utils/common/text/formatters.py +6 -2
- ansible/module_utils/common/validation.py +11 -9
- ansible/module_utils/connection.py +8 -3
- ansible/module_utils/facts/hardware/linux.py +23 -7
- ansible/module_utils/facts/hardware/netbsd.py +1 -1
- ansible/module_utils/facts/hardware/sunos.py +2 -1
- ansible/module_utils/facts/packages.py +6 -2
- ansible/module_utils/facts/system/distribution.py +2 -1
- ansible/module_utils/facts/system/env.py +6 -3
- ansible/module_utils/facts/system/local.py +3 -1
- ansible/module_utils/parsing/convert_bool.py +6 -2
- ansible/module_utils/service.py +2 -3
- ansible/module_utils/six/__init__.py +11 -6
- ansible/module_utils/yumdnf.py +0 -5
- ansible/modules/apt.py +18 -13
- ansible/modules/apt_repository.py +1 -1
- ansible/modules/assemble.py +5 -9
- ansible/modules/blockinfile.py +39 -23
- ansible/modules/cron.py +26 -35
- ansible/modules/deb822_repository.py +83 -12
- ansible/modules/dnf.py +3 -7
- ansible/modules/dnf5.py +4 -6
- ansible/modules/expect.py +0 -3
- ansible/modules/find.py +1 -2
- ansible/modules/get_url.py +1 -1
- ansible/modules/git.py +4 -5
- ansible/modules/include_vars.py +1 -1
- ansible/modules/known_hosts.py +7 -1
- ansible/modules/lineinfile.py +71 -63
- ansible/modules/package_facts.py +1 -1
- ansible/modules/pip.py +8 -2
- ansible/modules/replace.py +6 -6
- ansible/modules/service.py +3 -4
- ansible/modules/stat.py +20 -0
- ansible/modules/uri.py +9 -10
- ansible/modules/user.py +1 -2
- ansible/modules/wait_for.py +2 -2
- ansible/modules/wait_for_connection.py +2 -1
- ansible/modules/yum_repository.py +1 -16
- ansible/parsing/dataloader.py +24 -31
- ansible/parsing/vault/__init__.py +1 -2
- ansible/playbook/base.py +8 -56
- ansible/playbook/block.py +0 -60
- ansible/playbook/collectionsearch.py +1 -2
- ansible/playbook/handler.py +1 -7
- ansible/playbook/helpers.py +0 -7
- ansible/playbook/included_file.py +1 -1
- ansible/playbook/play.py +102 -36
- ansible/playbook/play_context.py +4 -0
- ansible/playbook/role/__init__.py +10 -65
- ansible/playbook/role/definition.py +3 -4
- ansible/playbook/role/include.py +2 -3
- ansible/playbook/role/metadata.py +1 -12
- ansible/playbook/role/requirement.py +1 -2
- ansible/playbook/role_include.py +1 -2
- ansible/playbook/taggable.py +16 -5
- ansible/playbook/task.py +11 -50
- ansible/plugins/action/__init__.py +20 -19
- ansible/plugins/action/add_host.py +1 -2
- ansible/plugins/action/fetch.py +3 -5
- ansible/plugins/action/group_by.py +1 -2
- ansible/plugins/action/include_vars.py +20 -22
- ansible/plugins/action/script.py +1 -3
- ansible/plugins/action/template.py +1 -2
- ansible/plugins/action/uri.py +4 -2
- ansible/plugins/cache/__init__.py +1 -0
- ansible/plugins/callback/__init__.py +13 -6
- ansible/plugins/connection/__init__.py +3 -7
- ansible/plugins/connection/local.py +2 -3
- ansible/plugins/connection/psrp.py +0 -2
- ansible/plugins/connection/ssh.py +2 -7
- ansible/plugins/connection/winrm.py +0 -2
- ansible/plugins/doc_fragments/result_format_callback.py +15 -0
- ansible/plugins/filter/core.py +4 -5
- ansible/plugins/filter/encryption.py +3 -27
- ansible/plugins/filter/mathstuff.py +1 -2
- ansible/plugins/filter/to_nice_yaml.yml +31 -3
- ansible/plugins/filter/to_yaml.yml +29 -12
- ansible/plugins/inventory/__init__.py +1 -2
- ansible/plugins/inventory/toml.py +3 -6
- ansible/plugins/inventory/yaml.py +1 -2
- ansible/plugins/loader.py +3 -4
- ansible/plugins/lookup/password.py +1 -2
- ansible/plugins/lookup/subelements.py +2 -3
- ansible/plugins/lookup/url.py +1 -1
- ansible/plugins/lookup/varnames.py +1 -2
- ansible/plugins/shell/__init__.py +9 -4
- ansible/plugins/shell/powershell.py +8 -24
- ansible/plugins/strategy/__init__.py +5 -2
- ansible/plugins/test/core.py +4 -1
- ansible/plugins/test/falsy.yml +1 -1
- ansible/plugins/test/regex.yml +18 -6
- ansible/plugins/test/truthy.yml +1 -1
- ansible/release.py +2 -2
- ansible/template/__init__.py +3 -7
- ansible/utils/collection_loader/_collection_config.py +5 -0
- ansible/utils/collection_loader/_collection_finder.py +11 -14
- ansible/utils/context_objects.py +7 -4
- ansible/utils/display.py +7 -6
- ansible/utils/encrypt.py +0 -5
- ansible/utils/helpers.py +6 -2
- ansible/utils/jsonrpc.py +7 -3
- ansible/utils/plugin_docs.py +49 -38
- ansible/utils/ssh_functions.py +0 -19
- ansible/utils/unsafe_proxy.py +7 -7
- ansible/vars/clean.py +2 -3
- ansible/vars/manager.py +28 -22
- ansible/vars/plugins.py +1 -31
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/METADATA +4 -4
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/RECORD +213 -214
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/network.txt +0 -1
- ansible_test/_data/completion/remote.txt +4 -4
- ansible_test/_data/requirements/ansible-test.txt +1 -1
- ansible_test/_data/requirements/ansible.txt +1 -1
- ansible_test/_data/requirements/sanity.ansible-doc.txt +2 -2
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
- ansible_test/_data/requirements/sanity.import.txt +1 -1
- ansible_test/_data/requirements/sanity.integration-aliases.txt +1 -1
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +6 -6
- ansible_test/_data/requirements/sanity.runtime-metadata.txt +1 -1
- ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_internal/cache.py +2 -5
- ansible_test/_internal/cli/compat.py +1 -1
- ansible_test/_internal/commands/coverage/combine.py +1 -3
- ansible_test/_internal/commands/integration/__init__.py +3 -7
- ansible_test/_internal/commands/integration/cloud/httptester.py +1 -1
- ansible_test/_internal/commands/integration/coverage.py +1 -3
- ansible_test/_internal/commands/integration/filters.py +5 -10
- ansible_test/_internal/commands/sanity/pylint.py +11 -0
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -5
- ansible_test/_internal/commands/units/__init__.py +1 -13
- ansible_test/_internal/compat/packaging.py +2 -2
- ansible_test/_internal/compat/yaml.py +2 -2
- ansible_test/_internal/completion.py +2 -5
- ansible_test/_internal/config.py +2 -7
- ansible_test/_internal/coverage_util.py +1 -1
- ansible_test/_internal/delegation.py +2 -0
- ansible_test/_internal/docker_util.py +1 -1
- ansible_test/_internal/host_profiles.py +6 -11
- ansible_test/_internal/provider/__init__.py +2 -5
- ansible_test/_internal/provisioning.py +2 -5
- ansible_test/_internal/pypi_proxy.py +1 -1
- ansible_test/_internal/python_requirements.py +1 -1
- ansible_test/_internal/target.py +2 -6
- ansible_test/_internal/thread.py +1 -4
- ansible_test/_internal/util.py +9 -14
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +14 -19
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +48 -45
- ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +9 -7
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +51 -37
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +31 -18
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +1 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +59 -71
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -2
- ansible_test/_util/target/cli/ansible_test_cli_stub.py +4 -2
- ansible_test/_util/target/common/constants.py +2 -2
- ansible_test/_util/target/setup/bootstrap.sh +0 -6
- ansible/utils/py3compat.py +0 -27
- ansible_test/_data/pytest/config/legacy.ini +0 -4
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.19.4rc1.dist-info → ansible_core-2.20.0.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
|
|
275
|
-
#
|
|
276
|
-
#
|
|
277
|
-
# the specified host.
|
|
278
|
-
|
|
279
|
-
|
|
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
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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,
|
|
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
|
|
670
|
-
|
|
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
|
|
765
|
-
|
|
766
|
-
|
|
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"] = {
|
|
780
|
-
|
|
781
|
-
|
|
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
|
|
785
|
-
if
|
|
786
|
-
|
|
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.
|
|
1226
|
+
write_to_stream(p.stdin, play_context.dump_attrs())
|
|
1219
1227
|
|
|
1220
1228
|
(stdout, stderr) = p.communicate()
|
|
1221
1229
|
|
|
@@ -60,8 +60,6 @@ STDERR_FILENO = 2
|
|
|
60
60
|
|
|
61
61
|
display = Display()
|
|
62
62
|
|
|
63
|
-
_T = t.TypeVar('_T')
|
|
64
|
-
|
|
65
63
|
|
|
66
64
|
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
|
67
65
|
class CallbackSend:
|
|
@@ -455,7 +453,7 @@ class TaskQueueManager:
|
|
|
455
453
|
return defunct
|
|
456
454
|
|
|
457
455
|
@staticmethod
|
|
458
|
-
def _first_arg_of_type(value_type: t.Type[
|
|
456
|
+
def _first_arg_of_type[T](value_type: t.Type[T], args: t.Sequence) -> T | None:
|
|
459
457
|
return next((arg for arg in args if isinstance(arg, value_type)), None)
|
|
460
458
|
|
|
461
459
|
@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
|
-
|
|
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 '
|
|
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', '
|
|
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,
|
|
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,
|
|
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(['
|
|
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
|
-
|
|
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(['
|
|
695
|
-
def wait_import_task(self,
|
|
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
|
-
|
|
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(
|
|
721
|
-
error_context_msg='Error when getting import task results at %s' %
|
|
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(
|
|
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" %
|
|
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(['
|
|
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
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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(['
|
|
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
|
|
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(['
|
|
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
|
-
|
|
837
|
-
|
|
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
|
-
|
|
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
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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(['
|
|
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
|
|
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,
|
|
343
|
-
for name in
|
|
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(
|
|
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:
|
|
@@ -1852,10 +1839,13 @@ def _resolve_depenency_map(
|
|
|
1852
1839
|
offline=offline,
|
|
1853
1840
|
)
|
|
1854
1841
|
try:
|
|
1855
|
-
return
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1842
|
+
return t.cast(
|
|
1843
|
+
dict[str, Candidate],
|
|
1844
|
+
collection_dep_resolver.resolve(
|
|
1845
|
+
requested_requirements,
|
|
1846
|
+
max_rounds=2000000, # NOTE: same constant pip uses
|
|
1847
|
+
).mapping,
|
|
1848
|
+
)
|
|
1859
1849
|
except CollectionDependencyResolutionImpossible as dep_exc:
|
|
1860
1850
|
conflict_causes = (
|
|
1861
1851
|
'* {req.fqcn!s}:{req.ver!s} ({dep_origin!s})'.format(
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import collections.abc as _c
|
|
8
9
|
import typing as t
|
|
9
10
|
|
|
10
11
|
if t.TYPE_CHECKING:
|
|
@@ -21,15 +22,15 @@ from ansible.galaxy.dependency_resolution.resolvers import CollectionDependencyR
|
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def build_collection_dependency_resolver(
|
|
24
|
-
galaxy_apis
|
|
25
|
-
concrete_artifacts_manager
|
|
26
|
-
preferred_candidates
|
|
27
|
-
with_deps=True,
|
|
28
|
-
with_pre_releases=False,
|
|
29
|
-
upgrade=False,
|
|
30
|
-
include_signatures=True,
|
|
31
|
-
offline=False,
|
|
32
|
-
)
|
|
25
|
+
galaxy_apis: _c.Iterable[GalaxyAPI],
|
|
26
|
+
concrete_artifacts_manager: ConcreteArtifactsManager,
|
|
27
|
+
preferred_candidates: _c.Iterable[Candidate] | None = None,
|
|
28
|
+
with_deps: bool = True,
|
|
29
|
+
with_pre_releases: bool = False,
|
|
30
|
+
upgrade: bool = False,
|
|
31
|
+
include_signatures: bool = True,
|
|
32
|
+
offline: bool = False,
|
|
33
|
+
) -> CollectionDependencyResolver:
|
|
33
34
|
"""Return a collection dependency resolver.
|
|
34
35
|
|
|
35
36
|
The returned instance will have a ``resolve()`` method for
|