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,1066 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import ast
|
4
|
+
import collections.abc as c
|
5
|
+
import dataclasses
|
6
|
+
import enum
|
7
|
+
import pathlib
|
8
|
+
import tempfile
|
9
|
+
import types
|
10
|
+
import typing as t
|
11
|
+
|
12
|
+
from collections import ChainMap
|
13
|
+
|
14
|
+
import jinja2.nodes
|
15
|
+
|
16
|
+
from jinja2 import pass_context, defaults, TemplateSyntaxError, FileSystemLoader
|
17
|
+
from jinja2.environment import Environment, Template, TemplateModule, TemplateExpression
|
18
|
+
from jinja2.compiler import Frame
|
19
|
+
from jinja2.lexer import TOKEN_VARIABLE_BEGIN, TOKEN_VARIABLE_END, TOKEN_STRING, Lexer
|
20
|
+
from jinja2.nativetypes import NativeCodeGenerator
|
21
|
+
from jinja2.nodes import Const, EvalContext
|
22
|
+
from jinja2.runtime import Context
|
23
|
+
from jinja2.sandbox import ImmutableSandboxedEnvironment
|
24
|
+
from jinja2.utils import missing, LRUCache
|
25
|
+
|
26
|
+
from ansible.utils.display import Display
|
27
|
+
from ansible.errors import AnsibleVariableTypeError, AnsibleTemplateSyntaxError, AnsibleTemplateError
|
28
|
+
from ansible.module_utils.common.text.converters import to_text
|
29
|
+
from ansible.module_utils._internal._datatag import (
|
30
|
+
_AnsibleTaggedDict,
|
31
|
+
_AnsibleTaggedList,
|
32
|
+
_AnsibleTaggedTuple,
|
33
|
+
_AnsibleTaggedStr,
|
34
|
+
AnsibleTagHelper,
|
35
|
+
)
|
36
|
+
|
37
|
+
from ansible._internal._errors._handler import ErrorAction
|
38
|
+
from ansible._internal._datatag._tags import Origin, TrustedAsTemplate
|
39
|
+
|
40
|
+
from ._access import AnsibleAccessContext
|
41
|
+
from ._datatag import _JinjaConstTemplate
|
42
|
+
from ._utils import LazyOptions
|
43
|
+
from ._jinja_common import (
|
44
|
+
MarkerError,
|
45
|
+
Marker,
|
46
|
+
CapturedExceptionMarker,
|
47
|
+
UndefinedMarker,
|
48
|
+
_TemplateConfig,
|
49
|
+
TruncationMarker,
|
50
|
+
validate_arg_type,
|
51
|
+
JinjaCallContext,
|
52
|
+
)
|
53
|
+
from ._jinja_plugins import JinjaPluginIntercept, _query, _lookup, _now, _wrap_plugin_output, get_first_marker_arg, _DirectCall, _jinja_const_template_warning
|
54
|
+
from ._lazy_containers import (
|
55
|
+
_AnsibleLazyTemplateMixin,
|
56
|
+
_AnsibleLazyTemplateDict,
|
57
|
+
_AnsibleLazyTemplateList,
|
58
|
+
_AnsibleLazyAccessTuple,
|
59
|
+
lazify_container_args,
|
60
|
+
lazify_container_kwargs,
|
61
|
+
lazify_container,
|
62
|
+
register_known_types,
|
63
|
+
)
|
64
|
+
from ._utils import Omit, TemplateContext, PASS_THROUGH_SCALAR_VAR_TYPES
|
65
|
+
|
66
|
+
from ansible.module_utils._internal._json._profiles import _json_subclassable_scalar_types
|
67
|
+
from ansible.module_utils import _internal
|
68
|
+
from ansible.module_utils._internal import _ambient_context, _dataclass_validation
|
69
|
+
from ansible.plugins.loader import filter_loader, test_loader
|
70
|
+
from ansible.vars.hostvars import HostVars, HostVarsVars
|
71
|
+
from ...module_utils.datatag import native_type_name
|
72
|
+
|
73
|
+
JINJA2_OVERRIDE = '#jinja2:'
|
74
|
+
|
75
|
+
display = Display()
|
76
|
+
|
77
|
+
|
78
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
|
79
|
+
class TemplateOverrides:
|
80
|
+
DEFAULT: t.ClassVar[t.Self]
|
81
|
+
|
82
|
+
block_start_string: str = defaults.BLOCK_START_STRING
|
83
|
+
block_end_string: str = defaults.BLOCK_END_STRING
|
84
|
+
variable_start_string: str = defaults.VARIABLE_START_STRING
|
85
|
+
variable_end_string: str = defaults.VARIABLE_END_STRING
|
86
|
+
comment_start_string: str = defaults.COMMENT_START_STRING
|
87
|
+
comment_end_string: str = defaults.COMMENT_END_STRING
|
88
|
+
line_statement_prefix: str | None = defaults.LINE_STATEMENT_PREFIX
|
89
|
+
line_comment_prefix: str | None = defaults.LINE_COMMENT_PREFIX
|
90
|
+
trim_blocks: bool = True # AnsibleEnvironment overrides this default, so don't use the Jinja default here
|
91
|
+
lstrip_blocks: bool = defaults.LSTRIP_BLOCKS
|
92
|
+
newline_sequence: t.Literal['\n', '\r\n', '\r'] = defaults.NEWLINE_SEQUENCE
|
93
|
+
keep_trailing_newline: bool = defaults.KEEP_TRAILING_NEWLINE
|
94
|
+
|
95
|
+
def __post_init__(self) -> None:
|
96
|
+
pass # overridden by _dataclass_validation._inject_post_init_validation
|
97
|
+
|
98
|
+
def _post_validate(self) -> None:
|
99
|
+
if not (self.block_start_string != self.variable_start_string != self.comment_start_string != self.block_start_string):
|
100
|
+
raise ValueError('Block, variable and comment start strings must be different.')
|
101
|
+
|
102
|
+
def overlay_kwargs(self) -> dict[str, t.Any]:
|
103
|
+
"""
|
104
|
+
Return a dictionary of arguments for passing to Environment.overlay.
|
105
|
+
The dictionary will be empty if all fields have their default value.
|
106
|
+
"""
|
107
|
+
# DTFIX-FUTURE: calculate default/non-default during __post_init__
|
108
|
+
fields = [(field, getattr(self, field.name)) for field in dataclasses.fields(self)]
|
109
|
+
kwargs = {field.name: value for field, value in fields if value != field.default}
|
110
|
+
|
111
|
+
return kwargs
|
112
|
+
|
113
|
+
def _contains_start_string(self, value: str) -> bool:
|
114
|
+
"""Returns True if the given value contains a variable, block or comment start string."""
|
115
|
+
# DTFIX-FUTURE: this is inefficient, use a compiled regex instead
|
116
|
+
|
117
|
+
for marker in (self.block_start_string, self.variable_start_string, self.comment_start_string):
|
118
|
+
if marker in value:
|
119
|
+
return True
|
120
|
+
|
121
|
+
return False
|
122
|
+
|
123
|
+
def _starts_and_ends_with_jinja_delimiters(self, value: str) -> bool:
|
124
|
+
"""Returns True if the given value starts and ends with Jinja variable, block or comment delimiters."""
|
125
|
+
# DTFIX-FUTURE: this is inefficient, use a compiled regex instead
|
126
|
+
|
127
|
+
for marker in (self.block_start_string, self.variable_start_string, self.comment_start_string):
|
128
|
+
if value.startswith(marker):
|
129
|
+
break
|
130
|
+
else:
|
131
|
+
return False
|
132
|
+
|
133
|
+
for marker in (self.block_end_string, self.variable_end_string, self.comment_end_string):
|
134
|
+
if value.endswith(marker):
|
135
|
+
return True
|
136
|
+
|
137
|
+
return False
|
138
|
+
|
139
|
+
def _extract_template_overrides(self, template: str) -> tuple[str, TemplateOverrides]:
|
140
|
+
if template.startswith(JINJA2_OVERRIDE):
|
141
|
+
eol = template.find('\n')
|
142
|
+
|
143
|
+
if eol == -1:
|
144
|
+
raise ValueError(f"Missing newline after {JINJA2_OVERRIDE!r} override.")
|
145
|
+
|
146
|
+
line = template[len(JINJA2_OVERRIDE) : eol]
|
147
|
+
template = template[eol + 1 :]
|
148
|
+
override_kwargs = {}
|
149
|
+
|
150
|
+
for pair in line.split(','):
|
151
|
+
if not pair.strip():
|
152
|
+
raise ValueError(f"Empty {JINJA2_OVERRIDE!r} override pair not allowed.")
|
153
|
+
|
154
|
+
if ':' not in pair:
|
155
|
+
raise ValueError(f"Missing key-value separator `:` in {JINJA2_OVERRIDE!r} override pair {pair!r}.")
|
156
|
+
|
157
|
+
key, val = pair.split(':', 1)
|
158
|
+
key = key.strip()
|
159
|
+
|
160
|
+
if key not in _TEMPLATE_OVERRIDE_FIELD_NAMES:
|
161
|
+
raise ValueError(f"Invalid {JINJA2_OVERRIDE!r} override key {key!r}.")
|
162
|
+
|
163
|
+
override_kwargs[key] = ast.literal_eval(val)
|
164
|
+
|
165
|
+
overrides = dataclasses.replace(self, **override_kwargs)
|
166
|
+
else:
|
167
|
+
overrides = self
|
168
|
+
|
169
|
+
return template, overrides
|
170
|
+
|
171
|
+
def merge(self, kwargs: dict[str, t.Any] | None, /) -> TemplateOverrides:
|
172
|
+
"""Return a new instance based on the current instance with the given kwargs overridden."""
|
173
|
+
if kwargs:
|
174
|
+
return self.from_kwargs(dataclasses.asdict(self) | kwargs)
|
175
|
+
|
176
|
+
return self
|
177
|
+
|
178
|
+
@classmethod
|
179
|
+
def from_kwargs(cls, kwargs: dict[str, t.Any] | None, /) -> TemplateOverrides:
|
180
|
+
"""TemplateOverrides instance factory; instances resolving to all default values will instead return the DEFAULT singleton for optimization."""
|
181
|
+
if kwargs:
|
182
|
+
value = cls(**kwargs)
|
183
|
+
|
184
|
+
if value.overlay_kwargs():
|
185
|
+
return value
|
186
|
+
|
187
|
+
return cls.DEFAULT
|
188
|
+
|
189
|
+
|
190
|
+
_dataclass_validation.inject_post_init_validation(TemplateOverrides, allow_subclasses=True)
|
191
|
+
|
192
|
+
TemplateOverrides.DEFAULT = TemplateOverrides()
|
193
|
+
|
194
|
+
_TEMPLATE_OVERRIDE_FIELD_NAMES: t.Final[tuple[str, ...]] = tuple(sorted(field.name for field in dataclasses.fields(TemplateOverrides)))
|
195
|
+
|
196
|
+
|
197
|
+
class AnsibleContext(Context):
|
198
|
+
"""
|
199
|
+
A custom context which intercepts resolve_or_missing() calls and
|
200
|
+
runs them through AnsibleAccessContext. This allows usage of variables
|
201
|
+
to be tracked. If needed, values can also be modified before being returned.
|
202
|
+
"""
|
203
|
+
|
204
|
+
environment: AnsibleEnvironment # narrow the type specified by the base
|
205
|
+
|
206
|
+
def __init__(self, *args, **kwargs):
|
207
|
+
super(AnsibleContext, self).__init__(*args, **kwargs)
|
208
|
+
|
209
|
+
__repr__ = object.__repr__ # prevent Jinja from dumping vars in case this gets repr'd
|
210
|
+
|
211
|
+
def get_all(self):
|
212
|
+
"""
|
213
|
+
Override Jinja's default get_all to return all vars in the context as a ChainMap with a mutable layer at the bottom.
|
214
|
+
This provides some isolation against accidental changes to inherited variable contexts without requiring copies.
|
215
|
+
"""
|
216
|
+
layers = []
|
217
|
+
|
218
|
+
if self.vars:
|
219
|
+
layers.append(self.vars)
|
220
|
+
if self.parent:
|
221
|
+
layers.append(self.parent)
|
222
|
+
|
223
|
+
# HACK: always include a sacrificial plain-dict on the bottom layer, since Jinja's debug and stacktrace rewrite code invokes
|
224
|
+
# `__setitem__` outside a call context; this will ensure that it always occurs on a plain dict instead of a lazy one.
|
225
|
+
return ChainMap({}, *layers)
|
226
|
+
|
227
|
+
# noinspection PyShadowingBuiltins
|
228
|
+
def derived(self, locals: t.Optional[t.Dict[str, t.Any]] = None) -> Context:
|
229
|
+
# this is a clone of Jinja's impl of derived, but using our lazy-aware _new_context
|
230
|
+
|
231
|
+
context = _new_context(
|
232
|
+
environment=self.environment,
|
233
|
+
template_name=self.name,
|
234
|
+
blocks={},
|
235
|
+
shared=True,
|
236
|
+
jinja_locals=locals,
|
237
|
+
jinja_vars=self.get_all(),
|
238
|
+
)
|
239
|
+
context.eval_ctx = self.eval_ctx
|
240
|
+
context.blocks.update((k, list(v)) for k, v in self.blocks.items())
|
241
|
+
return context
|
242
|
+
|
243
|
+
def keys(self, *args, **kwargs):
|
244
|
+
"""Base Context delegates to `dict.keys` against `get_all`, which would fail since we return a ChainMap. No known usage."""
|
245
|
+
raise NotImplementedError()
|
246
|
+
|
247
|
+
def values(self, *args, **kwargs):
|
248
|
+
"""Base Context delegates to `dict.values` against `get_all`, which would fail since we return a ChainMap. No known usage."""
|
249
|
+
raise NotImplementedError()
|
250
|
+
|
251
|
+
def items(self, *args, **kwargs):
|
252
|
+
"""Base Context delegates to built-in `dict.items` against `get_all`, which would fail since we return a ChainMap. No known usage."""
|
253
|
+
raise NotImplementedError()
|
254
|
+
|
255
|
+
|
256
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
257
|
+
class ArgSmuggler:
|
258
|
+
"""
|
259
|
+
Utility wrapper to wrap/unwrap args passed to Jinja `Template.render` and `TemplateExpression.__call__`.
|
260
|
+
e.g., see https://github.com/pallets/jinja/blob/3.1.3/src/jinja2/environment.py#L1296 and
|
261
|
+
https://github.com/pallets/jinja/blob/3.1.3/src/jinja2/environment.py#L1566.
|
262
|
+
"""
|
263
|
+
|
264
|
+
jinja_vars: c.Mapping[str, t.Any] | None
|
265
|
+
|
266
|
+
@classmethod
|
267
|
+
def package_jinja_vars(cls, jinja_vars: c.Mapping[str, t.Any]) -> dict[str, ArgSmuggler]:
|
268
|
+
"""Wrap the supplied vars dict in an ArgSmuggler to prevent premature templating from Jinja's internal dict copy."""
|
269
|
+
return dict(_smuggled_vars=ArgSmuggler(jinja_vars=jinja_vars))
|
270
|
+
|
271
|
+
@classmethod
|
272
|
+
def extract_jinja_vars(cls, maybe_smuggled_vars: c.Mapping[str, t.Any] | None) -> c.Mapping[str, t.Any]:
|
273
|
+
"""
|
274
|
+
If the supplied vars dict contains an ArgSmuggler instance with the expected key, unwrap it and return the smuggled value.
|
275
|
+
Otherwise, return the supplied dict as-is.
|
276
|
+
"""
|
277
|
+
if maybe_smuggled_vars and ((smuggler := maybe_smuggled_vars.get('_smuggled_vars')) and isinstance(smuggler, ArgSmuggler)):
|
278
|
+
return smuggler.jinja_vars
|
279
|
+
|
280
|
+
return maybe_smuggled_vars
|
281
|
+
|
282
|
+
|
283
|
+
class AnsibleTemplateExpression:
|
284
|
+
"""
|
285
|
+
Wrapper around Jinja's TemplateExpression for converting MarkerError back into Marker.
|
286
|
+
This is needed to make expression error handling consistent with templates, since Jinja does not support a custom type for Environment.compile_expression.
|
287
|
+
"""
|
288
|
+
|
289
|
+
def __init__(self, template_expression: TemplateExpression) -> None:
|
290
|
+
self._template_expression = template_expression
|
291
|
+
|
292
|
+
def __call__(self, jinja_vars: c.Mapping[str, t.Any]) -> t.Any:
|
293
|
+
try:
|
294
|
+
return self._template_expression(ArgSmuggler.package_jinja_vars(jinja_vars))
|
295
|
+
except MarkerError as ex:
|
296
|
+
return ex.source
|
297
|
+
|
298
|
+
|
299
|
+
class AnsibleTemplate(Template):
|
300
|
+
"""
|
301
|
+
A helper class, which prevents Jinja2 from running lazy containers through dict().
|
302
|
+
"""
|
303
|
+
|
304
|
+
_python_source_temp_path: pathlib.Path | None = None
|
305
|
+
|
306
|
+
def __del__(self):
|
307
|
+
# DTFIX-RELEASE: this still isn't working reliably; something else must be keeping the template object alive
|
308
|
+
if self._python_source_temp_path:
|
309
|
+
self._python_source_temp_path.unlink(missing_ok=True)
|
310
|
+
|
311
|
+
def __call__(self, jinja_vars: c.Mapping[str, t.Any]) -> t.Any:
|
312
|
+
return self.render(ArgSmuggler.package_jinja_vars(jinja_vars))
|
313
|
+
|
314
|
+
# noinspection PyShadowingBuiltins
|
315
|
+
def new_context(
|
316
|
+
self,
|
317
|
+
vars: c.Mapping[str, t.Any] | None = None,
|
318
|
+
shared: bool = False,
|
319
|
+
locals: c.Mapping[str, t.Any] | None = None,
|
320
|
+
) -> Context:
|
321
|
+
return _new_context(
|
322
|
+
environment=self.environment,
|
323
|
+
template_name=self.name,
|
324
|
+
blocks=self.blocks,
|
325
|
+
shared=shared,
|
326
|
+
jinja_locals=locals,
|
327
|
+
jinja_vars=ArgSmuggler.extract_jinja_vars(vars),
|
328
|
+
jinja_globals=self.globals,
|
329
|
+
)
|
330
|
+
|
331
|
+
|
332
|
+
class AnsibleCodeGenerator(NativeCodeGenerator):
|
333
|
+
"""
|
334
|
+
Custom code generation behavior to support deprecated Ansible features and fill in gaps in Jinja native.
|
335
|
+
This can be removed once the deprecated Ansible features are removed and the native fixes are upstreamed in Jinja.
|
336
|
+
"""
|
337
|
+
|
338
|
+
def _output_const_repr(self, group: t.Iterable[t.Any]) -> str:
|
339
|
+
"""
|
340
|
+
Prevent Jinja's code generation from stringifying single nodes before generating its repr.
|
341
|
+
This complements the behavioral change in AnsibleEnvironment.concat which returns single nodes without stringifying them.
|
342
|
+
"""
|
343
|
+
# DTFIX-FUTURE: contribute this upstream as a fix to Jinja's native support
|
344
|
+
group_list = list(group)
|
345
|
+
|
346
|
+
if len(group_list) == 1:
|
347
|
+
return repr(group_list[0])
|
348
|
+
|
349
|
+
# NB: This is slightly more efficient than Jinja's _output_const_repr, which generates a throw-away list instance to pass to join.
|
350
|
+
# Before removing this, ensure that upstream Jinja has this change.
|
351
|
+
return repr("".join(map(str, group_list)))
|
352
|
+
|
353
|
+
def visit_Const(self, node: Const, frame: Frame) -> None:
|
354
|
+
"""
|
355
|
+
Override Jinja's visit_Const to inject a runtime call to AnsibleEnvironment._access_const for constant strings that are possibly templates, which
|
356
|
+
may require special handling at runtime. See that method for details. An example that hits this path:
|
357
|
+
{{ lookup("file", "{{ output_dir }}/bla") }}
|
358
|
+
"""
|
359
|
+
value = node.as_const(frame.eval_ctx)
|
360
|
+
|
361
|
+
if _TemplateConfig.allow_embedded_templates and type(value) is str and is_possibly_template(value): # pylint: disable=unidiomatic-typecheck
|
362
|
+
# deprecated: description='embedded Jinja constant string template support' core_version='2.23'
|
363
|
+
self.write(f'environment._access_const({value!r})')
|
364
|
+
else:
|
365
|
+
# NB: This is actually more efficient than Jinja's visit_Const, which contains obsolete (as of Py2.7/3.1) float conversion instance checks. Before
|
366
|
+
# removing this override entirely, ensure that upstream Jinja has removed the obsolete code.
|
367
|
+
# See https://docs.python.org/release/2.7/whatsnew/2.7.html#python-3-1-features for more details.
|
368
|
+
self.write(repr(value))
|
369
|
+
|
370
|
+
|
371
|
+
@pass_context
|
372
|
+
def _ansible_finalize(_ctx: AnsibleContext, value: t.Any) -> t.Any:
|
373
|
+
"""
|
374
|
+
This function is called by Jinja with the result of each variable template block (e.g., {{ }}) encountered in a template.
|
375
|
+
The pass_context decorator prevents finalize from being called on constants at template compile time.
|
376
|
+
The passed in AnsibleContext is unused -- it is the result of using the pass_context decorator.
|
377
|
+
The important part for us is that this blocks constant folding, which ensures our custom visit_Const is used.
|
378
|
+
It also ensures that template results are wrapped in lazy containers.
|
379
|
+
"""
|
380
|
+
return lazify_container(value)
|
381
|
+
|
382
|
+
|
383
|
+
@dataclasses.dataclass(kw_only=True, slots=True)
|
384
|
+
class _TemplateCompileContext(_ambient_context.AmbientContextBase):
|
385
|
+
"""
|
386
|
+
This context is active during Ansible's explicit compilation of templates/expressions, but not during Jinja's runtime compilation.
|
387
|
+
Historically, Ansible-specific pre-processing like `escape_backslashes` was not applied to imported/included templates.
|
388
|
+
"""
|
389
|
+
|
390
|
+
escape_backslashes: bool
|
391
|
+
|
392
|
+
|
393
|
+
class _CompileStateSmugglingCtx(_ambient_context.AmbientContextBase):
|
394
|
+
template_source: str | None = None
|
395
|
+
python_source: str | None = None
|
396
|
+
python_source_temp_path: pathlib.Path | None = None
|
397
|
+
|
398
|
+
|
399
|
+
class AnsibleLexer(Lexer):
|
400
|
+
"""
|
401
|
+
Lexer override to escape backslashes in string constants within Jinja expressions; prevents Jinja from double-escaping them.
|
402
|
+
|
403
|
+
NOTE: This behavior is only applied to string constants within Jinja expressions (eg {{ "c:\newfile" }}), *not* statements ("{% set foo="c:\\newfile" %}").
|
404
|
+
|
405
|
+
This is useful when templates are sourced from YAML double-quoted strings, as it avoids having backslashes processed twice: first by the
|
406
|
+
YAML parser, and then again by the Jinja parser. Instead, backslashes are only processed by YAML.
|
407
|
+
|
408
|
+
Example YAML:
|
409
|
+
|
410
|
+
- debug:
|
411
|
+
msg: "Test Case 1\\3; {{ test1_name | regex_replace('^(.*)_name$', '\\1')}}"
|
412
|
+
|
413
|
+
Since the outermost YAML string is double-quoted, the YAML parser converts the double backslashes to single backslashes. Without escaping, Jinja
|
414
|
+
would see only a single backslash ('\1') while processing the embedded template expression, interpret it as an escape sequence, and convert it
|
415
|
+
to '\x01' (ASCII "SOH"). This is clearly not the intended `\1` backreference argument to the `regex_replace` filter (which would require the
|
416
|
+
double-escaped string '\\\\1' to yield the intended result).
|
417
|
+
|
418
|
+
Since the "\\3" in the input YAML was not part of a template expression, the YAML-parsed "\3" remains after Jinja rendering. This would be
|
419
|
+
confusing for playbook authors, as different escaping rules would be needed inside and outside the template expression.
|
420
|
+
|
421
|
+
When templates are not sourced from YAML, escaping backslashes will prevent use of backslash escape sequences such as "\n" and "\t".
|
422
|
+
|
423
|
+
See relevant Jinja lexer impl at e.g.: https://github.com/pallets/jinja/blob/3.1.2/src/jinja2/lexer.py#L646-L653.
|
424
|
+
"""
|
425
|
+
|
426
|
+
def tokeniter(self, *args, **kwargs) -> t.Iterator[t.Tuple[int, str, str]]:
|
427
|
+
"""Pre-escape backslashes in expression ({{ }}) raw string constants before Jinja's Lexer.wrap() can interpret them as ASCII escape sequences."""
|
428
|
+
token_stream = super().tokeniter(*args, **kwargs)
|
429
|
+
|
430
|
+
# if we have no context, Jinja's doing a nested compile at runtime (eg, import/include); historically, no backslash escaping is performed
|
431
|
+
if not (tcc := _TemplateCompileContext.current(optional=True)) or not tcc.escape_backslashes:
|
432
|
+
yield from token_stream
|
433
|
+
return
|
434
|
+
|
435
|
+
in_variable = False
|
436
|
+
|
437
|
+
for token in token_stream:
|
438
|
+
token_type = token[1]
|
439
|
+
|
440
|
+
if token_type == TOKEN_VARIABLE_BEGIN:
|
441
|
+
in_variable = True
|
442
|
+
elif token_type == TOKEN_VARIABLE_END:
|
443
|
+
in_variable = False
|
444
|
+
elif in_variable and token_type == TOKEN_STRING:
|
445
|
+
token = token[0], token_type, token[2].replace('\\', '\\\\')
|
446
|
+
|
447
|
+
yield token
|
448
|
+
|
449
|
+
|
450
|
+
def defer_template_error(ex: Exception, variable: t.Any, *, is_expression: bool) -> Marker:
|
451
|
+
if not ex.__traceback__:
|
452
|
+
raise AssertionError('ex must be a previously raised exception')
|
453
|
+
|
454
|
+
if isinstance(ex, MarkerError):
|
455
|
+
return ex.source
|
456
|
+
|
457
|
+
exception_to_raise = create_template_error(ex, variable, is_expression)
|
458
|
+
|
459
|
+
if exception_to_raise is ex:
|
460
|
+
return CapturedExceptionMarker(ex) # capture the previously raised exception
|
461
|
+
|
462
|
+
try:
|
463
|
+
raise exception_to_raise from ex # raise the newly synthesized exception before capturing it
|
464
|
+
except Exception as captured_ex:
|
465
|
+
return CapturedExceptionMarker(captured_ex)
|
466
|
+
|
467
|
+
|
468
|
+
def create_template_error(ex: Exception, variable: t.Any, is_expression: bool) -> AnsibleTemplateError:
|
469
|
+
if isinstance(ex, AnsibleTemplateError):
|
470
|
+
exception_to_raise = ex
|
471
|
+
else:
|
472
|
+
kind = "expression" if is_expression else "template"
|
473
|
+
ex_type = AnsibleTemplateError # always raise an AnsibleTemplateError/subclass
|
474
|
+
|
475
|
+
if isinstance(ex, RecursionError):
|
476
|
+
msg = f"Recursive loop detected in {kind}."
|
477
|
+
elif isinstance(ex, TemplateSyntaxError):
|
478
|
+
msg = f"Syntax error in {kind}."
|
479
|
+
|
480
|
+
if is_expression and is_possibly_template(variable):
|
481
|
+
msg += " Template delimiters are not supported in expressions."
|
482
|
+
|
483
|
+
ex_type = AnsibleTemplateSyntaxError
|
484
|
+
else:
|
485
|
+
msg = f"Error rendering {kind}."
|
486
|
+
|
487
|
+
exception_to_raise = ex_type(msg, obj=variable)
|
488
|
+
|
489
|
+
if exception_to_raise.obj is None:
|
490
|
+
exception_to_raise.obj = TemplateContext.current().template_value
|
491
|
+
|
492
|
+
# DTFIX-FUTURE: Look through the TemplateContext hierarchy to find the most recent non-template
|
493
|
+
# caller and use that for origin when no origin is available on obj. This could be useful for situations where the template
|
494
|
+
# was embedded in a plugin, or a plugin is otherwise responsible for losing the origin and/or trust. We can't just use the first
|
495
|
+
# non-template caller as that will lead to false positives for re-entrant calls (e.g. template plugins that call into templar).
|
496
|
+
|
497
|
+
return exception_to_raise
|
498
|
+
|
499
|
+
|
500
|
+
# DTFIX-RELEASE: implement CapturedExceptionMarker deferral support on call (and lookup), filter/test plugins, etc.
|
501
|
+
# also update the protomatter integration test once this is done (the test was written differently since this wasn't done yet)
|
502
|
+
|
503
|
+
_BUILTIN_FILTER_ALIASES: dict[str, str] = {}
|
504
|
+
_BUILTIN_TEST_ALIASES: dict[str, str] = {
|
505
|
+
'!=': 'ne',
|
506
|
+
'<': 'lt',
|
507
|
+
'<=': 'le',
|
508
|
+
'==': 'eq',
|
509
|
+
'>': 'gt',
|
510
|
+
'>=': 'ge',
|
511
|
+
}
|
512
|
+
|
513
|
+
_BUILTIN_FILTERS = filter_loader._wrap_funcs(defaults.DEFAULT_FILTERS, _BUILTIN_FILTER_ALIASES)
|
514
|
+
_BUILTIN_TESTS = test_loader._wrap_funcs(t.cast(dict[str, t.Callable], defaults.DEFAULT_TESTS), _BUILTIN_TEST_ALIASES)
|
515
|
+
|
516
|
+
|
517
|
+
class AnsibleEnvironment(ImmutableSandboxedEnvironment):
|
518
|
+
"""
|
519
|
+
Our custom environment, which simply allows us to override the class-level
|
520
|
+
values for the Template and Context classes used by jinja2 internally.
|
521
|
+
"""
|
522
|
+
|
523
|
+
context_class = AnsibleContext
|
524
|
+
template_class = AnsibleTemplate
|
525
|
+
code_generator_class = AnsibleCodeGenerator
|
526
|
+
intercepted_binops = frozenset(('eq',))
|
527
|
+
|
528
|
+
_lexer_cache = LRUCache(50)
|
529
|
+
|
530
|
+
# DTFIX-FUTURE: bikeshed a name/mechanism to control template debugging
|
531
|
+
_debuggable_template_source = False
|
532
|
+
_debuggable_template_source_path: pathlib.Path = pathlib.Path(__file__).parent.parent.parent.parent / '.template_debug_source'
|
533
|
+
|
534
|
+
def __init__(self, *args, ansible_basedir: str | None = None, **kwargs) -> None:
|
535
|
+
if ansible_basedir:
|
536
|
+
kwargs.update(loader=FileSystemLoader(ansible_basedir))
|
537
|
+
|
538
|
+
super().__init__(*args, extensions=_TemplateConfig.jinja_extensions, **kwargs)
|
539
|
+
|
540
|
+
self.filters = JinjaPluginIntercept(_BUILTIN_FILTERS, filter_loader) # type: ignore[assignment]
|
541
|
+
self.tests = JinjaPluginIntercept(_BUILTIN_TESTS, test_loader) # type: ignore[assignment,arg-type]
|
542
|
+
|
543
|
+
# future Jinja releases may default-enable autoescape; force-disable to prevent the problems it could cause
|
544
|
+
# see https://github.com/pallets/jinja/blob/3.1.2/docs/api.rst?plain=1#L69
|
545
|
+
self.autoescape = False
|
546
|
+
|
547
|
+
self.trim_blocks = True
|
548
|
+
|
549
|
+
self.undefined = UndefinedMarker
|
550
|
+
self.finalize = _ansible_finalize
|
551
|
+
|
552
|
+
self.globals.update(
|
553
|
+
range=range, # the sandboxed environment limits range in ways that may cause us problems; use the real Python one
|
554
|
+
now=_now,
|
555
|
+
undef=_undef,
|
556
|
+
omit=Omit,
|
557
|
+
lookup=_lookup,
|
558
|
+
query=_query,
|
559
|
+
q=_query,
|
560
|
+
)
|
561
|
+
|
562
|
+
# Disabling the optimizer prevents compile-time constant expression folding, which prevents our
|
563
|
+
# visit_Const recursive inline template expansion tricks from working in many cases where Jinja's
|
564
|
+
# ignorance of our embedded templates are optimized away as fully-constant expressions,
|
565
|
+
# eg {{ "{{'hi'}}" == "hi" }}. As of Jinja ~3.1, this specifically avoids cases where the @optimizeconst
|
566
|
+
# visitor decorator performs constant folding, which bypasses our visit_Const impl and causes embedded
|
567
|
+
# templates to be lost.
|
568
|
+
# See also optimizeconst impl: https://github.com/pallets/jinja/blob/3.1.0/src/jinja2/compiler.py#L48-L49
|
569
|
+
self.optimized = False
|
570
|
+
|
571
|
+
def get_template(
|
572
|
+
self,
|
573
|
+
name: str | Template,
|
574
|
+
parent: str | None = None,
|
575
|
+
globals: c.MutableMapping[str, t.Any] | None = None,
|
576
|
+
) -> Template:
|
577
|
+
"""Ensures that templates built via `get_template` are also source debuggable."""
|
578
|
+
with _CompileStateSmugglingCtx.when(self._debuggable_template_source) as ctx:
|
579
|
+
template_obj = t.cast(AnsibleTemplate, super().get_template(name, parent, globals))
|
580
|
+
|
581
|
+
if isinstance(ctx, _CompileStateSmugglingCtx): # only present if debugging is enabled
|
582
|
+
template_obj._python_source_temp_path = ctx.python_source_temp_path # facilitate deletion of the temp file when template_obj is deleted
|
583
|
+
|
584
|
+
return template_obj
|
585
|
+
|
586
|
+
@property
|
587
|
+
def lexer(self) -> AnsibleLexer:
|
588
|
+
"""Return/cache an AnsibleLexer with settings from the current AnsibleEnvironment"""
|
589
|
+
# DTFIX-RELEASE: optimization - we should pre-generate the default cached lexer before forking, not leave it to chance (e.g. simple playbooks)
|
590
|
+
key = tuple(getattr(self, name) for name in _TEMPLATE_OVERRIDE_FIELD_NAMES)
|
591
|
+
|
592
|
+
lex = self._lexer_cache.get(key)
|
593
|
+
|
594
|
+
if lex is None:
|
595
|
+
self._lexer_cache[key] = lex = AnsibleLexer(self)
|
596
|
+
|
597
|
+
return lex
|
598
|
+
|
599
|
+
def call_filter(
|
600
|
+
self,
|
601
|
+
name: str,
|
602
|
+
value: t.Any,
|
603
|
+
args: c.Sequence[t.Any] | None = None,
|
604
|
+
kwargs: c.Mapping[str, t.Any] | None = None,
|
605
|
+
context: Context | None = None,
|
606
|
+
eval_ctx: EvalContext | None = None,
|
607
|
+
) -> t.Any:
|
608
|
+
"""
|
609
|
+
Ensure that filters directly invoked by plugins will see non-templating lazy containers.
|
610
|
+
Without this, `_wrap_filter` will wrap `args` and `kwargs` in templating lazy containers.
|
611
|
+
This provides consistency with plugin output handling by preventing auto-templating of trusted templates passed in native containers.
|
612
|
+
"""
|
613
|
+
# DTFIX-RELEASE: need better logic to handle non-list/non-dict inputs for args/kwargs
|
614
|
+
args = _AnsibleLazyTemplateMixin._try_create(list(args or []), LazyOptions.SKIP_TEMPLATES)
|
615
|
+
kwargs = _AnsibleLazyTemplateMixin._try_create(kwargs, LazyOptions.SKIP_TEMPLATES)
|
616
|
+
|
617
|
+
return super().call_filter(name, value, args, kwargs, context, eval_ctx)
|
618
|
+
|
619
|
+
def call_test(
|
620
|
+
self,
|
621
|
+
name: str,
|
622
|
+
value: t.Any,
|
623
|
+
args: c.Sequence[t.Any] | None = None,
|
624
|
+
kwargs: c.Mapping[str, t.Any] | None = None,
|
625
|
+
context: Context | None = None,
|
626
|
+
eval_ctx: EvalContext | None = None,
|
627
|
+
) -> t.Any:
|
628
|
+
"""
|
629
|
+
Ensure that tests directly invoked by plugins will see non-templating lazy containers.
|
630
|
+
Without this, `_wrap_test` will wrap `args` and `kwargs` in templating lazy containers.
|
631
|
+
This provides consistency with plugin output handling by preventing auto-templating of trusted templates passed in native containers.
|
632
|
+
"""
|
633
|
+
# DTFIX-RELEASE: need better logic to handle non-list/non-dict inputs for args/kwargs
|
634
|
+
args = _AnsibleLazyTemplateMixin._try_create(list(args or []), LazyOptions.SKIP_TEMPLATES)
|
635
|
+
kwargs = _AnsibleLazyTemplateMixin._try_create(kwargs, LazyOptions.SKIP_TEMPLATES)
|
636
|
+
|
637
|
+
return super().call_test(name, value, args, kwargs, context, eval_ctx)
|
638
|
+
|
639
|
+
def compile_expression(self, source: str, *args, **kwargs) -> TemplateExpression:
|
640
|
+
# compile_expression parses and passes the tree to from_string; for debug support, activate the context here to capture the intermediate results
|
641
|
+
with _CompileStateSmugglingCtx.when(self._debuggable_template_source) as ctx:
|
642
|
+
if isinstance(ctx, _CompileStateSmugglingCtx): # only present if debugging is enabled
|
643
|
+
ctx.template_source = source
|
644
|
+
|
645
|
+
return super().compile_expression(source, *args, **kwargs)
|
646
|
+
|
647
|
+
def from_string(self, source: str | jinja2.nodes.Template, *args, **kwargs) -> AnsibleTemplate:
|
648
|
+
# if debugging is enabled, use existing context when present (e.g., from compile_expression)
|
649
|
+
current_ctx = _CompileStateSmugglingCtx.current(optional=True) if self._debuggable_template_source else None
|
650
|
+
|
651
|
+
with _CompileStateSmugglingCtx.when(self._debuggable_template_source and not current_ctx) as new_ctx:
|
652
|
+
template_obj = t.cast(AnsibleTemplate, super().from_string(source, *args, **kwargs))
|
653
|
+
|
654
|
+
if isinstance(ctx := current_ctx or new_ctx, _CompileStateSmugglingCtx): # only present if debugging is enabled
|
655
|
+
template_obj._python_source_temp_path = ctx.python_source_temp_path # facilitate deletion of the temp file when template_obj is deleted
|
656
|
+
|
657
|
+
return template_obj
|
658
|
+
|
659
|
+
def _parse(self, source: str, *args, **kwargs) -> jinja2.nodes.Template:
|
660
|
+
if csc := _CompileStateSmugglingCtx.current(optional=True):
|
661
|
+
csc.template_source = source
|
662
|
+
|
663
|
+
return super()._parse(source, *args, **kwargs)
|
664
|
+
|
665
|
+
def _compile(self, source: str, filename: str) -> types.CodeType:
|
666
|
+
if csc := _CompileStateSmugglingCtx.current(optional=True):
|
667
|
+
origin = Origin.get_tag(csc.template_source) or Origin.UNKNOWN
|
668
|
+
|
669
|
+
source = '\n'.join(
|
670
|
+
(
|
671
|
+
"import sys; breakpoint() if type(sys.breakpointhook) is not type(breakpoint) else None",
|
672
|
+
f"# original template source from {str(origin)!r}: ",
|
673
|
+
'\n'.join(f'# {line}' for line in (csc.template_source or '').splitlines()),
|
674
|
+
source,
|
675
|
+
)
|
676
|
+
)
|
677
|
+
|
678
|
+
source_temp_dir = self._debuggable_template_source_path
|
679
|
+
source_temp_dir.mkdir(parents=True, exist_ok=True)
|
680
|
+
|
681
|
+
with tempfile.NamedTemporaryFile(dir=source_temp_dir, mode='w', suffix='.py', prefix='j2_src_', delete=False) as source_file:
|
682
|
+
filename = source_file.name
|
683
|
+
|
684
|
+
source_file.write(source)
|
685
|
+
source_file.flush()
|
686
|
+
|
687
|
+
csc.python_source = source
|
688
|
+
csc.python_source_temp_path = pathlib.Path(filename)
|
689
|
+
|
690
|
+
res = super()._compile(source, filename)
|
691
|
+
|
692
|
+
return res
|
693
|
+
|
694
|
+
@staticmethod
|
695
|
+
def concat(nodes: t.Iterable[t.Any]) -> t.Any: # type: ignore[override]
|
696
|
+
node_list = list(_flatten_nodes(nodes))
|
697
|
+
|
698
|
+
if not node_list:
|
699
|
+
return None
|
700
|
+
|
701
|
+
# this code is complemented by our tweaked CodeGenerator _output_const_repr that ensures that literal constants
|
702
|
+
# in templates aren't double-repr'd in the generated code
|
703
|
+
if len(node_list) == 1:
|
704
|
+
# DTFIX-RELEASE: determine if we should do managed access here (we *should* have hit them all during templating/resolve, but ?)
|
705
|
+
return node_list[0]
|
706
|
+
|
707
|
+
# In order to ensure that all markers are tripped, do a recursive finalize before we repr (otherwise we can end up
|
708
|
+
# repr'ing a Marker). This requires two passes, but avoids the need for a parallel reimplementation of all repr methods.
|
709
|
+
try:
|
710
|
+
node_list = _finalize_template_result(node_list, FinalizeMode.CONCAT)
|
711
|
+
except MarkerError as ex:
|
712
|
+
return ex.source # return the first Marker encountered
|
713
|
+
|
714
|
+
return ''.join([to_text(v) for v in node_list])
|
715
|
+
|
716
|
+
@staticmethod
|
717
|
+
def _access_const(const_template: t.LiteralString) -> t.Any:
|
718
|
+
"""
|
719
|
+
Called during template rendering on template-looking string constants embedded in the template.
|
720
|
+
It provides the following functionality:
|
721
|
+
* Propagates origin from the containing template.
|
722
|
+
* For backward compatibility when embedded templates are enabled:
|
723
|
+
* Conditionals - Renders embedded template constants and accesses the result. Warns on each constant immediately.
|
724
|
+
* Non-conditionals - Tags constants for deferred rendering of templates in lookup terms. Warns on each constant during lookup invocation.
|
725
|
+
"""
|
726
|
+
ctx = TemplateContext.current()
|
727
|
+
|
728
|
+
if (tv := ctx.template_value) and (origin := Origin.get_tag(tv)):
|
729
|
+
const_template = origin.tag(const_template)
|
730
|
+
|
731
|
+
if ctx._render_jinja_const_template:
|
732
|
+
_jinja_const_template_warning(const_template, is_conditional=True)
|
733
|
+
|
734
|
+
result = ctx.templar.template(TrustedAsTemplate().tag(const_template))
|
735
|
+
AnsibleAccessContext.current().access(result)
|
736
|
+
else:
|
737
|
+
# warnings will be issued when lookup terms processing occurs, to avoid false positives
|
738
|
+
result = _JinjaConstTemplate().tag(const_template)
|
739
|
+
|
740
|
+
return result
|
741
|
+
|
742
|
+
def getitem(self, obj: t.Any, argument: t.Any) -> t.Any:
|
743
|
+
value = super().getitem(obj, argument)
|
744
|
+
|
745
|
+
AnsibleAccessContext.current().access(value)
|
746
|
+
|
747
|
+
return value
|
748
|
+
|
749
|
+
def getattr(self, obj: t.Any, attribute: str) -> t.Any:
|
750
|
+
"""
|
751
|
+
Get `attribute` from the attributes of `obj`, falling back to items in `obj`.
|
752
|
+
If no item was found, return a sandbox-specific `UndefinedMarker` if `attribute` is protected by the sandbox,
|
753
|
+
otherwise return a normal `UndefinedMarker` instance.
|
754
|
+
This differs from the built-in Jinja behavior which will not fall back to items if `attribute` is protected by the sandbox.
|
755
|
+
"""
|
756
|
+
# example template that uses this: "{{ some.thing }}" -- obj is the "some" dict, attribute is "thing"
|
757
|
+
|
758
|
+
is_safe = True
|
759
|
+
|
760
|
+
try:
|
761
|
+
value = getattr(obj, attribute)
|
762
|
+
except AttributeError:
|
763
|
+
value = _sentinel
|
764
|
+
else:
|
765
|
+
if not (is_safe := self.is_safe_attribute(obj, attribute, value)):
|
766
|
+
value = _sentinel
|
767
|
+
|
768
|
+
if value is _sentinel:
|
769
|
+
try:
|
770
|
+
value = obj[attribute]
|
771
|
+
except (TypeError, LookupError):
|
772
|
+
return self.undefined(obj=obj, name=attribute) if is_safe else self.unsafe_undefined(obj, attribute)
|
773
|
+
|
774
|
+
AnsibleAccessContext.current().access(value)
|
775
|
+
|
776
|
+
return value
|
777
|
+
|
778
|
+
def call(
|
779
|
+
self,
|
780
|
+
__context: Context,
|
781
|
+
__obj: t.Any,
|
782
|
+
*args: t.Any,
|
783
|
+
**kwargs: t.Any,
|
784
|
+
) -> t.Any:
|
785
|
+
if _DirectCall.is_marked(__obj):
|
786
|
+
# Both `_lookup` and `_query` handle arg proxying and `Marker` args internally.
|
787
|
+
# Performing either before calling them will interfere with that processing.
|
788
|
+
return super().call(__context, __obj, *args, **kwargs)
|
789
|
+
|
790
|
+
if (first_marker := get_first_marker_arg(args, kwargs)) is not None:
|
791
|
+
return first_marker
|
792
|
+
|
793
|
+
try:
|
794
|
+
with JinjaCallContext(accept_lazy_markers=False):
|
795
|
+
call_res = super().call(__context, __obj, *lazify_container_args(args), **lazify_container_kwargs(kwargs))
|
796
|
+
|
797
|
+
if __obj is range:
|
798
|
+
# Preserve the ability to do `range(1000000000) | random` by not converting range objects to lists.
|
799
|
+
# Historically, range objects were only converted on Jinja finalize and filter outputs, so they've always been floating around in templating
|
800
|
+
# code and visible to user plugins.
|
801
|
+
return call_res
|
802
|
+
|
803
|
+
return _wrap_plugin_output(call_res)
|
804
|
+
|
805
|
+
except MarkerError as ex:
|
806
|
+
return ex.source
|
807
|
+
|
808
|
+
|
809
|
+
AnsibleTemplate.environment_class = AnsibleEnvironment
|
810
|
+
|
811
|
+
_DEFAULT_UNDEF = UndefinedMarker("Mandatory variable has not been overridden", _no_template_source=True)
|
812
|
+
|
813
|
+
_sentinel: t.Final[object] = object()
|
814
|
+
|
815
|
+
|
816
|
+
@_DirectCall.mark
|
817
|
+
def _undef(hint=None):
|
818
|
+
"""Jinja2 global function (undef) for creating getting a `UndefinedMarker` instance, optionally with a custom hint."""
|
819
|
+
validate_arg_type('hint', hint, (str, type(None)))
|
820
|
+
|
821
|
+
if not hint:
|
822
|
+
return _DEFAULT_UNDEF
|
823
|
+
|
824
|
+
return UndefinedMarker(hint)
|
825
|
+
|
826
|
+
|
827
|
+
def _flatten_nodes(nodes: t.Iterable[t.Any]) -> t.Iterable[t.Any]:
|
828
|
+
"""
|
829
|
+
Yield nodes from a potentially recursive iterable of nodes.
|
830
|
+
The recursion is required to expand template imports (TemplateModule).
|
831
|
+
Any exception raised while consuming a template node will be yielded as a Marker for that node.
|
832
|
+
"""
|
833
|
+
iterator = iter(nodes)
|
834
|
+
|
835
|
+
while True:
|
836
|
+
try:
|
837
|
+
node = next(iterator)
|
838
|
+
except StopIteration:
|
839
|
+
break
|
840
|
+
except Exception as ex:
|
841
|
+
yield defer_template_error(ex, TemplateContext.current().template_value, is_expression=False)
|
842
|
+
# DTFIX-FUTURE: We should be able to determine if truncation occurred by having the code generator smuggle out the number of expected nodes.
|
843
|
+
yield TruncationMarker()
|
844
|
+
else:
|
845
|
+
if type(node) is TemplateModule: # pylint: disable=unidiomatic-typecheck
|
846
|
+
yield from _flatten_nodes(node._body_stream)
|
847
|
+
else:
|
848
|
+
yield node
|
849
|
+
|
850
|
+
|
851
|
+
def _flatten_and_lazify_vars(mapping: c.Mapping) -> t.Iterable[c.Mapping]:
|
852
|
+
"""Prevent deeply-nested Jinja vars ChainMaps from being created by nested contexts and ensure that all top-level containers support lazy templating."""
|
853
|
+
mapping_type = type(mapping)
|
854
|
+
if mapping_type is ChainMap:
|
855
|
+
# noinspection PyUnresolvedReferences
|
856
|
+
for m in mapping.maps:
|
857
|
+
yield from _flatten_and_lazify_vars(m)
|
858
|
+
elif mapping_type is _AnsibleLazyTemplateDict:
|
859
|
+
if not mapping:
|
860
|
+
# DTFIX-RELEASE: handle or remove?
|
861
|
+
raise Exception("we didn't think it was possible to have an empty lazy here...")
|
862
|
+
yield mapping
|
863
|
+
elif mapping_type in (dict, _AnsibleTaggedDict):
|
864
|
+
# don't propagate empty dictionary layers
|
865
|
+
if mapping:
|
866
|
+
yield _AnsibleLazyTemplateMixin._try_create(mapping)
|
867
|
+
else:
|
868
|
+
raise NotImplementedError(f"unsupported mapping type in Jinja vars: {mapping_type}")
|
869
|
+
|
870
|
+
|
871
|
+
def _new_context(
|
872
|
+
*,
|
873
|
+
environment: Environment,
|
874
|
+
template_name: str | None,
|
875
|
+
blocks: dict[str, t.Callable[[Context], c.Iterator[str]]],
|
876
|
+
shared: bool = False,
|
877
|
+
jinja_locals: c.Mapping[str, t.Any] | None = None,
|
878
|
+
jinja_vars: c.Mapping[str, t.Any] | None = None,
|
879
|
+
jinja_globals: c.MutableMapping[str, t.Any] | None = None,
|
880
|
+
) -> Context:
|
881
|
+
"""Override Jinja's context vars setup to use ChainMaps and containers that support lazy templating."""
|
882
|
+
layers = []
|
883
|
+
|
884
|
+
if jinja_locals:
|
885
|
+
# DTFIX-RELEASE: if we can't trip this in coverage, kill it off?
|
886
|
+
if type(jinja_locals) is not dict: # pylint: disable=unidiomatic-typecheck
|
887
|
+
raise NotImplementedError("locals must be a dict")
|
888
|
+
|
889
|
+
# Omit values set to Jinja's internal `missing` sentinel; they are locals that have not yet been
|
890
|
+
# initialized in the current context, and should not be exposed to child contexts. e.g.: {% import 'a' as b with context %}.
|
891
|
+
# The `b` local will be `missing` in the `a` context and should not be propagated as a local to the child context we're creating.
|
892
|
+
layers.append(_AnsibleLazyTemplateMixin._try_create({k: v for k, v in jinja_locals.items() if v is not missing}))
|
893
|
+
|
894
|
+
if jinja_vars:
|
895
|
+
layers.extend(_flatten_and_lazify_vars(jinja_vars))
|
896
|
+
|
897
|
+
if jinja_globals and not shared:
|
898
|
+
# Even though we don't currently support templating globals, it's easier to ensure that everything is template-able rather than trying to
|
899
|
+
# pick apart the ChainMaps to enforce non-template-able globals, or to risk things that *should* be template-able not being lazified.
|
900
|
+
layers.extend(_flatten_and_lazify_vars(jinja_globals))
|
901
|
+
|
902
|
+
if not layers:
|
903
|
+
# ensure we have at least one layer (which should be lazy), since _flatten_and_lazify_vars eliminates most empty layers
|
904
|
+
layers.append(_AnsibleLazyTemplateMixin._try_create({}))
|
905
|
+
|
906
|
+
# only return a ChainMap if we're combining layers, or we have none
|
907
|
+
parent = layers[0] if len(layers) == 1 else ChainMap(*layers)
|
908
|
+
|
909
|
+
# the `parent` cast is only to satisfy Jinja's overly-strict type hint
|
910
|
+
return environment.context_class(environment, t.cast(dict, parent), template_name, blocks, globals=jinja_globals)
|
911
|
+
|
912
|
+
|
913
|
+
def is_possibly_template(value: str, overrides: TemplateOverrides = TemplateOverrides.DEFAULT):
|
914
|
+
"""
|
915
|
+
A lightweight check to determine if the given string looks like it contains a template, even if that template is invalid.
|
916
|
+
Returns `True` if the given string starts with a Jinja overrides header or if it contains template start strings.
|
917
|
+
"""
|
918
|
+
return value.startswith(JINJA2_OVERRIDE) or overrides._contains_start_string(value)
|
919
|
+
|
920
|
+
|
921
|
+
def is_possibly_all_template(value: str, overrides: TemplateOverrides = TemplateOverrides.DEFAULT):
|
922
|
+
"""
|
923
|
+
A lightweight check to determine if the given string looks like it contains *only* a template, even if that template is invalid.
|
924
|
+
Returns `True` if the given string starts with a Jinja overrides header or if it starts and ends with Jinja template delimiters.
|
925
|
+
"""
|
926
|
+
return value.startswith(JINJA2_OVERRIDE) or overrides._starts_and_ends_with_jinja_delimiters(value)
|
927
|
+
|
928
|
+
|
929
|
+
class FinalizeMode(enum.Enum):
|
930
|
+
TOP_LEVEL = enum.auto()
|
931
|
+
CONCAT = enum.auto()
|
932
|
+
|
933
|
+
|
934
|
+
_FINALIZE_FAST_PATH_EXACT_MAPPING_TYPES = frozenset(
|
935
|
+
(
|
936
|
+
dict,
|
937
|
+
_AnsibleTaggedDict,
|
938
|
+
_AnsibleLazyTemplateDict,
|
939
|
+
HostVars,
|
940
|
+
HostVarsVars,
|
941
|
+
)
|
942
|
+
)
|
943
|
+
"""Fast-path exact mapping types for finalization. These types bypass diagnostic warnings for type conversion."""
|
944
|
+
|
945
|
+
_FINALIZE_FAST_PATH_EXACT_ITERABLE_TYPES = frozenset(
|
946
|
+
(
|
947
|
+
list,
|
948
|
+
_AnsibleTaggedList,
|
949
|
+
_AnsibleLazyTemplateList,
|
950
|
+
tuple,
|
951
|
+
_AnsibleTaggedTuple,
|
952
|
+
_AnsibleLazyAccessTuple,
|
953
|
+
)
|
954
|
+
)
|
955
|
+
"""Fast-path exact iterable types for finalization. These types bypass diagnostic warnings for type conversion."""
|
956
|
+
|
957
|
+
_FINALIZE_DISALLOWED_EXACT_TYPES = frozenset((range,))
|
958
|
+
"""Exact types that cannot be finalized."""
|
959
|
+
|
960
|
+
# Jinja passes these into filters/tests via @pass_environment
|
961
|
+
register_known_types(
|
962
|
+
AnsibleContext,
|
963
|
+
AnsibleEnvironment,
|
964
|
+
EvalContext,
|
965
|
+
)
|
966
|
+
|
967
|
+
|
968
|
+
def _finalize_dict(o: t.Any, mode: FinalizeMode) -> t.Iterator[tuple[t.Any, t.Any]]:
|
969
|
+
for k, v in o.items():
|
970
|
+
if v is not Omit:
|
971
|
+
yield _finalize_template_result(k, mode), _finalize_template_result(v, mode)
|
972
|
+
|
973
|
+
|
974
|
+
def _finalize_list(o: t.Any, mode: FinalizeMode) -> t.Iterator[t.Any]:
|
975
|
+
for v in o:
|
976
|
+
if v is not Omit:
|
977
|
+
yield _finalize_template_result(v, mode)
|
978
|
+
|
979
|
+
|
980
|
+
def _maybe_finalize_scalar(o: t.Any) -> t.Any:
|
981
|
+
# DTFIX-RELEASE: this should check all supported scalar subclasses, not just JSON ones (also, does the JSON serializer handle these cases?)
|
982
|
+
for target_type in _json_subclassable_scalar_types:
|
983
|
+
if not isinstance(o, target_type):
|
984
|
+
continue
|
985
|
+
|
986
|
+
match _TemplateConfig.unknown_type_conversion_handler.action:
|
987
|
+
# we don't want to show the object value, and it can't be Origin-tagged; send the current template value for best effort
|
988
|
+
case ErrorAction.WARNING:
|
989
|
+
display.warning(
|
990
|
+
msg=f'Type {native_type_name(o)!r} is unsupported in variable storage, converting to {native_type_name(target_type)!r}.',
|
991
|
+
obj=TemplateContext.current(optional=True).template_value,
|
992
|
+
)
|
993
|
+
case ErrorAction.ERROR:
|
994
|
+
raise AnsibleVariableTypeError.from_value(obj=TemplateContext.current(optional=True).template_value)
|
995
|
+
|
996
|
+
return target_type(o)
|
997
|
+
|
998
|
+
return None
|
999
|
+
|
1000
|
+
|
1001
|
+
def _finalize_fallback_collection(
|
1002
|
+
o: t.Any,
|
1003
|
+
mode: FinalizeMode,
|
1004
|
+
finalizer: t.Callable[[t.Any, FinalizeMode], t.Iterator],
|
1005
|
+
target_type: type[list | dict],
|
1006
|
+
) -> t.Collection[t.Any]:
|
1007
|
+
match _TemplateConfig.unknown_type_conversion_handler.action:
|
1008
|
+
# we don't want to show the object value, and it can't be Origin-tagged; send the current template value for best effort
|
1009
|
+
case ErrorAction.WARNING:
|
1010
|
+
display.warning(
|
1011
|
+
msg=f'Type {native_type_name(o)!r} is unsupported in variable storage, converting to {native_type_name(target_type)!r}.',
|
1012
|
+
obj=TemplateContext.current(optional=True).template_value,
|
1013
|
+
)
|
1014
|
+
case ErrorAction.ERROR:
|
1015
|
+
raise AnsibleVariableTypeError.from_value(obj=TemplateContext.current(optional=True).template_value)
|
1016
|
+
|
1017
|
+
return _finalize_collection(o, mode, finalizer, target_type)
|
1018
|
+
|
1019
|
+
|
1020
|
+
def _finalize_collection(
|
1021
|
+
o: t.Any,
|
1022
|
+
mode: FinalizeMode,
|
1023
|
+
finalizer: t.Callable[[t.Any, FinalizeMode], t.Iterator],
|
1024
|
+
target_type: type[list | dict],
|
1025
|
+
) -> t.Collection[t.Any]:
|
1026
|
+
return AnsibleTagHelper.tag(finalizer(o, mode), AnsibleTagHelper.tags(o), value_type=target_type)
|
1027
|
+
|
1028
|
+
|
1029
|
+
def _finalize_template_result(o: t.Any, mode: FinalizeMode) -> t.Any:
|
1030
|
+
"""Recurse the template result, rendering any encountered templates, converting containers to non-lazy versions."""
|
1031
|
+
# DTFIX-RELEASE: add tests to ensure this method doesn't drift from allowed types
|
1032
|
+
o_type = type(o)
|
1033
|
+
|
1034
|
+
# DTFIX-FUTURE: provide an optional way to check for trusted templates leaking out of templating (injected, but not passed through templar.template)
|
1035
|
+
|
1036
|
+
if o_type is _AnsibleTaggedStr:
|
1037
|
+
return _JinjaConstTemplate.untag(o) # prevent _JinjaConstTemplate from leaking into finalized results
|
1038
|
+
|
1039
|
+
if o_type in PASS_THROUGH_SCALAR_VAR_TYPES:
|
1040
|
+
return o
|
1041
|
+
|
1042
|
+
if o_type in _FINALIZE_FAST_PATH_EXACT_MAPPING_TYPES: # silently convert known mapping types to dict
|
1043
|
+
return _finalize_collection(o, mode, _finalize_dict, dict)
|
1044
|
+
|
1045
|
+
if o_type in _FINALIZE_FAST_PATH_EXACT_ITERABLE_TYPES: # silently convert known sequence types to list
|
1046
|
+
return _finalize_collection(o, mode, _finalize_list, list)
|
1047
|
+
|
1048
|
+
if o_type in Marker.concrete_subclasses: # this early return assumes handle_marker follows our variable type rules
|
1049
|
+
return TemplateContext.current().templar.marker_behavior.handle_marker(o)
|
1050
|
+
|
1051
|
+
if mode is not FinalizeMode.TOP_LEVEL: # unsupported type (do not raise)
|
1052
|
+
return o
|
1053
|
+
|
1054
|
+
if o_type in _FINALIZE_DISALLOWED_EXACT_TYPES: # early abort for disallowed types that would otherwise be handled below
|
1055
|
+
raise AnsibleVariableTypeError.from_value(obj=o)
|
1056
|
+
|
1057
|
+
if _internal.is_intermediate_mapping(o): # since isinstance checks are slower, this is separate from the exact type check above
|
1058
|
+
return _finalize_fallback_collection(o, mode, _finalize_dict, dict)
|
1059
|
+
|
1060
|
+
if _internal.is_intermediate_iterable(o): # since isinstance checks are slower, this is separate from the exact type check above
|
1061
|
+
return _finalize_fallback_collection(o, mode, _finalize_list, list)
|
1062
|
+
|
1063
|
+
if (result := _maybe_finalize_scalar(o)) is not None:
|
1064
|
+
return result
|
1065
|
+
|
1066
|
+
raise AnsibleVariableTypeError.from_value(obj=o)
|