Merge pull request 'Docs: add missing documentation to api functions' (#4243) from api-cleanup into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4243
This commit is contained in:
hsjobeki
2025-07-07 14:02:08 +00:00
15 changed files with 165 additions and 54 deletions

View File

@@ -49,6 +49,21 @@ def run_machine_flash(
extra_args: list[str] | None = None,
graphical: bool = False,
) -> None:
"""Flash a machine with the given configuration.
Args:
machine: The Machine instance to flash.
mode: The mode to use for flashing (e.g., "install", "reinstall
disks: List of Disk instances representing the disks to flash.
system_config: SystemConfig instance containing language, keymap, and SSH keys.
dry_run: If True, perform a dry run without making changes.
write_efi_boot_entries: If True, write EFI boot entries.
debug: If True, enable debug mode.
extra_args: Additional arguments to pass to the disko-install command.
graphical: If True, run the command in graphical mode.
Raises:
ClanError: If the language or keymap is invalid, or if there are issues with
reading SSH keys, or if disko-install fails.
"""
devices = [Path(disk.device) for disk in disks]
with pause_automounting(devices, machine, request_graphical=graphical):
if extra_args is None:

View File

@@ -19,6 +19,12 @@ class FlashOptions(TypedDict):
@API.register
def get_flash_options() -> FlashOptions:
"""Retrieve available languages and keymaps for flash configuration.
Returns:
FlashOptions: A dictionary containing lists of available languages and keymaps.
Raises:
ClanError: If the locale file or keymaps directory does not exist.
"""
return {"languages": list_languages(), "keymaps": list_keymaps()}

View File

@@ -429,6 +429,21 @@ def get_generators(
full_closure: bool = False,
include_previous_values: bool = False,
) -> list[Generator]:
"""
Get the list of generators for a machine, optionally with previous values.
If `full_closure` is True, it returns the full closure of generators.
If `include_previous_values` is True, it includes the previous values for prompts.
Args:
machine_name (str): The name of the machine.
base_dir (Path): The base directory of the flake.
full_closure (bool): Whether to return the full closure of generators. If False,
it returns only the generators that are missing or need to be regenerated.
include_previous_values (bool): Whether to include previous values for prompts.
Returns:
list[Generator]: A list of generators for the machine.
"""
from clan_lib.machines.machines import Machine
return get_closure(
@@ -468,6 +483,20 @@ def run_generators(
base_dir: Path,
no_sandbox: bool = False,
) -> bool:
"""Run the specified generators for a machine.
Args:
machine_name (str): The name of the machine.
generators (list[str]): The list of generator names to run.
all_prompt_values (dict[str, dict[str, str]]): A dictionary mapping generator names
to their prompt values.
base_dir (Path): The base directory of the flake.
no_sandbox (bool): Whether to disable sandboxing when executing the generator.
Returns:
bool: True if any variables were generated, False otherwise.
Raises:
ClanError: If the machine or generator is not found, or if there are issues with
executing the generator.
"""
from clan_lib.machines.machines import Machine
machine = Machine(name=machine_name, flake=Flake(str(base_dir)))

View File

@@ -1,12 +1,8 @@
import json
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Literal
from clan_lib.cmd import RunOpts, run
from clan_lib.errors import ClanError
from clan_lib.flake import Flake
from clan_lib.nix import nix_shell
from . import API
@@ -42,55 +38,6 @@ def open_file(file_request: FileRequest) -> list[str] | None:
raise NotImplementedError(msg)
@dataclass
class File:
path: str
file_type: Literal["file", "directory", "symlink"]
@dataclass
class Directory:
path: str
files: list[File] = field(default_factory=list)
@API.register
def get_directory(flake: Flake) -> Directory:
curr_dir = flake.path
directory = Directory(path=str(curr_dir))
if not curr_dir.is_dir():
msg = f"Path {curr_dir} is not a directory"
raise ClanError(msg)
with os.scandir(curr_dir.resolve()) as it:
for entry in it:
if entry.is_symlink():
directory.files.append(
File(
path=str(curr_dir / Path(entry.name)),
file_type="symlink",
)
)
elif entry.is_file():
directory.files.append(
File(
path=str(curr_dir / Path(entry.name)),
file_type="file",
)
)
elif entry.is_dir():
directory.files.append(
File(
path=str(curr_dir / Path(entry.name)),
file_type="directory",
)
)
return directory
@dataclass
class BlkInfo:
name: str

View File

@@ -89,6 +89,10 @@ def parse_avahi_output(output: str) -> DNSInfo:
@API.register
def list_mdns_services() -> DNSInfo:
"""List mDNS/DNS-SD services on the local network.
Returns:
DNSInfo: A dictionary containing discovered mDNS/DNS-SD services.
"""
cmd = nix_shell(
["avahi"],
[

View File

@@ -31,6 +31,15 @@ def git_command(directory: Path, *args: str) -> list[str]:
@API.register
def create_clan(opts: CreateOptions) -> None:
"""Create a new clan repository with the specified template.
Args:
opts: CreateOptions containing the destination path, template name,
source flake, and other options.
Raises:
ClanError: If the source flake is not a valid flake or if the destination
directory already exists.
"""
dest = opts.dest.resolve()
if opts.src_flake is not None:

View File

@@ -7,6 +7,14 @@ from clan_lib.persist.inventory_store import InventoryStore
@API.register
def get_clan_details(flake: Flake) -> InventoryMeta:
"""Retrieve the clan details from the inventory of a given flake.
Args:
flake: The Flake instance representing the clan.
Returns:
InventoryMeta: The meta information from the clan's inventory.
Raises:
ClanError: If the flake does not exist, or if the inventory is invalid (missing the meta attribute).
"""
if flake.is_local and not flake.path.exists():
msg = f"Path {flake} does not exist"
raise ClanError(msg, description="clan directory does not exist")

View File

@@ -15,6 +15,14 @@ class UpdateOptions:
@API.register
def set_clan_details(options: UpdateOptions) -> InventorySnapshot:
"""Update the clan metadata in the inventory of a given flake.
Args:
options: UpdateOptions containing the flake and the new metadata.
Returns:
InventorySnapshot: The updated inventory snapshot after modifying the metadata.
Raises:
ClanError: If the flake does not exist or if the inventory is invalid (missing the meta attribute).
"""
inventory_store = InventoryStore(options.flake)
inventory = inventory_store.read()
set_value_by_path(inventory, "meta", options.meta)

View File

@@ -54,6 +54,19 @@ def list_machines(
@API.register
def get_machine(flake: Flake, name: str) -> InventoryMachine:
"""
Retrieve a machine's inventory details by name from the given flake.
Args:
flake (Flake): The flake object representing the configuration source.
name (str): The name of the machine to retrieve from the inventory.
Returns:
InventoryMachine: An instance representing the machine's inventory details.
Raises:
ClanError: If the machine with the specified name is not found in the inventory.
"""
inventory_store = InventoryStore(flake=flake)
inventory = inventory_store.read()

View File

@@ -19,6 +19,13 @@ log = logging.getLogger(__name__)
@API.register
def delete_machine(machine: Machine) -> None:
"""Delete a machine from the clan's inventory and remove its associated files.
Args:
machine: The Machine instance to be deleted.
Raises:
ClanError: If the machine does not exist in the inventory or if there are issues with
removing its files.
"""
inventory_store = InventoryStore(machine.flake)
try:
inventory_store.delete(

View File

@@ -40,6 +40,16 @@ class InstallOptions:
@API.register
def run_machine_install(opts: InstallOptions, target_host: Remote) -> None:
"""Install a machine using nixos-anywhere.
Args:
opts: InstallOptions containing the machine to install, kexec option, debug mode,
no-reboot option, phases, build-on option, hardware config update, password,
identity file, and use_tor flag.
target_host: Remote object representing the target host for installation.
Raises:
ClanError: If the machine is not found in the inventory or if there are issues with
generating facts or variables.
"""
machine = opts.machine
machine.debug(f"installing {machine.name}")

View File

@@ -52,8 +52,22 @@ def extract_header(c: str) -> str:
return "\n".join(header_lines)
# TODO: Remove this function
# Split out the disko schema extraction into a separate function
# get machine returns the machine already
@API.register
def get_machine_details(machine: Machine) -> MachineDetails:
"""Retrieve detailed information about a machine, including its inventory,
hardware configuration, and disk schema if available.
Args:
machine (Machine): The machine instance for which details are to be retrieved.
Returns:
MachineDetails: An instance containing the machine's inventory, hardware configuration,
and disk schema.
Raises:
ClanError: If the machine's inventory cannot be found or if there are issues with the
hardware configuration or disk schema extraction.
"""
machine_inv = get_machine(machine.flake, machine.name)
hw_config = HardwareConfig.detect_type(machine)

View File

@@ -106,6 +106,16 @@ def upload_sources(machine: Machine, ssh: Remote) -> str:
def run_machine_deploy(
machine: Machine, target_host: Remote, build_host: Remote | None
) -> None:
"""Update an existing machine using nixos-rebuild or darwin-rebuild.
Args:
machine: The Machine instance to deploy.
target_host: Remote object representing the target host for deployment.
build_host: Optional Remote object representing the build host.
Raises:
ClanError: If the machine is not found in the inventory or if there are issues with
generating facts or variables.
"""
with ExitStack() as stack:
target_host = stack.enter_context(target_host.ssh_control_master())

View File

@@ -444,6 +444,21 @@ class CheckResult:
def check_machine_ssh_login(
remote: Remote, opts: ConnectionOptions | None = None
) -> CheckResult:
"""Checks if a remote machine is reachable via SSH by attempting to run a simple command.
Args:
remote (Remote): The remote host to check for SSH login.
opts (ConnectionOptions, optional): Connection options such as timeout and number of retries.
If not provided, default values are used.
Returns:
CheckResult: An object indicating whether the SSH login is successful (`ok=True`) or not (`ok=False`),
and a reason if the check failed.
Usage:
result = check_machine_ssh_login(remote)
if result.ok:
print("SSH login successful")
else:
print(f"SSH login failed: {result.reason}")
"""
if opts is None:
opts = ConnectionOptions()
@@ -470,6 +485,22 @@ def check_machine_ssh_login(
def check_machine_ssh_reachable(
remote: Remote, opts: ConnectionOptions | None = None
) -> CheckResult:
"""
Checks if a remote machine is reachable via SSH by attempting to open a TCP connection
to the specified address and port.
Args:
remote (Remote): The remote host to check for SSH reachability.
opts (ConnectionOptions, optional): Connection options such as timeout and number of retries.
If not provided, default values are used.
Returns:
CheckResult: An object indicating whether the SSH port is reachable (`ok=True`) or not (`ok=False`),
and a reason if the check failed.
Usage:
result = check_machine_ssh_reachable(remote)
if result.ok:
print("SSH port is reachable")
print(f"SSH port is not reachable: {result.reason}")
"""
if opts is None:
opts = ConnectionOptions()

View File

@@ -150,7 +150,7 @@ def main() -> None:
errors.extend(check_res)
if not func_schema.get("description"):
warnings.append(
errors.append(
f"{func_name} doesn't have a description. Python docstring is required for an API function."
)