lib.ec.ssh.AsyncSSH: Code beautification

- Remove _run_on_conn() because it doesn't add any value

  - Add verbose try-except block around connect()

  - Add try-except block around failing close

  - Prefix private member variables with "__"

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2026-04-19 19:52:18 +02:00
commit 8210baa683

View file

@ -29,68 +29,37 @@ class AsyncSSH(Base):
**kwargs **kwargs
) )
self.client_keys = client_keys self.__client_keys = client_keys
self.known_hosts = known_hosts self.__known_hosts = known_hosts
self.term_type = term_type or os.environ.get('TERM', 'xterm') self.__term_type = term_type or os.environ.get('TERM', 'xterm')
self.connect_timeout = connect_timeout self.__connect_timeout = connect_timeout
self.__conn: asyncssh.SSHClientConnection|None = None self.__conn: asyncssh.SSHClientConnection|None = None
def _connect_kwargs(self, hide_secrets: bool=False) -> dict: async def _close(self) -> None:
if self.__conn is not None:
try:
self.__conn.close()
await self.__conn.wait_closed()
except Exception as e:
log(DEBUG, f'Failed to close connection ({str(e)}, ignored)')
self.__conn = None
def _connect_kwargs(self, hide_secrets: bool=False) -> dict:
kwargs: dict = { kwargs: dict = {
'host': self.hostname, 'host': self.hostname,
'port': self.port, 'port': self.port,
'username': self.username, 'username': self.username,
'password': self.password, 'password': self.password,
'client_keys': self.client_keys, 'client_keys': self.__client_keys,
'connect_timeout': self.connect_timeout, 'connect_timeout': self.__connect_timeout,
} }
if self.__known_hosts is not _USE_DEFAULT_KNOWN_HOSTS:
if self.known_hosts is not _USE_DEFAULT_KNOWN_HOSTS: kwargs['known_hosts'] = self.__known_hosts
kwargs['known_hosts'] = self.known_hosts
ret = {k: v for k, v in kwargs.items() if v is not None} ret = {k: v for k, v in kwargs.items() if v is not None}
if hide_secrets and 'password' in kwargs: if hide_secrets and 'password' in kwargs:
kwargs['password'] = '<hidden>' kwargs['password'] = '<hidden>'
return ret return ret
@staticmethod
def _build_remote_command(cmd: list[str], wd: str | None) -> str:
if not cmd:
raise ValueError('cmd must not be empty')
inner = f'exec {join_cmd(cmd)}'
if wd is not None:
inner = f'cd {shlex.quote(wd)} && {inner}'
return f'/bin/sh -lc {shlex.quote(inner)}'
@staticmethod
def _has_local_tty() -> bool:
try:
return sys.stdin.isatty() and sys.stdout.isatty()
except Exception:
return False
@staticmethod
def _get_local_term_size() -> tuple[int, int, int, int]:
cols, rows = shutil.get_terminal_size(fallback=(80, 24))
xpixel = ypixel = 0
try:
import fcntl, termios, struct
packed = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, b'\0' * 8)
rows2, cols2, xpixel, ypixel = struct.unpack('HHHH', packed)
if cols2 > 0 and rows2 > 0:
cols, rows = cols2, rows2
except Exception:
pass
return (cols, rows, xpixel, ypixel)
@property @property
async def _conn(self) -> asyncssh.SSHClientConnection: async def _conn(self) -> asyncssh.SSHClientConnection:
if self.__conn is None: if self.__conn is None:
@ -105,14 +74,33 @@ class AsyncSSH(Base):
raise raise
return self.__conn return self.__conn
async def _close(self) -> None: @staticmethod
if self.__conn is not None: def _build_remote_command(cmd: list[str], wd: str | None) -> str:
inner = f'exec {join_cmd(cmd)}'
if wd is not None:
inner = f'cd {shlex.quote(wd)} && {inner}'
return f'/bin/sh -lc {shlex.quote(inner)}'
@staticmethod
def _has_local_tty() -> bool:
try: try:
self.__conn.close() return sys.stdin.isatty() and sys.stdout.isatty()
await self.__conn.wait_closed() except Exception:
except Exception as e: return False
log(DEBUG, f'Failed to close connection ({str(e)}, ignored)')
self.__conn = None @staticmethod
def _get_local_term_size() -> tuple[int, int, int, int]:
cols, rows = shutil.get_terminal_size(fallback=(80, 24))
xpixel = ypixel = 0
try:
import fcntl, termios, struct
packed = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, b'\0' * 8)
rows2, cols2, xpixel, ypixel = struct.unpack('HHHH', packed)
if cols2 > 0 and rows2 > 0:
cols, rows = cols2, rows2
except Exception:
pass
return (cols, rows, xpixel, ypixel)
async def _read_stream( async def _read_stream(
self, self,
@ -125,20 +113,16 @@ class AsyncSSH(Base):
log_enc: str, log_enc: str,
) -> None: ) -> None:
buf = b'' buf = b''
while True: while True:
chunk = await stream.read(4096) chunk = await stream.read(4096)
if not chunk: if not chunk:
break break
collector.append(chunk) collector.append(chunk)
if verbose: if verbose:
buf += chunk buf += chunk
while b'\n' in buf: while b'\n' in buf:
line, buf = buf.split(b'\n', 1) line, buf = buf.split(b'\n', 1)
log(prio, log_prefix, line.decode(log_enc, errors='replace')) log(prio, log_prefix, line.decode(log_enc, errors='replace'))
if verbose and buf: if verbose and buf:
log(prio, log_prefix, buf.decode(log_enc, errors='replace')) log(prio, log_prefix, buf.decode(log_enc, errors='replace'))
@ -162,7 +146,7 @@ class AsyncSSH(Base):
stderr = asyncssh.STDOUT, stderr = asyncssh.STDOUT,
encoding = None, encoding = None,
request_pty = 'force', request_pty = 'force',
term_type = self.term_type, term_type = self.__term_type,
term_size = self._get_local_term_size(), term_size = self._get_local_term_size(),
) )
@ -324,7 +308,7 @@ class AsyncSSH(Base):
stderr = asyncssh.STDOUT, stderr = asyncssh.STDOUT,
encoding = None, encoding = None,
request_pty = 'force', request_pty = 'force',
term_type = self.term_type, term_type = self.__term_type,
) )
task = asyncio.create_task( task = asyncio.create_task(
@ -353,16 +337,19 @@ class AsyncSSH(Base):
stdout = b''.join(stdout_parts) if stdout_parts else None stdout = b''.join(stdout_parts) if stdout_parts else None
return Result(stdout, None, exit_code) return Result(stdout, None, exit_code)
async def _run_on_conn( async def _run_ssh(
self, self,
cmd: list[str], cmd: list[str],
wd: str | None, wd: str | None,
verbose: bool, verbose: bool,
cmd_input: bytes | None, cmd_input: str | None,
mod_env: dict[str, str] | None, mod_env: dict[str, str] | None,
interactive: bool, interactive: bool,
log_prefix: str, log_prefix: str,
) -> Result: ) -> Result:
try:
if interactive: if interactive:
if self._has_local_tty(): if self._has_local_tty():
return await self._run_interactive_on_conn( return await self._run_interactive_on_conn(
@ -371,7 +358,6 @@ class AsyncSSH(Base):
cmd_input = cmd_input, cmd_input = cmd_input,
mod_env = mod_env, mod_env = mod_env,
) )
return await self._run_captured_pty_on_conn( return await self._run_captured_pty_on_conn(
cmd = cmd, cmd = cmd,
wd = wd, wd = wd,
@ -443,26 +429,6 @@ class AsyncSSH(Base):
return Result(stdout, stderr, exit_code) return Result(stdout, stderr, exit_code)
async def _run_ssh(
self,
cmd: list[str],
wd: str | None,
verbose: bool,
cmd_input: str | None,
mod_env: dict[str, str] | None,
interactive: bool,
log_prefix: str,
) -> Result:
try:
return await self._run_on_conn(
cmd = cmd,
wd = wd,
verbose = verbose,
cmd_input = cmd_input,
mod_env = mod_env,
interactive = interactive,
log_prefix = log_prefix,
)
except Exception as e: except Exception as e:
log(ERR, f'Failed to run command {" ".join(cmd)} ({e})' log(ERR, f'Failed to run command {" ".join(cmd)} ({e})')
raise raise