Skip to content

Commit 0794bc6

Browse files
committed
Align ssh username for logger with paramiko
* Fix code duplication & several SonarLint warnings
1 parent f928a5c commit 0794bc6

File tree

8 files changed

+46
-60
lines changed

8 files changed

+46
-60
lines changed

exec_helpers/_ssh_client_base.py

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@
2424
import datetime
2525
import getpass
2626
import logging
27-
import os
2827
import shlex
2928
import socket
3029
import stat
31-
import sys
3230
import time
3331
import typing
3432
import warnings
@@ -238,7 +236,7 @@ def __init__(
238236
for proxy connection auth information is collected from SSHConfig
239237
if ssh_auth_map record is not available
240238
241-
.. versionchanged:: 6.0.0 private_keys, auth and vebose became keyword-only arguments
239+
.. versionchanged:: 6.0.0 private_keys, auth and verbose became keyword-only arguments
242240
.. versionchanged:: 6.0.0 added optional ssh_config for ssh-proxy & low level connection parameters handling
243241
.. versionchanged:: 6.0.0 added optional ssh_auth_map for ssh proxy cases with authentication on each step
244242
.. versionchanged:: 6.0.0 added optional sock for manual proxy chain handling
@@ -263,7 +261,10 @@ def __init__(
263261

264262
# Save resolved hostname and port
265263
self.__hostname: str = config.hostname
266-
self.__port: int = port if port is not None else config.port if config.port is not None else 22
264+
if port is not None:
265+
self.__port: int = port
266+
else:
267+
self.__port = config.port if config.port is not None else 22
267268

268269
# Store initial auth mapping
269270
self.__auth_mapping = ssh_auth.SSHAuthMapping(ssh_auth_map)
@@ -295,11 +296,11 @@ def __init__(
295296

296297
# Init super with host and real port and username
297298
mod_name = "exec_helpers" if self.__module__.startswith("exec_helpers") else self.__module__
298-
log_username: typing.Optional[str] = self.__get_user_for_log(real_auth)
299+
log_username: str = real_auth.username if real_auth.username is not None else getpass.getuser()
299300

300-
super(SSHClientBase, self).__init__(
301+
super().__init__(
301302
logger=logging.getLogger(f"{mod_name}.{self.__class__.__name__}").getChild(
302-
f"({log_username}@{host}:{self.__port})"
303+
f"({log_username}@{host}:{self.port})"
303304
)
304305
)
305306

@@ -315,21 +316,6 @@ def __init__(
315316

316317
self.__connect()
317318

318-
@staticmethod
319-
def __get_user_for_log(real_auth: ssh_auth.SSHAuth) -> typing.Optional[str]: # pragma: no cover
320-
if real_auth.username is not None:
321-
return real_auth.username
322-
# noinspection PyBroadException
323-
try:
324-
if sys.platform != "win32":
325-
import pwd # pylint: disable=import-outside-toplevel,import-error
326-
327-
uid: int = os.getuid() # pylint: disable=no-member
328-
return pwd.getpwuid(uid).pw_name # Correct for not windows only
329-
return getpass.getuser()
330-
except Exception:
331-
return None
332-
333319
def __rebuild_ssh_config(self) -> None:
334320
"""Rebuild main ssh config from available information."""
335321
self.__ssh_config[self.hostname] = self.__ssh_config[self.hostname].overridden_by(
@@ -1116,7 +1102,10 @@ def _get_proxy_channel(self, port: typing.Optional[int], ssh_config: SSHConfig,)
11161102
11171103
.. versionadded:: 6.0.0
11181104
"""
1119-
dest_port: int = port if port is not None else ssh_config.port if ssh_config.port is not None else 22
1105+
if port is not None:
1106+
dest_port: int = port
1107+
else:
1108+
dest_port = ssh_config.port if ssh_config.port is not None else 22
11201109

11211110
return self._ssh.get_transport().open_channel(
11221111
kind="direct-tcpip", dest_addr=(ssh_config.hostname, dest_port), src_addr=(self.hostname, 0)
@@ -1257,8 +1246,7 @@ def execute_through_host(
12571246

12581247
if target_port is not None: # pragma: no cover
12591248
warnings.warn(
1260-
f'"target_port" argument was renamed to "port". '
1261-
f"Old version will be droppped in the next major release.",
1249+
f'"target_port" argument was renamed to "port". Old version will be dropped in the next major release.',
12621250
DeprecationWarning,
12631251
)
12641252
port = target_port
@@ -1343,13 +1331,8 @@ def get_result(remote: "SSHClientBase") -> exec_result.ExecResult:
13431331
"""
13441332
# pylint: disable=protected-access
13451333
cmd_for_log: str = remote._mask_command(cmd=command, log_mask_re=log_mask_re)
1334+
remote._log_command_execute(command=command, log_mask_re=log_mask_re, log_level=log_level, **kwargs)
13461335

1347-
target_path: typing.Optional[str] = kwargs.get("chroot_path", remote._chroot_path)
1348-
chroot_info: str = "" if not target_path or target_path == "/" else f" (with chroot to: {target_path!r})"
1349-
1350-
remote.logger.log(
1351-
level=log_level, msg=f"Executing command{chroot_info}:\n{cmd_for_log!r}\n",
1352-
)
13531336
async_result: SshExecuteAsyncResult = remote._execute_async(
13541337
command,
13551338
stdin=stdin,

exec_helpers/api.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
_OptionalTimeoutT = typing.Union[int, float, None]
5151
_OptionalStdinT = typing.Union[bytes, str, bytearray, None]
5252
_ExitCodeT = typing.Union[int, proc_enums.ExitCodes]
53+
_CalledProcessErrorSubClass = typing.Type[exceptions.CalledProcessError]
5354

5455

5556
# noinspection PyProtectedMember
@@ -323,6 +324,21 @@ def _exec_command(
323324
.. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd
324325
"""
325326

327+
def _log_command_execute(
328+
self,
329+
command: str,
330+
log_mask_re: typing.Union[str, None],
331+
log_level: int,
332+
chroot_path: typing.Union[str, None],
333+
**_: typing.Any,
334+
) -> None:
335+
"""Log command execution."""
336+
cmd_for_log: str = self._mask_command(cmd=command, log_mask_re=log_mask_re)
337+
target_path: typing.Optional[str] = chroot_path if chroot_path is not None else self._chroot_path
338+
chroot_info: str = "" if not target_path or target_path == "/" else f" (with chroot to: {target_path!r})"
339+
340+
self.logger.log(level=log_level, msg=f"Executing command{chroot_info}:\n{cmd_for_log!r}\n")
341+
326342
def execute(
327343
self,
328344
command: str,
@@ -361,16 +377,8 @@ def execute(
361377
.. versionchanged:: 1.2.0 default timeout 1 hour
362378
.. versionchanged:: 2.1.0 Allow parallel calls
363379
"""
364-
cmd_for_log: str = self._mask_command(cmd=command, log_mask_re=log_mask_re)
365380
log_level: int = logging.INFO if verbose else logging.DEBUG
366-
367-
target_path: typing.Optional[str] = kwargs.get("chroot_path", self._chroot_path)
368-
chroot_info: str = "" if not target_path or target_path == "/" else f" (with chroot to: {target_path!r})"
369-
370-
self.logger.log(
371-
level=log_level, msg=f"Executing command{chroot_info}:\n{cmd_for_log!r}\n",
372-
)
373-
381+
self._log_command_execute(command=command, log_mask_re=log_mask_re, log_level=log_level, **kwargs)
374382
async_result: ExecuteAsyncResult = self._execute_async(
375383
command,
376384
verbose=verbose,
@@ -454,7 +462,7 @@ def check_call(
454462
stdin: _OptionalStdinT = None,
455463
open_stdout: bool = True,
456464
open_stderr: bool = True,
457-
exception_class: "typing.Type[exceptions.CalledProcessError]" = exceptions.CalledProcessError,
465+
exception_class: _CalledProcessErrorSubClass = exceptions.CalledProcessError,
458466
**kwargs: typing.Any,
459467
) -> exec_result.ExecResult:
460468
"""Execute command and check for return code.
@@ -528,7 +536,7 @@ def check_stderr(
528536
stdin: _OptionalStdinT = None,
529537
open_stdout: bool = True,
530538
open_stderr: bool = True,
531-
exception_class: "typing.Type[exceptions.CalledProcessError]" = exceptions.CalledProcessError,
539+
exception_class: _CalledProcessErrorSubClass = exceptions.CalledProcessError,
532540
**kwargs: typing.Any,
533541
) -> exec_result.ExecResult:
534542
"""Execute command expecting return code 0 and empty STDERR.
@@ -595,7 +603,7 @@ def _handle_stderr(
595603
error_info: typing.Optional[str],
596604
raise_on_err: bool,
597605
expected: typing.Iterable[_ExitCodeT],
598-
exception_class: "typing.Type[exceptions.CalledProcessError]",
606+
exception_class: _CalledProcessErrorSubClass,
599607
) -> exec_result.ExecResult:
600608
"""Internal check_stderr logic (synchronous)."""
601609
append: str = error_info + "\n" if error_info else ""

exec_helpers/async_api/api.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -219,15 +219,8 @@ async def execute( # type: ignore
219219
:rtype: ExecResult
220220
:raises ExecHelperTimeoutError: Timeout exceeded
221221
"""
222-
cmd_for_log: str = self._mask_command(cmd=command, log_mask_re=log_mask_re)
223222
log_level: int = logging.INFO if verbose else logging.DEBUG
224-
225-
target_path: typing.Optional[str] = kwargs.get("chroot_path", self._chroot_path)
226-
chroot_info: str = "" if not target_path or target_path == "/" else f" (with chroot to: {target_path!r})"
227-
228-
self.logger.log(
229-
level=log_level, msg=f"Executing command{chroot_info}:\n{cmd_for_log!r}\n",
230-
)
223+
self._log_command_execute(command=command, log_mask_re=log_mask_re, log_level=log_level, **kwargs)
231224

232225
async_result: api.ExecuteAsyncResult = await self._execute_async(
233226
command,

exec_helpers/async_api/subprocess_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def interface(self) -> asyncio.subprocess.Process: # type: ignore
6161
@property
6262
def stdin(self) -> typing.Optional[asyncio.StreamWriter]: # type: ignore
6363
"""Override original NamedTuple with proper typing."""
64-
return super(SubprocessExecuteAsyncResult, self).stdin # type: ignore
64+
return super().stdin # type: ignore
6565

6666
@property
6767
def stderr(self) -> typing.Optional[typing.AsyncIterable[bytes]]: # type: ignore
@@ -273,7 +273,7 @@ async def _execute_async( # type: ignore # pylint: disable=arguments-differ
273273
process.stdin.close() # type: ignore
274274
except OSError as exc:
275275
if exc.errno in (errno.EINVAL, errno.EPIPE, errno.ESHUTDOWN):
276-
pass
276+
pass # PIPE already closed
277277
else:
278278
process.kill()
279279
raise

exec_helpers/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def __init__(self, result: "exec_result.ExecResult", timeout: typing.Union[int,
142142
:type timeout: typing.Union[int, float]
143143
"""
144144
message: str = _log_templates.CMD_WAIT_ERROR.format(result=result, timeout=timeout)
145-
super(ExecHelperTimeoutError, self).__init__(message, result=result, timeout=timeout)
145+
super().__init__(message, result=result, timeout=timeout)
146146

147147

148148
class CalledProcessError(ExecCalledProcessError):

exec_helpers/exec_result.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ def stdout_yaml(self) -> typing.Any:
556556
with self.stdout_lock:
557557
return self.__deserialize(fmt="yaml")
558558

559+
# noinspection PyUnresolvedReferences
559560
@property
560561
def stdout_xml(self) -> "xml.etree.ElementTree.Element":
561562
"""XML from stdout.

exec_helpers/ssh_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def download(self, destination: str, target: str) -> bool:
115115
if self.exists(destination):
116116
self._sftp.get(destination, target)
117117
else:
118-
self.logger.debug(f"Can't download {destination} because it doesn't exist")
118+
self.logger.debug(f"Can't download {destination} because it does not exist")
119119
else:
120120
self.logger.debug(f"Can't download {destination} because it is a directory")
121121
return os.path.exists(target)

exec_helpers/subprocess_runner.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
_OptionalTimeoutT = typing.Union[int, float, None]
4848
_OptionalStdinT = typing.Union[bytes, str, bytearray, None]
4949
_ExitCodeT = typing.Union[int, proc_enums.ExitCodes]
50+
_OptionalIOBytes = typing.Optional[typing.IO[bytes]]
5051

5152

5253
# noinspection PyTypeHints
@@ -59,17 +60,17 @@ def interface(self) -> "subprocess.Popen[bytes]":
5960
return super().interface # type: ignore
6061

6162
@property
62-
def stdin(self) -> "typing.Optional[typing.IO[bytes]]": # type: ignore
63+
def stdin(self) -> _OptionalIOBytes: # type: ignore
6364
"""Override original NamedTuple with proper typing."""
64-
return super(SubprocessExecuteAsyncResult, self).stdin
65+
return super().stdin
6566

6667
@property
67-
def stderr(self) -> "typing.Optional[typing.IO[bytes]]": # type: ignore
68+
def stderr(self) -> _OptionalIOBytes: # type: ignore
6869
"""Override original NamedTuple with proper typing."""
6970
return super().stderr
7071

7172
@property
72-
def stdout(self) -> "typing.Optional[typing.IO[bytes]]": # type: ignore
73+
def stdout(self) -> _OptionalIOBytes: # type: ignore
7374
"""Override original NamedTuple with proper typing."""
7475
return super().stdout
7576

@@ -287,7 +288,7 @@ def _execute_async( # pylint: disable=arguments-differ
287288
process.stdin.close()
288289
except OSError as exc:
289290
if exc.errno in (errno.EINVAL, errno.EPIPE, errno.ESHUTDOWN):
290-
pass
291+
pass # PIPE already closed
291292
else:
292293
process.kill()
293294
raise

0 commit comments

Comments
 (0)