Skip to content

Commit

Permalink
Merge 0ae00dc into 8c80864
Browse files Browse the repository at this point in the history
  • Loading branch information
penguinolog committed Nov 13, 2019
2 parents 8c80864 + 0ae00dc commit 6f2786f
Show file tree
Hide file tree
Showing 15 changed files with 705 additions and 104 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pythonpackage.yml
Expand Up @@ -29,4 +29,4 @@ jobs:
- name: Test with pytest
run: |
py.test --cov-config .coveragerc --cov-report= --cov=exec_helpers test
coverage report -m --fail-under 87
coverage report -m --fail-under 85
4 changes: 2 additions & 2 deletions .travis.yml
Expand Up @@ -50,8 +50,8 @@ _helpers:

script:
- python setup.py develop -v
- py.test --cov-config .coveragerc --cov-report= --cov=exec_helpers test
- coverage report -m --fail-under 87
- py.test --cov-config .coveragerc --cov=exec_helpers test

after_success:
- coveralls

Expand Down
10 changes: 9 additions & 1 deletion doc/source/SSHClient.rst
Expand Up @@ -10,7 +10,7 @@ API: SSHClient and SSHAuth.
SSHClient helper.

.. py:method:: __init__(host, port=22, username=None, password=None, private_keys=None, auth=None)
.. py:method:: __init__(host, port=22, username=None, password=None, private_keys=None, auth=None, *, verbose=True, ssh_config=None)
:param host: remote hostname
:type host: ``str``
Expand All @@ -26,6 +26,14 @@ API: SSHClient and SSHAuth.
:type auth: typing.Optional[SSHAuth]
:param verbose: show additional error/warning messages
:type verbose: bool
:param ssh_config: SSH configuration for connection. Maybe config path, parsed as dict and paramiko parsed.
:type ssh_config:
typing.Union[
str,
paramiko.SSHConfig,
typing.Dict[str, typing.Dict[str, typing.Union[str, int, bool, typing.List[str]]]],
None
]

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

Expand Down
60 changes: 52 additions & 8 deletions exec_helpers/_ssh_client_base.py
Expand Up @@ -35,6 +35,7 @@

# Exec-Helpers Implementation
from exec_helpers import _log_templates
from exec_helpers import _ssh_helpers
from exec_helpers import api
from exec_helpers import constants
from exec_helpers import exceptions
Expand All @@ -43,7 +44,6 @@
from exec_helpers import ssh_auth

logging.getLogger("paramiko").setLevel(logging.WARNING)
logging.getLogger("iso8601").setLevel(logging.WARNING)


class RetryOnExceptions(tenacity.retry_if_exception): # type: ignore
Expand Down Expand Up @@ -146,7 +146,17 @@ def __exit__(self, exc_type: typing.Any, exc_val: typing.Any, exc_tb: typing.Any
class SSHClientBase(api.ExecHelper):
"""SSH Client helper."""

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

def __hash__(self) -> int:
"""Hash for usage as dict keys."""
Expand All @@ -155,19 +165,26 @@ def __hash__(self) -> int:
def __init__(
self,
host: str,
port: int = 22,
port: typing.Optional[int] = None,
username: typing.Optional[str] = None,
password: typing.Optional[str] = None,
private_keys: typing.Optional[typing.Iterable[paramiko.RSAKey]] = None,
auth: typing.Optional[ssh_auth.SSHAuth] = None,
*,
verbose: bool = True,
ssh_config: typing.Union[
str,
paramiko.SSHConfig,
typing.Dict[str, typing.Dict[str, typing.Union[str, int, bool, typing.List[str]]]],
None,
] = None,
) -> None:
"""Main SSH Client helper.
:param host: remote hostname
:type host: str
:param port: remote ssh port
:type port: int
:type port: typing.Optional[int]
:param username: remote username.
:type username: typing.Optional[str]
:param password: remote password
Expand All @@ -178,15 +195,27 @@ def __init__(
:type auth: typing.Optional[ssh_auth.SSHAuth]
:param verbose: show additional error/warning messages
:type verbose: bool
:param ssh_config: SSH configuration for connection. Maybe config path, parsed as dict and paramiko parsed.
:type ssh_config:
typing.Union[
str,
paramiko.SSHConfig,
typing.Dict[str, typing.Dict[str, typing.Union[str, int, bool, typing.List[str]]]],
None
]
.. note:: auth has priority over username/password/private_keys
"""
super(SSHClientBase, self).__init__(
logger=logging.getLogger(self.__class__.__name__).getChild(f"({host}:{port})")
)

self.__hostname: str = host
self.__port: int = port
self.__ssh_config: typing.Dict[str, _ssh_helpers.SSHConfig] = _ssh_helpers.parse_ssh_config(ssh_config, host)

config: _ssh_helpers.SSHConfig = self.__ssh_config[host]

self.__hostname: str = config.hostname
self.__port: int = port if port is not None else config.port if config.port is not None else 22

self.__sudo_mode = False
self.__keepalive_mode = True
Expand All @@ -197,7 +226,12 @@ def __init__(
self.__sftp: typing.Optional[paramiko.SFTPClient] = None

if auth is None:
self.__auth = ssh_auth.SSHAuth(username=username, password=password, keys=private_keys)
self.__auth = ssh_auth.SSHAuth(
username=username if username is not None else config.user,
password=password,
keys=private_keys,
key_filename=config.identityfile,
)
else:
self.__auth = copy.copy(auth)

Expand Down Expand Up @@ -232,6 +266,14 @@ def port(self) -> int:
"""
return self.__port

@property
def ssh_config(self) -> typing.Dict[str, _ssh_helpers.SSHConfig]:
"""SSH connection config.
:rtype: typing.Dict[str, _ssh_helpers.SSHConfig]
"""
return copy.deepcopy(self.__ssh_config)

@property
def is_alive(self) -> bool:
"""Paramiko status: ready to use|reconnect required.
Expand Down Expand Up @@ -268,7 +310,9 @@ def _ssh(self) -> paramiko.SSHClient:
def __connect(self) -> None:
"""Main method for connection open."""
with self.lock:
self.auth.connect(client=self.__ssh, hostname=self.hostname, port=self.port, log=self.__verbose)
self.auth.connect(
client=self.__ssh, hostname=self.hostname, port=self.port, log=self.__verbose,
)

def __connect_sftp(self) -> None:
"""SFTP connection opener."""
Expand Down

0 comments on commit 6f2786f

Please sign in to comment.