This is an automated email from the ASF dual-hosted git repository.
shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 7dfa25664a2 Fix SFTPHookAsync letting a connection's known_hosts extra
override an explicit constructor path (#73593)
7dfa25664a2 is described below
commit 7dfa25664a2eec1fe5052790ada7977ada12ec8a
Author: namanjain24-sudo <[email protected]>
AuthorDate: Thu Sep 24 01:26:55 2026 +0530
Fix SFTPHookAsync letting a connection's known_hosts extra override an
explicit constructor path (#73593)
_parse_extras() decided whether to take known_hosts from the connection's
extra by comparing the already-expanded self.known_hosts against the
literal, un-expanded "~/.ssh/known_hosts" default. Since __init__ always
runs known_hosts through os.path.expanduser(), the two values can never
be equal, so the comparison was always true: a connection-level
known_hosts extra silently replaced even a path passed explicitly to the
constructor.
SSHHookAsync gets this right elsewhere in the same file, by expanding the
default before comparing and only falling back to the extra when the
constructor was left at that default. This applies the same pattern to
SFTPHookAsync.
---
.../sftp/src/airflow/providers/sftp/hooks/sftp.py | 6 +++--
providers/sftp/tests/unit/sftp/hooks/test_sftp.py | 28 ++++++++++++++++++++++
2 files changed, 32 insertions(+), 2 deletions(-)
diff --git a/providers/sftp/src/airflow/providers/sftp/hooks/sftp.py
b/providers/sftp/src/airflow/providers/sftp/hooks/sftp.py
index f89ff3553a8..bab90ca031d 100644
--- a/providers/sftp/src/airflow/providers/sftp/hooks/sftp.py
+++ b/providers/sftp/src/airflow/providers/sftp/hooks/sftp.py
@@ -878,8 +878,10 @@ class SFTPHookAsync(BaseHook):
extra_options = conn.extra_dejson
if "key_file" in extra_options and self.key_file == "":
self.key_file = extra_options["key_file"]
- if "known_hosts" in extra_options and self.known_hosts !=
self.default_known_hosts:
- self.known_hosts = extra_options["known_hosts"]
+ if "known_hosts" in extra_options:
+ expanded_default = os.path.expanduser(self.default_known_hosts)
+ if self.known_hosts == expanded_default:
+ self.known_hosts = extra_options["known_hosts"]
if "passphrase" in extra_options or "private_key_passphrase" in
extra_options:
self.passphrase = extra_options.get("passphrase") or
extra_options.get(
"private_key_passphrase", ""
diff --git a/providers/sftp/tests/unit/sftp/hooks/test_sftp.py
b/providers/sftp/tests/unit/sftp/hooks/test_sftp.py
index 7b51940172a..cf074a4a30c 100644
--- a/providers/sftp/tests/unit/sftp/hooks/test_sftp.py
+++ b/providers/sftp/tests/unit/sftp/hooks/test_sftp.py
@@ -786,6 +786,34 @@ class MockAirflowConnectionWithPrivate:
class TestSFTPHookAsync:
+ @patch("asyncssh.connect", new_callable=AsyncMock)
+ @patch("airflow.providers.sftp.hooks.sftp.get_async_connection")
+ @pytest.mark.asyncio
+ async def
test_explicit_known_hosts_constructor_arg_is_not_overridden_by_connection_extra(
+ self, mock_get_connection, mock_connect
+ ):
+ """
+ An explicit `known_hosts` passed to the constructor must win over the
connection's
+ `known_hosts` extra. `_parse_extras` should only fall back to the
extra when the
+ constructor was left at its default, mirroring `SSHHookAsync`.
+
+ `no_host_key_check` is pinned to `False` so that unrelated
default-skip behavior
+ does not also overwrite `known_hosts`, which would mask what this test
checks.
+ """
+ mock_get_connection.return_value = SimpleNamespace(
+ host="localhost",
+ port=22,
+ login="username",
+ password="password",
+ extra="{}",
+ extra_dejson={"known_hosts": "/connection/known_hosts",
"no_host_key_check": False},
+ )
+
+ hook = SFTPHookAsync(known_hosts="/explicit/known_hosts")
+ await hook._get_conn()
+
+ assert mock_connect.call_args.kwargs["known_hosts"] ==
"/explicit/known_hosts"
+
@patch("asyncssh.connect", new_callable=AsyncMock)
@patch("airflow.providers.sftp.hooks.sftp.get_async_connection")
@pytest.mark.asyncio