diff --git a/dvc/config.py b/dvc/config.py index a01e39291a..372a51d1f1 100644 --- a/dvc/config.py +++ b/dvc/config.py @@ -190,6 +190,7 @@ class RelPath(str): "keyfile": str, "timeout": Coerce(int), "gss_auth": Bool, + "allow_agent": Bool, **REMOTE_COMMON, }, "hdfs": {"user": str, **REMOTE_COMMON}, diff --git a/dvc/tree/ssh/__init__.py b/dvc/tree/ssh/__init__.py index 2faa6ec5fc..d6ba6a89af 100644 --- a/dvc/tree/ssh/__init__.py +++ b/dvc/tree/ssh/__init__.py @@ -91,6 +91,7 @@ def __init__(self, repo, config): self.sock = paramiko.ProxyCommand(proxy_command) else: self.sock = None + self.allow_agent = config.get("allow_agent", True) @staticmethod def ssh_config_filename(): @@ -143,6 +144,7 @@ def ssh(self, path_info): password=self.password, gss_auth=self.gss_auth, sock=self.sock, + allow_agent=self.allow_agent, ) @contextmanager diff --git a/tests/unit/remote/ssh/test_ssh.py b/tests/unit/remote/ssh/test_ssh.py index be6cc704f9..7c15b00f8a 100644 --- a/tests/unit/remote/ssh/test_ssh.py +++ b/tests/unit/remote/ssh/test_ssh.py @@ -183,6 +183,31 @@ def test_ssh_gss_auth(mock_file, mock_exists, dvc, config, expected_gss_auth): assert tree.gss_auth == expected_gss_auth +@pytest.mark.parametrize( + "config,expected_allow_agent", + [ + ({"url": "ssh://example.com"}, True), + ({"url": "ssh://not_in_ssh_config.com"}, True), + ({"url": "ssh://example.com", "allow_agent": True}, True), + ({"url": "ssh://example.com", "allow_agent": False}, False), + ], +) +@patch("os.path.exists", return_value=True) +@patch( + f"{builtin_module_name}.open", + new_callable=mock_open, + read_data=mock_ssh_config, +) +def test_ssh_allow_agent( + mock_file, mock_exists, dvc, config, expected_allow_agent +): + tree = SSHTree(dvc, config) + + mock_exists.assert_called_with(SSHTree.ssh_config_filename()) + mock_file.assert_called_with(SSHTree.ssh_config_filename()) + assert tree.allow_agent == expected_allow_agent + + def test_hardlink_optimization(dvc, tmp_dir, ssh): tree = SSHTree(dvc, ssh.config)