From 5f7b95ac7335587d60dd9c984a7fe2dbb48e0df7 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Thu, 8 May 2025 12:30:16 +0200 Subject: [PATCH 1/3] clan-cli: remove useless run_no_stdout function --- pkgs/clan-cli/clan_cli/cmd.py | 20 -------------------- pkgs/clan-cli/clan_cli/inventory/__init__.py | 6 +++--- pkgs/clan-cli/clan_cli/machines/machines.py | 4 ++-- pkgs/clan-cli/clan_cli/state/list.py | 4 ++-- pkgs/clan-cli/clan_cli/tags.py | 4 ++-- pkgs/clan-cli/clan_cli/tests/test_modules.py | 4 ++-- pkgs/clan-cli/clan_lib/api/directory.py | 11 ++++++----- pkgs/clan-cli/clan_lib/api/mdns_discovery.py | 4 ++-- 8 files changed, 19 insertions(+), 38 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/cmd.py b/pkgs/clan-cli/clan_cli/cmd.py index fb5db7b5e..e64392008 100644 --- a/pkgs/clan-cli/clan_cli/cmd.py +++ b/pkgs/clan-cli/clan_cli/cmd.py @@ -403,23 +403,3 @@ def run( raise ClanCmdError(cmd_out) return cmd_out - - -def run_no_stdout( - cmd: list[str], - opts: RunOpts | None = None, -) -> CmdOut: - """ - Like run, but automatically suppresses all output, if not in DEBUG log level. - If in DEBUG log level the stdout of commands will be shown. - """ - if opts is None: - opts = RunOpts() - - if cmdlog.isEnabledFor(logging.DEBUG): - opts.log = opts.log if opts.log.value > Log.STDERR.value else Log.STDERR - - return run( - cmd, - opts, - ) diff --git a/pkgs/clan-cli/clan_cli/inventory/__init__.py b/pkgs/clan-cli/clan_cli/inventory/__init__.py index d5a1a6135..5389f28f0 100644 --- a/pkgs/clan-cli/clan_cli/inventory/__init__.py +++ b/pkgs/clan-cli/clan_cli/inventory/__init__.py @@ -21,7 +21,7 @@ from typing import Any from clan_lib.api import API, dataclass_to_dict, from_dict -from clan_cli.cmd import run_no_stdout +from clan_cli.cmd import run from clan_cli.errors import ClanCmdError, ClanError from clan_cli.flake import Flake from clan_cli.git import commit_file @@ -80,7 +80,7 @@ def load_inventory_eval(flake_dir: Flake) -> Inventory: ] ) - proc = run_no_stdout(cmd) + proc = run(cmd) try: res = proc.stdout.strip() @@ -380,7 +380,7 @@ def get_inventory_current_priority(flake: Flake) -> dict: ] ) - proc = run_no_stdout(cmd) + proc = run(cmd) try: res = proc.stdout.strip() diff --git a/pkgs/clan-cli/clan_cli/machines/machines.py b/pkgs/clan-cli/clan_cli/machines/machines.py index 7dd9ca7f0..83f23347c 100644 --- a/pkgs/clan-cli/clan_cli/machines/machines.py +++ b/pkgs/clan-cli/clan_cli/machines/machines.py @@ -9,7 +9,7 @@ from functools import cached_property from pathlib import Path from typing import TYPE_CHECKING, Any -from clan_cli.cmd import Log, RunOpts, run_no_stdout +from clan_cli.cmd import Log, RunOpts, run from clan_cli.errors import ClanCmdError, ClanError from clan_cli.facts import public_modules as facts_public_modules from clan_cli.facts import secret_modules as facts_secret_modules @@ -188,7 +188,7 @@ class Machine: # however there is a soon to be merged PR that requires deployment # as root to match NixOS: https://github.com/nix-darwin/nix-darwin/pull/1341 return json.loads( - run_no_stdout( + run( nix_eval( [ f"{self.flake}#darwinConfigurations.{self.name}.options.system", diff --git a/pkgs/clan-cli/clan_cli/state/list.py b/pkgs/clan-cli/clan_cli/state/list.py index 5827affd0..4a6835d1b 100644 --- a/pkgs/clan-cli/clan_cli/state/list.py +++ b/pkgs/clan-cli/clan_cli/state/list.py @@ -3,7 +3,7 @@ import json import logging from pathlib import Path -from clan_cli.cmd import RunOpts, run_no_stdout +from clan_cli.cmd import RunOpts, run from clan_cli.completions import ( add_dynamic_completer, complete_machines, @@ -32,7 +32,7 @@ def list_state_folders(machine: Machine, service: None | str = None) -> None: res = "{}" try: - proc = run_no_stdout(cmd, opts=RunOpts(prefix=machine.name)) + proc = run(cmd, RunOpts(prefix=machine.name)) res = proc.stdout.strip() except ClanCmdError as e: msg = "Clan might not have meta attributes" diff --git a/pkgs/clan-cli/clan_cli/tags.py b/pkgs/clan-cli/clan_cli/tags.py index 4f84a792e..1f65dfcd6 100644 --- a/pkgs/clan-cli/clan_cli/tags.py +++ b/pkgs/clan-cli/clan_cli/tags.py @@ -2,7 +2,7 @@ import json from pathlib import Path from typing import Any -from clan_cli.cmd import run_no_stdout +from clan_cli.cmd import run from clan_cli.errors import ClanError from clan_cli.nix import nix_eval @@ -18,7 +18,7 @@ def list_tagged_machines(flake_url: str | Path) -> dict[str, Any]: "--json", ] ) - proc = run_no_stdout(cmd) + proc = run(cmd) try: res = proc.stdout.strip() diff --git a/pkgs/clan-cli/clan_cli/tests/test_modules.py b/pkgs/clan-cli/clan_cli/tests/test_modules.py index b29261d04..9b8418124 100644 --- a/pkgs/clan-cli/clan_cli/tests/test_modules.py +++ b/pkgs/clan-cli/clan_cli/tests/test_modules.py @@ -11,7 +11,7 @@ from clan_cli.inventory import ( set_inventory, ) from clan_cli.machines.create import CreateOptions, create_machine -from clan_cli.nix import nix_eval, run_no_stdout +from clan_cli.nix import nix_eval, run from clan_cli.tests.fixtures_flakes import FlakeForTest from clan_lib.api.modules import list_modules @@ -120,7 +120,7 @@ def test_add_module_to_inventory( "--json", ] ) - proc = run_no_stdout(cmd) + proc = run(cmd) res = json.loads(proc.stdout.strip()) assert res["machine1"]["authorizedKeys"] == [ssh_key.decode()] diff --git a/pkgs/clan-cli/clan_lib/api/directory.py b/pkgs/clan-cli/clan_lib/api/directory.py index 907705686..1e7cc38ac 100644 --- a/pkgs/clan-cli/clan_lib/api/directory.py +++ b/pkgs/clan-cli/clan_lib/api/directory.py @@ -4,9 +4,10 @@ from dataclasses import dataclass, field from pathlib import Path from typing import Any, Literal -from clan_cli.cmd import RunOpts +from clan_cli.cmd import RunOpts, run from clan_cli.errors import ClanError -from clan_cli.nix import nix_shell, run_no_stdout +from clan_cli.flake import Flake +from clan_cli.nix import nix_shell from . import API @@ -52,8 +53,8 @@ class Directory: @API.register -def get_directory(current_path: str) -> Directory: - curr_dir = Path(current_path) +def get_directory(flake: Flake) -> Directory: + curr_dir = flake.path directory = Directory(path=str(curr_dir)) if not curr_dir.is_dir(): @@ -135,7 +136,7 @@ def show_block_devices() -> Blockdevices: "PATH,NAME,RM,SIZE,RO,MOUNTPOINTS,TYPE,ID-LINK", ], ) - proc = run_no_stdout(cmd, RunOpts(needs_user_terminal=True)) + proc = run(cmd, RunOpts(needs_user_terminal=True)) res = proc.stdout.strip() blk_info: dict[str, Any] = json.loads(res) diff --git a/pkgs/clan-cli/clan_lib/api/mdns_discovery.py b/pkgs/clan-cli/clan_lib/api/mdns_discovery.py index 8dea84f75..043b18522 100644 --- a/pkgs/clan-cli/clan_lib/api/mdns_discovery.py +++ b/pkgs/clan-cli/clan_lib/api/mdns_discovery.py @@ -2,7 +2,7 @@ import argparse import re from dataclasses import dataclass -from clan_cli.cmd import run_no_stdout +from clan_cli.cmd import run from clan_cli.nix import nix_shell from . import API @@ -100,7 +100,7 @@ def show_mdns() -> DNSInfo: "--terminate", ], ) - proc = run_no_stdout(cmd) + proc = run(cmd) data = parse_avahi_output(proc.stdout) return data From c87e768bfac471a23437994f7a5afc64f013befb Mon Sep 17 00:00:00 2001 From: Qubasa Date: Thu, 8 May 2025 12:31:21 +0200 Subject: [PATCH 2/3] clan-cli: Fix clan not finding vendored packages when running from git repo --- pkgs/clan-cli/clan_cli/nix/__init__.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/nix/__init__.py b/pkgs/clan-cli/clan_cli/nix/__init__.py index 05b5d4367..6d19c8a79 100644 --- a/pkgs/clan-cli/clan_cli/nix/__init__.py +++ b/pkgs/clan-cli/clan_cli/nix/__init__.py @@ -1,12 +1,13 @@ import json import logging import os +import shutil import tempfile from functools import cache from pathlib import Path from typing import Any -from clan_cli.cmd import run, run_no_stdout +from clan_cli.cmd import run from clan_cli.dirs import nixpkgs_flake, nixpkgs_source from clan_cli.errors import ClanError from clan_cli.locked_open import locked_open @@ -55,7 +56,7 @@ def nix_add_to_gcroots(nix_path: Path, dest: Path) -> None: @cache def nix_config() -> dict[str, Any]: cmd = nix_command(["config", "show", "--json"]) - proc = run_no_stdout(cmd) + proc = run(cmd) data = json.loads(proc.stdout) config = {} for key, value in data.items(): @@ -131,7 +132,16 @@ class Packages: cls.static_packages = set( os.environ.get("CLAN_PROVIDED_PACKAGES", "").split(":") ) - return program in cls.static_packages + + if program in cls.static_packages: + if shutil.which(program) is None: + log.warning( + "Program %s is not in the path even though it should be shipped with clan", + program, + ) + return False + return True + return False # Features: From 01f9bb358e68af40881ccb9e4e94940340298dca Mon Sep 17 00:00:00 2001 From: Qubasa Date: Fri, 9 May 2025 13:13:14 +0200 Subject: [PATCH 3/3] clan-cli: Use machine object everywhere instead of name + flake --- pkgs/clan-cli/clan_cli/clan/show.py | 21 ++++---- pkgs/clan-cli/clan_cli/dirs.py | 5 +- pkgs/clan-cli/clan_cli/facts/upload.py | 23 ++++---- pkgs/clan-cli/clan_cli/machines/create.py | 2 +- pkgs/clan-cli/clan_cli/machines/delete.py | 26 +++++---- pkgs/clan-cli/clan_cli/machines/hardware.py | 54 ++++++++----------- pkgs/clan-cli/clan_cli/machines/install.py | 6 +-- pkgs/clan-cli/clan_cli/machines/list.py | 4 +- pkgs/clan-cli/clan_cli/machines/update.py | 2 +- pkgs/clan-cli/clan_cli/ssh/upload.py | 3 +- .../clan_cli/tests/fixtures_flakes.py | 17 ++++-- pkgs/clan-cli/clan_lib/api/disk.py | 32 +++++------ pkgs/clan-cli/clan_lib/tests/test_create.py | 9 ++-- pkgs/webview-ui/app/src/App.tsx | 4 +- .../app/src/components/Sidebar/index.tsx | 4 +- .../app/src/routes/clans/details.tsx | 4 +- pkgs/webview-ui/app/src/routes/clans/list.tsx | 4 +- .../app/src/routes/machines/details.tsx | 6 ++- .../src/routes/machines/install/disk-step.tsx | 8 +-- .../routes/machines/install/hardware-step.tsx | 18 ++++--- 20 files changed, 128 insertions(+), 124 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/clan/show.py b/pkgs/clan-cli/clan_cli/clan/show.py index e08dc5562..4e6ba997b 100644 --- a/pkgs/clan-cli/clan_cli/clan/show.py +++ b/pkgs/clan-cli/clan_cli/clan/show.py @@ -6,8 +6,9 @@ from urllib.parse import urlparse from clan_lib.api import API -from clan_cli.cmd import run_no_stdout +from clan_cli.cmd import run from clan_cli.errors import ClanCmdError, ClanError +from clan_cli.flake import Flake from clan_cli.inventory import Meta from clan_cli.nix import nix_eval @@ -15,26 +16,26 @@ log = logging.getLogger(__name__) @API.register -def show_clan_meta(uri: str) -> Meta: - if uri.startswith("/") and not Path(uri).exists(): - msg = f"Path {uri} does not exist" +def show_clan_meta(flake: Flake) -> Meta: + 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") cmd = nix_eval( [ - f"{uri}#clanInternals.inventory.meta", + f"{flake}#clanInternals.inventory.meta", "--json", ] ) res = "{}" try: - proc = run_no_stdout(cmd) + proc = run(cmd) res = proc.stdout.strip() except ClanCmdError as e: msg = "Evaluation failed on meta attribute" raise ClanError( msg, - location=f"show_clan {uri}", + location=f"show_clan {flake}", description=str(e.cmd), ) from e @@ -53,16 +54,16 @@ def show_clan_meta(uri: str) -> Meta: msg = "Invalid absolute path" raise ClanError( msg, - location=f"show_clan {uri}", + location=f"show_clan {flake}", description="Icon path must be a URL or a relative path", ) - icon_path = str((Path(uri) / meta_icon).resolve()) + icon_path = str((flake.path / meta_icon).resolve()) else: msg = "Invalid schema" raise ClanError( msg, - location=f"show_clan {uri}", + location=f"show_clan {flake}", description="Icon path must be a URL or a relative path", ) diff --git a/pkgs/clan-cli/clan_cli/dirs.py b/pkgs/clan-cli/clan_cli/dirs.py index ffb850aad..5926a4733 100644 --- a/pkgs/clan-cli/clan_cli/dirs.py +++ b/pkgs/clan-cli/clan_cli/dirs.py @@ -10,6 +10,7 @@ from .errors import ClanError if TYPE_CHECKING: from clan_cli.flake import Flake + from clan_cli.machines.machines import Machine log = logging.getLogger(__name__) @@ -144,8 +145,8 @@ def machines_dir(flake: "Flake") -> Path: return Path(store_path) / "machines" -def specific_machine_dir(flake: "Flake", machine: str) -> Path: - return machines_dir(flake) / machine +def specific_machine_dir(machine: "Machine") -> Path: + return machines_dir(machine.flake) / machine.name def module_root() -> Path: diff --git a/pkgs/clan-cli/clan_cli/facts/upload.py b/pkgs/clan-cli/clan_cli/facts/upload.py index 317f260ba..af6def441 100644 --- a/pkgs/clan-cli/clan_cli/facts/upload.py +++ b/pkgs/clan-cli/clan_cli/facts/upload.py @@ -5,28 +5,27 @@ from tempfile import TemporaryDirectory from clan_cli.completions import add_dynamic_completer, complete_machines from clan_cli.machines.machines import Machine -from clan_cli.ssh.host import Host from clan_cli.ssh.upload import upload log = logging.getLogger(__name__) -def upload_secrets(machine: Machine, host: Host) -> None: - if not machine.secret_facts_store.needs_upload(host): - machine.info("Secrets already uploaded") - return +def upload_secrets(machine: Machine) -> None: + with machine.target_host() as host: + if not machine.secret_facts_store.needs_upload(host): + machine.info("Secrets already uploaded") + return - with TemporaryDirectory(prefix="facts-upload-") as _tempdir: - local_secret_dir = Path(_tempdir).resolve() - machine.secret_facts_store.upload(local_secret_dir) - remote_secret_dir = Path(machine.secrets_upload_directory) - upload(host, local_secret_dir, remote_secret_dir) + with TemporaryDirectory(prefix="facts-upload-") as _tempdir: + local_secret_dir = Path(_tempdir).resolve() + machine.secret_facts_store.upload(local_secret_dir) + remote_secret_dir = Path(machine.secrets_upload_directory) + upload(host, local_secret_dir, remote_secret_dir) def upload_command(args: argparse.Namespace) -> None: machine = Machine(name=args.machine, flake=args.flake) - with machine.target_host() as host: - upload_secrets(machine, host) + upload_secrets(machine) def register_upload_parser(parser: argparse.ArgumentParser) -> None: diff --git a/pkgs/clan-cli/clan_cli/machines/create.py b/pkgs/clan-cli/clan_cli/machines/create.py index 3f9ef22fd..1be52545a 100644 --- a/pkgs/clan-cli/clan_cli/machines/create.py +++ b/pkgs/clan-cli/clan_cli/machines/create.py @@ -110,7 +110,7 @@ def create_machine(opts: CreateOptions, commit: bool = True) -> None: new_machine["deploy"] = {"targetHost": target_host} patch_inventory_with( - Flake(str(clan_dir)), f"machines.{machine_name}", dataclass_to_dict(new_machine) + opts.clan_dir, f"machines.{machine_name}", dataclass_to_dict(new_machine) ) # Commit at the end in that order to avoid committing halve-baked machines diff --git a/pkgs/clan-cli/clan_cli/machines/delete.py b/pkgs/clan-cli/clan_cli/machines/delete.py index 540111a1c..8dc7c1cc1 100644 --- a/pkgs/clan-cli/clan_cli/machines/delete.py +++ b/pkgs/clan-cli/clan_cli/machines/delete.py @@ -5,9 +5,10 @@ from pathlib import Path from clan_lib.api import API -from clan_cli import Flake, inventory +from clan_cli import inventory from clan_cli.completions import add_dynamic_completer, complete_machines from clan_cli.dirs import specific_machine_dir +from clan_cli.machines.machines import Machine from clan_cli.secrets.folders import sops_secrets_folder from clan_cli.secrets.machines import has_machine as secrets_has_machine from clan_cli.secrets.machines import remove_machine as secrets_machine_remove @@ -15,49 +16,46 @@ from clan_cli.secrets.secrets import ( list_secrets, ) -from .machines import Machine - log = logging.getLogger(__name__) @API.register -def delete_machine(flake: Flake, name: str) -> None: +def delete_machine(machine: Machine) -> None: try: - inventory.delete(flake, {f"machines.{name}"}) + inventory.delete(machine.flake, {f"machines.{machine.name}"}) except KeyError as exc: # louis@(2025-03-09): test infrastructure does not seem to set the # inventory properly, but more importantly only one machine in my # personal clan ended up in the inventory for some reason, so I think # it makes sense to eat the exception here. log.warning( - f"{name} was missing or already deleted from the machines inventory: {exc}" + f"{machine.name} was missing or already deleted from the machines inventory: {exc}" ) changed_paths: list[Path] = [] - folder = specific_machine_dir(flake, name) + folder = specific_machine_dir(machine) if folder.exists(): changed_paths.append(folder) shutil.rmtree(folder) # louis@(2025-02-04): clean-up legacy (pre-vars) secrets: - sops_folder = sops_secrets_folder(flake.path) - filter_fn = lambda secret_name: secret_name.startswith(f"{name}-") - for secret_name in list_secrets(flake.path, filter_fn): + sops_folder = sops_secrets_folder(machine.flake.path) + filter_fn = lambda secret_name: secret_name.startswith(f"{machine.name}-") + for secret_name in list_secrets(machine.flake.path, filter_fn): secret_path = sops_folder / secret_name changed_paths.append(secret_path) shutil.rmtree(secret_path) - machine = Machine(name, flake) changed_paths.extend(machine.public_vars_store.delete_store()) changed_paths.extend(machine.secret_vars_store.delete_store()) # Remove the machine's key, and update secrets & vars that referenced it: - if secrets_has_machine(flake.path, name): - secrets_machine_remove(flake.path, name) + if secrets_has_machine(machine.flake.path, machine.name): + secrets_machine_remove(machine.flake.path, machine.name) def delete_command(args: argparse.Namespace) -> None: - delete_machine(args.flake, args.name) + delete_machine(Machine(flake=args.flake, name=args.name)) def register_delete_parser(parser: argparse.ArgumentParser) -> None: diff --git a/pkgs/clan-cli/clan_cli/machines/hardware.py b/pkgs/clan-cli/clan_cli/machines/hardware.py index 543ecfdc8..0f41bece3 100644 --- a/pkgs/clan-cli/clan_cli/machines/hardware.py +++ b/pkgs/clan-cli/clan_cli/machines/hardware.py @@ -7,11 +7,10 @@ from pathlib import Path from clan_lib.api import API -from clan_cli.cmd import RunOpts, run_no_stdout +from clan_cli.cmd import RunOpts, run from clan_cli.completions import add_dynamic_completer, complete_machines from clan_cli.dirs import specific_machine_dir from clan_cli.errors import ClanCmdError, ClanError -from clan_cli.flake import Flake from clan_cli.git import commit_file from clan_cli.machines.machines import Machine from clan_cli.nix import nix_config, nix_eval @@ -26,39 +25,35 @@ class HardwareConfig(Enum): NIXOS_GENERATE_CONFIG = "nixos-generate-config" NONE = "none" - def config_path(self, flake: Flake, machine_name: str) -> Path: - machine_dir = specific_machine_dir(flake, machine_name) + def config_path(self, machine: Machine) -> Path: + machine_dir = specific_machine_dir(machine) if self == HardwareConfig.NIXOS_FACTER: return machine_dir / "facter.json" return machine_dir / "hardware-configuration.nix" @classmethod - def detect_type( - cls: type["HardwareConfig"], flake: Flake, machine_name: str - ) -> "HardwareConfig": - hardware_config = HardwareConfig.NIXOS_GENERATE_CONFIG.config_path( - flake, machine_name - ) + def detect_type(cls: type["HardwareConfig"], machine: Machine) -> "HardwareConfig": + hardware_config = HardwareConfig.NIXOS_GENERATE_CONFIG.config_path(machine) if hardware_config.exists() and "throw" not in hardware_config.read_text(): return HardwareConfig.NIXOS_GENERATE_CONFIG - if HardwareConfig.NIXOS_FACTER.config_path(flake, machine_name).exists(): + if HardwareConfig.NIXOS_FACTER.config_path(machine).exists(): return HardwareConfig.NIXOS_FACTER return HardwareConfig.NONE @API.register -def show_machine_hardware_config(flake: Flake, machine_name: str) -> HardwareConfig: +def show_machine_hardware_config(machine: Machine) -> HardwareConfig: """ Show hardware information for a machine returns None if none exist. """ - return HardwareConfig.detect_type(flake, machine_name) + return HardwareConfig.detect_type(machine) @API.register -def show_machine_hardware_platform(flake: Flake, machine_name: str) -> str | None: +def show_machine_hardware_platform(machine: Machine) -> str | None: """ Show hardware information for a machine returns None if none exist. """ @@ -66,13 +61,13 @@ def show_machine_hardware_platform(flake: Flake, machine_name: str) -> str | Non system = config["system"] cmd = nix_eval( [ - f"{flake}#clanInternals.machines.{system}.{machine_name}", + f"{machine.flake}#clanInternals.machines.{system}.{machine.name}", "--apply", "machine: { inherit (machine.pkgs) system; }", "--json", ] ) - proc = run_no_stdout(cmd, RunOpts(prefix=machine_name)) + proc = run(cmd, RunOpts(prefix=machine.name)) res = proc.stdout.strip() host_platform = json.loads(res) @@ -81,11 +76,8 @@ def show_machine_hardware_platform(flake: Flake, machine_name: str) -> str | Non @dataclass class HardwareGenerateOptions: - flake: Flake - machine: str + machine: Machine backend: HardwareConfig - target_host: str | None = None - keyfile: str | None = None password: str | None = None @@ -96,14 +88,9 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareCon and place the resulting *.nix file in the machine's directory. """ - machine = Machine( - opts.machine, - flake=opts.flake, - private_key=Path(opts.keyfile) if opts.keyfile else None, - override_target_host=opts.target_host, - ) + machine = opts.machine - hw_file = opts.backend.config_path(opts.flake, opts.machine) + hw_file = opts.backend.config_path(opts.machine) hw_file.parent.mkdir(parents=True, exist_ok=True) if opts.backend == HardwareConfig.NIXOS_FACTER: @@ -148,11 +135,11 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareCon commit_file( hw_file, - opts.flake.path, + opts.machine.flake.path, f"machines/{opts.machine}/{hw_file.name}: update hardware configuration", ) try: - show_machine_hardware_platform(opts.flake, opts.machine) + show_machine_hardware_platform(opts.machine) if backup_file: backup_file.unlink(missing_ok=True) except ClanCmdError as e: @@ -173,10 +160,13 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareCon def update_hardware_config_command(args: argparse.Namespace) -> None: - opts = HardwareGenerateOptions( + machine = Machine( flake=args.flake, - machine=args.machine, - target_host=args.target_host, + name=args.machine, + override_target_host=args.target_host, + ) + opts = HardwareGenerateOptions( + machine=machine, password=args.password, backend=HardwareConfig(args.backend), ) diff --git a/pkgs/clan-cli/clan_cli/machines/install.py b/pkgs/clan-cli/clan_cli/machines/install.py index 707b8b06d..34d2dbf4a 100644 --- a/pkgs/clan-cli/clan_cli/machines/install.py +++ b/pkgs/clan-cli/clan_cli/machines/install.py @@ -111,11 +111,7 @@ def install_machine(opts: InstallOptions) -> None: [ "--generate-hardware-config", str(opts.update_hardware_config.value), - str( - opts.update_hardware_config.config_path( - machine.flake, machine.name - ) - ), + str(opts.update_hardware_config.config_path(machine)), ] ) diff --git a/pkgs/clan-cli/clan_cli/machines/list.py b/pkgs/clan-cli/clan_cli/machines/list.py index 6ee3e903e..2ad964196 100644 --- a/pkgs/clan-cli/clan_cli/machines/list.py +++ b/pkgs/clan-cli/clan_cli/machines/list.py @@ -67,9 +67,9 @@ def get_machine_details(machine: Machine) -> MachineDetails: msg = f"Machine {machine.name} not found in inventory" raise ClanError(msg) - hw_config = HardwareConfig.detect_type(machine.flake, machine.name) + hw_config = HardwareConfig.detect_type(machine) - machine_dir = specific_machine_dir(machine.flake, machine.name) + machine_dir = specific_machine_dir(machine) disk_schema: MachineDiskMatter | None = None disk_path = machine_dir / "disko.nix" if disk_path.exists(): diff --git a/pkgs/clan-cli/clan_cli/machines/update.py b/pkgs/clan-cli/clan_cli/machines/update.py index 0909ea94a..56af9cd97 100644 --- a/pkgs/clan-cli/clan_cli/machines/update.py +++ b/pkgs/clan-cli/clan_cli/machines/update.py @@ -141,7 +141,7 @@ def deploy_machine(machine: Machine) -> None: generate_facts([machine], service=None, regenerate=False) generate_vars([machine], generator_name=None, regenerate=False) - upload_secrets(machine, target_host) + upload_secrets(machine) upload_secret_vars(machine, target_host) path = upload_sources(machine, host) diff --git a/pkgs/clan-cli/clan_cli/ssh/upload.py b/pkgs/clan-cli/clan_cli/ssh/upload.py index 955b4882c..27fea030a 100644 --- a/pkgs/clan-cli/clan_cli/ssh/upload.py +++ b/pkgs/clan-cli/clan_cli/ssh/upload.py @@ -63,7 +63,8 @@ def upload( for mdir in dirs: dir_path = Path(root) / mdir tarinfo = tar.gettarinfo( - dir_path, arcname=str(dir_path.relative_to(str(local_src))) + dir_path, + arcname=str(dir_path.relative_to(str(local_src))), ) tarinfo.mode = dir_mode tarinfo.uname = file_user diff --git a/pkgs/clan-cli/clan_cli/tests/fixtures_flakes.py b/pkgs/clan-cli/clan_cli/tests/fixtures_flakes.py index 76cae2d0b..90dd2cf5d 100644 --- a/pkgs/clan-cli/clan_cli/tests/fixtures_flakes.py +++ b/pkgs/clan-cli/clan_cli/tests/fixtures_flakes.py @@ -10,8 +10,15 @@ from pathlib import Path from typing import Any, NamedTuple import pytest -from clan_cli.dirs import TemplateType, clan_templates, nixpkgs_source +from clan_cli.dirs import ( + TemplateType, + clan_templates, + nixpkgs_source, + specific_machine_dir, +) +from clan_cli.flake import Flake from clan_cli.locked_open import locked_open +from clan_cli.machines.machines import Machine from clan_cli.nix import nix_test_store from clan_cli.tests import age_keys from clan_cli.tests.fixture_error import FixtureError @@ -70,11 +77,10 @@ class FlakeForTest(NamedTuple): def set_machine_settings( - flake: Path, - machine_name: str, + machine: Machine, machine_settings: dict, ) -> None: - config_path = flake / "machines" / machine_name / "configuration.json" + config_path = specific_machine_dir(machine) / "configuration.json" config_path.write_text(json.dumps(machine_settings, indent=2)) @@ -202,7 +208,8 @@ class ClanFlake: }} """ ) - set_machine_settings(self.path, machine_name, machine_config) + machine = Machine(name=machine_name, flake=Flake(str(self.path))) + set_machine_settings(machine, machine_config) sp.run(["git", "add", "."], cwd=self.path, check=True) sp.run( ["git", "commit", "-a", "-m", "Update by flake generator"], diff --git a/pkgs/clan-cli/clan_lib/api/disk.py b/pkgs/clan-cli/clan_lib/api/disk.py index 2ff98ea9e..e9700d36c 100644 --- a/pkgs/clan-cli/clan_lib/api/disk.py +++ b/pkgs/clan-cli/clan_lib/api/disk.py @@ -7,9 +7,9 @@ from uuid import uuid4 from clan_cli.dirs import TemplateType, clan_templates from clan_cli.errors import ClanError -from clan_cli.flake import Flake from clan_cli.git import commit_file from clan_cli.machines.hardware import HardwareConfig, show_machine_hardware_config +from clan_cli.machines.machines import Machine from clan_lib.api import API from clan_lib.api.modules import Frontmatter, extract_frontmatter @@ -74,9 +74,7 @@ templates: dict[str, dict[str, Callable[[dict[str, Any]], Placeholder]]] = { @API.register -def get_disk_schemas( - flake: Flake, machine_name: str | None = None -) -> dict[str, DiskSchema]: +def get_disk_schemas(machine: Machine) -> dict[str, DiskSchema]: """ Get the available disk schemas """ @@ -84,13 +82,12 @@ def get_disk_schemas( disk_schemas = {} hw_report = {} - if machine_name is not None: - hw_report_path = HardwareConfig.NIXOS_FACTER.config_path(flake, machine_name) - if not hw_report_path.exists(): - msg = "Hardware configuration missing" - raise ClanError(msg) - with hw_report_path.open("r") as hw_report_file: - hw_report = json.load(hw_report_file) + hw_report_path = HardwareConfig.NIXOS_FACTER.config_path(machine) + if not hw_report_path.exists(): + msg = "Hardware configuration missing" + raise ClanError(msg) + with hw_report_path.open("r") as hw_report_file: + hw_report = json.load(hw_report_file) for disk_template in disk_templates.iterdir(): if disk_template.is_dir(): @@ -130,8 +127,7 @@ class MachineDiskMatter(TypedDict): @API.register def set_machine_disk_schema( - flake: Flake, - machine_name: str, + machine: Machine, schema_name: str, # Placeholders are used to fill in the disk schema # Use get disk schemas to get the placeholders and their options @@ -142,8 +138,8 @@ def set_machine_disk_schema( Set the disk placeholders of the template """ # Assert the hw-config must exist before setting the disk - hw_config = show_machine_hardware_config(flake, machine_name) - hw_config_path = hw_config.config_path(flake, machine_name) + hw_config = show_machine_hardware_config(machine) + hw_config_path = hw_config.config_path(machine) if not hw_config_path.exists(): msg = "Hardware configuration must exist before applying disk schema" @@ -160,7 +156,7 @@ def set_machine_disk_schema( raise ClanError(msg) # Check that the placeholders are valid - disk_schema = get_disk_schemas(flake, machine_name)[schema_name] + disk_schema = get_disk_schemas(machine)[schema_name] # check that all required placeholders are present for placeholder_name, schema_placeholder in disk_schema.placeholders.items(): if schema_placeholder.required and placeholder_name not in placeholders: @@ -221,6 +217,6 @@ def set_machine_disk_schema( commit_file( disko_file_path, - flake.path, - commit_message=f"Set disk schema of machine: {machine_name} to {schema_name}", + machine.flake.path, + commit_message=f"Set disk schema of machine: {machine.name} to {schema_name}", ) diff --git a/pkgs/clan-cli/clan_lib/tests/test_create.py b/pkgs/clan-cli/clan_lib/tests/test_create.py index 5fa2352bd..4f82e8247 100644 --- a/pkgs/clan-cli/clan_lib/tests/test_create.py +++ b/pkgs/clan-cli/clan_lib/tests/test_create.py @@ -240,7 +240,7 @@ def test_clan_create_api( facter_json = test_lib_root / "assets" / "facter.json" assert facter_json.exists(), f"Source facter file not found: {facter_json}" - dest_dir = specific_machine_dir(Flake(str(clan_dir_flake.path)), machine.name) + dest_dir = specific_machine_dir(machine) # specific_machine_dir should create the directory, but ensure it exists just in case dest_dir.mkdir(parents=True, exist_ok=True) @@ -253,10 +253,7 @@ def test_clan_create_api( ) # ===== Create Disko Config ====== - facter_path = ( - specific_machine_dir(Flake(str(clan_dir_flake.path)), machine.name) - / "facter.json" - ) + facter_path = specific_machine_dir(machine) / "facter.json" with facter_path.open("r") as f: facter_report = json.load(f) @@ -265,7 +262,7 @@ def test_clan_create_api( assert disk_devs is not None placeholders = {"mainDisk": disk_devs[0]} - set_machine_disk_schema(clan_dir_flake, machine.name, "single-disk", placeholders) + set_machine_disk_schema(machine, "single-disk", placeholders) clan_dir_flake.invalidate_cache() with pytest.raises(ClanError) as exc_info: diff --git a/pkgs/webview-ui/app/src/App.tsx b/pkgs/webview-ui/app/src/App.tsx index a6117cf9d..894da8432 100644 --- a/pkgs/webview-ui/app/src/App.tsx +++ b/pkgs/webview-ui/app/src/App.tsx @@ -22,7 +22,9 @@ export { clanList, setClanList }; (async function () { const curr = activeURI(); if (curr) { - const result = await callApi("show_clan_meta", { uri: curr }); + const result = await callApi("show_clan_meta", { + flake: { identifier: curr }, + }); console.log("refetched meta for ", curr); if (result.status === "error") { result.errors.forEach((error) => { diff --git a/pkgs/webview-ui/app/src/components/Sidebar/index.tsx b/pkgs/webview-ui/app/src/components/Sidebar/index.tsx index 73c26b385..d841559b7 100644 --- a/pkgs/webview-ui/app/src/components/Sidebar/index.tsx +++ b/pkgs/webview-ui/app/src/components/Sidebar/index.tsx @@ -52,7 +52,9 @@ export const Sidebar = (props: RouteSectionProps) => { queryFn: async () => { const curr = activeURI(); if (curr) { - const result = await callApi("show_clan_meta", { uri: curr }); + const result = await callApi("show_clan_meta", { + flake: { identifier: curr }, + }); console.log("refetched meta for ", curr); if (result.status === "error") throw new Error("Failed to fetch data"); diff --git a/pkgs/webview-ui/app/src/routes/clans/details.tsx b/pkgs/webview-ui/app/src/routes/clans/details.tsx index 7f0b4945e..7ca49df5c 100644 --- a/pkgs/webview-ui/app/src/routes/clans/details.tsx +++ b/pkgs/webview-ui/app/src/routes/clans/details.tsx @@ -329,7 +329,9 @@ export const ClanDetails = () => { const clanQuery = createQuery(() => ({ queryKey: [clan_dir, "inventory", "meta"], queryFn: async () => { - const result = await callApi("show_clan_meta", { uri: clan_dir }); + const result = await callApi("show_clan_meta", { + flake: { identifier: clan_dir }, + }); if (result.status === "error") throw new Error("Failed to fetch data"); return result.data; }, diff --git a/pkgs/webview-ui/app/src/routes/clans/list.tsx b/pkgs/webview-ui/app/src/routes/clans/list.tsx index 123c70f54..5d8878817 100644 --- a/pkgs/webview-ui/app/src/routes/clans/list.tsx +++ b/pkgs/webview-ui/app/src/routes/clans/list.tsx @@ -18,7 +18,9 @@ const ClanItem = (props: ClanItemProps) => { const details = createQuery(() => ({ queryKey: [clan_dir, "meta"], queryFn: async () => { - const result = await callApi("show_clan_meta", { uri: clan_dir }); + const result = await callApi("show_clan_meta", { + flake: { identifier: clan_dir }, + }); if (result.status === "error") throw new Error("Failed to fetch data"); return result.data; }, diff --git a/pkgs/webview-ui/app/src/routes/machines/details.tsx b/pkgs/webview-ui/app/src/routes/machines/details.tsx index 38740fb27..c22b44477 100644 --- a/pkgs/webview-ui/app/src/routes/machines/details.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/details.tsx @@ -117,8 +117,10 @@ const InstallMachine = (props: InstallMachineProps) => { if (shouldRunDisk) { setProgressText("Setting up disk ... (1/5)"); const disk_response = await callApi("set_machine_disk_schema", { - flake: { identifier: curr_uri }, - machine_name: props.name, + machine: { + flake: { identifier: curr_uri }, + name: props.name, + }, placeholders: diskValues.placeholders, schema_name: diskValues.schema, force: true, diff --git a/pkgs/webview-ui/app/src/routes/machines/install/disk-step.tsx b/pkgs/webview-ui/app/src/routes/machines/install/disk-step.tsx index a12570506..b1e6d2b23 100644 --- a/pkgs/webview-ui/app/src/routes/machines/install/disk-step.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/install/disk-step.tsx @@ -37,10 +37,12 @@ export const DiskStep = (props: StepProps) => { queryKey: [props.dir, props.machine_id, "disk_schemas"], queryFn: async () => { const result = await callApi("get_disk_schemas", { - flake: { - identifier: props.dir, + machine: { + flake: { + identifier: props.dir, + }, + name: props.machine_id, }, - machine_name: props.machine_id, }); if (result.status === "error") throw new Error("Failed to fetch data"); return result.data; diff --git a/pkgs/webview-ui/app/src/routes/machines/install/hardware-step.tsx b/pkgs/webview-ui/app/src/routes/machines/install/hardware-step.tsx index daf6be0af..9cb17e744 100644 --- a/pkgs/webview-ui/app/src/routes/machines/install/hardware-step.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/install/hardware-step.tsx @@ -52,10 +52,12 @@ export const HWStep = (props: StepProps) => { queryKey: [props.dir, props.machine_id, "hw_report"], queryFn: async () => { const result = await callApi("show_machine_hardware_config", { - flake: { - identifier: props.dir, + machine: { + flake: { + identifier: props.dir, + }, + name: props.machine_id, }, - machine_name: props.machine_id, }); if (result.status === "error") throw new Error("Failed to fetch data"); return result.data; @@ -85,9 +87,13 @@ export const HWStep = (props: StepProps) => { setIsGenerating(true); const r = await callApi("generate_machine_hardware_info", { opts: { - flake: { identifier: curr_uri }, - machine: props.machine_id, - target_host: target, + machine: { + name: props.machine_id, + override_target_host: target, + flake: { + identifier: curr_uri, + }, + }, backend: "nixos-facter", }, });