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,91 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import contextlib
|
4
|
+
import enum
|
5
|
+
import typing as t
|
6
|
+
|
7
|
+
from ansible.utils.display import Display
|
8
|
+
from ansible.constants import config
|
9
|
+
|
10
|
+
display = Display()
|
11
|
+
|
12
|
+
# FUTURE: add sanity test to detect use of skip_on_ignore without Skippable (and vice versa)
|
13
|
+
|
14
|
+
|
15
|
+
class ErrorAction(enum.Enum):
|
16
|
+
"""Action to take when an error is encountered."""
|
17
|
+
|
18
|
+
IGNORE = enum.auto()
|
19
|
+
WARNING = enum.auto()
|
20
|
+
ERROR = enum.auto()
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
def from_config(cls, setting: str, variables: dict[str, t.Any] | None = None) -> t.Self:
|
24
|
+
"""Return an `ErrorAction` enum from the specified Ansible config setting."""
|
25
|
+
return cls[config.get_config_value(setting, variables=variables).upper()]
|
26
|
+
|
27
|
+
|
28
|
+
class _SkipException(BaseException):
|
29
|
+
"""Internal flow control exception for skipping code blocks within a `Skippable` context manager."""
|
30
|
+
|
31
|
+
def __init__(self) -> None:
|
32
|
+
super().__init__('Skipping ignored action due to use of `skip_on_ignore`. It is a bug to encounter this message outside of debugging.')
|
33
|
+
|
34
|
+
|
35
|
+
class _SkippableContextManager:
|
36
|
+
"""Internal context manager to support flow control for skipping code blocks."""
|
37
|
+
|
38
|
+
def __enter__(self) -> None:
|
39
|
+
pass
|
40
|
+
|
41
|
+
def __exit__(self, exc_type, _exc_val, _exc_tb) -> bool:
|
42
|
+
if exc_type is None:
|
43
|
+
raise RuntimeError('A `Skippable` context manager was entered, but a `skip_on_ignore` handler was never invoked.')
|
44
|
+
|
45
|
+
return exc_type is _SkipException # only mask a _SkipException, allow all others to raise
|
46
|
+
|
47
|
+
|
48
|
+
Skippable = _SkippableContextManager()
|
49
|
+
"""Context manager singleton required to enclose `ErrorHandler.handle` invocations when `skip_on_ignore` is `True`."""
|
50
|
+
|
51
|
+
|
52
|
+
class ErrorHandler:
|
53
|
+
"""
|
54
|
+
Provides a configurable error handler context manager for a specific list of exception types.
|
55
|
+
Unhandled errors leaving the context manager can be ignored, treated as warnings, or allowed to raise by setting `ErrorAction`.
|
56
|
+
"""
|
57
|
+
|
58
|
+
def __init__(self, action: ErrorAction) -> None:
|
59
|
+
self.action = action
|
60
|
+
|
61
|
+
@contextlib.contextmanager
|
62
|
+
def handle(self, *args: type[BaseException], skip_on_ignore: bool = False) -> t.Iterator[None]:
|
63
|
+
"""
|
64
|
+
Handle the specified exception(s) using the defined error action.
|
65
|
+
If `skip_on_ignore` is `True`, the body of the context manager will be skipped for `ErrorAction.IGNORE`.
|
66
|
+
Use of `skip_on_ignore` requires enclosure within the `Skippable` context manager.
|
67
|
+
"""
|
68
|
+
if not args:
|
69
|
+
raise ValueError('At least one exception type is required.')
|
70
|
+
|
71
|
+
if skip_on_ignore and self.action == ErrorAction.IGNORE:
|
72
|
+
raise _SkipException() # skipping ignored action
|
73
|
+
|
74
|
+
try:
|
75
|
+
yield
|
76
|
+
except args as ex:
|
77
|
+
match self.action:
|
78
|
+
case ErrorAction.WARNING:
|
79
|
+
display.error_as_warning(msg=None, exception=ex)
|
80
|
+
case ErrorAction.ERROR:
|
81
|
+
raise
|
82
|
+
case _: # ErrorAction.IGNORE
|
83
|
+
pass
|
84
|
+
|
85
|
+
if skip_on_ignore:
|
86
|
+
raise _SkipException() # completed skippable action, ensures the `Skippable` context was used
|
87
|
+
|
88
|
+
@classmethod
|
89
|
+
def from_config(cls, setting: str, variables: dict[str, t.Any] | None = None) -> t.Self:
|
90
|
+
"""Return an `ErrorHandler` instance configured using the specified Ansible config setting."""
|
91
|
+
return cls(ErrorAction.from_config(setting, variables=variables))
|
@@ -0,0 +1,310 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import dataclasses
|
4
|
+
import itertools
|
5
|
+
import pathlib
|
6
|
+
import sys
|
7
|
+
import textwrap
|
8
|
+
import typing as t
|
9
|
+
|
10
|
+
from ansible.module_utils.common.messages import Detail, ErrorSummary
|
11
|
+
from ansible._internal._datatag._tags import Origin
|
12
|
+
from ansible.module_utils._internal import _ambient_context, _traceback
|
13
|
+
from ansible import errors
|
14
|
+
|
15
|
+
if t.TYPE_CHECKING:
|
16
|
+
from ansible.utils.display import Display
|
17
|
+
|
18
|
+
|
19
|
+
class RedactAnnotatedSourceContext(_ambient_context.AmbientContextBase):
|
20
|
+
"""
|
21
|
+
When active, this context will redact annotated source lines, showing only the origin.
|
22
|
+
"""
|
23
|
+
|
24
|
+
|
25
|
+
def _dedupe_and_concat_message_chain(message_parts: list[str]) -> str:
|
26
|
+
message_parts = list(reversed(message_parts))
|
27
|
+
|
28
|
+
message = message_parts.pop(0)
|
29
|
+
|
30
|
+
for message_part in message_parts:
|
31
|
+
# avoid duplicate messages where the cause was already concatenated to the exception message
|
32
|
+
if message_part.endswith(message):
|
33
|
+
message = message_part
|
34
|
+
else:
|
35
|
+
message = concat_message(message_part, message)
|
36
|
+
|
37
|
+
return message
|
38
|
+
|
39
|
+
|
40
|
+
def _collapse_error_details(error_details: t.Sequence[Detail]) -> list[Detail]:
|
41
|
+
"""
|
42
|
+
Return a potentially modified error chain, with redundant errors collapsed into previous error(s) in the chain.
|
43
|
+
This reduces the verbosity of messages by eliminating repetition when multiple errors in the chain share the same contextual information.
|
44
|
+
"""
|
45
|
+
previous_error = error_details[0]
|
46
|
+
previous_warnings: list[str] = []
|
47
|
+
collapsed_error_details: list[tuple[Detail, list[str]]] = [(previous_error, previous_warnings)]
|
48
|
+
|
49
|
+
for error in error_details[1:]:
|
50
|
+
details_present = error.formatted_source_context or error.help_text
|
51
|
+
details_changed = error.formatted_source_context != previous_error.formatted_source_context or error.help_text != previous_error.help_text
|
52
|
+
|
53
|
+
if details_present and details_changed:
|
54
|
+
previous_error = error
|
55
|
+
previous_warnings = []
|
56
|
+
collapsed_error_details.append((previous_error, previous_warnings))
|
57
|
+
else:
|
58
|
+
previous_warnings.append(error.msg)
|
59
|
+
|
60
|
+
final_error_details: list[Detail] = []
|
61
|
+
|
62
|
+
for error, messages in collapsed_error_details:
|
63
|
+
final_error_details.append(dataclasses.replace(error, msg=_dedupe_and_concat_message_chain([error.msg] + messages)))
|
64
|
+
|
65
|
+
return final_error_details
|
66
|
+
|
67
|
+
|
68
|
+
def _get_cause(exception: BaseException) -> BaseException | None:
|
69
|
+
# deprecated: description='remove support for orig_exc (deprecated in 2.23)' core_version='2.27'
|
70
|
+
|
71
|
+
if not isinstance(exception, errors.AnsibleError):
|
72
|
+
return exception.__cause__
|
73
|
+
|
74
|
+
if exception.__cause__:
|
75
|
+
if exception.orig_exc and exception.orig_exc is not exception.__cause__:
|
76
|
+
_get_display().warning(
|
77
|
+
msg=f"The `orig_exc` argument to `{type(exception).__name__}` was given, but differed from the cause given by `raise ... from`.",
|
78
|
+
)
|
79
|
+
|
80
|
+
return exception.__cause__
|
81
|
+
|
82
|
+
if exception.orig_exc:
|
83
|
+
# encourage the use of `raise ... from` before deprecating `orig_exc`
|
84
|
+
_get_display().warning(msg=f"The `orig_exc` argument to `{type(exception).__name__}` was given without using `raise ... from orig_exc`.")
|
85
|
+
|
86
|
+
return exception.orig_exc
|
87
|
+
|
88
|
+
return None
|
89
|
+
|
90
|
+
|
91
|
+
class _TemporaryDisplay:
|
92
|
+
# DTFIX-FUTURE: generalize this and hide it in the display module so all users of Display can benefit
|
93
|
+
|
94
|
+
@staticmethod
|
95
|
+
def warning(*args, **kwargs):
|
96
|
+
print(f'FALLBACK WARNING: {args} {kwargs}', file=sys.stderr)
|
97
|
+
|
98
|
+
@staticmethod
|
99
|
+
def deprecated(*args, **kwargs):
|
100
|
+
print(f'FALLBACK DEPRECATION: {args} {kwargs}', file=sys.stderr)
|
101
|
+
|
102
|
+
|
103
|
+
def _get_display() -> Display | _TemporaryDisplay:
|
104
|
+
try:
|
105
|
+
from ansible.utils.display import Display
|
106
|
+
except ImportError:
|
107
|
+
return _TemporaryDisplay()
|
108
|
+
|
109
|
+
return Display()
|
110
|
+
|
111
|
+
|
112
|
+
def _create_error_summary(exception: BaseException, event: _traceback.TracebackEvent | None = None) -> ErrorSummary:
|
113
|
+
from . import _captured # avoid circular import due to AnsibleError import
|
114
|
+
|
115
|
+
current_exception: BaseException | None = exception
|
116
|
+
error_details: list[Detail] = []
|
117
|
+
|
118
|
+
if event:
|
119
|
+
formatted_traceback = _traceback.maybe_extract_traceback(exception, event)
|
120
|
+
else:
|
121
|
+
formatted_traceback = None
|
122
|
+
|
123
|
+
while current_exception:
|
124
|
+
if isinstance(current_exception, errors.AnsibleError):
|
125
|
+
include_cause_message = current_exception._include_cause_message
|
126
|
+
edc = Detail(
|
127
|
+
msg=current_exception._original_message.strip(),
|
128
|
+
formatted_source_context=current_exception._formatted_source_context,
|
129
|
+
help_text=current_exception._help_text,
|
130
|
+
)
|
131
|
+
else:
|
132
|
+
include_cause_message = True
|
133
|
+
edc = Detail(
|
134
|
+
msg=str(current_exception).strip(),
|
135
|
+
)
|
136
|
+
|
137
|
+
error_details.append(edc)
|
138
|
+
|
139
|
+
if isinstance(current_exception, _captured.AnsibleCapturedError):
|
140
|
+
detail = current_exception.error_summary
|
141
|
+
error_details.extend(detail.details)
|
142
|
+
|
143
|
+
if formatted_traceback and detail.formatted_traceback:
|
144
|
+
formatted_traceback = (
|
145
|
+
f'{detail.formatted_traceback}\n'
|
146
|
+
f'The {current_exception.context} exception above was the direct cause of the following controller exception:\n\n'
|
147
|
+
f'{formatted_traceback}'
|
148
|
+
)
|
149
|
+
|
150
|
+
if not include_cause_message:
|
151
|
+
break
|
152
|
+
|
153
|
+
current_exception = _get_cause(current_exception)
|
154
|
+
|
155
|
+
return ErrorSummary(details=tuple(error_details), formatted_traceback=formatted_traceback)
|
156
|
+
|
157
|
+
|
158
|
+
def concat_message(left: str, right: str) -> str:
|
159
|
+
"""Normalize `left` by removing trailing punctuation and spaces before appending new punctuation and `right`."""
|
160
|
+
return f'{left.rstrip(". ")}: {right}'
|
161
|
+
|
162
|
+
|
163
|
+
def get_chained_message(exception: BaseException) -> str:
|
164
|
+
"""
|
165
|
+
Return the full chain of exception messages by concatenating the cause(s) until all are exhausted.
|
166
|
+
"""
|
167
|
+
error_summary = _create_error_summary(exception)
|
168
|
+
message_parts = [edc.msg for edc in error_summary.details]
|
169
|
+
|
170
|
+
return _dedupe_and_concat_message_chain(message_parts)
|
171
|
+
|
172
|
+
|
173
|
+
@dataclasses.dataclass(kw_only=True, frozen=True)
|
174
|
+
class SourceContext:
|
175
|
+
origin: Origin
|
176
|
+
annotated_source_lines: list[str]
|
177
|
+
target_line: str | None
|
178
|
+
|
179
|
+
def __str__(self) -> str:
|
180
|
+
msg_lines = [f'Origin: {self.origin}']
|
181
|
+
|
182
|
+
if self.annotated_source_lines:
|
183
|
+
msg_lines.append('')
|
184
|
+
msg_lines.extend(self.annotated_source_lines)
|
185
|
+
|
186
|
+
return '\n'.join(msg_lines)
|
187
|
+
|
188
|
+
@classmethod
|
189
|
+
def from_value(cls, value: t.Any) -> SourceContext | None:
|
190
|
+
"""Attempt to retrieve source and render a contextual indicator from the value's origin (if any)."""
|
191
|
+
if value is None:
|
192
|
+
return None
|
193
|
+
|
194
|
+
if isinstance(value, Origin):
|
195
|
+
origin = value
|
196
|
+
value = None
|
197
|
+
else:
|
198
|
+
origin = Origin.get_tag(value)
|
199
|
+
|
200
|
+
if RedactAnnotatedSourceContext.current(optional=True):
|
201
|
+
return cls.error('content redacted')
|
202
|
+
|
203
|
+
if origin and origin.path:
|
204
|
+
return cls.from_origin(origin)
|
205
|
+
|
206
|
+
# DTFIX-RELEASE: redaction context may not be sufficient to avoid secret disclosure without SensitiveData and other enhancements
|
207
|
+
if value is None:
|
208
|
+
truncated_value = None
|
209
|
+
annotated_source_lines = []
|
210
|
+
else:
|
211
|
+
# DTFIX-FUTURE: cleanup/share width
|
212
|
+
try:
|
213
|
+
value = str(value)
|
214
|
+
except Exception as ex:
|
215
|
+
value = f'<< context unavailable: {ex} >>'
|
216
|
+
|
217
|
+
truncated_value = textwrap.shorten(value, width=120)
|
218
|
+
annotated_source_lines = [truncated_value]
|
219
|
+
|
220
|
+
return SourceContext(
|
221
|
+
origin=origin or Origin.UNKNOWN,
|
222
|
+
annotated_source_lines=annotated_source_lines,
|
223
|
+
target_line=truncated_value,
|
224
|
+
)
|
225
|
+
|
226
|
+
@staticmethod
|
227
|
+
def error(message: str | None, origin: Origin | None = None) -> SourceContext:
|
228
|
+
return SourceContext(
|
229
|
+
origin=origin,
|
230
|
+
annotated_source_lines=[f'(source not shown: {message})'] if message else [],
|
231
|
+
target_line=None,
|
232
|
+
)
|
233
|
+
|
234
|
+
@classmethod
|
235
|
+
def from_origin(cls, origin: Origin) -> SourceContext:
|
236
|
+
"""Attempt to retrieve source and render a contextual indicator of an error location."""
|
237
|
+
from ansible.parsing.vault import is_encrypted # avoid circular import
|
238
|
+
|
239
|
+
# DTFIX-FUTURE: support referencing the column after the end of the target line, so we can indicate where a missing character (quote) needs to be added
|
240
|
+
# this is also useful for cases like end-of-stream reported by the YAML parser
|
241
|
+
|
242
|
+
# DTFIX-FUTURE: Implement line wrapping and match annotated line width to the terminal display width.
|
243
|
+
|
244
|
+
context_line_count: t.Final = 2
|
245
|
+
max_annotated_line_width: t.Final = 120
|
246
|
+
truncation_marker: t.Final = '...'
|
247
|
+
|
248
|
+
target_line_num = origin.line_num
|
249
|
+
|
250
|
+
if RedactAnnotatedSourceContext.current(optional=True):
|
251
|
+
return cls.error('content redacted', origin)
|
252
|
+
|
253
|
+
if not target_line_num or target_line_num < 1:
|
254
|
+
return cls.error(None, origin) # message omitted since lack of line number is obvious from pos
|
255
|
+
|
256
|
+
start_line_idx = max(0, (target_line_num - 1) - context_line_count) # if near start of file
|
257
|
+
target_col_num = origin.col_num
|
258
|
+
|
259
|
+
try:
|
260
|
+
with pathlib.Path(origin.path).open() as src:
|
261
|
+
first_line = src.readline()
|
262
|
+
lines = list(itertools.islice(itertools.chain((first_line,), src), start_line_idx, target_line_num))
|
263
|
+
except Exception as ex:
|
264
|
+
return cls.error(type(ex).__name__, origin)
|
265
|
+
|
266
|
+
if is_encrypted(first_line):
|
267
|
+
return cls.error('content encrypted', origin)
|
268
|
+
|
269
|
+
if len(lines) != target_line_num - start_line_idx:
|
270
|
+
return cls.error('file truncated', origin)
|
271
|
+
|
272
|
+
annotated_source_lines = []
|
273
|
+
|
274
|
+
line_label_width = len(str(target_line_num))
|
275
|
+
max_src_line_len = max_annotated_line_width - line_label_width - 1
|
276
|
+
|
277
|
+
usable_line_len = max_src_line_len
|
278
|
+
|
279
|
+
for line_num, line in enumerate(lines, start_line_idx + 1):
|
280
|
+
line = line.rstrip('\n') # universal newline default mode on `open` ensures we'll never see anything but \n
|
281
|
+
line = line.replace('\t', ' ') # mixed tab/space handling is intentionally disabled since we're both format and display config agnostic
|
282
|
+
|
283
|
+
if len(line) > max_src_line_len:
|
284
|
+
line = line[: max_src_line_len - len(truncation_marker)] + truncation_marker
|
285
|
+
usable_line_len = max_src_line_len - len(truncation_marker)
|
286
|
+
|
287
|
+
annotated_source_lines.append(f'{str(line_num).rjust(line_label_width)}{" " if line else ""}{line}')
|
288
|
+
|
289
|
+
if target_col_num and usable_line_len >= target_col_num >= 1:
|
290
|
+
column_marker = f'column {target_col_num}'
|
291
|
+
|
292
|
+
target_col_idx = target_col_num - 1
|
293
|
+
|
294
|
+
if target_col_idx + 2 + len(column_marker) > max_src_line_len:
|
295
|
+
column_marker = f'{" " * (target_col_idx - len(column_marker) - 1)}{column_marker} ^'
|
296
|
+
else:
|
297
|
+
column_marker = f'{" " * target_col_idx}^ {column_marker}'
|
298
|
+
|
299
|
+
column_marker = f'{" " * line_label_width} {column_marker}'
|
300
|
+
|
301
|
+
annotated_source_lines.append(column_marker)
|
302
|
+
elif target_col_num is None:
|
303
|
+
underline_length = len(annotated_source_lines[-1]) - line_label_width - 1
|
304
|
+
annotated_source_lines.append(f'{" " * line_label_width} {"^" * underline_length}')
|
305
|
+
|
306
|
+
return SourceContext(
|
307
|
+
origin=origin,
|
308
|
+
annotated_source_lines=annotated_source_lines,
|
309
|
+
target_line=lines[-1].rstrip('\n'), # universal newline default mode on `open` ensures we'll never see anything but \n
|
310
|
+
)
|
@@ -0,0 +1,203 @@
|
|
1
|
+
"""Internal utilities for serialization and deserialization."""
|
2
|
+
|
3
|
+
# DTFIX-RELEASE: most of this isn't JSON specific, find a better home
|
4
|
+
|
5
|
+
from __future__ import annotations
|
6
|
+
|
7
|
+
import enum
|
8
|
+
import json
|
9
|
+
import typing as t
|
10
|
+
|
11
|
+
from ansible.errors import AnsibleVariableTypeError
|
12
|
+
|
13
|
+
from ansible.module_utils._internal._datatag import (
|
14
|
+
_ANSIBLE_ALLOWED_MAPPING_VAR_TYPES,
|
15
|
+
_ANSIBLE_ALLOWED_NON_SCALAR_COLLECTION_VAR_TYPES,
|
16
|
+
_ANSIBLE_ALLOWED_VAR_TYPES,
|
17
|
+
_AnsibleTaggedStr,
|
18
|
+
AnsibleTagHelper,
|
19
|
+
)
|
20
|
+
from ansible.module_utils._internal._json._profiles import _tagless
|
21
|
+
from ansible.parsing.vault import EncryptedString
|
22
|
+
from ansible._internal._datatag._tags import Origin, TrustedAsTemplate
|
23
|
+
from ansible._internal._templating import _transform
|
24
|
+
from ansible.module_utils import _internal
|
25
|
+
from ansible.module_utils._internal import _datatag
|
26
|
+
|
27
|
+
_T = t.TypeVar('_T')
|
28
|
+
_sentinel = object()
|
29
|
+
|
30
|
+
|
31
|
+
class HasCurrent(t.Protocol):
|
32
|
+
"""Utility protocol for mixin type safety."""
|
33
|
+
|
34
|
+
_current: t.Any
|
35
|
+
|
36
|
+
|
37
|
+
class StateTrackingMixIn(HasCurrent):
|
38
|
+
"""Mixin for use with `AnsibleVariableVisitor` to track current visitation context."""
|
39
|
+
|
40
|
+
def __init__(self, *args, **kwargs) -> None:
|
41
|
+
super().__init__(*args, **kwargs)
|
42
|
+
|
43
|
+
self._stack: list[t.Any] = []
|
44
|
+
|
45
|
+
def __enter__(self) -> None:
|
46
|
+
self._stack.append(self._current)
|
47
|
+
|
48
|
+
def __exit__(self, *_args, **_kwargs) -> None:
|
49
|
+
self._stack.pop()
|
50
|
+
|
51
|
+
def _get_stack(self) -> list[t.Any]:
|
52
|
+
if not self._stack:
|
53
|
+
return []
|
54
|
+
|
55
|
+
return self._stack[1:] + [self._current]
|
56
|
+
|
57
|
+
|
58
|
+
class EncryptedStringBehavior(enum.Enum):
|
59
|
+
"""How `AnsibleVariableVisitor` will handle instances of `EncryptedString`."""
|
60
|
+
|
61
|
+
PRESERVE = enum.auto()
|
62
|
+
"""Preserves the unmodified `EncryptedString` instance."""
|
63
|
+
DECRYPT = enum.auto()
|
64
|
+
"""Replaces the value with its decrypted plaintext."""
|
65
|
+
REDACT = enum.auto()
|
66
|
+
"""Replaces the value with a placeholder string."""
|
67
|
+
FAIL = enum.auto()
|
68
|
+
"""Raises an `AnsibleVariableTypeError` error."""
|
69
|
+
|
70
|
+
|
71
|
+
class AnsibleVariableVisitor:
|
72
|
+
"""Utility visitor base class to recursively apply various behaviors and checks to variable object graphs."""
|
73
|
+
|
74
|
+
def __init__(
|
75
|
+
self,
|
76
|
+
*,
|
77
|
+
trusted_as_template: bool = False,
|
78
|
+
origin: Origin | None = None,
|
79
|
+
convert_mapping_to_dict: bool = False,
|
80
|
+
convert_sequence_to_list: bool = False,
|
81
|
+
convert_custom_scalars: bool = False,
|
82
|
+
convert_to_native_values: bool = False,
|
83
|
+
apply_transforms: bool = False,
|
84
|
+
encrypted_string_behavior: EncryptedStringBehavior = EncryptedStringBehavior.DECRYPT,
|
85
|
+
):
|
86
|
+
super().__init__() # supports StateTrackingMixIn
|
87
|
+
|
88
|
+
self.trusted_as_template = trusted_as_template
|
89
|
+
self.origin = origin
|
90
|
+
self.convert_mapping_to_dict = convert_mapping_to_dict
|
91
|
+
self.convert_sequence_to_list = convert_sequence_to_list
|
92
|
+
self.convert_custom_scalars = convert_custom_scalars
|
93
|
+
self.convert_to_native_values = convert_to_native_values
|
94
|
+
self.apply_transforms = apply_transforms
|
95
|
+
self.encrypted_string_behavior = encrypted_string_behavior
|
96
|
+
|
97
|
+
if apply_transforms:
|
98
|
+
from ansible._internal._templating import _engine
|
99
|
+
|
100
|
+
self._template_engine = _engine.TemplateEngine()
|
101
|
+
else:
|
102
|
+
self._template_engine = None
|
103
|
+
|
104
|
+
self._current: t.Any = None # supports StateTrackingMixIn
|
105
|
+
|
106
|
+
def __enter__(self) -> t.Any:
|
107
|
+
"""No-op context manager dispatcher (delegates to mixin behavior if present)."""
|
108
|
+
if func := getattr(super(), '__enter__', None):
|
109
|
+
func()
|
110
|
+
|
111
|
+
def __exit__(self, *args, **kwargs) -> t.Any:
|
112
|
+
"""No-op context manager dispatcher (delegates to mixin behavior if present)."""
|
113
|
+
if func := getattr(super(), '__exit__', None):
|
114
|
+
func(*args, **kwargs)
|
115
|
+
|
116
|
+
def visit(self, value: _T) -> _T:
|
117
|
+
"""
|
118
|
+
Enforces Ansible's variable type system restrictions before a var is accepted in inventory. Also, conditionally implements template trust
|
119
|
+
compatibility, depending on the plugin's declared understanding (or lack thereof). This always recursively copies inputs to fully isolate
|
120
|
+
inventory data from what the plugin provided, and prevent any later mutation.
|
121
|
+
"""
|
122
|
+
return self._visit(None, value)
|
123
|
+
|
124
|
+
def _early_visit(self, value, value_type) -> t.Any:
|
125
|
+
"""Overridable hook point to allow custom string handling in derived visitors."""
|
126
|
+
if value_type in (str, _AnsibleTaggedStr):
|
127
|
+
# apply compatibility behavior
|
128
|
+
if self.trusted_as_template:
|
129
|
+
result = TrustedAsTemplate().tag(value)
|
130
|
+
else:
|
131
|
+
result = value
|
132
|
+
else:
|
133
|
+
result = _sentinel
|
134
|
+
|
135
|
+
return result
|
136
|
+
|
137
|
+
def _visit(self, key: t.Any, value: _T) -> _T:
|
138
|
+
"""Internal implementation to recursively visit a data structure's contents."""
|
139
|
+
self._current = key # supports StateTrackingMixIn
|
140
|
+
|
141
|
+
value_type = type(value)
|
142
|
+
|
143
|
+
if self.apply_transforms and value_type in _transform._type_transform_mapping:
|
144
|
+
value = self._template_engine.transform(value)
|
145
|
+
value_type = type(value)
|
146
|
+
|
147
|
+
# DTFIX-RELEASE: need to handle native copy for keys too
|
148
|
+
if self.convert_to_native_values and isinstance(value, _datatag.AnsibleTaggedObject):
|
149
|
+
value = value._native_copy()
|
150
|
+
value_type = type(value)
|
151
|
+
|
152
|
+
result: _T
|
153
|
+
|
154
|
+
# DTFIX-RELEASE: the visitor is ignoring dict/mapping keys except for debugging and schema-aware checking, it should be doing type checks on keys
|
155
|
+
# keep in mind the allowed types for keys is a more restrictive set than for values (str and taggged str only, not EncryptedString)
|
156
|
+
# DTFIX-RELEASE: some type lists being consulted (the ones from datatag) are probably too permissive, and perhaps should not be dynamic
|
157
|
+
|
158
|
+
if (result := self._early_visit(value, value_type)) is not _sentinel:
|
159
|
+
pass
|
160
|
+
# DTFIX-RELEASE: de-duplicate and optimize; extract inline generator expressions and fallback function or mapping for native type calculation?
|
161
|
+
elif value_type in _ANSIBLE_ALLOWED_MAPPING_VAR_TYPES: # check mappings first, because they're also collections
|
162
|
+
with self: # supports StateTrackingMixIn
|
163
|
+
result = AnsibleTagHelper.tag_copy(value, ((k, self._visit(k, v)) for k, v in value.items()), value_type=value_type)
|
164
|
+
elif value_type in _ANSIBLE_ALLOWED_NON_SCALAR_COLLECTION_VAR_TYPES:
|
165
|
+
with self: # supports StateTrackingMixIn
|
166
|
+
result = AnsibleTagHelper.tag_copy(value, (self._visit(k, v) for k, v in enumerate(t.cast(t.Iterable, value))), value_type=value_type)
|
167
|
+
elif self.encrypted_string_behavior != EncryptedStringBehavior.FAIL and isinstance(value, EncryptedString):
|
168
|
+
match self.encrypted_string_behavior:
|
169
|
+
case EncryptedStringBehavior.REDACT:
|
170
|
+
result = "<redacted>" # type: ignore[assignment]
|
171
|
+
case EncryptedStringBehavior.PRESERVE:
|
172
|
+
result = value # type: ignore[assignment]
|
173
|
+
case EncryptedStringBehavior.DECRYPT:
|
174
|
+
result = str(value) # type: ignore[assignment]
|
175
|
+
elif self.convert_mapping_to_dict and _internal.is_intermediate_mapping(value):
|
176
|
+
with self: # supports StateTrackingMixIn
|
177
|
+
result = {k: self._visit(k, v) for k, v in value.items()} # type: ignore[assignment]
|
178
|
+
elif self.convert_sequence_to_list and _internal.is_intermediate_iterable(value):
|
179
|
+
with self: # supports StateTrackingMixIn
|
180
|
+
result = [self._visit(k, v) for k, v in enumerate(t.cast(t.Iterable, value))] # type: ignore[assignment]
|
181
|
+
elif self.convert_custom_scalars and isinstance(value, str):
|
182
|
+
result = str(value) # type: ignore[assignment]
|
183
|
+
elif self.convert_custom_scalars and isinstance(value, float):
|
184
|
+
result = float(value) # type: ignore[assignment]
|
185
|
+
elif self.convert_custom_scalars and isinstance(value, int) and not isinstance(value, bool):
|
186
|
+
result = int(value) # type: ignore[assignment]
|
187
|
+
else:
|
188
|
+
if value_type not in _ANSIBLE_ALLOWED_VAR_TYPES:
|
189
|
+
raise AnsibleVariableTypeError.from_value(obj=value)
|
190
|
+
|
191
|
+
# supported scalar type that requires no special handling, just return as-is
|
192
|
+
result = value
|
193
|
+
|
194
|
+
if self.origin and not Origin.is_tagged_on(result):
|
195
|
+
# apply shared instance default origin tag
|
196
|
+
result = self.origin.tag(result)
|
197
|
+
|
198
|
+
return result
|
199
|
+
|
200
|
+
|
201
|
+
def json_dumps_formatted(value: object) -> str:
|
202
|
+
"""Return a JSON dump of `value` with formatting and keys sorted."""
|
203
|
+
return json.dumps(value, cls=_tagless.Encoder, sort_keys=True, indent=4)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from __future__ import annotations as _annotations
|
2
|
+
|
3
|
+
import typing as _t
|
4
|
+
|
5
|
+
from ansible.module_utils._internal._json import _profiles
|
6
|
+
from ansible._internal._json._profiles import _legacy
|
7
|
+
from ansible.parsing import vault as _vault
|
8
|
+
|
9
|
+
|
10
|
+
class LegacyControllerJSONEncoder(_legacy.Encoder):
|
11
|
+
"""Compatibility wrapper over `legacy` profile JSON encoder to support trust stripping and vault value plaintext conversion."""
|
12
|
+
|
13
|
+
def __init__(self, preprocess_unsafe: bool = False, vault_to_text: bool = False, _decode_bytes: bool = False, **kwargs) -> None:
|
14
|
+
self._preprocess_unsafe = preprocess_unsafe
|
15
|
+
self._vault_to_text = vault_to_text
|
16
|
+
self._decode_bytes = _decode_bytes
|
17
|
+
|
18
|
+
super().__init__(**kwargs)
|
19
|
+
|
20
|
+
def default(self, o: _t.Any) -> _t.Any:
|
21
|
+
"""Hooked default that can conditionally bypass base encoder behavior based on this instance's config."""
|
22
|
+
if type(o) is _profiles._WrappedValue: # pylint: disable=unidiomatic-typecheck
|
23
|
+
o = o.wrapped
|
24
|
+
|
25
|
+
if not self._preprocess_unsafe and type(o) is _legacy._Untrusted: # pylint: disable=unidiomatic-typecheck
|
26
|
+
return o.value # if not emitting unsafe markers, bypass custom unsafe serialization and just return the raw value
|
27
|
+
|
28
|
+
if self._vault_to_text and type(o) is _vault.EncryptedString: # pylint: disable=unidiomatic-typecheck
|
29
|
+
return str(o) # decrypt and return the plaintext (or fail trying)
|
30
|
+
|
31
|
+
if self._decode_bytes and isinstance(o, bytes):
|
32
|
+
return o.decode(errors='surrogateescape') # backward compatibility with `ansible.module_utils.basic.jsonify`
|
33
|
+
|
34
|
+
return super().default(o)
|
File without changes
|