Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/source/SSHClient.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ API: SSHClient and SSHAuth.
:type private_keys: ``typing.Optional[typing.Iterable[paramiko.RSAKey]]``
:param auth: credentials for connection
:type auth: typing.Optional[SSHAuth]
:param verbose: show additional error/warning messages
:type verbose: bool

.. note:: auth has priority over username/password/private_keys

Expand Down
12 changes: 9 additions & 3 deletions exec_helpers/_ssh_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def __call__(
password=None, # type: typing.Optional[str]
private_keys=None, # type: typing.Optional[typing.Iterable[paramiko.RSAKey]]
auth=None, # type: typing.Optional[ssh_auth.SSHAuth]
verbose=True, # type: bool
): # type: (...) -> SSHClientBase
"""Main memorize method: check for cached instance and return it.

Expand All @@ -125,6 +126,7 @@ def __call__(
:type password: str
:type private_keys: list
:type auth: ssh_auth.SSHAuth
:type verbose: bool
:rtype: SSHClient
"""
if (host, port) in cls.__cache:
Expand Down Expand Up @@ -160,7 +162,7 @@ def __call__(
).__call__(
host=host, port=port,
username=username, password=password, private_keys=private_keys,
auth=auth)
auth=auth, verbose=verbose)
cls.__cache[(ssh.hostname, ssh.port)] = ssh
return ssh

Expand Down Expand Up @@ -195,7 +197,7 @@ class SSHClientBase(six.with_metaclass(_MemorizedSSH, _api.ExecHelper)):

__slots__ = (
'__hostname', '__port', '__auth', '__ssh', '__sftp',
'__sudo_mode', '__keepalive_mode',
'__sudo_mode', '__keepalive_mode', '__verbose',
)

class __get_sudo(object):
Expand Down Expand Up @@ -279,6 +281,7 @@ def __init__(
password=None, # type: typing.Optional[str]
private_keys=None, # type: typing.Optional[typing.Iterable[paramiko.RSAKey]]
auth=None, # type: typing.Optional[ssh_auth.SSHAuth]
verbose=True, # type: bool
): # type: (...) -> None
"""SSHClient helper.

Expand All @@ -294,6 +297,8 @@ def __init__(
:type private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]]
:param auth: credentials for connection
:type auth: typing.Optional[ssh_auth.SSHAuth]
:param verbose: show additional error/warning messages
:type verbose: bool

.. note:: auth has priority over username/password/private_keys
"""
Expand All @@ -310,6 +315,7 @@ def __init__(

self.__sudo_mode = False
self.__keepalive_mode = True
self.__verbose = verbose

self.__ssh = paramiko.SSHClient()
self.__ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
Expand Down Expand Up @@ -400,7 +406,7 @@ def __connect(self):
self.auth.connect(
client=self.__ssh,
hostname=self.hostname, port=self.port,
log=True)
log=self.__verbose)

def __connect_sftp(self):
"""SFTP connection opener."""
Expand Down
6 changes: 4 additions & 2 deletions exec_helpers/_ssh_client_base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class _MemorizedSSH(type):
username: typing.Optional[str]=...,
password: typing.Optional[str]=...,
private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]]=...,
auth: typing.Optional[ssh_auth.SSHAuth]=...
auth: typing.Optional[ssh_auth.SSHAuth]=...,
verbose: bool=...
) -> SSHClientBase: ...

@classmethod
Expand All @@ -37,7 +38,8 @@ class SSHClientBase(_api.ExecHelper, metaclass=_MemorizedSSH):
username: typing.Optional[str]=...,
password: typing.Optional[str]=...,
private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]]=...,
auth: typing.Optional[ssh_auth.SSHAuth]=...
auth: typing.Optional[ssh_auth.SSHAuth]=...,
verbose: bool=...
) -> None: ...

@property
Expand Down
18 changes: 18 additions & 0 deletions test/test_ssh_client_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,3 +759,21 @@ def test_025_init_memorize_reconnect(self, execute, client, policy, logger):
exec_helpers.SSHClient(host=host)
client.assert_called_once()
policy.assert_called_once()

@mock.patch('time.sleep', autospec=True)
def test_026_init_auth_impossible_key_no_verbose(
self, sleep, client, policy, logger):
connect = mock.Mock(side_effect=paramiko.AuthenticationException)

_ssh = mock.Mock()
_ssh.attach_mock(connect, 'connect')
client.return_value = _ssh

with self.assertRaises(paramiko.AuthenticationException):
exec_helpers.SSHClient(
host=host,
auth=exec_helpers.SSHAuth(key=gen_private_keys(1).pop()),
verbose=False
)

logger.assert_not_called()