ansible-core 2.19.0b1__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/_collection_proxy.py +47 -0
- ansible/_internal/_errors/_handler.py +4 -4
- ansible/_internal/_json/__init__.py +47 -4
- ansible/_internal/_json/_profiles/_legacy.py +2 -3
- ansible/_internal/_templating/_datatag.py +3 -4
- ansible/_internal/_templating/_engine.py +6 -1
- ansible/_internal/_templating/_jinja_bits.py +4 -4
- ansible/_internal/_templating/_jinja_plugins.py +7 -17
- ansible/cli/__init__.py +12 -5
- ansible/cli/arguments/option_helpers.py +4 -1
- ansible/cli/doc.py +14 -8
- ansible/config/base.yml +17 -20
- 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/process/worker.py +31 -26
- ansible/executor/task_executor.py +38 -31
- ansible/executor/task_queue_manager.py +62 -52
- ansible/executor/task_result.py +168 -72
- ansible/galaxy/api.py +1 -1
- ansible/galaxy/collection/__init__.py +3 -3
- ansible/inventory/manager.py +2 -1
- 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 +68 -23
- 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/parsing/utils/jsonify.py +40 -0
- ansible/parsing/yaml/objects.py +16 -5
- ansible/playbook/included_file.py +25 -12
- 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/__init__.py +173 -86
- ansible/plugins/callback/default.py +79 -79
- ansible/plugins/callback/junit.py +20 -19
- ansible/plugins/callback/minimal.py +17 -17
- ansible/plugins/callback/oneline.py +23 -16
- ansible/plugins/callback/tree.py +13 -6
- 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 +12 -2
- 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 +76 -82
- ansible/plugins/strategy/free.py +4 -4
- ansible/plugins/strategy/linear.py +11 -9
- ansible/plugins/test/core.py +1 -1
- ansible/release.py +1 -1
- ansible/template/__init__.py +8 -6
- ansible/utils/collection_loader/_collection_meta.py +5 -3
- ansible/utils/display.py +141 -79
- ansible/utils/py3compat.py +1 -7
- ansible/utils/ssh_functions.py +4 -1
- ansible/utils/vars.py +23 -0
- ansible/vars/clean.py +1 -1
- ansible/vars/manager.py +18 -27
- ansible/vars/plugins.py +4 -4
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/METADATA +1 -1
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/RECORD +89 -85
- 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.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/Apache-License.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/COPYING +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/MIT-license.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/PSF-license.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/WHEEL +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b1.dist-info → ansible_core-2.19.0b3.dist-info}/top_level.txt +0 -0
ansible/plugins/loader.py
CHANGED
@@ -29,7 +29,7 @@ from ansible.module_utils.common.text.converters import to_bytes, to_text, to_na
|
|
29
29
|
from ansible.module_utils.six import string_types
|
30
30
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
31
31
|
from ansible._internal._yaml._loader import AnsibleInstrumentedLoader
|
32
|
-
from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE
|
32
|
+
from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE, AnsibleJinja2Plugin
|
33
33
|
from ansible.utils.collection_loader import AnsibleCollectionConfig, AnsibleCollectionRef
|
34
34
|
from ansible.utils.collection_loader._collection_finder import _AnsibleCollectionFinder, _get_collection_metadata
|
35
35
|
from ansible.utils.display import Display
|
@@ -135,29 +135,44 @@ class PluginPathContext(object):
|
|
135
135
|
|
136
136
|
|
137
137
|
class PluginLoadContext(object):
|
138
|
-
def __init__(self):
|
139
|
-
self.original_name = None
|
140
|
-
self.redirect_list = []
|
141
|
-
self.
|
142
|
-
|
143
|
-
self.
|
144
|
-
|
145
|
-
self.
|
146
|
-
|
147
|
-
self.
|
148
|
-
self.
|
149
|
-
self.
|
150
|
-
self.
|
151
|
-
self.
|
152
|
-
|
153
|
-
self.
|
154
|
-
|
155
|
-
self.
|
138
|
+
def __init__(self, plugin_type: str, legacy_package_name: str) -> None:
|
139
|
+
self.original_name: str | None = None
|
140
|
+
self.redirect_list: list[str] = []
|
141
|
+
self.raw_error_list: list[Exception] = []
|
142
|
+
"""All exception instances encountered during the plugin load."""
|
143
|
+
self.error_list: list[str] = []
|
144
|
+
"""Stringified exceptions, excluding import errors."""
|
145
|
+
self.import_error_list: list[Exception] = []
|
146
|
+
"""All ImportError exception instances encountered during the plugin load."""
|
147
|
+
self.load_attempts: list[str] = []
|
148
|
+
self.pending_redirect: str | None = None
|
149
|
+
self.exit_reason: str | None = None
|
150
|
+
self.plugin_resolved_path: str | None = None
|
151
|
+
self.plugin_resolved_name: str | None = None
|
152
|
+
"""For collection plugins, the resolved Python module FQ __name__; for non-collections, the short name."""
|
153
|
+
self.plugin_resolved_collection: str | None = None # empty string for resolved plugins from user-supplied paths
|
154
|
+
"""For collection plugins, the resolved collection {ns}.{col}; empty string for non-collection plugins."""
|
155
|
+
self.deprecated: bool = False
|
156
|
+
self.removal_date: str | None = None
|
157
|
+
self.removal_version: str | None = None
|
158
|
+
self.deprecation_warnings: list[str] = []
|
159
|
+
self.resolved: bool = False
|
160
|
+
self._resolved_fqcn: str | None = None
|
161
|
+
self.action_plugin: str | None = None
|
162
|
+
self._plugin_type: str = plugin_type
|
163
|
+
"""The type of the plugin."""
|
164
|
+
self._legacy_package_name = legacy_package_name
|
165
|
+
"""The legacy sys.modules package name from the plugin loader instance; stored to prevent potentially incorrect manual computation."""
|
166
|
+
self._python_module_name: str | None = None
|
167
|
+
"""
|
168
|
+
The fully qualified Python module name for the plugin (accessible via `sys.modules`).
|
169
|
+
For non-collection non-core plugins, this may include a non-existent synthetic package element with a hash of the file path to avoid collisions.
|
170
|
+
"""
|
156
171
|
|
157
172
|
@property
|
158
|
-
def resolved_fqcn(self):
|
173
|
+
def resolved_fqcn(self) -> str | None:
|
159
174
|
if not self.resolved:
|
160
|
-
return
|
175
|
+
return None
|
161
176
|
|
162
177
|
if not self._resolved_fqcn:
|
163
178
|
final_plugin = self.redirect_list[-1]
|
@@ -169,7 +184,7 @@ class PluginLoadContext(object):
|
|
169
184
|
|
170
185
|
return self._resolved_fqcn
|
171
186
|
|
172
|
-
def record_deprecation(self, name, deprecation, collection_name):
|
187
|
+
def record_deprecation(self, name: str, deprecation: dict[str, t.Any] | None, collection_name: str) -> t.Self:
|
173
188
|
if not deprecation:
|
174
189
|
return self
|
175
190
|
|
@@ -183,7 +198,12 @@ class PluginLoadContext(object):
|
|
183
198
|
removal_version = None
|
184
199
|
warning_text = '{0} has been deprecated.{1}{2}'.format(name, ' ' if warning_text else '', warning_text)
|
185
200
|
|
186
|
-
display.deprecated(
|
201
|
+
display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
202
|
+
msg=warning_text,
|
203
|
+
date=removal_date,
|
204
|
+
version=removal_version,
|
205
|
+
deprecator=PluginInfo._from_collection_name(collection_name),
|
206
|
+
)
|
187
207
|
|
188
208
|
self.deprecated = True
|
189
209
|
if removal_date:
|
@@ -193,28 +213,79 @@ class PluginLoadContext(object):
|
|
193
213
|
self.deprecation_warnings.append(warning_text)
|
194
214
|
return self
|
195
215
|
|
196
|
-
def resolve(self, resolved_name, resolved_path, resolved_collection, exit_reason, action_plugin):
|
216
|
+
def resolve(self, resolved_name: str, resolved_path: str, resolved_collection: str, exit_reason: str, action_plugin: str) -> t.Self:
|
217
|
+
"""Record a resolved collection plugin."""
|
197
218
|
self.pending_redirect = None
|
198
219
|
self.plugin_resolved_name = resolved_name
|
199
220
|
self.plugin_resolved_path = resolved_path
|
200
221
|
self.plugin_resolved_collection = resolved_collection
|
201
222
|
self.exit_reason = exit_reason
|
223
|
+
self._python_module_name = resolved_name
|
202
224
|
self.resolved = True
|
203
225
|
self.action_plugin = action_plugin
|
226
|
+
|
227
|
+
return self
|
228
|
+
|
229
|
+
def resolve_legacy(self, name: str, pull_cache: dict[str, PluginPathContext]) -> t.Self:
|
230
|
+
"""Record a resolved legacy plugin."""
|
231
|
+
plugin_path_context = pull_cache[name]
|
232
|
+
|
233
|
+
self.plugin_resolved_name = name
|
234
|
+
self.plugin_resolved_path = plugin_path_context.path
|
235
|
+
self.plugin_resolved_collection = 'ansible.builtin' if plugin_path_context.internal else ''
|
236
|
+
self._resolved_fqcn = 'ansible.builtin.' + name if plugin_path_context.internal else name
|
237
|
+
self._python_module_name = self._make_legacy_python_module_name()
|
238
|
+
self.resolved = True
|
239
|
+
|
240
|
+
return self
|
241
|
+
|
242
|
+
def resolve_legacy_jinja_plugin(self, name: str, known_plugin: AnsibleJinja2Plugin) -> t.Self:
|
243
|
+
"""Record a resolved legacy Jinja plugin."""
|
244
|
+
internal = known_plugin.ansible_name.startswith('ansible.builtin.')
|
245
|
+
|
246
|
+
self.plugin_resolved_name = name
|
247
|
+
self.plugin_resolved_path = known_plugin._original_path
|
248
|
+
self.plugin_resolved_collection = 'ansible.builtin' if internal else ''
|
249
|
+
self._resolved_fqcn = known_plugin.ansible_name
|
250
|
+
self._python_module_name = self._make_legacy_python_module_name()
|
251
|
+
self.resolved = True
|
252
|
+
|
204
253
|
return self
|
205
254
|
|
206
|
-
def redirect(self, redirect_name):
|
255
|
+
def redirect(self, redirect_name: str) -> t.Self:
|
207
256
|
self.pending_redirect = redirect_name
|
208
257
|
self.exit_reason = 'pending redirect resolution from {0} to {1}'.format(self.original_name, redirect_name)
|
209
258
|
self.resolved = False
|
259
|
+
|
210
260
|
return self
|
211
261
|
|
212
|
-
def nope(self, exit_reason):
|
262
|
+
def nope(self, exit_reason: str) -> t.Self:
|
213
263
|
self.pending_redirect = None
|
214
264
|
self.exit_reason = exit_reason
|
215
265
|
self.resolved = False
|
266
|
+
|
216
267
|
return self
|
217
268
|
|
269
|
+
def _make_legacy_python_module_name(self) -> str:
|
270
|
+
"""
|
271
|
+
Generate a fully-qualified Python module name for a legacy/builtin plugin.
|
272
|
+
|
273
|
+
The same package namespace is shared for builtin and legacy plugins.
|
274
|
+
Explicit requests for builtins via `ansible.builtin` are handled elsewhere with an aliased collection package resolved by the collection loader.
|
275
|
+
Only unqualified and `ansible.legacy`-qualified requests land here; whichever plugin is visible at the time will end up in sys.modules.
|
276
|
+
Filter and test plugin host modules receive special name suffixes to avoid collisions unrelated to the actual plugin name.
|
277
|
+
"""
|
278
|
+
name = os.path.splitext(self.plugin_resolved_path)[0]
|
279
|
+
basename = os.path.basename(name)
|
280
|
+
|
281
|
+
if self._plugin_type in ('filter', 'test'):
|
282
|
+
# Unlike other plugin types, filter and test plugin names are independent of the file where they are defined.
|
283
|
+
# As a result, the Python module name must be derived from the full path of the plugin.
|
284
|
+
# This prevents accidental shadowing of unrelated plugins of the same type.
|
285
|
+
basename += f'_{abs(hash(self.plugin_resolved_path))}'
|
286
|
+
|
287
|
+
return f'{self._legacy_package_name}.{basename}'
|
288
|
+
|
218
289
|
|
219
290
|
class PluginLoader:
|
220
291
|
"""
|
@@ -224,7 +295,15 @@ class PluginLoader:
|
|
224
295
|
paths, and the python path. The first match is used.
|
225
296
|
"""
|
226
297
|
|
227
|
-
def __init__(
|
298
|
+
def __init__(
|
299
|
+
self,
|
300
|
+
class_name: str,
|
301
|
+
package: str,
|
302
|
+
config: str | list[str],
|
303
|
+
subdir: str,
|
304
|
+
aliases: dict[str, str] | None = None,
|
305
|
+
required_base_class: str | None = None,
|
306
|
+
) -> None:
|
228
307
|
aliases = {} if aliases is None else aliases
|
229
308
|
|
230
309
|
self.class_name = class_name
|
@@ -250,15 +329,15 @@ class PluginLoader:
|
|
250
329
|
PLUGIN_PATH_CACHE[class_name] = defaultdict(dict)
|
251
330
|
|
252
331
|
# hold dirs added at runtime outside of config
|
253
|
-
self._extra_dirs = []
|
332
|
+
self._extra_dirs: list[str] = []
|
254
333
|
|
255
334
|
# caches
|
256
335
|
self._module_cache = MODULE_CACHE[class_name]
|
257
336
|
self._paths = PATH_CACHE[class_name]
|
258
337
|
self._plugin_path_cache = PLUGIN_PATH_CACHE[class_name]
|
259
|
-
self._plugin_instance_cache = {} if self.subdir == 'vars_plugins' else None
|
338
|
+
self._plugin_instance_cache: dict[str, tuple[object, PluginLoadContext]] | None = {} if self.subdir == 'vars_plugins' else None
|
260
339
|
|
261
|
-
self._searched_paths = set()
|
340
|
+
self._searched_paths: set[str] = set()
|
262
341
|
|
263
342
|
@property
|
264
343
|
def type(self):
|
@@ -488,7 +567,13 @@ class PluginLoader:
|
|
488
567
|
entry = collection_meta.get('plugin_routing', {}).get(plugin_type, {}).get(subdir_qualified_resource, None)
|
489
568
|
return entry
|
490
569
|
|
491
|
-
def _find_fq_plugin(
|
570
|
+
def _find_fq_plugin(
|
571
|
+
self,
|
572
|
+
fq_name: str,
|
573
|
+
extension: str | None,
|
574
|
+
plugin_load_context: PluginLoadContext,
|
575
|
+
ignore_deprecated: bool = False,
|
576
|
+
) -> PluginLoadContext:
|
492
577
|
"""Search builtin paths to find a plugin. No external paths are searched,
|
493
578
|
meaning plugins inside roles inside collections will be ignored.
|
494
579
|
"""
|
@@ -525,17 +610,13 @@ class PluginLoader:
|
|
525
610
|
version=removal_version,
|
526
611
|
date=removal_date,
|
527
612
|
removed=True,
|
528
|
-
|
529
|
-
requested_name=acr.collection,
|
530
|
-
resolved_name=acr.collection,
|
531
|
-
type='collection',
|
532
|
-
),
|
613
|
+
deprecator=PluginInfo._from_collection_name(acr.collection),
|
533
614
|
)
|
534
|
-
plugin_load_context.
|
535
|
-
plugin_load_context.
|
615
|
+
plugin_load_context.date = removal_date
|
616
|
+
plugin_load_context.version = removal_version
|
536
617
|
plugin_load_context.resolved = True
|
537
618
|
plugin_load_context.exit_reason = removed_msg
|
538
|
-
raise AnsiblePluginRemovedError(removed_msg, plugin_load_context=plugin_load_context)
|
619
|
+
raise AnsiblePluginRemovedError(message=removed_msg, plugin_load_context=plugin_load_context)
|
539
620
|
|
540
621
|
redirect = routing_metadata.get('redirect', None)
|
541
622
|
|
@@ -623,7 +704,7 @@ class PluginLoader:
|
|
623
704
|
collection_list: list[str] | None = None,
|
624
705
|
) -> PluginLoadContext:
|
625
706
|
""" Find a plugin named name, returning contextual info about the load, recursively resolving redirection """
|
626
|
-
plugin_load_context = PluginLoadContext()
|
707
|
+
plugin_load_context = PluginLoadContext(self.type, self.package)
|
627
708
|
plugin_load_context.original_name = name
|
628
709
|
while True:
|
629
710
|
result = self._resolve_plugin_step(name, mod_type, ignore_deprecated, check_aliases, collection_list, plugin_load_context=plugin_load_context)
|
@@ -636,11 +717,8 @@ class PluginLoader:
|
|
636
717
|
else:
|
637
718
|
break
|
638
719
|
|
639
|
-
|
640
|
-
|
641
|
-
display.warning("errors were encountered during the plugin load for {0}:\n{1}".format(name, plugin_load_context.error_list))
|
642
|
-
|
643
|
-
# TODO: display/return import_error_list? Only useful for forensics...
|
720
|
+
for ex in plugin_load_context.raw_error_list:
|
721
|
+
display.error_as_warning(f"Error loading plugin {name!r}.", ex)
|
644
722
|
|
645
723
|
# FIXME: store structured deprecation data in PluginLoadContext and use display.deprecate
|
646
724
|
# if plugin_load_context.deprecated and C.config.get_config_value('DEPRECATION_WARNINGS'):
|
@@ -650,9 +728,15 @@ class PluginLoader:
|
|
650
728
|
|
651
729
|
return plugin_load_context
|
652
730
|
|
653
|
-
|
654
|
-
|
655
|
-
|
731
|
+
def _resolve_plugin_step(
|
732
|
+
self,
|
733
|
+
name: str,
|
734
|
+
mod_type: str = '',
|
735
|
+
ignore_deprecated: bool = False,
|
736
|
+
check_aliases: bool = False,
|
737
|
+
collection_list: list[str] | None = None,
|
738
|
+
plugin_load_context: PluginLoadContext | None = None,
|
739
|
+
) -> PluginLoadContext:
|
656
740
|
if not plugin_load_context:
|
657
741
|
raise ValueError('A PluginLoadContext is required')
|
658
742
|
|
@@ -707,11 +791,14 @@ class PluginLoader:
|
|
707
791
|
except (AnsiblePluginRemovedError, AnsiblePluginCircularRedirect, AnsibleCollectionUnsupportedVersionError):
|
708
792
|
# these are generally fatal, let them fly
|
709
793
|
raise
|
710
|
-
except ImportError as ie:
|
711
|
-
plugin_load_context.import_error_list.append(ie)
|
712
794
|
except Exception as ex:
|
713
|
-
|
714
|
-
|
795
|
+
plugin_load_context.raw_error_list.append(ex)
|
796
|
+
|
797
|
+
# DTFIX-RELEASE: can we deprecate/remove these stringified versions?
|
798
|
+
if isinstance(ex, ImportError):
|
799
|
+
plugin_load_context.import_error_list.append(ex)
|
800
|
+
else:
|
801
|
+
plugin_load_context.error_list.append(str(ex))
|
715
802
|
|
716
803
|
if plugin_load_context.error_list:
|
717
804
|
display.debug(msg='plugin lookup for {0} failed; errors: {1}'.format(name, '; '.join(plugin_load_context.error_list)))
|
@@ -737,13 +824,7 @@ class PluginLoader:
|
|
737
824
|
# requested mod_type
|
738
825
|
pull_cache = self._plugin_path_cache[suffix]
|
739
826
|
try:
|
740
|
-
|
741
|
-
plugin_load_context.plugin_resolved_path = path_with_context.path
|
742
|
-
plugin_load_context.plugin_resolved_name = name
|
743
|
-
plugin_load_context.plugin_resolved_collection = 'ansible.builtin' if path_with_context.internal else ''
|
744
|
-
plugin_load_context._resolved_fqcn = ('ansible.builtin.' + name if path_with_context.internal else name)
|
745
|
-
plugin_load_context.resolved = True
|
746
|
-
return plugin_load_context
|
827
|
+
return plugin_load_context.resolve_legacy(name=name, pull_cache=pull_cache)
|
747
828
|
except KeyError:
|
748
829
|
# Cache miss. Now let's find the plugin
|
749
830
|
pass
|
@@ -796,13 +877,7 @@ class PluginLoader:
|
|
796
877
|
|
797
878
|
self._searched_paths.add(path)
|
798
879
|
try:
|
799
|
-
|
800
|
-
plugin_load_context.plugin_resolved_path = path_with_context.path
|
801
|
-
plugin_load_context.plugin_resolved_name = name
|
802
|
-
plugin_load_context.plugin_resolved_collection = 'ansible.builtin' if path_with_context.internal else ''
|
803
|
-
plugin_load_context._resolved_fqcn = 'ansible.builtin.' + name if path_with_context.internal else name
|
804
|
-
plugin_load_context.resolved = True
|
805
|
-
return plugin_load_context
|
880
|
+
return plugin_load_context.resolve_legacy(name=name, pull_cache=pull_cache)
|
806
881
|
except KeyError:
|
807
882
|
# Didn't find the plugin in this directory. Load modules from the next one
|
808
883
|
pass
|
@@ -810,18 +885,18 @@ class PluginLoader:
|
|
810
885
|
# if nothing is found, try finding alias/deprecated
|
811
886
|
if not name.startswith('_'):
|
812
887
|
alias_name = '_' + name
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
888
|
+
|
889
|
+
try:
|
890
|
+
plugin_load_context.resolve_legacy(name=alias_name, pull_cache=pull_cache)
|
891
|
+
except KeyError:
|
892
|
+
pass
|
893
|
+
else:
|
894
|
+
display.deprecated(
|
895
|
+
msg=f'Plugin {name!r} automatically redirected to {alias_name!r}.',
|
896
|
+
help_text=f'Use {alias_name!r} instead of {name!r} to refer to the plugin.',
|
897
|
+
version='2.23',
|
898
|
+
)
|
899
|
+
|
825
900
|
return plugin_load_context
|
826
901
|
|
827
902
|
# last ditch, if it's something that can be redirected, look for a builtin redirect before giving up
|
@@ -831,7 +906,7 @@ class PluginLoader:
|
|
831
906
|
|
832
907
|
return plugin_load_context.nope('{0} is not eligible for last-chance resolution'.format(name))
|
833
908
|
|
834
|
-
def has_plugin(self, name, collection_list=None):
|
909
|
+
def has_plugin(self, name: str, collection_list: list[str] | None = None) -> bool:
|
835
910
|
""" Checks if a plugin named name exists """
|
836
911
|
|
837
912
|
try:
|
@@ -842,41 +917,37 @@ class PluginLoader:
|
|
842
917
|
# log and continue, likely an innocuous type/package loading failure in collections import
|
843
918
|
display.debug('has_plugin error: {0}'.format(to_text(ex)))
|
844
919
|
|
845
|
-
|
846
|
-
|
847
|
-
def _load_module_source(self, name, path):
|
920
|
+
return False
|
848
921
|
|
849
|
-
|
850
|
-
if name.startswith('ansible_collections.'):
|
851
|
-
full_name = name
|
852
|
-
else:
|
853
|
-
full_name = '.'.join([self.package, name])
|
922
|
+
__contains__ = has_plugin
|
854
923
|
|
855
|
-
|
924
|
+
def _load_module_source(self, *, python_module_name: str, path: str) -> types.ModuleType:
|
925
|
+
if python_module_name in sys.modules:
|
856
926
|
# Avoids double loading, See https://github.com/ansible/ansible/issues/13110
|
857
|
-
return sys.modules[
|
927
|
+
return sys.modules[python_module_name]
|
858
928
|
|
859
929
|
with warnings.catch_warnings():
|
860
930
|
# FIXME: this still has issues if the module was previously imported but not "cached",
|
861
931
|
# we should bypass this entire codepath for things that are directly importable
|
862
932
|
warnings.simplefilter("ignore", RuntimeWarning)
|
863
|
-
spec = importlib.util.spec_from_file_location(to_native(
|
933
|
+
spec = importlib.util.spec_from_file_location(to_native(python_module_name), to_native(path))
|
864
934
|
module = importlib.util.module_from_spec(spec)
|
865
935
|
|
866
936
|
# mimic import machinery; make the module-being-loaded available in sys.modules during import
|
867
937
|
# and remove if there's a failure...
|
868
|
-
sys.modules[
|
938
|
+
sys.modules[python_module_name] = module
|
869
939
|
|
870
940
|
try:
|
871
941
|
spec.loader.exec_module(module)
|
872
942
|
except Exception:
|
873
|
-
del sys.modules[
|
943
|
+
del sys.modules[python_module_name]
|
874
944
|
raise
|
875
945
|
|
876
946
|
return module
|
877
947
|
|
878
948
|
def _update_object(
|
879
949
|
self,
|
950
|
+
*,
|
880
951
|
obj: _AnsiblePluginInfoMixin,
|
881
952
|
name: str,
|
882
953
|
path: str,
|
@@ -907,9 +978,9 @@ class PluginLoader:
|
|
907
978
|
is_core_plugin = ctx.plugin_load_context.plugin_resolved_collection == 'ansible.builtin'
|
908
979
|
if self.class_name == 'StrategyModule' and not is_core_plugin:
|
909
980
|
display.deprecated( # pylint: disable=ansible-deprecated-no-version
|
910
|
-
'Use of strategy plugins not included in ansible.builtin are deprecated and do not carry '
|
911
|
-
|
912
|
-
|
981
|
+
msg='Use of strategy plugins not included in ansible.builtin are deprecated and do not carry '
|
982
|
+
'any backwards compatibility guarantees. No alternative for third party strategy plugins '
|
983
|
+
'is currently planned.',
|
913
984
|
)
|
914
985
|
|
915
986
|
return ctx.object
|
@@ -936,8 +1007,6 @@ class PluginLoader:
|
|
936
1007
|
return get_with_context_result(None, plugin_load_context)
|
937
1008
|
|
938
1009
|
fq_name = plugin_load_context.resolved_fqcn
|
939
|
-
if '.' not in fq_name and plugin_load_context.plugin_resolved_collection:
|
940
|
-
fq_name = '.'.join((plugin_load_context.plugin_resolved_collection, fq_name))
|
941
1010
|
resolved_type_name = plugin_load_context.plugin_resolved_name
|
942
1011
|
path = plugin_load_context.plugin_resolved_path
|
943
1012
|
if (cached_result := (self._plugin_instance_cache or {}).get(fq_name)) and cached_result[1].resolved:
|
@@ -947,7 +1016,7 @@ class PluginLoader:
|
|
947
1016
|
redirected_names = plugin_load_context.redirect_list or []
|
948
1017
|
|
949
1018
|
if path not in self._module_cache:
|
950
|
-
self._module_cache[path] = self._load_module_source(
|
1019
|
+
self._module_cache[path] = self._load_module_source(python_module_name=plugin_load_context._python_module_name, path=path)
|
951
1020
|
found_in_cache = False
|
952
1021
|
|
953
1022
|
self._load_config_defs(resolved_type_name, self._module_cache[path], path)
|
@@ -974,7 +1043,7 @@ class PluginLoader:
|
|
974
1043
|
# A plugin may need to use its _load_name in __init__ (for example, to set
|
975
1044
|
# or get options from config), so update the object before using the constructor
|
976
1045
|
instance = object.__new__(obj)
|
977
|
-
self._update_object(instance, resolved_type_name, path, redirected_names, fq_name)
|
1046
|
+
self._update_object(obj=instance, name=resolved_type_name, path=path, redirected_names=redirected_names, resolved=fq_name)
|
978
1047
|
obj.__init__(instance, *args, **kwargs) # pylint: disable=unnecessary-dunder-call
|
979
1048
|
obj = instance
|
980
1049
|
except TypeError as e:
|
@@ -984,12 +1053,12 @@ class PluginLoader:
|
|
984
1053
|
return get_with_context_result(None, plugin_load_context)
|
985
1054
|
raise
|
986
1055
|
|
987
|
-
self._update_object(obj, resolved_type_name, path, redirected_names, fq_name)
|
1056
|
+
self._update_object(obj=obj, name=resolved_type_name, path=path, redirected_names=redirected_names, resolved=fq_name)
|
988
1057
|
if self._plugin_instance_cache is not None and getattr(obj, 'is_stateless', False):
|
989
1058
|
self._plugin_instance_cache[fq_name] = (obj, plugin_load_context)
|
990
1059
|
elif self._plugin_instance_cache is not None:
|
991
1060
|
# The cache doubles as the load order, so record the FQCN even if the plugin hasn't set is_stateless = True
|
992
|
-
self._plugin_instance_cache[fq_name] = (None, PluginLoadContext())
|
1061
|
+
self._plugin_instance_cache[fq_name] = (None, PluginLoadContext(self.type, self.package))
|
993
1062
|
return get_with_context_result(obj, plugin_load_context)
|
994
1063
|
|
995
1064
|
def _display_plugin_load(self, class_name, name, searched_paths, path, found_in_cache=None, class_only=None):
|
@@ -1064,10 +1133,15 @@ class PluginLoader:
|
|
1064
1133
|
basename = os.path.basename(name)
|
1065
1134
|
is_j2 = isinstance(self, Jinja2Loader)
|
1066
1135
|
|
1136
|
+
if path in legacy_excluding_builtin:
|
1137
|
+
fqcn = basename
|
1138
|
+
else:
|
1139
|
+
fqcn = f"ansible.builtin.{basename}"
|
1140
|
+
|
1067
1141
|
if is_j2:
|
1068
1142
|
ref_name = path
|
1069
1143
|
else:
|
1070
|
-
ref_name =
|
1144
|
+
ref_name = fqcn
|
1071
1145
|
|
1072
1146
|
if not is_j2 and basename in _PLUGIN_FILTERS[self.package]:
|
1073
1147
|
# j2 plugins get processed in own class, here they would just be container files
|
@@ -1090,26 +1164,18 @@ class PluginLoader:
|
|
1090
1164
|
yield path
|
1091
1165
|
continue
|
1092
1166
|
|
1093
|
-
if path in legacy_excluding_builtin:
|
1094
|
-
fqcn = basename
|
1095
|
-
else:
|
1096
|
-
fqcn = f"ansible.builtin.{basename}"
|
1097
|
-
|
1098
1167
|
if (cached_result := (self._plugin_instance_cache or {}).get(fqcn)) and cached_result[1].resolved:
|
1099
1168
|
# Here just in case, but we don't call all() multiple times for vars plugins, so this should not be used.
|
1100
1169
|
yield cached_result[0]
|
1101
1170
|
continue
|
1102
1171
|
|
1103
1172
|
if path not in self._module_cache:
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
full_name = '{0}_{1}'.format(abs(hash(path)), basename)
|
1108
|
-
else:
|
1109
|
-
full_name = basename
|
1173
|
+
path_context = PluginPathContext(path, path not in legacy_excluding_builtin)
|
1174
|
+
load_context = PluginLoadContext(self.type, self.package)
|
1175
|
+
load_context.resolve_legacy(basename, {basename: path_context})
|
1110
1176
|
|
1111
1177
|
try:
|
1112
|
-
module = self._load_module_source(
|
1178
|
+
module = self._load_module_source(python_module_name=load_context._python_module_name, path=path)
|
1113
1179
|
except Exception as e:
|
1114
1180
|
display.warning("Skipping plugin (%s), cannot load: %s" % (path, to_text(e)))
|
1115
1181
|
continue
|
@@ -1147,7 +1213,7 @@ class PluginLoader:
|
|
1147
1213
|
except TypeError as e:
|
1148
1214
|
display.warning("Skipping plugin (%s) as it seems to be incomplete: %s" % (path, to_text(e)))
|
1149
1215
|
|
1150
|
-
self._update_object(obj, basename, path, resolved=fqcn)
|
1216
|
+
self._update_object(obj=obj, name=basename, path=path, resolved=fqcn)
|
1151
1217
|
|
1152
1218
|
if self._plugin_instance_cache is not None:
|
1153
1219
|
needs_enabled = False
|
@@ -1239,7 +1305,7 @@ class Jinja2Loader(PluginLoader):
|
|
1239
1305
|
try:
|
1240
1306
|
# use 'parent' loader class to find files, but cannot return this as it can contain multiple plugins per file
|
1241
1307
|
if plugin_path not in self._module_cache:
|
1242
|
-
self._module_cache[plugin_path] = self._load_module_source(full_name, plugin_path)
|
1308
|
+
self._module_cache[plugin_path] = self._load_module_source(python_module_name=full_name, path=plugin_path)
|
1243
1309
|
module = self._module_cache[plugin_path]
|
1244
1310
|
obj = getattr(module, self.class_name)
|
1245
1311
|
except Exception as e:
|
@@ -1262,7 +1328,7 @@ class Jinja2Loader(PluginLoader):
|
|
1262
1328
|
plugin = self._plugin_wrapper_type(func)
|
1263
1329
|
if plugin in plugins:
|
1264
1330
|
continue
|
1265
|
-
self._update_object(plugin, full, plugin_path, resolved=fq_name)
|
1331
|
+
self._update_object(obj=plugin, name=full, path=plugin_path, resolved=fq_name)
|
1266
1332
|
plugins.append(plugin)
|
1267
1333
|
|
1268
1334
|
return plugins
|
@@ -1276,7 +1342,7 @@ class Jinja2Loader(PluginLoader):
|
|
1276
1342
|
|
1277
1343
|
requested_name = name
|
1278
1344
|
|
1279
|
-
context = PluginLoadContext()
|
1345
|
+
context = PluginLoadContext(self.type, self.package)
|
1280
1346
|
|
1281
1347
|
# avoid collection path for legacy
|
1282
1348
|
name = name.removeprefix('ansible.legacy.')
|
@@ -1288,11 +1354,8 @@ class Jinja2Loader(PluginLoader):
|
|
1288
1354
|
if isinstance(known_plugin, _DeferredPluginLoadFailure):
|
1289
1355
|
raise known_plugin.ex
|
1290
1356
|
|
1291
|
-
context.
|
1292
|
-
|
1293
|
-
context.plugin_resolved_path = known_plugin._original_path
|
1294
|
-
context.plugin_resolved_collection = 'ansible.builtin' if known_plugin.ansible_name.startswith('ansible.builtin.') else ''
|
1295
|
-
context._resolved_fqcn = known_plugin.ansible_name
|
1357
|
+
context.resolve_legacy_jinja_plugin(name, known_plugin)
|
1358
|
+
|
1296
1359
|
return get_with_context_result(known_plugin, context)
|
1297
1360
|
|
1298
1361
|
plugin = None
|
@@ -1328,7 +1391,12 @@ class Jinja2Loader(PluginLoader):
|
|
1328
1391
|
|
1329
1392
|
warning_text = f'{self.type.title()} "{key}" has been deprecated.{" " if warning_text else ""}{warning_text}'
|
1330
1393
|
|
1331
|
-
display.deprecated(
|
1394
|
+
display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
1395
|
+
msg=warning_text,
|
1396
|
+
version=removal_version,
|
1397
|
+
date=removal_date,
|
1398
|
+
deprecator=PluginInfo._from_collection_name(acr.collection),
|
1399
|
+
)
|
1332
1400
|
|
1333
1401
|
# check removal
|
1334
1402
|
tombstone_entry = routing_entry.get('tombstone')
|
@@ -1343,11 +1411,7 @@ class Jinja2Loader(PluginLoader):
|
|
1343
1411
|
version=removal_version,
|
1344
1412
|
date=removal_date,
|
1345
1413
|
removed=True,
|
1346
|
-
|
1347
|
-
requested_name=acr.collection,
|
1348
|
-
resolved_name=acr.collection,
|
1349
|
-
type='collection',
|
1350
|
-
),
|
1414
|
+
deprecator=PluginInfo._from_collection_name(acr.collection),
|
1351
1415
|
)
|
1352
1416
|
|
1353
1417
|
raise AnsiblePluginRemovedError(exc_msg)
|
@@ -1400,7 +1464,7 @@ class Jinja2Loader(PluginLoader):
|
|
1400
1464
|
plugin = self._plugin_wrapper_type(func)
|
1401
1465
|
if plugin:
|
1402
1466
|
context = plugin_impl.plugin_load_context
|
1403
|
-
self._update_object(plugin, requested_name, plugin_impl.object._original_path, resolved=fq_name)
|
1467
|
+
self._update_object(obj=plugin, name=requested_name, path=plugin_impl.object._original_path, resolved=fq_name)
|
1404
1468
|
# context will have filename, which for tests/filters might not be correct
|
1405
1469
|
context._resolved_fqcn = plugin.ansible_name
|
1406
1470
|
# FIXME: once we start caching these results, we'll be missing functions that would have loaded later
|
ansible/plugins/lookup/url.py
CHANGED
@@ -230,8 +230,8 @@ class LookupModule(LookupBase):
|
|
230
230
|
display.vvvv("url lookup connecting to %s" % term)
|
231
231
|
if self.get_option('follow_redirects') in ('yes', 'no'):
|
232
232
|
display.deprecated(
|
233
|
-
"Using 'yes' or 'no' for 'follow_redirects' parameter is deprecated.",
|
234
|
-
version='2.22'
|
233
|
+
msg="Using 'yes' or 'no' for 'follow_redirects' parameter is deprecated.",
|
234
|
+
version='2.22',
|
235
235
|
)
|
236
236
|
try:
|
237
237
|
response = open_url(
|