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
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
from __future__ import annotations
|
8
8
|
|
9
|
-
DOCUMENTATION =
|
9
|
+
DOCUMENTATION = """
|
10
10
|
name: ssh
|
11
11
|
short_description: connect via SSH client binary
|
12
12
|
description:
|
@@ -62,10 +62,27 @@ DOCUMENTATION = '''
|
|
62
62
|
- name: ansible_password
|
63
63
|
- name: ansible_ssh_pass
|
64
64
|
- name: ansible_ssh_password
|
65
|
+
password_mechanism:
|
66
|
+
description: Mechanism to use for handling ssh password prompt
|
67
|
+
type: string
|
68
|
+
default: ssh_askpass
|
69
|
+
choices:
|
70
|
+
- ssh_askpass
|
71
|
+
- sshpass
|
72
|
+
- disable
|
73
|
+
version_added: '2.19'
|
74
|
+
env:
|
75
|
+
- name: ANSIBLE_SSH_PASSWORD_MECHANISM
|
76
|
+
ini:
|
77
|
+
- {key: password_mechanism, section: ssh_connection}
|
78
|
+
vars:
|
79
|
+
- name: ansible_ssh_password_mechanism
|
65
80
|
sshpass_prompt:
|
66
81
|
description:
|
67
|
-
- Password prompt that sshpass should search for.
|
82
|
+
- Password prompt that C(sshpass)/C(SSH_ASKPASS) should search for.
|
83
|
+
- Supported by sshpass 1.06 and up when O(password_mechanism) set to V(sshpass).
|
68
84
|
- Defaults to C(Enter PIN for) when pkcs11_provider is set.
|
85
|
+
- Defaults to C(assword) when O(password_mechanism) set to V(ssh_askpass).
|
69
86
|
default: ''
|
70
87
|
type: string
|
71
88
|
ini:
|
@@ -248,7 +265,6 @@ DOCUMENTATION = '''
|
|
248
265
|
vars:
|
249
266
|
- name: ansible_pipelining
|
250
267
|
- name: ansible_ssh_pipelining
|
251
|
-
|
252
268
|
private_key_file:
|
253
269
|
description:
|
254
270
|
- Path to private key file to use for authentication.
|
@@ -264,7 +280,27 @@ DOCUMENTATION = '''
|
|
264
280
|
cli:
|
265
281
|
- name: private_key_file
|
266
282
|
option: '--private-key'
|
267
|
-
|
283
|
+
private_key:
|
284
|
+
description:
|
285
|
+
- Private key contents in PEM format. Requires the C(SSH_AGENT) configuration to be enabled.
|
286
|
+
type: string
|
287
|
+
env:
|
288
|
+
- name: ANSIBLE_PRIVATE_KEY
|
289
|
+
vars:
|
290
|
+
- name: ansible_private_key
|
291
|
+
- name: ansible_ssh_private_key
|
292
|
+
version_added: '2.19'
|
293
|
+
private_key_passphrase:
|
294
|
+
description:
|
295
|
+
- Private key passphrase, dependent on O(private_key).
|
296
|
+
- This does NOT have any effect when used with O(private_key_file).
|
297
|
+
type: string
|
298
|
+
env:
|
299
|
+
- name: ANSIBLE_PRIVATE_KEY_PASSPHRASE
|
300
|
+
vars:
|
301
|
+
- name: ansible_private_key_passphrase
|
302
|
+
- name: ansible_ssh_private_key_passphrase
|
303
|
+
version_added: '2.19'
|
268
304
|
control_path:
|
269
305
|
description:
|
270
306
|
- This is the location to save SSH's ControlPath sockets, it uses SSH's variable substitution.
|
@@ -357,41 +393,58 @@ DOCUMENTATION = '''
|
|
357
393
|
type: string
|
358
394
|
description:
|
359
395
|
- "PKCS11 SmartCard provider such as opensc, example: /usr/local/lib/opensc-pkcs11.so"
|
360
|
-
- Requires sshpass version 1.06+, sshpass must support the -P option.
|
361
396
|
env: [{name: ANSIBLE_PKCS11_PROVIDER}]
|
362
397
|
ini:
|
363
398
|
- {key: pkcs11_provider, section: ssh_connection}
|
364
399
|
vars:
|
365
400
|
- name: ansible_ssh_pkcs11_provider
|
366
|
-
|
401
|
+
"""
|
367
402
|
|
368
403
|
import collections.abc as c
|
404
|
+
import argparse
|
369
405
|
import errno
|
406
|
+
import contextlib
|
370
407
|
import fcntl
|
371
408
|
import hashlib
|
372
409
|
import io
|
410
|
+
import json
|
373
411
|
import os
|
412
|
+
import pathlib
|
374
413
|
import pty
|
375
414
|
import re
|
376
415
|
import selectors
|
377
416
|
import shlex
|
417
|
+
import shutil
|
378
418
|
import subprocess
|
419
|
+
import sys
|
420
|
+
import tempfile
|
379
421
|
import time
|
380
422
|
import typing as t
|
381
|
-
|
382
423
|
from functools import wraps
|
424
|
+
from multiprocessing.shared_memory import SharedMemory
|
425
|
+
|
426
|
+
from ansible import constants as C
|
383
427
|
from ansible.errors import (
|
384
428
|
AnsibleAuthenticationFailure,
|
385
429
|
AnsibleConnectionFailure,
|
386
430
|
AnsibleError,
|
387
431
|
AnsibleFileNotFound,
|
388
432
|
)
|
389
|
-
from ansible.module_utils.six import
|
433
|
+
from ansible.module_utils.six import text_type, binary_type
|
390
434
|
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
391
435
|
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
392
436
|
from ansible.plugins.shell.powershell import _replace_stderr_clixml
|
393
437
|
from ansible.utils.display import Display
|
394
438
|
from ansible.utils.path import unfrackpath, makedirs_safe
|
439
|
+
from ansible.utils._ssh_agent import SshAgentClient, _key_data_into_crypto_objects
|
440
|
+
|
441
|
+
try:
|
442
|
+
from cryptography.hazmat.primitives import serialization
|
443
|
+
except ImportError:
|
444
|
+
HAS_CRYPTOGRAPHY = False
|
445
|
+
else:
|
446
|
+
HAS_CRYPTOGRAPHY = True
|
447
|
+
|
395
448
|
|
396
449
|
display = Display()
|
397
450
|
|
@@ -408,9 +461,14 @@ b_NOT_SSH_ERRORS = (b'Traceback (most recent call last):', # Python-2.6 when th
|
|
408
461
|
SSHPASS_AVAILABLE = None
|
409
462
|
SSH_DEBUG = re.compile(r'^debug\d+: .*')
|
410
463
|
|
464
|
+
_HAS_RESOURCE_TRACK = sys.version_info[:2] >= (3, 13)
|
465
|
+
|
466
|
+
PKCS11_DEFAULT_PROMPT = 'Enter PIN for '
|
467
|
+
SSH_ASKPASS_DEFAULT_PROMPT = 'assword'
|
468
|
+
|
411
469
|
|
412
470
|
class AnsibleControlPersistBrokenPipeError(AnsibleError):
|
413
|
-
|
471
|
+
""" ControlPersist broken pipe """
|
414
472
|
pass
|
415
473
|
|
416
474
|
|
@@ -450,6 +508,7 @@ def _handle_error(
|
|
450
508
|
'Upgrade sshpass to use sshpass_prompt, or otherwise switch to ssh keys.'
|
451
509
|
raise AnsibleError('{0} {1}'.format(msg, details))
|
452
510
|
msg = '{0} {1}'.format(msg, details)
|
511
|
+
raise AnsibleConnectionFailure(msg)
|
453
512
|
|
454
513
|
if return_tuple[0] == 255:
|
455
514
|
SSH_ERROR = True
|
@@ -496,9 +555,10 @@ def _ssh_retry(
|
|
496
555
|
remaining_tries = int(self.get_option('reconnection_retries')) + 1
|
497
556
|
cmd_summary = u"%s..." % to_text(args[0])
|
498
557
|
conn_password = self.get_option('password') or self._play_context.password
|
558
|
+
is_sshpass = self.get_option('password_mechanism') == 'sshpass'
|
499
559
|
for attempt in range(remaining_tries):
|
500
560
|
cmd = t.cast(list[bytes], args[0])
|
501
|
-
if attempt != 0 and conn_password and isinstance(cmd, list):
|
561
|
+
if attempt != 0 and is_sshpass and conn_password and isinstance(cmd, list):
|
502
562
|
# If this is a retry, the fd/pipe for sshpass is closed, and we need a new one
|
503
563
|
self.sshpass_pipe = os.pipe()
|
504
564
|
cmd[1] = b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict')
|
@@ -517,7 +577,7 @@ def _ssh_retry(
|
|
517
577
|
except (AnsibleControlPersistBrokenPipeError):
|
518
578
|
# Retry one more time because of the ControlPersist broken pipe (see #16731)
|
519
579
|
cmd = t.cast(list[bytes], args[0])
|
520
|
-
if conn_password and isinstance(cmd, list):
|
580
|
+
if is_sshpass and conn_password and isinstance(cmd, list):
|
521
581
|
# This is a retry, so the fd/pipe for sshpass is closed, and we need a new one
|
522
582
|
self.sshpass_pipe = os.pipe()
|
523
583
|
cmd[1] = b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict')
|
@@ -558,8 +618,26 @@ def _ssh_retry(
|
|
558
618
|
return wrapped
|
559
619
|
|
560
620
|
|
621
|
+
def _clean_shm(func):
|
622
|
+
def inner(self, *args, **kwargs):
|
623
|
+
try:
|
624
|
+
ret = func(self, *args, **kwargs)
|
625
|
+
finally:
|
626
|
+
if self.shm:
|
627
|
+
self.shm.close()
|
628
|
+
with contextlib.suppress(FileNotFoundError):
|
629
|
+
self.shm.unlink()
|
630
|
+
if not _HAS_RESOURCE_TRACK:
|
631
|
+
# deprecated: description='unneeded due to track argument for SharedMemory' python_version='3.12'
|
632
|
+
# There is a resource tracking issue where the resource is deleted, but tracking still has a record
|
633
|
+
# This will effectively overwrite the record and remove it
|
634
|
+
SharedMemory(name=self.shm.name, create=True, size=1).unlink()
|
635
|
+
return ret
|
636
|
+
return inner
|
637
|
+
|
638
|
+
|
561
639
|
class Connection(ConnectionBase):
|
562
|
-
|
640
|
+
""" ssh based connections """
|
563
641
|
|
564
642
|
transport = 'ssh'
|
565
643
|
has_pipelining = True
|
@@ -573,6 +651,8 @@ class Connection(ConnectionBase):
|
|
573
651
|
self.user = self._play_context.remote_user
|
574
652
|
self.control_path: str | None = None
|
575
653
|
self.control_path_dir: str | None = None
|
654
|
+
self.shm: SharedMemory | None = None
|
655
|
+
self.sshpass_pipe: tuple[int, int] | None = None
|
576
656
|
|
577
657
|
# Windows operates differently from a POSIX connection/shell plugin,
|
578
658
|
# we need to set various properties to ensure SSH on Windows continues
|
@@ -583,6 +663,13 @@ class Connection(ConnectionBase):
|
|
583
663
|
self.module_implementation_preferences = ('.ps1', '.exe', '')
|
584
664
|
self.allow_executable = False
|
585
665
|
|
666
|
+
# parser to discover 'passed options', used later on for pipelining resolution
|
667
|
+
self._tty_parser = argparse.ArgumentParser()
|
668
|
+
self._tty_parser.add_argument('-t', action='count')
|
669
|
+
self._tty_parser.add_argument('-o', action='append')
|
670
|
+
|
671
|
+
self._populated_agent: pathlib.Path | None = None
|
672
|
+
|
586
673
|
# The connection is created by running ssh/scp/sftp from the exec_command,
|
587
674
|
# put_file, and fetch_file methods, so we don't need to do any connection
|
588
675
|
# management here.
|
@@ -598,7 +685,7 @@ class Connection(ConnectionBase):
|
|
598
685
|
connection: ConnectionBase | None = None,
|
599
686
|
pid: int | None = None,
|
600
687
|
) -> str:
|
601
|
-
|
688
|
+
"""Make a hash for the controlpath based on con attributes"""
|
602
689
|
pstring = '%s-%s-%s' % (host, port, user)
|
603
690
|
if connection:
|
604
691
|
pstring += '-%s' % connection
|
@@ -614,28 +701,21 @@ class Connection(ConnectionBase):
|
|
614
701
|
def _sshpass_available() -> bool:
|
615
702
|
global SSHPASS_AVAILABLE
|
616
703
|
|
617
|
-
# We test once if sshpass is available, and remember the result.
|
618
|
-
# would be nice to use distutils.spawn.find_executable for this, but
|
619
|
-
# distutils isn't always available; shutils.which() is Python3-only.
|
704
|
+
# We test once if sshpass is available, and remember the result.
|
620
705
|
|
621
706
|
if SSHPASS_AVAILABLE is None:
|
622
|
-
|
623
|
-
p = subprocess.Popen(["sshpass"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
624
|
-
p.communicate()
|
625
|
-
SSHPASS_AVAILABLE = True
|
626
|
-
except OSError:
|
627
|
-
SSHPASS_AVAILABLE = False
|
707
|
+
SSHPASS_AVAILABLE = shutil.which('sshpass') is not None
|
628
708
|
|
629
709
|
return SSHPASS_AVAILABLE
|
630
710
|
|
631
711
|
@staticmethod
|
632
712
|
def _persistence_controls(b_command: list[bytes]) -> tuple[bool, bool]:
|
633
|
-
|
713
|
+
"""
|
634
714
|
Takes a command array and scans it for ControlPersist and ControlPath
|
635
715
|
settings and returns two booleans indicating whether either was found.
|
636
716
|
This could be smarter, e.g. returning false if ControlPersist is 'no',
|
637
717
|
but for now we do it simple way.
|
638
|
-
|
718
|
+
"""
|
639
719
|
|
640
720
|
controlpersist = False
|
641
721
|
controlpath = False
|
@@ -664,8 +744,54 @@ class Connection(ConnectionBase):
|
|
664
744
|
display.vvvvv(u'SSH: %s: (%s)' % (explanation, ')('.join(to_text(a) for a in b_args)), host=self.host)
|
665
745
|
b_command += b_args
|
666
746
|
|
747
|
+
def _populate_agent(self) -> pathlib.Path:
|
748
|
+
"""Adds configured private key identity to the SSH agent. Returns a path to a file containing the public key."""
|
749
|
+
if self._populated_agent:
|
750
|
+
return self._populated_agent
|
751
|
+
|
752
|
+
if (auth_sock := C.config.get_config_value('SSH_AGENT')) == 'none':
|
753
|
+
raise AnsibleError('Cannot utilize private_key with SSH_AGENT disabled')
|
754
|
+
|
755
|
+
key_data = self.get_option('private_key')
|
756
|
+
passphrase = self.get_option('private_key_passphrase')
|
757
|
+
|
758
|
+
private_key, public_key, fingerprint = _key_data_into_crypto_objects(
|
759
|
+
to_bytes(key_data),
|
760
|
+
to_bytes(passphrase) if passphrase else None,
|
761
|
+
)
|
762
|
+
|
763
|
+
with SshAgentClient(auth_sock) as client:
|
764
|
+
if public_key not in client:
|
765
|
+
display.vvv(f'SSH: SSH_AGENT adding {fingerprint} to agent', host=self.host)
|
766
|
+
client.add(
|
767
|
+
private_key,
|
768
|
+
f'[added by ansible: PID={os.getpid()}, UID={os.getuid()}, EUID={os.geteuid()}, TIME={time.time()}]',
|
769
|
+
C.config.get_config_value('SSH_AGENT_KEY_LIFETIME'),
|
770
|
+
)
|
771
|
+
else:
|
772
|
+
display.vvv(f'SSH: SSH_AGENT {fingerprint} exists in agent', host=self.host)
|
773
|
+
# Write the public key to disk, to be provided as IdentityFile.
|
774
|
+
# This allows ssh to pick an explicit key in the agent to use,
|
775
|
+
# preventing ssh from attempting all keys in the agent.
|
776
|
+
pubkey_path = self._populated_agent = pathlib.Path(C.DEFAULT_LOCAL_TMP).joinpath(
|
777
|
+
fingerprint.replace('/', '-') + '.pub'
|
778
|
+
)
|
779
|
+
if os.path.exists(pubkey_path):
|
780
|
+
return pubkey_path
|
781
|
+
|
782
|
+
with tempfile.NamedTemporaryFile(dir=C.DEFAULT_LOCAL_TMP, delete=False) as f:
|
783
|
+
f.write(public_key.public_bytes(
|
784
|
+
encoding=serialization.Encoding.OpenSSH,
|
785
|
+
format=serialization.PublicFormat.OpenSSH
|
786
|
+
))
|
787
|
+
# move atomically to prevent race conditions, silently succeeds if the target exists
|
788
|
+
os.rename(f.name, pubkey_path)
|
789
|
+
os.chmod(pubkey_path, mode=0o400)
|
790
|
+
|
791
|
+
return self._populated_agent
|
792
|
+
|
667
793
|
def _build_command(self, binary: str, subsystem: str, *other_args: bytes | str) -> list[bytes]:
|
668
|
-
|
794
|
+
"""
|
669
795
|
Takes a executable (ssh, scp, sftp or wrapper) and optional extra arguments and returns the remote command
|
670
796
|
wrapped in local ssh shell commands and ready for execution.
|
671
797
|
|
@@ -673,21 +799,22 @@ class Connection(ConnectionBase):
|
|
673
799
|
:arg subsystem: type of executable provided, ssh/sftp/scp, needed because wrappers for ssh might have diff names.
|
674
800
|
:arg other_args: dict of, value pairs passed as arguments to the ssh binary
|
675
801
|
|
676
|
-
|
802
|
+
"""
|
677
803
|
|
678
804
|
b_command = []
|
679
805
|
conn_password = self.get_option('password') or self._play_context.password
|
806
|
+
pkcs11_provider = self.get_option("pkcs11_provider")
|
807
|
+
password_mechanism = self.get_option('password_mechanism')
|
680
808
|
|
681
809
|
#
|
682
810
|
# First, the command to invoke
|
683
811
|
#
|
684
812
|
|
685
|
-
# If we want to use password authentication, we have to set up a pipe to
|
813
|
+
# If we want to use sshpass for password authentication, we have to set up a pipe to
|
686
814
|
# write the password to sshpass.
|
687
|
-
|
688
|
-
if conn_password or pkcs11_provider:
|
815
|
+
if password_mechanism == 'sshpass' and (conn_password or pkcs11_provider):
|
689
816
|
if not self._sshpass_available():
|
690
|
-
raise AnsibleError("to use the
|
817
|
+
raise AnsibleError("to use the password_mechanism=sshpass, you must install the sshpass program")
|
691
818
|
if not conn_password and pkcs11_provider:
|
692
819
|
raise AnsibleError("to use pkcs11_provider you must specify a password/pin")
|
693
820
|
|
@@ -697,7 +824,7 @@ class Connection(ConnectionBase):
|
|
697
824
|
password_prompt = self.get_option('sshpass_prompt')
|
698
825
|
if not password_prompt and pkcs11_provider:
|
699
826
|
# Set default password prompt for pkcs11_provider to make it clear its a PIN
|
700
|
-
password_prompt =
|
827
|
+
password_prompt = PKCS11_DEFAULT_PROMPT
|
701
828
|
|
702
829
|
if password_prompt:
|
703
830
|
b_command += [b'-P', to_bytes(password_prompt, errors='surrogate_or_strict')]
|
@@ -720,12 +847,12 @@ class Connection(ConnectionBase):
|
|
720
847
|
# sftp batch mode allows us to correctly catch failed transfers, but can
|
721
848
|
# be disabled if the client side doesn't support the option. However,
|
722
849
|
# sftp batch mode does not prompt for passwords so it must be disabled
|
723
|
-
# if not using controlpersist and using
|
850
|
+
# if not using controlpersist and using password auth
|
724
851
|
b_args: t.Iterable[bytes]
|
725
852
|
if subsystem == 'sftp' and self.get_option('sftp_batch_mode'):
|
726
853
|
if conn_password:
|
727
854
|
b_args = [b'-o', b'BatchMode=no']
|
728
|
-
self._add_args(b_command, b_args, u'disable batch mode for
|
855
|
+
self._add_args(b_command, b_args, u'disable batch mode for password auth')
|
729
856
|
b_command += [b'-b', b'-']
|
730
857
|
|
731
858
|
if display.verbosity:
|
@@ -748,8 +875,17 @@ class Connection(ConnectionBase):
|
|
748
875
|
b_args = (b"-o", b"Port=" + to_bytes(self.port, nonstring='simplerepr', errors='surrogate_or_strict'))
|
749
876
|
self._add_args(b_command, b_args, u"ANSIBLE_REMOTE_PORT/remote_port/ansible_port set")
|
750
877
|
|
751
|
-
|
752
|
-
|
878
|
+
if self.get_option('private_key'):
|
879
|
+
try:
|
880
|
+
key = self._populate_agent()
|
881
|
+
except Exception as e:
|
882
|
+
raise AnsibleAuthenticationFailure(
|
883
|
+
'Failed to add configured private key into ssh-agent',
|
884
|
+
orig_exc=e,
|
885
|
+
)
|
886
|
+
b_args = (b'-o', b'IdentitiesOnly=yes', b'-o', to_bytes(f'IdentityFile="{key}"', errors='surrogate_or_strict'))
|
887
|
+
self._add_args(b_command, b_args, "ANSIBLE_PRIVATE_KEY/private_key set")
|
888
|
+
elif key := self.get_option('private_key_file'):
|
753
889
|
b_args = (b"-o", b'IdentityFile="' + to_bytes(os.path.expanduser(key), errors='surrogate_or_strict') + b'"')
|
754
890
|
self._add_args(b_command, b_args, u"ANSIBLE_PRIVATE_KEY_FILE/private_key_file/ansible_ssh_private_key_file set")
|
755
891
|
|
@@ -822,27 +958,24 @@ class Connection(ConnectionBase):
|
|
822
958
|
return b_command
|
823
959
|
|
824
960
|
def _send_initial_data(self, fh: io.IOBase, in_data: bytes, ssh_process: subprocess.Popen) -> None:
|
825
|
-
|
961
|
+
"""
|
826
962
|
Writes initial data to the stdin filehandle of the subprocess and closes
|
827
963
|
it. (The handle must be closed; otherwise, for example, "sftp -b -" will
|
828
964
|
just hang forever waiting for more commands.)
|
829
|
-
|
965
|
+
"""
|
830
966
|
|
831
967
|
display.debug(u'Sending initial data')
|
832
968
|
|
833
969
|
try:
|
834
970
|
fh.write(to_bytes(in_data))
|
835
971
|
fh.close()
|
836
|
-
except (OSError, IOError) as
|
972
|
+
except (OSError, IOError) as ex:
|
837
973
|
# The ssh connection may have already terminated at this point, with a more useful error
|
838
974
|
# Only raise AnsibleConnectionFailure if the ssh process is still alive
|
839
975
|
time.sleep(0.001)
|
840
976
|
ssh_process.poll()
|
841
977
|
if getattr(ssh_process, 'returncode', None) is None:
|
842
|
-
raise AnsibleConnectionFailure(
|
843
|
-
'Data could not be sent to remote host "%s". Make sure this host can be reached '
|
844
|
-
'over ssh: %s' % (self.host, to_native(e)), orig_exc=e
|
845
|
-
)
|
978
|
+
raise AnsibleConnectionFailure(f'Data could not be sent to remote host {self.host!r}. Make sure this host can be reached over SSH.') from ex
|
846
979
|
|
847
980
|
display.debug(u'Sent initial data (%d bytes)' % len(in_data))
|
848
981
|
|
@@ -858,14 +991,14 @@ class Connection(ConnectionBase):
|
|
858
991
|
# This is separate from _run() because we need to do the same thing for stdout
|
859
992
|
# and stderr.
|
860
993
|
def _examine_output(self, source: str, state: str, b_chunk: bytes, sudoable: bool) -> tuple[bytes, bytes]:
|
861
|
-
|
994
|
+
"""
|
862
995
|
Takes a string, extracts complete lines from it, tests to see if they
|
863
996
|
are a prompt, error message, etc., and sets appropriate flags in self.
|
864
997
|
Prompt and success lines are removed.
|
865
998
|
|
866
999
|
Returns the processed (i.e. possibly-edited) output and the unprocessed
|
867
1000
|
remainder (to be processed with the next chunk) as strings.
|
868
|
-
|
1001
|
+
"""
|
869
1002
|
|
870
1003
|
output = []
|
871
1004
|
for b_line in b_chunk.splitlines(True):
|
@@ -906,15 +1039,69 @@ class Connection(ConnectionBase):
|
|
906
1039
|
|
907
1040
|
return b''.join(output), remainder
|
908
1041
|
|
1042
|
+
def _init_shm(self) -> dict[str, t.Any]:
|
1043
|
+
env = os.environ.copy()
|
1044
|
+
popen_kwargs: dict[str, t.Any] = {}
|
1045
|
+
|
1046
|
+
if self.get_option('password_mechanism') != 'ssh_askpass':
|
1047
|
+
return popen_kwargs
|
1048
|
+
|
1049
|
+
conn_password = self.get_option('password') or self._play_context.password
|
1050
|
+
pkcs11_provider = self.get_option("pkcs11_provider")
|
1051
|
+
if not conn_password and pkcs11_provider:
|
1052
|
+
raise AnsibleError("to use pkcs11_provider you must specify a password/pin")
|
1053
|
+
|
1054
|
+
if not conn_password:
|
1055
|
+
return popen_kwargs
|
1056
|
+
|
1057
|
+
kwargs = {}
|
1058
|
+
if _HAS_RESOURCE_TRACK:
|
1059
|
+
# deprecated: description='track argument for SharedMemory always available' python_version='3.12'
|
1060
|
+
kwargs['track'] = False
|
1061
|
+
self.shm = shm = SharedMemory(create=True, size=16384, **kwargs) # type: ignore[arg-type]
|
1062
|
+
|
1063
|
+
sshpass_prompt = self.get_option('sshpass_prompt')
|
1064
|
+
if not sshpass_prompt and pkcs11_provider:
|
1065
|
+
sshpass_prompt = PKCS11_DEFAULT_PROMPT
|
1066
|
+
elif not sshpass_prompt:
|
1067
|
+
sshpass_prompt = SSH_ASKPASS_DEFAULT_PROMPT
|
1068
|
+
|
1069
|
+
data = json.dumps({
|
1070
|
+
'password': conn_password,
|
1071
|
+
'prompt': sshpass_prompt,
|
1072
|
+
}).encode('utf-8')
|
1073
|
+
shm.buf[:len(data)] = bytearray(data)
|
1074
|
+
shm.close()
|
1075
|
+
|
1076
|
+
env['_ANSIBLE_SSH_ASKPASS_SHM'] = str(self.shm.name)
|
1077
|
+
adhoc = pathlib.Path(sys.argv[0]).with_name('ansible')
|
1078
|
+
env['SSH_ASKPASS'] = str(adhoc) if adhoc.is_file() else 'ansible'
|
1079
|
+
|
1080
|
+
# SSH_ASKPASS_REQUIRE was added in openssh 8.4, prior to 8.4 there must be no tty, and DISPLAY must be set
|
1081
|
+
env['SSH_ASKPASS_REQUIRE'] = 'force'
|
1082
|
+
if not env.get('DISPLAY'):
|
1083
|
+
# If the user has DISPLAY set, assume it is there for a reason
|
1084
|
+
env['DISPLAY'] = '-'
|
1085
|
+
|
1086
|
+
popen_kwargs['env'] = env
|
1087
|
+
# start_new_session runs setsid which detaches the tty to support the use of ASKPASS prior to openssh 8.4
|
1088
|
+
popen_kwargs['start_new_session'] = True
|
1089
|
+
|
1090
|
+
return popen_kwargs
|
1091
|
+
|
1092
|
+
@_clean_shm
|
909
1093
|
def _bare_run(self, cmd: list[bytes], in_data: bytes | None, sudoable: bool = True, checkrc: bool = True) -> tuple[int, bytes, bytes]:
|
910
|
-
|
1094
|
+
"""
|
911
1095
|
Starts the command and communicates with it until it ends.
|
912
|
-
|
1096
|
+
"""
|
913
1097
|
|
914
1098
|
# We don't use _shell.quote as this is run on the controller and independent from the shell plugin chosen
|
915
1099
|
display_cmd = u' '.join(shlex.quote(to_text(c)) for c in cmd)
|
916
1100
|
display.vvv(u'SSH: EXEC {0}'.format(display_cmd), host=self.host)
|
917
1101
|
|
1102
|
+
conn_password = self.get_option('password') or self._play_context.password
|
1103
|
+
password_mechanism = self.get_option('password_mechanism')
|
1104
|
+
|
918
1105
|
# Start the given command. If we don't need to pipeline data, we can try
|
919
1106
|
# to use a pseudo-tty (ssh will have been invoked with -tt). If we are
|
920
1107
|
# pipelining data, or can't create a pty, we fall back to using plain
|
@@ -927,17 +1114,16 @@ class Connection(ConnectionBase):
|
|
927
1114
|
else:
|
928
1115
|
cmd = list(map(to_bytes, cmd))
|
929
1116
|
|
930
|
-
|
1117
|
+
popen_kwargs = self._init_shm()
|
1118
|
+
|
1119
|
+
if self.sshpass_pipe:
|
1120
|
+
popen_kwargs['pass_fds'] = self.sshpass_pipe
|
931
1121
|
|
932
1122
|
if not in_data:
|
933
1123
|
try:
|
934
1124
|
# Make sure stdin is a proper pty to avoid tcgetattr errors
|
935
1125
|
master, slave = pty.openpty()
|
936
|
-
|
937
|
-
# pylint: disable=unexpected-keyword-arg
|
938
|
-
p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE, pass_fds=self.sshpass_pipe)
|
939
|
-
else:
|
940
|
-
p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
1126
|
+
p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **popen_kwargs)
|
941
1127
|
stdin = os.fdopen(master, 'wb', 0)
|
942
1128
|
os.close(slave)
|
943
1129
|
except (OSError, IOError):
|
@@ -945,21 +1131,13 @@ class Connection(ConnectionBase):
|
|
945
1131
|
|
946
1132
|
if not p:
|
947
1133
|
try:
|
948
|
-
|
949
|
-
|
950
|
-
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
951
|
-
stderr=subprocess.PIPE, pass_fds=self.sshpass_pipe)
|
952
|
-
else:
|
953
|
-
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
954
|
-
stderr=subprocess.PIPE)
|
1134
|
+
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
1135
|
+
stderr=subprocess.PIPE, **popen_kwargs)
|
955
1136
|
stdin = p.stdin # type: ignore[assignment] # stdin will be set and not None due to the calls above
|
956
1137
|
except (OSError, IOError) as e:
|
957
1138
|
raise AnsibleError('Unable to execute ssh command line on a controller due to: %s' % to_native(e))
|
958
1139
|
|
959
|
-
|
960
|
-
# the pipe we opened in _build_command.
|
961
|
-
|
962
|
-
if conn_password:
|
1140
|
+
if password_mechanism == 'sshpass' and conn_password:
|
963
1141
|
os.close(self.sshpass_pipe[0])
|
964
1142
|
try:
|
965
1143
|
os.write(self.sshpass_pipe[1], to_bytes(conn_password) + b'\n')
|
@@ -1047,7 +1225,9 @@ class Connection(ConnectionBase):
|
|
1047
1225
|
if poll is not None:
|
1048
1226
|
break
|
1049
1227
|
self._terminate_process(p)
|
1050
|
-
raise
|
1228
|
+
raise AnsibleConnectionFailure('Timeout (%ds) waiting for privilege escalation prompt: %s' % (timeout, to_native(b_stdout)))
|
1229
|
+
|
1230
|
+
display.vvvvv(f'SSH: Timeout ({timeout}s) waiting for the output', host=self.host)
|
1051
1231
|
|
1052
1232
|
# Read whatever output is available on stdout and stderr, and stop
|
1053
1233
|
# listening to the pipe if it's been closed.
|
@@ -1117,23 +1297,23 @@ class Connection(ConnectionBase):
|
|
1117
1297
|
|
1118
1298
|
if states[state] == 'awaiting_escalation':
|
1119
1299
|
if self._flags['become_success']:
|
1120
|
-
display.vvv(u'Escalation succeeded')
|
1300
|
+
display.vvv(u'Escalation succeeded', host=self.host)
|
1121
1301
|
self._flags['become_success'] = False
|
1122
1302
|
state += 1
|
1123
1303
|
elif self._flags['become_error']:
|
1124
|
-
display.vvv(u'Escalation failed')
|
1304
|
+
display.vvv(u'Escalation failed', host=self.host)
|
1125
1305
|
self._terminate_process(p)
|
1126
1306
|
self._flags['become_error'] = False
|
1127
1307
|
raise AnsibleError('Incorrect %s password' % self.become.name)
|
1128
1308
|
elif self._flags['become_nopasswd_error']:
|
1129
|
-
display.vvv(u'Escalation requires password')
|
1309
|
+
display.vvv(u'Escalation requires password', host=self.host)
|
1130
1310
|
self._terminate_process(p)
|
1131
1311
|
self._flags['become_nopasswd_error'] = False
|
1132
1312
|
raise AnsibleError('Missing %s password' % self.become.name)
|
1133
1313
|
elif self._flags['become_prompt']:
|
1134
1314
|
# This shouldn't happen, because we should see the "Sorry,
|
1135
1315
|
# try again" message first.
|
1136
|
-
display.vvv(u'Escalation prompt repeated')
|
1316
|
+
display.vvv(u'Escalation prompt repeated', host=self.host)
|
1137
1317
|
self._terminate_process(p)
|
1138
1318
|
self._flags['become_prompt'] = False
|
1139
1319
|
raise AnsibleError('Incorrect %s password' % self.become.name)
|
@@ -1176,10 +1356,15 @@ class Connection(ConnectionBase):
|
|
1176
1356
|
p.stdout.close()
|
1177
1357
|
p.stderr.close()
|
1178
1358
|
|
1179
|
-
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
1359
|
+
conn_password = self.get_option('password') or self._play_context.password
|
1360
|
+
hostkey_fail = any((
|
1361
|
+
(cmd[0] == b"sshpass" and p.returncode == 6),
|
1362
|
+
b"read_passphrase: can't open /dev/tty" in b_stderr,
|
1363
|
+
b"Host key verification failed" in b_stderr,
|
1364
|
+
))
|
1365
|
+
if password_mechanism and self.get_option('host_key_checking') and conn_password and hostkey_fail:
|
1366
|
+
raise AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled. '
|
1367
|
+
'Please add this host\'s fingerprint to your known_hosts file to manage this host.')
|
1183
1368
|
|
1184
1369
|
controlpersisterror = b'Bad configuration option: ControlPersist' in b_stderr or b'unknown configuration option: ControlPersist' in b_stderr
|
1185
1370
|
if p.returncode != 0 and controlpersisterror:
|
@@ -1292,7 +1477,7 @@ class Connection(ConnectionBase):
|
|
1292
1477
|
# Main public methods
|
1293
1478
|
#
|
1294
1479
|
def exec_command(self, cmd: str, in_data: bytes | None = None, sudoable: bool = True) -> tuple[int, bytes, bytes]:
|
1295
|
-
|
1480
|
+
""" run a command on the remote host """
|
1296
1481
|
|
1297
1482
|
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
1298
1483
|
|
@@ -1333,7 +1518,7 @@ class Connection(ConnectionBase):
|
|
1333
1518
|
return (returncode, stdout, stderr)
|
1334
1519
|
|
1335
1520
|
def put_file(self, in_path: str, out_path: str) -> tuple[int, bytes, bytes]: # type: ignore[override] # Used by tests and would break API
|
1336
|
-
|
1521
|
+
""" transfer a file from local to remote """
|
1337
1522
|
|
1338
1523
|
super(Connection, self).put_file(in_path, out_path)
|
1339
1524
|
|
@@ -1349,7 +1534,7 @@ class Connection(ConnectionBase):
|
|
1349
1534
|
return self._file_transport_command(in_path, out_path, 'put')
|
1350
1535
|
|
1351
1536
|
def fetch_file(self, in_path: str, out_path: str) -> tuple[int, bytes, bytes]: # type: ignore[override] # Used by tests and would break API
|
1352
|
-
|
1537
|
+
""" fetch a file from remote to local """
|
1353
1538
|
|
1354
1539
|
super(Connection, self).fetch_file(in_path, out_path)
|
1355
1540
|
|
@@ -1372,18 +1557,18 @@ class Connection(ConnectionBase):
|
|
1372
1557
|
# only run the reset if the ControlPath already exists or if it isn't configured and ControlPersist is set
|
1373
1558
|
# 'check' will determine this.
|
1374
1559
|
cmd = self._build_command(self.get_option('ssh_executable'), 'ssh', '-O', 'check', self.host)
|
1375
|
-
display.vvv(u'sending connection check: %s' % to_text(cmd))
|
1560
|
+
display.vvv(u'sending connection check: %s' % to_text(cmd), host=self.host)
|
1376
1561
|
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
1377
1562
|
stdout, stderr = p.communicate()
|
1378
1563
|
status_code = p.wait()
|
1379
1564
|
if status_code != 0:
|
1380
|
-
display.vvv(u"No connection to reset: %s" % to_text(stderr))
|
1565
|
+
display.vvv(u"No connection to reset: %s" % to_text(stderr), host=self.host)
|
1381
1566
|
else:
|
1382
1567
|
run_reset = True
|
1383
1568
|
|
1384
1569
|
if run_reset:
|
1385
1570
|
cmd = self._build_command(self.get_option('ssh_executable'), 'ssh', '-O', 'stop', self.host)
|
1386
|
-
display.vvv(u'sending connection stop: %s' % to_text(cmd))
|
1571
|
+
display.vvv(u'sending connection stop: %s' % to_text(cmd), host=self.host)
|
1387
1572
|
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
1388
1573
|
stdout, stderr = p.communicate()
|
1389
1574
|
status_code = p.wait()
|
@@ -1394,3 +1579,41 @@ class Connection(ConnectionBase):
|
|
1394
1579
|
|
1395
1580
|
def close(self) -> None:
|
1396
1581
|
self._connected = False
|
1582
|
+
|
1583
|
+
@property
|
1584
|
+
def has_tty(self):
|
1585
|
+
return self._is_tty_requested()
|
1586
|
+
|
1587
|
+
def _is_tty_requested(self):
|
1588
|
+
|
1589
|
+
# check if we require tty (only from our args, cannot see options in configuration files)
|
1590
|
+
opts = []
|
1591
|
+
for opt in ('ssh_args', 'ssh_common_args', 'ssh_extra_args'):
|
1592
|
+
attr = self.get_option(opt)
|
1593
|
+
if attr is not None:
|
1594
|
+
opts.extend(self._split_ssh_args(attr))
|
1595
|
+
|
1596
|
+
args, dummy = self._tty_parser.parse_known_args(opts)
|
1597
|
+
|
1598
|
+
if args.t:
|
1599
|
+
return True
|
1600
|
+
|
1601
|
+
for arg in args.o or []:
|
1602
|
+
if '=' in arg:
|
1603
|
+
val = arg.split('=', 1)
|
1604
|
+
else:
|
1605
|
+
val = arg.split(maxsplit=1)
|
1606
|
+
|
1607
|
+
if val[0].lower().strip() == 'requesttty':
|
1608
|
+
if val[1].lower().strip() in ('yes', 'force'):
|
1609
|
+
return True
|
1610
|
+
|
1611
|
+
return False
|
1612
|
+
|
1613
|
+
def is_pipelining_enabled(self, wrap_async=False):
|
1614
|
+
""" override parent method and ensure we don't request a tty """
|
1615
|
+
|
1616
|
+
if self._is_tty_requested():
|
1617
|
+
return False
|
1618
|
+
else:
|
1619
|
+
return super(Connection, self).is_pipelining_enabled(wrap_async)
|