Skip to content

Commit d79680a

Browse files
committed
Fix tests & move forward: simplify API
Also update copyrights
1 parent 030f2af commit d79680a

15 files changed

+62
-37
lines changed

exec_helpers/_ssh_client_base.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,9 @@ def __init__(
174174
port: typing.Optional[int] = None,
175175
username: typing.Optional[str] = None,
176176
password: typing.Optional[str] = None,
177+
*,
177178
private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]] = None,
178179
auth: typing.Optional[ssh_auth.SSHAuth] = None,
179-
*,
180180
verbose: bool = True,
181181
ssh_config: typing.Union[
182182
str,
@@ -186,6 +186,7 @@ def __init__(
186186
None,
187187
] = None,
188188
sock: typing.Optional[typing.Union[paramiko.ProxyCommand, paramiko.Channel, socket.socket]] = None,
189+
keepalive: bool = True,
189190
) -> None:
190191
"""Main SSH Client helper.
191192
@@ -214,25 +215,39 @@ def __init__(
214215
]
215216
:param sock: socket for connection. Useful for ssh proxies support
216217
:type sock: typing.Optional[typing.Union[paramiko.ProxyCommand, paramiko.Channel, socket.socket]]
218+
:param keepalive: keepalive mode
219+
:type keepalive: bool
217220
218221
.. note:: auth has priority over username/password/private_keys
222+
.. note::
223+
224+
for proxy connection auth information is collected from SSHConfig
225+
if ssh_auth_map record is not available
226+
227+
.. versionchanged:: 6.0.0 private_keys, auth and vebose became keyword-only arguments
228+
.. versionchanged:: 6.0.0 added optional ssh_config for ssh-proxy & low level connection parameters handling
229+
.. versionchanged:: 6.0.0 added optional sock for manual proxy chain handling
230+
.. versionchanged:: 6.0.0 keepalive exposed to constructor
219231
"""
220232
super(SSHClientBase, self).__init__(
221233
logger=logging.getLogger(self.__class__.__name__).getChild(f"({host}:{port})")
222234
)
223235

236+
# Init ssh config. It's main source for connection parameters
224237
if isinstance(ssh_config, HostsSSHConfigs):
225238
self.__ssh_config: HostsSSHConfigs = ssh_config
226239
else:
227240
self.__ssh_config = _ssh_helpers.parse_ssh_config(ssh_config, host)
228241

242+
# Get config. We are not resolving full chain. If you are have a chain by some reason - init config manually.
229243
config: SSHConfig = self.__ssh_config[host]
230244

245+
# Save resolved hostname and port
231246
self.__hostname: str = config.hostname
232247
self.__port: int = port if port is not None else config.port if config.port is not None else 22
233248

234249
self.__sudo_mode = False
235-
self.__keepalive_mode = True
250+
self.__keepalive_mode: bool = keepalive
236251
self.__verbose: bool = verbose
237252
self.__sock = sock
238253

