ansible-core 2.18.7__py3-none-any.whl → 2.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ansible-core might be problematic. Click here for more details.
- ansible/_internal/__init__.py +53 -0
- ansible/_internal/_ansiballz/__init__.py +0 -0
- ansible/_internal/_ansiballz/_builder.py +101 -0
- ansible/_internal/_ansiballz/_wrapper.py +262 -0
- ansible/_internal/_collection_proxy.py +47 -0
- ansible/_internal/_datatag/__init__.py +0 -0
- ansible/_internal/_datatag/_tags.py +130 -0
- ansible/_internal/_datatag/_utils.py +19 -0
- ansible/_internal/_datatag/_wrappers.py +33 -0
- ansible/_internal/_errors/__init__.py +0 -0
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +123 -0
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_handler.py +91 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +214 -0
- ansible/_internal/_json/_legacy_encoder.py +34 -0
- ansible/_internal/_json/_profiles/__init__.py +0 -0
- ansible/_internal/_json/_profiles/_cache_persistence.py +57 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
- ansible/_internal/_json/_profiles/_legacy.py +189 -0
- ansible/_internal/_locking.py +21 -0
- ansible/_internal/_plugins/__init__.py +0 -0
- ansible/_internal/_plugins/_cache.py +57 -0
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/_internal/_ssh/_ssh_agent.py +619 -0
- ansible/_internal/_task.py +78 -0
- ansible/_internal/_templating/__init__.py +12 -0
- ansible/_internal/_templating/_access.py +86 -0
- ansible/_internal/_templating/_chain_templar.py +63 -0
- ansible/_internal/_templating/_datatag.py +95 -0
- ansible/_internal/_templating/_engine.py +592 -0
- ansible/_internal/_templating/_errors.py +28 -0
- ansible/_internal/_templating/_jinja_bits.py +1106 -0
- ansible/_internal/_templating/_jinja_common.py +323 -0
- ansible/_internal/_templating/_jinja_patches.py +44 -0
- ansible/_internal/_templating/_jinja_plugins.py +375 -0
- ansible/_internal/_templating/_lazy_containers.py +633 -0
- ansible/_internal/_templating/_marker_behaviors.py +103 -0
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +70 -0
- ansible/_internal/_templating/_utils.py +108 -0
- ansible/_internal/_testing.py +26 -0
- ansible/_internal/_wrapt.py +1052 -0
- ansible/_internal/_yaml/__init__.py +0 -0
- ansible/_internal/_yaml/_constructor.py +240 -0
- ansible/_internal/_yaml/_dumper.py +70 -0
- ansible/_internal/_yaml/_errors.py +166 -0
- ansible/_internal/_yaml/_loader.py +66 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/README.md +11 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/action/debug.py +36 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/apply_trust.py +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +27 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/finalize.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/origin.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.py +24 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.yml +33 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/tag_names.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +17 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +49 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.py +21 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.yml +2 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.py +15 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.yml +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.yml +19 -0
- ansible/cli/__init__.py +93 -104
- ansible/cli/_ssh_askpass.py +54 -0
- ansible/cli/adhoc.py +20 -10
- ansible/cli/arguments/option_helpers.py +163 -10
- ansible/cli/config.py +43 -68
- ansible/cli/console.py +13 -11
- ansible/cli/doc.py +134 -77
- ansible/cli/galaxy.py +27 -20
- ansible/cli/inventory.py +28 -28
- ansible/cli/playbook.py +4 -12
- ansible/cli/pull.py +6 -3
- ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
- ansible/cli/vault.py +12 -11
- ansible/compat/__init__.py +2 -2
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +218 -133
- ansible/config/manager.py +220 -159
- ansible/constants.py +2 -65
- ansible/errors/__init__.py +350 -256
- ansible/executor/interpreter_discovery.py +28 -149
- ansible/executor/module_common.py +480 -514
- ansible/executor/play_iterator.py +22 -27
- ansible/executor/playbook_executor.py +11 -11
- ansible/executor/powershell/async_watchdog.ps1 +97 -102
- ansible/executor/powershell/async_wrapper.ps1 +204 -153
- ansible/executor/powershell/become_wrapper.ps1 +107 -144
- ansible/executor/powershell/bootstrap_wrapper.ps1 +46 -9
- ansible/executor/powershell/coverage_wrapper.ps1 +91 -135
- ansible/executor/powershell/exec_wrapper.ps1 +675 -196
- ansible/executor/powershell/module_manifest.py +469 -265
- ansible/executor/powershell/module_wrapper.ps1 +195 -186
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/powershell/psrp_fetch_file.ps1 +41 -0
- ansible/executor/powershell/psrp_put_file.ps1 +122 -0
- ansible/executor/powershell/winrm_fetch_file.ps1 +46 -0
- ansible/executor/powershell/winrm_put_file.ps1 +36 -0
- ansible/executor/process/worker.py +139 -149
- ansible/executor/stats.py +5 -5
- ansible/executor/task_executor.py +270 -297
- ansible/executor/task_queue_manager.py +135 -137
- ansible/executor/task_result.py +182 -79
- ansible/galaxy/__init__.py +2 -2
- ansible/galaxy/api.py +26 -25
- ansible/galaxy/collection/__init__.py +6 -14
- ansible/galaxy/collection/concrete_artifact_manager.py +12 -21
- ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
- ansible/galaxy/dependency_resolution/providers.py +4 -4
- ansible/galaxy/dependency_resolution/reporters.py +81 -0
- ansible/galaxy/role.py +6 -10
- ansible/galaxy/token.py +28 -21
- ansible/inventory/data.py +47 -57
- ansible/inventory/group.py +50 -73
- ansible/inventory/helpers.py +9 -0
- ansible/inventory/host.py +37 -54
- ansible/inventory/manager.py +79 -34
- ansible/keyword_desc.yml +1 -1
- ansible/module_utils/_internal/__init__.py +55 -0
- ansible/module_utils/_internal/_ambient_context.py +58 -0
- ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
- ansible/module_utils/_internal/_ansiballz/_loader.py +81 -0
- ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
- ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
- ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
- ansible/module_utils/_internal/_dataclass_validation.py +217 -0
- ansible/module_utils/_internal/_datatag/__init__.py +961 -0
- ansible/module_utils/_internal/_datatag/_tags.py +16 -0
- ansible/module_utils/_internal/_debugging.py +31 -0
- ansible/module_utils/_internal/_deprecator.py +157 -0
- ansible/module_utils/_internal/_errors.py +101 -0
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/__init__.py +63 -0
- ansible/module_utils/_internal/_json/_legacy_encoder.py +26 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +428 -0
- ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +33 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +37 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_c2m.py +35 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_m2c.py +33 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +52 -0
- ansible/module_utils/_internal/_messages.py +130 -0
- ansible/module_utils/_internal/_patches/__init__.py +66 -0
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +53 -0
- ansible/module_utils/_internal/_patches/_socket_patch.py +34 -0
- ansible/module_utils/_internal/_patches/_sys_intern_patch.py +34 -0
- ansible/module_utils/_internal/_plugin_info.py +38 -0
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_testing.py +0 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +92 -0
- ansible/module_utils/_internal/_validation.py +14 -0
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/api.py +1 -2
- ansible/module_utils/basic.py +303 -202
- ansible/module_utils/common/_utils.py +24 -28
- ansible/module_utils/common/arg_spec.py +8 -3
- ansible/module_utils/common/collections.py +7 -2
- ansible/module_utils/common/dict_transformations.py +2 -2
- ansible/module_utils/common/file.py +2 -2
- ansible/module_utils/common/json.py +90 -84
- ansible/module_utils/common/locale.py +2 -2
- ansible/module_utils/common/parameters.py +27 -24
- ansible/module_utils/common/process.py +2 -3
- ansible/module_utils/common/respawn.py +11 -33
- ansible/module_utils/common/sentinel.py +66 -0
- ansible/module_utils/common/sys_info.py +8 -8
- ansible/module_utils/common/text/converters.py +16 -37
- ansible/module_utils/common/validation.py +35 -24
- ansible/module_utils/common/warnings.py +143 -25
- ansible/module_utils/common/yaml.py +29 -3
- ansible/module_utils/compat/datetime.py +33 -21
- ansible/module_utils/compat/paramiko.py +21 -10
- ansible/module_utils/compat/typing.py +6 -5
- ansible/module_utils/connection.py +10 -13
- ansible/module_utils/csharp/Ansible.Basic.cs +15 -12
- ansible/module_utils/csharp/Ansible.Become.cs +1 -0
- ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
- ansible/module_utils/csharp/Ansible._Async.cs +517 -0
- ansible/module_utils/datatag.py +49 -0
- ansible/module_utils/distro/__init__.py +2 -2
- ansible/module_utils/facts/ansible_collector.py +4 -5
- ansible/module_utils/facts/collector.py +13 -14
- ansible/module_utils/facts/compat.py +4 -4
- ansible/module_utils/facts/default_collectors.py +1 -1
- ansible/module_utils/facts/hardware/aix.py +34 -0
- ansible/module_utils/facts/hardware/base.py +2 -2
- ansible/module_utils/facts/hardware/darwin.py +1 -3
- ansible/module_utils/facts/hardware/freebsd.py +2 -2
- ansible/module_utils/facts/hardware/linux.py +5 -5
- ansible/module_utils/facts/namespace.py +1 -1
- ansible/module_utils/facts/network/base.py +1 -1
- ansible/module_utils/facts/network/fc_wwn.py +1 -2
- ansible/module_utils/facts/network/iscsi.py +1 -2
- ansible/module_utils/facts/network/nvme.py +1 -2
- ansible/module_utils/facts/other/facter.py +2 -3
- ansible/module_utils/facts/other/ohai.py +2 -3
- ansible/module_utils/facts/sysctl.py +4 -6
- ansible/module_utils/facts/system/apparmor.py +1 -2
- ansible/module_utils/facts/system/caps.py +3 -3
- ansible/module_utils/facts/system/chroot.py +1 -2
- ansible/module_utils/facts/system/cmdline.py +1 -2
- ansible/module_utils/facts/system/date_time.py +5 -3
- ansible/module_utils/facts/system/distribution.py +27 -13
- ansible/module_utils/facts/system/dns.py +1 -1
- ansible/module_utils/facts/system/env.py +1 -2
- ansible/module_utils/facts/system/fips.py +7 -20
- ansible/module_utils/facts/system/loadavg.py +1 -2
- ansible/module_utils/facts/system/local.py +2 -3
- ansible/module_utils/facts/system/lsb.py +1 -2
- ansible/module_utils/facts/system/pkg_mgr.py +1 -2
- ansible/module_utils/facts/system/platform.py +1 -2
- ansible/module_utils/facts/system/python.py +1 -2
- ansible/module_utils/facts/system/selinux.py +1 -1
- ansible/module_utils/facts/system/service_mgr.py +1 -2
- ansible/module_utils/facts/system/ssh_pub_keys.py +1 -1
- ansible/module_utils/facts/system/systemd.py +1 -1
- ansible/module_utils/facts/system/user.py +1 -2
- ansible/module_utils/facts/utils.py +3 -3
- ansible/module_utils/facts/virtual/base.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +3 -3
- ansible/module_utils/facts/virtual/sunos.py +3 -15
- ansible/module_utils/facts/virtual/sysctl.py +3 -16
- ansible/module_utils/json_utils.py +2 -2
- ansible/module_utils/parsing/convert_bool.py +7 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
- ansible/module_utils/service.py +21 -31
- ansible/module_utils/splitter.py +7 -7
- ansible/module_utils/testing.py +31 -0
- ansible/module_utils/urls.py +64 -35
- ansible/modules/add_host.py +4 -4
- ansible/modules/apt.py +69 -49
- ansible/modules/apt_key.py +19 -12
- ansible/modules/apt_repository.py +32 -51
- ansible/modules/assemble.py +16 -14
- ansible/modules/assert.py +4 -4
- ansible/modules/async_status.py +24 -24
- ansible/modules/async_wrapper.py +20 -25
- ansible/modules/blockinfile.py +6 -7
- ansible/modules/command.py +13 -20
- ansible/modules/copy.py +60 -147
- ansible/modules/cron.py +24 -21
- ansible/modules/deb822_repository.py +8 -9
- ansible/modules/debconf.py +5 -5
- ansible/modules/debug.py +4 -4
- ansible/modules/dnf.py +8 -8
- ansible/modules/dnf5.py +39 -13
- ansible/modules/dpkg_selections.py +4 -4
- ansible/modules/expect.py +13 -15
- ansible/modules/fail.py +4 -4
- ansible/modules/fetch.py +4 -4
- ansible/modules/file.py +184 -144
- ansible/modules/find.py +22 -20
- ansible/modules/gather_facts.py +3 -3
- ansible/modules/get_url.py +77 -54
- ansible/modules/getent.py +7 -9
- ansible/modules/git.py +38 -38
- ansible/modules/group.py +6 -6
- ansible/modules/group_by.py +4 -4
- ansible/modules/hostname.py +15 -32
- ansible/modules/import_playbook.py +6 -6
- ansible/modules/import_role.py +6 -6
- ansible/modules/import_tasks.py +6 -6
- ansible/modules/include_role.py +6 -6
- ansible/modules/include_tasks.py +6 -6
- ansible/modules/include_vars.py +6 -6
- ansible/modules/iptables.py +86 -73
- ansible/modules/known_hosts.py +22 -24
- ansible/modules/lineinfile.py +5 -5
- ansible/modules/meta.py +4 -4
- ansible/modules/mount_facts.py +2 -2
- ansible/modules/package.py +10 -4
- ansible/modules/package_facts.py +22 -10
- ansible/modules/pause.py +6 -6
- ansible/modules/ping.py +6 -6
- ansible/modules/pip.py +21 -26
- ansible/modules/raw.py +6 -6
- ansible/modules/reboot.py +6 -6
- ansible/modules/replace.py +10 -14
- ansible/modules/rpm_key.py +7 -8
- ansible/modules/script.py +4 -4
- ansible/modules/service.py +10 -17
- ansible/modules/service_facts.py +87 -10
- ansible/modules/set_fact.py +5 -5
- ansible/modules/set_stats.py +4 -4
- ansible/modules/setup.py +2 -2
- ansible/modules/shell.py +6 -6
- ansible/modules/slurp.py +16 -19
- ansible/modules/stat.py +15 -31
- ansible/modules/subversion.py +15 -15
- ansible/modules/systemd.py +7 -7
- ansible/modules/systemd_service.py +7 -7
- ansible/modules/sysvinit.py +9 -9
- ansible/modules/tempfile.py +5 -6
- ansible/modules/template.py +6 -6
- ansible/modules/unarchive.py +38 -17
- ansible/modules/uri.py +33 -26
- ansible/modules/user.py +45 -32
- ansible/modules/validate_argument_spec.py +10 -7
- ansible/modules/wait_for.py +70 -60
- ansible/modules/wait_for_connection.py +6 -6
- ansible/modules/yum_repository.py +10 -9
- ansible/parsing/ajson.py +17 -37
- ansible/parsing/dataloader.py +99 -54
- ansible/parsing/mod_args.py +62 -60
- ansible/parsing/plugin_docs.py +21 -86
- ansible/parsing/quoting.py +1 -1
- ansible/parsing/splitter.py +27 -12
- ansible/parsing/utils/addresses.py +24 -24
- ansible/parsing/utils/jsonify.py +5 -1
- ansible/parsing/utils/yaml.py +32 -61
- ansible/parsing/vault/__init__.py +327 -99
- ansible/parsing/yaml/__init__.py +0 -18
- ansible/parsing/yaml/dumper.py +6 -120
- ansible/parsing/yaml/loader.py +6 -39
- ansible/parsing/yaml/objects.py +43 -335
- ansible/playbook/__init__.py +1 -1
- ansible/playbook/attribute.py +8 -3
- ansible/playbook/base.py +187 -134
- ansible/playbook/block.py +26 -24
- ansible/playbook/collectionsearch.py +1 -15
- ansible/playbook/conditional.py +3 -77
- ansible/playbook/handler.py +8 -2
- ansible/playbook/helpers.py +41 -53
- ansible/playbook/included_file.py +32 -26
- ansible/playbook/loop_control.py +2 -2
- ansible/playbook/play.py +85 -44
- ansible/playbook/play_context.py +14 -17
- ansible/playbook/playbook_include.py +27 -62
- ansible/playbook/role/__init__.py +64 -49
- ansible/playbook/role/definition.py +15 -17
- ansible/playbook/role/include.py +2 -4
- ansible/playbook/role/metadata.py +10 -11
- ansible/playbook/role_include.py +3 -3
- ansible/playbook/taggable.py +28 -12
- ansible/playbook/task.py +192 -121
- ansible/playbook/task_include.py +5 -5
- ansible/plugins/__init__.py +58 -26
- ansible/plugins/action/__init__.py +188 -186
- ansible/plugins/action/add_host.py +2 -2
- ansible/plugins/action/assemble.py +11 -18
- ansible/plugins/action/assert.py +55 -67
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +14 -17
- ansible/plugins/action/debug.py +37 -31
- ansible/plugins/action/dnf.py +3 -4
- ansible/plugins/action/fail.py +1 -1
- ansible/plugins/action/fetch.py +7 -8
- ansible/plugins/action/gather_facts.py +13 -14
- ansible/plugins/action/group_by.py +1 -1
- ansible/plugins/action/include_vars.py +10 -11
- ansible/plugins/action/package.py +8 -14
- ansible/plugins/action/pause.py +2 -2
- ansible/plugins/action/script.py +27 -38
- ansible/plugins/action/service.py +9 -18
- ansible/plugins/action/set_fact.py +3 -12
- ansible/plugins/action/set_stats.py +3 -8
- ansible/plugins/action/template.py +47 -67
- ansible/plugins/action/unarchive.py +6 -16
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/action/validate_argument_spec.py +5 -5
- ansible/plugins/action/wait_for_connection.py +1 -1
- ansible/plugins/become/__init__.py +31 -8
- ansible/plugins/become/runas.py +71 -0
- ansible/plugins/become/su.py +13 -8
- ansible/plugins/become/sudo.py +19 -0
- ansible/plugins/cache/__init__.py +52 -63
- ansible/plugins/cache/base.py +8 -0
- ansible/plugins/cache/jsonfile.py +10 -16
- ansible/plugins/cache/memory.py +6 -12
- ansible/plugins/callback/__init__.py +294 -201
- ansible/plugins/callback/default.py +99 -95
- ansible/plugins/callback/junit.py +44 -43
- ansible/plugins/callback/minimal.py +28 -25
- ansible/plugins/callback/oneline.py +34 -21
- ansible/plugins/callback/tree.py +27 -16
- ansible/plugins/connection/__init__.py +47 -34
- ansible/plugins/connection/local.py +156 -60
- ansible/plugins/connection/paramiko_ssh.py +34 -24
- ansible/plugins/connection/psrp.py +76 -165
- ansible/plugins/connection/ssh.py +326 -86
- ansible/plugins/connection/winrm.py +62 -141
- ansible/plugins/doc_fragments/action_common_attributes.py +14 -14
- ansible/plugins/doc_fragments/action_core.py +6 -6
- ansible/plugins/doc_fragments/backup.py +2 -2
- ansible/plugins/doc_fragments/checksum_common.py +27 -0
- ansible/plugins/doc_fragments/constructed.py +8 -4
- ansible/plugins/doc_fragments/decrypt.py +2 -2
- ansible/plugins/doc_fragments/default_callback.py +2 -2
- ansible/plugins/doc_fragments/files.py +2 -2
- ansible/plugins/doc_fragments/inventory_cache.py +2 -2
- ansible/plugins/doc_fragments/result_format_callback.py +2 -2
- ansible/plugins/doc_fragments/return_common.py +2 -2
- ansible/plugins/doc_fragments/template_common.py +4 -4
- ansible/plugins/doc_fragments/url.py +17 -1
- ansible/plugins/doc_fragments/url_windows.py +2 -2
- ansible/plugins/doc_fragments/validate.py +2 -2
- ansible/plugins/doc_fragments/vars_plugin_staging.py +2 -2
- ansible/plugins/filter/__init__.py +6 -2
- ansible/plugins/filter/b64decode.yml +22 -0
- ansible/plugins/filter/b64encode.yml +22 -0
- ansible/plugins/filter/bool.yml +11 -4
- ansible/plugins/filter/core.py +245 -120
- ansible/plugins/filter/encryption.py +42 -34
- ansible/plugins/filter/flatten.yml +3 -2
- ansible/plugins/filter/human_to_bytes.yml +1 -1
- ansible/plugins/filter/mathstuff.py +30 -37
- ansible/plugins/filter/password_hash.yml +8 -0
- ansible/plugins/filter/pow.yml +1 -1
- ansible/plugins/filter/regex_search.yml +1 -4
- ansible/plugins/filter/root.yml +1 -1
- ansible/plugins/filter/split.yml +1 -1
- ansible/plugins/filter/strftime.yml +3 -3
- ansible/plugins/filter/to_nice_yaml.yml +0 -4
- ansible/plugins/filter/to_uuid.yml +1 -1
- ansible/plugins/filter/to_yaml.yml +0 -4
- ansible/plugins/filter/unvault.yml +1 -1
- ansible/plugins/filter/urls.py +1 -1
- ansible/plugins/filter/urlsplit.py +8 -9
- ansible/plugins/filter/vault.yml +14 -9
- ansible/plugins/filter/win_basename.yml +6 -1
- ansible/plugins/filter/win_dirname.yml +5 -0
- ansible/plugins/inventory/__init__.py +107 -86
- ansible/plugins/inventory/advanced_host_list.py +7 -5
- ansible/plugins/inventory/auto.py +11 -4
- ansible/plugins/inventory/constructed.py +21 -24
- ansible/plugins/inventory/generator.py +16 -11
- ansible/plugins/inventory/host_list.py +7 -5
- ansible/plugins/inventory/ini.py +78 -44
- ansible/plugins/inventory/script.py +190 -120
- ansible/plugins/inventory/toml.py +16 -126
- ansible/plugins/inventory/yaml.py +10 -8
- ansible/plugins/list.py +72 -19
- ansible/plugins/loader.py +383 -198
- ansible/plugins/lookup/__init__.py +21 -4
- ansible/plugins/lookup/config.py +21 -35
- ansible/plugins/lookup/csvfile.py +19 -73
- ansible/plugins/lookup/dict.py +1 -6
- ansible/plugins/lookup/env.py +12 -9
- ansible/plugins/lookup/file.py +5 -8
- ansible/plugins/lookup/first_found.py +87 -55
- ansible/plugins/lookup/indexed_items.py +1 -10
- ansible/plugins/lookup/ini.py +14 -13
- ansible/plugins/lookup/items.py +1 -1
- ansible/plugins/lookup/lines.py +8 -1
- ansible/plugins/lookup/list.py +1 -1
- ansible/plugins/lookup/nested.py +2 -18
- ansible/plugins/lookup/password.py +5 -5
- ansible/plugins/lookup/pipe.py +5 -7
- ansible/plugins/lookup/sequence.py +18 -8
- ansible/plugins/lookup/subelements.py +1 -4
- ansible/plugins/lookup/template.py +47 -36
- ansible/plugins/lookup/together.py +0 -12
- ansible/plugins/lookup/unvault.py +1 -5
- ansible/plugins/lookup/url.py +4 -10
- ansible/plugins/lookup/vars.py +16 -24
- ansible/plugins/shell/__init__.py +58 -4
- ansible/plugins/shell/cmd.py +2 -2
- ansible/plugins/shell/powershell.py +106 -31
- ansible/plugins/shell/sh.py +13 -7
- ansible/plugins/strategy/__init__.py +168 -193
- ansible/plugins/strategy/debug.py +2 -2
- ansible/plugins/strategy/free.py +16 -31
- ansible/plugins/strategy/host_pinned.py +2 -2
- ansible/plugins/strategy/linear.py +41 -41
- ansible/plugins/terminal/__init__.py +4 -4
- ansible/plugins/test/__init__.py +7 -2
- ansible/plugins/test/core.py +75 -35
- ansible/plugins/test/files.py +1 -1
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/mathstuff.py +3 -3
- ansible/plugins/test/uri.py +5 -8
- ansible/plugins/vars/host_group_vars.py +7 -14
- ansible/release.py +2 -2
- ansible/template/__init__.py +353 -943
- ansible/utils/__init__.py +0 -18
- ansible/utils/collection_loader/__init__.py +54 -5
- ansible/utils/collection_loader/_collection_config.py +5 -6
- ansible/utils/collection_loader/_collection_finder.py +82 -96
- ansible/utils/collection_loader/_collection_meta.py +15 -8
- ansible/utils/display.py +485 -73
- ansible/utils/encrypt.py +27 -19
- ansible/utils/fqcn.py +2 -2
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +8 -10
- ansible/utils/helpers.py +2 -2
- ansible/utils/listify.py +10 -8
- ansible/utils/lock.py +2 -2
- ansible/utils/path.py +10 -12
- ansible/utils/plugin_docs.py +16 -14
- ansible/utils/py3compat.py +2 -7
- ansible/utils/sentinel.py +4 -62
- ansible/utils/singleton.py +2 -0
- ansible/utils/ssh_functions.py +6 -2
- ansible/utils/unsafe_proxy.py +23 -332
- ansible/utils/vars.py +55 -8
- ansible/utils/version.py +2 -2
- ansible/vars/clean.py +5 -5
- ansible/vars/hostvars.py +60 -90
- ansible/vars/manager.py +220 -285
- ansible/vars/plugins.py +4 -4
- ansible/vars/reserved.py +13 -12
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/METADATA +4 -3
- ansible_core-2.19.0.dist-info/RECORD +1097 -0
- ansible_core-2.19.0.dist-info/licenses/licenses/BSD-3-Clause.txt +28 -0
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/remote.txt +6 -6
- ansible_test/_data/completion/windows.txt +1 -0
- ansible_test/_data/requirements/ansible.txt +2 -2
- ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +5 -5
- ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_data/requirements/units.txt +1 -0
- ansible_test/_internal/__init__.py +6 -0
- ansible_test/_internal/ansible_util.py +3 -1
- ansible_test/_internal/become.py +1 -0
- ansible_test/_internal/bootstrap.py +1 -0
- ansible_test/_internal/cache.py +1 -0
- ansible_test/_internal/cgroup.py +1 -0
- ansible_test/_internal/ci/__init__.py +1 -0
- ansible_test/_internal/ci/azp.py +1 -0
- ansible_test/_internal/ci/local.py +1 -0
- ansible_test/_internal/classification/__init__.py +1 -0
- ansible_test/_internal/classification/common.py +1 -0
- ansible_test/_internal/classification/csharp.py +1 -0
- ansible_test/_internal/classification/powershell.py +1 -0
- ansible_test/_internal/classification/python.py +1 -0
- ansible_test/_internal/cli/__init__.py +1 -0
- ansible_test/_internal/cli/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/__init__.py +1 -0
- ansible_test/_internal/cli/argparsing/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/argcompletion.py +1 -0
- ansible_test/_internal/cli/argparsing/parsers.py +1 -0
- ansible_test/_internal/cli/commands/__init__.py +11 -5
- ansible_test/_internal/cli/commands/coverage/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/cli/commands/coverage/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/erase.py +1 -0
- ansible_test/_internal/cli/commands/coverage/html.py +1 -0
- ansible_test/_internal/cli/commands/coverage/report.py +1 -0
- ansible_test/_internal/cli/commands/coverage/xml.py +1 -0
- ansible_test/_internal/cli/commands/env.py +1 -0
- ansible_test/_internal/cli/commands/integration/__init__.py +1 -0
- ansible_test/_internal/cli/commands/integration/network.py +1 -0
- ansible_test/_internal/cli/commands/integration/posix.py +1 -0
- ansible_test/_internal/cli/commands/integration/windows.py +1 -0
- ansible_test/_internal/cli/commands/sanity.py +9 -0
- ansible_test/_internal/cli/commands/shell.py +1 -0
- ansible_test/_internal/cli/commands/units.py +1 -0
- ansible_test/_internal/cli/compat.py +1 -0
- ansible_test/_internal/cli/completers.py +1 -0
- ansible_test/_internal/cli/converters.py +1 -0
- ansible_test/_internal/cli/environments.py +52 -5
- ansible_test/_internal/cli/epilog.py +1 -0
- ansible_test/_internal/cli/parsers/__init__.py +1 -0
- ansible_test/_internal/cli/parsers/base_argument_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/helpers.py +1 -0
- ansible_test/_internal/cli/parsers/host_config_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/key_value_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/value_parsers.py +1 -0
- ansible_test/_internal/commands/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/__init__.py +3 -2
- ansible_test/_internal/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/commands/coverage/combine.py +2 -1
- ansible_test/_internal/commands/coverage/erase.py +1 -0
- ansible_test/_internal/commands/coverage/html.py +1 -0
- ansible_test/_internal/commands/coverage/report.py +1 -0
- ansible_test/_internal/commands/coverage/xml.py +1 -0
- ansible_test/_internal/commands/env/__init__.py +2 -0
- ansible_test/_internal/commands/integration/__init__.py +22 -5
- ansible_test/_internal/commands/integration/cloud/__init__.py +1 -0
- ansible_test/_internal/commands/integration/cloud/acme.py +2 -1
- ansible_test/_internal/commands/integration/cloud/aws.py +1 -0
- ansible_test/_internal/commands/integration/cloud/azure.py +1 -0
- ansible_test/_internal/commands/integration/cloud/cs.py +1 -0
- ansible_test/_internal/commands/integration/cloud/digitalocean.py +1 -0
- ansible_test/_internal/commands/integration/cloud/galaxy.py +3 -2
- ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -0
- ansible_test/_internal/commands/integration/cloud/httptester.py +3 -2
- ansible_test/_internal/commands/integration/cloud/nios.py +2 -1
- ansible_test/_internal/commands/integration/cloud/opennebula.py +1 -0
- ansible_test/_internal/commands/integration/cloud/openshift.py +1 -0
- ansible_test/_internal/commands/integration/cloud/scaleway.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vcenter.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vultr.py +1 -0
- ansible_test/_internal/commands/integration/coverage.py +8 -2
- ansible_test/_internal/commands/integration/filters.py +1 -0
- ansible_test/_internal/commands/integration/network.py +1 -0
- ansible_test/_internal/commands/integration/posix.py +1 -0
- ansible_test/_internal/commands/integration/windows.py +1 -0
- ansible_test/_internal/commands/sanity/__init__.py +19 -2
- ansible_test/_internal/commands/sanity/ansible_doc.py +1 -0
- ansible_test/_internal/commands/sanity/bin_symlinks.py +1 -0
- ansible_test/_internal/commands/sanity/compile.py +1 -0
- ansible_test/_internal/commands/sanity/ignores.py +1 -0
- ansible_test/_internal/commands/sanity/import.py +1 -0
- ansible_test/_internal/commands/sanity/integration_aliases.py +12 -0
- ansible_test/_internal/commands/sanity/pep8.py +1 -0
- ansible_test/_internal/commands/sanity/pslint.py +1 -0
- ansible_test/_internal/commands/sanity/pylint.py +25 -26
- ansible_test/_internal/commands/sanity/shellcheck.py +1 -0
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -0
- ansible_test/_internal/commands/sanity/yamllint.py +1 -0
- ansible_test/_internal/commands/shell/__init__.py +44 -4
- ansible_test/_internal/commands/units/__init__.py +5 -1
- ansible_test/_internal/compat/__init__.py +1 -0
- ansible_test/_internal/compat/packaging.py +1 -0
- ansible_test/_internal/compat/yaml.py +1 -0
- ansible_test/_internal/completion.py +1 -0
- ansible_test/_internal/config.py +23 -13
- ansible_test/_internal/connections.py +1 -0
- ansible_test/_internal/constants.py +1 -0
- ansible_test/_internal/containers.py +1 -0
- ansible_test/_internal/content_config.py +1 -0
- ansible_test/_internal/core_ci.py +1 -0
- ansible_test/_internal/coverage_util.py +11 -10
- ansible_test/_internal/data.py +1 -0
- ansible_test/_internal/debugging.py +166 -0
- ansible_test/_internal/delegation.py +22 -13
- ansible_test/_internal/dev/__init__.py +1 -0
- ansible_test/_internal/dev/container_probe.py +1 -0
- ansible_test/_internal/diff.py +3 -2
- ansible_test/_internal/docker_util.py +2 -1
- ansible_test/_internal/encoding.py +1 -0
- ansible_test/_internal/executor.py +1 -0
- ansible_test/_internal/git.py +1 -0
- ansible_test/_internal/host_configs.py +1 -0
- ansible_test/_internal/host_profiles.py +260 -16
- ansible_test/_internal/http.py +1 -0
- ansible_test/_internal/init.py +1 -0
- ansible_test/_internal/inventory.py +39 -3
- ansible_test/_internal/io.py +1 -0
- ansible_test/_internal/metadata.py +95 -4
- ansible_test/_internal/payload.py +1 -0
- ansible_test/_internal/processes.py +80 -0
- ansible_test/_internal/provider/__init__.py +1 -0
- ansible_test/_internal/provider/layout/__init__.py +1 -0
- ansible_test/_internal/provider/layout/ansible.py +1 -0
- ansible_test/_internal/provider/layout/collection.py +1 -0
- ansible_test/_internal/provider/layout/unsupported.py +1 -0
- ansible_test/_internal/provider/source/__init__.py +1 -0
- ansible_test/_internal/provider/source/git.py +1 -0
- ansible_test/_internal/provider/source/installed.py +1 -0
- ansible_test/_internal/provider/source/unsupported.py +1 -0
- ansible_test/_internal/provider/source/unversioned.py +1 -0
- ansible_test/_internal/provisioning.py +11 -4
- ansible_test/_internal/pypi_proxy.py +6 -5
- ansible_test/_internal/python_requirements.py +28 -0
- ansible_test/_internal/ssh.py +2 -5
- ansible_test/_internal/target.py +9 -0
- ansible_test/_internal/test.py +3 -2
- ansible_test/_internal/thread.py +3 -1
- ansible_test/_internal/timeout.py +2 -1
- ansible_test/_internal/util.py +41 -12
- ansible_test/_internal/util_common.py +18 -5
- ansible_test/_internal/venv.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/empty-init.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/line-endings.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-assert.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-get-exception.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-illegal-filenames.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-smart-quotes.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/replace-urlopen.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +28 -1
- ansible_test/_util/controller/sanity/code-smell/shebang.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/symlinks.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-argspec-type-path.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-compat-six.py +1 -0
- ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py +2 -1
- ansible_test/_util/controller/sanity/pep8/current-ignore.txt +4 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +8 -5
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +8 -5
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +8 -5
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +4 -5
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +8 -7
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +541 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
- ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +1 -8
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +1 -8
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +55 -28
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +12 -5
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +13 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -0
- ansible_test/_util/controller/sanity/yamllint/yamllinter.py +35 -17
- ansible_test/_util/controller/tools/collection_detail.py +1 -0
- ansible_test/_util/controller/tools/yaml_to_json.py +2 -1
- ansible_test/_util/target/injector/python.py +8 -0
- ansible_test/_util/target/pytest/plugins/ansible_forked.py +6 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +2 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -0
- ansible_test/_util/target/sanity/compile/compile.py +1 -0
- ansible_test/_util/target/sanity/import/importer.py +15 -16
- ansible_test/_util/target/setup/bootstrap.sh +9 -20
- ansible_test/_util/target/setup/probe_cgroups.py +1 -0
- ansible_test/_util/target/setup/quiet_pip.py +1 -0
- ansible_test/_util/target/setup/requirements.py +38 -36
- ansible_test/_util/target/tools/virtualenvcheck.py +2 -1
- ansible_test/_util/target/tools/yamlcheck.py +2 -1
- ansible/compat/selectors.py +0 -32
- ansible/errors/yaml_strings.py +0 -138
- ansible/executor/action_write_locks.py +0 -44
- ansible/executor/discovery/python_target.py +0 -47
- ansible/executor/powershell/module_powershell_wrapper.ps1 +0 -86
- ansible/executor/powershell/module_script_wrapper.ps1 +0 -22
- ansible/module_utils/compat/importlib.py +0 -26
- ansible/module_utils/compat/selectors.py +0 -32
- ansible/module_utils/pycompat24.py +0 -73
- ansible/parsing/yaml/constructor.py +0 -178
- ansible/template/native_helpers.py +0 -251
- ansible/template/template.py +0 -43
- ansible/template/vars.py +0 -77
- ansible/utils/native_jinja.py +0 -11
- ansible/vars/fact_cache.py +0 -71
- ansible_core-2.18.7.dist-info/RECORD +0 -992
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -411
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/WHEEL +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/top_level.txt +0 -0
|
@@ -20,42 +20,79 @@ from __future__ import annotations
|
|
|
20
20
|
|
|
21
21
|
import ast
|
|
22
22
|
import base64
|
|
23
|
+
import dataclasses
|
|
23
24
|
import datetime
|
|
24
25
|
import json
|
|
25
26
|
import os
|
|
27
|
+
import pathlib
|
|
28
|
+
import pickle
|
|
26
29
|
import shlex
|
|
27
|
-
import time
|
|
28
30
|
import zipfile
|
|
29
31
|
import re
|
|
30
32
|
import pkgutil
|
|
33
|
+
import types
|
|
34
|
+
import typing as t
|
|
31
35
|
|
|
32
36
|
from ast import AST, Import, ImportFrom
|
|
33
37
|
from io import BytesIO
|
|
34
38
|
|
|
39
|
+
from ansible._internal import _locking
|
|
40
|
+
from ansible._internal._ansiballz import _builder
|
|
41
|
+
from ansible._internal import _ansiballz
|
|
42
|
+
from ansible._internal._datatag import _utils
|
|
43
|
+
from ansible.module_utils._internal import _dataclass_validation
|
|
44
|
+
from ansible.module_utils.common.yaml import yaml_load
|
|
45
|
+
from ansible.module_utils.datatag import deprecator_from_collection_name
|
|
46
|
+
from ansible._internal._datatag._tags import Origin
|
|
47
|
+
from ansible.module_utils.common.json import Direction, get_module_encoder
|
|
35
48
|
from ansible.release import __version__, __author__
|
|
36
49
|
from ansible import constants as C
|
|
37
50
|
from ansible.errors import AnsibleError
|
|
38
51
|
from ansible.executor.interpreter_discovery import InterpreterDiscoveryRequiredError
|
|
39
52
|
from ansible.executor.powershell import module_manifest as ps_manifest
|
|
40
|
-
from ansible.module_utils.common.json import AnsibleJSONEncoder
|
|
41
53
|
from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
|
|
54
|
+
from ansible.plugins.become import BecomeBase
|
|
42
55
|
from ansible.plugins.loader import module_utils_loader
|
|
56
|
+
from ansible._internal._templating._engine import TemplateOptions, TemplateEngine
|
|
57
|
+
from ansible.template import Templar
|
|
43
58
|
from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
|
|
59
|
+
from ansible.module_utils._internal import _json
|
|
60
|
+
from ansible.module_utils._internal._ansiballz import _loader
|
|
61
|
+
from ansible.module_utils import basic as _basic
|
|
44
62
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
from ansible.executor import action_write_locks
|
|
63
|
+
if t.TYPE_CHECKING:
|
|
64
|
+
from ansible import template as _template
|
|
65
|
+
from ansible.playbook.task import Task
|
|
49
66
|
|
|
50
67
|
from ansible.utils.display import Display
|
|
51
|
-
from collections import namedtuple
|
|
52
68
|
|
|
53
69
|
import importlib.util
|
|
54
70
|
import importlib.machinery
|
|
55
71
|
|
|
56
72
|
display = Display()
|
|
57
73
|
|
|
58
|
-
|
|
74
|
+
|
|
75
|
+
@dataclasses.dataclass(frozen=True, order=True)
|
|
76
|
+
class _ModuleUtilsProcessEntry:
|
|
77
|
+
"""Represents a module/module_utils item awaiting import analysis."""
|
|
78
|
+
name_parts: tuple[str, ...]
|
|
79
|
+
is_ambiguous: bool = False
|
|
80
|
+
child_is_redirected: bool = False
|
|
81
|
+
is_optional: bool = False
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def from_module(cls, module: types.ModuleType, append: str | None = None) -> t.Self:
|
|
85
|
+
name = module.__name__
|
|
86
|
+
|
|
87
|
+
if append:
|
|
88
|
+
name += '.' + append
|
|
89
|
+
|
|
90
|
+
return cls.from_module_name(name)
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def from_module_name(cls, module_name: str) -> t.Self:
|
|
94
|
+
return cls(tuple(module_name.split('.')))
|
|
95
|
+
|
|
59
96
|
|
|
60
97
|
REPLACER = b"#<<INCLUDE_ANSIBLE_MODULE_COMMON>>"
|
|
61
98
|
REPLACER_VERSION = b"\"<<ANSIBLE_VERSION>>\""
|
|
@@ -64,348 +101,45 @@ REPLACER_WINDOWS = b"# POWERSHELL_COMMON"
|
|
|
64
101
|
REPLACER_JSONARGS = b"<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>"
|
|
65
102
|
REPLACER_SELINUX = b"<<SELINUX_SPECIAL_FILESYSTEMS>>"
|
|
66
103
|
|
|
67
|
-
# We could end up writing out parameters with unicode characters so we need to
|
|
68
|
-
# specify an encoding for the python source file
|
|
69
|
-
ENCODING_STRING = u'# -*- coding: utf-8 -*-'
|
|
70
|
-
b_ENCODING_STRING = b'# -*- coding: utf-8 -*-'
|
|
71
|
-
|
|
72
104
|
# module_common is relative to module_utils, so fix the path
|
|
73
105
|
_MODULE_UTILS_PATH = os.path.join(os.path.dirname(__file__), '..', 'module_utils')
|
|
106
|
+
_SHEBANG_PLACEHOLDER = '# shebang placeholder'
|
|
74
107
|
|
|
75
108
|
# ******************************************************************************
|
|
76
109
|
|
|
77
|
-
ANSIBALLZ_TEMPLATE = u'''%(shebang)s
|
|
78
|
-
%(coding)s
|
|
79
|
-
_ANSIBALLZ_WRAPPER = True # For test-module.py script to tell this is a ANSIBALLZ_WRAPPER
|
|
80
|
-
# This code is part of Ansible, but is an independent component.
|
|
81
|
-
# The code in this particular templatable string, and this templatable string
|
|
82
|
-
# only, is BSD licensed. Modules which end up using this snippet, which is
|
|
83
|
-
# dynamically combined together by Ansible still belong to the author of the
|
|
84
|
-
# module, and they may assign their own license to the complete work.
|
|
85
|
-
#
|
|
86
|
-
# Copyright (c), James Cammarata, 2016
|
|
87
|
-
# Copyright (c), Toshio Kuratomi, 2016
|
|
88
|
-
#
|
|
89
|
-
# Redistribution and use in source and binary forms, with or without modification,
|
|
90
|
-
# are permitted provided that the following conditions are met:
|
|
91
|
-
#
|
|
92
|
-
# * Redistributions of source code must retain the above copyright
|
|
93
|
-
# notice, this list of conditions and the following disclaimer.
|
|
94
|
-
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
95
|
-
# this list of conditions and the following disclaimer in the documentation
|
|
96
|
-
# and/or other materials provided with the distribution.
|
|
97
|
-
#
|
|
98
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
99
|
-
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
100
|
-
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
101
|
-
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
102
|
-
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
103
|
-
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
104
|
-
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
105
|
-
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
106
|
-
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
107
|
-
def _ansiballz_main():
|
|
108
|
-
import os
|
|
109
|
-
import os.path
|
|
110
|
-
|
|
111
|
-
# Access to the working directory is required by Python when using pipelining, as well as for the coverage module.
|
|
112
|
-
# Some platforms, such as macOS, may not allow querying the working directory when using become to drop privileges.
|
|
113
|
-
try:
|
|
114
|
-
os.getcwd()
|
|
115
|
-
except OSError:
|
|
116
|
-
try:
|
|
117
|
-
os.chdir(os.path.expanduser('~'))
|
|
118
|
-
except OSError:
|
|
119
|
-
os.chdir('/')
|
|
120
|
-
|
|
121
|
-
%(rlimit)s
|
|
122
|
-
|
|
123
|
-
import sys
|
|
124
|
-
import __main__
|
|
125
|
-
|
|
126
|
-
# For some distros and python versions we pick up this script in the temporary
|
|
127
|
-
# directory. This leads to problems when the ansible module masks a python
|
|
128
|
-
# library that another import needs. We have not figured out what about the
|
|
129
|
-
# specific distros and python versions causes this to behave differently.
|
|
130
|
-
#
|
|
131
|
-
# Tested distros:
|
|
132
|
-
# Fedora23 with python3.4 Works
|
|
133
|
-
# Ubuntu15.10 with python2.7 Works
|
|
134
|
-
# Ubuntu15.10 with python3.4 Fails without this
|
|
135
|
-
# Ubuntu16.04.1 with python3.5 Fails without this
|
|
136
|
-
# To test on another platform:
|
|
137
|
-
# * use the copy module (since this shadows the stdlib copy module)
|
|
138
|
-
# * Turn off pipelining
|
|
139
|
-
# * Make sure that the destination file does not exist
|
|
140
|
-
# * ansible ubuntu16-test -m copy -a 'src=/etc/motd dest=/var/tmp/m'
|
|
141
|
-
# This will traceback in shutil. Looking at the complete traceback will show
|
|
142
|
-
# that shutil is importing copy which finds the ansible module instead of the
|
|
143
|
-
# stdlib module
|
|
144
|
-
scriptdir = None
|
|
145
|
-
try:
|
|
146
|
-
scriptdir = os.path.dirname(os.path.realpath(__main__.__file__))
|
|
147
|
-
except (AttributeError, OSError):
|
|
148
|
-
# Some platforms don't set __file__ when reading from stdin
|
|
149
|
-
# OSX raises OSError if using abspath() in a directory we don't have
|
|
150
|
-
# permission to read (realpath calls abspath)
|
|
151
|
-
pass
|
|
152
|
-
|
|
153
|
-
# Strip cwd from sys.path to avoid potential permissions issues
|
|
154
|
-
excludes = set(('', '.', scriptdir))
|
|
155
|
-
sys.path = [p for p in sys.path if p not in excludes]
|
|
156
|
-
|
|
157
|
-
import base64
|
|
158
|
-
import runpy
|
|
159
|
-
import shutil
|
|
160
|
-
import tempfile
|
|
161
|
-
import zipfile
|
|
162
|
-
|
|
163
|
-
if sys.version_info < (3,):
|
|
164
|
-
PY3 = False
|
|
165
|
-
else:
|
|
166
|
-
PY3 = True
|
|
167
|
-
|
|
168
|
-
ZIPDATA = %(zipdata)r
|
|
169
|
-
|
|
170
|
-
# Note: temp_path isn't needed once we switch to zipimport
|
|
171
|
-
def invoke_module(modlib_path, temp_path, json_params):
|
|
172
|
-
# When installed via setuptools (including python setup.py install),
|
|
173
|
-
# ansible may be installed with an easy-install.pth file. That file
|
|
174
|
-
# may load the system-wide install of ansible rather than the one in
|
|
175
|
-
# the module. sitecustomize is the only way to override that setting.
|
|
176
|
-
z = zipfile.ZipFile(modlib_path, mode='a')
|
|
177
|
-
|
|
178
|
-
# py3: modlib_path will be text, py2: it's bytes. Need bytes at the end
|
|
179
|
-
sitecustomize = u'import sys\\nsys.path.insert(0,"%%s")\\n' %% modlib_path
|
|
180
|
-
sitecustomize = sitecustomize.encode('utf-8')
|
|
181
|
-
# Use a ZipInfo to work around zipfile limitation on hosts with
|
|
182
|
-
# clocks set to a pre-1980 year (for instance, Raspberry Pi)
|
|
183
|
-
zinfo = zipfile.ZipInfo()
|
|
184
|
-
zinfo.filename = 'sitecustomize.py'
|
|
185
|
-
zinfo.date_time = %(date_time)s
|
|
186
|
-
z.writestr(zinfo, sitecustomize)
|
|
187
|
-
z.close()
|
|
188
|
-
|
|
189
|
-
# Put the zipped up module_utils we got from the controller first in the python path so that we
|
|
190
|
-
# can monkeypatch the right basic
|
|
191
|
-
sys.path.insert(0, modlib_path)
|
|
192
|
-
|
|
193
|
-
# Monkeypatch the parameters into basic
|
|
194
|
-
from ansible.module_utils import basic
|
|
195
|
-
basic._ANSIBLE_ARGS = json_params
|
|
196
|
-
%(coverage)s
|
|
197
|
-
# Run the module! By importing it as '__main__', it thinks it is executing as a script
|
|
198
|
-
runpy.run_module(mod_name=%(module_fqn)r, init_globals=dict(_module_fqn=%(module_fqn)r, _modlib_path=modlib_path),
|
|
199
|
-
run_name='__main__', alter_sys=True)
|
|
200
|
-
|
|
201
|
-
# Ansible modules must exit themselves
|
|
202
|
-
print('{"msg": "New-style module did not handle its own exit", "failed": true}')
|
|
203
|
-
sys.exit(1)
|
|
204
|
-
|
|
205
|
-
def debug(command, zipped_mod, json_params):
|
|
206
|
-
# The code here normally doesn't run. It's only used for debugging on the
|
|
207
|
-
# remote machine.
|
|
208
|
-
#
|
|
209
|
-
# The subcommands in this function make it easier to debug ansiballz
|
|
210
|
-
# modules. Here's the basic steps:
|
|
211
|
-
#
|
|
212
|
-
# Run ansible with the environment variable: ANSIBLE_KEEP_REMOTE_FILES=1 and -vvv
|
|
213
|
-
# to save the module file remotely::
|
|
214
|
-
# $ ANSIBLE_KEEP_REMOTE_FILES=1 ansible host1 -m ping -a 'data=october' -vvv
|
|
215
|
-
#
|
|
216
|
-
# Part of the verbose output will tell you where on the remote machine the
|
|
217
|
-
# module was written to::
|
|
218
|
-
# [...]
|
|
219
|
-
# <host1> SSH: EXEC ssh -C -q -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o
|
|
220
|
-
# PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o
|
|
221
|
-
# ControlPath=/home/badger/.ansible/cp/ansible-ssh-%%h-%%p-%%r -tt rhel7 '/bin/sh -c '"'"'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
|
222
|
-
# LC_MESSAGES=en_US.UTF-8 /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping'"'"''
|
|
223
|
-
# [...]
|
|
224
|
-
#
|
|
225
|
-
# Login to the remote machine and run the module file via from the previous
|
|
226
|
-
# step with the explode subcommand to extract the module payload into
|
|
227
|
-
# source files::
|
|
228
|
-
# $ ssh host1
|
|
229
|
-
# $ /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping explode
|
|
230
|
-
# Module expanded into:
|
|
231
|
-
# /home/badger/.ansible/tmp/ansible-tmp-1461173408.08-279692652635227/ansible
|
|
232
|
-
#
|
|
233
|
-
# You can now edit the source files to instrument the code or experiment with
|
|
234
|
-
# different parameter values. When you're ready to run the code you've modified
|
|
235
|
-
# (instead of the code from the actual zipped module), use the execute subcommand like this::
|
|
236
|
-
# $ /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping execute
|
|
237
|
-
|
|
238
|
-
# Okay to use __file__ here because we're running from a kept file
|
|
239
|
-
basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'debug_dir')
|
|
240
|
-
args_path = os.path.join(basedir, 'args')
|
|
241
|
-
|
|
242
|
-
if command == 'explode':
|
|
243
|
-
# transform the ZIPDATA into an exploded directory of code and then
|
|
244
|
-
# print the path to the code. This is an easy way for people to look
|
|
245
|
-
# at the code on the remote machine for debugging it in that
|
|
246
|
-
# environment
|
|
247
|
-
z = zipfile.ZipFile(zipped_mod)
|
|
248
|
-
for filename in z.namelist():
|
|
249
|
-
if filename.startswith('/'):
|
|
250
|
-
raise Exception('Something wrong with this module zip file: should not contain absolute paths')
|
|
251
|
-
|
|
252
|
-
dest_filename = os.path.join(basedir, filename)
|
|
253
|
-
if dest_filename.endswith(os.path.sep) and not os.path.exists(dest_filename):
|
|
254
|
-
os.makedirs(dest_filename)
|
|
255
|
-
else:
|
|
256
|
-
directory = os.path.dirname(dest_filename)
|
|
257
|
-
if not os.path.exists(directory):
|
|
258
|
-
os.makedirs(directory)
|
|
259
|
-
f = open(dest_filename, 'wb')
|
|
260
|
-
f.write(z.read(filename))
|
|
261
|
-
f.close()
|
|
262
|
-
|
|
263
|
-
# write the args file
|
|
264
|
-
f = open(args_path, 'wb')
|
|
265
|
-
f.write(json_params)
|
|
266
|
-
f.close()
|
|
267
|
-
|
|
268
|
-
print('Module expanded into:')
|
|
269
|
-
print('%%s' %% basedir)
|
|
270
|
-
exitcode = 0
|
|
271
|
-
|
|
272
|
-
elif command == 'execute':
|
|
273
|
-
# Execute the exploded code instead of executing the module from the
|
|
274
|
-
# embedded ZIPDATA. This allows people to easily run their modified
|
|
275
|
-
# code on the remote machine to see how changes will affect it.
|
|
276
|
-
|
|
277
|
-
# Set pythonpath to the debug dir
|
|
278
|
-
sys.path.insert(0, basedir)
|
|
279
|
-
|
|
280
|
-
# read in the args file which the user may have modified
|
|
281
|
-
with open(args_path, 'rb') as f:
|
|
282
|
-
json_params = f.read()
|
|
283
|
-
|
|
284
|
-
# Monkeypatch the parameters into basic
|
|
285
|
-
from ansible.module_utils import basic
|
|
286
|
-
basic._ANSIBLE_ARGS = json_params
|
|
287
|
-
|
|
288
|
-
# Run the module! By importing it as '__main__', it thinks it is executing as a script
|
|
289
|
-
runpy.run_module(mod_name=%(module_fqn)r, init_globals=None, run_name='__main__', alter_sys=True)
|
|
290
|
-
|
|
291
|
-
# Ansible modules must exit themselves
|
|
292
|
-
print('{"msg": "New-style module did not handle its own exit", "failed": true}')
|
|
293
|
-
sys.exit(1)
|
|
294
|
-
|
|
295
|
-
else:
|
|
296
|
-
print('WARNING: Unknown debug command. Doing nothing.')
|
|
297
|
-
exitcode = 0
|
|
298
|
-
|
|
299
|
-
return exitcode
|
|
300
|
-
|
|
301
|
-
#
|
|
302
|
-
# See comments in the debug() method for information on debugging
|
|
303
|
-
#
|
|
304
|
-
|
|
305
|
-
ANSIBALLZ_PARAMS = %(params)s
|
|
306
|
-
if PY3:
|
|
307
|
-
ANSIBALLZ_PARAMS = ANSIBALLZ_PARAMS.encode('utf-8')
|
|
308
|
-
try:
|
|
309
|
-
# There's a race condition with the controller removing the
|
|
310
|
-
# remote_tmpdir and this module executing under async. So we cannot
|
|
311
|
-
# store this in remote_tmpdir (use system tempdir instead)
|
|
312
|
-
# Only need to use [ansible_module]_payload_ in the temp_path until we move to zipimport
|
|
313
|
-
# (this helps ansible-test produce coverage stats)
|
|
314
|
-
temp_path = tempfile.mkdtemp(prefix='ansible_' + %(ansible_module)r + '_payload_')
|
|
315
|
-
|
|
316
|
-
zipped_mod = os.path.join(temp_path, 'ansible_' + %(ansible_module)r + '_payload.zip')
|
|
317
|
-
|
|
318
|
-
with open(zipped_mod, 'wb') as modlib:
|
|
319
|
-
modlib.write(base64.b64decode(ZIPDATA))
|
|
320
|
-
|
|
321
|
-
if len(sys.argv) == 2:
|
|
322
|
-
exitcode = debug(sys.argv[1], zipped_mod, ANSIBALLZ_PARAMS)
|
|
323
|
-
else:
|
|
324
|
-
# Note: temp_path isn't needed once we switch to zipimport
|
|
325
|
-
invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
|
|
326
|
-
finally:
|
|
327
|
-
try:
|
|
328
|
-
shutil.rmtree(temp_path)
|
|
329
|
-
except (NameError, OSError):
|
|
330
|
-
# tempdir creation probably failed
|
|
331
|
-
pass
|
|
332
|
-
sys.exit(exitcode)
|
|
333
|
-
|
|
334
|
-
if __name__ == '__main__':
|
|
335
|
-
_ansiballz_main()
|
|
336
|
-
'''
|
|
337
|
-
|
|
338
|
-
ANSIBALLZ_COVERAGE_TEMPLATE = '''
|
|
339
|
-
os.environ['COVERAGE_FILE'] = %(coverage_output)r + '=python-%%s=coverage' %% '.'.join(str(v) for v in sys.version_info[:2])
|
|
340
|
-
|
|
341
|
-
import atexit
|
|
342
|
-
|
|
343
|
-
try:
|
|
344
|
-
import coverage
|
|
345
|
-
except ImportError:
|
|
346
|
-
print('{"msg": "Could not import `coverage` module.", "failed": true}')
|
|
347
|
-
sys.exit(1)
|
|
348
|
-
|
|
349
|
-
cov = coverage.Coverage(config_file=%(coverage_config)r)
|
|
350
110
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
111
|
+
def _strip_comments(source: str) -> str:
|
|
112
|
+
# Strip comments and blank lines from the wrapper
|
|
113
|
+
buf = []
|
|
114
|
+
for line in source.splitlines():
|
|
115
|
+
l = line.strip()
|
|
116
|
+
if (not l or l.startswith('#')) and l != _SHEBANG_PLACEHOLDER:
|
|
117
|
+
line = ''
|
|
118
|
+
buf.append(line)
|
|
119
|
+
return '\n'.join(buf)
|
|
354
120
|
|
|
355
|
-
atexit.register(atexit_coverage)
|
|
356
121
|
|
|
357
|
-
|
|
358
|
-
''
|
|
122
|
+
def _read_ansiballz_code() -> str:
|
|
123
|
+
code = (pathlib.Path(_ansiballz.__file__).parent / '_wrapper.py').read_text()
|
|
359
124
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
raise ImportError
|
|
366
|
-
else:
|
|
367
|
-
import imp
|
|
368
|
-
imp.find_module('coverage')
|
|
369
|
-
except ImportError:
|
|
370
|
-
print('{"msg": "Could not find `coverage` module.", "failed": true}')
|
|
371
|
-
sys.exit(1)
|
|
372
|
-
'''
|
|
125
|
+
if not C.DEFAULT_KEEP_REMOTE_FILES:
|
|
126
|
+
# Keep comments when KEEP_REMOTE_FILES is set. That way users will see
|
|
127
|
+
# the comments with some nice usage instructions.
|
|
128
|
+
# Otherwise, strip comments for smaller over the wire size.
|
|
129
|
+
code = _strip_comments(code)
|
|
373
130
|
|
|
374
|
-
|
|
375
|
-
import resource
|
|
131
|
+
return code
|
|
376
132
|
|
|
377
|
-
existing_soft, existing_hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
378
133
|
|
|
379
|
-
|
|
380
|
-
requested_soft = min(existing_hard, %(rlimit_nofile)d)
|
|
134
|
+
_ANSIBALLZ_CODE = _read_ansiballz_code() # read during startup to prevent individual workers from doing so
|
|
381
135
|
|
|
382
|
-
if requested_soft != existing_soft:
|
|
383
|
-
try:
|
|
384
|
-
resource.setrlimit(resource.RLIMIT_NOFILE, (requested_soft, existing_hard))
|
|
385
|
-
except ValueError:
|
|
386
|
-
# some platforms (eg macOS) lie about their hard limit
|
|
387
|
-
pass
|
|
388
|
-
'''
|
|
389
136
|
|
|
137
|
+
def _get_ansiballz_code(shebang: str) -> str:
|
|
138
|
+
code = _ANSIBALLZ_CODE
|
|
139
|
+
code = code.replace(_SHEBANG_PLACEHOLDER, shebang)
|
|
390
140
|
|
|
391
|
-
|
|
392
|
-
# Strip comments and blank lines from the wrapper
|
|
393
|
-
buf = []
|
|
394
|
-
for line in source.splitlines():
|
|
395
|
-
l = line.strip()
|
|
396
|
-
if not l or l.startswith(u'#'):
|
|
397
|
-
continue
|
|
398
|
-
buf.append(line)
|
|
399
|
-
return u'\n'.join(buf)
|
|
400
|
-
|
|
141
|
+
return code
|
|
401
142
|
|
|
402
|
-
if C.DEFAULT_KEEP_REMOTE_FILES:
|
|
403
|
-
# Keep comments when KEEP_REMOTE_FILES is set. That way users will see
|
|
404
|
-
# the comments with some nice usage instructions
|
|
405
|
-
ACTIVE_ANSIBALLZ_TEMPLATE = ANSIBALLZ_TEMPLATE
|
|
406
|
-
else:
|
|
407
|
-
# ANSIBALLZ_TEMPLATE stripped of comments for smaller over the wire size
|
|
408
|
-
ACTIVE_ANSIBALLZ_TEMPLATE = _strip_comments(ANSIBALLZ_TEMPLATE)
|
|
409
143
|
|
|
410
144
|
# dirname(dirname(dirname(site-packages/ansible/executor/module_common.py) == site-packages
|
|
411
145
|
# Do this instead of getting site-packages from distutils.sysconfig so we work when we
|
|
@@ -435,6 +169,7 @@ NEW_STYLE_PYTHON_MODULE_RE = re.compile(
|
|
|
435
169
|
|
|
436
170
|
|
|
437
171
|
class ModuleDepFinder(ast.NodeVisitor):
|
|
172
|
+
# DTFIX-FUTURE: add support for ignoring imports with a "controller only" comment, this will allow replacing import_controller_module with standard imports
|
|
438
173
|
def __init__(self, module_fqn, tree, is_pkg_init=False, *args, **kwargs):
|
|
439
174
|
"""
|
|
440
175
|
Walk the ast tree for the python module.
|
|
@@ -581,7 +316,7 @@ def _slurp(path):
|
|
|
581
316
|
return data
|
|
582
317
|
|
|
583
318
|
|
|
584
|
-
def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=False):
|
|
319
|
+
def _get_shebang(interpreter, task_vars, templar: _template.Templar, args=tuple(), remote_is_local=False):
|
|
585
320
|
"""
|
|
586
321
|
Handles the different ways ansible allows overriding the shebang target for a module.
|
|
587
322
|
"""
|
|
@@ -606,7 +341,8 @@ def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=
|
|
|
606
341
|
elif C.config.get_configuration_definition(interpreter_config_key):
|
|
607
342
|
|
|
608
343
|
interpreter_from_config = C.config.get_config_value(interpreter_config_key, variables=task_vars)
|
|
609
|
-
interpreter_out = templar.template(
|
|
344
|
+
interpreter_out = templar._engine.template(_utils.str_problematic_strip(interpreter_from_config),
|
|
345
|
+
options=TemplateOptions(value_for_omit=C.config.get_config_default(interpreter_config_key)))
|
|
610
346
|
|
|
611
347
|
# handle interpreter discovery if requested or empty interpreter was provided
|
|
612
348
|
if not interpreter_out or interpreter_out in ['auto', 'auto_legacy', 'auto_silent', 'auto_legacy_silent']:
|
|
@@ -624,10 +360,11 @@ def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=
|
|
|
624
360
|
|
|
625
361
|
elif interpreter_config in task_vars:
|
|
626
362
|
# for non python we consult vars for a possible direct override
|
|
627
|
-
interpreter_out = templar.template(task_vars.get(interpreter_config)
|
|
363
|
+
interpreter_out = templar._engine.template(_utils.str_problematic_strip(task_vars.get(interpreter_config)),
|
|
364
|
+
options=TemplateOptions(value_for_omit=None))
|
|
628
365
|
|
|
629
366
|
if not interpreter_out:
|
|
630
|
-
# nothing matched(None) or in case someone configures empty string or empty
|
|
367
|
+
# nothing matched(None) or in case someone configures empty string or empty interpreter
|
|
631
368
|
interpreter_out = interpreter
|
|
632
369
|
|
|
633
370
|
# set shebang
|
|
@@ -700,7 +437,13 @@ class ModuleUtilLocatorBase:
|
|
|
700
437
|
else:
|
|
701
438
|
msg += '.'
|
|
702
439
|
|
|
703
|
-
display.deprecated(
|
|
440
|
+
display.deprecated( # pylint: disable=ansible-deprecated-date-not-permitted,ansible-deprecated-unnecessary-collection-name
|
|
441
|
+
msg=msg,
|
|
442
|
+
version=removal_version,
|
|
443
|
+
removed=removed,
|
|
444
|
+
date=removal_date,
|
|
445
|
+
deprecator=deprecator_from_collection_name(self._collection_name),
|
|
446
|
+
)
|
|
704
447
|
if 'redirect' in routing_entry:
|
|
705
448
|
self.redirected = True
|
|
706
449
|
source_pkg = '.'.join(name_parts)
|
|
@@ -803,12 +546,12 @@ class LegacyModuleUtilLocator(ModuleUtilLocatorBase):
|
|
|
803
546
|
|
|
804
547
|
# find_spec needs the full module name
|
|
805
548
|
self._info = info = importlib.machinery.PathFinder.find_spec('.'.join(name_parts), paths)
|
|
806
|
-
if info is not None and os.path.splitext(info.origin)[1] in importlib.machinery.SOURCE_SUFFIXES:
|
|
549
|
+
if info is not None and info.origin is not None and os.path.splitext(info.origin)[1] in importlib.machinery.SOURCE_SUFFIXES:
|
|
807
550
|
self.is_package = info.origin.endswith('/__init__.py')
|
|
808
551
|
path = info.origin
|
|
809
552
|
else:
|
|
810
553
|
return False
|
|
811
|
-
self.source_code = _slurp(path)
|
|
554
|
+
self.source_code = Origin(path=path).tag(_slurp(path))
|
|
812
555
|
|
|
813
556
|
return True
|
|
814
557
|
|
|
@@ -843,9 +586,18 @@ class CollectionModuleUtilLocator(ModuleUtilLocatorBase):
|
|
|
843
586
|
resource_base_path = os.path.join(*name_parts[3:])
|
|
844
587
|
|
|
845
588
|
src = None
|
|
589
|
+
|
|
846
590
|
# look for package_dir first, then module
|
|
591
|
+
src_path = to_native(os.path.join(resource_base_path, '__init__.py'))
|
|
592
|
+
|
|
847
593
|
try:
|
|
848
|
-
|
|
594
|
+
collection_pkg = importlib.import_module(collection_pkg_name)
|
|
595
|
+
pkg_path = os.path.dirname(collection_pkg.__file__)
|
|
596
|
+
except (ImportError, AttributeError):
|
|
597
|
+
pkg_path = None
|
|
598
|
+
|
|
599
|
+
try:
|
|
600
|
+
src = pkgutil.get_data(collection_pkg_name, src_path)
|
|
849
601
|
except ImportError:
|
|
850
602
|
pass
|
|
851
603
|
|
|
@@ -854,32 +606,123 @@ class CollectionModuleUtilLocator(ModuleUtilLocatorBase):
|
|
|
854
606
|
if src is not None: # empty string is OK
|
|
855
607
|
self.is_package = True
|
|
856
608
|
else:
|
|
609
|
+
src_path = to_native(resource_base_path + '.py')
|
|
610
|
+
|
|
857
611
|
try:
|
|
858
|
-
src = pkgutil.get_data(collection_pkg_name,
|
|
612
|
+
src = pkgutil.get_data(collection_pkg_name, src_path)
|
|
859
613
|
except ImportError:
|
|
860
614
|
pass
|
|
861
615
|
|
|
862
616
|
if src is None: # empty string is OK
|
|
863
617
|
return False
|
|
864
618
|
|
|
865
|
-
|
|
619
|
+
# TODO: this feels brittle and funky; we should be able to more definitively assure the source path
|
|
620
|
+
|
|
621
|
+
if pkg_path:
|
|
622
|
+
origin = Origin(path=os.path.join(pkg_path, src_path))
|
|
623
|
+
else:
|
|
624
|
+
# DTFIX-FUTURE: not sure if this case is even reachable
|
|
625
|
+
origin = Origin(description=f'<synthetic collection package for {collection_pkg_name}!r>')
|
|
626
|
+
|
|
627
|
+
self.source_code = origin.tag(src)
|
|
866
628
|
return True
|
|
867
629
|
|
|
868
630
|
def _get_module_utils_remainder_parts(self, name_parts):
|
|
869
631
|
return name_parts[5:] # eg, foo.bar for ansible_collections.ns.coll.plugins.module_utils.foo.bar
|
|
870
632
|
|
|
871
633
|
|
|
872
|
-
def _make_zinfo(filename, date_time, zf=None):
|
|
634
|
+
def _make_zinfo(filename: str, date_time: datetime.datetime, zf: zipfile.ZipFile | None = None) -> zipfile.ZipInfo:
|
|
873
635
|
zinfo = zipfile.ZipInfo(
|
|
874
636
|
filename=filename,
|
|
875
|
-
date_time=date_time
|
|
637
|
+
date_time=date_time.utctimetuple()[:6],
|
|
876
638
|
)
|
|
639
|
+
|
|
877
640
|
if zf:
|
|
878
641
|
zinfo.compress_type = zf.compression
|
|
642
|
+
|
|
879
643
|
return zinfo
|
|
880
644
|
|
|
881
645
|
|
|
882
|
-
|
|
646
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
|
647
|
+
class ModuleMetadata:
|
|
648
|
+
@classmethod
|
|
649
|
+
def __post_init__(cls):
|
|
650
|
+
_dataclass_validation.inject_post_init_validation(cls)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
|
654
|
+
class ModuleMetadataV1(ModuleMetadata):
|
|
655
|
+
serialization_profile: str
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
metadata_versions: dict[t.Any, type[ModuleMetadata]] = {
|
|
659
|
+
1: ModuleMetadataV1,
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
_DEFAULT_LEGACY_METADATA = ModuleMetadataV1(serialization_profile='legacy')
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
|
|
666
|
+
# experimental module metadata; off by default
|
|
667
|
+
if not C.config.get_config_value('_MODULE_METADATA'):
|
|
668
|
+
return _DEFAULT_LEGACY_METADATA
|
|
669
|
+
|
|
670
|
+
metadata_nodes: list[ast.Assign] = []
|
|
671
|
+
|
|
672
|
+
for node in module.body:
|
|
673
|
+
if isinstance(node, ast.Assign):
|
|
674
|
+
if len(node.targets) == 1:
|
|
675
|
+
target = node.targets[0]
|
|
676
|
+
|
|
677
|
+
if isinstance(target, ast.Name):
|
|
678
|
+
if target.id == 'METADATA':
|
|
679
|
+
metadata_nodes.append(node)
|
|
680
|
+
|
|
681
|
+
if not metadata_nodes:
|
|
682
|
+
return _DEFAULT_LEGACY_METADATA
|
|
683
|
+
|
|
684
|
+
if len(metadata_nodes) > 1:
|
|
685
|
+
raise ValueError('Module METADATA must defined only once.')
|
|
686
|
+
|
|
687
|
+
metadata_node = metadata_nodes[0]
|
|
688
|
+
|
|
689
|
+
if not isinstance(metadata_node.value, ast.Constant):
|
|
690
|
+
raise TypeError(f'Module METADATA node must be {ast.Constant} not {type(metadata_node)}.')
|
|
691
|
+
|
|
692
|
+
unparsed_metadata = metadata_node.value.value
|
|
693
|
+
|
|
694
|
+
if not isinstance(unparsed_metadata, str):
|
|
695
|
+
raise TypeError(f'Module METADATA must be {str} not {type(unparsed_metadata)}.')
|
|
696
|
+
|
|
697
|
+
try:
|
|
698
|
+
parsed_metadata = yaml_load(unparsed_metadata)
|
|
699
|
+
except Exception as ex:
|
|
700
|
+
raise ValueError('Module METADATA must be valid YAML.') from ex
|
|
701
|
+
|
|
702
|
+
if not isinstance(parsed_metadata, dict):
|
|
703
|
+
raise TypeError(f'Module METADATA must parse to {dict} not {type(parsed_metadata)}.')
|
|
704
|
+
|
|
705
|
+
schema_version = parsed_metadata.pop('schema_version', None)
|
|
706
|
+
|
|
707
|
+
if not (metadata_type := metadata_versions.get(schema_version)):
|
|
708
|
+
raise ValueError(f'Module METADATA schema_version {schema_version} is unknown.')
|
|
709
|
+
|
|
710
|
+
try:
|
|
711
|
+
metadata = metadata_type(**parsed_metadata) # type: ignore
|
|
712
|
+
except Exception as ex:
|
|
713
|
+
raise ValueError('Module METADATA is invalid.') from ex
|
|
714
|
+
|
|
715
|
+
return metadata
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def recursive_finder(
|
|
719
|
+
name: str,
|
|
720
|
+
module_fqn: str,
|
|
721
|
+
module_data: str | bytes,
|
|
722
|
+
zf: zipfile.ZipFile,
|
|
723
|
+
date_time: datetime.datetime,
|
|
724
|
+
extension_manager: _builder.ExtensionManager,
|
|
725
|
+
) -> ModuleMetadata:
|
|
883
726
|
"""
|
|
884
727
|
Using ModuleDepFinder, make sure we have all of the module_utils files that
|
|
885
728
|
the module and its module_utils files needs. (no longer actually recursive)
|
|
@@ -889,9 +732,6 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
|
|
|
889
732
|
:arg zf: An open :python:class:`zipfile.ZipFile` object that holds the Ansible module payload
|
|
890
733
|
which we're assembling
|
|
891
734
|
"""
|
|
892
|
-
if date_time is None:
|
|
893
|
-
date_time = time.gmtime()[:6]
|
|
894
|
-
|
|
895
735
|
# py_module_cache maps python module names to a tuple of the code in the module
|
|
896
736
|
# and the pathname to the module.
|
|
897
737
|
# Here we pre-load it with modules which we create without bothering to
|
|
@@ -913,49 +753,59 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
|
|
|
913
753
|
module_utils_paths = [p for p in module_utils_loader._get_paths(subdirs=False) if os.path.isdir(p)]
|
|
914
754
|
module_utils_paths.append(_MODULE_UTILS_PATH)
|
|
915
755
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
tree = compile(module_data, '<unknown>', 'exec', ast.PyCF_ONLY_AST)
|
|
919
|
-
except (SyntaxError, IndentationError) as e:
|
|
920
|
-
raise AnsibleError("Unable to import %s due to %s" % (name, e.msg))
|
|
921
|
-
|
|
756
|
+
tree = _compile_module_ast(name, module_data)
|
|
757
|
+
module_metadata = _get_module_metadata(tree)
|
|
922
758
|
finder = ModuleDepFinder(module_fqn, tree)
|
|
923
759
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
modules_to_process = [ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports) for m in finder.submodules]
|
|
760
|
+
if not isinstance(module_metadata, ModuleMetadataV1):
|
|
761
|
+
raise NotImplementedError()
|
|
927
762
|
|
|
928
|
-
|
|
929
|
-
|
|
763
|
+
profile = module_metadata.serialization_profile
|
|
764
|
+
|
|
765
|
+
# the format of this set is a tuple of the module name and whether the import is ambiguous as a module name
|
|
766
|
+
# or an attribute of a module (e.g. from x.y import z <-- is z a module or an attribute of x.y?)
|
|
767
|
+
modules_to_process = [_ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports) for m in finder.submodules]
|
|
768
|
+
|
|
769
|
+
# include module_utils that are always required
|
|
770
|
+
modules_to_process.extend((
|
|
771
|
+
_ModuleUtilsProcessEntry.from_module(_loader),
|
|
772
|
+
_ModuleUtilsProcessEntry.from_module(_basic),
|
|
773
|
+
_ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, True)),
|
|
774
|
+
_ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, False)),
|
|
775
|
+
))
|
|
776
|
+
|
|
777
|
+
modules_to_process.extend(_ModuleUtilsProcessEntry.from_module_name(name) for name in extension_manager.module_names)
|
|
778
|
+
|
|
779
|
+
module_info: ModuleUtilLocatorBase
|
|
930
780
|
|
|
931
781
|
# we'll be adding new modules inline as we discover them, so just keep going til we've processed them all
|
|
932
782
|
while modules_to_process:
|
|
933
783
|
modules_to_process.sort() # not strictly necessary, but nice to process things in predictable and repeatable order
|
|
934
|
-
|
|
784
|
+
entry = modules_to_process.pop(0)
|
|
935
785
|
|
|
936
|
-
if
|
|
786
|
+
if entry.name_parts in py_module_cache:
|
|
937
787
|
# this is normal; we'll often see the same module imported many times, but we only need to process it once
|
|
938
788
|
continue
|
|
939
789
|
|
|
940
|
-
if
|
|
941
|
-
module_info = LegacyModuleUtilLocator(
|
|
942
|
-
mu_paths=module_utils_paths, child_is_redirected=child_is_redirected)
|
|
943
|
-
elif
|
|
944
|
-
module_info = CollectionModuleUtilLocator(
|
|
945
|
-
child_is_redirected=child_is_redirected, is_optional=is_optional)
|
|
790
|
+
if entry.name_parts[0:2] == ('ansible', 'module_utils'):
|
|
791
|
+
module_info = LegacyModuleUtilLocator(entry.name_parts, is_ambiguous=entry.is_ambiguous,
|
|
792
|
+
mu_paths=module_utils_paths, child_is_redirected=entry.child_is_redirected)
|
|
793
|
+
elif entry.name_parts[0] == 'ansible_collections':
|
|
794
|
+
module_info = CollectionModuleUtilLocator(entry.name_parts, is_ambiguous=entry.is_ambiguous,
|
|
795
|
+
child_is_redirected=entry.child_is_redirected, is_optional=entry.is_optional)
|
|
946
796
|
else:
|
|
947
797
|
# FIXME: dot-joined result
|
|
948
798
|
display.warning('ModuleDepFinder improperly found a non-module_utils import %s'
|
|
949
|
-
% [
|
|
799
|
+
% [entry.name_parts])
|
|
950
800
|
continue
|
|
951
801
|
|
|
952
802
|
# Could not find the module. Construct a helpful error message.
|
|
953
803
|
if not module_info.found:
|
|
954
|
-
if is_optional:
|
|
804
|
+
if entry.is_optional:
|
|
955
805
|
# this was a best-effort optional import that we couldn't find, oh well, move along...
|
|
956
806
|
continue
|
|
957
807
|
# FIXME: use dot-joined candidate names
|
|
958
|
-
msg = 'Could not find imported module support code for {0}.
|
|
808
|
+
msg = 'Could not find imported module support code for {0}. Looked for ({1})'.format(module_fqn, module_info.candidate_names_joined)
|
|
959
809
|
raise AnsibleError(msg)
|
|
960
810
|
|
|
961
811
|
# check the cache one more time with the module we actually found, since the name could be different than the input
|
|
@@ -963,14 +813,9 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
|
|
|
963
813
|
if module_info.fq_name_parts in py_module_cache:
|
|
964
814
|
continue
|
|
965
815
|
|
|
966
|
-
|
|
967
|
-
try:
|
|
968
|
-
tree = compile(module_info.source_code, '<unknown>', 'exec', ast.PyCF_ONLY_AST)
|
|
969
|
-
except (SyntaxError, IndentationError) as e:
|
|
970
|
-
raise AnsibleError("Unable to import %s due to %s" % (module_info.fq_name_parts, e.msg))
|
|
971
|
-
|
|
816
|
+
tree = _compile_module_ast('.'.join(module_info.fq_name_parts), module_info.source_code)
|
|
972
817
|
finder = ModuleDepFinder('.'.join(module_info.fq_name_parts), tree, module_info.is_package)
|
|
973
|
-
modules_to_process.extend(
|
|
818
|
+
modules_to_process.extend(_ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports)
|
|
974
819
|
for m in finder.submodules if m not in py_module_cache)
|
|
975
820
|
|
|
976
821
|
# we've processed this item, add it to the output list
|
|
@@ -982,20 +827,36 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
|
|
|
982
827
|
accumulated_pkg_name.append(pkg) # we're accumulating this across iterations
|
|
983
828
|
normalized_name = tuple(accumulated_pkg_name) # extra machinations to get a hashable type (list is not)
|
|
984
829
|
if normalized_name not in py_module_cache:
|
|
985
|
-
modules_to_process.append(
|
|
830
|
+
modules_to_process.append(_ModuleUtilsProcessEntry(normalized_name, False, module_info.redirected, is_optional=entry.is_optional))
|
|
986
831
|
|
|
987
832
|
for py_module_name in py_module_cache:
|
|
988
|
-
py_module_file_name = py_module_cache[py_module_name]
|
|
833
|
+
source_code, py_module_file_name = py_module_cache[py_module_name]
|
|
834
|
+
|
|
835
|
+
zf.writestr(_make_zinfo(py_module_file_name, date_time, zf=zf), source_code)
|
|
836
|
+
|
|
837
|
+
if extension_manager.debugger_enabled and (origin := Origin.get_tag(source_code)) and origin.path:
|
|
838
|
+
extension_manager.source_mapping[origin.path] = py_module_file_name
|
|
989
839
|
|
|
990
|
-
zf.writestr(
|
|
991
|
-
_make_zinfo(py_module_file_name, date_time, zf=zf),
|
|
992
|
-
py_module_cache[py_module_name][0]
|
|
993
|
-
)
|
|
994
840
|
mu_file = to_text(py_module_file_name, errors='surrogate_or_strict')
|
|
995
841
|
display.vvvvv("Including module_utils file %s" % mu_file)
|
|
996
842
|
|
|
843
|
+
return module_metadata
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def _compile_module_ast(module_name: str, source_code: str | bytes) -> ast.Module:
|
|
847
|
+
origin = Origin.get_tag(source_code) or Origin.UNKNOWN
|
|
848
|
+
|
|
849
|
+
# compile the source, process all relevant imported modules
|
|
850
|
+
try:
|
|
851
|
+
tree = t.cast(ast.Module, compile(source_code, str(origin), 'exec', ast.PyCF_ONLY_AST))
|
|
852
|
+
except SyntaxError as ex:
|
|
853
|
+
raise AnsibleError(f"Unable to compile {module_name!r}.", obj=origin.replace(line_num=ex.lineno, col_num=ex.offset)) from ex
|
|
854
|
+
|
|
855
|
+
return tree
|
|
856
|
+
|
|
997
857
|
|
|
998
858
|
def _is_binary(b_module_data):
|
|
859
|
+
"""Heuristic to classify a file as binary by sniffing a 1k header; see https://stackoverflow.com/a/7392391"""
|
|
999
860
|
textchars = bytearray(set([7, 8, 9, 10, 12, 13, 27]) | set(range(0x20, 0x100)) - set([0x7f]))
|
|
1000
861
|
start = b_module_data[:1024]
|
|
1001
862
|
return bool(start.translate(None, textchars))
|
|
@@ -1034,17 +895,29 @@ def _get_ansible_module_fqn(module_path):
|
|
|
1034
895
|
return remote_module_fqn
|
|
1035
896
|
|
|
1036
897
|
|
|
1037
|
-
def _add_module_to_zip(
|
|
898
|
+
def _add_module_to_zip(
|
|
899
|
+
zf: zipfile.ZipFile,
|
|
900
|
+
date_time: datetime.datetime,
|
|
901
|
+
remote_module_fqn: str,
|
|
902
|
+
b_module_data: bytes,
|
|
903
|
+
module_path: str,
|
|
904
|
+
extension_manager: _builder.ExtensionManager,
|
|
905
|
+
) -> None:
|
|
1038
906
|
"""Add a module from ansible or from an ansible collection into the module zip"""
|
|
1039
907
|
module_path_parts = remote_module_fqn.split('.')
|
|
1040
908
|
|
|
1041
909
|
# Write the module
|
|
1042
|
-
|
|
910
|
+
zip_module_path = '/'.join(module_path_parts) + '.py'
|
|
1043
911
|
zf.writestr(
|
|
1044
|
-
_make_zinfo(
|
|
912
|
+
_make_zinfo(zip_module_path, date_time, zf=zf),
|
|
1045
913
|
b_module_data
|
|
1046
914
|
)
|
|
1047
915
|
|
|
916
|
+
if extension_manager.debugger_enabled:
|
|
917
|
+
extension_manager.source_mapping[module_path] = zip_module_path
|
|
918
|
+
|
|
919
|
+
existing_paths: frozenset[str]
|
|
920
|
+
|
|
1048
921
|
# Write the __init__.py's necessary to get there
|
|
1049
922
|
if module_path_parts[0] == 'ansible':
|
|
1050
923
|
# The ansible namespace is setup as part of the module_utils setup...
|
|
@@ -1068,12 +941,60 @@ def _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data):
|
|
|
1068
941
|
)
|
|
1069
942
|
|
|
1070
943
|
|
|
1071
|
-
|
|
1072
|
-
|
|
944
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
|
|
945
|
+
class _BuiltModule:
|
|
946
|
+
"""Payload required to execute an Ansible module, along with information required to do so."""
|
|
947
|
+
b_module_data: bytes
|
|
948
|
+
module_style: t.Literal['binary', 'new', 'non_native_want_json', 'old']
|
|
949
|
+
shebang: str | None
|
|
950
|
+
serialization_profile: str
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
|
|
954
|
+
class _CachedModule:
|
|
955
|
+
"""Cached Python module created by AnsiballZ."""
|
|
956
|
+
|
|
957
|
+
# FIXME: switch this to use a locked down pickle config or don't use pickle- easy to mess up and reach objects that shouldn't be pickled
|
|
958
|
+
|
|
959
|
+
zip_data: bytes
|
|
960
|
+
metadata: ModuleMetadata
|
|
961
|
+
source_mapping: dict[str, str]
|
|
962
|
+
"""A mapping of controller absolute source locations to target relative source locations within the AnsiballZ payload."""
|
|
963
|
+
|
|
964
|
+
def dump(self, path: str) -> None:
|
|
965
|
+
temp_path = pathlib.Path(path + '-part')
|
|
966
|
+
|
|
967
|
+
with temp_path.open('wb') as cache_file:
|
|
968
|
+
pickle.dump(self, cache_file)
|
|
969
|
+
|
|
970
|
+
temp_path.rename(path)
|
|
971
|
+
|
|
972
|
+
@classmethod
|
|
973
|
+
def load(cls, path: str) -> t.Self:
|
|
974
|
+
with pathlib.Path(path).open('rb') as cache_file:
|
|
975
|
+
return pickle.load(cache_file)
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def _find_module_utils(
|
|
979
|
+
*,
|
|
980
|
+
module_name: str,
|
|
981
|
+
b_module_data: bytes,
|
|
982
|
+
module_path: str,
|
|
983
|
+
module_args: dict[object, object],
|
|
984
|
+
task_vars: dict[str, object],
|
|
985
|
+
templar: Templar,
|
|
986
|
+
module_compression: str,
|
|
987
|
+
async_timeout: int,
|
|
988
|
+
become_plugin: BecomeBase | None,
|
|
989
|
+
environment: dict[str, str],
|
|
990
|
+
remote_is_local: bool = False
|
|
991
|
+
) -> _BuiltModule:
|
|
1073
992
|
"""
|
|
1074
993
|
Given the source of the module, convert it to a Jinja2 template to insert
|
|
1075
994
|
module code and return whether it's a new or old style module.
|
|
1076
995
|
"""
|
|
996
|
+
module_substyle: t.Literal['binary', 'jsonargs', 'non_native_want_json', 'old', 'powershell', 'python']
|
|
997
|
+
module_style: t.Literal['binary', 'new', 'non_native_want_json', 'old']
|
|
1077
998
|
module_substyle = module_style = 'old'
|
|
1078
999
|
|
|
1079
1000
|
# module_style is something important to calling code (ActionBase). It
|
|
@@ -1096,12 +1017,10 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
|
1096
1017
|
elif REPLACER_WINDOWS in b_module_data:
|
|
1097
1018
|
module_style = 'new'
|
|
1098
1019
|
module_substyle = 'powershell'
|
|
1099
|
-
b_module_data = b_module_data.replace(REPLACER_WINDOWS, b'#
|
|
1020
|
+
b_module_data = b_module_data.replace(REPLACER_WINDOWS, b'#AnsibleRequires -PowerShell Ansible.ModuleUtils.Legacy')
|
|
1100
1021
|
elif re.search(b'#Requires -Module', b_module_data, re.IGNORECASE) \
|
|
1101
|
-
or re.search(b'#Requires -Version', b_module_data, re.IGNORECASE)\
|
|
1102
|
-
or re.search(b'#AnsibleRequires -OSVersion', b_module_data, re.IGNORECASE)
|
|
1103
|
-
or re.search(b'#AnsibleRequires -Powershell', b_module_data, re.IGNORECASE) \
|
|
1104
|
-
or re.search(b'#AnsibleRequires -CSharpUtil', b_module_data, re.IGNORECASE):
|
|
1022
|
+
or re.search(b'#Requires -Version', b_module_data, re.IGNORECASE) \
|
|
1023
|
+
or re.search(b'#AnsibleRequires -(OSVersion|PowerShell|CSharpUtil|Wrapper)', b_module_data, re.IGNORECASE):
|
|
1105
1024
|
module_style = 'new'
|
|
1106
1025
|
module_substyle = 'powershell'
|
|
1107
1026
|
elif REPLACER_JSONARGS in b_module_data:
|
|
@@ -1114,7 +1033,12 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
|
1114
1033
|
# Neither old-style, non_native_want_json nor binary modules should be modified
|
|
1115
1034
|
# except for the shebang line (Done by modify_module)
|
|
1116
1035
|
if module_style in ('old', 'non_native_want_json', 'binary'):
|
|
1117
|
-
return
|
|
1036
|
+
return _BuiltModule(
|
|
1037
|
+
b_module_data=b_module_data,
|
|
1038
|
+
module_style=module_style,
|
|
1039
|
+
shebang=shebang,
|
|
1040
|
+
serialization_profile='legacy',
|
|
1041
|
+
)
|
|
1118
1042
|
|
|
1119
1043
|
output = BytesIO()
|
|
1120
1044
|
|
|
@@ -1127,18 +1051,15 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
|
1127
1051
|
# People should start writing collections instead of modules in roles so we
|
|
1128
1052
|
# may never fix this
|
|
1129
1053
|
display.debug('ANSIBALLZ: Could not determine module FQN')
|
|
1130
|
-
|
|
1054
|
+
# FIXME: add integration test to validate that builtins and legacy modules with the same name are tracked separately by the caching mechanism
|
|
1055
|
+
# FIXME: surrogate FQN should be unique per source path- role-packaged modules with name collisions can still be aliased
|
|
1056
|
+
remote_module_fqn = 'ansible.legacy.%s' % module_name
|
|
1131
1057
|
|
|
1132
1058
|
if module_substyle == 'python':
|
|
1133
|
-
date_time =
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
raise AnsibleError(f'Cannot create zipfile due to pre-1980 configured date: {
|
|
1137
|
-
params = dict(ANSIBLE_MODULE_ARGS=module_args,)
|
|
1138
|
-
try:
|
|
1139
|
-
python_repred_params = repr(json.dumps(params, cls=AnsibleJSONEncoder, vault_to_text=True))
|
|
1140
|
-
except TypeError as e:
|
|
1141
|
-
raise AnsibleError("Unable to pass options to module, they must be JSON serializable: %s" % to_native(e))
|
|
1059
|
+
date_time = datetime.datetime.now(datetime.timezone.utc)
|
|
1060
|
+
|
|
1061
|
+
if date_time.year < 1980:
|
|
1062
|
+
raise AnsibleError(f'Cannot create zipfile due to pre-1980 configured date: {date_time}')
|
|
1142
1063
|
|
|
1143
1064
|
try:
|
|
1144
1065
|
compression_method = getattr(zipfile, module_compression)
|
|
@@ -1146,30 +1067,24 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
|
1146
1067
|
display.warning(u'Bad module compression string specified: %s. Using ZIP_STORED (no compression)' % module_compression)
|
|
1147
1068
|
compression_method = zipfile.ZIP_STORED
|
|
1148
1069
|
|
|
1149
|
-
|
|
1150
|
-
|
|
1070
|
+
extension_manager = _builder.ExtensionManager.create(task_vars=task_vars)
|
|
1071
|
+
extension_key = '~'.join(extension_manager.extension_names) if extension_manager.extension_names else 'none'
|
|
1072
|
+
lookup_path = os.path.join(C.DEFAULT_LOCAL_TMP, 'ansiballz_cache') # type: ignore[attr-defined]
|
|
1073
|
+
cached_module_filename = os.path.join(lookup_path, '-'.join((remote_module_fqn, module_compression, extension_key)))
|
|
1074
|
+
|
|
1075
|
+
os.makedirs(os.path.dirname(cached_module_filename), exist_ok=True)
|
|
1076
|
+
|
|
1077
|
+
cached_module: _CachedModule | None = None
|
|
1151
1078
|
|
|
1152
|
-
zipdata = None
|
|
1153
1079
|
# Optimization -- don't lock if the module has already been cached
|
|
1154
1080
|
if os.path.exists(cached_module_filename):
|
|
1155
1081
|
display.debug('ANSIBALLZ: using cached module: %s' % cached_module_filename)
|
|
1156
|
-
|
|
1157
|
-
zipdata = module_data.read()
|
|
1082
|
+
cached_module = _CachedModule.load(cached_module_filename)
|
|
1158
1083
|
else:
|
|
1159
|
-
if module_name in action_write_locks.action_write_locks:
|
|
1160
|
-
display.debug('ANSIBALLZ: Using lock for %s' % module_name)
|
|
1161
|
-
lock = action_write_locks.action_write_locks[module_name]
|
|
1162
|
-
else:
|
|
1163
|
-
# If the action plugin directly invokes the module (instead of
|
|
1164
|
-
# going through a strategy) then we don't have a cross-process
|
|
1165
|
-
# Lock specifically for this module. Use the "unexpected
|
|
1166
|
-
# module" lock instead
|
|
1167
|
-
display.debug('ANSIBALLZ: Using generic lock for %s' % module_name)
|
|
1168
|
-
lock = action_write_locks.action_write_locks[None]
|
|
1169
|
-
|
|
1170
1084
|
display.debug('ANSIBALLZ: Acquiring lock')
|
|
1171
|
-
|
|
1172
|
-
|
|
1085
|
+
lock_path = f'{cached_module_filename}.lock'
|
|
1086
|
+
with _locking.named_mutex(lock_path):
|
|
1087
|
+
display.debug(f'ANSIBALLZ: Lock acquired: {lock_path}')
|
|
1173
1088
|
# Check that no other process has created this while we were
|
|
1174
1089
|
# waiting for the lock
|
|
1175
1090
|
if not os.path.exists(cached_module_filename):
|
|
@@ -1179,53 +1094,40 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
|
1179
1094
|
zf = zipfile.ZipFile(zipoutput, mode='w', compression=compression_method)
|
|
1180
1095
|
|
|
1181
1096
|
# walk the module imports, looking for module_utils to send- they'll be added to the zipfile
|
|
1182
|
-
recursive_finder(
|
|
1097
|
+
module_metadata = recursive_finder(
|
|
1098
|
+
module_name,
|
|
1099
|
+
remote_module_fqn,
|
|
1100
|
+
Origin(path=module_path).tag(b_module_data),
|
|
1101
|
+
zf,
|
|
1102
|
+
date_time,
|
|
1103
|
+
extension_manager,
|
|
1104
|
+
)
|
|
1183
1105
|
|
|
1184
1106
|
display.debug('ANSIBALLZ: Writing module into payload')
|
|
1185
|
-
_add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data)
|
|
1107
|
+
_add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data, module_path, extension_manager)
|
|
1186
1108
|
|
|
1187
1109
|
zf.close()
|
|
1188
|
-
|
|
1110
|
+
zip_data = base64.b64encode(zipoutput.getvalue())
|
|
1189
1111
|
|
|
1190
1112
|
# Write the assembled module to a temp file (write to temp
|
|
1191
1113
|
# so that no one looking for the file reads a partially
|
|
1192
1114
|
# written file)
|
|
1193
|
-
|
|
1194
|
-
# FIXME: Once split controller/remote is merged, this can be simplified to
|
|
1195
|
-
# os.makedirs(lookup_path, exist_ok=True)
|
|
1196
|
-
if not os.path.exists(lookup_path):
|
|
1197
|
-
try:
|
|
1198
|
-
# Note -- if we have a global function to setup, that would
|
|
1199
|
-
# be a better place to run this
|
|
1200
|
-
os.makedirs(lookup_path)
|
|
1201
|
-
except OSError:
|
|
1202
|
-
# Multiple processes tried to create the directory. If it still does not
|
|
1203
|
-
# exist, raise the original exception.
|
|
1204
|
-
if not os.path.exists(lookup_path):
|
|
1205
|
-
raise
|
|
1115
|
+
os.makedirs(lookup_path, exist_ok=True)
|
|
1206
1116
|
display.debug('ANSIBALLZ: Writing module')
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
# Rename the file into its final position in the cache so
|
|
1211
|
-
# future users of this module can read it off the
|
|
1212
|
-
# filesystem instead of constructing from scratch.
|
|
1213
|
-
display.debug('ANSIBALLZ: Renaming module')
|
|
1214
|
-
os.rename(cached_module_filename + '-part', cached_module_filename)
|
|
1117
|
+
cached_module = _CachedModule(zip_data=zip_data, metadata=module_metadata, source_mapping=extension_manager.source_mapping)
|
|
1118
|
+
cached_module.dump(cached_module_filename)
|
|
1215
1119
|
display.debug('ANSIBALLZ: Done creating module')
|
|
1216
1120
|
|
|
1217
|
-
if
|
|
1121
|
+
if not cached_module:
|
|
1218
1122
|
display.debug('ANSIBALLZ: Reading module after lock')
|
|
1219
1123
|
# Another process wrote the file while we were waiting for
|
|
1220
1124
|
# the write lock. Go ahead and read the data from disk
|
|
1221
1125
|
# instead of re-creating it.
|
|
1222
1126
|
try:
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
except IOError:
|
|
1127
|
+
cached_module = _CachedModule.load(cached_module_filename)
|
|
1128
|
+
except OSError as ex:
|
|
1226
1129
|
raise AnsibleError('A different worker process failed to create module file. '
|
|
1227
|
-
'Look at traceback for that process for debugging information.')
|
|
1228
|
-
zipdata = to_text(zipdata, errors='surrogate_or_strict')
|
|
1130
|
+
'Look at traceback for that process for debugging information.') from ex
|
|
1229
1131
|
|
|
1230
1132
|
o_interpreter, o_args = _extract_interpreter(b_module_data)
|
|
1231
1133
|
if o_interpreter is None:
|
|
@@ -1237,63 +1139,75 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
|
1237
1139
|
rlimit_nofile = C.config.get_config_value('PYTHON_MODULE_RLIMIT_NOFILE', variables=task_vars)
|
|
1238
1140
|
|
|
1239
1141
|
if not isinstance(rlimit_nofile, int):
|
|
1240
|
-
rlimit_nofile = int(templar.template(rlimit_nofile))
|
|
1142
|
+
rlimit_nofile = int(templar._engine.template(rlimit_nofile, options=TemplateOptions(value_for_omit=0)))
|
|
1241
1143
|
|
|
1242
|
-
if
|
|
1243
|
-
|
|
1244
|
-
rlimit_nofile=rlimit_nofile,
|
|
1245
|
-
)
|
|
1246
|
-
else:
|
|
1247
|
-
rlimit = ''
|
|
1144
|
+
if not isinstance(cached_module.metadata, ModuleMetadataV1):
|
|
1145
|
+
raise NotImplementedError()
|
|
1248
1146
|
|
|
1249
|
-
|
|
1147
|
+
params = dict(ANSIBLE_MODULE_ARGS=module_args,)
|
|
1148
|
+
encoder = get_module_encoder(cached_module.metadata.serialization_profile, Direction.CONTROLLER_TO_MODULE)
|
|
1250
1149
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1150
|
+
try:
|
|
1151
|
+
encoded_params = json.dumps(params, cls=encoder)
|
|
1152
|
+
except TypeError as ex:
|
|
1153
|
+
raise AnsibleError(f'Failed to serialize arguments for the {module_name!r} module.') from ex
|
|
1253
1154
|
|
|
1254
|
-
|
|
1255
|
-
# Enable code coverage analysis of the module.
|
|
1256
|
-
# This feature is for internal testing and may change without notice.
|
|
1257
|
-
coverage = ANSIBALLZ_COVERAGE_TEMPLATE % dict(
|
|
1258
|
-
coverage_config=coverage_config,
|
|
1259
|
-
coverage_output=coverage_output,
|
|
1260
|
-
)
|
|
1261
|
-
else:
|
|
1262
|
-
# Verify coverage is available without importing it.
|
|
1263
|
-
# This will detect when a module would fail with coverage enabled with minimal overhead.
|
|
1264
|
-
coverage = ANSIBALLZ_COVERAGE_CHECK_TEMPLATE
|
|
1265
|
-
else:
|
|
1266
|
-
coverage = ''
|
|
1155
|
+
extension_manager.source_mapping = cached_module.source_mapping
|
|
1267
1156
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1157
|
+
code = _get_ansiballz_code(shebang)
|
|
1158
|
+
args = dict(
|
|
1270
1159
|
ansible_module=module_name,
|
|
1271
1160
|
module_fqn=remote_module_fqn,
|
|
1272
|
-
|
|
1273
|
-
shebang=shebang,
|
|
1274
|
-
coding=ENCODING_STRING,
|
|
1161
|
+
profile=cached_module.metadata.serialization_profile,
|
|
1275
1162
|
date_time=date_time,
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1163
|
+
rlimit_nofile=rlimit_nofile,
|
|
1164
|
+
params=encoded_params,
|
|
1165
|
+
extensions=extension_manager.get_extensions(),
|
|
1166
|
+
zip_data=to_text(cached_module.zip_data),
|
|
1167
|
+
)
|
|
1168
|
+
|
|
1169
|
+
args_string = '\n'.join(f'{key}={value!r},' for key, value in args.items())
|
|
1170
|
+
|
|
1171
|
+
wrapper = f"""{code}
|
|
1172
|
+
|
|
1173
|
+
|
|
1174
|
+
if __name__ == "__main__":
|
|
1175
|
+
_ansiballz_main(
|
|
1176
|
+
{args_string}
|
|
1177
|
+
)
|
|
1178
|
+
"""
|
|
1179
|
+
|
|
1180
|
+
output.write(to_bytes(wrapper))
|
|
1181
|
+
|
|
1182
|
+
module_metadata = cached_module.metadata
|
|
1279
1183
|
b_module_data = output.getvalue()
|
|
1280
1184
|
|
|
1281
1185
|
elif module_substyle == 'powershell':
|
|
1186
|
+
module_metadata = ModuleMetadataV1(serialization_profile='legacy') # DTFIX-FUTURE: support serialization profiles for PowerShell modules
|
|
1187
|
+
|
|
1282
1188
|
# Powershell/winrm don't actually make use of shebang so we can
|
|
1283
1189
|
# safely set this here. If we let the fallback code handle this
|
|
1284
1190
|
# it can fail in the presence of the UTF8 BOM commonly added by
|
|
1285
1191
|
# Windows text editors
|
|
1286
|
-
shebang =
|
|
1192
|
+
shebang = '#!powershell'
|
|
1287
1193
|
# create the common exec wrapper payload and set that as the module_data
|
|
1288
1194
|
# bytes
|
|
1289
1195
|
b_module_data = ps_manifest._create_powershell_wrapper(
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1196
|
+
name=remote_module_fqn,
|
|
1197
|
+
module_data=b_module_data,
|
|
1198
|
+
module_path=module_path,
|
|
1199
|
+
module_args=module_args,
|
|
1200
|
+
environment=environment,
|
|
1201
|
+
async_timeout=async_timeout,
|
|
1202
|
+
become_plugin=become_plugin,
|
|
1203
|
+
substyle=module_substyle,
|
|
1204
|
+
task_vars=task_vars,
|
|
1205
|
+
profile=module_metadata.serialization_profile,
|
|
1293
1206
|
)
|
|
1294
1207
|
|
|
1295
1208
|
elif module_substyle == 'jsonargs':
|
|
1296
|
-
|
|
1209
|
+
encoder = get_module_encoder('legacy', Direction.CONTROLLER_TO_MODULE)
|
|
1210
|
+
module_args_json = to_bytes(json.dumps(module_args, cls=encoder))
|
|
1297
1211
|
|
|
1298
1212
|
# these strings could be included in a third-party module but
|
|
1299
1213
|
# officially they were included in the 'basic' snippet for new-style
|
|
@@ -1303,15 +1217,32 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
|
1303
1217
|
python_repred_args = to_bytes(repr(module_args_json))
|
|
1304
1218
|
b_module_data = b_module_data.replace(REPLACER_VERSION, to_bytes(repr(__version__)))
|
|
1305
1219
|
b_module_data = b_module_data.replace(REPLACER_COMPLEX, python_repred_args)
|
|
1306
|
-
b_module_data = b_module_data.replace(
|
|
1220
|
+
b_module_data = b_module_data.replace(
|
|
1221
|
+
REPLACER_SELINUX,
|
|
1222
|
+
to_bytes(','.join(C.DEFAULT_SELINUX_SPECIAL_FS))) # type: ignore[attr-defined]
|
|
1307
1223
|
|
|
1308
1224
|
# The main event -- substitute the JSON args string into the module
|
|
1309
1225
|
b_module_data = b_module_data.replace(REPLACER_JSONARGS, module_args_json)
|
|
1310
1226
|
|
|
1311
|
-
|
|
1227
|
+
syslog_facility = task_vars.get(
|
|
1228
|
+
'ansible_syslog_facility',
|
|
1229
|
+
C.DEFAULT_SYSLOG_FACILITY) # type: ignore[attr-defined]
|
|
1230
|
+
facility = b'syslog.' + to_bytes(syslog_facility, errors='surrogate_or_strict')
|
|
1312
1231
|
b_module_data = b_module_data.replace(b'syslog.LOG_USER', facility)
|
|
1313
1232
|
|
|
1314
|
-
|
|
1233
|
+
module_metadata = ModuleMetadataV1(serialization_profile='legacy')
|
|
1234
|
+
else:
|
|
1235
|
+
module_metadata = ModuleMetadataV1(serialization_profile='legacy')
|
|
1236
|
+
|
|
1237
|
+
if not isinstance(module_metadata, ModuleMetadataV1):
|
|
1238
|
+
raise NotImplementedError(type(module_metadata))
|
|
1239
|
+
|
|
1240
|
+
return _BuiltModule(
|
|
1241
|
+
b_module_data=b_module_data,
|
|
1242
|
+
module_style=module_style,
|
|
1243
|
+
shebang=shebang,
|
|
1244
|
+
serialization_profile=module_metadata.serialization_profile,
|
|
1245
|
+
)
|
|
1315
1246
|
|
|
1316
1247
|
|
|
1317
1248
|
def _extract_interpreter(b_module_data):
|
|
@@ -1337,8 +1268,19 @@ def _extract_interpreter(b_module_data):
|
|
|
1337
1268
|
return interpreter, args
|
|
1338
1269
|
|
|
1339
1270
|
|
|
1340
|
-
def modify_module(
|
|
1341
|
-
|
|
1271
|
+
def modify_module(
|
|
1272
|
+
*,
|
|
1273
|
+
module_name: str,
|
|
1274
|
+
module_path,
|
|
1275
|
+
module_args,
|
|
1276
|
+
templar,
|
|
1277
|
+
task_vars=None,
|
|
1278
|
+
module_compression='ZIP_STORED',
|
|
1279
|
+
async_timeout=0,
|
|
1280
|
+
become_plugin=None,
|
|
1281
|
+
environment=None,
|
|
1282
|
+
remote_is_local=False,
|
|
1283
|
+
) -> _BuiltModule:
|
|
1342
1284
|
"""
|
|
1343
1285
|
Used to insert chunks of code into modules before transfer rather than
|
|
1344
1286
|
doing regular python imports. This allows for more efficient transfer in
|
|
@@ -1367,13 +1309,30 @@ def modify_module(module_name, module_path, module_args, templar, task_vars=None
|
|
|
1367
1309
|
# read in the module source
|
|
1368
1310
|
b_module_data = f.read()
|
|
1369
1311
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1312
|
+
module_bits = _find_module_utils(
|
|
1313
|
+
module_name=module_name,
|
|
1314
|
+
b_module_data=b_module_data,
|
|
1315
|
+
module_path=module_path,
|
|
1316
|
+
module_args=module_args,
|
|
1317
|
+
task_vars=task_vars,
|
|
1318
|
+
templar=templar,
|
|
1319
|
+
module_compression=module_compression,
|
|
1320
|
+
async_timeout=async_timeout,
|
|
1321
|
+
become_plugin=become_plugin,
|
|
1322
|
+
environment=environment,
|
|
1323
|
+
remote_is_local=remote_is_local,
|
|
1324
|
+
)
|
|
1325
|
+
|
|
1326
|
+
b_module_data = module_bits.b_module_data
|
|
1327
|
+
shebang = module_bits.shebang
|
|
1374
1328
|
|
|
1375
|
-
if module_style == 'binary':
|
|
1376
|
-
return (
|
|
1329
|
+
if module_bits.module_style == 'binary':
|
|
1330
|
+
return _BuiltModule(
|
|
1331
|
+
b_module_data=module_bits.b_module_data,
|
|
1332
|
+
module_style=module_bits.module_style,
|
|
1333
|
+
shebang=to_text(module_bits.shebang, nonstring='passthru'),
|
|
1334
|
+
serialization_profile=module_bits.serialization_profile,
|
|
1335
|
+
)
|
|
1377
1336
|
elif shebang is None:
|
|
1378
1337
|
interpreter, args = _extract_interpreter(b_module_data)
|
|
1379
1338
|
# No interpreter/shebang, assume a binary module?
|
|
@@ -1387,15 +1346,20 @@ def modify_module(module_name, module_path, module_args, templar, task_vars=None
|
|
|
1387
1346
|
if interpreter != new_interpreter:
|
|
1388
1347
|
b_lines[0] = to_bytes(shebang, errors='surrogate_or_strict', nonstring='passthru')
|
|
1389
1348
|
|
|
1390
|
-
if os.path.basename(interpreter).startswith(u'python'):
|
|
1391
|
-
b_lines.insert(1, b_ENCODING_STRING)
|
|
1392
|
-
|
|
1393
1349
|
b_module_data = b"\n".join(b_lines)
|
|
1394
1350
|
|
|
1395
|
-
return (
|
|
1351
|
+
return _BuiltModule(
|
|
1352
|
+
b_module_data=b_module_data,
|
|
1353
|
+
module_style=module_bits.module_style,
|
|
1354
|
+
shebang=shebang,
|
|
1355
|
+
serialization_profile=module_bits.serialization_profile,
|
|
1356
|
+
)
|
|
1357
|
+
|
|
1396
1358
|
|
|
1359
|
+
def _get_action_arg_defaults(action: str, task: Task, templar: TemplateEngine) -> dict[str, t.Any]:
|
|
1360
|
+
action_groups = task._parent._play._action_groups
|
|
1361
|
+
defaults = task.module_defaults
|
|
1397
1362
|
|
|
1398
|
-
def get_action_args_with_defaults(action, args, defaults, templar, action_groups=None):
|
|
1399
1363
|
# Get the list of groups that contain this action
|
|
1400
1364
|
if action_groups is None:
|
|
1401
1365
|
msg = (
|
|
@@ -1408,7 +1372,7 @@ def get_action_args_with_defaults(action, args, defaults, templar, action_groups
|
|
|
1408
1372
|
else:
|
|
1409
1373
|
group_names = action_groups.get(action, [])
|
|
1410
1374
|
|
|
1411
|
-
tmp_args = {}
|
|
1375
|
+
tmp_args: dict[str, t.Any] = {}
|
|
1412
1376
|
module_defaults = {}
|
|
1413
1377
|
|
|
1414
1378
|
# Merge latest defaults into dict, since they are a list of dicts
|
|
@@ -1416,18 +1380,20 @@ def get_action_args_with_defaults(action, args, defaults, templar, action_groups
|
|
|
1416
1380
|
for default in defaults:
|
|
1417
1381
|
module_defaults.update(default)
|
|
1418
1382
|
|
|
1419
|
-
# module_defaults keys are static, but the values may be templated
|
|
1420
|
-
module_defaults = templar.template(module_defaults)
|
|
1421
1383
|
for default in module_defaults:
|
|
1422
1384
|
if default.startswith('group/'):
|
|
1423
1385
|
group_name = default.split('group/')[-1]
|
|
1424
1386
|
if group_name in group_names:
|
|
1425
|
-
tmp_args.update((module_defaults.get('group
|
|
1387
|
+
tmp_args.update(templar.resolve_to_container(module_defaults.get(f'group/{group_name}', {})))
|
|
1426
1388
|
|
|
1427
1389
|
# handle specific action defaults
|
|
1428
|
-
tmp_args.update(module_defaults.get(action, {})
|
|
1429
|
-
|
|
1430
|
-
# direct args override all
|
|
1431
|
-
tmp_args.update(args)
|
|
1390
|
+
tmp_args.update(templar.resolve_to_container(module_defaults.get(action, {})))
|
|
1432
1391
|
|
|
1433
1392
|
return tmp_args
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
def _apply_action_arg_defaults(action: str, task: Task, action_args: dict[str, t.Any], templar: Templar) -> dict[str, t.Any]:
|
|
1396
|
+
args = _get_action_arg_defaults(action, task, templar._engine)
|
|
1397
|
+
args.update(action_args)
|
|
1398
|
+
|
|
1399
|
+
return args
|