ansible-core 2.18.5rc1__py3-none-any.whl → 2.19.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible/_internal/__init__.py +53 -0
- ansible/_internal/_ansiballz.py +265 -0
- ansible/_internal/_collection_proxy.py +47 -0
- ansible/_internal/_datatag/__init__.py +0 -0
- ansible/_internal/_datatag/_tags.py +130 -0
- ansible/_internal/_datatag/_utils.py +19 -0
- ansible/_internal/_datatag/_wrappers.py +33 -0
- ansible/_internal/_errors/__init__.py +0 -0
- ansible/_internal/_errors/_captured.py +128 -0
- ansible/_internal/_errors/_handler.py +91 -0
- ansible/_internal/_errors/_utils.py +310 -0
- ansible/_internal/_json/__init__.py +203 -0
- ansible/_internal/_json/_legacy_encoder.py +34 -0
- ansible/_internal/_json/_profiles/__init__.py +0 -0
- ansible/_internal/_json/_profiles/_cache_persistence.py +55 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
- ansible/_internal/_json/_profiles/_legacy.py +197 -0
- ansible/_internal/_locking.py +21 -0
- ansible/_internal/_plugins/__init__.py +0 -0
- ansible/_internal/_plugins/_cache.py +57 -0
- ansible/_internal/_task.py +78 -0
- ansible/_internal/_templating/__init__.py +10 -0
- ansible/_internal/_templating/_access.py +86 -0
- ansible/_internal/_templating/_chain_templar.py +63 -0
- ansible/_internal/_templating/_datatag.py +95 -0
- ansible/_internal/_templating/_engine.py +588 -0
- ansible/_internal/_templating/_errors.py +28 -0
- ansible/_internal/_templating/_jinja_bits.py +1066 -0
- ansible/_internal/_templating/_jinja_common.py +332 -0
- ansible/_internal/_templating/_jinja_patches.py +44 -0
- ansible/_internal/_templating/_jinja_plugins.py +345 -0
- ansible/_internal/_templating/_lazy_containers.py +633 -0
- ansible/_internal/_templating/_marker_behaviors.py +103 -0
- ansible/_internal/_templating/_transform.py +63 -0
- ansible/_internal/_templating/_utils.py +107 -0
- ansible/_internal/_wrapt.py +1052 -0
- ansible/_internal/_yaml/__init__.py +0 -0
- ansible/_internal/_yaml/_constructor.py +240 -0
- ansible/_internal/_yaml/_dumper.py +62 -0
- ansible/_internal/_yaml/_errors.py +166 -0
- ansible/_internal/_yaml/_loader.py +66 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/README.md +11 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/action/debug.py +36 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/apply_trust.py +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/finalize.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/origin.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.py +24 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.yml +33 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/tag_names.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +17 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +49 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.py +21 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.yml +2 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.py +15 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.yml +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.yml +19 -0
- ansible/cli/__init__.py +159 -89
- ansible/cli/_ssh_askpass.py +47 -0
- ansible/cli/adhoc.py +14 -7
- ansible/cli/arguments/option_helpers.py +154 -7
- ansible/cli/config.py +43 -68
- ansible/cli/console.py +10 -8
- ansible/cli/doc.py +62 -53
- ansible/cli/galaxy.py +27 -20
- ansible/cli/inventory.py +28 -26
- ansible/cli/playbook.py +4 -12
- ansible/cli/pull.py +51 -11
- ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
- ansible/cli/vault.py +12 -11
- ansible/compat/__init__.py +2 -2
- ansible/config/base.yml +166 -112
- ansible/config/manager.py +52 -49
- ansible/constants.py +3 -4
- ansible/errors/__init__.py +277 -235
- ansible/executor/interpreter_discovery.py +28 -149
- ansible/executor/module_common.py +426 -493
- ansible/executor/play_iterator.py +22 -27
- ansible/executor/playbook_executor.py +11 -11
- ansible/executor/powershell/async_watchdog.ps1 +97 -102
- ansible/executor/powershell/async_wrapper.ps1 +202 -151
- ansible/executor/powershell/become_wrapper.ps1 +89 -144
- ansible/executor/powershell/bootstrap_wrapper.ps1 +24 -9
- ansible/executor/powershell/coverage_wrapper.ps1 +82 -135
- ansible/executor/powershell/exec_wrapper.ps1 +462 -196
- ansible/executor/powershell/module_manifest.py +417 -265
- ansible/executor/powershell/module_wrapper.ps1 +169 -186
- ansible/executor/powershell/psrp_fetch_file.ps1 +41 -0
- ansible/executor/powershell/psrp_put_file.ps1 +122 -0
- ansible/executor/powershell/winrm_fetch_file.ps1 +46 -0
- ansible/executor/powershell/winrm_put_file.ps1 +36 -0
- ansible/executor/process/worker.py +161 -96
- ansible/executor/stats.py +5 -5
- ansible/executor/task_executor.py +268 -258
- ansible/executor/task_queue_manager.py +124 -90
- ansible/executor/task_result.py +183 -78
- ansible/galaxy/__init__.py +2 -2
- ansible/galaxy/api.py +22 -18
- ansible/galaxy/collection/__init__.py +1 -1
- ansible/galaxy/collection/concrete_artifact_manager.py +8 -11
- ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
- ansible/galaxy/dependency_resolution/providers.py +1 -1
- ansible/galaxy/dependency_resolution/reporters.py +81 -0
- ansible/galaxy/role.py +4 -8
- ansible/galaxy/token.py +28 -21
- ansible/inventory/data.py +47 -57
- ansible/inventory/group.py +44 -72
- ansible/inventory/helpers.py +9 -0
- ansible/inventory/host.py +32 -54
- ansible/inventory/manager.py +78 -34
- ansible/keyword_desc.yml +1 -1
- ansible/module_utils/_internal/__init__.py +55 -0
- ansible/module_utils/_internal/_ambient_context.py +58 -0
- ansible/module_utils/_internal/_ansiballz.py +133 -0
- ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
- ansible/module_utils/_internal/_dataclass_annotation_patch.py +64 -0
- ansible/module_utils/_internal/_dataclass_validation.py +217 -0
- ansible/module_utils/_internal/_datatag/__init__.py +928 -0
- ansible/module_utils/_internal/_datatag/_tags.py +38 -0
- ansible/module_utils/_internal/_debugging.py +31 -0
- ansible/module_utils/_internal/_errors.py +30 -0
- ansible/module_utils/_internal/_json/__init__.py +63 -0
- ansible/module_utils/_internal/_json/_legacy_encoder.py +26 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +410 -0
- ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +31 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +35 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_c2m.py +35 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_m2c.py +33 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +50 -0
- ansible/module_utils/_internal/_patches/__init__.py +66 -0
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +55 -0
- ansible/module_utils/_internal/_patches/_socket_patch.py +34 -0
- ansible/module_utils/_internal/_patches/_sys_intern_patch.py +34 -0
- ansible/module_utils/_internal/_plugin_exec_context.py +49 -0
- ansible/module_utils/_internal/_testing.py +0 -0
- ansible/module_utils/_internal/_traceback.py +89 -0
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/api.py +1 -2
- ansible/module_utils/basic.py +152 -120
- ansible/module_utils/common/_utils.py +24 -28
- ansible/module_utils/common/collections.py +1 -2
- ansible/module_utils/common/dict_transformations.py +2 -2
- ansible/module_utils/common/file.py +2 -2
- ansible/module_utils/common/json.py +90 -84
- ansible/module_utils/common/locale.py +2 -2
- ansible/module_utils/common/messages.py +108 -0
- ansible/module_utils/common/parameters.py +27 -24
- ansible/module_utils/common/process.py +2 -2
- ansible/module_utils/common/respawn.py +41 -19
- ansible/module_utils/common/sentinel.py +66 -0
- ansible/module_utils/common/sys_info.py +8 -8
- ansible/module_utils/common/text/converters.py +16 -37
- ansible/module_utils/common/validation.py +35 -24
- ansible/module_utils/common/warnings.py +86 -25
- ansible/module_utils/common/yaml.py +29 -3
- ansible/module_utils/compat/datetime.py +33 -21
- ansible/module_utils/compat/paramiko.py +21 -10
- ansible/module_utils/compat/typing.py +6 -5
- ansible/module_utils/connection.py +2 -2
- ansible/module_utils/csharp/Ansible.Basic.cs +14 -11
- ansible/module_utils/csharp/Ansible.Become.cs +1 -0
- ansible/module_utils/csharp/Ansible._Async.cs +517 -0
- ansible/module_utils/datatag.py +46 -0
- ansible/module_utils/distro/__init__.py +2 -2
- ansible/module_utils/facts/ansible_collector.py +4 -5
- ansible/module_utils/facts/collector.py +13 -14
- ansible/module_utils/facts/compat.py +4 -4
- ansible/module_utils/facts/default_collectors.py +1 -1
- ansible/module_utils/facts/hardware/aix.py +34 -0
- ansible/module_utils/facts/hardware/base.py +1 -1
- ansible/module_utils/facts/hardware/darwin.py +1 -3
- ansible/module_utils/facts/hardware/freebsd.py +2 -2
- ansible/module_utils/facts/hardware/linux.py +4 -4
- ansible/module_utils/facts/namespace.py +1 -1
- ansible/module_utils/facts/network/base.py +1 -1
- ansible/module_utils/facts/network/fc_wwn.py +1 -2
- ansible/module_utils/facts/network/iscsi.py +1 -2
- ansible/module_utils/facts/network/nvme.py +1 -2
- ansible/module_utils/facts/other/facter.py +1 -2
- ansible/module_utils/facts/other/ohai.py +2 -3
- ansible/module_utils/facts/system/apparmor.py +1 -2
- ansible/module_utils/facts/system/caps.py +1 -1
- ansible/module_utils/facts/system/chroot.py +1 -2
- ansible/module_utils/facts/system/cmdline.py +1 -2
- ansible/module_utils/facts/system/date_time.py +5 -3
- ansible/module_utils/facts/system/distribution.py +9 -8
- ansible/module_utils/facts/system/dns.py +1 -1
- ansible/module_utils/facts/system/env.py +1 -2
- ansible/module_utils/facts/system/fips.py +7 -20
- ansible/module_utils/facts/system/loadavg.py +1 -2
- ansible/module_utils/facts/system/local.py +1 -2
- ansible/module_utils/facts/system/lsb.py +1 -2
- ansible/module_utils/facts/system/pkg_mgr.py +1 -2
- ansible/module_utils/facts/system/platform.py +1 -2
- ansible/module_utils/facts/system/python.py +1 -2
- ansible/module_utils/facts/system/selinux.py +1 -1
- ansible/module_utils/facts/system/service_mgr.py +1 -2
- ansible/module_utils/facts/system/ssh_pub_keys.py +1 -1
- ansible/module_utils/facts/system/systemd.py +1 -1
- ansible/module_utils/facts/system/user.py +1 -2
- ansible/module_utils/facts/utils.py +3 -3
- ansible/module_utils/facts/virtual/base.py +1 -1
- ansible/module_utils/facts/virtual/sunos.py +3 -15
- ansible/module_utils/facts/virtual/sysctl.py +3 -16
- ansible/module_utils/json_utils.py +2 -2
- ansible/module_utils/parsing/convert_bool.py +1 -1
- ansible/module_utils/service.py +18 -21
- ansible/module_utils/splitter.py +7 -7
- ansible/module_utils/testing.py +31 -0
- ansible/module_utils/urls.py +60 -31
- ansible/modules/add_host.py +4 -4
- ansible/modules/apt.py +60 -46
- ansible/modules/apt_key.py +19 -12
- ansible/modules/apt_repository.py +19 -16
- ansible/modules/assemble.py +6 -6
- ansible/modules/assert.py +4 -4
- ansible/modules/async_status.py +10 -12
- ansible/modules/async_wrapper.py +8 -3
- ansible/modules/blockinfile.py +6 -7
- ansible/modules/command.py +10 -17
- ansible/modules/copy.py +57 -144
- ansible/modules/cron.py +20 -15
- ansible/modules/deb822_repository.py +8 -9
- ansible/modules/debconf.py +5 -5
- ansible/modules/debug.py +4 -4
- ansible/modules/dnf.py +8 -8
- ansible/modules/dnf5.py +39 -13
- ansible/modules/dpkg_selections.py +4 -4
- ansible/modules/expect.py +8 -10
- ansible/modules/fail.py +4 -4
- ansible/modules/fetch.py +4 -4
- ansible/modules/file.py +174 -133
- ansible/modules/find.py +19 -17
- ansible/modules/gather_facts.py +3 -3
- ansible/modules/get_url.py +59 -53
- ansible/modules/getent.py +7 -9
- ansible/modules/git.py +28 -25
- ansible/modules/group.py +6 -6
- ansible/modules/group_by.py +4 -4
- ansible/modules/hostname.py +13 -29
- ansible/modules/import_playbook.py +6 -6
- ansible/modules/import_role.py +6 -6
- ansible/modules/import_tasks.py +6 -6
- ansible/modules/include_role.py +6 -6
- ansible/modules/include_tasks.py +6 -6
- ansible/modules/include_vars.py +6 -6
- ansible/modules/iptables.py +86 -73
- ansible/modules/known_hosts.py +10 -10
- ansible/modules/lineinfile.py +5 -5
- ansible/modules/meta.py +4 -4
- ansible/modules/mount_facts.py +2 -2
- ansible/modules/package.py +4 -4
- ansible/modules/package_facts.py +22 -10
- ansible/modules/pause.py +6 -6
- ansible/modules/ping.py +6 -6
- ansible/modules/pip.py +10 -11
- ansible/modules/raw.py +4 -4
- ansible/modules/reboot.py +6 -6
- ansible/modules/replace.py +9 -13
- ansible/modules/rpm_key.py +7 -8
- ansible/modules/script.py +4 -4
- ansible/modules/service.py +7 -8
- ansible/modules/service_facts.py +87 -10
- ansible/modules/set_fact.py +5 -5
- ansible/modules/set_stats.py +4 -4
- ansible/modules/setup.py +2 -2
- ansible/modules/shell.py +6 -6
- ansible/modules/slurp.py +6 -6
- ansible/modules/stat.py +9 -23
- ansible/modules/subversion.py +15 -15
- ansible/modules/systemd.py +6 -6
- ansible/modules/systemd_service.py +6 -6
- ansible/modules/sysvinit.py +6 -6
- ansible/modules/tempfile.py +5 -6
- ansible/modules/template.py +6 -6
- ansible/modules/unarchive.py +32 -11
- ansible/modules/uri.py +33 -26
- ansible/modules/user.py +53 -34
- ansible/modules/validate_argument_spec.py +10 -7
- ansible/modules/wait_for.py +32 -27
- ansible/modules/wait_for_connection.py +6 -6
- ansible/modules/yum_repository.py +6 -6
- ansible/parsing/ajson.py +14 -32
- ansible/parsing/dataloader.py +99 -54
- ansible/parsing/mod_args.py +28 -44
- ansible/parsing/plugin_docs.py +21 -86
- ansible/parsing/quoting.py +1 -1
- ansible/parsing/splitter.py +27 -12
- ansible/parsing/utils/addresses.py +24 -24
- ansible/parsing/utils/jsonify.py +5 -1
- ansible/parsing/utils/yaml.py +32 -61
- ansible/parsing/vault/__init__.py +319 -87
- ansible/parsing/yaml/__init__.py +0 -18
- ansible/parsing/yaml/dumper.py +6 -120
- ansible/parsing/yaml/loader.py +6 -39
- ansible/parsing/yaml/objects.py +43 -335
- ansible/playbook/__init__.py +1 -1
- ansible/playbook/attribute.py +8 -3
- ansible/playbook/base.py +182 -132
- ansible/playbook/block.py +26 -24
- ansible/playbook/collectionsearch.py +1 -15
- ansible/playbook/conditional.py +3 -77
- ansible/playbook/handler.py +8 -2
- ansible/playbook/helpers.py +41 -53
- ansible/playbook/included_file.py +31 -27
- ansible/playbook/loop_control.py +2 -2
- ansible/playbook/play.py +85 -44
- ansible/playbook/play_context.py +12 -17
- ansible/playbook/playbook_include.py +14 -15
- ansible/playbook/role/__init__.py +24 -26
- ansible/playbook/role/definition.py +15 -17
- ansible/playbook/role/include.py +2 -4
- ansible/playbook/role/metadata.py +10 -11
- ansible/playbook/role_include.py +3 -3
- ansible/playbook/taggable.py +13 -8
- ansible/playbook/task.py +188 -118
- ansible/playbook/task_include.py +5 -5
- ansible/plugins/__init__.py +68 -21
- ansible/plugins/action/__init__.py +209 -176
- ansible/plugins/action/add_host.py +1 -1
- ansible/plugins/action/assemble.py +1 -1
- ansible/plugins/action/assert.py +54 -66
- ansible/plugins/action/copy.py +7 -11
- ansible/plugins/action/debug.py +37 -31
- ansible/plugins/action/dnf.py +3 -4
- ansible/plugins/action/fail.py +1 -1
- ansible/plugins/action/fetch.py +4 -5
- ansible/plugins/action/gather_facts.py +7 -6
- ansible/plugins/action/group_by.py +1 -1
- ansible/plugins/action/include_vars.py +10 -11
- ansible/plugins/action/package.py +3 -6
- ansible/plugins/action/pause.py +2 -2
- ansible/plugins/action/script.py +15 -8
- ansible/plugins/action/service.py +6 -11
- ansible/plugins/action/set_fact.py +3 -12
- ansible/plugins/action/set_stats.py +3 -8
- ansible/plugins/action/template.py +35 -59
- ansible/plugins/action/unarchive.py +1 -1
- ansible/plugins/action/validate_argument_spec.py +5 -5
- ansible/plugins/action/wait_for_connection.py +1 -1
- ansible/plugins/become/__init__.py +31 -8
- ansible/plugins/become/runas.py +71 -0
- ansible/plugins/become/su.py +13 -8
- ansible/plugins/become/sudo.py +19 -0
- ansible/plugins/cache/__init__.py +35 -44
- ansible/plugins/cache/base.py +8 -0
- ansible/plugins/cache/jsonfile.py +10 -16
- ansible/plugins/cache/memory.py +6 -12
- ansible/plugins/callback/__init__.py +284 -179
- ansible/plugins/callback/default.py +99 -92
- ansible/plugins/callback/junit.py +44 -39
- ansible/plugins/callback/minimal.py +28 -25
- ansible/plugins/callback/oneline.py +28 -21
- ansible/plugins/callback/tree.py +16 -11
- ansible/plugins/connection/__init__.py +47 -34
- ansible/plugins/connection/local.py +150 -54
- ansible/plugins/connection/paramiko_ssh.py +21 -18
- ansible/plugins/connection/psrp.py +76 -165
- ansible/plugins/connection/ssh.py +301 -78
- ansible/plugins/connection/winrm.py +58 -140
- ansible/plugins/doc_fragments/action_common_attributes.py +14 -14
- ansible/plugins/doc_fragments/action_core.py +6 -6
- ansible/plugins/doc_fragments/backup.py +2 -2
- ansible/plugins/doc_fragments/checksum_common.py +27 -0
- ansible/plugins/doc_fragments/constructed.py +6 -2
- ansible/plugins/doc_fragments/decrypt.py +2 -2
- ansible/plugins/doc_fragments/default_callback.py +2 -2
- ansible/plugins/doc_fragments/files.py +2 -2
- ansible/plugins/doc_fragments/inventory_cache.py +2 -2
- ansible/plugins/doc_fragments/result_format_callback.py +2 -2
- ansible/plugins/doc_fragments/return_common.py +2 -2
- ansible/plugins/doc_fragments/template_common.py +4 -4
- ansible/plugins/doc_fragments/url.py +17 -1
- ansible/plugins/doc_fragments/url_windows.py +2 -2
- ansible/plugins/doc_fragments/validate.py +2 -2
- ansible/plugins/doc_fragments/vars_plugin_staging.py +2 -2
- ansible/plugins/filter/__init__.py +6 -2
- ansible/plugins/filter/b64decode.yml +22 -0
- ansible/plugins/filter/b64encode.yml +22 -0
- ansible/plugins/filter/bool.yml +11 -4
- ansible/plugins/filter/core.py +225 -108
- ansible/plugins/filter/encryption.py +32 -32
- ansible/plugins/filter/flatten.yml +3 -2
- ansible/plugins/filter/human_to_bytes.yml +1 -1
- ansible/plugins/filter/mathstuff.py +30 -37
- ansible/plugins/filter/password_hash.yml +8 -0
- ansible/plugins/filter/regex_search.yml +1 -4
- ansible/plugins/filter/split.yml +1 -1
- ansible/plugins/filter/to_nice_yaml.yml +0 -4
- ansible/plugins/filter/to_yaml.yml +0 -4
- ansible/plugins/filter/unvault.yml +1 -1
- ansible/plugins/filter/urls.py +1 -1
- ansible/plugins/filter/urlsplit.py +8 -9
- ansible/plugins/filter/vault.yml +14 -9
- ansible/plugins/filter/win_basename.yml +6 -1
- ansible/plugins/filter/win_dirname.yml +5 -0
- ansible/plugins/inventory/__init__.py +97 -77
- ansible/plugins/inventory/advanced_host_list.py +7 -5
- ansible/plugins/inventory/auto.py +11 -4
- ansible/plugins/inventory/constructed.py +21 -24
- ansible/plugins/inventory/generator.py +16 -11
- ansible/plugins/inventory/host_list.py +7 -5
- ansible/plugins/inventory/ini.py +78 -44
- ansible/plugins/inventory/script.py +189 -119
- ansible/plugins/inventory/toml.py +16 -126
- ansible/plugins/inventory/yaml.py +10 -8
- ansible/plugins/list.py +3 -3
- ansible/plugins/loader.py +197 -82
- ansible/plugins/lookup/__init__.py +21 -4
- ansible/plugins/lookup/config.py +21 -35
- ansible/plugins/lookup/csvfile.py +3 -2
- ansible/plugins/lookup/dict.py +1 -6
- ansible/plugins/lookup/env.py +12 -9
- ansible/plugins/lookup/file.py +5 -8
- ansible/plugins/lookup/first_found.py +86 -55
- ansible/plugins/lookup/indexed_items.py +1 -10
- ansible/plugins/lookup/ini.py +14 -13
- ansible/plugins/lookup/items.py +1 -1
- ansible/plugins/lookup/lines.py +8 -1
- ansible/plugins/lookup/list.py +1 -1
- ansible/plugins/lookup/nested.py +2 -18
- ansible/plugins/lookup/password.py +5 -5
- ansible/plugins/lookup/pipe.py +5 -7
- ansible/plugins/lookup/sequence.py +18 -8
- ansible/plugins/lookup/subelements.py +1 -4
- ansible/plugins/lookup/template.py +42 -36
- ansible/plugins/lookup/together.py +0 -12
- ansible/plugins/lookup/unvault.py +1 -5
- ansible/plugins/lookup/url.py +2 -8
- ansible/plugins/lookup/vars.py +16 -24
- ansible/plugins/shell/__init__.py +2 -2
- ansible/plugins/shell/cmd.py +2 -2
- ansible/plugins/shell/powershell.py +39 -22
- ansible/plugins/shell/sh.py +3 -2
- ansible/plugins/strategy/__init__.py +159 -184
- ansible/plugins/strategy/debug.py +2 -2
- ansible/plugins/strategy/free.py +16 -31
- ansible/plugins/strategy/host_pinned.py +2 -2
- ansible/plugins/strategy/linear.py +41 -41
- ansible/plugins/terminal/__init__.py +4 -4
- ansible/plugins/test/__init__.py +7 -2
- ansible/plugins/test/core.py +55 -21
- ansible/plugins/test/files.py +1 -1
- ansible/plugins/test/mathstuff.py +3 -3
- ansible/plugins/test/uri.py +3 -3
- ansible/plugins/vars/host_group_vars.py +7 -14
- ansible/release.py +2 -2
- ansible/template/__init__.py +370 -944
- ansible/utils/__init__.py +0 -18
- ansible/utils/_ssh_agent.py +657 -0
- ansible/utils/collection_loader/__init__.py +52 -5
- ansible/utils/collection_loader/_collection_config.py +5 -6
- ansible/utils/collection_loader/_collection_finder.py +79 -93
- ansible/utils/collection_loader/_collection_meta.py +13 -8
- ansible/utils/display.py +433 -63
- ansible/utils/encrypt.py +27 -19
- ansible/utils/fqcn.py +2 -2
- ansible/utils/hashing.py +2 -2
- ansible/utils/helpers.py +2 -2
- ansible/utils/listify.py +8 -8
- ansible/utils/lock.py +2 -2
- ansible/utils/path.py +4 -4
- ansible/utils/plugin_docs.py +14 -13
- ansible/utils/sentinel.py +4 -62
- ansible/utils/singleton.py +2 -0
- ansible/utils/ssh_functions.py +1 -1
- ansible/utils/unsafe_proxy.py +23 -332
- ansible/utils/vars.py +51 -8
- ansible/utils/version.py +2 -2
- ansible/vars/clean.py +5 -5
- ansible/vars/hostvars.py +60 -90
- ansible/vars/manager.py +206 -282
- ansible/vars/reserved.py +8 -9
- ansible_core-2.19.0b2.dist-info/BSD-3-Clause.txt +28 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/METADATA +5 -4
- ansible_core-2.19.0b2.dist-info/RECORD +1072 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/WHEEL +1 -1
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/remote.txt +6 -6
- ansible_test/_data/completion/windows.txt +1 -0
- ansible_test/_data/requirements/ansible.txt +2 -2
- ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
- ansible_test/_data/requirements/sanity.changelog.txt +1 -1
- ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
- ansible_test/_data/requirements/sanity.pylint.txt +4 -4
- ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_data/requirements/units.txt +1 -0
- ansible_test/_internal/__init__.py +1 -0
- ansible_test/_internal/ansible_util.py +2 -0
- ansible_test/_internal/become.py +1 -0
- ansible_test/_internal/bootstrap.py +1 -0
- ansible_test/_internal/cache.py +1 -0
- ansible_test/_internal/cgroup.py +1 -0
- ansible_test/_internal/ci/__init__.py +1 -0
- ansible_test/_internal/ci/azp.py +1 -0
- ansible_test/_internal/ci/local.py +1 -0
- ansible_test/_internal/classification/__init__.py +1 -0
- ansible_test/_internal/classification/common.py +1 -0
- ansible_test/_internal/classification/csharp.py +1 -0
- ansible_test/_internal/classification/powershell.py +1 -0
- ansible_test/_internal/classification/python.py +1 -0
- ansible_test/_internal/cli/__init__.py +1 -0
- ansible_test/_internal/cli/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/__init__.py +1 -0
- ansible_test/_internal/cli/argparsing/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/argcompletion.py +1 -0
- ansible_test/_internal/cli/argparsing/parsers.py +1 -0
- ansible_test/_internal/cli/commands/__init__.py +11 -0
- ansible_test/_internal/cli/commands/coverage/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/cli/commands/coverage/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/erase.py +1 -0
- ansible_test/_internal/cli/commands/coverage/html.py +1 -0
- ansible_test/_internal/cli/commands/coverage/report.py +1 -0
- ansible_test/_internal/cli/commands/coverage/xml.py +1 -0
- ansible_test/_internal/cli/commands/env.py +1 -0
- ansible_test/_internal/cli/commands/integration/__init__.py +1 -0
- ansible_test/_internal/cli/commands/integration/network.py +1 -0
- ansible_test/_internal/cli/commands/integration/posix.py +1 -0
- ansible_test/_internal/cli/commands/integration/windows.py +1 -0
- ansible_test/_internal/cli/commands/sanity.py +9 -0
- ansible_test/_internal/cli/commands/shell.py +1 -0
- ansible_test/_internal/cli/commands/units.py +1 -0
- ansible_test/_internal/cli/compat.py +1 -0
- ansible_test/_internal/cli/completers.py +1 -0
- ansible_test/_internal/cli/converters.py +1 -0
- ansible_test/_internal/cli/environments.py +1 -0
- ansible_test/_internal/cli/epilog.py +1 -0
- ansible_test/_internal/cli/parsers/__init__.py +1 -0
- ansible_test/_internal/cli/parsers/base_argument_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/helpers.py +1 -0
- ansible_test/_internal/cli/parsers/host_config_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/key_value_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/value_parsers.py +1 -0
- ansible_test/_internal/commands/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/__init__.py +2 -1
- ansible_test/_internal/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/commands/coverage/combine.py +2 -1
- ansible_test/_internal/commands/coverage/erase.py +1 -0
- ansible_test/_internal/commands/coverage/html.py +1 -0
- ansible_test/_internal/commands/coverage/report.py +1 -0
- ansible_test/_internal/commands/coverage/xml.py +1 -0
- ansible_test/_internal/commands/env/__init__.py +2 -0
- ansible_test/_internal/commands/integration/__init__.py +4 -0
- ansible_test/_internal/commands/integration/cloud/__init__.py +1 -0
- ansible_test/_internal/commands/integration/cloud/acme.py +2 -1
- ansible_test/_internal/commands/integration/cloud/aws.py +1 -0
- ansible_test/_internal/commands/integration/cloud/azure.py +1 -0
- ansible_test/_internal/commands/integration/cloud/cs.py +1 -0
- ansible_test/_internal/commands/integration/cloud/digitalocean.py +1 -0
- ansible_test/_internal/commands/integration/cloud/galaxy.py +3 -2
- ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -0
- ansible_test/_internal/commands/integration/cloud/httptester.py +2 -1
- ansible_test/_internal/commands/integration/cloud/nios.py +2 -1
- ansible_test/_internal/commands/integration/cloud/opennebula.py +1 -0
- ansible_test/_internal/commands/integration/cloud/openshift.py +1 -0
- ansible_test/_internal/commands/integration/cloud/scaleway.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vcenter.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vultr.py +1 -0
- ansible_test/_internal/commands/integration/coverage.py +1 -0
- ansible_test/_internal/commands/integration/filters.py +1 -0
- ansible_test/_internal/commands/integration/network.py +1 -0
- ansible_test/_internal/commands/integration/posix.py +1 -0
- ansible_test/_internal/commands/integration/windows.py +1 -0
- ansible_test/_internal/commands/sanity/__init__.py +16 -1
- ansible_test/_internal/commands/sanity/ansible_doc.py +1 -0
- ansible_test/_internal/commands/sanity/bin_symlinks.py +1 -0
- ansible_test/_internal/commands/sanity/compile.py +1 -0
- ansible_test/_internal/commands/sanity/ignores.py +1 -0
- ansible_test/_internal/commands/sanity/import.py +1 -0
- ansible_test/_internal/commands/sanity/integration_aliases.py +1 -0
- ansible_test/_internal/commands/sanity/pep8.py +1 -0
- ansible_test/_internal/commands/sanity/pslint.py +1 -0
- ansible_test/_internal/commands/sanity/pylint.py +24 -26
- ansible_test/_internal/commands/sanity/shellcheck.py +1 -0
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -0
- ansible_test/_internal/commands/sanity/yamllint.py +1 -0
- ansible_test/_internal/commands/shell/__init__.py +1 -0
- ansible_test/_internal/commands/units/__init__.py +1 -0
- ansible_test/_internal/compat/__init__.py +1 -0
- ansible_test/_internal/compat/packaging.py +1 -0
- ansible_test/_internal/compat/yaml.py +1 -0
- ansible_test/_internal/completion.py +1 -0
- ansible_test/_internal/config.py +2 -0
- ansible_test/_internal/connections.py +1 -0
- ansible_test/_internal/constants.py +1 -0
- ansible_test/_internal/containers.py +1 -0
- ansible_test/_internal/content_config.py +1 -0
- ansible_test/_internal/core_ci.py +1 -0
- ansible_test/_internal/coverage_util.py +11 -10
- ansible_test/_internal/data.py +1 -0
- ansible_test/_internal/delegation.py +1 -0
- ansible_test/_internal/dev/__init__.py +1 -0
- ansible_test/_internal/dev/container_probe.py +1 -0
- ansible_test/_internal/diff.py +3 -2
- ansible_test/_internal/docker_util.py +2 -1
- ansible_test/_internal/encoding.py +1 -0
- ansible_test/_internal/executor.py +1 -0
- ansible_test/_internal/git.py +1 -0
- ansible_test/_internal/host_configs.py +1 -0
- ansible_test/_internal/host_profiles.py +1 -0
- ansible_test/_internal/http.py +1 -0
- ansible_test/_internal/init.py +1 -0
- ansible_test/_internal/inventory.py +35 -3
- ansible_test/_internal/io.py +1 -0
- ansible_test/_internal/metadata.py +1 -0
- ansible_test/_internal/payload.py +1 -0
- ansible_test/_internal/provider/__init__.py +1 -0
- ansible_test/_internal/provider/layout/__init__.py +1 -0
- ansible_test/_internal/provider/layout/ansible.py +1 -0
- ansible_test/_internal/provider/layout/collection.py +1 -0
- ansible_test/_internal/provider/layout/unsupported.py +1 -0
- ansible_test/_internal/provider/source/__init__.py +1 -0
- ansible_test/_internal/provider/source/git.py +1 -0
- ansible_test/_internal/provider/source/installed.py +1 -0
- ansible_test/_internal/provider/source/unsupported.py +1 -0
- ansible_test/_internal/provider/source/unversioned.py +1 -0
- ansible_test/_internal/provisioning.py +1 -0
- ansible_test/_internal/pypi_proxy.py +6 -5
- ansible_test/_internal/python_requirements.py +1 -0
- ansible_test/_internal/ssh.py +1 -0
- ansible_test/_internal/target.py +1 -0
- ansible_test/_internal/test.py +3 -2
- ansible_test/_internal/thread.py +1 -0
- ansible_test/_internal/timeout.py +1 -0
- ansible_test/_internal/util.py +1 -0
- ansible_test/_internal/util_common.py +5 -2
- ansible_test/_internal/venv.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/empty-init.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/line-endings.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-assert.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-get-exception.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-illegal-filenames.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-smart-quotes.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/replace-urlopen.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +28 -1
- ansible_test/_util/controller/sanity/code-smell/shebang.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/symlinks.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-argspec-type-path.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-compat-six.py +1 -0
- ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py +2 -1
- ansible_test/_util/controller/sanity/pep8/current-ignore.txt +4 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +7 -5
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +7 -5
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +7 -5
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +3 -5
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +7 -7
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +1 -13
- ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +1 -8
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +1 -8
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +55 -28
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +12 -5
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +13 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -0
- ansible_test/_util/controller/sanity/yamllint/yamllinter.py +35 -17
- ansible_test/_util/controller/tools/collection_detail.py +1 -0
- ansible_test/_util/controller/tools/yaml_to_json.py +2 -1
- ansible_test/_util/target/pytest/plugins/ansible_forked.py +6 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +2 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -0
- ansible_test/_util/target/sanity/compile/compile.py +1 -0
- ansible_test/_util/target/sanity/import/importer.py +15 -16
- ansible_test/_util/target/setup/bootstrap.sh +9 -20
- ansible_test/_util/target/setup/probe_cgroups.py +1 -0
- ansible_test/_util/target/setup/quiet_pip.py +1 -0
- ansible_test/_util/target/setup/requirements.py +35 -27
- ansible_test/_util/target/tools/virtualenvcheck.py +2 -1
- ansible_test/_util/target/tools/yamlcheck.py +2 -1
- ansible/compat/selectors.py +0 -32
- ansible/errors/yaml_strings.py +0 -138
- ansible/executor/action_write_locks.py +0 -44
- ansible/executor/discovery/python_target.py +0 -47
- ansible/executor/powershell/module_powershell_wrapper.ps1 +0 -86
- ansible/executor/powershell/module_script_wrapper.ps1 +0 -22
- ansible/module_utils/compat/importlib.py +0 -26
- ansible/module_utils/compat/selectors.py +0 -32
- ansible/module_utils/pycompat24.py +0 -73
- ansible/parsing/yaml/constructor.py +0 -178
- ansible/template/native_helpers.py +0 -251
- ansible/template/template.py +0 -43
- ansible/template/vars.py +0 -77
- ansible/utils/native_jinja.py +0 -11
- ansible/vars/fact_cache.py +0 -71
- ansible_core-2.18.5rc1.dist-info/RECORD +0 -992
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/Apache-License.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/COPYING +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/MIT-license.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/PSF-license.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/simplified_bsd.txt +0 -0
- {ansible_core-2.18.5rc1.dist-info → ansible_core-2.19.0b2.dist-info}/top_level.txt +0 -0
@@ -20,42 +20,76 @@ 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._datatag import _utils
|
41
|
+
from ansible.module_utils._internal import _dataclass_validation
|
42
|
+
from ansible.module_utils.common.messages import PluginInfo
|
43
|
+
from ansible.module_utils.common.yaml import yaml_load
|
44
|
+
from ansible._internal._datatag._tags import Origin
|
45
|
+
from ansible.module_utils.common.json import Direction, get_module_encoder
|
35
46
|
from ansible.release import __version__, __author__
|
36
47
|
from ansible import constants as C
|
37
48
|
from ansible.errors import AnsibleError
|
38
49
|
from ansible.executor.interpreter_discovery import InterpreterDiscoveryRequiredError
|
39
50
|
from ansible.executor.powershell import module_manifest as ps_manifest
|
40
|
-
from ansible.module_utils.common.json import AnsibleJSONEncoder
|
41
51
|
from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
|
52
|
+
from ansible.plugins.become import BecomeBase
|
42
53
|
from ansible.plugins.loader import module_utils_loader
|
54
|
+
from ansible._internal._templating._engine import TemplateOptions, TemplateEngine
|
55
|
+
from ansible.template import Templar
|
43
56
|
from ansible.utils.collection_loader._collection_finder import _get_collection_metadata, _nested_dict_get
|
57
|
+
from ansible.module_utils._internal import _json, _ansiballz
|
58
|
+
from ansible.module_utils import basic as _basic
|
44
59
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
from ansible.executor import action_write_locks
|
60
|
+
if t.TYPE_CHECKING:
|
61
|
+
from ansible import template as _template
|
62
|
+
from ansible.playbook.task import Task
|
49
63
|
|
50
64
|
from ansible.utils.display import Display
|
51
|
-
from collections import namedtuple
|
52
65
|
|
53
66
|
import importlib.util
|
54
67
|
import importlib.machinery
|
55
68
|
|
56
69
|
display = Display()
|
57
70
|
|
58
|
-
|
71
|
+
|
72
|
+
@dataclasses.dataclass(frozen=True, order=True)
|
73
|
+
class _ModuleUtilsProcessEntry:
|
74
|
+
"""Represents a module/module_utils item awaiting import analysis."""
|
75
|
+
name_parts: tuple[str, ...]
|
76
|
+
is_ambiguous: bool = False
|
77
|
+
child_is_redirected: bool = False
|
78
|
+
is_optional: bool = False
|
79
|
+
|
80
|
+
@classmethod
|
81
|
+
def from_module(cls, module: types.ModuleType, append: str | None = None) -> t.Self:
|
82
|
+
name = module.__name__
|
83
|
+
|
84
|
+
if append:
|
85
|
+
name += '.' + append
|
86
|
+
|
87
|
+
return cls.from_module_name(name)
|
88
|
+
|
89
|
+
@classmethod
|
90
|
+
def from_module_name(cls, module_name: str) -> t.Self:
|
91
|
+
return cls(tuple(module_name.split('.')))
|
92
|
+
|
59
93
|
|
60
94
|
REPLACER = b"#<<INCLUDE_ANSIBLE_MODULE_COMMON>>"
|
61
95
|
REPLACER_VERSION = b"\"<<ANSIBLE_VERSION>>\""
|
@@ -64,348 +98,45 @@ REPLACER_WINDOWS = b"# POWERSHELL_COMMON"
|
|
64
98
|
REPLACER_JSONARGS = b"<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>"
|
65
99
|
REPLACER_SELINUX = b"<<SELINUX_SPECIAL_FILESYSTEMS>>"
|
66
100
|
|
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
101
|
# module_common is relative to module_utils, so fix the path
|
73
102
|
_MODULE_UTILS_PATH = os.path.join(os.path.dirname(__file__), '..', 'module_utils')
|
103
|
+
_SHEBANG_PLACEHOLDER = '# shebang placeholder'
|
74
104
|
|
75
105
|
# ******************************************************************************
|
76
106
|
|
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
107
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
-
|
351
|
-
def atexit_coverage():
|
352
|
-
cov.stop()
|
353
|
-
cov.save()
|
354
|
-
|
355
|
-
atexit.register(atexit_coverage)
|
108
|
+
def _strip_comments(source: str) -> str:
|
109
|
+
# Strip comments and blank lines from the wrapper
|
110
|
+
buf = []
|
111
|
+
for line in source.splitlines():
|
112
|
+
l = line.strip()
|
113
|
+
if (not l or l.startswith('#')) and l != _SHEBANG_PLACEHOLDER:
|
114
|
+
line = ''
|
115
|
+
buf.append(line)
|
116
|
+
return '\n'.join(buf)
|
356
117
|
|
357
|
-
cov.start()
|
358
|
-
'''
|
359
118
|
|
360
|
-
|
361
|
-
|
362
|
-
if PY3:
|
363
|
-
import importlib.util
|
364
|
-
if importlib.util.find_spec('coverage') is None:
|
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
|
-
'''
|
119
|
+
def _read_ansiballz_code() -> str:
|
120
|
+
code = (pathlib.Path(__file__).parent.parent / '_internal/_ansiballz.py').read_text()
|
373
121
|
|
374
|
-
|
375
|
-
|
122
|
+
if not C.DEFAULT_KEEP_REMOTE_FILES:
|
123
|
+
# Keep comments when KEEP_REMOTE_FILES is set. That way users will see
|
124
|
+
# the comments with some nice usage instructions.
|
125
|
+
# Otherwise, strip comments for smaller over the wire size.
|
126
|
+
code = _strip_comments(code)
|
376
127
|
|
377
|
-
|
128
|
+
return code
|
378
129
|
|
379
|
-
# adjust soft limit subject to existing hard limit
|
380
|
-
requested_soft = min(existing_hard, %(rlimit_nofile)d)
|
381
130
|
|
382
|
-
|
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
|
-
'''
|
131
|
+
_ANSIBALLZ_CODE = _read_ansiballz_code() # read during startup to prevent individual workers from doing so
|
389
132
|
|
390
133
|
|
391
|
-
def
|
392
|
-
|
393
|
-
|
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)
|
134
|
+
def _get_ansiballz_code(shebang: str) -> str:
|
135
|
+
code = _ANSIBALLZ_CODE
|
136
|
+
code = code.replace(_SHEBANG_PLACEHOLDER, shebang)
|
400
137
|
|
138
|
+
return code
|
401
139
|
|
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
140
|
|
410
141
|
# dirname(dirname(dirname(site-packages/ansible/executor/module_common.py) == site-packages
|
411
142
|
# Do this instead of getting site-packages from distutils.sysconfig so we work when we
|
@@ -435,6 +166,7 @@ NEW_STYLE_PYTHON_MODULE_RE = re.compile(
|
|
435
166
|
|
436
167
|
|
437
168
|
class ModuleDepFinder(ast.NodeVisitor):
|
169
|
+
# DTFIX-RELEASE: add support for ignoring imports with a "controller only" comment, this will allow replacing import_controller_module with standard imports
|
438
170
|
def __init__(self, module_fqn, tree, is_pkg_init=False, *args, **kwargs):
|
439
171
|
"""
|
440
172
|
Walk the ast tree for the python module.
|
@@ -581,7 +313,7 @@ def _slurp(path):
|
|
581
313
|
return data
|
582
314
|
|
583
315
|
|
584
|
-
def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=False):
|
316
|
+
def _get_shebang(interpreter, task_vars, templar: _template.Templar, args=tuple(), remote_is_local=False):
|
585
317
|
"""
|
586
318
|
Handles the different ways ansible allows overriding the shebang target for a module.
|
587
319
|
"""
|
@@ -606,7 +338,8 @@ def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=
|
|
606
338
|
elif C.config.get_configuration_definition(interpreter_config_key):
|
607
339
|
|
608
340
|
interpreter_from_config = C.config.get_config_value(interpreter_config_key, variables=task_vars)
|
609
|
-
interpreter_out = templar.template(
|
341
|
+
interpreter_out = templar._engine.template(_utils.str_problematic_strip(interpreter_from_config),
|
342
|
+
options=TemplateOptions(value_for_omit=C.config.get_config_default(interpreter_config_key)))
|
610
343
|
|
611
344
|
# handle interpreter discovery if requested or empty interpreter was provided
|
612
345
|
if not interpreter_out or interpreter_out in ['auto', 'auto_legacy', 'auto_silent', 'auto_legacy_silent']:
|
@@ -624,7 +357,8 @@ def _get_shebang(interpreter, task_vars, templar, args=tuple(), remote_is_local=
|
|
624
357
|
|
625
358
|
elif interpreter_config in task_vars:
|
626
359
|
# for non python we consult vars for a possible direct override
|
627
|
-
interpreter_out = templar.template(task_vars.get(interpreter_config)
|
360
|
+
interpreter_out = templar._engine.template(_utils.str_problematic_strip(task_vars.get(interpreter_config)),
|
361
|
+
options=TemplateOptions(value_for_omit=None))
|
628
362
|
|
629
363
|
if not interpreter_out:
|
630
364
|
# nothing matched(None) or in case someone configures empty string or empty intepreter
|
@@ -803,12 +537,12 @@ class LegacyModuleUtilLocator(ModuleUtilLocatorBase):
|
|
803
537
|
|
804
538
|
# find_spec needs the full module name
|
805
539
|
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:
|
540
|
+
if info is not None and info.origin is not None and os.path.splitext(info.origin)[1] in importlib.machinery.SOURCE_SUFFIXES:
|
807
541
|
self.is_package = info.origin.endswith('/__init__.py')
|
808
542
|
path = info.origin
|
809
543
|
else:
|
810
544
|
return False
|
811
|
-
self.source_code = _slurp(path)
|
545
|
+
self.source_code = Origin(path=path).tag(_slurp(path))
|
812
546
|
|
813
547
|
return True
|
814
548
|
|
@@ -843,9 +577,18 @@ class CollectionModuleUtilLocator(ModuleUtilLocatorBase):
|
|
843
577
|
resource_base_path = os.path.join(*name_parts[3:])
|
844
578
|
|
845
579
|
src = None
|
580
|
+
|
846
581
|
# look for package_dir first, then module
|
582
|
+
src_path = to_native(os.path.join(resource_base_path, '__init__.py'))
|
583
|
+
|
584
|
+
try:
|
585
|
+
collection_pkg = importlib.import_module(collection_pkg_name)
|
586
|
+
pkg_path = os.path.dirname(collection_pkg.__file__)
|
587
|
+
except (ImportError, AttributeError):
|
588
|
+
pkg_path = None
|
589
|
+
|
847
590
|
try:
|
848
|
-
src = pkgutil.get_data(collection_pkg_name,
|
591
|
+
src = pkgutil.get_data(collection_pkg_name, src_path)
|
849
592
|
except ImportError:
|
850
593
|
pass
|
851
594
|
|
@@ -854,32 +597,113 @@ class CollectionModuleUtilLocator(ModuleUtilLocatorBase):
|
|
854
597
|
if src is not None: # empty string is OK
|
855
598
|
self.is_package = True
|
856
599
|
else:
|
600
|
+
src_path = to_native(resource_base_path + '.py')
|
601
|
+
|
857
602
|
try:
|
858
|
-
src = pkgutil.get_data(collection_pkg_name,
|
603
|
+
src = pkgutil.get_data(collection_pkg_name, src_path)
|
859
604
|
except ImportError:
|
860
605
|
pass
|
861
606
|
|
862
607
|
if src is None: # empty string is OK
|
863
608
|
return False
|
864
609
|
|
865
|
-
|
610
|
+
# TODO: this feels brittle and funky; we should be able to more definitively assure the source path
|
611
|
+
|
612
|
+
if pkg_path:
|
613
|
+
origin = Origin(path=os.path.join(pkg_path, src_path))
|
614
|
+
else:
|
615
|
+
# DTFIX-RELEASE: not sure if this case is even reachable
|
616
|
+
origin = Origin(description=f'<synthetic collection package for {collection_pkg_name}!r>')
|
617
|
+
|
618
|
+
self.source_code = origin.tag(src)
|
866
619
|
return True
|
867
620
|
|
868
621
|
def _get_module_utils_remainder_parts(self, name_parts):
|
869
622
|
return name_parts[5:] # eg, foo.bar for ansible_collections.ns.coll.plugins.module_utils.foo.bar
|
870
623
|
|
871
624
|
|
872
|
-
def _make_zinfo(filename, date_time, zf=None):
|
625
|
+
def _make_zinfo(filename: str, date_time: datetime.datetime, zf: zipfile.ZipFile | None = None) -> zipfile.ZipInfo:
|
873
626
|
zinfo = zipfile.ZipInfo(
|
874
627
|
filename=filename,
|
875
|
-
date_time=date_time
|
628
|
+
date_time=date_time.utctimetuple()[:6],
|
876
629
|
)
|
630
|
+
|
877
631
|
if zf:
|
878
632
|
zinfo.compress_type = zf.compression
|
633
|
+
|
879
634
|
return zinfo
|
880
635
|
|
881
636
|
|
882
|
-
|
637
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
638
|
+
class ModuleMetadata:
|
639
|
+
@classmethod
|
640
|
+
def __post_init__(cls):
|
641
|
+
_dataclass_validation.inject_post_init_validation(cls)
|
642
|
+
|
643
|
+
|
644
|
+
@dataclasses.dataclass(frozen=True, kw_only=True, slots=True)
|
645
|
+
class ModuleMetadataV1(ModuleMetadata):
|
646
|
+
serialization_profile: str
|
647
|
+
|
648
|
+
|
649
|
+
metadata_versions: dict[t.Any, type[ModuleMetadata]] = {
|
650
|
+
1: ModuleMetadataV1,
|
651
|
+
}
|
652
|
+
|
653
|
+
|
654
|
+
def _get_module_metadata(module: ast.Module) -> ModuleMetadata:
|
655
|
+
# DTFIX-RELEASE: while module metadata works, this feature isn't fully baked and should be turned off before release
|
656
|
+
metadata_nodes: list[ast.Assign] = []
|
657
|
+
|
658
|
+
for node in module.body:
|
659
|
+
if isinstance(node, ast.Assign):
|
660
|
+
if len(node.targets) == 1:
|
661
|
+
target = node.targets[0]
|
662
|
+
|
663
|
+
if isinstance(target, ast.Name):
|
664
|
+
if target.id == 'METADATA':
|
665
|
+
metadata_nodes.append(node)
|
666
|
+
|
667
|
+
if not metadata_nodes:
|
668
|
+
return ModuleMetadataV1(
|
669
|
+
serialization_profile='legacy',
|
670
|
+
)
|
671
|
+
|
672
|
+
if len(metadata_nodes) > 1:
|
673
|
+
raise ValueError('Module METADATA must defined only once.')
|
674
|
+
|
675
|
+
metadata_node = metadata_nodes[0]
|
676
|
+
|
677
|
+
if not isinstance(metadata_node.value, ast.Constant):
|
678
|
+
raise TypeError(f'Module METADATA node must be {ast.Constant} not {type(metadata_node)}.')
|
679
|
+
|
680
|
+
unparsed_metadata = metadata_node.value.value
|
681
|
+
|
682
|
+
if not isinstance(unparsed_metadata, str):
|
683
|
+
raise TypeError(f'Module METADATA must be {str} not {type(unparsed_metadata)}.')
|
684
|
+
|
685
|
+
try:
|
686
|
+
parsed_metadata = yaml_load(unparsed_metadata)
|
687
|
+
except Exception as ex:
|
688
|
+
raise ValueError('Module METADATA must be valid YAML.') from ex
|
689
|
+
|
690
|
+
if not isinstance(parsed_metadata, dict):
|
691
|
+
raise TypeError(f'Module METADATA must parse to {dict} not {type(parsed_metadata)}.')
|
692
|
+
|
693
|
+
schema_version = parsed_metadata.pop('schema_version', None)
|
694
|
+
|
695
|
+
if not (metadata_type := metadata_versions.get(schema_version)):
|
696
|
+
raise ValueError(f'Module METADATA schema_version {schema_version} is unknown.')
|
697
|
+
|
698
|
+
try:
|
699
|
+
metadata = metadata_type(**parsed_metadata) # type: ignore
|
700
|
+
except Exception as ex:
|
701
|
+
raise ValueError('Module METADATA is invalid.') from ex
|
702
|
+
|
703
|
+
return metadata
|
704
|
+
|
705
|
+
|
706
|
+
def recursive_finder(name: str, module_fqn: str, module_data: str | bytes, zf: zipfile.ZipFile, date_time: datetime.datetime) -> ModuleMetadata:
|
883
707
|
"""
|
884
708
|
Using ModuleDepFinder, make sure we have all of the module_utils files that
|
885
709
|
the module and its module_utils files needs. (no longer actually recursive)
|
@@ -889,9 +713,6 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
|
|
889
713
|
:arg zf: An open :python:class:`zipfile.ZipFile` object that holds the Ansible module payload
|
890
714
|
which we're assembling
|
891
715
|
"""
|
892
|
-
if date_time is None:
|
893
|
-
date_time = time.gmtime()[:6]
|
894
|
-
|
895
716
|
# py_module_cache maps python module names to a tuple of the code in the module
|
896
717
|
# and the pathname to the module.
|
897
718
|
# Here we pre-load it with modules which we create without bothering to
|
@@ -913,49 +734,57 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
|
|
913
734
|
module_utils_paths = [p for p in module_utils_loader._get_paths(subdirs=False) if os.path.isdir(p)]
|
914
735
|
module_utils_paths.append(_MODULE_UTILS_PATH)
|
915
736
|
|
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
|
-
|
737
|
+
tree = _compile_module_ast(name, module_data)
|
738
|
+
module_metadata = _get_module_metadata(tree)
|
922
739
|
finder = ModuleDepFinder(module_fqn, tree)
|
923
740
|
|
924
|
-
|
925
|
-
|
926
|
-
|
741
|
+
if not isinstance(module_metadata, ModuleMetadataV1):
|
742
|
+
raise NotImplementedError()
|
743
|
+
|
744
|
+
profile = module_metadata.serialization_profile
|
745
|
+
|
746
|
+
# the format of this set is a tuple of the module name and whether the import is ambiguous as a module name
|
747
|
+
# or an attribute of a module (e.g. from x.y import z <-- is z a module or an attribute of x.y?)
|
748
|
+
modules_to_process = [_ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports) for m in finder.submodules]
|
749
|
+
|
750
|
+
# include module_utils that are always required
|
751
|
+
modules_to_process.extend((
|
752
|
+
_ModuleUtilsProcessEntry.from_module(_ansiballz),
|
753
|
+
_ModuleUtilsProcessEntry.from_module(_basic),
|
754
|
+
_ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, True)),
|
755
|
+
_ModuleUtilsProcessEntry.from_module_name(_json.get_module_serialization_profile_module_name(profile, False)),
|
756
|
+
))
|
927
757
|
|
928
|
-
|
929
|
-
modules_to_process.append(ModuleUtilsProcessEntry(('ansible', 'module_utils', 'basic'), False, False, is_optional=False))
|
758
|
+
module_info: ModuleUtilLocatorBase
|
930
759
|
|
931
760
|
# we'll be adding new modules inline as we discover them, so just keep going til we've processed them all
|
932
761
|
while modules_to_process:
|
933
762
|
modules_to_process.sort() # not strictly necessary, but nice to process things in predictable and repeatable order
|
934
|
-
|
763
|
+
entry = modules_to_process.pop(0)
|
935
764
|
|
936
|
-
if
|
765
|
+
if entry.name_parts in py_module_cache:
|
937
766
|
# this is normal; we'll often see the same module imported many times, but we only need to process it once
|
938
767
|
continue
|
939
768
|
|
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)
|
769
|
+
if entry.name_parts[0:2] == ('ansible', 'module_utils'):
|
770
|
+
module_info = LegacyModuleUtilLocator(entry.name_parts, is_ambiguous=entry.is_ambiguous,
|
771
|
+
mu_paths=module_utils_paths, child_is_redirected=entry.child_is_redirected)
|
772
|
+
elif entry.name_parts[0] == 'ansible_collections':
|
773
|
+
module_info = CollectionModuleUtilLocator(entry.name_parts, is_ambiguous=entry.is_ambiguous,
|
774
|
+
child_is_redirected=entry.child_is_redirected, is_optional=entry.is_optional)
|
946
775
|
else:
|
947
776
|
# FIXME: dot-joined result
|
948
777
|
display.warning('ModuleDepFinder improperly found a non-module_utils import %s'
|
949
|
-
% [
|
778
|
+
% [entry.name_parts])
|
950
779
|
continue
|
951
780
|
|
952
781
|
# Could not find the module. Construct a helpful error message.
|
953
782
|
if not module_info.found:
|
954
|
-
if is_optional:
|
783
|
+
if entry.is_optional:
|
955
784
|
# this was a best-effort optional import that we couldn't find, oh well, move along...
|
956
785
|
continue
|
957
786
|
# FIXME: use dot-joined candidate names
|
958
|
-
msg = 'Could not find imported module support code for {0}.
|
787
|
+
msg = 'Could not find imported module support code for {0}. Looked for ({1})'.format(module_fqn, module_info.candidate_names_joined)
|
959
788
|
raise AnsibleError(msg)
|
960
789
|
|
961
790
|
# check the cache one more time with the module we actually found, since the name could be different than the input
|
@@ -963,14 +792,9 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
|
|
963
792
|
if module_info.fq_name_parts in py_module_cache:
|
964
793
|
continue
|
965
794
|
|
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
|
-
|
795
|
+
tree = _compile_module_ast('.'.join(module_info.fq_name_parts), module_info.source_code)
|
972
796
|
finder = ModuleDepFinder('.'.join(module_info.fq_name_parts), tree, module_info.is_package)
|
973
|
-
modules_to_process.extend(
|
797
|
+
modules_to_process.extend(_ModuleUtilsProcessEntry(m, True, False, is_optional=m in finder.optional_imports)
|
974
798
|
for m in finder.submodules if m not in py_module_cache)
|
975
799
|
|
976
800
|
# we've processed this item, add it to the output list
|
@@ -982,7 +806,7 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
|
|
982
806
|
accumulated_pkg_name.append(pkg) # we're accumulating this across iterations
|
983
807
|
normalized_name = tuple(accumulated_pkg_name) # extra machinations to get a hashable type (list is not)
|
984
808
|
if normalized_name not in py_module_cache:
|
985
|
-
modules_to_process.append(
|
809
|
+
modules_to_process.append(_ModuleUtilsProcessEntry(normalized_name, False, module_info.redirected, is_optional=entry.is_optional))
|
986
810
|
|
987
811
|
for py_module_name in py_module_cache:
|
988
812
|
py_module_file_name = py_module_cache[py_module_name][1]
|
@@ -994,8 +818,23 @@ def recursive_finder(name, module_fqn, module_data, zf, date_time=None):
|
|
994
818
|
mu_file = to_text(py_module_file_name, errors='surrogate_or_strict')
|
995
819
|
display.vvvvv("Including module_utils file %s" % mu_file)
|
996
820
|
|
821
|
+
return module_metadata
|
822
|
+
|
823
|
+
|
824
|
+
def _compile_module_ast(module_name: str, source_code: str | bytes) -> ast.Module:
|
825
|
+
origin = Origin.get_tag(source_code) or Origin.UNKNOWN
|
826
|
+
|
827
|
+
# compile the source, process all relevant imported modules
|
828
|
+
try:
|
829
|
+
tree = t.cast(ast.Module, compile(source_code, str(origin), 'exec', ast.PyCF_ONLY_AST))
|
830
|
+
except SyntaxError as ex:
|
831
|
+
raise AnsibleError(f"Unable to compile {module_name!r}.", obj=origin.replace(line_num=ex.lineno, col_num=ex.offset)) from ex
|
832
|
+
|
833
|
+
return tree
|
834
|
+
|
997
835
|
|
998
836
|
def _is_binary(b_module_data):
|
837
|
+
"""Heuristic to classify a file as binary by sniffing a 1k header; see https://stackoverflow.com/a/7392391"""
|
999
838
|
textchars = bytearray(set([7, 8, 9, 10, 12, 13, 27]) | set(range(0x20, 0x100)) - set([0x7f]))
|
1000
839
|
start = b_module_data[:1024]
|
1001
840
|
return bool(start.translate(None, textchars))
|
@@ -1034,7 +873,7 @@ def _get_ansible_module_fqn(module_path):
|
|
1034
873
|
return remote_module_fqn
|
1035
874
|
|
1036
875
|
|
1037
|
-
def _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data):
|
876
|
+
def _add_module_to_zip(zf: zipfile.ZipFile, date_time: datetime.datetime, remote_module_fqn: str, b_module_data: bytes) -> None:
|
1038
877
|
"""Add a module from ansible or from an ansible collection into the module zip"""
|
1039
878
|
module_path_parts = remote_module_fqn.split('.')
|
1040
879
|
|
@@ -1045,6 +884,8 @@ def _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data):
|
|
1045
884
|
b_module_data
|
1046
885
|
)
|
1047
886
|
|
887
|
+
existing_paths: frozenset[str]
|
888
|
+
|
1048
889
|
# Write the __init__.py's necessary to get there
|
1049
890
|
if module_path_parts[0] == 'ansible':
|
1050
891
|
# The ansible namespace is setup as part of the module_utils setup...
|
@@ -1068,12 +909,59 @@ def _add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data):
|
|
1068
909
|
)
|
1069
910
|
|
1070
911
|
|
1071
|
-
|
1072
|
-
|
912
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
|
913
|
+
class _BuiltModule:
|
914
|
+
"""Payload required to execute an Ansible module, along with information required to do so."""
|
915
|
+
b_module_data: bytes
|
916
|
+
module_style: t.Literal['binary', 'new', 'non_native_want_json', 'old']
|
917
|
+
shebang: str | None
|
918
|
+
serialization_profile: str
|
919
|
+
|
920
|
+
|
921
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
|
922
|
+
class _CachedModule:
|
923
|
+
"""Cached Python module created by AnsiballZ."""
|
924
|
+
|
925
|
+
# DTFIX-RELEASE: secure this (locked down pickle, don't use pickle, etc.)
|
926
|
+
|
927
|
+
zip_data: bytes
|
928
|
+
metadata: ModuleMetadata
|
929
|
+
|
930
|
+
def dump(self, path: str) -> None:
|
931
|
+
temp_path = pathlib.Path(path + '-part')
|
932
|
+
|
933
|
+
with temp_path.open('wb') as cache_file:
|
934
|
+
pickle.dump(self, cache_file)
|
935
|
+
|
936
|
+
temp_path.rename(path)
|
937
|
+
|
938
|
+
@classmethod
|
939
|
+
def load(cls, path: str) -> t.Self:
|
940
|
+
with pathlib.Path(path).open('rb') as cache_file:
|
941
|
+
return pickle.load(cache_file)
|
942
|
+
|
943
|
+
|
944
|
+
def _find_module_utils(
|
945
|
+
*,
|
946
|
+
module_name: str,
|
947
|
+
plugin: PluginInfo,
|
948
|
+
b_module_data: bytes,
|
949
|
+
module_path: str,
|
950
|
+
module_args: dict[object, object],
|
951
|
+
task_vars: dict[str, object],
|
952
|
+
templar: Templar,
|
953
|
+
module_compression: str,
|
954
|
+
async_timeout: int,
|
955
|
+
become_plugin: BecomeBase | None,
|
956
|
+
environment: dict[str, str],
|
957
|
+
remote_is_local: bool = False
|
958
|
+
) -> _BuiltModule:
|
1073
959
|
"""
|
1074
960
|
Given the source of the module, convert it to a Jinja2 template to insert
|
1075
961
|
module code and return whether it's a new or old style module.
|
1076
962
|
"""
|
963
|
+
module_substyle: t.Literal['binary', 'jsonargs', 'non_native_want_json', 'old', 'powershell', 'python']
|
964
|
+
module_style: t.Literal['binary', 'new', 'non_native_want_json', 'old']
|
1077
965
|
module_substyle = module_style = 'old'
|
1078
966
|
|
1079
967
|
# module_style is something important to calling code (ActionBase). It
|
@@ -1096,7 +984,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
1096
984
|
elif REPLACER_WINDOWS in b_module_data:
|
1097
985
|
module_style = 'new'
|
1098
986
|
module_substyle = 'powershell'
|
1099
|
-
b_module_data = b_module_data.replace(REPLACER_WINDOWS, b'#
|
987
|
+
b_module_data = b_module_data.replace(REPLACER_WINDOWS, b'#AnsibleRequires -PowerShell Ansible.ModuleUtils.Legacy')
|
1100
988
|
elif re.search(b'#Requires -Module', b_module_data, re.IGNORECASE) \
|
1101
989
|
or re.search(b'#Requires -Version', b_module_data, re.IGNORECASE)\
|
1102
990
|
or re.search(b'#AnsibleRequires -OSVersion', b_module_data, re.IGNORECASE) \
|
@@ -1114,7 +1002,12 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
1114
1002
|
# Neither old-style, non_native_want_json nor binary modules should be modified
|
1115
1003
|
# except for the shebang line (Done by modify_module)
|
1116
1004
|
if module_style in ('old', 'non_native_want_json', 'binary'):
|
1117
|
-
return
|
1005
|
+
return _BuiltModule(
|
1006
|
+
b_module_data=b_module_data,
|
1007
|
+
module_style=module_style,
|
1008
|
+
shebang=shebang,
|
1009
|
+
serialization_profile='legacy',
|
1010
|
+
)
|
1118
1011
|
|
1119
1012
|
output = BytesIO()
|
1120
1013
|
|
@@ -1130,15 +1023,9 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
1130
1023
|
remote_module_fqn = 'ansible.modules.%s' % module_name
|
1131
1024
|
|
1132
1025
|
if module_substyle == 'python':
|
1133
|
-
date_time =
|
1134
|
-
if date_time
|
1135
|
-
|
1136
|
-
raise AnsibleError(f'Cannot create zipfile due to pre-1980 configured date: {date_string}')
|
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))
|
1026
|
+
date_time = datetime.datetime.now(datetime.timezone.utc)
|
1027
|
+
if date_time.year < 1980:
|
1028
|
+
raise AnsibleError(f'Cannot create zipfile due to pre-1980 configured date: {date_time}')
|
1142
1029
|
|
1143
1030
|
try:
|
1144
1031
|
compression_method = getattr(zipfile, module_compression)
|
@@ -1146,30 +1033,24 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
1146
1033
|
display.warning(u'Bad module compression string specified: %s. Using ZIP_STORED (no compression)' % module_compression)
|
1147
1034
|
compression_method = zipfile.ZIP_STORED
|
1148
1035
|
|
1149
|
-
lookup_path = os.path.join(C.DEFAULT_LOCAL_TMP, 'ansiballz_cache')
|
1036
|
+
lookup_path = os.path.join(C.DEFAULT_LOCAL_TMP, 'ansiballz_cache') # type: ignore[attr-defined]
|
1150
1037
|
cached_module_filename = os.path.join(lookup_path, "%s-%s" % (remote_module_fqn, module_compression))
|
1151
1038
|
|
1152
|
-
|
1039
|
+
os.makedirs(os.path.dirname(cached_module_filename), exist_ok=True)
|
1040
|
+
|
1041
|
+
zipdata: bytes | None = None
|
1042
|
+
module_metadata: ModuleMetadata | None = None
|
1043
|
+
|
1153
1044
|
# Optimization -- don't lock if the module has already been cached
|
1154
1045
|
if os.path.exists(cached_module_filename):
|
1155
1046
|
display.debug('ANSIBALLZ: using cached module: %s' % cached_module_filename)
|
1156
|
-
|
1157
|
-
|
1047
|
+
cached_module = _CachedModule.load(cached_module_filename)
|
1048
|
+
zipdata, module_metadata = cached_module.zip_data, cached_module.metadata
|
1158
1049
|
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
1050
|
display.debug('ANSIBALLZ: Acquiring lock')
|
1171
|
-
|
1172
|
-
|
1051
|
+
lock_path = f'{cached_module_filename}.lock'
|
1052
|
+
with _locking.named_mutex(lock_path):
|
1053
|
+
display.debug(f'ANSIBALLZ: Lock acquired: {lock_path}')
|
1173
1054
|
# Check that no other process has created this while we were
|
1174
1055
|
# waiting for the lock
|
1175
1056
|
if not os.path.exists(cached_module_filename):
|
@@ -1179,7 +1060,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
1179
1060
|
zf = zipfile.ZipFile(zipoutput, mode='w', compression=compression_method)
|
1180
1061
|
|
1181
1062
|
# walk the module imports, looking for module_utils to send- they'll be added to the zipfile
|
1182
|
-
recursive_finder(module_name, remote_module_fqn, b_module_data, zf, date_time)
|
1063
|
+
module_metadata = recursive_finder(module_name, remote_module_fqn, Origin(path=module_path).tag(b_module_data), zf, date_time)
|
1183
1064
|
|
1184
1065
|
display.debug('ANSIBALLZ: Writing module into payload')
|
1185
1066
|
_add_module_to_zip(zf, date_time, remote_module_fqn, b_module_data)
|
@@ -1190,42 +1071,24 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
1190
1071
|
# Write the assembled module to a temp file (write to temp
|
1191
1072
|
# so that no one looking for the file reads a partially
|
1192
1073
|
# 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
|
1074
|
+
os.makedirs(lookup_path, exist_ok=True)
|
1206
1075
|
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)
|
1076
|
+
cached_module = _CachedModule(zip_data=zipdata, metadata=module_metadata)
|
1077
|
+
cached_module.dump(cached_module_filename)
|
1215
1078
|
display.debug('ANSIBALLZ: Done creating module')
|
1216
1079
|
|
1217
|
-
if zipdata
|
1080
|
+
if not zipdata:
|
1218
1081
|
display.debug('ANSIBALLZ: Reading module after lock')
|
1219
1082
|
# Another process wrote the file while we were waiting for
|
1220
1083
|
# the write lock. Go ahead and read the data from disk
|
1221
1084
|
# instead of re-creating it.
|
1222
1085
|
try:
|
1223
|
-
|
1224
|
-
zipdata = f.read()
|
1086
|
+
cached_module = _CachedModule.load(cached_module_filename)
|
1225
1087
|
except IOError:
|
1226
1088
|
raise AnsibleError('A different worker process failed to create module file. '
|
1227
1089
|
'Look at traceback for that process for debugging information.')
|
1228
|
-
|
1090
|
+
|
1091
|
+
zipdata, module_metadata = cached_module.zip_data, cached_module.metadata
|
1229
1092
|
|
1230
1093
|
o_interpreter, o_args = _extract_interpreter(b_module_data)
|
1231
1094
|
if o_interpreter is None:
|
@@ -1237,63 +1100,79 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
1237
1100
|
rlimit_nofile = C.config.get_config_value('PYTHON_MODULE_RLIMIT_NOFILE', variables=task_vars)
|
1238
1101
|
|
1239
1102
|
if not isinstance(rlimit_nofile, int):
|
1240
|
-
rlimit_nofile = int(templar.template(rlimit_nofile))
|
1241
|
-
|
1242
|
-
if rlimit_nofile:
|
1243
|
-
rlimit = ANSIBALLZ_RLIMIT_TEMPLATE % dict(
|
1244
|
-
rlimit_nofile=rlimit_nofile,
|
1245
|
-
)
|
1246
|
-
else:
|
1247
|
-
rlimit = ''
|
1103
|
+
rlimit_nofile = int(templar._engine.template(rlimit_nofile, options=TemplateOptions(value_for_omit=0)))
|
1248
1104
|
|
1249
1105
|
coverage_config = os.environ.get('_ANSIBLE_COVERAGE_CONFIG')
|
1250
1106
|
|
1251
1107
|
if coverage_config:
|
1252
1108
|
coverage_output = os.environ['_ANSIBLE_COVERAGE_OUTPUT']
|
1253
|
-
|
1254
|
-
if coverage_output:
|
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
1109
|
else:
|
1266
|
-
|
1110
|
+
coverage_output = None
|
1111
|
+
|
1112
|
+
if not isinstance(module_metadata, ModuleMetadataV1):
|
1113
|
+
raise NotImplementedError()
|
1114
|
+
|
1115
|
+
params = dict(ANSIBLE_MODULE_ARGS=module_args,)
|
1116
|
+
encoder = get_module_encoder(module_metadata.serialization_profile, Direction.CONTROLLER_TO_MODULE)
|
1117
|
+
try:
|
1118
|
+
encoded_params = json.dumps(params, cls=encoder)
|
1119
|
+
except TypeError as ex:
|
1120
|
+
raise AnsibleError(f'Failed to serialize arguments for the {module_name!r} module.') from ex
|
1267
1121
|
|
1268
|
-
|
1269
|
-
|
1122
|
+
code = _get_ansiballz_code(shebang)
|
1123
|
+
args = dict(
|
1124
|
+
zipdata=to_text(zipdata),
|
1270
1125
|
ansible_module=module_name,
|
1271
1126
|
module_fqn=remote_module_fqn,
|
1272
|
-
params=
|
1273
|
-
|
1274
|
-
|
1127
|
+
params=encoded_params,
|
1128
|
+
profile=module_metadata.serialization_profile,
|
1129
|
+
plugin_info_dict=dataclasses.asdict(plugin),
|
1275
1130
|
date_time=date_time,
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1131
|
+
coverage_config=coverage_config,
|
1132
|
+
coverage_output=coverage_output,
|
1133
|
+
rlimit_nofile=rlimit_nofile,
|
1134
|
+
)
|
1135
|
+
|
1136
|
+
args_string = '\n'.join(f'{key}={value!r},' for key, value in args.items())
|
1137
|
+
|
1138
|
+
wrapper = f"""{code}
|
1139
|
+
|
1140
|
+
if __name__ == "__main__":
|
1141
|
+
_ansiballz_main(
|
1142
|
+
{args_string}
|
1143
|
+
)
|
1144
|
+
"""
|
1145
|
+
|
1146
|
+
output.write(to_bytes(wrapper))
|
1147
|
+
|
1279
1148
|
b_module_data = output.getvalue()
|
1280
1149
|
|
1281
1150
|
elif module_substyle == 'powershell':
|
1151
|
+
module_metadata = ModuleMetadataV1(serialization_profile='legacy') # DTFIX-FUTURE: support serialization profiles for PowerShell modules
|
1152
|
+
|
1282
1153
|
# Powershell/winrm don't actually make use of shebang so we can
|
1283
1154
|
# safely set this here. If we let the fallback code handle this
|
1284
1155
|
# it can fail in the presence of the UTF8 BOM commonly added by
|
1285
1156
|
# Windows text editors
|
1286
|
-
shebang =
|
1157
|
+
shebang = '#!powershell'
|
1287
1158
|
# create the common exec wrapper payload and set that as the module_data
|
1288
1159
|
# bytes
|
1289
1160
|
b_module_data = ps_manifest._create_powershell_wrapper(
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1161
|
+
name=remote_module_fqn,
|
1162
|
+
module_data=b_module_data,
|
1163
|
+
module_path=module_path,
|
1164
|
+
module_args=module_args,
|
1165
|
+
environment=environment,
|
1166
|
+
async_timeout=async_timeout,
|
1167
|
+
become_plugin=become_plugin,
|
1168
|
+
substyle=module_substyle,
|
1169
|
+
task_vars=task_vars,
|
1170
|
+
profile=module_metadata.serialization_profile,
|
1293
1171
|
)
|
1294
1172
|
|
1295
1173
|
elif module_substyle == 'jsonargs':
|
1296
|
-
|
1174
|
+
encoder = get_module_encoder('legacy', Direction.CONTROLLER_TO_MODULE)
|
1175
|
+
module_args_json = to_bytes(json.dumps(module_args, cls=encoder))
|
1297
1176
|
|
1298
1177
|
# these strings could be included in a third-party module but
|
1299
1178
|
# officially they were included in the 'basic' snippet for new-style
|
@@ -1303,15 +1182,32 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
|
|
1303
1182
|
python_repred_args = to_bytes(repr(module_args_json))
|
1304
1183
|
b_module_data = b_module_data.replace(REPLACER_VERSION, to_bytes(repr(__version__)))
|
1305
1184
|
b_module_data = b_module_data.replace(REPLACER_COMPLEX, python_repred_args)
|
1306
|
-
b_module_data = b_module_data.replace(
|
1185
|
+
b_module_data = b_module_data.replace(
|
1186
|
+
REPLACER_SELINUX,
|
1187
|
+
to_bytes(','.join(C.DEFAULT_SELINUX_SPECIAL_FS))) # type: ignore[attr-defined]
|
1307
1188
|
|
1308
1189
|
# The main event -- substitute the JSON args string into the module
|
1309
1190
|
b_module_data = b_module_data.replace(REPLACER_JSONARGS, module_args_json)
|
1310
1191
|
|
1311
|
-
|
1192
|
+
syslog_facility = task_vars.get(
|
1193
|
+
'ansible_syslog_facility',
|
1194
|
+
C.DEFAULT_SYSLOG_FACILITY) # type: ignore[attr-defined]
|
1195
|
+
facility = b'syslog.' + to_bytes(syslog_facility, errors='surrogate_or_strict')
|
1312
1196
|
b_module_data = b_module_data.replace(b'syslog.LOG_USER', facility)
|
1313
1197
|
|
1314
|
-
|
1198
|
+
module_metadata = ModuleMetadataV1(serialization_profile='legacy')
|
1199
|
+
else:
|
1200
|
+
module_metadata = ModuleMetadataV1(serialization_profile='legacy')
|
1201
|
+
|
1202
|
+
if not isinstance(module_metadata, ModuleMetadataV1):
|
1203
|
+
raise NotImplementedError(type(module_metadata))
|
1204
|
+
|
1205
|
+
return _BuiltModule(
|
1206
|
+
b_module_data=b_module_data,
|
1207
|
+
module_style=module_style,
|
1208
|
+
shebang=shebang,
|
1209
|
+
serialization_profile=module_metadata.serialization_profile,
|
1210
|
+
)
|
1315
1211
|
|
1316
1212
|
|
1317
1213
|
def _extract_interpreter(b_module_data):
|
@@ -1337,8 +1233,20 @@ def _extract_interpreter(b_module_data):
|
|
1337
1233
|
return interpreter, args
|
1338
1234
|
|
1339
1235
|
|
1340
|
-
def modify_module(
|
1341
|
-
|
1236
|
+
def modify_module(
|
1237
|
+
*,
|
1238
|
+
module_name: str,
|
1239
|
+
plugin: PluginInfo,
|
1240
|
+
module_path,
|
1241
|
+
module_args,
|
1242
|
+
templar,
|
1243
|
+
task_vars=None,
|
1244
|
+
module_compression='ZIP_STORED',
|
1245
|
+
async_timeout=0,
|
1246
|
+
become_plugin=None,
|
1247
|
+
environment=None,
|
1248
|
+
remote_is_local=False,
|
1249
|
+
) -> _BuiltModule:
|
1342
1250
|
"""
|
1343
1251
|
Used to insert chunks of code into modules before transfer rather than
|
1344
1252
|
doing regular python imports. This allows for more efficient transfer in
|
@@ -1367,13 +1275,31 @@ def modify_module(module_name, module_path, module_args, templar, task_vars=None
|
|
1367
1275
|
# read in the module source
|
1368
1276
|
b_module_data = f.read()
|
1369
1277
|
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1278
|
+
module_bits = _find_module_utils(
|
1279
|
+
module_name=module_name,
|
1280
|
+
plugin=plugin,
|
1281
|
+
b_module_data=b_module_data,
|
1282
|
+
module_path=module_path,
|
1283
|
+
module_args=module_args,
|
1284
|
+
task_vars=task_vars,
|
1285
|
+
templar=templar,
|
1286
|
+
module_compression=module_compression,
|
1287
|
+
async_timeout=async_timeout,
|
1288
|
+
become_plugin=become_plugin,
|
1289
|
+
environment=environment,
|
1290
|
+
remote_is_local=remote_is_local,
|
1291
|
+
)
|
1292
|
+
|
1293
|
+
b_module_data = module_bits.b_module_data
|
1294
|
+
shebang = module_bits.shebang
|
1374
1295
|
|
1375
|
-
if module_style == 'binary':
|
1376
|
-
return (
|
1296
|
+
if module_bits.module_style == 'binary':
|
1297
|
+
return _BuiltModule(
|
1298
|
+
b_module_data=module_bits.b_module_data,
|
1299
|
+
module_style=module_bits.module_style,
|
1300
|
+
shebang=to_text(module_bits.shebang, nonstring='passthru'),
|
1301
|
+
serialization_profile=module_bits.serialization_profile,
|
1302
|
+
)
|
1377
1303
|
elif shebang is None:
|
1378
1304
|
interpreter, args = _extract_interpreter(b_module_data)
|
1379
1305
|
# No interpreter/shebang, assume a binary module?
|
@@ -1387,15 +1313,20 @@ def modify_module(module_name, module_path, module_args, templar, task_vars=None
|
|
1387
1313
|
if interpreter != new_interpreter:
|
1388
1314
|
b_lines[0] = to_bytes(shebang, errors='surrogate_or_strict', nonstring='passthru')
|
1389
1315
|
|
1390
|
-
if os.path.basename(interpreter).startswith(u'python'):
|
1391
|
-
b_lines.insert(1, b_ENCODING_STRING)
|
1392
|
-
|
1393
1316
|
b_module_data = b"\n".join(b_lines)
|
1394
1317
|
|
1395
|
-
return (
|
1318
|
+
return _BuiltModule(
|
1319
|
+
b_module_data=b_module_data,
|
1320
|
+
module_style=module_bits.module_style,
|
1321
|
+
shebang=shebang,
|
1322
|
+
serialization_profile=module_bits.serialization_profile,
|
1323
|
+
)
|
1324
|
+
|
1396
1325
|
|
1326
|
+
def _get_action_arg_defaults(action: str, task: Task, templar: TemplateEngine) -> dict[str, t.Any]:
|
1327
|
+
action_groups = task._parent._play._action_groups
|
1328
|
+
defaults = task.module_defaults
|
1397
1329
|
|
1398
|
-
def get_action_args_with_defaults(action, args, defaults, templar, action_groups=None):
|
1399
1330
|
# Get the list of groups that contain this action
|
1400
1331
|
if action_groups is None:
|
1401
1332
|
msg = (
|
@@ -1408,7 +1339,7 @@ def get_action_args_with_defaults(action, args, defaults, templar, action_groups
|
|
1408
1339
|
else:
|
1409
1340
|
group_names = action_groups.get(action, [])
|
1410
1341
|
|
1411
|
-
tmp_args = {}
|
1342
|
+
tmp_args: dict[str, t.Any] = {}
|
1412
1343
|
module_defaults = {}
|
1413
1344
|
|
1414
1345
|
# Merge latest defaults into dict, since they are a list of dicts
|
@@ -1416,18 +1347,20 @@ def get_action_args_with_defaults(action, args, defaults, templar, action_groups
|
|
1416
1347
|
for default in defaults:
|
1417
1348
|
module_defaults.update(default)
|
1418
1349
|
|
1419
|
-
# module_defaults keys are static, but the values may be templated
|
1420
|
-
module_defaults = templar.template(module_defaults)
|
1421
1350
|
for default in module_defaults:
|
1422
1351
|
if default.startswith('group/'):
|
1423
1352
|
group_name = default.split('group/')[-1]
|
1424
1353
|
if group_name in group_names:
|
1425
|
-
tmp_args.update((module_defaults.get('group
|
1354
|
+
tmp_args.update(templar.resolve_to_container(module_defaults.get(f'group/{group_name}', {})))
|
1426
1355
|
|
1427
1356
|
# handle specific action defaults
|
1428
|
-
tmp_args.update(module_defaults.get(action, {})
|
1429
|
-
|
1430
|
-
# direct args override all
|
1431
|
-
tmp_args.update(args)
|
1357
|
+
tmp_args.update(templar.resolve_to_container(module_defaults.get(action, {})))
|
1432
1358
|
|
1433
1359
|
return tmp_args
|
1360
|
+
|
1361
|
+
|
1362
|
+
def _apply_action_arg_defaults(action: str, task: Task, action_args: dict[str, t.Any], templar: Templar) -> dict[str, t.Any]:
|
1363
|
+
args = _get_action_arg_defaults(action, task, templar._engine)
|
1364
|
+
args.update(action_args)
|
1365
|
+
|
1366
|
+
return args
|