From 6560738502e6b2f45fb5ff862b86e9e774e55447 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Mon, 7 Jul 2025 13:01:18 +0200 Subject: [PATCH] api/machine checks: rename, add checkResult --- pkgs/clan-cli/clan_cli/ssh/deploy_info.py | 2 +- pkgs/clan-cli/clan_lib/ssh/remote.py | 33 +++++++++++++-------- pkgs/clan-cli/clan_lib/tests/test_create.py | 7 +++-- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/ssh/deploy_info.py b/pkgs/clan-cli/clan_cli/ssh/deploy_info.py index 9ba609c95..5bf05d798 100644 --- a/pkgs/clan-cli/clan_cli/ssh/deploy_info.py +++ b/pkgs/clan-cli/clan_cli/ssh/deploy_info.py @@ -98,7 +98,7 @@ def find_reachable_host(deploy_info: DeployInfo) -> Remote | None: return deploy_info.addrs[0] for addr in deploy_info.addrs: - if addr.is_ssh_reachable(): + if addr.check_machine_ssh_reachable(): return addr return None diff --git a/pkgs/clan-cli/clan_lib/ssh/remote.py b/pkgs/clan-cli/clan_lib/ssh/remote.py index 8f4d4f8af..ec8c4e5d0 100644 --- a/pkgs/clan-cli/clan_lib/ssh/remote.py +++ b/pkgs/clan-cli/clan_lib/ssh/remote.py @@ -12,7 +12,6 @@ from dataclasses import dataclass, field from pathlib import Path from shlex import quote from tempfile import TemporaryDirectory -from typing import Literal from clan_lib.api import API from clan_lib.cmd import ClanCmdError, ClanCmdTimeoutError, CmdOut, RunOpts, run @@ -74,9 +73,9 @@ class Remote: private_key=private_key if private_key is not None else self.private_key, password=password if password is not None else self.password, forward_agent=self.forward_agent, - host_key_check=host_key_check - if host_key_check is not None - else self.host_key_check, + host_key_check=( + host_key_check if host_key_check is not None else self.host_key_check + ), verbose_ssh=self.verbose_ssh, ssh_options=self.ssh_options, tor_socks=tor_socks if tor_socks is not None else self.tor_socks, @@ -425,8 +424,8 @@ class Remote: self.check_sshpass_errorcode(res) - def is_ssh_reachable(self) -> bool: - return is_ssh_reachable(self) + def check_machine_ssh_reachable(self) -> bool: + return check_machine_ssh_reachable(self).ok @dataclass(frozen=True) @@ -435,10 +434,16 @@ class ConnectionOptions: retries: int = 10 +@dataclass +class CheckResult: + ok: bool + reason: str | None = None + + @API.register -def can_ssh_login( +def check_machine_ssh_login( remote: Remote, opts: ConnectionOptions | None = None -) -> Literal["Online", "Offline"]: +) -> CheckResult: if opts is None: opts = ConnectionOptions() @@ -449,7 +454,7 @@ def can_ssh_login( ["true"], RunOpts(timeout=opts.timeout, needs_user_terminal=True), ) - return "Online" + return CheckResult(True) except ClanCmdTimeoutError: pass except ClanCmdError as e: @@ -458,11 +463,13 @@ def can_ssh_login( else: time.sleep(opts.timeout) - return "Offline" + return CheckResult(False, f"failed after {opts.retries} attempts") @API.register -def is_ssh_reachable(remote: Remote, opts: ConnectionOptions | None = None) -> bool: +def check_machine_ssh_reachable( + remote: Remote, opts: ConnectionOptions | None = None +) -> CheckResult: if opts is None: opts = ConnectionOptions() @@ -472,10 +479,10 @@ def is_ssh_reachable(remote: Remote, opts: ConnectionOptions | None = None) -> b sock.settimeout(opts.timeout) try: sock.connect((remote.address, remote.port or 22)) - return True + return CheckResult(True) except (TimeoutError, OSError): pass else: time.sleep(opts.timeout) - return False + return CheckResult(False, f"failed after {opts.retries} attempts") diff --git a/pkgs/clan-cli/clan_lib/tests/test_create.py b/pkgs/clan-cli/clan_lib/tests/test_create.py index ac2758374..cae4d5590 100644 --- a/pkgs/clan-cli/clan_lib/tests/test_create.py +++ b/pkgs/clan-cli/clan_lib/tests/test_create.py @@ -33,7 +33,7 @@ from clan_lib.nix_models.clan import ( ) from clan_lib.nix_models.clan import InventoryMachineDeploy as MachineDeploy from clan_lib.persist.util import set_value_by_path -from clan_lib.ssh.remote import Remote, can_ssh_login +from clan_lib.ssh.remote import Remote, check_machine_ssh_login log = logging.getLogger(__name__) @@ -190,8 +190,9 @@ def test_clan_create_api( target_host = machine.target_host().override( private_key=private_key, host_key_check="none" ) - result = can_ssh_login(target_host) - assert result == "Online", f"Machine {machine.name} is not online" + assert check_machine_ssh_login(target_host).ok, ( + f"Machine {machine.name} is not online" + ) ssh_keys = [ SSHKeyPair(