def test_019_init_auth_pass_no_key(self, client, policy, logger): connect = mock.Mock( side_effect=[paramiko.AuthenticationException, mock.Mock()]) _ssh = mock.Mock() _ssh.attach_mock(connect, 'connect') client.return_value = _ssh key = gen_private_keys(1).pop() ssh = exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth( username=username, password=password, key=key)) client.assert_called_once() policy.assert_called_once() logger.assert_has_calls((mock.call.debug( 'Main key has been updated, public key is: \nNone'), )) self.assertEqual( ssh.auth, exec_helpers.SSHAuth(username=username, password=password, keys=[key])) sftp = ssh._sftp self.assertEqual(sftp, client().open_sftp()) self.assertEqual(ssh._ssh, client())
def test_014_init_reconnect(self, client, policy, logger): """Test reconnect :type client: mock.Mock :type policy: mock.Mock :type logger: mock.Mock """ ssh = exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth()) client.assert_called_once() policy.assert_called_once() logger.assert_not_called() self.assertEqual(ssh.auth, exec_helpers.SSHAuth()) sftp = ssh._sftp self.assertEqual(sftp, client().open_sftp()) self.assertEqual(ssh._ssh, client()) client.reset_mock() policy.reset_mock() self.assertEqual(ssh.hostname, host) self.assertEqual(ssh.port, port) ssh.reconnect() _ssh = mock.call() expected_calls = [ _ssh.close(), _ssh, _ssh.set_missing_host_key_policy('AutoAddPolicy'), _ssh.connect( hostname='127.0.0.1', password=None, pkey=None, port=22, username=None, ), ] self.assertIn(expected_calls, client.mock_calls) client.assert_called_once() policy.assert_called_once() logger.assert_not_called() self.assertEqual(ssh.auth, exec_helpers.SSHAuth()) sftp = ssh._sftp self.assertEqual(sftp, client().open_sftp()) self.assertEqual(ssh._ssh, client())
def test_013_init_clear_failed(self, client, policy, logger): """Test reconnect :type client: mock.Mock :type policy: mock.Mock :type logger: mock.Mock """ _ssh = mock.Mock() _ssh.attach_mock( mock.Mock( side_effect=[Exception('Mocked SSH close()'), mock.Mock()]), 'close') _sftp = mock.Mock() _sftp.attach_mock( mock.Mock( side_effect=[Exception('Mocked SFTP close()'), mock.Mock()]), 'close') client.return_value = _ssh _ssh.attach_mock(mock.Mock(return_value=_sftp), 'open_sftp') with mock.patch('logging.getLogger', autospec=True) as get_logger: ssh_logger = get_logger(exec_helpers.SSHClient.__name__) ssh = exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth()) client.assert_called_once() policy.assert_called_once() ssh_logger.assert_not_called() self.assertEqual(ssh.auth, exec_helpers.SSHAuth()) sftp = ssh._sftp self.assertEqual(sftp, _sftp) self.assertEqual(ssh._ssh, _ssh) self.assertEqual(ssh.hostname, host) self.assertEqual(ssh.port, port) ssh_logger.reset_mock() ssh.close() log = ssh_logger.getChild('{host}:{port}'.format(host=host, port=port)) log.assert_has_calls(( mock.call.exception('Could not close ssh connection'), mock.call.exception('Could not close sftp connection'), ))
def test_002_equality_copy(): """Equality is calculated using hash, copy=deepcopy.""" auth1 = exec_helpers.SSHAuth(username="******") auth2 = exec_helpers.SSHAuth(username="******") auth3 = exec_helpers.SSHAuth(username="******") assert auth1 == auth2 assert auth1 != auth3 assert auth3 == copy.copy(auth3) assert auth3 is not copy.copy(auth3) assert auth3 == copy.deepcopy(auth3) assert auth3 is not copy.deepcopy(auth3)
def test_equality_copy(self): """Equality is calculated using hash, copy=deepcopy.""" auth1 = exec_helpers.SSHAuth(username='******', ) auth2 = exec_helpers.SSHAuth(username='******', ) auth3 = exec_helpers.SSHAuth(username='******', ) self.assertEqual(auth1, auth2) self.assertNotEqual(auth1, auth3) self.assertEqual(auth3, copy.copy(auth3)) self.assertIsNot(auth3, copy.copy(auth3)) self.assertEqual(auth3, copy.deepcopy(auth3)) self.assertIsNot(auth3, copy.deepcopy(auth3))
def test_mkdir(self, execute, exists, *args): exists.side_effect = [False, True] dst = "~/tst dir" escaped_dst = r"~/tst\ dir" # noinspection PyTypeChecker ssh = exec_helpers.SSHClient(host=host, port=port, auth=exec_helpers.SSHAuth( username=username, password=password)) # Path not exists # noinspection PyTypeChecker ssh.mkdir(dst) exists.assert_called_once_with(dst) execute.assert_called_once_with(f"mkdir -p {escaped_dst}\n") # Path exists exists.reset_mock() execute.reset_mock() # noinspection PyTypeChecker ssh.mkdir(dst) exists.assert_called_once_with(dst) execute.assert_not_called()
def test_001_init_checks(run_parameters) -> None: """Object create validation.""" auth = exec_helpers.SSHAuth(**run_parameters) int_keys = get_internal_keys(**run_parameters) assert auth.username == username with contextlib.closing(io.BytesIO()) as tgt: auth.enter_password(tgt) assert tgt.getvalue( ) == f"{run_parameters.get('password', '')}\n".encode("utf-8") key = int_keys[0] if key is not None: assert auth.public_key == gen_public_key(key) else: assert auth.public_key is None _key = None if auth.public_key is None else f"<private for pub: {auth.public_key}>" _keys = [] for k in int_keys: if k is key or (k is not None and key is not None and k == key): continue _keys.append(f"<private for pub: {gen_public_key(k)}>" if k is not None else None) assert repr(auth) == (f"{exec_helpers.SSHAuth.__name__}(" f"username={auth.username!r}, " f"password=<*masked*>, " f"key={_key}, " f"keys={_keys}, " f"key_filename={auth.key_filename!r}, " f"passphrase=<*masked*>," f")") assert str(auth) == f"{exec_helpers.SSHAuth.__name__} for {auth.username}"
def test_012_re_connect(paramiko_ssh_client, auto_add_policy, ssh_auth_logger): """Re-connect.""" # Test ssh = exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth()) paramiko_ssh_client.reset_mock() auto_add_policy.reset_mock() ssh.reconnect() _ssh = mock.call() expected_calls = [ _ssh.close(), _ssh, _ssh.set_missing_host_key_policy("AutoAddPolicy"), _ssh.connect(hostname="127.0.0.1", password=None, pkey=None, port=22, username=None, key_filename=()), _ssh.get_transport(), _ssh.get_transport().set_keepalive(1), ] assert paramiko_ssh_client.mock_calls == expected_calls
def test_022_init_sftp_repair(self, client, policy, logger): _sftp = mock.Mock() open_sftp = mock.Mock( side_effect=[paramiko.SSHException, _sftp, _sftp]) _ssh = mock.Mock() _ssh.attach_mock(open_sftp, 'open_sftp') client.return_value = _ssh with mock.patch('logging.getLogger', autospec=True) as get_logger: ssh_logger = get_logger(exec_helpers.SSHClient.__name__) ssh = exec_helpers.SSHClient( host=host, auth=exec_helpers.SSHAuth(password=password)) with self.assertRaises(paramiko.SSHException): # pylint: disable=pointless-statement # noinspection PyStatementEffect ssh._sftp # pylint: enable=pointless-statement ssh_logger.reset_mock() sftp = ssh._sftp self.assertEqual(sftp, open_sftp()) log = ssh_logger.getChild('{host}:{port}'.format(host=host, port=port)) log.assert_has_calls(( mock.call.debug('SFTP is not connected, try to connect...'), ))
def test_014_sftp_repair(paramiko_ssh_client, auto_add_policy, ssh_auth_logger, get_logger): """No sftp available.""" # Helper code _sftp = mock.Mock() open_sftp = mock.Mock(side_effect=[paramiko.SSHException, _sftp, _sftp]) _ssh = mock.Mock() _ssh.attach_mock(open_sftp, "open_sftp") paramiko_ssh_client.return_value = _ssh ssh_logger = get_logger(exec_helpers.SSHClient.__name__) log = ssh_logger.getChild(f"{host}:{port}") # Test ssh = exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth(password=password)) with pytest.raises(paramiko.SSHException): # noinspection PyStatementEffect ssh._sftp # pylint: disable=pointless-statement log.assert_has_calls(( mock.call.debug("SFTP is not connected, try to connect..."), mock.call.warning("SFTP enable failed! SSH only is accessible."), )) ssh_logger.reset_mock() log.reset_mock() sftp = ssh._sftp assert sftp == open_sftp() log.assert_has_calls( (mock.call.debug("SFTP is not connected, try to connect..."), ))
def test_010_check_stdin_closed(paramiko_ssh_client, chan_makefile, auto_add_policy, get_logger): chan = mock.Mock(makefile=chan_makefile, closed=True) chan_makefile.channel = chan chan.attach_mock(mock.Mock(return_value=FakeFileStream(*stderr_src)), "makefile_stderr") chan.configure_mock(exit_status=0) chan.status_event.attach_mock(mock.Mock(return_value=True), "is_set") open_session = mock.Mock(return_value=chan) transport = mock.Mock() transport.attach_mock(open_session, "open_session") get_transport = mock.Mock(return_value=transport) _ssh = mock.Mock() _ssh.attach_mock(get_transport, "get_transport") paramiko_ssh_client.return_value = _ssh stdin_val = "this is a line" ssh = exec_helpers.SSHClient(host=host, port=port, auth=exec_helpers.SSHAuth(username=username, password=password)) ssh._execute_async(command=print_stdin, stdin=stdin_val) log = get_logger(ssh.__class__.__name__).getChild(f"{host}:{port}") log.warning.assert_called_once_with("STDIN Send failed: closed channel")
def test_012_init_context(self, client, policy, logger): with exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth()) as ssh: client.assert_called_once() policy.assert_called_once() logger.assert_not_called() self.assertEqual(ssh.auth, exec_helpers.SSHAuth()) sftp = ssh._sftp self.assertEqual(sftp, client().open_sftp()) self.assertEqual(ssh._ssh, client()) self.assertEqual(ssh.hostname, host) self.assertEqual(ssh.port, port)
def test_010_context(paramiko_ssh_client, auto_add_policy, ssh_auth_logger): """Context manager.""" # Test with exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth()) as ssh: paramiko_ssh_client.assert_called_once() auto_add_policy.assert_called_once() ssh_auth_logger.assert_not_called() assert ssh.auth == exec_helpers.SSHAuth() sftp = ssh._sftp assert sftp == paramiko_ssh_client().open_sftp() assert ssh._ssh == paramiko_ssh_client() assert ssh.hostname == host assert ssh.port == port
def test_010_init_auth(self, client, policy, logger): self.init_checks(client, policy, logger, host=host, auth=exec_helpers.SSHAuth( username=username, password=password, key=gen_private_keys(1).pop()))
def test_init_base(paramiko_ssh_client, auto_add_policy, run_parameters, ssh_auth_logger): """Parametrized validation of SSH client initialization.""" # Helper code _ssh = mock.call port = run_parameters.get("port", 22) username = run_parameters.get("username", None) password = run_parameters.get("password", None) auth = run_parameters.get("auth", None) # Test ssh = exec_helpers.SSHClient(**run_parameters) paramiko_ssh_client.assert_called_once() auto_add_policy.assert_called_once() if auth is None: expected_calls = [ _ssh.set_missing_host_key_policy("AutoAddPolicy"), _ssh.connect(hostname=host, password=password, pkey=None, port=port, username=username, key_filename=()), _ssh.get_transport(), _ssh.get_transport().set_keepalive(1), ] assert expected_calls == paramiko_ssh_client().mock_calls assert ssh.auth == exec_helpers.SSHAuth(username=username, password=password) else: ssh_auth_logger.assert_not_called() sftp = ssh._sftp assert sftp == paramiko_ssh_client().open_sftp() assert ssh._ssh == paramiko_ssh_client() assert ssh.hostname == host assert ssh.port == port assert repr( ssh) == "{cls}(host={host}, port={port}, auth={auth!r})".format( cls=ssh.__class__.__name__, host=ssh.hostname, port=ssh.port, auth=ssh.auth) # ssh config for main connection is synchronised with connection parameters expected_config_dict = {host: {"hostname": host, "port": ssh.port}} if ssh.auth.username: expected_config_dict[host]["user"] = ssh.auth.username assert ssh.ssh_config[host] == expected_config_dict[host] assert ssh.ssh_config == expected_config_dict assert ssh.ssh_config[host].hostname == host
def test_011_clear_failed(paramiko_ssh_client, auto_add_policy, ssh_auth_logger, get_logger): """TearDown failed.""" # Helper code _ssh = mock.Mock() _ssh.attach_mock( mock.Mock(side_effect=[Exception("Mocked SSH close()"), mock.Mock()]), "close") _sftp = mock.Mock() _sftp.attach_mock( mock.Mock(side_effect=[Exception("Mocked SFTP close()"), mock.Mock()]), "close") _ssh.attach_mock(mock.Mock(return_value=_sftp), "open_sftp") paramiko_ssh_client.return_value = _ssh ssh_logger = get_logger(exec_helpers.SSHClient.__name__) log = ssh_logger.getChild(f"{host}:{port}") # Test ssh = exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth()) paramiko_ssh_client.assert_called_once() auto_add_policy.assert_called_once() ssh_logger.assert_not_called() ssh_auth_logger.assert_not_called() assert ssh.auth == exec_helpers.SSHAuth() sftp = ssh._sftp assert sftp == paramiko_ssh_client().open_sftp() assert ssh._ssh == paramiko_ssh_client() assert ssh.hostname == host assert ssh.port == port ssh_logger.reset_mock() ssh.close() log.assert_has_calls( (mock.call.exception("Could not close ssh connection"), mock.call.exception("Could not close sftp connection")))
def test_015_init_password_required(self, sleep, client, policy, logger): connect = mock.Mock(side_effect=paramiko.PasswordRequiredException) _ssh = mock.Mock() _ssh.attach_mock(connect, 'connect') client.return_value = _ssh with self.assertRaises(paramiko.PasswordRequiredException): exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth()) logger.assert_has_calls( (mock.call.exception('No password has been set!'), ))
def test_rm_rf(self, execute, *args): dst = "~/tst" # noinspection PyTypeChecker ssh = exec_helpers.SSHClient(host=host, port=port, auth=exec_helpers.SSHAuth( username=username, password=password)) # Path not exists # noinspection PyTypeChecker ssh.rm_rf(dst) execute.assert_called_once_with(f"rm -rf {dst}")
def test_023_init_memorize(self, Result, client, policy, logger): port1 = 2222 host1 = '127.0.0.2' # 1. Normal init ssh01 = exec_helpers.SSHClient(host=host) ssh02 = exec_helpers.SSHClient(host=host) ssh11 = exec_helpers.SSHClient(host=host, port=port1) ssh12 = exec_helpers.SSHClient(host=host, port=port1) ssh21 = exec_helpers.SSHClient(host=host1) ssh22 = exec_helpers.SSHClient(host=host1) self.assertTrue(ssh01 is ssh02) self.assertTrue(ssh11 is ssh12) self.assertTrue(ssh21 is ssh22) self.assertFalse(ssh01 is ssh11) self.assertFalse(ssh01 is ssh21) self.assertFalse(ssh11 is ssh21) # 2. Close connections check with mock.patch('exec_helpers.ssh_client.SSHClient.close_connections' ) as no_call: exec_helpers.SSHClient.close() no_call.assert_not_called() # Mock returns false-connected state, so we just count close calls client().close.assert_has_calls(( mock.call(), mock.call(), mock.call(), )) # change creds exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth(username=username)) # Change back: new connection differs from old with the same creds ssh004 = exec_helpers.SSHAuth(host) self.assertFalse(ssh01 is ssh004)
def test_016_init_password_broken(self, sleep, client, policy, logger): connect = mock.Mock(side_effect=paramiko.PasswordRequiredException) _ssh = mock.Mock() _ssh.attach_mock(connect, 'connect') client.return_value = _ssh with self.assertRaises(paramiko.PasswordRequiredException): exec_helpers.SSHClient( host=host, auth=exec_helpers.SSHAuth(password=password)) logger.assert_has_calls( (mock.call.critical('Unexpected PasswordRequiredException, ' 'when password is set!'), ))
def test_018_init_auth_impossible_key(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())) logger.assert_has_calls((mock.call.exception( 'Connection using stored authentication info failed!'), ) * 3)
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()
def test_005_auth_impossible_password(paramiko_ssh_client, auto_add_policy, ssh_auth_logger): """Reject password.""" # Helper code connect = mock.Mock(side_effect=paramiko.AuthenticationException) _ssh_inst = mock.Mock() _ssh_inst.attach_mock(connect, "connect") paramiko_ssh_client.return_value = _ssh_inst # Test with pytest.raises(paramiko.AuthenticationException): exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth(password=password)) ssh_auth_logger.assert_has_calls((mock.call.exception( "Connection using stored authentication info failed!"), ))
def test_024_init_memorize_close_unused(self, warn, client, policy, logger): ssh0 = exec_helpers.SSHClient(host=host) del ssh0 # remove reference - now it's cached and unused client.reset_mock() logger.reset_mock() # New connection on the same host:port with different auth ssh1 = exec_helpers.SSHClient( host=host, auth=exec_helpers.SSHAuth(username=username)) client.assert_has_calls((mock.call().close(), )) del ssh1 # remove reference - now it's cached and unused client.reset_mock() logger.reset_mock() exec_helpers.SSHClient._clear_cache() client.assert_has_calls((mock.call().close(), ))
def test_002_use_next_key(paramiko_ssh_client, auto_add_policy, ssh_auth_logger): """Reject 1 key and use next one.""" # Helper code _ssh = mock.call connect = mock.Mock(side_effect=[ paramiko.AuthenticationException, paramiko.AuthenticationException, mock.Mock() ]) _ssh_inst = mock.Mock() _ssh_inst.attach_mock(connect, "connect") paramiko_ssh_client.return_value = _ssh_inst private_keys = gen_private_keys(2) # Test ssh = exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth(username=username, keys=private_keys)) paramiko_ssh_client.assert_called_once() auto_add_policy.assert_called_once() ssh_auth_logger.debug.assert_called_once_with( f"Main key has been updated, public key is: \n{ssh.auth.public_key}") kwargs_no_key = dict(hostname=host, pkey=None, port=port, username=username, password=None, key_filename=()) kwargs_key_0 = {key: kwargs_no_key[key] for key in kwargs_no_key} kwargs_key_0["pkey"] = private_keys[0] kwargs_key_1 = {key: kwargs_no_key[key] for key in kwargs_no_key} kwargs_key_1["pkey"] = private_keys[1] expected_calls = [ _ssh.set_missing_host_key_policy("AutoAddPolicy"), _ssh.connect(**kwargs_key_0), _ssh.connect(**kwargs_key_1), _ssh.connect(**kwargs_no_key), _ssh.get_transport(), _ssh.get_transport().set_keepalive(1), ] assert expected_calls == paramiko_ssh_client().mock_calls
def test_008_auth_impossible_key_no_verbose(paramiko_ssh_client, auto_add_policy, ssh_auth_logger): """Reject auth without log.""" # Helper code connect = mock.Mock(side_effect=paramiko.AuthenticationException) _ssh_inst = mock.Mock() _ssh_inst.attach_mock(connect, "connect") paramiko_ssh_client.return_value = _ssh_inst # Test with pytest.raises(paramiko.AuthenticationException): exec_helpers.SSHClient( host=host, auth=exec_helpers.SSHAuth(key=gen_private_keys(1).pop()), verbose=False) ssh_auth_logger.assert_not_called()
def test_003_password_required(paramiko_ssh_client, auto_add_policy, ssh_auth_logger): """No password provided.""" # Helper code connect = mock.Mock(side_effect=paramiko.PasswordRequiredException) _ssh_inst = mock.Mock() _ssh_inst.attach_mock(connect, "connect") paramiko_ssh_client.return_value = _ssh_inst private_keys = gen_private_keys(2) # Test with pytest.raises(paramiko.PasswordRequiredException): exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth(username=username, keys=private_keys)) ssh_auth_logger.assert_has_calls( (mock.call.exception("No password has been set!"), ))
def test_004_unexpected_password_required(paramiko_ssh_client, auto_add_policy, ssh_auth_logger): """Password available, but requested anyway.""" # Helper code connect = mock.Mock(side_effect=paramiko.PasswordRequiredException) _ssh_inst = mock.Mock() _ssh_inst.attach_mock(connect, "connect") paramiko_ssh_client.return_value = _ssh_inst private_keys = gen_private_keys(2) # Test with pytest.raises(paramiko.PasswordRequiredException): exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth(username=username, password=password, keys=private_keys)) ssh_auth_logger.assert_has_calls((mock.call.critical( "Unexpected PasswordRequiredException, when password is set!"), ))
def prepare_sftp_file_tests(client): _ssh = mock.Mock() client.return_value = _ssh _sftp = mock.Mock() open_sftp = mock.Mock(parent=_ssh, return_value=_sftp) _ssh.attach_mock(open_sftp, "open_sftp") with mock.patch("exec_helpers._ssh_helpers.SSH_CONFIG_FILE_SYSTEM", autospec=True) as conf_sys, mock.patch( "exec_helpers._ssh_helpers.SSH_CONFIG_FILE_USER", autospec=True) as conf_user: conf_sys.exists.return_value = False conf_user.exists.return_value = False # noinspection PyTypeChecker ssh = exec_helpers.SSHClient(host=host, port=port, auth=exec_helpers.SSHAuth( username=username, password=password)) return ssh, _sftp
def test_009_auth_pass_no_key(paramiko_ssh_client, auto_add_policy, ssh_auth_logger): """Reject key and use password.""" # Helper code connect = mock.Mock( side_effect=[paramiko.AuthenticationException, mock.Mock()]) _ssh_inst = mock.Mock() _ssh_inst.attach_mock(connect, "connect") paramiko_ssh_client.return_value = _ssh_inst key = gen_private_keys(1).pop() # Test ssh = exec_helpers.SSHClient(host=host, auth=exec_helpers.SSHAuth(username=username, password=password, key=key)) ssh_auth_logger.assert_has_calls((mock.call.debug( f"Main key has been updated, public key is: \n{ssh.auth.public_key}"), )) assert ssh.auth.public_key is None