Merge pull request 'api/tasks: prefix impure actions with run' (#4239) from api-cleanup into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4239
This commit is contained in:
hsjobeki
2025-07-07 11:28:07 +00:00
20 changed files with 69 additions and 56 deletions

View File

@@ -14,7 +14,7 @@ class UpdateOptions:
@API.register
def update_clan_meta(options: UpdateOptions) -> InventorySnapshot:
def set_clan_details(options: UpdateOptions) -> InventorySnapshot:
inventory_store = InventoryStore(options.flake)
inventory = inventory_store.read()
set_value_by_path(inventory, "meta", options.meta)

View File

@@ -17,7 +17,7 @@ def example_function() -> None:
"""Example function for creating logs."""
def deploy_machine() -> None:
def run_machine_deploy() -> None:
"""Function for deploying machines."""
@@ -41,7 +41,7 @@ def main() -> None:
for repo in repos:
for machine in machines:
log_manager.create_log_file(
deploy_machine,
run_machine_deploy,
f"deploy_{machine}",
["clans", repo, "machines", machine],
)

View File

@@ -17,7 +17,7 @@ from clan_lib.log_manager import (
# Test functions for log creation
def deploy_machine() -> None:
def run_machine_deploy() -> None:
"""Test function for deploying machines."""
@@ -194,13 +194,13 @@ class TestLogFileCreation:
for repo in repos:
for machine in machines:
log_file = configured_log_manager.create_log_file(
deploy_machine,
run_machine_deploy,
f"deploy_{machine}",
["clans", repo, "machines", machine],
)
assert log_file.op_key == f"deploy_{machine}"
assert log_file.func_name == "deploy_machine"
assert log_file.func_name == "run_machine_deploy"
assert log_file.get_file_path().exists()
# Check the group structure includes URL encoding for dynamic parts
@@ -241,7 +241,7 @@ class TestFilterFunction:
"""Test that empty filter returns top-level groups."""
# Create some log files first
configured_log_manager.create_log_file(
deploy_machine, "test_op", ["clans", "repo1", "machines", "machine1"]
run_machine_deploy, "test_op", ["clans", "repo1", "machines", "machine1"]
)
top_level = configured_log_manager.filter([])
@@ -258,7 +258,7 @@ class TestFilterFunction:
for repo in repos:
for machine in machines:
configured_log_manager.create_log_file(
deploy_machine,
run_machine_deploy,
f"deploy_{machine}",
["clans", repo, "machines", machine],
)
@@ -281,7 +281,7 @@ class TestFilterFunction:
"""Test filtering with specific date."""
# Create log file
log_file = configured_log_manager.create_log_file(
deploy_machine, "test_op", ["clans", "repo1", "machines", "machine1"]
run_machine_deploy, "test_op", ["clans", "repo1", "machines", "machine1"]
)
# Filter with the specific date
@@ -308,14 +308,16 @@ class TestGetLogFile:
"""Test getting log file by operation key."""
# Create log file
configured_log_manager.create_log_file(
deploy_machine, "deploy_wintux", ["clans", "repo1", "machines", "wintux"]
run_machine_deploy,
"deploy_wintux",
["clans", "repo1", "machines", "wintux"],
)
# Find it by op_key
found_log_file = configured_log_manager.get_log_file("deploy_wintux")
assert found_log_file is not None
assert found_log_file.op_key == "deploy_wintux"
assert found_log_file.func_name == "deploy_machine"
assert found_log_file.func_name == "run_machine_deploy"
def test_get_log_file_with_selector(
self, configured_log_manager: LogManager
@@ -323,10 +325,14 @@ class TestGetLogFile:
"""Test getting log file with specific selector like example_usage.py."""
# Create log files in different locations
configured_log_manager.create_log_file(
deploy_machine, "deploy_wintux", ["clans", "repo1", "machines", "wintux"]
run_machine_deploy,
"deploy_wintux",
["clans", "repo1", "machines", "wintux"],
)
configured_log_manager.create_log_file(
deploy_machine, "deploy_wintux", ["clans", "repo2", "machines", "wintux"]
run_machine_deploy,
"deploy_wintux",
["clans", "repo2", "machines", "wintux"],
)
# Find specific one using selector
@@ -341,7 +347,7 @@ class TestGetLogFile:
"""Test getting log file with specific date."""
# Create log file
log_file = configured_log_manager.create_log_file(
deploy_machine, "deploy_demo", ["clans", "repo1", "machines", "demo"]
run_machine_deploy, "deploy_demo", ["clans", "repo1", "machines", "demo"]
)
# Find it by op_key and date
@@ -378,10 +384,10 @@ class TestListLogDays:
"""Test listing log days when logs exist."""
# Create log files
configured_log_manager.create_log_file(
deploy_machine, "op1", ["clans", "repo1", "machines", "machine1"]
run_machine_deploy, "op1", ["clans", "repo1", "machines", "machine1"]
)
configured_log_manager.create_log_file(
deploy_machine, "op2", ["clans", "repo2", "machines", "machine2"]
run_machine_deploy, "op2", ["clans", "repo2", "machines", "machine2"]
)
days = configured_log_manager.list_log_days()
@@ -406,7 +412,7 @@ class TestApiCompatibility:
for repo in repos:
for machine in machines:
configured_log_manager.create_log_file(
deploy_machine,
run_machine_deploy,
f"deploy_{machine}",
["clans", repo, "machines", machine],
)
@@ -741,19 +747,19 @@ class TestLogFileSorting:
# This simulates the realistic scenario where the same operation runs on different machines
configured_log_manager.create_log_file(
deploy_machine,
run_machine_deploy,
"deploy_operation",
["clans", "repo1", "machines", "machine1"],
)
configured_log_manager.create_log_file(
deploy_machine,
run_machine_deploy,
"deploy_operation",
["clans", "repo1", "machines", "machine2"],
)
configured_log_manager.create_log_file(
deploy_machine,
run_machine_deploy,
"deploy_operation",
["clans", "repo2", "machines", "machine1"],
)
@@ -819,7 +825,7 @@ class TestURLEncoding:
# Create log file with special characters
log_file = configured_log_manager.create_log_file(
deploy_machine,
run_machine_deploy,
"deploy_special",
["clans", special_repo, "machines", special_machine],
)

View File

@@ -39,7 +39,7 @@ class InstallOptions:
@API.register
def install_machine(opts: InstallOptions, target_host: Remote) -> None:
def run_machine_install(opts: InstallOptions, target_host: Remote) -> None:
machine = opts.machine
machine.debug(f"installing {machine.name}")

View File

@@ -103,7 +103,7 @@ def upload_sources(machine: Machine, ssh: Remote) -> str:
@API.register
def deploy_machine(
def run_machine_deploy(
machine: Machine, target_host: Remote, build_host: Remote | None
) -> None:
with ExitStack() as stack:

View File

@@ -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")

View File

@@ -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(