diff --git a/doc/source/SSHClient.rst b/doc/source/SSHClient.rst index 1b0a88b..37cf9ac 100644 --- a/doc/source/SSHClient.rst +++ b/doc/source/SSHClient.rst @@ -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 diff --git a/exec_helpers/_ssh_client_base.py b/exec_helpers/_ssh_client_base.py index f0ce5b5..253a39e 100644 --- a/exec_helpers/_ssh_client_base.py +++ b/exec_helpers/_ssh_client_base.py @@ -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. @@ -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: @@ -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 @@ -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): @@ -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. @@ -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 """ @@ -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()) @@ -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.""" diff --git a/exec_helpers/_ssh_client_base.pyi b/exec_helpers/_ssh_client_base.pyi index 79dc13a..42f3d52 100644 --- a/exec_helpers/_ssh_client_base.pyi +++ b/exec_helpers/_ssh_client_base.pyi @@ -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 @@ -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 diff --git a/test/test_ssh_client_init.py b/test/test_ssh_client_init.py index a5f400d..dd14664 100644 --- a/test/test_ssh_client_init.py +++ b/test/test_ssh_client_init.py @@ -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()