ansible-core 2.19.0b2__py3-none-any.whl → 2.19.0b4__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/__init__.py +1 -1
- ansible/_internal/_ansiballz.py +1 -4
- ansible/_internal/_json/__init__.py +1 -1
- ansible/_internal/_templating/_datatag.py +3 -4
- ansible/_internal/_templating/_engine.py +6 -1
- ansible/_internal/_templating/_jinja_plugins.py +2 -6
- ansible/_internal/_testing.py +26 -0
- ansible/cli/__init__.py +3 -2
- ansible/cli/arguments/option_helpers.py +10 -3
- ansible/cli/doc.py +0 -1
- ansible/config/base.yml +5 -23
- ansible/config/manager.py +144 -103
- ansible/constants.py +1 -63
- 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/inventory/manager.py +1 -0
- 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/module_utils/facts/virtual/linux.py +1 -1
- ansible/module_utils/parsing/convert_bool.py +6 -0
- ansible/modules/assemble.py +4 -4
- ansible/modules/async_status.py +1 -1
- ansible/modules/cron.py +3 -5
- ansible/modules/dnf5.py +2 -1
- ansible/modules/get_url.py +1 -1
- ansible/modules/git.py +1 -6
- ansible/modules/pip.py +2 -4
- ansible/modules/sysvinit.py +3 -3
- ansible/playbook/task.py +0 -2
- ansible/plugins/__init__.py +18 -8
- ansible/plugins/action/__init__.py +7 -15
- ansible/plugins/action/gather_facts.py +2 -4
- ansible/plugins/action/template.py +3 -0
- 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 +197 -132
- 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/__init__.py +2 -0
- ansible/utils/collection_loader/_collection_meta.py +5 -3
- ansible/utils/display.py +137 -71
- ansible/utils/plugin_docs.py +2 -1
- 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.0b4.dist-info}/METADATA +3 -2
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/RECORD +82 -79
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info}/WHEEL +1 -1
- 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 +486 -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.0b4.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses}/COPYING +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/Apache-License.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/BSD-3-Clause.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/MIT-license.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/PSF-license.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.dist-info/licenses/licenses}/simplified_bsd.txt +0 -0
- {ansible_core-2.19.0b2.dist-info → ansible_core-2.19.0b4.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):
|
@@ -426,7 +505,8 @@ class PluginLoader:
|
|
426
505
|
|
427
506
|
# if type name != 'module_doc_fragment':
|
428
507
|
if type_name in C.CONFIGURABLE_PLUGINS and not C.config.has_configuration_definition(type_name, name):
|
429
|
-
|
508
|
+
# trust-tagged source propagates to loaded values; expressions and templates in config require trust
|
509
|
+
documentation_source = _tags.TrustedAsTemplate().tag(getattr(module, 'DOCUMENTATION', ''))
|
430
510
|
try:
|
431
511
|
dstring = yaml.load(_tags.Origin(path=path).tag(documentation_source), Loader=AnsibleLoader)
|
432
512
|
except ParserError as e:
|
@@ -488,7 +568,13 @@ class PluginLoader:
|
|
488
568
|
entry = collection_meta.get('plugin_routing', {}).get(plugin_type, {}).get(subdir_qualified_resource, None)
|
489
569
|
return entry
|
490
570
|
|
491
|
-
def _find_fq_plugin(
|
571
|
+
def _find_fq_plugin(
|
572
|
+
self,
|
573
|
+
fq_name: str,
|
574
|
+
extension: str | None,
|
575
|
+
plugin_load_context: PluginLoadContext,
|
576
|
+
ignore_deprecated: bool = False,
|
577
|
+
) -> PluginLoadContext:
|
492
578
|
"""Search builtin paths to find a plugin. No external paths are searched,
|
493
579
|
meaning plugins inside roles inside collections will be ignored.
|
494
580
|
"""
|
@@ -525,17 +611,13 @@ class PluginLoader:
|
|
525
611
|
version=removal_version,
|
526
612
|
date=removal_date,
|
527
613
|
removed=True,
|
528
|
-
|
529
|
-
requested_name=acr.collection,
|
530
|
-
resolved_name=acr.collection,
|
531
|
-
type='collection',
|
532
|
-
),
|
614
|
+
deprecator=PluginInfo._from_collection_name(acr.collection),
|
533
615
|
)
|
534
|
-
plugin_load_context.
|
535
|
-
plugin_load_context.
|
616
|
+
plugin_load_context.date = removal_date
|
617
|
+
plugin_load_context.version = removal_version
|
536
618
|
plugin_load_context.resolved = True
|
537
619
|
plugin_load_context.exit_reason = removed_msg
|
538
|
-
raise AnsiblePluginRemovedError(removed_msg, plugin_load_context=plugin_load_context)
|
620
|
+
raise AnsiblePluginRemovedError(message=removed_msg, plugin_load_context=plugin_load_context)
|
539
621
|
|
540
622
|
redirect = routing_metadata.get('redirect', None)
|
541
623
|
|
@@ -592,7 +674,7 @@ class PluginLoader:
|
|
592
674
|
# look for any matching extension in the package location (sans filter)
|
593
675
|
found_files = [f
|
594
676
|
for f in glob.iglob(os.path.join(pkg_path, n_resource) + '.*')
|
595
|
-
if os.path.isfile(f) and not f.endswith(C.MODULE_IGNORE_EXTS)]
|
677
|
+
if os.path.isfile(f) and not any(f.endswith(ext) for ext in C.MODULE_IGNORE_EXTS)]
|
596
678
|
|
597
679
|
if not found_files:
|
598
680
|
return plugin_load_context.nope('failed fuzzy extension match for {0} in {1}'.format(full_name, acr.collection))
|
@@ -623,7 +705,7 @@ class PluginLoader:
|
|
623
705
|
collection_list: list[str] | None = None,
|
624
706
|
) -> PluginLoadContext:
|
625
707
|
""" Find a plugin named name, returning contextual info about the load, recursively resolving redirection """
|
626
|
-
plugin_load_context = PluginLoadContext()
|
708
|
+
plugin_load_context = PluginLoadContext(self.type, self.package)
|
627
709
|
plugin_load_context.original_name = name
|
628
710
|
while True:
|
629
711
|
result = self._resolve_plugin_step(name, mod_type, ignore_deprecated, check_aliases, collection_list, plugin_load_context=plugin_load_context)
|
@@ -636,11 +718,8 @@ class PluginLoader:
|
|
636
718
|
else:
|
637
719
|
break
|
638
720
|
|
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...
|
721
|
+
for ex in plugin_load_context.raw_error_list:
|
722
|
+
display.error_as_warning(f"Error loading plugin {name!r}.", ex)
|
644
723
|
|
645
724
|
# FIXME: store structured deprecation data in PluginLoadContext and use display.deprecate
|
646
725
|
# if plugin_load_context.deprecated and C.config.get_config_value('DEPRECATION_WARNINGS'):
|
@@ -650,9 +729,15 @@ class PluginLoader:
|
|
650
729
|
|
651
730
|
return plugin_load_context
|
652
731
|
|
653
|
-
|
654
|
-
|
655
|
-
|
732
|
+
def _resolve_plugin_step(
|
733
|
+
self,
|
734
|
+
name: str,
|
735
|
+
mod_type: str = '',
|
736
|
+
ignore_deprecated: bool = False,
|
737
|
+
check_aliases: bool = False,
|
738
|
+
collection_list: list[str] | None = None,
|
739
|
+
plugin_load_context: PluginLoadContext | None = None,
|
740
|
+
) -> PluginLoadContext:
|
656
741
|
if not plugin_load_context:
|
657
742
|
raise ValueError('A PluginLoadContext is required')
|
658
743
|
|
@@ -707,11 +792,14 @@ class PluginLoader:
|
|
707
792
|
except (AnsiblePluginRemovedError, AnsiblePluginCircularRedirect, AnsibleCollectionUnsupportedVersionError):
|
708
793
|
# these are generally fatal, let them fly
|
709
794
|
raise
|
710
|
-
except ImportError as ie:
|
711
|
-
plugin_load_context.import_error_list.append(ie)
|
712
795
|
except Exception as ex:
|
713
|
-
|
714
|
-
|
796
|
+
plugin_load_context.raw_error_list.append(ex)
|
797
|
+
|
798
|
+
# DTFIX-RELEASE: can we deprecate/remove these stringified versions?
|
799
|
+
if isinstance(ex, ImportError):
|
800
|
+
plugin_load_context.import_error_list.append(ex)
|
801
|
+
else:
|
802
|
+
plugin_load_context.error_list.append(str(ex))
|
715
803
|
|
716
804
|
if plugin_load_context.error_list:
|
717
805
|
display.debug(msg='plugin lookup for {0} failed; errors: {1}'.format(name, '; '.join(plugin_load_context.error_list)))
|
@@ -737,13 +825,7 @@ class PluginLoader:
|
|
737
825
|
# requested mod_type
|
738
826
|
pull_cache = self._plugin_path_cache[suffix]
|
739
827
|
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
|
828
|
+
return plugin_load_context.resolve_legacy(name=name, pull_cache=pull_cache)
|
747
829
|
except KeyError:
|
748
830
|
# Cache miss. Now let's find the plugin
|
749
831
|
pass
|
@@ -796,13 +878,7 @@ class PluginLoader:
|
|
796
878
|
|
797
879
|
self._searched_paths.add(path)
|
798
880
|
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
|
881
|
+
return plugin_load_context.resolve_legacy(name=name, pull_cache=pull_cache)
|
806
882
|
except KeyError:
|
807
883
|
# Didn't find the plugin in this directory. Load modules from the next one
|
808
884
|
pass
|
@@ -810,18 +886,18 @@ class PluginLoader:
|
|
810
886
|
# if nothing is found, try finding alias/deprecated
|
811
887
|
if not name.startswith('_'):
|
812
888
|
alias_name = '_' + name
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
889
|
+
|
890
|
+
try:
|
891
|
+
plugin_load_context.resolve_legacy(name=alias_name, pull_cache=pull_cache)
|
892
|
+
except KeyError:
|
893
|
+
pass
|
894
|
+
else:
|
895
|
+
display.deprecated(
|
896
|
+
msg=f'Plugin {name!r} automatically redirected to {alias_name!r}.',
|
897
|
+
help_text=f'Use {alias_name!r} instead of {name!r} to refer to the plugin.',
|
898
|
+
version='2.23',
|
899
|
+
)
|
900
|
+
|
825
901
|
return plugin_load_context
|
826
902
|
|
827
903
|
# last ditch, if it's something that can be redirected, look for a builtin redirect before giving up
|
@@ -831,7 +907,7 @@ class PluginLoader:
|
|
831
907
|
|
832
908
|
return plugin_load_context.nope('{0} is not eligible for last-chance resolution'.format(name))
|
833
909
|
|
834
|
-
def has_plugin(self, name, collection_list=None):
|
910
|
+
def has_plugin(self, name: str, collection_list: list[str] | None = None) -> bool:
|
835
911
|
""" Checks if a plugin named name exists """
|
836
912
|
|
837
913
|
try:
|
@@ -842,41 +918,37 @@ class PluginLoader:
|
|
842
918
|
# log and continue, likely an innocuous type/package loading failure in collections import
|
843
919
|
display.debug('has_plugin error: {0}'.format(to_text(ex)))
|
844
920
|
|
845
|
-
|
846
|
-
|
847
|
-
def _load_module_source(self, name, path):
|
921
|
+
return False
|
848
922
|
|
849
|
-
|
850
|
-
if name.startswith('ansible_collections.'):
|
851
|
-
full_name = name
|
852
|
-
else:
|
853
|
-
full_name = '.'.join([self.package, name])
|
923
|
+
__contains__ = has_plugin
|
854
924
|
|
855
|
-
|
925
|
+
def _load_module_source(self, *, python_module_name: str, path: str) -> types.ModuleType:
|
926
|
+
if python_module_name in sys.modules:
|
856
927
|
# Avoids double loading, See https://github.com/ansible/ansible/issues/13110
|
857
|
-
return sys.modules[
|
928
|
+
return sys.modules[python_module_name]
|
858
929
|
|
859
930
|
with warnings.catch_warnings():
|
860
931
|
# FIXME: this still has issues if the module was previously imported but not "cached",
|
861
932
|
# we should bypass this entire codepath for things that are directly importable
|
862
933
|
warnings.simplefilter("ignore", RuntimeWarning)
|
863
|
-
spec = importlib.util.spec_from_file_location(to_native(
|
934
|
+
spec = importlib.util.spec_from_file_location(to_native(python_module_name), to_native(path))
|
864
935
|
module = importlib.util.module_from_spec(spec)
|
865
936
|
|
866
937
|
# mimic import machinery; make the module-being-loaded available in sys.modules during import
|
867
938
|
# and remove if there's a failure...
|
868
|
-
sys.modules[
|
939
|
+
sys.modules[python_module_name] = module
|
869
940
|
|
870
941
|
try:
|
871
942
|
spec.loader.exec_module(module)
|
872
943
|
except Exception:
|
873
|
-
del sys.modules[
|
944
|
+
del sys.modules[python_module_name]
|
874
945
|
raise
|
875
946
|
|
876
947
|
return module
|
877
948
|
|
878
949
|
def _update_object(
|
879
950
|
self,
|
951
|
+
*,
|
880
952
|
obj: _AnsiblePluginInfoMixin,
|
881
953
|
name: str,
|
882
954
|
path: str,
|
@@ -907,9 +979,9 @@ class PluginLoader:
|
|
907
979
|
is_core_plugin = ctx.plugin_load_context.plugin_resolved_collection == 'ansible.builtin'
|
908
980
|
if self.class_name == 'StrategyModule' and not is_core_plugin:
|
909
981
|
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
|
-
|
982
|
+
msg='Use of strategy plugins not included in ansible.builtin are deprecated and do not carry '
|
983
|
+
'any backwards compatibility guarantees. No alternative for third party strategy plugins '
|
984
|
+
'is currently planned.',
|
913
985
|
)
|
914
986
|
|
915
987
|
return ctx.object
|
@@ -936,8 +1008,6 @@ class PluginLoader:
|
|
936
1008
|
return get_with_context_result(None, plugin_load_context)
|
937
1009
|
|
938
1010
|
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
1011
|
resolved_type_name = plugin_load_context.plugin_resolved_name
|
942
1012
|
path = plugin_load_context.plugin_resolved_path
|
943
1013
|
if (cached_result := (self._plugin_instance_cache or {}).get(fq_name)) and cached_result[1].resolved:
|
@@ -947,7 +1017,7 @@ class PluginLoader:
|
|
947
1017
|
redirected_names = plugin_load_context.redirect_list or []
|
948
1018
|
|
949
1019
|
if path not in self._module_cache:
|
950
|
-
self._module_cache[path] = self._load_module_source(
|
1020
|
+
self._module_cache[path] = self._load_module_source(python_module_name=plugin_load_context._python_module_name, path=path)
|
951
1021
|
found_in_cache = False
|
952
1022
|
|
953
1023
|
self._load_config_defs(resolved_type_name, self._module_cache[path], path)
|
@@ -974,7 +1044,7 @@ class PluginLoader:
|
|
974
1044
|
# A plugin may need to use its _load_name in __init__ (for example, to set
|
975
1045
|
# or get options from config), so update the object before using the constructor
|
976
1046
|
instance = object.__new__(obj)
|
977
|
-
self._update_object(instance, resolved_type_name, path, redirected_names, fq_name)
|
1047
|
+
self._update_object(obj=instance, name=resolved_type_name, path=path, redirected_names=redirected_names, resolved=fq_name)
|
978
1048
|
obj.__init__(instance, *args, **kwargs) # pylint: disable=unnecessary-dunder-call
|
979
1049
|
obj = instance
|
980
1050
|
except TypeError as e:
|
@@ -984,12 +1054,12 @@ class PluginLoader:
|
|
984
1054
|
return get_with_context_result(None, plugin_load_context)
|
985
1055
|
raise
|
986
1056
|
|
987
|
-
self._update_object(obj, resolved_type_name, path, redirected_names, fq_name)
|
1057
|
+
self._update_object(obj=obj, name=resolved_type_name, path=path, redirected_names=redirected_names, resolved=fq_name)
|
988
1058
|
if self._plugin_instance_cache is not None and getattr(obj, 'is_stateless', False):
|
989
1059
|
self._plugin_instance_cache[fq_name] = (obj, plugin_load_context)
|
990
1060
|
elif self._plugin_instance_cache is not None:
|
991
1061
|
# 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())
|
1062
|
+
self._plugin_instance_cache[fq_name] = (None, PluginLoadContext(self.type, self.package))
|
993
1063
|
return get_with_context_result(obj, plugin_load_context)
|
994
1064
|
|
995
1065
|
def _display_plugin_load(self, class_name, name, searched_paths, path, found_in_cache=None, class_only=None):
|
@@ -1064,10 +1134,15 @@ class PluginLoader:
|
|
1064
1134
|
basename = os.path.basename(name)
|
1065
1135
|
is_j2 = isinstance(self, Jinja2Loader)
|
1066
1136
|
|
1137
|
+
if path in legacy_excluding_builtin:
|
1138
|
+
fqcn = basename
|
1139
|
+
else:
|
1140
|
+
fqcn = f"ansible.builtin.{basename}"
|
1141
|
+
|
1067
1142
|
if is_j2:
|
1068
1143
|
ref_name = path
|
1069
1144
|
else:
|
1070
|
-
ref_name =
|
1145
|
+
ref_name = fqcn
|
1071
1146
|
|
1072
1147
|
if not is_j2 and basename in _PLUGIN_FILTERS[self.package]:
|
1073
1148
|
# j2 plugins get processed in own class, here they would just be container files
|
@@ -1090,26 +1165,18 @@ class PluginLoader:
|
|
1090
1165
|
yield path
|
1091
1166
|
continue
|
1092
1167
|
|
1093
|
-
if path in legacy_excluding_builtin:
|
1094
|
-
fqcn = basename
|
1095
|
-
else:
|
1096
|
-
fqcn = f"ansible.builtin.{basename}"
|
1097
|
-
|
1098
1168
|
if (cached_result := (self._plugin_instance_cache or {}).get(fqcn)) and cached_result[1].resolved:
|
1099
1169
|
# Here just in case, but we don't call all() multiple times for vars plugins, so this should not be used.
|
1100
1170
|
yield cached_result[0]
|
1101
1171
|
continue
|
1102
1172
|
|
1103
1173
|
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
|
1174
|
+
path_context = PluginPathContext(path, path not in legacy_excluding_builtin)
|
1175
|
+
load_context = PluginLoadContext(self.type, self.package)
|
1176
|
+
load_context.resolve_legacy(basename, {basename: path_context})
|
1110
1177
|
|
1111
1178
|
try:
|
1112
|
-
module = self._load_module_source(
|
1179
|
+
module = self._load_module_source(python_module_name=load_context._python_module_name, path=path)
|
1113
1180
|
except Exception as e:
|
1114
1181
|
display.warning("Skipping plugin (%s), cannot load: %s" % (path, to_text(e)))
|
1115
1182
|
continue
|
@@ -1147,7 +1214,7 @@ class PluginLoader:
|
|
1147
1214
|
except TypeError as e:
|
1148
1215
|
display.warning("Skipping plugin (%s) as it seems to be incomplete: %s" % (path, to_text(e)))
|
1149
1216
|
|
1150
|
-
self._update_object(obj, basename, path, resolved=fqcn)
|
1217
|
+
self._update_object(obj=obj, name=basename, path=path, resolved=fqcn)
|
1151
1218
|
|
1152
1219
|
if self._plugin_instance_cache is not None:
|
1153
1220
|
needs_enabled = False
|
@@ -1239,7 +1306,7 @@ class Jinja2Loader(PluginLoader):
|
|
1239
1306
|
try:
|
1240
1307
|
# use 'parent' loader class to find files, but cannot return this as it can contain multiple plugins per file
|
1241
1308
|
if plugin_path not in self._module_cache:
|
1242
|
-
self._module_cache[plugin_path] = self._load_module_source(full_name, plugin_path)
|
1309
|
+
self._module_cache[plugin_path] = self._load_module_source(python_module_name=full_name, path=plugin_path)
|
1243
1310
|
module = self._module_cache[plugin_path]
|
1244
1311
|
obj = getattr(module, self.class_name)
|
1245
1312
|
except Exception as e:
|
@@ -1262,7 +1329,7 @@ class Jinja2Loader(PluginLoader):
|
|
1262
1329
|
plugin = self._plugin_wrapper_type(func)
|
1263
1330
|
if plugin in plugins:
|
1264
1331
|
continue
|
1265
|
-
self._update_object(plugin, full, plugin_path, resolved=fq_name)
|
1332
|
+
self._update_object(obj=plugin, name=full, path=plugin_path, resolved=fq_name)
|
1266
1333
|
plugins.append(plugin)
|
1267
1334
|
|
1268
1335
|
return plugins
|
@@ -1276,7 +1343,7 @@ class Jinja2Loader(PluginLoader):
|
|
1276
1343
|
|
1277
1344
|
requested_name = name
|
1278
1345
|
|
1279
|
-
context = PluginLoadContext()
|
1346
|
+
context = PluginLoadContext(self.type, self.package)
|
1280
1347
|
|
1281
1348
|
# avoid collection path for legacy
|
1282
1349
|
name = name.removeprefix('ansible.legacy.')
|
@@ -1288,11 +1355,8 @@ class Jinja2Loader(PluginLoader):
|
|
1288
1355
|
if isinstance(known_plugin, _DeferredPluginLoadFailure):
|
1289
1356
|
raise known_plugin.ex
|
1290
1357
|
|
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
|
1358
|
+
context.resolve_legacy_jinja_plugin(name, known_plugin)
|
1359
|
+
|
1296
1360
|
return get_with_context_result(known_plugin, context)
|
1297
1361
|
|
1298
1362
|
plugin = None
|
@@ -1328,7 +1392,12 @@ class Jinja2Loader(PluginLoader):
|
|
1328
1392
|
|
1329
1393
|
warning_text = f'{self.type.title()} "{key}" has been deprecated.{" " if warning_text else ""}{warning_text}'
|
1330
1394
|
|
1331
|
-
display.deprecated(
|
1395
|
+
display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
1396
|
+
msg=warning_text,
|
1397
|
+
version=removal_version,
|
1398
|
+
date=removal_date,
|
1399
|
+
deprecator=PluginInfo._from_collection_name(acr.collection),
|
1400
|
+
)
|
1332
1401
|
|
1333
1402
|
# check removal
|
1334
1403
|
tombstone_entry = routing_entry.get('tombstone')
|
@@ -1343,11 +1412,7 @@ class Jinja2Loader(PluginLoader):
|
|
1343
1412
|
version=removal_version,
|
1344
1413
|
date=removal_date,
|
1345
1414
|
removed=True,
|
1346
|
-
|
1347
|
-
requested_name=acr.collection,
|
1348
|
-
resolved_name=acr.collection,
|
1349
|
-
type='collection',
|
1350
|
-
),
|
1415
|
+
deprecator=PluginInfo._from_collection_name(acr.collection),
|
1351
1416
|
)
|
1352
1417
|
|
1353
1418
|
raise AnsiblePluginRemovedError(exc_msg)
|
@@ -1400,7 +1465,7 @@ class Jinja2Loader(PluginLoader):
|
|
1400
1465
|
plugin = self._plugin_wrapper_type(func)
|
1401
1466
|
if plugin:
|
1402
1467
|
context = plugin_impl.plugin_load_context
|
1403
|
-
self._update_object(plugin, requested_name, plugin_impl.object._original_path, resolved=fq_name)
|
1468
|
+
self._update_object(obj=plugin, name=requested_name, path=plugin_impl.object._original_path, resolved=fq_name)
|
1404
1469
|
# context will have filename, which for tests/filters might not be correct
|
1405
1470
|
context._resolved_fqcn = plugin.ansible_name
|
1406
1471
|
# 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(
|