2026-03-20 13:28:33 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
import paramiko # type: ignore # error: Library stubs not installed for "paramiko"
|
|
|
|
|
|
|
|
|
|
from ...log import *
|
|
|
|
|
from ...ExecContext import Result
|
|
|
|
|
from ..SSHClient import SSHClient as Base
|
|
|
|
|
|
2026-04-10 14:58:29 +02:00
|
|
|
from .util import join_cmd
|
|
|
|
|
|
2026-04-10 13:56:31 +02:00
|
|
|
class Paramiko(Base):
|
2026-03-20 13:28:33 +01:00
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs) -> None:
|
|
|
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
self.__timeout: float|None = None # Untested
|
|
|
|
|
self.___ssh: Any|None = None
|
|
|
|
|
|
|
|
|
|
def __ssh_connect(self):
|
|
|
|
|
ret = paramiko.SSHClient()
|
|
|
|
|
ret.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
|
|
|
try:
|
|
|
|
|
ret.connect(
|
|
|
|
|
hostname=self.hostname,
|
|
|
|
|
username=self.username,
|
|
|
|
|
allow_agent=True
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
2026-03-30 13:39:48 +02:00
|
|
|
log(ERR, f'Failed to connect to {self.hostname} ({str(e)})')
|
2026-03-20 13:28:33 +01:00
|
|
|
raise
|
|
|
|
|
s = ret.get_transport().open_session()
|
|
|
|
|
# set up the agent request handler to handle agent requests from the server
|
|
|
|
|
paramiko.agent.AgentRequestHandler(s)
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def __ssh(self):
|
|
|
|
|
if self.___ssh is None:
|
|
|
|
|
self.___ssh = self.__ssh_connect()
|
|
|
|
|
return self.___ssh
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def __scp(self):
|
|
|
|
|
return SCPClient(self.__ssh.get_transport())
|
|
|
|
|
|
2026-03-21 03:41:10 +01:00
|
|
|
async def _run_ssh(self, cmd: list[str], cmd_input: str|None, *args, **kwargs) -> Result:
|
2026-03-20 13:28:33 +01:00
|
|
|
try:
|
2026-04-10 14:58:29 +02:00
|
|
|
stdin, stdout, stderr = self.__ssh.exec_command(join_cmd(cmd), timeout=self.__timeout)
|
2026-03-20 13:28:33 +01:00
|
|
|
except Exception as e:
|
2026-04-10 14:58:29 +02:00
|
|
|
log(ERR, f'Command failed for {self.uri}: "{join_cmd(cmd)}"')
|
2026-03-20 13:28:33 +01:00
|
|
|
raise
|
|
|
|
|
if cmd_input is not None:
|
|
|
|
|
stdin.write(cmd_input)
|
|
|
|
|
exit_status = stdout.channel.recv_exit_status()
|
|
|
|
|
return Result(stdout.read(), stderr.read(), exit_status)
|
|
|
|
|
|
|
|
|
|
|