@@ -688,9 +703,9 @@ def proxy_to(
688703
port: typing.Optional[int] = None,
689704
username: typing.Optional[str] = None,
690705
password: typing.Optional[str] = None,
706+
*,
691707
private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]] = None,
692708
auth: typing.Optional[ssh_auth.SSHAuth] = None,
693-
*,
694709
verbose: bool = True,
695710
ssh_config: typing.Union[
696711
str,
@@ -699,6 +714,7 @@ def proxy_to(
699714
HostsSSHConfigs,
700715
None,
701716
] = None,
717+
keepalive: bool = True,
702718
) -> "SSHClientBase":
703719
"""Start new SSH connection using current as proxy.
704720
@@ -725,6 +741,8 @@ def proxy_to(
725741
HostsSSHConfigs,
726742
None
727743
]
744+
:param keepalive: keepalive mode
745+
:type keepalive: bool
728746
:returns: new ssh client instance using current as a proxy
729747
:rtype: SSHClientBase
730748
@@ -751,6 +769,7 @@ def proxy_to(
751769
verbose=verbose,
752770
ssh_config=ssh_config,
753771
sock=sock,
772+
keepalive=keepalive,
754773
)
755774

756775
def execute_through_host(
@@ -814,9 +833,8 @@ def execute_through_host(
814833
auth = self.auth
815834

816835
with self.proxy_to( # type: ignore
817-
host=hostname, port=target_port, auth=auth, verbose=verbose, ssh_config=self.ssh_config
836+
host=hostname, port=target_port, auth=auth, verbose=verbose, ssh_config=self.ssh_config, keepalive=False
818837
) as conn:
819-
conn.keepalive_mode = False # pylint: disable=assigning-non-slot
820838
return conn.execute(
821839
command,
822840
timeout=timeout,

exec_helpers/ssh_auth.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ def __init__(
7878
for k in keys:
7979
if k not in self.__keys:
8080
self.__keys.append(k)
81-
self.__key_filename: typing.Union[typing.List[str], str, None] = key_filename
81+
if key_filename is None or isinstance(key_filename, list):
82+
self.__key_filename: typing.Optional[typing.List[str]] = key_filename
83+
else:
84+
self.__key_filename = [key_filename]
8285
self.__passphrase: typing.Optional[str] = passphrase
8386

8487
@property
@@ -113,7 +116,7 @@ def public_key(self) -> typing.Optional[str]:
113116
return self.__get_public_key(self.__key)
114117

115118
@property
116-
def key_filename(self) -> typing.Union[typing.List[str], str, None]:
119+
def key_filename(self) -> typing.Optional[typing.List[str]]:
117120
"""Key filename(s).
118121
119122
:returns: copy of used key filename (original should not be changed via mutability).
@@ -130,7 +133,7 @@ def enter_password(self, tgt: typing.BinaryIO) -> None:
130133
:type tgt: typing.BinaryIO
131134
"""
132135
# noinspection PyTypeChecker
133-
tgt.write("{}\n".format(self.__password if self.__password is not None else "").encode("utf-8"))
136+
tgt.write(f"{self.__password if self.__password is not None else ''}\n".encode("utf-8"))
134137

135138
def connect(
136139
self,

test/async_api/test_subprocess.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2018 Alexey Stepanov aka penguinolog.
1+
# Copyright 2018 - 2019 Alexey Stepanov aka penguinolog.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may
44
# not use this file except in compliance with the License. You may obtain

test/async_api/test_subprocess_special.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2018 Alexey Stepanov aka penguinolog.
1+
# Copyright 2018 - 2019 Alexey Stepanov aka penguinolog.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may
44
# not use this file except in compliance with the License. You may obtain

test/test_exec_result.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2018 Alexey Stepanov aka penguinolog.
1+
# Copyright 2018 - 2019 Alexey Stepanov aka penguinolog.
22

33
# Copyright 2016 Mirantis, Inc.
44
#

test/test_sftp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2018 Alexey Stepanov aka penguinolog.
1+
# Copyright 2018 - 2019 Alexey Stepanov aka penguinolog.
22

33
# Copyright 2016 Mirantis, Inc.
44
#

test/test_ssh_client_execute.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2018 Alexey Stepanov aka penguinolog.
1+
# Copyright 2018 - 2019 Alexey Stepanov aka penguinolog.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may
44
# not use this file except in compliance with the License. You may obtain
@@ -357,17 +357,19 @@ def test_003_context_manager(ssh, exec_result, run_parameters, mocker) -> None:
357357
if "height" in run_parameters:
358358
kwargs["height"] = run_parameters["height"]
359359

360-
with mocker.patch("threading.RLock") as lock:
361-
with ssh:
362-
res = ssh.execute(
363-
command,
364-
stdin=run_parameters["stdin"],
365-
open_stdout=run_parameters["open_stdout"],
366-
open_stderr=run_parameters["open_stderr"],
367-
**kwargs,
368-
)
369-
lock.acquire_assert_called_once()
370-
lock.release_assert_called_once()
360+
lock_mock = mocker.patch("threading.RLock")
361+
362+
with ssh:
363+
res = ssh.execute(
364+
command,
365+
stdin=run_parameters["stdin"],
366+
open_stdout=run_parameters["open_stdout"],
367+
open_stderr=run_parameters["open_stderr"],
368+
**kwargs,
369+
)
370+
lock_mock.acquire_assert_called_once()
371+
lock_mock.release_assert_called_once()
372+
371373
assert isinstance(res, exec_helpers.ExecResult)
372374
assert res == exec_result
373375

test/test_ssh_client_execute_async_special.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2018 Alexey Stepanov aka penguinolog.
1+
# Copyright 2018 - 2019 Alexey Stepanov aka penguinolog.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may
44
# not use this file except in compliance with the License. You may obtain

test/test_ssh_client_execute_special.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2018 Alexey Stepanov aka penguinolog.
1+
# Copyright 2018 - 2019 Alexey Stepanov aka penguinolog.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may
44
# not use this file except in compliance with the License. You may obtain

test/test_ssh_client_execute_throw_host.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2018 Alexey Stepanov aka penguinolog.
1+
# Copyright 2018 - 2019 Alexey Stepanov aka penguinolog.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License"); you may
44
# not use this file except in compliance with the License. You may obtain

0 commit comments

Comments
 (0)