Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 20d48c4

Browse files
committed
driver-ssh: SSHWrapperClient.run return stdout and stderr
Add SSHCommandExecResult to wrap the subprocess SSH command return and use it as return type for SSHWrapperClient.run, so that the output of the command can be inspected after the call. Signed-off-by: Albert Esteve <aesteve@redhat.com>
1 parent 66691f4 commit 20d48c4

2 files changed

Lines changed: 98 additions & 40 deletions

File tree

packages/jumpstarter-driver-ssh/jumpstarter_driver_ssh/client.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,22 @@
1313
from jumpstarter.client.decorators import driver_click_command
1414

1515

16+
@dataclass
17+
class SSHCommandRunResult:
18+
"""Result of executing an SSH command"""
19+
return_code: int
20+
stdout: str | bytes
21+
stderr: str | bytes
22+
23+
@staticmethod
24+
def from_completed_process(result: subprocess.CompletedProcess) -> "SSHCommandRunResult":
25+
return SSHCommandRunResult(
26+
return_code=result.returncode,
27+
stdout=result.stdout or "",
28+
stderr=result.stderr or "",
29+
)
30+
31+
1632
@dataclass(kw_only=True)
1733
class SSHWrapperClient(CompositeClient):
1834
"""
@@ -31,10 +47,17 @@ def cli(self):
3147
@click.argument("args", nargs=-1)
3248
def ssh(direct, args):
3349
result = self.run(direct, args)
34-
self.logger.debug(f"SSH result: {result}")
35-
if result != 0:
36-
click.get_current_context().exit(result)
37-
return result
50+
self.logger.debug("SSH exit code: %s", result.return_code)
51+
52+
if result.stdout:
53+
click.echo(result.stdout, nl=False)
54+
if result.stderr:
55+
click.echo(result.stderr, nl=False, err=True)
56+
57+
if result.return_code != 0:
58+
click.get_current_context().exit(result.return_code)
59+
60+
return result.return_code
3861

3962
return ssh
4063

@@ -46,7 +69,7 @@ def stream(self, method="connect"):
4669
async def stream_async(self, method):
4770
return await self.tcp.stream_async(method)
4871

49-
def run(self, direct, args):
72+
def run(self, direct, args) -> SSHCommandRunResult:
5073
"""Run SSH command with the given parameters and arguments"""
5174
# Get SSH command and default username from driver
5275
ssh_command = self.call("get_ssh_command")
@@ -73,8 +96,7 @@ def run(self, direct, args):
7396
with TcpPortforwardAdapter(
7497
client=self.tcp,
7598
) as addr:
76-
host = addr[0]
77-
port = addr[1]
99+
host, port = addr
78100
self.logger.debug(f"SSH port forward established - host: {host}, port: {port}")
79101
return self._run_ssh_local(host, port, ssh_command, default_username, ssh_identity, args)
80102

@@ -212,13 +234,17 @@ def _build_final_ssh_command(self, ssh_args, ssh_options, host, command_args):
212234
self.logger.debug(f"Running SSH command: {ssh_args}")
213235
return ssh_args
214236

215-
def _execute_ssh_command(self, ssh_args):
237+
def _execute_ssh_command(self, ssh_args) -> SSHCommandRunResult:
216238
"""Execute the SSH command and return the result"""
217239
try:
218-
result = subprocess.run(ssh_args)
219-
return result.returncode
240+
result = subprocess.run(ssh_args, capture_output=True, text=True)
241+
return SSHCommandRunResult.from_completed_process(result)
220242
except FileNotFoundError:
221243
self.logger.error(
222244
f"SSH command '{ssh_args[0]}' not found. Please ensure SSH is installed and available in PATH."
223245
)
224-
return 127 # Standard exit code for "command not found"
246+
return SSHCommandRunResult(
247+
return_code=127, # Standard exit code for "command not found"
248+
stdout="",
249+
stderr=f"SSH command '{ssh_args[0]}' not found",
250+
)

0 commit comments

Comments
 (0)