Merge pull request 'clan-cli: Use machine object everywhere instead of name + flake' (#3541) from Qubasa/clan-core:replace_machine_name_with_machine_obj2 into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3541
This commit is contained in:
Luis Hebendanz
2025-05-09 14:01:25 +00:00
29 changed files with 160 additions and 165 deletions

View File

@@ -6,8 +6,9 @@ from urllib.parse import urlparse
from clan_lib.api import API 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.errors import ClanCmdError, ClanError
from clan_cli.flake import Flake
from clan_cli.inventory import Meta from clan_cli.inventory import Meta
from clan_cli.nix import nix_eval from clan_cli.nix import nix_eval
@@ -15,26 +16,26 @@ log = logging.getLogger(__name__)
@API.register @API.register
def show_clan_meta(uri: str) -> Meta: def show_clan_meta(flake: Flake) -> Meta:
if uri.startswith("/") and not Path(uri).exists(): if flake.is_local and not flake.path.exists():
msg = f"Path {uri} does not exist" msg = f"Path {flake} does not exist"
raise ClanError(msg, description="clan directory does not exist") raise ClanError(msg, description="clan directory does not exist")
cmd = nix_eval( cmd = nix_eval(
[ [
f"{uri}#clanInternals.inventory.meta", f"{flake}#clanInternals.inventory.meta",
"--json", "--json",
] ]
) )
res = "{}" res = "{}"
try: try:
proc = run_no_stdout(cmd) proc = run(cmd)
res = proc.stdout.strip() res = proc.stdout.strip()
except ClanCmdError as e: except ClanCmdError as e:
msg = "Evaluation failed on meta attribute" msg = "Evaluation failed on meta attribute"
raise ClanError( raise ClanError(
msg, msg,
location=f"show_clan {uri}", location=f"show_clan {flake}",
description=str(e.cmd), description=str(e.cmd),
) from e ) from e
@@ -53,16 +54,16 @@ def show_clan_meta(uri: str) -> Meta:
msg = "Invalid absolute path" msg = "Invalid absolute path"
raise ClanError( raise ClanError(
msg, msg,
location=f"show_clan {uri}", location=f"show_clan {flake}",
description="Icon path must be a URL or a relative path", 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: else:
msg = "Invalid schema" msg = "Invalid schema"
raise ClanError( raise ClanError(
msg, msg,
location=f"show_clan {uri}", location=f"show_clan {flake}",
description="Icon path must be a URL or a relative path", description="Icon path must be a URL or a relative path",
) )

View File

@@ -403,23 +403,3 @@ def run(
raise ClanCmdError(cmd_out) raise ClanCmdError(cmd_out)
return 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,
)

View File

@@ -10,6 +10,7 @@ from .errors import ClanError
if TYPE_CHECKING: if TYPE_CHECKING:
from clan_cli.flake import Flake from clan_cli.flake import Flake
from clan_cli.machines.machines import Machine
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -144,8 +145,8 @@ def machines_dir(flake: "Flake") -> Path:
return Path(store_path) / "machines" return Path(store_path) / "machines"
def specific_machine_dir(flake: "Flake", machine: str) -> Path: def specific_machine_dir(machine: "Machine") -> Path:
return machines_dir(flake) / machine return machines_dir(machine.flake) / machine.name
def module_root() -> Path: def module_root() -> Path:

View File

@@ -5,13 +5,13 @@ from tempfile import TemporaryDirectory
from clan_cli.completions import add_dynamic_completer, complete_machines from clan_cli.completions import add_dynamic_completer, complete_machines
from clan_cli.machines.machines import Machine from clan_cli.machines.machines import Machine
from clan_cli.ssh.host import Host
from clan_cli.ssh.upload import upload from clan_cli.ssh.upload import upload
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def upload_secrets(machine: Machine, host: Host) -> None: def upload_secrets(machine: Machine) -> None:
with machine.target_host() as host:
if not machine.secret_facts_store.needs_upload(host): if not machine.secret_facts_store.needs_upload(host):
machine.info("Secrets already uploaded") machine.info("Secrets already uploaded")
return return
@@ -25,8 +25,7 @@ def upload_secrets(machine: Machine, host: Host) -> None:
def upload_command(args: argparse.Namespace) -> None: def upload_command(args: argparse.Namespace) -> None:
machine = Machine(name=args.machine, flake=args.flake) machine = Machine(name=args.machine, flake=args.flake)
with machine.target_host() as host: upload_secrets(machine)
upload_secrets(machine, host)
def register_upload_parser(parser: argparse.ArgumentParser) -> None: def register_upload_parser(parser: argparse.ArgumentParser) -> None:

View File

@@ -21,7 +21,7 @@ from typing import Any
from clan_lib.api import API, dataclass_to_dict, from_dict 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.errors import ClanCmdError, ClanError
from clan_cli.flake import Flake from clan_cli.flake import Flake
from clan_cli.git import commit_file 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: try:
res = proc.stdout.strip() 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: try:
res = proc.stdout.strip() res = proc.stdout.strip()

View File

@@ -110,7 +110,7 @@ def create_machine(opts: CreateOptions, commit: bool = True) -> None:
new_machine["deploy"] = {"targetHost": target_host} new_machine["deploy"] = {"targetHost": target_host}
patch_inventory_with( 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 # Commit at the end in that order to avoid committing halve-baked machines

View File

@@ -5,9 +5,10 @@ from pathlib import Path
from clan_lib.api import API 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.completions import add_dynamic_completer, complete_machines
from clan_cli.dirs import specific_machine_dir 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.folders import sops_secrets_folder
from clan_cli.secrets.machines import has_machine as secrets_has_machine from clan_cli.secrets.machines import has_machine as secrets_has_machine
from clan_cli.secrets.machines import remove_machine as secrets_machine_remove from clan_cli.secrets.machines import remove_machine as secrets_machine_remove
@@ -15,49 +16,46 @@ from clan_cli.secrets.secrets import (
list_secrets, list_secrets,
) )
from .machines import Machine
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@API.register @API.register
def delete_machine(flake: Flake, name: str) -> None: def delete_machine(machine: Machine) -> None:
try: try:
inventory.delete(flake, {f"machines.{name}"}) inventory.delete(machine.flake, {f"machines.{machine.name}"})
except KeyError as exc: except KeyError as exc:
# louis@(2025-03-09): test infrastructure does not seem to set the # louis@(2025-03-09): test infrastructure does not seem to set the
# inventory properly, but more importantly only one machine in my # inventory properly, but more importantly only one machine in my
# personal clan ended up in the inventory for some reason, so I think # personal clan ended up in the inventory for some reason, so I think
# it makes sense to eat the exception here. # it makes sense to eat the exception here.
log.warning( 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] = [] changed_paths: list[Path] = []
folder = specific_machine_dir(flake, name) folder = specific_machine_dir(machine)
if folder.exists(): if folder.exists():
changed_paths.append(folder) changed_paths.append(folder)
shutil.rmtree(folder) shutil.rmtree(folder)
# louis@(2025-02-04): clean-up legacy (pre-vars) secrets: # louis@(2025-02-04): clean-up legacy (pre-vars) secrets:
sops_folder = sops_secrets_folder(flake.path) sops_folder = sops_secrets_folder(machine.flake.path)
filter_fn = lambda secret_name: secret_name.startswith(f"{name}-") filter_fn = lambda secret_name: secret_name.startswith(f"{machine.name}-")
for secret_name in list_secrets(flake.path, filter_fn): for secret_name in list_secrets(machine.flake.path, filter_fn):
secret_path = sops_folder / secret_name secret_path = sops_folder / secret_name
changed_paths.append(secret_path) changed_paths.append(secret_path)
shutil.rmtree(secret_path) shutil.rmtree(secret_path)
machine = Machine(name, flake)
changed_paths.extend(machine.public_vars_store.delete_store()) changed_paths.extend(machine.public_vars_store.delete_store())
changed_paths.extend(machine.secret_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: # Remove the machine's key, and update secrets & vars that referenced it:
if secrets_has_machine(flake.path, name): if secrets_has_machine(machine.flake.path, machine.name):
secrets_machine_remove(flake.path, name) secrets_machine_remove(machine.flake.path, machine.name)
def delete_command(args: argparse.Namespace) -> None: 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: def register_delete_parser(parser: argparse.ArgumentParser) -> None:

View File

@@ -7,11 +7,10 @@ from pathlib import Path
from clan_lib.api import API 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.completions import add_dynamic_completer, complete_machines
from clan_cli.dirs import specific_machine_dir from clan_cli.dirs import specific_machine_dir
from clan_cli.errors import ClanCmdError, ClanError from clan_cli.errors import ClanCmdError, ClanError
from clan_cli.flake import Flake
from clan_cli.git import commit_file from clan_cli.git import commit_file
from clan_cli.machines.machines import Machine from clan_cli.machines.machines import Machine
from clan_cli.nix import nix_config, nix_eval from clan_cli.nix import nix_config, nix_eval
@@ -26,39 +25,35 @@ class HardwareConfig(Enum):
NIXOS_GENERATE_CONFIG = "nixos-generate-config" NIXOS_GENERATE_CONFIG = "nixos-generate-config"
NONE = "none" NONE = "none"
def config_path(self, flake: Flake, machine_name: str) -> Path: def config_path(self, machine: Machine) -> Path:
machine_dir = specific_machine_dir(flake, machine_name) machine_dir = specific_machine_dir(machine)
if self == HardwareConfig.NIXOS_FACTER: if self == HardwareConfig.NIXOS_FACTER:
return machine_dir / "facter.json" return machine_dir / "facter.json"
return machine_dir / "hardware-configuration.nix" return machine_dir / "hardware-configuration.nix"
@classmethod @classmethod
def detect_type( def detect_type(cls: type["HardwareConfig"], machine: Machine) -> "HardwareConfig":
cls: type["HardwareConfig"], flake: Flake, machine_name: str hardware_config = HardwareConfig.NIXOS_GENERATE_CONFIG.config_path(machine)
) -> "HardwareConfig":
hardware_config = HardwareConfig.NIXOS_GENERATE_CONFIG.config_path(
flake, machine_name
)
if hardware_config.exists() and "throw" not in hardware_config.read_text(): if hardware_config.exists() and "throw" not in hardware_config.read_text():
return HardwareConfig.NIXOS_GENERATE_CONFIG 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.NIXOS_FACTER
return HardwareConfig.NONE return HardwareConfig.NONE
@API.register @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. 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 @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. 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"] system = config["system"]
cmd = nix_eval( cmd = nix_eval(
[ [
f"{flake}#clanInternals.machines.{system}.{machine_name}", f"{machine.flake}#clanInternals.machines.{system}.{machine.name}",
"--apply", "--apply",
"machine: { inherit (machine.pkgs) system; }", "machine: { inherit (machine.pkgs) system; }",
"--json", "--json",
] ]
) )
proc = run_no_stdout(cmd, RunOpts(prefix=machine_name)) proc = run(cmd, RunOpts(prefix=machine.name))
res = proc.stdout.strip() res = proc.stdout.strip()
host_platform = json.loads(res) host_platform = json.loads(res)
@@ -81,11 +76,8 @@ def show_machine_hardware_platform(flake: Flake, machine_name: str) -> str | Non
@dataclass @dataclass
class HardwareGenerateOptions: class HardwareGenerateOptions:
flake: Flake machine: Machine
machine: str
backend: HardwareConfig backend: HardwareConfig
target_host: str | None = None
keyfile: str | None = None
password: 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. and place the resulting *.nix file in the machine's directory.
""" """
machine = Machine( machine = opts.machine
opts.machine,
flake=opts.flake,
private_key=Path(opts.keyfile) if opts.keyfile else None,
override_target_host=opts.target_host,
)
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) hw_file.parent.mkdir(parents=True, exist_ok=True)
if opts.backend == HardwareConfig.NIXOS_FACTER: if opts.backend == HardwareConfig.NIXOS_FACTER:
@@ -148,11 +135,11 @@ def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareCon
commit_file( commit_file(
hw_file, hw_file,
opts.flake.path, opts.machine.flake.path,
f"machines/{opts.machine}/{hw_file.name}: update hardware configuration", f"machines/{opts.machine}/{hw_file.name}: update hardware configuration",
) )
try: try:
show_machine_hardware_platform(opts.flake, opts.machine) show_machine_hardware_platform(opts.machine)
if backup_file: if backup_file:
backup_file.unlink(missing_ok=True) backup_file.unlink(missing_ok=True)
except ClanCmdError as e: 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: def update_hardware_config_command(args: argparse.Namespace) -> None:
opts = HardwareGenerateOptions( machine = Machine(
flake=args.flake, flake=args.flake,
machine=args.machine, name=args.machine,
target_host=args.target_host, override_target_host=args.target_host,
)
opts = HardwareGenerateOptions(
machine=machine,
password=args.password, password=args.password,
backend=HardwareConfig(args.backend), backend=HardwareConfig(args.backend),
) )

View File

@@ -111,11 +111,7 @@ def install_machine(opts: InstallOptions) -> None:
[ [
"--generate-hardware-config", "--generate-hardware-config",
str(opts.update_hardware_config.value), str(opts.update_hardware_config.value),
str( str(opts.update_hardware_config.config_path(machine)),
opts.update_hardware_config.config_path(
machine.flake, machine.name
)
),
] ]
) )

View File

@@ -67,9 +67,9 @@ def get_machine_details(machine: Machine) -> MachineDetails:
msg = f"Machine {machine.name} not found in inventory" msg = f"Machine {machine.name} not found in inventory"
raise ClanError(msg) 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_schema: MachineDiskMatter | None = None
disk_path = machine_dir / "disko.nix" disk_path = machine_dir / "disko.nix"
if disk_path.exists(): if disk_path.exists():

View File

@@ -9,7 +9,7 @@ from functools import cached_property
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Any 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.errors import ClanCmdError, ClanError
from clan_cli.facts import public_modules as facts_public_modules from clan_cli.facts import public_modules as facts_public_modules
from clan_cli.facts import secret_modules as facts_secret_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 # 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 # as root to match NixOS: https://github.com/nix-darwin/nix-darwin/pull/1341
return json.loads( return json.loads(
run_no_stdout( run(
nix_eval( nix_eval(
[ [
f"{self.flake}#darwinConfigurations.{self.name}.options.system", f"{self.flake}#darwinConfigurations.{self.name}.options.system",

View File

@@ -141,7 +141,7 @@ def deploy_machine(machine: Machine) -> None:
generate_facts([machine], service=None, regenerate=False) generate_facts([machine], service=None, regenerate=False)
generate_vars([machine], generator_name=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) upload_secret_vars(machine, target_host)
path = upload_sources(machine, host) path = upload_sources(machine, host)

View File

@@ -1,12 +1,13 @@
import json import json
import logging import logging
import os import os
import shutil
import tempfile import tempfile
from functools import cache from functools import cache
from pathlib import Path from pathlib import Path
from typing import Any 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.dirs import nixpkgs_flake, nixpkgs_source
from clan_cli.errors import ClanError from clan_cli.errors import ClanError
from clan_cli.locked_open import locked_open from clan_cli.locked_open import locked_open
@@ -55,7 +56,7 @@ def nix_add_to_gcroots(nix_path: Path, dest: Path) -> None:
@cache @cache
def nix_config() -> dict[str, Any]: def nix_config() -> dict[str, Any]:
cmd = nix_command(["config", "show", "--json"]) cmd = nix_command(["config", "show", "--json"])
proc = run_no_stdout(cmd) proc = run(cmd)
data = json.loads(proc.stdout) data = json.loads(proc.stdout)
config = {} config = {}
for key, value in data.items(): for key, value in data.items():
@@ -131,7 +132,16 @@ class Packages:
cls.static_packages = set( cls.static_packages = set(
os.environ.get("CLAN_PROVIDED_PACKAGES", "").split(":") 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: # Features:

View File

@@ -63,7 +63,8 @@ def upload(
for mdir in dirs: for mdir in dirs:
dir_path = Path(root) / mdir dir_path = Path(root) / mdir
tarinfo = tar.gettarinfo( 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.mode = dir_mode
tarinfo.uname = file_user tarinfo.uname = file_user

View File

@@ -3,7 +3,7 @@ import json
import logging import logging
from pathlib import Path 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 ( from clan_cli.completions import (
add_dynamic_completer, add_dynamic_completer,
complete_machines, complete_machines,
@@ -32,7 +32,7 @@ def list_state_folders(machine: Machine, service: None | str = None) -> None:
res = "{}" res = "{}"
try: try:
proc = run_no_stdout(cmd, opts=RunOpts(prefix=machine.name)) proc = run(cmd, RunOpts(prefix=machine.name))
res = proc.stdout.strip() res = proc.stdout.strip()
except ClanCmdError as e: except ClanCmdError as e:
msg = "Clan might not have meta attributes" msg = "Clan might not have meta attributes"

View File

@@ -2,7 +2,7 @@ import json
from pathlib import Path from pathlib import Path
from typing import Any 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.errors import ClanError
from clan_cli.nix import nix_eval from clan_cli.nix import nix_eval
@@ -18,7 +18,7 @@ def list_tagged_machines(flake_url: str | Path) -> dict[str, Any]:
"--json", "--json",
] ]
) )
proc = run_no_stdout(cmd) proc = run(cmd)
try: try:
res = proc.stdout.strip() res = proc.stdout.strip()

View File

@@ -10,8 +10,15 @@ from pathlib import Path
from typing import Any, NamedTuple from typing import Any, NamedTuple
import pytest 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.locked_open import locked_open
from clan_cli.machines.machines import Machine
from clan_cli.nix import nix_test_store from clan_cli.nix import nix_test_store
from clan_cli.tests import age_keys from clan_cli.tests import age_keys
from clan_cli.tests.fixture_error import FixtureError from clan_cli.tests.fixture_error import FixtureError
@@ -70,11 +77,10 @@ class FlakeForTest(NamedTuple):
def set_machine_settings( def set_machine_settings(
flake: Path, machine: Machine,
machine_name: str,
machine_settings: dict, machine_settings: dict,
) -> None: ) -> 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)) 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", "add", "."], cwd=self.path, check=True)
sp.run( sp.run(
["git", "commit", "-a", "-m", "Update by flake generator"], ["git", "commit", "-a", "-m", "Update by flake generator"],

View File

@@ -11,7 +11,7 @@ from clan_cli.inventory import (
set_inventory, set_inventory,
) )
from clan_cli.machines.create import CreateOptions, create_machine 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_cli.tests.fixtures_flakes import FlakeForTest
from clan_lib.api.modules import list_modules from clan_lib.api.modules import list_modules
@@ -120,7 +120,7 @@ def test_add_module_to_inventory(
"--json", "--json",
] ]
) )
proc = run_no_stdout(cmd) proc = run(cmd)
res = json.loads(proc.stdout.strip()) res = json.loads(proc.stdout.strip())
assert res["machine1"]["authorizedKeys"] == [ssh_key.decode()] assert res["machine1"]["authorizedKeys"] == [ssh_key.decode()]

View File

@@ -4,9 +4,10 @@ from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import Any, Literal 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.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 from . import API
@@ -52,8 +53,8 @@ class Directory:
@API.register @API.register
def get_directory(current_path: str) -> Directory: def get_directory(flake: Flake) -> Directory:
curr_dir = Path(current_path) curr_dir = flake.path
directory = Directory(path=str(curr_dir)) directory = Directory(path=str(curr_dir))
if not curr_dir.is_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", "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() res = proc.stdout.strip()
blk_info: dict[str, Any] = json.loads(res) blk_info: dict[str, Any] = json.loads(res)

View File

@@ -7,9 +7,9 @@ from uuid import uuid4
from clan_cli.dirs import TemplateType, clan_templates from clan_cli.dirs import TemplateType, clan_templates
from clan_cli.errors import ClanError from clan_cli.errors import ClanError
from clan_cli.flake import Flake
from clan_cli.git import commit_file from clan_cli.git import commit_file
from clan_cli.machines.hardware import HardwareConfig, show_machine_hardware_config 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 import API
from clan_lib.api.modules import Frontmatter, extract_frontmatter 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 @API.register
def get_disk_schemas( def get_disk_schemas(machine: Machine) -> dict[str, DiskSchema]:
flake: Flake, machine_name: str | None = None
) -> dict[str, DiskSchema]:
""" """
Get the available disk schemas Get the available disk schemas
""" """
@@ -84,8 +82,7 @@ def get_disk_schemas(
disk_schemas = {} disk_schemas = {}
hw_report = {} hw_report = {}
if machine_name is not None: hw_report_path = HardwareConfig.NIXOS_FACTER.config_path(machine)
hw_report_path = HardwareConfig.NIXOS_FACTER.config_path(flake, machine_name)
if not hw_report_path.exists(): if not hw_report_path.exists():
msg = "Hardware configuration missing" msg = "Hardware configuration missing"
raise ClanError(msg) raise ClanError(msg)
@@ -130,8 +127,7 @@ class MachineDiskMatter(TypedDict):
@API.register @API.register
def set_machine_disk_schema( def set_machine_disk_schema(
flake: Flake, machine: Machine,
machine_name: str,
schema_name: str, schema_name: str,
# Placeholders are used to fill in the disk schema # Placeholders are used to fill in the disk schema
# Use get disk schemas to get the placeholders and their options # 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 Set the disk placeholders of the template
""" """
# Assert the hw-config must exist before setting the disk # Assert the hw-config must exist before setting the disk
hw_config = show_machine_hardware_config(flake, machine_name) hw_config = show_machine_hardware_config(machine)
hw_config_path = hw_config.config_path(flake, machine_name) hw_config_path = hw_config.config_path(machine)
if not hw_config_path.exists(): if not hw_config_path.exists():
msg = "Hardware configuration must exist before applying disk schema" msg = "Hardware configuration must exist before applying disk schema"
@@ -160,7 +156,7 @@ def set_machine_disk_schema(
raise ClanError(msg) raise ClanError(msg)
# Check that the placeholders are valid # 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 # check that all required placeholders are present
for placeholder_name, schema_placeholder in disk_schema.placeholders.items(): for placeholder_name, schema_placeholder in disk_schema.placeholders.items():
if schema_placeholder.required and placeholder_name not in placeholders: if schema_placeholder.required and placeholder_name not in placeholders:
@@ -221,6 +217,6 @@ def set_machine_disk_schema(
commit_file( commit_file(
disko_file_path, disko_file_path,
flake.path, machine.flake.path,
commit_message=f"Set disk schema of machine: {machine_name} to {schema_name}", commit_message=f"Set disk schema of machine: {machine.name} to {schema_name}",
) )

View File

@@ -2,7 +2,7 @@ import argparse
import re import re
from dataclasses import dataclass 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 clan_cli.nix import nix_shell
from . import API from . import API
@@ -100,7 +100,7 @@ def show_mdns() -> DNSInfo:
"--terminate", "--terminate",
], ],
) )
proc = run_no_stdout(cmd) proc = run(cmd)
data = parse_avahi_output(proc.stdout) data = parse_avahi_output(proc.stdout)
return data return data

View File

@@ -240,7 +240,7 @@ def test_clan_create_api(
facter_json = test_lib_root / "assets" / "facter.json" facter_json = test_lib_root / "assets" / "facter.json"
assert facter_json.exists(), f"Source facter file not found: {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 # specific_machine_dir should create the directory, but ensure it exists just in case
dest_dir.mkdir(parents=True, exist_ok=True) dest_dir.mkdir(parents=True, exist_ok=True)
@@ -253,10 +253,7 @@ def test_clan_create_api(
) )
# ===== Create Disko Config ====== # ===== Create Disko Config ======
facter_path = ( facter_path = specific_machine_dir(machine) / "facter.json"
specific_machine_dir(Flake(str(clan_dir_flake.path)), machine.name)
/ "facter.json"
)
with facter_path.open("r") as f: with facter_path.open("r") as f:
facter_report = json.load(f) facter_report = json.load(f)
@@ -265,7 +262,7 @@ def test_clan_create_api(
assert disk_devs is not None assert disk_devs is not None
placeholders = {"mainDisk": disk_devs[0]} 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() clan_dir_flake.invalidate_cache()
with pytest.raises(ClanError) as exc_info: with pytest.raises(ClanError) as exc_info:

View File

@@ -22,7 +22,9 @@ export { clanList, setClanList };
(async function () { (async function () {
const curr = activeURI(); const curr = activeURI();
if (curr) { 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); console.log("refetched meta for ", curr);
if (result.status === "error") { if (result.status === "error") {
result.errors.forEach((error) => { result.errors.forEach((error) => {

View File

@@ -52,7 +52,9 @@ export const Sidebar = (props: RouteSectionProps) => {
queryFn: async () => { queryFn: async () => {
const curr = activeURI(); const curr = activeURI();
if (curr) { 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); console.log("refetched meta for ", curr);
if (result.status === "error") throw new Error("Failed to fetch data"); if (result.status === "error") throw new Error("Failed to fetch data");

View File

@@ -329,7 +329,9 @@ export const ClanDetails = () => {
const clanQuery = createQuery(() => ({ const clanQuery = createQuery(() => ({
queryKey: [clan_dir, "inventory", "meta"], queryKey: [clan_dir, "inventory", "meta"],
queryFn: async () => { 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"); if (result.status === "error") throw new Error("Failed to fetch data");
return result.data; return result.data;
}, },

View File

@@ -18,7 +18,9 @@ const ClanItem = (props: ClanItemProps) => {
const details = createQuery(() => ({ const details = createQuery(() => ({
queryKey: [clan_dir, "meta"], queryKey: [clan_dir, "meta"],
queryFn: async () => { 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"); if (result.status === "error") throw new Error("Failed to fetch data");
return result.data; return result.data;
}, },

View File

@@ -117,8 +117,10 @@ const InstallMachine = (props: InstallMachineProps) => {
if (shouldRunDisk) { if (shouldRunDisk) {
setProgressText("Setting up disk ... (1/5)"); setProgressText("Setting up disk ... (1/5)");
const disk_response = await callApi("set_machine_disk_schema", { const disk_response = await callApi("set_machine_disk_schema", {
machine: {
flake: { identifier: curr_uri }, flake: { identifier: curr_uri },
machine_name: props.name, name: props.name,
},
placeholders: diskValues.placeholders, placeholders: diskValues.placeholders,
schema_name: diskValues.schema, schema_name: diskValues.schema,
force: true, force: true,

View File

@@ -37,10 +37,12 @@ export const DiskStep = (props: StepProps<DiskValues>) => {
queryKey: [props.dir, props.machine_id, "disk_schemas"], queryKey: [props.dir, props.machine_id, "disk_schemas"],
queryFn: async () => { queryFn: async () => {
const result = await callApi("get_disk_schemas", { const result = await callApi("get_disk_schemas", {
machine: {
flake: { flake: {
identifier: props.dir, identifier: props.dir,
}, },
machine_name: props.machine_id, name: props.machine_id,
},
}); });
if (result.status === "error") throw new Error("Failed to fetch data"); if (result.status === "error") throw new Error("Failed to fetch data");
return result.data; return result.data;

View File

@@ -52,10 +52,12 @@ export const HWStep = (props: StepProps<HardwareValues>) => {
queryKey: [props.dir, props.machine_id, "hw_report"], queryKey: [props.dir, props.machine_id, "hw_report"],
queryFn: async () => { queryFn: async () => {
const result = await callApi("show_machine_hardware_config", { const result = await callApi("show_machine_hardware_config", {
machine: {
flake: { flake: {
identifier: props.dir, identifier: props.dir,
}, },
machine_name: props.machine_id, name: props.machine_id,
},
}); });
if (result.status === "error") throw new Error("Failed to fetch data"); if (result.status === "error") throw new Error("Failed to fetch data");
return result.data; return result.data;
@@ -85,9 +87,13 @@ export const HWStep = (props: StepProps<HardwareValues>) => {
setIsGenerating(true); setIsGenerating(true);
const r = await callApi("generate_machine_hardware_info", { const r = await callApi("generate_machine_hardware_info", {
opts: { opts: {
flake: { identifier: curr_uri }, machine: {
machine: props.machine_id, name: props.machine_id,
target_host: target, override_target_host: target,
flake: {
identifier: curr_uri,
},
},
backend: "nixos-facter", backend: "nixos-facter",
}, },
}); });