ansible-core 2.19.0b2__py3-none-any.whl → 2.19.0b3__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.
- ansible/_internal/_ansiballz.py +1 -4
- ansible/_internal/_templating/_datatag.py +3 -4
- ansible/_internal/_templating/_engine.py +6 -1
- ansible/_internal/_templating/_jinja_plugins.py +2 -6
- ansible/cli/__init__.py +3 -2
- ansible/cli/arguments/option_helpers.py +4 -1
- ansible/cli/doc.py +0 -1
- ansible/config/manager.py +2 -2
- ansible/constants.py +0 -62
- ansible/errors/__init__.py +6 -2
- ansible/executor/module_common.py +11 -7
- ansible/executor/task_executor.py +6 -8
- ansible/galaxy/api.py +1 -1
- ansible/galaxy/collection/__init__.py +3 -3
- ansible/module_utils/_internal/_ansiballz.py +4 -30
- ansible/module_utils/_internal/_datatag/_tags.py +3 -25
- ansible/module_utils/_internal/_deprecator.py +134 -0
- ansible/module_utils/_internal/_plugin_info.py +25 -0
- ansible/module_utils/_internal/_validation.py +14 -0
- ansible/module_utils/ansible_release.py +1 -1
- ansible/module_utils/basic.py +64 -17
- ansible/module_utils/common/arg_spec.py +8 -3
- ansible/module_utils/common/messages.py +40 -23
- ansible/module_utils/common/process.py +0 -1
- ansible/module_utils/common/respawn.py +0 -7
- ansible/module_utils/common/warnings.py +13 -13
- ansible/module_utils/datatag.py +13 -13
- ansible/modules/async_status.py +1 -1
- ansible/modules/dnf5.py +1 -1
- ansible/modules/get_url.py +1 -1
- ansible/playbook/task.py +0 -2
- ansible/plugins/__init__.py +18 -8
- ansible/plugins/action/__init__.py +6 -14
- ansible/plugins/action/gather_facts.py +2 -4
- ansible/plugins/callback/oneline.py +7 -1
- ansible/plugins/callback/tree.py +7 -1
- ansible/plugins/connection/local.py +1 -1
- ansible/plugins/connection/paramiko_ssh.py +9 -2
- ansible/plugins/doc_fragments/action_core.py +1 -1
- ansible/plugins/filter/core.py +4 -1
- ansible/plugins/inventory/__init__.py +2 -2
- ansible/plugins/loader.py +194 -130
- ansible/plugins/lookup/url.py +2 -2
- ansible/plugins/strategy/__init__.py +6 -6
- ansible/release.py +1 -1
- ansible/template/__init__.py +1 -1
- ansible/utils/collection_loader/_collection_meta.py +5 -3
- ansible/utils/display.py +133 -71
- ansible/utils/py3compat.py +1 -7
- ansible/utils/ssh_functions.py +4 -1
- ansible/vars/manager.py +18 -10
- ansible/vars/plugins.py +4 -4
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/RECORD +67 -65
- ansible_test/_internal/commands/sanity/pylint.py +1 -0
- ansible_test/_internal/docker_util.py +4 -3
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +475 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
- ansible/module_utils/_internal/_dataclass_annotation_patch.py +0 -64
- ansible/module_utils/_internal/_plugin_exec_context.py +0 -49
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -399
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/Apache-License.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/COPYING +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/MIT-license.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/PSF-license.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b3.dist-info}/top_level.txt +0 -0
ansible/_internal/_ansiballz.py
CHANGED
@@ -42,7 +42,6 @@ def _ansiballz_main(
|
|
42
42
|
module_fqn: str,
|
43
43
|
params: str,
|
44
44
|
profile: str,
|
45
|
-
plugin_info_dict: dict[str, object],
|
46
45
|
date_time: datetime.datetime,
|
47
46
|
coverage_config: str | None,
|
48
47
|
coverage_output: str | None,
|
@@ -142,7 +141,6 @@ def _ansiballz_main(
|
|
142
141
|
run_module(
|
143
142
|
json_params=json_params,
|
144
143
|
profile=profile,
|
145
|
-
plugin_info_dict=plugin_info_dict,
|
146
144
|
module_fqn=module_fqn,
|
147
145
|
modlib_path=modlib_path,
|
148
146
|
coverage_config=coverage_config,
|
@@ -230,13 +228,12 @@ def _ansiballz_main(
|
|
230
228
|
run_module(
|
231
229
|
json_params=json_params,
|
232
230
|
profile=profile,
|
233
|
-
plugin_info_dict=plugin_info_dict,
|
234
231
|
module_fqn=module_fqn,
|
235
232
|
modlib_path=modlib_path,
|
236
233
|
)
|
237
234
|
|
238
235
|
else:
|
239
|
-
print('
|
236
|
+
print(f'FATAL: Unknown debug command {command!r}. Doing nothing.')
|
240
237
|
|
241
238
|
#
|
242
239
|
# See comments in the debug() method for information on debugging
|
@@ -12,7 +12,6 @@ from ansible.utils.display import Display
|
|
12
12
|
from ._access import NotifiableAccessContextBase
|
13
13
|
from ._utils import TemplateContext
|
14
14
|
|
15
|
-
|
16
15
|
display = Display()
|
17
16
|
|
18
17
|
|
@@ -57,10 +56,10 @@ class DeprecatedAccessAuditContext(NotifiableAccessContextBase):
|
|
57
56
|
display._deprecated_with_plugin_info(
|
58
57
|
msg=msg,
|
59
58
|
help_text=item.deprecated.help_text,
|
60
|
-
version=item.deprecated.
|
61
|
-
date=item.deprecated.
|
59
|
+
version=item.deprecated.version,
|
60
|
+
date=item.deprecated.date,
|
62
61
|
obj=item.template,
|
63
|
-
|
62
|
+
deprecator=item.deprecated.deprecator,
|
64
63
|
)
|
65
64
|
|
66
65
|
return result
|
@@ -566,7 +566,12 @@ class TemplateEngine:
|
|
566
566
|
)
|
567
567
|
|
568
568
|
if _TemplateConfig.allow_broken_conditionals:
|
569
|
-
_display.deprecated(
|
569
|
+
_display.deprecated(
|
570
|
+
msg=msg,
|
571
|
+
obj=conditional,
|
572
|
+
help_text=self._BROKEN_CONDITIONAL_ALLOWED_FRAGMENT,
|
573
|
+
version='2.23',
|
574
|
+
)
|
570
575
|
|
571
576
|
return bool_result
|
572
577
|
|
@@ -9,7 +9,6 @@ import functools
|
|
9
9
|
import typing as t
|
10
10
|
|
11
11
|
from ansible.module_utils._internal._ambient_context import AmbientContextBase
|
12
|
-
from ansible.module_utils._internal._plugin_exec_context import PluginExecContext
|
13
12
|
from ansible.module_utils.common.collections import is_sequence
|
14
13
|
from ansible.module_utils._internal._datatag import AnsibleTagHelper
|
15
14
|
from ansible._internal._datatag._tags import TrustedAsTemplate
|
@@ -111,7 +110,7 @@ class JinjaPluginIntercept(c.MutableMapping):
|
|
111
110
|
return first_marker
|
112
111
|
|
113
112
|
try:
|
114
|
-
with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers)
|
113
|
+
with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers):
|
115
114
|
return instance.j2_function(*lazify_container_args(args), **lazify_container_kwargs(kwargs))
|
116
115
|
except MarkerError as ex:
|
117
116
|
return ex.source
|
@@ -212,10 +211,7 @@ def _invoke_lookup(*, plugin_name: str, lookup_terms: list, lookup_kwargs: dict[
|
|
212
211
|
wantlist = lookup_kwargs.pop('wantlist', False)
|
213
212
|
errors = lookup_kwargs.pop('errors', 'strict')
|
214
213
|
|
215
|
-
with (
|
216
|
-
JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers),
|
217
|
-
PluginExecContext(executing_plugin=instance),
|
218
|
-
):
|
214
|
+
with JinjaCallContext(accept_lazy_markers=instance.accept_lazy_markers):
|
219
215
|
try:
|
220
216
|
if _TemplateConfig.allow_embedded_templates:
|
221
217
|
# for backwards compat, only trust constant templates in lookup terms
|
ansible/cli/__init__.py
CHANGED
@@ -10,7 +10,6 @@ import os
|
|
10
10
|
import signal
|
11
11
|
import sys
|
12
12
|
|
13
|
-
|
14
13
|
# We overload the ``ansible`` adhoc command to provide the functionality for
|
15
14
|
# ``SSH_ASKPASS``. This code is here, and not in ``adhoc.py`` to bypass
|
16
15
|
# unnecessary code. The program provided to ``SSH_ASKPASS`` can only be invoked
|
@@ -106,6 +105,7 @@ except Exception as ex:
|
|
106
105
|
|
107
106
|
|
108
107
|
from ansible import context
|
108
|
+
from ansible.utils import display as _display
|
109
109
|
from ansible.cli.arguments import option_helpers as opt_help
|
110
110
|
from ansible.inventory.manager import InventoryManager
|
111
111
|
from ansible.module_utils.six import string_types
|
@@ -122,6 +122,7 @@ from ansible.utils.collection_loader import AnsibleCollectionConfig
|
|
122
122
|
from ansible.utils.collection_loader._collection_finder import _get_collection_name_from_path
|
123
123
|
from ansible.utils.path import unfrackpath
|
124
124
|
from ansible.vars.manager import VariableManager
|
125
|
+
from ansible.module_utils._internal import _deprecator
|
125
126
|
|
126
127
|
try:
|
127
128
|
import argcomplete
|
@@ -257,7 +258,7 @@ class CLI(ABC):
|
|
257
258
|
else:
|
258
259
|
display.v(u"No config file found; using defaults")
|
259
260
|
|
260
|
-
|
261
|
+
_display._report_config_warnings(_deprecator.ANSIBLE_CORE_DEPRECATOR)
|
261
262
|
|
262
263
|
@staticmethod
|
263
264
|
def split_vault_id(vault_id):
|
@@ -56,7 +56,10 @@ class DeprecatedArgument:
|
|
56
56
|
|
57
57
|
from ansible.utils.display import Display
|
58
58
|
|
59
|
-
Display().deprecated(
|
59
|
+
Display().deprecated( # pylint: disable=ansible-invalid-deprecated-version
|
60
|
+
msg=f'The {option!r} argument is deprecated.',
|
61
|
+
version=self.version,
|
62
|
+
)
|
60
63
|
|
61
64
|
|
62
65
|
class ArgumentParser(argparse.ArgumentParser):
|
ansible/cli/doc.py
CHANGED
@@ -1335,7 +1335,6 @@ class DocCLI(CLI, RoleMixin):
|
|
1335
1335
|
'This was unintentionally allowed when plugin attributes were added, '
|
1336
1336
|
'but the feature does not map well to role argument specs.',
|
1337
1337
|
version='2.20',
|
1338
|
-
collection_name='ansible.builtin',
|
1339
1338
|
)
|
1340
1339
|
text.append("")
|
1341
1340
|
text.append(_format("ATTRIBUTES:", 'bold'))
|
ansible/config/manager.py
CHANGED
@@ -480,9 +480,9 @@ class ConfigManager(object):
|
|
480
480
|
else:
|
481
481
|
ret = self._plugins.get(plugin_type, {}).get(name, {})
|
482
482
|
|
483
|
-
if ignore_private:
|
483
|
+
if ignore_private: # ignore 'test' config entries, they should not change runtime behaviors
|
484
484
|
for cdef in list(ret.keys()):
|
485
|
-
if cdef.startswith('
|
485
|
+
if cdef.startswith('_Z_'):
|
486
486
|
del ret[cdef]
|
487
487
|
return ret
|
488
488
|
|
ansible/constants.py
CHANGED
@@ -10,9 +10,7 @@ from string import ascii_letters, digits
|
|
10
10
|
|
11
11
|
from ansible.config.manager import ConfigManager
|
12
12
|
from ansible.module_utils.common.text.converters import to_text
|
13
|
-
from ansible.module_utils.common.collections import Sequence
|
14
13
|
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
|
15
|
-
from ansible.release import __version__
|
16
14
|
from ansible.utils.fqcn import add_internal_fqcns
|
17
15
|
|
18
16
|
# initialize config manager/config data to read/store global settings
|
@@ -20,68 +18,11 @@ from ansible.utils.fqcn import add_internal_fqcns
|
|
20
18
|
config = ConfigManager()
|
21
19
|
|
22
20
|
|
23
|
-
def _warning(msg):
|
24
|
-
""" display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write """
|
25
|
-
try:
|
26
|
-
from ansible.utils.display import Display
|
27
|
-
Display().warning(msg)
|
28
|
-
except Exception:
|
29
|
-
import sys
|
30
|
-
sys.stderr.write(' [WARNING] %s\n' % (msg))
|
31
|
-
|
32
|
-
|
33
|
-
def _deprecated(msg, version):
|
34
|
-
""" display is not guaranteed here, nor it being the full class, but try anyways, fallback to sys.stderr.write """
|
35
|
-
try:
|
36
|
-
from ansible.utils.display import Display
|
37
|
-
Display().deprecated(msg, version=version)
|
38
|
-
except Exception:
|
39
|
-
import sys
|
40
|
-
sys.stderr.write(' [DEPRECATED] %s, to be removed in %s\n' % (msg, version))
|
41
|
-
|
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
|
-
|
65
21
|
def set_constant(name, value, export=vars()):
|
66
22
|
""" sets constants and returns resolved options dict """
|
67
23
|
export[name] = value
|
68
24
|
|
69
25
|
|
70
|
-
class _DeprecatedSequenceConstant(Sequence):
|
71
|
-
def __init__(self, value, msg, version):
|
72
|
-
self._value = value
|
73
|
-
self._msg = msg
|
74
|
-
self._version = version
|
75
|
-
|
76
|
-
def __len__(self):
|
77
|
-
_deprecated(self._msg, self._version)
|
78
|
-
return len(self._value)
|
79
|
-
|
80
|
-
def __getitem__(self, y):
|
81
|
-
_deprecated(self._msg, self._version)
|
82
|
-
return self._value[y]
|
83
|
-
|
84
|
-
|
85
26
|
# CONSTANTS ### yes, actual ones
|
86
27
|
|
87
28
|
# The following are hard-coded action names
|
@@ -245,6 +186,3 @@ MAGIC_VARIABLE_MAPPING = dict(
|
|
245
186
|
# POPULATE SETTINGS FROM CONFIG ###
|
246
187
|
for setting in config.get_configuration_definitions():
|
247
188
|
set_constant(setting, config.get_config_value(setting, variables=vars()))
|
248
|
-
|
249
|
-
# emit any warnings or deprecations
|
250
|
-
handle_config_noise()
|
ansible/errors/__init__.py
CHANGED
@@ -18,6 +18,9 @@ from ..module_utils.datatag import native_type_name
|
|
18
18
|
from ansible._internal._datatag import _tags
|
19
19
|
from .._internal._errors import _utils
|
20
20
|
|
21
|
+
if t.TYPE_CHECKING:
|
22
|
+
from ansible.plugins import loader as _t_loader
|
23
|
+
|
21
24
|
|
22
25
|
class ExitCode(enum.IntEnum):
|
23
26
|
SUCCESS = 0 # used by TQM, must be bit-flag safe
|
@@ -374,8 +377,9 @@ class _AnsibleActionDone(AnsibleAction):
|
|
374
377
|
class AnsiblePluginError(AnsibleError):
|
375
378
|
"""Base class for Ansible plugin-related errors that do not need AnsibleError contextual data."""
|
376
379
|
|
377
|
-
def __init__(self, message=None, plugin_load_context=None):
|
378
|
-
super(AnsiblePluginError, self).__init__(message)
|
380
|
+
def __init__(self, message: str | None = None, plugin_load_context: _t_loader.PluginLoadContext | None = None, help_text: str | None = None) -> None:
|
381
|
+
super(AnsiblePluginError, self).__init__(message, help_text=help_text)
|
382
|
+
|
379
383
|
self.plugin_load_context = plugin_load_context
|
380
384
|
|
381
385
|
|
@@ -39,7 +39,6 @@ from io import BytesIO
|
|
39
39
|
from ansible._internal import _locking
|
40
40
|
from ansible._internal._datatag import _utils
|
41
41
|
from ansible.module_utils._internal import _dataclass_validation
|
42
|
-
from ansible.module_utils.common.messages import PluginInfo
|
43
42
|
from ansible.module_utils.common.yaml import yaml_load
|
44
43
|
from ansible._internal._datatag._tags import Origin
|
45
44
|
from ansible.module_utils.common.json import Direction, get_module_encoder
|
@@ -56,6 +55,7 @@ from ansible.template import Templar
|
|
56
55
|
from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
|
57
56
|
from ansible.module_utils._internal import _json, _ansiballz
|
58
57
|
from ansible.module_utils import basic as _basic
|
58
|
+
from ansible.module_utils.common import messages as _messages
|
59
59
|
|
60
60
|
if t.TYPE_CHECKING:
|
61
61
|
from ansible import template as _template
|
@@ -434,7 +434,13 @@ class ModuleUtilLocatorBase:
|
|
434
434
|
else:
|
435
435
|
msg += '.'
|
436
436
|
|
437
|
-
display.deprecated(
|
437
|
+
display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
438
|
+
msg=msg,
|
439
|
+
version=removal_version,
|
440
|
+
removed=removed,
|
441
|
+
date=removal_date,
|
442
|
+
deprecator=_messages.PluginInfo._from_collection_name(self._collection_name),
|
443
|
+
)
|
438
444
|
if 'redirect' in routing_entry:
|
439
445
|
self.redirected = True
|
440
446
|
source_pkg = '.'.join(name_parts)
|
@@ -944,7 +950,6 @@ class _CachedModule:
|
|
944
950
|
def _find_module_utils(
|
945
951
|
*,
|
946
952
|
module_name: str,
|
947
|
-
plugin: PluginInfo,
|
948
953
|
b_module_data: bytes,
|
949
954
|
module_path: str,
|
950
955
|
module_args: dict[object, object],
|
@@ -1020,7 +1025,9 @@ def _find_module_utils(
|
|
1020
1025
|
# People should start writing collections instead of modules in roles so we
|
1021
1026
|
# may never fix this
|
1022
1027
|
display.debug('ANSIBALLZ: Could not determine module FQN')
|
1023
|
-
|
1028
|
+
# FIXME: add integration test to validate that builtins and legacy modules with the same name are tracked separately by the caching mechanism
|
1029
|
+
# FIXME: surrogate FQN should be unique per source path- role-packaged modules with name collisions can still be aliased
|
1030
|
+
remote_module_fqn = 'ansible.legacy.%s' % module_name
|
1024
1031
|
|
1025
1032
|
if module_substyle == 'python':
|
1026
1033
|
date_time = datetime.datetime.now(datetime.timezone.utc)
|
@@ -1126,7 +1133,6 @@ def _find_module_utils(
|
|
1126
1133
|
module_fqn=remote_module_fqn,
|
1127
1134
|
params=encoded_params,
|
1128
1135
|
profile=module_metadata.serialization_profile,
|
1129
|
-
plugin_info_dict=dataclasses.asdict(plugin),
|
1130
1136
|
date_time=date_time,
|
1131
1137
|
coverage_config=coverage_config,
|
1132
1138
|
coverage_output=coverage_output,
|
@@ -1236,7 +1242,6 @@ def _extract_interpreter(b_module_data):
|
|
1236
1242
|
def modify_module(
|
1237
1243
|
*,
|
1238
1244
|
module_name: str,
|
1239
|
-
plugin: PluginInfo,
|
1240
1245
|
module_path,
|
1241
1246
|
module_args,
|
1242
1247
|
templar,
|
@@ -1277,7 +1282,6 @@ def modify_module(
|
|
1277
1282
|
|
1278
1283
|
module_bits = _find_module_utils(
|
1279
1284
|
module_name=module_name,
|
1280
|
-
plugin=plugin,
|
1281
1285
|
b_module_data=b_module_data,
|
1282
1286
|
module_path=module_path,
|
1283
1287
|
module_args=module_args,
|
@@ -22,8 +22,7 @@ from ansible.errors import (
|
|
22
22
|
)
|
23
23
|
from ansible.executor.task_result import _RawTaskResult
|
24
24
|
from ansible._internal._datatag import _utils
|
25
|
-
from ansible.module_utils.
|
26
|
-
from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary
|
25
|
+
from ansible.module_utils.common.messages import Detail, WarningSummary, DeprecationSummary, PluginInfo
|
27
26
|
from ansible.module_utils.datatag import native_type_name
|
28
27
|
from ansible._internal._datatag._tags import TrustedAsTemplate
|
29
28
|
from ansible.module_utils.parsing.convert_bool import boolean
|
@@ -640,8 +639,8 @@ class TaskExecutor:
|
|
640
639
|
if self._task.timeout:
|
641
640
|
old_sig = signal.signal(signal.SIGALRM, task_timeout)
|
642
641
|
signal.alarm(self._task.timeout)
|
643
|
-
|
644
|
-
|
642
|
+
|
643
|
+
result = self._handler.run(task_vars=vars_copy)
|
645
644
|
|
646
645
|
# DTFIX-RELEASE: nuke this, it hides a lot of error detail- remove the active exception propagation hack from AnsibleActionFail at the same time
|
647
646
|
except (AnsibleActionFail, AnsibleActionSkip) as e:
|
@@ -844,13 +843,12 @@ class TaskExecutor:
|
|
844
843
|
if not isinstance(deprecation, DeprecationSummary):
|
845
844
|
# translate non-DeprecationMessageDetail message dicts
|
846
845
|
try:
|
847
|
-
if deprecation.pop('collection_name', ...) is not ...:
|
846
|
+
if (collection_name := deprecation.pop('collection_name', ...)) is not ...:
|
848
847
|
# deprecated: description='enable the deprecation message for collection_name' core_version='2.23'
|
848
|
+
# CAUTION: This deprecation cannot be enabled until the replacement (deprecator) has been documented, and the schema finalized.
|
849
849
|
# self.deprecated('The `collection_name` key in the `deprecations` dictionary is deprecated.', version='2.27')
|
850
|
-
|
850
|
+
deprecation.update(deprecator=PluginInfo._from_collection_name(collection_name))
|
851
851
|
|
852
|
-
# DTFIX-RELEASE: when plugin isn't set, do it at the boundary where we receive the module/action results
|
853
|
-
# that may even allow us to never set it in modules/actions directly and to populate it at the boundary
|
854
852
|
deprecation = DeprecationSummary(
|
855
853
|
details=(
|
856
854
|
Detail(msg=deprecation.pop('msg')),
|
ansible/galaxy/api.py
CHANGED
@@ -138,7 +138,7 @@ def g_connect(versions):
|
|
138
138
|
'The v2 Ansible Galaxy API is deprecated and no longer supported. '
|
139
139
|
'Ensure that you have configured the ansible-galaxy CLI to utilize an '
|
140
140
|
'updated and supported version of Ansible Galaxy.',
|
141
|
-
version='2.20'
|
141
|
+
version='2.20',
|
142
142
|
)
|
143
143
|
|
144
144
|
return method(self, *args, **kwargs)
|
@@ -201,9 +201,9 @@ class CollectionSignatureError(Exception):
|
|
201
201
|
|
202
202
|
# FUTURE: expose actual verify result details for a collection on this object, maybe reimplement as dataclass on py3.8+
|
203
203
|
class CollectionVerifyResult:
|
204
|
-
def __init__(self, collection_name
|
205
|
-
self.collection_name = collection_name
|
206
|
-
self.success = True
|
204
|
+
def __init__(self, collection_name: str) -> None:
|
205
|
+
self.collection_name = collection_name
|
206
|
+
self.success = True
|
207
207
|
|
208
208
|
|
209
209
|
def verify_local_collection(local_collection, remote_collection, artifacts_manager):
|
@@ -6,7 +6,6 @@
|
|
6
6
|
from __future__ import annotations
|
7
7
|
|
8
8
|
import atexit
|
9
|
-
import dataclasses
|
10
9
|
import importlib.util
|
11
10
|
import json
|
12
11
|
import os
|
@@ -15,17 +14,14 @@ import sys
|
|
15
14
|
import typing as t
|
16
15
|
|
17
16
|
from . import _errors
|
18
|
-
from ._plugin_exec_context import PluginExecContext, HasPluginInfo
|
19
17
|
from .. import basic
|
20
18
|
from ..common.json import get_module_encoder, Direction
|
21
|
-
from ..common.messages import PluginInfo
|
22
19
|
|
23
20
|
|
24
21
|
def run_module(
|
25
22
|
*,
|
26
23
|
json_params: bytes,
|
27
24
|
profile: str,
|
28
|
-
plugin_info_dict: dict[str, object],
|
29
25
|
module_fqn: str,
|
30
26
|
modlib_path: str,
|
31
27
|
init_globals: dict[str, t.Any] | None = None,
|
@@ -38,7 +34,6 @@ def run_module(
|
|
38
34
|
_run_module(
|
39
35
|
json_params=json_params,
|
40
36
|
profile=profile,
|
41
|
-
plugin_info_dict=plugin_info_dict,
|
42
37
|
module_fqn=module_fqn,
|
43
38
|
modlib_path=modlib_path,
|
44
39
|
init_globals=init_globals,
|
@@ -80,7 +75,6 @@ def _run_module(
|
|
80
75
|
*,
|
81
76
|
json_params: bytes,
|
82
77
|
profile: str,
|
83
|
-
plugin_info_dict: dict[str, object],
|
84
78
|
module_fqn: str,
|
85
79
|
modlib_path: str,
|
86
80
|
init_globals: dict[str, t.Any] | None = None,
|
@@ -92,12 +86,11 @@ def _run_module(
|
|
92
86
|
init_globals = init_globals or {}
|
93
87
|
init_globals.update(_module_fqn=module_fqn, _modlib_path=modlib_path)
|
94
88
|
|
95
|
-
|
96
|
-
|
97
|
-
runpy.run_module(mod_name=module_fqn, init_globals=init_globals, run_name='__main__', alter_sys=True)
|
89
|
+
# Run the module. By importing it as '__main__', it executes as a script.
|
90
|
+
runpy.run_module(mod_name=module_fqn, init_globals=init_globals, run_name='__main__', alter_sys=True)
|
98
91
|
|
99
|
-
|
100
|
-
|
92
|
+
# An Ansible module must print its own results and exit. If execution reaches this point, that did not happen.
|
93
|
+
raise RuntimeError('New-style module did not handle its own exit.')
|
101
94
|
|
102
95
|
|
103
96
|
def _handle_exception(exception: BaseException, profile: str) -> t.NoReturn:
|
@@ -112,22 +105,3 @@ def _handle_exception(exception: BaseException, profile: str) -> t.NoReturn:
|
|
112
105
|
print(json.dumps(result, cls=encoder)) # pylint: disable=ansible-bad-function
|
113
106
|
|
114
107
|
sys.exit(1) # pylint: disable=ansible-bad-function
|
115
|
-
|
116
|
-
|
117
|
-
@dataclasses.dataclass(frozen=True)
|
118
|
-
class _ModulePluginWrapper(HasPluginInfo):
|
119
|
-
"""Modules aren't plugin instances; this adapter implements the `HasPluginInfo` protocol to allow `PluginExecContext` infra to work with modules."""
|
120
|
-
|
121
|
-
plugin: PluginInfo
|
122
|
-
|
123
|
-
@property
|
124
|
-
def _load_name(self) -> str:
|
125
|
-
return self.plugin.requested_name
|
126
|
-
|
127
|
-
@property
|
128
|
-
def ansible_name(self) -> str:
|
129
|
-
return self.plugin.resolved_name
|
130
|
-
|
131
|
-
@property
|
132
|
-
def plugin_type(self) -> str:
|
133
|
-
return self.plugin.type
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import dataclasses
|
4
|
-
import datetime
|
5
4
|
import typing as t
|
6
5
|
|
7
6
|
from ansible.module_utils.common import messages as _messages
|
@@ -12,27 +11,6 @@ from ansible.module_utils._internal import _datatag
|
|
12
11
|
class Deprecated(_datatag.AnsibleDatatagBase):
|
13
12
|
msg: str
|
14
13
|
help_text: t.Optional[str] = None
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@classmethod
|
20
|
-
def _from_dict(cls, d: t.Dict[str, t.Any]) -> Deprecated:
|
21
|
-
source = d
|
22
|
-
removal_date = source.get('removal_date')
|
23
|
-
|
24
|
-
if removal_date is not None:
|
25
|
-
source = source.copy()
|
26
|
-
source['removal_date'] = datetime.date.fromisoformat(removal_date)
|
27
|
-
|
28
|
-
return cls(**source)
|
29
|
-
|
30
|
-
def _as_dict(self) -> t.Dict[str, t.Any]:
|
31
|
-
# deprecated: description='no-args super() with slotted dataclass requires 3.14+' python_version='3.13'
|
32
|
-
# see: https://github.com/python/cpython/pull/124455
|
33
|
-
value = super(Deprecated, self)._as_dict()
|
34
|
-
|
35
|
-
if self.removal_date is not None:
|
36
|
-
value['removal_date'] = self.removal_date.isoformat()
|
37
|
-
|
38
|
-
return value
|
14
|
+
date: t.Optional[str] = None
|
15
|
+
version: t.Optional[str] = None
|
16
|
+
deprecator: t.Optional[_messages.PluginInfo] = None
|
@@ -0,0 +1,134 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import inspect
|
4
|
+
import re
|
5
|
+
import pathlib
|
6
|
+
import sys
|
7
|
+
import typing as t
|
8
|
+
|
9
|
+
from ansible.module_utils.common.messages import PluginInfo
|
10
|
+
|
11
|
+
_ansible_module_base_path: t.Final = pathlib.Path(sys.modules['ansible'].__file__).parent
|
12
|
+
"""Runtime-detected base path of the `ansible` Python package to distinguish between Ansible-owned and external code."""
|
13
|
+
|
14
|
+
ANSIBLE_CORE_DEPRECATOR: t.Final = PluginInfo._from_collection_name('ansible.builtin')
|
15
|
+
"""Singleton `PluginInfo` instance for ansible-core callers where the plugin can/should not be identified in messages."""
|
16
|
+
|
17
|
+
INDETERMINATE_DEPRECATOR: t.Final = PluginInfo(resolved_name='indeterminate', type='indeterminate')
|
18
|
+
"""Singleton `PluginInfo` instance for indeterminate deprecator."""
|
19
|
+
|
20
|
+
_DEPRECATOR_PLUGIN_TYPES = frozenset(
|
21
|
+
{
|
22
|
+
'action',
|
23
|
+
'become',
|
24
|
+
'cache',
|
25
|
+
'callback',
|
26
|
+
'cliconf',
|
27
|
+
'connection',
|
28
|
+
# doc_fragments - no code execution
|
29
|
+
# filter - basename inadequate to identify plugin
|
30
|
+
'httpapi',
|
31
|
+
'inventory',
|
32
|
+
'lookup',
|
33
|
+
'module', # only for collections
|
34
|
+
'netconf',
|
35
|
+
'shell',
|
36
|
+
'strategy',
|
37
|
+
'terminal',
|
38
|
+
# test - basename inadequate to identify plugin
|
39
|
+
'vars',
|
40
|
+
}
|
41
|
+
)
|
42
|
+
"""Plugin types which are valid for identifying a deprecator for deprecation purposes."""
|
43
|
+
|
44
|
+
_AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES = frozenset(
|
45
|
+
{
|
46
|
+
'filter',
|
47
|
+
'test',
|
48
|
+
}
|
49
|
+
)
|
50
|
+
"""Plugin types for which basename cannot be used to identify the plugin name."""
|
51
|
+
|
52
|
+
|
53
|
+
def get_best_deprecator(*, deprecator: PluginInfo | None = None, collection_name: str | None = None) -> PluginInfo:
|
54
|
+
"""Return the best-available `PluginInfo` for the caller of this method."""
|
55
|
+
_skip_stackwalk = True
|
56
|
+
|
57
|
+
if deprecator and collection_name:
|
58
|
+
raise ValueError('Specify only one of `deprecator` or `collection_name`.')
|
59
|
+
|
60
|
+
return deprecator or PluginInfo._from_collection_name(collection_name) or get_caller_plugin_info() or INDETERMINATE_DEPRECATOR
|
61
|
+
|
62
|
+
|
63
|
+
def get_caller_plugin_info() -> PluginInfo | None:
|
64
|
+
"""Try to get `PluginInfo` for the caller of this method, ignoring marked infrastructure stack frames."""
|
65
|
+
_skip_stackwalk = True
|
66
|
+
|
67
|
+
if frame_info := next((frame_info for frame_info in inspect.stack() if '_skip_stackwalk' not in frame_info.frame.f_locals), None):
|
68
|
+
return _path_as_core_plugininfo(frame_info.filename) or _path_as_collection_plugininfo(frame_info.filename)
|
69
|
+
|
70
|
+
return None # pragma: nocover
|
71
|
+
|
72
|
+
|
73
|
+
def _path_as_core_plugininfo(path: str) -> PluginInfo | None:
|
74
|
+
"""Return a `PluginInfo` instance if the provided `path` refers to a core plugin."""
|
75
|
+
try:
|
76
|
+
relpath = str(pathlib.Path(path).relative_to(_ansible_module_base_path))
|
77
|
+
except ValueError:
|
78
|
+
return None # not ansible-core
|
79
|
+
|
80
|
+
namespace = 'ansible.builtin'
|
81
|
+
|
82
|
+
if match := re.match(r'plugins/(?P<plugin_type>\w+)/(?P<plugin_name>\w+)', relpath):
|
83
|
+
plugin_name = match.group("plugin_name")
|
84
|
+
plugin_type = match.group("plugin_type")
|
85
|
+
|
86
|
+
if plugin_type not in _DEPRECATOR_PLUGIN_TYPES:
|
87
|
+
# The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code.
|
88
|
+
# We have no way of knowing if the intermediate code is deprecating its own feature, or acting on behalf of another plugin.
|
89
|
+
# Callers in this case need to identify the deprecating plugin name, otherwise only ansible-core will be reported.
|
90
|
+
# Reporting ansible-core is never wrong, it just may be missing an additional detail (plugin name) in the "on behalf of" case.
|
91
|
+
return ANSIBLE_CORE_DEPRECATOR
|
92
|
+
elif match := re.match(r'modules/(?P<module_name>\w+)', relpath):
|
93
|
+
# AnsiballZ Python package for core modules
|
94
|
+
plugin_name = match.group("module_name")
|
95
|
+
plugin_type = "module"
|
96
|
+
elif match := re.match(r'legacy/(?P<module_name>\w+)', relpath):
|
97
|
+
# AnsiballZ Python package for non-core library/role modules
|
98
|
+
namespace = 'ansible.legacy'
|
99
|
+
|
100
|
+
plugin_name = match.group("module_name")
|
101
|
+
plugin_type = "module"
|
102
|
+
else:
|
103
|
+
return ANSIBLE_CORE_DEPRECATOR # non-plugin core path, safe to use ansible-core for the same reason as the non-deprecator plugin type case above
|
104
|
+
|
105
|
+
name = f'{namespace}.{plugin_name}'
|
106
|
+
|
107
|
+
return PluginInfo(resolved_name=name, type=plugin_type)
|
108
|
+
|
109
|
+
|
110
|
+
def _path_as_collection_plugininfo(path: str) -> PluginInfo | None:
|
111
|
+
"""Return a `PluginInfo` instance if the provided `path` refers to a collection plugin."""
|
112
|
+
if not (match := re.search(r'/ansible_collections/(?P<ns>\w+)/(?P<coll>\w+)/plugins/(?P<plugin_type>\w+)/(?P<plugin_name>\w+)', path)):
|
113
|
+
return None
|
114
|
+
|
115
|
+
plugin_type = match.group('plugin_type')
|
116
|
+
|
117
|
+
if plugin_type in _AMBIGUOUS_DEPRECATOR_PLUGIN_TYPES:
|
118
|
+
# We're able to detect the namespace, collection and plugin type -- but we have no way to identify the plugin name currently.
|
119
|
+
# To keep things simple we'll fall back to just identifying the namespace and collection.
|
120
|
+
# In the future we could improve the detection and/or make it easier for a caller to identify the plugin name.
|
121
|
+
return PluginInfo._from_collection_name('.'.join((match.group('ns'), match.group('coll'))))
|
122
|
+
|
123
|
+
if plugin_type == 'modules':
|
124
|
+
plugin_type = 'module'
|
125
|
+
|
126
|
+
if plugin_type not in _DEPRECATOR_PLUGIN_TYPES:
|
127
|
+
# The plugin type isn't a known deprecator type, so we have to assume the caller is intermediate code.
|
128
|
+
# We have no way of knowing if the intermediate code is deprecating its own feature, or acting on behalf of another plugin.
|
129
|
+
# Callers in this case need to identify the deprecator to avoid ambiguity, since it could be the same collection or another collection.
|
130
|
+
return INDETERMINATE_DEPRECATOR
|
131
|
+
|
132
|
+
name = '.'.join((match.group('ns'), match.group('coll'), match.group('plugin_name')))
|
133
|
+
|
134
|
+
return PluginInfo(resolved_name=name, type=plugin_type)
|