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:
@@ -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:
|
||||
|
||||
@@ -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()}
|
||||
|
||||
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"],
|
||||
[
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user