ansible-core 2.18.7__py3-none-any.whl → 2.19.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ansible-core might be problematic. Click here for more details.
- ansible/_internal/__init__.py +53 -0
- ansible/_internal/_ansiballz/__init__.py +0 -0
- ansible/_internal/_ansiballz/_builder.py +101 -0
- ansible/_internal/_ansiballz/_wrapper.py +262 -0
- ansible/_internal/_collection_proxy.py +47 -0
- ansible/_internal/_datatag/__init__.py +0 -0
- ansible/_internal/_datatag/_tags.py +130 -0
- ansible/_internal/_datatag/_utils.py +19 -0
- ansible/_internal/_datatag/_wrappers.py +33 -0
- ansible/_internal/_errors/__init__.py +0 -0
- ansible/_internal/_errors/_alarm_timeout.py +66 -0
- ansible/_internal/_errors/_captured.py +123 -0
- ansible/_internal/_errors/_error_factory.py +89 -0
- ansible/_internal/_errors/_error_utils.py +240 -0
- ansible/_internal/_errors/_handler.py +91 -0
- ansible/_internal/_errors/_task_timeout.py +28 -0
- ansible/_internal/_event_formatting.py +127 -0
- ansible/_internal/_json/__init__.py +214 -0
- ansible/_internal/_json/_legacy_encoder.py +34 -0
- ansible/_internal/_json/_profiles/__init__.py +0 -0
- ansible/_internal/_json/_profiles/_cache_persistence.py +57 -0
- ansible/_internal/_json/_profiles/_inventory_legacy.py +40 -0
- ansible/_internal/_json/_profiles/_legacy.py +189 -0
- ansible/_internal/_locking.py +21 -0
- ansible/_internal/_plugins/__init__.py +0 -0
- ansible/_internal/_plugins/_cache.py +57 -0
- ansible/_internal/_ssh/__init__.py +0 -0
- ansible/_internal/_ssh/_agent_launch.py +91 -0
- ansible/_internal/_ssh/_ssh_agent.py +619 -0
- ansible/_internal/_task.py +78 -0
- ansible/_internal/_templating/__init__.py +12 -0
- ansible/_internal/_templating/_access.py +86 -0
- ansible/_internal/_templating/_chain_templar.py +63 -0
- ansible/_internal/_templating/_datatag.py +95 -0
- ansible/_internal/_templating/_engine.py +592 -0
- ansible/_internal/_templating/_errors.py +28 -0
- ansible/_internal/_templating/_jinja_bits.py +1106 -0
- ansible/_internal/_templating/_jinja_common.py +323 -0
- ansible/_internal/_templating/_jinja_patches.py +44 -0
- ansible/_internal/_templating/_jinja_plugins.py +375 -0
- ansible/_internal/_templating/_lazy_containers.py +633 -0
- ansible/_internal/_templating/_marker_behaviors.py +103 -0
- ansible/_internal/_templating/_template_vars.py +72 -0
- ansible/_internal/_templating/_transform.py +70 -0
- ansible/_internal/_templating/_utils.py +108 -0
- ansible/_internal/_testing.py +26 -0
- ansible/_internal/_wrapt.py +1052 -0
- ansible/_internal/_yaml/__init__.py +0 -0
- ansible/_internal/_yaml/_constructor.py +240 -0
- ansible/_internal/_yaml/_dumper.py +70 -0
- ansible/_internal/_yaml/_errors.py +166 -0
- ansible/_internal/_yaml/_loader.py +66 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/README.md +11 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/action/debug.py +36 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/apply_trust.py +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/dump_object.py +27 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/finalize.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/origin.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.py +24 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/python_literal_eval.yml +33 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/tag_names.py +16 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/true_type.py +17 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/filter/unmask.py +49 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.py +21 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/lookup/config.yml +2 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.py +15 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged.yml +19 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.py +18 -0
- ansible/_internal/ansible_collections/ansible/_protomatter/plugins/test/tagged_with.yml +19 -0
- ansible/cli/__init__.py +93 -104
- ansible/cli/_ssh_askpass.py +54 -0
- ansible/cli/adhoc.py +20 -10
- ansible/cli/arguments/option_helpers.py +163 -10
- ansible/cli/config.py +43 -68
- ansible/cli/console.py +13 -11
- ansible/cli/doc.py +134 -77
- ansible/cli/galaxy.py +27 -20
- ansible/cli/inventory.py +28 -28
- ansible/cli/playbook.py +4 -12
- ansible/cli/pull.py +6 -3
- ansible/cli/scripts/ansible_connection_cli_stub.py +7 -7
- ansible/cli/vault.py +12 -11
- ansible/compat/__init__.py +2 -2
- ansible/compat/importlib_resources.py +9 -12
- ansible/config/base.yml +218 -133
- ansible/config/manager.py +220 -159
- ansible/constants.py +2 -65
- ansible/errors/__init__.py +350 -256
- ansible/executor/interpreter_discovery.py +28 -149
- ansible/executor/module_common.py +480 -514
- ansible/executor/play_iterator.py +22 -27
- ansible/executor/playbook_executor.py +11 -11
- ansible/executor/powershell/async_watchdog.ps1 +97 -102
- ansible/executor/powershell/async_wrapper.ps1 +204 -153
- ansible/executor/powershell/become_wrapper.ps1 +107 -144
- ansible/executor/powershell/bootstrap_wrapper.ps1 +46 -9
- ansible/executor/powershell/coverage_wrapper.ps1 +91 -135
- ansible/executor/powershell/exec_wrapper.ps1 +675 -196
- ansible/executor/powershell/module_manifest.py +469 -265
- ansible/executor/powershell/module_wrapper.ps1 +195 -186
- ansible/executor/powershell/powershell_expand_user.ps1 +20 -0
- ansible/executor/powershell/powershell_mkdtemp.ps1 +17 -0
- ansible/executor/powershell/psrp_fetch_file.ps1 +41 -0
- ansible/executor/powershell/psrp_put_file.ps1 +122 -0
- ansible/executor/powershell/winrm_fetch_file.ps1 +46 -0
- ansible/executor/powershell/winrm_put_file.ps1 +36 -0
- ansible/executor/process/worker.py +139 -149
- ansible/executor/stats.py +5 -5
- ansible/executor/task_executor.py +270 -297
- ansible/executor/task_queue_manager.py +135 -137
- ansible/executor/task_result.py +182 -79
- ansible/galaxy/__init__.py +2 -2
- ansible/galaxy/api.py +26 -25
- ansible/galaxy/collection/__init__.py +6 -14
- ansible/galaxy/collection/concrete_artifact_manager.py +12 -21
- ansible/galaxy/dependency_resolution/dataclasses.py +14 -4
- ansible/galaxy/dependency_resolution/providers.py +4 -4
- ansible/galaxy/dependency_resolution/reporters.py +81 -0
- ansible/galaxy/role.py +6 -10
- ansible/galaxy/token.py +28 -21
- ansible/inventory/data.py +47 -57
- ansible/inventory/group.py +50 -73
- ansible/inventory/helpers.py +9 -0
- ansible/inventory/host.py +37 -54
- ansible/inventory/manager.py +79 -34
- ansible/keyword_desc.yml +1 -1
- ansible/module_utils/_internal/__init__.py +55 -0
- ansible/module_utils/_internal/_ambient_context.py +58 -0
- ansible/module_utils/_internal/_ansiballz/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/__init__.py +0 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_coverage.py +45 -0
- ansible/module_utils/_internal/_ansiballz/_extensions/_pydevd.py +62 -0
- ansible/module_utils/_internal/_ansiballz/_loader.py +81 -0
- ansible/module_utils/_internal/_ansiballz/_respawn.py +32 -0
- ansible/module_utils/_internal/_ansiballz/_respawn_wrapper.py +23 -0
- ansible/module_utils/_internal/_concurrent/_daemon_threading.py +1 -0
- ansible/module_utils/_internal/_dataclass_validation.py +217 -0
- ansible/module_utils/_internal/_datatag/__init__.py +961 -0
- ansible/module_utils/_internal/_datatag/_tags.py +16 -0
- ansible/module_utils/_internal/_debugging.py +31 -0
- ansible/module_utils/_internal/_deprecator.py +157 -0
- ansible/module_utils/_internal/_errors.py +101 -0
- ansible/module_utils/_internal/_event_utils.py +61 -0
- ansible/module_utils/_internal/_json/__init__.py +63 -0
- ansible/module_utils/_internal/_json/_legacy_encoder.py +26 -0
- ansible/module_utils/_internal/_json/_profiles/__init__.py +428 -0
- ansible/module_utils/_internal/_json/_profiles/_fallback_to_str.py +73 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_c2m.py +33 -0
- ansible/module_utils/_internal/_json/_profiles/_module_legacy_m2c.py +37 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_c2m.py +35 -0
- ansible/module_utils/_internal/_json/_profiles/_module_modern_m2c.py +33 -0
- ansible/module_utils/_internal/_json/_profiles/_tagless.py +52 -0
- ansible/module_utils/_internal/_messages.py +130 -0
- ansible/module_utils/_internal/_patches/__init__.py +66 -0
- ansible/module_utils/_internal/_patches/_dataclass_annotation_patch.py +53 -0
- ansible/module_utils/_internal/_patches/_socket_patch.py +34 -0
- ansible/module_utils/_internal/_patches/_sys_intern_patch.py +34 -0
- ansible/module_utils/_internal/_plugin_info.py +38 -0
- ansible/module_utils/_internal/_stack.py +22 -0
- ansible/module_utils/_internal/_testing.py +0 -0
- ansible/module_utils/_internal/_text_utils.py +6 -0
- ansible/module_utils/_internal/_traceback.py +92 -0
- ansible/module_utils/_internal/_validation.py +14 -0
- ansible/module_utils/ansible_release.py +2 -2
- ansible/module_utils/api.py +1 -2
- ansible/module_utils/basic.py +303 -202
- ansible/module_utils/common/_utils.py +24 -28
- ansible/module_utils/common/arg_spec.py +8 -3
- ansible/module_utils/common/collections.py +7 -2
- ansible/module_utils/common/dict_transformations.py +2 -2
- ansible/module_utils/common/file.py +2 -2
- ansible/module_utils/common/json.py +90 -84
- ansible/module_utils/common/locale.py +2 -2
- ansible/module_utils/common/parameters.py +27 -24
- ansible/module_utils/common/process.py +2 -3
- ansible/module_utils/common/respawn.py +11 -33
- ansible/module_utils/common/sentinel.py +66 -0
- ansible/module_utils/common/sys_info.py +8 -8
- ansible/module_utils/common/text/converters.py +16 -37
- ansible/module_utils/common/validation.py +35 -24
- ansible/module_utils/common/warnings.py +143 -25
- ansible/module_utils/common/yaml.py +29 -3
- ansible/module_utils/compat/datetime.py +33 -21
- ansible/module_utils/compat/paramiko.py +21 -10
- ansible/module_utils/compat/typing.py +6 -5
- ansible/module_utils/connection.py +10 -13
- ansible/module_utils/csharp/Ansible.Basic.cs +15 -12
- ansible/module_utils/csharp/Ansible.Become.cs +1 -0
- ansible/module_utils/csharp/Ansible.Privilege.cs +2 -2
- ansible/module_utils/csharp/Ansible._Async.cs +517 -0
- ansible/module_utils/datatag.py +49 -0
- ansible/module_utils/distro/__init__.py +2 -2
- ansible/module_utils/facts/ansible_collector.py +4 -5
- ansible/module_utils/facts/collector.py +13 -14
- ansible/module_utils/facts/compat.py +4 -4
- ansible/module_utils/facts/default_collectors.py +1 -1
- ansible/module_utils/facts/hardware/aix.py +34 -0
- ansible/module_utils/facts/hardware/base.py +2 -2
- ansible/module_utils/facts/hardware/darwin.py +1 -3
- ansible/module_utils/facts/hardware/freebsd.py +2 -2
- ansible/module_utils/facts/hardware/linux.py +5 -5
- ansible/module_utils/facts/namespace.py +1 -1
- ansible/module_utils/facts/network/base.py +1 -1
- ansible/module_utils/facts/network/fc_wwn.py +1 -2
- ansible/module_utils/facts/network/iscsi.py +1 -2
- ansible/module_utils/facts/network/nvme.py +1 -2
- ansible/module_utils/facts/other/facter.py +2 -3
- ansible/module_utils/facts/other/ohai.py +2 -3
- ansible/module_utils/facts/sysctl.py +4 -6
- ansible/module_utils/facts/system/apparmor.py +1 -2
- ansible/module_utils/facts/system/caps.py +3 -3
- ansible/module_utils/facts/system/chroot.py +1 -2
- ansible/module_utils/facts/system/cmdline.py +1 -2
- ansible/module_utils/facts/system/date_time.py +5 -3
- ansible/module_utils/facts/system/distribution.py +27 -13
- ansible/module_utils/facts/system/dns.py +1 -1
- ansible/module_utils/facts/system/env.py +1 -2
- ansible/module_utils/facts/system/fips.py +7 -20
- ansible/module_utils/facts/system/loadavg.py +1 -2
- ansible/module_utils/facts/system/local.py +2 -3
- ansible/module_utils/facts/system/lsb.py +1 -2
- ansible/module_utils/facts/system/pkg_mgr.py +1 -2
- ansible/module_utils/facts/system/platform.py +1 -2
- ansible/module_utils/facts/system/python.py +1 -2
- ansible/module_utils/facts/system/selinux.py +1 -1
- ansible/module_utils/facts/system/service_mgr.py +1 -2
- ansible/module_utils/facts/system/ssh_pub_keys.py +1 -1
- ansible/module_utils/facts/system/systemd.py +1 -1
- ansible/module_utils/facts/system/user.py +1 -2
- ansible/module_utils/facts/utils.py +3 -3
- ansible/module_utils/facts/virtual/base.py +1 -1
- ansible/module_utils/facts/virtual/linux.py +3 -3
- ansible/module_utils/facts/virtual/sunos.py +3 -15
- ansible/module_utils/facts/virtual/sysctl.py +3 -16
- ansible/module_utils/json_utils.py +2 -2
- ansible/module_utils/parsing/convert_bool.py +7 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.AddType.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CamelConversion.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.CommandUtil.psm1 +1 -1
- ansible/module_utils/powershell/Ansible.ModuleUtils.WebRequest.psm1 +1 -1
- ansible/module_utils/service.py +21 -31
- ansible/module_utils/splitter.py +7 -7
- ansible/module_utils/testing.py +31 -0
- ansible/module_utils/urls.py +64 -35
- ansible/modules/add_host.py +4 -4
- ansible/modules/apt.py +69 -49
- ansible/modules/apt_key.py +19 -12
- ansible/modules/apt_repository.py +32 -51
- ansible/modules/assemble.py +16 -14
- ansible/modules/assert.py +4 -4
- ansible/modules/async_status.py +24 -24
- ansible/modules/async_wrapper.py +20 -25
- ansible/modules/blockinfile.py +6 -7
- ansible/modules/command.py +13 -20
- ansible/modules/copy.py +60 -147
- ansible/modules/cron.py +24 -21
- ansible/modules/deb822_repository.py +8 -9
- ansible/modules/debconf.py +5 -5
- ansible/modules/debug.py +4 -4
- ansible/modules/dnf.py +8 -8
- ansible/modules/dnf5.py +39 -13
- ansible/modules/dpkg_selections.py +4 -4
- ansible/modules/expect.py +13 -15
- ansible/modules/fail.py +4 -4
- ansible/modules/fetch.py +4 -4
- ansible/modules/file.py +184 -144
- ansible/modules/find.py +22 -20
- ansible/modules/gather_facts.py +3 -3
- ansible/modules/get_url.py +77 -54
- ansible/modules/getent.py +7 -9
- ansible/modules/git.py +38 -38
- ansible/modules/group.py +6 -6
- ansible/modules/group_by.py +4 -4
- ansible/modules/hostname.py +15 -32
- ansible/modules/import_playbook.py +6 -6
- ansible/modules/import_role.py +6 -6
- ansible/modules/import_tasks.py +6 -6
- ansible/modules/include_role.py +6 -6
- ansible/modules/include_tasks.py +6 -6
- ansible/modules/include_vars.py +6 -6
- ansible/modules/iptables.py +86 -73
- ansible/modules/known_hosts.py +22 -24
- ansible/modules/lineinfile.py +5 -5
- ansible/modules/meta.py +4 -4
- ansible/modules/mount_facts.py +2 -2
- ansible/modules/package.py +10 -4
- ansible/modules/package_facts.py +22 -10
- ansible/modules/pause.py +6 -6
- ansible/modules/ping.py +6 -6
- ansible/modules/pip.py +21 -26
- ansible/modules/raw.py +6 -6
- ansible/modules/reboot.py +6 -6
- ansible/modules/replace.py +10 -14
- ansible/modules/rpm_key.py +7 -8
- ansible/modules/script.py +4 -4
- ansible/modules/service.py +10 -17
- ansible/modules/service_facts.py +87 -10
- ansible/modules/set_fact.py +5 -5
- ansible/modules/set_stats.py +4 -4
- ansible/modules/setup.py +2 -2
- ansible/modules/shell.py +6 -6
- ansible/modules/slurp.py +16 -19
- ansible/modules/stat.py +15 -31
- ansible/modules/subversion.py +15 -15
- ansible/modules/systemd.py +7 -7
- ansible/modules/systemd_service.py +7 -7
- ansible/modules/sysvinit.py +9 -9
- ansible/modules/tempfile.py +5 -6
- ansible/modules/template.py +6 -6
- ansible/modules/unarchive.py +38 -17
- ansible/modules/uri.py +33 -26
- ansible/modules/user.py +45 -32
- ansible/modules/validate_argument_spec.py +10 -7
- ansible/modules/wait_for.py +70 -60
- ansible/modules/wait_for_connection.py +6 -6
- ansible/modules/yum_repository.py +10 -9
- ansible/parsing/ajson.py +17 -37
- ansible/parsing/dataloader.py +99 -54
- ansible/parsing/mod_args.py +62 -60
- ansible/parsing/plugin_docs.py +21 -86
- ansible/parsing/quoting.py +1 -1
- ansible/parsing/splitter.py +27 -12
- ansible/parsing/utils/addresses.py +24 -24
- ansible/parsing/utils/jsonify.py +5 -1
- ansible/parsing/utils/yaml.py +32 -61
- ansible/parsing/vault/__init__.py +327 -99
- ansible/parsing/yaml/__init__.py +0 -18
- ansible/parsing/yaml/dumper.py +6 -120
- ansible/parsing/yaml/loader.py +6 -39
- ansible/parsing/yaml/objects.py +43 -335
- ansible/playbook/__init__.py +1 -1
- ansible/playbook/attribute.py +8 -3
- ansible/playbook/base.py +187 -134
- ansible/playbook/block.py +26 -24
- ansible/playbook/collectionsearch.py +1 -15
- ansible/playbook/conditional.py +3 -77
- ansible/playbook/handler.py +8 -2
- ansible/playbook/helpers.py +41 -53
- ansible/playbook/included_file.py +32 -26
- ansible/playbook/loop_control.py +2 -2
- ansible/playbook/play.py +85 -44
- ansible/playbook/play_context.py +14 -17
- ansible/playbook/playbook_include.py +27 -62
- ansible/playbook/role/__init__.py +64 -49
- ansible/playbook/role/definition.py +15 -17
- ansible/playbook/role/include.py +2 -4
- ansible/playbook/role/metadata.py +10 -11
- ansible/playbook/role_include.py +3 -3
- ansible/playbook/taggable.py +28 -12
- ansible/playbook/task.py +192 -121
- ansible/playbook/task_include.py +5 -5
- ansible/plugins/__init__.py +58 -26
- ansible/plugins/action/__init__.py +188 -186
- ansible/plugins/action/add_host.py +2 -2
- ansible/plugins/action/assemble.py +11 -18
- ansible/plugins/action/assert.py +55 -67
- ansible/plugins/action/async_status.py +7 -2
- ansible/plugins/action/copy.py +14 -17
- ansible/plugins/action/debug.py +37 -31
- ansible/plugins/action/dnf.py +3 -4
- ansible/plugins/action/fail.py +1 -1
- ansible/plugins/action/fetch.py +7 -8
- ansible/plugins/action/gather_facts.py +13 -14
- ansible/plugins/action/group_by.py +1 -1
- ansible/plugins/action/include_vars.py +10 -11
- ansible/plugins/action/package.py +8 -14
- ansible/plugins/action/pause.py +2 -2
- ansible/plugins/action/script.py +27 -38
- ansible/plugins/action/service.py +9 -18
- ansible/plugins/action/set_fact.py +3 -12
- ansible/plugins/action/set_stats.py +3 -8
- ansible/plugins/action/template.py +47 -67
- ansible/plugins/action/unarchive.py +6 -16
- ansible/plugins/action/uri.py +9 -20
- ansible/plugins/action/validate_argument_spec.py +5 -5
- ansible/plugins/action/wait_for_connection.py +1 -1
- ansible/plugins/become/__init__.py +31 -8
- ansible/plugins/become/runas.py +71 -0
- ansible/plugins/become/su.py +13 -8
- ansible/plugins/become/sudo.py +19 -0
- ansible/plugins/cache/__init__.py +52 -63
- ansible/plugins/cache/base.py +8 -0
- ansible/plugins/cache/jsonfile.py +10 -16
- ansible/plugins/cache/memory.py +6 -12
- ansible/plugins/callback/__init__.py +294 -201
- ansible/plugins/callback/default.py +99 -95
- ansible/plugins/callback/junit.py +44 -43
- ansible/plugins/callback/minimal.py +28 -25
- ansible/plugins/callback/oneline.py +34 -21
- ansible/plugins/callback/tree.py +27 -16
- ansible/plugins/connection/__init__.py +47 -34
- ansible/plugins/connection/local.py +156 -60
- ansible/plugins/connection/paramiko_ssh.py +34 -24
- ansible/plugins/connection/psrp.py +76 -165
- ansible/plugins/connection/ssh.py +326 -86
- ansible/plugins/connection/winrm.py +62 -141
- ansible/plugins/doc_fragments/action_common_attributes.py +14 -14
- ansible/plugins/doc_fragments/action_core.py +6 -6
- ansible/plugins/doc_fragments/backup.py +2 -2
- ansible/plugins/doc_fragments/checksum_common.py +27 -0
- ansible/plugins/doc_fragments/constructed.py +8 -4
- ansible/plugins/doc_fragments/decrypt.py +2 -2
- ansible/plugins/doc_fragments/default_callback.py +2 -2
- ansible/plugins/doc_fragments/files.py +2 -2
- ansible/plugins/doc_fragments/inventory_cache.py +2 -2
- ansible/plugins/doc_fragments/result_format_callback.py +2 -2
- ansible/plugins/doc_fragments/return_common.py +2 -2
- ansible/plugins/doc_fragments/template_common.py +4 -4
- ansible/plugins/doc_fragments/url.py +17 -1
- ansible/plugins/doc_fragments/url_windows.py +2 -2
- ansible/plugins/doc_fragments/validate.py +2 -2
- ansible/plugins/doc_fragments/vars_plugin_staging.py +2 -2
- ansible/plugins/filter/__init__.py +6 -2
- ansible/plugins/filter/b64decode.yml +22 -0
- ansible/plugins/filter/b64encode.yml +22 -0
- ansible/plugins/filter/bool.yml +11 -4
- ansible/plugins/filter/core.py +245 -120
- ansible/plugins/filter/encryption.py +42 -34
- ansible/plugins/filter/flatten.yml +3 -2
- ansible/plugins/filter/human_to_bytes.yml +1 -1
- ansible/plugins/filter/mathstuff.py +30 -37
- ansible/plugins/filter/password_hash.yml +8 -0
- ansible/plugins/filter/pow.yml +1 -1
- ansible/plugins/filter/regex_search.yml +1 -4
- ansible/plugins/filter/root.yml +1 -1
- ansible/plugins/filter/split.yml +1 -1
- ansible/plugins/filter/strftime.yml +3 -3
- ansible/plugins/filter/to_nice_yaml.yml +0 -4
- ansible/plugins/filter/to_uuid.yml +1 -1
- ansible/plugins/filter/to_yaml.yml +0 -4
- ansible/plugins/filter/unvault.yml +1 -1
- ansible/plugins/filter/urls.py +1 -1
- ansible/plugins/filter/urlsplit.py +8 -9
- ansible/plugins/filter/vault.yml +14 -9
- ansible/plugins/filter/win_basename.yml +6 -1
- ansible/plugins/filter/win_dirname.yml +5 -0
- ansible/plugins/inventory/__init__.py +107 -86
- ansible/plugins/inventory/advanced_host_list.py +7 -5
- ansible/plugins/inventory/auto.py +11 -4
- ansible/plugins/inventory/constructed.py +21 -24
- ansible/plugins/inventory/generator.py +16 -11
- ansible/plugins/inventory/host_list.py +7 -5
- ansible/plugins/inventory/ini.py +78 -44
- ansible/plugins/inventory/script.py +190 -120
- ansible/plugins/inventory/toml.py +16 -126
- ansible/plugins/inventory/yaml.py +10 -8
- ansible/plugins/list.py +72 -19
- ansible/plugins/loader.py +383 -198
- ansible/plugins/lookup/__init__.py +21 -4
- ansible/plugins/lookup/config.py +21 -35
- ansible/plugins/lookup/csvfile.py +19 -73
- ansible/plugins/lookup/dict.py +1 -6
- ansible/plugins/lookup/env.py +12 -9
- ansible/plugins/lookup/file.py +5 -8
- ansible/plugins/lookup/first_found.py +87 -55
- ansible/plugins/lookup/indexed_items.py +1 -10
- ansible/plugins/lookup/ini.py +14 -13
- ansible/plugins/lookup/items.py +1 -1
- ansible/plugins/lookup/lines.py +8 -1
- ansible/plugins/lookup/list.py +1 -1
- ansible/plugins/lookup/nested.py +2 -18
- ansible/plugins/lookup/password.py +5 -5
- ansible/plugins/lookup/pipe.py +5 -7
- ansible/plugins/lookup/sequence.py +18 -8
- ansible/plugins/lookup/subelements.py +1 -4
- ansible/plugins/lookup/template.py +47 -36
- ansible/plugins/lookup/together.py +0 -12
- ansible/plugins/lookup/unvault.py +1 -5
- ansible/plugins/lookup/url.py +4 -10
- ansible/plugins/lookup/vars.py +16 -24
- ansible/plugins/shell/__init__.py +58 -4
- ansible/plugins/shell/cmd.py +2 -2
- ansible/plugins/shell/powershell.py +106 -31
- ansible/plugins/shell/sh.py +13 -7
- ansible/plugins/strategy/__init__.py +168 -193
- ansible/plugins/strategy/debug.py +2 -2
- ansible/plugins/strategy/free.py +16 -31
- ansible/plugins/strategy/host_pinned.py +2 -2
- ansible/plugins/strategy/linear.py +41 -41
- ansible/plugins/terminal/__init__.py +4 -4
- ansible/plugins/test/__init__.py +7 -2
- ansible/plugins/test/core.py +75 -35
- ansible/plugins/test/files.py +1 -1
- ansible/plugins/test/finished.yml +1 -1
- ansible/plugins/test/mathstuff.py +3 -3
- ansible/plugins/test/uri.py +5 -8
- ansible/plugins/vars/host_group_vars.py +7 -14
- ansible/release.py +2 -2
- ansible/template/__init__.py +353 -943
- ansible/utils/__init__.py +0 -18
- ansible/utils/collection_loader/__init__.py +54 -5
- ansible/utils/collection_loader/_collection_config.py +5 -6
- ansible/utils/collection_loader/_collection_finder.py +82 -96
- ansible/utils/collection_loader/_collection_meta.py +15 -8
- ansible/utils/display.py +485 -73
- ansible/utils/encrypt.py +27 -19
- ansible/utils/fqcn.py +2 -2
- ansible/utils/galaxy.py +2 -2
- ansible/utils/hashing.py +8 -10
- ansible/utils/helpers.py +2 -2
- ansible/utils/listify.py +10 -8
- ansible/utils/lock.py +2 -2
- ansible/utils/path.py +10 -12
- ansible/utils/plugin_docs.py +16 -14
- ansible/utils/py3compat.py +2 -7
- ansible/utils/sentinel.py +4 -62
- ansible/utils/singleton.py +2 -0
- ansible/utils/ssh_functions.py +6 -2
- ansible/utils/unsafe_proxy.py +23 -332
- ansible/utils/vars.py +55 -8
- ansible/utils/version.py +2 -2
- ansible/vars/clean.py +5 -5
- ansible/vars/hostvars.py +60 -90
- ansible/vars/manager.py +220 -285
- ansible/vars/plugins.py +4 -4
- ansible/vars/reserved.py +13 -12
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/METADATA +4 -3
- ansible_core-2.19.0.dist-info/RECORD +1097 -0
- ansible_core-2.19.0.dist-info/licenses/licenses/BSD-3-Clause.txt +28 -0
- ansible_test/_data/completion/docker.txt +7 -7
- ansible_test/_data/completion/remote.txt +6 -6
- ansible_test/_data/completion/windows.txt +1 -0
- ansible_test/_data/requirements/ansible.txt +2 -2
- ansible_test/_data/requirements/sanity.ansible-doc.txt +3 -3
- ansible_test/_data/requirements/sanity.changelog.txt +2 -2
- ansible_test/_data/requirements/sanity.import.plugin.txt +2 -2
- ansible_test/_data/requirements/sanity.pep8.txt +1 -1
- ansible_test/_data/requirements/sanity.pylint.txt +5 -5
- ansible_test/_data/requirements/sanity.validate-modules.txt +2 -2
- ansible_test/_data/requirements/sanity.yamllint.txt +1 -1
- ansible_test/_data/requirements/units.txt +1 -0
- ansible_test/_internal/__init__.py +6 -0
- ansible_test/_internal/ansible_util.py +3 -1
- ansible_test/_internal/become.py +1 -0
- ansible_test/_internal/bootstrap.py +1 -0
- ansible_test/_internal/cache.py +1 -0
- ansible_test/_internal/cgroup.py +1 -0
- ansible_test/_internal/ci/__init__.py +1 -0
- ansible_test/_internal/ci/azp.py +1 -0
- ansible_test/_internal/ci/local.py +1 -0
- ansible_test/_internal/classification/__init__.py +1 -0
- ansible_test/_internal/classification/common.py +1 -0
- ansible_test/_internal/classification/csharp.py +1 -0
- ansible_test/_internal/classification/powershell.py +1 -0
- ansible_test/_internal/classification/python.py +1 -0
- ansible_test/_internal/cli/__init__.py +1 -0
- ansible_test/_internal/cli/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/__init__.py +1 -0
- ansible_test/_internal/cli/argparsing/actions.py +1 -0
- ansible_test/_internal/cli/argparsing/argcompletion.py +1 -0
- ansible_test/_internal/cli/argparsing/parsers.py +1 -0
- ansible_test/_internal/cli/commands/__init__.py +11 -5
- ansible_test/_internal/cli/commands/coverage/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/cli/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/cli/commands/coverage/combine.py +1 -0
- ansible_test/_internal/cli/commands/coverage/erase.py +1 -0
- ansible_test/_internal/cli/commands/coverage/html.py +1 -0
- ansible_test/_internal/cli/commands/coverage/report.py +1 -0
- ansible_test/_internal/cli/commands/coverage/xml.py +1 -0
- ansible_test/_internal/cli/commands/env.py +1 -0
- ansible_test/_internal/cli/commands/integration/__init__.py +1 -0
- ansible_test/_internal/cli/commands/integration/network.py +1 -0
- ansible_test/_internal/cli/commands/integration/posix.py +1 -0
- ansible_test/_internal/cli/commands/integration/windows.py +1 -0
- ansible_test/_internal/cli/commands/sanity.py +9 -0
- ansible_test/_internal/cli/commands/shell.py +1 -0
- ansible_test/_internal/cli/commands/units.py +1 -0
- ansible_test/_internal/cli/compat.py +1 -0
- ansible_test/_internal/cli/completers.py +1 -0
- ansible_test/_internal/cli/converters.py +1 -0
- ansible_test/_internal/cli/environments.py +52 -5
- ansible_test/_internal/cli/epilog.py +1 -0
- ansible_test/_internal/cli/parsers/__init__.py +1 -0
- ansible_test/_internal/cli/parsers/base_argument_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/helpers.py +1 -0
- ansible_test/_internal/cli/parsers/host_config_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/key_value_parsers.py +1 -0
- ansible_test/_internal/cli/parsers/value_parsers.py +1 -0
- ansible_test/_internal/commands/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/__init__.py +3 -2
- ansible_test/_internal/commands/coverage/analyze/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/__init__.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/combine.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/expand.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/filter.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/generate.py +1 -0
- ansible_test/_internal/commands/coverage/analyze/targets/missing.py +1 -0
- ansible_test/_internal/commands/coverage/combine.py +2 -1
- ansible_test/_internal/commands/coverage/erase.py +1 -0
- ansible_test/_internal/commands/coverage/html.py +1 -0
- ansible_test/_internal/commands/coverage/report.py +1 -0
- ansible_test/_internal/commands/coverage/xml.py +1 -0
- ansible_test/_internal/commands/env/__init__.py +2 -0
- ansible_test/_internal/commands/integration/__init__.py +22 -5
- ansible_test/_internal/commands/integration/cloud/__init__.py +1 -0
- ansible_test/_internal/commands/integration/cloud/acme.py +2 -1
- ansible_test/_internal/commands/integration/cloud/aws.py +1 -0
- ansible_test/_internal/commands/integration/cloud/azure.py +1 -0
- ansible_test/_internal/commands/integration/cloud/cs.py +1 -0
- ansible_test/_internal/commands/integration/cloud/digitalocean.py +1 -0
- ansible_test/_internal/commands/integration/cloud/galaxy.py +3 -2
- ansible_test/_internal/commands/integration/cloud/hcloud.py +1 -0
- ansible_test/_internal/commands/integration/cloud/httptester.py +3 -2
- ansible_test/_internal/commands/integration/cloud/nios.py +2 -1
- ansible_test/_internal/commands/integration/cloud/opennebula.py +1 -0
- ansible_test/_internal/commands/integration/cloud/openshift.py +1 -0
- ansible_test/_internal/commands/integration/cloud/scaleway.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vcenter.py +1 -0
- ansible_test/_internal/commands/integration/cloud/vultr.py +1 -0
- ansible_test/_internal/commands/integration/coverage.py +8 -2
- ansible_test/_internal/commands/integration/filters.py +1 -0
- ansible_test/_internal/commands/integration/network.py +1 -0
- ansible_test/_internal/commands/integration/posix.py +1 -0
- ansible_test/_internal/commands/integration/windows.py +1 -0
- ansible_test/_internal/commands/sanity/__init__.py +19 -2
- ansible_test/_internal/commands/sanity/ansible_doc.py +1 -0
- ansible_test/_internal/commands/sanity/bin_symlinks.py +1 -0
- ansible_test/_internal/commands/sanity/compile.py +1 -0
- ansible_test/_internal/commands/sanity/ignores.py +1 -0
- ansible_test/_internal/commands/sanity/import.py +1 -0
- ansible_test/_internal/commands/sanity/integration_aliases.py +12 -0
- ansible_test/_internal/commands/sanity/pep8.py +1 -0
- ansible_test/_internal/commands/sanity/pslint.py +1 -0
- ansible_test/_internal/commands/sanity/pylint.py +25 -26
- ansible_test/_internal/commands/sanity/shellcheck.py +1 -0
- ansible_test/_internal/commands/sanity/validate_modules.py +1 -0
- ansible_test/_internal/commands/sanity/yamllint.py +1 -0
- ansible_test/_internal/commands/shell/__init__.py +44 -4
- ansible_test/_internal/commands/units/__init__.py +5 -1
- ansible_test/_internal/compat/__init__.py +1 -0
- ansible_test/_internal/compat/packaging.py +1 -0
- ansible_test/_internal/compat/yaml.py +1 -0
- ansible_test/_internal/completion.py +1 -0
- ansible_test/_internal/config.py +23 -13
- ansible_test/_internal/connections.py +1 -0
- ansible_test/_internal/constants.py +1 -0
- ansible_test/_internal/containers.py +1 -0
- ansible_test/_internal/content_config.py +1 -0
- ansible_test/_internal/core_ci.py +1 -0
- ansible_test/_internal/coverage_util.py +11 -10
- ansible_test/_internal/data.py +1 -0
- ansible_test/_internal/debugging.py +166 -0
- ansible_test/_internal/delegation.py +22 -13
- ansible_test/_internal/dev/__init__.py +1 -0
- ansible_test/_internal/dev/container_probe.py +1 -0
- ansible_test/_internal/diff.py +3 -2
- ansible_test/_internal/docker_util.py +2 -1
- ansible_test/_internal/encoding.py +1 -0
- ansible_test/_internal/executor.py +1 -0
- ansible_test/_internal/git.py +1 -0
- ansible_test/_internal/host_configs.py +1 -0
- ansible_test/_internal/host_profiles.py +260 -16
- ansible_test/_internal/http.py +1 -0
- ansible_test/_internal/init.py +1 -0
- ansible_test/_internal/inventory.py +39 -3
- ansible_test/_internal/io.py +1 -0
- ansible_test/_internal/metadata.py +95 -4
- ansible_test/_internal/payload.py +1 -0
- ansible_test/_internal/processes.py +80 -0
- ansible_test/_internal/provider/__init__.py +1 -0
- ansible_test/_internal/provider/layout/__init__.py +1 -0
- ansible_test/_internal/provider/layout/ansible.py +1 -0
- ansible_test/_internal/provider/layout/collection.py +1 -0
- ansible_test/_internal/provider/layout/unsupported.py +1 -0
- ansible_test/_internal/provider/source/__init__.py +1 -0
- ansible_test/_internal/provider/source/git.py +1 -0
- ansible_test/_internal/provider/source/installed.py +1 -0
- ansible_test/_internal/provider/source/unsupported.py +1 -0
- ansible_test/_internal/provider/source/unversioned.py +1 -0
- ansible_test/_internal/provisioning.py +11 -4
- ansible_test/_internal/pypi_proxy.py +6 -5
- ansible_test/_internal/python_requirements.py +28 -0
- ansible_test/_internal/ssh.py +2 -5
- ansible_test/_internal/target.py +9 -0
- ansible_test/_internal/test.py +3 -2
- ansible_test/_internal/thread.py +3 -1
- ansible_test/_internal/timeout.py +2 -1
- ansible_test/_internal/util.py +41 -12
- ansible_test/_internal/util_common.py +18 -5
- ansible_test/_internal/venv.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog/sphinx.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/changelog.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/empty-init.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/line-endings.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-assert.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-get-exception.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-illegal-filenames.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/no-smart-quotes.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/replace-urlopen.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py +28 -1
- ansible_test/_util/controller/sanity/code-smell/shebang.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/symlinks.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-argspec-type-path.py +1 -0
- ansible_test/_util/controller/sanity/code-smell/use-compat-six.py +1 -0
- ansible_test/_util/controller/sanity/integration-aliases/yaml_to_json.py +2 -1
- ansible_test/_util/controller/sanity/pep8/current-ignore.txt +4 -0
- ansible_test/_util/controller/sanity/pylint/config/ansible-test-target.cfg +8 -5
- ansible_test/_util/controller/sanity/pylint/config/ansible-test.cfg +8 -5
- ansible_test/_util/controller/sanity/pylint/config/code-smell.cfg +8 -5
- ansible_test/_util/controller/sanity/pylint/config/collection.cfg +4 -5
- ansible_test/_util/controller/sanity/pylint/config/default.cfg +8 -7
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_calls.py +541 -0
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated_comment.py +137 -0
- ansible_test/_util/controller/sanity/pylint/plugins/hide_unraisable.py +1 -0
- ansible_test/_util/controller/sanity/pylint/plugins/string_format.py +1 -8
- ansible_test/_util/controller/sanity/pylint/plugins/unwanted.py +1 -8
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/main.py +55 -28
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/module_args.py +12 -5
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/schema.py +13 -2
- ansible_test/_util/controller/sanity/validate-modules/validate_modules/utils.py +1 -0
- ansible_test/_util/controller/sanity/yamllint/yamllinter.py +35 -17
- ansible_test/_util/controller/tools/collection_detail.py +1 -0
- ansible_test/_util/controller/tools/yaml_to_json.py +2 -1
- ansible_test/_util/target/injector/python.py +8 -0
- ansible_test/_util/target/pytest/plugins/ansible_forked.py +6 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_collections.py +2 -1
- ansible_test/_util/target/pytest/plugins/ansible_pytest_coverage.py +1 -0
- ansible_test/_util/target/sanity/compile/compile.py +1 -0
- ansible_test/_util/target/sanity/import/importer.py +15 -16
- ansible_test/_util/target/setup/bootstrap.sh +9 -20
- ansible_test/_util/target/setup/probe_cgroups.py +1 -0
- ansible_test/_util/target/setup/quiet_pip.py +1 -0
- ansible_test/_util/target/setup/requirements.py +38 -36
- ansible_test/_util/target/tools/virtualenvcheck.py +2 -1
- ansible_test/_util/target/tools/yamlcheck.py +2 -1
- ansible/compat/selectors.py +0 -32
- ansible/errors/yaml_strings.py +0 -138
- ansible/executor/action_write_locks.py +0 -44
- ansible/executor/discovery/python_target.py +0 -47
- ansible/executor/powershell/module_powershell_wrapper.ps1 +0 -86
- ansible/executor/powershell/module_script_wrapper.ps1 +0 -22
- ansible/module_utils/compat/importlib.py +0 -26
- ansible/module_utils/compat/selectors.py +0 -32
- ansible/module_utils/pycompat24.py +0 -73
- ansible/parsing/yaml/constructor.py +0 -178
- ansible/template/native_helpers.py +0 -251
- ansible/template/template.py +0 -43
- ansible/template/vars.py +0 -77
- ansible/utils/native_jinja.py +0 -11
- ansible/vars/fact_cache.py +0 -71
- ansible_core-2.18.7.dist-info/RECORD +0 -992
- ansible_test/_util/controller/sanity/pylint/plugins/deprecated.py +0 -411
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/WHEEL +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.18.7.dist-info → ansible_core-2.19.0.dist-info}/top_level.txt +0 -0
|
@@ -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.
|
|
@@ -296,7 +332,9 @@ DOCUMENTATION = '''
|
|
|
296
332
|
version_added: '2.7'
|
|
297
333
|
sftp_batch_mode:
|
|
298
334
|
default: true
|
|
299
|
-
description:
|
|
335
|
+
description:
|
|
336
|
+
- When set to C(True), sftp will be run in batch mode, allowing detection of transfer errors.
|
|
337
|
+
- When set to C(False), sftp will not be run in batch mode, preventing detection of transfer errors.
|
|
300
338
|
env: [{name: ANSIBLE_SFTP_BATCH_MODE}]
|
|
301
339
|
ini:
|
|
302
340
|
- {key: sftp_batch_mode, section: ssh_connection}
|
|
@@ -357,41 +395,69 @@ DOCUMENTATION = '''
|
|
|
357
395
|
type: string
|
|
358
396
|
description:
|
|
359
397
|
- "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
398
|
env: [{name: ANSIBLE_PKCS11_PROVIDER}]
|
|
362
399
|
ini:
|
|
363
400
|
- {key: pkcs11_provider, section: ssh_connection}
|
|
364
401
|
vars:
|
|
365
402
|
- name: ansible_ssh_pkcs11_provider
|
|
366
|
-
|
|
403
|
+
verbosity:
|
|
404
|
+
version_added: '2.19'
|
|
405
|
+
default: 0
|
|
406
|
+
type: int
|
|
407
|
+
description:
|
|
408
|
+
- Requested verbosity level for the SSH CLI.
|
|
409
|
+
env: [{name: ANSIBLE_SSH_VERBOSITY}]
|
|
410
|
+
ini:
|
|
411
|
+
- {key: verbosity, section: ssh_connection}
|
|
412
|
+
vars:
|
|
413
|
+
- name: ansible_ssh_verbosity
|
|
414
|
+
"""
|
|
367
415
|
|
|
368
416
|
import collections.abc as c
|
|
417
|
+
import argparse
|
|
369
418
|
import errno
|
|
419
|
+
import contextlib
|
|
370
420
|
import fcntl
|
|
371
421
|
import hashlib
|
|
372
422
|
import io
|
|
423
|
+
import json
|
|
373
424
|
import os
|
|
425
|
+
import pathlib
|
|
374
426
|
import pty
|
|
375
427
|
import re
|
|
376
428
|
import selectors
|
|
377
429
|
import shlex
|
|
430
|
+
import shutil
|
|
378
431
|
import subprocess
|
|
432
|
+
import sys
|
|
433
|
+
import tempfile
|
|
379
434
|
import time
|
|
380
435
|
import typing as t
|
|
381
|
-
|
|
382
436
|
from functools import wraps
|
|
437
|
+
from multiprocessing.shared_memory import SharedMemory
|
|
438
|
+
|
|
439
|
+
from ansible import constants as C
|
|
383
440
|
from ansible.errors import (
|
|
384
441
|
AnsibleAuthenticationFailure,
|
|
385
442
|
AnsibleConnectionFailure,
|
|
386
443
|
AnsibleError,
|
|
387
444
|
AnsibleFileNotFound,
|
|
388
445
|
)
|
|
389
|
-
from ansible.module_utils.six import
|
|
446
|
+
from ansible.module_utils.six import text_type, binary_type
|
|
390
447
|
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
|
391
448
|
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
|
392
449
|
from ansible.plugins.shell.powershell import _replace_stderr_clixml
|
|
393
450
|
from ansible.utils.display import Display
|
|
394
451
|
from ansible.utils.path import unfrackpath, makedirs_safe
|
|
452
|
+
from ansible._internal._ssh import _ssh_agent
|
|
453
|
+
|
|
454
|
+
try:
|
|
455
|
+
from cryptography.hazmat.primitives import serialization
|
|
456
|
+
except ImportError:
|
|
457
|
+
HAS_CRYPTOGRAPHY = False
|
|
458
|
+
else:
|
|
459
|
+
HAS_CRYPTOGRAPHY = True
|
|
460
|
+
|
|
395
461
|
|
|
396
462
|
display = Display()
|
|
397
463
|
|
|
@@ -408,9 +474,14 @@ b_NOT_SSH_ERRORS = (b'Traceback (most recent call last):', # Python-2.6 when th
|
|
|
408
474
|
SSHPASS_AVAILABLE = None
|
|
409
475
|
SSH_DEBUG = re.compile(r'^debug\d+: .*')
|
|
410
476
|
|
|
477
|
+
_HAS_RESOURCE_TRACK = sys.version_info[:2] >= (3, 13)
|
|
478
|
+
|
|
479
|
+
PKCS11_DEFAULT_PROMPT = 'Enter PIN for '
|
|
480
|
+
SSH_ASKPASS_DEFAULT_PROMPT = 'assword'
|
|
481
|
+
|
|
411
482
|
|
|
412
483
|
class AnsibleControlPersistBrokenPipeError(AnsibleError):
|
|
413
|
-
|
|
484
|
+
""" ControlPersist broken pipe """
|
|
414
485
|
pass
|
|
415
486
|
|
|
416
487
|
|
|
@@ -450,6 +521,7 @@ def _handle_error(
|
|
|
450
521
|
'Upgrade sshpass to use sshpass_prompt, or otherwise switch to ssh keys.'
|
|
451
522
|
raise AnsibleError('{0} {1}'.format(msg, details))
|
|
452
523
|
msg = '{0} {1}'.format(msg, details)
|
|
524
|
+
raise AnsibleConnectionFailure(msg)
|
|
453
525
|
|
|
454
526
|
if return_tuple[0] == 255:
|
|
455
527
|
SSH_ERROR = True
|
|
@@ -496,9 +568,10 @@ def _ssh_retry(
|
|
|
496
568
|
remaining_tries = int(self.get_option('reconnection_retries')) + 1
|
|
497
569
|
cmd_summary = u"%s..." % to_text(args[0])
|
|
498
570
|
conn_password = self.get_option('password') or self._play_context.password
|
|
571
|
+
is_sshpass = self.get_option('password_mechanism') == 'sshpass'
|
|
499
572
|
for attempt in range(remaining_tries):
|
|
500
573
|
cmd = t.cast(list[bytes], args[0])
|
|
501
|
-
if attempt != 0 and conn_password and isinstance(cmd, list):
|
|
574
|
+
if attempt != 0 and is_sshpass and conn_password and isinstance(cmd, list):
|
|
502
575
|
# If this is a retry, the fd/pipe for sshpass is closed, and we need a new one
|
|
503
576
|
self.sshpass_pipe = os.pipe()
|
|
504
577
|
cmd[1] = b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict')
|
|
@@ -517,7 +590,7 @@ def _ssh_retry(
|
|
|
517
590
|
except (AnsibleControlPersistBrokenPipeError):
|
|
518
591
|
# Retry one more time because of the ControlPersist broken pipe (see #16731)
|
|
519
592
|
cmd = t.cast(list[bytes], args[0])
|
|
520
|
-
if conn_password and isinstance(cmd, list):
|
|
593
|
+
if is_sshpass and conn_password and isinstance(cmd, list):
|
|
521
594
|
# This is a retry, so the fd/pipe for sshpass is closed, and we need a new one
|
|
522
595
|
self.sshpass_pipe = os.pipe()
|
|
523
596
|
cmd[1] = b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict')
|
|
@@ -558,8 +631,26 @@ def _ssh_retry(
|
|
|
558
631
|
return wrapped
|
|
559
632
|
|
|
560
633
|
|
|
634
|
+
def _clean_shm(func):
|
|
635
|
+
def inner(self, *args, **kwargs):
|
|
636
|
+
try:
|
|
637
|
+
ret = func(self, *args, **kwargs)
|
|
638
|
+
finally:
|
|
639
|
+
if self.shm:
|
|
640
|
+
self.shm.close()
|
|
641
|
+
with contextlib.suppress(FileNotFoundError):
|
|
642
|
+
self.shm.unlink()
|
|
643
|
+
if not _HAS_RESOURCE_TRACK:
|
|
644
|
+
# deprecated: description='unneeded due to track argument for SharedMemory' python_version='3.12'
|
|
645
|
+
# There is a resource tracking issue where the resource is deleted, but tracking still has a record
|
|
646
|
+
# This will effectively overwrite the record and remove it
|
|
647
|
+
SharedMemory(name=self.shm.name, create=True, size=1).unlink()
|
|
648
|
+
return ret
|
|
649
|
+
return inner
|
|
650
|
+
|
|
651
|
+
|
|
561
652
|
class Connection(ConnectionBase):
|
|
562
|
-
|
|
653
|
+
""" ssh based connections """
|
|
563
654
|
|
|
564
655
|
transport = 'ssh'
|
|
565
656
|
has_pipelining = True
|
|
@@ -573,6 +664,8 @@ class Connection(ConnectionBase):
|
|
|
573
664
|
self.user = self._play_context.remote_user
|
|
574
665
|
self.control_path: str | None = None
|
|
575
666
|
self.control_path_dir: str | None = None
|
|
667
|
+
self.shm: SharedMemory | None = None
|
|
668
|
+
self.sshpass_pipe: tuple[int, int] | None = None
|
|
576
669
|
|
|
577
670
|
# Windows operates differently from a POSIX connection/shell plugin,
|
|
578
671
|
# we need to set various properties to ensure SSH on Windows continues
|
|
@@ -583,6 +676,13 @@ class Connection(ConnectionBase):
|
|
|
583
676
|
self.module_implementation_preferences = ('.ps1', '.exe', '')
|
|
584
677
|
self.allow_executable = False
|
|
585
678
|
|
|
679
|
+
# parser to discover 'passed options', used later on for pipelining resolution
|
|
680
|
+
self._tty_parser = argparse.ArgumentParser()
|
|
681
|
+
self._tty_parser.add_argument('-t', action='count')
|
|
682
|
+
self._tty_parser.add_argument('-o', action='append')
|
|
683
|
+
|
|
684
|
+
self._populated_agent: pathlib.Path | None = None
|
|
685
|
+
|
|
586
686
|
# The connection is created by running ssh/scp/sftp from the exec_command,
|
|
587
687
|
# put_file, and fetch_file methods, so we don't need to do any connection
|
|
588
688
|
# management here.
|
|
@@ -598,7 +698,7 @@ class Connection(ConnectionBase):
|
|
|
598
698
|
connection: ConnectionBase | None = None,
|
|
599
699
|
pid: int | None = None,
|
|
600
700
|
) -> str:
|
|
601
|
-
|
|
701
|
+
"""Make a hash for the controlpath based on con attributes"""
|
|
602
702
|
pstring = '%s-%s-%s' % (host, port, user)
|
|
603
703
|
if connection:
|
|
604
704
|
pstring += '-%s' % connection
|
|
@@ -614,28 +714,21 @@ class Connection(ConnectionBase):
|
|
|
614
714
|
def _sshpass_available() -> bool:
|
|
615
715
|
global SSHPASS_AVAILABLE
|
|
616
716
|
|
|
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.
|
|
717
|
+
# We test once if sshpass is available, and remember the result.
|
|
620
718
|
|
|
621
719
|
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
|
|
720
|
+
SSHPASS_AVAILABLE = shutil.which('sshpass') is not None
|
|
628
721
|
|
|
629
722
|
return SSHPASS_AVAILABLE
|
|
630
723
|
|
|
631
724
|
@staticmethod
|
|
632
725
|
def _persistence_controls(b_command: list[bytes]) -> tuple[bool, bool]:
|
|
633
|
-
|
|
726
|
+
"""
|
|
634
727
|
Takes a command array and scans it for ControlPersist and ControlPath
|
|
635
728
|
settings and returns two booleans indicating whether either was found.
|
|
636
729
|
This could be smarter, e.g. returning false if ControlPersist is 'no',
|
|
637
730
|
but for now we do it simple way.
|
|
638
|
-
|
|
731
|
+
"""
|
|
639
732
|
|
|
640
733
|
controlpersist = False
|
|
641
734
|
controlpath = False
|
|
@@ -664,8 +757,54 @@ class Connection(ConnectionBase):
|
|
|
664
757
|
display.vvvvv(u'SSH: %s: (%s)' % (explanation, ')('.join(to_text(a) for a in b_args)), host=self.host)
|
|
665
758
|
b_command += b_args
|
|
666
759
|
|
|
760
|
+
def _populate_agent(self) -> pathlib.Path:
|
|
761
|
+
"""Adds configured private key identity to the SSH agent. Returns a path to a file containing the public key."""
|
|
762
|
+
if self._populated_agent:
|
|
763
|
+
return self._populated_agent
|
|
764
|
+
|
|
765
|
+
if (auth_sock := C.config.get_config_value('SSH_AGENT')) == 'none':
|
|
766
|
+
raise AnsibleError('Cannot utilize private_key with SSH_AGENT disabled')
|
|
767
|
+
|
|
768
|
+
key_data = self.get_option('private_key')
|
|
769
|
+
passphrase = self.get_option('private_key_passphrase')
|
|
770
|
+
|
|
771
|
+
private_key, public_key, fingerprint = _ssh_agent.key_data_into_crypto_objects(
|
|
772
|
+
to_bytes(key_data),
|
|
773
|
+
to_bytes(passphrase) if passphrase else None,
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
with _ssh_agent.SshAgentClient(auth_sock) as client:
|
|
777
|
+
if public_key not in client:
|
|
778
|
+
display.vvv(f'SSH: SSH_AGENT adding {fingerprint} to agent', host=self.host)
|
|
779
|
+
client.add(
|
|
780
|
+
private_key,
|
|
781
|
+
f'[added by ansible: PID={os.getpid()}, UID={os.getuid()}, EUID={os.geteuid()}, TIME={time.time()}]',
|
|
782
|
+
C.config.get_config_value('SSH_AGENT_KEY_LIFETIME'),
|
|
783
|
+
)
|
|
784
|
+
else:
|
|
785
|
+
display.vvv(f'SSH: SSH_AGENT {fingerprint} exists in agent', host=self.host)
|
|
786
|
+
# Write the public key to disk, to be provided as IdentityFile.
|
|
787
|
+
# This allows ssh to pick an explicit key in the agent to use,
|
|
788
|
+
# preventing ssh from attempting all keys in the agent.
|
|
789
|
+
pubkey_path = self._populated_agent = pathlib.Path(C.DEFAULT_LOCAL_TMP).joinpath(
|
|
790
|
+
fingerprint.replace('/', '-') + '.pub'
|
|
791
|
+
)
|
|
792
|
+
if os.path.exists(pubkey_path):
|
|
793
|
+
return pubkey_path
|
|
794
|
+
|
|
795
|
+
with tempfile.NamedTemporaryFile(dir=C.DEFAULT_LOCAL_TMP, delete=False) as f:
|
|
796
|
+
f.write(public_key.public_bytes(
|
|
797
|
+
encoding=serialization.Encoding.OpenSSH,
|
|
798
|
+
format=serialization.PublicFormat.OpenSSH
|
|
799
|
+
))
|
|
800
|
+
# move atomically to prevent race conditions, silently succeeds if the target exists
|
|
801
|
+
os.rename(f.name, pubkey_path)
|
|
802
|
+
os.chmod(pubkey_path, mode=0o400)
|
|
803
|
+
|
|
804
|
+
return self._populated_agent
|
|
805
|
+
|
|
667
806
|
def _build_command(self, binary: str, subsystem: str, *other_args: bytes | str) -> list[bytes]:
|
|
668
|
-
|
|
807
|
+
"""
|
|
669
808
|
Takes a executable (ssh, scp, sftp or wrapper) and optional extra arguments and returns the remote command
|
|
670
809
|
wrapped in local ssh shell commands and ready for execution.
|
|
671
810
|
|
|
@@ -673,21 +812,22 @@ class Connection(ConnectionBase):
|
|
|
673
812
|
:arg subsystem: type of executable provided, ssh/sftp/scp, needed because wrappers for ssh might have diff names.
|
|
674
813
|
:arg other_args: dict of, value pairs passed as arguments to the ssh binary
|
|
675
814
|
|
|
676
|
-
|
|
815
|
+
"""
|
|
677
816
|
|
|
678
817
|
b_command = []
|
|
679
818
|
conn_password = self.get_option('password') or self._play_context.password
|
|
819
|
+
pkcs11_provider = self.get_option("pkcs11_provider")
|
|
820
|
+
password_mechanism = self.get_option('password_mechanism')
|
|
680
821
|
|
|
681
822
|
#
|
|
682
823
|
# First, the command to invoke
|
|
683
824
|
#
|
|
684
825
|
|
|
685
|
-
# If we want to use password authentication, we have to set up a pipe to
|
|
826
|
+
# If we want to use sshpass for password authentication, we have to set up a pipe to
|
|
686
827
|
# write the password to sshpass.
|
|
687
|
-
|
|
688
|
-
if conn_password or pkcs11_provider:
|
|
828
|
+
if password_mechanism == 'sshpass' and (conn_password or pkcs11_provider):
|
|
689
829
|
if not self._sshpass_available():
|
|
690
|
-
raise AnsibleError("to use the
|
|
830
|
+
raise AnsibleError("to use the password_mechanism=sshpass, you must install the sshpass program")
|
|
691
831
|
if not conn_password and pkcs11_provider:
|
|
692
832
|
raise AnsibleError("to use pkcs11_provider you must specify a password/pin")
|
|
693
833
|
|
|
@@ -697,7 +837,7 @@ class Connection(ConnectionBase):
|
|
|
697
837
|
password_prompt = self.get_option('sshpass_prompt')
|
|
698
838
|
if not password_prompt and pkcs11_provider:
|
|
699
839
|
# Set default password prompt for pkcs11_provider to make it clear its a PIN
|
|
700
|
-
password_prompt =
|
|
840
|
+
password_prompt = PKCS11_DEFAULT_PROMPT
|
|
701
841
|
|
|
702
842
|
if password_prompt:
|
|
703
843
|
b_command += [b'-P', to_bytes(password_prompt, errors='surrogate_or_strict')]
|
|
@@ -720,16 +860,16 @@ class Connection(ConnectionBase):
|
|
|
720
860
|
# sftp batch mode allows us to correctly catch failed transfers, but can
|
|
721
861
|
# be disabled if the client side doesn't support the option. However,
|
|
722
862
|
# sftp batch mode does not prompt for passwords so it must be disabled
|
|
723
|
-
# if not using controlpersist and using
|
|
863
|
+
# if not using controlpersist and using password auth
|
|
724
864
|
b_args: t.Iterable[bytes]
|
|
725
865
|
if subsystem == 'sftp' and self.get_option('sftp_batch_mode'):
|
|
726
866
|
if conn_password:
|
|
727
867
|
b_args = [b'-o', b'BatchMode=no']
|
|
728
|
-
self._add_args(b_command, b_args, u'disable batch mode for
|
|
868
|
+
self._add_args(b_command, b_args, u'disable batch mode for password auth')
|
|
729
869
|
b_command += [b'-b', b'-']
|
|
730
870
|
|
|
731
|
-
if
|
|
732
|
-
b_command.append(b'-' + (b'v' *
|
|
871
|
+
if (verbosity := self.get_option('verbosity')) > 0:
|
|
872
|
+
b_command.append(b'-' + (b'v' * verbosity))
|
|
733
873
|
|
|
734
874
|
# Next, we add ssh_args
|
|
735
875
|
ssh_args = self.get_option('ssh_args')
|
|
@@ -748,8 +888,14 @@ class Connection(ConnectionBase):
|
|
|
748
888
|
b_args = (b"-o", b"Port=" + to_bytes(self.port, nonstring='simplerepr', errors='surrogate_or_strict'))
|
|
749
889
|
self._add_args(b_command, b_args, u"ANSIBLE_REMOTE_PORT/remote_port/ansible_port set")
|
|
750
890
|
|
|
751
|
-
|
|
752
|
-
|
|
891
|
+
if self.get_option('private_key'):
|
|
892
|
+
try:
|
|
893
|
+
key = self._populate_agent()
|
|
894
|
+
except Exception as e:
|
|
895
|
+
raise AnsibleAuthenticationFailure('Failed to add configured private key into ssh-agent.') from e
|
|
896
|
+
b_args = (b'-o', b'IdentitiesOnly=yes', b'-o', to_bytes(f'IdentityFile="{key}"', errors='surrogate_or_strict'))
|
|
897
|
+
self._add_args(b_command, b_args, "ANSIBLE_PRIVATE_KEY/private_key set")
|
|
898
|
+
elif key := self.get_option('private_key_file'):
|
|
753
899
|
b_args = (b"-o", b'IdentityFile="' + to_bytes(os.path.expanduser(key), errors='surrogate_or_strict') + b'"')
|
|
754
900
|
self._add_args(b_command, b_args, u"ANSIBLE_PRIVATE_KEY_FILE/private_key_file/ansible_ssh_private_key_file set")
|
|
755
901
|
|
|
@@ -815,6 +961,13 @@ class Connection(ConnectionBase):
|
|
|
815
961
|
b_args = (b"-o", b'ControlPath="%s"' % to_bytes(self.control_path % dict(directory=cpdir), errors='surrogate_or_strict'))
|
|
816
962
|
self._add_args(b_command, b_args, u"found only ControlPersist; added ControlPath")
|
|
817
963
|
|
|
964
|
+
if password_mechanism == "ssh_askpass":
|
|
965
|
+
self._add_args(
|
|
966
|
+
b_command,
|
|
967
|
+
(b"-o", b"NumberOfPasswordPrompts=1"),
|
|
968
|
+
"Restrict number of password prompts in case incorrect password is provided.",
|
|
969
|
+
)
|
|
970
|
+
|
|
818
971
|
# Finally, we add any caller-supplied extras.
|
|
819
972
|
if other_args:
|
|
820
973
|
b_command += [to_bytes(a) for a in other_args]
|
|
@@ -822,27 +975,24 @@ class Connection(ConnectionBase):
|
|
|
822
975
|
return b_command
|
|
823
976
|
|
|
824
977
|
def _send_initial_data(self, fh: io.IOBase, in_data: bytes, ssh_process: subprocess.Popen) -> None:
|
|
825
|
-
|
|
978
|
+
"""
|
|
826
979
|
Writes initial data to the stdin filehandle of the subprocess and closes
|
|
827
980
|
it. (The handle must be closed; otherwise, for example, "sftp -b -" will
|
|
828
981
|
just hang forever waiting for more commands.)
|
|
829
|
-
|
|
982
|
+
"""
|
|
830
983
|
|
|
831
984
|
display.debug(u'Sending initial data')
|
|
832
985
|
|
|
833
986
|
try:
|
|
834
987
|
fh.write(to_bytes(in_data))
|
|
835
988
|
fh.close()
|
|
836
|
-
except
|
|
989
|
+
except OSError as ex:
|
|
837
990
|
# The ssh connection may have already terminated at this point, with a more useful error
|
|
838
991
|
# Only raise AnsibleConnectionFailure if the ssh process is still alive
|
|
839
992
|
time.sleep(0.001)
|
|
840
993
|
ssh_process.poll()
|
|
841
994
|
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
|
-
)
|
|
995
|
+
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
996
|
|
|
847
997
|
display.debug(u'Sent initial data (%d bytes)' % len(in_data))
|
|
848
998
|
|
|
@@ -852,20 +1002,20 @@ class Connection(ConnectionBase):
|
|
|
852
1002
|
""" Terminate a process, ignoring errors """
|
|
853
1003
|
try:
|
|
854
1004
|
p.terminate()
|
|
855
|
-
except
|
|
1005
|
+
except OSError:
|
|
856
1006
|
pass
|
|
857
1007
|
|
|
858
1008
|
# This is separate from _run() because we need to do the same thing for stdout
|
|
859
1009
|
# and stderr.
|
|
860
1010
|
def _examine_output(self, source: str, state: str, b_chunk: bytes, sudoable: bool) -> tuple[bytes, bytes]:
|
|
861
|
-
|
|
1011
|
+
"""
|
|
862
1012
|
Takes a string, extracts complete lines from it, tests to see if they
|
|
863
1013
|
are a prompt, error message, etc., and sets appropriate flags in self.
|
|
864
1014
|
Prompt and success lines are removed.
|
|
865
1015
|
|
|
866
1016
|
Returns the processed (i.e. possibly-edited) output and the unprocessed
|
|
867
1017
|
remainder (to be processed with the next chunk) as strings.
|
|
868
|
-
|
|
1018
|
+
"""
|
|
869
1019
|
|
|
870
1020
|
output = []
|
|
871
1021
|
for b_line in b_chunk.splitlines(True):
|
|
@@ -906,15 +1056,69 @@ class Connection(ConnectionBase):
|
|
|
906
1056
|
|
|
907
1057
|
return b''.join(output), remainder
|
|
908
1058
|
|
|
1059
|
+
def _init_shm(self) -> dict[str, t.Any]:
|
|
1060
|
+
env = os.environ.copy()
|
|
1061
|
+
popen_kwargs: dict[str, t.Any] = {}
|
|
1062
|
+
|
|
1063
|
+
if self.get_option('password_mechanism') != 'ssh_askpass':
|
|
1064
|
+
return popen_kwargs
|
|
1065
|
+
|
|
1066
|
+
conn_password = self.get_option('password') or self._play_context.password
|
|
1067
|
+
pkcs11_provider = self.get_option("pkcs11_provider")
|
|
1068
|
+
if not conn_password and pkcs11_provider:
|
|
1069
|
+
raise AnsibleError("to use pkcs11_provider you must specify a password/pin")
|
|
1070
|
+
|
|
1071
|
+
if not conn_password:
|
|
1072
|
+
return popen_kwargs
|
|
1073
|
+
|
|
1074
|
+
kwargs = {}
|
|
1075
|
+
if _HAS_RESOURCE_TRACK:
|
|
1076
|
+
# deprecated: description='track argument for SharedMemory always available' python_version='3.12'
|
|
1077
|
+
kwargs['track'] = False
|
|
1078
|
+
self.shm = shm = SharedMemory(create=True, size=16384, **kwargs) # type: ignore[arg-type]
|
|
1079
|
+
|
|
1080
|
+
sshpass_prompt = self.get_option('sshpass_prompt')
|
|
1081
|
+
if not sshpass_prompt and pkcs11_provider:
|
|
1082
|
+
sshpass_prompt = PKCS11_DEFAULT_PROMPT
|
|
1083
|
+
elif not sshpass_prompt:
|
|
1084
|
+
sshpass_prompt = SSH_ASKPASS_DEFAULT_PROMPT
|
|
1085
|
+
|
|
1086
|
+
data = json.dumps({
|
|
1087
|
+
'password': conn_password,
|
|
1088
|
+
'prompt': sshpass_prompt,
|
|
1089
|
+
}).encode('utf-8')
|
|
1090
|
+
shm.buf[:len(data)] = bytearray(data)
|
|
1091
|
+
shm.close()
|
|
1092
|
+
|
|
1093
|
+
env['_ANSIBLE_SSH_ASKPASS_SHM'] = str(self.shm.name)
|
|
1094
|
+
adhoc = pathlib.Path(sys.argv[0]).with_name('ansible')
|
|
1095
|
+
env['SSH_ASKPASS'] = str(adhoc) if adhoc.is_file() else 'ansible'
|
|
1096
|
+
|
|
1097
|
+
# SSH_ASKPASS_REQUIRE was added in openssh 8.4, prior to 8.4 there must be no tty, and DISPLAY must be set
|
|
1098
|
+
env['SSH_ASKPASS_REQUIRE'] = 'force'
|
|
1099
|
+
if not env.get('DISPLAY'):
|
|
1100
|
+
# If the user has DISPLAY set, assume it is there for a reason
|
|
1101
|
+
env['DISPLAY'] = '-'
|
|
1102
|
+
|
|
1103
|
+
popen_kwargs['env'] = env
|
|
1104
|
+
# start_new_session runs setsid which detaches the tty to support the use of ASKPASS prior to openssh 8.4
|
|
1105
|
+
popen_kwargs['start_new_session'] = True
|
|
1106
|
+
|
|
1107
|
+
return popen_kwargs
|
|
1108
|
+
|
|
1109
|
+
@_clean_shm
|
|
909
1110
|
def _bare_run(self, cmd: list[bytes], in_data: bytes | None, sudoable: bool = True, checkrc: bool = True) -> tuple[int, bytes, bytes]:
|
|
910
|
-
|
|
1111
|
+
"""
|
|
911
1112
|
Starts the command and communicates with it until it ends.
|
|
912
|
-
|
|
1113
|
+
"""
|
|
913
1114
|
|
|
914
1115
|
# We don't use _shell.quote as this is run on the controller and independent from the shell plugin chosen
|
|
915
1116
|
display_cmd = u' '.join(shlex.quote(to_text(c)) for c in cmd)
|
|
916
1117
|
display.vvv(u'SSH: EXEC {0}'.format(display_cmd), host=self.host)
|
|
917
1118
|
|
|
1119
|
+
conn_password = self.get_option('password') or self._play_context.password
|
|
1120
|
+
password_mechanism = self.get_option('password_mechanism')
|
|
1121
|
+
|
|
918
1122
|
# Start the given command. If we don't need to pipeline data, we can try
|
|
919
1123
|
# to use a pseudo-tty (ssh will have been invoked with -tt). If we are
|
|
920
1124
|
# pipelining data, or can't create a pty, we fall back to using plain
|
|
@@ -927,39 +1131,30 @@ class Connection(ConnectionBase):
|
|
|
927
1131
|
else:
|
|
928
1132
|
cmd = list(map(to_bytes, cmd))
|
|
929
1133
|
|
|
930
|
-
|
|
1134
|
+
popen_kwargs = self._init_shm()
|
|
1135
|
+
|
|
1136
|
+
if self.sshpass_pipe:
|
|
1137
|
+
popen_kwargs['pass_fds'] = self.sshpass_pipe
|
|
931
1138
|
|
|
932
1139
|
if not in_data:
|
|
933
1140
|
try:
|
|
934
1141
|
# Make sure stdin is a proper pty to avoid tcgetattr errors
|
|
935
1142
|
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)
|
|
1143
|
+
p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **popen_kwargs)
|
|
941
1144
|
stdin = os.fdopen(master, 'wb', 0)
|
|
942
1145
|
os.close(slave)
|
|
943
|
-
except
|
|
1146
|
+
except OSError:
|
|
944
1147
|
p = None
|
|
945
1148
|
|
|
946
1149
|
if not p:
|
|
947
1150
|
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)
|
|
1151
|
+
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
1152
|
+
stderr=subprocess.PIPE, **popen_kwargs)
|
|
955
1153
|
stdin = p.stdin # type: ignore[assignment] # stdin will be set and not None due to the calls above
|
|
956
|
-
except
|
|
957
|
-
raise AnsibleError('Unable to execute ssh command line on a controller
|
|
1154
|
+
except OSError as ex:
|
|
1155
|
+
raise AnsibleError('Unable to execute ssh command line on a controller.') from ex
|
|
958
1156
|
|
|
959
|
-
|
|
960
|
-
# the pipe we opened in _build_command.
|
|
961
|
-
|
|
962
|
-
if conn_password:
|
|
1157
|
+
if password_mechanism == 'sshpass' and conn_password:
|
|
963
1158
|
os.close(self.sshpass_pipe[0])
|
|
964
1159
|
try:
|
|
965
1160
|
os.write(self.sshpass_pipe[1], to_bytes(conn_password) + b'\n')
|
|
@@ -983,7 +1178,7 @@ class Connection(ConnectionBase):
|
|
|
983
1178
|
|
|
984
1179
|
# Are we requesting privilege escalation? Right now, we may be invoked
|
|
985
1180
|
# to execute sftp/scp with sudoable=True, but we can request escalation
|
|
986
|
-
# only when using ssh. Otherwise we can send initial data
|
|
1181
|
+
# only when using ssh. Otherwise, we can send initial data straight away.
|
|
987
1182
|
|
|
988
1183
|
state = states.index('ready_to_send')
|
|
989
1184
|
if to_bytes(self.get_option('ssh_executable')) in cmd and sudoable:
|
|
@@ -1047,7 +1242,9 @@ class Connection(ConnectionBase):
|
|
|
1047
1242
|
if poll is not None:
|
|
1048
1243
|
break
|
|
1049
1244
|
self._terminate_process(p)
|
|
1050
|
-
raise
|
|
1245
|
+
raise AnsibleConnectionFailure('Timeout (%ds) waiting for privilege escalation prompt: %s' % (timeout, to_native(b_stdout)))
|
|
1246
|
+
|
|
1247
|
+
display.vvvvv(f'SSH: Timeout ({timeout}s) waiting for the output', host=self.host)
|
|
1051
1248
|
|
|
1052
1249
|
# Read whatever output is available on stdout and stderr, and stop
|
|
1053
1250
|
# listening to the pipe if it's been closed.
|
|
@@ -1117,23 +1314,23 @@ class Connection(ConnectionBase):
|
|
|
1117
1314
|
|
|
1118
1315
|
if states[state] == 'awaiting_escalation':
|
|
1119
1316
|
if self._flags['become_success']:
|
|
1120
|
-
display.vvv(u'Escalation succeeded')
|
|
1317
|
+
display.vvv(u'Escalation succeeded', host=self.host)
|
|
1121
1318
|
self._flags['become_success'] = False
|
|
1122
1319
|
state += 1
|
|
1123
1320
|
elif self._flags['become_error']:
|
|
1124
|
-
display.vvv(u'Escalation failed')
|
|
1321
|
+
display.vvv(u'Escalation failed', host=self.host)
|
|
1125
1322
|
self._terminate_process(p)
|
|
1126
1323
|
self._flags['become_error'] = False
|
|
1127
1324
|
raise AnsibleError('Incorrect %s password' % self.become.name)
|
|
1128
1325
|
elif self._flags['become_nopasswd_error']:
|
|
1129
|
-
display.vvv(u'Escalation requires password')
|
|
1326
|
+
display.vvv(u'Escalation requires password', host=self.host)
|
|
1130
1327
|
self._terminate_process(p)
|
|
1131
1328
|
self._flags['become_nopasswd_error'] = False
|
|
1132
1329
|
raise AnsibleError('Missing %s password' % self.become.name)
|
|
1133
1330
|
elif self._flags['become_prompt']:
|
|
1134
1331
|
# This shouldn't happen, because we should see the "Sorry,
|
|
1135
1332
|
# try again" message first.
|
|
1136
|
-
display.vvv(u'Escalation prompt repeated')
|
|
1333
|
+
display.vvv(u'Escalation prompt repeated', host=self.host)
|
|
1137
1334
|
self._terminate_process(p)
|
|
1138
1335
|
self._flags['become_prompt'] = False
|
|
1139
1336
|
raise AnsibleError('Incorrect %s password' % self.become.name)
|
|
@@ -1176,10 +1373,15 @@ class Connection(ConnectionBase):
|
|
|
1176
1373
|
p.stdout.close()
|
|
1177
1374
|
p.stderr.close()
|
|
1178
1375
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1376
|
+
conn_password = self.get_option('password') or self._play_context.password
|
|
1377
|
+
hostkey_fail = any((
|
|
1378
|
+
(cmd[0] == b"sshpass" and p.returncode == 6),
|
|
1379
|
+
b"read_passphrase: can't open /dev/tty" in b_stderr,
|
|
1380
|
+
b"Host key verification failed" in b_stderr,
|
|
1381
|
+
))
|
|
1382
|
+
if password_mechanism and self.get_option('host_key_checking') and conn_password and hostkey_fail:
|
|
1383
|
+
raise AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled. '
|
|
1384
|
+
'Please add this host\'s fingerprint to your known_hosts file to manage this host.')
|
|
1183
1385
|
|
|
1184
1386
|
controlpersisterror = b'Bad configuration option: ControlPersist' in b_stderr or b'unknown configuration option: ControlPersist' in b_stderr
|
|
1185
1387
|
if p.returncode != 0 and controlpersisterror:
|
|
@@ -1292,7 +1494,7 @@ class Connection(ConnectionBase):
|
|
|
1292
1494
|
# Main public methods
|
|
1293
1495
|
#
|
|
1294
1496
|
def exec_command(self, cmd: str, in_data: bytes | None = None, sudoable: bool = True) -> tuple[int, bytes, bytes]:
|
|
1295
|
-
|
|
1497
|
+
""" run a command on the remote host """
|
|
1296
1498
|
|
|
1297
1499
|
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
|
1298
1500
|
|
|
@@ -1333,7 +1535,7 @@ class Connection(ConnectionBase):
|
|
|
1333
1535
|
return (returncode, stdout, stderr)
|
|
1334
1536
|
|
|
1335
1537
|
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
|
-
|
|
1538
|
+
""" transfer a file from local to remote """
|
|
1337
1539
|
|
|
1338
1540
|
super(Connection, self).put_file(in_path, out_path)
|
|
1339
1541
|
|
|
@@ -1349,7 +1551,7 @@ class Connection(ConnectionBase):
|
|
|
1349
1551
|
return self._file_transport_command(in_path, out_path, 'put')
|
|
1350
1552
|
|
|
1351
1553
|
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
|
-
|
|
1554
|
+
""" fetch a file from remote to local """
|
|
1353
1555
|
|
|
1354
1556
|
super(Connection, self).fetch_file(in_path, out_path)
|
|
1355
1557
|
|
|
@@ -1372,18 +1574,18 @@ class Connection(ConnectionBase):
|
|
|
1372
1574
|
# only run the reset if the ControlPath already exists or if it isn't configured and ControlPersist is set
|
|
1373
1575
|
# 'check' will determine this.
|
|
1374
1576
|
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))
|
|
1577
|
+
display.vvv(u'sending connection check: %s' % to_text(cmd), host=self.host)
|
|
1376
1578
|
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1377
1579
|
stdout, stderr = p.communicate()
|
|
1378
1580
|
status_code = p.wait()
|
|
1379
1581
|
if status_code != 0:
|
|
1380
|
-
display.vvv(u"No connection to reset: %s" % to_text(stderr))
|
|
1582
|
+
display.vvv(u"No connection to reset: %s" % to_text(stderr), host=self.host)
|
|
1381
1583
|
else:
|
|
1382
1584
|
run_reset = True
|
|
1383
1585
|
|
|
1384
1586
|
if run_reset:
|
|
1385
1587
|
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))
|
|
1588
|
+
display.vvv(u'sending connection stop: %s' % to_text(cmd), host=self.host)
|
|
1387
1589
|
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
1388
1590
|
stdout, stderr = p.communicate()
|
|
1389
1591
|
status_code = p.wait()
|
|
@@ -1394,3 +1596,41 @@ class Connection(ConnectionBase):
|
|
|
1394
1596
|
|
|
1395
1597
|
def close(self) -> None:
|
|
1396
1598
|
self._connected = False
|
|
1599
|
+
|
|
1600
|
+
@property
|
|
1601
|
+
def has_tty(self):
|
|
1602
|
+
return self._is_tty_requested()
|
|
1603
|
+
|
|
1604
|
+
def _is_tty_requested(self):
|
|
1605
|
+
|
|
1606
|
+
# check if we require tty (only from our args, cannot see options in configuration files)
|
|
1607
|
+
opts = []
|
|
1608
|
+
for opt in ('ssh_args', 'ssh_common_args', 'ssh_extra_args'):
|
|
1609
|
+
attr = self.get_option(opt)
|
|
1610
|
+
if attr is not None:
|
|
1611
|
+
opts.extend(self._split_ssh_args(attr))
|
|
1612
|
+
|
|
1613
|
+
args, dummy = self._tty_parser.parse_known_args(opts)
|
|
1614
|
+
|
|
1615
|
+
if args.t:
|
|
1616
|
+
return True
|
|
1617
|
+
|
|
1618
|
+
for arg in args.o or []:
|
|
1619
|
+
if '=' in arg:
|
|
1620
|
+
val = arg.split('=', 1)
|
|
1621
|
+
else:
|
|
1622
|
+
val = arg.split(maxsplit=1)
|
|
1623
|
+
|
|
1624
|
+
if val[0].lower().strip() == 'requesttty':
|
|
1625
|
+
if val[1].lower().strip() in ('yes', 'force'):
|
|
1626
|
+
return True
|
|
1627
|
+
|
|
1628
|
+
return False
|
|
1629
|
+
|
|
1630
|
+
def is_pipelining_enabled(self, wrap_async=False):
|
|
1631
|
+
""" override parent method and ensure we don't request a tty """
|
|
1632
|
+
|
|
1633
|
+
if self._is_tty_requested():
|
|
1634
|
+
return False
|
|
1635
|
+
else:
|
|
1636
|
+
return super(Connection, self).is_pipelining_enabled(wrap_async)
|