ansible-core 2.18.5rc1__py3-none-any.whl → 2.19.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible/_internal/__init__.py +53 -0
- ansible/_internal/_ansiballz.py +265 -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/_captured.py +128 -0
- ansible/_internal/_errors/_handler.py +91 -0
- ansible/_internal/_errors/_utils.py +310 -0
- ansible/_internal/_json/__init__.py +203 -0
- ansible/_internal/_json/_legacy_encoder.py +34 -0
- ansible/_internal/_json/_profiles/__init__.py +0 -0
- ansible/_internal/_json/_profiles/_cache_persistence.py +55 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
- ansible/_internal/_json/_profiles/_legacy.py +197 -0
- ansible/_internal/_locking.py +21 -0
- ansible/_internal/_plugins/__init__.py +0 -0
- ansible/_internal/_plugins/_cache.py +57 -0
- ansible/_internal/_task.py +78 -0
- ansible/_internal/_templating/__init__.py +10 -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 +588 -0
- ansible/_internal/_templating/_errors.py +28 -0
- ansible/_internal/_templating/_jinja_bits.py +1066 -0
- ansible/_internal/_templating/_jinja_common.py +332 -0
- ansible/_internal/_templating/_jinja_patches.py +44 -0
- ansible/_internal/_templating/_jinja_plugins.py +345 -0
- ansible/_internal/_templating/_lazy_containers.py +633 -0
- ansible/_internal/_templating/_marker_behaviors.py +103 -0
- ansible/_internal/_templating/_transform.py +63 -0
- ansible/_internal/_templating/_utils.py +107 -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 +62 -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 +18 -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 +159 -89
- ansible/cli/_ssh_askpass.py +47 -0
- ansible/cli/adhoc.py +14 -7
- ansible/cli/arguments/option_helpers.py +154 -7
- ansible/cli/config.py +43 -68
- ansible/cli/console.py +10 -8
- ansible/cli/doc.py +62 -53
- ansible/cli/galaxy.py +27 -20
- ansible/cli/inventory.py +28 -26
- ansible/cli/playbook.py +4 -12
- ansible/cli/pull.py +51 -11
- ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
- ansible/cli/vault.py +12 -11
- ansible/compat/__init__.py +2 -2
- ansible/config/base.yml +166 -112
- ansible/config/manager.py +52 -49
- ansible/constants.py +3 -4
- ansible/errors/__init__.py +277 -235
- ansible/executor/interpreter_discovery.py +28 -149
- ansible/executor/module_common.py +426 -493
- 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 +202 -151
- ansible/executor/powershell/become_wrapper.ps1 +89 -144
- ansible/executor/powershell/bootstrap_wrapper.ps1 +24 -9
- ansible/executor/powershell/coverage_wrapper.ps1 +82 -135
- ansible/executor/powershell/exec_wrapper.ps1 +462 -196
- ansible/executor/powershell/module_manifest.py +417 -265
- ansible/executor/powershell/module_wrapper.ps1 +169 -186
- 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 +161 -96
- ansible/executor/stats.py +5 -5
- ansible/executor/task_executor.py +268 -258
- ansible/executor/task_queue_manager.py +124 -90
- ansible/executor/task_result.py +183 -78
- ansible/galaxy/__init__.py +2 -2
- ansible/galaxy/api.py +22 -18
- ansible/galaxy/collection/__init__.py +1 -1
- ansible/galaxy/collection/concrete_artifact_manager.py +8 -11
- ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
- ansible/galaxy/dependency_resolution/providers.py +1 -1
- ansible/galaxy/dependency_resolution/reporters.py +81 -0
- ansible/galaxy/role.py +4 -8
- ansible/galaxy/token.py +28 -21
- ansible/inventory/data.py +47 -57
- ansible/inventory/group.py +44 -72
- ansible/inventory/helpers.py +9 -0
- ansible/inventory/host.py +32 -54
- ansible/inventory/manager.py +78 -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.py +133 -0
- ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
- ansible/module_utils/_internal/_dataclass_annotation_patch.py +64 -0
- ansible/module_utils/_internal/_dataclass_validation.py +217 -0
- ansible/module_utils/_internal/_datatag/__init__.py +928 -0
- ansible/module_utils/_internal/_datatag/_tags.py +38 -0
- ansible/module_utils/_internal/_debugging.py +31 -0
- ansible/module_utils/_internal/_errors.py +30 -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 +410 -0
- ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +31 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +35 -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 +50 -0
- ansible/module_utils/_internal/_patches/__init__.py +66 -0
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +55 -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_exec_context.py +49 -0
- ansible/module_utils/_internal/_testing.py +0 -0
- ansible/module_utils/_internal/_traceback.py +89 -0
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/api.py +1 -2
- ansible/module_utils/basic.py +152 -120
- ansible/module_utils/common/_utils.py +24 -28
- ansible/module_utils/common/collections.py +1 -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/messages.py +108 -0
- ansible/module_utils/common/parameters.py +27 -24
- ansible/module_utils/common/process.py +2 -2
- ansible/module_utils/common/respawn.py +41 -19
- 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 +86 -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 +2 -2
- ansible/module_utils/csharp/Ansible.Basic.cs +14 -11
- ansible/module_utils/csharp/Ansible.Become.cs +1 -0
- ansible/module_utils/csharp/Ansible._Async.cs +517 -0
- ansible/module_utils/datatag.py +46 -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 +1 -1
- 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 +4 -4
- 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 +1 -2
- ansible/module_utils/facts/other/ohai.py +2 -3
- ansible/module_utils/facts/system/apparmor.py +1 -2
- ansible/module_utils/facts/system/caps.py +1 -1
- 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 +9 -8
- 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 +1 -2
- 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/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 +1 -1
- ansible/module_utils/service.py +18 -21
- ansible/module_utils/splitter.py +7 -7
- ansible/module_utils/testing.py +31 -0
- ansible/module_utils/urls.py +60 -31
- ansible/modules/add_host.py +4 -4
- ansible/modules/apt.py +60 -46
- ansible/modules/apt_key.py +19 -12
- ansible/modules/apt_repository.py +19 -16
- ansible/modules/assemble.py +6 -6
- ansible/modules/assert.py +4 -4
- ansible/modules/async_status.py +10 -12
- ansible/modules/async_wrapper.py +8 -3
- ansible/modules/blockinfile.py +6 -7
- ansible/modules/command.py +10 -17
- ansible/modules/copy.py +57 -144
- ansible/modules/cron.py +20 -15
- 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 +8 -10
- ansible/modules/fail.py +4 -4
- ansible/modules/fetch.py +4 -4
- ansible/modules/file.py +174 -133
- ansible/modules/find.py +19 -17
- ansible/modules/gather_facts.py +3 -3
- ansible/modules/get_url.py +59 -53
- ansible/modules/getent.py +7 -9
- ansible/modules/git.py +28 -25
- ansible/modules/group.py +6 -6
- ansible/modules/group_by.py +4 -4
- ansible/modules/hostname.py +13 -29
- 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 +10 -10
- ansible/modules/lineinfile.py +5 -5
- ansible/modules/meta.py +4 -4
- ansible/modules/mount_facts.py +2 -2
- ansible/modules/package.py +4 -4
- ansible/modules/package_facts.py +22 -10
- ansible/modules/pause.py +6 -6
- ansible/modules/ping.py +6 -6
- ansible/modules/pip.py +10 -11
- ansible/modules/raw.py +4 -4
- ansible/modules/reboot.py +6 -6
- ansible/modules/replace.py +9 -13
- ansible/modules/rpm_key.py +7 -8
- ansible/modules/script.py +4 -4
- ansible/modules/service.py +7 -8
- 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 +6 -6
- ansible/modules/stat.py +9 -23
- ansible/modules/subversion.py +15 -15
- ansible/modules/systemd.py +6 -6
- ansible/modules/systemd_service.py +6 -6
- ansible/modules/sysvinit.py +6 -6
- ansible/modules/tempfile.py +5 -6
- ansible/modules/template.py +6 -6
- ansible/modules/unarchive.py +32 -11
- ansible/modules/uri.py +33 -26
- ansible/modules/user.py +53 -34
- ansible/modules/validate_argument_spec.py +10 -7
- ansible/modules/wait_for.py +32 -27
- ansible/modules/wait_for_connection.py +6 -6
- ansible/modules/yum_repository.py +6 -6
- ansible/parsing/ajson.py +14 -32
- ansible/parsing/dataloader.py +99 -54
- ansible/parsing/mod_args.py +28 -44
- 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 +319 -87
- 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 +182 -132
- 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 +31 -27
- ansible/playbook/loop_control.py +2 -2
- ansible/playbook/play.py +85 -44
- ansible/playbook/play_context.py +12 -17
- ansible/playbook/playbook_include.py +14 -15
- ansible/playbook/role/__init__.py +24 -26
- 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 +13 -8
- ansible/playbook/task.py +188 -118
- ansible/playbook/task_include.py +5 -5
- ansible/plugins/__init__.py +68 -21
- ansible/plugins/action/__init__.py +209 -176
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +1 -1
- ansible/plugins/action/assert.py +54 -66
- ansible/plugins/action/copy.py +7 -11
- 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 +4 -5
- ansible/plugins/action/gather_facts.py +7 -6
- ansible/plugins/action/group_by.py +1 -1
- ansible/plugins/action/include_vars.py +10 -11
- ansible/plugins/action/package.py +3 -6
- ansible/plugins/action/pause.py +2 -2
- ansible/plugins/action/script.py +15 -8
- ansible/plugins/action/service.py +6 -11
- ansible/plugins/action/set_fact.py +3 -12
- ansible/plugins/action/set_stats.py +3 -8
- ansible/plugins/action/template.py +35 -59
- ansible/plugins/action/unarchive.py +1 -1
- 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 +35 -44
- 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 +284 -179
- ansible/plugins/callback/default.py +99 -92
- ansible/plugins/callback/junit.py +44 -39
- ansible/plugins/callback/minimal.py +28 -25
- ansible/plugins/callback/oneline.py +28 -21
- ansible/plugins/callback/tree.py +16 -11
- ansible/plugins/connection/__init__.py +47 -34
- ansible/plugins/connection/local.py +150 -54
- ansible/plugins/connection/paramiko_ssh.py +21 -18
- ansible/plugins/connection/psrp.py +76 -165
- ansible/plugins/connection/ssh.py +301 -78
- ansible/plugins/connection/winrm.py +58 -140
- 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 +6 -2
- 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 +225 -108
- ansible/plugins/filter/encryption.py +32 -32
- 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/regex_search.yml +1 -4
- ansible/plugins/filter/split.yml +1 -1
- ansible/plugins/filter/to_nice_yaml.yml +0 -4
- 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 +97 -77
- 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 +189 -119
- ansible/plugins/inventory/toml.py +16 -126
- ansible/plugins/inventory/yaml.py +10 -8
- ansible/plugins/list.py +3 -3
- ansible/plugins/loader.py +197 -82
- ansible/plugins/lookup/__init__.py +21 -4
- ansible/plugins/lookup/config.py +21 -35
- ansible/plugins/lookup/csvfile.py +3 -2
- 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 +86 -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 +42 -36
- ansible/plugins/lookup/together.py +0 -12
- ansible/plugins/lookup/unvault.py +1 -5
- ansible/plugins/lookup/url.py +2 -8
- ansible/plugins/lookup/vars.py +16 -24
- ansible/plugins/shell/__init__.py +2 -2
- ansible/plugins/shell/cmd.py +2 -2
- ansible/plugins/shell/powershell.py +39 -22
- ansible/plugins/shell/sh.py +3 -2
- ansible/plugins/strategy/__init__.py +159 -184
- 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 +55 -21
- ansible/plugins/test/files.py +1 -1
- ansible/plugins/test/mathstuff.py +3 -3
- ansible/plugins/test/uri.py +3 -3
- ansible/plugins/vars/host_group_vars.py +7 -14
- ansible/release.py +2 -2
- ansible/template/__init__.py +370 -944
- ansible/utils/__init__.py +0 -18
- ansible/utils/_ssh_agent.py +657 -0
- ansible/utils/collection_loader/__init__.py +52 -5
- ansible/utils/collection_loader/_collection_config.py +5 -6
- ansible/utils/collection_loader/_collection_finder.py +79 -93
- ansible/utils/collection_loader/_collection_meta.py +13 -8
- ansible/utils/display.py +433 -63
- ansible/utils/encrypt.py +27 -19
- ansible/utils/fqcn.py +2 -2
- ansible/utils/hashing.py +2 -2
- ansible/utils/helpers.py +2 -2
- ansible/utils/listify.py +8 -8
- ansible/utils/lock.py +2 -2
- ansible/utils/path.py +4 -4
- ansible/utils/plugin_docs.py +14 -13
- ansible/utils/sentinel.py +4 -62
- ansible/utils/singleton.py +2 -0
- ansible/utils/ssh_functions.py +1 -1
- ansible/utils/unsafe_proxy.py +23 -332
- ansible/utils/vars.py +51 -8
- ansible/utils/version.py +2 -2
- ansible/vars/clean.py +5 -5
- ansible/vars/hostvars.py +60 -90
- ansible/vars/manager.py +206 -282
- ansible/vars/reserved.py +8 -9
- ansible_core-2.19.0b2.dist-info/BSD-3-Clause.txt +28 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/METADATA +5 -4
- ansible_core-2.19.0b2.dist-info/RECORD +1072 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/WHEEL +1 -1
- 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 +1 -1
- ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- 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 +1 -0
- ansible_test/_internal/ansible_util.py +2 -0
- 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 -0
- 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 +1 -0
- 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 +2 -1
- 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 +4 -0
- 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 +2 -1
- 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 +1 -0
- 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 +16 -1
- 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 +1 -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 +24 -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 +1 -0
- ansible_test/_internal/commands/units/__init__.py +1 -0
- 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 +2 -0
- 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/delegation.py +1 -0
- 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 +1 -0
- ansible_test/_internal/http.py +1 -0
- ansible_test/_internal/init.py +1 -0
- ansible_test/_internal/inventory.py +35 -3
- ansible_test/_internal/io.py +1 -0
- ansible_test/_internal/metadata.py +1 -0
- ansible_test/_internal/payload.py +1 -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 +1 -0
- ansible_test/_internal/pypi_proxy.py +6 -5
- ansible_test/_internal/python_requirements.py +1 -0
- ansible_test/_internal/ssh.py +1 -0
- ansible_test/_internal/target.py +1 -0
- ansible_test/_internal/test.py +3 -2
- ansible_test/_internal/thread.py +1 -0
- ansible_test/_internal/timeout.py +1 -0
- ansible_test/_internal/util.py +1 -0
- ansible_test/_internal/util_common.py +5 -2
- 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 +7 -5
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +7 -5
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +7 -5
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +3 -5
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +7 -7
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -13
- 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/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 +35 -27
- 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.5rc1.dist-info/RECORD +0 -992
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/Apache-License.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/COPYING +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/MIT-license.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/PSF-license.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/simplified_bsd.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import contextlib as _contextlib
|
4
|
+
import dataclasses
|
5
|
+
import typing as t
|
6
|
+
|
7
|
+
from ansible.module_utils._internal._datatag import AnsibleSingletonTagBase, _tag_dataclass_kwargs
|
8
|
+
from ansible.module_utils._internal._datatag._tags import Deprecated
|
9
|
+
from ansible._internal._datatag._tags import Origin
|
10
|
+
from ansible.utils.display import Display
|
11
|
+
|
12
|
+
from ._access import NotifiableAccessContextBase
|
13
|
+
from ._utils import TemplateContext
|
14
|
+
|
15
|
+
|
16
|
+
display = Display()
|
17
|
+
|
18
|
+
|
19
|
+
@dataclasses.dataclass(**_tag_dataclass_kwargs)
|
20
|
+
class _JinjaConstTemplate(AnsibleSingletonTagBase):
|
21
|
+
# deprecated: description='embedded Jinja constant string template support' core_version='2.23'
|
22
|
+
pass
|
23
|
+
|
24
|
+
|
25
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
26
|
+
class _TrippedDeprecationInfo:
|
27
|
+
template: str
|
28
|
+
deprecated: Deprecated
|
29
|
+
|
30
|
+
|
31
|
+
class DeprecatedAccessAuditContext(NotifiableAccessContextBase):
|
32
|
+
"""When active, captures metadata about managed accesses to `Deprecated` tagged objects."""
|
33
|
+
|
34
|
+
_type_interest = frozenset([Deprecated])
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def when(cls, condition: bool, /) -> t.Self | _contextlib.nullcontext:
|
38
|
+
"""Returns a new instance if `condition` is True (usually `TemplateContext.is_top_level`), otherwise a `nullcontext` instance."""
|
39
|
+
if condition:
|
40
|
+
return cls()
|
41
|
+
|
42
|
+
return _contextlib.nullcontext()
|
43
|
+
|
44
|
+
def __init__(self) -> None:
|
45
|
+
self._tripped_deprecation_info: dict[int, _TrippedDeprecationInfo] = {}
|
46
|
+
|
47
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
48
|
+
result = super().__exit__(exc_type, exc_val, exc_tb)
|
49
|
+
|
50
|
+
for item in self._tripped_deprecation_info.values():
|
51
|
+
if Origin.is_tagged_on(item.template):
|
52
|
+
msg = item.deprecated.msg
|
53
|
+
else:
|
54
|
+
# without an origin, we need to include what context we do have (the template)
|
55
|
+
msg = f'While processing {item.template!r}: {item.deprecated.msg}'
|
56
|
+
|
57
|
+
display._deprecated_with_plugin_info(
|
58
|
+
msg=msg,
|
59
|
+
help_text=item.deprecated.help_text,
|
60
|
+
version=item.deprecated.removal_version,
|
61
|
+
date=item.deprecated.removal_date,
|
62
|
+
obj=item.template,
|
63
|
+
plugin=item.deprecated.plugin,
|
64
|
+
)
|
65
|
+
|
66
|
+
return result
|
67
|
+
|
68
|
+
def _notify(self, o: t.Any) -> None:
|
69
|
+
deprecated = Deprecated.get_required_tag(o)
|
70
|
+
deprecated_key = id(deprecated)
|
71
|
+
|
72
|
+
if deprecated_key in self._tripped_deprecation_info:
|
73
|
+
return # record only the first access for each deprecated tag in a given context
|
74
|
+
|
75
|
+
template_ctx = TemplateContext.current(optional=True)
|
76
|
+
template = template_ctx.template_value if template_ctx else None
|
77
|
+
|
78
|
+
# when the current template input is a container, provide a descriptive string with origin propagated (if possible)
|
79
|
+
if not isinstance(template, str):
|
80
|
+
# DTFIX-FUTURE: ascend the template stack to try and find the nearest string source template
|
81
|
+
origin = Origin.get_tag(template)
|
82
|
+
|
83
|
+
# DTFIX-RELEASE: this should probably use a synthesized description value on the tag
|
84
|
+
# it is reachable from the data_tagging_controller test: ../playbook_output_validator/filter.py actual_stdout.txt actual_stderr.txt
|
85
|
+
# -[DEPRECATION WARNING]: `something_old` is deprecated, don't use it! This feature will be removed in version 1.2.3.
|
86
|
+
# +[DEPRECATION WARNING]: While processing '<<container>>': `something_old` is deprecated, don't use it! This feature will be removed in ...
|
87
|
+
template = '<<container>>'
|
88
|
+
|
89
|
+
if origin:
|
90
|
+
origin.tag(template)
|
91
|
+
|
92
|
+
self._tripped_deprecation_info[deprecated_key] = _TrippedDeprecationInfo(
|
93
|
+
template=template,
|
94
|
+
deprecated=deprecated,
|
95
|
+
)
|
@@ -0,0 +1,588 @@
|
|
1
|
+
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
2
|
+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
|
4
|
+
from __future__ import annotations
|
5
|
+
|
6
|
+
import copy
|
7
|
+
import dataclasses
|
8
|
+
import enum
|
9
|
+
import textwrap
|
10
|
+
import typing as t
|
11
|
+
import collections.abc as c
|
12
|
+
import re
|
13
|
+
|
14
|
+
from collections import ChainMap
|
15
|
+
|
16
|
+
from ansible.errors import (
|
17
|
+
AnsibleError,
|
18
|
+
AnsibleValueOmittedError,
|
19
|
+
AnsibleUndefinedVariable,
|
20
|
+
AnsibleTemplateSyntaxError,
|
21
|
+
AnsibleBrokenConditionalError,
|
22
|
+
AnsibleTemplateTransformLimitError,
|
23
|
+
TemplateTrustCheckFailedError,
|
24
|
+
)
|
25
|
+
|
26
|
+
from ansible.module_utils._internal._datatag import AnsibleTaggedObject, NotTaggableError, AnsibleTagHelper
|
27
|
+
from ansible._internal._errors._handler import Skippable
|
28
|
+
from ansible._internal._datatag._tags import Origin, TrustedAsTemplate
|
29
|
+
from ansible.utils.display import Display
|
30
|
+
from ansible.utils.vars import validate_variable_name
|
31
|
+
from ansible.parsing.dataloader import DataLoader
|
32
|
+
|
33
|
+
from ._datatag import DeprecatedAccessAuditContext
|
34
|
+
from ._jinja_bits import (
|
35
|
+
AnsibleTemplate,
|
36
|
+
_TemplateCompileContext,
|
37
|
+
TemplateOverrides,
|
38
|
+
AnsibleEnvironment,
|
39
|
+
defer_template_error,
|
40
|
+
create_template_error,
|
41
|
+
is_possibly_template,
|
42
|
+
is_possibly_all_template,
|
43
|
+
AnsibleTemplateExpression,
|
44
|
+
_finalize_template_result,
|
45
|
+
FinalizeMode,
|
46
|
+
)
|
47
|
+
from ._jinja_common import _TemplateConfig, MarkerError, ExceptionMarker
|
48
|
+
from ._lazy_containers import _AnsibleLazyTemplateMixin
|
49
|
+
from ._marker_behaviors import MarkerBehavior, FAIL_ON_UNDEFINED
|
50
|
+
from ._transform import _type_transform_mapping
|
51
|
+
from ._utils import Omit, TemplateContext, IGNORE_SCALAR_VAR_TYPES, LazyOptions
|
52
|
+
from ...module_utils.datatag import native_type_name
|
53
|
+
|
54
|
+
_display = Display()
|
55
|
+
|
56
|
+
|
57
|
+
_shared_empty_unmask_type_names: frozenset[str] = frozenset()
|
58
|
+
|
59
|
+
TRANSFORM_CHAIN_LIMIT: int = 10
|
60
|
+
"""Arbitrary limit for chained transforms to prevent cycles; an exception will be raised if exceeded."""
|
61
|
+
|
62
|
+
|
63
|
+
class TemplateMode(enum.Enum):
|
64
|
+
# DTFIX-FUTURE: this enum ideally wouldn't exist - revisit/rename before making public
|
65
|
+
DEFAULT = enum.auto()
|
66
|
+
STOP_ON_TEMPLATE = enum.auto()
|
67
|
+
STOP_ON_CONTAINER = enum.auto()
|
68
|
+
ALWAYS_FINALIZE = enum.auto()
|
69
|
+
|
70
|
+
|
71
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
|
72
|
+
class TemplateOptions:
|
73
|
+
DEFAULT: t.ClassVar[t.Self]
|
74
|
+
|
75
|
+
value_for_omit: object = Omit
|
76
|
+
escape_backslashes: bool = True
|
77
|
+
preserve_trailing_newlines: bool = True
|
78
|
+
# DTFIX-RELEASE: these aren't really overrides anymore, rename the dataclass and this field
|
79
|
+
# also mention in docstring this has no effect unless used to template a string
|
80
|
+
overrides: TemplateOverrides = TemplateOverrides.DEFAULT
|
81
|
+
|
82
|
+
|
83
|
+
TemplateOptions.DEFAULT = TemplateOptions()
|
84
|
+
|
85
|
+
|
86
|
+
class TemplateEncountered(Exception):
|
87
|
+
pass
|
88
|
+
|
89
|
+
|
90
|
+
class TemplateEngine:
|
91
|
+
"""
|
92
|
+
The main class for templating, with the main entry-point of template().
|
93
|
+
"""
|
94
|
+
|
95
|
+
_sentinel = object()
|
96
|
+
|
97
|
+
def __init__(
|
98
|
+
self,
|
99
|
+
loader: DataLoader | None = None,
|
100
|
+
variables: dict[str, t.Any] | ChainMap[str, t.Any] | None = None,
|
101
|
+
variables_factory: t.Callable[[], dict[str, t.Any] | ChainMap[str, t.Any]] | None = None,
|
102
|
+
marker_behavior: MarkerBehavior | None = None,
|
103
|
+
):
|
104
|
+
self._loader = loader
|
105
|
+
self._variables = variables
|
106
|
+
self._variables_factory = variables_factory
|
107
|
+
self._environment: AnsibleEnvironment | None = None
|
108
|
+
|
109
|
+
# inherit marker behavior from the active template context's templar unless otherwise specified
|
110
|
+
if not marker_behavior:
|
111
|
+
if template_ctx := TemplateContext.current(optional=True):
|
112
|
+
marker_behavior = template_ctx.templar.marker_behavior
|
113
|
+
else:
|
114
|
+
marker_behavior = FAIL_ON_UNDEFINED
|
115
|
+
|
116
|
+
self._marker_behavior = marker_behavior
|
117
|
+
|
118
|
+
def copy(self) -> t.Self:
|
119
|
+
new_engine = copy.copy(self)
|
120
|
+
new_engine._environment = None
|
121
|
+
|
122
|
+
return new_engine
|
123
|
+
|
124
|
+
def extend(self, marker_behavior: MarkerBehavior | None = None) -> t.Self:
|
125
|
+
# DTFIX-RELEASE: bikeshed name, supported features
|
126
|
+
new_templar = type(self)(
|
127
|
+
loader=self._loader,
|
128
|
+
variables=self._variables,
|
129
|
+
variables_factory=self._variables_factory,
|
130
|
+
marker_behavior=marker_behavior or self._marker_behavior,
|
131
|
+
)
|
132
|
+
|
133
|
+
if self._environment:
|
134
|
+
new_templar._environment = self._environment
|
135
|
+
|
136
|
+
return new_templar
|
137
|
+
|
138
|
+
@property
|
139
|
+
def marker_behavior(self) -> MarkerBehavior:
|
140
|
+
return self._marker_behavior
|
141
|
+
|
142
|
+
@property
|
143
|
+
def basedir(self) -> str:
|
144
|
+
"""The basedir from DataLoader."""
|
145
|
+
return self._loader.get_basedir() if self._loader else '.'
|
146
|
+
|
147
|
+
@property
|
148
|
+
def environment(self) -> AnsibleEnvironment:
|
149
|
+
if not self._environment:
|
150
|
+
self._environment = AnsibleEnvironment(ansible_basedir=self.basedir)
|
151
|
+
|
152
|
+
return self._environment
|
153
|
+
|
154
|
+
def _create_overlay(self, template: str, overrides: TemplateOverrides) -> tuple[str, AnsibleEnvironment]:
|
155
|
+
try:
|
156
|
+
template, overrides = overrides._extract_template_overrides(template)
|
157
|
+
except Exception as ex:
|
158
|
+
raise AnsibleTemplateSyntaxError("Syntax error in template.", obj=template) from ex
|
159
|
+
|
160
|
+
env = self.environment
|
161
|
+
|
162
|
+
if overrides is not TemplateOverrides.DEFAULT and (overlay_kwargs := overrides.overlay_kwargs()):
|
163
|
+
env = t.cast(AnsibleEnvironment, env.overlay(**overlay_kwargs))
|
164
|
+
|
165
|
+
return template, env
|
166
|
+
|
167
|
+
@staticmethod
|
168
|
+
def _count_newlines_from_end(in_str):
|
169
|
+
"""
|
170
|
+
Counts the number of newlines at the end of a string. This is used during
|
171
|
+
the jinja2 templating to ensure the count matches the input, since some newlines
|
172
|
+
may be thrown away during the templating.
|
173
|
+
"""
|
174
|
+
|
175
|
+
i = len(in_str)
|
176
|
+
j = i - 1
|
177
|
+
|
178
|
+
try:
|
179
|
+
while in_str[j] == '\n':
|
180
|
+
j -= 1
|
181
|
+
except IndexError:
|
182
|
+
# Uncommon cases: zero length string and string containing only newlines
|
183
|
+
return i
|
184
|
+
|
185
|
+
return i - 1 - j
|
186
|
+
|
187
|
+
@property
|
188
|
+
def available_variables(self) -> dict[str, t.Any] | ChainMap[str, t.Any]:
|
189
|
+
"""Available variables this instance will use when templating."""
|
190
|
+
# DTFIX-RELEASE: ensure that we're always accessing this as a shallow container-level snapshot, and eliminate uses of anything
|
191
|
+
# that directly mutates this value. _new_context may resolve this for us?
|
192
|
+
if self._variables is None:
|
193
|
+
self._variables = self._variables_factory() if self._variables_factory else {}
|
194
|
+
|
195
|
+
return self._variables
|
196
|
+
|
197
|
+
@available_variables.setter
|
198
|
+
def available_variables(self, variables: dict[str, t.Any]) -> None:
|
199
|
+
self._variables = variables
|
200
|
+
|
201
|
+
def resolve_variable_expression(
|
202
|
+
self,
|
203
|
+
expression: str,
|
204
|
+
*,
|
205
|
+
local_variables: dict[str, t.Any] | None = None,
|
206
|
+
) -> t.Any:
|
207
|
+
"""
|
208
|
+
Resolve a potentially untrusted string variable expression consisting only of valid identifiers, integers, dots, and indexing containing these.
|
209
|
+
Optional local variables may be provided, which can only be referenced directly by the given expression.
|
210
|
+
Valid: x, x.y, x[y].z, x[1], 1, x[y.z]
|
211
|
+
Error: 'x', x['y'], q('env')
|
212
|
+
"""
|
213
|
+
components = re.split(r'[.\[\]]', expression)
|
214
|
+
|
215
|
+
try:
|
216
|
+
for component in components:
|
217
|
+
if re.fullmatch('[0-9]*', component):
|
218
|
+
continue # allow empty strings and integers
|
219
|
+
|
220
|
+
validate_variable_name(component)
|
221
|
+
except Exception as ex:
|
222
|
+
raise AnsibleError(f'Invalid variable expression: {expression}', obj=expression) from ex
|
223
|
+
|
224
|
+
return self.evaluate_expression(TrustedAsTemplate().tag(expression), local_variables=local_variables)
|
225
|
+
|
226
|
+
@staticmethod
|
227
|
+
def variable_name_as_template(name: str) -> str:
|
228
|
+
"""Return a trusted template string that will resolve the provided variable name. Raises an error if `name` is not a valid identifier."""
|
229
|
+
validate_variable_name(name)
|
230
|
+
return AnsibleTagHelper.tag('{{' + name + '}}', (AnsibleTagHelper.tags(name) | {TrustedAsTemplate()}))
|
231
|
+
|
232
|
+
def transform(self, variable: t.Any) -> t.Any:
|
233
|
+
"""Recursively apply transformations to the given value and return the result."""
|
234
|
+
return self.template(variable, mode=TemplateMode.ALWAYS_FINALIZE, lazy_options=LazyOptions.SKIP_TEMPLATES_AND_ACCESS)
|
235
|
+
|
236
|
+
def template(
|
237
|
+
self,
|
238
|
+
variable: t.Any, # DTFIX-RELEASE: once we settle the new/old API boundaries, rename this (here and in other methods)
|
239
|
+
*,
|
240
|
+
options: TemplateOptions = TemplateOptions.DEFAULT,
|
241
|
+
mode: TemplateMode = TemplateMode.DEFAULT,
|
242
|
+
lazy_options: LazyOptions = LazyOptions.DEFAULT,
|
243
|
+
) -> t.Any:
|
244
|
+
"""Templates (possibly recursively) any given data as input."""
|
245
|
+
original_variable = variable
|
246
|
+
|
247
|
+
for _attempt in range(TRANSFORM_CHAIN_LIMIT):
|
248
|
+
if variable is None or (value_type := type(variable)) in IGNORE_SCALAR_VAR_TYPES:
|
249
|
+
return variable # quickly ignore supported scalar types which are not be templated
|
250
|
+
|
251
|
+
value_is_str = isinstance(variable, str)
|
252
|
+
|
253
|
+
if template_ctx := TemplateContext.current(optional=True):
|
254
|
+
stop_on_template = template_ctx.stop_on_template
|
255
|
+
else:
|
256
|
+
stop_on_template = False
|
257
|
+
|
258
|
+
if mode is TemplateMode.STOP_ON_TEMPLATE:
|
259
|
+
stop_on_template = True
|
260
|
+
|
261
|
+
with (
|
262
|
+
TemplateContext(template_value=variable, templar=self, options=options, stop_on_template=stop_on_template) as ctx,
|
263
|
+
DeprecatedAccessAuditContext.when(ctx.is_top_level),
|
264
|
+
):
|
265
|
+
try:
|
266
|
+
if not value_is_str:
|
267
|
+
# transforms are currently limited to non-str types as an optimization
|
268
|
+
if (transform := _type_transform_mapping.get(value_type)) and value_type.__name__ not in lazy_options.unmask_type_names:
|
269
|
+
variable = transform(variable)
|
270
|
+
continue
|
271
|
+
|
272
|
+
template_result = _AnsibleLazyTemplateMixin._try_create(variable, lazy_options)
|
273
|
+
elif not lazy_options.template:
|
274
|
+
template_result = variable
|
275
|
+
elif not is_possibly_template(variable, options.overrides):
|
276
|
+
template_result = variable
|
277
|
+
elif not self._trust_check(variable, skip_handler=stop_on_template):
|
278
|
+
template_result = variable
|
279
|
+
elif stop_on_template:
|
280
|
+
raise TemplateEncountered()
|
281
|
+
else:
|
282
|
+
compiled_template = self._compile_template(variable, options)
|
283
|
+
|
284
|
+
template_result = compiled_template(self.available_variables)
|
285
|
+
template_result = self._post_render_mutation(variable, template_result, options)
|
286
|
+
except TemplateEncountered:
|
287
|
+
raise
|
288
|
+
except Exception as ex:
|
289
|
+
template_result = defer_template_error(ex, variable, is_expression=False)
|
290
|
+
|
291
|
+
if ctx.is_top_level or mode is TemplateMode.ALWAYS_FINALIZE:
|
292
|
+
template_result = self._finalize_top_level_template_result(
|
293
|
+
variable, options, template_result, stop_on_container=mode is TemplateMode.STOP_ON_CONTAINER
|
294
|
+
)
|
295
|
+
|
296
|
+
return template_result
|
297
|
+
|
298
|
+
raise AnsibleTemplateTransformLimitError(obj=original_variable)
|
299
|
+
|
300
|
+
@staticmethod
|
301
|
+
def _finalize_top_level_template_result(
|
302
|
+
variable: t.Any,
|
303
|
+
options: TemplateOptions,
|
304
|
+
template_result: t.Any,
|
305
|
+
is_expression: bool = False,
|
306
|
+
stop_on_container: bool = False,
|
307
|
+
) -> t.Any:
|
308
|
+
"""
|
309
|
+
This method must be called for expressions and top-level templates to recursively finalize the result.
|
310
|
+
This renders any embedded templates and triggers `Marker` and omit behaviors.
|
311
|
+
"""
|
312
|
+
try:
|
313
|
+
if template_result is Omit:
|
314
|
+
# When the template result is Omit, raise an AnsibleValueOmittedError if value_for_omit is Omit, otherwise return value_for_omit.
|
315
|
+
# Other occurrences of Omit will simply drop out of containers during _finalize_template_result.
|
316
|
+
if options.value_for_omit is Omit:
|
317
|
+
raise AnsibleValueOmittedError()
|
318
|
+
|
319
|
+
return options.value_for_omit # trust that value_for_omit is an allowed type
|
320
|
+
|
321
|
+
if stop_on_container and type(template_result) in AnsibleTaggedObject._collection_types:
|
322
|
+
# Use of stop_on_container implies the caller will perform necessary checks on values,
|
323
|
+
# most likely by passing them back into the templating system.
|
324
|
+
try:
|
325
|
+
return template_result._non_lazy_copy()
|
326
|
+
except AttributeError:
|
327
|
+
return template_result # non-lazy containers are returned as-is
|
328
|
+
|
329
|
+
return _finalize_template_result(template_result, FinalizeMode.TOP_LEVEL)
|
330
|
+
except TemplateEncountered:
|
331
|
+
raise
|
332
|
+
except Exception as ex:
|
333
|
+
raise_from: BaseException
|
334
|
+
|
335
|
+
if isinstance(ex, MarkerError):
|
336
|
+
exception_to_raise = ex.source._as_exception()
|
337
|
+
|
338
|
+
# MarkerError is never suitable for use as the cause of another exception, it is merely a raiseable container for the source marker
|
339
|
+
# used for flow control (so its stack trace is rarely useful). However, if the source derives from a ExceptionMarker, its contained
|
340
|
+
# exception (previously raised) should be used as the cause. Other sources do not contain exceptions, so cannot provide a cause.
|
341
|
+
raise_from = exception_to_raise if isinstance(ex.source, ExceptionMarker) else None
|
342
|
+
else:
|
343
|
+
exception_to_raise = ex
|
344
|
+
raise_from = ex
|
345
|
+
|
346
|
+
exception_to_raise = create_template_error(exception_to_raise, variable, is_expression)
|
347
|
+
|
348
|
+
if exception_to_raise is ex:
|
349
|
+
raise # when the exception to raise is the active exception, just re-raise it
|
350
|
+
|
351
|
+
if exception_to_raise is raise_from:
|
352
|
+
raise_from = exception_to_raise.__cause__ # preserve the exception's cause, if any, otherwise no cause will be used
|
353
|
+
|
354
|
+
raise exception_to_raise from raise_from # always raise from something to avoid the currently active exception becoming __context__
|
355
|
+
|
356
|
+
def _compile_template(self, template: str, options: TemplateOptions) -> t.Callable[[c.Mapping[str, t.Any]], t.Any]:
|
357
|
+
# NOTE: Creating an overlay that lives only inside _compile_template means that overrides are not applied
|
358
|
+
# when templating nested variables, where Templar.environment is used, not the overlay. They are, however,
|
359
|
+
# applied to includes and imports.
|
360
|
+
try:
|
361
|
+
stripped_template, env = self._create_overlay(template, options.overrides)
|
362
|
+
|
363
|
+
with _TemplateCompileContext(escape_backslashes=options.escape_backslashes):
|
364
|
+
return t.cast(AnsibleTemplate, env.from_string(stripped_template))
|
365
|
+
except Exception as ex:
|
366
|
+
return self._defer_jinja_compile_error(ex, template, False)
|
367
|
+
|
368
|
+
def _compile_expression(self, expression: str, options: TemplateOptions) -> t.Callable[[c.Mapping[str, t.Any]], t.Any]:
|
369
|
+
"""
|
370
|
+
Compile a Jinja expression, applying optional compile-time behavior via an environment overlay (if needed). The overlay is
|
371
|
+
necessary to avoid mutating settings on the Templar's shared environment, which could be visible to other code running concurrently.
|
372
|
+
In the specific case of escape_backslashes, the setting only applies to a top-level template at compile-time, not runtime, to
|
373
|
+
ensure that any nested template calls (e.g., include and import) do not inherit the (lack of) escaping behavior.
|
374
|
+
"""
|
375
|
+
try:
|
376
|
+
with _TemplateCompileContext(escape_backslashes=options.escape_backslashes):
|
377
|
+
return AnsibleTemplateExpression(self.environment.compile_expression(expression, False))
|
378
|
+
except Exception as ex:
|
379
|
+
return self._defer_jinja_compile_error(ex, expression, True)
|
380
|
+
|
381
|
+
def _defer_jinja_compile_error(self, ex: Exception, variable: str, is_expression: bool) -> t.Callable[[c.Mapping[str, t.Any]], t.Any]:
|
382
|
+
deferred_error = defer_template_error(ex, variable, is_expression=is_expression)
|
383
|
+
|
384
|
+
def deferred_exception(_jinja_vars: c.Mapping[str, t.Any]) -> t.Any:
|
385
|
+
# a template/expression compile error always results in a single node representing the compile error
|
386
|
+
return self.marker_behavior.handle_marker(deferred_error)
|
387
|
+
|
388
|
+
return deferred_exception
|
389
|
+
|
390
|
+
def _post_render_mutation(self, template: str, result: t.Any, options: TemplateOptions) -> t.Any:
|
391
|
+
if options.preserve_trailing_newlines and isinstance(result, str):
|
392
|
+
# The low level calls above do not preserve the newline
|
393
|
+
# characters at the end of the input data, so we
|
394
|
+
# calculate the difference in newlines and append them
|
395
|
+
# to the resulting output for parity
|
396
|
+
#
|
397
|
+
# Using AnsibleEnvironment's keep_trailing_newline instead would
|
398
|
+
# result in change in behavior when trailing newlines
|
399
|
+
# would be kept also for included templates, for example:
|
400
|
+
# "Hello {% include 'world.txt' %}!" would render as
|
401
|
+
# "Hello world\n!\n" instead of "Hello world!\n".
|
402
|
+
data_newlines = self._count_newlines_from_end(template)
|
403
|
+
res_newlines = self._count_newlines_from_end(result)
|
404
|
+
|
405
|
+
if data_newlines > res_newlines:
|
406
|
+
newlines = options.overrides.newline_sequence * (data_newlines - res_newlines)
|
407
|
+
result = AnsibleTagHelper.tag_copy(result, result + newlines)
|
408
|
+
|
409
|
+
# If the input string template was source-tagged and the result is not, propagate the source tag to the new value.
|
410
|
+
# This provides further contextual information when a template-derived value/var causes an error.
|
411
|
+
if not Origin.is_tagged_on(result) and (origin := Origin.get_tag(template)):
|
412
|
+
try:
|
413
|
+
result = origin.tag(result)
|
414
|
+
except NotTaggableError:
|
415
|
+
pass # best effort- if we can't, oh well
|
416
|
+
|
417
|
+
return result
|
418
|
+
|
419
|
+
def is_template(self, data: t.Any, overrides: TemplateOverrides = TemplateOverrides.DEFAULT) -> bool:
|
420
|
+
"""
|
421
|
+
Evaluate the input data to determine if it contains a template, even if that template is invalid. Containers will be recursively searched.
|
422
|
+
Objects subject to template-time transforms that do not yield a template are not considered templates by this method.
|
423
|
+
Gating a conditional call to `template` with this method is redundant and inefficient -- request templating unconditionally instead.
|
424
|
+
"""
|
425
|
+
options = TemplateOptions(overrides=overrides) if overrides is not TemplateOverrides.DEFAULT else TemplateOptions.DEFAULT
|
426
|
+
|
427
|
+
try:
|
428
|
+
self.template(data, options=options, mode=TemplateMode.STOP_ON_TEMPLATE)
|
429
|
+
except TemplateEncountered:
|
430
|
+
return True
|
431
|
+
else:
|
432
|
+
return False
|
433
|
+
|
434
|
+
def resolve_to_container(self, variable: t.Any, options: TemplateOptions = TemplateOptions.DEFAULT) -> t.Any:
|
435
|
+
"""
|
436
|
+
Recursively resolve scalar string template input, stopping at the first container encountered (if any).
|
437
|
+
Used for e.g., partial templating of task arguments, where the plugin needs to handle final resolution of some args internally.
|
438
|
+
"""
|
439
|
+
return self.template(variable, options=options, mode=TemplateMode.STOP_ON_CONTAINER)
|
440
|
+
|
441
|
+
def evaluate_expression(
|
442
|
+
self,
|
443
|
+
expression: str,
|
444
|
+
*,
|
445
|
+
local_variables: dict[str, t.Any] | None = None,
|
446
|
+
escape_backslashes: bool = True,
|
447
|
+
_render_jinja_const_template: bool = False,
|
448
|
+
) -> t.Any:
|
449
|
+
"""
|
450
|
+
Evaluate a trusted string expression and return its result.
|
451
|
+
Optional local variables may be provided, which can only be referenced directly by the given expression.
|
452
|
+
"""
|
453
|
+
if not isinstance(expression, str):
|
454
|
+
raise TypeError(f"Expressions must be {str!r}, got {type(expression)!r}.")
|
455
|
+
|
456
|
+
options = TemplateOptions(escape_backslashes=escape_backslashes, preserve_trailing_newlines=False)
|
457
|
+
|
458
|
+
with (
|
459
|
+
TemplateContext(template_value=expression, templar=self, options=options, _render_jinja_const_template=_render_jinja_const_template) as ctx,
|
460
|
+
DeprecatedAccessAuditContext.when(ctx.is_top_level),
|
461
|
+
):
|
462
|
+
try:
|
463
|
+
if not TrustedAsTemplate.is_tagged_on(expression):
|
464
|
+
raise TemplateTrustCheckFailedError(obj=expression)
|
465
|
+
|
466
|
+
template_variables = ChainMap(local_variables, self.available_variables) if local_variables else self.available_variables
|
467
|
+
compiled_template = self._compile_expression(expression, options)
|
468
|
+
|
469
|
+
template_result = compiled_template(template_variables)
|
470
|
+
template_result = self._post_render_mutation(expression, template_result, options)
|
471
|
+
except Exception as ex:
|
472
|
+
template_result = defer_template_error(ex, expression, is_expression=True)
|
473
|
+
|
474
|
+
return self._finalize_top_level_template_result(expression, options, template_result, is_expression=True)
|
475
|
+
|
476
|
+
_BROKEN_CONDITIONAL_ALLOWED_FRAGMENT = 'Broken conditionals are currently allowed because the `ALLOW_BROKEN_CONDITIONALS` configuration option is enabled.'
|
477
|
+
_CONDITIONAL_AS_TEMPLATE_MSG = 'Conditionals should not be surrounded by templating delimiters such as {{ }} or {% %}.'
|
478
|
+
|
479
|
+
def _strip_conditional_handle_empty(self, conditional) -> t.Any:
|
480
|
+
"""
|
481
|
+
Strips leading/trailing whitespace from the input expression.
|
482
|
+
If `ALLOW_BROKEN_CONDITIONALS` is enabled, None/empty is coerced to True (legacy behavior, deprecated).
|
483
|
+
Otherwise, None/empty results in a broken conditional error being raised.
|
484
|
+
"""
|
485
|
+
if isinstance(conditional, str):
|
486
|
+
# Leading/trailing whitespace on conditional expressions is not a problem, except we can't tell if the expression is empty (which *is* a problem).
|
487
|
+
# Always strip conditional input strings. Neither conditional expressions nor all-template conditionals have legit reasons to preserve
|
488
|
+
# surrounding whitespace, and they complicate detection and processing of all-template fallback cases.
|
489
|
+
conditional = AnsibleTagHelper.tag_copy(conditional, conditional.strip())
|
490
|
+
|
491
|
+
if conditional in (None, ''):
|
492
|
+
# deprecated backward-compatible behavior; None/empty input conditionals are always True
|
493
|
+
if _TemplateConfig.allow_broken_conditionals:
|
494
|
+
_display.deprecated(
|
495
|
+
msg='Empty conditional expression was evaluated as True.',
|
496
|
+
help_text=self._BROKEN_CONDITIONAL_ALLOWED_FRAGMENT,
|
497
|
+
obj=conditional,
|
498
|
+
version='2.23',
|
499
|
+
)
|
500
|
+
|
501
|
+
return True
|
502
|
+
|
503
|
+
raise AnsibleBrokenConditionalError("Empty conditional expressions are not allowed.", obj=conditional)
|
504
|
+
|
505
|
+
return conditional
|
506
|
+
|
507
|
+
def _normalize_and_evaluate_conditional(self, conditional: str | bool) -> t.Any:
|
508
|
+
"""Validate and normalize a conditional input value, resolving allowed embedded template cases and evaluating the resulting expression."""
|
509
|
+
conditional = self._strip_conditional_handle_empty(conditional)
|
510
|
+
|
511
|
+
# this must follow `_strip_conditional_handle_empty`, since None/empty are coerced to bool (deprecated)
|
512
|
+
if type(conditional) is bool: # pylint: disable=unidiomatic-typecheck
|
513
|
+
return conditional
|
514
|
+
|
515
|
+
try:
|
516
|
+
if not isinstance(conditional, str):
|
517
|
+
if _TemplateConfig.allow_broken_conditionals:
|
518
|
+
# because the input isn't a string, the result will never be a bool; the broken conditional warning in the caller will apply on the result
|
519
|
+
return self.template(conditional, mode=TemplateMode.ALWAYS_FINALIZE)
|
520
|
+
|
521
|
+
raise AnsibleBrokenConditionalError(message="Conditional expressions must be strings.", obj=conditional)
|
522
|
+
|
523
|
+
if is_possibly_all_template(conditional):
|
524
|
+
# Indirection of trusted expressions is always allowed. If the expression appears to be entirely wrapped in template delimiters,
|
525
|
+
# we must resolve it. e.g. `when: "{{ some_var_resolving_to_a_trusted_expression_string }}"`.
|
526
|
+
# Some invalid meta-templating corner cases may sneak through here (e.g., `when: '{{ "foo" }} == {{ "bar" }}'`); these will
|
527
|
+
# result in an untrusted expression error.
|
528
|
+
result = self.template(conditional, mode=TemplateMode.ALWAYS_FINALIZE)
|
529
|
+
result = self._strip_conditional_handle_empty(result)
|
530
|
+
|
531
|
+
if not isinstance(result, str):
|
532
|
+
_display.deprecated(msg=self._CONDITIONAL_AS_TEMPLATE_MSG, obj=conditional, version='2.23')
|
533
|
+
|
534
|
+
return result # not an expression
|
535
|
+
|
536
|
+
# The only allowed use of templates for conditionals is for indirect usage of an expression.
|
537
|
+
# Any other usage should simply be an expression, not an attempt at meta templating.
|
538
|
+
expression = result
|
539
|
+
else:
|
540
|
+
expression = conditional
|
541
|
+
|
542
|
+
# Disable escape_backslashes when processing conditionals, to maintain backwards compatibility.
|
543
|
+
# This is necessary because conditionals were previously evaluated using {% %}, which was *NOT* affected by escape_backslashes.
|
544
|
+
# Now that conditionals use expressions, they would be affected by escape_backslashes if it was not disabled.
|
545
|
+
return self.evaluate_expression(expression, escape_backslashes=False, _render_jinja_const_template=True)
|
546
|
+
|
547
|
+
except AnsibleUndefinedVariable as ex:
|
548
|
+
# DTFIX-FUTURE: we're only augmenting the message for context here; once we have proper contextual tracking, we can dump the re-raise
|
549
|
+
raise AnsibleUndefinedVariable("Error while evaluating conditional.", obj=conditional) from ex
|
550
|
+
|
551
|
+
def evaluate_conditional(self, conditional: str | bool) -> bool:
|
552
|
+
"""
|
553
|
+
Evaluate a trusted string expression or boolean and return its boolean result. A non-boolean result will raise `AnsibleBrokenConditionalError`.
|
554
|
+
The ALLOW_BROKEN_CONDITIONALS configuration option can temporarily relax this requirement, allowing truthy conditionals to succeed.
|
555
|
+
"""
|
556
|
+
result = self._normalize_and_evaluate_conditional(conditional)
|
557
|
+
|
558
|
+
if isinstance(result, bool):
|
559
|
+
return result
|
560
|
+
|
561
|
+
bool_result = bool(result)
|
562
|
+
|
563
|
+
msg = (
|
564
|
+
f'Conditional result was {textwrap.shorten(str(result), width=40)!r} of type {native_type_name(result)!r}, '
|
565
|
+
f'which evaluates to {bool_result}. Conditionals must have a boolean result.'
|
566
|
+
)
|
567
|
+
|
568
|
+
if _TemplateConfig.allow_broken_conditionals:
|
569
|
+
_display.deprecated(msg=msg, obj=conditional, help_text=self._BROKEN_CONDITIONAL_ALLOWED_FRAGMENT, version='2.23')
|
570
|
+
|
571
|
+
return bool_result
|
572
|
+
|
573
|
+
raise AnsibleBrokenConditionalError(msg, obj=conditional)
|
574
|
+
|
575
|
+
@staticmethod
|
576
|
+
def _trust_check(value: str, skip_handler: bool = False) -> bool:
|
577
|
+
"""
|
578
|
+
Return True if the given value is trusted for templating, otherwise return False.
|
579
|
+
When the value is not trusted, a warning or error may be generated, depending on configuration.
|
580
|
+
"""
|
581
|
+
if TrustedAsTemplate.is_tagged_on(value):
|
582
|
+
return True
|
583
|
+
|
584
|
+
if not skip_handler:
|
585
|
+
with Skippable, _TemplateConfig.untrusted_template_handler.handle(TemplateTrustCheckFailedError, skip_on_ignore=True):
|
586
|
+
raise TemplateTrustCheckFailedError(obj=value)
|
587
|
+
|
588
|
+
return False
|