ansible-core 2.18.7rc1__py3-none-any.whl → 2.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ansible-core might be problematic. Click here for more details.
- ansible/_internal/__init__.py +53 -0
- ansible/_internal/_ansiballz/__init__.py +0 -0
- ansible/_internal/_ansiballz/_builder.py +101 -0
- ansible/_internal/_ansiballz/_wrapper.py +262 -0
- ansible/_internal/_collection_proxy.py +47 -0
- ansible/_internal/_datatag/__init__.py +0 -0
- ansible/_internal/_datatag/_tags.py +130 -0
- ansible/_internal/_datatag/_utils.py +19 -0
- ansible/_internal/_datatag/_wrappers.py +33 -0
- ansible/_internal/_errors/__init__.py +0 -0
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +123 -0
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_handler.py +91 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +214 -0
- ansible/_internal/_json/_legacy_encoder.py +34 -0
- ansible/_internal/_json/_profiles/__init__.py +0 -0
- ansible/_internal/_json/_profiles/_cache_persistence.py +57 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
- ansible/_internal/_json/_profiles/_legacy.py +189 -0
- ansible/_internal/_locking.py +21 -0
- ansible/_internal/_plugins/__init__.py +0 -0
- ansible/_internal/_plugins/_cache.py +57 -0
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/_internal/_ssh/_ssh_agent.py +619 -0
- ansible/_internal/_task.py +78 -0
- ansible/_internal/_templating/__init__.py +12 -0
- ansible/_internal/_templating/_access.py +86 -0
- ansible/_internal/_templating/_chain_templar.py +63 -0
- ansible/_internal/_templating/_datatag.py +95 -0
- ansible/_internal/_templating/_engine.py +592 -0
- ansible/_internal/_templating/_errors.py +28 -0
- ansible/_internal/_templating/_jinja_bits.py +1106 -0
- ansible/_internal/_templating/_jinja_common.py +323 -0
- ansible/_internal/_templating/_jinja_patches.py +44 -0
- ansible/_internal/_templating/_jinja_plugins.py +375 -0
- ansible/_internal/_templating/_lazy_containers.py +633 -0
- ansible/_internal/_templating/_marker_behaviors.py +103 -0
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +70 -0
- ansible/_internal/_templating/_utils.py +108 -0
- ansible/_internal/_testing.py +26 -0
- ansible/_internal/_wrapt.py +1052 -0
- ansible/_internal/_yaml/__init__.py +0 -0
- ansible/_internal/_yaml/_constructor.py +240 -0
- ansible/_internal/_yaml/_dumper.py +70 -0
- ansible/_internal/_yaml/_errors.py +166 -0
- ansible/_internal/_yaml/_loader.py +66 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/README.md +11 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/action/debug.py +36 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/apply_trust.py +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +27 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/finalize.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/origin.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.py +24 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.yml +33 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/tag_names.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +17 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +49 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.py +21 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.yml +2 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.py +15 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.yml +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.yml +19 -0
- ansible/cli/__init__.py +93 -104
- ansible/cli/_ssh_askpass.py +54 -0
- ansible/cli/adhoc.py +20 -10
- ansible/cli/arguments/option_helpers.py +163 -10
- ansible/cli/config.py +43 -68
- ansible/cli/console.py +13 -11
- ansible/cli/doc.py +134 -77
- ansible/cli/galaxy.py +27 -20
- ansible/cli/inventory.py +28 -28
- ansible/cli/playbook.py +4 -12
- ansible/cli/pull.py +6 -3
- ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
- ansible/cli/vault.py +12 -11
- ansible/compat/__init__.py +2 -2
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +218 -133
- ansible/config/manager.py +220 -159
- ansible/constants.py +2 -65
- ansible/errors/__init__.py +350 -256
- ansible/executor/interpreter_discovery.py +28 -149
- ansible/executor/module_common.py +480 -514
- ansible/executor/play_iterator.py +22 -27
- ansible/executor/playbook_executor.py +11 -11
- ansible/executor/powershell/async_watchdog.ps1 +97 -102
- ansible/executor/powershell/async_wrapper.ps1 +204 -153
- ansible/executor/powershell/become_wrapper.ps1 +107 -144
- ansible/executor/powershell/bootstrap_wrapper.ps1 +46 -9
- ansible/executor/powershell/coverage_wrapper.ps1 +91 -135
- ansible/executor/powershell/exec_wrapper.ps1 +675 -196
- ansible/executor/powershell/module_manifest.py +469 -265
- ansible/executor/powershell/module_wrapper.ps1 +195 -186
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/powershell/psrp_fetch_file.ps1 +41 -0
- ansible/executor/powershell/psrp_put_file.ps1 +122 -0
- ansible/executor/powershell/winrm_fetch_file.ps1 +46 -0
- ansible/executor/powershell/winrm_put_file.ps1 +36 -0
- ansible/executor/process/worker.py +139 -149
- ansible/executor/stats.py +5 -5
- ansible/executor/task_executor.py +270 -297
- ansible/executor/task_queue_manager.py +135 -137
- ansible/executor/task_result.py +182 -79
- ansible/galaxy/__init__.py +2 -2
- ansible/galaxy/api.py +26 -25
- ansible/galaxy/collection/__init__.py +6 -14
- ansible/galaxy/collection/concrete_artifact_manager.py +12 -21
- ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
- ansible/galaxy/dependency_resolution/providers.py +4 -4
- ansible/galaxy/dependency_resolution/reporters.py +81 -0
- ansible/galaxy/role.py +6 -10
- ansible/galaxy/token.py +28 -21
- ansible/inventory/data.py +47 -57
- ansible/inventory/group.py +50 -73
- ansible/inventory/helpers.py +9 -0
- ansible/inventory/host.py +37 -54
- ansible/inventory/manager.py +79 -34
- ansible/keyword_desc.yml +1 -1
- ansible/module_utils/_internal/__init__.py +55 -0
- ansible/module_utils/_internal/_ambient_context.py +58 -0
- ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
- ansible/module_utils/_internal/_ansiballz/_loader.py +81 -0
- ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
- ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
- ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
- ansible/module_utils/_internal/_dataclass_validation.py +217 -0
- ansible/module_utils/_internal/_datatag/__init__.py +961 -0
- ansible/module_utils/_internal/_datatag/_tags.py +16 -0
- ansible/module_utils/_internal/_debugging.py +31 -0
- ansible/module_utils/_internal/_deprecator.py +157 -0
- ansible/module_utils/_internal/_errors.py +101 -0
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/__init__.py +63 -0
- ansible/module_utils/_internal/_json/_legacy_encoder.py +26 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +428 -0
- ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +33 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +37 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_c2m.py +35 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_m2c.py +33 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +52 -0
- ansible/module_utils/_internal/_messages.py +130 -0
- ansible/module_utils/_internal/_patches/__init__.py +66 -0
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +53 -0
- ansible/module_utils/_internal/_patches/_socket_patch.py +34 -0
- ansible/module_utils/_internal/_patches/_sys_intern_patch.py +34 -0
- ansible/module_utils/_internal/_plugin_info.py +38 -0
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_testing.py +0 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +92 -0
- ansible/module_utils/_internal/_validation.py +14 -0
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/api.py +1 -2
- ansible/module_utils/basic.py +303 -202
- ansible/module_utils/common/_utils.py +24 -28
- ansible/module_utils/common/arg_spec.py +8 -3
- ansible/module_utils/common/collections.py +7 -2
- ansible/module_utils/common/dict_transformations.py +2 -2
- ansible/module_utils/common/file.py +2 -2
- ansible/module_utils/common/json.py +90 -84
- ansible/module_utils/common/locale.py +2 -2
- ansible/module_utils/common/parameters.py +27 -24
- ansible/module_utils/common/process.py +2 -3
- ansible/module_utils/common/respawn.py +11 -33
- ansible/module_utils/common/sentinel.py +66 -0
- ansible/module_utils/common/sys_info.py +8 -8
- ansible/module_utils/common/text/converters.py +16 -37
- ansible/module_utils/common/validation.py +35 -24
- ansible/module_utils/common/warnings.py +143 -25
- ansible/module_utils/common/yaml.py +29 -3
- ansible/module_utils/compat/datetime.py +33 -21
- ansible/module_utils/compat/paramiko.py +21 -10
- ansible/module_utils/compat/typing.py +6 -5
- ansible/module_utils/connection.py +10 -13
- ansible/module_utils/csharp/Ansible.Basic.cs +15 -12
- ansible/module_utils/csharp/Ansible.Become.cs +1 -0
- ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
- ansible/module_utils/csharp/Ansible._Async.cs +517 -0
- ansible/module_utils/datatag.py +49 -0
- ansible/module_utils/distro/__init__.py +2 -2
- ansible/module_utils/facts/ansible_collector.py +4 -5
- ansible/module_utils/facts/collector.py +13 -14
- ansible/module_utils/facts/compat.py +4 -4
- ansible/module_utils/facts/default_collectors.py +1 -1
- ansible/module_utils/facts/hardware/aix.py +34 -0
- ansible/module_utils/facts/hardware/base.py +2 -2
- ansible/module_utils/facts/hardware/darwin.py +1 -3
- ansible/module_utils/facts/hardware/freebsd.py +2 -2
- ansible/module_utils/facts/hardware/linux.py +5 -5
- ansible/module_utils/facts/namespace.py +1 -1
- ansible/module_utils/facts/network/base.py +1 -1
- ansible/module_utils/facts/network/fc_wwn.py +1 -2
- ansible/module_utils/facts/network/iscsi.py +1 -2
- ansible/module_utils/facts/network/nvme.py +1 -2
- ansible/module_utils/facts/other/facter.py +2 -3
- ansible/module_utils/facts/other/ohai.py +2 -3
- ansible/module_utils/facts/sysctl.py +4 -6
- ansible/module_utils/facts/system/apparmor.py +1 -2
- ansible/module_utils/facts/system/caps.py +3 -3
- ansible/module_utils/facts/system/chroot.py +1 -2
- ansible/module_utils/facts/system/cmdline.py +1 -2
- ansible/module_utils/facts/system/date_time.py +5 -3
- ansible/module_utils/facts/system/distribution.py +27 -13
- ansible/module_utils/facts/system/dns.py +1 -1
- ansible/module_utils/facts/system/env.py +1 -2
- ansible/module_utils/facts/system/fips.py +7 -20
- ansible/module_utils/facts/system/loadavg.py +1 -2
- ansible/module_utils/facts/system/local.py +2 -3
- ansible/module_utils/facts/system/lsb.py +1 -2
- ansible/module_utils/facts/system/pkg_mgr.py +1 -2
- ansible/module_utils/facts/system/platform.py +1 -2
- ansible/module_utils/facts/system/python.py +1 -2
- ansible/module_utils/facts/system/selinux.py +1 -1
- ansible/module_utils/facts/system/service_mgr.py +1 -2
- ansible/module_utils/facts/system/ssh_pub_keys.py +1 -1
- ansible/module_utils/facts/system/systemd.py +1 -1
- ansible/module_utils/facts/system/user.py +1 -2
- ansible/module_utils/facts/utils.py +3 -3
- ansible/module_utils/facts/virtual/base.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +3 -3
- ansible/module_utils/facts/virtual/sunos.py +3 -15
- ansible/module_utils/facts/virtual/sysctl.py +3 -16
- ansible/module_utils/json_utils.py +2 -2
- ansible/module_utils/parsing/convert_bool.py +7 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
- ansible/module_utils/service.py +21 -31
- ansible/module_utils/splitter.py +7 -7
- ansible/module_utils/testing.py +31 -0
- ansible/module_utils/urls.py +64 -35
- ansible/modules/add_host.py +4 -4
- ansible/modules/apt.py +69 -49
- ansible/modules/apt_key.py +19 -12
- ansible/modules/apt_repository.py +32 -51
- ansible/modules/assemble.py +16 -14
- ansible/modules/assert.py +4 -4
- ansible/modules/async_status.py +24 -24
- ansible/modules/async_wrapper.py +20 -25
- ansible/modules/blockinfile.py +6 -7
- ansible/modules/command.py +13 -20
- ansible/modules/copy.py +60 -147
- ansible/modules/cron.py +24 -21
- ansible/modules/deb822_repository.py +8 -9
- ansible/modules/debconf.py +5 -5
- ansible/modules/debug.py +4 -4
- ansible/modules/dnf.py +8 -8
- ansible/modules/dnf5.py +39 -13
- ansible/modules/dpkg_selections.py +4 -4
- ansible/modules/expect.py +13 -15
- ansible/modules/fail.py +4 -4
- ansible/modules/fetch.py +4 -4
- ansible/modules/file.py +184 -144
- ansible/modules/find.py +22 -20
- ansible/modules/gather_facts.py +3 -3
- ansible/modules/get_url.py +77 -54
- ansible/modules/getent.py +7 -9
- ansible/modules/git.py +38 -38
- ansible/modules/group.py +6 -6
- ansible/modules/group_by.py +4 -4
- ansible/modules/hostname.py +15 -32
- ansible/modules/import_playbook.py +6 -6
- ansible/modules/import_role.py +6 -6
- ansible/modules/import_tasks.py +6 -6
- ansible/modules/include_role.py +6 -6
- ansible/modules/include_tasks.py +6 -6
- ansible/modules/include_vars.py +6 -6
- ansible/modules/iptables.py +86 -73
- ansible/modules/known_hosts.py +22 -24
- ansible/modules/lineinfile.py +5 -5
- ansible/modules/meta.py +4 -4
- ansible/modules/mount_facts.py +2 -2
- ansible/modules/package.py +10 -4
- ansible/modules/package_facts.py +22 -10
- ansible/modules/pause.py +6 -6
- ansible/modules/ping.py +6 -6
- ansible/modules/pip.py +21 -26
- ansible/modules/raw.py +6 -6
- ansible/modules/reboot.py +6 -6
- ansible/modules/replace.py +10 -14
- ansible/modules/rpm_key.py +7 -8
- ansible/modules/script.py +4 -4
- ansible/modules/service.py +10 -17
- ansible/modules/service_facts.py +87 -10
- ansible/modules/set_fact.py +5 -5
- ansible/modules/set_stats.py +4 -4
- ansible/modules/setup.py +2 -2
- ansible/modules/shell.py +6 -6
- ansible/modules/slurp.py +16 -19
- ansible/modules/stat.py +15 -31
- ansible/modules/subversion.py +15 -15
- ansible/modules/systemd.py +7 -7
- ansible/modules/systemd_service.py +7 -7
- ansible/modules/sysvinit.py +9 -9
- ansible/modules/tempfile.py +5 -6
- ansible/modules/template.py +6 -6
- ansible/modules/unarchive.py +38 -17
- ansible/modules/uri.py +33 -26
- ansible/modules/user.py +45 -32
- ansible/modules/validate_argument_spec.py +10 -7
- ansible/modules/wait_for.py +70 -60
- ansible/modules/wait_for_connection.py +6 -6
- ansible/modules/yum_repository.py +10 -9
- ansible/parsing/ajson.py +17 -37
- ansible/parsing/dataloader.py +99 -54
- ansible/parsing/mod_args.py +62 -60
- ansible/parsing/plugin_docs.py +21 -86
- ansible/parsing/quoting.py +1 -1
- ansible/parsing/splitter.py +27 -12
- ansible/parsing/utils/addresses.py +24 -24
- ansible/parsing/utils/jsonify.py +5 -1
- ansible/parsing/utils/yaml.py +32 -61
- ansible/parsing/vault/__init__.py +327 -99
- ansible/parsing/yaml/__init__.py +0 -18
- ansible/parsing/yaml/dumper.py +6 -120
- ansible/parsing/yaml/loader.py +6 -39
- ansible/parsing/yaml/objects.py +43 -335
- ansible/playbook/__init__.py +1 -1
- ansible/playbook/attribute.py +8 -3
- ansible/playbook/base.py +187 -134
- ansible/playbook/block.py +26 -24
- ansible/playbook/collectionsearch.py +1 -15
- ansible/playbook/conditional.py +3 -77
- ansible/playbook/handler.py +8 -2
- ansible/playbook/helpers.py +41 -53
- ansible/playbook/included_file.py +32 -26
- ansible/playbook/loop_control.py +2 -2
- ansible/playbook/play.py +85 -44
- ansible/playbook/play_context.py +14 -17
- ansible/playbook/playbook_include.py +27 -62
- ansible/playbook/role/__init__.py +64 -49
- ansible/playbook/role/definition.py +15 -17
- ansible/playbook/role/include.py +2 -4
- ansible/playbook/role/metadata.py +10 -11
- ansible/playbook/role_include.py +3 -3
- ansible/playbook/taggable.py +28 -12
- ansible/playbook/task.py +192 -121
- ansible/playbook/task_include.py +5 -5
- ansible/plugins/__init__.py +58 -26
- ansible/plugins/action/__init__.py +188 -186
- ansible/plugins/action/add_host.py +2 -2
- ansible/plugins/action/assemble.py +11 -18
- ansible/plugins/action/assert.py +55 -67
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +14 -17
- ansible/plugins/action/debug.py +37 -31
- ansible/plugins/action/dnf.py +3 -4
- ansible/plugins/action/fail.py +1 -1
- ansible/plugins/action/fetch.py +7 -8
- ansible/plugins/action/gather_facts.py +13 -14
- ansible/plugins/action/group_by.py +1 -1
- ansible/plugins/action/include_vars.py +10 -11
- ansible/plugins/action/package.py +8 -14
- ansible/plugins/action/pause.py +2 -2
- ansible/plugins/action/script.py +27 -38
- ansible/plugins/action/service.py +9 -18
- ansible/plugins/action/set_fact.py +3 -12
- ansible/plugins/action/set_stats.py +3 -8
- ansible/plugins/action/template.py +47 -67
- ansible/plugins/action/unarchive.py +6 -16
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/action/validate_argument_spec.py +5 -5
- ansible/plugins/action/wait_for_connection.py +1 -1
- ansible/plugins/become/__init__.py +31 -8
- ansible/plugins/become/runas.py +71 -0
- ansible/plugins/become/su.py +13 -8
- ansible/plugins/become/sudo.py +19 -0
- ansible/plugins/cache/__init__.py +52 -63
- ansible/plugins/cache/base.py +8 -0
- ansible/plugins/cache/jsonfile.py +10 -16
- ansible/plugins/cache/memory.py +6 -12
- ansible/plugins/callback/__init__.py +294 -201
- ansible/plugins/callback/default.py +99 -95
- ansible/plugins/callback/junit.py +44 -43
- ansible/plugins/callback/minimal.py +28 -25
- ansible/plugins/callback/oneline.py +34 -21
- ansible/plugins/callback/tree.py +27 -16
- ansible/plugins/connection/__init__.py +47 -34
- ansible/plugins/connection/local.py +156 -60
- ansible/plugins/connection/paramiko_ssh.py +34 -24
- ansible/plugins/connection/psrp.py +76 -165
- ansible/plugins/connection/ssh.py +326 -86
- ansible/plugins/connection/winrm.py +62 -141
- ansible/plugins/doc_fragments/action_common_attributes.py +14 -14
- ansible/plugins/doc_fragments/action_core.py +6 -6
- ansible/plugins/doc_fragments/backup.py +2 -2
- ansible/plugins/doc_fragments/checksum_common.py +27 -0
- ansible/plugins/doc_fragments/constructed.py +8 -4
- ansible/plugins/doc_fragments/decrypt.py +2 -2
- ansible/plugins/doc_fragments/default_callback.py +2 -2
- ansible/plugins/doc_fragments/files.py +2 -2
- ansible/plugins/doc_fragments/inventory_cache.py +2 -2
- ansible/plugins/doc_fragments/result_format_callback.py +2 -2
- ansible/plugins/doc_fragments/return_common.py +2 -2
- ansible/plugins/doc_fragments/template_common.py +4 -4
- ansible/plugins/doc_fragments/url.py +17 -1
- ansible/plugins/doc_fragments/url_windows.py +2 -2
- ansible/plugins/doc_fragments/validate.py +2 -2
- ansible/plugins/doc_fragments/vars_plugin_staging.py +2 -2
- ansible/plugins/filter/__init__.py +6 -2
- ansible/plugins/filter/b64decode.yml +22 -0
- ansible/plugins/filter/b64encode.yml +22 -0
- ansible/plugins/filter/bool.yml +11 -4
- ansible/plugins/filter/core.py +245 -120
- ansible/plugins/filter/encryption.py +42 -34
- ansible/plugins/filter/flatten.yml +3 -2
- ansible/plugins/filter/human_to_bytes.yml +1 -1
- ansible/plugins/filter/mathstuff.py +30 -37
- ansible/plugins/filter/password_hash.yml +8 -0
- ansible/plugins/filter/pow.yml +1 -1
- ansible/plugins/filter/regex_search.yml +1 -4
- ansible/plugins/filter/root.yml +1 -1
- ansible/plugins/filter/split.yml +1 -1
- ansible/plugins/filter/strftime.yml +3 -3
- ansible/plugins/filter/to_nice_yaml.yml +0 -4
- ansible/plugins/filter/to_uuid.yml +1 -1
- ansible/plugins/filter/to_yaml.yml +0 -4
- ansible/plugins/filter/unvault.yml +1 -1
- ansible/plugins/filter/urls.py +1 -1
- ansible/plugins/filter/urlsplit.py +8 -9
- ansible/plugins/filter/vault.yml +14 -9
- ansible/plugins/filter/win_basename.yml +6 -1
- ansible/plugins/filter/win_dirname.yml +5 -0
- ansible/plugins/inventory/__init__.py +107 -86
- ansible/plugins/inventory/advanced_host_list.py +7 -5
- ansible/plugins/inventory/auto.py +11 -4
- ansible/plugins/inventory/constructed.py +21 -24
- ansible/plugins/inventory/generator.py +16 -11
- ansible/plugins/inventory/host_list.py +7 -5
- ansible/plugins/inventory/ini.py +78 -44
- ansible/plugins/inventory/script.py +190 -120
- ansible/plugins/inventory/toml.py +16 -126
- ansible/plugins/inventory/yaml.py +10 -8
- ansible/plugins/list.py +72 -19
- ansible/plugins/loader.py +383 -198
- ansible/plugins/lookup/__init__.py +21 -4
- ansible/plugins/lookup/config.py +21 -35
- ansible/plugins/lookup/csvfile.py +19 -73
- ansible/plugins/lookup/dict.py +1 -6
- ansible/plugins/lookup/env.py +12 -9
- ansible/plugins/lookup/file.py +5 -8
- ansible/plugins/lookup/first_found.py +87 -55
- ansible/plugins/lookup/indexed_items.py +1 -10
- ansible/plugins/lookup/ini.py +14 -13
- ansible/plugins/lookup/items.py +1 -1
- ansible/plugins/lookup/lines.py +8 -1
- ansible/plugins/lookup/list.py +1 -1
- ansible/plugins/lookup/nested.py +2 -18
- ansible/plugins/lookup/password.py +5 -5
- ansible/plugins/lookup/pipe.py +5 -7
- ansible/plugins/lookup/sequence.py +18 -8
- ansible/plugins/lookup/subelements.py +1 -4
- ansible/plugins/lookup/template.py +47 -36
- ansible/plugins/lookup/together.py +0 -12
- ansible/plugins/lookup/unvault.py +1 -5
- ansible/plugins/lookup/url.py +4 -10
- ansible/plugins/lookup/vars.py +16 -24
- ansible/plugins/shell/__init__.py +58 -4
- ansible/plugins/shell/cmd.py +2 -2
- ansible/plugins/shell/powershell.py +106 -31
- ansible/plugins/shell/sh.py +13 -7
- ansible/plugins/strategy/__init__.py +168 -193
- ansible/plugins/strategy/debug.py +2 -2
- ansible/plugins/strategy/free.py +16 -31
- ansible/plugins/strategy/host_pinned.py +2 -2
- ansible/plugins/strategy/linear.py +41 -41
- ansible/plugins/terminal/__init__.py +4 -4
- ansible/plugins/test/__init__.py +7 -2
- ansible/plugins/test/core.py +75 -35
- ansible/plugins/test/files.py +1 -1
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/mathstuff.py +3 -3
- ansible/plugins/test/uri.py +5 -8
- ansible/plugins/vars/host_group_vars.py +7 -14
- ansible/release.py +2 -2
- ansible/template/__init__.py +353 -943
- ansible/utils/__init__.py +0 -18
- ansible/utils/collection_loader/__init__.py +54 -5
- ansible/utils/collection_loader/_collection_config.py +5 -6
- ansible/utils/collection_loader/_collection_finder.py +82 -96
- ansible/utils/collection_loader/_collection_meta.py +15 -8
- ansible/utils/display.py +485 -73
- ansible/utils/encrypt.py +27 -19
- ansible/utils/fqcn.py +2 -2
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +8 -10
- ansible/utils/helpers.py +2 -2
- ansible/utils/listify.py +10 -8
- ansible/utils/lock.py +2 -2
- ansible/utils/path.py +10 -12
- ansible/utils/plugin_docs.py +16 -14
- ansible/utils/py3compat.py +2 -7
- ansible/utils/sentinel.py +4 -62
- ansible/utils/singleton.py +2 -0
- ansible/utils/ssh_functions.py +6 -2
- ansible/utils/unsafe_proxy.py +23 -332
- ansible/utils/vars.py +55 -8
- ansible/utils/version.py +2 -2
- ansible/vars/clean.py +5 -5
- ansible/vars/hostvars.py +60 -90
- ansible/vars/manager.py +220 -285
- ansible/vars/plugins.py +4 -4
- ansible/vars/reserved.py +13 -12
- {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/METADATA +4 -3
- ansible_core-2.19.0.dist-info/RECORD +1097 -0
- ansible_core-2.19.0.dist-info/licenses/licenses/BSD-3-Clause.txt +28 -0
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/remote.txt +6 -6
- ansible_test/_data/completion/windows.txt +1 -0
- ansible_test/_data/requirements/ansible.txt +2 -2
- ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +5 -5
- ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_data/requirements/units.txt +1 -0
- ansible_test/_internal/__init__.py +6 -0
- ansible_test/_internal/ansible_util.py +3 -1
- ansible_test/_internal/become.py +1 -0
- ansible_test/_internal/bootstrap.py +1 -0
- ansible_test/_internal/cache.py +1 -0
- ansible_test/_internal/cgroup.py +1 -0
- ansible_test/_internal/ci/__init__.py +1 -0
- ansible_test/_internal/ci/azp.py +1 -0
- ansible_test/_internal/ci/local.py +1 -0
- ansible_test/_internal/classification/__init__.py +1 -0
- ansible_test/_internal/classification/common.py +1 -0
- ansible_test/_internal/classification/csharp.py +1 -0
- ansible_test/_internal/classification/powershell.py +1 -0
- ansible_test/_internal/classification/python.py +1 -0
- ansible_test/_internal/cli/__init__.py +1 -0
- ansible_test/_internal/cli/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/__init__.py +1 -0
- ansible_test/_internal/cli/argparsing/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/argcompletion.py +1 -0
- ansible_test/_internal/cli/argparsing/parsers.py +1 -0
- ansible_test/_internal/cli/commands/__init__.py +11 -5
- ansible_test/_internal/cli/commands/coverage/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/cli/commands/coverage/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/erase.py +1 -0
- ansible_test/_internal/cli/commands/coverage/html.py +1 -0
- ansible_test/_internal/cli/commands/coverage/report.py +1 -0
- ansible_test/_internal/cli/commands/coverage/xml.py +1 -0
- ansible_test/_internal/cli/commands/env.py +1 -0
- ansible_test/_internal/cli/commands/integration/__init__.py +1 -0
- ansible_test/_internal/cli/commands/integration/network.py +1 -0
- ansible_test/_internal/cli/commands/integration/posix.py +1 -0
- ansible_test/_internal/cli/commands/integration/windows.py +1 -0
- ansible_test/_internal/cli/commands/sanity.py +9 -0
- ansible_test/_internal/cli/commands/shell.py +1 -0
- ansible_test/_internal/cli/commands/units.py +1 -0
- ansible_test/_internal/cli/compat.py +1 -0
- ansible_test/_internal/cli/completers.py +1 -0
- ansible_test/_internal/cli/converters.py +1 -0
- ansible_test/_internal/cli/environments.py +52 -5
- ansible_test/_internal/cli/epilog.py +1 -0
- ansible_test/_internal/cli/parsers/__init__.py +1 -0
- ansible_test/_internal/cli/parsers/base_argument_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/helpers.py +1 -0
- ansible_test/_internal/cli/parsers/host_config_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/key_value_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/value_parsers.py +1 -0
- ansible_test/_internal/commands/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/__init__.py +3 -2
- ansible_test/_internal/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/commands/coverage/combine.py +2 -1
- ansible_test/_internal/commands/coverage/erase.py +1 -0
- ansible_test/_internal/commands/coverage/html.py +1 -0
- ansible_test/_internal/commands/coverage/report.py +1 -0
- ansible_test/_internal/commands/coverage/xml.py +1 -0
- ansible_test/_internal/commands/env/__init__.py +2 -0
- ansible_test/_internal/commands/integration/__init__.py +22 -5
- ansible_test/_internal/commands/integration/cloud/__init__.py +1 -0
- ansible_test/_internal/commands/integration/cloud/acme.py +2 -1
- ansible_test/_internal/commands/integration/cloud/aws.py +1 -0
- ansible_test/_internal/commands/integration/cloud/azure.py +1 -0
- ansible_test/_internal/commands/integration/cloud/cs.py +1 -0
- ansible_test/_internal/commands/integration/cloud/digitalocean.py +1 -0
- ansible_test/_internal/commands/integration/cloud/galaxy.py +3 -2
- ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -0
- ansible_test/_internal/commands/integration/cloud/httptester.py +3 -2
- ansible_test/_internal/commands/integration/cloud/nios.py +2 -1
- ansible_test/_internal/commands/integration/cloud/opennebula.py +1 -0
- ansible_test/_internal/commands/integration/cloud/openshift.py +1 -0
- ansible_test/_internal/commands/integration/cloud/scaleway.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vcenter.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vultr.py +1 -0
- ansible_test/_internal/commands/integration/coverage.py +8 -2
- ansible_test/_internal/commands/integration/filters.py +1 -0
- ansible_test/_internal/commands/integration/network.py +1 -0
- ansible_test/_internal/commands/integration/posix.py +1 -0
- ansible_test/_internal/commands/integration/windows.py +1 -0
- ansible_test/_internal/commands/sanity/__init__.py +19 -2
- ansible_test/_internal/commands/sanity/ansible_doc.py +1 -0
- ansible_test/_internal/commands/sanity/bin_symlinks.py +1 -0
- ansible_test/_internal/commands/sanity/compile.py +1 -0
- ansible_test/_internal/commands/sanity/ignores.py +1 -0
- ansible_test/_internal/commands/sanity/import.py +1 -0
- ansible_test/_internal/commands/sanity/integration_aliases.py +12 -0
- ansible_test/_internal/commands/sanity/pep8.py +1 -0
- ansible_test/_internal/commands/sanity/pslint.py +1 -0
- ansible_test/_internal/commands/sanity/pylint.py +25 -26
- ansible_test/_internal/commands/sanity/shellcheck.py +1 -0
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -0
- ansible_test/_internal/commands/sanity/yamllint.py +1 -0
- ansible_test/_internal/commands/shell/__init__.py +44 -4
- ansible_test/_internal/commands/units/__init__.py +5 -1
- ansible_test/_internal/compat/__init__.py +1 -0
- ansible_test/_internal/compat/packaging.py +1 -0
- ansible_test/_internal/compat/yaml.py +1 -0
- ansible_test/_internal/completion.py +1 -0
- ansible_test/_internal/config.py +23 -13
- ansible_test/_internal/connections.py +1 -0
- ansible_test/_internal/constants.py +1 -0
- ansible_test/_internal/containers.py +1 -0
- ansible_test/_internal/content_config.py +1 -0
- ansible_test/_internal/core_ci.py +1 -0
- ansible_test/_internal/coverage_util.py +11 -10
- ansible_test/_internal/data.py +1 -0
- ansible_test/_internal/debugging.py +166 -0
- ansible_test/_internal/delegation.py +22 -13
- ansible_test/_internal/dev/__init__.py +1 -0
- ansible_test/_internal/dev/container_probe.py +1 -0
- ansible_test/_internal/diff.py +3 -2
- ansible_test/_internal/docker_util.py +2 -1
- ansible_test/_internal/encoding.py +1 -0
- ansible_test/_internal/executor.py +1 -0
- ansible_test/_internal/git.py +1 -0
- ansible_test/_internal/host_configs.py +1 -0
- ansible_test/_internal/host_profiles.py +260 -16
- ansible_test/_internal/http.py +1 -0
- ansible_test/_internal/init.py +1 -0
- ansible_test/_internal/inventory.py +39 -3
- ansible_test/_internal/io.py +1 -0
- ansible_test/_internal/metadata.py +95 -4
- ansible_test/_internal/payload.py +1 -0
- ansible_test/_internal/processes.py +80 -0
- ansible_test/_internal/provider/__init__.py +1 -0
- ansible_test/_internal/provider/layout/__init__.py +1 -0
- ansible_test/_internal/provider/layout/ansible.py +1 -0
- ansible_test/_internal/provider/layout/collection.py +1 -0
- ansible_test/_internal/provider/layout/unsupported.py +1 -0
- ansible_test/_internal/provider/source/__init__.py +1 -0
- ansible_test/_internal/provider/source/git.py +1 -0
- ansible_test/_internal/provider/source/installed.py +1 -0
- ansible_test/_internal/provider/source/unsupported.py +1 -0
- ansible_test/_internal/provider/source/unversioned.py +1 -0
- ansible_test/_internal/provisioning.py +11 -4
- ansible_test/_internal/pypi_proxy.py +6 -5
- ansible_test/_internal/python_requirements.py +28 -0
- ansible_test/_internal/ssh.py +2 -5
- ansible_test/_internal/target.py +9 -0
- ansible_test/_internal/test.py +3 -2
- ansible_test/_internal/thread.py +3 -1
- ansible_test/_internal/timeout.py +2 -1
- ansible_test/_internal/util.py +41 -12
- ansible_test/_internal/util_common.py +18 -5
- ansible_test/_internal/venv.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/empty-init.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/line-endings.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-assert.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-get-exception.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-illegal-filenames.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-smart-quotes.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/replace-urlopen.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +28 -1
- ansible_test/_util/controller/sanity/code-smell/shebang.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/symlinks.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-argspec-type-path.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-compat-six.py +1 -0
- ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py +2 -1
- ansible_test/_util/controller/sanity/pep8/current-ignore.txt +4 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +8 -5
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +8 -5
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +8 -5
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +4 -5
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +8 -7
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +541 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
- ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +1 -8
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +1 -8
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +55 -28
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +12 -5
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +13 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -0
- ansible_test/_util/controller/sanity/yamllint/yamllinter.py +35 -17
- ansible_test/_util/controller/tools/collection_detail.py +1 -0
- ansible_test/_util/controller/tools/yaml_to_json.py +2 -1
- ansible_test/_util/target/injector/python.py +8 -0
- ansible_test/_util/target/pytest/plugins/ansible_forked.py +6 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +2 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -0
- ansible_test/_util/target/sanity/compile/compile.py +1 -0
- ansible_test/_util/target/sanity/import/importer.py +15 -16
- ansible_test/_util/target/setup/bootstrap.sh +9 -20
- ansible_test/_util/target/setup/probe_cgroups.py +1 -0
- ansible_test/_util/target/setup/quiet_pip.py +1 -0
- ansible_test/_util/target/setup/requirements.py +38 -36
- ansible_test/_util/target/tools/virtualenvcheck.py +2 -1
- ansible_test/_util/target/tools/yamlcheck.py +2 -1
- ansible/compat/selectors.py +0 -32
- ansible/errors/yaml_strings.py +0 -138
- ansible/executor/action_write_locks.py +0 -44
- ansible/executor/discovery/python_target.py +0 -47
- ansible/executor/powershell/module_powershell_wrapper.ps1 +0 -86
- ansible/executor/powershell/module_script_wrapper.ps1 +0 -22
- ansible/module_utils/compat/importlib.py +0 -26
- ansible/module_utils/compat/selectors.py +0 -32
- ansible/module_utils/pycompat24.py +0 -73
- ansible/parsing/yaml/constructor.py +0 -178
- ansible/template/native_helpers.py +0 -251
- ansible/template/template.py +0 -43
- ansible/template/vars.py +0 -77
- ansible/utils/native_jinja.py +0 -11
- ansible/vars/fact_cache.py +0 -71
- ansible_core-2.18.7rc1.dist-info/RECORD +0 -992
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -411
- {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/WHEEL +0 -0
- {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.18.7rc1.dist-info → ansible_core-2.19.0.dist-info}/top_level.txt +0 -0
ansible/plugins/loader.py
CHANGED
|
@@ -6,35 +6,41 @@
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import functools
|
|
9
10
|
import glob
|
|
10
11
|
import os
|
|
11
12
|
import os.path
|
|
12
13
|
import pkgutil
|
|
13
14
|
import sys
|
|
15
|
+
import types
|
|
14
16
|
import warnings
|
|
17
|
+
import typing as t
|
|
18
|
+
|
|
19
|
+
import yaml
|
|
15
20
|
|
|
16
21
|
from collections import defaultdict, namedtuple
|
|
17
22
|
from importlib import import_module
|
|
18
|
-
from
|
|
19
|
-
|
|
20
|
-
import ansible.module_utils.compat.typing as t
|
|
21
|
-
|
|
22
|
-
from .filter import AnsibleJinja2Filter
|
|
23
|
-
from .test import AnsibleJinja2Test
|
|
23
|
+
from yaml.parser import ParserError
|
|
24
24
|
|
|
25
25
|
from ansible import __version__ as ansible_version
|
|
26
|
-
from ansible import constants as C
|
|
26
|
+
from ansible import _internal, constants as C
|
|
27
27
|
from ansible.errors import AnsibleError, AnsiblePluginCircularRedirect, AnsiblePluginRemovedError, AnsibleCollectionUnsupportedVersionError
|
|
28
28
|
from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
|
|
29
|
+
from ansible.module_utils.datatag import deprecator_from_collection_name
|
|
29
30
|
from ansible.module_utils.six import string_types
|
|
30
|
-
from ansible.parsing.utils.yaml import from_yaml
|
|
31
31
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
|
32
|
-
from ansible.
|
|
32
|
+
from ansible._internal._yaml._loader import AnsibleInstrumentedLoader
|
|
33
|
+
from ansible.plugins import get_plugin_class, MODULE_CACHE, PATH_CACHE, PLUGIN_PATH_CACHE, AnsibleJinja2Plugin
|
|
33
34
|
from ansible.utils.collection_loader import AnsibleCollectionConfig, AnsibleCollectionRef
|
|
34
35
|
from ansible.utils.collection_loader._collection_finder import _AnsibleCollectionFinder, _get_collection_metadata
|
|
35
36
|
from ansible.utils.display import Display
|
|
36
37
|
from ansible.utils.plugin_docs import add_fragments
|
|
37
|
-
from ansible.
|
|
38
|
+
from ansible._internal._datatag import _tags
|
|
39
|
+
|
|
40
|
+
from . import _AnsiblePluginInfoMixin
|
|
41
|
+
from .filter import AnsibleJinja2Filter
|
|
42
|
+
from .test import AnsibleJinja2Test
|
|
43
|
+
from .._internal._plugins import _cache
|
|
38
44
|
|
|
39
45
|
# TODO: take the packaging dep, or vendor SpecifierSet?
|
|
40
46
|
|
|
@@ -47,18 +53,30 @@ except ImportError:
|
|
|
47
53
|
|
|
48
54
|
import importlib.util
|
|
49
55
|
|
|
56
|
+
if t.TYPE_CHECKING:
|
|
57
|
+
from ansible.plugins.cache import BaseCacheModule
|
|
58
|
+
|
|
50
59
|
_PLUGIN_FILTERS = defaultdict(frozenset) # type: t.DefaultDict[str, frozenset]
|
|
51
60
|
display = Display()
|
|
52
61
|
|
|
53
62
|
get_with_context_result = namedtuple('get_with_context_result', ['object', 'plugin_load_context'])
|
|
54
63
|
|
|
55
64
|
|
|
56
|
-
|
|
65
|
+
@functools.cache
|
|
66
|
+
def get_all_plugin_loaders() -> list[tuple[str, 'PluginLoader']]:
|
|
57
67
|
return [(name, obj) for (name, obj) in globals().items() if isinstance(obj, PluginLoader)]
|
|
58
68
|
|
|
59
69
|
|
|
70
|
+
@functools.cache
|
|
71
|
+
def get_plugin_loader_namespace() -> types.SimpleNamespace:
|
|
72
|
+
ns = types.SimpleNamespace()
|
|
73
|
+
for name, obj in get_all_plugin_loaders():
|
|
74
|
+
setattr(ns, name, obj)
|
|
75
|
+
return ns
|
|
76
|
+
|
|
77
|
+
|
|
60
78
|
def add_all_plugin_dirs(path):
|
|
61
|
-
|
|
79
|
+
""" add any existing plugin dirs in the path provided """
|
|
62
80
|
b_path = os.path.expanduser(to_bytes(path, errors='surrogate_or_strict'))
|
|
63
81
|
if os.path.isdir(b_path):
|
|
64
82
|
for name, obj in get_all_plugin_loaders():
|
|
@@ -117,29 +135,44 @@ class PluginPathContext(object):
|
|
|
117
135
|
|
|
118
136
|
|
|
119
137
|
class PluginLoadContext(object):
|
|
120
|
-
def __init__(self):
|
|
121
|
-
self.original_name = None
|
|
122
|
-
self.redirect_list = []
|
|
123
|
-
self.
|
|
124
|
-
|
|
125
|
-
self.
|
|
126
|
-
|
|
127
|
-
self.
|
|
128
|
-
|
|
129
|
-
self.
|
|
130
|
-
self.
|
|
131
|
-
self.
|
|
132
|
-
self.
|
|
133
|
-
self.
|
|
134
|
-
|
|
135
|
-
self.
|
|
136
|
-
|
|
137
|
-
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
|
+
"""
|
|
138
171
|
|
|
139
172
|
@property
|
|
140
|
-
def resolved_fqcn(self):
|
|
173
|
+
def resolved_fqcn(self) -> str | None:
|
|
141
174
|
if not self.resolved:
|
|
142
|
-
return
|
|
175
|
+
return None
|
|
143
176
|
|
|
144
177
|
if not self._resolved_fqcn:
|
|
145
178
|
final_plugin = self.redirect_list[-1]
|
|
@@ -151,7 +184,7 @@ class PluginLoadContext(object):
|
|
|
151
184
|
|
|
152
185
|
return self._resolved_fqcn
|
|
153
186
|
|
|
154
|
-
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:
|
|
155
188
|
if not deprecation:
|
|
156
189
|
return self
|
|
157
190
|
|
|
@@ -165,7 +198,12 @@ class PluginLoadContext(object):
|
|
|
165
198
|
removal_version = None
|
|
166
199
|
warning_text = '{0} has been deprecated.{1}{2}'.format(name, ' ' if warning_text else '', warning_text)
|
|
167
200
|
|
|
168
|
-
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=deprecator_from_collection_name(collection_name),
|
|
206
|
+
)
|
|
169
207
|
|
|
170
208
|
self.deprecated = True
|
|
171
209
|
if removal_date:
|
|
@@ -175,38 +213,97 @@ class PluginLoadContext(object):
|
|
|
175
213
|
self.deprecation_warnings.append(warning_text)
|
|
176
214
|
return self
|
|
177
215
|
|
|
178
|
-
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."""
|
|
179
218
|
self.pending_redirect = None
|
|
180
219
|
self.plugin_resolved_name = resolved_name
|
|
181
220
|
self.plugin_resolved_path = resolved_path
|
|
182
221
|
self.plugin_resolved_collection = resolved_collection
|
|
183
222
|
self.exit_reason = exit_reason
|
|
223
|
+
self._python_module_name = resolved_name
|
|
184
224
|
self.resolved = True
|
|
185
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
|
+
|
|
186
253
|
return self
|
|
187
254
|
|
|
188
|
-
def redirect(self, redirect_name):
|
|
255
|
+
def redirect(self, redirect_name: str) -> t.Self:
|
|
189
256
|
self.pending_redirect = redirect_name
|
|
190
257
|
self.exit_reason = 'pending redirect resolution from {0} to {1}'.format(self.original_name, redirect_name)
|
|
191
258
|
self.resolved = False
|
|
259
|
+
|
|
192
260
|
return self
|
|
193
261
|
|
|
194
|
-
def nope(self, exit_reason):
|
|
262
|
+
def nope(self, exit_reason: str) -> t.Self:
|
|
195
263
|
self.pending_redirect = None
|
|
196
264
|
self.exit_reason = exit_reason
|
|
197
265
|
self.resolved = False
|
|
266
|
+
|
|
198
267
|
return self
|
|
199
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
|
+
|
|
200
289
|
|
|
201
290
|
class PluginLoader:
|
|
202
|
-
|
|
291
|
+
"""
|
|
203
292
|
PluginLoader loads plugins from the configured plugin directories.
|
|
204
293
|
|
|
205
294
|
It searches for plugins by iterating through the combined list of play basedirs, configured
|
|
206
295
|
paths, and the python path. The first match is used.
|
|
207
|
-
|
|
296
|
+
"""
|
|
208
297
|
|
|
209
|
-
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:
|
|
210
307
|
aliases = {} if aliases is None else aliases
|
|
211
308
|
|
|
212
309
|
self.class_name = class_name
|
|
@@ -232,15 +329,15 @@ class PluginLoader:
|
|
|
232
329
|
PLUGIN_PATH_CACHE[class_name] = defaultdict(dict)
|
|
233
330
|
|
|
234
331
|
# hold dirs added at runtime outside of config
|
|
235
|
-
self._extra_dirs = []
|
|
332
|
+
self._extra_dirs: list[str] = []
|
|
236
333
|
|
|
237
334
|
# caches
|
|
238
335
|
self._module_cache = MODULE_CACHE[class_name]
|
|
239
336
|
self._paths = PATH_CACHE[class_name]
|
|
240
337
|
self._plugin_path_cache = PLUGIN_PATH_CACHE[class_name]
|
|
241
|
-
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
|
|
242
339
|
|
|
243
|
-
self._searched_paths = set()
|
|
340
|
+
self._searched_paths: set[str] = set()
|
|
244
341
|
|
|
245
342
|
@property
|
|
246
343
|
def type(self):
|
|
@@ -267,9 +364,9 @@ class PluginLoader:
|
|
|
267
364
|
self._searched_paths = set()
|
|
268
365
|
|
|
269
366
|
def __setstate__(self, data):
|
|
270
|
-
|
|
367
|
+
"""
|
|
271
368
|
Deserializer.
|
|
272
|
-
|
|
369
|
+
"""
|
|
273
370
|
|
|
274
371
|
class_name = data.get('class_name')
|
|
275
372
|
package = data.get('package')
|
|
@@ -286,9 +383,9 @@ class PluginLoader:
|
|
|
286
383
|
self._searched_paths = data.get('_searched_paths', set())
|
|
287
384
|
|
|
288
385
|
def __getstate__(self):
|
|
289
|
-
|
|
386
|
+
"""
|
|
290
387
|
Serializer.
|
|
291
|
-
|
|
388
|
+
"""
|
|
292
389
|
|
|
293
390
|
return dict(
|
|
294
391
|
class_name=self.class_name,
|
|
@@ -304,7 +401,7 @@ class PluginLoader:
|
|
|
304
401
|
)
|
|
305
402
|
|
|
306
403
|
def format_paths(self, paths):
|
|
307
|
-
|
|
404
|
+
""" Returns a string suitable for printing of the search path """
|
|
308
405
|
|
|
309
406
|
# Uses a list to get the order right
|
|
310
407
|
ret = []
|
|
@@ -326,7 +423,7 @@ class PluginLoader:
|
|
|
326
423
|
return results
|
|
327
424
|
|
|
328
425
|
def _get_package_paths(self, subdirs=True):
|
|
329
|
-
|
|
426
|
+
""" Gets the path of a Python package """
|
|
330
427
|
|
|
331
428
|
if not self.package:
|
|
332
429
|
return []
|
|
@@ -341,7 +438,7 @@ class PluginLoader:
|
|
|
341
438
|
return [self.package_path]
|
|
342
439
|
|
|
343
440
|
def _get_paths_with_context(self, subdirs=True):
|
|
344
|
-
|
|
441
|
+
""" Return a list of PluginPathContext objects to search for plugins in """
|
|
345
442
|
|
|
346
443
|
# FIXME: This is potentially buggy if subdirs is sometimes True and sometimes False.
|
|
347
444
|
# In current usage, everything calls this with subdirs=True except for module_utils_loader and ansible-doc
|
|
@@ -394,13 +491,13 @@ class PluginLoader:
|
|
|
394
491
|
return ret
|
|
395
492
|
|
|
396
493
|
def _get_paths(self, subdirs=True):
|
|
397
|
-
|
|
494
|
+
""" Return a list of paths to search for plugins in """
|
|
398
495
|
|
|
399
496
|
paths_with_context = self._get_paths_with_context(subdirs=subdirs)
|
|
400
497
|
return [path_with_context.path for path_with_context in paths_with_context]
|
|
401
498
|
|
|
402
499
|
def _load_config_defs(self, name, module, path):
|
|
403
|
-
|
|
500
|
+
""" Reads plugin docs to find configuration setting definitions, to push to config manager for later use """
|
|
404
501
|
|
|
405
502
|
# plugins w/o class name don't support config
|
|
406
503
|
if self.class_name:
|
|
@@ -408,12 +505,16 @@ class PluginLoader:
|
|
|
408
505
|
|
|
409
506
|
# if type name != 'module_doc_fragment':
|
|
410
507
|
if type_name in C.CONFIGURABLE_PLUGINS and not C.config.has_configuration_definition(type_name, name):
|
|
411
|
-
|
|
508
|
+
# trust-tagged source propagates to loaded values; expressions and templates in config require trust
|
|
509
|
+
documentation_source = _tags.TrustedAsTemplate().tag(getattr(module, 'DOCUMENTATION', ''))
|
|
510
|
+
try:
|
|
511
|
+
dstring = yaml.load(_tags.Origin(path=path).tag(documentation_source), Loader=AnsibleLoader)
|
|
512
|
+
except ParserError as e:
|
|
513
|
+
raise AnsibleError(f"plugin {name} has malformed documentation!") from e
|
|
412
514
|
|
|
413
515
|
# TODO: allow configurable plugins to use sidecar
|
|
414
516
|
# if not dstring:
|
|
415
517
|
# filename, cn = find_plugin_docfile( name, type_name, self, [os.path.dirname(path)], C.YAML_DOC_EXTENSIONS)
|
|
416
|
-
# # TODO: dstring = AnsibleLoader(, file_name=path).get_single_data()
|
|
417
518
|
|
|
418
519
|
if dstring:
|
|
419
520
|
add_fragments(dstring, path, fragment_loader=fragment_loader, is_module=(type_name == 'module'))
|
|
@@ -423,7 +524,7 @@ class PluginLoader:
|
|
|
423
524
|
display.debug('Loaded config def from plugin (%s/%s)' % (type_name, name))
|
|
424
525
|
|
|
425
526
|
def add_directory(self, directory, with_subdir=False):
|
|
426
|
-
|
|
527
|
+
""" Adds an additional directory to the search path """
|
|
427
528
|
|
|
428
529
|
directory = os.path.realpath(directory)
|
|
429
530
|
|
|
@@ -467,7 +568,13 @@ class PluginLoader:
|
|
|
467
568
|
entry = collection_meta.get('plugin_routing', {}).get(plugin_type, {}).get(subdir_qualified_resource, None)
|
|
468
569
|
return entry
|
|
469
570
|
|
|
470
|
-
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:
|
|
471
578
|
"""Search builtin paths to find a plugin. No external paths are searched,
|
|
472
579
|
meaning plugins inside roles inside collections will be ignored.
|
|
473
580
|
"""
|
|
@@ -497,15 +604,20 @@ class PluginLoader:
|
|
|
497
604
|
removal_date = tombstone.get('removal_date')
|
|
498
605
|
removal_version = tombstone.get('removal_version')
|
|
499
606
|
warning_text = tombstone.get('warning_text') or ''
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
607
|
+
warning_plugin_type = "module" if self.type == "modules" else f'{self.type} plugin'
|
|
608
|
+
warning_text = f'The {fq_name!r} {warning_plugin_type} has been removed.{" " if warning_text else ""}{warning_text}'
|
|
609
|
+
removed_msg = display._get_deprecation_message_with_plugin_info(
|
|
610
|
+
msg=warning_text,
|
|
611
|
+
version=removal_version,
|
|
612
|
+
date=removal_date,
|
|
613
|
+
removed=True,
|
|
614
|
+
deprecator=deprecator_from_collection_name(acr.collection),
|
|
615
|
+
)
|
|
616
|
+
plugin_load_context.date = removal_date
|
|
617
|
+
plugin_load_context.version = removal_version
|
|
506
618
|
plugin_load_context.resolved = True
|
|
507
619
|
plugin_load_context.exit_reason = removed_msg
|
|
508
|
-
raise AnsiblePluginRemovedError(removed_msg, plugin_load_context=plugin_load_context)
|
|
620
|
+
raise AnsiblePluginRemovedError(message=removed_msg, plugin_load_context=plugin_load_context)
|
|
509
621
|
|
|
510
622
|
redirect = routing_metadata.get('redirect', None)
|
|
511
623
|
|
|
@@ -562,7 +674,7 @@ class PluginLoader:
|
|
|
562
674
|
# look for any matching extension in the package location (sans filter)
|
|
563
675
|
found_files = [f
|
|
564
676
|
for f in glob.iglob(os.path.join(pkg_path, n_resource) + '.*')
|
|
565
|
-
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)]
|
|
566
678
|
|
|
567
679
|
if not found_files:
|
|
568
680
|
return plugin_load_context.nope('failed fuzzy extension match for {0} in {1}'.format(full_name, acr.collection))
|
|
@@ -577,16 +689,23 @@ class PluginLoader:
|
|
|
577
689
|
'found fuzzy extension match for {0} in {1}'.format(full_name, acr.collection), action_plugin)
|
|
578
690
|
|
|
579
691
|
def find_plugin(self, name, mod_type='', ignore_deprecated=False, check_aliases=False, collection_list=None):
|
|
580
|
-
|
|
692
|
+
""" Find a plugin named name """
|
|
581
693
|
result = self.find_plugin_with_context(name, mod_type, ignore_deprecated, check_aliases, collection_list)
|
|
582
694
|
if result.resolved and result.plugin_resolved_path:
|
|
583
695
|
return result.plugin_resolved_path
|
|
584
696
|
|
|
585
697
|
return None
|
|
586
698
|
|
|
587
|
-
def find_plugin_with_context(
|
|
588
|
-
|
|
589
|
-
|
|
699
|
+
def find_plugin_with_context(
|
|
700
|
+
self,
|
|
701
|
+
name: str,
|
|
702
|
+
mod_type: str = '',
|
|
703
|
+
ignore_deprecated: bool = False,
|
|
704
|
+
check_aliases: bool = False,
|
|
705
|
+
collection_list: list[str] | None = None,
|
|
706
|
+
) -> PluginLoadContext:
|
|
707
|
+
""" Find a plugin named name, returning contextual info about the load, recursively resolving redirection """
|
|
708
|
+
plugin_load_context = PluginLoadContext(self.type, self.package)
|
|
590
709
|
plugin_load_context.original_name = name
|
|
591
710
|
while True:
|
|
592
711
|
result = self._resolve_plugin_step(name, mod_type, ignore_deprecated, check_aliases, collection_list, plugin_load_context=plugin_load_context)
|
|
@@ -599,11 +718,8 @@ class PluginLoader:
|
|
|
599
718
|
else:
|
|
600
719
|
break
|
|
601
720
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
display.warning("errors were encountered during the plugin load for {0}:\n{1}".format(name, plugin_load_context.error_list))
|
|
605
|
-
|
|
606
|
-
# 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)
|
|
607
723
|
|
|
608
724
|
# FIXME: store structured deprecation data in PluginLoadContext and use display.deprecate
|
|
609
725
|
# if plugin_load_context.deprecated and C.config.get_config_value('DEPRECATION_WARNINGS'):
|
|
@@ -613,9 +729,15 @@ class PluginLoader:
|
|
|
613
729
|
|
|
614
730
|
return plugin_load_context
|
|
615
731
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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:
|
|
619
741
|
if not plugin_load_context:
|
|
620
742
|
raise ValueError('A PluginLoadContext is required')
|
|
621
743
|
|
|
@@ -670,11 +792,14 @@ class PluginLoader:
|
|
|
670
792
|
except (AnsiblePluginRemovedError, AnsiblePluginCircularRedirect, AnsibleCollectionUnsupportedVersionError):
|
|
671
793
|
# these are generally fatal, let them fly
|
|
672
794
|
raise
|
|
673
|
-
except ImportError as ie:
|
|
674
|
-
plugin_load_context.import_error_list.append(ie)
|
|
675
795
|
except Exception as ex:
|
|
676
|
-
|
|
677
|
-
|
|
796
|
+
plugin_load_context.raw_error_list.append(ex)
|
|
797
|
+
|
|
798
|
+
# DTFIX-FUTURE: 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))
|
|
678
803
|
|
|
679
804
|
if plugin_load_context.error_list:
|
|
680
805
|
display.debug(msg='plugin lookup for {0} failed; errors: {1}'.format(name, '; '.join(plugin_load_context.error_list)))
|
|
@@ -700,13 +825,7 @@ class PluginLoader:
|
|
|
700
825
|
# requested mod_type
|
|
701
826
|
pull_cache = self._plugin_path_cache[suffix]
|
|
702
827
|
try:
|
|
703
|
-
|
|
704
|
-
plugin_load_context.plugin_resolved_path = path_with_context.path
|
|
705
|
-
plugin_load_context.plugin_resolved_name = name
|
|
706
|
-
plugin_load_context.plugin_resolved_collection = 'ansible.builtin' if path_with_context.internal else ''
|
|
707
|
-
plugin_load_context._resolved_fqcn = ('ansible.builtin.' + name if path_with_context.internal else name)
|
|
708
|
-
plugin_load_context.resolved = True
|
|
709
|
-
return plugin_load_context
|
|
828
|
+
return plugin_load_context.resolve_legacy(name=name, pull_cache=pull_cache)
|
|
710
829
|
except KeyError:
|
|
711
830
|
# Cache miss. Now let's find the plugin
|
|
712
831
|
pass
|
|
@@ -759,13 +878,7 @@ class PluginLoader:
|
|
|
759
878
|
|
|
760
879
|
self._searched_paths.add(path)
|
|
761
880
|
try:
|
|
762
|
-
|
|
763
|
-
plugin_load_context.plugin_resolved_path = path_with_context.path
|
|
764
|
-
plugin_load_context.plugin_resolved_name = name
|
|
765
|
-
plugin_load_context.plugin_resolved_collection = 'ansible.builtin' if path_with_context.internal else ''
|
|
766
|
-
plugin_load_context._resolved_fqcn = 'ansible.builtin.' + name if path_with_context.internal else name
|
|
767
|
-
plugin_load_context.resolved = True
|
|
768
|
-
return plugin_load_context
|
|
881
|
+
return plugin_load_context.resolve_legacy(name=name, pull_cache=pull_cache)
|
|
769
882
|
except KeyError:
|
|
770
883
|
# Didn't find the plugin in this directory. Load modules from the next one
|
|
771
884
|
pass
|
|
@@ -773,18 +886,18 @@ class PluginLoader:
|
|
|
773
886
|
# if nothing is found, try finding alias/deprecated
|
|
774
887
|
if not name.startswith('_'):
|
|
775
888
|
alias_name = '_' + name
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
+
|
|
788
901
|
return plugin_load_context
|
|
789
902
|
|
|
790
903
|
# last ditch, if it's something that can be redirected, look for a builtin redirect before giving up
|
|
@@ -794,8 +907,8 @@ class PluginLoader:
|
|
|
794
907
|
|
|
795
908
|
return plugin_load_context.nope('{0} is not eligible for last-chance resolution'.format(name))
|
|
796
909
|
|
|
797
|
-
def has_plugin(self, name, collection_list=None):
|
|
798
|
-
|
|
910
|
+
def has_plugin(self, name: str, collection_list: list[str] | None = None) -> bool:
|
|
911
|
+
""" Checks if a plugin named name exists """
|
|
799
912
|
|
|
800
913
|
try:
|
|
801
914
|
return self.find_plugin(name, collection_list=collection_list) is not None
|
|
@@ -805,45 +918,49 @@ class PluginLoader:
|
|
|
805
918
|
# log and continue, likely an innocuous type/package loading failure in collections import
|
|
806
919
|
display.debug('has_plugin error: {0}'.format(to_text(ex)))
|
|
807
920
|
|
|
808
|
-
|
|
921
|
+
return False
|
|
809
922
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
# avoid collisions across plugins
|
|
813
|
-
if name.startswith('ansible_collections.'):
|
|
814
|
-
full_name = name
|
|
815
|
-
else:
|
|
816
|
-
full_name = '.'.join([self.package, name])
|
|
923
|
+
__contains__ = has_plugin
|
|
817
924
|
|
|
818
|
-
|
|
925
|
+
def _load_module_source(self, *, python_module_name: str, path: str) -> types.ModuleType:
|
|
926
|
+
if python_module_name in sys.modules:
|
|
819
927
|
# Avoids double loading, See https://github.com/ansible/ansible/issues/13110
|
|
820
|
-
return sys.modules[
|
|
928
|
+
return sys.modules[python_module_name]
|
|
821
929
|
|
|
822
930
|
with warnings.catch_warnings():
|
|
823
931
|
# FIXME: this still has issues if the module was previously imported but not "cached",
|
|
824
932
|
# we should bypass this entire codepath for things that are directly importable
|
|
825
933
|
warnings.simplefilter("ignore", RuntimeWarning)
|
|
826
|
-
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))
|
|
827
935
|
module = importlib.util.module_from_spec(spec)
|
|
828
936
|
|
|
829
937
|
# mimic import machinery; make the module-being-loaded available in sys.modules during import
|
|
830
938
|
# and remove if there's a failure...
|
|
831
|
-
sys.modules[
|
|
939
|
+
sys.modules[python_module_name] = module
|
|
832
940
|
|
|
833
941
|
try:
|
|
834
942
|
spec.loader.exec_module(module)
|
|
835
943
|
except Exception:
|
|
836
|
-
del sys.modules[
|
|
944
|
+
del sys.modules[python_module_name]
|
|
837
945
|
raise
|
|
838
946
|
|
|
839
947
|
return module
|
|
840
948
|
|
|
841
|
-
def _update_object(
|
|
949
|
+
def _update_object(
|
|
950
|
+
self,
|
|
951
|
+
*,
|
|
952
|
+
obj: _AnsiblePluginInfoMixin,
|
|
953
|
+
name: str,
|
|
954
|
+
path: str,
|
|
955
|
+
redirected_names: list[str] | None = None,
|
|
956
|
+
resolved: str | None = None,
|
|
957
|
+
) -> None:
|
|
958
|
+
# DTFIX-FUTURE: clean this up- standardize types, document, split/remove redundant bits
|
|
842
959
|
|
|
843
960
|
# set extra info on the module, in case we want it later
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
961
|
+
obj._original_path = path
|
|
962
|
+
obj._load_name = name
|
|
963
|
+
obj._redirected_names = redirected_names or []
|
|
847
964
|
|
|
848
965
|
names = []
|
|
849
966
|
if resolved:
|
|
@@ -854,25 +971,26 @@ class PluginLoader:
|
|
|
854
971
|
if not names:
|
|
855
972
|
raise AnsibleError(f"Missing FQCN for plugin source {name}")
|
|
856
973
|
|
|
857
|
-
|
|
858
|
-
|
|
974
|
+
obj.ansible_aliases = names
|
|
975
|
+
obj.ansible_name = names[0]
|
|
859
976
|
|
|
860
977
|
def get(self, name, *args, **kwargs):
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
978
|
+
ctx = self.get_with_context(name, *args, **kwargs)
|
|
979
|
+
is_core_plugin = ctx.plugin_load_context.plugin_resolved_collection == 'ansible.builtin'
|
|
980
|
+
if self.class_name == 'StrategyModule' and not is_core_plugin:
|
|
981
|
+
display.deprecated( # pylint: disable=ansible-deprecated-no-version
|
|
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.',
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
return ctx.object
|
|
988
|
+
|
|
989
|
+
def get_with_context(self, name, *args, **kwargs) -> get_with_context_result:
|
|
990
|
+
""" instantiates a plugin of the given name using arguments """
|
|
991
|
+
|
|
992
|
+
if not name:
|
|
993
|
+
raise ValueError('A non-empty plugin name is required.')
|
|
876
994
|
|
|
877
995
|
found_in_cache = True
|
|
878
996
|
class_only = kwargs.pop('class_only', False)
|
|
@@ -893,8 +1011,6 @@ class PluginLoader:
|
|
|
893
1011
|
return get_with_context_result(None, plugin_load_context)
|
|
894
1012
|
|
|
895
1013
|
fq_name = plugin_load_context.resolved_fqcn
|
|
896
|
-
if '.' not in fq_name and plugin_load_context.plugin_resolved_collection:
|
|
897
|
-
fq_name = '.'.join((plugin_load_context.plugin_resolved_collection, fq_name))
|
|
898
1014
|
resolved_type_name = plugin_load_context.plugin_resolved_name
|
|
899
1015
|
path = plugin_load_context.plugin_resolved_path
|
|
900
1016
|
if (cached_result := (self._plugin_instance_cache or {}).get(fq_name)) and cached_result[1].resolved:
|
|
@@ -904,7 +1020,7 @@ class PluginLoader:
|
|
|
904
1020
|
redirected_names = plugin_load_context.redirect_list or []
|
|
905
1021
|
|
|
906
1022
|
if path not in self._module_cache:
|
|
907
|
-
self._module_cache[path] = self._load_module_source(
|
|
1023
|
+
self._module_cache[path] = self._load_module_source(python_module_name=plugin_load_context._python_module_name, path=path)
|
|
908
1024
|
found_in_cache = False
|
|
909
1025
|
|
|
910
1026
|
self._load_config_defs(resolved_type_name, self._module_cache[path], path)
|
|
@@ -921,6 +1037,7 @@ class PluginLoader:
|
|
|
921
1037
|
except AttributeError:
|
|
922
1038
|
return get_with_context_result(None, plugin_load_context)
|
|
923
1039
|
if not issubclass(obj, plugin_class):
|
|
1040
|
+
display.warning(f"Ignoring {self.type} plugin {resolved_type_name!r} due to missing base class {self.base_class!r}.")
|
|
924
1041
|
return get_with_context_result(None, plugin_load_context)
|
|
925
1042
|
|
|
926
1043
|
# FIXME: update this to use the load context
|
|
@@ -931,7 +1048,7 @@ class PluginLoader:
|
|
|
931
1048
|
# A plugin may need to use its _load_name in __init__ (for example, to set
|
|
932
1049
|
# or get options from config), so update the object before using the constructor
|
|
933
1050
|
instance = object.__new__(obj)
|
|
934
|
-
self._update_object(instance, resolved_type_name, path, redirected_names, fq_name)
|
|
1051
|
+
self._update_object(obj=instance, name=resolved_type_name, path=path, redirected_names=redirected_names, resolved=fq_name)
|
|
935
1052
|
obj.__init__(instance, *args, **kwargs) # pylint: disable=unnecessary-dunder-call
|
|
936
1053
|
obj = instance
|
|
937
1054
|
except TypeError as e:
|
|
@@ -941,16 +1058,16 @@ class PluginLoader:
|
|
|
941
1058
|
return get_with_context_result(None, plugin_load_context)
|
|
942
1059
|
raise
|
|
943
1060
|
|
|
944
|
-
self._update_object(obj, resolved_type_name, path, redirected_names, fq_name)
|
|
1061
|
+
self._update_object(obj=obj, name=resolved_type_name, path=path, redirected_names=redirected_names, resolved=fq_name)
|
|
945
1062
|
if self._plugin_instance_cache is not None and getattr(obj, 'is_stateless', False):
|
|
946
1063
|
self._plugin_instance_cache[fq_name] = (obj, plugin_load_context)
|
|
947
1064
|
elif self._plugin_instance_cache is not None:
|
|
948
1065
|
# The cache doubles as the load order, so record the FQCN even if the plugin hasn't set is_stateless = True
|
|
949
|
-
self._plugin_instance_cache[fq_name] = (None, PluginLoadContext())
|
|
1066
|
+
self._plugin_instance_cache[fq_name] = (None, PluginLoadContext(self.type, self.package))
|
|
950
1067
|
return get_with_context_result(obj, plugin_load_context)
|
|
951
1068
|
|
|
952
1069
|
def _display_plugin_load(self, class_name, name, searched_paths, path, found_in_cache=None, class_only=None):
|
|
953
|
-
|
|
1070
|
+
""" formats data to display debug info for plugin loading, also avoids processing unless really needed """
|
|
954
1071
|
if C.DEFAULT_DEBUG:
|
|
955
1072
|
msg = 'Loading %s \'%s\' from %s' % (class_name, os.path.basename(name), path)
|
|
956
1073
|
|
|
@@ -963,7 +1080,7 @@ class PluginLoader:
|
|
|
963
1080
|
display.debug(msg)
|
|
964
1081
|
|
|
965
1082
|
def all(self, *args, **kwargs):
|
|
966
|
-
|
|
1083
|
+
"""
|
|
967
1084
|
Iterate through all plugins of this type, in configured paths (no collections)
|
|
968
1085
|
|
|
969
1086
|
A plugin loader is initialized with a specific type. This function is an iterator returning
|
|
@@ -984,7 +1101,7 @@ class PluginLoader:
|
|
|
984
1101
|
want to manage their own deduplication of the plugins.
|
|
985
1102
|
:*args: Any extra arguments are passed to each plugin when it is instantiated.
|
|
986
1103
|
:**kwargs: Any extra keyword arguments are passed to each plugin when it is instantiated.
|
|
987
|
-
|
|
1104
|
+
"""
|
|
988
1105
|
# TODO: Change the signature of this method to:
|
|
989
1106
|
# def all(return_type='instance', args=None, kwargs=None):
|
|
990
1107
|
# if args is None: args = []
|
|
@@ -1021,10 +1138,15 @@ class PluginLoader:
|
|
|
1021
1138
|
basename = os.path.basename(name)
|
|
1022
1139
|
is_j2 = isinstance(self, Jinja2Loader)
|
|
1023
1140
|
|
|
1141
|
+
if path in legacy_excluding_builtin:
|
|
1142
|
+
fqcn = basename
|
|
1143
|
+
else:
|
|
1144
|
+
fqcn = f"ansible.builtin.{basename}"
|
|
1145
|
+
|
|
1024
1146
|
if is_j2:
|
|
1025
1147
|
ref_name = path
|
|
1026
1148
|
else:
|
|
1027
|
-
ref_name =
|
|
1149
|
+
ref_name = fqcn
|
|
1028
1150
|
|
|
1029
1151
|
if not is_j2 and basename in _PLUGIN_FILTERS[self.package]:
|
|
1030
1152
|
# j2 plugins get processed in own class, here they would just be container files
|
|
@@ -1047,26 +1169,18 @@ class PluginLoader:
|
|
|
1047
1169
|
yield path
|
|
1048
1170
|
continue
|
|
1049
1171
|
|
|
1050
|
-
if path in legacy_excluding_builtin:
|
|
1051
|
-
fqcn = basename
|
|
1052
|
-
else:
|
|
1053
|
-
fqcn = f"ansible.builtin.{basename}"
|
|
1054
|
-
|
|
1055
1172
|
if (cached_result := (self._plugin_instance_cache or {}).get(fqcn)) and cached_result[1].resolved:
|
|
1056
1173
|
# Here just in case, but we don't call all() multiple times for vars plugins, so this should not be used.
|
|
1057
1174
|
yield cached_result[0]
|
|
1058
1175
|
continue
|
|
1059
1176
|
|
|
1060
1177
|
if path not in self._module_cache:
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
full_name = '{0}_{1}'.format(abs(hash(path)), basename)
|
|
1065
|
-
else:
|
|
1066
|
-
full_name = basename
|
|
1178
|
+
path_context = PluginPathContext(path, path not in legacy_excluding_builtin)
|
|
1179
|
+
load_context = PluginLoadContext(self.type, self.package)
|
|
1180
|
+
load_context.resolve_legacy(basename, {basename: path_context})
|
|
1067
1181
|
|
|
1068
1182
|
try:
|
|
1069
|
-
module = self._load_module_source(
|
|
1183
|
+
module = self._load_module_source(python_module_name=load_context._python_module_name, path=path)
|
|
1070
1184
|
except Exception as e:
|
|
1071
1185
|
display.warning("Skipping plugin (%s), cannot load: %s" % (path, to_text(e)))
|
|
1072
1186
|
continue
|
|
@@ -1104,7 +1218,7 @@ class PluginLoader:
|
|
|
1104
1218
|
except TypeError as e:
|
|
1105
1219
|
display.warning("Skipping plugin (%s) as it seems to be incomplete: %s" % (path, to_text(e)))
|
|
1106
1220
|
|
|
1107
|
-
self._update_object(obj, basename, path, resolved=fqcn)
|
|
1221
|
+
self._update_object(obj=obj, name=basename, path=path, resolved=fqcn)
|
|
1108
1222
|
|
|
1109
1223
|
if self._plugin_instance_cache is not None:
|
|
1110
1224
|
needs_enabled = False
|
|
@@ -1117,6 +1231,21 @@ class PluginLoader:
|
|
|
1117
1231
|
yield obj
|
|
1118
1232
|
|
|
1119
1233
|
|
|
1234
|
+
class _CacheLoader(PluginLoader):
|
|
1235
|
+
"""Customized loader for cache plugins that wraps the requested plugin with an interposer that schema-qualifies keys and JSON encodes the values."""
|
|
1236
|
+
|
|
1237
|
+
def get(self, name: str, *args, **kwargs) -> BaseCacheModule:
|
|
1238
|
+
plugin = super().get(name, *args, **kwargs)
|
|
1239
|
+
|
|
1240
|
+
if not plugin:
|
|
1241
|
+
raise AnsibleError(f'Unable to load the cache plugin {name!r}.')
|
|
1242
|
+
|
|
1243
|
+
if plugin._persistent:
|
|
1244
|
+
return _cache.PluginInterposer(plugin)
|
|
1245
|
+
|
|
1246
|
+
return plugin
|
|
1247
|
+
|
|
1248
|
+
|
|
1120
1249
|
class Jinja2Loader(PluginLoader):
|
|
1121
1250
|
"""
|
|
1122
1251
|
PluginLoader optimized for Jinja2 plugins
|
|
@@ -1125,10 +1254,12 @@ class Jinja2Loader(PluginLoader):
|
|
|
1125
1254
|
We need to do a few things differently in the base class because of file == plugin
|
|
1126
1255
|
assumptions and dedupe logic.
|
|
1127
1256
|
"""
|
|
1128
|
-
|
|
1257
|
+
|
|
1258
|
+
def __init__(self, class_name, package, config, subdir, plugin_wrapper_type, aliases=None, required_base_class=None) -> None:
|
|
1129
1259
|
super(Jinja2Loader, self).__init__(class_name, package, config, subdir, aliases=aliases, required_base_class=required_base_class)
|
|
1130
1260
|
self._plugin_wrapper_type = plugin_wrapper_type
|
|
1131
|
-
self.
|
|
1261
|
+
self._plugin_type_friendly_name = 'filter' if plugin_wrapper_type is AnsibleJinja2Filter else 'test'
|
|
1262
|
+
self._cached_non_collection_wrappers: dict[str, AnsibleJinja2Filter | AnsibleJinja2Test | _DeferredPluginLoadFailure] = {}
|
|
1132
1263
|
|
|
1133
1264
|
def _clear_caches(self):
|
|
1134
1265
|
super(Jinja2Loader, self)._clear_caches()
|
|
@@ -1141,6 +1272,36 @@ class Jinja2Loader(PluginLoader):
|
|
|
1141
1272
|
def method_map_name(self):
|
|
1142
1273
|
return get_plugin_class(self.class_name) + 's'
|
|
1143
1274
|
|
|
1275
|
+
def _wrap_func(self, name: str, resolved: str, func: t.Callable) -> AnsibleJinja2Test | AnsibleJinja2Filter:
|
|
1276
|
+
"""Wrap a Jinja builtin function in a `AnsibleJinja2Plugin` instance."""
|
|
1277
|
+
try:
|
|
1278
|
+
path = sys.modules[func.__module__].__file__
|
|
1279
|
+
except AttributeError:
|
|
1280
|
+
path = None
|
|
1281
|
+
|
|
1282
|
+
wrapper = self._plugin_wrapper_type(func)
|
|
1283
|
+
|
|
1284
|
+
self._update_object(obj=wrapper, name=name, path=path, resolved=resolved)
|
|
1285
|
+
|
|
1286
|
+
return wrapper
|
|
1287
|
+
|
|
1288
|
+
def _wrap_funcs(self, plugins: dict[str, t.Callable], aliases: dict[str, str]) -> dict[str, AnsibleJinja2Test | AnsibleJinja2Filter]:
|
|
1289
|
+
"""Map a dictionary of Jinja builtin functions to one containing `AnsibleJinja2Plugin` instances."""
|
|
1290
|
+
wrappers: dict[str, AnsibleJinja2Test | AnsibleJinja2Filter] = {}
|
|
1291
|
+
|
|
1292
|
+
for load_name, func in plugins.items():
|
|
1293
|
+
name = aliases.get(load_name, load_name)
|
|
1294
|
+
resolved = f'ansible.builtin.{name}'
|
|
1295
|
+
|
|
1296
|
+
wrappers[load_name] = self._wrap_func(load_name, resolved, func)
|
|
1297
|
+
|
|
1298
|
+
if resolved not in wrappers:
|
|
1299
|
+
# When the resolved name hasn't been cached, do so.
|
|
1300
|
+
# Functions that have aliases will appear more than once, and we don't need to overwrite them.
|
|
1301
|
+
wrappers[resolved] = self._wrap_func(resolved, resolved, func)
|
|
1302
|
+
|
|
1303
|
+
return wrappers
|
|
1304
|
+
|
|
1144
1305
|
def get_contained_plugins(self, collection, plugin_path, name):
|
|
1145
1306
|
|
|
1146
1307
|
plugins = []
|
|
@@ -1149,7 +1310,7 @@ class Jinja2Loader(PluginLoader):
|
|
|
1149
1310
|
try:
|
|
1150
1311
|
# use 'parent' loader class to find files, but cannot return this as it can contain multiple plugins per file
|
|
1151
1312
|
if plugin_path not in self._module_cache:
|
|
1152
|
-
self._module_cache[plugin_path] = self._load_module_source(full_name, plugin_path)
|
|
1313
|
+
self._module_cache[plugin_path] = self._load_module_source(python_module_name=full_name, path=plugin_path)
|
|
1153
1314
|
module = self._module_cache[plugin_path]
|
|
1154
1315
|
obj = getattr(module, self.class_name)
|
|
1155
1316
|
except Exception as e:
|
|
@@ -1172,19 +1333,21 @@ class Jinja2Loader(PluginLoader):
|
|
|
1172
1333
|
plugin = self._plugin_wrapper_type(func)
|
|
1173
1334
|
if plugin in plugins:
|
|
1174
1335
|
continue
|
|
1175
|
-
self._update_object(plugin, full, plugin_path, resolved=fq_name)
|
|
1336
|
+
self._update_object(obj=plugin, name=full, path=plugin_path, resolved=fq_name)
|
|
1176
1337
|
plugins.append(plugin)
|
|
1177
1338
|
|
|
1178
1339
|
return plugins
|
|
1179
1340
|
|
|
1180
1341
|
# FUTURE: now that the resulting plugins are closer, refactor base class method with some extra
|
|
1181
1342
|
# hooks so we can avoid all the duplicated plugin metadata logic, and also cache the collection results properly here
|
|
1182
|
-
def get_with_context(self, name, *args, **kwargs):
|
|
1343
|
+
def get_with_context(self, name: str, *args, **kwargs) -> get_with_context_result:
|
|
1183
1344
|
# pop N/A kwargs to avoid passthrough to parent methods
|
|
1184
1345
|
kwargs.pop('class_only', False)
|
|
1185
1346
|
kwargs.pop('collection_list', None)
|
|
1186
1347
|
|
|
1187
|
-
|
|
1348
|
+
requested_name = name
|
|
1349
|
+
|
|
1350
|
+
context = PluginLoadContext(self.type, self.package)
|
|
1188
1351
|
|
|
1189
1352
|
# avoid collection path for legacy
|
|
1190
1353
|
name = name.removeprefix('ansible.legacy.')
|
|
@@ -1193,11 +1356,11 @@ class Jinja2Loader(PluginLoader):
|
|
|
1193
1356
|
|
|
1194
1357
|
# check for stuff loaded via legacy/builtin paths first
|
|
1195
1358
|
if known_plugin := self._cached_non_collection_wrappers.get(name):
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
context.
|
|
1200
|
-
|
|
1359
|
+
if isinstance(known_plugin, _DeferredPluginLoadFailure):
|
|
1360
|
+
raise known_plugin.ex
|
|
1361
|
+
|
|
1362
|
+
context.resolve_legacy_jinja_plugin(name, known_plugin)
|
|
1363
|
+
|
|
1201
1364
|
return get_with_context_result(known_plugin, context)
|
|
1202
1365
|
|
|
1203
1366
|
plugin = None
|
|
@@ -1219,7 +1382,7 @@ class Jinja2Loader(PluginLoader):
|
|
|
1219
1382
|
ts = _get_collection_metadata(acr.collection)
|
|
1220
1383
|
except ValueError as e:
|
|
1221
1384
|
# no collection
|
|
1222
|
-
raise KeyError('Invalid plugin FQCN ({0}): {1}'.format(key, to_native(e)))
|
|
1385
|
+
raise KeyError('Invalid plugin FQCN ({0}): {1}'.format(key, to_native(e))) from e
|
|
1223
1386
|
|
|
1224
1387
|
# TODO: implement cycle detection (unified across collection redir as well)
|
|
1225
1388
|
routing_entry = ts.get('plugin_routing', {}).get(self.type, {}).get(leaf_key, {})
|
|
@@ -1233,7 +1396,12 @@ class Jinja2Loader(PluginLoader):
|
|
|
1233
1396
|
|
|
1234
1397
|
warning_text = f'{self.type.title()} "{key}" has been deprecated.{" " if warning_text else ""}{warning_text}'
|
|
1235
1398
|
|
|
1236
|
-
display.deprecated(
|
|
1399
|
+
display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
|
1400
|
+
msg=warning_text,
|
|
1401
|
+
version=removal_version,
|
|
1402
|
+
date=removal_date,
|
|
1403
|
+
deprecator=deprecator_from_collection_name(acr.collection),
|
|
1404
|
+
)
|
|
1237
1405
|
|
|
1238
1406
|
# check removal
|
|
1239
1407
|
tombstone_entry = routing_entry.get('tombstone')
|
|
@@ -1241,11 +1409,15 @@ class Jinja2Loader(PluginLoader):
|
|
|
1241
1409
|
warning_text = tombstone_entry.get('warning_text') or ''
|
|
1242
1410
|
removal_date = tombstone_entry.get('removal_date')
|
|
1243
1411
|
removal_version = tombstone_entry.get('removal_version')
|
|
1412
|
+
warning_text = f'The {key!r} {self.type} plugin has been removed.{" " if warning_text else ""}{warning_text}'
|
|
1244
1413
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1414
|
+
exc_msg = display._get_deprecation_message_with_plugin_info(
|
|
1415
|
+
msg=warning_text,
|
|
1416
|
+
version=removal_version,
|
|
1417
|
+
date=removal_date,
|
|
1418
|
+
removed=True,
|
|
1419
|
+
deprecator=deprecator_from_collection_name(acr.collection),
|
|
1420
|
+
)
|
|
1249
1421
|
|
|
1250
1422
|
raise AnsiblePluginRemovedError(exc_msg)
|
|
1251
1423
|
|
|
@@ -1297,20 +1469,16 @@ class Jinja2Loader(PluginLoader):
|
|
|
1297
1469
|
plugin = self._plugin_wrapper_type(func)
|
|
1298
1470
|
if plugin:
|
|
1299
1471
|
context = plugin_impl.plugin_load_context
|
|
1300
|
-
self._update_object(plugin,
|
|
1472
|
+
self._update_object(obj=plugin, name=requested_name, path=plugin_impl.object._original_path, resolved=fq_name)
|
|
1301
1473
|
# context will have filename, which for tests/filters might not be correct
|
|
1302
1474
|
context._resolved_fqcn = plugin.ansible_name
|
|
1303
1475
|
# FIXME: once we start caching these results, we'll be missing functions that would have loaded later
|
|
1304
1476
|
break # go to next file as it can override if dupe (dont break both loops)
|
|
1305
1477
|
|
|
1306
|
-
except AnsiblePluginRemovedError as apre:
|
|
1307
|
-
raise AnsibleError(to_native(apre), 0, orig_exc=apre)
|
|
1308
1478
|
except (AnsibleError, KeyError):
|
|
1309
1479
|
raise
|
|
1310
1480
|
except Exception as ex:
|
|
1311
|
-
|
|
1312
|
-
display.vvv('Unexpected error during Jinja2 plugin loading: {0}'.format(format_exc()))
|
|
1313
|
-
raise AnsibleError(to_native(ex), 0, orig_exc=ex)
|
|
1481
|
+
raise AnsibleError('An unexpected error occurred during Jinja2 plugin loading.') from ex
|
|
1314
1482
|
|
|
1315
1483
|
return get_with_context_result(plugin, context)
|
|
1316
1484
|
|
|
@@ -1324,10 +1492,13 @@ class Jinja2Loader(PluginLoader):
|
|
|
1324
1492
|
raise AnsibleError('Do not set both path_only and class_only when calling PluginLoader.all()')
|
|
1325
1493
|
|
|
1326
1494
|
self._ensure_non_collection_wrappers(*args, **kwargs)
|
|
1495
|
+
|
|
1496
|
+
plugins = [plugin for plugin in self._cached_non_collection_wrappers.values() if not isinstance(plugin, _DeferredPluginLoadFailure)]
|
|
1497
|
+
|
|
1327
1498
|
if path_only:
|
|
1328
|
-
yield from (w._original_path for w in
|
|
1499
|
+
yield from (w._original_path for w in plugins)
|
|
1329
1500
|
else:
|
|
1330
|
-
yield from (w for w in
|
|
1501
|
+
yield from (w for w in plugins)
|
|
1331
1502
|
|
|
1332
1503
|
def _ensure_non_collection_wrappers(self, *args, **kwargs):
|
|
1333
1504
|
if self._cached_non_collection_wrappers:
|
|
@@ -1354,21 +1525,26 @@ class Jinja2Loader(PluginLoader):
|
|
|
1354
1525
|
display.debug("%s skipped due to a defined plugin filter" % plugin_name)
|
|
1355
1526
|
continue
|
|
1356
1527
|
|
|
1357
|
-
# the plugin class returned by the loader may host multiple Jinja plugins, but we wrap each plugin in
|
|
1358
|
-
# its own surrogate wrapper instance here to ease the bookkeeping...
|
|
1359
|
-
wrapper = self._plugin_wrapper_type(plugins[plugin_name])
|
|
1360
1528
|
fqcn = plugin_name
|
|
1361
1529
|
collection = '.'.join(p_map.ansible_name.split('.')[:2]) if p_map.ansible_name.count('.') >= 2 else ''
|
|
1362
1530
|
if not plugin_name.startswith(collection):
|
|
1363
1531
|
fqcn = f"{collection}.{plugin_name}"
|
|
1364
1532
|
|
|
1365
|
-
self._update_object(wrapper, plugin_name, p_map._original_path, resolved=fqcn)
|
|
1366
|
-
|
|
1367
1533
|
target_names = {plugin_name, fqcn}
|
|
1534
|
+
|
|
1368
1535
|
if is_builtin:
|
|
1369
1536
|
target_names.add(f'ansible.builtin.{plugin_name}')
|
|
1370
1537
|
|
|
1371
1538
|
for target_name in target_names:
|
|
1539
|
+
# the plugin class returned by the loader may host multiple Jinja plugins, but we wrap each plugin in
|
|
1540
|
+
# its own surrogate wrapper instance here to ease the bookkeeping...
|
|
1541
|
+
try:
|
|
1542
|
+
wrapper = self._plugin_wrapper_type(plugins[plugin_name])
|
|
1543
|
+
except Exception as ex:
|
|
1544
|
+
wrapper = _DeferredPluginLoadFailure(ex)
|
|
1545
|
+
|
|
1546
|
+
self._update_object(obj=wrapper, name=target_name, path=p_map._original_path, resolved=fqcn)
|
|
1547
|
+
|
|
1372
1548
|
if existing_plugin := self._cached_non_collection_wrappers.get(target_name):
|
|
1373
1549
|
display.debug(f'Jinja plugin {target_name} from {p_map._original_path} skipped; '
|
|
1374
1550
|
f'shadowed by plugin from {existing_plugin._original_path})')
|
|
@@ -1377,6 +1553,13 @@ class Jinja2Loader(PluginLoader):
|
|
|
1377
1553
|
self._cached_non_collection_wrappers[target_name] = wrapper
|
|
1378
1554
|
|
|
1379
1555
|
|
|
1556
|
+
class _DeferredPluginLoadFailure:
|
|
1557
|
+
"""Represents a plugin which failed to load. For internal use only within plugin loader."""
|
|
1558
|
+
|
|
1559
|
+
def __init__(self, ex: Exception) -> None:
|
|
1560
|
+
self.ex = ex
|
|
1561
|
+
|
|
1562
|
+
|
|
1380
1563
|
def get_fqcr_and_name(resource, collection='ansible.builtin'):
|
|
1381
1564
|
if '.' not in resource:
|
|
1382
1565
|
name = resource
|
|
@@ -1400,7 +1583,7 @@ def _load_plugin_filter():
|
|
|
1400
1583
|
if os.path.exists(filter_cfg):
|
|
1401
1584
|
with open(filter_cfg, 'rb') as f:
|
|
1402
1585
|
try:
|
|
1403
|
-
filter_data =
|
|
1586
|
+
filter_data = yaml.load(f, Loader=AnsibleInstrumentedLoader)
|
|
1404
1587
|
except Exception as e:
|
|
1405
1588
|
display.warning(u'The plugin filter file, {0} was not parsable.'
|
|
1406
1589
|
u' Skipping: {1}'.format(filter_cfg, to_text(e)))
|
|
@@ -1489,7 +1672,8 @@ def _configure_collection_loader(prefix_collections_path=None):
|
|
|
1489
1672
|
if prefix_collections_path is None:
|
|
1490
1673
|
prefix_collections_path = []
|
|
1491
1674
|
|
|
1492
|
-
|
|
1675
|
+
# insert the internal ansible._protomatter collection up front
|
|
1676
|
+
paths = [os.path.dirname(_internal.__file__)] + list(prefix_collections_path) + C.COLLECTIONS_PATHS
|
|
1493
1677
|
finder = _AnsibleCollectionFinder(paths, C.COLLECTIONS_SCAN_SYS_PATH)
|
|
1494
1678
|
finder._install()
|
|
1495
1679
|
|
|
@@ -1529,7 +1713,7 @@ action_loader = PluginLoader(
|
|
|
1529
1713
|
required_base_class='ActionBase',
|
|
1530
1714
|
)
|
|
1531
1715
|
|
|
1532
|
-
cache_loader =
|
|
1716
|
+
cache_loader = _CacheLoader(
|
|
1533
1717
|
'CacheModule',
|
|
1534
1718
|
'ansible.plugins.cache',
|
|
1535
1719
|
C.DEFAULT_CACHE_PLUGIN_PATH,
|
|
@@ -1541,6 +1725,7 @@ callback_loader = PluginLoader(
|
|
|
1541
1725
|
'ansible.plugins.callback',
|
|
1542
1726
|
C.DEFAULT_CALLBACK_PLUGIN_PATH,
|
|
1543
1727
|
'callback_plugins',
|
|
1728
|
+
required_base_class='CallbackBase',
|
|
1544
1729
|
)
|
|
1545
1730
|
|
|
1546
1731
|
connection_loader = PluginLoader(
|