Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pyinfra for openSUSE:Factory checked in at 2026-03-30 18:32:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyinfra (Old) and /work/SRC/openSUSE:Factory/.python-pyinfra.new.1999 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyinfra" Mon Mar 30 18:32:07 2026 rev:3 rq:1343557 version:3.7 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyinfra/python-pyinfra.changes 2026-03-13 21:22:03.138330925 +0100 +++ /work/SRC/openSUSE:Factory/.python-pyinfra.new.1999/python-pyinfra.changes 2026-03-30 18:35:35.409821634 +0200 @@ -1,0 +2,32 @@ +Mon Mar 30 04:14:29 UTC 2026 - Steve Kowalik <[email protected]> + +- Update to 3.7: + * api.facts: fix requires_command shell operator precedence with compound + commands + * operations.docker: add support for env files + * operations.docker: add support for dns + * operations.files.sync: support symlinks + * facts.server.Port: add UDP support and multi-platform backends + * facts.hardware.Memory: use LANG=C while calling vmstat + * facts.hardware: add CpuInfo fact + * facts.server.Sysctl: fix non-zero exit code causing empty result + * facts.runit.RunitManaged: handle missing service directory gracefully + * facts.hardware.Memory: fix FreeBSD support by summing page categories + * facts.server.SecurityLimits: handle missing limits.conf + * facts.server.Port: fix ss command for Alpine/BusyBox compatibility + * facts.iptables: add requires_command to all iptables facts + * facts.opkg: add requires_command and stop hardcoding /bin/opkg + * facts.choco: add requires_command to ChocoPackages and ChocoVersion + * connectors: @podmanssh connector for remote Podman container operations + * connectors.ssh: exclude _chdir from global arguments used when removing + temporary file + * connectors.ssh: don't include password in SSH exception authentication + data + * cli: use gevent for the progress spinner + * cli: add --ssh-password-prompt flag for interactive SSH password input + * global arguments: _su_password argument for su password authentication + * operations+facts: server.timezone and server.Timezone fact +- Add patch support-paramiko-4.patch: + * Support paramiko 4 changes. + +------------------------------------------------------------------- Old: ---- pyinfra-3.6.1.tar.gz New: ---- pyinfra-3.7.tar.gz support-paramiko-4.patch ----------(New B)---------- New: * operations+facts: server.timezone and server.Timezone fact - Add patch support-paramiko-4.patch: * Support paramiko 4 changes. ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyinfra.spec ++++++ --- /var/tmp/diff_new_pack.NIDnHw/_old 2026-03-30 18:35:36.169853409 +0200 +++ /var/tmp/diff_new_pack.NIDnHw/_new 2026-03-30 18:35:36.169853409 +0200 @@ -24,16 +24,17 @@ %{?sle15_python_module_pythons} Name: python-pyinfra -Version: 3.6.1 +Version: 3.7 Release: 0 Summary: Infrastructure automation, provisioning and deployment License: MIT URL: https://pyinfra.com Source: https://files.pythonhosted.org/packages/source/p/pyinfra/pyinfra-%{version}.tar.gz Source1: https://raw.githubusercontent.com/pyinfra-dev/testgen/ad6673/testgen/__init__.py#/testgen.py +# PATCH-FIX-UPSTREAM gh#pyinfra-dev/pyinfra#1525 +Patch0: support-paramiko-4.patch BuildRequires: %{python_module hatchling} BuildRequires: %{python_module pip} -BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-click > 2 ++++++ pyinfra-3.6.1.tar.gz -> pyinfra-3.7.tar.gz ++++++ ++++ 3810 lines of diff (skipped) ++++++ support-paramiko-4.patch ++++++ >From 227490a3582c667586cb1930a0ba82900c147c2b Mon Sep 17 00:00:00 2001 From: Whyme Lyu <[email protected]> Date: Mon, 12 Jan 2026 14:47:51 +0800 Subject: [PATCH] dependencies/paramiko: support paramiko v4, remove DSS key support These keys are so out of date that this does not warrant a breaking change version bump. --- pyproject.toml | 3 +- src/pyinfra/connectors/ssh_util.py | 5 +- tests/test_connectors/test_ssh.py | 120 +---------------------------- uv.lock | 4 +- 4 files changed, 8 insertions(+), 124 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3e8aab3b9..17aa94a03 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ license-files = ["LICENSE.md"] requires-python = ">=3.10,<4.0" dependencies = [ "gevent>=1.5", - "paramiko>=2.7,<4", # 2.7 (2019) adds OpenSSH key format + Match SSH config + "paramiko>=2.7,<5", # 2.7 (2019) adds OpenSSH key format + Match SSH config "click>2", "jinja2>3,<4", "python-dateutil>2,<3", @@ -22,6 +22,7 @@ dependencies = [ "packaging>=16.1", "pydantic>=2.11,<3", "typing-extensions; python_version < '3.11'", # Backport of typing for Unpack (added 3.11) + "types-paramiko>=2.7,<5", ] classifiers = [ "Development Status :: 5 - Production/Stable", diff --git a/src/pyinfra/connectors/ssh_util.py b/src/pyinfra/connectors/ssh_util.py index a70685880..5d4bf7997 100644 --- a/src/pyinfra/connectors/ssh_util.py +++ b/src/pyinfra/connectors/ssh_util.py @@ -3,7 +3,6 @@ from typing import TYPE_CHECKING, Type, Union from paramiko import ( - DSSKey, ECDSAKey, Ed25519Key, PasswordRequiredException, @@ -28,9 +27,9 @@ def raise_connect_error(host: "Host", message, data): def _load_private_key_file(filename: str, key_filename: str, key_password: str): exception: Union[PyinfraError, SSHException] = PyinfraError("Invalid key: {0}".format(filename)) - key_cls: Union[Type[RSAKey], Type[DSSKey], Type[ECDSAKey], Type[Ed25519Key]] + key_cls: Union[Type[RSAKey], Type[ECDSAKey], Type[Ed25519Key]] - for key_cls in (RSAKey, DSSKey, ECDSAKey, Ed25519Key): + for key_cls in (RSAKey, ECDSAKey, Ed25519Key): try: return key_cls.from_private_key_file( filename=filename, diff --git a/tests/test_connectors/test_ssh.py b/tests/test_connectors/test_ssh.py index 0aad2c6cb..86391eb63 100644 --- a/tests/test_connectors/test_ssh.py +++ b/tests/test_connectors/test_ssh.py @@ -313,10 +313,6 @@ def test_connect_with_rsa_ssh_key_wrong_password(self): with ( mock.patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), - mock.patch( - "pyinfra.connectors.ssh_util.DSSKey.from_private_key_file", - fake_fail_from_private_key_file, - ), mock.patch( "pyinfra.connectors.ssh_util.ECDSAKey.from_private_key_file", fake_fail_from_private_key_file, @@ -345,121 +341,7 @@ def fake_key_open_fail(*args, **kwargs): assert e.exception.args[0] == "Invalid private key file: testkey" - assert fake_fail_from_private_key_file.call_count == 3 - - def test_connect_with_dss_ssh_key(self): - state = State(make_inventory(hosts=(("somehost", {"ssh_key": "testkey"}),)), Config()) - - with ( - mock.patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), - mock.patch( - "pyinfra.connectors.ssh_util.RSAKey.from_private_key_file", - ) as fake_rsa_key_open, - mock.patch( - "pyinfra.connectors.ssh_util.DSSKey.from_private_key_file", - ) as fake_key_open, - ): # noqa - fake_rsa_key_open.side_effect = make_raise_exception_function(SSHException) - - fake_key = mock.MagicMock() - fake_key_open.return_value = fake_key - - connect_all(state) - - # Check the key was created properly - fake_key_open.assert_called_with(filename="testkey") - - # And check the Paramiko SSH call was correct - self.fake_connect_mock.assert_called_with( - "somehost", - allow_agent=False, - look_for_keys=False, - pkey=fake_key, - timeout=10, - username="vagrant", - _pyinfra_ssh_forward_agent=False, - _pyinfra_ssh_config_file=None, - _pyinfra_ssh_known_hosts_file=None, - _pyinfra_ssh_strict_host_key_checking="accept-new", - _pyinfra_ssh_paramiko_connect_kwargs=None, - ) - - # Check that loading the same key again is cached in the state - second_state = State( - make_inventory(hosts=(("somehost", {"ssh_key": "testkey"}),)), - Config(), - ) - second_state.private_keys = state.private_keys - - connect_all(second_state) - - def test_connect_with_dss_ssh_key_password(self): - state = State( - make_inventory( - hosts=( - ( - "somehost", - {"ssh_key": "testkey", "ssh_key_password": "testpass"}, - ), - ), - ), - Config(), - ) - - with ( - mock.patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), - mock.patch( - "pyinfra.connectors.ssh_util.RSAKey.from_private_key_file", - ) as fake_rsa_key_open, - mock.patch( - "pyinfra.connectors.ssh_util.DSSKey.from_private_key_file", - ) as fake_dss_key_open, - ): # noqa - - def fake_rsa_key_open_fail(*args, **kwargs): - if "password" not in kwargs: - raise PasswordRequiredException - raise SSHException - - fake_rsa_key_open.side_effect = fake_rsa_key_open_fail - - fake_dss_key = mock.MagicMock() - - def fake_dss_key_func(*args, **kwargs): - if "password" not in kwargs: - raise PasswordRequiredException - return fake_dss_key - - fake_dss_key_open.side_effect = fake_dss_key_func - - connect_all(state) - - # Check the key was created properly - fake_dss_key_open.assert_called_with(filename="testkey", password="testpass") - - # And check the Paramiko SSH call was correct - self.fake_connect_mock.assert_called_with( - "somehost", - allow_agent=False, - look_for_keys=False, - pkey=fake_dss_key, - timeout=10, - username="vagrant", - _pyinfra_ssh_forward_agent=False, - _pyinfra_ssh_config_file=None, - _pyinfra_ssh_known_hosts_file=None, - _pyinfra_ssh_strict_host_key_checking="accept-new", - _pyinfra_ssh_paramiko_connect_kwargs=None, - ) - - # Check that loading the same key again is cached in the state - second_state = State( - make_inventory(hosts=(("somehost", {"ssh_key": "testkey"}),)), - Config(), - ) - second_state.private_keys = state.private_keys - - connect_all(second_state) + assert fake_fail_from_private_key_file.call_count == 2 def test_connect_with_missing_ssh_key(self): state = State(make_inventory(hosts=(("somehost", {"ssh_key": "testkey"}),)), Config()) diff --git a/uv.lock b/uv.lock index 81e0372ef..14f8eb9a9 100644 --- a/uv.lock +++ b/uv.lock @@ -1262,6 +1262,7 @@ dependencies = [ { name = "pydantic" }, { name = "python-dateutil" }, { name = "typeguard" }, + { name = "types-paramiko" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] @@ -1318,10 +1319,11 @@ requires-dist = [ { name = "gevent", specifier = ">=1.5" }, { name = "jinja2", specifier = ">3,<4" }, { name = "packaging", specifier = ">=16.1" }, - { name = "paramiko", specifier = ">=2.7,<4" }, + { name = "paramiko", specifier = ">=2.7,<5" }, { name = "pydantic", specifier = ">=2.11,<3" }, { name = "python-dateutil", specifier = ">2,<3" }, { name = "typeguard", specifier = ">=4,<5" }, + { name = "types-paramiko", specifier = ">=2.7,<5" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ]
